diff options
author | Artem Dyomin <artem.dyomin@qt.io> | 2022-11-04 15:04:03 +0100 |
---|---|---|
committer | Artem Dyomin <artem.dyomin@qt.io> | 2022-11-07 13:40:42 +0100 |
commit | 2466273b5b60227427ccca20680542dea9fe581d (patch) | |
tree | c9a735a79a41849a5ac8d7635e6a05e402719e8e | |
parent | fe2e90b6e3b61671b94c9e517ce736fba995f3ef (diff) |
Add audio listeners for macOS and cleanup code
QMediaDevices doc says:
The default device can change during the runtime of the application.
The audioInputsChanged/audioOutputsChanged signal
is emitted in this case.
What's done:
- add listening of properties kAudioHardwarePropertyDefaultOutputDevice
and kAudioHardwarePropertyDefaultInputDevice in order to handle
default input/output changes.
- cleanup a mess of defines in qdarwinmediadevices.mm
- some refactoring with code improving.
Task-number: QTBUG-108020
Pick-to: 6.4
Change-Id: I35aec4f9da9755036141a70f5ce48f6db73d8148
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Lars Knoll <lars@knoll.priv.no>
-rw-r--r-- | src/multimedia/audio/qaudiodevice.cpp | 2 | ||||
-rw-r--r-- | src/multimedia/darwin/qdarwinmediadevices.mm | 273 | ||||
-rw-r--r-- | src/multimedia/darwin/qdarwinmediadevices_p.h | 13 | ||||
-rw-r--r-- | tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp | 8 |
4 files changed, 188 insertions, 108 deletions
diff --git a/src/multimedia/audio/qaudiodevice.cpp b/src/multimedia/audio/qaudiodevice.cpp index 54a21f20e..2d7899dbe 100644 --- a/src/multimedia/audio/qaudiodevice.cpp +++ b/src/multimedia/audio/qaudiodevice.cpp @@ -131,7 +131,7 @@ bool QAudioDevice::operator==(const QAudioDevice &other) const return true; if (!d || !other.d) return false; - if (d->mode == other.d->mode && d->id == other.d->id) + if (d->mode == other.d->mode && d->id == other.d->id && d->isDefault == other.d->isDefault) return true; return false; } diff --git a/src/multimedia/darwin/qdarwinmediadevices.mm b/src/multimedia/darwin/qdarwinmediadevices.mm index 5fc05f9d4..08ea21e8b 100644 --- a/src/multimedia/darwin/qdarwinmediadevices.mm +++ b/src/multimedia/darwin/qdarwinmediadevices.mm @@ -8,25 +8,38 @@ #include "qdarwinaudiosource_p.h" #include "qdarwinaudiosink_p.h" +#include <qloggingcategory.h> + #include <qdebug.h> -#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#if defined(Q_OS_IOS) #include "qcoreaudiosessionmanager_p.h" #import <AVFoundation/AVFoundation.h> #endif +Q_LOGGING_CATEGORY(qLcDarwinMediaDevices, "qt.multimedia.darwin.mediaDevices") + 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) -AudioDeviceID defaultAudioDevice(QAudioDevice::Mode mode) + +static AudioDeviceID defaultAudioDevice(QAudioDevice::Mode mode) { AudioDeviceID audioDevice; UInt32 size = sizeof(audioDevice); const AudioObjectPropertySelector selector = (mode == QAudioDevice::Output) ? kAudioHardwarePropertyDefaultOutputDevice : kAudioHardwarePropertyDefaultInputDevice; - AudioObjectPropertyAddress defaultDevicePropertyAddress = { selector, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; + const AudioObjectPropertyAddress defaultDevicePropertyAddress = { + selector, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster + }; if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDevicePropertyAddress, @@ -43,11 +56,13 @@ static QByteArray uniqueId(AudioDeviceID device, QAudioDevice::Mode mode) CFStringRef name; UInt32 size = sizeof(CFStringRef); - AudioObjectPropertyScope audioPropertyScope = mode == QAudioDevice::Input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + const AudioObjectPropertyScope audioPropertyScope = mode == QAudioDevice::Input + ? kAudioDevicePropertyScopeInput + : kAudioDevicePropertyScopeOutput; - AudioObjectPropertyAddress audioDeviceNamePropertyAddress = { kAudioDevicePropertyDeviceUID, - audioPropertyScope, - kAudioObjectPropertyElementMaster }; + const AudioObjectPropertyAddress audioDeviceNamePropertyAddress = { + kAudioDevicePropertyDeviceUID, audioPropertyScope, kAudioObjectPropertyElementMaster + }; if (AudioObjectGetPropertyData(device, &audioDeviceNamePropertyAddress, 0, NULL, &size, &name) != noErr) { qWarning() << "QAudioDevice: Unable to get device UID"; @@ -59,144 +74,209 @@ static QByteArray uniqueId(AudioDeviceID device, QAudioDevice::Mode mode) return s.toUtf8(); } -QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode) +static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode) { - QList<QAudioDevice> devices; AudioDeviceID defaultDevice = defaultAudioDevice(mode); - if (defaultDevice != 0) { - auto *dev = new QCoreAudioDeviceInfo(defaultDevice, uniqueId(defaultDevice, mode), mode); - dev->isDefault = true; - devices << dev->create(); - } + if (defaultDevice != 0) + devices << createAudioDevice(true, defaultDevice, uniqueId(defaultDevice, mode), mode); UInt32 propSize = 0; - AudioObjectPropertyAddress audioDevicesPropertyAddress = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; + const AudioObjectPropertyAddress audioDevicesPropertyAddress = { + kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &audioDevicesPropertyAddress, 0, + nullptr, &propSize) + == noErr) { + + std::vector<AudioDeviceID> audioDevices(propSize / sizeof(AudioDeviceID)); + + if (!audioDevices.empty() + && AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesPropertyAddress, 0, + nullptr, &propSize, audioDevices.data()) + == noErr) { + + const auto propertyScope = mode == QAudioDevice::Input + ? kAudioDevicePropertyScopeInput + : kAudioDevicePropertyScopeOutput; + + for (const auto &device : audioDevices) { + if (device == defaultDevice) + continue; + + AudioStreamBasicDescription sf = {}; + UInt32 size = sizeof(AudioStreamBasicDescription); + const AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress = { + kAudioDevicePropertyStreamFormat, propertyScope, + kAudioObjectPropertyElementMaster + }; + + if (AudioObjectGetPropertyData(device, &audioDeviceStreamFormatPropertyAddress, 0, + nullptr, &size, &sf) + == noErr) + devices << createAudioDevice(false, device, uniqueId(device, mode), mode); + } + } + } + + return devices; +} - if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, - &audioDevicesPropertyAddress, - 0, NULL, &propSize) == noErr) { +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; + } - const int dc = propSize / sizeof(AudioDeviceID); + return 0; +} - if (dc > 0) { - AudioDeviceID* audioDevices = new AudioDeviceID[dc]; +static constexpr AudioObjectPropertyAddress listenerAddresses[] = { + { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }, + { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }, + { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster } +}; - if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &audioDevicesPropertyAddress, 0, NULL, &propSize, audioDevices) == noErr) { - for (int i = 0; i < dc; ++i) { - if (audioDevices[i] == defaultDevice) - continue; +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; + } +} - AudioStreamBasicDescription sf; - UInt32 size = sizeof(AudioStreamBasicDescription); - AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress = { kAudioDevicePropertyStreamFormat, - (mode == QAudioDevice::Input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput), - kAudioObjectPropertyElementMaster }; +#elif defined(Q_OS_IOS) - if (AudioObjectGetPropertyData(audioDevices[i], &audioDeviceStreamFormatPropertyAddress, 0, NULL, &size, &sf) == noErr) - devices << (new QCoreAudioDeviceInfo(audioDevices[i], uniqueId(audioDevices[i], mode), mode))->create(); - } - } +static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode) +{ + QList<QAudioDevice> devices; - delete[] audioDevices; + 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 OSStatus -audioDeviceChangeListener(AudioObjectID, UInt32, const AudioObjectPropertyAddress*, void* ptr) +static void setAudioListeners(QDarwinMediaDevices &) { - QDarwinMediaDevices *m = static_cast<QDarwinMediaDevices *>(ptr); - m->updateAudioDevices(); - return 0; + // ### 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 - OSStatus err = noErr; - AudioObjectPropertyAddress *audioDevicesAddress = new AudioObjectPropertyAddress{ kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - m_audioDevicesProperty = audioDevicesAddress; - err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, audioDevicesAddress, audioDeviceChangeListener, this); - if (err) - qDebug("error on AudioObjectAddPropertyListener"); -#else - // ### This should use the audio session manager +#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 - updateAudioDevices(); + + setAudioListeners(*this); } QDarwinMediaDevices::~QDarwinMediaDevices() { - -#ifdef Q_OS_MACOS - AudioObjectRemovePropertyListener(kAudioObjectSystemObject, (AudioObjectPropertyAddress *)m_audioDevicesProperty, audioDeviceChangeListener, this); -#endif + removeAudioListeners(*this); } QList<QAudioDevice> QDarwinMediaDevices::audioInputs() const { -#ifdef Q_OS_IOS - AVCaptureDevice *defaultDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; - - // TODO: Support Bluetooth and USB devices - QList<QAudioDevice> devices; - AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession = [AVCaptureDeviceDiscoverySession - discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone] - mediaType:AVMediaTypeAudio - position:AVCaptureDevicePositionUnspecified]; - - NSArray *captureDevices = [captureDeviceDiscoverySession devices]; - for (AVCaptureDevice *device in captureDevices) { - auto *dev = new QCoreAudioDeviceInfo(QString::fromNSString(device.uniqueID).toUtf8(), QAudioDevice::Input); - if (defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID]) - dev->isDefault = true; - devices << dev->create(); - } - return devices; -#else return availableAudioDevices(QAudioDevice::Input); -#endif } QList<QAudioDevice> QDarwinMediaDevices::audioOutputs() const { -#ifdef Q_OS_IOS - QList<QAudioDevice> devices; - auto *dev = new QCoreAudioDeviceInfo("default", QAudioDevice::Output); - dev->isDefault = true; - devices.append(dev->create()); - return devices; -#else return availableAudioDevices(QAudioDevice::Output); -#endif } -void QDarwinMediaDevices::updateAudioDevices() +void QDarwinMediaDevices::onInputsUpdated() { -#ifdef Q_OS_MACOS - QList<QAudioDevice> inputs = availableAudioDevices(QAudioDevice::Input); - if (m_audioInputs != inputs) { - m_audioInputs = inputs; + auto inputs = availableAudioDevices(QAudioDevice::Input); + if (m_cachedAudioInputs != inputs) { + m_cachedAudioInputs = inputs; audioInputsChanged(); } +} - QList<QAudioDevice> outputs = availableAudioDevices(QAudioDevice::Output); - if (m_audioOutputs!= outputs) { - m_audioOutputs = outputs; +void QDarwinMediaDevices::onOutputsUpdated() +{ + auto outputs = availableAudioDevices(QAudioDevice::Output); + if (m_cachedAudioOutputs != outputs) { + m_cachedAudioOutputs = outputs; audioOutputsChanged(); } -#endif } QPlatformAudioSource *QDarwinMediaDevices::createAudioSource(const QAudioDevice &info) @@ -209,5 +289,4 @@ QPlatformAudioSink *QDarwinMediaDevices::createAudioSink(const QAudioDevice &inf return new QDarwinAudioSink(info); } - QT_END_NAMESPACE diff --git a/src/multimedia/darwin/qdarwinmediadevices_p.h b/src/multimedia/darwin/qdarwinmediadevices_p.h index fa4021bce..6134d0544 100644 --- a/src/multimedia/darwin/qdarwinmediadevices_p.h +++ b/src/multimedia/darwin/qdarwinmediadevices_p.h @@ -27,22 +27,19 @@ class QDarwinMediaDevices : public QPlatformMediaDevices { public: QDarwinMediaDevices(); - ~QDarwinMediaDevices(); + ~QDarwinMediaDevices() override; QList<QAudioDevice> audioInputs() const override; QList<QAudioDevice> audioOutputs() const override; QPlatformAudioSource *createAudioSource(const QAudioDevice &info) override; QPlatformAudioSink *createAudioSink(const QAudioDevice &info) override; - void updateAudioDevices(); + void onInputsUpdated(); + void onOutputsUpdated(); private: - QList<QAudioDevice> m_audioInputs; - QList<QAudioDevice> m_audioOutputs; - -#ifdef Q_OS_MACOS - void *m_audioDevicesProperty; -#endif + QList<QAudioDevice> m_cachedAudioInputs; + QList<QAudioDevice> m_cachedAudioOutputs; }; QT_END_NAMESPACE diff --git a/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp index e4554084a..0bdbf88dd 100644 --- a/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp +++ b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp @@ -52,7 +52,9 @@ void tst_QAudioDevice::checkAvailableDefaultInput() // Only perform tests if audio input device exists! QList<QAudioDevice> devices = QMediaDevices::audioInputs(); if (devices.size() > 0) { - QVERIFY(!QMediaDevices::defaultAudioInput().isNull()); + auto defaultInput = QMediaDevices::defaultAudioInput(); + QVERIFY(!defaultInput.isNull()); + QCOMPARE(std::count(devices.begin(), devices.end(), defaultInput), 1); } } @@ -61,7 +63,9 @@ void tst_QAudioDevice::checkAvailableDefaultOutput() // Only perform tests if audio input device exists! QList<QAudioDevice> devices = QMediaDevices::audioOutputs(); if (devices.size() > 0) { - QVERIFY(!QMediaDevices::defaultAudioOutput().isNull()); + auto defaultOutput = QMediaDevices::defaultAudioOutput(); + QVERIFY(!defaultOutput.isNull()); + QCOMPARE(std::count(devices.begin(), devices.end(), defaultOutput), 1); } } |