summaryrefslogtreecommitdiffstats
path: root/src/plugins/coreaudio/coreaudiooutput.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/coreaudio/coreaudiooutput.mm')
-rw-r--r--src/plugins/coreaudio/coreaudiooutput.mm752
1 files changed, 0 insertions, 752 deletions
diff --git a/src/plugins/coreaudio/coreaudiooutput.mm b/src/plugins/coreaudio/coreaudiooutput.mm
deleted file mode 100644
index 1138de3e2..000000000
--- a/src/plugins/coreaudio/coreaudiooutput.mm
+++ /dev/null
@@ -1,752 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-#include "coreaudiooutput.h"
-#include "coreaudiosessionmanager.h"
-#include "coreaudiodeviceinfo.h"
-#include "coreaudioutils.h"
-
-#include <QtCore/QDataStream>
-#include <QtCore/QTimer>
-#include <QtCore/QDebug>
-
-#include <AudioUnit/AudioUnit.h>
-#include <AudioToolbox/AudioToolbox.h>
-#if defined(Q_OS_OSX)
-# include <AudioUnit/AudioComponent.h>
-#endif
-
-#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
-# include <QtMultimedia/private/qaudiohelpers_p.h>
-#endif
-
-QT_BEGIN_NAMESPACE
-
-static const int DEFAULT_BUFFER_SIZE = 8 * 1024;
-
-CoreAudioOutputBuffer::CoreAudioOutputBuffer(int bufferSize, int maxPeriodSize, const QAudioFormat &audioFormat)
- : m_deviceError(false)
- , m_maxPeriodSize(maxPeriodSize)
- , m_device(0)
-{
- m_buffer = new CoreAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
- m_bytesPerFrame = (audioFormat.sampleSize() / 8) * audioFormat.channelCount();
- m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.sampleRate();
-
- m_fillTimer = new QTimer(this);
- connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer()));
-}
-
-CoreAudioOutputBuffer::~CoreAudioOutputBuffer()
-{
- delete m_buffer;
-}
-
-qint64 CoreAudioOutputBuffer::readFrames(char *data, qint64 maxFrames)
-{
- bool wecan = true;
- qint64 framesRead = 0;
-
- while (wecan && framesRead < maxFrames) {
- CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame);
-
- if (region.second > 0) {
- // Ensure that we only read whole frames.
- region.second -= region.second % m_bytesPerFrame;
-
- if (region.second > 0) {
- memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second);
- framesRead += region.second / m_bytesPerFrame;
- } else
- wecan = false; // If there is only a partial frame left we should exit.
- }
- else
- wecan = false;
-
- m_buffer->releaseReadRegion(region);
- }
-
- if (framesRead == 0 && m_deviceError)
- framesRead = -1;
-
- return framesRead;
-}
-
-qint64 CoreAudioOutputBuffer::writeBytes(const char *data, qint64 maxSize)
-{
- bool wecan = true;
- qint64 bytesWritten = 0;
-
- maxSize -= maxSize % m_bytesPerFrame;
- while (wecan && bytesWritten < maxSize) {
- CoreAudioRingBuffer::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 CoreAudioOutputBuffer::available() const
-{
- return m_buffer->free();
-}
-
-void CoreAudioOutputBuffer::reset()
-{
- m_buffer->reset();
- m_device = 0;
- m_deviceError = false;
-}
-
-void CoreAudioOutputBuffer::setPrefetchDevice(QIODevice *device)
-{
- if (m_device != device) {
- m_device = device;
- if (m_device != 0)
- fillBuffer();
- }
-}
-
-void CoreAudioOutputBuffer::startFillTimer()
-{
- if (m_device != 0)
- m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime);
-}
-
-void CoreAudioOutputBuffer::stopFillTimer()
-{
- m_fillTimer->stop();
-}
-
-void CoreAudioOutputBuffer::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) {
- CoreAudioRingBuffer::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();
- }
-}
-
-CoreAudioOutputDevice::CoreAudioOutputDevice(CoreAudioOutputBuffer *audioBuffer, QObject *parent)
- : QIODevice(parent)
- , m_audioBuffer(audioBuffer)
-{
- open(QIODevice::WriteOnly | QIODevice::Unbuffered);
-}
-
-qint64 CoreAudioOutputDevice::readData(char *data, qint64 len)
-{
- Q_UNUSED(data);
- Q_UNUSED(len);
-
- return 0;
-}
-
-qint64 CoreAudioOutputDevice::writeData(const char *data, qint64 len)
-{
- return m_audioBuffer->writeBytes(data, len);
-}
-
-CoreAudioOutput::CoreAudioOutput(const QByteArray &device)
- : m_isOpen(false)
- , m_internalBufferSize(DEFAULT_BUFFER_SIZE)
- , m_totalFrames(0)
- , m_audioIO(0)
- , m_audioUnit(0)
- , m_startTime(0)
- , m_audioBuffer(0)
- , m_cachedVolume(1.0)
- , m_volume(1.0)
- , m_pullMode(false)
- , m_errorCode(QAudio::NoError)
- , m_stateCode(QAudio::StoppedState)
-{
-#if defined(Q_OS_OSX)
- quint32 deviceID;
- QDataStream dataStream(device);
- dataStream >> deviceID >> m_device;
- m_audioDeviceId = AudioDeviceID(deviceID);
-#else //iOS
- m_device = device;
-#endif
-
- m_clockFrequency = CoreAudioUtils::frequency() / 1000;
- m_audioDeviceInfo = new CoreAudioDeviceInfo(device, QAudio::AudioOutput);
- m_audioThreadState.storeRelaxed(Stopped);
-
- m_intervalTimer = new QTimer(this);
- m_intervalTimer->setInterval(1000);
- connect(m_intervalTimer, SIGNAL(timeout()), this, SIGNAL(notify()));
-}
-
-CoreAudioOutput::~CoreAudioOutput()
-{
- close();
- delete m_audioDeviceInfo;
-}
-
-void CoreAudioOutput::start(QIODevice *device)
-{
- QIODevice* op = device;
-
- if (!m_audioDeviceInfo->isFormatSupported(m_audioFormat) || !open()) {
- m_stateCode = QAudio::StoppedState;
- m_errorCode = QAudio::OpenError;
- return;
- }
-
- reset();
- m_audioBuffer->reset();
- m_audioBuffer->setPrefetchDevice(op);
-
- if (op == 0) {
- op = m_audioIO;
- m_stateCode = QAudio::IdleState;
- }
- else
- m_stateCode = QAudio::ActiveState;
-
- // Start
- m_pullMode = true;
- m_errorCode = QAudio::NoError;
- m_totalFrames = 0;
- m_startTime = CoreAudioUtils::currentTime();
-
- if (m_stateCode == QAudio::ActiveState)
- audioThreadStart();
-
- emit stateChanged(m_stateCode);
-}
-
-QIODevice *CoreAudioOutput::start()
-{
- if (!m_audioDeviceInfo->isFormatSupported(m_audioFormat) || !open()) {
- m_stateCode = QAudio::StoppedState;
- m_errorCode = QAudio::OpenError;
- return m_audioIO;
- }
-
- reset();
- m_audioBuffer->reset();
- m_audioBuffer->setPrefetchDevice(0);
-
- m_stateCode = QAudio::IdleState;
-
- // Start
- m_pullMode = false;
- m_errorCode = QAudio::NoError;
- m_totalFrames = 0;
- m_startTime = CoreAudioUtils::currentTime();
-
- emit stateChanged(m_stateCode);
-
- return m_audioIO;
-}
-
-void CoreAudioOutput::stop()
-{
- QMutexLocker lock(&m_mutex);
- if (m_stateCode != QAudio::StoppedState) {
- audioThreadDrain();
-
- m_stateCode = QAudio::StoppedState;
- m_errorCode = QAudio::NoError;
- emit stateChanged(m_stateCode);
- }
-}
-
-void CoreAudioOutput::reset()
-{
- QMutexLocker lock(&m_mutex);
- if (m_stateCode != QAudio::StoppedState) {
- audioThreadStop();
-
- m_stateCode = QAudio::StoppedState;
- m_errorCode = QAudio::NoError;
- emit stateChanged(m_stateCode);
- }
-}
-
-void CoreAudioOutput::suspend()
-{
- QMutexLocker lock(&m_mutex);
- if (m_stateCode == QAudio::ActiveState || m_stateCode == QAudio::IdleState) {
- audioThreadStop();
-
- m_stateCode = QAudio::SuspendedState;
- m_errorCode = QAudio::NoError;
- emit stateChanged(m_stateCode);
- }
-}
-
-void CoreAudioOutput::resume()
-{
- QMutexLocker lock(&m_mutex);
- if (m_stateCode == QAudio::SuspendedState) {
- audioThreadStart();
-
- m_stateCode = m_pullMode ? QAudio::ActiveState : QAudio::IdleState;
- m_errorCode = QAudio::NoError;
- emit stateChanged(m_stateCode);
- }
-}
-
-int CoreAudioOutput::bytesFree() const
-{
- return m_audioBuffer->available();
-}
-
-int CoreAudioOutput::periodSize() const
-{
- return m_periodSizeBytes;
-}
-
-void CoreAudioOutput::setBufferSize(int value)
-{
- if (m_stateCode == QAudio::StoppedState)
- m_internalBufferSize = value;
-}
-
-int CoreAudioOutput::bufferSize() const
-{
- return m_internalBufferSize;
-}
-
-void CoreAudioOutput::setNotifyInterval(int milliSeconds)
-{
- if (m_intervalTimer->interval() == milliSeconds)
- return;
-
- if (milliSeconds <= 0)
- milliSeconds = 0;
-
- m_intervalTimer->setInterval(milliSeconds);
-}
-
-int CoreAudioOutput::notifyInterval() const
-{
- return m_intervalTimer->interval();
-}
-
-qint64 CoreAudioOutput::processedUSecs() const
-{
- return m_totalFrames * 1000000 / m_audioFormat.sampleRate();
-}
-
-qint64 CoreAudioOutput::elapsedUSecs() const
-{
- if (m_stateCode == QAudio::StoppedState)
- return 0;
-
- return (CoreAudioUtils::currentTime() - m_startTime) / (m_clockFrequency / 1000);
-}
-
-QAudio::Error CoreAudioOutput::error() const
-{
- return m_errorCode;
-}
-
-QAudio::State CoreAudioOutput::state() const
-{
- return m_stateCode;
-}
-
-void CoreAudioOutput::setFormat(const QAudioFormat &format)
-{
- if (m_stateCode == QAudio::StoppedState)
- m_audioFormat = format;
-}
-
-QAudioFormat CoreAudioOutput::format() const
-{
- return m_audioFormat;
-}
-
-void CoreAudioOutput::setVolume(qreal volume)
-{
- m_cachedVolume = qBound(qreal(0.0), volume, qreal(1.0));
- if (!m_isOpen)
- return;
-
-#if defined(Q_OS_OSX)
- //on OS X the volume can be set directly on the AudioUnit
- if (AudioUnitSetParameter(m_audioUnit,
- kHALOutputParam_Volume,
- kAudioUnitScope_Global,
- 0 /* bus */,
- m_cachedVolume,
- 0) == noErr)
- m_volume = m_cachedVolume;
-#endif
-}
-
-qreal CoreAudioOutput::volume() const
-{
- return m_cachedVolume;
-}
-
-void CoreAudioOutput::setCategory(const QString &category)
-{
- Q_UNUSED(category);
-}
-
-QString CoreAudioOutput::category() const
-{
- return QString();
-}
-
-void CoreAudioOutput::deviceStopped()
-{
- m_intervalTimer->stop();
- emit stateChanged(m_stateCode);
-}
-
-void CoreAudioOutput::inputReady()
-{
- QMutexLocker lock(&m_mutex);
- if (m_stateCode == QAudio::IdleState) {
- audioThreadStart();
-
- m_stateCode = QAudio::ActiveState;
- m_errorCode = QAudio::NoError;
-
- emit stateChanged(m_stateCode);
- }
-}
-
-OSStatus CoreAudioOutput::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);
-
- CoreAudioOutput* d = static_cast<CoreAudioOutput*>(inRefCon);
-
- const int threadState = d->m_audioThreadState.fetchAndAddAcquire(0);
- if (threadState == Stopped) {
- ioData->mBuffers[0].mDataByteSize = 0;
- d->audioDeviceStop();
- }
- else {
- const UInt32 bytesPerFrame = d->m_streamFormat.mBytesPerFrame;
- qint64 framesRead;
-
- framesRead = d->m_audioBuffer->readFrames((char*)ioData->mBuffers[0].mData,
- ioData->mBuffers[0].mDataByteSize / bytesPerFrame);
-
- if (framesRead > 0) {
- ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame;
- d->m_totalFrames += framesRead;
-
-#if defined(Q_OS_MACOS)
- // If playback is already stopped.
- if (threadState != Running) {
- qreal oldVolume = d->m_cachedVolume;
- // Decrease volume smoothly.
- d->setVolume(d->m_volume / 2);
- d->m_cachedVolume = oldVolume;
- }
-#elif defined(Q_OS_IOS) || defined(Q_OS_TVOS)
- // on iOS we have to adjust the sound volume ourselves
- if (!qFuzzyCompare(d->m_cachedVolume, qreal(1.0f))) {
- QAudioHelperInternal::qMultiplySamples(d->m_cachedVolume,
- d->m_audioFormat,
- ioData->mBuffers[0].mData, /* input */
- ioData->mBuffers[0].mData, /* output */
- ioData->mBuffers[0].mDataByteSize);
- }
-#endif
-
- }
- else {
- ioData->mBuffers[0].mDataByteSize = 0;
- if (framesRead == 0) {
- if (threadState == Draining)
- d->audioDeviceStop();
- else
- d->audioDeviceIdle();
- }
- else
- d->audioDeviceError();
- }
- }
-
- return noErr;
-}
-
-bool CoreAudioOutput::open()
-{
-#if defined(Q_OS_IOS)
- // Set default category to Ambient (implies MixWithOthers). This makes sure audio stops playing
- // if the screen is locked or if the Silent switch is toggled.
- CoreAudioSessionManager::instance().setCategory(CoreAudioSessionManager::Ambient, CoreAudioSessionManager::None);
- CoreAudioSessionManager::instance().setActive(true);
-#endif
-
- if (m_errorCode != QAudio::NoError)
- return false;
-
- if (m_isOpen) {
- setVolume(m_cachedVolume);
- return true;
- }
-
- AudioComponentDescription componentDescription;
- componentDescription.componentType = kAudioUnitType_Output;
-#if defined(Q_OS_OSX)
- componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
-#else
- componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
-#endif
- componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
- componentDescription.componentFlags = 0;
- componentDescription.componentFlagsMask = 0;
-
- AudioComponent component = AudioComponentFindNext(0, &componentDescription);
- if (component == 0) {
- qWarning() << "QAudioOutput: Failed to find Output component";
- return false;
- }
-
- if (AudioComponentInstanceNew(component, &m_audioUnit) != noErr) {
- qWarning() << "QAudioOutput: Unable to Open Output Component";
- return false;
- }
-
- // register callback
- AURenderCallbackStruct callback;
- callback.inputProc = renderCallback;
- callback.inputProcRefCon = this;
-
- if (AudioUnitSetProperty(m_audioUnit,
- kAudioUnitProperty_SetRenderCallback,
- kAudioUnitScope_Global,
- 0,
- &callback,
- sizeof(callback)) != noErr) {
- qWarning() << "QAudioOutput: Failed to set AudioUnit callback";
- return false;
- }
-
-#if defined(Q_OS_OSX)
- //Set Audio Device
- if (AudioUnitSetProperty(m_audioUnit,
- kAudioOutputUnitProperty_CurrentDevice,
- kAudioUnitScope_Global,
- 0,
- &m_audioDeviceId,
- sizeof(m_audioDeviceId)) != noErr) {
- qWarning() << "QAudioOutput: Unable to use configured device";
- return false;
- }
-#endif
-
- // Set stream format
- m_streamFormat = CoreAudioUtils::toAudioStreamBasicDescription(m_audioFormat);
-
- UInt32 size = sizeof(m_streamFormat);
- if (AudioUnitSetProperty(m_audioUnit,
- kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input,
- 0,
- &m_streamFormat,
- size) != noErr) {
- qWarning() << "QAudioOutput: Unable to Set Stream information";
- return false;
- }
-
- // Allocate buffer
- UInt32 numberOfFrames = 0;
-#if defined(Q_OS_OSX)
- size = sizeof(UInt32);
- if (AudioUnitGetProperty(m_audioUnit,
- kAudioDevicePropertyBufferFrameSize,
- kAudioUnitScope_Global,
- 0,
- &numberOfFrames,
- &size) != noErr) {
- qWarning() << "QAudioInput: Failed to get audio period size";
- return false;
- }
-#else //iOS
- Float32 bufferSize = CoreAudioSessionManager::instance().currentIOBufferDuration();
- bufferSize *= m_streamFormat.mSampleRate;
- numberOfFrames = bufferSize;
-#endif
-
- m_periodSizeBytes = numberOfFrames * m_streamFormat.mBytesPerFrame;
- if (m_internalBufferSize < m_periodSizeBytes * 2)
- m_internalBufferSize = m_periodSizeBytes * 2;
- else
- m_internalBufferSize -= m_internalBufferSize % m_streamFormat.mBytesPerFrame;
-
- m_audioBuffer = new CoreAudioOutputBuffer(m_internalBufferSize, m_periodSizeBytes, m_audioFormat);
- connect(m_audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); //Pull
-
- m_audioIO = new CoreAudioOutputDevice(m_audioBuffer, this);
-
- //Init
- if (AudioUnitInitialize(m_audioUnit)) {
- qWarning() << "QAudioOutput: Failed to initialize AudioUnit";
- return false;
- }
-
- m_isOpen = true;
-
- setVolume(m_cachedVolume);
-
- return true;
-}
-
-void CoreAudioOutput::close()
-{
- if (m_audioUnit != 0) {
- AudioOutputUnitStop(m_audioUnit);
- AudioUnitUninitialize(m_audioUnit);
- AudioComponentInstanceDispose(m_audioUnit);
- }
-
- delete m_audioBuffer;
-}
-
-void CoreAudioOutput::audioThreadStart()
-{
- startTimers();
- m_audioThreadState.storeRelaxed(Running);
- AudioOutputUnitStart(m_audioUnit);
-}
-
-void CoreAudioOutput::audioThreadStop()
-{
- stopTimers();
- if (m_audioThreadState.testAndSetAcquire(Running, Stopped))
- m_threadFinished.wait(&m_mutex, 500);
-}
-
-void CoreAudioOutput::audioThreadDrain()
-{
- stopTimers();
- if (m_audioThreadState.testAndSetAcquire(Running, Draining))
- m_threadFinished.wait(&m_mutex, 500);
-}
-
-void CoreAudioOutput::audioDeviceStop()
-{
- AudioOutputUnitStop(m_audioUnit);
- m_audioThreadState.storeRelaxed(Stopped);
- m_threadFinished.wakeOne();
-}
-
-void CoreAudioOutput::audioDeviceIdle()
-{
- if (m_stateCode == QAudio::ActiveState) {
- QMutexLocker lock(&m_mutex);
- audioDeviceStop();
-
- m_errorCode = QAudio::UnderrunError;
- m_stateCode = QAudio::IdleState;
- QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
- }
-}
-
-void CoreAudioOutput::audioDeviceError()
-{
- if (m_stateCode == QAudio::ActiveState) {
- QMutexLocker lock(&m_mutex);
- audioDeviceStop();
-
- m_errorCode = QAudio::IOError;
- m_stateCode = QAudio::StoppedState;
- QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
- }
-}
-
-void CoreAudioOutput::startTimers()
-{
- m_audioBuffer->startFillTimer();
- if (m_intervalTimer->interval() > 0)
- m_intervalTimer->start();
-}
-
-void CoreAudioOutput::stopTimers()
-{
- m_audioBuffer->stopFillTimer();
- m_intervalTimer->stop();
-}
-
-QT_END_NAMESPACE
-
-#include "moc_coreaudiooutput.cpp"