6#include <QtCore/qdatastream.h>
7#include <QtCore/qdebug.h>
8#include <QtCore/qloggingcategory.h>
9#include <QtMultimedia/qmediadevices.h>
10#include <QtMultimedia/private/qaudio_qiodevice_support_p.h>
11#include <QtMultimedia/private/qaudiohelpers_p.h>
12#include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
13#include <QtMultimedia/private/qautoresetevent_p.h>
14#include <QtMultimedia/private/qcoreaudioutils_p.h>
15#include <QtMultimedia/private/qapple_utils_p.h>
16#include <QtMultimedia/private/qcoreaudiodevice_p.h>
17#include <QtMultimedia/private/qcoreaudiodevices_p.h>
19#include <AudioUnit/AudioComponent.h>
20#include <AudioToolbox/AudioConverter.h>
22# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
24# import <AVFoundation/AVAudioSession.h>
33 : QPlatformAudioSourceStream{
34 std::move(audioDevice),
44QCoreAudioSourceStream::~QCoreAudioSourceStream()
47 m_stopOnDisconnected.cancelChain();
51 AudioConverterDispose(m_audioConverter);
52 free(m_bufferList.mBuffers[0].mData);
55bool QCoreAudioSourceStream::
open()
57 using namespace QCoreAudioUtils;
59 if (
auto audioUnit = makeAudioUnitForIO())
60 m_audioUnit = std::move(*audioUnit);
64 audioUnitSetInputEnabled(m_audioUnit,
true);
65 audioUnitSetOutputEnabled(m_audioUnit,
false);
68 AURenderCallbackStruct callback{
69 .inputProc = [](
void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
70 const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
71 UInt32 inNumberFrames,
72 AudioBufferList *ioData)
noexcept Q_DECL_NONBLOCKING_FUNCTION -> OSStatus {
73 return reinterpret_cast<QCoreAudioSourceStream *>(inRefCon)->processInput(
74 ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
76 .inputProcRefCon =
this,
79 if (AudioUnitSetProperty(m_audioUnit.get(), kAudioOutputUnitProperty_SetInputCallback,
80 kAudioUnitScope_Global, 0, &callback,
sizeof(callback))
82 qWarning() <<
"QAudioSource: Failed to set AudioUnit callback";
86 AudioStreamBasicDescription streamFormat = toAudioStreamBasicDescription(m_format);
91 const std::optional<AudioDeviceID> nativeDeviceId = findAudioDeviceId(m_audioDevice);
92 if (!nativeDeviceId) {
93 qWarning() <<
"QAudioSource: Unable to use find most recent CoreAudio AudioDeviceID for "
94 "given device-id. The device might not be connected.";
97 if (!setDisconnectListener(*nativeDeviceId))
101 if (!audioUnitSetCurrentDevice(m_audioUnit, *nativeDeviceId))
104 std::optional<
int> deviceSamplingRate = audioObjectGetSamplingRate(*nativeDeviceId);
106 if (!deviceSamplingRate) {
107 qWarning() <<
"QAudioSource: Device does not support any sampling rate. This should not "
112 if (m_nativePeriodFrames)
113 audioObjectSetFramesPerBuffer(*nativeDeviceId,
114 qToUnderlying(*m_nativePeriodFrames));
116 if (deviceSamplingRate != m_format.sampleRate()) {
117 AudioStreamBasicDescription desiredFormat = streamFormat;
119 streamFormat.mSampleRate = *deviceSamplingRate;
121 OSStatus status = AudioConverterNew(&streamFormat, &desiredFormat, &m_audioConverter);
122 if (status != noErr) {
123 qWarning() <<
"QAudioSource: Failed to create AudioConverter:"
124 << QtMultimediaPrivate::QOSStatus(status);
129 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
130 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
133 AVAudioSession *session = [AVAudioSession sharedInstance];
134 double hwRate = session.sampleRate;
135 std::optional<
int> deviceSamplingRate =
int(hwRate);
137 audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat);
138 audioUnitSetOutputStreamFormat(m_audioUnit, 1, streamFormat);
141 std::optional<
int> framesPerBuffer = audioUnitGetFramesPerSlice(m_audioUnit);
143 m_bufferCapacity = m_format.bytesForFrames(framesPerBuffer.value_or(2048));
144 m_bufferList.mNumberBuffers = 1;
145 m_bufferList.mBuffers[0].mNumberChannels = m_format.channelCount();
146 m_bufferList.mBuffers[0].mDataByteSize = m_bufferCapacity;
147 m_bufferList.mBuffers[0].mData = malloc(m_bufferList.mBuffers[0].mDataByteSize);
149 if (m_audioConverter) {
150 size_t outputBufferSize = m_bufferList.mBuffers[0].mDataByteSize * m_format.sampleRate()
151 /
static_cast<
float>(*deviceSamplingRate)
153 m_outputBuffer.resize(outputBufferSize);
154 m_outputBufferList.mNumberBuffers = 1;
155 m_outputBufferList.mBuffers[0].mNumberChannels = m_format.channelCount();
156 m_outputBufferList.mBuffers[0].mDataByteSize = outputBufferSize;
157 m_outputBufferList.mBuffers[0].mData = m_outputBuffer.data();
160 return m_audioUnit.initialize();
163bool QCoreAudioSourceStream::
start(QIODevice *device)
165 setQIODevice(device);
168 m_sessionActivation = QtMultimediaPrivate::QAVAudioSessionManager::instance()->activate(
172 if (!startAudioUnit())
175 createQIODeviceConnections(device);
182 QIODevice *device = createRingbufferReaderDevice();
183 bool opened = start(device);
190bool QCoreAudioSourceStream::
start(AudioCallback &&cb)
192 m_audioCallback = std::move(cb);
195 m_sessionActivation = QtMultimediaPrivate::QAVAudioSessionManager::instance()->activate(
199 return startAudioUnit();
202void QCoreAudioSourceStream::
stop(ShutdownPolicy shutdownPolicy)
213 disconnectQIODeviceConnections();
215 finalizeQIODevice(shutdownPolicy);
216 if (shutdownPolicy == ShutdownPolicy::DiscardRingbuffer)
220 m_sessionActivation.reset();
226 const auto status = AudioOutputUnitStop(m_audioUnit.get());
230 qDebug() <<
"AudioOutputUnitStop failed:" << QtMultimediaPrivate::QOSStatus(status);
235 return startAudioUnit();
240 if (audioUnitIsRunning(m_audioUnit))
249 handleIOError(m_parent);
255 m_parent->updateStreamIdle(idle);
258bool QCoreAudioSourceStream::startAudioUnit()
265 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
266 if (status != noErr) {
267 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
274void QCoreAudioSourceStream::stopAudioUnit()
279 m_stopOnDisconnected.cancelChain();
288 const auto status = AudioOutputUnitStop(m_audioUnit.get());
290 qDebug() <<
"AudioOutputUnitStop failed:" << QtMultimediaPrivate::QOSStatus(status);
296QCoreAudioSourceStream::processInput(AudioUnitRenderActionFlags *ioActionFlags,
297 const AudioTimeStamp *timeStamp, UInt32 inBusNumber,
298 UInt32 inNumberFrames,
299 AudioBufferList * )
noexcept Q_DECL_NONBLOCKING_FUNCTION
301 m_bufferList.mBuffers[0].mDataByteSize = m_bufferCapacity;
303 OSStatus status = AudioUnitRender(m_audioUnit.get(), ioActionFlags, timeStamp, inBusNumber,
304 inNumberFrames, &m_bufferList);
310 case kAudioUnitErr_CannotDoInCurrentContext:
316 qDebug() <<
"AudioUnitRender failed" << QtMultimediaPrivate::QOSStatus(status);
320 QSpan<
const std::byte> inputSpan;
321 if (m_audioConverter) {
323 struct InputProcState
325 QCoreAudioSourceStream *self;
326 UInt32 inNumberFrames;
329 InputProcState state{
334 auto inputProc = [](AudioConverterRef, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,
335 AudioStreamPacketDescription **outDataPacketDescription,
336 void *inUserData) -> OSStatus {
337 auto *state =
static_cast<InputProcState *>(inUserData);
338 *ioNumberDataPackets = state->inNumberFrames;
339 ioData->mNumberBuffers = 1;
340 ioData->mBuffers[0] = state->self->m_bufferList.mBuffers[0];
341 if (outDataPacketDescription)
342 *outDataPacketDescription =
nullptr;
346 m_outputBufferList.mBuffers[0].mDataByteSize = m_outputBuffer.size();
348 UInt32 outputFrames = m_format.framesForBytes(m_outputBuffer.size());
349 OSStatus convStatus = AudioConverterFillComplexBuffer(
350 m_audioConverter, inputProc, &state, &outputFrames, &m_outputBufferList,
nullptr);
351 if (convStatus != noErr) {
352 qDebug() <<
"AudioConverterFillComplexBuffer failed:" << convStatus;
356 inputSpan = QSpan<
const std::byte>{
357 reinterpret_cast<
const std::byte *>(m_outputBuffer.data()),
358 m_outputBufferList.mBuffers[0].mDataByteSize,
360 inNumberFrames = outputFrames;
362 inputSpan = QSpan<
const std::byte>{
363 reinterpret_cast<
const std::byte *>(m_bufferList.mBuffers[0].mData),
364 m_bufferList.mBuffers[0].mDataByteSize,
368 return m_audioCallback ? processAudioCallback(inputSpan)
369 : processRingbuffer(inputSpan, inNumberFrames);
373QCoreAudioSourceStream::processRingbuffer(QSpan<
const std::byte> inputSpan,
374 UInt32 inNumberFrames)
noexcept Q_DECL_NONBLOCKING_FUNCTION
376 QPlatformAudioSourceStream::process(inputSpan, inNumberFrames);
380OSStatus QCoreAudioSourceStream::processAudioCallback(QSpan<
const std::byte> inputSpan)
noexcept
381 Q_DECL_NONBLOCKING_FUNCTION
383 using namespace QtMultimediaPrivate;
384 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
390bool QCoreAudioSourceStream::setDisconnectListener(AudioObjectID id)
392 m_stopOnDisconnected.cancelChain();
394 auto disconnectionFuture = m_disconnectMonitor.setDisconnectListener(id);
395 if (!disconnectionFuture)
398 m_stopOnDisconnected = disconnectionFuture->then(m_parent, [
this] {
406 finalizeQIODevice(ShutdownPolicy::DrainRingbuffer);
408 QPlatformAudioSourceStream::handleIOError(m_parent);
417QCoreAudioSource::QCoreAudioSource(QAudioDevice device,
const QAudioFormat &format,
419 : BaseClass(std::move(device), format, parent)
423QCoreAudioSource::~QCoreAudioSource()
430 if (!m_stream || state() == QAudio::SuspendedState)
432 if (!m_stream->resumeIfNecessary())
433 qWarning() <<
"QCoreAudioSource: failed to resume audio unit";
440 m_recovery.userResumeRequested();
The QAudioDevice class provides an information about audio devices and their functionality.
bool start(AudioCallback &&)
void stop(ShutdownPolicy)
void invalidateAudioUnit()
void updateStreamIdle(bool idle) override
void notifyStreamStopped()
void resumeStreamIfNecessary()
Combined button and popup list for selecting options.