1
/****************************************************************************
3
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
7
** This file is part of the QtMultimedia module of the Qt Toolkit.
9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file. Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights. These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
40
****************************************************************************/
46
// This file is not part of the Qt API. It exists for the convenience
47
// of other Qt classes. This header file may change from version to
48
// version without notice, or even be removed.
53
#include <CoreServices/CoreServices.h>
54
#include <CoreAudio/CoreAudio.h>
55
#include <AudioUnit/AudioUnit.h>
56
#include <AudioToolbox/AudioToolbox.h>
58
#include <QtCore/qendian.h>
59
#include <QtCore/qbuffer.h>
60
#include <QtCore/qtimer.h>
61
#include <QtCore/qdebug.h>
63
#include <QtMultimedia/qaudiodeviceinfo.h>
64
#include <QtMultimedia/qaudiooutput.h>
66
#include "qaudio_mac_p.h"
67
#include "qaudiooutput_mac_p.h"
76
static const int default_buffer_size = 8 * 1024;
79
class QAudioOutputBuffer : public QObject
84
QAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat):
86
m_maxPeriodSize(maxPeriodSize),
89
m_buffer = new QAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
90
m_bytesPerFrame = (audioFormat.sampleSize() / 8) * audioFormat.channels();
91
m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.frequency();
93
m_fillTimer = new QTimer(this);
94
connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer()));
102
qint64 readFrames(char* data, qint64 maxFrames)
105
qint64 framesRead = 0;
107
while (wecan && framesRead < maxFrames) {
108
QAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame);
110
if (region.second > 0) {
111
region.second -= region.second % m_bytesPerFrame;
112
memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second);
113
framesRead += region.second / m_bytesPerFrame;
118
m_buffer->releaseReadRegion(region);
121
if (framesRead == 0 && m_deviceError)
127
qint64 writeBytes(const char* data, qint64 maxSize)
130
qint64 bytesWritten = 0;
132
maxSize -= maxSize % m_bytesPerFrame;
133
while (wecan && bytesWritten < maxSize) {
134
QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(maxSize - bytesWritten);
136
if (region.second > 0) {
137
memcpy(region.first, data + bytesWritten, region.second);
138
bytesWritten += region.second;
143
m_buffer->releaseWriteRegion(region);
146
if (bytesWritten > 0)
152
int available() const
154
return m_buffer->free();
160
m_deviceError = false;
163
void setPrefetchDevice(QIODevice* device)
165
if (m_device != device) {
172
void startFillTimer()
175
m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime);
189
const int free = m_buffer->free();
190
const int writeSize = free - (free % m_maxPeriodSize);
196
while (!m_deviceError && wecan && filled < writeSize) {
197
QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled);
199
if (region.second > 0) {
200
region.second = m_device->read(region.first, region.second);
201
if (region.second > 0)
202
filled += region.second;
203
else if (region.second == 0)
205
else if (region.second < 0) {
208
m_deviceError = true;
214
m_buffer->releaseWriteRegion(region);
229
QAudioRingBuffer* m_buffer;
235
class MacOutputDevice : public QIODevice
240
MacOutputDevice(QAudioOutputBuffer* audioBuffer, QObject* parent):
242
m_audioBuffer(audioBuffer)
244
open(QIODevice::WriteOnly | QIODevice::Unbuffered);
247
qint64 readData(char* data, qint64 len)
255
qint64 writeData(const char* data, qint64 len)
257
return m_audioBuffer->writeBytes(data, len);
260
bool isSequential() const
266
QAudioOutputBuffer* m_audioBuffer;
270
QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray& device, const QAudioFormat& format):
273
QDataStream ds(device);
278
if (QAudio::Mode(mode) == QAudio::AudioInput)
279
errorCode = QAudio::OpenError;
282
audioDeviceId = AudioDeviceID(did);
288
internalBufferSize = default_buffer_size;
289
clockFrequency = AudioGetHostClockFrequency() / 1000;
290
errorCode = QAudio::NoError;
291
stateCode = QAudio::StopState;
292
audioThreadState = Stopped;
294
intervalTimer = new QTimer(this);
295
intervalTimer->setInterval(1000);
296
connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify()));
300
QAudioOutputPrivate::~QAudioOutputPrivate()
305
bool QAudioOutputPrivate::open()
307
if (errorCode != QAudio::NoError)
313
ComponentDescription cd;
314
cd.componentType = kAudioUnitType_Output;
315
cd.componentSubType = kAudioUnitSubType_HALOutput;
316
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
317
cd.componentFlags = 0;
318
cd.componentFlagsMask = 0;
321
Component cp = FindNextComponent(NULL, &cd);
323
qWarning() << "QAudioOutput: Failed to find HAL Output component";
327
if (OpenAComponent(cp, &audioUnit) != noErr) {
328
qWarning() << "QAudioOutput: Unable to Open Output Component";
333
AURenderCallbackStruct cb;
334
cb.inputProc = renderCallback;
335
cb.inputProcRefCon = this;
337
if (AudioUnitSetProperty(audioUnit,
338
kAudioUnitProperty_SetRenderCallback,
339
kAudioUnitScope_Global,
342
sizeof(cb)) != noErr) {
343
qWarning() << "QAudioOutput: Failed to set AudioUnit callback";
348
if (AudioUnitSetProperty(audioUnit,
349
kAudioOutputUnitProperty_CurrentDevice,
350
kAudioUnitScope_Global,
353
sizeof(audioDeviceId)) != noErr) {
354
qWarning() << "QAudioOutput: Unable to use configured device";
359
streamFormat = toAudioStreamBasicDescription(audioFormat);
361
UInt32 size = sizeof(deviceFormat);
362
if (AudioUnitGetProperty(audioUnit,
363
kAudioUnitProperty_StreamFormat,
364
kAudioUnitScope_Input,
368
qWarning() << "QAudioOutput: Unable to retrieve device format";
372
if (AudioUnitSetProperty(audioUnit,
373
kAudioUnitProperty_StreamFormat,
374
kAudioUnitScope_Input,
377
sizeof(streamFormat)) != noErr) {
378
qWarning() << "QAudioOutput: Unable to Set Stream information";
383
UInt32 numberOfFrames = 0;
384
size = sizeof(UInt32);
385
if (AudioUnitGetProperty(audioUnit,
386
kAudioDevicePropertyBufferFrameSize,
387
kAudioUnitScope_Global,
391
qWarning() << "QAudioInput: Failed to get audio period size";
395
periodSizeBytes = (numberOfFrames * streamFormat.mSampleRate / deviceFormat.mSampleRate) *
396
streamFormat.mBytesPerFrame;
397
if (internalBufferSize < periodSizeBytes * 2)
398
internalBufferSize = periodSizeBytes * 2;
400
internalBufferSize -= internalBufferSize % streamFormat.mBytesPerFrame;
402
audioBuffer = new QAudioOutputBuffer(internalBufferSize, periodSizeBytes, audioFormat);
403
connect(audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); // Pull
405
audioIO = new MacOutputDevice(audioBuffer, this);
408
if (AudioUnitInitialize(audioUnit)) {
409
qWarning() << "QAudioOutput: Failed to initialize AudioUnit";
418
void QAudioOutputPrivate::close()
420
if (audioUnit != 0) {
421
AudioOutputUnitStop(audioUnit);
422
AudioUnitUninitialize(audioUnit);
423
CloseComponent(audioUnit);
429
QAudioFormat QAudioOutputPrivate::format() const
434
QIODevice* QAudioOutputPrivate::start(QIODevice* device)
436
QIODevice* op = device;
439
stateCode = QAudio::StopState;
440
errorCode = QAudio::OpenError;
445
audioBuffer->reset();
446
audioBuffer->setPrefetchDevice(op);
450
stateCode = QAudio::IdleState;
453
stateCode = QAudio::ActiveState;
456
errorCode = QAudio::NoError;
458
startTime = AudioGetCurrentHostTime();
460
if (stateCode == QAudio::ActiveState)
463
emit stateChanged(stateCode);
468
void QAudioOutputPrivate::stop()
470
QMutexLocker lock(&mutex);
471
if (stateCode != QAudio::StopState) {
474
stateCode = QAudio::StopState;
475
errorCode = QAudio::NoError;
476
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
480
void QAudioOutputPrivate::reset()
482
QMutexLocker lock(&mutex);
483
if (stateCode != QAudio::StopState) {
486
stateCode = QAudio::StopState;
487
errorCode = QAudio::NoError;
488
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
492
void QAudioOutputPrivate::suspend()
494
QMutexLocker lock(&mutex);
495
if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) {
498
stateCode = QAudio::SuspendState;
499
errorCode = QAudio::NoError;
500
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
504
void QAudioOutputPrivate::resume()
506
QMutexLocker lock(&mutex);
507
if (stateCode == QAudio::SuspendState) {
510
stateCode = QAudio::ActiveState;
511
errorCode = QAudio::NoError;
512
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
516
int QAudioOutputPrivate::bytesFree() const
518
return audioBuffer->available();
521
int QAudioOutputPrivate::periodSize() const
523
return periodSizeBytes;
526
void QAudioOutputPrivate::setBufferSize(int bs)
528
if (stateCode == QAudio::StopState)
529
internalBufferSize = bs;
532
int QAudioOutputPrivate::bufferSize() const
534
return internalBufferSize;
537
void QAudioOutputPrivate::setNotifyInterval(int milliSeconds)
539
intervalTimer->setInterval(milliSeconds);
542
int QAudioOutputPrivate::notifyInterval() const
544
return intervalTimer->interval();
547
qint64 QAudioOutputPrivate::totalTime() const
549
return totalFrames * 1000000 / audioFormat.frequency();
552
qint64 QAudioOutputPrivate::clock() const
554
if (stateCode == QAudio::StopState)
557
return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000);
560
QAudio::Error QAudioOutputPrivate::error() const
565
QAudio::State QAudioOutputPrivate::state() const
570
void QAudioOutputPrivate::audioThreadStart()
573
audioThreadState = Running;
574
AudioOutputUnitStart(audioUnit);
577
void QAudioOutputPrivate::audioThreadStop()
580
if (audioThreadState.testAndSetAcquire(Running, Stopped))
581
threadFinished.wait(&mutex);
584
void QAudioOutputPrivate::audioThreadDrain()
587
if (audioThreadState.testAndSetAcquire(Running, Draining))
588
threadFinished.wait(&mutex);
591
void QAudioOutputPrivate::audioDeviceStop()
593
AudioOutputUnitStop(audioUnit);
594
audioThreadState = Stopped;
595
threadFinished.wakeOne();
598
void QAudioOutputPrivate::audioDeviceIdle()
600
QMutexLocker lock(&mutex);
601
if (stateCode == QAudio::ActiveState) {
604
errorCode = QAudio::UnderrunError;
605
stateCode = QAudio::IdleState;
606
QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
610
void QAudioOutputPrivate::audioDeviceError()
612
QMutexLocker lock(&mutex);
613
if (stateCode == QAudio::ActiveState) {
616
errorCode = QAudio::IOError;
617
stateCode = QAudio::StopState;
618
QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
622
void QAudioOutputPrivate::startTimers()
624
audioBuffer->startFillTimer();
625
intervalTimer->start();
628
void QAudioOutputPrivate::stopTimers()
630
audioBuffer->stopFillTimer();
631
intervalTimer->stop();
635
void QAudioOutputPrivate::deviceStopped()
637
intervalTimer->stop();
638
emit stateChanged(stateCode);
641
void QAudioOutputPrivate::inputReady()
643
QMutexLocker lock(&mutex);
644
if (stateCode == QAudio::IdleState) {
647
stateCode = QAudio::ActiveState;
648
errorCode = QAudio::NoError;
650
QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode));
655
OSStatus QAudioOutputPrivate::renderCallback(void* inRefCon,
656
AudioUnitRenderActionFlags* ioActionFlags,
657
const AudioTimeStamp* inTimeStamp,
659
UInt32 inNumberFrames,
660
AudioBufferList* ioData)
662
Q_UNUSED(ioActionFlags)
663
Q_UNUSED(inTimeStamp)
664
Q_UNUSED(inBusNumber)
665
Q_UNUSED(inNumberFrames)
667
QAudioOutputPrivate* d = static_cast<QAudioOutputPrivate*>(inRefCon);
669
const int threadState = d->audioThreadState.fetchAndAddAcquire(0);
670
if (threadState == Stopped) {
671
ioData->mBuffers[0].mDataByteSize = 0;
672
d->audioDeviceStop();
675
const UInt32 bytesPerFrame = d->streamFormat.mBytesPerFrame;
678
framesRead = d->audioBuffer->readFrames((char*)ioData->mBuffers[0].mData,
679
ioData->mBuffers[0].mDataByteSize / bytesPerFrame);
681
if (framesRead > 0) {
682
ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame;
683
d->totalFrames += framesRead;
686
ioData->mBuffers[0].mDataByteSize = 0;
687
if (framesRead == 0) {
688
if (threadState == Draining)
689
d->audioDeviceStop();
691
d->audioDeviceIdle();
694
d->audioDeviceError();
704
#include "qaudiooutput_mac_p.moc"