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>
20#include <AudioUnit/AudioComponent.h>
21#include <AudioToolbox/AudioConverter.h>
23# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
25# import <AVFoundation/AVAudioSession.h>
26# include <QtMultimedia/private/qcoreaudiosessionmanager_p.h>
35 : QPlatformAudioSourceStream{
36 std::move(audioDevice),
46QCoreAudioSourceStream::~QCoreAudioSourceStream()
49 m_stopOnDisconnected.cancelChain();
53 AudioConverterDispose(m_audioConverter);
54 free(m_bufferList.mBuffers[0].mData);
57bool QCoreAudioSourceStream::
open()
59 using namespace QCoreAudioUtils;
61 if (
auto audioUnit = makeAudioUnitForIO())
62 m_audioUnit = std::move(*audioUnit);
66 audioUnitSetInputEnabled(m_audioUnit,
true);
67 audioUnitSetOutputEnabled(m_audioUnit,
false);
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);
78 .inputProcRefCon =
this,
81 if (AudioUnitSetProperty(m_audioUnit.get(), kAudioOutputUnitProperty_SetInputCallback,
82 kAudioUnitScope_Global, 0, &callback,
sizeof(callback))
84 qWarning() <<
"QAudioSource: Failed to set AudioUnit callback";
88 AudioStreamBasicDescription streamFormat = toAudioStreamBasicDescription(m_format);
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.";
99 if (!setDisconnectListener(*nativeDeviceId))
103 if (!audioUnitSetCurrentDevice(m_audioUnit, *nativeDeviceId))
106 std::optional<
int> deviceSamplingRate = audioObjectGetSamplingRate(*nativeDeviceId);
108 if (!deviceSamplingRate) {
109 qWarning() <<
"QAudioSource: Device does not support any sampling rate. This should not "
114 if (m_nativePeriodFrames)
115 audioObjectSetFramesPerBuffer(*nativeDeviceId,
116 qToUnderlying(*m_nativePeriodFrames));
118 if (deviceSamplingRate != m_format.sampleRate()) {
119 AudioStreamBasicDescription desiredFormat = streamFormat;
121 streamFormat.mSampleRate = *deviceSamplingRate;
123 OSStatus status = AudioConverterNew(&streamFormat, &desiredFormat, &m_audioConverter);
124 if (status != noErr) {
125 qWarning() <<
"QAudioSource: Failed to create AudioConverter:"
126 << QtMultimediaPrivate::QOSStatus(status);
131 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
132 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
135 AVAudioSession *session = [AVAudioSession sharedInstance];
136 double hwRate = session.sampleRate;
137 std::optional<
int> deviceSamplingRate =
int(hwRate);
139 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
140 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
143 std::optional<
int> framesPerBuffer = audioUnitGetFramesPerSlice(m_audioUnit);
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);
151 if (m_audioConverter) {
152 size_t outputBufferSize = m_bufferList.mBuffers[0].mDataByteSize * m_format.sampleRate()
153 /
static_cast<
float>(*deviceSamplingRate)
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();
162 return m_audioUnit.initialize();
165bool QCoreAudioSourceStream::
start(QIODevice *device)
167 setQIODevice(device);
169 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
170 if (status != noErr) {
171 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
175 m_audioUnitRunning =
true;
176 createQIODeviceConnections(device);
183 QIODevice *device = createRingbufferReaderDevice();
184 bool opened = start(device);
191bool QCoreAudioSourceStream::
start(AudioCallback &&cb)
193 m_audioCallback = std::move(cb);
195 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
196 if (status != noErr) {
197 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
201 m_audioUnitRunning =
true;
206void QCoreAudioSourceStream::
stop(ShutdownPolicy shutdownPolicy)
212 disconnectQIODeviceConnections();
214 finalizeQIODevice(shutdownPolicy);
215 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
221 const auto status = AudioOutputUnitStop(m_audioUnit.get());
225 qDebug() <<
"AudioOutputUnitStop failed:" << QtMultimediaPrivate::QOSStatus(status);
230 const auto status = AudioOutputUnitStart(m_audioUnit.get());
234 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
239 if (!audioUnitIsRunning(m_audioUnit))
246 m_parent->updateStreamIdle(idle);
249void QCoreAudioSourceStream::stopAudioUnit()
251 const auto status = AudioOutputUnitStop(m_audioUnit.get());
253 qDebug() <<
"AudioOutputUnitStop failed:" << QtMultimediaPrivate::QOSStatus(status);
255 m_audioUnitRunning =
false;
258 m_stopOnDisconnected.cancelChain();
264QCoreAudioSourceStream::processInput(AudioUnitRenderActionFlags *ioActionFlags,
265 const AudioTimeStamp *timeStamp, UInt32 inBusNumber,
266 UInt32 inNumberFrames,
267 AudioBufferList * )
noexcept Q_DECL_NONBLOCKING_FUNCTION
269 OSStatus status = AudioUnitRender(m_audioUnit.get(), ioActionFlags, timeStamp, inBusNumber,
270 inNumberFrames, &m_bufferList);
276 case kAudioUnitErr_CannotDoInCurrentContext:
282 qDebug() <<
"AudioUnitRender failed" << QtMultimediaPrivate::QOSStatus(status);
286 QSpan<
const std::byte> inputSpan;
287 if (m_audioConverter) {
289 struct InputProcState
291 QCoreAudioSourceStream *self;
292 UInt32 inNumberFrames;
295 InputProcState state{
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;
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;
320 uint32_t outputBytes = m_format.bytesForFrames(outputFrames);
321 inputSpan = QSpan<
const std::byte>{
322 reinterpret_cast<
const std::byte *>(m_outputBuffer.data()),
325 inNumberFrames = outputFrames;
327 inputSpan = QSpan<
const std::byte>{
328 reinterpret_cast<
const std::byte *>(m_bufferList.mBuffers[0].mData),
329 m_bufferList.mBuffers[0].mDataByteSize,
333 return m_audioCallback ? processAudioCallback(inputSpan)
334 : processRingbuffer(inputSpan, inNumberFrames);
338QCoreAudioSourceStream::processRingbuffer(QSpan<
const std::byte> inputSpan,
339 UInt32 inNumberFrames)
noexcept Q_DECL_NONBLOCKING_FUNCTION
341 QPlatformAudioSourceStream::process(inputSpan, inNumberFrames);
345OSStatus QCoreAudioSourceStream::processAudioCallback(QSpan<
const std::byte> inputSpan)
noexcept
346 Q_DECL_NONBLOCKING_FUNCTION
348 using namespace QtMultimediaPrivate;
349 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
355bool QCoreAudioSourceStream::setDisconnectListener(AudioObjectID id)
357 m_stopOnDisconnected.cancelChain();
359 auto disconnectionFuture = m_disconnectMonitor.setDisconnectListener(id);
360 if (!disconnectionFuture)
363 m_stopOnDisconnected = disconnectionFuture->then(m_parent, [
this] {
371 finalizeQIODevice(ShutdownPolicy::DrainRingbuffer);
373 QPlatformAudioSourceStream::handleIOError(m_parent);
382QCoreAudioSource::QCoreAudioSource(QAudioDevice device,
const QAudioFormat &format,
384 : BaseClass(std::move(device), format, parent)
388 QObject::connect(qGuiApp, &QGuiApplication::applicationStateChanged,
this,
389 [
this](Qt::ApplicationState state) {
390 if (state == Qt::ApplicationState::ApplicationActive)
391 resumeStreamIfNecessary();
396QCoreAudioSource::~QCoreAudioSource()
402 m_stream->resumeIfNecessary();
The QAudioDevice class provides an information about audio devices and their functionality.
bool start(AudioCallback &&)
void stop(ShutdownPolicy)
void updateStreamIdle(bool idle) override
void resumeStreamIfNecessary()
Combined button and popup list for selecting options.