/**************************************************************************** ** ** 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 "coreaudiodeviceinfo.h" #include "coreaudioutils.h" #if defined(Q_OS_IOS) || defined(Q_OS_TVOS) # include "coreaudiosessionmanager.h" #endif #include #include #include QT_BEGIN_NAMESPACE CoreAudioDeviceInfo::CoreAudioDeviceInfo(const QByteArray &device, QAudio::Mode mode) : m_mode(mode) { #if defined(Q_OS_OSX) quint32 deviceID; QDataStream dataStream(device); dataStream >> deviceID >> m_device; m_deviceId = AudioDeviceID(deviceID); #else //iOS m_device = device; #endif } QAudioFormat CoreAudioDeviceInfo::preferredFormat() const { QAudioFormat format; #if defined(Q_OS_OSX) UInt32 propSize = 0; AudioObjectPropertyScope audioDevicePropertyScope = m_mode == QAudio::AudioInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; AudioObjectPropertyAddress audioDevicePropertyStreamsAddress = { kAudioDevicePropertyStreams, audioDevicePropertyScope, kAudioObjectPropertyElementMaster }; if (AudioObjectGetPropertyDataSize(m_deviceId, &audioDevicePropertyStreamsAddress, 0, NULL, &propSize) == noErr) { const int sc = propSize / sizeof(AudioStreamID); if (sc > 0) { AudioStreamID* streams = new AudioStreamID[sc]; if (AudioObjectGetPropertyData(m_deviceId, &audioDevicePropertyStreamsAddress, 0, NULL, &propSize, streams) == noErr) { AudioObjectPropertyAddress audioDevicePhysicalFormatPropertyAddress = { kAudioStreamPropertyPhysicalFormat, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; for (int i = 0; i < sc; ++i) { if (AudioObjectGetPropertyDataSize(streams[i], &audioDevicePhysicalFormatPropertyAddress, 0, NULL, &propSize) == noErr) { AudioStreamBasicDescription sf; if (AudioObjectGetPropertyData(streams[i], &audioDevicePhysicalFormatPropertyAddress, 0, NULL, &propSize, &sf) == noErr) { format = CoreAudioUtils::toQAudioFormat(sf); break; } else { qWarning() << "QAudioDeviceInfo: Unable to find perferedFormat for stream"; } } else { qWarning() << "QAudioDeviceInfo: Unable to find size of perferedFormat for stream"; } } } delete[] streams; } } #else //iOS format.setSampleSize(16); if (m_mode == QAudio::AudioInput) { format.setChannelCount(1); format.setSampleRate(8000); } else { format.setChannelCount(2); format.setSampleRate(44100); } format.setCodec(QString::fromLatin1("audio/pcm")); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); #endif return format; } bool CoreAudioDeviceInfo::isFormatSupported(const QAudioFormat &format) const { CoreAudioDeviceInfo *self = const_cast(this); //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 CoreAudioDeviceInfo::deviceName() const { return m_device; } QStringList CoreAudioDeviceInfo::supportedCodecs() { return QStringList() << QString::fromLatin1("audio/pcm"); } QList CoreAudioDeviceInfo::supportedSampleRates() { QSet sampleRates; #if defined(Q_OS_OSX) UInt32 propSize = 0; AudioObjectPropertyScope scope = m_mode == QAudio::AudioInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; AudioObjectPropertyAddress availableNominalSampleRatesAddress = { kAudioDevicePropertyAvailableNominalSampleRates, scope, kAudioObjectPropertyElementMaster }; if (AudioObjectGetPropertyDataSize(m_deviceId, &availableNominalSampleRatesAddress, 0, NULL, &propSize) == noErr) { const int pc = propSize / sizeof(AudioValueRange); if (pc > 0) { AudioValueRange* vr = new AudioValueRange[pc]; if (AudioObjectGetPropertyData(m_deviceId, &availableNominalSampleRatesAddress, 0, NULL, &propSize, vr) == noErr) { for (int i = 0; i < pc; ++i) { sampleRates << vr[i].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 CoreAudioDeviceInfo::supportedChannelCounts() { static QList 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 CoreAudioDeviceInfo::supportedSampleSizes() { return QList() << 8 << 16 << 24 << 32 << 64; } QList CoreAudioDeviceInfo::supportedByteOrders() { return QList() << QAudioFormat::LittleEndian << QAudioFormat::BigEndian; } QList CoreAudioDeviceInfo::supportedSampleTypes() { return QList() << QAudioFormat::SignedInt << QAudioFormat::UnSignedInt << QAudioFormat::Float; } #if defined(Q_OS_OSX) // XXX: remove at some future date static inline QString cfStringToQString(CFStringRef str) { CFIndex length = CFStringGetLength(str); const UniChar *chars = CFStringGetCharactersPtr(str); if (chars) return QString(reinterpret_cast(chars), length); UniChar buffer[length]; CFStringGetCharacters(str, CFRangeMake(0, length), buffer); return QString(reinterpret_cast(buffer), length); } static QByteArray get_device_info(AudioDeviceID audioDevice, QAudio::Mode mode) { UInt32 size; QByteArray device; QDataStream ds(&device, QIODevice::WriteOnly); AudioStreamBasicDescription sf; CFStringRef name; Boolean isInput = mode == QAudio::AudioInput; AudioObjectPropertyScope audioPropertyScope = isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; // Id ds << quint32(audioDevice); // Mode //TODO: Why don't we use the Stream Format we ask for? size = sizeof(AudioStreamBasicDescription); AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress = { kAudioDevicePropertyStreamFormat, audioPropertyScope, kAudioObjectPropertyElementMaster }; if (AudioObjectGetPropertyData(audioDevice, &audioDeviceStreamFormatPropertyAddress, 0, NULL, &size, &sf) != noErr) { return QByteArray(); } // Name size = sizeof(CFStringRef); AudioObjectPropertyAddress audioDeviceNamePropertyAddress = { kAudioObjectPropertyName, audioPropertyScope, kAudioObjectPropertyElementMaster }; if (AudioObjectGetPropertyData(audioDevice, &audioDeviceNamePropertyAddress, 0, NULL, &size, &name) != noErr) { qWarning() << "QAudioDeviceInfo: Unable to find device name"; return QByteArray(); } ds << cfStringToQString(name); CFRelease(name); return device; } #endif QByteArray CoreAudioDeviceInfo::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 CoreAudioDeviceInfo::availableDevices(QAudio::Mode mode) { QList devices; #if defined(Q_OS_OSX) UInt32 propSize = 0; AudioObjectPropertyAddress audioDevicesPropertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesPropertyAddress, 0, NULL, &propSize) == noErr) { const int dc = propSize / sizeof(AudioDeviceID); if (dc > 0) { AudioDeviceID* audioDevices = new AudioDeviceID[dc]; if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesPropertyAddress, 0, NULL, &propSize, audioDevices) == noErr) { for (int i = 0; i < dc; ++i) { 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_coreaudiodeviceinfo.cpp"