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#ifdef Q_OS_MACOS
21# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
22#else
23# include <QtMultimedia/private/qcoreaudiosessionmanager_p.h>
24#endif
25
27
28QCoreAudioSourceStream::QCoreAudioSourceStream(QAudioDevice audioDevice,
29 const QAudioFormat &format,
32 : QPlatformAudioSourceStream{
33 std::move(audioDevice),
34 format,
35 ringbufferSize,
36 hardwareBufferFrames,
37 volume,
38 },
39 m_parent(parent)
40{
41}
42
43QCoreAudioSourceStream::~QCoreAudioSourceStream()
44{
45 free(m_bufferList.mBuffers[0].mData);
46}
47
48bool QCoreAudioSourceStream::open()
49{
50 using namespace QCoreAudioUtils;
51
52 if (auto audioUnit = makeAudioUnitForIO())
53 m_audioUnit = std::move(*audioUnit);
54 else
55 return false;
56
57 audioUnitSetInputEnabled(m_audioUnit, true);
58 audioUnitSetOutputEnabled(m_audioUnit, false);
59
60 // register callback
61 AURenderCallbackStruct callback;
62 callback.inputProc = inputCallback;
63 callback.inputProcRefCon = this;
64
65 if (AudioUnitSetProperty(m_audioUnit.get(), kAudioOutputUnitProperty_SetInputCallback,
66 kAudioUnitScope_Global, 0, &callback, sizeof(callback))
67 != noErr) {
68 qWarning() << "QAudioSource: Failed to set AudioUnit callback";
69 return false;
70 }
71
72#ifdef Q_OS_MACOS
73 // Find the the most recent CoreAudio AudioDeviceID for the current device
74 // to start the audio stream.
75 const std::optional<AudioDeviceID> nativeDeviceId = findAudioDeviceId(m_audioDevice);
76 if (!nativeDeviceId) {
77 qWarning() << "QAudioSource: Unable to use find most recent CoreAudio AudioDeviceID for "
78 "given device-id. The device might not be connected.";
79 return false;
80 }
81 if (!addDisconnectListener(*nativeDeviceId))
82 return false;
83
84 // Set Audio Device
85 if (!audioUnitSetCurrentDevice(m_audioUnit, *nativeDeviceId))
86 return false;
87
88 std::optional<int> bestNominalSamplingRate = audioObjectFindBestNominalSampleRate(
89 *nativeDeviceId, QAudioDevice::Input, m_format.sampleRate());
90
91 if (bestNominalSamplingRate) {
92 if (!audioObjectSetSamplingRate(*nativeDeviceId, *bestNominalSamplingRate))
93 return false;
94 }
95
96 if (m_hardwareBufferFrames)
97 audioObjectSetFramesPerBuffer(*nativeDeviceId, *m_hardwareBufferFrames);
98#endif
99
100 AudioStreamBasicDescription streamFormat = toAudioStreamBasicDescription(m_format);
101
102 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
103 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
104
105 std::optional<int> framesPerBuffer = audioUnitGetFramesPerSlice(m_audioUnit);
106
107 m_bufferList.mNumberBuffers = 1;
108 m_bufferList.mBuffers[0].mNumberChannels = m_format.channelCount();
109 m_bufferList.mBuffers[0].mDataByteSize =
110 m_format.bytesForFrames(framesPerBuffer.value_or(2048));
111 m_bufferList.mBuffers[0].mData = malloc(m_bufferList.mBuffers[0].mDataByteSize);
112
113 return m_audioUnit.initialize();
114}
115
116bool QCoreAudioSourceStream::start(QIODevice *device)
117{
118 setQIODevice(device);
119
120 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
121 if (status != noErr) {
122 qDebug() << "AudioOutputUnitStart failed:" << status;
123 return false;
124 }
125
126 m_audioUnitRunning = true;
127 createQIODeviceConnections(device);
128
129 return true;
130}
131
132QIODevice *QCoreAudioSourceStream::start()
133{
134 QIODevice *device = createRingbufferReaderDevice();
135 bool opened = start(device);
136 if (!opened)
137 return nullptr;
138
139 return device;
140}
141
142bool QCoreAudioSourceStream::start(AudioCallback &&cb)
143{
144 m_audioCallback = std::move(cb);
145
146 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
147 if (status != noErr) {
148 qDebug() << "AudioOutputUnitStart failed:" << status;
149 return false;
150 }
151
152 m_audioUnitRunning = true;
153
154 return true;
155}
156
157void QCoreAudioSourceStream::stop(ShutdownPolicy shutdownPolicy)
158{
159 requestStop();
160
161 stopAudioUnit();
162
163 disconnectQIODeviceConnections();
164
165 finalizeQIODevice(shutdownPolicy);
166 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
167 emptyRingbuffer();
168}
169
170void QCoreAudioSourceStream::suspend()
171{
172 const auto status = AudioOutputUnitStop(m_audioUnit.get());
173 if (status == noErr)
174 return;
175 else
176 qDebug() << "AudioOutputUnitStop failed:" << status;
177}
178
179void QCoreAudioSourceStream::resume()
180{
181 const auto status = AudioOutputUnitStart(m_audioUnit.get());
182 if (status == noErr)
183 return;
184 else
185 qDebug() << "AudioOutputUnitStart failed:" << status;
186}
187
188void QCoreAudioSourceStream::resumeIfNecessary()
189{
190 if (!audioUnitIsRunning(m_audioUnit))
191 resume();
192}
193
194void QCoreAudioSourceStream::updateStreamIdle(bool idle)
195{
196 if (m_parent)
197 m_parent->updateStreamIdle(idle);
198}
199
200void QCoreAudioSourceStream::stopAudioUnit()
201{
202 const auto status = AudioOutputUnitStop(m_audioUnit.get());
203 if (status != noErr)
204 qDebug() << "AudioOutputUnitStop failed:" << status;
205
206 m_audioUnitRunning = false;
207
208#ifdef Q_OS_MACOS
209 removeDisconnectListener();
210#endif
211 m_audioUnit = {};
212}
213
214OSStatus QCoreAudioSourceStream::inputCallback(void *inRefCon,
215 AudioUnitRenderActionFlags *ioActionFlags,
216 const AudioTimeStamp *inTimeStamp,
217 UInt32 inBusNumber, UInt32 inNumberFrames,
218 AudioBufferList *ioData)
219{
220 auto *self = reinterpret_cast<QCoreAudioSourceStream *>(inRefCon);
221 if (self->m_audioCallback)
222 return self->processAudioCallback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames,
223 ioData);
224 else
225 return self->processRingbuffer(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames,
226 ioData);
227}
228
229OSStatus
230QCoreAudioSourceStream::processRingbuffer(AudioUnitRenderActionFlags *ioActionFlags,
231 const AudioTimeStamp *timeStamp, UInt32 inBusNumber,
232 UInt32 inNumberFrames,
233 AudioBufferList * /*ioData*/) noexcept QT_MM_NONBLOCKING
234{
235 OSStatus status = AudioUnitRender(m_audioUnit.get(), ioActionFlags, timeStamp, inBusNumber,
236 inNumberFrames, &m_bufferList);
237
238 switch (status) {
239 case noErr:
240 break;
241
242 case kAudioUnitErr_CannotDoInCurrentContext:
243 // it seems that during warmup, kAudioUnitErr_CannotDoInCurrentContext can occur for a few
244 // times at startup
245 return status;
246
247 default:
248 qDebug() << "AudioUnitRender failed" << status;
249 return status;
250 }
251
252 QSpan<const std::byte> inputSpan{
253 reinterpret_cast<const std::byte *>(m_bufferList.mBuffers[0].mData),
254 m_bufferList.mBuffers[0].mDataByteSize,
255 };
256
257 QPlatformAudioSourceStream::process(inputSpan, inNumberFrames);
258
259 return noErr;
260}
261
262OSStatus QCoreAudioSourceStream::processAudioCallback(AudioUnitRenderActionFlags *ioActionFlags,
263 const AudioTimeStamp *timeStamp,
264 UInt32 inBusNumber, UInt32 inNumberFrames,
265 AudioBufferList * /*ioData*/) noexcept
266{
267 OSStatus status = AudioUnitRender(m_audioUnit.get(), ioActionFlags, timeStamp, inBusNumber,
268 inNumberFrames, &m_bufferList);
269
270 switch (status) {
271 case noErr:
272 break;
273
274 case kAudioUnitErr_CannotDoInCurrentContext:
275 // it seems that during warmup, kAudioUnitErr_CannotDoInCurrentContext can occur for a few
276 // times at startup
277 return status;
278
279 default:
280 qDebug() << "AudioUnitRender failed" << status;
281 return status;
282 }
283
284 QSpan<const std::byte> inputSpan{
285 reinterpret_cast<const std::byte *>(m_bufferList.mBuffers[0].mData),
286 m_bufferList.mBuffers[0].mDataByteSize,
287 };
288
289 using namespace QtMultimediaPrivate;
290 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
291
292 return noErr;
293}
294
295#ifdef Q_OS_MACOS
296bool QCoreAudioSourceStream::addDisconnectListener(AudioObjectID id)
297{
298 m_stopOnDisconnected.cancel();
299
300 if (!m_disconnectMonitor.addDisconnectListener(id))
301 return false;
302
303 m_stopOnDisconnected = m_disconnectMonitor.then(m_parent, [this] {
304 // Coreaudio will pause for a bit and restart the audio unit with a different device.
305 // This is problematic, as it switches kAudioOutputUnitProperty_CurrentDevice and
306 // invalidates the native device ID (and the disconnect handler). furthermore, we don't have
307 // a way to re-synchronize the audio stream. so we explicitly stop the audio unit
308
309 requestStop();
310 stopAudioUnit();
311 finalizeQIODevice(ShutdownPolicy::DrainRingbuffer);
312
313 QPlatformAudioSourceStream::handleIOError(m_parent);
314 });
315
316 return true;
317}
318
319void QCoreAudioSourceStream::removeDisconnectListener()
320{
321 m_stopOnDisconnected.cancel();
322 m_disconnectMonitor.removeDisconnectListener();
323}
324#endif
325
326////////////////////////////////////////////////////////////////////////////////////////////////////
327
328QDarwinAudioSource::QDarwinAudioSource(QAudioDevice device, const QAudioFormat &format,
329 QObject *parent)
330 : BaseClass(std::move(device), format, parent)
331{
332#ifndef Q_OS_MACOS
333 if (qGuiApp)
334 QObject::connect(qGuiApp, &QGuiApplication::applicationStateChanged, this,
335 [this](Qt::ApplicationState state) {
336 if (state == Qt::ApplicationState::ApplicationActive)
337 resumeStreamIfNecessary();
338 });
339#endif
340}
341
342void QDarwinAudioSource::resumeStreamIfNecessary()
343{
344 if (m_stream)
345 m_stream->resumeIfNecessary();
346}
347
348QT_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