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