summaryrefslogtreecommitdiffstats
path: root/src/plugins/coreaudio/coreaudiodeviceinfo.mm
diff options
context:
space:
mode:
authorAndy Nichols <andy.nichols@digia.com>2013-03-08 15:18:36 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-19 14:36:28 +0200
commitb357c55f2dfe44e5c2a2524b93478aecf668ca0a (patch)
tree6dec038966d3a45e708a3c4f8d8bb4df1e55bd46 /src/plugins/coreaudio/coreaudiodeviceinfo.mm
parent044e48d5a4e0281efb1f6d5136c9a732e3119559 (diff)
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>
Diffstat (limited to 'src/plugins/coreaudio/coreaudiodeviceinfo.mm')
-rw-r--r--src/plugins/coreaudio/coreaudiodeviceinfo.mm386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/plugins/coreaudio/coreaudiodeviceinfo.mm b/src/plugins/coreaudio/coreaudiodeviceinfo.mm
new file mode 100644
index 000000000..77a9b835d
--- /dev/null
+++ b/src/plugins/coreaudio/coreaudiodeviceinfo.mm
@@ -0,0 +1,386 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "coreaudiodeviceinfo.h"
+#include "coreaudioutils.h"
+#if defined(Q_OS_IOS)
+# include "coreaudiosessionmanager.h"
+#endif
+
+#include <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"