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/qaudiohelpers_p.h>
12#include <QtMultimedia/private/qaudioringbuffer_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/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 QCoreAudioSink *parent,
float volume,
37 std::optional<QtMultimediaPrivate::NativePeriodFrames> nativePeriodFrames,
39 : QPlatformAudioSinkStream {
40 std::move(audioDevice), format, ringbufferSize, nativePeriodFrames, 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);
76 if (!setDisconnectListener(*audioDeviceId))
80 audioUnitSetCurrentDevice(m_audioUnit, nativeDeviceId);
82 if (m_nativePeriodFrames)
83 audioObjectSetFramesPerBuffer(*audioDeviceId,
84 qToUnderlying(*m_nativePeriodFrames));
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 AURenderCallbackStruct callback{
98 .inputProc = [](
void *self, [[maybe_unused]] AudioUnitRenderActionFlags *ioActionFlags,
99 [[maybe_unused]]
const AudioTimeStamp *inTimeStamp,
100 [[maybe_unused]] UInt32 inBusNumber, UInt32 inNumberFrames,
101 AudioBufferList *ioData)
noexcept Q_DECL_NONBLOCKING_FUNCTION -> OSStatus {
102 return reinterpret_cast<QCoreAudioSinkStream *>(self)->processRingbuffer(inNumberFrames,
105 .inputProcRefCon =
this,
107 if (!audioUnitSetRenderCallback(m_audioUnit, callback))
110 setQIODevice(device);
113 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
114 if (status != noErr) {
115 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
119 m_audioUnitRunning =
true;
121 createQIODeviceConnections(device);
128 QIODevice *reader = createRingbufferWriterDevice();
130 bool success = start(reader);
137bool QCoreAudioSinkStream::
start(AudioCallback cb)
139 m_audioCallback = std::move(cb);
141 AURenderCallbackStruct callback{
142 .inputProc = [](
void *self, [[maybe_unused]] AudioUnitRenderActionFlags *ioActionFlags,
143 [[maybe_unused]]
const AudioTimeStamp *inTimeStamp,
144 [[maybe_unused]] UInt32 inBusNumber, UInt32 inNumberFrames,
145 AudioBufferList *ioData)
noexcept Q_DECL_NONBLOCKING_FUNCTION -> OSStatus {
146 return reinterpret_cast<QCoreAudioSinkStream *>(self)->processAudioCallback(inNumberFrames,
149 .inputProcRefCon =
this,
151 if (!audioUnitSetRenderCallback(m_audioUnit, callback))
154 const OSStatus status = AudioOutputUnitStart(m_audioUnit.get());
155 if (status != noErr) {
156 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
162void QCoreAudioSinkStream::
stop(ShutdownPolicy policy)
166 if (m_audioCallback) {
171 disconnectQIODeviceConnections();
175 case ShutdownPolicy::DrainRingbuffer:
178 case ShutdownPolicy::DiscardRingbuffer:
182 Q_UNREACHABLE_RETURN();
194 connectIdleHandler([
this, ownedSelf = shared_from_this()]()
mutable {
208 m_stopOnDisconnected.cancelChain();
216 const auto status = AudioOutputUnitStop(m_audioUnit.get());
220 qDebug() <<
"AudioOutputUnitStop failed:" << QtMultimediaPrivate::QOSStatus(status);
225 const auto status = AudioOutputUnitStart(m_audioUnit.get());
229 qDebug() <<
"AudioOutputUnitStart failed:" << QtMultimediaPrivate::QOSStatus(status);
234 if (!QCoreAudioUtils::audioUnitIsRunning(m_audioUnit))
238OSStatus QCoreAudioSinkStream::processRingbuffer(uint32_t numberOfFrames,
239 AudioBufferList *ioData)
noexcept Q_DECL_NONBLOCKING_FUNCTION
241 Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
243 QSpan audioBufferSpan{
244 reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
245 ioData->mBuffers[0].mDataByteSize,
248 QPlatformAudioSinkStream::process(audioBufferSpan, numberOfFrames);
253OSStatus QCoreAudioSinkStream::processAudioCallback(uint32_t numberOfFrames,
254 AudioBufferList *ioData)
noexcept Q_DECL_NONBLOCKING_FUNCTION
256 using namespace QtMultimediaPrivate;
258 Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
260 QSpan<std::byte> inputSpan{
261 reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
262 ioData->mBuffers[0].mDataByteSize,
265 runAudioCallback(*m_audioCallback, inputSpan, m_format, volume());
271void QCoreAudioSinkStream::updateStreamIdle(
bool arg)
273 m_parent->updateStreamIdle(arg);
276void QCoreAudioSinkStream::stopAudioUnit()
278 const auto status = AudioOutputUnitStop(m_audioUnit.get());
280 qDebug() <<
"AudioOutputUnitStop failed:" << QtMultimediaPrivate::QOSStatus(status);
282 m_audioUnitRunning =
false;
285 m_stopOnDisconnected.cancelChain();
291bool QCoreAudioSinkStream::setDisconnectListener(AudioObjectID id)
293 m_stopOnDisconnected.cancelChain();
295 auto disconnectionFuture = m_disconnectMonitor.setDisconnectListener(id);
296 if (!disconnectionFuture)
299 m_stopOnDisconnected = disconnectionFuture->then(m_parent, [
this] {
307 handleIOError(m_parent);
316QCoreAudioSink::QCoreAudioSink(QAudioDevice device,
const QAudioFormat &format, QObject *parent)
317 : BaseClass(std::move(device), format, parent)
321 QObject::connect(qGuiApp, &QGuiApplication::applicationStateChanged,
this,
322 [
this](Qt::ApplicationState state) {
323 if (state == Qt::ApplicationState::ApplicationActive)
324 resumeStreamIfNecessary();
329QCoreAudioSink::~QCoreAudioSink()
335 m_stream->resumeIfNecessary();
void stop(ShutdownPolicy policy)
bool start(AudioCallback cb)
bool start(QIODevice *device)
void stopStreamWhenBufferDrained()
void resumeStreamIfNecessary()