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
qqnxsndaudiodevices.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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
7
8#include "private/qqnxsndaudiosource_p.h"
9#include "private/qqnxsndaudiosink_p.h"
10#include "private/qqnxsndaudiodevice_p.h"
11
12#include <alsa/asoundlib.h>
13
14#include <QtCore/qdir.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qloggingcategory.h>
17
18#include <algorithm>
19
21
22// Declared in qqnxsndaudiodevice_p.h; shared with the device-info TU.
23Q_LOGGING_CATEGORY(lcQnxSndDevices, "qt.multimedia.qnxsnd.devices");
24
25namespace {
26
27struct free_char
28{
29 void operator()(char *c) const { ::free(c); }
30};
31
32using unique_str = std::unique_ptr<char, free_char>;
33
34bool operator==(const unique_str &str, std::string_view sv)
35{
36 return str && std::string_view{ str.get() } == sv;
37}
38bool operator!=(const unique_str &str, std::string_view sv)
39{
40 return !(str == sv);
41}
42
43QList<QAudioDevice> availableDevicesViaHints(QAudioDevice::Mode mode)
44{
45 QList<QAudioDevice> devices;
46
47 void **rawHints;
48 if (snd_device_name_hint(-1, "pcm", &rawHints) < 0) {
49 qCWarning(lcQnxSndDevices) << "snd_device_name_hint failed";
50 return devices;
51 }
52 QnxSndHelpers::HintsGuard hints(rawHints);
53
54 std::string_view filter = (mode == QAudioDevice::Input) ? "Input" : "Output";
55
56 auto makeDeviceInfo = [filter, mode](void *entry) -> std::unique_ptr<QQnxSndAudioDeviceInfo> {
57 unique_str name(snd_device_name_get_hint(entry, "NAME"));
58 if (name && name != "null") {
59 unique_str descr(snd_device_name_get_hint(entry, "DESC"));
60 unique_str io(snd_device_name_get_hint(entry, "IOID"));
61
62 if (descr && (!io || (io == filter))) {
63 auto info = std::make_unique<QQnxSndAudioDeviceInfo>(
64 name.get(), QString::fromUtf8(descr.get()), mode);
65 return info;
66 }
67 }
68 return nullptr;
69 };
70
71 // QList cannot host move-only unique_ptr<T> because detach() copy-appends.
72 // Stage in std::vector, then move into the QList<QAudioDevice>.
73 std::vector<std::unique_ptr<QQnxSndAudioDeviceInfo>> pending;
74
75 for (void **n = hints.get(); *n != nullptr; ++n) {
76 if (auto info = makeDeviceInfo(*n))
77 pending.push_back(std::move(info));
78 }
79
80 // snd_device_name_hint enumeration order is not contractually stable
81 // across calls, so sort by id before picking a default. That way the
82 // "first match" semantics (and the position-0 fallback) are reproducible
83 // and QMediaDevices::defaultAudioOutput() returns the same device on
84 // repeated queries.
85 std::sort(pending.begin(), pending.end(),
86 [](const auto &a, const auto &b) { return a->id < b->id; });
87
88 qsizetype defaultIdx = -1;
89 qsizetype preferredIdx = -1;
90 for (qsizetype i = 0; i < static_cast<qsizetype>(pending.size()); ++i) {
91 if (defaultIdx < 0 && pending[i]->id.startsWith("default"))
92 defaultIdx = i;
93 if (preferredIdx < 0 && pending[i]->id.contains("pcmPreferred"))
94 preferredIdx = i;
95 }
96
97 const qsizetype chosen = defaultIdx >= 0 ? defaultIdx
98 : preferredIdx >= 0 ? preferredIdx
99 : pending.empty() ? -1
100 : 0;
101 if (chosen >= 0)
102 pending[chosen]->isDefault = true;
103
104 devices.reserve(pending.size());
105 // Explicitly move each unique_ptr out of the staging vector into the QList.
106 for (auto &&info : pending)
107 devices.append(QAudioDevicePrivate::createQAudioDevice(std::move(info)));
108
109 return devices;
110}
111
112bool hasDeviceFilesForMode(QAudioDevice::Mode mode)
113{
114 QDir dir(QStringLiteral("/dev/snd"));
115 const char16_t suffix = mode == QAudioDevice::Input ? u'c' : u'p';
116 for (const QString &entry : dir.entryList(QDir::Files)) {
117 if (entry.startsWith(QStringLiteral("pcm")) && entry.back() == suffix)
118 return true;
119 }
120 return false;
121}
122
123} // namespace
124
125QQnxSndAudioDevices::QQnxSndAudioDevices() = default;
126
127static QList<QAudioDevice> availableDevices(QAudioDevice::Mode mode)
128{
129 QList<QAudioDevice> devices = availableDevicesViaHints(mode);
130
131 // snd_device_name_hint on QNX returns devices without IOID, causing them
132 // to pass the filter for both Input and Output modes. Validate against
133 // /dev/snd/pcm*{c,p} entries to discard phantom devices on systems that
134 // lack the hardware for the requested mode.
135 if (!devices.isEmpty() && !hasDeviceFilesForMode(mode)) {
136 qCDebug(lcQnxSndDevices) << "No /dev/snd/ device files for"
137 << (mode == QAudioDevice::Input ? "capture" : "playback")
138 << "- discarding" << devices.size() << "hint-reported device(s)";
139 devices.clear();
140 }
141
142 if (devices.isEmpty())
143 qCWarning(lcQnxSndDevices) << "No audio devices found for"
144 << (mode == QAudioDevice::Input ? "input" : "output");
145 return devices;
146}
147
148QList<QAudioDevice> QQnxSndAudioDevices::findAudioInputs() const
149{
150 return availableDevices(QAudioDevice::Input);
151}
152
153QList<QAudioDevice> QQnxSndAudioDevices::findAudioOutputs() const
154{
155 return availableDevices(QAudioDevice::Output);
156}
157
158QPlatformAudioSource *QQnxSndAudioDevices::createAudioSource(const QAudioDevice &deviceInfo,
159 const QAudioFormat &fmt,
160 QObject *parent)
161{
162 return new QQnxSndAudioSource(deviceInfo, fmt, parent);
163}
164
165QPlatformAudioSink *QQnxSndAudioDevices::createAudioSink(const QAudioDevice &deviceInfo,
166 const QAudioFormat &fmt,
167 QObject *parent)
168{
169 return new QQnxSndAudioSink(deviceInfo, fmt, parent);
170}
171
172QT_END_NAMESPACE
std::unique_ptr< void *, HintsDeleter > HintsGuard
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static QList< QAudioDevice > availableDevices(QAudioDevice::Mode mode)