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/multimedia/audio/audio.pri | 14 - src/multimedia/audio/qaudio_mac.cpp | 130 --- src/multimedia/audio/qaudio_mac_p.h | 135 --- src/multimedia/audio/qaudiodevicefactory.cpp | 16 +- src/multimedia/audio/qaudiodeviceinfo_mac_p.cpp | 351 ------- src/multimedia/audio/qaudiodeviceinfo_mac_p.h | 93 -- src/multimedia/audio/qaudioinput_mac_p.cpp | 1067 -------------------- src/multimedia/audio/qaudioinput_mac_p.h | 173 ---- src/multimedia/audio/qaudiooutput_mac_p.cpp | 759 -------------- src/multimedia/audio/qaudiooutput_mac_p.h | 172 ---- 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 ++++ src/plugins/plugins.pro | 2 +- .../integration/qaudiooutput/tst_qaudiooutput.cpp | 5 +- 26 files changed, 3796 insertions(+), 2907 deletions(-) delete mode 100644 src/multimedia/audio/qaudio_mac.cpp delete mode 100644 src/multimedia/audio/qaudio_mac_p.h delete mode 100644 src/multimedia/audio/qaudiodeviceinfo_mac_p.cpp delete mode 100644 src/multimedia/audio/qaudiodeviceinfo_mac_p.h delete mode 100644 src/multimedia/audio/qaudioinput_mac_p.cpp delete mode 100644 src/multimedia/audio/qaudioinput_mac_p.h delete mode 100644 src/multimedia/audio/qaudiooutput_mac_p.cpp delete mode 100644 src/multimedia/audio/qaudiooutput_mac_p.h 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 diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri index f0706e3aa..f76d13264 100644 --- a/src/multimedia/audio/audio.pri +++ b/src/multimedia/audio/audio.pri @@ -39,20 +39,6 @@ SOURCES += \ audio/qaudiodecoder.cpp \ audio/qaudiohelpers.cpp -mac:!ios { - - PRIVATE_HEADERS += audio/qaudioinput_mac_p.h \ - audio/qaudiooutput_mac_p.h \ - audio/qaudiodeviceinfo_mac_p.h \ - audio/qaudio_mac_p.h - - SOURCES += audio/qaudiodeviceinfo_mac_p.cpp \ - audio/qaudiooutput_mac_p.cpp \ - audio/qaudioinput_mac_p.cpp \ - audio/qaudio_mac.cpp - LIBS += -framework ApplicationServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox -} - win32 { PRIVATE_HEADERS += audio/qaudioinput_win32_p.h audio/qaudiooutput_win32_p.h audio/qaudiodeviceinfo_win32_p.h SOURCES += audio/qaudiodeviceinfo_win32_p.cpp \ diff --git a/src/multimedia/audio/qaudio_mac.cpp b/src/multimedia/audio/qaudio_mac.cpp deleted file mode 100644 index 3084dfe03..000000000 --- a/src/multimedia/audio/qaudio_mac.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/**************************************************************************** -** -** 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 "qaudio_mac_p.h" - -QT_BEGIN_NAMESPACE - -// Conversion -QAudioFormat 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 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 -QAudioRingBuffer::QAudioRingBuffer(int bufferSize): - m_bufferSize(bufferSize) -{ - m_buffer = new char[m_bufferSize]; - reset(); -} - -QAudioRingBuffer::~QAudioRingBuffer() -{ - delete m_buffer; -} - -int QAudioRingBuffer::used() const -{ - return m_bufferUsed.load(); -} - -int QAudioRingBuffer::free() const -{ - return m_bufferSize - m_bufferUsed.load(); -} - -int QAudioRingBuffer::size() const -{ - return m_bufferSize; -} - -void QAudioRingBuffer::reset() -{ - m_readPos = 0; - m_writePos = 0; - m_bufferUsed.store(0); -} - -QT_END_NAMESPACE - - diff --git a/src/multimedia/audio/qaudio_mac_p.h b/src/multimedia/audio/qaudio_mac_p.h deleted file mode 100644 index 5a22bbf44..000000000 --- a/src/multimedia/audio/qaudio_mac_p.h +++ /dev/null @@ -1,135 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - - -#ifndef QAUDIO_MAC_P_H -#define QAUDIO_MAC_P_H - -#include - -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -extern QAudioFormat toQAudioFormat(const AudioStreamBasicDescription& streamFormat); -extern AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat); - -class QAudioRingBuffer -{ -public: - typedef QPair Region; - - QAudioRingBuffer(int bufferSize); - ~QAudioRingBuffer(); - - Region 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 releaseReadRegion(Region const& region) - { - m_readPos = (m_readPos + region.second) % m_bufferSize; - - m_bufferUsed.fetchAndAddRelease(-region.second); - } - - Region acquireWriteRegion(int size) - { - const int free = m_bufferSize - m_bufferUsed.fetchAndAddAcquire(0); - - if (free > 0) { - const int writeSize = qMin(size, qMin(m_bufferSize - m_writePos, free)); - - return writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region(0, 0); - } - - return Region(0, 0); - } - - void releaseWriteRegion(Region const& region) - { - m_writePos = (m_writePos + region.second) % m_bufferSize; - - m_bufferUsed.fetchAndAddRelease(region.second); - } - - 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 // QAUDIO_MAC_P_H - - diff --git a/src/multimedia/audio/qaudiodevicefactory.cpp b/src/multimedia/audio/qaudiodevicefactory.cpp index 76f122790..e2d4ec3e8 100644 --- a/src/multimedia/audio/qaudiodevicefactory.cpp +++ b/src/multimedia/audio/qaudiodevicefactory.cpp @@ -52,10 +52,6 @@ #include "qaudiodeviceinfo_win32_p.h" #include "qaudiooutput_win32_p.h" #include "qaudioinput_win32_p.h" -#elif defined(Q_OS_MAC) && !defined(Q_OS_IOS) -#include "qaudiodeviceinfo_mac_p.h" -#include "qaudiooutput_mac_p.h" -#include "qaudioinput_mac_p.h" #elif defined(HAS_ALSA) #include "qaudiodeviceinfo_alsa_p.h" #include "qaudiooutput_alsa_p.h" @@ -137,7 +133,7 @@ QList QAudioDeviceFactory::availableDevices(QAudio::Mode mode) { QList devices; #ifndef QT_NO_AUDIO_BACKEND -#if (defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || defined(HAS_ALSA)) +#if (defined(Q_OS_WIN) || defined(HAS_ALSA)) foreach (const QByteArray &handle, QAudioDeviceInfoInternal::availableDevices(mode)) devices << QAudioDeviceInfo(QLatin1String("builtin"), handle, mode); #endif @@ -170,7 +166,7 @@ QAudioDeviceInfo QAudioDeviceFactory::defaultInputDevice() #endif #ifndef QT_NO_AUDIO_BACKEND -#if (defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || defined(HAS_ALSA)) +#if (defined(Q_OS_WIN) || defined(HAS_ALSA)) return QAudioDeviceInfo(QLatin1String("builtin"), QAudioDeviceInfoInternal::defaultInputDevice(), QAudio::AudioInput); #endif #endif @@ -190,7 +186,7 @@ QAudioDeviceInfo QAudioDeviceFactory::defaultOutputDevice() #endif #ifndef QT_NO_AUDIO_BACKEND -#if (defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || defined(HAS_ALSA)) +#if (defined(Q_OS_WIN) || defined(HAS_ALSA)) return QAudioDeviceInfo(QLatin1String("builtin"), QAudioDeviceInfoInternal::defaultOutputDevice(), QAudio::AudioOutput); #endif #endif @@ -202,7 +198,7 @@ QAbstractAudioDeviceInfo* QAudioDeviceFactory::audioDeviceInfo(const QString &re QAbstractAudioDeviceInfo *rc = 0; #ifndef QT_NO_AUDIO_BACKEND -#if (defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || defined(HAS_ALSA)) +#if (defined(Q_OS_WIN) || defined(HAS_ALSA)) if (realm == QLatin1String("builtin")) return new QAudioDeviceInfoInternal(handle, mode); #endif @@ -234,7 +230,7 @@ QAbstractAudioInput* QAudioDeviceFactory::createInputDevice(QAudioDeviceInfo con if (deviceInfo.isNull()) return new QNullInputDevice(); #ifndef QT_NO_AUDIO_BACKEND -#if (defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || defined(HAS_ALSA)) +#if (defined(Q_OS_WIN) || defined(HAS_ALSA)) if (deviceInfo.realm() == QLatin1String("builtin")) { QAbstractAudioInput* p = new QAudioInputPrivate(deviceInfo.handle()); if (p) p->setFormat(format); @@ -261,7 +257,7 @@ QAbstractAudioOutput* QAudioDeviceFactory::createOutputDevice(QAudioDeviceInfo c if (deviceInfo.isNull()) return new QNullOutputDevice(); #ifndef QT_NO_AUDIO_BACKEND -#if (defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS)) || defined(HAS_ALSA)) +#if (defined(Q_OS_WIN) || defined(HAS_ALSA)) if (deviceInfo.realm() == QLatin1String("builtin")) { QAbstractAudioOutput* p = new QAudioOutputPrivate(deviceInfo.handle()); if (p) p->setFormat(format); diff --git a/src/multimedia/audio/qaudiodeviceinfo_mac_p.cpp b/src/multimedia/audio/qaudiodeviceinfo_mac_p.cpp deleted file mode 100644 index 0afa65593..000000000 --- a/src/multimedia/audio/qaudiodeviceinfo_mac_p.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// INTERNAL USE ONLY: Do NOT use for any other purpose. -// - -#include -#include -#include -#include -#include - -#include -#include "qaudio_mac_p.h" -#include "qaudiodeviceinfo_mac_p.h" - - - -QT_BEGIN_NAMESPACE - -// 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); -} - -QAudioDeviceInfoInternal::QAudioDeviceInfoInternal(QByteArray const& handle, QAudio::Mode) -{ - QDataStream ds(handle); - quint32 did, tm; - - ds >> did >> tm >> name; - deviceId = AudioDeviceID(did); - mode = QAudio::Mode(tm); -} - -bool QAudioDeviceInfoInternal::isFormatSupported(const QAudioFormat& format) const -{ - QAudioDeviceInfoInternal *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()); -} - -QAudioFormat QAudioDeviceInfoInternal::preferredFormat() const -{ - QAudioFormat rc; - - UInt32 propSize = 0; - - if (AudioDeviceGetPropertyInfo(deviceId, - 0, - mode == QAudio::AudioInput, - kAudioDevicePropertyStreams, - &propSize, - 0) == noErr) { - - const int sc = propSize / sizeof(AudioStreamID); - - if (sc > 0) { - AudioStreamID* streams = new AudioStreamID[sc]; - - if (AudioDeviceGetProperty(deviceId, - 0, - mode == QAudio::AudioInput, - kAudioDevicePropertyStreams, - &propSize, - streams) == noErr) { - - for (int i = 0; i < sc; ++i) { - if (AudioStreamGetPropertyInfo(streams[i], - 0, - kAudioStreamPropertyPhysicalFormat, - &propSize, - 0) == noErr) { - - AudioStreamBasicDescription sf; - - if (AudioStreamGetProperty(streams[i], - 0, - kAudioStreamPropertyPhysicalFormat, - &propSize, - &sf) == noErr) { - rc = toQAudioFormat(sf); - break; - } - } - } - } - - delete streams; - } - } - - return rc; -} - -QString QAudioDeviceInfoInternal::deviceName() const -{ - return name; -} - -QStringList QAudioDeviceInfoInternal::supportedCodecs() -{ - return QStringList() << QString::fromLatin1("audio/pcm"); -} - -QList QAudioDeviceInfoInternal::supportedSampleRates() -{ - QSet rc; - - // Add some common frequencies - rc << 8000 << 11025 << 22050 << 44100; - - // - UInt32 propSize = 0; - - if (AudioDeviceGetPropertyInfo(deviceId, - 0, - mode == QAudio::AudioInput, - kAudioDevicePropertyAvailableNominalSampleRates, - &propSize, - 0) == noErr) { - - const int pc = propSize / sizeof(AudioValueRange); - - if (pc > 0) { - AudioValueRange* vr = new AudioValueRange[pc]; - - if (AudioDeviceGetProperty(deviceId, - 0, - mode == QAudio::AudioInput, - kAudioDevicePropertyAvailableNominalSampleRates, - &propSize, - vr) == noErr) { - - for (int i = 0; i < pc; ++i) - rc << vr[i].mMaximum; - } - - delete vr; - } - } - - return rc.toList(); -} - -QList QAudioDeviceInfoInternal::supportedChannelCounts() -{ - QList rc; - - // Can mix down to 1 channel - rc << 1; - - UInt32 propSize = 0; - int channels = 0; - - if (AudioDeviceGetPropertyInfo(deviceId, - 0, - mode == QAudio::AudioInput, - kAudioDevicePropertyStreamConfiguration, - &propSize, - 0) == noErr) { - - AudioBufferList* audioBufferList = static_cast(malloc(propSize)); - - if (audioBufferList != 0) { - if (AudioDeviceGetProperty(deviceId, - 0, - mode == QAudio::AudioInput, - kAudioDevicePropertyStreamConfiguration, - &propSize, - audioBufferList) == noErr) { - - for (int i = 0; i < int(audioBufferList->mNumberBuffers); ++i) { - channels += audioBufferList->mBuffers[i].mNumberChannels; - rc << channels; - } - } - - free(audioBufferList); - } - } - - return rc; -} - -QList QAudioDeviceInfoInternal::supportedSampleSizes() -{ - return QList() << 8 << 16 << 24 << 32 << 64; -} - -QList QAudioDeviceInfoInternal::supportedByteOrders() -{ - return QList() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian; -} - -QList QAudioDeviceInfoInternal::supportedSampleTypes() -{ - return QList() << QAudioFormat::SignedInt << QAudioFormat::UnSignedInt << QAudioFormat::Float; -} - -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; - - // Id - ds << quint32(audioDevice); - - // Mode - size = sizeof(AudioStreamBasicDescription); - if (AudioDeviceGetProperty(audioDevice, 0, isInput, kAudioDevicePropertyStreamFormat, - &size, &sf) != noErr) { - return QByteArray(); - } - ds << quint32(mode); - - // Name - size = sizeof(CFStringRef); - if (AudioDeviceGetProperty(audioDevice, 0, isInput, kAudioObjectPropertyName, - &size, &name) != noErr) { - qWarning() << "QAudioDeviceInfo: Unable to find device name"; - return QByteArray(); - } - ds << cfStringToQString(name); - - CFRelease(name); - - return device; -} - -QByteArray QAudioDeviceInfoInternal::defaultInputDevice() -{ - AudioDeviceID audioDevice; - UInt32 size = sizeof(audioDevice); - - if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, - &audioDevice) != noErr) { - qWarning() << "QAudioDeviceInfo: Unable to find default input device"; - return QByteArray(); - } - - return get_device_info(audioDevice, QAudio::AudioInput); -} - -QByteArray QAudioDeviceInfoInternal::defaultOutputDevice() -{ - AudioDeviceID audioDevice; - UInt32 size = sizeof(audioDevice); - - if (AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, - &audioDevice) != noErr) { - qWarning() << "QAudioDeviceInfo: Unable to find default output device"; - return QByteArray(); - } - - return get_device_info(audioDevice, QAudio::AudioOutput); -} - -QList QAudioDeviceInfoInternal::availableDevices(QAudio::Mode mode) -{ - QList devices; - - UInt32 propSize = 0; - - if (AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propSize, 0) == noErr) { - - const int dc = propSize / sizeof(AudioDeviceID); - - if (dc > 0) { - AudioDeviceID* audioDevices = new AudioDeviceID[dc]; - - if (AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &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; - } - } - - return devices; -} - - -QT_END_NAMESPACE - diff --git a/src/multimedia/audio/qaudiodeviceinfo_mac_p.h b/src/multimedia/audio/qaudiodeviceinfo_mac_p.h deleted file mode 100644 index 2afb21285..000000000 --- a/src/multimedia/audio/qaudiodeviceinfo_mac_p.h +++ /dev/null @@ -1,93 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - - -#ifndef QDEVICEINFO_MAC_P_H -#define QDEVICEINFO_MAC_P_H - -#include - -#include - -QT_BEGIN_NAMESPACE - - -class QAudioDeviceInfoInternal : public QAbstractAudioDeviceInfo -{ -public: - AudioDeviceID deviceId; - QString name; - QAudio::Mode mode; - - QAudioDeviceInfoInternal(QByteArray const& handle, QAudio::Mode mode); - - bool isFormatSupported(const QAudioFormat& format) const; - QAudioFormat preferredFormat() 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); -}; - -QT_END_NAMESPACE - -#endif // QDEVICEINFO_MAC_P_H diff --git a/src/multimedia/audio/qaudioinput_mac_p.cpp b/src/multimedia/audio/qaudioinput_mac_p.cpp deleted file mode 100644 index ea5be5d30..000000000 --- a/src/multimedia/audio/qaudioinput_mac_p.cpp +++ /dev/null @@ -1,1067 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// INTERNAL USE ONLY: Do NOT use for any other purpose. -// - -#include -#include -#include - -#include - -#include "qaudio_mac_p.h" -#include "qaudioinput_mac_p.h" -#include "qaudiodeviceinfo_mac_p.h" -#include "qaudiohelpers_p.h" - -QT_BEGIN_NAMESPACE - - -namespace QtMultimediaInternal -{ - -static const int default_buffer_size = 4 * 1024; - -class QAudioBufferList -{ -public: - QAudioBufferList(AudioStreamBasicDescription const& streamFormat): - owner(false), - sf(streamFormat) - { - const bool isInterleaved = (sf.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; - const int numberOfBuffers = isInterleaved ? 1 : sf.mChannelsPerFrame; - - dataSize = 0; - - bfs = reinterpret_cast(malloc(sizeof(AudioBufferList) + - (sizeof(AudioBuffer) * numberOfBuffers))); - - bfs->mNumberBuffers = numberOfBuffers; - for (int i = 0; i < numberOfBuffers; ++i) { - bfs->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1; - bfs->mBuffers[i].mDataByteSize = 0; - bfs->mBuffers[i].mData = 0; - } - } - - QAudioBufferList(AudioStreamBasicDescription const& streamFormat, char* buffer, int bufferSize): - owner(false), - sf(streamFormat), - bfs(0) - { - dataSize = bufferSize; - - bfs = reinterpret_cast(malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer))); - - bfs->mNumberBuffers = 1; - bfs->mBuffers[0].mNumberChannels = 1; - bfs->mBuffers[0].mDataByteSize = dataSize; - bfs->mBuffers[0].mData = buffer; - } - - QAudioBufferList(AudioStreamBasicDescription const& streamFormat, int framesToBuffer): - owner(true), - sf(streamFormat), - bfs(0) - { - const bool isInterleaved = (sf.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; - const int numberOfBuffers = isInterleaved ? 1 : sf.mChannelsPerFrame; - - dataSize = framesToBuffer * sf.mBytesPerFrame; - - bfs = reinterpret_cast(malloc(sizeof(AudioBufferList) + - (sizeof(AudioBuffer) * numberOfBuffers))); - bfs->mNumberBuffers = numberOfBuffers; - for (int i = 0; i < numberOfBuffers; ++i) { - bfs->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1; - bfs->mBuffers[i].mDataByteSize = dataSize; - bfs->mBuffers[i].mData = malloc(dataSize); - } - } - - ~QAudioBufferList() - { - if (owner) { - for (UInt32 i = 0; i < bfs->mNumberBuffers; ++i) - free(bfs->mBuffers[i].mData); - } - - free(bfs); - } - - AudioBufferList* audioBufferList() const - { - return bfs; - } - - char* data(int buffer = 0) const - { - return static_cast(bfs->mBuffers[buffer].mData); - } - - qint64 bufferSize(int buffer = 0) const - { - return bfs->mBuffers[buffer].mDataByteSize; - } - - int frameCount(int buffer = 0) const - { - return bfs->mBuffers[buffer].mDataByteSize / sf.mBytesPerFrame; - } - - int packetCount(int buffer = 0) const - { - return bfs->mBuffers[buffer].mDataByteSize / sf.mBytesPerPacket; - } - - int packetSize() const - { - return sf.mBytesPerPacket; - } - - void reset() - { - for (UInt32 i = 0; i < bfs->mNumberBuffers; ++i) { - bfs->mBuffers[i].mDataByteSize = dataSize; - bfs->mBuffers[i].mData = 0; - } - } - -private: - bool owner; - int dataSize; - AudioStreamBasicDescription sf; - AudioBufferList* bfs; -}; - -class QAudioPacketFeeder -{ -public: - QAudioPacketFeeder(QAudioBufferList* abl): - audioBufferList(abl) - { - totalPackets = audioBufferList->packetCount(); - position = 0; - } - - bool feed(AudioBufferList& dst, UInt32& packetCount) - { - if (position == totalPackets) { - dst.mBuffers[0].mDataByteSize = 0; - packetCount = 0; - return false; - } - - if (totalPackets - position < packetCount) - packetCount = totalPackets - position; - - dst.mBuffers[0].mDataByteSize = packetCount * audioBufferList->packetSize(); - dst.mBuffers[0].mData = audioBufferList->data() + (position * audioBufferList->packetSize()); - - position += packetCount; - - return true; - } - - bool empty() const - { - return position == totalPackets; - } - -private: - UInt32 totalPackets; - UInt32 position; - QAudioBufferList* audioBufferList; -}; - -class QAudioInputBuffer : public QObject -{ - Q_OBJECT - -public: - QAudioInputBuffer(int bufferSize, - int maxPeriodSize, - AudioStreamBasicDescription const& inputFormat, - AudioStreamBasicDescription const& outputFormat, - QObject* parent): - QObject(parent), - m_deviceError(false), - 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 QAudioRingBuffer(bufferSize); - - m_inputBufferList = new QAudioBufferList(m_inputFormat); - - m_flushTimer = new QTimer(this); - connect(m_flushTimer, SIGNAL(timeout()), SLOT(flushBuffer())); - - if (toQAudioFormat(inputFormat) != toQAudioFormat(outputFormat)) { - if (AudioConverterNew(&m_inputFormat, &m_outputFormat, &m_audioConverter) != noErr) { - qWarning() << "QAudioInput: Unable to create an Audio Converter"; - m_audioConverter = 0; - } - } - - m_qFormat = toQAudioFormat(inputFormat); // we adjust volume before conversion - } - - ~QAudioInputBuffer() - { - delete m_buffer; - } - - qreal volume() const - { - return m_volume; - } - - void setVolume(qreal v) - { - m_volume = v; - } - - qint64 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) { - QAudioPacketFeeder feeder(m_inputBufferList); - - int copied = 0; - const int available = m_buffer->free(); - - while (err == noErr && !feeder.empty()) { - QAudioRingBuffer::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) { - QAudioRingBuffer::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 readBytes(char* data, qint64 len) - { - bool wecan = true; - qint64 bytesCopied = 0; - - len -= len % m_maxPeriodSize; - while (wecan && bytesCopied < len) { - QAudioRingBuffer::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 setFlushDevice(QIODevice* device) - { - if (m_device != device) - m_device = device; - } - - void 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 stopFlushTimer() - { - m_flushTimer->stop(); - } - - void flush(bool all = false) - { - 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) { - QAudioRingBuffer::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 reset() - { - m_buffer->reset(); - m_deviceError = false; - } - - int available() const - { - return m_buffer->free(); - } - - int used() const - { - return m_buffer->used(); - } - -signals: - void readyRead(); - -private slots: - void flushBuffer() - { - flush(); - } - -private: - bool m_deviceError; - int m_maxPeriodSize; - int m_periodTime; - QIODevice* m_device; - QTimer* m_flushTimer; - QAudioRingBuffer* m_buffer; - QAudioBufferList* 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) - { - Q_UNUSED(inAudioConverter); - Q_UNUSED(outDataPacketDescription); - - QAudioPacketFeeder* feeder = static_cast(inUserData); - - if (!feeder->feed(*ioData, *ioNumberDataPackets)) - return as_empty; - - return noErr; - } -}; - - -class MacInputDevice : public QIODevice -{ - Q_OBJECT - -public: - MacInputDevice(QAudioInputBuffer* audioBuffer, QObject* parent): - QIODevice(parent), - m_audioBuffer(audioBuffer) - { - open(QIODevice::ReadOnly | QIODevice::Unbuffered); - connect(m_audioBuffer, SIGNAL(readyRead()), SIGNAL(readyRead())); - } - - qint64 readData(char* data, qint64 len) - { - return m_audioBuffer->readBytes(data, len); - } - - qint64 writeData(const char* data, qint64 len) - { - Q_UNUSED(data); - Q_UNUSED(len); - - return 0; - } - - bool isSequential() const - { - return true; - } - -private: - QAudioInputBuffer* m_audioBuffer; -}; - -} - - -QAudioInputPrivate::QAudioInputPrivate(const QByteArray& device) -{ - QDataStream ds(device); - quint32 did, mode; - - ds >> did >> mode; - - if (QAudio::Mode(mode) == QAudio::AudioOutput) - errorCode = QAudio::OpenError; - else { - audioDeviceInfo = new QAudioDeviceInfoInternal(device, QAudio::AudioInput); - isOpen = false; - audioDeviceId = AudioDeviceID(did); - audioUnit = 0; - startTime = 0; - totalFrames = 0; - audioBuffer = 0; - internalBufferSize = QtMultimediaInternal::default_buffer_size; - clockFrequency = AudioGetHostClockFrequency() / 1000; - errorCode = QAudio::NoError; - stateCode = QAudio::StoppedState; - - m_volume = qreal(1.0f); - - intervalTimer = new QTimer(this); - intervalTimer->setInterval(1000); - connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify())); - } -} - -QAudioInputPrivate::~QAudioInputPrivate() -{ - close(); - delete audioDeviceInfo; -} - -bool QAudioInputPrivate::open() -{ - UInt32 size = 0; - - if (isOpen) - return true; - - ComponentDescription cd; - cd.componentType = kAudioUnitType_Output; - cd.componentSubType = kAudioUnitSubType_HALOutput; - cd.componentManufacturer = kAudioUnitManufacturer_Apple; - cd.componentFlags = 0; - cd.componentFlagsMask = 0; - - // Open - Component cp = FindNextComponent(NULL, &cd); - if (cp == 0) { - qWarning() << "QAudioInput: Failed to find HAL Output component"; - return false; - } - - if (OpenAComponent(cp, &audioUnit) != noErr) { - qWarning() << "QAudioInput: Unable to Open Output Component"; - return false; - } - - // Set mode - // switch to input mode - UInt32 enable = 1; - if (AudioUnitSetProperty(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(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 cb; - cb.inputProc = inputCallback; - cb.inputProcRefCon = this; - - if (AudioUnitSetProperty(audioUnit, - kAudioOutputUnitProperty_SetInputCallback, - kAudioUnitScope_Global, - 0, - &cb, - sizeof(cb)) != noErr) { - qWarning() << "QAudioInput: Failed to set AudioUnit callback"; - return false; - } - - // Set Audio Device - if (AudioUnitSetProperty(audioUnit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &audioDeviceId, - sizeof(audioDeviceId)) != noErr) { - qWarning() << "QAudioInput: Unable to use configured device"; - return false; - } - - // Set format - // Wanted - streamFormat = toAudioStreamBasicDescription(audioFormat); - - // Required on unit - if (audioFormat == audioDeviceInfo->preferredFormat()) { - deviceFormat = streamFormat; - AudioUnitSetProperty(audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - 1, - &deviceFormat, - sizeof(deviceFormat)); - } - else { - size = sizeof(deviceFormat); - if (AudioUnitGetProperty(audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 1, - &deviceFormat, - &size) != noErr) { - qWarning() << "QAudioInput: Unable to retrieve device format"; - return false; - } - - if (AudioUnitSetProperty(audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - 1, - &deviceFormat, - sizeof(deviceFormat)) != noErr) { - qWarning() << "QAudioInput: Unable to set device format"; - return false; - } - } - - // Setup buffers - UInt32 numberOfFrames; - size = sizeof(UInt32); - if (AudioUnitGetProperty(audioUnit, - kAudioDevicePropertyBufferFrameSize, - kAudioUnitScope_Global, - 0, - &numberOfFrames, - &size) != noErr) { - qWarning() << "QAudioInput: Failed to get audio period size"; - return false; - } - - AudioValueRange bufferRange; - size = sizeof(AudioValueRange); - - if (AudioUnitGetProperty(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 - UInt32 frames = qBound((UInt32)bufferRange.mMinimum, internalBufferSize / streamFormat.mBytesPerFrame, (UInt32)bufferRange.mMaximum); - - // Set it back - if (AudioUnitSetProperty(audioUnit, - kAudioDevicePropertyBufferFrameSize, - kAudioUnitScope_Global, - 0, - &frames, - sizeof(UInt32)) != noErr) { - qWarning() << "QAudioInput: Failed to set audio buffer size"; - return false; - } - - // Now allocate a few buffers to be safe. - periodSizeBytes = internalBufferSize = frames * streamFormat.mBytesPerFrame; - - audioBuffer = new QtMultimediaInternal::QAudioInputBuffer(internalBufferSize * 4, - periodSizeBytes, - deviceFormat, - streamFormat, - this); - - audioBuffer->setVolume(m_volume); - audioIO = new QtMultimediaInternal::MacInputDevice(audioBuffer, this); - - // Init - if (AudioUnitInitialize(audioUnit) != noErr) { - qWarning() << "QAudioInput: Failed to initialize AudioUnit"; - return false; - } - - isOpen = true; - - return isOpen; -} - -void QAudioInputPrivate::close() -{ - if (audioUnit != 0) { - AudioOutputUnitStop(audioUnit); - AudioUnitUninitialize(audioUnit); - CloseComponent(audioUnit); - } - - delete audioBuffer; -} - -QAudioFormat QAudioInputPrivate::format() const -{ - return audioFormat; -} - -void QAudioInputPrivate::setFormat(const QAudioFormat& fmt) -{ - if (stateCode == QAudio::StoppedState) - audioFormat = fmt; -} - -void QAudioInputPrivate::start(QIODevice* device) -{ - QIODevice* op = device; - - if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) { - stateCode = QAudio::StoppedState; - errorCode = QAudio::OpenError; - return; - } - - reset(); - audioBuffer->reset(); - audioBuffer->setFlushDevice(op); - - if (op == 0) - op = audioIO; - - // Start - startTime = AudioGetCurrentHostTime(); - totalFrames = 0; - - stateCode = QAudio::IdleState; - errorCode = QAudio::NoError; - emit stateChanged(stateCode); - - audioThreadStart(); -} - -QIODevice* QAudioInputPrivate::start() -{ - QIODevice* op = 0; - - if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) { - stateCode = QAudio::StoppedState; - errorCode = QAudio::OpenError; - return audioIO; - } - - reset(); - audioBuffer->reset(); - audioBuffer->setFlushDevice(op); - - if (op == 0) - op = audioIO; - - // Start - startTime = AudioGetCurrentHostTime(); - totalFrames = 0; - - stateCode = QAudio::IdleState; - errorCode = QAudio::NoError; - emit stateChanged(stateCode); - - audioThreadStart(); - - return op; -} - -void QAudioInputPrivate::stop() -{ - QMutexLocker lock(&mutex); - if (stateCode != QAudio::StoppedState) { - audioThreadStop(); - audioBuffer->flush(true); - - errorCode = QAudio::NoError; - stateCode = QAudio::StoppedState; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioInputPrivate::reset() -{ - QMutexLocker lock(&mutex); - if (stateCode != QAudio::StoppedState) { - audioThreadStop(); - - errorCode = QAudio::NoError; - stateCode = QAudio::StoppedState; - audioBuffer->reset(); - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioInputPrivate::suspend() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) { - audioThreadStop(); - - errorCode = QAudio::NoError; - stateCode = QAudio::SuspendedState; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioInputPrivate::resume() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::SuspendedState) { - audioThreadStart(); - - errorCode = QAudio::NoError; - stateCode = QAudio::ActiveState; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -int QAudioInputPrivate::bytesReady() const -{ - if (!audioBuffer) - return 0; - return audioBuffer->used(); -} - -int QAudioInputPrivate::periodSize() const -{ - return periodSizeBytes; -} - -void QAudioInputPrivate::setBufferSize(int bs) -{ - internalBufferSize = bs; -} - -int QAudioInputPrivate::bufferSize() const -{ - return internalBufferSize; -} - -void QAudioInputPrivate::setNotifyInterval(int milliSeconds) -{ - if (intervalTimer->interval() == milliSeconds) - return; - - if (milliSeconds <= 0) - milliSeconds = 0; - - intervalTimer->setInterval(milliSeconds); -} - -int QAudioInputPrivate::notifyInterval() const -{ - return intervalTimer->interval(); -} - -qint64 QAudioInputPrivate::processedUSecs() const -{ - return totalFrames * 1000000 / audioFormat.sampleRate(); -} - -qint64 QAudioInputPrivate::elapsedUSecs() const -{ - if (stateCode == QAudio::StoppedState) - return 0; - - return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000); -} - -QAudio::Error QAudioInputPrivate::error() const -{ - return errorCode; -} - -QAudio::State QAudioInputPrivate::state() const -{ - return stateCode; -} - -qreal QAudioInputPrivate::volume() const -{ - return m_volume; -} - -void QAudioInputPrivate::setVolume(qreal volume) -{ - m_volume = volume; - if (audioBuffer) - audioBuffer->setVolume(m_volume); -} - - -void QAudioInputPrivate::audioThreadStop() -{ - stopTimers(); - if (audioThreadState.testAndSetAcquire(Running, Stopped)) - threadFinished.wait(&mutex); -} - -void QAudioInputPrivate::audioThreadStart() -{ - startTimers(); - audioThreadState.store(Running); - AudioOutputUnitStart(audioUnit); -} - -void QAudioInputPrivate::audioDeviceStop() -{ - AudioOutputUnitStop(audioUnit); - audioThreadState.store(Stopped); - threadFinished.wakeOne(); -} - -void QAudioInputPrivate::audioDeviceActive() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::IdleState) { - stateCode = QAudio::ActiveState; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioInputPrivate::audioDeviceFull() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::ActiveState) { - errorCode = QAudio::UnderrunError; - stateCode = QAudio::IdleState; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioInputPrivate::audioDeviceError() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::ActiveState) { - audioDeviceStop(); - - errorCode = QAudio::IOError; - stateCode = QAudio::StoppedState; - QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); - } -} - -void QAudioInputPrivate::startTimers() -{ - audioBuffer->startFlushTimer(); - if (intervalTimer->interval() > 0) - intervalTimer->start(); -} - -void QAudioInputPrivate::stopTimers() -{ - audioBuffer->stopFlushTimer(); - intervalTimer->stop(); -} - -void QAudioInputPrivate::deviceStopped() -{ - stopTimers(); - emit stateChanged(stateCode); -} - -// Input callback -OSStatus QAudioInputPrivate::inputCallback(void* inRefCon, - AudioUnitRenderActionFlags* ioActionFlags, - const AudioTimeStamp* inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList* ioData) -{ - Q_UNUSED(ioData); - - QAudioInputPrivate* d = static_cast(inRefCon); - - const int threadState = d->audioThreadState.loadAcquire(); - if (threadState == Stopped) - d->audioDeviceStop(); - else { - qint64 framesWritten; - - framesWritten = d->audioBuffer->renderFromDevice(d->audioUnit, - ioActionFlags, - inTimeStamp, - inBusNumber, - inNumberFrames); - - if (framesWritten > 0) { - d->totalFrames += framesWritten; - d->audioDeviceActive(); - } else if (framesWritten == 0) - d->audioDeviceFull(); - else if (framesWritten < 0) - d->audioDeviceError(); - } - - return noErr; -} - - -QT_END_NAMESPACE - -#include "qaudioinput_mac_p.moc" diff --git a/src/multimedia/audio/qaudioinput_mac_p.h b/src/multimedia/audio/qaudioinput_mac_p.h deleted file mode 100644 index 7faaa6980..000000000 --- a/src/multimedia/audio/qaudioinput_mac_p.h +++ /dev/null @@ -1,173 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - - -#ifndef QAUDIOINPUT_MAC_P_H -#define QAUDIOINPUT_MAC_P_H - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - - -class QTimer; -class QIODevice; -class QAbstractAudioDeviceInfo; - -namespace QtMultimediaInternal -{ -class QAudioInputBuffer; -} - -class QAudioInputPrivate : public QAbstractAudioInput -{ - Q_OBJECT - -public: - bool isOpen; - int periodSizeBytes; - int internalBufferSize; - qint64 totalFrames; - QAudioFormat audioFormat; - QIODevice* audioIO; - AudioUnit audioUnit; - AudioDeviceID audioDeviceId; - Float64 clockFrequency; - UInt64 startTime; - QAudio::Error errorCode; - QAudio::State stateCode; - QtMultimediaInternal::QAudioInputBuffer* audioBuffer; - QMutex mutex; - QWaitCondition threadFinished; - QAtomicInt audioThreadState; - QTimer* intervalTimer; - AudioStreamBasicDescription streamFormat; - AudioStreamBasicDescription deviceFormat; - QAbstractAudioDeviceInfo *audioDeviceInfo; - qreal m_volume; - - QAudioInputPrivate(const QByteArray& device); - ~QAudioInputPrivate(); - - bool open(); - void close(); - - QAudioFormat format() const; - void setFormat(const QAudioFormat& fmt); - - QIODevice* start(); - void start(QIODevice* device); - void stop(); - void reset(); - void suspend(); - void resume(); - void idle(); - - 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; - - qreal volume() const; - void setVolume(qreal volume); - - void audioThreadStart(); - void audioThreadStop(); - - void audioDeviceStop(); - void audioDeviceActive(); - void audioDeviceFull(); - void audioDeviceError(); - - void startTimers(); - void stopTimers(); - -private slots: - void deviceStopped(); - -private: - enum { Running, Stopped }; - - // Input callback - static OSStatus inputCallback(void* inRefCon, - AudioUnitRenderActionFlags* ioActionFlags, - const AudioTimeStamp* inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList* ioData); -}; - -QT_END_NAMESPACE - -#endif // QAUDIOINPUT_MAC_P_H diff --git a/src/multimedia/audio/qaudiooutput_mac_p.cpp b/src/multimedia/audio/qaudiooutput_mac_p.cpp deleted file mode 100644 index 8ac4007cd..000000000 --- a/src/multimedia/audio/qaudiooutput_mac_p.cpp +++ /dev/null @@ -1,759 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// INTERNAL USE ONLY: Do NOT use for any other purpose. -// - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "qaudio_mac_p.h" -#include "qaudiooutput_mac_p.h" -#include "qaudiodeviceinfo_mac_p.h" - - -QT_BEGIN_NAMESPACE - - -namespace QtMultimediaInternal -{ - -static const int default_buffer_size = 8 * 1024; - - -class QAudioOutputBuffer : public QObject -{ - Q_OBJECT - -public: - QAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat): - m_deviceError(false), - m_maxPeriodSize(maxPeriodSize), - m_device(0) - { - m_buffer = new QAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize))); - m_bytesPerFrame = (audioFormat.sampleSize() / 8) * audioFormat.channelCount(); - m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.sampleRate(); - - m_fillTimer = new QTimer(this); - connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer())); - } - - ~QAudioOutputBuffer() - { - delete m_buffer; - } - - qint64 readFrames(char* data, qint64 maxFrames) - { - bool wecan = true; - qint64 framesRead = 0; - - while (wecan && framesRead < maxFrames) { - QAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame); - - if (region.second > 0) { - // 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 writeBytes(const char* data, qint64 maxSize) - { - bool wecan = true; - qint64 bytesWritten = 0; - - maxSize -= maxSize % m_bytesPerFrame; - while (wecan && bytesWritten < maxSize) { - QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(maxSize - bytesWritten); - - if (region.second > 0) { - memcpy(region.first, data + bytesWritten, region.second); - bytesWritten += region.second; - } - else - wecan = false; - - m_buffer->releaseWriteRegion(region); - } - - if (bytesWritten > 0) - emit readyRead(); - - return bytesWritten; - } - - int available() const - { - return m_buffer->free(); - } - - void reset() - { - m_buffer->reset(); - m_device = 0; - m_deviceError = false; - } - - void setPrefetchDevice(QIODevice* device) - { - if (m_device != device) { - m_device = device; - if (m_device != 0) - fillBuffer(); - } - } - - void startFillTimer() - { - if (m_device != 0) - m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime); - } - - void stopFillTimer() - { - m_fillTimer->stop(); - } - -signals: - void readyRead(); - -private slots: - void fillBuffer() - { - const int free = m_buffer->free(); - const int writeSize = free - (free % m_maxPeriodSize); - - if (writeSize > 0) { - bool wecan = true; - int filled = 0; - - while (!m_deviceError && wecan && filled < writeSize) { - QAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled); - - if (region.second > 0) { - region.second = m_device->read(region.first, region.second); - if (region.second > 0) - filled += region.second; - else if (region.second == 0) - wecan = false; - else if (region.second < 0) { - m_fillTimer->stop(); - region.second = 0; - m_deviceError = true; - } - } - else - wecan = false; - - m_buffer->releaseWriteRegion(region); - } - - if (filled > 0) - emit readyRead(); - } - } - -private: - bool m_deviceError; - int m_maxPeriodSize; - int m_bytesPerFrame; - int m_periodTime; - QIODevice* m_device; - QTimer* m_fillTimer; - QAudioRingBuffer* m_buffer; -}; - - -} - -class MacOutputDevice : public QIODevice -{ - Q_OBJECT - -public: - MacOutputDevice(QtMultimediaInternal::QAudioOutputBuffer* audioBuffer, QObject* parent): - QIODevice(parent), - m_audioBuffer(audioBuffer) - { - open(QIODevice::WriteOnly | QIODevice::Unbuffered); - } - - qint64 readData(char* data, qint64 len) - { - Q_UNUSED(data); - Q_UNUSED(len); - - return 0; - } - - qint64 writeData(const char* data, qint64 len) - { - return m_audioBuffer->writeBytes(data, len); - } - - bool isSequential() const - { - return true; - } - -private: - QtMultimediaInternal::QAudioOutputBuffer* m_audioBuffer; -}; - - -QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray& device) -{ - QDataStream ds(device); - quint32 did, mode; - - ds >> did >> mode; - - if (QAudio::Mode(mode) == QAudio::AudioInput) - errorCode = QAudio::OpenError; - else { - audioDeviceInfo = new QAudioDeviceInfoInternal(device, QAudio::AudioOutput); - isOpen = false; - audioDeviceId = AudioDeviceID(did); - audioUnit = 0; - audioIO = 0; - startTime = 0; - totalFrames = 0; - audioBuffer = 0; - internalBufferSize = QtMultimediaInternal::default_buffer_size; - clockFrequency = AudioGetHostClockFrequency() / 1000; - errorCode = QAudio::NoError; - stateCode = QAudio::StoppedState; - audioThreadState.store(Stopped); - - cachedVolume = (qreal)1.; - - intervalTimer = new QTimer(this); - intervalTimer->setInterval(1000); - connect(intervalTimer, SIGNAL(timeout()), SIGNAL(notify())); - } -} - -QAudioOutputPrivate::~QAudioOutputPrivate() -{ - delete audioDeviceInfo; - close(); -} - -bool QAudioOutputPrivate::open() -{ - if (errorCode != QAudio::NoError) - return false; - - if (isOpen) - return true; - - ComponentDescription cd; - cd.componentType = kAudioUnitType_Output; - cd.componentSubType = kAudioUnitSubType_HALOutput; - cd.componentManufacturer = kAudioUnitManufacturer_Apple; - cd.componentFlags = 0; - cd.componentFlagsMask = 0; - - // Open - Component cp = FindNextComponent(NULL, &cd); - if (cp == 0) { - qWarning() << "QAudioOutput: Failed to find HAL Output component"; - return false; - } - - if (OpenAComponent(cp, &audioUnit) != noErr) { - qWarning() << "QAudioOutput: Unable to Open Output Component"; - return false; - } - - // register callback - AURenderCallbackStruct cb; - cb.inputProc = renderCallback; - cb.inputProcRefCon = this; - - if (AudioUnitSetProperty(audioUnit, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Global, - 0, - &cb, - sizeof(cb)) != noErr) { - qWarning() << "QAudioOutput: Failed to set AudioUnit callback"; - return false; - } - - // Set Audio Device - if (AudioUnitSetProperty(audioUnit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &audioDeviceId, - sizeof(audioDeviceId)) != noErr) { - qWarning() << "QAudioOutput: Unable to use configured device"; - return false; - } - - // Set stream format - streamFormat = toAudioStreamBasicDescription(audioFormat); - - UInt32 size = sizeof(streamFormat); - if (AudioUnitSetProperty(audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &streamFormat, - sizeof(streamFormat)) != noErr) { - qWarning() << "QAudioOutput: Unable to Set Stream information"; - return false; - } - - // Allocate buffer - UInt32 numberOfFrames = 0; - size = sizeof(UInt32); - if (AudioUnitGetProperty(audioUnit, - kAudioDevicePropertyBufferFrameSize, - kAudioUnitScope_Global, - 0, - &numberOfFrames, - &size) != noErr) { - qWarning() << "QAudioInput: Failed to get audio period size"; - return false; - } - - periodSizeBytes = numberOfFrames * streamFormat.mBytesPerFrame; - if (internalBufferSize < periodSizeBytes * 2) - internalBufferSize = periodSizeBytes * 2; - else - internalBufferSize -= internalBufferSize % streamFormat.mBytesPerFrame; - - audioBuffer = new QtMultimediaInternal::QAudioOutputBuffer(internalBufferSize, periodSizeBytes, audioFormat); - connect(audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); // Pull - - audioIO = new MacOutputDevice(audioBuffer, this); - - // Init - if (AudioUnitInitialize(audioUnit)) { - qWarning() << "QAudioOutput: Failed to initialize AudioUnit"; - return false; - } - - isOpen = true; - - setVolume(cachedVolume); - - return true; -} - -void QAudioOutputPrivate::close() -{ - if (audioUnit != 0) { - AudioOutputUnitStop(audioUnit); - AudioUnitUninitialize(audioUnit); - CloseComponent(audioUnit); - } - - delete audioBuffer; -} - -QAudioFormat QAudioOutputPrivate::format() const -{ - return audioFormat; -} - -void QAudioOutputPrivate::setFormat(const QAudioFormat& fmt) -{ - if (stateCode == QAudio::StoppedState) - audioFormat = fmt; -} - -void QAudioOutputPrivate::start(QIODevice* device) -{ - QIODevice* op = device; - - if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) { - stateCode = QAudio::StoppedState; - errorCode = QAudio::OpenError; - } - - reset(); - audioBuffer->reset(); - audioBuffer->setPrefetchDevice(op); - - if (op == 0) { - op = audioIO; - stateCode = QAudio::IdleState; - } - else - stateCode = QAudio::ActiveState; - - // Start - errorCode = QAudio::NoError; - totalFrames = 0; - startTime = AudioGetCurrentHostTime(); - - if (stateCode == QAudio::ActiveState) - audioThreadStart(); - - emit stateChanged(stateCode); -} - -QIODevice* QAudioOutputPrivate::start() -{ - if (!audioDeviceInfo->isFormatSupported(audioFormat) || !open()) { - stateCode = QAudio::StoppedState; - errorCode = QAudio::OpenError; - return audioIO; - } - - reset(); - audioBuffer->reset(); - audioBuffer->setPrefetchDevice(0); - - stateCode = QAudio::IdleState; - - // Start - errorCode = QAudio::NoError; - totalFrames = 0; - startTime = AudioGetCurrentHostTime(); - - emit stateChanged(stateCode); - - return audioIO; -} - -void QAudioOutputPrivate::stop() -{ - QMutexLocker lock(&mutex); - if (stateCode != QAudio::StoppedState) { - audioThreadDrain(); - - stateCode = QAudio::StoppedState; - errorCode = QAudio::NoError; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioOutputPrivate::reset() -{ - QMutexLocker lock(&mutex); - if (stateCode != QAudio::StoppedState) { - audioThreadStop(); - - stateCode = QAudio::StoppedState; - errorCode = QAudio::NoError; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioOutputPrivate::suspend() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::ActiveState || stateCode == QAudio::IdleState) { - audioThreadStop(); - - stateCode = QAudio::SuspendedState; - errorCode = QAudio::NoError; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -void QAudioOutputPrivate::resume() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::SuspendedState) { - audioThreadStart(); - - stateCode = QAudio::ActiveState; - errorCode = QAudio::NoError; - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - -int QAudioOutputPrivate::bytesFree() const -{ - return audioBuffer->available(); -} - -int QAudioOutputPrivate::periodSize() const -{ - return periodSizeBytes; -} - -void QAudioOutputPrivate::setBufferSize(int bs) -{ - if (stateCode == QAudio::StoppedState) - internalBufferSize = bs; -} - -int QAudioOutputPrivate::bufferSize() const -{ - return internalBufferSize; -} - -void QAudioOutputPrivate::setNotifyInterval(int milliSeconds) -{ - if (intervalTimer->interval() == milliSeconds) - return; - - if (milliSeconds <= 0) - milliSeconds = 0; - - intervalTimer->setInterval(milliSeconds); -} - -int QAudioOutputPrivate::notifyInterval() const -{ - return intervalTimer->interval(); -} - -qint64 QAudioOutputPrivate::processedUSecs() const -{ - return totalFrames * 1000000 / audioFormat.sampleRate(); -} - -qint64 QAudioOutputPrivate::elapsedUSecs() const -{ - if (stateCode == QAudio::StoppedState) - return 0; - - return (AudioGetCurrentHostTime() - startTime) / (clockFrequency / 1000); -} - -QAudio::Error QAudioOutputPrivate::error() const -{ - return errorCode; -} - -QAudio::State QAudioOutputPrivate::state() const -{ - return stateCode; -} - -void QAudioOutputPrivate::audioThreadStart() -{ - startTimers(); - audioThreadState.store(Running); - AudioOutputUnitStart(audioUnit); -} - -void QAudioOutputPrivate::audioThreadStop() -{ - stopTimers(); - if (audioThreadState.testAndSetAcquire(Running, Stopped)) - threadFinished.wait(&mutex); -} - -void QAudioOutputPrivate::audioThreadDrain() -{ - stopTimers(); - if (audioThreadState.testAndSetAcquire(Running, Draining)) - threadFinished.wait(&mutex); -} - -void QAudioOutputPrivate::audioDeviceStop() -{ - AudioOutputUnitStop(audioUnit); - audioThreadState.store(Stopped); - threadFinished.wakeOne(); -} - -void QAudioOutputPrivate::audioDeviceIdle() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::ActiveState) { - audioDeviceStop(); - - errorCode = QAudio::UnderrunError; - stateCode = QAudio::IdleState; - QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); - } -} - -void QAudioOutputPrivate::audioDeviceError() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::ActiveState) { - audioDeviceStop(); - - errorCode = QAudio::IOError; - stateCode = QAudio::StoppedState; - QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); - } -} - -void QAudioOutputPrivate::startTimers() -{ - audioBuffer->startFillTimer(); - if (intervalTimer->interval() > 0) - intervalTimer->start(); -} - -void QAudioOutputPrivate::stopTimers() -{ - audioBuffer->stopFillTimer(); - intervalTimer->stop(); -} - -void QAudioOutputPrivate::setVolume(qreal v) -{ - const qreal normalizedVolume = qBound(qreal(0.0), v, qreal(1.0)); - if (!isOpen) { - cachedVolume = normalizedVolume; - return; - } - - if (AudioUnitSetParameter(audioUnit, - kHALOutputParam_Volume, - kAudioUnitScope_Global, - 0 /* bus */, - (float)normalizedVolume, - 0) == noErr) - cachedVolume = normalizedVolume; -} - -qreal QAudioOutputPrivate::volume() const -{ - return cachedVolume; -} - -void QAudioOutputPrivate::deviceStopped() -{ - intervalTimer->stop(); - emit stateChanged(stateCode); -} - -void QAudioOutputPrivate::inputReady() -{ - QMutexLocker lock(&mutex); - if (stateCode == QAudio::IdleState) { - audioThreadStart(); - - stateCode = QAudio::ActiveState; - errorCode = QAudio::NoError; - - QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, stateCode)); - } -} - - -OSStatus QAudioOutputPrivate::renderCallback(void* inRefCon, - AudioUnitRenderActionFlags* ioActionFlags, - const AudioTimeStamp* inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList* ioData) -{ - Q_UNUSED(ioActionFlags) - Q_UNUSED(inTimeStamp) - Q_UNUSED(inBusNumber) - Q_UNUSED(inNumberFrames) - - QAudioOutputPrivate* d = static_cast(inRefCon); - - const int threadState = d->audioThreadState.fetchAndAddAcquire(0); - if (threadState == Stopped) { - ioData->mBuffers[0].mDataByteSize = 0; - d->audioDeviceStop(); - } - else { - const UInt32 bytesPerFrame = d->streamFormat.mBytesPerFrame; - qint64 framesRead; - - framesRead = d->audioBuffer->readFrames((char*)ioData->mBuffers[0].mData, - ioData->mBuffers[0].mDataByteSize / bytesPerFrame); - - if (framesRead > 0) { - ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame; - d->totalFrames += framesRead; - } - else { - ioData->mBuffers[0].mDataByteSize = 0; - if (framesRead == 0) { - if (threadState == Draining) - d->audioDeviceStop(); - else - d->audioDeviceIdle(); - } - else - d->audioDeviceError(); - } - } - - return noErr; -} - - -QT_END_NAMESPACE - -#include "qaudiooutput_mac_p.moc" diff --git a/src/multimedia/audio/qaudiooutput_mac_p.h b/src/multimedia/audio/qaudiooutput_mac_p.h deleted file mode 100644 index 89e54fd16..000000000 --- a/src/multimedia/audio/qaudiooutput_mac_p.h +++ /dev/null @@ -1,172 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#ifndef QAUDIOOUTPUT_MAC_P_H -#define QAUDIOOUTPUT_MAC_P_H - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - - -class QIODevice; -class QAbstractAudioDeviceInfo; - -namespace QtMultimediaInternal -{ -class QAudioOutputBuffer; -} - -class QAudioOutputPrivate : public QAbstractAudioOutput -{ - Q_OBJECT - -public: - bool isOpen; - int internalBufferSize; - int periodSizeBytes; - qint64 totalFrames; - QAudioFormat audioFormat; - QIODevice* audioIO; - AudioDeviceID audioDeviceId; - AudioUnit audioUnit; - Float64 clockFrequency; - UInt64 startTime; - AudioStreamBasicDescription deviceFormat; - AudioStreamBasicDescription streamFormat; - QtMultimediaInternal::QAudioOutputBuffer* audioBuffer; - QAtomicInt audioThreadState; - QWaitCondition threadFinished; - QMutex mutex; - QTimer* intervalTimer; - QAbstractAudioDeviceInfo *audioDeviceInfo; - qreal cachedVolume; - - QAudio::Error errorCode; - QAudio::State stateCode; - - QAudioOutputPrivate(const QByteArray& device); - ~QAudioOutputPrivate(); - - bool open(); - void close(); - - QAudioFormat format() const; - void setFormat(const QAudioFormat& fmt); - - QIODevice* start(); - void start(QIODevice* device); - 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 audioThreadStart(); - void audioThreadStop(); - void audioThreadDrain(); - - void audioDeviceStop(); - void audioDeviceIdle(); - void audioDeviceError(); - - void startTimers(); - void stopTimers(); - - void setVolume(qreal); - qreal volume() 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); -}; - -QT_END_NAMESPACE - -#endif 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 diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 742a4f7f0..5da1bb739 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -45,7 +45,7 @@ unix:!mac:!android { } mac:!simulator { - SUBDIRS += audiocapture + SUBDIRS += audiocapture coreaudio config_avfoundation: SUBDIRS += avfoundation diff --git a/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp b/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp index 3d2be8260..477727497 100755 --- a/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp +++ b/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp @@ -829,8 +829,9 @@ void tst_QAudioOutput::pushSuspendResume() audioOutput.resume(); - // Give backends running in separate threads a chance to resume. - QTest::qWait(100); + // Give backends running in separate threads a chance to resume + // but not too much or the rest of the file may be processed + QTest::qWait(20); // Check that QAudioOutput immediately transitions to ActiveState QVERIFY2((stateSignal.count() == 1), -- cgit v1.2.1