Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcoreaudioutils.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/qdebug.h>
7
8#ifdef Q_OS_MACOS
9# include <CoreAudio/AudioHardware.h>
10# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
11# include <QtMultimedia/private/qaudioformat_p.h>
12#endif
13
15
16namespace QCoreAudioUtils {
17
18QAudioFormat toQAudioFormat(AudioStreamBasicDescription const& sf)
19{
20 QAudioFormat audioFormat;
21 // all Darwin HW is little endian, we ignore those formats
22 if ((sf.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0 && QSysInfo::ByteOrder != QSysInfo::LittleEndian)
23 return audioFormat;
24
25 // filter out the formats we're interested in
26 QAudioFormat::SampleFormat format = QAudioFormat::Unknown;
27 switch (sf.mBitsPerChannel) {
28 case 8:
29 if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) == 0)
30 format = QAudioFormat::UInt8;
31 break;
32 case 16:
33 if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0)
34 format = QAudioFormat::Int16;
35 break;
36 case 32:
37 if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0)
38 format = QAudioFormat::Int32;
39 else if ((sf.mFormatFlags & kAudioFormatFlagIsFloat) != 0)
40 format = QAudioFormat::Float;
41 break;
42 default:
43 break;
44 }
45
46 audioFormat.setSampleFormat(format);
47 audioFormat.setSampleRate(sf.mSampleRate);
48 audioFormat.setChannelCount(sf.mChannelsPerFrame);
49
50 return audioFormat;
51}
52
83
84
85static constexpr struct {
88} channelMap[] = {
111
113toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
114{
115 auto channelConfig = format.channelConfig();
116 if (channelConfig == QAudioFormat::ChannelConfigUnknown)
117 channelConfig = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
118
119 *size = sizeof(AudioChannelLayout) + int(QAudioFormat::NChannelPositions)*sizeof(AudioChannelDescription);
120 auto *layout = static_cast<AudioChannelLayout *>(malloc(*size));
121 memset(layout, 0, *size);
122 layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
123
124 for (const auto &m : channelMap) {
125 if (channelConfig & QAudioFormat::channelConfig(m.pos))
126 layout->mChannelDescriptions[layout->mNumberChannelDescriptions++].mChannelLabel = m.label;
127 }
128
129 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontCenter)) {
130 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
131 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
132 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
133 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 0.f;
134 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
135 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
136 }
137 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontLeft)) {
138 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
139 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
140 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
141 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = -45.f;
142 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
143 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
144 }
145 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontRight)) {
146 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
147 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
148 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
149 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 45.f;
150 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
151 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
152 }
153
154 return std::unique_ptr<AudioChannelLayout, QFreeDeleter>(layout);
155}
156
157static constexpr struct {
160} layoutTagMap[] = {
191
198
199
200QAudioFormat::ChannelConfig fromAudioChannelLayout(const AudioChannelLayout *layout)
201{
202 for (const auto &m : layoutTagMap) {
203 if (m.tag == layout->mChannelLayoutTag)
204 return m.channelConfig;
205 }
206
207 quint32 channels = 0;
208 if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
209 // special case 1 and 2 channel configs, as they are often reported without proper descriptions
210 if (layout->mNumberChannelDescriptions == 1
211 && (layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown
212 || layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Mono))
213 return QAudioFormat::ChannelConfigMono;
214 if (layout->mNumberChannelDescriptions == 2 &&
215 layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown &&
216 layout->mChannelDescriptions[1].mChannelLabel == kAudioChannelLabel_Unknown)
217 return QAudioFormat::ChannelConfigStereo;
218
219 for (uint i = 0; i < layout->mNumberChannelDescriptions; ++i) {
220 const auto channelLabel = layout->mChannelDescriptions[i].mChannelLabel;
221 if (channelLabel == kAudioChannelLabel_Unknown) {
222 // Any number of unknown channel labels occurs for loopback audio devices.
223 // E.g. the case is reproduced with installed software Soundflower.
224 continue;
225 }
226
227 const auto found = std::find_if(channelMap, std::end(channelMap),
228 [channelLabel](const auto &labelWithPos) {
229 return labelWithPos.label == channelLabel;
230 });
231
232 if (found == std::end(channelMap))
233 qWarning() << "audio device has unrecognized channel, index:" << i
234 << "label:" << channelLabel;
235 else
236 channels |= QAudioFormat::channelConfig(found->pos);
237 }
238 } else {
239 qWarning() << "Channel layout uses unimplemented format, channelLayoutTag:"
240 << layout->mChannelLayoutTag;
241 }
242 return QAudioFormat::ChannelConfig(channels);
243}
244
245#ifdef Q_OS_MACOS
246
249 void *self)
250{
251 return reinterpret_cast<DeviceDisconnectMonitor *>(self)->streamDisconnectListener(
253}
254
259};
260
262{
263 m_currentId = id;
266
269
270 if (status != noErr) {
271 qWarning() << "QAudioOutput: Failed to add property listener";
272 return false;
273 }
274 return true;
275}
276
278{
282
283 switch (status) {
284 case noErr:
285 case kAudioHardwareBadObjectError: // when the listener fires, we may get
286 // kAudioHardwareBadObjectError
287 return;
288
289 default:
290 qWarning() << "QAudioOutput: Failed to remove property listener" << status;
291 }
292}
293
296{
297 // Called on HAL thread
298 // we use futures/continuations to notify the application thread, as we can cancel in-flight
299 // continuations
301
306
308 }
309 }
310
312}
313
314#endif
315
317{
318 AudioComponentDescription componentDescription;
319 componentDescription.componentType = kAudioUnitType_Output;
320#if defined(Q_OS_MACOS)
321 componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
322#else
323 componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
324#endif
325 componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
326 componentDescription.componentFlags = 0;
327 componentDescription.componentFlagsMask = 0;
328
329 AudioComponent component = AudioComponentFindNext(nullptr, &componentDescription);
330 if (component == nullptr) {
331 qWarning() << "makeAudioUnitForIO: Failed to find Output component";
332 return std::nullopt;
333 }
334
335 AudioUnitHandle audioUnit;
336 if (AudioComponentInstanceNew(component, &audioUnit) != noErr) {
337 qWarning() << "makeAudioUnitForIO: Unable to Open Output Component";
338 return std::nullopt;
339 }
340
341 return audioUnit;
342}
343
344bool audioUnitSetInputEnabled(AudioUnitHandle &audioUnit, bool enabled)
345{
346 UInt32 enable = enabled ? 1 : 0;
347 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
348 kAudioUnitScope_Input, 1, &enable, sizeof(enable))
349 != noErr) {
350 qWarning() << "AudioUnit: Unable to enable input";
351 return false;
352 }
353 return true;
354}
355
356bool audioUnitSetOutputEnabled(AudioUnitHandle &audioUnit, bool enabled)
357{
358 UInt32 enable = enabled ? 1 : 0;
359 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
360 kAudioUnitScope_Output, 0, &enable, sizeof(enable))
361 != noErr) {
362 qWarning() << "AudioUnit: Unable to enable output";
363 return false;
364 }
365 return true;
366}
367
368#ifdef Q_OS_MACOS
370{
373 != noErr) {
374 qWarning() << "AudioUnit: Unable to use set device ID";
375 return false;
376 }
377
378 return true;
379}
380#endif
381
382bool audioUnitSetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
383 const AudioStreamBasicDescription &format)
384{
385 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
386 kAudioUnitScope_Input, element, &format, sizeof(format));
387 if (status != noErr) {
388 qWarning() << "AudioUnit: Unable to set stream format" << status;
389 return false;
390 }
391 return true;
392}
393
394bool audioUnitSetOutputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
395 const AudioStreamBasicDescription &format)
396{
397 if (AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
398 kAudioUnitScope_Output, element, &format, sizeof(format))
399 != noErr) {
400 qWarning() << "AudioUnit: Unable to set stream format";
401 return false;
402 }
403 return true;
404}
405
406#ifdef Q_OS_MACOS
408{
409 int numberOfFrames;
410 UInt32 size = sizeof(UInt32);
413 == noErr) {
414 return numberOfFrames;
415 }
416
417 qWarning() << "audioUnitGetFramesPerBuffer: Failed to get audio period size";
418
420 size = sizeof(AudioValueRange);
423 == noErr) {
424 return int(bufferRange.mMaximum);
425 }
426
427 return std::nullopt;
428}
429
431{
436 };
437
439
441 sizeof(Float64), &sampleRateArg);
442
443 if (status != noErr) {
444 qDebug() << "AudioObjectSetPropertyData failed" << status;
445 return false;
446 }
447
448 return true;
449}
450
452 int rate)
453{
457 return std::nullopt;
458
459 using namespace QtMultimediaPrivate;
461 QSpan<const double>{
463 });
464}
465
467{
472 };
473
475 sizeof(int32_t), &bufferFrames);
476
477 if (status != noErr) {
478 qDebug() << "AudioObjectSetPropertyData failed" << status;
479 return false;
480 }
481
482 return true;
483}
484
485#endif
486
488{
489 UInt32 isRunning = 0;
490 UInt32 size = sizeof(isRunning);
491
492 OSStatus status = AudioUnitGetProperty(audioUnit.get(), kAudioOutputUnitProperty_IsRunning,
493 kAudioUnitScope_Global, 0, &isRunning, &size);
494
495 if (status != noErr) {
496 qDebug() << "AudioUnitGetProperty failed" << status;
497 return false;
498 }
499
500 return bool(isRunning);
501}
502
503bool audioUnitSetRenderCallback(AudioUnitHandle &audioUnit, AURenderCallbackStruct &callback)
504{
505 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_SetRenderCallback,
506 kAudioUnitScope_Global, 0, &callback, sizeof(callback));
507
508 if (status != noErr) {
509 qWarning() << "AudioUnitSetProperty: Failed to set AudioUnit "
510 "kAudioUnitProperty_SetRenderCallback"
511 << status;
512 return false;
513 }
514 return true;
515}
516
518{
519 int numberOfFrames;
520 UInt32 size = sizeof(UInt32);
521 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_MaximumFramesPerSlice,
522 kAudioUnitScope_Global, 0, &numberOfFrames, &size)
523 == noErr) {
524 return numberOfFrames;
525 }
526
527 return std::nullopt;
528}
529
531 AudioUnitElement element)
532{
533 AudioStreamBasicDescription ret;
534 UInt32 size = sizeof(AudioStreamBasicDescription);
535
536 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
537 kAudioUnitScope_Input, element, &ret, &size)
538 != noErr) {
539 qWarning() << "QAudioSource: Unable to retrieve device format";
540 return std::nullopt;
541 }
542
543 return ret;
544}
545
550
552{
553 Q_ASSERT(!m_initialized);
554 if (AudioUnitInitialize(get())) {
555 qWarning() << "AudioUnitHandle: Failed to initialize AudioUnit";
556 return false;
557 }
558 m_initialized = true;
559 return true;
560}
561
563{
564 if (m_initialized)
565 AudioUnitUninitialize(get());
566
567 m_initialized = false;
568}
569
570} // namespace QCoreAudioUtils
571
572QT_END_NAMESPACE
std::optional< AudioStreamBasicDescription > audioUnitGetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element)
QAudioFormat::AudioChannelPosition pos
bool audioUnitIsRunning(AudioUnitHandle &audioUnit)
AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const &audioFormat)
QAudioFormat::ChannelConfig fromAudioChannelLayout(const AudioChannelLayout *layout)
std::optional< int > audioUnitGetFramesPerSlice(AudioUnitHandle &audioUnit)
bool audioUnitSetInputEnabled(AudioUnitHandle &audioUnit, bool enabled)
bool audioUnitSetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element, const AudioStreamBasicDescription &format)
std::unique_ptr< AudioChannelLayout, QFreeDeleter > toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
AudioChannelLayoutTag tag
bool audioUnitSetRenderCallback(AudioUnitHandle &audioUnit, AURenderCallbackStruct &callback)
QAudioFormat::ChannelConfig channelConfig
bool audioUnitSetOutputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element, const AudioStreamBasicDescription &format)
AudioChannelLabel label
bool audioUnitSetOutputEnabled(AudioUnitHandle &audioUnit, bool enabled)
QAudioFormat toQAudioFormat(AudioStreamBasicDescription const &sf)
std::optional< AudioUnitHandle > makeAudioUnitForIO()