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 | |
4 | #include "qpulsehelpers_p.h" |
5 | |
6 | QT_BEGIN_NAMESPACE |
7 | |
8 | Q_LOGGING_CATEGORY(qLcPulseAudioOut, "qt.multimedia.pulseaudio.output" ) |
9 | Q_LOGGING_CATEGORY(qLcPulseAudioIn, "qt.multimedia.pulseaudio.input" ) |
10 | Q_LOGGING_CATEGORY(qLcPulseAudioEngine, "qt.multimedia.pulseaudio.engine" ) |
11 | |
12 | namespace QPulseAudioInternal |
13 | { |
14 | pa_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 | |
23 | if (format.sampleFormat() == QAudioFormat::UInt8) { |
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 | |
36 | pa_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(channelCount: 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(channels: QAudioFormat::FrontLeft)) |
50 | map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT; |
51 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::FrontRight)) |
52 | map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT; |
53 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::FrontCenter)) |
54 | map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_CENTER; |
55 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::LFE)) |
56 | map.map[map.channels++] = PA_CHANNEL_POSITION_LFE; |
57 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::BackLeft)) |
58 | map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_LEFT; |
59 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::BackRight)) |
60 | map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_RIGHT; |
61 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::FrontLeftOfCenter)) |
62 | map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; |
63 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::FrontRightOfCenter)) |
64 | map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; |
65 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::BackCenter)) |
66 | map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_CENTER; |
67 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::LFE2)) |
68 | map.map[map.channels++] = PA_CHANNEL_POSITION_LFE; |
69 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::SideLeft)) |
70 | map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_LEFT; |
71 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::SideRight)) |
72 | map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_RIGHT; |
73 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopFrontLeft)) |
74 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT; |
75 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopFrontRight)) |
76 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; |
77 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopFrontCenter)) |
78 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER; |
79 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopCenter)) |
80 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_CENTER; |
81 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopBackLeft)) |
82 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_LEFT; |
83 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopBackRight)) |
84 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT; |
85 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopSideLeft)) |
86 | map.map[map.channels++] = PA_CHANNEL_POSITION_AUX0; |
87 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopSideRight)) |
88 | map.map[map.channels++] = PA_CHANNEL_POSITION_AUX1; |
89 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::TopBackCenter)) |
90 | map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_CENTER; |
91 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::BottomFrontCenter)) |
92 | map.map[map.channels++] = PA_CHANNEL_POSITION_AUX2; |
93 | if (config & QAudioFormat::channelConfig(channels: QAudioFormat::BottomFrontLeft)) |
94 | map.map[map.channels++] = PA_CHANNEL_POSITION_AUX3; |
95 | if (config & QAudioFormat::channelConfig(channels: 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 | |
103 | QAudioFormat::ChannelConfig channelConfigFromMap(const pa_channel_map &map) |
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(channels: QAudioFormat::FrontCenter); |
111 | break; |
112 | case PA_CHANNEL_POSITION_FRONT_LEFT: |
113 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::FrontLeft); |
114 | break; |
115 | case PA_CHANNEL_POSITION_FRONT_RIGHT: |
116 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::FrontRight); |
117 | break; |
118 | case PA_CHANNEL_POSITION_REAR_CENTER: |
119 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::BackCenter); |
120 | break; |
121 | case PA_CHANNEL_POSITION_REAR_LEFT: |
122 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::BackLeft); |
123 | break; |
124 | case PA_CHANNEL_POSITION_REAR_RIGHT: |
125 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::BackRight); |
126 | break; |
127 | case PA_CHANNEL_POSITION_LFE: |
128 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::LFE); |
129 | break; |
130 | case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: |
131 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::FrontLeftOfCenter); |
132 | break; |
133 | case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: |
134 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::FrontRightOfCenter); |
135 | break; |
136 | case PA_CHANNEL_POSITION_SIDE_LEFT: |
137 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::SideLeft); |
138 | break; |
139 | case PA_CHANNEL_POSITION_SIDE_RIGHT: |
140 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::SideRight); |
141 | break; |
142 | |
143 | case PA_CHANNEL_POSITION_TOP_CENTER: |
144 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopCenter); |
145 | break; |
146 | case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: |
147 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopFrontLeft); |
148 | break; |
149 | case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: |
150 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopFrontRight); |
151 | break; |
152 | case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: |
153 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopFrontCenter); |
154 | break; |
155 | case PA_CHANNEL_POSITION_TOP_REAR_LEFT: |
156 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopBackLeft); |
157 | break; |
158 | case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: |
159 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopBackRight); |
160 | break; |
161 | case PA_CHANNEL_POSITION_TOP_REAR_CENTER: |
162 | config |= QAudioFormat::channelConfig(channels: QAudioFormat::TopBackCenter); |
163 | break; |
164 | default: |
165 | break; |
166 | } |
167 | } |
168 | return QAudioFormat::ChannelConfig(config); |
169 | } |
170 | |
171 | QAudioFormat 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 | |
202 | QUtf8StringView currentError(const pa_context *context) |
203 | { |
204 | return pa_strerror(error: pa_context_errno(c: context)); |
205 | } |
206 | |
207 | QUtf8StringView currentError(const pa_stream *stream) |
208 | { |
209 | return currentError(context: pa_stream_get_context(p: stream)); |
210 | } |
211 | |
212 | } // namespace QPulseAudioInternal |
213 | |
214 | static QLatin1StringView stateToQStringView(pa_stream_state_t state) |
215 | { |
216 | using namespace Qt::StringLiterals; |
217 | switch (state) |
218 | { |
219 | case PA_STREAM_UNCONNECTED: return "Unconnected"_L1 ; |
220 | case PA_STREAM_CREATING: return "Creating"_L1 ; |
221 | case PA_STREAM_READY: return "Ready"_L1 ; |
222 | case PA_STREAM_FAILED: return "Failed"_L1 ; |
223 | case PA_STREAM_TERMINATED: return "Terminated"_L1 ; |
224 | default: Q_UNREACHABLE_RETURN("Unknown stream state"_L1 ); |
225 | } |
226 | } |
227 | |
228 | static QLatin1StringView sampleFormatToQStringView(pa_sample_format format) |
229 | { |
230 | using namespace Qt::StringLiterals; |
231 | switch (format) |
232 | { |
233 | case PA_SAMPLE_U8: return "Unsigned 8 Bit PCM."_L1 ; |
234 | case PA_SAMPLE_ALAW: return "8 Bit a-Law "_L1 ; |
235 | case PA_SAMPLE_ULAW: return "8 Bit mu-Law"_L1 ; |
236 | case PA_SAMPLE_S16LE: return "Signed 16 Bit PCM, little endian (PC)."_L1 ; |
237 | case PA_SAMPLE_S16BE: return "Signed 16 Bit PCM, big endian."_L1 ; |
238 | case PA_SAMPLE_FLOAT32LE: return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0"_L1 ; |
239 | case PA_SAMPLE_FLOAT32BE: return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0"_L1 ; |
240 | case PA_SAMPLE_S32LE: return "Signed 32 Bit PCM, little endian (PC)."_L1 ; |
241 | case PA_SAMPLE_S32BE: return "Signed 32 Bit PCM, big endian."_L1 ; |
242 | case PA_SAMPLE_S24LE: return "Signed 24 Bit PCM packed, little endian (PC)."_L1 ; |
243 | case PA_SAMPLE_S24BE: return "Signed 24 Bit PCM packed, big endian."_L1 ; |
244 | case PA_SAMPLE_S24_32LE: return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC)."_L1 ; |
245 | case PA_SAMPLE_S24_32BE: return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian."_L1 ; |
246 | case PA_SAMPLE_MAX: return "Upper limit of valid sample types."_L1 ; |
247 | case PA_SAMPLE_INVALID: return "Invalid sample format"_L1 ; |
248 | default: Q_UNREACHABLE_RETURN("Unknown sample format"_L1 ); |
249 | } |
250 | } |
251 | |
252 | static QLatin1StringView stateToQStringView(pa_context_state_t state) |
253 | { |
254 | using namespace Qt::StringLiterals; |
255 | switch (state) |
256 | { |
257 | case PA_CONTEXT_UNCONNECTED: return "Unconnected"_L1 ; |
258 | case PA_CONTEXT_CONNECTING: return "Connecting"_L1 ; |
259 | case PA_CONTEXT_AUTHORIZING: return "Authorizing"_L1 ; |
260 | case PA_CONTEXT_SETTING_NAME: return "Setting Name"_L1 ; |
261 | case PA_CONTEXT_READY: return "Ready"_L1 ; |
262 | case PA_CONTEXT_FAILED: return "Failed"_L1 ; |
263 | case PA_CONTEXT_TERMINATED: return "Terminated"_L1 ; |
264 | default: Q_UNREACHABLE_RETURN("Unknown context state"_L1 ); |
265 | } |
266 | } |
267 | |
268 | |
269 | QDebug operator<<(QDebug dbg, pa_stream_state_t state) |
270 | { |
271 | return dbg << stateToQStringView(state); |
272 | } |
273 | |
274 | QDebug operator<<(QDebug dbg, pa_sample_format format) |
275 | { |
276 | return dbg << sampleFormatToQStringView(format); |
277 | } |
278 | |
279 | QDebug operator<<(QDebug dbg, pa_context_state_t state) |
280 | { |
281 | return dbg << stateToQStringView(state); |
282 | } |
283 | |
284 | QT_END_NAMESPACE |
285 | |