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:
committed by
The Qt Project
parent
044e48d5a4
commit
b357c55f2d
@@ -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 \
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
3
src/plugins/coreaudio/coreaudio.json
Normal file
3
src/plugins/coreaudio/coreaudio.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"Keys": ["default"]
|
||||||
|
}
|
||||||
39
src/plugins/coreaudio/coreaudio.pro
Normal file
39
src/plugins/coreaudio/coreaudio.pro
Normal 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
|
||||||
@@ -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
|
||||||
386
src/plugins/coreaudio/coreaudiodeviceinfo.mm
Normal file
386
src/plugins/coreaudio/coreaudiodeviceinfo.mm
Normal 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"
|
||||||
267
src/plugins/coreaudio/coreaudioinput.h
Normal file
267
src/plugins/coreaudio/coreaudioinput.h
Normal 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
|
||||||
1018
src/plugins/coreaudio/coreaudioinput.mm
Normal file
1018
src/plugins/coreaudio/coreaudioinput.mm
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
732
src/plugins/coreaudio/coreaudiooutput.mm
Normal file
732
src/plugins/coreaudio/coreaudiooutput.mm
Normal 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"
|
||||||
65
src/plugins/coreaudio/coreaudioplugin.h
Normal file
65
src/plugins/coreaudio/coreaudioplugin.h
Normal 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
|
||||||
80
src/plugins/coreaudio/coreaudioplugin.mm
Normal file
80
src/plugins/coreaudio/coreaudioplugin.mm
Normal 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"
|
||||||
130
src/plugins/coreaudio/coreaudiosessionmanager.h
Normal file
130
src/plugins/coreaudio/coreaudiosessionmanager.h
Normal 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 ©);
|
||||||
|
CoreAudioSessionManager& operator =(CoreAudioSessionManager const ©);
|
||||||
|
|
||||||
|
CoreAudioSessionObserver *m_sessionObserver;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef QT_DEBUG_COREAUDIO
|
||||||
|
QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category);
|
||||||
|
QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option);
|
||||||
|
QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionCategorys)
|
||||||
|
Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionCategoryOptions)
|
||||||
|
Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionModes)
|
||||||
|
|
||||||
|
#endif // IOSAUDIOSESSIONMANAGER_H
|
||||||
481
src/plugins/coreaudio/coreaudiosessionmanager.mm
Normal file
481
src/plugins/coreaudio/coreaudiosessionmanager.mm
Normal 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"
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
@@ -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 ®ion)
|
||||||
|
{
|
||||||
|
m_readPos = (m_readPos + region.second) % m_bufferSize;
|
||||||
|
|
||||||
|
m_bufferUsed.fetchAndAddRelease(-region.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreAudioRingBuffer::Region CoreAudioRingBuffer::acquireWriteRegion(int size)
|
||||||
|
{
|
||||||
|
const int free = m_bufferSize - m_bufferUsed.fetchAndAddAcquire(0);
|
||||||
|
|
||||||
|
Region output;
|
||||||
|
|
||||||
|
if (free > 0) {
|
||||||
|
const int writeSize = qMin(size, qMin(m_bufferSize - m_writePos, free));
|
||||||
|
output = writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region(0, 0);
|
||||||
|
} else {
|
||||||
|
output = Region(0, 0);
|
||||||
|
}
|
||||||
|
#ifdef QT_DEBUG_COREAUDIO
|
||||||
|
qDebug("acquireWriteRegion(%d) free: %d returning Region(%p, %d)", size, free, output.first, output.second);
|
||||||
|
#endif
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
void CoreAudioRingBuffer::releaseWriteRegion(const CoreAudioRingBuffer::Region ®ion)
|
||||||
|
{
|
||||||
|
m_writePos = (m_writePos + region.second) % m_bufferSize;
|
||||||
|
|
||||||
|
m_bufferUsed.fetchAndAddRelease(region.second);
|
||||||
|
#ifdef QT_DEBUG_COREAUDIO
|
||||||
|
qDebug("releaseWriteRegion(%p,%d): m_writePos:%d", region.first, region.second, m_writePos);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int CoreAudioRingBuffer::used() const
|
||||||
{
|
{
|
||||||
return m_bufferUsed.load();
|
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
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ unix:!mac:!android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mac:!simulator {
|
mac:!simulator {
|
||||||
SUBDIRS += audiocapture
|
SUBDIRS += audiocapture coreaudio
|
||||||
|
|
||||||
config_avfoundation: SUBDIRS += avfoundation
|
config_avfoundation: SUBDIRS += avfoundation
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
Reference in New Issue
Block a user