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
qqnxsndaudiodevice.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
6
7#include <QtMultimedia/private/qaudioformat_p.h>
8
9#include <alsa/asoundlib.h>
10
11#include <QtCore/qloggingcategory.h>
12
13#include <algorithm>
14
15QT_BEGIN_NAMESPACE
16
17// lcQnxSndDevices is declared in qqnxsndaudiodevice_p.h and defined in
18// qqnxsndaudiodevices.cpp; the device-info and enumeration TUs share it.
19
20namespace {
21
22// Conservative format returned when the device's parameter space can't be probed.
23// io-snd is non-exclusive, so any openable device is probed for its real caps; this
24// is reached only for a hint-reported phantom with no backing hardware (whose device
25// is then discarded by the /dev/snd cross-check in qqnxsndaudiodevices.cpp) and for
26// the offline unit test, which uses a non-openable id to exercise this path.
28{
29 QAudioDevicePrivate::AudioDeviceFormat format;
30
31 format.minimumChannelCount = 1;
32 format.maximumChannelCount = 2;
33
34 format.minimumSampleRate = 8000;
35 format.maximumSampleRate = 48000;
36
37 format.supportedSampleFormats = {
38 QAudioFormat::UInt8,
39 QAudioFormat::Int16,
40 QAudioFormat::Int32,
41 QAudioFormat::Float,
42 };
43
44 format.preferredFormat.setChannelCount(mode == QAudioDevice::Input ? 1 : 2);
45 format.preferredFormat.setSampleFormat(QAudioFormat::Float);
46 format.preferredFormat.setSampleRate(48000);
47
48 return format;
49}
50
51// Query the device's real capabilities by opening it and inspecting the ALSA
52// hw_params space, in the spirit of the WASAPI backend's IsFormatSupported probe.
53// SALSA on QNX has no plugin/resampling layer, so the hint-reported format must
54// reflect what the hardware actually accepts. On any failure we fall back to the
55// conservative defaults rather than dropping the device.
57 QAudioDevice::Mode mode)
58{
59 QAudioDevicePrivate::AudioDeviceFormat format = defaultDeviceFormat(mode);
60
61 const snd_pcm_stream_t direction =
62 mode == QAudioDevice::Input ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
63
64 // Non-blocking open: probing only reads the parameter space and must never
65 // stall enumeration if the device is busy.
66 snd_pcm_t *handle = nullptr;
67 if (int err = snd_pcm_open(&handle, dev.constData(), direction, SND_PCM_NONBLOCK); err < 0) {
68 qCDebug(lcQnxSndDevices) << "probe: cannot open" << dev << ":" << snd_strerror(err)
69 << "- using default format";
70 return format;
71 }
72
73 snd_pcm_hw_params_t *hwparams;
74 snd_pcm_hw_params_alloca(&hwparams);
75 if (int err = snd_pcm_hw_params_any(handle, hwparams); err < 0) {
76 qCDebug(lcQnxSndDevices) << "probe: hw_params_any failed for" << dev << ":"
77 << snd_strerror(err) << "- using default format";
78 snd_pcm_close(handle);
79 return format;
80 }
81
82 // Sample rate range: test the standard rates against the device and keep the
83 // span of supported ones. (get_rate_max reports an unbounded value on io-snd.)
84 QList<int> supportedRates;
85 for (int rate : QtMultimediaPrivate::allSupportedSampleRates) {
86 if (snd_pcm_hw_params_test_rate(handle, hwparams, static_cast<unsigned>(rate), 0) == 0)
87 supportedRates.append(rate);
88 }
89 if (!supportedRates.isEmpty()) {
90 // allSupportedSampleRates is ascending, so first/last bound the range.
91 format.minimumSampleRate = supportedRates.first();
92 format.maximumSampleRate = supportedRates.last();
93 }
94
95 // io-snd converts sample formats internally; combined with the backend's
96 // "always best-native + convert" open strategy (see openConfiguredPcm), every
97 // QAudioFormat sample format is usable regardless of the device's native
98 // format, so advertise them all. The probe below is kept only to report the
99 // device-native formats in the debug log.
100 QList<QAudioFormat::SampleFormat> nativeFormats;
101 for (QAudioFormat::SampleFormat sf : { QAudioFormat::UInt8, QAudioFormat::Int16,
102 QAudioFormat::Int32, QAudioFormat::Float }) {
103 const snd_pcm_format_t pcmFormat = QnxSndHelpers::mapSampleFormat(sf);
104 if (pcmFormat != SND_PCM_FORMAT_UNKNOWN
105 && snd_pcm_hw_params_test_format(handle, hwparams, pcmFormat) == 0) {
106 nativeFormats.append(sf);
107 }
108 }
109 format.supportedSampleFormats = qAllSupportedSampleFormats();
110
111 // Channel count range.
112 unsigned int minChannels = 0;
113 unsigned int maxChannels = 0;
114 if (snd_pcm_hw_params_get_channels_min(hwparams, &minChannels) == 0
115 && snd_pcm_hw_params_get_channels_max(hwparams, &maxChannels) == 0 && maxChannels > 0) {
116 format.minimumChannelCount = static_cast<int>(minChannels);
117 format.maximumChannelCount = static_cast<int>(maxChannels);
118 }
119
120 // Float is always usable (converted to the device-native format on open), so
121 // it is the preferred application format.
122 format.preferredFormat.setSampleFormat(QAudioFormat::Float);
123
124 // Prefer 48 kHz, else the supported rate closest to it (logarithmic distance).
125 const int preferredRate = supportedRates.isEmpty()
126 ? ((48000 >= format.minimumSampleRate && 48000 <= format.maximumSampleRate)
127 ? 48000
128 : format.maximumSampleRate)
129 : QtMultimediaPrivate::findClosestSamplingRate(48000, QSpan<const int>{ supportedRates });
130 format.preferredFormat.setSampleRate(preferredRate);
131
132 const int preferredChannels = mode == QAudioDevice::Input ? 1 : 2;
133 format.preferredFormat.setChannelCount(
134 std::clamp(preferredChannels, format.minimumChannelCount, format.maximumChannelCount));
135
136 if (const int err = snd_pcm_close(handle); err < 0)
137 qCDebug(lcQnxSndDevices) << "probe: snd_pcm_close failed:" << snd_strerror(err);
138
139 qCDebug(lcQnxSndDevices) << "probe:" << dev << "rate" << format.minimumSampleRate << "-"
140 << format.maximumSampleRate << "ch" << format.minimumChannelCount << "-"
141 << format.maximumChannelCount << "native formats" << nativeFormats
142 << "(advertising all)";
143 return format;
144}
145
146} // namespace
147
148QQnxSndAudioDeviceInfo::QQnxSndAudioDeviceInfo(const QByteArray &dev, const QString &desc,
149 QAudioDevice::Mode mode)
150 : QAudioDevicePrivate(dev, mode, desc, false, probeDeviceFormat(dev, mode))
151{
152}
153
154QQnxSndAudioDeviceInfo::~QQnxSndAudioDeviceInfo() = default;
155
156QT_END_NAMESPACE
QAudioDevicePrivate::AudioDeviceFormat defaultDeviceFormat(QAudioDevice::Mode mode)
QAudioDevicePrivate::AudioDeviceFormat probeDeviceFormat(const QByteArray &dev, QAudioDevice::Mode mode)