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
qpulsehelpers.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
7
8Q_LOGGING_CATEGORY(qLcPulseAudioOut, "qt.multimedia.pulseaudio.output")
9Q_LOGGING_CATEGORY(qLcPulseAudioIn, "qt.multimedia.pulseaudio.input")
10Q_LOGGING_CATEGORY(qLcPulseAudioEngine, "qt.multimedia.pulseaudio.engine")
11
12namespace QPulseAudioInternal
13{
14pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
15{
16 pa_sample_spec spec;
17
18 spec.rate = format.sampleRate();
19 spec.channels = format.channelCount();
20 spec.format = PA_SAMPLE_INVALID;
21 const bool isBigEndian = QSysInfo::ByteOrder == QSysInfo::BigEndian;
22
24 spec.format = PA_SAMPLE_U8;
25 } else if (format.sampleFormat() == QAudioFormat::Int16) {
26 spec.format = isBigEndian ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
27 } else if (format.sampleFormat() == QAudioFormat::Int32) {
28 spec.format = isBigEndian ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
29 } else if (format.sampleFormat() == QAudioFormat::Float) {
30 spec.format = isBigEndian ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE;
31 }
32
33 return spec;
34}
35
36pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
37{
38 pa_channel_map map;
39 map.channels = 0;
40
41 auto config = format.channelConfig();
42 if (config == QAudioFormat::ChannelConfigUnknown)
43 config = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
44
45 if (config == QAudioFormat::ChannelConfigMono) {
46 map.channels = 1;
47 map.map[0] = PA_CHANNEL_POSITION_MONO;
48 } else {
49 if (config & QAudioFormat::channelConfig(QAudioFormat::FrontLeft))
50 map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT;
51 if (config & QAudioFormat::channelConfig(QAudioFormat::FrontRight))
52 map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT;
53 if (config & QAudioFormat::channelConfig(QAudioFormat::FrontCenter))
54 map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_CENTER;
55 if (config & QAudioFormat::channelConfig(QAudioFormat::LFE))
56 map.map[map.channels++] = PA_CHANNEL_POSITION_LFE;
57 if (config & QAudioFormat::channelConfig(QAudioFormat::BackLeft))
58 map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_LEFT;
59 if (config & QAudioFormat::channelConfig(QAudioFormat::BackRight))
60 map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_RIGHT;
61 if (config & QAudioFormat::channelConfig(QAudioFormat::FrontLeftOfCenter))
62 map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
63 if (config & QAudioFormat::channelConfig(QAudioFormat::FrontRightOfCenter))
64 map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
65 if (config & QAudioFormat::channelConfig(QAudioFormat::BackCenter))
66 map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_CENTER;
67 if (config & QAudioFormat::channelConfig(QAudioFormat::LFE2))
68 map.map[map.channels++] = PA_CHANNEL_POSITION_LFE;
69 if (config & QAudioFormat::channelConfig(QAudioFormat::SideLeft))
70 map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_LEFT;
71 if (config & QAudioFormat::channelConfig(QAudioFormat::SideRight))
72 map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_RIGHT;
73 if (config & QAudioFormat::channelConfig(QAudioFormat::TopFrontLeft))
74 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
75 if (config & QAudioFormat::channelConfig(QAudioFormat::TopFrontRight))
76 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
77 if (config & QAudioFormat::channelConfig(QAudioFormat::TopFrontCenter))
78 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
79 if (config & QAudioFormat::channelConfig(QAudioFormat::TopCenter))
80 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_CENTER;
81 if (config & QAudioFormat::channelConfig(QAudioFormat::TopBackLeft))
82 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_LEFT;
83 if (config & QAudioFormat::channelConfig(QAudioFormat::TopBackRight))
84 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
85 if (config & QAudioFormat::channelConfig(QAudioFormat::TopSideLeft))
86 map.map[map.channels++] = PA_CHANNEL_POSITION_AUX0;
87 if (config & QAudioFormat::channelConfig(QAudioFormat::TopSideRight))
88 map.map[map.channels++] = PA_CHANNEL_POSITION_AUX1;
89 if (config & QAudioFormat::channelConfig(QAudioFormat::TopBackCenter))
90 map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_CENTER;
91 if (config & QAudioFormat::channelConfig(QAudioFormat::BottomFrontCenter))
92 map.map[map.channels++] = PA_CHANNEL_POSITION_AUX2;
93 if (config & QAudioFormat::channelConfig(QAudioFormat::BottomFrontLeft))
94 map.map[map.channels++] = PA_CHANNEL_POSITION_AUX3;
95 if (config & QAudioFormat::channelConfig(QAudioFormat::BottomFrontRight))
96 map.map[map.channels++] = PA_CHANNEL_POSITION_AUX4;
97 }
98
99 Q_ASSERT(qPopulationCount(config) == map.channels);
100 return map;
101}
102
104{
105 quint32 config = 0;
106 for (int i = 0; i < map.channels; ++i) {
107 switch (map.map[i]) {
108 case PA_CHANNEL_POSITION_MONO:
109 case PA_CHANNEL_POSITION_FRONT_CENTER:
110 config |= QAudioFormat::channelConfig(QAudioFormat::FrontCenter);
111 break;
112 case PA_CHANNEL_POSITION_FRONT_LEFT:
113 config |= QAudioFormat::channelConfig(QAudioFormat::FrontLeft);
114 break;
115 case PA_CHANNEL_POSITION_FRONT_RIGHT:
116 config |= QAudioFormat::channelConfig(QAudioFormat::FrontRight);
117 break;
118 case PA_CHANNEL_POSITION_REAR_CENTER:
119 config |= QAudioFormat::channelConfig(QAudioFormat::BackCenter);
120 break;
121 case PA_CHANNEL_POSITION_REAR_LEFT:
122 config |= QAudioFormat::channelConfig(QAudioFormat::BackLeft);
123 break;
124 case PA_CHANNEL_POSITION_REAR_RIGHT:
125 config |= QAudioFormat::channelConfig(QAudioFormat::BackRight);
126 break;
127 case PA_CHANNEL_POSITION_LFE:
128 config |= QAudioFormat::channelConfig(QAudioFormat::LFE);
129 break;
130 case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
131 config |= QAudioFormat::channelConfig(QAudioFormat::FrontLeftOfCenter);
132 break;
133 case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
134 config |= QAudioFormat::channelConfig(QAudioFormat::FrontRightOfCenter);
135 break;
136 case PA_CHANNEL_POSITION_SIDE_LEFT:
137 config |= QAudioFormat::channelConfig(QAudioFormat::SideLeft);
138 break;
139 case PA_CHANNEL_POSITION_SIDE_RIGHT:
140 config |= QAudioFormat::channelConfig(QAudioFormat::SideRight);
141 break;
142
143 case PA_CHANNEL_POSITION_TOP_CENTER:
144 config |= QAudioFormat::channelConfig(QAudioFormat::TopCenter);
145 break;
146 case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
147 config |= QAudioFormat::channelConfig(QAudioFormat::TopFrontLeft);
148 break;
149 case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
150 config |= QAudioFormat::channelConfig(QAudioFormat::TopFrontRight);
151 break;
152 case PA_CHANNEL_POSITION_TOP_FRONT_CENTER:
153 config |= QAudioFormat::channelConfig(QAudioFormat::TopFrontCenter);
154 break;
155 case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
156 config |= QAudioFormat::channelConfig(QAudioFormat::TopBackLeft);
157 break;
158 case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
159 config |= QAudioFormat::channelConfig(QAudioFormat::TopBackRight);
160 break;
161 case PA_CHANNEL_POSITION_TOP_REAR_CENTER:
162 config |= QAudioFormat::channelConfig(QAudioFormat::TopBackCenter);
163 break;
164 default:
165 break;
166 }
167 }
168 return QAudioFormat::ChannelConfig(config);
169}
170
171QAudioFormat sampleSpecToAudioFormat(const pa_sample_spec &spec)
172{
173 QAudioFormat format;
174
175 format.setSampleRate(spec.rate);
176 format.setChannelCount(spec.channels);
177 QAudioFormat::SampleFormat sampleFormat;
178 switch (spec.format) {
179 case PA_SAMPLE_U8:
180 sampleFormat = QAudioFormat::UInt8;
181 break;
182 case PA_SAMPLE_S16LE:
183 case PA_SAMPLE_S16BE:
184 sampleFormat = QAudioFormat::Int16;
185 break;
186 case PA_SAMPLE_FLOAT32LE:
187 case PA_SAMPLE_FLOAT32BE:
188 sampleFormat = QAudioFormat::Float;
189 break;
190 case PA_SAMPLE_S32LE:
191 case PA_SAMPLE_S32BE:
192 sampleFormat = QAudioFormat::Int32;
193 break;
194 default:
195 return {};
196 }
197
198 format.setSampleFormat(sampleFormat);
199 return format;
200}
201
202QUtf8StringView currentError(const pa_context *context)
203{
204 return pa_strerror(pa_context_errno(context));
205}
206
207QUtf8StringView currentError(const pa_stream *stream)
208{
209 return currentError(pa_stream_get_context(stream));
210}
211
212PAOperationHandle streamCork(const PAStreamHandle &stream, bool corkStream)
213{
214 return PAOperationHandle{
215 pa_stream_cork(stream.get(), corkStream ? 1 : 0, nullptr, nullptr),
216 PAOperationHandle::HasRef,
217 };
218}
219
221{
222 using namespace Qt::StringLiterals;
223
224 auto ml = std::unique_ptr<pa_mainloop, PaMainLoopDeleter>(pa_mainloop_new());
225 pa_mainloop_api *api = pa_mainloop_get_api(ml.get());
226 PAContextHandle ctx{
227 pa_context_new(api, "pipewire-check"),
228 PAContextHandle::HasRef,
229 };
230
231 struct ContextData
232 {
233 bool ready = false;
234 bool is_pipewire = false;
235 };
236 ContextData data;
237
238 pa_context_set_state_callback(ctx.get(), [](pa_context *ctx, void *userdata) {
239 auto *data = static_cast<ContextData *>(userdata);
240 switch (pa_context_get_state(ctx)) {
241 case PA_CONTEXT_READY:
242 case PA_CONTEXT_FAILED:
243 case PA_CONTEXT_TERMINATED:
244 data->ready = true;
245 break;
246 default:
247 break;
248 }
249 }, &data);
250 pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr);
251 auto disconnect = qScopeGuard([&] {
252 pa_context_disconnect(ctx.get());
253 });
254
255 while (!data.ready)
256 pa_mainloop_iterate(ml.get(), 1, nullptr);
257
258 if (pa_context_get_state(ctx.get()) != PA_CONTEXT_READY)
259 return PulseaudioServerType::NotConnected;
260
261 PAOperationHandle op{
262 pa_context_get_server_info(ctx.get(),
263 [](pa_context *, const pa_server_info *info, void *userdata) {
264 auto *data = static_cast<ContextData *>(userdata);
265 if (info && info->server_name)
266 data->is_pipewire = QLatin1StringView(info->server_name).contains("PipeWire"_L1);
267 }, &data),
268 PAOperationHandle::HasRef,
269 };
270
271 while (pa_operation_get_state(op.get()) == PA_OPERATION_RUNNING)
272 pa_mainloop_iterate(ml.get(), 1, nullptr);
273
274 return data.is_pipewire ? PulseaudioServerType::Pipewire : PulseaudioServerType::Pulseaudio;
275}
276
277} // namespace QPulseAudioInternal
278
279static QLatin1StringView stateToQStringView(pa_stream_state_t state)
280{
281 using namespace Qt::StringLiterals;
282 switch (state)
283 {
284 case PA_STREAM_UNCONNECTED: return "Unconnected"_L1;
285 case PA_STREAM_CREATING: return "Creating"_L1;
286 case PA_STREAM_READY: return "Ready"_L1;
287 case PA_STREAM_FAILED: return "Failed"_L1;
288 case PA_STREAM_TERMINATED: return "Terminated"_L1;
289 default: Q_UNREACHABLE_RETURN("Unknown stream state"_L1);
290 }
291}
292
293static QLatin1StringView sampleFormatToQStringView(pa_sample_format format)
294{
295 using namespace Qt::StringLiterals;
296 switch (format)
297 {
298 case PA_SAMPLE_U8: return "Unsigned 8 Bit PCM."_L1;
299 case PA_SAMPLE_ALAW: return "8 Bit a-Law "_L1;
300 case PA_SAMPLE_ULAW: return "8 Bit mu-Law"_L1;
301 case PA_SAMPLE_S16LE: return "Signed 16 Bit PCM, little endian (PC)."_L1;
302 case PA_SAMPLE_S16BE: return "Signed 16 Bit PCM, big endian."_L1;
303 case PA_SAMPLE_FLOAT32LE: return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0"_L1;
304 case PA_SAMPLE_FLOAT32BE: return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0"_L1;
305 case PA_SAMPLE_S32LE: return "Signed 32 Bit PCM, little endian (PC)."_L1;
306 case PA_SAMPLE_S32BE: return "Signed 32 Bit PCM, big endian."_L1;
307 case PA_SAMPLE_S24LE: return "Signed 24 Bit PCM packed, little endian (PC)."_L1;
308 case PA_SAMPLE_S24BE: return "Signed 24 Bit PCM packed, big endian."_L1;
309 case PA_SAMPLE_S24_32LE: return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC)."_L1;
310 case PA_SAMPLE_S24_32BE: return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian."_L1;
311 case PA_SAMPLE_MAX: return "Upper limit of valid sample types."_L1;
312 case PA_SAMPLE_INVALID: return "Invalid sample format"_L1;
313 default: Q_UNREACHABLE_RETURN("Unknown sample format"_L1);
314 }
315}
316
317static QLatin1StringView stateToQStringView(pa_context_state_t state)
318{
319 using namespace Qt::StringLiterals;
320 switch (state)
321 {
322 case PA_CONTEXT_UNCONNECTED: return "Unconnected"_L1;
323 case PA_CONTEXT_CONNECTING: return "Connecting"_L1;
324 case PA_CONTEXT_AUTHORIZING: return "Authorizing"_L1;
325 case PA_CONTEXT_SETTING_NAME: return "Setting Name"_L1;
326 case PA_CONTEXT_READY: return "Ready"_L1;
327 case PA_CONTEXT_FAILED: return "Failed"_L1;
328 case PA_CONTEXT_TERMINATED: return "Terminated"_L1;
329 default: Q_UNREACHABLE_RETURN("Unknown context state"_L1);
330 }
331}
332
333
334QDebug operator<<(QDebug dbg, pa_stream_state_t state)
335{
336 return dbg << stateToQStringView(state);
337}
338
339QDebug operator<<(QDebug dbg, pa_sample_format format)
340{
341 return dbg << sampleFormatToQStringView(format);
342}
343
344QDebug operator<<(QDebug dbg, pa_context_state_t state)
345{
346 return dbg << stateToQStringView(state);
347}
348
349QT_END_NAMESPACE
The QAudioFormat class stores audio stream parameter information.
constexpr ChannelConfig channelConfig() const noexcept
Returns the current channel configuration.
constexpr SampleFormat sampleFormat() const noexcept
Returns the current sample format.
constexpr void setChannelCount(int channelCount) noexcept
Sets the channel count to channels.
constexpr void setSampleFormat(SampleFormat f) noexcept
Sets the sample format to format.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
SampleFormat
Qt will always expect and use samples in the endianness of the host platform.
constexpr int channelCount() const noexcept
Returns the current channel count value.
constexpr void setSampleRate(int sampleRate) noexcept
Sets the sample rate to samplerate in Hertz.
QUtf8StringView currentError(const pa_context *context)
QUtf8StringView currentError(const pa_stream *stream)
pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
QAudioFormat::ChannelConfig channelConfigFromMap(const pa_channel_map &map)
QAudioFormat sampleSpecToAudioFormat(const pa_sample_spec &spec)
PulseaudioServerType pulseaudioDetectServerType()
pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
PAOperationHandle streamCork(const PAStreamHandle &stream, bool corkStream)
Combined button and popup list for selecting options.
static QLatin1StringView stateToQStringView(pa_stream_state_t state)
static QLatin1StringView stateToQStringView(pa_context_state_t state)
static QLatin1StringView sampleFormatToQStringView(pa_sample_format format)
QDebug operator<<(QDebug dbg, pa_sample_format format)
QDebug operator<<(QDebug dbg, pa_stream_state_t state)
QDebug operator<<(QDebug dbg, pa_context_state_t state)