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 toPreferredQAudioFormat(AudioStreamBasicDescription const &sf)
19{
20 // coreaudio will do the format conversions for us, we only need to give the best match
21 const QAudioFormat::SampleFormat format = [&] {
22 const bool isFloat = sf.mFormatFlags & kAudioFormatFlagIsFloat;
23 switch (sf.mBitsPerChannel) {
24 case 8:
25 return QAudioFormat::UInt8;
26 case 16:
27 return QAudioFormat::Int16;
28 case 32:
29 return isFloat ? QAudioFormat::Float : QAudioFormat::Int32;
30 default:
31 return QAudioFormat::Float;
32 }
33 }();
34
35 QAudioFormat audioFormat;
36 audioFormat.setSampleFormat(format);
37 audioFormat.setSampleRate(sf.mSampleRate);
38 audioFormat.setChannelCount(sf.mChannelsPerFrame);
39
40 return audioFormat;
41}
42
73
74
75static constexpr struct {
78} channelMap[] = {
101
103toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
104{
105 auto channelConfig = format.channelConfig();
106 if (channelConfig == QAudioFormat::ChannelConfigUnknown)
107 channelConfig = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
108
109 *size = sizeof(AudioChannelLayout) + int(QAudioFormat::NChannelPositions)*sizeof(AudioChannelDescription);
110 auto *layout = static_cast<AudioChannelLayout *>(malloc(*size));
111 memset(layout, 0, *size);
112 layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
113
114 for (const auto &m : channelMap) {
115 if (channelConfig & QAudioFormat::channelConfig(m.pos))
116 layout->mChannelDescriptions[layout->mNumberChannelDescriptions++].mChannelLabel = m.label;
117 }
118
119 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontCenter)) {
120 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
121 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
122 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
123 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 0.f;
124 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
125 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
126 }
127 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontLeft)) {
128 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
129 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
130 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
131 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = -45.f;
132 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
133 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
134 }
135 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontRight)) {
136 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
137 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
138 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
139 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 45.f;
140 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
141 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
142 }
143
144 return std::unique_ptr<AudioChannelLayout, QFreeDeleter>(layout);
145}
146
147static constexpr struct {
150} layoutTagMap[] = {
181
188
189
190QAudioFormat::ChannelConfig fromAudioChannelLayout(const AudioChannelLayout *layout)
191{
192 for (const auto &m : layoutTagMap) {
193 if (m.tag == layout->mChannelLayoutTag)
194 return m.channelConfig;
195 }
196
197 quint32 channels = 0;
198 if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
199 // special case 1 and 2 channel configs, as they are often reported without proper descriptions
200 if (layout->mNumberChannelDescriptions == 1
201 && (layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown
202 || layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Mono))
203 return QAudioFormat::ChannelConfigMono;
204 if (layout->mNumberChannelDescriptions == 2 &&
205 layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown &&
206 layout->mChannelDescriptions[1].mChannelLabel == kAudioChannelLabel_Unknown)
207 return QAudioFormat::ChannelConfigStereo;
208
209 for (uint i = 0; i < layout->mNumberChannelDescriptions; ++i) {
210 const auto channelLabel = layout->mChannelDescriptions[i].mChannelLabel;
211 if (channelLabel == kAudioChannelLabel_Unknown) {
212 // Any number of unknown channel labels occurs for loopback audio devices.
213 // E.g. the case is reproduced with installed software Soundflower.
214 continue;
215 }
216
217 const auto found = std::find_if(channelMap, std::end(channelMap),
218 [channelLabel](const auto &labelWithPos) {
219 return labelWithPos.label == channelLabel;
220 });
221
222 if (found == std::end(channelMap))
223 qWarning() << "audio device has unrecognized channel, index:" << i
224 << "label:" << channelLabel;
225 else
226 channels |= QAudioFormat::channelConfig(found->pos);
227 }
228 } else {
229 qWarning() << "Channel layout uses unimplemented format, channelLayoutTag:"
230 << layout->mChannelLayoutTag;
231 }
232 return QAudioFormat::ChannelConfig(channels);
233}
234
235#ifdef Q_OS_MACOS
236
239 void *self)
240{
241 return reinterpret_cast<DeviceDisconnectMonitor *>(self)->streamDisconnectListener(
243}
244
249};
250
252{
253 m_currentId = id;
256
259
260 if (status != noErr) {
261 qWarning() << "QAudioOutput: Failed to add property listener";
262 return false;
263 }
264 return true;
265}
266
268{
272
273 switch (status) {
274 case noErr:
275 case kAudioHardwareBadObjectError: // when the listener fires, we may get
276 // kAudioHardwareBadObjectError
277 return;
278
279 default:
280 qWarning() << "QAudioOutput: Failed to remove property listener" << status;
281 }
282}
283
286{
287 // Called on HAL thread
288 // we use futures/continuations to notify the application thread, as we can cancel in-flight
289 // continuations
291
296
298 }
299 }
300
302}
303
304#endif
305
307{
308 AudioComponentDescription componentDescription;
309 componentDescription.componentType = kAudioUnitType_Output;
310#if defined(Q_OS_MACOS)
311 componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
312#else
313 componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
314#endif
315 componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
316 componentDescription.componentFlags = 0;
317 componentDescription.componentFlagsMask = 0;
318
319 AudioComponent component = AudioComponentFindNext(nullptr, &componentDescription);
320 if (component == nullptr) {
321 qWarning() << "makeAudioUnitForIO: Failed to find Output component";
322 return std::nullopt;
323 }
324
325 AudioUnitHandle audioUnit;
326 if (AudioComponentInstanceNew(component, &audioUnit) != noErr) {
327 qWarning() << "makeAudioUnitForIO: Unable to Open Output Component";
328 return std::nullopt;
329 }
330
331 return audioUnit;
332}
333
334bool audioUnitSetInputEnabled(AudioUnitHandle &audioUnit, bool enabled)
335{
336 UInt32 enable = enabled ? 1 : 0;
337 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
338 kAudioUnitScope_Input, 1, &enable, sizeof(enable))
339 != noErr) {
340 qWarning() << "AudioUnit: Unable to enable input";
341 return false;
342 }
343 return true;
344}
345
346bool audioUnitSetOutputEnabled(AudioUnitHandle &audioUnit, bool enabled)
347{
348 UInt32 enable = enabled ? 1 : 0;
349 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
350 kAudioUnitScope_Output, 0, &enable, sizeof(enable))
351 != noErr) {
352 qWarning() << "AudioUnit: Unable to enable output";
353 return false;
354 }
355 return true;
356}
357
358#ifdef Q_OS_MACOS
360{
363 != noErr) {
364 qWarning() << "AudioUnit: Unable to use set device ID";
365 return false;
366 }
367
368 return true;
369}
370#endif
371
372bool audioUnitSetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
373 const AudioStreamBasicDescription &format)
374{
375 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
376 kAudioUnitScope_Input, element, &format, sizeof(format));
377 if (status != noErr) {
378 qWarning() << "AudioUnit: Unable to set stream format" << status;
379 return false;
380 }
381 return true;
382}
383
384bool audioUnitSetOutputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
385 const AudioStreamBasicDescription &format)
386{
387 if (AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
388 kAudioUnitScope_Output, element, &format, sizeof(format))
389 != noErr) {
390 qWarning() << "AudioUnit: Unable to set stream format";
391 return false;
392 }
393 return true;
394}
395
396#ifdef Q_OS_MACOS
398{
399 int numberOfFrames;
400 UInt32 size = sizeof(UInt32);
403 == noErr) {
404 return numberOfFrames;
405 }
406
407 qWarning() << "audioUnitGetFramesPerBuffer: Failed to get audio period size";
408
410 size = sizeof(AudioValueRange);
413 == noErr) {
414 return int(bufferRange.mMaximum);
415 }
416
417 return std::nullopt;
418}
419
421{
426 };
427
429
431 sizeof(Float64), &sampleRateArg);
432
433 if (status != noErr) {
434 qDebug() << "AudioObjectSetPropertyData failed" << status;
435 return false;
436 }
437
438 return true;
439}
440
442 int rate)
443{
447 return std::nullopt;
448
449 using namespace QtMultimediaPrivate;
451 QSpan<const double>{
453 });
454}
455
457{
462 };
463
465 sizeof(int32_t), &bufferFrames);
466
467 if (status != noErr) {
468 qDebug() << "AudioObjectSetPropertyData failed" << status;
469 return false;
470 }
471
472 return true;
473}
474
475#endif
476
478{
479 UInt32 isRunning = 0;
480 UInt32 size = sizeof(isRunning);
481
482 OSStatus status = AudioUnitGetProperty(audioUnit.get(), kAudioOutputUnitProperty_IsRunning,
483 kAudioUnitScope_Global, 0, &isRunning, &size);
484
485 if (status != noErr) {
486 qDebug() << "AudioUnitGetProperty failed" << status;
487 return false;
488 }
489
490 return bool(isRunning);
491}
492
493bool audioUnitSetRenderCallback(AudioUnitHandle &audioUnit, AURenderCallbackStruct &callback)
494{
495 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_SetRenderCallback,
496 kAudioUnitScope_Global, 0, &callback, sizeof(callback));
497
498 if (status != noErr) {
499 qWarning() << "AudioUnitSetProperty: Failed to set AudioUnit "
500 "kAudioUnitProperty_SetRenderCallback"
501 << status;
502 return false;
503 }
504 return true;
505}
506
508{
509 int numberOfFrames;
510 UInt32 size = sizeof(UInt32);
511 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_MaximumFramesPerSlice,
512 kAudioUnitScope_Global, 0, &numberOfFrames, &size)
513 == noErr) {
514 return numberOfFrames;
515 }
516
517 return std::nullopt;
518}
519
521 AudioUnitElement element)
522{
523 AudioStreamBasicDescription ret;
524 UInt32 size = sizeof(AudioStreamBasicDescription);
525
526 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
527 kAudioUnitScope_Input, element, &ret, &size)
528 != noErr) {
529 qWarning() << "QAudioSource: Unable to retrieve device format";
530 return std::nullopt;
531 }
532
533 return ret;
534}
535
540
542{
543 Q_ASSERT(!m_initialized);
544 if (AudioUnitInitialize(get())) {
545 qWarning() << "AudioUnitHandle: Failed to initialize AudioUnit";
546 return false;
547 }
548 m_initialized = true;
549 return true;
550}
551
553{
554 if (m_initialized)
555 AudioUnitUninitialize(get());
556
557 m_initialized = false;
558}
559
560} // namespace QCoreAudioUtils
561
562QT_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
QAudioFormat toPreferredQAudioFormat(AudioStreamBasicDescription const &sf)
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)
std::optional< AudioUnitHandle > makeAudioUnitForIO()
Combined button and popup list for selecting options.