From b357c55f2dfe44e5c2a2524b93478aecf668ca0a Mon Sep 17 00:00:00 2001 From: Andy Nichols Date: Fri, 8 Mar 2013 15:18:36 +0100 Subject: CoreAudio: Create an audio plugin supporting iOS and OS X This removes the Mac audio backend that was hardcoded into QtMultimedia and adds a new audio plugin using the CoreAudio API. Change-Id: Ib15291825f9452a3763e0eeb281d952deb0bad3d Reviewed-by: Richard Moe Gustavsen Reviewed-by: Christian Stromme Reviewed-by: Yoann Lopes --- src/plugins/coreaudio/coreaudio.json | 3 + src/plugins/coreaudio/coreaudio.pro | 39 + src/plugins/coreaudio/coreaudiodeviceinfo.h | 86 ++ src/plugins/coreaudio/coreaudiodeviceinfo.mm | 386 ++++++++ src/plugins/coreaudio/coreaudioinput.h | 267 ++++++ src/plugins/coreaudio/coreaudioinput.mm | 1018 ++++++++++++++++++++++ src/plugins/coreaudio/coreaudiooutput.h | 204 +++++ src/plugins/coreaudio/coreaudiooutput.mm | 732 ++++++++++++++++ src/plugins/coreaudio/coreaudioplugin.h | 65 ++ src/plugins/coreaudio/coreaudioplugin.mm | 80 ++ src/plugins/coreaudio/coreaudiosessionmanager.h | 130 +++ src/plugins/coreaudio/coreaudiosessionmanager.mm | 481 ++++++++++ src/plugins/coreaudio/coreaudioutils.h | 95 ++ src/plugins/coreaudio/coreaudioutils.mm | 200 +++++ 14 files changed, 3786 insertions(+) create mode 100644 src/plugins/coreaudio/coreaudio.json create mode 100644 src/plugins/coreaudio/coreaudio.pro create mode 100644 src/plugins/coreaudio/coreaudiodeviceinfo.h create mode 100644 src/plugins/coreaudio/coreaudiodeviceinfo.mm create mode 100644 src/plugins/coreaudio/coreaudioinput.h create mode 100644 src/plugins/coreaudio/coreaudioinput.mm create mode 100644 src/plugins/coreaudio/coreaudiooutput.h create mode 100644 src/plugins/coreaudio/coreaudiooutput.mm create mode 100644 src/plugins/coreaudio/coreaudioplugin.h create mode 100644 src/plugins/coreaudio/coreaudioplugin.mm create mode 100644 src/plugins/coreaudio/coreaudiosessionmanager.h create mode 100644 src/plugins/coreaudio/coreaudiosessionmanager.mm create mode 100644 src/plugins/coreaudio/coreaudioutils.h create mode 100644 src/plugins/coreaudio/coreaudioutils.mm (limited to 'src/plugins/coreaudio') diff --git a/src/plugins/coreaudio/coreaudio.json b/src/plugins/coreaudio/coreaudio.json new file mode 100644 index 000000000..a31d52107 --- /dev/null +++ b/src/plugins/coreaudio/coreaudio.json @@ -0,0 +1,3 @@ +{ + "Keys": ["default"] +} diff --git a/src/plugins/coreaudio/coreaudio.pro b/src/plugins/coreaudio/coreaudio.pro new file mode 100644 index 000000000..146851493 --- /dev/null +++ b/src/plugins/coreaudio/coreaudio.pro @@ -0,0 +1,39 @@ +TARGET = qtaudio_coreaudio +QT += multimedia-private + +PLUGIN_TYPE = audio +PLUGIN_CLASS_NAME = CoreAudioPlugin + +load(qt_plugin) +OTHER_FILES += \ + coreaudio.json + +#DEFINES += QT_DEBUG_COREAUDIO + +HEADERS += \ + coreaudiodeviceinfo.h \ + coreaudioinput.h \ + coreaudiooutput.h \ + coreaudioplugin.h \ + coreaudioutils.h + +OBJECTIVE_SOURCES += \ + coreaudiodeviceinfo.mm \ + coreaudioinput.mm \ + coreaudiooutput.mm \ + coreaudioplugin.mm \ + coreaudioutils.mm + +ios { + HEADERS += coreaudiosessionmanager.h + OBJECTIVE_SOURCES += coreaudiosessionmanager.mm + LIBS += -framework AVFoundation +} else { + LIBS += \ + -framework ApplicationServices \ + -framework AudioUnit +} + +LIBS += \ + -framework CoreAudio \ + -framework AudioToolbox diff --git a/src/plugins/coreaudio/coreaudiodeviceinfo.h b/src/plugins/coreaudio/coreaudiodeviceinfo.h new file mode 100644 index 000000000..1a8bcec8f --- /dev/null +++ b/src/plugins/coreaudio/coreaudiodeviceinfo.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef IOSAUDIODEVICEINFO_H +#define IOSAUDIODEVICEINFO_H + +#include + +#if defined(Q_OS_OSX) +# include +#endif + +QT_BEGIN_NAMESPACE + +class CoreAudioDeviceInfo : public QAbstractAudioDeviceInfo +{ + Q_OBJECT + +public: + CoreAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode); + ~CoreAudioDeviceInfo() {} + + QAudioFormat preferredFormat() const; + bool isFormatSupported(const QAudioFormat &format) const; + QString deviceName() const; + QStringList supportedCodecs(); + QList supportedSampleRates(); + QList supportedChannelCounts(); + QList supportedSampleSizes(); + QList supportedByteOrders(); + QList supportedSampleTypes(); + + static QByteArray defaultInputDevice(); + static QByteArray defaultOutputDevice(); + + static QList availableDevices(QAudio::Mode mode); + +private: +#if defined(Q_OS_OSX) + AudioDeviceID m_deviceId; +#endif + + QString m_device; + QAudio::Mode m_mode; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/coreaudio/coreaudiodeviceinfo.mm b/src/plugins/coreaudio/coreaudiodeviceinfo.mm new file mode 100644 index 000000000..77a9b835d --- /dev/null +++ b/src/plugins/coreaudio/coreaudiodeviceinfo.mm @@ -0,0 +1,386 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "coreaudiodeviceinfo.h" +#include "coreaudioutils.h" +#if defined(Q_OS_IOS) +# include "coreaudiosessionmanager.h" +#endif + +#include +#include + +QT_BEGIN_NAMESPACE + +CoreAudioDeviceInfo::CoreAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode) + : m_mode(mode) +{ +#if defined(Q_OS_OSX) + quint32 deviceID; + + QDataStream dataStream(device); + dataStream >> deviceID >> m_device; + m_deviceId = AudioDeviceID(deviceID); +#else //iOS + m_device = device; +#endif +} + + +QAudioFormat CoreAudioDeviceInfo::preferredFormat() const +{ + QAudioFormat format; + +#if defined(Q_OS_OSX) + UInt32 propSize = 0; + AudioObjectPropertyScope audioDevicePropertyScope = m_mode == QAudio::AudioInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + AudioObjectPropertyAddress audioDevicePropertyStreamsAddress = { kAudioDevicePropertyStreams, + audioDevicePropertyScope, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyDataSize(m_deviceId, &audioDevicePropertyStreamsAddress, 0, NULL, &propSize) == noErr) { + + const int sc = propSize / sizeof(AudioStreamID); + + if (sc > 0) { + AudioStreamID* streams = new AudioStreamID[sc]; + + if (AudioObjectGetPropertyData(m_deviceId, &audioDevicePropertyStreamsAddress, 0, NULL, &propSize, streams) == noErr) { + + AudioObjectPropertyAddress audioDevicePhysicalFormatPropertyAddress = { kAudioStreamPropertyPhysicalFormat, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + for (int i = 0; i < sc; ++i) { + if (AudioObjectGetPropertyDataSize(streams[i], &audioDevicePhysicalFormatPropertyAddress, 0, NULL, &propSize) == noErr) { + AudioStreamBasicDescription sf; + + if (AudioObjectGetPropertyData(streams[i], &audioDevicePhysicalFormatPropertyAddress, 0, NULL, &propSize, &sf) == noErr) { + format = CoreAudioUtils::toQAudioFormat(sf); + break; + } else { + qWarning() << "QAudioDeviceInfo: Unable to find perferedFormat for stream"; + } + } else { + qWarning() << "QAudioDeviceInfo: Unable to find size of perferedFormat for stream"; + } + } + } + + delete streams; + } + } +#else //iOS + format.setSampleSize(16); + if (m_mode == QAudio::AudioInput) { + format.setChannelCount(1); + format.setSampleRate(8000); + } else { + format.setChannelCount(2); + format.setSampleRate(44100); + } + format.setCodec(QString::fromLatin1("audio/pcm")); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setSampleType(QAudioFormat::SignedInt); +#endif + + return format; +} + + +bool CoreAudioDeviceInfo::isFormatSupported(const QAudioFormat &format) const +{ + CoreAudioDeviceInfo *self = const_cast(this); + + return format.isValid() + && format.codec() == QString::fromLatin1("audio/pcm") + && self->supportedSampleRates().contains(format.sampleRate()) + && self->supportedChannelCounts().contains(format.channelCount()) + && self->supportedSampleSizes().contains(format.sampleSize()); +} + + +QString CoreAudioDeviceInfo::deviceName() const +{ + return m_device; +} + + +QStringList CoreAudioDeviceInfo::supportedCodecs() +{ + return QStringList() << QString::fromLatin1("audio/pcm"); +} + + +QList CoreAudioDeviceInfo::supportedSampleRates() +{ + QSet sampleRates; + +#if defined(Q_OS_OSX) + UInt32 propSize = 0; + AudioObjectPropertyScope scope = m_mode == QAudio::AudioInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + AudioObjectPropertyAddress availableNominalSampleRatesAddress = { kAudioDevicePropertyAvailableNominalSampleRates, + scope, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyDataSize(m_deviceId, &availableNominalSampleRatesAddress, 0, NULL, &propSize) == noErr) { + const int pc = propSize / sizeof(AudioValueRange); + + if (pc > 0) { + AudioValueRange* vr = new AudioValueRange[pc]; + + if (AudioObjectGetPropertyData(m_deviceId, &availableNominalSampleRatesAddress, 0, NULL, &propSize, vr) == noErr) { + for (int i = 0; i < pc; ++i) + sampleRates << vr[i].mMaximum; + } + + delete vr; + } + } +#else //iOS + //iOS doesn't have a way to query available sample rates + //instead we provide reasonable targets + //It may be necessary have CoreAudioSessionManger test combinations + //with available hardware + sampleRates << 8000 << 11025 << 22050 << 44100 << 48000; +#endif + return sampleRates.toList(); +} + + +QList CoreAudioDeviceInfo::supportedChannelCounts() +{ + QSet supportedChannels; + +#if defined(Q_OS_OSX) + UInt32 propSize = 0; + int channels = 0; + AudioObjectPropertyScope scope = m_mode == QAudio::AudioInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + AudioObjectPropertyAddress streamConfigurationPropertyAddress = { kAudioDevicePropertyStreamConfiguration, + scope, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyDataSize(m_deviceId, &streamConfigurationPropertyAddress, 0, NULL, &propSize) == noErr) { + AudioBufferList* audioBufferList = static_cast(malloc(propSize)); + + if (audioBufferList != 0) { + if (AudioObjectGetPropertyData(m_deviceId, &streamConfigurationPropertyAddress, 0, NULL, &propSize, audioBufferList) == noErr) { + for (int i = 0; i < int(audioBufferList->mNumberBuffers); ++i) { + channels += audioBufferList->mBuffers[i].mNumberChannels; + supportedChannels << channels; + } + } + + free(audioBufferList); + } + } +#else //iOS + if (m_mode == QAudio::AudioInput) { + supportedChannels << CoreAudioSessionManager::instance().inputChannelCount(); + } else if (m_mode == QAudio::AudioOutput) { + supportedChannels << CoreAudioSessionManager::instance().outputChannelCount(); + } +#endif + + return supportedChannels.toList(); +} + + +QList CoreAudioDeviceInfo::supportedSampleSizes() +{ + return QList() << 8 << 16 << 24 << 32 << 64; +} + + +QList CoreAudioDeviceInfo::supportedByteOrders() +{ + return QList() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian; +} + + +QList CoreAudioDeviceInfo::supportedSampleTypes() +{ + return QList() << QAudioFormat::SignedInt << QAudioFormat::UnSignedInt << QAudioFormat::Float; +} + +#if defined(Q_OS_OSX) +// XXX: remove at some future date +static inline QString cfStringToQString(CFStringRef str) +{ + CFIndex length = CFStringGetLength(str); + const UniChar *chars = CFStringGetCharactersPtr(str); + if (chars) + return QString(reinterpret_cast(chars), length); + + UniChar buffer[length]; + CFStringGetCharacters(str, CFRangeMake(0, length), buffer); + return QString(reinterpret_cast(buffer), length); +} + +static QByteArray get_device_info(AudioDeviceID audioDevice, QAudio::Mode mode) +{ + UInt32 size; + QByteArray device; + QDataStream ds(&device, QIODevice::WriteOnly); + AudioStreamBasicDescription sf; + CFStringRef name; + Boolean isInput = mode == QAudio::AudioInput; + AudioObjectPropertyScope audioPropertyScope = isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + + // Id + ds << quint32(audioDevice); + + // Mode //TODO: Why don't we use the Stream Format we ask for? + size = sizeof(AudioStreamBasicDescription); + AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress = { kAudioDevicePropertyStreamFormat, + audioPropertyScope, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyData(audioDevice, &audioDeviceStreamFormatPropertyAddress, 0, NULL, &size, &sf) != noErr) { + return QByteArray(); + } + + // Name + size = sizeof(CFStringRef); + AudioObjectPropertyAddress audioDeviceNamePropertyAddress = { kAudioObjectPropertyName, + audioPropertyScope, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyData(audioDevice, &audioDeviceNamePropertyAddress, 0, NULL, &size, &name) != noErr) { + qWarning() << "QAudioDeviceInfo: Unable to find device name"; + return QByteArray(); + } + ds << cfStringToQString(name); + + CFRelease(name); + + return device; +} +#endif + +QByteArray CoreAudioDeviceInfo::defaultInputDevice() +{ +#if defined(Q_OS_OSX) + AudioDeviceID audioDevice; + UInt32 size = sizeof(audioDevice); + AudioObjectPropertyAddress defaultInputDevicePropertyAddress = { kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, + &defaultInputDevicePropertyAddress, + 0, NULL, &size, &audioDevice) != noErr) { + qWarning() << "QAudioDeviceInfo: Unable to find default input device"; + return QByteArray(); + } + + return get_device_info(audioDevice, QAudio::AudioInput); +#else //iOS + return CoreAudioSessionManager::instance().inputDevices().first(); +#endif +} + +QByteArray CoreAudioDeviceInfo::defaultOutputDevice() +{ +#if defined(Q_OS_OSX) + AudioDeviceID audioDevice; + UInt32 size = sizeof(audioDevice); + AudioObjectPropertyAddress defaultOutputDevicePropertyAddress = { kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, + &defaultOutputDevicePropertyAddress, + 0, NULL, &size, &audioDevice) != noErr) { + qWarning() << "QAudioDeviceInfo: Unable to find default output device"; + return QByteArray(); + } + + return get_device_info(audioDevice, QAudio::AudioOutput); +#else //iOS + return CoreAudioSessionManager::instance().outputDevices().first(); +#endif +} + +QList CoreAudioDeviceInfo::availableDevices(QAudio::Mode mode) +{ + QList devices; +#if defined(Q_OS_OSX) + UInt32 propSize = 0; + AudioObjectPropertyAddress audioDevicesPropertyAddress = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, + &audioDevicesPropertyAddress, + 0, NULL, &propSize) == noErr) { + + const int dc = propSize / sizeof(AudioDeviceID); + + if (dc > 0) { + AudioDeviceID* audioDevices = new AudioDeviceID[dc]; + + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesPropertyAddress, 0, NULL, &propSize, audioDevices) == noErr) { + for (int i = 0; i < dc; ++i) { + QByteArray info = get_device_info(audioDevices[i], mode); + if (!info.isNull()) + devices << info; + } + } + + delete audioDevices; + } + } +#else //iOS + CoreAudioSessionManager::instance().setActive(true); + + if (mode == QAudio::AudioOutput) + return CoreAudioSessionManager::instance().outputDevices(); + if (mode == QAudio::AudioInput) + return CoreAudioSessionManager::instance().inputDevices(); +#endif + + return devices; +} + +QT_END_NAMESPACE + +#include "moc_coreaudiodeviceinfo.cpp" diff --git a/src/plugins/coreaudio/coreaudioinput.h b/src/plugins/coreaudio/coreaudioinput.h new file mode 100644 index 000000000..a54db7773 --- /dev/null +++ b/src/plugins/coreaudio/coreaudioinput.h @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef IOSAUDIOINPUT_H +#define IOSAUDIOINPUT_H + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class CoreAudioRingBuffer; +class CoreAudioPacketFeeder; +class CoreAudioInputBuffer; +class CoreAudioInputDevice; + +class CoreAudioBufferList +{ +public: + CoreAudioBufferList(AudioStreamBasicDescription const& streamFormat); + CoreAudioBufferList(AudioStreamBasicDescription const& streamFormat, char *buffer, int bufferSize); + CoreAudioBufferList(AudioStreamBasicDescription const& streamFormat, int framesToBuffer); + + ~CoreAudioBufferList(); + + AudioBufferList* audioBufferList() const { return m_bufferList; } + char *data(int buffer = 0) const; + qint64 bufferSize(int buffer = 0) const; + int frameCount(int buffer = 0) const; + int packetCount(int buffer = 0) const; + int packetSize() const; + void reset(); + +private: + bool m_owner; + int m_dataSize; + AudioStreamBasicDescription m_streamDescription; + AudioBufferList *m_bufferList; +}; + +class CoreAudioPacketFeeder +{ +public: + CoreAudioPacketFeeder(CoreAudioBufferList *abl); + + bool feed(AudioBufferList& dst, UInt32& packetCount); + bool empty() const; + +private: + UInt32 m_totalPackets; + UInt32 m_position; + CoreAudioBufferList *m_audioBufferList; +}; + +class CoreAudioInputBuffer : public QObject +{ + Q_OBJECT + +public: + CoreAudioInputBuffer(int bufferSize, + int maxPeriodSize, + AudioStreamBasicDescription const& inputFormat, + AudioStreamBasicDescription const& outputFormat, + QObject *parent); + + ~CoreAudioInputBuffer(); + + qreal volume() const; + void setVolume(qreal v); + + qint64 renderFromDevice(AudioUnit audioUnit, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames); + + qint64 readBytes(char *data, qint64 len); + + void setFlushDevice(QIODevice *device); + + void startFlushTimer(); + void stopFlushTimer(); + + void flush(bool all = false); + void reset(); + int available() const; + int used() const; + +signals: + void readyRead(); + +private slots: + void flushBuffer(); + +private: + bool m_deviceError; + int m_maxPeriodSize; + int m_periodTime; + QIODevice *m_device; + QTimer *m_flushTimer; + CoreAudioRingBuffer *m_buffer; + CoreAudioBufferList *m_inputBufferList; + AudioConverterRef m_audioConverter; + AudioStreamBasicDescription m_inputFormat; + AudioStreamBasicDescription m_outputFormat; + QAudioFormat m_qFormat; + qreal m_volume; + + const static OSStatus as_empty = 'qtem'; + + // Converter callback + static OSStatus converterCallback(AudioConverterRef inAudioConverter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **outDataPacketDescription, + void *inUserData); +}; + +class CoreAudioInputDevice : public QIODevice +{ + Q_OBJECT + +public: + CoreAudioInputDevice(CoreAudioInputBuffer *audioBuffer, QObject *parent); + + qint64 readData(char *data, qint64 len); + qint64 writeData(const char *data, qint64 len); + + bool isSequential() const { return true; } + +private: + CoreAudioInputBuffer *m_audioBuffer; +}; + +class CoreAudioInput : public QAbstractAudioInput +{ + Q_OBJECT + +public: + CoreAudioInput(const QByteArray &device); + ~CoreAudioInput(); + + void start(QIODevice *device); + QIODevice *start(); + void stop(); + void reset(); + void suspend(); + void resume(); + int bytesReady() const; + int periodSize() const; + void setBufferSize(int value); + int bufferSize() const; + void setNotifyInterval(int milliSeconds); + int notifyInterval() const; + qint64 processedUSecs() const; + qint64 elapsedUSecs() const; + QAudio::Error error() const; + QAudio::State state() const; + void setFormat(const QAudioFormat &format); + QAudioFormat format() const; + + void setVolume(qreal volume); + qreal volume() const; + +private slots: + void deviceStoppped(); + +private: + enum { + Running, + Stopped + }; + + bool open(); + void close(); + + void audioThreadStart(); + void audioThreadStop(); + + void audioDeviceStop(); + void audioDeviceActive(); + void audioDeviceFull(); + void audioDeviceError(); + + void startTimers(); + void stopTimers(); + + // Input callback + static OSStatus inputCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); + + QByteArray m_device; + bool m_isOpen; + int m_periodSizeBytes; + int m_internalBufferSize; + qint64 m_totalFrames; + QAudioFormat m_audioFormat; + QIODevice *m_audioIO; + AudioUnit m_audioUnit; +#if defined(Q_OS_OSX) + AudioDeviceID m_audioDeviceId; +#endif + Float64 m_clockFrequency; + UInt64 m_startTime; + QAudio::Error m_errorCode; + QAudio::State m_stateCode; + CoreAudioInputBuffer *m_audioBuffer; + QMutex m_mutex; + QWaitCondition m_threadFinished; + QAtomicInt m_audioThreadState; + QTimer *m_intervalTimer; + AudioStreamBasicDescription m_streamFormat; + AudioStreamBasicDescription m_deviceFormat; + QAbstractAudioDeviceInfo *m_audioDeviceInfo; + qreal m_volume; +}; + +QT_END_NAMESPACE + +#endif // IOSAUDIOINPUT_H diff --git a/src/plugins/coreaudio/coreaudioinput.mm b/src/plugins/coreaudio/coreaudioinput.mm new file mode 100644 index 000000000..c41e1a51e --- /dev/null +++ b/src/plugins/coreaudio/coreaudioinput.mm @@ -0,0 +1,1018 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "coreaudioinput.h" +#include "coreaudiosessionmanager.h" +#include "coreaudiodeviceinfo.h" +#include "coreaudioutils.h" + +#if defined(Q_OS_OSX) +# include +#endif + +#include +#include + +QT_BEGIN_NAMESPACE + +static const int DEFAULT_BUFFER_SIZE = 4 * 1024; + +CoreAudioBufferList::CoreAudioBufferList(const AudioStreamBasicDescription &streamFormat) + : m_owner(false) + , m_streamDescription(streamFormat) +{ + const bool isInterleaved = (m_streamDescription.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; + const int numberOfBuffers = isInterleaved ? 1 : m_streamDescription.mChannelsPerFrame; + + m_dataSize = 0; + + m_bufferList = reinterpret_cast(malloc(sizeof(AudioBufferList) + + (sizeof(AudioBuffer) * numberOfBuffers))); + + m_bufferList->mNumberBuffers = numberOfBuffers; + for (int i = 0; i < numberOfBuffers; ++i) { + m_bufferList->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1; + m_bufferList->mBuffers[i].mDataByteSize = 0; + m_bufferList->mBuffers[i].mData = 0; + } +} + +CoreAudioBufferList::CoreAudioBufferList(const AudioStreamBasicDescription &streamFormat, char *buffer, int bufferSize) + : m_owner(false) + , m_streamDescription(streamFormat) + , m_bufferList(0) +{ + m_dataSize = bufferSize; + + m_bufferList = reinterpret_cast(malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer))); + + m_bufferList->mNumberBuffers = 1; + m_bufferList->mBuffers[0].mNumberChannels = 1; + m_bufferList->mBuffers[0].mDataByteSize = m_dataSize; + m_bufferList->mBuffers[0].mData = buffer; +} + +CoreAudioBufferList::CoreAudioBufferList(const AudioStreamBasicDescription &streamFormat, int framesToBuffer) + : m_owner(true) + , m_streamDescription(streamFormat) + , m_bufferList(0) +{ + const bool isInterleaved = (m_streamDescription.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; + const int numberOfBuffers = isInterleaved ? 1 : m_streamDescription.mChannelsPerFrame; + + m_dataSize = framesToBuffer * m_streamDescription.mBytesPerFrame; + + m_bufferList = reinterpret_cast(malloc(sizeof(AudioBufferList) + + (sizeof(AudioBuffer) * numberOfBuffers))); + m_bufferList->mNumberBuffers = numberOfBuffers; + for (int i = 0; i < numberOfBuffers; ++i) { + m_bufferList->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1; + m_bufferList->mBuffers[i].mDataByteSize = m_dataSize; + m_bufferList->mBuffers[i].mData = malloc(m_dataSize); + } +} + +CoreAudioBufferList::~CoreAudioBufferList() +{ + if (m_owner) { + for (UInt32 i = 0; i < m_bufferList->mNumberBuffers; ++i) + free(m_bufferList->mBuffers[i].mData); + } + + free(m_bufferList); +} + +char *CoreAudioBufferList::data(int buffer) const +{ + return static_cast(m_bufferList->mBuffers[buffer].mData); +} + +qint64 CoreAudioBufferList::bufferSize(int buffer) const +{ + return m_bufferList->mBuffers[buffer].mDataByteSize; +} + +int CoreAudioBufferList::frameCount(int buffer) const +{ + return m_bufferList->mBuffers[buffer].mDataByteSize / m_streamDescription.mBytesPerFrame; +} + +int CoreAudioBufferList::packetCount(int buffer) const +{ + return m_bufferList->mBuffers[buffer].mDataByteSize / m_streamDescription.mBytesPerPacket; +} + +int CoreAudioBufferList::packetSize() const +{ + return m_streamDescription.mBytesPerPacket; +} + +void CoreAudioBufferList::reset() +{ + for (UInt32 i = 0; i < m_bufferList->mNumberBuffers; ++i) { + m_bufferList->mBuffers[i].mDataByteSize = m_dataSize; + m_bufferList->mBuffers[i].mData = 0; + } +} + +CoreAudioPacketFeeder::CoreAudioPacketFeeder(CoreAudioBufferList *abl) + : m_audioBufferList(abl) +{ + m_totalPackets = m_audioBufferList->packetCount(); + m_position = 0; +} + +bool CoreAudioPacketFeeder::feed(AudioBufferList &dst, UInt32 &packetCount) +{ + if (m_position == m_totalPackets) { + dst.mBuffers[0].mDataByteSize = 0; + packetCount = 0; + return false; + } + + if (m_totalPackets - m_position < packetCount) + packetCount = m_totalPackets - m_position; + + dst.mBuffers[0].mDataByteSize = packetCount * m_audioBufferList->packetSize(); + dst.mBuffers[0].mData = m_audioBufferList->data() + (m_position * m_audioBufferList->packetSize()); + + m_position += packetCount; + + return true; +} + +bool CoreAudioPacketFeeder::empty() const +{ + return m_position == m_totalPackets; +} + +CoreAudioInputBuffer::CoreAudioInputBuffer(int bufferSize, int maxPeriodSize, const AudioStreamBasicDescription &inputFormat, const AudioStreamBasicDescription &outputFormat, QObject *parent) + : QObject(parent) + , m_deviceError(false) + , m_device(0) + , m_audioConverter(0) + , m_inputFormat(inputFormat) + , m_outputFormat(outputFormat) + , m_volume(qreal(1.0f)) +{ + m_maxPeriodSize = maxPeriodSize; + m_periodTime = m_maxPeriodSize / m_outputFormat.mBytesPerFrame * 1000 / m_outputFormat.mSampleRate; + + m_buffer = new CoreAudioRingBuffer(bufferSize); + + m_inputBufferList = new CoreAudioBufferList(m_inputFormat); + + m_flushTimer = new QTimer(this); + connect(m_flushTimer, SIGNAL(timeout()), SLOT(flushBuffer())); + + if (CoreAudioUtils::toQAudioFormat(inputFormat) != CoreAudioUtils::toQAudioFormat(outputFormat)) { + if (AudioConverterNew(&m_inputFormat, &m_outputFormat, &m_audioConverter) != noErr) { + qWarning() << "QAudioInput: Unable to create an Audio Converter"; + m_audioConverter = 0; + } + } + + m_qFormat = CoreAudioUtils::toQAudioFormat(inputFormat); // we adjust volume before conversion +} + +CoreAudioInputBuffer::~CoreAudioInputBuffer() +{ + delete m_buffer; +} + +qreal CoreAudioInputBuffer::volume() const +{ + return m_volume; +} + +void CoreAudioInputBuffer::setVolume(qreal v) +{ + m_volume = v; +} + +qint64 CoreAudioInputBuffer::renderFromDevice(AudioUnit audioUnit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames) +{ + const bool pullMode = m_device == 0; + + OSStatus err; + qint64 framesRendered = 0; + + m_inputBufferList->reset(); + err = AudioUnitRender(audioUnit, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + m_inputBufferList->audioBufferList()); + + // adjust volume, if necessary + if (!qFuzzyCompare(m_volume, qreal(1.0f))) { + QAudioHelperInternal::qMultiplySamples(m_volume, + m_qFormat, + m_inputBufferList->data(), /* input */ + m_inputBufferList->data(), /* output */ + m_inputBufferList->bufferSize()); + } + + if (m_audioConverter != 0) { + CoreAudioPacketFeeder feeder(m_inputBufferList); + + int copied = 0; + const int available = m_buffer->free(); + + while (err == noErr && !feeder.empty()) { + CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied); + + if (region.second == 0) + break; + + AudioBufferList output; + output.mNumberBuffers = 1; + output.mBuffers[0].mNumberChannels = 1; + output.mBuffers[0].mDataByteSize = region.second; + output.mBuffers[0].mData = region.first; + + UInt32 packetSize = region.second / m_outputFormat.mBytesPerPacket; + err = AudioConverterFillComplexBuffer(m_audioConverter, + converterCallback, + &feeder, + &packetSize, + &output, + 0); + region.second = output.mBuffers[0].mDataByteSize; + copied += region.second; + + m_buffer->releaseWriteRegion(region); + } + + framesRendered += copied / m_outputFormat.mBytesPerFrame; + } + else { + const int available = m_inputBufferList->bufferSize(); + bool wecan = true; + int copied = 0; + + while (wecan && copied < available) { + CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied); + + if (region.second > 0) { + memcpy(region.first, m_inputBufferList->data() + copied, region.second); + copied += region.second; + } + else + wecan = false; + + m_buffer->releaseWriteRegion(region); + } + + framesRendered = copied / m_outputFormat.mBytesPerFrame; + } + + if (pullMode && framesRendered > 0) + emit readyRead(); + + return framesRendered; +} + +qint64 CoreAudioInputBuffer::readBytes(char *data, qint64 len) +{ + bool wecan = true; + qint64 bytesCopied = 0; + + len -= len % m_maxPeriodSize; + while (wecan && bytesCopied < len) { + CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion(len - bytesCopied); + + if (region.second > 0) { + memcpy(data + bytesCopied, region.first, region.second); + bytesCopied += region.second; + } + else + wecan = false; + + m_buffer->releaseReadRegion(region); + } + + return bytesCopied; +} + +void CoreAudioInputBuffer::setFlushDevice(QIODevice *device) +{ + if (m_device != device) + m_device = device; +} + +void CoreAudioInputBuffer::startFlushTimer() +{ + if (m_device != 0) { + // We use the period time for the timer, since that's + // around the buffer size (pre conversion >.>) + m_flushTimer->start(qMax(1, m_periodTime)); + } +} + +void CoreAudioInputBuffer::stopFlushTimer() +{ + m_flushTimer->stop(); +} + +void CoreAudioInputBuffer::flush(bool all) +{ + if (m_device == 0) + return; + + const int used = m_buffer->used(); + const int readSize = all ? used : used - (used % m_maxPeriodSize); + + if (readSize > 0) { + bool wecan = true; + int flushed = 0; + + while (!m_deviceError && wecan && flushed < readSize) { + CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion(readSize - flushed); + + if (region.second > 0) { + int bytesWritten = m_device->write(region.first, region.second); + if (bytesWritten < 0) { + stopFlushTimer(); + m_deviceError = true; + } + else { + region.second = bytesWritten; + flushed += bytesWritten; + wecan = bytesWritten != 0; + } + } + else + wecan = false; + + m_buffer->releaseReadRegion(region); + } + } +} + +void CoreAudioInputBuffer::reset() +{ + m_buffer->reset(); + m_deviceError = false; +} + +int CoreAudioInputBuffer::available() const +{ + return m_buffer->free(); +} + +int CoreAudioInputBuffer::used() const +{ + return m_buffer->used(); +} + +void CoreAudioInputBuffer::flushBuffer() +{ + flush(); +} + +OSStatus CoreAudioInputBuffer::converterCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) +{ + Q_UNUSED(inAudioConverter); + Q_UNUSED(outDataPacketDescription); + + CoreAudioPacketFeeder* feeder = static_cast(inUserData); + + if (!feeder->feed(*ioData, *ioNumberDataPackets)) + return as_empty; + + return noErr; +} + +CoreAudioInputDevice::CoreAudioInputDevice(CoreAudioInputBuffer *audioBuffer, QObject *parent) + : QIODevice(parent) + , m_audioBuffer(audioBuffer) +{ + open(QIODevice::ReadOnly | QIODevice::Unbuffered); + connect(m_audioBuffer, SIGNAL(readyRead()), SIGNAL(readyRead())); +} + +qint64 CoreAudioInputDevice::readData(char *data, qint64 len) +{ + return m_audioBuffer->readBytes(data, len); +} + +qint64 CoreAudioInputDevice::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + + return 0; +} + +CoreAudioInput::CoreAudioInput(const QByteArray &device) + : m_isOpen(false) + , m_internalBufferSize(DEFAULT_BUFFER_SIZE) + , m_totalFrames(0) + , m_audioUnit(0) + , m_clockFrequency(CoreAudioUtils::frequency() / 1000) + , m_startTime(0) + , m_errorCode(QAudio::NoError) + , m_stateCode(QAudio::StoppedState) + , m_audioBuffer(0) + , m_volume(1.0) +{ +#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_audioDeviceInfo = new CoreAudioDeviceInfo(device, QAudio::AudioInput); + + m_intervalTimer = new QTimer(this); + m_intervalTimer->setInterval(1000); + connect(m_intervalTimer, SIGNAL(timeout()), this, SIGNAL(notify())); +} + + +CoreAudioInput::~CoreAudioInput() +{ + close(); + delete m_audioDeviceInfo; +} + +bool CoreAudioInput::open() +{ + if (m_isOpen) + return true; + +#if defined(Q_OS_OSX) + UInt32 size = 0; + + ComponentDescription componentDescription; + componentDescription.componentType = kAudioUnitType_Output; + componentDescription.componentSubType = kAudioUnitSubType_HALOutput; + componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + componentDescription.componentFlags = 0; + componentDescription.componentFlagsMask = 0; + + // Open + Component component = FindNextComponent(NULL, &componentDescription); + if (component == 0) { + qWarning() << "QAudioInput: Failed to find HAL Output component"; + return false; + } + + if (OpenAComponent(component, &m_audioUnit) != noErr) { + qWarning() << "QAudioInput: Unable to Open Output Component"; + return false; + } +#else //iOS + AudioComponentDescription componentDescription; + componentDescription.componentType = kAudioUnitType_Output; + componentDescription.componentSubType = kAudioUnitSubType_RemoteIO; + componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + componentDescription.componentFlags = 0; + componentDescription.componentFlagsMask = 0; + + AudioComponent component = AudioComponentFindNext(0, &componentDescription); + if (component == 0) { + qWarning() << "QAudioInput: Failed to find Output component"; + return false; + } + + if (AudioComponentInstanceNew(component, &m_audioUnit) != noErr) { + qWarning() << "QAudioInput: Unable to Open Output Component"; + return false; + } +#endif + // Set mode + // switch to input mode + UInt32 enable = 1; + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + 1, + &enable, + sizeof(enable)) != noErr) { + qWarning() << "QAudioInput: Unable to switch to input mode (Enable Input)"; + return false; + } + + enable = 0; + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, + &enable, + sizeof(enable)) != noErr) { + qWarning() << "QAudioInput: Unable to switch to input mode (Disable output)"; + return false; + } + + // register callback + AURenderCallbackStruct callback; + callback.inputProc = inputCallback; + callback.inputProcRefCon = this; + + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + 0, + &callback, + sizeof(callback)) != noErr) { + qWarning() << "QAudioInput: 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() << "QAudioInput: Unable to use configured device"; + return false; + } +#endif + + //set format + m_streamFormat = CoreAudioUtils::toAudioStreamBasicDescription(m_audioFormat); + +#if defined(Q_OS_OSX) + if (m_audioFormat == m_audioDeviceInfo->preferredFormat()) { +#endif + + m_deviceFormat = m_streamFormat; + AudioUnitSetProperty(m_audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &m_deviceFormat, + sizeof(m_deviceFormat)); +#if defined(Q_OS_OSX) + } else { + size = sizeof(m_deviceFormat); + if (AudioUnitGetProperty(m_audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 1, + &m_deviceFormat, + &size) != noErr) { + qWarning() << "QAudioInput: Unable to retrieve device format"; + return false; + } + + if (AudioUnitSetProperty(m_audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &m_deviceFormat, + sizeof(m_deviceFormat)) != noErr) { + qWarning() << "QAudioInput: Unable to set device format"; + return false; + } + } +#endif + + //setup buffers + UInt32 numberOfFrames; +#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; + } + //BUG: numberOfFrames gets ignored after this point + + AudioValueRange bufferRange; + size = sizeof(AudioValueRange); + + if (AudioUnitGetProperty(m_audioUnit, + kAudioDevicePropertyBufferFrameSizeRange, + kAudioUnitScope_Global, + 0, + &bufferRange, + &size) != noErr) { + qWarning() << "QAudioInput: Failed to get audio period size range"; + return false; + } + + // See if the requested buffer size is permissible + numberOfFrames = qBound((UInt32)bufferRange.mMinimum, m_internalBufferSize / m_streamFormat.mBytesPerFrame, (UInt32)bufferRange.mMaximum); + + // Set it back + if (AudioUnitSetProperty(m_audioUnit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Global, + 0, + &numberOfFrames, + sizeof(UInt32)) != noErr) { + qWarning() << "QAudioInput: Failed to set audio buffer size"; + return false; + } +#else //iOS + Float32 bufferSize = CoreAudioSessionManager::instance().currentIOBufferDuration(); + bufferSize *= m_streamFormat.mSampleRate; + numberOfFrames = bufferSize; +#endif + + // Now allocate a few buffers to be safe. + m_periodSizeBytes = m_internalBufferSize = numberOfFrames * m_streamFormat.mBytesPerFrame; + + m_audioBuffer = new CoreAudioInputBuffer(m_internalBufferSize * 4, + m_periodSizeBytes, + m_deviceFormat, + m_streamFormat, + this); + + m_audioBuffer->setVolume(m_volume); + m_audioIO = new CoreAudioInputDevice(m_audioBuffer, this); + + // Init + if (AudioUnitInitialize(m_audioUnit) != noErr) { + qWarning() << "QAudioInput: Failed to initialize AudioUnit"; + return false; + } + + m_isOpen = true; + + return m_isOpen; + +} + +void CoreAudioInput::close() +{ + if (m_audioUnit != 0) { + AudioOutputUnitStop(m_audioUnit); + AudioUnitUninitialize(m_audioUnit); +#if defined(Q_OS_OSX) + CloseComponent(m_audioUnit); +#else //iOS + AudioComponentInstanceDispose(m_audioUnit); +#endif + + } + + delete m_audioBuffer; +} + +void CoreAudioInput::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->setFlushDevice(op); + + if (op == 0) + op = m_audioIO; + + // Start + m_startTime = CoreAudioUtils::currentTime(); + m_totalFrames = 0; + + m_stateCode = QAudio::IdleState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + + audioThreadStart(); +} + + +QIODevice *CoreAudioInput::start() +{ + QIODevice* op = 0; + + if (!m_audioDeviceInfo->isFormatSupported(m_audioFormat) || !open()) { + m_stateCode = QAudio::StoppedState; + m_errorCode = QAudio::OpenError; + return m_audioIO; + } + + reset(); + m_audioBuffer->reset(); + m_audioBuffer->setFlushDevice(op); + + if (op == 0) + op = m_audioIO; + + // Start + m_startTime = CoreAudioUtils::currentTime(); + m_totalFrames = 0; + + m_stateCode = QAudio::IdleState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + + audioThreadStart(); + + return op; +} + + +void CoreAudioInput::stop() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode != QAudio::StoppedState) { + audioThreadStop(); + m_audioBuffer->flush(true); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::StoppedState; + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +void CoreAudioInput::reset() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode != QAudio::StoppedState) { + audioThreadStop(); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::StoppedState; + m_audioBuffer->reset(); + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +void CoreAudioInput::suspend() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode == QAudio::ActiveState || m_stateCode == QAudio::IdleState) { + audioThreadStop(); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::SuspendedState; + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +void CoreAudioInput::resume() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode == QAudio::SuspendedState) { + audioThreadStart(); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::ActiveState; + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +int CoreAudioInput::bytesReady() const +{ + if (!m_audioBuffer) + return 0; + return m_audioBuffer->used(); +} + + +int CoreAudioInput::periodSize() const +{ + return m_periodSizeBytes; +} + + +void CoreAudioInput::setBufferSize(int value) +{ + m_internalBufferSize = value; +} + + +int CoreAudioInput::bufferSize() const +{ + return m_internalBufferSize; +} + + +void CoreAudioInput::setNotifyInterval(int milliSeconds) +{ + if (m_intervalTimer->interval() == milliSeconds) + return; + + if (milliSeconds <= 0) + milliSeconds = 0; + + m_intervalTimer->setInterval(milliSeconds); +} + + +int CoreAudioInput::notifyInterval() const +{ + return m_intervalTimer->interval(); +} + + +qint64 CoreAudioInput::processedUSecs() const +{ + return m_totalFrames * 1000000 / m_audioFormat.sampleRate(); +} + + +qint64 CoreAudioInput::elapsedUSecs() const +{ + if (m_stateCode == QAudio::StoppedState) + return 0; + + return (CoreAudioUtils::currentTime() - m_startTime) / (m_clockFrequency / 1000); +} + + +QAudio::Error CoreAudioInput::error() const +{ + return m_errorCode; +} + + +QAudio::State CoreAudioInput::state() const +{ + return m_stateCode; +} + + +void CoreAudioInput::setFormat(const QAudioFormat &format) +{ + if (m_stateCode == QAudio::StoppedState) + m_audioFormat = format; +} + + +QAudioFormat CoreAudioInput::format() const +{ + return m_audioFormat; +} + + +void CoreAudioInput::setVolume(qreal volume) +{ + m_volume = volume; + if (m_audioBuffer) + m_audioBuffer->setVolume(m_volume); +} + + +qreal CoreAudioInput::volume() const +{ + return m_volume; +} + +void CoreAudioInput::deviceStoppped() +{ + stopTimers(); + emit stateChanged(m_stateCode); +} + +void CoreAudioInput::audioThreadStart() +{ + startTimers(); + m_audioThreadState.store(Running); + AudioOutputUnitStart(m_audioUnit); +} + +void CoreAudioInput::audioThreadStop() +{ + stopTimers(); + if (m_audioThreadState.testAndSetAcquire(Running, Stopped)) + m_threadFinished.wait(&m_mutex); +} + +void CoreAudioInput::audioDeviceStop() +{ + AudioOutputUnitStop(m_audioUnit); + m_audioThreadState.store(Stopped); + m_threadFinished.wakeOne(); +} + +void CoreAudioInput::audioDeviceActive() +{ + if (m_stateCode == QAudio::IdleState) { + QMutexLocker lock(&m_mutex); + m_stateCode = QAudio::ActiveState; + emit stateChanged(m_stateCode); + } +} + +void CoreAudioInput::audioDeviceFull() +{ + if (m_stateCode == QAudio::ActiveState) { + QMutexLocker lock(&m_mutex); + m_errorCode = QAudio::UnderrunError; + m_stateCode = QAudio::IdleState; + emit stateChanged(m_stateCode); + } +} + +void CoreAudioInput::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 CoreAudioInput::startTimers() +{ + m_audioBuffer->startFlushTimer(); + if (m_intervalTimer->interval() > 0) + m_intervalTimer->start(); +} + +void CoreAudioInput::stopTimers() +{ + m_audioBuffer->stopFlushTimer(); + m_intervalTimer->stop(); +} + +OSStatus CoreAudioInput::inputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) +{ + Q_UNUSED(ioData); + + CoreAudioInput* d = static_cast(inRefCon); + + const int threadState = d->m_audioThreadState.loadAcquire(); + if (threadState == Stopped) + d->audioDeviceStop(); + else { + qint64 framesWritten; + + framesWritten = d->m_audioBuffer->renderFromDevice(d->m_audioUnit, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames); + + if (framesWritten > 0) { + d->m_totalFrames += framesWritten; + d->audioDeviceActive(); + } else if (framesWritten == 0) + d->audioDeviceFull(); + else if (framesWritten < 0) + d->audioDeviceError(); + } + + return noErr; +} + +QT_END_NAMESPACE + +#include "moc_coreaudioinput.cpp" diff --git a/src/plugins/coreaudio/coreaudiooutput.h b/src/plugins/coreaudio/coreaudiooutput.h new file mode 100644 index 000000000..d4636e052 --- /dev/null +++ b/src/plugins/coreaudio/coreaudiooutput.h @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef IOSAUDIOOUTPUT_H +#define IOSAUDIOOUTPUT_H + +#include + +#if defined(Q_OS_OSX) +# include +#endif +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class CoreAudioOutputBuffer; +class QTimer; +class CoreAudioDeviceInfo; +class CoreAudioRingBuffer; + +class CoreAudioOutputBuffer : public QObject +{ + Q_OBJECT + +public: + CoreAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat); + ~CoreAudioOutputBuffer(); + + qint64 readFrames(char *data, qint64 maxFrames); + qint64 writeBytes(const char *data, qint64 maxSize); + + int available() const; + void reset(); + + void setPrefetchDevice(QIODevice *device); + + void startFillTimer(); + void stopFillTimer(); + +signals: + void readyRead(); + +private slots: + void fillBuffer(); + +private: + bool m_deviceError; + int m_maxPeriodSize; + int m_bytesPerFrame; + int m_periodTime; + QIODevice *m_device; + QTimer *m_fillTimer; + CoreAudioRingBuffer *m_buffer; +}; + +class CoreAudioOutputDevice : public QIODevice +{ +public: + CoreAudioOutputDevice(CoreAudioOutputBuffer *audioBuffer, QObject *parent); + + qint64 readData(char *data, qint64 len); + qint64 writeData(const char *data, qint64 len); + + bool isSequential() const { return true; } + +private: + CoreAudioOutputBuffer *m_audioBuffer; +}; + + +class CoreAudioOutput : public QAbstractAudioOutput +{ + Q_OBJECT + +public: + CoreAudioOutput(const QByteArray &device); + ~CoreAudioOutput(); + + void start(QIODevice *device); + QIODevice *start(); + void stop(); + void reset(); + void suspend(); + void resume(); + int bytesFree() const; + int periodSize() const; + void setBufferSize(int value); + int bufferSize() const; + void setNotifyInterval(int milliSeconds); + int notifyInterval() const; + qint64 processedUSecs() const; + qint64 elapsedUSecs() const; + QAudio::Error error() const; + QAudio::State state() const; + void setFormat(const QAudioFormat &format); + QAudioFormat format() const; + + void setVolume(qreal volume); + qreal volume() const; + + void setCategory(const QString &category); + QString category() const; + +private slots: + void deviceStopped(); + void inputReady(); + +private: + enum { + Running, + Draining, + Stopped + }; + + static OSStatus renderCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); + + bool open(); + void close(); + void audioThreadStart(); + void audioThreadStop(); + void audioThreadDrain(); + void audioDeviceStop(); + void audioDeviceIdle(); + void audioDeviceError(); + + void startTimers(); + void stopTimers(); + + QByteArray m_device; + + bool m_isOpen; + int m_internalBufferSize; + int m_periodSizeBytes; + qint64 m_totalFrames; + QAudioFormat m_audioFormat; + QIODevice *m_audioIO; +#if defined(Q_OS_OSX) + AudioDeviceID m_audioDeviceId; +#endif + AudioUnit m_audioUnit; + Float64 m_clockFrequency; + UInt64 m_startTime; + AudioStreamBasicDescription m_streamFormat; + CoreAudioOutputBuffer *m_audioBuffer; + QAtomicInt m_audioThreadState; + QWaitCondition m_threadFinished; + QMutex m_mutex; + QTimer *m_intervalTimer; + CoreAudioDeviceInfo *m_audioDeviceInfo; + qreal m_cachedVolume; + + QAudio::Error m_errorCode; + QAudio::State m_stateCode; +}; + +QT_END_NAMESPACE + +#endif // IOSAUDIOOUTPUT_H diff --git a/src/plugins/coreaudio/coreaudiooutput.mm b/src/plugins/coreaudio/coreaudiooutput.mm new file mode 100644 index 000000000..bb0da57f3 --- /dev/null +++ b/src/plugins/coreaudio/coreaudiooutput.mm @@ -0,0 +1,732 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "coreaudiooutput.h" +#include "coreaudiosessionmanager.h" +#include "coreaudiodeviceinfo.h" +#include "coreaudioutils.h" + +#include +#include + +#include +#include +#if defined(Q_OS_OSX) +# include +#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_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.store(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_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_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 = QAudio::ActiveState; + 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) +{ + const qreal normalizedVolume = qBound(qreal(0.0), volume, qreal(1.0)); + m_cachedVolume = normalizedVolume; + if (!m_isOpen) { + return; + } + + //TODO: actually set the output volume here + //To set the output volume you need a handle to the mixer unit +} + +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(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; + } + 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 (m_errorCode != QAudio::NoError) + return false; + + if (m_isOpen) + return true; + +#if defined(Q_OS_OSX) + ComponentDescription componentDescription; + componentDescription.componentType = kAudioUnitType_Output; + componentDescription.componentSubType = kAudioUnitSubType_HALOutput; + componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + componentDescription.componentFlags = 0; + componentDescription.componentFlagsMask = 0; + + // Open + Component component = FindNextComponent(NULL, &componentDescription); + if (component == 0) { + qWarning() << "QAudioOutput: Failed to find HAL Output component"; + return false; + } + + if (OpenAComponent(component, &m_audioUnit) != noErr) { + qWarning() << "QAudioOutput: Unable to Open Output Component"; + return false; + } +#else //iOS + + AudioComponentDescription componentDescription; + componentDescription.componentType = kAudioUnitType_Output; + componentDescription.componentSubType = kAudioUnitSubType_RemoteIO; + 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; + } +#endif + + // 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); +#if defined(Q_OS_OSX) + CloseComponent(m_audioUnit); +#else //iOS + AudioComponentInstanceDispose(m_audioUnit); +#endif + } + + delete m_audioBuffer; +} + +void CoreAudioOutput::audioThreadStart() +{ + startTimers(); + m_audioThreadState.store(Running); + AudioOutputUnitStart(m_audioUnit); +} + +void CoreAudioOutput::audioThreadStop() +{ + stopTimers(); + if (m_audioThreadState.testAndSetAcquire(Running, Stopped)) + m_threadFinished.wait(&m_mutex); +} + +void CoreAudioOutput::audioThreadDrain() +{ + stopTimers(); + if (m_audioThreadState.testAndSetAcquire(Running, Draining)) + m_threadFinished.wait(&m_mutex); +} + +void CoreAudioOutput::audioDeviceStop() +{ + AudioOutputUnitStop(m_audioUnit); + m_audioThreadState.store(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" diff --git a/src/plugins/coreaudio/coreaudioplugin.h b/src/plugins/coreaudio/coreaudioplugin.h new file mode 100644 index 000000000..88b10c6e5 --- /dev/null +++ b/src/plugins/coreaudio/coreaudioplugin.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef IOSAUDIOPLUGIN_H +#define IOSAUDIOPLUGIN_H + +#include + +QT_BEGIN_NAMESPACE + +class CoreAudioPlugin : public QAudioSystemPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.audiosystemfactory/5.0" FILE "coreaudio.json") + +public: + explicit CoreAudioPlugin(QObject *parent = 0); + ~CoreAudioPlugin() {} + + QList availableDevices(QAudio::Mode mode) const Q_DECL_OVERRIDE; + QAbstractAudioInput *createInput(const QByteArray &device) Q_DECL_OVERRIDE; + QAbstractAudioOutput *createOutput(const QByteArray &device) Q_DECL_OVERRIDE; + QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/coreaudio/coreaudioplugin.mm b/src/plugins/coreaudio/coreaudioplugin.mm new file mode 100644 index 000000000..587815e6f --- /dev/null +++ b/src/plugins/coreaudio/coreaudioplugin.mm @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "coreaudioplugin.h" + +#include "coreaudiodeviceinfo.h" +#include "coreaudioinput.h" +#include "coreaudiooutput.h" + +QT_BEGIN_NAMESPACE + +CoreAudioPlugin::CoreAudioPlugin(QObject *parent) + : QAudioSystemPlugin(parent) +{ +} + + +QList CoreAudioPlugin::availableDevices(QAudio::Mode mode) const +{ + return CoreAudioDeviceInfo::availableDevices(mode); +} + + +QAbstractAudioInput *CoreAudioPlugin::createInput(const QByteArray &device) +{ + return new CoreAudioInput(device); +} + + +QAbstractAudioOutput *CoreAudioPlugin::createOutput(const QByteArray &device) +{ + return new CoreAudioOutput(device); +} + + +QAbstractAudioDeviceInfo *CoreAudioPlugin::createDeviceInfo(const QByteArray &device, QAudio::Mode mode) +{ + return new CoreAudioDeviceInfo(device, mode); +} + +QT_END_NAMESPACE + +#include "moc_coreaudioplugin.cpp" diff --git a/src/plugins/coreaudio/coreaudiosessionmanager.h b/src/plugins/coreaudio/coreaudiosessionmanager.h new file mode 100644 index 000000000..61d8967b1 --- /dev/null +++ b/src/plugins/coreaudio/coreaudiosessionmanager.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef IOSAUDIOSESSIONMANAGER_H +#define IOSAUDIOSESSIONMANAGER_H + +#include +#ifdef QT_DEBUG_COREAUDIO +# include +#endif + +@class CoreAudioSessionObserver; + +QT_BEGIN_NAMESPACE + +class CoreAudioSessionManager : public QObject +{ + Q_OBJECT +public: + enum AudioSessionCategorys { + Ambient, + SoloAmbient, + Playback, + Record, + PlayAndRecord, + AudioProcessing, + MultiRoute + }; + enum AudioSessionCategoryOptions { + None = 0, + MixWithOthers = 1, + DuckOthers = 2, + AllowBluetooth = 4, + DefaultToSpeaker = 8 + }; + enum AudioSessionModes { + Default, + VoiceChat, + GameChat, + VideoRecording, + Measurement, + MoviePlayback + }; + + static CoreAudioSessionManager& instance(); + + bool setActive(bool active); + bool setCategory(AudioSessionCategorys category, AudioSessionCategoryOptions options = None); + bool setMode(AudioSessionModes mode); + + AudioSessionCategorys category(); + AudioSessionModes mode(); + + QList inputDevices(); + QList outputDevices(); + + int inputChannelCount(); + int outputChannelCount(); + + float currentIOBufferDuration(); + float preferredSampleRate(); + +signals: + void activeChanged(); + void categoryChanged(); + void modeChanged(); + void routeChanged(); + void inputDevicesAvailableChanged(); + void outputDevicesAvailableChanged(); + +private: + CoreAudioSessionManager(); + ~CoreAudioSessionManager(); + CoreAudioSessionManager(CoreAudioSessionManager const ©); + CoreAudioSessionManager& operator =(CoreAudioSessionManager const ©); + + CoreAudioSessionObserver *m_sessionObserver; +}; + +#ifdef QT_DEBUG_COREAUDIO +QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category); +QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option); +QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionCategorys) +Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionCategoryOptions) +Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionModes) + +#endif // IOSAUDIOSESSIONMANAGER_H diff --git a/src/plugins/coreaudio/coreaudiosessionmanager.mm b/src/plugins/coreaudio/coreaudiosessionmanager.mm new file mode 100644 index 000000000..4b3bdb7dc --- /dev/null +++ b/src/plugins/coreaudio/coreaudiosessionmanager.mm @@ -0,0 +1,481 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "coreaudiosessionmanager.h" + +#import +#import + +QT_BEGIN_NAMESPACE + +@interface CoreAudioSessionObserver : NSObject +{ + CoreAudioSessionManager *m_sessionManager; + AVAudioSession *m_audioSession; +} + +@property (readonly, getter=sessionManager) CoreAudioSessionManager *m_sessionManager; +@property (readonly, getter=audioSession) AVAudioSession *m_audioSession; + +-(CoreAudioSessionObserver *)initWithAudioSessionManager:(CoreAudioSessionManager *)sessionManager; + +-(BOOL)activateAudio; +-(BOOL)deactivateAudio; + +//Notification handlers +-(void)audioSessionInterruption:(NSNotification *)notification; +-(void)audioSessionRouteChange:(NSNotification *)notification; +-(void)audioSessionMediaServicesWereReset:(NSNotification *)notification; + +@end //interface CoreAudioSessionObserver + +@implementation CoreAudioSessionObserver + +@synthesize m_sessionManager, m_audioSession; + +-(CoreAudioSessionObserver *)initWithAudioSessionManager:(CoreAudioSessionManager *)sessionManager +{ + if (!(self = [super init])) + return nil; + + self->m_sessionManager = sessionManager; + self->m_audioSession = [AVAudioSession sharedInstance]; + + //Set up observers + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioSessionInterruption:) + name:AVAudioSessionInterruptionNotification + object:self->m_audioSession]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioSessionMediaServicesWereReset:) + name:AVAudioSessionMediaServicesWereResetNotification + object:self->m_audioSession]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(audioSessionRouteChange:) + name:AVAudioSessionRouteChangeNotification + object:self->m_audioSession]; + + return self; +} + +-(void)dealloc +{ +#ifdef QT_DEBUG_COREAUDIO + qDebug() << Q_FUNC_INFO; +#endif + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVAudioSessionInterruptionNotification + object:self->m_audioSession]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVAudioSessionMediaServicesWereResetNotification + object:self->m_audioSession]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVAudioSessionRouteChangeNotification + object:self->m_audioSession]; + [super dealloc]; +} + +-(BOOL)activateAudio +{ + NSError *error = nil; + BOOL success = [self->m_audioSession setActive:YES error:&error]; + if (![self->m_audioSession setActive:YES error:&error]) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audio session activation failed: %s", [[error localizedDescription] UTF8String]); + } else { + qDebug("audio session activated"); +#endif + } + + return success; +} + +-(BOOL)deactivateAudio +{ + NSError *error = nil; + BOOL success = [m_audioSession setActive:NO error:&error]; +#ifdef QT_DEBUG_COREAUDIO + if (!success) { + qDebug("%s", [[error localizedDescription] UTF8String]); + } +#endif + return success; +} + +-(void)audioSessionInterruption:(NSNotification *)notification +{ + NSNumber *type = [[notification userInfo] valueForKey:AVAudioSessionInterruptionTypeKey]; + if ([type intValue] == AVAudioSessionInterruptionTypeBegan) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession Interuption begain"); +#endif + } else if ([type intValue] == AVAudioSessionInterruptionTypeEnded) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession Interuption ended"); +#endif + NSNumber *option = [[notification userInfo] valueForKey:AVAudioSessionInterruptionOptionKey]; + if ([option intValue] == AVAudioSessionInterruptionOptionShouldResume) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession is active and immediately ready to be used."); +#endif + } else { + [self activateAudio]; + } + } +} + +-(void)audioSessionMediaServicesWereReset:(NSNotification *)notification +{ + Q_UNUSED(notification) +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession Media Services were reset"); +#endif + //Reactivate audio when this occurs + [self activateAudio]; +} + +-(void)audioSessionRouteChange:(NSNotification *)notification +{ + NSNumber *reason = [[notification userInfo] valueForKey:AVAudioSessionRouteChangeReasonKey]; + NSUInteger reasonEnum = [reason intValue]; + + if (reasonEnum == AVAudioSessionRouteChangeReasonUnknown) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: unknown"); +#endif + } else if (reasonEnum == AVAudioSessionRouteChangeReasonNewDeviceAvailable) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: new device available"); +#endif + } else if (reasonEnum == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: old device unavailable"); +#endif + } else if (reasonEnum == AVAudioSessionRouteChangeReasonCategoryChange) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: category changed"); +#endif + } else if (reasonEnum == AVAudioSessionRouteChangeReasonOverride) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: override"); +#endif + } else if (reasonEnum == AVAudioSessionRouteChangeReasonWakeFromSleep) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: woken from sleep"); +#endif + } else if (reasonEnum == AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory) { +#ifdef QT_DEBUG_COREAUDIO + qDebug("audioSession route changed. reason: no suitable route for category"); +#endif + } + +} + +@end //implementation CoreAudioSessionObserver + +CoreAudioSessionManager::CoreAudioSessionManager() : + QObject(0) +{ + m_sessionObserver = [[CoreAudioSessionObserver alloc] initWithAudioSessionManager:this]; + setActive(true); + setCategory(CoreAudioSessionManager::PlayAndRecord, CoreAudioSessionManager::MixWithOthers); +} + +CoreAudioSessionManager::~CoreAudioSessionManager() +{ +#ifdef QT_DEBUG_COREAUDIO + qDebug() << Q_FUNC_INFO; +#endif + [m_sessionObserver release]; +} + + +CoreAudioSessionManager &CoreAudioSessionManager::instance() +{ + static CoreAudioSessionManager instance; + return instance; +} + +bool CoreAudioSessionManager::setActive(bool active) +{ + if (active) { + return [m_sessionObserver activateAudio]; + } else { + return [m_sessionObserver deactivateAudio]; + } +} + +bool CoreAudioSessionManager::setCategory(CoreAudioSessionManager::AudioSessionCategorys category, CoreAudioSessionManager::AudioSessionCategoryOptions options) +{ + NSString *targetCategory = nil; + + switch (category) { + case CoreAudioSessionManager::Ambient: + targetCategory = AVAudioSessionCategoryAmbient; + break; + case CoreAudioSessionManager::SoloAmbient: + targetCategory = AVAudioSessionCategorySoloAmbient; + break; + case CoreAudioSessionManager::Playback: + targetCategory = AVAudioSessionCategoryPlayback; + break; + case CoreAudioSessionManager::Record: + targetCategory = AVAudioSessionCategoryRecord; + break; + case CoreAudioSessionManager::PlayAndRecord: + targetCategory = AVAudioSessionCategoryPlayAndRecord; + break; + case CoreAudioSessionManager::AudioProcessing: + targetCategory = AVAudioSessionCategoryAudioProcessing; + break; + case CoreAudioSessionManager::MultiRoute: + targetCategory = AVAudioSessionCategoryMultiRoute; + break; + } + + if (targetCategory == nil) + return false; + + return [[m_sessionObserver audioSession] setCategory:targetCategory + withOptions:(AVAudioSessionCategoryOptions)options + error:nil]; +} + +bool CoreAudioSessionManager::setMode(CoreAudioSessionManager::AudioSessionModes mode) +{ + NSString *targetMode = nil; + switch (mode) { + case CoreAudioSessionManager::Default: + targetMode = AVAudioSessionModeDefault; + break; + case CoreAudioSessionManager::VoiceChat: + targetMode = AVAudioSessionModeVoiceChat; + break; + case CoreAudioSessionManager::GameChat: + targetMode = AVAudioSessionModeGameChat; + break; + case CoreAudioSessionManager::VideoRecording: + targetMode = AVAudioSessionModeVideoRecording; + break; + case CoreAudioSessionManager::Measurement: + targetMode = AVAudioSessionModeMeasurement; + break; + case CoreAudioSessionManager::MoviePlayback: + targetMode = AVAudioSessionModeMoviePlayback; + break; + } + + if (targetMode == nil) + return false; + + return [[m_sessionObserver audioSession] setMode:targetMode error:nil]; + +} + +CoreAudioSessionManager::AudioSessionCategorys CoreAudioSessionManager::category() +{ + NSString *category = [[m_sessionObserver audioSession] category]; + AudioSessionCategorys localCategory = Ambient; + + if (category == AVAudioSessionCategoryAmbient) { + localCategory = Ambient; + } else if (category == AVAudioSessionCategorySoloAmbient) { + localCategory = SoloAmbient; + } else if (category == AVAudioSessionCategoryPlayback) { + localCategory = Playback; + } else if (category == AVAudioSessionCategoryRecord) { + localCategory = Record; + } else if (category == AVAudioSessionCategoryPlayAndRecord) { + localCategory = PlayAndRecord; + } else if (category == AVAudioSessionCategoryAudioProcessing) { + localCategory = AudioProcessing; + } else if (category == AVAudioSessionCategoryMultiRoute) { + localCategory = MultiRoute; + } + + return localCategory; +} + +CoreAudioSessionManager::AudioSessionModes CoreAudioSessionManager::mode() +{ + NSString *mode = [[m_sessionObserver audioSession] mode]; + AudioSessionModes localMode = Default; + + if (mode == AVAudioSessionModeDefault) { + localMode = Default; + } else if (mode == AVAudioSessionModeVoiceChat) { + localMode = VoiceChat; + } else if (mode == AVAudioSessionModeGameChat) { + localMode = GameChat; + } else if (mode == AVAudioSessionModeVideoRecording) { + localMode = VideoRecording; + } else if (mode == AVAudioSessionModeMeasurement) { + localMode = Measurement; + } else if (mode == AVAudioSessionModeMoviePlayback) { + localMode = MoviePlayback; + } + + return localMode; +} + +QList CoreAudioSessionManager::inputDevices() +{ + //TODO: Add support for USB input devices + //Right now the default behavior on iOS is to have only one input route + //at a time. + QList inputDevices; + inputDevices << "default"; + return inputDevices; +} + +QList CoreAudioSessionManager::outputDevices() +{ + //TODO: Add support for USB output devices + //Right now the default behavior on iOS is to have only one output route + //at a time. + QList outputDevices; + outputDevices << "default"; + return outputDevices; +} + +int CoreAudioSessionManager::inputChannelCount() +{ + return [[m_sessionObserver audioSession] inputNumberOfChannels]; +} + +int CoreAudioSessionManager::outputChannelCount() +{ + return [[m_sessionObserver audioSession] outputNumberOfChannels]; +} + +float CoreAudioSessionManager::currentIOBufferDuration() +{ + return [[m_sessionObserver audioSession] IOBufferDuration]; +} + +float CoreAudioSessionManager::preferredSampleRate() +{ + return [[m_sessionObserver audioSession] preferredSampleRate]; +} + +#ifdef QT_DEBUG_COREAUDIO +QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category) +{ + QDebug output = dbg.nospace(); + switch (category) { + case CoreAudioSessionManager::Ambient: + output << "AudioSessionCategoryAmbient"; + break; + case CoreAudioSessionManager::SoloAmbient: + output << "AudioSessionCategorySoloAmbient"; + break; + case CoreAudioSessionManager::Playback: + output << "AudioSessionCategoryPlayback"; + break; + case CoreAudioSessionManager::Record: + output << "AudioSessionCategoryRecord"; + break; + case CoreAudioSessionManager::PlayAndRecord: + output << "AudioSessionCategoryPlayAndRecord"; + break; + case CoreAudioSessionManager::AudioProcessing: + output << "AudioSessionCategoryAudioProcessing"; + break; + case CoreAudioSessionManager::MultiRoute: + output << "AudioSessionCategoryMultiRoute"; + break; + } + return output; +} + +QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option) +{ + QDebug output = dbg.nospace(); + switch (option) { + case CoreAudioSessionManager::None: + output << "AudioSessionCategoryOptionNone"; + break; + case CoreAudioSessionManager::MixWithOthers: + output << "AudioSessionCategoryOptionMixWithOthers"; + break; + case CoreAudioSessionManager::DuckOthers: + output << "AudioSessionCategoryOptionDuckOthers"; + break; + case CoreAudioSessionManager::AllowBluetooth: + output << "AudioSessionCategoryOptionAllowBluetooth"; + break; + case CoreAudioSessionManager::DefaultToSpeaker: + output << "AudioSessionCategoryOptionDefaultToSpeaker"; + break; + } + return output; +} + +QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode) +{ + QDebug output = dbg.nospace(); + switch (mode) { + case CoreAudioSessionManager::Default: + output << "AudioSessionModeDefault"; + break; + case CoreAudioSessionManager::VoiceChat: + output << "AudioSessionModeVoiceChat"; + break; + case CoreAudioSessionManager::GameChat: + output << "AudioSessionModeGameChat"; + break; + case CoreAudioSessionManager::VideoRecording: + output << "AudioSessionModeVideoRecording"; + break; + case CoreAudioSessionManager::Measurement: + output << "AudioSessionModeMeasurement"; + break; + case CoreAudioSessionManager::MoviePlayback: + output << "AudioSessionModeMoviePlayback"; + break; + } + return output; +} +#endif + +QT_END_NAMESPACE + +#include "moc_coreaudiosessionmanager.cpp" diff --git a/src/plugins/coreaudio/coreaudioutils.h b/src/plugins/coreaudio/coreaudioutils.h new file mode 100644 index 000000000..8b7188c5d --- /dev/null +++ b/src/plugins/coreaudio/coreaudioutils.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef IOSAUDIOUTILS_H +#define IOSAUDIOUTILS_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class CoreAudioUtils +{ +public: + static quint64 currentTime(); + static double frequency(); + static QAudioFormat toQAudioFormat(const AudioStreamBasicDescription& streamFormat); + static AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat); + +private: + static void initialize(); + static double sFrequency; + static bool sIsInitialized; +}; + +class CoreAudioRingBuffer +{ +public: + typedef QPair Region; + + CoreAudioRingBuffer(int bufferSize); + ~CoreAudioRingBuffer(); + + Region acquireReadRegion(int size); + void releaseReadRegion(Region const& region); + Region acquireWriteRegion(int size); + void releaseWriteRegion(Region const& region); + + int used() const; + int free() const; + int size() const; + + void reset(); + +private: + int m_bufferSize; + int m_readPos; + int m_writePos; + char* m_buffer; + QAtomicInt m_bufferUsed; +}; + +QT_END_NAMESPACE + +#endif // IOSAUDIOUTILS_H diff --git a/src/plugins/coreaudio/coreaudioutils.mm b/src/plugins/coreaudio/coreaudioutils.mm new file mode 100644 index 000000000..d43303a1d --- /dev/null +++ b/src/plugins/coreaudio/coreaudioutils.mm @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "coreaudioutils.h" +#include + +QT_BEGIN_NAMESPACE + +double CoreAudioUtils::sFrequency = 0.0; +bool CoreAudioUtils::sIsInitialized = false; + +void CoreAudioUtils::initialize() +{ + struct mach_timebase_info timeBaseInfo; + mach_timebase_info(&timeBaseInfo); + sFrequency = static_cast(timeBaseInfo.denom) / static_cast(timeBaseInfo.numer); + sFrequency *= 1000000000.0; + + sIsInitialized = true; +} + + +quint64 CoreAudioUtils::currentTime() +{ + return mach_absolute_time(); +} + +double CoreAudioUtils::frequency() +{ + if (!sIsInitialized) + initialize(); + return sFrequency; +} + +QAudioFormat CoreAudioUtils::toQAudioFormat(AudioStreamBasicDescription const& sf) +{ + QAudioFormat audioFormat; + + audioFormat.setSampleRate(sf.mSampleRate); + audioFormat.setChannelCount(sf.mChannelsPerFrame); + audioFormat.setSampleSize(sf.mBitsPerChannel); + audioFormat.setCodec(QString::fromLatin1("audio/pcm")); + audioFormat.setByteOrder((sf.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0 ? QAudioFormat::BigEndian : QAudioFormat::LittleEndian); + QAudioFormat::SampleType type = QAudioFormat::UnSignedInt; + if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0) + type = QAudioFormat::SignedInt; + else if ((sf.mFormatFlags & kAudioFormatFlagIsFloat) != 0) + type = QAudioFormat::Float; + audioFormat.setSampleType(type); + + return audioFormat; +} + +AudioStreamBasicDescription CoreAudioUtils::toAudioStreamBasicDescription(QAudioFormat const& audioFormat) +{ + AudioStreamBasicDescription sf; + + sf.mFormatFlags = kAudioFormatFlagIsPacked; + sf.mSampleRate = audioFormat.sampleRate(); + sf.mFramesPerPacket = 1; + sf.mChannelsPerFrame = audioFormat.channelCount(); + sf.mBitsPerChannel = audioFormat.sampleSize(); + sf.mBytesPerFrame = sf.mChannelsPerFrame * (sf.mBitsPerChannel / 8); + sf.mBytesPerPacket = sf.mFramesPerPacket * sf.mBytesPerFrame; + sf.mFormatID = kAudioFormatLinearPCM; + + switch (audioFormat.sampleType()) { + case QAudioFormat::SignedInt: sf.mFormatFlags |= kAudioFormatFlagIsSignedInteger; break; + case QAudioFormat::UnSignedInt: /* default */ break; + case QAudioFormat::Float: sf.mFormatFlags |= kAudioFormatFlagIsFloat; break; + case QAudioFormat::Unknown: default: break; + } + + if (audioFormat.byteOrder() == QAudioFormat::BigEndian) + sf.mFormatFlags |= kAudioFormatFlagIsBigEndian; + + return sf; +} + +// QAudioRingBuffer +CoreAudioRingBuffer::CoreAudioRingBuffer(int bufferSize): + m_bufferSize(bufferSize) +{ + m_buffer = new char[m_bufferSize]; + reset(); +} + +CoreAudioRingBuffer::~CoreAudioRingBuffer() +{ + delete m_buffer; +} + +CoreAudioRingBuffer::Region CoreAudioRingBuffer::acquireReadRegion(int size) +{ + const int used = m_bufferUsed.fetchAndAddAcquire(0); + + if (used > 0) { + const int readSize = qMin(size, qMin(m_bufferSize - m_readPos, used)); + + return readSize > 0 ? Region(m_buffer + m_readPos, readSize) : Region(0, 0); + } + + return Region(0, 0); +} + +void CoreAudioRingBuffer::releaseReadRegion(const CoreAudioRingBuffer::Region ®ion) +{ + m_readPos = (m_readPos + region.second) % m_bufferSize; + + m_bufferUsed.fetchAndAddRelease(-region.second); +} + +CoreAudioRingBuffer::Region CoreAudioRingBuffer::acquireWriteRegion(int size) +{ + const int free = m_bufferSize - m_bufferUsed.fetchAndAddAcquire(0); + + Region output; + + if (free > 0) { + const int writeSize = qMin(size, qMin(m_bufferSize - m_writePos, free)); + output = writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region(0, 0); + } else { + output = Region(0, 0); + } +#ifdef QT_DEBUG_COREAUDIO + qDebug("acquireWriteRegion(%d) free: %d returning Region(%p, %d)", size, free, output.first, output.second); +#endif + return output; +} +void CoreAudioRingBuffer::releaseWriteRegion(const CoreAudioRingBuffer::Region ®ion) +{ + m_writePos = (m_writePos + region.second) % m_bufferSize; + + m_bufferUsed.fetchAndAddRelease(region.second); +#ifdef QT_DEBUG_COREAUDIO + qDebug("releaseWriteRegion(%p,%d): m_writePos:%d", region.first, region.second, m_writePos); +#endif +} + +int CoreAudioRingBuffer::used() const +{ + return m_bufferUsed.load(); +} + +int CoreAudioRingBuffer::free() const +{ + return m_bufferSize - m_bufferUsed.load(); +} + +int CoreAudioRingBuffer::size() const +{ + return m_bufferSize; +} + +void CoreAudioRingBuffer::reset() +{ + m_readPos = 0; + m_writePos = 0; + m_bufferUsed.store(0); +} + +QT_END_NAMESPACE -- cgit v1.2.3