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