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