diff options
author | Lars Knoll <lars.knoll@qt.io> | 2020-12-16 15:45:00 +0100 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2021-01-20 15:28:16 +0000 |
commit | 57888ffdaf4ebf0538954aa47f101ce0af1887de (patch) | |
tree | e1bb199aab21696815b095351024cb827115b9b3 /src/multimedia/audio | |
parent | 494c116bf1248fd74bf800785c50673aff2a47fc (diff) |
Move coreaudio code into Qt Multimedia
Change-Id: I8662d63fb9e17ae2c50ca3095f1baba4d626f293
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/multimedia/audio')
-rw-r--r-- | src/multimedia/audio/audio.pri | 2 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/coreaudio.pri | 27 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo.mm | 346 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo_p.h | 93 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudioinput.mm | 1005 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudioinput_p.h | 277 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiointerface.mm | 75 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiointerface_p.h | 69 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiooutput.mm | 752 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiooutput_p.h | 216 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiosessionmanager.mm | 472 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudiosessionmanager_p.h | 136 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudioutils.mm | 198 | ||||
-rw-r--r-- | src/multimedia/audio/coreaudio/qcoreaudioutils_p.h | 104 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiosystem.cpp | 6 |
15 files changed, 3776 insertions, 2 deletions
diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri index 7cd59c804..5796be260 100644 --- a/src/multimedia/audio/audio.pri +++ b/src/multimedia/audio/audio.pri @@ -47,8 +47,8 @@ qtConfig(pulseaudio) { #android:SUBDIRS += opensles #qnx:SUBDIRS += qnx-audio #win32:SUBDIRS += windowsaudio -#darwin:!watchos:SUBDIRS += coreaudio +darwin:!watchos:SUBDIRS += include(coreaudio/coreaudio.pri) qtConfig(pulseaudio): include(pulseaudio/pulseaudio.pri) qtConfig(alsa): include(alsa/alsa.pri) diff --git a/src/multimedia/audio/coreaudio/coreaudio.pri b/src/multimedia/audio/coreaudio/coreaudio.pri new file mode 100644 index 000000000..65f66b366 --- /dev/null +++ b/src/multimedia/audio/coreaudio/coreaudio.pri @@ -0,0 +1,27 @@ +HEADERS += \ + audio/coreaudio/qcoreaudiodeviceinfo_p.h \ + audio/coreaudio/qcoreaudioinput_p.h \ + audio/coreaudio/qcoreaudiooutput_p.h \ + audio/coreaudio/qcoreaudiointerface_p.h \ + audio/coreaudio/qcoreaudioutils_p.h + +OBJECTIVE_SOURCES += \ + audio/coreaudio/qcoreaudiodeviceinfo.mm \ + audio/coreaudio/qcoreaudioinput.mm \ + audio/coreaudio/qcoreaudiooutput.mm \ + audio/coreaudio/qcoreaudiointerface.mm \ + audio/coreaudio/qcoreaudioutils.mm + +ios|tvos { + HEADERS += audio/coreaudio/qcoreaudiosessionmanager_p.h + OBJECTIVE_SOURCES += audio/coreaudio/qcoreaudiosessionmanager.mm + LIBS += -framework Foundation -framework AVFoundation +} else { + LIBS += \ + -framework ApplicationServices \ + -framework AudioUnit +} + +LIBS += \ + -framework CoreAudio \ + -framework AudioToolbox diff --git a/src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo.mm b/src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo.mm new file mode 100644 index 000000000..946b3beab --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo.mm @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcoreaudiodeviceinfo_p.h" +#include "qcoreaudioutils_p.h" +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +# include "qcoreaudiosessionmanager_p.h" +#endif + +#include <QtCore/QDataStream> +#include <QtCore/QDebug> +#include <QtCore/QSet> +#include <QIODevice> + +QT_BEGIN_NAMESPACE + +QCoreAudioDeviceInfo::QCoreAudioDeviceInfo(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 QCoreAudioDeviceInfo::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 QCoreAudioDeviceInfo::isFormatSupported(const QAudioFormat &format) const +{ + QCoreAudioDeviceInfo *self = const_cast<QCoreAudioDeviceInfo*>(this); + + //Sample rates are more of a suggestion with CoreAudio so as long as we get a + //sane value then we can likely use it. + return format.isValid() + && format.codec() == QString::fromLatin1("audio/pcm") + && format.sampleRate() > 0 + && self->supportedChannelCounts().contains(format.channelCount()) + && self->supportedSampleSizes().contains(format.sampleSize()); +} + + +QString QCoreAudioDeviceInfo::deviceName() const +{ + return m_device; +} + + +QStringList QCoreAudioDeviceInfo::supportedCodecs() +{ + return QStringList() << QString::fromLatin1("audio/pcm"); +} + + +QList<int> QCoreAudioDeviceInfo::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].mMinimum << 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.values(); +} + + +QList<int> QCoreAudioDeviceInfo::supportedChannelCounts() +{ + static QList<int> supportedChannels; + + if (supportedChannels.isEmpty()) { + // If the number of channels is not supported by an audio device, Core Audio will + // automatically convert the audio data. + for (int i = 1; i <= 16; ++i) + supportedChannels.append(i); + } + + return supportedChannels; +} + + +QList<int> QCoreAudioDeviceInfo::supportedSampleSizes() +{ + return QList<int>() << 8 << 16 << 24 << 32 << 64; +} + + +QList<QAudioFormat::Endian> QCoreAudioDeviceInfo::supportedByteOrders() +{ + return QList<QAudioFormat::Endian>() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian; +} + + +QList<QAudioFormat::SampleType> QCoreAudioDeviceInfo::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 QCoreAudioDeviceInfo::defaultDevice(QAudio::Mode mode) +{ +#if defined(Q_OS_OSX) + AudioDeviceID audioDevice; + UInt32 size = sizeof(audioDevice); + const AudioObjectPropertySelector selector = (mode == QAudio::AudioOutput) ? kAudioHardwarePropertyDefaultOutputDevice + : kAudioHardwarePropertyDefaultInputDevice; + AudioObjectPropertyAddress defaultDevicePropertyAddress = { selector, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, + &defaultDevicePropertyAddress, + 0, NULL, &size, &audioDevice) != noErr) { + qWarning("QAudioDeviceInfo: Unable to find default %s device", (mode == QAudio::AudioOutput) ? "output" : "input"); + return QByteArray(); + } + + return get_device_info(audioDevice, mode); +#else //iOS + const auto &devices = (mode == QAudio::AudioOutput) ? CoreAudioSessionManager::instance().outputDevices() + : CoreAudioSessionManager::instance().inputDevices(); + return !devices.isEmpty() ? devices.first() : QByteArray(); +#endif +} + +QList<QByteArray> QCoreAudioDeviceInfo::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) { + const QByteArray &info = get_device_info(audioDevices[i], mode); + if (!info.isNull()) + devices << info; + } + } + + delete[] audioDevices; + } + } +#else //iOS + if (mode == QAudio::AudioOutput) + return CoreAudioSessionManager::instance().outputDevices(); + if (mode == QAudio::AudioInput) + return CoreAudioSessionManager::instance().inputDevices(); +#endif + + return devices; +} + +QT_END_NAMESPACE + +#include "moc_qcoreaudiodeviceinfo_p.cpp" diff --git a/src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo_p.h b/src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo_p.h new file mode 100644 index 000000000..7c5004f10 --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiodeviceinfo_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $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 purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qaudiosystem_p.h> + +#if defined(Q_OS_OSX) +# include <CoreAudio/CoreAudio.h> +#endif + +QT_BEGIN_NAMESPACE + +class QCoreAudioDeviceInfo : public QAbstractAudioDeviceInfo +{ + Q_OBJECT + +public: + QCoreAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode); + ~QCoreAudioDeviceInfo() {} + + QAudioFormat preferredFormat() const; + bool isFormatSupported(const QAudioFormat &format) const; + QString deviceName() const; + QStringList supportedCodecs(); + QList<int> supportedSampleRates(); + QList<int> supportedChannelCounts(); + QList<int> supportedSampleSizes(); + QList<QAudioFormat::Endian> supportedByteOrders(); + QList<QAudioFormat::SampleType> supportedSampleTypes(); + + static QByteArray defaultDevice(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 + +#endif diff --git a/src/multimedia/audio/coreaudio/qcoreaudioinput.mm b/src/multimedia/audio/coreaudio/qcoreaudioinput.mm new file mode 100644 index 000000000..89b63ae0f --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudioinput.mm @@ -0,0 +1,1005 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qcoreaudioinput_p.h" +#include "qcoreaudiosessionmanager_p.h" +#include "qcoreaudiodeviceinfo_p.h" +#include "qcoreaudioutils_p.h" + +#if defined(Q_OS_OSX) +# include <AudioUnit/AudioComponent.h> +#endif + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +# include "coreaudiosessionmanager.h" +#endif + +#include <QtMultimedia/private/qaudiohelpers_p.h> +#include <QtCore/QDataStream> +#include <QtCore/QDebug> + +QT_BEGIN_NAMESPACE + +static const int DEFAULT_BUFFER_SIZE = 4 * 1024; + +QCoreAudioBufferList::QCoreAudioBufferList(const AudioStreamBasicDescription &streamFormat) + : m_owner(false) + , m_streamDescription(streamFormat) +{ + const bool isInterleaved = (m_streamDescription.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; + const int numberOfBuffers = isInterleaved ? 1 : m_streamDescription.mChannelsPerFrame; + + m_dataSize = 0; + + m_bufferList = reinterpret_cast<AudioBufferList*>(malloc(sizeof(AudioBufferList) + + (sizeof(AudioBuffer) * numberOfBuffers))); + + m_bufferList->mNumberBuffers = numberOfBuffers; + for (int i = 0; i < numberOfBuffers; ++i) { + m_bufferList->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1; + m_bufferList->mBuffers[i].mDataByteSize = 0; + m_bufferList->mBuffers[i].mData = 0; + } +} + +QCoreAudioBufferList::QCoreAudioBufferList(const AudioStreamBasicDescription &streamFormat, char *buffer, int bufferSize) + : m_owner(false) + , m_streamDescription(streamFormat) + , m_bufferList(0) +{ + m_dataSize = bufferSize; + + m_bufferList = reinterpret_cast<AudioBufferList*>(malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer))); + + m_bufferList->mNumberBuffers = 1; + m_bufferList->mBuffers[0].mNumberChannels = 1; + m_bufferList->mBuffers[0].mDataByteSize = m_dataSize; + m_bufferList->mBuffers[0].mData = buffer; +} + +QCoreAudioBufferList::QCoreAudioBufferList(const AudioStreamBasicDescription &streamFormat, int framesToBuffer) + : m_owner(true) + , m_streamDescription(streamFormat) + , m_bufferList(0) +{ + const bool isInterleaved = (m_streamDescription.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; + const int numberOfBuffers = isInterleaved ? 1 : m_streamDescription.mChannelsPerFrame; + + m_dataSize = framesToBuffer * m_streamDescription.mBytesPerFrame; + + m_bufferList = reinterpret_cast<AudioBufferList*>(malloc(sizeof(AudioBufferList) + + (sizeof(AudioBuffer) * numberOfBuffers))); + m_bufferList->mNumberBuffers = numberOfBuffers; + for (int i = 0; i < numberOfBuffers; ++i) { + m_bufferList->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1; + m_bufferList->mBuffers[i].mDataByteSize = m_dataSize; + m_bufferList->mBuffers[i].mData = malloc(m_dataSize); + } +} + +QCoreAudioBufferList::~QCoreAudioBufferList() +{ + if (m_owner) { + for (UInt32 i = 0; i < m_bufferList->mNumberBuffers; ++i) + free(m_bufferList->mBuffers[i].mData); + } + + free(m_bufferList); +} + +char *QCoreAudioBufferList::data(int buffer) const +{ + return static_cast<char*>(m_bufferList->mBuffers[buffer].mData); +} + +qint64 QCoreAudioBufferList::bufferSize(int buffer) const +{ + return m_bufferList->mBuffers[buffer].mDataByteSize; +} + +int QCoreAudioBufferList::frameCount(int buffer) const +{ + return m_bufferList->mBuffers[buffer].mDataByteSize / m_streamDescription.mBytesPerFrame; +} + +int QCoreAudioBufferList::packetCount(int buffer) const +{ + return m_bufferList->mBuffers[buffer].mDataByteSize / m_streamDescription.mBytesPerPacket; +} + +int QCoreAudioBufferList::packetSize() const +{ + return m_streamDescription.mBytesPerPacket; +} + +void QCoreAudioBufferList::reset() +{ + for (UInt32 i = 0; i < m_bufferList->mNumberBuffers; ++i) { + m_bufferList->mBuffers[i].mDataByteSize = m_dataSize; + m_bufferList->mBuffers[i].mData = 0; + } +} + +QCoreAudioPacketFeeder::QCoreAudioPacketFeeder(QCoreAudioBufferList *abl) + : m_audioBufferList(abl) +{ + m_totalPackets = m_audioBufferList->packetCount(); + m_position = 0; +} + +bool QCoreAudioPacketFeeder::feed(AudioBufferList &dst, UInt32 &packetCount) +{ + if (m_position == m_totalPackets) { + dst.mBuffers[0].mDataByteSize = 0; + packetCount = 0; + return false; + } + + if (m_totalPackets - m_position < packetCount) + packetCount = m_totalPackets - m_position; + + dst.mBuffers[0].mDataByteSize = packetCount * m_audioBufferList->packetSize(); + dst.mBuffers[0].mData = m_audioBufferList->data() + (m_position * m_audioBufferList->packetSize()); + + m_position += packetCount; + + return true; +} + +bool QCoreAudioPacketFeeder::empty() const +{ + return m_position == m_totalPackets; +} + +QCoreAudioInputBuffer::QCoreAudioInputBuffer(int bufferSize, int maxPeriodSize, const AudioStreamBasicDescription &inputFormat, const AudioStreamBasicDescription &outputFormat, QObject *parent) + : QObject(parent) + , m_deviceError(false) + , m_device(0) + , m_audioConverter(0) + , m_inputFormat(inputFormat) + , m_outputFormat(outputFormat) + , m_volume(qreal(1.0f)) +{ + m_maxPeriodSize = maxPeriodSize; + m_periodTime = m_maxPeriodSize / m_outputFormat.mBytesPerFrame * 1000 / m_outputFormat.mSampleRate; + + m_buffer = new CoreAudioRingBuffer(bufferSize); + + m_inputBufferList = new QCoreAudioBufferList(m_inputFormat); + + m_flushTimer = new QTimer(this); + connect(m_flushTimer, SIGNAL(timeout()), SLOT(flushBuffer())); + + if (CoreAudioUtils::toQAudioFormat(inputFormat) != CoreAudioUtils::toQAudioFormat(outputFormat)) { + if (AudioConverterNew(&m_inputFormat, &m_outputFormat, &m_audioConverter) != noErr) { + qWarning() << "QAudioInput: Unable to create an Audio Converter"; + m_audioConverter = 0; + } + } + + m_qFormat = CoreAudioUtils::toQAudioFormat(inputFormat); // we adjust volume before conversion +} + +QCoreAudioInputBuffer::~QCoreAudioInputBuffer() +{ + delete m_buffer; +} + +qreal QCoreAudioInputBuffer::volume() const +{ + return m_volume; +} + +void QCoreAudioInputBuffer::setVolume(qreal v) +{ + m_volume = v; +} + +qint64 QCoreAudioInputBuffer::renderFromDevice(AudioUnit audioUnit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames) +{ + const bool pullMode = m_device == 0; + + OSStatus err; + qint64 framesRendered = 0; + + m_inputBufferList->reset(); + err = AudioUnitRender(audioUnit, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + m_inputBufferList->audioBufferList()); + + // adjust volume, if necessary + if (!qFuzzyCompare(m_volume, qreal(1.0f))) { + QAudioHelperInternal::qMultiplySamples(m_volume, + m_qFormat, + m_inputBufferList->data(), /* input */ + m_inputBufferList->data(), /* output */ + m_inputBufferList->bufferSize()); + } + + if (m_audioConverter != 0) { + QCoreAudioPacketFeeder feeder(m_inputBufferList); + + int copied = 0; + const int available = m_buffer->free(); + + while (err == noErr && !feeder.empty()) { + CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied); + + if (region.second == 0) + break; + + AudioBufferList output; + output.mNumberBuffers = 1; + output.mBuffers[0].mNumberChannels = 1; + output.mBuffers[0].mDataByteSize = region.second; + output.mBuffers[0].mData = region.first; + + UInt32 packetSize = region.second / m_outputFormat.mBytesPerPacket; + err = AudioConverterFillComplexBuffer(m_audioConverter, + converterCallback, + &feeder, + &packetSize, + &output, + 0); + region.second = output.mBuffers[0].mDataByteSize; + copied += region.second; + + m_buffer->releaseWriteRegion(region); + } + + framesRendered += copied / m_outputFormat.mBytesPerFrame; + } + else { + const int available = m_inputBufferList->bufferSize(); + bool wecan = true; + int copied = 0; + + while (wecan && copied < available) { + CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied); + + if (region.second > 0) { + memcpy(region.first, m_inputBufferList->data() + copied, region.second); + copied += region.second; + } + else + wecan = false; + + m_buffer->releaseWriteRegion(region); + } + + framesRendered = copied / m_outputFormat.mBytesPerFrame; + } + + if (pullMode && framesRendered > 0) + emit readyRead(); + + return framesRendered; +} + +qint64 QCoreAudioInputBuffer::readBytes(char *data, qint64 len) +{ + bool wecan = true; + qint64 bytesCopied = 0; + + len -= len % m_maxPeriodSize; + while (wecan && bytesCopied < len) { + CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion(len - bytesCopied); + + if (region.second > 0) { + memcpy(data + bytesCopied, region.first, region.second); + bytesCopied += region.second; + } + else + wecan = false; + + m_buffer->releaseReadRegion(region); + } + + return bytesCopied; +} + +void QCoreAudioInputBuffer::setFlushDevice(QIODevice *device) +{ + if (m_device != device) + m_device = device; +} + +void QCoreAudioInputBuffer::startFlushTimer() +{ + if (m_device != 0) { + // We use the period time for the timer, since that's + // around the buffer size (pre conversion >.>) + m_flushTimer->start(qMax(1, m_periodTime)); + } +} + +void QCoreAudioInputBuffer::stopFlushTimer() +{ + m_flushTimer->stop(); +} + +void QCoreAudioInputBuffer::flush(bool all) +{ + if (m_device == 0) + return; + + const int used = m_buffer->used(); + const int readSize = all ? used : used - (used % m_maxPeriodSize); + + if (readSize > 0) { + bool wecan = true; + int flushed = 0; + + while (!m_deviceError && wecan && flushed < readSize) { + CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion(readSize - flushed); + + if (region.second > 0) { + int bytesWritten = m_device->write(region.first, region.second); + if (bytesWritten < 0) { + stopFlushTimer(); + m_deviceError = true; + } + else { + region.second = bytesWritten; + flushed += bytesWritten; + wecan = bytesWritten != 0; + } + } + else + wecan = false; + + m_buffer->releaseReadRegion(region); + } + } +} + +void QCoreAudioInputBuffer::reset() +{ + m_buffer->reset(); + m_deviceError = false; +} + +int QCoreAudioInputBuffer::available() const +{ + return m_buffer->free(); +} + +int QCoreAudioInputBuffer::used() const +{ + return m_buffer->used(); +} + +void QCoreAudioInputBuffer::flushBuffer() +{ + flush(); +} + +OSStatus QCoreAudioInputBuffer::converterCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) +{ + Q_UNUSED(inAudioConverter); + Q_UNUSED(outDataPacketDescription); + + QCoreAudioPacketFeeder* feeder = static_cast<QCoreAudioPacketFeeder*>(inUserData); + + if (!feeder->feed(*ioData, *ioNumberDataPackets)) + return as_empty; + + return noErr; +} + +QCoreAudioInputDevice::QCoreAudioInputDevice(QCoreAudioInputBuffer *audioBuffer, QObject *parent) + : QIODevice(parent) + , m_audioBuffer(audioBuffer) +{ + open(QIODevice::ReadOnly | QIODevice::Unbuffered); + connect(m_audioBuffer, SIGNAL(readyRead()), SIGNAL(readyRead())); +} + +qint64 QCoreAudioInputDevice::readData(char *data, qint64 len) +{ + return m_audioBuffer->readBytes(data, len); +} + +qint64 QCoreAudioInputDevice::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + + return 0; +} + +CoreAudioInput::CoreAudioInput(const QByteArray &device) + : m_isOpen(false) + , m_internalBufferSize(DEFAULT_BUFFER_SIZE) + , m_totalFrames(0) + , m_audioUnit(0) + , m_clockFrequency(CoreAudioUtils::frequency() / 1000) + , m_startTime(0) + , m_errorCode(QAudio::NoError) + , m_stateCode(QAudio::StoppedState) + , m_audioBuffer(0) + , m_volume(1.0) +{ +#if defined(Q_OS_OSX) + quint32 deviceId; + QDataStream dataStream(device); + dataStream >> deviceId >> m_device; + m_audioDeviceId = AudioDeviceID(deviceId); +#else //iOS + m_device = device; +#endif + + m_audioDeviceInfo = new QCoreAudioDeviceInfo(device, QAudio::AudioInput); + + m_intervalTimer = new QTimer(this); + m_intervalTimer->setInterval(1000); + connect(m_intervalTimer, SIGNAL(timeout()), this, SIGNAL(notify())); +} + + +CoreAudioInput::~CoreAudioInput() +{ + close(); + delete m_audioDeviceInfo; +} + +bool CoreAudioInput::open() +{ +#if defined(Q_OS_IOS) + CoreAudioSessionManager::instance().setCategory(CoreAudioSessionManager::PlayAndRecord, CoreAudioSessionManager::MixWithOthers); + CoreAudioSessionManager::instance().setActive(true); +#endif + + if (m_isOpen) + return true; + + UInt32 size = 0; + + AudioComponentDescription componentDescription; + componentDescription.componentType = kAudioUnitType_Output; +#if defined(Q_OS_OSX) + componentDescription.componentSubType = kAudioUnitSubType_HALOutput; +#else + componentDescription.componentSubType = kAudioUnitSubType_RemoteIO; +#endif + componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + componentDescription.componentFlags = 0; + componentDescription.componentFlagsMask = 0; + + AudioComponent component = AudioComponentFindNext(0, &componentDescription); + if (component == 0) { + qWarning() << "QAudioInput: Failed to find Output component"; + return false; + } + + if (AudioComponentInstanceNew(component, &m_audioUnit) != noErr) { + qWarning() << "QAudioInput: Unable to Open Output Component"; + return false; + } + + // Set mode + // switch to input mode + UInt32 enable = 1; + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + 1, + &enable, + sizeof(enable)) != noErr) { + qWarning() << "QAudioInput: Unable to switch to input mode (Enable Input)"; + return false; + } + + enable = 0; + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, + &enable, + sizeof(enable)) != noErr) { + qWarning() << "QAudioInput: Unable to switch to input mode (Disable output)"; + return false; + } + + // register callback + AURenderCallbackStruct callback; + callback.inputProc = inputCallback; + callback.inputProcRefCon = this; + + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + 0, + &callback, + sizeof(callback)) != noErr) { + qWarning() << "QAudioInput: Failed to set AudioUnit callback"; + return false; + } + +#if defined(Q_OS_OSX) + //Set Audio Device + if (AudioUnitSetProperty(m_audioUnit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &m_audioDeviceId, + sizeof(m_audioDeviceId)) != noErr) { + qWarning() << "QAudioInput: Unable to use configured device"; + return false; + } +#endif + + //set format + m_streamFormat = CoreAudioUtils::toAudioStreamBasicDescription(m_audioFormat); + +#if defined(Q_OS_OSX) + if (m_audioFormat == m_audioDeviceInfo->preferredFormat()) { +#endif + + m_deviceFormat = m_streamFormat; + AudioUnitSetProperty(m_audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &m_deviceFormat, + sizeof(m_deviceFormat)); +#if defined(Q_OS_OSX) + } else { + size = sizeof(m_deviceFormat); + if (AudioUnitGetProperty(m_audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 1, + &m_deviceFormat, + &size) != noErr) { + qWarning() << "QAudioInput: Unable to retrieve device format"; + return false; + } + + if (AudioUnitSetProperty(m_audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &m_deviceFormat, + sizeof(m_deviceFormat)) != noErr) { + qWarning() << "QAudioInput: Unable to set device format"; + return false; + } + } +#endif + + //setup buffers + UInt32 numberOfFrames; +#if defined(Q_OS_OSX) + size = sizeof(UInt32); + if (AudioUnitGetProperty(m_audioUnit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Global, + 0, + &numberOfFrames, + &size) != noErr) { + qWarning() << "QAudioInput: Failed to get audio period size"; + return false; + } + //BUG: numberOfFrames gets ignored after this point + + AudioValueRange bufferRange; + size = sizeof(AudioValueRange); + + if (AudioUnitGetProperty(m_audioUnit, + kAudioDevicePropertyBufferFrameSizeRange, + kAudioUnitScope_Global, + 0, + &bufferRange, + &size) != noErr) { + qWarning() << "QAudioInput: Failed to get audio period size range"; + return false; + } + + // See if the requested buffer size is permissible + numberOfFrames = qBound((UInt32)bufferRange.mMinimum, m_internalBufferSize / m_streamFormat.mBytesPerFrame, (UInt32)bufferRange.mMaximum); + + // Set it back + if (AudioUnitSetProperty(m_audioUnit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Global, + 0, + &numberOfFrames, + sizeof(UInt32)) != noErr) { + qWarning() << "QAudioInput: Failed to set audio buffer size"; + return false; + } +#else //iOS + Float32 bufferSize = CoreAudioSessionManager::instance().currentIOBufferDuration(); + bufferSize *= m_streamFormat.mSampleRate; + numberOfFrames = bufferSize; +#endif + + // Now allocate a few buffers to be safe. + m_periodSizeBytes = m_internalBufferSize = numberOfFrames * m_streamFormat.mBytesPerFrame; + + m_audioBuffer = new QCoreAudioInputBuffer(m_internalBufferSize * 4, + m_periodSizeBytes, + m_deviceFormat, + m_streamFormat, + this); + + m_audioBuffer->setVolume(m_volume); + m_audioIO = new QCoreAudioInputDevice(m_audioBuffer, this); + + // Init + if (AudioUnitInitialize(m_audioUnit) != noErr) { + qWarning() << "QAudioInput: Failed to initialize AudioUnit"; + return false; + } + + m_isOpen = true; + + return m_isOpen; + +} + +void CoreAudioInput::close() +{ + if (m_audioUnit != 0) { + AudioOutputUnitStop(m_audioUnit); + AudioUnitUninitialize(m_audioUnit); + AudioComponentInstanceDispose(m_audioUnit); + } + + delete m_audioBuffer; +} + +void CoreAudioInput::start(QIODevice *device) +{ + QIODevice* op = device; + + if (!m_audioDeviceInfo->isFormatSupported(m_audioFormat) || !open()) { + m_stateCode = QAudio::StoppedState; + m_errorCode = QAudio::OpenError; + return; + } + + reset(); + m_audioBuffer->reset(); + m_audioBuffer->setFlushDevice(op); + + if (op == 0) + op = m_audioIO; + + // Start + m_startTime = CoreAudioUtils::currentTime(); + m_totalFrames = 0; + + m_stateCode = QAudio::IdleState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + + audioThreadStart(); +} + + +QIODevice *CoreAudioInput::start() +{ + QIODevice* op = 0; + + if (!m_audioDeviceInfo->isFormatSupported(m_audioFormat) || !open()) { + m_stateCode = QAudio::StoppedState; + m_errorCode = QAudio::OpenError; + return m_audioIO; + } + + reset(); + m_audioBuffer->reset(); + m_audioBuffer->setFlushDevice(op); + + if (op == 0) + op = m_audioIO; + + // Start + m_startTime = CoreAudioUtils::currentTime(); + m_totalFrames = 0; + + m_stateCode = QAudio::IdleState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + + audioThreadStart(); + + return op; +} + + +void CoreAudioInput::stop() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode != QAudio::StoppedState) { + audioThreadStop(); + m_audioBuffer->flush(true); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::StoppedState; + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +void CoreAudioInput::reset() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode != QAudio::StoppedState) { + audioThreadStop(); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::StoppedState; + m_audioBuffer->reset(); + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +void CoreAudioInput::suspend() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode == QAudio::ActiveState || m_stateCode == QAudio::IdleState) { + audioThreadStop(); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::SuspendedState; + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +void CoreAudioInput::resume() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode == QAudio::SuspendedState) { + audioThreadStart(); + + m_errorCode = QAudio::NoError; + m_stateCode = QAudio::ActiveState; + QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode)); + } +} + + +int CoreAudioInput::bytesReady() const +{ + if (!m_audioBuffer) + return 0; + return m_audioBuffer->used(); +} + + +int CoreAudioInput::periodSize() const +{ + return m_periodSizeBytes; +} + + +void CoreAudioInput::setBufferSize(int value) +{ + m_internalBufferSize = value; +} + + +int CoreAudioInput::bufferSize() const +{ + return m_internalBufferSize; +} + + +void CoreAudioInput::setNotifyInterval(int milliSeconds) +{ + if (m_intervalTimer->interval() == milliSeconds) + return; + + if (milliSeconds <= 0) + milliSeconds = 0; + + m_intervalTimer->setInterval(milliSeconds); +} + + +int CoreAudioInput::notifyInterval() const +{ + return m_intervalTimer->interval(); +} + + +qint64 CoreAudioInput::processedUSecs() const +{ + return m_totalFrames * 1000000 / m_audioFormat.sampleRate(); +} + + +qint64 CoreAudioInput::elapsedUSecs() const +{ + if (m_stateCode == QAudio::StoppedState) + return 0; + + return (CoreAudioUtils::currentTime() - m_startTime) / (m_clockFrequency / 1000); +} + + +QAudio::Error CoreAudioInput::error() const +{ + return m_errorCode; +} + + +QAudio::State CoreAudioInput::state() const +{ + return m_stateCode; +} + + +void CoreAudioInput::setFormat(const QAudioFormat &format) +{ + if (m_stateCode == QAudio::StoppedState) + m_audioFormat = format; +} + + +QAudioFormat CoreAudioInput::format() const +{ + return m_audioFormat; +} + + +void CoreAudioInput::setVolume(qreal volume) +{ + m_volume = volume; + if (m_audioBuffer) + m_audioBuffer->setVolume(m_volume); +} + + +qreal CoreAudioInput::volume() const +{ + return m_volume; +} + +void CoreAudioInput::deviceStoppped() +{ + stopTimers(); + emit stateChanged(m_stateCode); +} + +void CoreAudioInput::audioThreadStart() +{ + startTimers(); + m_audioThreadState.storeRelaxed(Running); + AudioOutputUnitStart(m_audioUnit); +} + +void CoreAudioInput::audioThreadStop() +{ + stopTimers(); + if (m_audioThreadState.testAndSetAcquire(Running, Stopped)) + m_threadFinished.wait(&m_mutex); +} + +void CoreAudioInput::audioDeviceStop() +{ + AudioOutputUnitStop(m_audioUnit); + m_audioThreadState.storeRelaxed(Stopped); + m_threadFinished.wakeOne(); +} + +void CoreAudioInput::audioDeviceActive() +{ + if (m_stateCode == QAudio::IdleState) { + QMutexLocker lock(&m_mutex); + m_stateCode = QAudio::ActiveState; + emit stateChanged(m_stateCode); + } +} + +void CoreAudioInput::audioDeviceFull() +{ + if (m_stateCode == QAudio::ActiveState) { + QMutexLocker lock(&m_mutex); + m_errorCode = QAudio::UnderrunError; + m_stateCode = QAudio::IdleState; + emit stateChanged(m_stateCode); + } +} + +void CoreAudioInput::audioDeviceError() +{ + if (m_stateCode == QAudio::ActiveState) { + QMutexLocker lock(&m_mutex); + audioDeviceStop(); + + m_errorCode = QAudio::IOError; + m_stateCode = QAudio::StoppedState; + QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); + } +} + +void CoreAudioInput::startTimers() +{ + m_audioBuffer->startFlushTimer(); + if (m_intervalTimer->interval() > 0) + m_intervalTimer->start(); +} + +void CoreAudioInput::stopTimers() +{ + m_audioBuffer->stopFlushTimer(); + m_intervalTimer->stop(); +} + +OSStatus CoreAudioInput::inputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) +{ + Q_UNUSED(ioData); + + CoreAudioInput* d = static_cast<CoreAudioInput*>(inRefCon); + + const int threadState = d->m_audioThreadState.loadAcquire(); + if (threadState == Stopped) + d->audioDeviceStop(); + else { + qint64 framesWritten; + + framesWritten = d->m_audioBuffer->renderFromDevice(d->m_audioUnit, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames); + + if (framesWritten > 0) { + d->m_totalFrames += framesWritten; + d->audioDeviceActive(); + } else if (framesWritten == 0) + d->audioDeviceFull(); + else if (framesWritten < 0) + d->audioDeviceError(); + } + + return noErr; +} + +QT_END_NAMESPACE + +#include "moc_qcoreaudioinput_p.cpp" diff --git a/src/multimedia/audio/coreaudio/qcoreaudioinput_p.h b/src/multimedia/audio/coreaudio/qcoreaudioinput_p.h new file mode 100644 index 000000000..5e9ecae2d --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudioinput_p.h @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef IOSAUDIOINPUT_H +#define IOSAUDIOINPUT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qaudiosystem_p.h> +#include <AudioUnit/AudioUnit.h> +#include <CoreAudio/CoreAudioTypes.h> +#include <AudioToolbox/AudioToolbox.h> + +#include <QtCore/QIODevice> +#include <QtCore/QWaitCondition> +#include <QtCore/QMutex> +#include <QtCore/QTimer> + +QT_BEGIN_NAMESPACE + +class CoreAudioRingBuffer; +class QCoreAudioPacketFeeder; +class QCoreAudioInputBuffer; +class QCoreAudioInputDevice; + +class QCoreAudioBufferList +{ +public: + QCoreAudioBufferList(AudioStreamBasicDescription const& streamFormat); + QCoreAudioBufferList(AudioStreamBasicDescription const& streamFormat, char *buffer, int bufferSize); + QCoreAudioBufferList(AudioStreamBasicDescription const& streamFormat, int framesToBuffer); + + ~QCoreAudioBufferList(); + + 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 QCoreAudioPacketFeeder +{ +public: + QCoreAudioPacketFeeder(QCoreAudioBufferList *abl); + + bool feed(AudioBufferList& dst, UInt32& packetCount); + bool empty() const; + +private: + UInt32 m_totalPackets; + UInt32 m_position; + QCoreAudioBufferList *m_audioBufferList; +}; + +class QCoreAudioInputBuffer : public QObject +{ + Q_OBJECT + +public: + QCoreAudioInputBuffer(int bufferSize, + int maxPeriodSize, + AudioStreamBasicDescription const& inputFormat, + AudioStreamBasicDescription const& outputFormat, + QObject *parent); + + ~QCoreAudioInputBuffer(); + + 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; + QCoreAudioBufferList *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 QCoreAudioInputDevice : public QIODevice +{ + Q_OBJECT + +public: + QCoreAudioInputDevice(QCoreAudioInputBuffer *audioBuffer, QObject *parent); + + qint64 readData(char *data, qint64 len); + qint64 writeData(const char *data, qint64 len); + + bool isSequential() const { return true; } + +private: + QCoreAudioInputBuffer *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; + QCoreAudioInputBuffer *m_audioBuffer; + QMutex m_mutex; + QWaitCondition m_threadFinished; + QAtomicInt m_audioThreadState; + QTimer *m_intervalTimer; + AudioStreamBasicDescription m_streamFormat; + AudioStreamBasicDescription m_deviceFormat; + QAbstractAudioDeviceInfo *m_audioDeviceInfo; + qreal m_volume; +}; + +QT_END_NAMESPACE + +#endif // IOSAUDIOINPUT_H diff --git a/src/multimedia/audio/coreaudio/qcoreaudiointerface.mm b/src/multimedia/audio/coreaudio/qcoreaudiointerface.mm new file mode 100644 index 000000000..f3679c083 --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiointerface.mm @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qcoreaudiointerface_p.h" + +#include "qcoreaudiodeviceinfo_p.h" +#include "qcoreaudioinput_p.h" +#include "qcoreaudiooutput_p.h" + +QT_BEGIN_NAMESPACE + +QByteArray QCoreAudioInterface::defaultDevice(QAudio::Mode mode) const +{ + return QCoreAudioDeviceInfo::defaultDevice(mode); +} + +QList<QByteArray> QCoreAudioInterface::availableDevices(QAudio::Mode mode) const +{ + return QCoreAudioDeviceInfo::availableDevices(mode); +} + + +QAbstractAudioInput *QCoreAudioInterface::createInput(const QByteArray &device) +{ + return new CoreAudioInput(device); +} + + +QAbstractAudioOutput *QCoreAudioInterface::createOutput(const QByteArray &device) +{ + return new QCoreAudioOutput(device); +} + + +QAbstractAudioDeviceInfo *QCoreAudioInterface::createDeviceInfo(const QByteArray &device, QAudio::Mode mode) +{ + return new QCoreAudioDeviceInfo(device, mode); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/coreaudio/qcoreaudiointerface_p.h b/src/multimedia/audio/coreaudio/qcoreaudiointerface_p.h new file mode 100644 index 000000000..d3b5f7e84 --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiointerface_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef IOSAUDIOPLUGIN_H +#define IOSAUDIOPLUGIN_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qaudiosystem_p.h> + +QT_BEGIN_NAMESPACE + +class QCoreAudioInterface : public QAudioSystemInterface +{ +public: + QByteArray defaultDevice(QAudio::Mode mode) const override; + QList<QByteArray> availableDevices(QAudio::Mode mode) const override; + QAbstractAudioInput *createInput(const QByteArray &device) override; + QAbstractAudioOutput *createOutput(const QByteArray &device) override; + QAbstractAudioDeviceInfo *createDeviceInfo(const QByteArray &device, QAudio::Mode mode) override; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/audio/coreaudio/qcoreaudiooutput.mm b/src/multimedia/audio/coreaudio/qcoreaudiooutput.mm new file mode 100644 index 000000000..ba48469d6 --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiooutput.mm @@ -0,0 +1,752 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qcoreaudiooutput_p.h" +#include "qcoreaudiosessionmanager_p.h" +#include "qcoreaudiodeviceinfo_p.h" +#include "qcoreaudioutils_p.h" + +#include <QtCore/QDataStream> +#include <QtCore/QTimer> +#include <QtCore/QDebug> + +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> +#if defined(Q_OS_OSX) +# include <AudioUnit/AudioComponent.h> +#endif + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +# include <QtMultimedia/private/qaudiohelpers_p.h> +#endif + +QT_BEGIN_NAMESPACE + +static const int DEFAULT_BUFFER_SIZE = 8 * 1024; + +QCoreAudioOutputBuffer::QCoreAudioOutputBuffer(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())); +} + +QCoreAudioOutputBuffer::~QCoreAudioOutputBuffer() +{ + delete m_buffer; +} + +qint64 QCoreAudioOutputBuffer::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 QCoreAudioOutputBuffer::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 QCoreAudioOutputBuffer::available() const +{ + return m_buffer->free(); +} + +void QCoreAudioOutputBuffer::reset() +{ + m_buffer->reset(); + m_device = 0; + m_deviceError = false; +} + +void QCoreAudioOutputBuffer::setPrefetchDevice(QIODevice *device) +{ + if (m_device != device) { + m_device = device; + if (m_device != 0) + fillBuffer(); + } +} + +void QCoreAudioOutputBuffer::startFillTimer() +{ + if (m_device != 0) + m_fillTimer->start(m_buffer->size() / 2 / m_maxPeriodSize * m_periodTime); +} + +void QCoreAudioOutputBuffer::stopFillTimer() +{ + m_fillTimer->stop(); +} + +void QCoreAudioOutputBuffer::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(); + } +} + +QCoreAudioOutputDevice::QCoreAudioOutputDevice(QCoreAudioOutputBuffer *audioBuffer, QObject *parent) + : QIODevice(parent) + , m_audioBuffer(audioBuffer) +{ + open(QIODevice::WriteOnly | QIODevice::Unbuffered); +} + +qint64 QCoreAudioOutputDevice::readData(char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + + return 0; +} + +qint64 QCoreAudioOutputDevice::writeData(const char *data, qint64 len) +{ + return m_audioBuffer->writeBytes(data, len); +} + +QCoreAudioOutput::QCoreAudioOutput(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_volume(1.0) + , m_pullMode(false) + , 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 QCoreAudioDeviceInfo(device, QAudio::AudioOutput); + m_audioThreadState.storeRelaxed(Stopped); + + m_intervalTimer = new QTimer(this); + m_intervalTimer->setInterval(1000); + connect(m_intervalTimer, SIGNAL(timeout()), this, SIGNAL(notify())); +} + +QCoreAudioOutput::~QCoreAudioOutput() +{ + close(); + delete m_audioDeviceInfo; +} + +void QCoreAudioOutput::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_pullMode = true; + m_errorCode = QAudio::NoError; + m_totalFrames = 0; + m_startTime = CoreAudioUtils::currentTime(); + + if (m_stateCode == QAudio::ActiveState) + audioThreadStart(); + + emit stateChanged(m_stateCode); +} + +QIODevice *QCoreAudioOutput::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_pullMode = false; + m_errorCode = QAudio::NoError; + m_totalFrames = 0; + m_startTime = CoreAudioUtils::currentTime(); + + emit stateChanged(m_stateCode); + + return m_audioIO; +} + +void QCoreAudioOutput::stop() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode != QAudio::StoppedState) { + audioThreadDrain(); + + m_stateCode = QAudio::StoppedState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + } +} + +void QCoreAudioOutput::reset() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode != QAudio::StoppedState) { + audioThreadStop(); + + m_stateCode = QAudio::StoppedState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + } +} + +void QCoreAudioOutput::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 QCoreAudioOutput::resume() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode == QAudio::SuspendedState) { + audioThreadStart(); + + m_stateCode = m_pullMode ? QAudio::ActiveState : QAudio::IdleState; + m_errorCode = QAudio::NoError; + emit stateChanged(m_stateCode); + } +} + +int QCoreAudioOutput::bytesFree() const +{ + return m_audioBuffer->available(); +} + +int QCoreAudioOutput::periodSize() const +{ + return m_periodSizeBytes; +} + +void QCoreAudioOutput::setBufferSize(int value) +{ + if (m_stateCode == QAudio::StoppedState) + m_internalBufferSize = value; +} + +int QCoreAudioOutput::bufferSize() const +{ + return m_internalBufferSize; +} + +void QCoreAudioOutput::setNotifyInterval(int milliSeconds) +{ + if (m_intervalTimer->interval() == milliSeconds) + return; + + if (milliSeconds <= 0) + milliSeconds = 0; + + m_intervalTimer->setInterval(milliSeconds); +} + +int QCoreAudioOutput::notifyInterval() const +{ + return m_intervalTimer->interval(); +} + +qint64 QCoreAudioOutput::processedUSecs() const +{ + return m_totalFrames * 1000000 / m_audioFormat.sampleRate(); +} + +qint64 QCoreAudioOutput::elapsedUSecs() const +{ + if (m_stateCode == QAudio::StoppedState) + return 0; + + return (CoreAudioUtils::currentTime() - m_startTime) / (m_clockFrequency / 1000); +} + +QAudio::Error QCoreAudioOutput::error() const +{ + return m_errorCode; +} + +QAudio::State QCoreAudioOutput::state() const +{ + return m_stateCode; +} + +void QCoreAudioOutput::setFormat(const QAudioFormat &format) +{ + if (m_stateCode == QAudio::StoppedState) + m_audioFormat = format; +} + +QAudioFormat QCoreAudioOutput::format() const +{ + return m_audioFormat; +} + +void QCoreAudioOutput::setVolume(qreal volume) +{ + m_cachedVolume = qBound(qreal(0.0), volume, qreal(1.0)); + if (!m_isOpen) + return; + +#if defined(Q_OS_OSX) + //on OS X the volume can be set directly on the AudioUnit + if (AudioUnitSetParameter(m_audioUnit, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0 /* bus */, + m_cachedVolume, + 0) == noErr) + m_volume = m_cachedVolume; +#endif +} + +qreal QCoreAudioOutput::volume() const +{ + return m_cachedVolume; +} + +void QCoreAudioOutput::setCategory(const QString &category) +{ + Q_UNUSED(category); +} + +QString QCoreAudioOutput::category() const +{ + return QString(); +} + +void QCoreAudioOutput::deviceStopped() +{ + m_intervalTimer->stop(); + emit stateChanged(m_stateCode); +} + +void QCoreAudioOutput::inputReady() +{ + QMutexLocker lock(&m_mutex); + if (m_stateCode == QAudio::IdleState) { + audioThreadStart(); + + m_stateCode = QAudio::ActiveState; + m_errorCode = QAudio::NoError; + + emit stateChanged(m_stateCode); + } +} + +OSStatus QCoreAudioOutput::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); + + QCoreAudioOutput* d = static_cast<QCoreAudioOutput*>(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; + +#if defined(Q_OS_MACOS) + // If playback is already stopped. + if (threadState != Running) { + qreal oldVolume = d->m_cachedVolume; + // Decrease volume smoothly. + d->setVolume(d->m_volume / 2); + d->m_cachedVolume = oldVolume; + } +#elif defined(Q_OS_IOS) || defined(Q_OS_TVOS) + // on iOS we have to adjust the sound volume ourselves + if (!qFuzzyCompare(d->m_cachedVolume, qreal(1.0f))) { + QAudioHelperInternal::qMultiplySamples(d->m_cachedVolume, + d->m_audioFormat, + ioData->mBuffers[0].mData, /* input */ + ioData->mBuffers[0].mData, /* output */ + ioData->mBuffers[0].mDataByteSize); + } +#endif + + } + else { + ioData->mBuffers[0].mDataByteSize = 0; + if (framesRead == 0) { + if (threadState == Draining) + d->audioDeviceStop(); + else + d->audioDeviceIdle(); + } + else + d->audioDeviceError(); + } + } + + return noErr; +} + +bool QCoreAudioOutput::open() +{ +#if defined(Q_OS_IOS) + // Set default category to Ambient (implies MixWithOthers). This makes sure audio stops playing + // if the screen is locked or if the Silent switch is toggled. + CoreAudioSessionManager::instance().setCategory(CoreAudioSessionManager::Ambient, CoreAudioSessionManager::None); + CoreAudioSessionManager::instance().setActive(true); +#endif + + if (m_errorCode != QAudio::NoError) + return false; + + if (m_isOpen) { + setVolume(m_cachedVolume); + return true; + } + + AudioComponentDescription componentDescription; + componentDescription.componentType = kAudioUnitType_Output; +#if defined(Q_OS_OSX) + componentDescription.componentSubType = kAudioUnitSubType_HALOutput; +#else + componentDescription.componentSubType = kAudioUnitSubType_RemoteIO; +#endif + 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; + } + + // 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 QCoreAudioOutputBuffer(m_internalBufferSize, m_periodSizeBytes, m_audioFormat); + connect(m_audioBuffer, SIGNAL(readyRead()), SLOT(inputReady())); //Pull + + m_audioIO = new QCoreAudioOutputDevice(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 QCoreAudioOutput::close() +{ + if (m_audioUnit != 0) { + AudioOutputUnitStop(m_audioUnit); + AudioUnitUninitialize(m_audioUnit); + AudioComponentInstanceDispose(m_audioUnit); + } + + delete m_audioBuffer; +} + +void QCoreAudioOutput::audioThreadStart() +{ + startTimers(); + m_audioThreadState.storeRelaxed(Running); + AudioOutputUnitStart(m_audioUnit); +} + +void QCoreAudioOutput::audioThreadStop() +{ + stopTimers(); + if (m_audioThreadState.testAndSetAcquire(Running, Stopped)) + m_threadFinished.wait(&m_mutex, 500); +} + +void QCoreAudioOutput::audioThreadDrain() +{ + stopTimers(); + if (m_audioThreadState.testAndSetAcquire(Running, Draining)) + m_threadFinished.wait(&m_mutex, 500); +} + +void QCoreAudioOutput::audioDeviceStop() +{ + AudioOutputUnitStop(m_audioUnit); + m_audioThreadState.storeRelaxed(Stopped); + m_threadFinished.wakeOne(); +} + +void QCoreAudioOutput::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 QCoreAudioOutput::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 QCoreAudioOutput::startTimers() +{ + m_audioBuffer->startFillTimer(); + if (m_intervalTimer->interval() > 0) + m_intervalTimer->start(); +} + +void QCoreAudioOutput::stopTimers() +{ + m_audioBuffer->stopFillTimer(); + m_intervalTimer->stop(); +} + +QT_END_NAMESPACE + +#include "moc_qcoreaudiooutput_p.cpp" diff --git a/src/multimedia/audio/coreaudio/qcoreaudiooutput_p.h b/src/multimedia/audio/coreaudio/qcoreaudiooutput_p.h new file mode 100644 index 000000000..b3ff6c1ff --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiooutput_p.h @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $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 purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qaudiosystem_p.h> + +#if defined(Q_OS_OSX) +# include <CoreAudio/CoreAudio.h> +#endif +#include <AudioUnit/AudioUnit.h> +#include <CoreAudio/CoreAudioTypes.h> + +#include <QtCore/QIODevice> +#include <QtCore/QWaitCondition> +#include <QtCore/QMutex> + +QT_BEGIN_NAMESPACE + +class QCoreAudioOutputBuffer; +class QTimer; +class QCoreAudioDeviceInfo; +class CoreAudioRingBuffer; + +class QCoreAudioOutputBuffer : public QObject +{ + Q_OBJECT + +public: + QCoreAudioOutputBuffer(int bufferSize, int maxPeriodSize, QAudioFormat const& audioFormat); + ~QCoreAudioOutputBuffer(); + + qint64 readFrames(char *data, qint64 maxFrames); + qint64 writeBytes(const char *data, qint64 maxSize); + + int available() const; + void reset(); + + void setPrefetchDevice(QIODevice *device); + + void startFillTimer(); + void stopFillTimer(); + +signals: + void readyRead(); + +private slots: + void fillBuffer(); + +private: + bool m_deviceError; + int m_maxPeriodSize; + int m_bytesPerFrame; + int m_periodTime; + QIODevice *m_device; + QTimer *m_fillTimer; + CoreAudioRingBuffer *m_buffer; +}; + +class QCoreAudioOutputDevice : public QIODevice +{ +public: + QCoreAudioOutputDevice(QCoreAudioOutputBuffer *audioBuffer, QObject *parent); + + qint64 readData(char *data, qint64 len); + qint64 writeData(const char *data, qint64 len); + + bool isSequential() const { return true; } + +private: + QCoreAudioOutputBuffer *m_audioBuffer; +}; + + +class QCoreAudioOutput : public QAbstractAudioOutput +{ + Q_OBJECT + +public: + QCoreAudioOutput(const QByteArray &device); + ~QCoreAudioOutput(); + + void start(QIODevice *device); + QIODevice *start(); + void stop(); + void reset(); + void suspend(); + void resume(); + int bytesFree() const; + int periodSize() const; + void setBufferSize(int value); + int bufferSize() const; + void setNotifyInterval(int milliSeconds); + int notifyInterval() const; + qint64 processedUSecs() const; + qint64 elapsedUSecs() const; + QAudio::Error error() const; + QAudio::State state() const; + void setFormat(const QAudioFormat &format); + QAudioFormat format() const; + + void setVolume(qreal volume); + qreal volume() const; + + void setCategory(const QString &category); + QString category() const; + +private slots: + void deviceStopped(); + void inputReady(); + +private: + enum { + Running, + Draining, + Stopped + }; + + static OSStatus renderCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); + + bool open(); + void close(); + void audioThreadStart(); + void audioThreadStop(); + void audioThreadDrain(); + void audioDeviceStop(); + void audioDeviceIdle(); + void audioDeviceError(); + + void startTimers(); + void stopTimers(); + + QByteArray m_device; + + bool m_isOpen; + int m_internalBufferSize; + int m_periodSizeBytes; + qint64 m_totalFrames; + QAudioFormat m_audioFormat; + QIODevice *m_audioIO; +#if defined(Q_OS_OSX) + AudioDeviceID m_audioDeviceId; +#endif + AudioUnit m_audioUnit; + Float64 m_clockFrequency; + UInt64 m_startTime; + AudioStreamBasicDescription m_streamFormat; + QCoreAudioOutputBuffer *m_audioBuffer; + QAtomicInt m_audioThreadState; + QWaitCondition m_threadFinished; + QMutex m_mutex; + QTimer *m_intervalTimer; + QCoreAudioDeviceInfo *m_audioDeviceInfo; + qreal m_cachedVolume; + qreal m_volume; + bool m_pullMode; + + QAudio::Error m_errorCode; + QAudio::State m_stateCode; +}; + +QT_END_NAMESPACE + +#endif // IOSAUDIOOUTPUT_H diff --git a/src/multimedia/audio/coreaudio/qcoreaudiosessionmanager.mm b/src/multimedia/audio/coreaudio/qcoreaudiosessionmanager.mm new file mode 100644 index 000000000..08d6020cf --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiosessionmanager.mm @@ -0,0 +1,472 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcoreaudiosessionmanager_p.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]; +} + +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: +#ifndef Q_OS_TVOS + targetCategory = AVAudioSessionCategoryAudioProcessing; +#endif + 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; +#ifndef Q_OS_TVOS + } else if (category == AVAudioSessionCategoryAudioProcessing) { + localCategory = AudioProcessing; +#endif + } 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; +} + +float CoreAudioSessionManager::currentIOBufferDuration() +{ + return [[m_sessionObserver audioSession] IOBufferDuration]; +} + +float CoreAudioSessionManager::preferredSampleRate() +{ + return [[m_sessionObserver audioSession] preferredSampleRate]; +} + +#ifdef QT_DEBUG_COREAUDIO +QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category) +{ + QDebug output = dbg.nospace(); + switch (category) { + case CoreAudioSessionManager::Ambient: + output << "AudioSessionCategoryAmbient"; + break; + case CoreAudioSessionManager::SoloAmbient: + output << "AudioSessionCategorySoloAmbient"; + break; + case CoreAudioSessionManager::Playback: + output << "AudioSessionCategoryPlayback"; + break; + case CoreAudioSessionManager::Record: + output << "AudioSessionCategoryRecord"; + break; + case CoreAudioSessionManager::PlayAndRecord: + output << "AudioSessionCategoryPlayAndRecord"; + break; + case CoreAudioSessionManager::AudioProcessing: + output << "AudioSessionCategoryAudioProcessing"; + break; + case CoreAudioSessionManager::MultiRoute: + output << "AudioSessionCategoryMultiRoute"; + break; + } + return output; +} + +QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option) +{ + QDebug output = dbg.nospace(); + switch (option) { + case CoreAudioSessionManager::None: + output << "AudioSessionCategoryOptionNone"; + break; + case CoreAudioSessionManager::MixWithOthers: + output << "AudioSessionCategoryOptionMixWithOthers"; + break; + case CoreAudioSessionManager::DuckOthers: + output << "AudioSessionCategoryOptionDuckOthers"; + break; + case CoreAudioSessionManager::AllowBluetooth: + output << "AudioSessionCategoryOptionAllowBluetooth"; + break; + case CoreAudioSessionManager::DefaultToSpeaker: + output << "AudioSessionCategoryOptionDefaultToSpeaker"; + break; + } + return output; +} + +QDebug operator<<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode) +{ + QDebug output = dbg.nospace(); + switch (mode) { + case CoreAudioSessionManager::Default: + output << "AudioSessionModeDefault"; + break; + case CoreAudioSessionManager::VoiceChat: + output << "AudioSessionModeVoiceChat"; + break; + case CoreAudioSessionManager::GameChat: + output << "AudioSessionModeGameChat"; + break; + case CoreAudioSessionManager::VideoRecording: + output << "AudioSessionModeVideoRecording"; + break; + case CoreAudioSessionManager::Measurement: + output << "AudioSessionModeMeasurement"; + break; + case CoreAudioSessionManager::MoviePlayback: + output << "AudioSessionModeMoviePlayback"; + break; + } + return output; +} +#endif + +QT_END_NAMESPACE + +#include "moc_coreaudiosessionmanager.cpp" diff --git a/src/multimedia/audio/coreaudio/qcoreaudiosessionmanager_p.h b/src/multimedia/audio/coreaudio/qcoreaudiosessionmanager_p.h new file mode 100644 index 000000000..ee0cbcc21 --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudiosessionmanager_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef IOSAUDIOSESSIONMANAGER_H +#define IOSAUDIOSESSIONMANAGER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#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(); + + float currentIOBufferDuration(); + float preferredSampleRate(); + +signals: + void activeChanged(); + void categoryChanged(); + void modeChanged(); + void routeChanged(); + void inputDevicesAvailableChanged(); + void outputDevicesAvailableChanged(); + +private: + CoreAudioSessionManager(); + ~CoreAudioSessionManager(); + CoreAudioSessionManager(CoreAudioSessionManager const ©); + CoreAudioSessionManager& operator =(CoreAudioSessionManager const ©); + + CoreAudioSessionObserver *m_sessionObserver; +}; + +#ifdef QT_DEBUG_COREAUDIO +QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategorys category); +QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionCategoryOptions option); +QDebug operator <<(QDebug dbg, CoreAudioSessionManager::AudioSessionModes mode); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionCategorys) +Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionCategoryOptions) +Q_DECLARE_METATYPE(CoreAudioSessionManager::AudioSessionModes) + +#endif // IOSAUDIOSESSIONMANAGER_H diff --git a/src/multimedia/audio/coreaudio/qcoreaudioutils.mm b/src/multimedia/audio/coreaudio/qcoreaudioutils.mm new file mode 100644 index 000000000..2fa0759da --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudioutils.mm @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcoreaudioutils_p.h" +#include <mach/mach_time.h> + +QT_BEGIN_NAMESPACE + +double CoreAudioUtils::sFrequency = 0.0; +bool CoreAudioUtils::sIsInitialized = false; + +void CoreAudioUtils::initialize() +{ + struct mach_timebase_info timeBaseInfo; + mach_timebase_info(&timeBaseInfo); + sFrequency = static_cast<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; + + audioFormat.setSampleRate(sf.mSampleRate); + audioFormat.setChannelCount(sf.mChannelsPerFrame); + audioFormat.setSampleSize(sf.mBitsPerChannel); + audioFormat.setCodec(QString::fromLatin1("audio/pcm")); + audioFormat.setByteOrder((sf.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0 ? QAudioFormat::BigEndian : QAudioFormat::LittleEndian); + QAudioFormat::SampleType type = QAudioFormat::UnSignedInt; + if ((sf.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0) + type = QAudioFormat::SignedInt; + else if ((sf.mFormatFlags & kAudioFormatFlagIsFloat) != 0) + type = QAudioFormat::Float; + audioFormat.setSampleType(type); + + return audioFormat; +} + +AudioStreamBasicDescription CoreAudioUtils::toAudioStreamBasicDescription(QAudioFormat const& audioFormat) +{ + AudioStreamBasicDescription sf; + + sf.mFormatFlags = kAudioFormatFlagIsPacked; + sf.mSampleRate = audioFormat.sampleRate(); + sf.mFramesPerPacket = 1; + sf.mChannelsPerFrame = audioFormat.channelCount(); + sf.mBitsPerChannel = audioFormat.sampleSize(); + sf.mBytesPerFrame = sf.mChannelsPerFrame * (sf.mBitsPerChannel / 8); + sf.mBytesPerPacket = sf.mFramesPerPacket * sf.mBytesPerFrame; + sf.mFormatID = kAudioFormatLinearPCM; + + switch (audioFormat.sampleType()) { + case QAudioFormat::SignedInt: sf.mFormatFlags |= kAudioFormatFlagIsSignedInteger; break; + case QAudioFormat::UnSignedInt: /* default */ break; + case QAudioFormat::Float: sf.mFormatFlags |= kAudioFormatFlagIsFloat; break; + case QAudioFormat::Unknown: default: break; + } + + if (audioFormat.byteOrder() == QAudioFormat::BigEndian) + sf.mFormatFlags |= kAudioFormatFlagIsBigEndian; + + return sf; +} + +// QAudioRingBuffer +CoreAudioRingBuffer::CoreAudioRingBuffer(int bufferSize): + m_bufferSize(bufferSize) +{ + m_buffer = new char[m_bufferSize]; + reset(); +} + +CoreAudioRingBuffer::~CoreAudioRingBuffer() +{ + delete[] m_buffer; +} + +CoreAudioRingBuffer::Region CoreAudioRingBuffer::acquireReadRegion(int size) +{ + const int used = m_bufferUsed.fetchAndAddAcquire(0); + + if (used > 0) { + const int readSize = qMin(size, qMin(m_bufferSize - m_readPos, used)); + + return readSize > 0 ? Region(m_buffer + m_readPos, readSize) : Region(0, 0); + } + + return Region(0, 0); +} + +void CoreAudioRingBuffer::releaseReadRegion(const CoreAudioRingBuffer::Region ®ion) +{ + m_readPos = (m_readPos + region.second) % m_bufferSize; + + m_bufferUsed.fetchAndAddRelease(-region.second); +} + +CoreAudioRingBuffer::Region CoreAudioRingBuffer::acquireWriteRegion(int size) +{ + const int free = m_bufferSize - m_bufferUsed.fetchAndAddAcquire(0); + + Region output; + + if (free > 0) { + const int writeSize = qMin(size, qMin(m_bufferSize - m_writePos, free)); + output = writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region(0, 0); + } else { + output = Region(0, 0); + } +#ifdef QT_DEBUG_COREAUDIO + qDebug("acquireWriteRegion(%d) free: %d returning Region(%p, %d)", size, free, output.first, output.second); +#endif + return output; +} +void CoreAudioRingBuffer::releaseWriteRegion(const CoreAudioRingBuffer::Region ®ion) +{ + m_writePos = (m_writePos + region.second) % m_bufferSize; + + m_bufferUsed.fetchAndAddRelease(region.second); +#ifdef QT_DEBUG_COREAUDIO + qDebug("releaseWriteRegion(%p,%d): m_writePos:%d", region.first, region.second, m_writePos); +#endif +} + +int CoreAudioRingBuffer::used() const +{ + return m_bufferUsed.loadRelaxed(); +} + +int CoreAudioRingBuffer::free() const +{ + return m_bufferSize - m_bufferUsed.loadRelaxed(); +} + +int CoreAudioRingBuffer::size() const +{ + return m_bufferSize; +} + +void CoreAudioRingBuffer::reset() +{ + m_readPos = 0; + m_writePos = 0; + m_bufferUsed.storeRelaxed(0); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/coreaudio/qcoreaudioutils_p.h b/src/multimedia/audio/coreaudio/qcoreaudioutils_p.h new file mode 100644 index 000000000..4f9d5d327 --- /dev/null +++ b/src/multimedia/audio/coreaudio/qcoreaudioutils_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/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 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef IOSAUDIOUTILS_H +#define IOSAUDIOUTILS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <CoreAudio/CoreAudioTypes.h> + +#include <QtMultimedia/QAudioFormat> +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +class CoreAudioUtils +{ +public: + static quint64 currentTime(); + static double frequency(); + static QAudioFormat toQAudioFormat(const AudioStreamBasicDescription& streamFormat); + static AudioStreamBasicDescription toAudioStreamBasicDescription(QAudioFormat const& audioFormat); + +private: + static void initialize(); + static double sFrequency; + static bool sIsInitialized; +}; + +class CoreAudioRingBuffer +{ +public: + typedef QPair<char*, int> Region; + + CoreAudioRingBuffer(int bufferSize); + ~CoreAudioRingBuffer(); + + Region acquireReadRegion(int size); + void releaseReadRegion(Region const& region); + Region acquireWriteRegion(int size); + void releaseWriteRegion(Region const& region); + + int used() const; + int free() const; + int size() const; + + void reset(); + +private: + int m_bufferSize; + int m_readPos; + int m_writePos; + char* m_buffer; + QAtomicInt m_bufferUsed; +}; + +QT_END_NAMESPACE + +#endif // IOSAUDIOUTILS_H diff --git a/src/multimedia/audio/qaudiosystem.cpp b/src/multimedia/audio/qaudiosystem.cpp index 039ab3061..5408297d0 100644 --- a/src/multimedia/audio/qaudiosystem.cpp +++ b/src/multimedia/audio/qaudiosystem.cpp @@ -44,6 +44,8 @@ #include <private/qaudiointerface_pulse_p.h> #elif QT_CONFIG(alsa) #include <private/qalsainterface_p.h> +#elif defined(Q_OS_DARWIN) +#include <private/qcoreaudiointerface_p.h> #endif QT_BEGIN_NAMESPACE @@ -424,7 +426,9 @@ QAudioSystemInterface *QAudioSystemInterface::instance() #if QT_CONFIG(pulseaudio) system = new QPulseAudioInterface(); #elif QT_CONFIG(alsa) - system = new QAlsaInterface(); + system = new QAlsaInterface(); +#elif defined(Q_OS_DARWIN) + system = new QCoreAudioInterface(); #endif } return system; |