6#include <QtCore/qloggingcategory.h>
7#include <QtCore/qdebug.h>
8#include <QtGui/qguiapplication.h>
9#include <QtMultimedia/qmediadevices.h>
10#include <QtMultimedia/private/qaudio_qiodevice_support_p.h>
11#include <QtMultimedia/private/qaudio_rtsan_support_p.h>
12#include <QtMultimedia/private/qaudiohelpers_p.h>
13#include <QtMultimedia/private/qaudioringbuffer_p.h>
14#include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
15#include <QtMultimedia/private/qautoresetevent_p.h>
16#include <QtMultimedia/private/qcoreaudioutils_p.h>
17#include <QtMultimedia/private/qdarwinaudiodevice_p.h>
18#include <QtMultimedia/private/qdarwinaudiodevices_p.h>
20#include <AudioUnit/AudioUnit.h>
22# include <AudioUnit/AudioComponent.h>
23# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
25# include <QtMultimedia/private/qcoreaudiosessionmanager_p.h>
34QCoreAudioSinkStream::QCoreAudioSinkStream(QAudioDevice audioDevice,
const QAudioFormat& format,
35 std::optional<qsizetype> ringbufferSize,
36 QDarwinAudioSink *parent,
float volume,
37 std::optional<int32_t> hardwareBufferFrames,
39 : QPlatformAudioSinkStream {
40 std::move(audioDevice), format, ringbufferSize, hardwareBufferFrames, volume,
46QCoreAudioSinkStream::~QCoreAudioSinkStream()
49 m_stopOnDisconnected.cancelChain();
53bool QCoreAudioSinkStream::
open()
55 using namespace QCoreAudioUtils;
60 std::optional<AudioDeviceID> audioDeviceId = findAudioDeviceId(m_audioDevice);
62 qWarning() <<
"QAudioSource: Unable to use find most recent CoreAudio AudioDeviceID for "
63 "given device-id. The device might not be connected.";
66 const AudioDeviceID nativeDeviceId = audioDeviceId.value();
69 if (
auto audioUnit = makeAudioUnitForIO())
70 m_audioUnit = std::move(*audioUnit);
75 std::optional<
int> bestNominalSamplingRate =
76 audioObjectFindBestNominalSampleRate(nativeDeviceId, QAudioDevice::Output, m_format.sampleRate());
78 if (bestNominalSamplingRate) {
79 if (!audioObjectSetSamplingRate(nativeDeviceId, *bestNominalSamplingRate))
84 if (!addDisconnectListener(*audioDeviceId))
88 audioUnitSetCurrentDevice(m_audioUnit, nativeDeviceId);
90 if (m_hardwareBufferFrames)
91 audioObjectSetFramesPerBuffer(*audioDeviceId, *m_hardwareBufferFrames);
95 const AudioStreamBasicDescription streamFormat = toAudioStreamBasicDescription(m_format);
96 if (!audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat))
99 return m_audioUnit.initialize();
102bool QCoreAudioSinkStream::
start(QIODevice *device)
104 auto renderCallback = [](
void *self, [[maybe_unused]] AudioUnitRenderActionFlags *ioActionFlags,
105 [[maybe_unused]]
const AudioTimeStamp *inTimeStamp,
106 [[maybe_unused]] UInt32 inBusNumber,
107 [[maybe_unused]] UInt32 inNumberFrames,
108 AudioBufferList *ioData) -> OSStatus {
109 return reinterpret_cast<QCoreAudioSinkStream *>(self)->processRingbuffer(inNumberFrames,
113 AURenderCallbackStruct callback{
114 .inputProc = renderCallback,
115 .inputProcRefCon =
this,
117 if (!audioUnitSetRenderCallback(m_audioUnit, callback))
120 setQIODevice(device);
123 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
124 if (status != noErr) {
125 qDebug() <<
"AudioOutputUnitStart failed:" << status;
129 m_audioUnitRunning =
true;
131 createQIODeviceConnections(device);
138 QIODevice *reader = createRingbufferWriterDevice();
140 bool success = start(reader);
147bool QCoreAudioSinkStream::
start(AudioCallback cb)
149 auto renderCallback = [](
void *self, [[maybe_unused]] AudioUnitRenderActionFlags *ioActionFlags,
150 [[maybe_unused]]
const AudioTimeStamp *inTimeStamp,
151 [[maybe_unused]] UInt32 inBusNumber,
152 [[maybe_unused]] UInt32 inNumberFrames,
153 AudioBufferList *ioData) -> OSStatus {
154 return reinterpret_cast<QCoreAudioSinkStream *>(self)->processAudioCallback(inNumberFrames,
158 m_audioCallback = std::move(cb);
160 AURenderCallbackStruct callback;
161 callback.inputProc = renderCallback;
162 callback.inputProcRefCon =
this;
163 if (!audioUnitSetRenderCallback(m_audioUnit, callback))
166 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
167 if (status != noErr) {
168 qDebug() <<
"AudioOutputUnitStart failed:" << status;
174void QCoreAudioSinkStream::
stop(ShutdownPolicy policy)
178 if (m_audioCallback) {
183 disconnectQIODeviceConnections();
187 case ShutdownPolicy::DrainRingbuffer:
190 case ShutdownPolicy::DiscardRingbuffer:
194 Q_UNREACHABLE_RETURN();
206 connectIdleHandler([
this, ownedSelf = shared_from_this()]()
mutable {
220 removeDisconnectListener();
228 const auto status = AudioOutputUnitStop(m_audioUnit.get());
232 qDebug() <<
"AudioOutputUnitStop failed:" << status;
237 const auto status = AudioOutputUnitStart(m_audioUnit.get());
241 qDebug() <<
"AudioOutputUnitStart failed:" << status;
246 if (!QCoreAudioUtils::audioUnitIsRunning(m_audioUnit))
250OSStatus QCoreAudioSinkStream::processRingbuffer(uint32_t numberOfFrames,
251 AudioBufferList *ioData)
noexcept QT_MM_NONBLOCKING
253 Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
255 QSpan audioBufferSpan{
256 reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
257 ioData->mBuffers[0].mDataByteSize,
260 QPlatformAudioSinkStream::process(audioBufferSpan, numberOfFrames);
265OSStatus QCoreAudioSinkStream::processAudioCallback(uint32_t numberOfFrames,
266 AudioBufferList *ioData)
noexcept QT_MM_NONBLOCKING
268 using namespace QtMultimediaPrivate;
270 Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
272 QSpan<std::byte> inputSpan{
273 reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
274 ioData->mBuffers[0].mDataByteSize,
277 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
283void QCoreAudioSinkStream::updateStreamIdle(
bool arg)
285 m_parent->updateStreamIdle(arg);
288void QCoreAudioSinkStream::stopAudioUnit()
290 const auto status = AudioOutputUnitStop(m_audioUnit.get());
292 qDebug() <<
"AudioOutputUnitStop failed:" << status;
294 m_audioUnitRunning =
false;
297 removeDisconnectListener();
303bool QCoreAudioSinkStream::addDisconnectListener(AudioObjectID id)
305 m_stopOnDisconnected.cancel();
307 auto disconnectionFuture = m_disconnectMonitor.addDisconnectListener(id);
308 if (!disconnectionFuture)
311 m_stopOnDisconnected = disconnectionFuture->then(m_parent, [
this] {
319 handleIOError(m_parent);
325void QCoreAudioSinkStream::removeDisconnectListener()
327 m_stopOnDisconnected.cancel();
328 m_disconnectMonitor.removeDisconnectListener();
334QDarwinAudioSink::QDarwinAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
335 : BaseClass(std::move(device), format, parent)
339 QObject::connect(qGuiApp, &QGuiApplication::applicationStateChanged,
this,
340 [
this](Qt::ApplicationState state) {
341 if (state == Qt::ApplicationState::ApplicationActive)
342 resumeStreamIfNecessary();
347QDarwinAudioSink::~QDarwinAudioSink()
353 m_stream->resumeIfNecessary();
void stop(ShutdownPolicy policy)
bool start(AudioCallback cb)
bool start(QIODevice *device)
void stopStreamWhenBufferDrained()
void resumeStreamIfNecessary()