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