summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArtem Dyomin <artem.dyomin@qt.io>2022-11-04 15:04:03 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-11-07 13:10:29 +0000
commit3fd4de78d83fc2646bde590cf18c93a678836e99 (patch)
treef44e9d24a7f2261666e831e381be92f9bef02571
parent112bdab6b6de1494281421fee6521b7c38b7a445 (diff)
downloadqtmultimedia-3fd4de78d83fc2646bde590cf18c93a678836e99.tar.gz
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 Change-Id: I35aec4f9da9755036141a70f5ce48f6db73d8148 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Lars Knoll <lars@knoll.priv.no> (cherry picked from commit 2466273b5b60227427ccca20680542dea9fe581d) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/multimedia/audio/qaudiodevice.cpp2
-rw-r--r--src/multimedia/darwin/qdarwinmediadevices.mm273
-rw-r--r--src/multimedia/darwin/qdarwinmediadevices_p.h13
-rw-r--r--tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp8
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 c9e0ae31c..4d38b5c0e 100644
--- a/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp
+++ b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp
@@ -58,7 +58,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);
}
}
@@ -67,7 +69,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);
}
}