diff options
Diffstat (limited to 'src/multimedia/darwin/qdarwinmediadevices.mm')
-rw-r--r-- | src/multimedia/darwin/qdarwinmediadevices.mm | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/multimedia/darwin/qdarwinmediadevices.mm b/src/multimedia/darwin/qdarwinmediadevices.mm new file mode 100644 index 000000000..b0a108935 --- /dev/null +++ b/src/multimedia/darwin/qdarwinmediadevices.mm @@ -0,0 +1,269 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinmediadevices_p.h" +#include "qmediadevices.h" +#include "private/qaudiodevice_p.h" +#include "qdarwinaudiodevice_p.h" +#include "qdarwinaudiosource_p.h" +#include "qdarwinaudiosink_p.h" + +#include <qloggingcategory.h> + +#include <qdebug.h> + +#if defined(Q_OS_IOS) +#include "qcoreaudiosessionmanager_p.h" +#import <AVFoundation/AVFoundation.h> +#else +#include "qmacosaudiodatautils_p.h" +#endif + +#if defined(Q_OS_MACOS) +static Q_LOGGING_CATEGORY(qLcDarwinMediaDevices, "qt.multimedia.darwin.mediaDevices") +#endif + +QT_BEGIN_NAMESPACE + +template<typename... Args> +QAudioDevice createAudioDevice(bool isDefault, Args &&...args) +{ + auto *dev = new QCoreAudioDeviceInfo(std::forward<Args>(args)...); + dev->isDefault = isDefault; + return dev->create(); +} + +#if defined(Q_OS_MACOS) + +static AudioDeviceID defaultAudioDevice(QAudioDevice::Mode mode) +{ + const AudioObjectPropertySelector selector = (mode == QAudioDevice::Output) ? kAudioHardwarePropertyDefaultOutputDevice + : kAudioHardwarePropertyDefaultInputDevice; + const AudioObjectPropertyAddress propertyAddress = { + selector, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain, + }; + + if (auto audioDevice = getAudioObject<AudioDeviceID>(kAudioObjectSystemObject, propertyAddress, + "Default Device")) { + return *audioDevice; + } + + return 0; +} + +static QByteArray uniqueId(AudioDeviceID device, QAudioDevice::Mode mode) +{ + const AudioObjectPropertyAddress propertyAddress = + makePropertyAddress(kAudioDevicePropertyDeviceUID, mode); + + if (auto name = getAudioObject<CFStringRef>(device, propertyAddress, "Device UID")) { + QString s = QString::fromCFString(*name); + CFRelease(*name); + return s.toUtf8(); + } + + return QByteArray(); +} + +static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode) +{ + QList<QAudioDevice> devices; + + AudioDeviceID defaultDevice = defaultAudioDevice(mode); + if (defaultDevice != 0) + devices << createAudioDevice(true, defaultDevice, uniqueId(defaultDevice, mode), mode); + + const AudioObjectPropertyAddress audioDevicesPropertyAddress = { + kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain + }; + + if (auto audioDevices = getAudioData<AudioDeviceID>( + kAudioObjectSystemObject, audioDevicesPropertyAddress, "Audio Devices")) { + const AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress = + makePropertyAddress(kAudioDevicePropertyStreamFormat, mode); + + for (const auto &device : *audioDevices) { + if (device == defaultDevice) + continue; + + if (getAudioObject<AudioStreamBasicDescription>(device, + audioDeviceStreamFormatPropertyAddress, + nullptr /*don't print logs*/)) { + devices << createAudioDevice(false, device, uniqueId(device, mode), mode); + } + } + } + + return devices; +} + +static OSStatus audioDeviceChangeListener(AudioObjectID id, UInt32, + const AudioObjectPropertyAddress *address, void *ptr) +{ + Q_ASSERT(address); + Q_ASSERT(ptr); + + QDarwinMediaDevices *instance = static_cast<QDarwinMediaDevices *>(ptr); + + qCDebug(qLcDarwinMediaDevices) + << "audioDeviceChangeListener: id:" << id << "address: " << address->mSelector + << address->mScope << address->mElement; + + switch (address->mSelector) { + case kAudioHardwarePropertyDefaultInputDevice: + instance->onInputsUpdated(); + break; + case kAudioHardwarePropertyDefaultOutputDevice: + instance->onOutputsUpdated(); + break; + default: + instance->onInputsUpdated(); + instance->onOutputsUpdated(); + break; + } + + return 0; +} + +static constexpr AudioObjectPropertyAddress listenerAddresses[] = { + { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain }, + { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain }, + { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMain } +}; + +static void setAudioListeners(QDarwinMediaDevices &instance) +{ + for (const auto &address : listenerAddresses) { + const auto err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &address, + audioDeviceChangeListener, &instance); + + if (err) + qWarning() << "Fail to add listener. mSelector:" << address.mSelector + << "mScope:" << address.mScope << "mElement:" << address.mElement + << "err:" << err; + } +} + +static void removeAudioListeners(QDarwinMediaDevices &instance) +{ + for (const auto &address : listenerAddresses) { + const auto err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &address, + audioDeviceChangeListener, &instance); + + if (err) + qWarning() << "Fail to remove listener. mSelector:" << address.mSelector + << "mScope:" << address.mScope << "mElement:" << address.mElement + << "err:" << err; + } +} + +#elif defined(Q_OS_IOS) + +static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode) +{ + QList<QAudioDevice> devices; + + if (mode == QAudioDevice::Output) { + devices.append(createAudioDevice(true, "default", QAudioDevice::Output)); + } else { + AVCaptureDevice *defaultDevice = + [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + + // TODO: Support Bluetooth and USB devices + AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = + [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInMicrophone ] + mediaType:AVMediaTypeAudio + position:AVCaptureDevicePositionUnspecified]; + + NSArray *captureDevices = [captureDeviceDiscoverySession devices]; + for (AVCaptureDevice *device in captureDevices) { + const bool isDefault = + defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID]; + devices.append(createAudioDevice(isDefault, + QString::fromNSString(device.uniqueID).toUtf8(), + QAudioDevice::Input)); + } + } + + return devices; +} + +static void setAudioListeners(QDarwinMediaDevices &) +{ + // ### This should use the audio session manager +} + +static void removeAudioListeners(QDarwinMediaDevices &) +{ + // ### This should use the audio session manager +} + +#endif + + +QDarwinMediaDevices::QDarwinMediaDevices() + : QPlatformMediaDevices() +{ +#ifdef Q_OS_MACOS // TODO: implement setAudioListeners, removeAudioListeners for Q_OS_IOS, after + // that - remove or modify the define + m_cachedAudioInputs = availableAudioDevices(QAudioDevice::Input); + m_cachedAudioOutputs = availableAudioDevices(QAudioDevice::Output); +#endif + + setAudioListeners(*this); +} + + +QDarwinMediaDevices::~QDarwinMediaDevices() +{ + removeAudioListeners(*this); +} + +QList<QAudioDevice> QDarwinMediaDevices::audioInputs() const +{ + return availableAudioDevices(QAudioDevice::Input); +} + +QList<QAudioDevice> QDarwinMediaDevices::audioOutputs() const +{ + return availableAudioDevices(QAudioDevice::Output); +} + +void QDarwinMediaDevices::onInputsUpdated() +{ + auto inputs = availableAudioDevices(QAudioDevice::Input); + if (m_cachedAudioInputs != inputs) { + m_cachedAudioInputs = inputs; + emit audioInputsChanged(); + } +} + +void QDarwinMediaDevices::onOutputsUpdated() +{ + auto outputs = availableAudioDevices(QAudioDevice::Output); + if (m_cachedAudioOutputs != outputs) { + m_cachedAudioOutputs = outputs; + emit audioOutputsChanged(); + } +} + +QPlatformAudioSource *QDarwinMediaDevices::createAudioSource(const QAudioDevice &info, + QObject *parent) +{ + return new QDarwinAudioSource(info, parent); +} + +QPlatformAudioSink *QDarwinMediaDevices::createAudioSink(const QAudioDevice &info, + QObject *parent) +{ + return new QDarwinAudioSink(info, parent); +} + +QT_END_NAMESPACE |