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