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
qdarwinaudiosource.mm
Go to the documentation of this file.
1// Copyright (C) 2022 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/qdatastream.h>
7#include <QtCore/qdebug.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtGui/qguiapplication.h>
10#include <QtMultimedia/qmediadevices.h>
11#include <QtMultimedia/private/qaudio_qiodevice_support_p.h>
12#include <QtMultimedia/private/qaudiohelpers_p.h>
13#include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
14#include <QtMultimedia/private/qautoresetevent_p.h>
15#include <QtMultimedia/private/qcoreaudioutils_p.h>
16#include <QtMultimedia/private/qdarwinaudiodevice_p.h>
17#include <QtMultimedia/private/qdarwinaudiodevices_p.h>
18
19#include <AudioUnit/AudioComponent.h>
20#include <AudioToolbox/AudioConverter.h>
21#ifdef Q_OS_MACOS
22# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
23#else
24# import <AVFoundation/AVAudioSession.h>
25# include <QtMultimedia/private/qcoreaudiosessionmanager_p.h>
26#endif
27
29
30QCoreAudioSourceStream::QCoreAudioSourceStream(QAudioDevice audioDevice,
31 const QAudioFormat &format,
34 : QPlatformAudioSourceStream{
35 std::move(audioDevice),
36 format,
37 ringbufferSize,
38 hardwareBufferFrames,
39 volume,
40 },
41 m_parent(parent)
42{
43}
44
45QCoreAudioSourceStream::~QCoreAudioSourceStream()
46{
47#ifdef Q_OS_MACOS
48 m_stopOnDisconnected.cancelChain();
49#endif
50
51 if (m_audioConverter)
52 AudioConverterDispose(m_audioConverter);
53 free(m_bufferList.mBuffers[0].mData);
54}
55
56bool QCoreAudioSourceStream::open()
57{
58 using namespace QCoreAudioUtils;
59
60 if (auto audioUnit = makeAudioUnitForIO())
61 m_audioUnit = std::move(*audioUnit);
62 else
63 return false;
64
65 audioUnitSetInputEnabled(m_audioUnit, true);
66 audioUnitSetOutputEnabled(m_audioUnit, false);
67
68 // register callback
69 AURenderCallbackStruct callback;
70 callback.inputProc = inputCallback;
71 callback.inputProcRefCon = this;
72
73 if (AudioUnitSetProperty(m_audioUnit.get(), kAudioOutputUnitProperty_SetInputCallback,
74 kAudioUnitScope_Global, 0, &callback, sizeof(callback))
75 != noErr) {
76 qWarning() << "QAudioSource: Failed to set AudioUnit callback";
77 return false;
78 }
79
80 AudioStreamBasicDescription streamFormat = toAudioStreamBasicDescription(m_format);
81
82#ifdef Q_OS_MACOS
83 // Find the the most recent CoreAudio AudioDeviceID for the current device
84 // to start the audio stream.
85 const std::optional<AudioDeviceID> nativeDeviceId = findAudioDeviceId(m_audioDevice);
86 if (!nativeDeviceId) {
87 qWarning() << "QAudioSource: Unable to use find most recent CoreAudio AudioDeviceID for "
88 "given device-id. The device might not be connected.";
89 return false;
90 }
91 if (!addDisconnectListener(*nativeDeviceId))
92 return false;
93
94 // Set Audio Device
95 if (!audioUnitSetCurrentDevice(m_audioUnit, *nativeDeviceId))
96 return false;
97
98 std::optional<int> bestNominalSamplingRate = audioObjectFindBestNominalSampleRate(
99 *nativeDeviceId, QAudioDevice::Input, m_format.sampleRate());
100
101 if (bestNominalSamplingRate) {
102 if (!audioObjectSetSamplingRate(*nativeDeviceId, *bestNominalSamplingRate))
103 return false;
104 } else {
105 qWarning() << "QAudioSource: Device does not support any sampling rate. This should not "
106 "happen";
107 return false;
108 }
109
110 if (m_hardwareBufferFrames)
111 audioObjectSetFramesPerBuffer(*nativeDeviceId, *m_hardwareBufferFrames);
112
113 if (bestNominalSamplingRate != m_format.sampleRate()) {
114 AudioStreamBasicDescription desiredFormat = streamFormat;
115
116 streamFormat.mSampleRate = *bestNominalSamplingRate;
117
118 OSStatus status = AudioConverterNew(&streamFormat, &desiredFormat, &m_audioConverter);
119 if (status != noErr) {
120 qWarning() << "QAudioSource: Failed to create AudioConverter:" << status;
121 return false;
122 }
123 }
124
125 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
126 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
127#else
128
129 AVAudioSession *session = [AVAudioSession sharedInstance];
130 double hwRate = session.sampleRate;
131 std::optional<int> bestNominalSamplingRate = int(hwRate);
132
133 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
134 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
135#endif
136
137 std::optional<int> framesPerBuffer = audioUnitGetFramesPerSlice(m_audioUnit);
138
139 m_bufferList.mNumberBuffers = 1;
140 m_bufferList.mBuffers[0].mNumberChannels = m_format.channelCount();
141 m_bufferList.mBuffers[0].mDataByteSize =
142 m_format.bytesForFrames(framesPerBuffer.value_or(2048));
143 m_bufferList.mBuffers[0].mData = malloc(m_bufferList.mBuffers[0].mDataByteSize);
144
145 if (m_audioConverter) {
146 size_t outputBufferSize = m_bufferList.mBuffers[0].mDataByteSize * m_format.sampleRate()
147 / static_cast<float>(*bestNominalSamplingRate)
148 + 128 /*padding*/;
149 m_outputBuffer.resize(outputBufferSize);
150 m_outputBufferList.mNumberBuffers = 1;
151 m_outputBufferList.mBuffers[0].mNumberChannels = m_format.channelCount();
152 m_outputBufferList.mBuffers[0].mDataByteSize = outputBufferSize;
153 m_outputBufferList.mBuffers[0].mData = m_outputBuffer.data();
154 }
155
156 return m_audioUnit.initialize();
157}
158
159bool QCoreAudioSourceStream::start(QIODevice *device)
160{
161 setQIODevice(device);
162
163 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
164 if (status != noErr) {
165 qDebug() << "AudioOutputUnitStart failed:" << status;
166 return false;
167 }
168
169 m_audioUnitRunning = true;
170 createQIODeviceConnections(device);
171
172 return true;
173}
174
175QIODevice *QCoreAudioSourceStream::start()
176{
177 QIODevice *device = createRingbufferReaderDevice();
178 bool opened = start(device);
179 if (!opened)
180 return nullptr;
181
182 return device;
183}
184
185bool QCoreAudioSourceStream::start(AudioCallback &&cb)
186{
187 m_audioCallback = std::move(cb);
188
189 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
190 if (status != noErr) {
191 qDebug() << "AudioOutputUnitStart failed:" << status;
192 return false;
193 }
194
195 m_audioUnitRunning = true;
196
197 return true;
198}
199
200void QCoreAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
201{
202 requestStop();
203
204 stopAudioUnit();
205
206 disconnectQIODeviceConnections();
207
208 finalizeQIODevice(shutdownPolicy);
209 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
210 emptyRingbuffer();
211}
212
213void QCoreAudioSourceStream::suspend()
214{
215 const auto status = AudioOutputUnitStop(m_audioUnit.get());
216 if (status == noErr)
217 return;
218 else
219 qDebug() << "AudioOutputUnitStop failed:" << status;
220}
221
222void QCoreAudioSourceStream::resume()
223{
224 const auto status = AudioOutputUnitStart(m_audioUnit.get());
225 if (status == noErr)
226 return;
227 else
228 qDebug() << "AudioOutputUnitStart failed:" << status;
229}
230
231void QCoreAudioSourceStream::resumeIfNecessary()
232{
233 if (!audioUnitIsRunning(m_audioUnit))
234 resume();
235}
236
237void QCoreAudioSourceStream::updateStreamIdle(bool idle)
238{
239 if (m_parent)
240 m_parent->updateStreamIdle(idle);
241}
242
243void QCoreAudioSourceStream::stopAudioUnit()
244{
245 const auto status = AudioOutputUnitStop(m_audioUnit.get());
246 if (status != noErr)
247 qDebug() << "AudioOutputUnitStop failed:" << status;
248
249 m_audioUnitRunning = false;
250
251#ifdef Q_OS_MACOS
252 removeDisconnectListener();
253#endif
254 m_audioUnit = {};
255}
256
257OSStatus QCoreAudioSourceStream::inputCallback(void *inRefCon,
258 AudioUnitRenderActionFlags *ioActionFlags,
259 const AudioTimeStamp *inTimeStamp,
260 UInt32 inBusNumber, UInt32 inNumberFrames,
261 AudioBufferList *ioData)
262{
263 auto *self = reinterpret_cast<QCoreAudioSourceStream *>(inRefCon);
264 return self->processInput(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
265}
266
267OSStatus
268QCoreAudioSourceStream::processInput(AudioUnitRenderActionFlags *ioActionFlags,
269 const AudioTimeStamp *timeStamp, UInt32 inBusNumber,
270 UInt32 inNumberFrames,
271 AudioBufferList * /*ioData*/) noexcept QT_MM_NONBLOCKING
272{
273 OSStatus status = AudioUnitRender(m_audioUnit.get(), ioActionFlags, timeStamp, inBusNumber,
274 inNumberFrames, &m_bufferList);
275
276 switch (status) {
277 case noErr:
278 break;
279
280 case kAudioUnitErr_CannotDoInCurrentContext:
281 // it seems that during warmup, kAudioUnitErr_CannotDoInCurrentContext can occur for a few
282 // times at startup
283 return status;
284
285 default:
286 qDebug() << "AudioUnitRender failed" << status;
287 return status;
288 }
289
290 QSpan<const std::byte> inputSpan;
291 if (m_audioConverter) {
292 // convert the data to the desired sample rate
293 struct InputProcState
294 {
295 QCoreAudioSourceStream *self;
296 UInt32 inNumberFrames;
297 };
298
299 InputProcState state{
300 /*self:*/ this,
301 /*inNumberFrames:*/ inNumberFrames,
302 };
303
304 auto inputProc = [](AudioConverterRef, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,
305 AudioStreamPacketDescription **outDataPacketDescription,
306 void *inUserData) -> OSStatus {
307 auto *state = static_cast<InputProcState *>(inUserData);
308 *ioNumberDataPackets = state->inNumberFrames;
309 ioData->mNumberBuffers = 1;
310 ioData->mBuffers[0] = state->self->m_bufferList.mBuffers[0];
311 if (outDataPacketDescription)
312 *outDataPacketDescription = nullptr;
313 return noErr;
314 };
315
316 UInt32 outputFrames = m_format.framesForBytes(m_outputBuffer.size());
317 OSStatus convStatus = AudioConverterFillComplexBuffer(
318 m_audioConverter, inputProc, &state, &outputFrames, &m_outputBufferList, nullptr);
319 if (convStatus != noErr) {
320 qDebug() << "AudioConverterFillComplexBuffer failed:" << convStatus;
321 return convStatus;
322 }
323
324 uint32_t outputBytes = m_format.bytesForFrames(outputFrames);
325 inputSpan = QSpan<const std::byte>{
326 reinterpret_cast<const std::byte *>(m_outputBuffer.data()),
327 outputBytes,
328 };
329 inNumberFrames = outputFrames;
330 } else {
331 inputSpan = QSpan<const std::byte>{
332 reinterpret_cast<const std::byte *>(m_bufferList.mBuffers[0].mData),
333 m_bufferList.mBuffers[0].mDataByteSize,
334 };
335 }
336
337 return m_audioCallback ? processAudioCallback(inputSpan)
338 : processRingbuffer(inputSpan, inNumberFrames);
339}
340
341OSStatus
342QCoreAudioSourceStream::processRingbuffer(QSpan<const std::byte> inputSpan,
343 UInt32 inNumberFrames) noexcept QT_MM_NONBLOCKING
344{
345 QPlatformAudioSourceStream::process(inputSpan, inNumberFrames);
346 return noErr;
347}
348
349OSStatus QCoreAudioSourceStream::processAudioCallback(QSpan<const std::byte> inputSpan) noexcept
350 QT_MM_NONBLOCKING
351{
352 using namespace QtMultimediaPrivate;
353 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
354
355 return noErr;
356}
357
358#ifdef Q_OS_MACOS
359bool QCoreAudioSourceStream::addDisconnectListener(AudioObjectID id)
360{
361 m_stopOnDisconnected.cancel();
362
363 auto disconnectionFuture = m_disconnectMonitor.addDisconnectListener(id);
364 if (!disconnectionFuture)
365 return false;
366
367 m_stopOnDisconnected = disconnectionFuture->then(m_parent, [this] {
368 // Coreaudio will pause for a bit and restart the audio unit with a different device.
369 // This is problematic, as it switches kAudioOutputUnitProperty_CurrentDevice and
370 // invalidates the native device ID (and the disconnect handler). furthermore, we don't have
371 // a way to re-synchronize the audio stream. so we explicitly stop the audio unit
372
373 requestStop();
374 stopAudioUnit();
375 finalizeQIODevice(ShutdownPolicy::DrainRingbuffer);
376
377 QPlatformAudioSourceStream::handleIOError(m_parent);
378 });
379
380 return true;
381}
382
383void QCoreAudioSourceStream::removeDisconnectListener()
384{
385 m_stopOnDisconnected.cancel();
386 m_disconnectMonitor.removeDisconnectListener();
387}
388#endif
389
390////////////////////////////////////////////////////////////////////////////////////////////////////
391
392QDarwinAudioSource::QDarwinAudioSource(QAudioDevice device, const QAudioFormat &format,
393 QObject *parent)
394 : BaseClass(std::move(device), format, parent)
395{
396#ifndef Q_OS_MACOS
397 if (qGuiApp)
398 QObject::connect(qGuiApp, &QGuiApplication::applicationStateChanged, this,
399 [this](Qt::ApplicationState state) {
400 if (state == Qt::ApplicationState::ApplicationActive)
401 resumeStreamIfNecessary();
402 });
403#endif
404}
405
406QDarwinAudioSource::~QDarwinAudioSource()
407 = default;
408
409void QDarwinAudioSource::resumeStreamIfNecessary()
410{
411 if (m_stream)
412 m_stream->resumeIfNecessary();
413}
414
415QT_END_NAMESPACE
The QAudioDevice class provides an information about audio devices and their functionality.
bool start(AudioCallback &&)
void stop(ShutdownPolicy)
void updateStreamIdle(bool idle) override
Combined button and popup list for selecting options.