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 <QtCore/qdebug.h>
7#include <QtMultimedia/private/qaudio_qspan_support_p.h>
8#include <QtSpatialAudio/private/qambisonicdecoderdata_p.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
143QAmbisonicDecoder::QAmbisonicDecoder(AmbisonicOrder ambisonicOrder, const QAudioFormat &format)
144 : order(ambisonicOrder)
145{
146 Q_ASSERT(order > 0 && order <= 3);
147 inputChannels = (order + 1) * (order + 1);
148 outputChannels = format.channelCount();
149
150 channelConfig = format.channelConfig();
151 if (channelConfig == QAudioFormat::ChannelConfigUnknown)
152 channelConfig = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
153
154 if (channelConfig == QAudioFormat::ChannelConfigMono ||
155 channelConfig == QAudioFormat::ChannelConfigStereo ||
156 channelConfig == QAudioFormat::ChannelConfig2Dot1 ||
157 channelConfig == QAudioFormat::ChannelConfig3Dot0 ||
158 channelConfig == QAudioFormat::ChannelConfig3Dot1) {
159 // these are non surround configs and handled manually to avoid
160 // audible holes for sounds coming from behing
161 //
162 // We use a simpler decoding process here, only taking first order
163 // ambisonics into account
164 //
165 // Left and right channels get 50% W and 50% X
166 // Center gets 50% W and 50% Y
167 // LFE gets 50% W
168 simpleDecoderFactors = std::make_unique<float[]>(4 * outputChannels);
169 m_reverbFactorsOwned = std::make_unique<float[]>(2 * outputChannels); // reverb output is in stereo
170 reverbFactors = m_reverbFactorsOwned.get();
171 float *f = simpleDecoderFactors.get();
172 float *r = m_reverbFactorsOwned.get();
173 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::FrontLeft)) {
174 f[0] = 0.5f; f[1] = 0.5f; f[2] = 0.; f[3] = 0.f;
175 f += 4;
176 r[0] = 1.; r[1] = 0.;
177 r += 2;
178 }
179 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::FrontRight)) {
180 f[0] = 0.5f; f[1] = -0.5f; f[2] = 0.; f[3] = 0.f;
181 f += 4;
182 r[0] = 0.; r[1] = 1.;
183 r += 2;
184 }
185 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::FrontCenter)) {
186 f[0] = 0.5f; f[1] = -0.f; f[2] = 0.; f[3] = 0.5f;
187 f += 4;
188 r[0] = .5; r[1] = .5;
189 r += 2;
190 }
191 if (channelConfig & QAudioFormat::channelConfig(QAudioFormat::LFE)) {
192 f[0] = 0.5f; f[1] = -0.f; f[2] = 0.; f[3] = 0.0f;
193 f += 4;
194 r[0] = 0.; r[1] = 0.;
195 r += 2;
196 }
197 Q_UNUSED(f);
198 Q_UNUSED(r);
199 Q_ASSERT((f - simpleDecoderFactors.get()) == 4 * outputChannels);
200 Q_ASSERT((r - reverbFactors) == 2*outputChannels);
201
202 return;
203 }
204
205 for (const auto &d : decoderMap) {
206 if (d.config == channelConfig) {
207 decoderData = &d;
208 reverbFactors = decoderData->reverb;
209 break;
210 }
211 }
212 if (!decoderData) {
213 // can't handle this,
214 outputChannels = 0;
215 return;
216 }
217
218 filters = std::make_unique<QAmbisonicDecoderFilter[]>(inputChannels);
219 for (int i = 0; i < inputChannels; ++i)
220 filters[i].configure(format.sampleRate());
221}
222
224
225void QAmbisonicDecoder::processBuffer(QSpan<const float *> input, QSpan<float> output)
226{
227 const int nSamples = int(output.size()) / outputChannels;
228 std::fill(output.begin(), output.end(), 0.f);
229 float *o = output.data();
230
231 if (simpleDecoderFactors) {
232 for (int i = 0; i < nSamples; ++i) {
233 for (int j = 0; j < 4; ++j) {
234 for (int k = 0; k < outputChannels; ++k)
235 o[k] += simpleDecoderFactors[k*4 + j]*input[j][i];
236 }
237 o += outputChannels;
238 }
239 return;
240 }
241
242 const float *matrix_hi = decoderData->hf[order - 1];
243 const float *matrix_lo = decoderData->lf[order - 1];
244 for (int i = 0; i < nSamples; ++i) {
246 for (int j = 0; j < inputChannels; ++j)
247 buf[j] = filters[j].next(input[j][i]);
248 for (int j = 0; j < inputChannels; ++j) {
249 for (int k = 0; k < outputChannels; ++k)
250 o[k] += matrix_lo[k*inputChannels + j]*buf[j].lf + matrix_hi[k*inputChannels + j]*buf[j].hf;
251 }
252 o += outputChannels;
253 }
254}
255
256void QAmbisonicDecoder::processBuffer(QSpan<const float *> input, QSpan<short> output)
257{
258 std::array<const float *, 2> reverb = { nullptr, nullptr };
259 return processBufferWithReverb(input, reverb, output);
260}
261
262void QAmbisonicDecoder::processBufferWithReverb(QSpan<const float *> input,
263 QSpan<const float *, 2> reverb, QSpan<short> output)
264{
265 Q_ASSERT(outputChannels > 0);
266 Q_ASSERT(int(output.size()) % outputChannels == 0);
267 const int nSamples = int(output.size()) / outputChannels;
268
269 using QtMultimediaPrivate::drop;
270
271 if (simpleDecoderFactors) {
272 QSpan out = output;
273 for (int i = 0; i < nSamples; ++i) {
274 std::array<float, 4> o = {};
275 for (int k = 0; k < outputChannels; ++k) {
276 for (int j = 0; j < 4; ++j)
277 o[k] += simpleDecoderFactors[k*4 + j]*input[j][i];
278 }
279 if (reverb[0]) {
280 for (int k = 0; k < outputChannels; ++k) {
281 o[k] += reverb[0][i]*reverbFactors[2*k] + reverb[1][i]*reverbFactors[2*k+1];
282 }
283 }
284
285 for (int k = 0; k < outputChannels; ++k)
286 out[k] = static_cast<short>(o[k] * 32768.);
287 out = drop(out, outputChannels);
288 }
289 return;
290 }
291
292 Q_ASSERT(filters);
293
294 // qDebug() << "XXX" << inputChannels << outputChannels;
295 const float *matrix_hi = decoderData->hf[order - 1];
296 const float *matrix_lo = decoderData->lf[order - 1];
297 for (int i = 0; i < nSamples; ++i) {
298 QSpan out = output;
300 for (int j = 0; j < inputChannels; ++j)
301 buf[j] = filters[j].next(input[j][i]);
302 std::array<float, 32> o = {};
303 for (int j = 0; j < inputChannels; ++j) {
304 for (int k = 0; k < outputChannels; ++k)
305 o[k] += matrix_lo[k*inputChannels + j]*buf[j].lf + matrix_hi[k*inputChannels + j]*buf[j].hf;
306 }
307 if (reverb[0]) {
308 for (int k = 0; k < outputChannels; ++k) {
309 o[k] += reverb[0][i]*reverbFactors[2*k] + reverb[1][i]*reverbFactors[2*k+1];
310 }
311 }
312 for (int k = 0; k < outputChannels; ++k)
313 out[k] = static_cast<short>(o[k] * 32768.);
314 out = drop(out, outputChannels);
315 }
316}
317
318QT_END_NAMESPACE
QAmbisonicDecoderFilter()=default
void configure(float sampleRate, float cutoffFrequency=380)
void processBuffer(QSpan< const float * > input, QSpan< float > output)
QAmbisonicDecoder(AmbisonicOrder ambisonicOrder, const QAudioFormat &format)
static constexpr int maxAmbisonicChannels
void processBufferWithReverb(QSpan< const float * > input, QSpan< const float *, 2 > reverb, QSpan< short > output)
static constexpr QAmbisonicDecoderData decoderMap[]
constexpr float reverb_x_1[]
constexpr float reverb_x_0[]
#define M_PI
Definition qmath.h:201