Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qdarwinaudiosink.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
6#include "qcoreaudioutils_p.h"
8#include <qmediadevices.h>
9
10#include <QtCore/QDataStream>
11#include <QtCore/QTimer>
12#include <QtCore/QDebug>
13
14#include <AudioUnit/AudioUnit.h>
15#include <AudioToolbox/AudioToolbox.h>
16#if defined(Q_OS_MACOS)
17# include <AudioUnit/AudioComponent.h>
18#endif
19
20#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
21# include <QtMultimedia/private/qaudiohelpers_p.h>
22#endif
23
25
26static int audioRingBufferSize(int bufferSize, int maxPeriodSize)
27{
28 // TODO: review this code
29 return bufferSize
30 + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize));
31}
32
33QDarwinAudioSinkBuffer::QDarwinAudioSinkBuffer(int bufferSize, int maxPeriodSize,
34 const QAudioFormat &audioFormat)
35 : m_maxPeriodSize(maxPeriodSize),
36 m_bytesPerFrame(audioFormat.bytesPerFrame()),
37 m_periodTime(maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.sampleRate()),
38 m_buffer(
39 std::make_unique<CoreAudioRingBuffer>(audioRingBufferSize(bufferSize, maxPeriodSize)))
40{
41 m_fillTimer = new QTimer(this);
42 m_fillTimer->setTimerType(Qt::PreciseTimer);
43 m_fillTimer->setInterval(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime);
44 connect(m_fillTimer, &QTimer::timeout, this, &QDarwinAudioSinkBuffer::fillBuffer);
45}
46
48
50{
51 bool wecan = true;
52 qint64 framesRead = 0;
53
54 while (wecan && framesRead < maxFrames) {
55 CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame);
56
57 if (region.second > 0) {
58 // Ensure that we only read whole frames.
59 region.second -= region.second % m_bytesPerFrame;
60
61 if (region.second > 0) {
62 memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second);
63 framesRead += region.second / m_bytesPerFrame;
64 } else
65 wecan = false; // If there is only a partial frame left we should exit.
66 }
67 else
68 wecan = false;
69
70 m_buffer->releaseReadRegion(region);
71 }
72
73 if (framesRead == 0 && m_deviceError)
74 framesRead = -1;
75
76 return framesRead;
77}
78
80{
81 bool wecan = true;
83
84 maxSize -= maxSize % m_bytesPerFrame;
85 while (wecan && bytesWritten < maxSize) {
86 CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(maxSize - bytesWritten);
87
88 if (region.second > 0) {
89 memcpy(region.first, data + bytesWritten, region.second);
90 bytesWritten += region.second;
91 }
92 else
93 wecan = false;
94
95 m_buffer->releaseWriteRegion(region);
96 }
97
98 if (bytesWritten > 0)
100
101 return bytesWritten;
102}
103
105{
106 return m_buffer->free();
107}
108
110{
111 return m_deviceAtEnd;
112}
113
115{
116 m_buffer->reset();
117 setFillingEnabled(false);
118 setPrefetchDevice(nullptr);
119}
120
122{
123 if (std::exchange(m_device, device) == device)
124 return;
125
126 m_deviceError = false;
127 m_deviceAtEnd = device && device->atEnd();
128 const auto wasFillingEnabled = m_fillingEnabled;
129 setFillingEnabled(false);
130 setFillingEnabled(wasFillingEnabled);
131}
132
134{
135 return m_device;
136}
137
139{
140 if (std::exchange(m_fillingEnabled, enabled) == enabled)
141 return;
142
143 if (!enabled)
144 m_fillTimer->stop();
145 else if (m_device) {
146 fillBuffer();
147 m_fillTimer->start();
148 }
149}
150
151void QDarwinAudioSinkBuffer::fillBuffer()
152{
153 const int free = m_buffer->free();
154 const int writeSize = free - (free % m_maxPeriodSize);
155
156 if (writeSize > 0) {
157 bool wecan = true;
158 int filled = 0;
159
160 while (!m_deviceError && wecan && filled < writeSize) {
161 CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled);
162
163 if (region.second > 0) {
164 region.second = m_device->read(region.first, region.second);
165 m_deviceAtEnd = m_device->atEnd();
166 if (region.second > 0)
167 filled += region.second;
168 else if (region.second == 0)
169 wecan = false;
170 else if (region.second < 0) {
171 setFillingEnabled(false);
172 region.second = 0;
173 m_deviceError = true;
174 }
175 }
176 else
177 wecan = false;
178
179 m_buffer->releaseWriteRegion(region);
180 }
181
182 if (filled > 0)
183 emit readyRead();
184 }
185}
186
188 : QIODevice(parent)
189 , m_audioBuffer(audioBuffer)
190{
192}
193
195{
196 Q_UNUSED(data);
197 Q_UNUSED(len);
198
199 return 0;
200}
201
203{
204 return m_audioBuffer->writeBytes(data, len);
205}
206
208 : QPlatformAudioSink(parent), m_audioDeviceInfo(device), m_stateMachine(*this)
209{
210 QAudioDevice di = device;
211 if (di.isNull())
213#if defined(Q_OS_MACOS)
214 const QCoreAudioDeviceInfo *info = static_cast<const QCoreAudioDeviceInfo *>(di.handle());
215 Q_ASSERT(info);
216 m_audioDeviceId = info->deviceID();
217#endif
218 m_device = di.id();
219
220 m_clockFrequency = CoreAudioUtils::frequency() / 1000;
221
222 connect(this, &QDarwinAudioSink::stateChanged, this, &QDarwinAudioSink::updateAudioDevice);
223}
224
226{
227 close();
228}
229
231{
232 reset();
233
234 if (!m_audioDeviceInfo.isFormatSupported(m_audioFormat) || !open()) {
235 m_stateMachine.setError(QAudio::OpenError);
236 return;
237 }
238
239 if (!device) {
240 m_stateMachine.setError(QAudio::IOError);
241 return;
242 }
243
244 m_audioBuffer->setPrefetchDevice(device);
245
246 m_pullMode = true;
247 m_totalFrames = 0;
248
249 m_stateMachine.start();
250}
251
253{
254 reset();
255
256 if (!m_audioDeviceInfo.isFormatSupported(m_audioFormat) || !open()) {
257 m_stateMachine.setError(QAudio::OpenError);
258 return m_audioIO;
259 }
260
261 m_pullMode = false;
262 m_totalFrames = 0;
263
264 m_stateMachine.start(false);
265
266 return m_audioIO;
267}
268
270{
271 if (m_audioBuffer)
272 m_audioBuffer->setFillingEnabled(false);
273
274 if (auto notifier = m_stateMachine.stop(QAudio::NoError, true)) {
275 Q_ASSERT((notifier.prevAudioState() == QAudio::ActiveState) == notifier.isDraining());
276 if (notifier.isDraining() && !m_drainSemaphore.tryAcquire(1, 500)) {
277 const bool wasDraining = m_stateMachine.onDrained();
278
279 qWarning() << "Failed wait for getting sink drained; was draining:" << wasDraining;
280
281 // handle a rare corner case when the audio thread managed to release the semaphore in
282 // the time window between tryAcquire and onDrained
283 if (!wasDraining)
284 m_drainSemaphore.acquire();
285 }
286 }
287}
288
290{
291 onAudioDeviceDrained();
292 m_stateMachine.stopOrUpdateError();
293}
294
296{
297 m_stateMachine.suspend();
298}
299
301{
302 m_stateMachine.resume();
303}
304
306{
307 return m_audioBuffer->available();
308}
309
311{
313 m_internalBufferSize = value;
314}
315
317{
318 return m_internalBufferSize;
319}
320
322{
323 return m_totalFrames * 1000000 / m_audioFormat.sampleRate();
324}
325
327{
328 return m_stateMachine.error();
329}
330
332{
333 return m_stateMachine.state();
334}
335
337{
339 m_audioFormat = format;
340}
341
343{
344 return m_audioFormat;
345}
346
348{
349 m_cachedVolume = qBound(qreal(0.0), volume, qreal(1.0));
350 if (!m_isOpen)
351 return;
352
353#if defined(Q_OS_MACOS)
354 //on OS X the volume can be set directly on the AudioUnit
355 if (AudioUnitSetParameter(m_audioUnit,
356 kHALOutputParam_Volume,
357 kAudioUnitScope_Global,
358 0 /* bus */,
359 m_cachedVolume,
360 0) == noErr)
361 m_volume = m_cachedVolume;
362#endif
363}
364
366{
367 return m_cachedVolume;
368}
369
370void QDarwinAudioSink::inputReady()
371{
372 m_stateMachine.activateFromIdle();
373}
374
375OSStatus QDarwinAudioSink::renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
376{
377 Q_UNUSED(ioActionFlags);
378 Q_UNUSED(inTimeStamp);
379 Q_UNUSED(inBusNumber);
380 Q_UNUSED(inNumberFrames);
381
382 QDarwinAudioSink* d = static_cast<QDarwinAudioSink*>(inRefCon);
383
384 const auto [drained, stopped] = d->m_stateMachine.getDrainedAndStopped();
385
386 if (drained && stopped) {
387 ioData->mBuffers[0].mDataByteSize = 0;
388 } else {
389 const UInt32 bytesPerFrame = d->m_streamFormat.mBytesPerFrame;
390 qint64 framesRead;
391
392 Q_ASSERT(ioData->mBuffers[0].mDataByteSize / bytesPerFrame == inNumberFrames);
393 framesRead = d->m_audioBuffer->readFrames((char*)ioData->mBuffers[0].mData,
394 ioData->mBuffers[0].mDataByteSize / bytesPerFrame);
395
396 if (framesRead > 0) {
397 ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame;
398 d->m_totalFrames += framesRead;
399
400#if defined(Q_OS_MACOS)
401 if (stopped) {
402 qreal oldVolume = d->m_cachedVolume;
403 // Decrease volume smoothly.
404 d->setVolume(d->m_volume / 2);
405 d->m_cachedVolume = oldVolume;
406 }
407#elif defined(Q_OS_IOS) || defined(Q_OS_TVOS)
408 // on iOS we have to adjust the sound volume ourselves
409 if (!qFuzzyCompare(d->m_cachedVolume, qreal(1.0f))) {
411 d->m_audioFormat,
412 ioData->mBuffers[0].mData, /* input */
413 ioData->mBuffers[0].mData, /* output */
414 ioData->mBuffers[0].mDataByteSize);
415 }
416#endif
417
418 }
419 else {
420 ioData->mBuffers[0].mDataByteSize = 0;
421 if (framesRead == 0)
422 d->onAudioDeviceIdle();
423 else
424 d->onAudioDeviceError();
425 }
426 }
427
428 return noErr;
429}
430
431bool QDarwinAudioSink::open()
432{
433#if defined(Q_OS_IOS)
434 // Set default category to Ambient (implies MixWithOthers). This makes sure audio stops playing
435 // if the screen is locked or if the Silent switch is toggled.
437 CoreAudioSessionManager::instance().setActive(true);
438#endif
439
440 if (error() != QAudio::NoError)
441 return false;
442
443 if (m_isOpen) {
444 setVolume(m_cachedVolume);
445 return true;
446 }
447
448 AudioComponentDescription componentDescription;
449 componentDescription.componentType = kAudioUnitType_Output;
450#if defined(Q_OS_MACOS)
451 componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
452#else
453 componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
454#endif
455 componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
456 componentDescription.componentFlags = 0;
457 componentDescription.componentFlagsMask = 0;
458
459 AudioComponent component = AudioComponentFindNext(0, &componentDescription);
460 if (component == 0) {
461 qWarning() << "QAudioOutput: Failed to find Output component";
462 return false;
463 }
464
465 if (AudioComponentInstanceNew(component, &m_audioUnit) != noErr) {
466 qWarning() << "QAudioOutput: Unable to Open Output Component";
467 return false;
468 }
469
470 // register callback
471 AURenderCallbackStruct callback;
472 callback.inputProc = renderCallback;
473 callback.inputProcRefCon = this;
474
475 if (AudioUnitSetProperty(m_audioUnit,
476 kAudioUnitProperty_SetRenderCallback,
477 kAudioUnitScope_Global,
478 0,
479 &callback,
480 sizeof(callback)) != noErr) {
481 qWarning() << "QAudioOutput: Failed to set AudioUnit callback";
482 return false;
483 }
484
485#if defined(Q_OS_MACOS)
486 //Set Audio Device
487 if (AudioUnitSetProperty(m_audioUnit,
488 kAudioOutputUnitProperty_CurrentDevice,
489 kAudioUnitScope_Global,
490 0,
491 &m_audioDeviceId,
492 sizeof(m_audioDeviceId)) != noErr) {
493 qWarning() << "QAudioOutput: Unable to use configured device";
494 return false;
495 }
496#endif
497 UInt32 size;
498
499
500 // Set stream format
501 m_streamFormat = CoreAudioUtils::toAudioStreamBasicDescription(m_audioFormat);
502 size = sizeof(m_streamFormat);
503
504 if (AudioUnitSetProperty(m_audioUnit,
505 kAudioUnitProperty_StreamFormat,
506 kAudioUnitScope_Input,
507 0,
508 &m_streamFormat,
509 size) != noErr) {
510 qWarning() << "QAudioOutput: Unable to Set Stream information";
511 return false;
512 }
513
514 // Allocate buffer
515 UInt32 numberOfFrames = 0;
516#if defined(Q_OS_MACOS)
517 size = sizeof(UInt32);
518 if (AudioUnitGetProperty(m_audioUnit,
519 kAudioDevicePropertyBufferFrameSize,
520 kAudioUnitScope_Global,
521 0,
522 &numberOfFrames,
523 &size) != noErr) {
524 qWarning() << "QAudioSource: Failed to get audio period size";
525 return false;
526 }
527#else //iOS
528 Float32 bufferSize = CoreAudioSessionManager::instance().currentIOBufferDuration();
529 bufferSize *= m_streamFormat.mSampleRate;
530 numberOfFrames = bufferSize;
531#endif
532
533 m_periodSizeBytes = numberOfFrames * m_streamFormat.mBytesPerFrame;
534 if (m_internalBufferSize < m_periodSizeBytes * 2)
535 m_internalBufferSize = m_periodSizeBytes * 2;
536 else
537 m_internalBufferSize -= m_internalBufferSize % m_streamFormat.mBytesPerFrame;
538
539 m_audioBuffer = std::make_unique<QDarwinAudioSinkBuffer>(m_internalBufferSize,
540 m_periodSizeBytes, m_audioFormat);
541 connect(m_audioBuffer.get(), &QDarwinAudioSinkBuffer::readyRead, this,
542 &QDarwinAudioSink::inputReady); // Pull
543
544 m_audioIO = new QDarwinAudioSinkDevice(m_audioBuffer.get(), this);
545
546 //Init
547 if (AudioUnitInitialize(m_audioUnit)) {
548 qWarning() << "QAudioOutput: Failed to initialize AudioUnit";
549 return false;
550 }
551
552 m_isOpen = true;
553
554 setVolume(m_cachedVolume);
555
556 return true;
557}
558
559void QDarwinAudioSink::close()
560{
561 if (m_audioUnit != 0) {
562 m_stateMachine.stop();
563
564 AudioUnitUninitialize(m_audioUnit);
565 AudioComponentInstanceDispose(m_audioUnit);
566 m_audioUnit = 0;
567 }
568
569 m_audioBuffer.reset();
570}
571
572void QDarwinAudioSink::onAudioDeviceIdle()
573{
574 const bool atEnd = m_audioBuffer->deviceAtEnd();
575 if (!m_stateMachine.updateActiveOrIdle(false, atEnd ? QAudio::NoError : QAudio::UnderrunError))
576 onAudioDeviceDrained();
577}
578
579void QDarwinAudioSink::onAudioDeviceDrained()
580{
581 if (m_stateMachine.onDrained())
582 m_drainSemaphore.release();
583}
584
585void QDarwinAudioSink::onAudioDeviceError()
586{
587 m_stateMachine.stop(QAudio::IOError);
588}
589
590void QDarwinAudioSink::updateAudioDevice()
591{
592 const auto state = m_stateMachine.state();
593
594 Q_ASSERT(m_audioBuffer);
595 Q_ASSERT(m_audioUnit);
596
598 m_audioBuffer->reset();
599 else
600 m_audioBuffer->setFillingEnabled(state != QAudio::SuspendedState);
601
602 const bool unitStarted = state == QAudio::ActiveState;
603 if (std::exchange(m_audioUnitStarted, unitStarted) != unitStarted)
604 (unitStarted ? AudioOutputUnitStart : AudioOutputUnitStop)(m_audioUnit);
605}
606
608
609#include "moc_qdarwinaudiosink_p.cpp"
DarwinBluetooth::LECBManagerNotifier * notifier
IOBluetoothDevice * device
QPair< char *, int > Region
static CoreAudioSessionManager & instance()
static AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const &audioFormat)
static double frequency()
The QAudioDevice class provides an information about audio devices and their functionality.
bool isFormatSupported(const QAudioFormat &format) const
Returns true if the supplied settings are supported by the audio device described by this QAudioDevic...
The QAudioFormat class stores audio stream parameter information.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
void stateChanged(QAudio::State state)
QAudio::Error error() const
Notifier start(bool isActive=true)
Notifier updateActiveOrIdle(bool isActive, QAudio::Error error=QAudio::NoError)
QAudio::State state() const
Notifier stopOrUpdateError(QAudio::Error error=QAudio::NoError)
Notifier setError(QAudio::Error error)
Notifier stop(QAudio::Error error=QAudio::NoError, bool shouldDrain=false, bool forceUpdateError=false)
std::pair< bool, bool > getDrainedAndStopped() const
void setFillingEnabled(bool enabled)
qint64 readFrames(char *data, qint64 maxFrames)
qint64 writeBytes(const char *data, qint64 maxSize)
void setPrefetchDevice(QIODevice *device)
QDarwinAudioSinkBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const &audioFormat)
QIODevice * prefetchDevice() const
QDarwinAudioSinkDevice(QDarwinAudioSinkBuffer *audioBuffer, QObject *parent)
qint64 writeData(const char *data, qint64 len)
Writes up to maxSize bytes from data to the device.
qint64 readData(char *data, qint64 len)
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
void setFormat(const QAudioFormat &format)
qint64 processedUSecs() const
QAudioFormat format() const
QDarwinAudioSink(const QAudioDevice &device, QObject *parent)
void setBufferSize(qsizetype value)
QAudio::State state() const
QAudio::Error error() const
void setVolume(qreal volume)
qsizetype bytesFree() const
qsizetype bufferSize() const
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void acquire(int n=1)
Tries to acquire n resources guarded by the semaphore.
bool tryAcquire(int n=1)
Tries to acquire n resources guarded by the semaphore and returns true on success.
void release(int n=1)
Releases n resources guarded by the semaphore.
\inmodule QtCore
Definition qtimer.h:20
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
void setTimerType(Qt::TimerType atype)
Definition qtimer.cpp:651
#define this
Definition dialogs.cpp:9
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void *src, void *dest, int len)
State
Definition qaudio.h:29
@ StoppedState
Definition qaudio.h:29
@ SuspendedState
Definition qaudio.h:29
@ ActiveState
Definition qaudio.h:29
Error
Definition qaudio.h:28
@ UnderrunError
Definition qaudio.h:28
@ OpenError
Definition qaudio.h:28
@ NoError
Definition qaudio.h:28
@ IOError
Definition qaudio.h:28
Combined button and popup list for selecting options.
@ PreciseTimer
static QT_BEGIN_NAMESPACE int audioRingBufferSize(int bufferSize, int maxPeriodSize)
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
#define qWarning
Definition qlogging.h:166
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLint GLsizei GLsizei GLenum format
GLenum GLsizei len
static qreal component(const QPointF &point, unsigned int i)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QHostInfo info
[0]