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#include <QtMultimedia/private/qmultimedia_ranges_p.h>
8#include <QtMultimedia/private/qapple_utils_p.h>
9
10#ifdef Q_OS_MACOS
11# include <CoreAudio/AudioHardware.h>
12# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
13# include <QtMultimedia/private/qaudioformat_p.h>
14#endif
15
17
18namespace QCoreAudioUtils {
19
21
22QAudioFormat toPreferredQAudioFormat(AudioStreamBasicDescription const &sf)
23{
24 // coreaudio will do the format conversions for us, we only need to give the best match
25 const QAudioFormat::SampleFormat format = [&] {
26 const bool isFloat = sf.mFormatFlags & kAudioFormatFlagIsFloat;
27 switch (sf.mBitsPerChannel) {
28 case 8:
29 return QAudioFormat::UInt8;
30 case 16:
31 return QAudioFormat::Int16;
32 case 32:
33 return isFloat ? QAudioFormat::Float : QAudioFormat::Int32;
34 default:
35 return QAudioFormat::Float;
36 }
37 }();
38
39 QAudioFormat audioFormat;
40 audioFormat.setSampleFormat(format);
41 audioFormat.setSampleRate(sf.mSampleRate);
42 audioFormat.setChannelCount(sf.mChannelsPerFrame);
43
44 return audioFormat;
45}
46
77
78
79static constexpr struct {
82} channelMap[] = {
105
107toAudioChannelLayout(const QAudioFormat &format, UInt32 *size)
108{
109 auto channelConfig = format.channelConfig();
110 if (channelConfig == QAudioFormat::ChannelConfigUnknown)
111 channelConfig = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
112
113 *size = sizeof(AudioChannelLayout) + int(QAudioFormat::NChannelPositions)*sizeof(AudioChannelDescription);
114 auto *layout = static_cast<AudioChannelLayout *>(malloc(*size));
115 memset(layout, 0, *size);
116 layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
117
118 for (const auto &m : channelMap) {
119 if (channelConfig & QAudioFormat::channelConfig(m.pos))
120 layout->mChannelDescriptions[layout->mNumberChannelDescriptions++].mChannelLabel = m.label;
121 }
122
123 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontCenter)) {
124 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
125 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
126 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
127 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 0.f;
128 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
129 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
130 }
131 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontLeft)) {
132 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
133 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
134 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
135 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = -45.f;
136 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
137 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
138 }
139 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::BottomFrontRight)) {
140 auto &desc = layout->mChannelDescriptions[layout->mNumberChannelDescriptions++];
141 desc.mChannelLabel = kAudioChannelLabel_UseCoordinates;
142 desc.mChannelFlags = kAudioChannelFlags_SphericalCoordinates;
143 desc.mCoordinates[kAudioChannelCoordinates_Azimuth] = 45.f;
144 desc.mCoordinates[kAudioChannelCoordinates_Elevation] = -20.;
145 desc.mCoordinates[kAudioChannelCoordinates_Distance] = 1.f;
146 }
147
148 return std::unique_ptr<AudioChannelLayout, QFreeDeleter>(layout);
149}
150
151static constexpr struct {
154} layoutTagMap[] = {
185
192
193
194QAudioFormat::ChannelConfig fromAudioChannelLayout(const AudioChannelLayout *layout)
195{
196 for (const auto &m : layoutTagMap) {
197 if (m.tag == layout->mChannelLayoutTag)
198 return m.channelConfig;
199 }
200
201 quint32 channels = 0;
202 if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) {
203 // special case 1 and 2 channel configs, as they are often reported without proper descriptions
204 if (layout->mNumberChannelDescriptions == 1
205 && (layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown
206 || layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Mono))
207 return QAudioFormat::ChannelConfigMono;
208 if (layout->mNumberChannelDescriptions == 2 &&
209 layout->mChannelDescriptions[0].mChannelLabel == kAudioChannelLabel_Unknown &&
210 layout->mChannelDescriptions[1].mChannelLabel == kAudioChannelLabel_Unknown)
211 return QAudioFormat::ChannelConfigStereo;
212
213 for (uint i = 0; i < layout->mNumberChannelDescriptions; ++i) {
214 const auto channelLabel = layout->mChannelDescriptions[i].mChannelLabel;
215 if (channelLabel == kAudioChannelLabel_Unknown) {
216 // Any number of unknown channel labels occurs for loopback audio devices.
217 // E.g. the case is reproduced with installed software Soundflower.
218 continue;
219 }
220
221 const auto found = ranges::find_if(channelMap, [&](const auto &labelWithPos) {
222 return labelWithPos.label == channelLabel;
223 });
224
225 if (found == std::end(channelMap))
226 qWarning() << "audio device has unrecognized channel, index:" << i
227 << "label:" << channelLabel;
228 else
229 channels |= QAudioFormat::channelConfig(found->pos);
230 }
231 } else {
232 qWarning() << "Channel layout uses unimplemented format, channelLayoutTag:"
233 << layout->mChannelLayoutTag;
234 }
235 return QAudioFormat::ChannelConfig(channels);
236}
237
239{
240 AudioComponentDescription componentDescription;
241 componentDescription.componentType = kAudioUnitType_Output;
242#if defined(Q_OS_MACOS)
243 componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
244#else
245 componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
246#endif
247 componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
248 componentDescription.componentFlags = 0;
249 componentDescription.componentFlagsMask = 0;
250
251 AudioComponent component = AudioComponentFindNext(nullptr, &componentDescription);
252 if (component == nullptr) {
253 qWarning() << "makeAudioUnitForIO: Failed to find Output component";
254 return std::nullopt;
255 }
256
257 AudioUnitHandle audioUnit;
258 if (AudioComponentInstanceNew(component, &audioUnit) != noErr) {
259 qWarning() << "makeAudioUnitForIO: Unable to Open Output Component";
260 return std::nullopt;
261 }
262
263 return audioUnit;
264}
265
266bool audioUnitSetInputEnabled(AudioUnitHandle &audioUnit, bool enabled)
267{
268 UInt32 enable = enabled ? 1 : 0;
269 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
270 kAudioUnitScope_Input, 1, &enable, sizeof(enable))
271 != noErr) {
272 qWarning() << "AudioUnit: Unable to enable input";
273 return false;
274 }
275 return true;
276}
277
278bool audioUnitSetOutputEnabled(AudioUnitHandle &audioUnit, bool enabled)
279{
280 UInt32 enable = enabled ? 1 : 0;
281 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
282 kAudioUnitScope_Output, 0, &enable, sizeof(enable))
283 != noErr) {
284 qWarning() << "AudioUnit: Unable to enable output";
285 return false;
286 }
287 return true;
288}
289
290#ifdef Q_OS_MACOS
292{
295 != noErr) {
296 qWarning() << "AudioUnit: Unable to use set device ID";
297 return false;
298 }
299
300 return true;
301}
302#endif
303
304bool audioUnitSetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
305 const AudioStreamBasicDescription &format)
306{
307 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
308 kAudioUnitScope_Input, element, &format, sizeof(format));
309 if (status != noErr) {
310 qWarning() << "AudioUnit: Unable to set stream format" << QtMultimediaPrivate::QOSStatus(status);
311 return false;
312 }
313 return true;
314}
315
316bool audioUnitSetOutputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
317 const AudioStreamBasicDescription &format)
318{
319 if (AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
320 kAudioUnitScope_Output, element, &format, sizeof(format))
321 != noErr) {
322 qWarning() << "AudioUnit: Unable to set stream format";
323 return false;
324 }
325 return true;
326}
327
328#ifdef Q_OS_MACOS
330{
331 int numberOfFrames;
332 UInt32 size = sizeof(UInt32);
335 == noErr) {
336 return numberOfFrames;
337 }
338
339 qWarning() << "audioUnitGetFramesPerBuffer: Failed to get audio period size";
340
342 size = sizeof(AudioValueRange);
345 == noErr) {
346 return int(bufferRange.mMaximum);
347 }
348
349 return std::nullopt;
350}
351
353{
358 };
359
361
363 sizeof(Float64), &sampleRateArg);
364
365 if (status != noErr) {
366 qDebug() << "AudioObjectSetPropertyData failed" << QtMultimediaPrivate::QOSStatus(status);
367 return false;
368 }
369
370 return true;
371}
372
374{
379 };
380
382 uint32_t size = sizeof(Float64);
384
385 if (status != noErr) {
386 qDebug() << "AudioObjectGetPropertyData failed" << status;
387 return {};
388 }
389
390 return int(rate);
391}
392
394 int rate)
395{
399 return std::nullopt;
400
401 using namespace QtMultimediaPrivate;
403 QSpan<const double>{
405 });
406}
407
409{
414 };
415
417 sizeof(int32_t), &bufferFrames);
418
419 if (status != noErr) {
420 qDebug() << "AudioObjectSetPropertyData failed" << QtMultimediaPrivate::QOSStatus(status);
421 return false;
422 }
423
424 return true;
425}
426
427#endif
428
430{
431 UInt32 isRunning = 0;
432 UInt32 size = sizeof(isRunning);
433
434 OSStatus status = AudioUnitGetProperty(audioUnit.get(), kAudioOutputUnitProperty_IsRunning,
435 kAudioUnitScope_Global, 0, &isRunning, &size);
436
437 if (status != noErr) {
438 qDebug() << "AudioUnitGetProperty failed" << QtMultimediaPrivate::QOSStatus(status);
439 return false;
440 }
441
442 return bool(isRunning);
443}
444
445bool audioUnitSetRenderCallback(AudioUnitHandle &audioUnit, AURenderCallbackStruct &callback)
446{
447 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_SetRenderCallback,
448 kAudioUnitScope_Global, 0, &callback, sizeof(callback));
449
450 if (status != noErr) {
451 qWarning() << "AudioUnitSetProperty: Failed to set AudioUnit "
452 "kAudioUnitProperty_SetRenderCallback"
453 << QtMultimediaPrivate::QOSStatus(status);
454 return false;
455 }
456 return true;
457}
458
460{
461 int numberOfFrames;
462 UInt32 size = sizeof(UInt32);
463 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_MaximumFramesPerSlice,
464 kAudioUnitScope_Global, 0, &numberOfFrames, &size)
465 == noErr) {
466 return numberOfFrames;
467 }
468
469 return std::nullopt;
470}
471
473 AudioUnitElement element)
474{
475 AudioStreamBasicDescription ret;
476 UInt32 size = sizeof(AudioStreamBasicDescription);
477
478 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
479 kAudioUnitScope_Input, element, &ret, &size)
480 != noErr) {
481 qWarning() << "QAudioSource: Unable to retrieve device format";
482 return std::nullopt;
483 }
484
485 return ret;
486}
487
492
494{
495 Q_ASSERT(!m_initialized);
496 if (AudioUnitInitialize(get())) {
497 qWarning() << "AudioUnitHandle: Failed to initialize AudioUnit";
498 return false;
499 }
500 m_initialized = true;
501 return true;
502}
503
505{
506 if (m_initialized)
507 AudioUnitUninitialize(get());
508
509 m_initialized = false;
510}
511
512} // namespace QCoreAudioUtils
513
514QT_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.