Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdarwinaudiodevices.mm
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6#include "private/qaudiodevice_p.h"
10
11#include <qloggingcategory.h>
12
13#include <qdebug.h>
14
15#if defined(QT_PLATFORM_UIKIT)
16#include "qcoreaudiosessionmanager_p.h"
17#import <AVFoundation/AVFoundation.h>
18#else
20#endif
21
22#if defined(Q_OS_MACOS)
23Q_STATIC_LOGGING_CATEGORY(qLcDarwinMediaDevices, "qt.multimedia.darwin.mediaDevices");
24#endif
25
27
28template<typename... Args>
29QAudioDevice createAudioDevice(bool isDefault, Args &&...args)
30{
31 auto dev = std::make_unique<QCoreAudioDeviceInfo>(std::forward<Args>(args)...);
32 dev->isDefault = isDefault;
33 return QAudioDevicePrivate::createQAudioDevice(std::move(dev));
34}
35
36#if defined(Q_OS_MACOS)
37
38static AudioDeviceID defaultAudioDevice(QAudioDevice::Mode mode)
39{
40 const AudioObjectPropertySelector selector = (mode == QAudioDevice::Output) ? kAudioHardwarePropertyDefaultOutputDevice
41 : kAudioHardwarePropertyDefaultInputDevice;
42 const AudioObjectPropertyAddress propertyAddress = {
43 selector,
44 kAudioObjectPropertyScopeGlobal,
45 kAudioObjectPropertyElementMain,
46 };
47
48 if (auto audioDevice = QCoreAudioUtils::getAudioProperty<AudioDeviceID>(kAudioObjectSystemObject, propertyAddress)) {
49 return *audioDevice;
50 }
51
52 return 0;
53}
54
55static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode)
56{
57 using namespace QCoreAudioUtils;
58
59 QList<QAudioDevice> devices;
60
61 AudioDeviceID defaultDevice = defaultAudioDevice(mode);
62 if (defaultDevice != 0)
63 devices << createAudioDevice(
64 true,
65 defaultDevice,
66 QCoreAudioUtils::readPersistentDeviceId(defaultDevice, mode),
67 mode);
68
69 const AudioObjectPropertyAddress audioDevicesPropertyAddress = {
70 kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
71 kAudioObjectPropertyElementMain
72 };
73
74 if (auto audioDevices = getAudioPropertyList<AudioDeviceID>(
75 kAudioObjectSystemObject, audioDevicesPropertyAddress)) {
76 const AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress =
77 makePropertyAddress(kAudioDevicePropertyStreamFormat, mode);
78
79 for (const auto &device : *audioDevices) {
80 if (device == defaultDevice)
81 continue;
82
83 if (getAudioProperty<AudioStreamBasicDescription>(device,
84 audioDeviceStreamFormatPropertyAddress,
85 /*warnIfMissing=*/false)) {
86 devices << createAudioDevice(false,
87 device,
88 QCoreAudioUtils::readPersistentDeviceId(device, mode),
89 mode);
90 }
91 }
92 }
93
94 return devices;
95}
96
97static OSStatus audioDeviceChangeListener(AudioObjectID id, UInt32,
98 const AudioObjectPropertyAddress *address, void *ptr)
99{
100 Q_ASSERT(address);
101 Q_ASSERT(ptr);
102
103 QDarwinAudioDevices *instance = static_cast<QDarwinAudioDevices *>(ptr);
104
105 qCDebug(qLcDarwinMediaDevices)
106 << "audioDeviceChangeListener: id:" << id << "address: " << address->mSelector
107 << address->mScope << address->mElement;
108
109 switch (address->mSelector) {
110 case kAudioHardwarePropertyDefaultInputDevice:
111 instance->updateAudioInputsCache();
112 break;
113 case kAudioHardwarePropertyDefaultOutputDevice:
114 instance->updateAudioOutputsCache();
115 break;
116 default:
117 instance->updateAudioInputsCache();
118 instance->updateAudioOutputsCache();
119 break;
120 }
121
122 return 0;
123}
124
125static constexpr AudioObjectPropertyAddress listenerAddresses[] = {
126 { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
127 kAudioObjectPropertyElementMain },
128 { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
129 kAudioObjectPropertyElementMain },
130 { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
131 kAudioObjectPropertyElementMain }
132};
133
134static void setAudioListeners(QDarwinAudioDevices &instance)
135{
136 for (const auto &address : listenerAddresses) {
137 const auto err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &address,
138 audioDeviceChangeListener, &instance);
139
140 if (err)
141 qWarning() << "Fail to add listener. mSelector:" << address.mSelector
142 << "mScope:" << address.mScope << "mElement:" << address.mElement
143 << "err:" << err;
144 }
145}
146
147static void removeAudioListeners(QDarwinAudioDevices &instance)
148{
149 for (const auto &address : listenerAddresses) {
150 const auto err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &address,
151 audioDeviceChangeListener, &instance);
152
153 if (err)
154 qWarning() << "Fail to remove listener. mSelector:" << address.mSelector
155 << "mScope:" << address.mScope << "mElement:" << address.mElement
156 << "err:" << err;
157 }
158}
159
160#elif defined(QT_PLATFORM_UIKIT)
161
162static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode)
163{
164 QList<QAudioDevice> devices;
165
166 if (mode == QAudioDevice::Output) {
167 devices.append(createAudioDevice(true, "default", QAudioDevice::Output));
168 } else {
169#if !defined(Q_OS_VISIONOS)
170 AVCaptureDevice *defaultDevice =
171 [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
172
173 // TODO: Support Bluetooth and USB devices
174 AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession =
175 [AVCaptureDeviceDiscoverySession
176 discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeBuiltInMicrophone ]
177 mediaType:AVMediaTypeAudio
178 position:AVCaptureDevicePositionUnspecified];
179
180 NSArray *captureDevices = [captureDeviceDiscoverySession devices];
181 for (AVCaptureDevice *device in captureDevices) {
182 const bool isDefault =
183 defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID];
184 devices.append(createAudioDevice(isDefault,
185 QString::fromNSString(device.uniqueID).toUtf8(),
186 QAudioDevice::Input));
187 }
188#endif
189 }
190
191 return devices;
192}
193
194static void setAudioListeners(QDarwinAudioDevices &)
195{
196 // ### This should use the audio session manager
197}
198
199static void removeAudioListeners(QDarwinAudioDevices &)
200{
201 // ### This should use the audio session manager
202}
203
204#endif
205
207{
208#ifdef Q_OS_MACOS // TODO: implement setAudioListeners, removeAudioListeners for Q_OS_IOS, after
209 // that - remove or modify the define
210 updateAudioInputsCache();
211 updateAudioOutputsCache();
212#endif
213
214 setAudioListeners(*this);
215}
216
218{
219 removeAudioListeners(*this);
220}
221
223{
224 return availableAudioDevices(QAudioDevice::Input);
225}
226
228{
229 return availableAudioDevices(QAudioDevice::Output);
230}
231
232QPlatformAudioSource *QDarwinAudioDevices::createAudioSource(const QAudioDevice &info,
233 const QAudioFormat &fmt,
234 QObject *parent)
235{
236 return new QDarwinAudioSource(info, fmt, parent);
237}
238
239QPlatformAudioSink *QDarwinAudioDevices::createAudioSink(const QAudioDevice &info,
240 const QAudioFormat &fmt, QObject *parent)
241{
242 return new QDarwinAudioSink(info, fmt, parent);
243}
244
245QT_END_NAMESPACE
QList< QAudioDevice > findAudioOutputs() const override
QList< QAudioDevice > findAudioInputs() const override