/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtMultimedia module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists for the convenience // of other Qt classes. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include #include #include #include #include #include #include #include "qaudio_mac_p.h" #include "qaudiooutput_mac_p.h" #include "qaudiodeviceinfo_mac_p.h" QT_BEGIN_NAMESPACE namespace QtMultimediaInternal { static const int default_buffer_size = 8 * 1024; class QAudioOutputBuffer : public QObject { Q_OBJECT public: QAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat): m_deviceError(false), m_maxPeriodSize(maxPeriodSize), m_device(0) { m_buffer = new QAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize))); m_bytesPerFrame = (audioFormat.sampleSize() / 8) * audioFormat.channels(); m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.frequency(); m_fillTimer = new QTimer(this); connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer())); } ~QAudioOutputBuffer() { delete m_buffer; } qint64 readFrames(char* data, qint64 maxFrames) { bool wecan = true; qint64 framesRead = 0; while (wecan && framesRead < maxFrames) { QAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame); if (region.second > 0) { region.second -= region.second % m_bytesPerFrame; memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second); framesRead += region.second / m_bytesPerFrame; } else wecan = false; m_buffer->releaseReadRegion(region); } if (framesRead == 0 && m_deviceError) framesRead = -1; return framesRead; } qint64 writeBytes(const char* data, qint64 maxSize) { bool wecan = true; qint64 bytesWritten = 0; maxSize -= maxSize % m_bytesPerFrame; while (wecan && bytesWritten < maxSize) { QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(maxSize - bytesWritten); if (region.second > 0) { memcpy(region.first, data + bytesWritten, region.second); bytesWritten += region.second; } else wecan = false; m_buffer->releaseWriteRegion(region); } if (bytesWritten > 0) emit readyRead(); return bytesWritten; } int available() const { return m_buffer->free(); } void reset() { m_buffer->reset(); m_deviceError = false; } void setPrefetchDevice(QIODevice* device) { if (m_device != device) { m_device = device; if (m_device != 0) fillBuffer(); } } void startFillTimer() { if (m_device != 0) m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime); } void stopFillTimer() { m_fillTimer->stop(); } signals: void readyRead(); private slots: void fillBuffer() { const int free = m_buffer->free(); const int writeSize = free - (free % m_maxPeriodSize); if (writeSize > 0) { bool wecan = true; int filled = 0; while (!m_deviceError && wecan && filled < writeSize) { QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled); if (region.second > 0) { region.second = m_device->read(region.first, region.second); if (region.second > 0) filled += region.second; else if (region.second == 0) wecan = false; else if (region.second < 0) { m_fillTimer->stop(); region.second = 0; m_deviceError = true; } } else wecan = false; m_buffer->releaseWriteRegion(region); } if (filled > 0) emit readyRead(); } } private: bool m_deviceError; int m_maxPeriodSize; int m_bytesPerFrame; int m_periodTime; QIODevice* m_device; QTimer* m_fillTimer; QAudioRingBuffer* m_buffer; }; } class MacOutputDevice : public QIODevice { Q_OBJECT public: MacOutputDevice(QtMultimediaInternal::QAudioOutputBuffer* audioBuffer, QObject* parent): QIODevice(parent), m_audioBuffer(audioBuffer) { open(QIODevice::WriteOnly | QIODevice::Unbuffered); } qint64 readData(char* data, qint64 len) { Q_UNUSED(data); Q_UNUSED(len); return 0; } qint64 writeData(const char* data, qint64 len) { return m_audioBuffer->writeBytes(data, len); } bool isSequential() const { return true; } private: QtMultimediaInternal::QAudioOutputBuffer* m_audioBuffer; }; QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray& device, const QAudioFormat& format): audioFormat(format) { QDataStream ds(device); quint32 did, mode; ds >> did >> mode; if (QAudio::Mode(mode) == QAudio::AudioInput) errorCode = QAudio::OpenError; else { audioDeviceInfo = new QAudioDeviceInfoInternal(device, QAudio::AudioOutput); isOpen = false; audioDeviceId = AudioDeviceID(did); audioUnit = 0; audioIO = 0; startTime = 0; totalFrames = 0; audioBuffer = 0; internalBufferSize = QtMultimediaInternal::default_buffer_size; clockFrequency = AudioGetHostClockFrequency() / 1000; errorCode = QAudio::NoError; stateCode = QAudio::StoppedState; audioThreadState = Stopped; intervalTimer = new QTimer(this); intervalTimer->setInterval(1000); connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify())); } } QAudioOutputPrivate::~QAudioOutputPrivate() { delete audioDeviceInfo; close(); } bool QAudioOutputPrivate::open() { if (errorCode != QAudio::NoError) return false; if (isOpen) return true; ComponentDescription cd; cd.componentType = kAudioUnitType_Output; cd.componentSubType = kAudioUnitSubType_HALOutput; cd.componentManufacturer = kAudioUnitManufacturer_Apple; cd.componentFlags = 0; cd.componentFlagsMask = 0; // Open Component cp = FindNextComponent(NULL, &cd); if (cp == 0) { qWarning() << "QAudioOutput: Failed to find HAL Output component"; return false; } if (OpenAComponent(cp, &audioUnit) != noErr) { qWarning() << "QAudioOutput: Unable to Open Output Component"; return false; } // register callback AURenderCallbackStruct cb; cb.inputProc = renderCallback; cb.inputProcRefCon = this; if (AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &cb, sizeof(cb)) != noErr) { qWarning() << "QAudioOutput: Failed to set AudioUnit callback"; return false; } // Set Audio Device if (AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &audioDeviceId, sizeof(audioDeviceId)) != noErr) { qWarning() << "QAudioOutput: Unable to use configured device"; return false; } // Set stream format streamFormat = toAudioStreamBasicDescription(audioFormat); UInt32 size = sizeof(streamFormat); if (AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(streamFormat)) != noErr) { qWarning() << "QAudioOutput: Unable to Set Stream information"; return false; } // Allocate buffer UInt32 numberOfFrames = 0; size = sizeof(UInt32); if (AudioUnitGetProperty(audioUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &numberOfFrames, &size) != noErr) { qWarning() << "QAudioInput: Failed to get audio period size"; return false; } periodSizeBytes = numberOfFrames * streamFormat.mBytesPerFrame; if (internalBufferSize < periodSizeBytes * 2) internalBufferSize = periodSizeBytes * 2; else internalBufferSize -= internalBufferSize % streamFormat.mBytesPerFrame; audioBuffer = new QtMultimediaInternal::QAudioOutputBuffer(internalBufferSize, periodSizeBytes, audioFormat); connect(audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); // Pull audioIO = new MacOutputDevice(audioBuffer, this); // Init if (AudioUnitInitialize(audioUnit)) { qWarning() << "QAudioOutput: Failed to initialize AudioUnit"; return false; } isOpen = true; return true; } void QAudioOutputPrivate::close() { if (audioUnit != 0) { AudioOutputUnitStop(audioUnit); AudioUnitUninitialize(audioUnit); CloseComponent(audioUnit); } delete audioBuffer; } QAudioFormat QAudioOutputPrivate::format() const { return audioFormat; } QIODevice* QAudioOutputPrivate::start(QIODevice* device) { QIODevice* op = device; if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) { stateCode = QAudio::StoppedState; errorCode = QAudio::OpenError; return audioIO; } reset(); audioBuffer->reset(); audioBuffer->setPrefetchDevice(op); if (op == 0) { op = audioIO; stateCode = QAudio::IdleState; } else stateCode = QAudio::ActiveState; // Start errorCode = QAudio::NoError; totalFrames = 0; startTime = AudioGetCurrentHostTime(); if (stateCode == QAudio::ActiveState) audioThreadStart(); emit stateChanged(stateCode); return op; } void QAudioOutputPrivate::stop() { QMutexLocker lock(&mutex); if (stateCode != QAudio::StoppedState) { audioThreadDrain(); stateCode = QAudio::StoppedState; errorCode = QAudio::NoError; QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); } } void QAudioOutputPrivate::reset() { QMutexLocker lock(&mutex); if (stateCode != QAudio::StoppedState) { audioThreadStop(); stateCode = QAudio::StoppedState; errorCode = QAudio::NoError; QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); } } void QAudioOutputPrivate::suspend() { QMutexLocker lock(&mutex); if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) { audioThreadStop(); stateCode = QAudio::SuspendedState; errorCode = QAudio::NoError; QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); } } void QAudioOutputPrivate::resume() { QMutexLocker lock(&mutex); if (stateCode == QAudio::SuspendedState) { audioThreadStart(); stateCode = QAudio::ActiveState; errorCode = QAudio::NoError; QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); } } int QAudioOutputPrivate::bytesFree() const { return audioBuffer->available(); } int QAudioOutputPrivate::periodSize() const { return periodSizeBytes; } void QAudioOutputPrivate::setBufferSize(int bs) { if (stateCode == QAudio::StoppedState) internalBufferSize = bs; } int QAudioOutputPrivate::bufferSize() const { return internalBufferSize; } void QAudioOutputPrivate::setNotifyInterval(int milliSeconds) { if (intervalTimer->interval() == milliSeconds) return; if (milliSeconds <= 0) milliSeconds = 0; intervalTimer->setInterval(milliSeconds); } int QAudioOutputPrivate::notifyInterval() const { return intervalTimer->interval(); } qint64 QAudioOutputPrivate::processedUSecs() const { return totalFrames * 1000000 / audioFormat.frequency(); } qint64 QAudioOutputPrivate::elapsedUSecs() const { if (stateCode == QAudio::StoppedState) return 0; return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000); } QAudio::Error QAudioOutputPrivate::error() const { return errorCode; } QAudio::State QAudioOutputPrivate::state() const { return stateCode; } void QAudioOutputPrivate::audioThreadStart() { startTimers(); audioThreadState = Running; AudioOutputUnitStart(audioUnit); } void QAudioOutputPrivate::audioThreadStop() { stopTimers(); if (audioThreadState.testAndSetAcquire(Running, Stopped)) threadFinished.wait(&mutex); } void QAudioOutputPrivate::audioThreadDrain() { stopTimers(); if (audioThreadState.testAndSetAcquire(Running, Draining)) threadFinished.wait(&mutex); } void QAudioOutputPrivate::audioDeviceStop() { AudioOutputUnitStop(audioUnit); audioThreadState = Stopped; threadFinished.wakeOne(); } void QAudioOutputPrivate::audioDeviceIdle() { QMutexLocker lock(&mutex); if (stateCode == QAudio::ActiveState) { audioDeviceStop(); errorCode = QAudio::UnderrunError; stateCode = QAudio::IdleState; QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); } } void QAudioOutputPrivate::audioDeviceError() { QMutexLocker lock(&mutex); if (stateCode == QAudio::ActiveState) { audioDeviceStop(); errorCode = QAudio::IOError; stateCode = QAudio::StoppedState; QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); } } void QAudioOutputPrivate::startTimers() { audioBuffer->startFillTimer(); if (intervalTimer->interval() > 0) intervalTimer->start(); } void QAudioOutputPrivate::stopTimers() { audioBuffer->stopFillTimer(); intervalTimer->stop(); } void QAudioOutputPrivate::deviceStopped() { intervalTimer->stop(); emit stateChanged(stateCode); } void QAudioOutputPrivate::inputReady() { QMutexLocker lock(&mutex); if (stateCode == QAudio::IdleState) { audioThreadStart(); stateCode = QAudio::ActiveState; errorCode = QAudio::NoError; QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); } } OSStatus QAudioOutputPrivate::renderCallback(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) { Q_UNUSED(ioActionFlags) Q_UNUSED(inTimeStamp) Q_UNUSED(inBusNumber) Q_UNUSED(inNumberFrames) QAudioOutputPrivate* d = static_cast(inRefCon); const int threadState = d->audioThreadState.fetchAndAddAcquire(0); if (threadState == Stopped) { ioData->mBuffers[0].mDataByteSize = 0; d->audioDeviceStop(); } else { const UInt32 bytesPerFrame = d->streamFormat.mBytesPerFrame; qint64 framesRead; framesRead = d->audioBuffer->readFrames((char*)ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize / bytesPerFrame); if (framesRead > 0) { ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame; d->totalFrames += framesRead; } else { ioData->mBuffers[0].mDataByteSize = 0; if (framesRead == 0) { if (threadState == Draining) d->audioDeviceStop(); else d->audioDeviceIdle(); } else d->audioDeviceError(); } } return noErr; } QT_END_NAMESPACE #include "qaudiooutput_mac_p.moc"