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 <richard.gustavsen@digia.com>
Reviewed-by: Christian Stromme <christian.stromme@digia.com>
Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
This commit is contained in:
Andy Nichols
2013-03-08 15:18:36 +01:00
committed by The Qt Project
parent 044e48d5a4
commit b357c55f2d
22 changed files with 3461 additions and 2572 deletions

View File

@@ -39,20 +39,6 @@ SOURCES += \
audio/qaudiodecoder.cpp \ audio/qaudiodecoder.cpp \
audio/qaudiohelpers.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 { win32 {
PRIVATE_HEADERS += audio/qaudioinput_win32_p.h audio/qaudiooutput_win32_p.h audio/qaudiodeviceinfo_win32_p.h PRIVATE_HEADERS += audio/qaudioinput_win32_p.h audio/qaudiooutput_win32_p.h audio/qaudiodeviceinfo_win32_p.h
SOURCES += audio/qaudiodeviceinfo_win32_p.cpp \ SOURCES += audio/qaudiodeviceinfo_win32_p.cpp \

View File

@@ -52,10 +52,6 @@
#include "qaudiodeviceinfo_win32_p.h" #include "qaudiodeviceinfo_win32_p.h"
#include "qaudiooutput_win32_p.h" #include "qaudiooutput_win32_p.h"
#include "qaudioinput_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) #elif defined(HAS_ALSA)
#include "qaudiodeviceinfo_alsa_p.h" #include "qaudiodeviceinfo_alsa_p.h"
#include "qaudiooutput_alsa_p.h" #include "qaudiooutput_alsa_p.h"
@@ -137,7 +133,7 @@ QList<QAudioDeviceInfo> QAudioDeviceFactory::availableDevices(QAudio::Mode mode)
{ {
QList<QAudioDeviceInfo> devices; QList<QAudioDeviceInfo> devices;
#ifndef QT_NO_AUDIO_BACKEND #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)) foreach (const QByteArray &handle, QAudioDeviceInfoInternal::availableDevices(mode))
devices << QAudioDeviceInfo(QLatin1String("builtin"), handle, mode); devices << QAudioDeviceInfo(QLatin1String("builtin"), handle, mode);
#endif #endif
@@ -170,7 +166,7 @@ QAudioDeviceInfo QAudioDeviceFactory::defaultInputDevice()
#endif #endif
#ifndef QT_NO_AUDIO_BACKEND #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); return QAudioDeviceInfo(QLatin1String("builtin"), QAudioDeviceInfoInternal::defaultInputDevice(), QAudio::AudioInput);
#endif #endif
#endif #endif
@@ -190,7 +186,7 @@ QAudioDeviceInfo QAudioDeviceFactory::defaultOutputDevice()
#endif #endif
#ifndef QT_NO_AUDIO_BACKEND #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); return QAudioDeviceInfo(QLatin1String("builtin"), QAudioDeviceInfoInternal::defaultOutputDevice(), QAudio::AudioOutput);
#endif #endif
#endif #endif
@@ -202,7 +198,7 @@ QAbstractAudioDeviceInfo* QAudioDeviceFactory::audioDeviceInfo(const QString &re
QAbstractAudioDeviceInfo *rc = 0; QAbstractAudioDeviceInfo *rc = 0;
#ifndef QT_NO_AUDIO_BACKEND #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")) if (realm == QLatin1String("builtin"))
return new QAudioDeviceInfoInternal(handle, mode); return new QAudioDeviceInfoInternal(handle, mode);
#endif #endif
@@ -234,7 +230,7 @@ QAbstractAudioInput* QAudioDeviceFactory::createInputDevice(QAudioDeviceInfo con
if (deviceInfo.isNull()) if (deviceInfo.isNull())
return new QNullInputDevice(); return new QNullInputDevice();
#ifndef QT_NO_AUDIO_BACKEND #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")) { if (deviceInfo.realm() == QLatin1String("builtin")) {
QAbstractAudioInput* p = new QAudioInputPrivate(deviceInfo.handle()); QAbstractAudioInput* p = new QAudioInputPrivate(deviceInfo.handle());
if (p) p->setFormat(format); if (p) p->setFormat(format);
@@ -261,7 +257,7 @@ QAbstractAudioOutput* QAudioDeviceFactory::createOutputDevice(QAudioDeviceInfo c
if (deviceInfo.isNull()) if (deviceInfo.isNull())
return new QNullOutputDevice(); return new QNullOutputDevice();
#ifndef QT_NO_AUDIO_BACKEND #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")) { if (deviceInfo.realm() == QLatin1String("builtin")) {
QAbstractAudioOutput* p = new QAudioOutputPrivate(deviceInfo.handle()); QAbstractAudioOutput* p = new QAudioOutputPrivate(deviceInfo.handle());
if (p) p->setFormat(format); if (p) p->setFormat(format);

View File

@@ -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 <QtCore/qstringlist.h>
#include <QtCore/qlist.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qdatastream.h>
#include <QtCore/qdebug.h>
#include <qaudiodeviceinfo.h>
#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<const QChar *>(chars), length);
UniChar buffer[length];
CFStringGetCharacters(str, CFRangeMake(0, length), buffer);
return QString(reinterpret_cast<const QChar *>(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<QAudioDeviceInfoInternal*>(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<int> QAudioDeviceInfoInternal::supportedSampleRates()
{
QSet<int> 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<int> QAudioDeviceInfoInternal::supportedChannelCounts()
{
QList<int> 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<AudioBufferList*>(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<int> QAudioDeviceInfoInternal::supportedSampleSizes()
{
return QList<int>() << 8 << 16 << 24 << 32 << 64;
}
QList<QAudioFormat::Endian> QAudioDeviceInfoInternal::supportedByteOrders()
{
return QList<QAudioFormat::Endian>() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian;
}
QList<QAudioFormat::SampleType> QAudioDeviceInfoInternal::supportedSampleTypes()
{
return QList<QAudioFormat::SampleType>() << 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<QByteArray> QAudioDeviceInfoInternal::availableDevices(QAudio::Mode mode)
{
QList<QByteArray> 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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <QtCore/qobject.h>
#include <QtCore/qmutex.h>
#include <QtCore/qwaitcondition.h>
#include <QtCore/qatomic.h>
#include <qaudio.h>
#include <qaudioformat.h>
#include <qaudiosystem.h>
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

View File

@@ -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 <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <QtCore/qendian.h>
#include <QtCore/qbuffer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qdebug.h>
#include <qaudiooutput.h>
#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<QAudioOutputPrivate*>(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"

View File

@@ -0,0 +1,3 @@
{
"Keys": ["default"]
}

View File

@@ -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

View File

@@ -38,43 +38,28 @@
** $QT_END_LICENSE$ ** $QT_END_LICENSE$
** **
****************************************************************************/ ****************************************************************************/
#ifndef IOSAUDIODEVICEINFO_H
// #define IOSAUDIODEVICEINFO_H
// 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 <CoreAudio/CoreAudio.h>
#include <qaudiosystem.h> #include <qaudiosystem.h>
#if defined(Q_OS_OSX)
# include <CoreAudio/CoreAudio.h>
#endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class CoreAudioDeviceInfo : public QAbstractAudioDeviceInfo
class QAudioDeviceInfoInternal : public QAbstractAudioDeviceInfo
{ {
Q_OBJECT
public: public:
AudioDeviceID deviceId; CoreAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode);
QString name; ~CoreAudioDeviceInfo() {}
QAudio::Mode mode;
QAudioDeviceInfoInternal(QByteArray const& handle, QAudio::Mode mode);
bool isFormatSupported(const QAudioFormat& format) const;
QAudioFormat preferredFormat() const; QAudioFormat preferredFormat() const;
bool isFormatSupported(const QAudioFormat &format) const;
QString deviceName() const; QString deviceName() const;
QStringList supportedCodecs(); QStringList supportedCodecs();
QList<int> supportedSampleRates(); QList<int> supportedSampleRates();
QList<int> supportedChannelCounts(); QList<int> supportedChannelCounts();
@@ -86,8 +71,16 @@ public:
static QByteArray defaultOutputDevice(); static QByteArray defaultOutputDevice();
static QList<QByteArray> availableDevices(QAudio::Mode mode); static QList<QByteArray> availableDevices(QAudio::Mode mode);
private:
#if defined(Q_OS_OSX)
AudioDeviceID m_deviceId;
#endif
QString m_device;
QAudio::Mode m_mode;
}; };
QT_END_NAMESPACE QT_END_NAMESPACE
#endif // QDEVICEINFO_MAC_P_H #endif

View File

@@ -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 <QtCore/QDebug>
#include <QtCore/QSet>
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<CoreAudioDeviceInfo*>(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<int> CoreAudioDeviceInfo::supportedSampleRates()
{
QSet<int> 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<int> CoreAudioDeviceInfo::supportedChannelCounts()
{
QSet<int> 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<AudioBufferList*>(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<int> CoreAudioDeviceInfo::supportedSampleSizes()
{
return QList<int>() << 8 << 16 << 24 << 32 << 64;
}
QList<QAudioFormat::Endian> CoreAudioDeviceInfo::supportedByteOrders()
{
return QList<QAudioFormat::Endian>() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian;
}
QList<QAudioFormat::SampleType> CoreAudioDeviceInfo::supportedSampleTypes()
{
return QList<QAudioFormat::SampleType>() << 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<const QChar *>(chars), length);
UniChar buffer[length];
CFStringGetCharacters(str, CFRangeMake(0, length), buffer);
return QString(reinterpret_cast<const QChar *>(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<QByteArray> CoreAudioDeviceInfo::availableDevices(QAudio::Mode mode)
{
QList<QByteArray> 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"

View File

@@ -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 <qaudiosystem.h>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudioTypes.h>
#include <AudioToolbox/AudioToolbox.h>
#include <QtCore/QWaitCondition>
#include <QtCore/QMutex>
#include <QtCore/QTimer>
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

File diff suppressed because it is too large Load Diff

View File

@@ -38,110 +38,133 @@
** $QT_END_LICENSE$ ** $QT_END_LICENSE$
** **
****************************************************************************/ ****************************************************************************/
#ifndef IOSAUDIOOUTPUT_H
#define IOSAUDIOOUTPUT_H
//
// 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 <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <QtCore/qobject.h>
#include <QtCore/qmutex.h>
#include <QtCore/qwaitcondition.h>
#include <QtCore/qtimer.h>
#include <QtCore/qatomic.h>
#include <qaudio.h>
#include <qaudioformat.h>
#include <qaudiosystem.h> #include <qaudiosystem.h>
#if defined(Q_OS_OSX)
# include <CoreAudio/CoreAudio.h>
#endif
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudioTypes.h>
#include <QtCore/QWaitCondition>
#include <QtCore/QMutex>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class CoreAudioOutputBuffer;
class QTimer;
class CoreAudioDeviceInfo;
class CoreAudioRingBuffer;
class QIODevice; class CoreAudioOutputBuffer : public QObject
class QAbstractAudioDeviceInfo;
namespace QtMultimediaInternal
{
class QAudioOutputBuffer;
}
class QAudioOutputPrivate : public QAbstractAudioOutput
{ {
Q_OBJECT Q_OBJECT
public: public:
bool isOpen; CoreAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat);
int internalBufferSize; ~CoreAudioOutputBuffer();
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; qint64 readFrames(char *data, qint64 maxFrames);
QAudio::State stateCode; qint64 writeBytes(const char *data, qint64 maxSize);
QAudioOutputPrivate(const QByteArray& device); int available() const;
~QAudioOutputPrivate(); void reset();
bool open(); void setPrefetchDevice(QIODevice *device);
void close();
QAudioFormat format() const; void startFillTimer();
void setFormat(const QAudioFormat& fmt); void stopFillTimer();
QIODevice* start(); signals:
void start(QIODevice* device); 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 stop();
void reset(); void reset();
void suspend(); void suspend();
void resume(); void resume();
int bytesFree() const; int bytesFree() const;
int periodSize() const; int periodSize() const;
void setBufferSize(int value); void setBufferSize(int value);
int bufferSize() const; int bufferSize() const;
void setNotifyInterval(int milliSeconds); void setNotifyInterval(int milliSeconds);
int notifyInterval() const; int notifyInterval() const;
qint64 processedUSecs() const; qint64 processedUSecs() const;
qint64 elapsedUSecs() const; qint64 elapsedUSecs() const;
QAudio::Error error() const; QAudio::Error error() const;
QAudio::State state() 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 audioThreadStart();
void audioThreadStop(); void audioThreadStop();
void audioThreadDrain(); void audioThreadDrain();
void audioDeviceStop(); void audioDeviceStop();
void audioDeviceIdle(); void audioDeviceIdle();
void audioDeviceError(); void audioDeviceError();
@@ -149,24 +172,33 @@ public:
void startTimers(); void startTimers();
void stopTimers(); void stopTimers();
void setVolume(qreal); QByteArray m_device;
qreal volume() const;
private slots: bool m_isOpen;
void deviceStopped(); int m_internalBufferSize;
void inputReady(); 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;
private: QAudio::Error m_errorCode;
enum { Running, Draining, Stopped }; QAudio::State m_stateCode;
static OSStatus renderCallback(void* inRefCon,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList* ioData);
}; };
QT_END_NAMESPACE QT_END_NAMESPACE
#endif #endif // IOSAUDIOOUTPUT_H

View File

@@ -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 <QtCore/QTimer>
#include <QtCore/QDebug>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#if defined(Q_OS_OSX)
# include <CoreServices/CoreServices.h>
#endif
QT_BEGIN_NAMESPACE
static const int DEFAULT_BUFFER_SIZE = 8 * 1024;
CoreAudioOutputBuffer::CoreAudioOutputBuffer(int bufferSize, int maxPeriodSize, const QAudioFormat &audioFormat)
: m_deviceError(false)
, m_maxPeriodSize(maxPeriodSize)
, m_device(0)
{
m_buffer = new CoreAudioRingBuffer(bufferSize + (bufferSize % maxPeriodSize == 0 ? 0 : maxPeriodSize - (bufferSize % maxPeriodSize)));
m_bytesPerFrame = (audioFormat.sampleSize() / 8) * audioFormat.channelCount();
m_periodTime = m_maxPeriodSize / m_bytesPerFrame * 1000 / audioFormat.sampleRate();
m_fillTimer = new QTimer(this);
connect(m_fillTimer, SIGNAL(timeout()), SLOT(fillBuffer()));
}
CoreAudioOutputBuffer::~CoreAudioOutputBuffer()
{
delete m_buffer;
}
qint64 CoreAudioOutputBuffer::readFrames(char *data, qint64 maxFrames)
{
bool wecan = true;
qint64 framesRead = 0;
while (wecan && framesRead < maxFrames) {
CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion((maxFrames - framesRead) * m_bytesPerFrame);
if (region.second > 0) {
// Ensure that we only read whole frames.
region.second -= region.second % m_bytesPerFrame;
if (region.second > 0) {
memcpy(data + (framesRead * m_bytesPerFrame), region.first, region.second);
framesRead += region.second / m_bytesPerFrame;
} else
wecan = false; // If there is only a partial frame left we should exit.
}
else
wecan = false;
m_buffer->releaseReadRegion(region);
}
if (framesRead == 0 && m_deviceError)
framesRead = -1;
return framesRead;
}
qint64 CoreAudioOutputBuffer::writeBytes(const char *data, qint64 maxSize)
{
bool wecan = true;
qint64 bytesWritten = 0;
maxSize -= maxSize % m_bytesPerFrame;
while (wecan && bytesWritten < maxSize) {
CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(maxSize - bytesWritten);
if (region.second > 0) {
memcpy(region.first, data + bytesWritten, region.second);
bytesWritten += region.second;
}
else
wecan = false;
m_buffer->releaseWriteRegion(region);
}
if (bytesWritten > 0)
emit readyRead();
return bytesWritten;
}
int CoreAudioOutputBuffer::available() const
{
return m_buffer->free();
}
void CoreAudioOutputBuffer::reset()
{
m_buffer->reset();
m_device = 0;
m_deviceError = false;
}
void CoreAudioOutputBuffer::setPrefetchDevice(QIODevice *device)
{
if (m_device != device) {
m_device = device;
if (m_device != 0)
fillBuffer();
}
}
void CoreAudioOutputBuffer::startFillTimer()
{
if (m_device != 0)
m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime);
}
void CoreAudioOutputBuffer::stopFillTimer()
{
m_fillTimer->stop();
}
void CoreAudioOutputBuffer::fillBuffer()
{
const int free = m_buffer->free();
const int writeSize = free - (free % m_maxPeriodSize);
if (writeSize > 0) {
bool wecan = true;
int filled = 0;
while (!m_deviceError && wecan && filled < writeSize) {
CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(writeSize - filled);
if (region.second > 0) {
region.second = m_device->read(region.first, region.second);
if (region.second > 0)
filled += region.second;
else if (region.second == 0)
wecan = false;
else if (region.second < 0) {
m_fillTimer->stop();
region.second = 0;
m_deviceError = true;
}
}
else
wecan = false;
m_buffer->releaseWriteRegion(region);
}
if (filled > 0)
emit readyRead();
}
}
CoreAudioOutputDevice::CoreAudioOutputDevice(CoreAudioOutputBuffer *audioBuffer, QObject *parent)
: QIODevice(parent)
, m_audioBuffer(audioBuffer)
{
open(QIODevice::WriteOnly | QIODevice::Unbuffered);
}
qint64 CoreAudioOutputDevice::readData(char *data, qint64 len)
{
Q_UNUSED(data);
Q_UNUSED(len);
return 0;
}
qint64 CoreAudioOutputDevice::writeData(const char *data, qint64 len)
{
return m_audioBuffer->writeBytes(data, len);
}
CoreAudioOutput::CoreAudioOutput(const QByteArray &device)
: m_isOpen(false)
, m_internalBufferSize(DEFAULT_BUFFER_SIZE)
, m_totalFrames(0)
, m_audioIO(0)
, m_audioUnit(0)
, m_startTime(0)
, m_audioBuffer(0)
, m_cachedVolume(1.0)
, m_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<CoreAudioOutput*>(inRefCon);
const int threadState = d->m_audioThreadState.fetchAndAddAcquire(0);
if (threadState == Stopped) {
ioData->mBuffers[0].mDataByteSize = 0;
d->audioDeviceStop();
}
else {
const UInt32 bytesPerFrame = d->m_streamFormat.mBytesPerFrame;
qint64 framesRead;
framesRead = d->m_audioBuffer->readFrames((char*)ioData->mBuffers[0].mData,
ioData->mBuffers[0].mDataByteSize / bytesPerFrame);
if (framesRead > 0) {
ioData->mBuffers[0].mDataByteSize = framesRead * bytesPerFrame;
d->m_totalFrames += framesRead;
}
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"

View File

@@ -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 <qaudiosystemplugin.h>
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<QByteArray> 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

View File

@@ -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<QByteArray> 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"

View File

@@ -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 <QObject>
#ifdef QT_DEBUG_COREAUDIO
# include <QtCore/QDebug>
#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<QByteArray> inputDevices();
QList<QByteArray> 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 &copy);
CoreAudioSessionManager& operator =(CoreAudioSessionManager const &copy);
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

View File

@@ -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 <AVFoundation/AVAudioSession.h>
#import <Foundation/Foundation.h>
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<QByteArray> 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<QByteArray> inputDevices;
inputDevices << "default";
return inputDevices;
}
QList<QByteArray> 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<QByteArray> 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"

View File

@@ -3,7 +3,7 @@
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal ** Contact: http://www.qt-project.org/legal
** **
** This file is part of the Qt Toolkit. ** This file is part of the plugins of the Qt Toolkit.
** **
** $QT_BEGIN_LICENSE:LGPL$ ** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage ** Commercial License Usage
@@ -39,80 +39,42 @@
** **
****************************************************************************/ ****************************************************************************/
// #ifndef IOSAUDIOUTILS_H
// W A R N I N G #define IOSAUDIOUTILS_H
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of other Qt classes. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <CoreAudio/CoreAudioTypes.h>
#ifndef QAUDIO_MAC_P_H #include <QtMultimedia/QAudioFormat>
#define QAUDIO_MAC_P_H #include <QtCore/qglobal.h>
#include <CoreAudio/CoreAudio.h>
#include <QtCore/qdebug.h>
#include <QtCore/qatomic.h>
#include <qaudioformat.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
extern QAudioFormat toQAudioFormat(const AudioStreamBasicDescription& streamFormat); class CoreAudioUtils
extern AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat); {
public:
static quint64 currentTime();
static double frequency();
static QAudioFormat toQAudioFormat(const AudioStreamBasicDescription& streamFormat);
static AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat);
class QAudioRingBuffer private:
static void initialize();
static double sFrequency;
static bool sIsInitialized;
};
class CoreAudioRingBuffer
{ {
public: public:
typedef QPair<char*, int> Region; typedef QPair<char*, int> Region;
QAudioRingBuffer(int bufferSize); CoreAudioRingBuffer(int bufferSize);
~QAudioRingBuffer(); ~CoreAudioRingBuffer();
Region acquireReadRegion(int size) Region acquireReadRegion(int size);
{ void releaseReadRegion(Region const& region);
const int used = m_bufferUsed.fetchAndAddAcquire(0); Region acquireWriteRegion(int size);
void releaseWriteRegion(Region const& region);
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 used() const;
int free() const; int free() const;
@@ -130,6 +92,4 @@ private:
QT_END_NAMESPACE QT_END_NAMESPACE
#endif // QAUDIO_MAC_P_H #endif // IOSAUDIOUTILS_H

View File

@@ -3,7 +3,7 @@
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal ** Contact: http://www.qt-project.org/legal
** **
** This file is part of the Qt Toolkit. ** This file is part of the plugins of the Qt Toolkit.
** **
** $QT_BEGIN_LICENSE:LGPL$ ** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage ** Commercial License Usage
@@ -39,13 +39,38 @@
** **
****************************************************************************/ ****************************************************************************/
#include "coreaudioutils.h"
#include "qaudio_mac_p.h" #include <mach/mach_time.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
// Conversion double CoreAudioUtils::sFrequency = 0.0;
QAudioFormat toQAudioFormat(AudioStreamBasicDescription const& sf) bool CoreAudioUtils::sIsInitialized = false;
void CoreAudioUtils::initialize()
{
struct mach_timebase_info timeBaseInfo;
mach_timebase_info(&timeBaseInfo);
sFrequency = static_cast<double>(timeBaseInfo.denom) / static_cast<double>(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; QAudioFormat audioFormat;
@@ -64,7 +89,7 @@ QAudioFormat toQAudioFormat(AudioStreamBasicDescription const& sf)
return audioFormat; return audioFormat;
} }
AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat) AudioStreamBasicDescription CoreAudioUtils::toAudioStreamBasicDescription(QAudioFormat const& audioFormat)
{ {
AudioStreamBasicDescription sf; AudioStreamBasicDescription sf;
@@ -91,34 +116,81 @@ AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& au
} }
// QAudioRingBuffer // QAudioRingBuffer
QAudioRingBuffer::QAudioRingBuffer(int bufferSize): CoreAudioRingBuffer::CoreAudioRingBuffer(int bufferSize):
m_bufferSize(bufferSize) m_bufferSize(bufferSize)
{ {
m_buffer = new char[m_bufferSize]; m_buffer = new char[m_bufferSize];
reset(); reset();
} }
QAudioRingBuffer::~QAudioRingBuffer() CoreAudioRingBuffer::~CoreAudioRingBuffer()
{ {
delete m_buffer; delete m_buffer;
} }
int QAudioRingBuffer::used() const 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 &region)
{
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 &region)
{
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(); return m_bufferUsed.load();
} }
int QAudioRingBuffer::free() const int CoreAudioRingBuffer::free() const
{ {
return m_bufferSize - m_bufferUsed.load(); return m_bufferSize - m_bufferUsed.load();
} }
int QAudioRingBuffer::size() const int CoreAudioRingBuffer::size() const
{ {
return m_bufferSize; return m_bufferSize;
} }
void QAudioRingBuffer::reset() void CoreAudioRingBuffer::reset()
{ {
m_readPos = 0; m_readPos = 0;
m_writePos = 0; m_writePos = 0;
@@ -126,5 +198,3 @@ void QAudioRingBuffer::reset()
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@@ -45,7 +45,7 @@ unix:!mac:!android {
} }
mac:!simulator { mac:!simulator {
SUBDIRS += audiocapture SUBDIRS += audiocapture coreaudio
config_avfoundation: SUBDIRS += avfoundation config_avfoundation: SUBDIRS += avfoundation

View File

@@ -829,8 +829,9 @@ void tst_QAudioOutput::pushSuspendResume()
audioOutput.resume(); audioOutput.resume();
// Give backends running in separate threads a chance to resume. // Give backends running in separate threads a chance to resume
QTest::qWait(100); // but not too much or the rest of the file may be processed
QTest::qWait(20);
// Check that QAudioOutput immediately transitions to ActiveState // Check that QAudioOutput immediately transitions to ActiveState
QVERIFY2((stateSignal.count() == 1), QVERIFY2((stateSignal.count() == 1),