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
qambisonicdecoder.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qaudio_qspan_support_p.h>
7#include <QtSpatialAudio/private/qambisonicdecoderdata_p.h>
8#include <QtCore/qdebug.h>
9
10#include <algorithm>
11#include <array>
12#include <cmath>
13
14QT_BEGIN_NAMESPACE
15
16// Ambisonic decoding is described in detail in https://ambisonics.dreamhosters.com/BLaH3.pdf.
17// We're using a phase matched band splitting filter to split the ambisonic signal into a low
18// and high frequency component and apply matrix conversions to those components individually
19// as described in the document.
20//
21// We are currently not using a near field compensation filter, something that could potentially
22// improve sound quality further.
23//
24// For mono and stereo decoding, we use a simpler algorithm to avoid artificially dampening signals
25// coming from the back, as we do not have any speakers in that direction and the calculations
26// through matlab would give us audible 'holes'.
27
28struct QAmbisonicDecoderData
29{
30 const QAudioFormat::ChannelConfig config;
31 const std::array<const float *, 3> lf;
32 const std::array<const float *, 3> hf;
33 const float *const reverb;
34};
35
36constexpr float reverb_x_0[] = {
37 1.f, 0.f, // L
38 0.f, 1.f, // R
39 .7f, .7f, // C
40 1.f, 0.f, // Ls
41 0.f, 1.f, // Rs
42 1.f, 0.f, // Lb
43 0.f, 1.f, // Rb
44};
45
46constexpr float reverb_x_1[] = {
47 1.f, 0.f, // L
48 0.f, 1.f, // R
49 .7f, .7f, // C
50 .0f, .0f, // LFE
51 1.f, 0.f, // Ls
52 0.f, 1.f, // Rs
53 1.f, 0.f, // Lb
54 0.f, 1.f, // Rb
55};
56
57static constexpr QAmbisonicDecoderData decoderMap[] = {
58 { QAudioFormat::ChannelConfigSurround5Dot0,
59 {{ decoderMatrix_5dot0_1_lf, decoderMatrix_5dot0_2_lf, decoderMatrix_5dot0_3_lf }},
60 {{ decoderMatrix_5dot0_1_hf, decoderMatrix_5dot0_2_hf, decoderMatrix_5dot0_3_hf }},
61 reverb_x_0 },
62 { QAudioFormat::ChannelConfigSurround5Dot1,
63 {{ decoderMatrix_5dot1_1_lf, decoderMatrix_5dot1_2_lf, decoderMatrix_5dot1_3_lf }},
64 {{ decoderMatrix_5dot1_1_hf, decoderMatrix_5dot1_2_hf, decoderMatrix_5dot1_3_hf }},
65 reverb_x_1 },
66 { QAudioFormat::ChannelConfigSurround7Dot0,
67 {{ decoderMatrix_7dot0_1_lf, decoderMatrix_7dot0_2_lf, decoderMatrix_7dot0_3_lf }},
68 {{ decoderMatrix_7dot0_1_hf, decoderMatrix_7dot0_2_hf, decoderMatrix_7dot0_3_hf }},
69 reverb_x_0 },
70 { QAudioFormat::ChannelConfigSurround7Dot1,
71 {{ decoderMatrix_7dot1_1_lf, decoderMatrix_7dot1_2_lf, decoderMatrix_7dot1_3_lf }},
72 {{ decoderMatrix_7dot1_1_hf, decoderMatrix_7dot1_2_hf, decoderMatrix_7dot1_3_hf }},
73 reverb_x_1 }
74};
75
76// Implements a split second order IIR filter (TDF-II)
77// The audio data is split into a phase synced low and high frequency part
78// This allows us to apply different factors to both parts for better sound
79// localization when converting from ambisonic formats
80//
81// Details are described in https://ambisonics.dreamhosters.com/BLaH3.pdf, Appendix A.2.
83{
84public:
86 void configure(float sampleRate, float cutoffFrequency = 380)
87 {
88 double k = std::tan(M_PI * cutoffFrequency / sampleRate);
89 double denom = k * k + 2.0 * k + 1.0;
90
91 a1 = 2.0 * (k * k - 1.0) / denom;
92 a2 = (k * k - 2.0 * k + 1.0) / denom;
93
94 b0_lf = (k * k) / denom;
95 b1_lf = 2.0 * b0_lf;
96
97 b0_hf = 1.0 / denom;
98 b1_hf = -2.0 * b0_hf;
99 }
100
101 struct Output
102 {
103 float lf;
104 float hf;
105 };
106
107 Output next(float x)
108 {
109#ifdef Q_PROCESSOR_X86
110 // tiny DC offset to prevent denormals, which can cause severe performance degradation on x86 CPUs
111 x += 1e-18f;
112#endif
113 // Process LF
114 double r_lf = x * b0_lf + s1_lf;
115 s1_lf = x * b1_lf - r_lf * a1 + s2_lf;
116 s2_lf = x * b0_lf - r_lf * a2;
117
118 // Process HF
119 double r_hf = x * b0_hf + s1_hf;
120 s1_hf = x * b1_hf - r_hf * a1 + s2_hf;
121 s2_hf = x * b0_hf - r_hf * a2;
122
123 return Output{
124 float(r_lf),
125 float(r_hf),
126 };
127 }
128
129private:
130 // coefficients
131 double a1 = 0.;
132 double a2 = 0.;
133 double b0_hf = 0.;
134 double b1_hf = 0.;
135 double b0_lf = 0.;
136 double b1_lf = 0.;
137
138 // state
139 double s1_lf = 0.0, s2_lf = 0.0;
140 double s1_hf = 0.0, s2_hf = 0.0;
141};
142
143namespace {
144
145int inputChannelsForAmbisonicOrder(QAmbisonicDecoder::AmbisonicOrder ambisonicOrder)
146{
147 auto order = qToUnderlying(ambisonicOrder);
148 Q_ASSERT(order > 0 && order <= 3);
149 return (order + 1) * (order + 1);
150}
151
152QAudioFormat::ChannelConfig ambisonicDecoderChannelConfig(QAudioFormat::ChannelConfig channelConfig,
153 int numberOfOutputChannels)
154{
155 if (channelConfig == QAudioFormat::ChannelConfigUnknown)
156 channelConfig = QAudioFormat::defaultChannelConfigForChannelCount(numberOfOutputChannels);
157 return channelConfig;
158}
159
160} // namespace
161
162QAmbisonicDecoder::QAmbisonicDecoder(AmbisonicOrder ambisonicOrder, int sampleRate,
163 int numberOfOutputChannels,
164 QAudioFormat::ChannelConfig channelCfg)
166 order(ambisonicOrder),
167 inputChannels(inputChannelsForAmbisonicOrder(ambisonicOrder)),
168 outputChannels(numberOfOutputChannels)
169{
170 if (channelConfig == QAudioFormat::ChannelConfigMono ||
171 channelConfig == QAudioFormat::ChannelConfigStereo ||
172 channelConfig == QAudioFormat::ChannelConfig2Dot1 ||
173 channelConfig == QAudioFormat::ChannelConfig3Dot0 ||
174 channelConfig == QAudioFormat::ChannelConfig3Dot1) {
175 // these are non surround configs and handled manually to avoid
176 // audible holes for sounds coming from behing
177 //
178 // We use a simpler decoding process here, only taking first order
179 // ambisonics into account
180 //
181 // Left and right channels get 50% W and 50% X
182 // Center gets 50% W and 50% Y
183 // LFE gets 50% W
184 simpleDecoderFactors = std::make_unique<float[]>(4 * outputChannels);
185 m_reverbFactorsOwned = std::make_unique<float[]>(2 * outputChannels); // reverb output is in stereo
186 reverbFactors = m_reverbFactorsOwned.get();
187 float *f = simpleDecoderFactors.get();
188 float *r = m_reverbFactorsOwned.get();
189 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::FrontLeft)) {
190 f[0] = 0.5f; f[1] = 0.5f; f[2] = 0.; f[3] = 0.f;
191 f += 4;
192 r[0] = 1.; r[1] = 0.;
193 r += 2;
194 }
195 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::FrontRight)) {
196 f[0] = 0.5f; f[1] = -0.5f; f[2] = 0.; f[3] = 0.f;
197 f += 4;
198 r[0] = 0.; r[1] = 1.;
199 r += 2;
200 }
201 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::FrontCenter)) {
202 f[0] = 0.5f; f[1] = -0.f; f[2] = 0.; f[3] = 0.5f;
203 f += 4;
204 r[0] = .5; r[1] = .5;
205 r += 2;
206 }
207 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::LFE)) {
208 f[0] = 0.5f; f[1] = -0.f; f[2] = 0.; f[3] = 0.0f;
209 f += 4;
210 r[0] = 0.; r[1] = 0.;
211 r += 2;
212 }
213 Q_UNUSED(f);
214 Q_UNUSED(r);
215 Q_ASSERT((f - simpleDecoderFactors.get()) == 4 * outputChannels);
216 Q_ASSERT((r - reverbFactors) == 2*outputChannels);
217
218 return;
219 }
220
221 for (const auto &d : decoderMap) {
222 if (d.config == channelConfig) {
223 decoderData = &d;
224 reverbFactors = decoderData->reverb;
225 break;
226 }
227 }
228 if (!decoderData)
229 return;
230
231 filters = std::make_unique<QAmbisonicDecoderFilter[]>(inputChannels);
232 for (int i = 0; i < inputChannels; ++i)
233 filters[i].configure(sampleRate);
234}
235
237
238void QAmbisonicDecoder::processBuffer(QSpan<const float *> input, QSpan<float> output)
239{
240 const int nSamples = int(output.size()) / outputChannels;
241 std::fill(output.begin(), output.end(), 0.f);
242 float *o = output.data();
243
244 if (simpleDecoderFactors) {
245 for (int i = 0; i < nSamples; ++i) {
246 for (int j = 0; j < 4; ++j) {
247 for (int k = 0; k < outputChannels; ++k)
248 o[k] += simpleDecoderFactors[k*4 + j]*input[j][i];
249 }
250 o += outputChannels;
251 }
252 return;
253 }
254
255 const float *matrix_hi = decoderData->hf[order - 1];
256 const float *matrix_lo = decoderData->lf[order - 1];
257 for (int i = 0; i < nSamples; ++i) {
259 for (int j = 0; j < inputChannels; ++j)
260 buf[j] = filters[j].next(input[j][i]);
261 for (int j = 0; j < inputChannels; ++j) {
262 for (int k = 0; k < outputChannels; ++k)
263 o[k] += matrix_lo[k*inputChannels + j]*buf[j].lf + matrix_hi[k*inputChannels + j]*buf[j].hf;
264 }
265 o += outputChannels;
266 }
267}
268
269void QAmbisonicDecoder::processBufferWithReverb(QSpan<const float *> input,
270 QSpan<const float *, 2> reverb, QSpan<float> output)
271{
272 Q_ASSERT(outputChannels > 0);
273 Q_ASSERT(int(output.size()) % outputChannels == 0);
274 const int nSamples = int(output.size()) / outputChannels;
275
276 std::fill(output.begin(), output.end(), 0.f);
277 float *o = output.data();
278
279 if (simpleDecoderFactors) {
280 for (int i = 0; i < nSamples; ++i) {
281 for (int k = 0; k < outputChannels; ++k) {
282 for (int j = 0; j < 4; ++j)
283 o[k] += simpleDecoderFactors[k*4 + j]*input[j][i];
284 }
285 if (reverb[0]) {
286 for (int k = 0; k < outputChannels; ++k)
287 o[k] += reverb[0][i] * reverbFactors[2 * k]
288 + reverb[1][i] * reverbFactors[2 * k + 1];
289 }
290
291 o += outputChannels;
292 }
293 return;
294 }
295
296 Q_ASSERT(filters);
297
298 const float *matrix_hi = decoderData->hf[order - 1];
299 const float *matrix_lo = decoderData->lf[order - 1];
300 for (int i = 0; i < nSamples; ++i) {
302 for (int j = 0; j < inputChannels; ++j)
303 buf[j] = filters[j].next(input[j][i]);
304 for (int j = 0; j < inputChannels; ++j) {
305 for (int k = 0; k < outputChannels; ++k)
306 o[k] += matrix_lo[k*inputChannels + j]*buf[j].lf + matrix_hi[k*inputChannels + j]*buf[j].hf;
307 }
308 if (reverb[0]) {
309 for (int k = 0; k < outputChannels; ++k)
310 o[k] += reverb[0][i]*reverbFactors[2*k] + reverb[1][i]*reverbFactors[2*k+1];
311 }
312 o += outputChannels;
313 }
314}
315
316QT_END_NAMESPACE
QAmbisonicDecoderFilter()=default
void configure(float sampleRate, float cutoffFrequency=380)
void processBuffer(QSpan< const float * > input, QSpan< float > output)
QAmbisonicDecoder(AmbisonicOrder, int sampleRate, int numberOfOutputChannels, QAudioFormat::ChannelConfig)
static constexpr int maxAmbisonicChannels
void processBufferWithReverb(QSpan< const float * > input, QSpan< const float *, 2 > reverb, QSpan< float > output)
static constexpr QAmbisonicDecoderData decoderMap[]
constexpr float reverb_x_1[]
constexpr float reverb_x_0[]
#define M_PI
Definition qmath.h:201