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
236{
237 AudioComponentDescription componentDescription;
238 componentDescription.componentType = kAudioUnitType_Output;
239#if defined(Q_OS_MACOS)
240 componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
241#else
242 componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
243#endif
244 componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
245 componentDescription.componentFlags = 0;
246 componentDescription.componentFlagsMask = 0;
247
248 AudioComponent component = AudioComponentFindNext(nullptr, &componentDescription);
249 if (component == nullptr) {
250 qWarning() << "makeAudioUnitForIO: Failed to find Output component";
251 return std::nullopt;
252 }
253
254 AudioUnitHandle audioUnit;
255 if (AudioComponentInstanceNew(component, &audioUnit) != noErr) {
256 qWarning() << "makeAudioUnitForIO: Unable to Open Output Component";
257 return std::nullopt;
258 }
259
260 return audioUnit;
261}
262
263bool audioUnitSetInputEnabled(AudioUnitHandle &audioUnit, bool enabled)
264{
265 UInt32 enable = enabled ? 1 : 0;
266 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
267 kAudioUnitScope_Input, 1, &enable, sizeof(enable))
268 != noErr) {
269 qWarning() << "AudioUnit: Unable to enable input";
270 return false;
271 }
272 return true;
273}
274
275bool audioUnitSetOutputEnabled(AudioUnitHandle &audioUnit, bool enabled)
276{
277 UInt32 enable = enabled ? 1 : 0;
278 if (AudioUnitSetProperty(audioUnit.get(), kAudioOutputUnitProperty_EnableIO,
279 kAudioUnitScope_Output, 0, &enable, sizeof(enable))
280 != noErr) {
281 qWarning() << "AudioUnit: Unable to enable output";
282 return false;
283 }
284 return true;
285}
286
287#ifdef Q_OS_MACOS
289{
292 != noErr) {
293 qWarning() << "AudioUnit: Unable to use set device ID";
294 return false;
295 }
296
297 return true;
298}
299#endif
300
301bool audioUnitSetInputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
302 const AudioStreamBasicDescription &format)
303{
304 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
305 kAudioUnitScope_Input, element, &format, sizeof(format));
306 if (status != noErr) {
307 qWarning() << "AudioUnit: Unable to set stream format" << status;
308 return false;
309 }
310 return true;
311}
312
313bool audioUnitSetOutputStreamFormat(AudioUnitHandle &audioUnit, AudioUnitElement element,
314 const AudioStreamBasicDescription &format)
315{
316 if (AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
317 kAudioUnitScope_Output, element, &format, sizeof(format))
318 != noErr) {
319 qWarning() << "AudioUnit: Unable to set stream format";
320 return false;
321 }
322 return true;
323}
324
325#ifdef Q_OS_MACOS
327{
328 int numberOfFrames;
329 UInt32 size = sizeof(UInt32);
332 == noErr) {
333 return numberOfFrames;
334 }
335
336 qWarning() << "audioUnitGetFramesPerBuffer: Failed to get audio period size";
337
339 size = sizeof(AudioValueRange);
342 == noErr) {
343 return int(bufferRange.mMaximum);
344 }
345
346 return std::nullopt;
347}
348
350{
355 };
356
358
360 sizeof(Float64), &sampleRateArg);
361
362 if (status != noErr) {
363 qDebug() << "AudioObjectSetPropertyData failed" << status;
364 return false;
365 }
366
367 return true;
368}
369
371 int rate)
372{
376 return std::nullopt;
377
378 using namespace QtMultimediaPrivate;
380 QSpan<const double>{
382 });
383}
384
386{
391 };
392
394 sizeof(int32_t), &bufferFrames);
395
396 if (status != noErr) {
397 qDebug() << "AudioObjectSetPropertyData failed" << status;
398 return false;
399 }
400
401 return true;
402}
403
404#endif
405
407{
408 UInt32 isRunning = 0;
409 UInt32 size = sizeof(isRunning);
410
411 OSStatus status = AudioUnitGetProperty(audioUnit.get(), kAudioOutputUnitProperty_IsRunning,
412 kAudioUnitScope_Global, 0, &isRunning, &size);
413
414 if (status != noErr) {
415 qDebug() << "AudioUnitGetProperty failed" << status;
416 return false;
417 }
418
419 return bool(isRunning);
420}
421
422bool audioUnitSetRenderCallback(AudioUnitHandle &audioUnit, AURenderCallbackStruct &callback)
423{
424 OSStatus status = AudioUnitSetProperty(audioUnit.get(), kAudioUnitProperty_SetRenderCallback,
425 kAudioUnitScope_Global, 0, &callback, sizeof(callback));
426
427 if (status != noErr) {
428 qWarning() << "AudioUnitSetProperty: Failed to set AudioUnit "
429 "kAudioUnitProperty_SetRenderCallback"
430 << status;
431 return false;
432 }
433 return true;
434}
435
437{
438 int numberOfFrames;
439 UInt32 size = sizeof(UInt32);
440 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_MaximumFramesPerSlice,
441 kAudioUnitScope_Global, 0, &numberOfFrames, &size)
442 == noErr) {
443 return numberOfFrames;
444 }
445
446 return std::nullopt;
447}
448
450 AudioUnitElement element)
451{
452 AudioStreamBasicDescription ret;
453 UInt32 size = sizeof(AudioStreamBasicDescription);
454
455 if (AudioUnitGetProperty(audioUnit.get(), kAudioUnitProperty_StreamFormat,
456 kAudioUnitScope_Input, element, &ret, &size)
457 != noErr) {
458 qWarning() << "QAudioSource: Unable to retrieve device format";
459 return std::nullopt;
460 }
461
462 return ret;
463}
464
469
471{
472 Q_ASSERT(!m_initialized);
473 if (AudioUnitInitialize(get())) {
474 qWarning() << "AudioUnitHandle: Failed to initialize AudioUnit";
475 return false;
476 }
477 m_initialized = true;
478 return true;
479}
480
482{
483 if (m_initialized)
484 AudioUnitUninitialize(get());
485
486 m_initialized = false;
487}
488
489} // namespace QCoreAudioUtils
490
491QT_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.