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
qohosaudiodevices.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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
9
10#include <private/qaudiodevice_p.h>
11#include <private/qaudioformat_p.h>
12
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qmutex.h>
15#include <QtCore/qspan.h>
16
17#include <ohaudio/native_audio_device_base.h>
18#include <ohaudio/native_audio_manager.h>
19#include <ohaudio/native_audio_routing_manager.h>
20#include <ohaudio/native_audiostream_base.h>
21
22#include <optional>
23
24QT_BEGIN_NAMESPACE
25
26namespace {
27
28Q_STATIC_LOGGING_CATEGORY(qLcOhosAudioDevices, "qt.multimedia.ohos.audiodevices")
29
30// OH_AudioRoutingManager_OnDeviceChangedCallback carries no user data, so the
31// device-change callbacks reach the (single) QOhosAudioDevices instance through
32// this pointer, set for the lifetime of the object.
33Q_CONSTINIT QOhosAudioDevices *g_audioDevicesInstance = nullptr;
34
35// Serializes access to g_audioDevicesInstance so a device-change callback
36// arriving on an OHAudio thread cannot run against a half-destroyed instance.
37Q_CONSTINIT QBasicMutex g_callbackMutex;
38
39QAudioFormat preferredDeviceFormat(OH_AudioDeviceDescriptor *descriptor)
40{
41 namespace ranges = QtMultimediaPrivate::ranges;
42
43 QAudioFormat format;
44
45 // OHAudio reports INVALID_PARAM for the channel counts (and sometimes the
46 // sample rates) of otherwise valid devices such as USB headsets, so fall
47 // back per field to sensible defaults.
48 constexpr int defaultSampleRate = 48000;
49 uint32_t *sampleRates = nullptr;
50 uint32_t sampleRateCount = 0;
51 if (OH_AudioDeviceDescriptor_GetDeviceSampleRates(descriptor, &sampleRates, &sampleRateCount)
52 == AUDIOCOMMON_RESULT_SUCCESS
53 && sampleRateCount > 0) {
54 format.setSampleRate(QtMultimediaPrivate::findClosestSamplingRate(
55 defaultSampleRate, QSpan<const uint32_t>{ sampleRates, sampleRateCount }));
56 } else {
57 format.setSampleRate(defaultSampleRate);
58 }
59
60 uint32_t *channelCounts = nullptr;
61 uint32_t channelCountSize = 0;
62 if (OH_AudioDeviceDescriptor_GetDeviceChannelCounts(descriptor, &channelCounts, &channelCountSize)
63 == AUDIOCOMMON_RESULT_SUCCESS
64 && channelCountSize > 0) {
65 const QSpan<const uint32_t> channels{ channelCounts, channelCountSize };
66 const uint32_t chosenChannels =
67 ranges::contains(channels, 2u) ? 2u : *ranges::max_element(channels);
68 format.setChannelConfig(
69 QAudioFormat::defaultChannelConfigForChannelCount(static_cast<int>(chosenChannels)));
70 } else {
71 format.setChannelConfig(QAudioFormat::ChannelConfigStereo);
72 }
73
74 format.setSampleFormat(QAudioFormat::Float);
75 return format;
76}
77
78QString deviceTypeLabel(OH_AudioDevice_Type type)
79{
80 switch (type) {
81 case AUDIO_DEVICE_TYPE_EARPIECE:
82 return QStringLiteral("Earpiece");
83 case AUDIO_DEVICE_TYPE_SPEAKER:
84 return QStringLiteral("Speaker");
85 case AUDIO_DEVICE_TYPE_WIRED_HEADSET:
86 return QStringLiteral("Wired Headset");
87 case AUDIO_DEVICE_TYPE_WIRED_HEADPHONES:
88 return QStringLiteral("Wired Headphones");
89 case AUDIO_DEVICE_TYPE_BLUETOOTH_SCO:
90 return QStringLiteral("Bluetooth (SCO)");
91 case AUDIO_DEVICE_TYPE_BLUETOOTH_A2DP:
92 return QStringLiteral("Bluetooth (A2DP)");
93 case AUDIO_DEVICE_TYPE_MIC:
94 return QStringLiteral("Microphone");
95 case AUDIO_DEVICE_TYPE_USB_HEADSET:
96 case AUDIO_DEVICE_TYPE_USB_DEVICE:
97 return QStringLiteral("USB Audio");
98 default:
99 break;
100 }
101 return {};
102}
103
104QString deviceDisplayDescription(OH_AudioDeviceDescriptor *descriptor)
105{
106 char *name = nullptr;
107 if (OH_AudioDeviceDescriptor_GetDeviceName(descriptor, &name) == AUDIOCOMMON_RESULT_SUCCESS
108 && name && *name) {
109 return QString::fromUtf8(name);
110 }
111
112 OH_AudioDevice_Type type{ AUDIO_DEVICE_TYPE_INVALID };
113 OH_AudioDeviceDescriptor_GetDeviceType(descriptor, &type);
114 const QString typeLabel = deviceTypeLabel(type);
115
116 QString displayName;
117 char *rawDisplayName = nullptr;
118 if (OH_AudioDeviceDescriptor_GetDeviceDisplayName(descriptor, &rawDisplayName)
119 == AUDIOCOMMON_RESULT_SUCCESS
120 && rawDisplayName && *rawDisplayName) {
121 displayName = QString::fromUtf8(rawDisplayName);
122 }
123
124 if (!displayName.isEmpty() && !typeLabel.isEmpty())
125 return displayName + u' ' + typeLabel;
126 if (!displayName.isEmpty())
127 return displayName;
128 if (!typeLabel.isEmpty())
129 return typeLabel;
130 return QStringLiteral("Audio Device");
131}
132
133std::optional<uint32_t> preferredDeviceId(OH_AudioRoutingManager *routing, QAudioDevice::Mode mode)
134{
135 OH_AudioDeviceDescriptorArray *preferred = nullptr;
136 const OH_AudioCommon_Result result = (mode == QAudioDevice::Input)
137 ? OH_AudioRoutingManager_GetPreferredInputDevice(routing, AUDIOSTREAM_SOURCE_TYPE_MIC,
138 &preferred)
139 : OH_AudioRoutingManager_GetPreferredOutputDevice(routing, AUDIOSTREAM_USAGE_MUSIC,
140 &preferred);
141 if (result != AUDIOCOMMON_RESULT_SUCCESS || !preferred || preferred->size == 0) {
142 if (preferred)
143 OH_AudioRoutingManager_ReleaseDevices(routing, preferred);
144 return std::nullopt;
145 }
146
147 uint32_t deviceId = 0;
148 OH_AudioDeviceDescriptor_GetDeviceId(preferred->descriptors[0], &deviceId);
149 OH_AudioRoutingManager_ReleaseDevices(routing, preferred);
150 return deviceId;
151}
152
153QByteArray deviceIdentifier(OH_AudioDeviceDescriptor *descriptor)
154{
155 // The numeric device id is reused after a device is unplugged, so prefer the
156 // stable device address as the identifier. Built-in devices report no
157 // address; fall back to their (stable) numeric id there.
158 char *address = nullptr;
159 if (OH_AudioDeviceDescriptor_GetDeviceAddress(descriptor, &address) == AUDIOCOMMON_RESULT_SUCCESS
160 && address && *address) {
161 return QByteArray{ address };
162 }
163 uint32_t deviceId = 0;
164 OH_AudioDeviceDescriptor_GetDeviceId(descriptor, &deviceId);
165 return QByteArray::number(deviceId);
166}
167
168QList<QAudioDevice> enumerateDevices(QAudioDevice::Mode mode)
169{
170 OH_AudioManager *manager = nullptr;
171 if (OH_GetAudioManager(&manager) != AUDIOCOMMON_RESULT_SUCCESS || !manager) {
172 qCWarning(qLcOhosAudioDevices) << "OH_GetAudioManager failed";
173 return {};
174 }
175
176 OH_AudioRoutingManager *routing = nullptr;
177 if (OH_AudioManager_GetAudioRoutingManager(&routing) != AUDIOCOMMON_RESULT_SUCCESS || !routing) {
178 qCWarning(qLcOhosAudioDevices) << "OH_AudioManager_GetAudioRoutingManager failed";
179 return {};
180 }
181
182 const OH_AudioDevice_Flag flag = (mode == QAudioDevice::Input) ? AUDIO_DEVICE_FLAG_INPUT
183 : AUDIO_DEVICE_FLAG_OUTPUT;
184 OH_AudioDeviceDescriptorArray *descriptors = nullptr;
185 if (OH_AudioRoutingManager_GetDevices(routing, flag, &descriptors)
186 != AUDIOCOMMON_RESULT_SUCCESS
187 || !descriptors) {
188 qCWarning(qLcOhosAudioDevices) << "OH_AudioRoutingManager_GetDevices failed for"
189 << (mode == QAudioDevice::Input ? "input" : "output");
190 return {};
191 }
192
193 const std::optional<uint32_t> defaultDeviceId = preferredDeviceId(routing, mode);
194
195 QList<QAudioDevice> devices;
196 devices.reserve(descriptors->size);
197 for (uint32_t i = 0; i < descriptors->size; ++i) {
198 OH_AudioDeviceDescriptor *descriptor = descriptors->descriptors[i];
199 if (!descriptor)
200 continue;
201
202 uint32_t deviceId = 0;
203 OH_AudioDeviceDescriptor_GetDeviceId(descriptor, &deviceId);
204
205 const QByteArray identifier = deviceIdentifier(descriptor);
206 const QAudioFormat preferredFormat = preferredDeviceFormat(descriptor);
207 const QString description = deviceDisplayDescription(descriptor);
208 const bool isDefault = defaultDeviceId ? deviceId == *defaultDeviceId : i == 0;
209
210 devices << QAudioDevicePrivate::createQAudioDevice(std::make_unique<QOhosAudioDevice>(
211 identifier, description, mode, preferredFormat,
212 isDefault));
213 }
214
215 OH_AudioRoutingManager_ReleaseDevices(routing, descriptors);
216 return devices;
217}
218
219} // namespace
220
221QOhosAudioDevices::QOhosAudioDevices() : QPlatformAudioDevices()
222{
223 Q_ASSERT(!g_audioDevicesInstance);
224 g_audioDevicesInstance = this;
225 registerDeviceChangeCallbacks();
226}
227
228QOhosAudioDevices::~QOhosAudioDevices()
229{
230 unregisterDeviceChangeCallbacks();
231 QMutexLocker guard{ &g_callbackMutex };
232 g_audioDevicesInstance = nullptr;
233}
234
235void QOhosAudioDevices::registerDeviceChangeCallbacks()
236{
237 OH_AudioRoutingManager *routing = nullptr;
238 if (OH_AudioManager_GetAudioRoutingManager(&routing) != AUDIOCOMMON_RESULT_SUCCESS || !routing) {
239 qCWarning(qLcOhosAudioDevices)
240 << "Cannot register device change callbacks: routing manager unavailable";
241 return;
242 }
243
244 const OH_AudioCommon_Result inputResult = OH_AudioRoutingManager_RegisterDeviceChangeCallback(
245 routing, AUDIO_DEVICE_FLAG_INPUT, &QOhosAudioDevices::onInputDevicesChanged);
246 const OH_AudioCommon_Result outputResult = OH_AudioRoutingManager_RegisterDeviceChangeCallback(
247 routing, AUDIO_DEVICE_FLAG_OUTPUT, &QOhosAudioDevices::onOutputDevicesChanged);
248
249 if (inputResult != AUDIOCOMMON_RESULT_SUCCESS || outputResult != AUDIOCOMMON_RESULT_SUCCESS)
250 qCWarning(qLcOhosAudioDevices) << "Failed to register audio device change callbacks";
251
252 m_deviceChangeCallbacksRegistered =
253 inputResult == AUDIOCOMMON_RESULT_SUCCESS || outputResult == AUDIOCOMMON_RESULT_SUCCESS;
254}
255
256void QOhosAudioDevices::unregisterDeviceChangeCallbacks()
257{
258 if (!m_deviceChangeCallbacksRegistered)
259 return;
260
261 OH_AudioRoutingManager *routing = nullptr;
262 if (OH_AudioManager_GetAudioRoutingManager(&routing) != AUDIOCOMMON_RESULT_SUCCESS || !routing)
263 return;
264
265 OH_AudioRoutingManager_UnregisterDeviceChangeCallback(
266 routing, &QOhosAudioDevices::onInputDevicesChanged);
267 OH_AudioRoutingManager_UnregisterDeviceChangeCallback(
268 routing, &QOhosAudioDevices::onOutputDevicesChanged);
269}
270
271// Invoked on an OHAudio service thread. We ignore the supplied descriptor array
272// (owned by the framework) and just invalidate the cache so the next query
273// re-enumerates.
274int32_t QOhosAudioDevices::onInputDevicesChanged(OH_AudioDevice_ChangeType /*type*/,
275 OH_AudioDeviceDescriptorArray * /*devices*/)
276{
277 QMutexLocker guard{ &g_callbackMutex };
278 if (g_audioDevicesInstance)
279 g_audioDevicesInstance->onAudioInputsChanged();
280 return 0;
281}
282
283int32_t QOhosAudioDevices::onOutputDevicesChanged(OH_AudioDevice_ChangeType /*type*/,
284 OH_AudioDeviceDescriptorArray * /*devices*/)
285{
286 QMutexLocker guard{ &g_callbackMutex };
287 if (g_audioDevicesInstance)
288 g_audioDevicesInstance->onAudioOutputsChanged();
289 return 0;
290}
291
292QList<QAudioDevice> QOhosAudioDevices::findAudioInputs() const
293{
294 return enumerateDevices(QAudioDevice::Input);
295}
296
297QList<QAudioDevice> QOhosAudioDevices::findAudioOutputs() const
298{
299 return enumerateDevices(QAudioDevice::Output);
300}
301
302QPlatformAudioSource *QOhosAudioDevices::createAudioSource(const QAudioDevice &device,
303 const QAudioFormat &format,
304 QObject *parent)
305{
306 return new QtOHAudio::QOhosAudioSource(device, format, parent);
307}
308
309QPlatformAudioSink *QOhosAudioDevices::createAudioSink(const QAudioDevice &device,
310 const QAudioFormat &format, QObject *parent)
311{
312 return new QtOHAudio::QOhosAudioSink(device, format, parent);
313}
314
315QT_END_NAMESPACE
std::optional< uint32_t > preferredDeviceId(OH_AudioRoutingManager *routing, QAudioDevice::Mode mode)
QByteArray deviceIdentifier(OH_AudioDeviceDescriptor *descriptor)
QString deviceTypeLabel(OH_AudioDevice_Type type)
QAudioFormat preferredDeviceFormat(OH_AudioDeviceDescriptor *descriptor)
QList< QAudioDevice > enumerateDevices(QAudioDevice::Mode mode)
QString deviceDisplayDescription(OH_AudioDeviceDescriptor *descriptor)