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
5
6#include <QtMultimedia/qmediadevices.h>
7#include <QtMultimedia/private/qaudiodevice_p.h>
8#include <QtMultimedia/private/qdarwinaudiodevice_p.h>
9#include <QtMultimedia/private/qdarwinaudiosink_p.h>
10#include <QtMultimedia/private/qdarwinaudiosource_p.h>
11
12#include <QtCore/qcoreapplication.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qdebug.h>
15
16#if defined(QT_PLATFORM_UIKIT)
17# include <QtMultimedia/private/qcoreaudiosessionmanager_p.h>
18# import <AVFoundation/AVFoundation.h>
19#else
20
21# include <QtMultimedia/private/qmacosaudiodatautils_p.h>
22#endif
23
24#if defined(Q_OS_MACOS)
25Q_STATIC_LOGGING_CATEGORY(qLcDarwinMediaDevices, "qt.multimedia.darwin.mediaDevices");
26#endif
27
29
30template <typename... Args>
31static QAudioDevice createAudioDevice(bool isDefault, Args &&...args)
32{
33 auto dev = std::make_unique<QCoreAudioDeviceInfo>(std::forward<Args>(args)...);
34 dev->isDefault = isDefault;
35 return QAudioDevicePrivate::createQAudioDevice(std::move(dev));
36}
37
38#if defined(Q_OS_MACOS)
39
40static AudioDeviceID defaultAudioDevice(QAudioDevice::Mode mode)
41{
42 const AudioObjectPropertySelector selector = (mode == QAudioDevice::Output)
43 ? kAudioHardwarePropertyDefaultOutputDevice
44 : kAudioHardwarePropertyDefaultInputDevice;
45 const AudioObjectPropertyAddress propertyAddress = {
46 selector,
47 kAudioObjectPropertyScopeGlobal,
48 kAudioObjectPropertyElementMain,
49 };
50
51 if (auto audioDevice = QCoreAudioUtils::getAudioProperty<AudioDeviceID>(kAudioObjectSystemObject, propertyAddress)) {
52 return *audioDevice;
53 }
54
55 return 0;
56}
57
58static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode)
59{
60 using namespace QCoreAudioUtils;
61
62 QList<QAudioDevice> devices;
63
64 AudioDeviceID defaultDevice = defaultAudioDevice(mode);
65 if (defaultDevice != 0)
66 devices << createAudioDevice(
67 true,
68 defaultDevice,
69 QCoreAudioUtils::readPersistentDeviceId(defaultDevice, mode),
70 mode);
71
72 const AudioObjectPropertyAddress audioDevicesPropertyAddress = {
73 kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
74 kAudioObjectPropertyElementMain
75 };
76
77 if (auto audioDevices = getAudioPropertyList<AudioDeviceID>(
78 kAudioObjectSystemObject, audioDevicesPropertyAddress)) {
79 const AudioObjectPropertyAddress audioDeviceStreamFormatPropertyAddress =
80 makePropertyAddress(kAudioDevicePropertyStreamFormat, mode);
81
82 for (const auto &device : *audioDevices) {
83 if (device == defaultDevice)
84 continue;
85
86 if (getAudioProperty<AudioStreamBasicDescription>(device,
87 audioDeviceStreamFormatPropertyAddress,
88 /*warnIfMissing=*/false)) {
89 devices << createAudioDevice(false,
90 device,
91 QCoreAudioUtils::readPersistentDeviceId(device, mode),
92 mode);
93 }
94 }
95 }
96
97 return devices;
98}
99
100#elif defined(QT_PLATFORM_UIKIT)
101
102static QList<QAudioDevice> availableAudioDevices(QAudioDevice::Mode mode)
103{
104 QList<QAudioDevice> devices;
105
106 if (mode == QAudioDevice::Output) {
107 devices.append(createAudioDevice(true, "default", QAudioDevice::Output));
108 } else {
109#if !defined(Q_OS_VISIONOS)
110 AVCaptureDevice *defaultDevice =
111 [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
112
113 // TODO: Support Bluetooth and USB devices
114 AVCaptureDeviceDiscoverySession *captureDeviceDiscoverySession =
115 [AVCaptureDeviceDiscoverySession
116 discoverySessionWithDeviceTypes:@[ AVCaptureDeviceTypeMicrophone ]
117 mediaType:AVMediaTypeAudio
118 position:AVCaptureDevicePositionUnspecified];
119
120 NSArray *captureDevices = [captureDeviceDiscoverySession devices];
121 for (AVCaptureDevice *device in captureDevices) {
122 const bool isDefault =
123 defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID];
124 devices.append(createAudioDevice(isDefault,
125 QString::fromNSString(device.uniqueID).toUtf8(),
126 QAudioDevice::Input));
127 }
128#endif
129 }
130
131 return devices;
132}
133
134#endif
135
136#ifdef Q_OS_MACOS
137
138static constexpr AudioObjectPropertyAddress listenerAddresses[] = {
139 { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
140 kAudioObjectPropertyElementMain },
141 { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
142 kAudioObjectPropertyElementMain },
143 { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
144 kAudioObjectPropertyElementMain }
145};
146
147#endif
148
149QDarwinAudioDevices::QDarwinAudioDevices()
150{
151 if (!QThread::isMainThread())
152 moveToThread(qApp->thread());
153
154#ifdef Q_OS_MACOS
155 m_listenerQueue.reset(
156 dispatch_queue_create("QtAudioDevicesListener", /*dispatch_queue_attr=*/nullptr));
157 dispatch_set_target_queue(m_listenerQueue.get(), dispatch_get_main_queue());
158
159 std::shared_ptr destroyedFlag = m_destroyed; // capture shared_ptr
160 m_deviceListenerBlock = std::make_unique<AudioObjectPropertyListenerBlock>(
161 [this, destroyedFlag](UInt32 numberOfProps, const AudioObjectPropertyAddress *props) {
162 // the block is dispatched via a dispatch queue on the main thread and can be in-flight when
163 // the QDarwinAudioDevices instance is being destroyed. Check the destroyed flag to avoid
164 // accessing deleted memory.
165 if (*destroyedFlag)
166 return;
167
168 Q_ASSERT(QThread::isMainThread());
169
170 auto properties = QSpan{ props, numberOfProps };
171
172 bool updateInputs = false;
173 bool updateOutputs = false;
174
175 for (const AudioObjectPropertyAddress &address : properties) {
176 qCDebug(qLcDarwinMediaDevices)
177 << "audioDeviceChangeListenerBlock: address: " << address.mSelector
178 << address.mScope << address.mElement;
179
180 switch (address.mSelector) {
181 case kAudioHardwarePropertyDefaultInputDevice:
182 updateInputs = true;
183 break;
184 case kAudioHardwarePropertyDefaultOutputDevice:
185 updateOutputs = true;
186 break;
187 default:
188 updateInputs = true;
189 updateOutputs = true;
190 break;
191 }
192 }
193
194 if (updateInputs)
195 updateAudioInputsCache();
196 if (updateOutputs)
197 updateAudioOutputsCache();
198 });
199
200 for (const auto &address : listenerAddresses) {
201 const auto err = AudioObjectAddPropertyListenerBlock(
202 kAudioObjectSystemObject, &address, m_listenerQueue.get(), *m_deviceListenerBlock);
203 if (err)
204 qWarning() << "Fail to add listener. mSelector:" << address.mSelector
205 << "mScope:" << address.mScope << "mElement:" << address.mElement
206 << "err:" << err;
207 }
208
209 updateAudioInputsCache();
210 updateAudioOutputsCache();
211#endif
212}
213
214QDarwinAudioDevices::~QDarwinAudioDevices()
215{
216#ifdef Q_OS_MACOS
217 *m_destroyed = true;
218 for (const auto &address : listenerAddresses) {
219 Q_ASSERT(m_deviceListenerBlock);
220 AudioObjectRemovePropertyListenerBlock(kAudioObjectSystemObject, &address,
221 m_listenerQueue.get(), *m_deviceListenerBlock);
222 }
223 m_listenerQueue = {};
224#endif
225}
226
227QList<QAudioDevice> QDarwinAudioDevices::findAudioInputs() const
228{
229 return availableAudioDevices(QAudioDevice::Input);
230}
231
232QList<QAudioDevice> QDarwinAudioDevices::findAudioOutputs() const
233{
234 return availableAudioDevices(QAudioDevice::Output);
235}
236
237QPlatformAudioSource *QDarwinAudioDevices::createAudioSource(const QAudioDevice &info,
238 const QAudioFormat &fmt,
239 QObject *parent)
240{
241 return new QDarwinAudioSource(info, fmt, parent);
242}
243
244QPlatformAudioSink *QDarwinAudioDevices::createAudioSink(const QAudioDevice &info,
245 const QAudioFormat &fmt, QObject *parent)
246{
247 return new QDarwinAudioSink(info, fmt, parent);
248}
249
250namespace QCoreAudioUtils {
251
252#ifdef Q_OS_MACOS
253
258};
259
260// force dtor in a translation unit with ARC enabled
262{
264}
265
267{
269
272
275 // Called on HAL thread
277
282 return;
283 }
284 }
285 });
286
289 /*inDispatchQueue=*/nullptr, *listenerBlock);
290
291 if (status != noErr) {
292 qWarning() << "QAudioOutput: Failed to add property listener";
293 return std::nullopt;
294 }
295
298 /*inDispatchQueue=*/nullptr, *listenerBlock);
299 };
300
301 return disconnectFuture;
302}
303
305{
307 return;
309 m_disconnectFunction = nullptr;
310}
311
312#endif
313
314} // namespace QCoreAudioUtils
315
316QT_END_NAMESPACE
Combined button and popup list for selecting options.