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,
46bool QCoreAudioSinkStream::open()
48 using namespace QCoreAudioUtils;
53 std::optional<AudioDeviceID> audioDeviceId = findAudioDeviceId(m_audioDevice);
55 qWarning() <<
"QAudioSource: Unable to use find most recent CoreAudio AudioDeviceID for "
56 "given device-id. The device might not be connected.";
59 const AudioDeviceID nativeDeviceId = audioDeviceId.value();
62 if (
auto audioUnit = makeAudioUnitForIO())
63 m_audioUnit = std::move(*audioUnit);
68 std::optional<
int> bestNominalSamplingRate =
69 audioObjectFindBestNominalSampleRate(nativeDeviceId, QAudioDevice::Output, m_format.sampleRate());
71 if (bestNominalSamplingRate) {
72 if (!audioObjectSetSamplingRate(nativeDeviceId, *bestNominalSamplingRate))
77 if (!addDisconnectListener(*audioDeviceId))
81 audioUnitSetCurrentDevice(m_audioUnit, nativeDeviceId);
83 if (m_hardwareBufferFrames)
84 audioObjectSetFramesPerBuffer(*audioDeviceId, *m_hardwareBufferFrames);
88 const AudioStreamBasicDescription streamFormat = toAudioStreamBasicDescription(m_format);
89 if (!audioUnitSetInputStreamFormat(m_audioUnit, 0, streamFormat))
92 return m_audioUnit.initialize();
95bool QCoreAudioSinkStream::
start(QIODevice *device)
97 auto renderCallback = [](
void *self, [[maybe_unused]] AudioUnitRenderActionFlags *ioActionFlags,
98 [[maybe_unused]]
const AudioTimeStamp *inTimeStamp,
99 [[maybe_unused]] UInt32 inBusNumber,
100 [[maybe_unused]] UInt32 inNumberFrames,
101 AudioBufferList *ioData) -> OSStatus {
102 return reinterpret_cast<QCoreAudioSinkStream *>(self)->processRingbuffer(inNumberFrames,
106 AURenderCallbackStruct callback{
107 .inputProc = renderCallback,
108 .inputProcRefCon =
this,
110 if (!audioUnitSetRenderCallback(m_audioUnit, callback))
113 setQIODevice(device);
116 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
117 if (status != noErr) {
118 qDebug() <<
"AudioOutputUnitStart failed:" << status;
122 m_audioUnitRunning =
true;
124 createQIODeviceConnections(device);
131 QIODevice *reader = createRingbufferWriterDevice();
133 bool success = start(reader);
140bool QCoreAudioSinkStream::
start(AudioCallback cb)
142 auto renderCallback = [](
void *self, [[maybe_unused]] AudioUnitRenderActionFlags *ioActionFlags,
143 [[maybe_unused]]
const AudioTimeStamp *inTimeStamp,
144 [[maybe_unused]] UInt32 inBusNumber,
145 [[maybe_unused]] UInt32 inNumberFrames,
146 AudioBufferList *ioData) -> OSStatus {
147 return reinterpret_cast<QCoreAudioSinkStream *>(self)->processAudioCallback(inNumberFrames,
151 m_audioCallback = std::move(cb);
153 AURenderCallbackStruct callback;
154 callback.inputProc = renderCallback;
155 callback.inputProcRefCon =
this;
156 if (!audioUnitSetRenderCallback(m_audioUnit, callback))
159 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
160 if (status != noErr) {
161 qDebug() <<
"AudioOutputUnitStart failed:" << status;
167void QCoreAudioSinkStream::
stop(ShutdownPolicy policy)
171 if (m_audioCallback) {
176 disconnectQIODeviceConnections();
180 case ShutdownPolicy::DrainRingbuffer:
183 case ShutdownPolicy::DiscardRingbuffer:
187 Q_UNREACHABLE_RETURN();
199 connectIdleHandler([
this, ownedSelf = shared_from_this()]()
mutable {
213 removeDisconnectListener();
221 const auto status = AudioOutputUnitStop(m_audioUnit.get());
225 qDebug() <<
"AudioOutputUnitStop failed:" << status;
230 const auto status = AudioOutputUnitStart(m_audioUnit.get());
234 qDebug() <<
"AudioOutputUnitStart failed:" << status;
239 if (!QCoreAudioUtils::audioUnitIsRunning(m_audioUnit))
243OSStatus QCoreAudioSinkStream::processRingbuffer(uint32_t numberOfFrames,
244 AudioBufferList *ioData)
noexcept QT_MM_NONBLOCKING
246 Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
248 QSpan audioBufferSpan{
249 reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
250 ioData->mBuffers[0].mDataByteSize,
253 QPlatformAudioSinkStream::process(audioBufferSpan, numberOfFrames);
258OSStatus QCoreAudioSinkStream::processAudioCallback(uint32_t numberOfFrames,
259 AudioBufferList *ioData)
noexcept QT_MM_NONBLOCKING
261 using namespace QtMultimediaPrivate;
263 Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
265 QSpan<std::byte> inputSpan{
266 reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
267 ioData->mBuffers[0].mDataByteSize,
270 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
276void QCoreAudioSinkStream::updateStreamIdle(
bool arg)
278 m_parent->updateStreamIdle(arg);
281void QCoreAudioSinkStream::stopAudioUnit()
283 const auto status = AudioOutputUnitStop(m_audioUnit.get());
285 qDebug() <<
"AudioOutputUnitStop failed:" << status;
287 m_audioUnitRunning =
false;
290 removeDisconnectListener();
296bool QCoreAudioSinkStream::addDisconnectListener(AudioObjectID id)
298 m_stopOnDisconnected.cancel();
300 if (!m_disconnectMonitor.addDisconnectListener(id))
303 m_stopOnDisconnected = m_disconnectMonitor.then(m_parent, [
this] {
311 handleIOError(m_parent);
317void QCoreAudioSinkStream::removeDisconnectListener()
319 m_stopOnDisconnected.cancel();
320 m_disconnectMonitor.removeDisconnectListener();
326QDarwinAudioSink::QDarwinAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
327 : BaseClass(std::move(device), format, parent)
331 QObject::connect(qGuiApp, &QGuiApplication::applicationStateChanged,
this,
332 [
this](Qt::ApplicationState state) {
333 if (state == Qt::ApplicationState::ApplicationActive)
334 resumeStreamIfNecessary();
339void QDarwinAudioSink::resumeStreamIfNecessary()
342 m_stream->resumeIfNecessary();
void stop(ShutdownPolicy policy)
bool start(AudioCallback cb)
bool start(QIODevice *device)
void stopStreamWhenBufferDrained()