Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qaudiohelpers.cpp
Go to the documentation of this file.
1
// Copyright (C) 2021 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
"qaudiohelpers_p.h"
5
6
#
include
<
QtMultimedia
/
private
/
qaudio_qspan_support_p
.
h
>
7
#
include
<
QtMultimedia
/
private
/
qmultimedia_ranges_p
.
h
>
8
#
include
<
QtMultimedia
/
private
/
qmultimedia_enum_to_string_converter_p
.
h
>
9
#
include
<
QtCore
/
qdebug
.
h
>
10
11
#
include
<
algorithm
>
12
#
include
<
limits
>
13
14
QT_BEGIN_NAMESPACE
15
16
namespace
QAudioHelperInternal
17
{
18
19
#
if
defined
(
Q_CC_GNU
)
20
#
define
QT_MM_RESTRICT
__restrict__
21
#
elif
defined
(
Q_CC_MSVC
)
22
#
define
QT_MM_RESTRICT
__restrict
23
#
else
24
#
define
QT_MM_RESTRICT
/*__restrict__*/
25
#
endif
26
27
namespace
{
28
29
template
<
class
T>
30
inline
T applyVolumeOnSample(T sample,
float
factor)
31
{
32
if
constexpr
(
std
::is_signed_v<T>) {
33
return
sample * factor;
34
}
else
{
35
using
SignedT = std::make_signed_t<T>;
36
// Unsigned samples are biased around 0x80/0x8000
37
constexpr
T offset = SignedT(1) << (
std
::numeric_limits<T>::digits - 1);
38
return
offset + (SignedT(sample - offset) * factor);
39
}
40
}
41
42
template
<
class
T>
43
void
adjustSamples(
float
factor,
44
const
void
*
QT_MM_RESTRICT
src,
45
void
*
QT_MM_RESTRICT
dst,
46
int
samples)
47
{
48
const
T *pSrc = (
const
T *)src;
49
T *pDst = (T *)dst;
50
51
for
(
int
i = 0; i < samples; i++)
52
pDst[i] = applyVolumeOnSample(pSrc[i], factor);
53
}
54
55
}
// namespace
56
57
void
qMultiplySamples
(
float
factor
,
58
const
QAudioFormat
&
format
,
59
const
void
*
src
,
60
void
*
dest
,
61
int
len
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
62
{
63
const
int
samplesCount
=
len
/
qMax
(1,
format
.
bytesPerSample
());
64
65
auto
clamp
= [](
float
arg
) {
66
float
realVolume
=
std
::
clamp
<
float
>(
arg
, 0.f, 1.f);
67
return
realVolume
;
68
};
69
70
switch
(
format
.
sampleFormat
()) {
71
case
QAudioFormat
::
UInt8
:
72
return
QAudioHelperInternal
::
adjustSamples
<
quint8
>(
clamp
(
factor
),
src
,
dest
,
samplesCount
);
73
case
QAudioFormat
::
Int16
:
74
return
QAudioHelperInternal
::
adjustSamples
<
qint16
>(
clamp
(
factor
),
src
,
dest
,
samplesCount
);
75
case
QAudioFormat
::
Int32
:
76
return
QAudioHelperInternal
::
adjustSamples
<
qint32
>(
clamp
(
factor
),
src
,
dest
,
samplesCount
);
77
case
QAudioFormat
::
Float
:
78
return
QAudioHelperInternal
::
adjustSamples
<
float
>(
factor
,
src
,
dest
,
samplesCount
);
79
default
:
80
Q_UNREACHABLE_RETURN
();
81
}
82
}
83
84
void
applyVolume
(
float
volume
,
85
const
QAudioFormat
&
format
,
86
QSpan
<
const
std
::
byte
>
source
,
87
QSpan
<
std
::
byte
>
destination
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
88
{
89
Q_ASSERT
(
source
.
size
() ==
destination
.
size
());
90
91
if
(
Q_LIKELY
(
volume
== 1.f)) {
92
if
(
source
.
data
() !=
destination
.
data
())
93
std
::
copy
(
source
.
begin
(),
source
.
end
(),
destination
.
begin
());
94
}
else
if
(
volume
== 0) {
95
std
::
byte
zero
=
96
format
.
sampleFormat
() ==
QAudioFormat
::
UInt8
?
std
::
byte
{ 0x80 } :
std
::
byte
{ 0 };
97
98
std
::
fill
(
destination
.
begin
(),
destination
.
begin
() +
source
.
size
(),
zero
);
99
}
else
{
100
QAudioHelperInternal
::
qMultiplySamples
(
volume
,
format
,
source
.
data
(),
destination
.
data
(),
101
source
.
size
());
102
}
103
}
104
105
namespace
{
106
107
template
<
NativeSampleFormat
Format
>
108
Q_ALWAYS_INLINE
constexpr
int32_t
toInt32
(
QSpan
<
const
std
::
byte
>
source
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
109
{
110
switch
(
Format
) {
111
case
NativeSampleFormat
::
uint8_t
: {
112
constexpr
int32_t
offset
= (1 << 7);
113
uint8_t
smp
=
uint8_t
(
source
.
front
());
114
int32_t
val
= (
int32_t
(
smp
) -
offset
) << 24;
115
return
val
;
116
}
117
case
NativeSampleFormat
::
int16_t
: {
118
union
CastUnion
{
119
std
::
int16_t
i16
;
120
std
::
byte
b
[2];
121
}
castUnion
= {};
122
123
std
::
copy_n
(
source
.
data
(), 2,
castUnion
.
b
);
124
125
int32_t
val
=
castUnion
.
i16
<< 16;
126
return
val
;
127
}
128
case
NativeSampleFormat
::
int24_t_3b
: {
129
union
CastUnion
{
130
std
::
int32_t
i32
;
131
std
::
byte
b
[4];
132
}
castUnion
= {};
133
134
std
::
copy_n
(
source
.
data
(), 3,
castUnion
.
b
);
135
136
int32_t
val
=
castUnion
.
i32
<< 8;
137
return
val
;
138
}
139
case
NativeSampleFormat
::
int24_t_4b_low
: {
140
union
CastUnion
{
141
std
::
int32_t
i32
;
142
std
::
byte
b
[4];
143
}
castUnion
= {};
144
145
std
::
copy_n
(
source
.
data
(), 4,
castUnion
.
b
);
146
int32_t
val
=
castUnion
.
i32
<< 8;
147
return
val
;
148
}
149
case
NativeSampleFormat
::
float32_t
: {
150
union
CastUnion
{
151
float
f32
;
152
std
::
byte
b
[4];
153
}
castUnion
= {};
154
155
std
::
copy_n
(
source
.
data
(), 4,
castUnion
.
b
);
156
constexpr
double
range
=
std
::
numeric_limits
<
int32_t
>::
max
();
157
int32_t
val
= (
castUnion
.
f32
*
range
);
158
return
val
;
159
}
160
case
NativeSampleFormat
::
int32_t
: {
161
union
CastUnion
{
162
std
::
int32_t
i32
;
163
std
::
byte
b
[4];
164
}
castUnion
= {};
165
166
std
::
copy_n
(
source
.
data
(), 4,
castUnion
.
b
);
167
return
castUnion
.
i32
;
168
}
169
170
default
:
171
Q_UNREACHABLE_RETURN
({});
172
}
173
}
174
175
template
<
NativeSampleFormat
Format
>
176
Q_ALWAYS_INLINE
constexpr
void
storeSampleWithFormat
(
QSpan
<
std
::
byte
>
destination
,
177
int32_t
value
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
178
{
179
switch
(
Format
) {
180
case
NativeSampleFormat
::
uint8_t
: {
181
value
=
value
>> 24;
182
constexpr
int32_t
offset
= 1 << 7;
183
uint8_t
sampleValue
=
value
+
offset
;
184
std
::
copy_n
(
reinterpret_cast
<
const
std
::
byte
*>(&
sampleValue
), 1,
destination
.
data
());
185
return
;
186
}
187
case
NativeSampleFormat
::
int16_t
: {
188
value
=
value
>> 16;
189
int16_t
sampleValue
=
value
;
190
std
::
copy_n
(
reinterpret_cast
<
const
std
::
byte
*>(&
sampleValue
), 2,
destination
.
data
());
191
return
;
192
}
193
194
case
NativeSampleFormat
::
int24_t_3b
: {
195
int32_t
sampleValue
=
value
>> 8;
196
std
::
copy_n
(
reinterpret_cast
<
const
std
::
byte
*>(&
sampleValue
), 3,
destination
.
data
());
197
return
;
198
}
199
200
case
NativeSampleFormat
::
int24_t_4b_low
: {
201
int32_t
sampleValue
= (
value
>> 8) & 0x00ffffff;
202
std
::
copy_n
(
reinterpret_cast
<
const
std
::
byte
*>(&
sampleValue
), 4,
destination
.
data
());
203
return
;
204
}
205
case
NativeSampleFormat
::
int32_t
: {
206
std
::
copy_n
(
reinterpret_cast
<
const
std
::
byte
*>(&
value
), 4,
destination
.
data
());
207
return
;
208
}
209
case
NativeSampleFormat
::
float32_t
: {
210
constexpr
double
normalizationFactor
= 1.0 /
std
::
numeric_limits
<
int32_t
>::
max
();
211
float
sampleValue
=
float
(
double
(
value
) *
normalizationFactor
);
212
std
::
copy_n
(
reinterpret_cast
<
const
std
::
byte
*>(&
sampleValue
), 4,
destination
.
data
());
213
return
;
214
}
215
default
:
216
Q_UNREACHABLE_RETURN
();
217
}
218
}
219
220
template
<
NativeSampleFormat
sourceFormat
,
NativeSampleFormat
destinationFormat
>
221
struct
WordConverter
222
{
223
void
operator
()(
QSpan
<
const
std
::
byte
>
source
,
QSpan
<
std
::
byte
>
destination
)
224
{
225
if
constexpr
(
sourceFormat
==
destinationFormat
) {
226
std
::
copy
(
source
.
begin
(),
source
.
end
(),
destination
.
begin
());
227
}
else
{
228
constexpr
size_t
bytesPerSampleSource
=
bytesPerSample
(
sourceFormat
);
229
constexpr
size_t
bytesPerSampleDest
=
bytesPerSample
(
destinationFormat
);
230
231
using
namespace
QtMultimediaPrivate
;
232
while
(!
source
.
isEmpty
()) {
233
if
(
size_t
(
source
.
size
()) >= 4 *
bytesPerSampleSource
) {
234
// unroll by 4.
235
// should help the compiler to vectorize, or at least avoid pipeline stalls.
236
237
auto
sourceSampleRange1
=
take
(
source
,
bytesPerSampleSource
);
238
source
=
drop
(
source
,
bytesPerSampleSource
);
239
auto
sourceSampleRange2
=
take
(
source
,
bytesPerSampleSource
);
240
source
=
drop
(
source
,
bytesPerSampleSource
);
241
auto
sourceSampleRange3
=
take
(
source
,
bytesPerSampleSource
);
242
source
=
drop
(
source
,
bytesPerSampleSource
);
243
auto
sourceSampleRange4
=
take
(
source
,
bytesPerSampleSource
);
244
source
=
drop
(
source
,
bytesPerSampleSource
);
245
246
int32_t
value1
=
toInt32
<
sourceFormat
>(
sourceSampleRange1
);
247
int32_t
value2
=
toInt32
<
sourceFormat
>(
sourceSampleRange2
);
248
int32_t
value3
=
toInt32
<
sourceFormat
>(
sourceSampleRange3
);
249
int32_t
value4
=
toInt32
<
sourceFormat
>(
sourceSampleRange4
);
250
251
auto
destSampleRange1
=
take
(
destination
,
bytesPerSampleDest
);
252
destination
=
drop
(
destination
,
bytesPerSampleDest
);
253
auto
destSampleRange2
=
take
(
destination
,
bytesPerSampleDest
);
254
destination
=
drop
(
destination
,
bytesPerSampleDest
);
255
auto
destSampleRange3
=
take
(
destination
,
bytesPerSampleDest
);
256
destination
=
drop
(
destination
,
bytesPerSampleDest
);
257
auto
destSampleRange4
=
take
(
destination
,
bytesPerSampleDest
);
258
destination
=
drop
(
destination
,
bytesPerSampleDest
);
259
260
storeSampleWithFormat
<
destinationFormat
>(
destSampleRange1
,
value1
);
261
storeSampleWithFormat
<
destinationFormat
>(
destSampleRange2
,
value2
);
262
storeSampleWithFormat
<
destinationFormat
>(
destSampleRange3
,
value3
);
263
storeSampleWithFormat
<
destinationFormat
>(
destSampleRange4
,
value4
);
264
}
else
{
265
auto
sourceSampleRange
=
take
(
source
,
bytesPerSampleSource
);
266
int32_t
value
=
toInt32
<
sourceFormat
>(
sourceSampleRange
);
267
268
auto
destSampleRange
=
take
(
destination
,
bytesPerSampleDest
);
269
storeSampleWithFormat
<
destinationFormat
>(
destSampleRange
,
value
);
270
271
source
=
drop
(
source
,
bytesPerSampleSource
);
272
destination
=
drop
(
destination
,
bytesPerSampleDest
);
273
}
274
}
275
}
276
}
277
};
278
279
template
<
NativeSampleFormat
destinationFormat
>
280
void
convertSampleFormatWithDestinationFormat
(
QSpan
<
const
std
::
byte
>
source
,
281
NativeSampleFormat
sourceFormat
,
282
QSpan
<
std
::
byte
>
destination
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
283
{
284
switch
(
sourceFormat
) {
285
case
NativeSampleFormat
::
uint8_t
:
286
return
WordConverter
<
NativeSampleFormat
::
uint8_t
,
destinationFormat
>()(
source
,
destination
);
287
case
NativeSampleFormat
::
int16_t
:
288
return
WordConverter
<
NativeSampleFormat
::
int16_t
,
destinationFormat
>()(
source
,
destination
);
289
case
NativeSampleFormat
::
int32_t
:
290
return
WordConverter
<
NativeSampleFormat
::
int32_t
,
destinationFormat
>()(
source
,
destination
);
291
case
NativeSampleFormat
::
int24_t_3b
:
292
return
WordConverter
<
NativeSampleFormat
::
int24_t_3b
,
destinationFormat
>()(
source
,
293
destination
);
294
case
NativeSampleFormat
::
int24_t_4b_low
:
295
return
WordConverter
<
NativeSampleFormat
::
int24_t_4b_low
,
destinationFormat
>()(
source
,
296
destination
);
297
case
NativeSampleFormat
::
float32_t
:
298
return
WordConverter
<
NativeSampleFormat
::
float32_t
,
destinationFormat
>()(
source
,
299
destination
);
300
default
:
301
Q_UNREACHABLE_RETURN
();
302
}
303
}
304
305
}
// namespace
306
307
void
convertSampleFormat
(
QSpan
<
const
std
::
byte
>
source
,
NativeSampleFormat
sourceFormat
,
308
QSpan
<
std
::
byte
>
destination
,
309
NativeSampleFormat
destinationFormat
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
310
{
311
using
namespace
QtMultimediaPrivate
;
312
313
size_t
bytesPerSampleSource
=
bytesPerSample
(
sourceFormat
);
314
size_t
bytesPerSampleDest
=
bytesPerSample
(
destinationFormat
);
315
316
Q_ASSERT
(
source
.
size
() *
bytesPerSampleDest
==
destination
.
size
() *
bytesPerSampleSource
);
317
318
switch
(
destinationFormat
) {
319
case
NativeSampleFormat
::
uint8_t
:
320
return
convertSampleFormatWithDestinationFormat
<
NativeSampleFormat
::
uint8_t
>(
321
source
,
sourceFormat
,
destination
);
322
case
NativeSampleFormat
::
int16_t
:
323
return
convertSampleFormatWithDestinationFormat
<
NativeSampleFormat
::
int16_t
>(
324
source
,
sourceFormat
,
destination
);
325
case
NativeSampleFormat
::
int32_t
:
326
return
convertSampleFormatWithDestinationFormat
<
NativeSampleFormat
::
int32_t
>(
327
source
,
sourceFormat
,
destination
);
328
case
NativeSampleFormat
::
int24_t_3b
:
329
return
convertSampleFormatWithDestinationFormat
<
NativeSampleFormat
::
int24_t_3b
>(
330
source
,
sourceFormat
,
destination
);
331
case
NativeSampleFormat
::
int24_t_4b_low
:
332
return
convertSampleFormatWithDestinationFormat
<
NativeSampleFormat
::
int24_t_4b_low
>(
333
source
,
sourceFormat
,
destination
);
334
case
NativeSampleFormat
::
float32_t
:
335
return
convertSampleFormatWithDestinationFormat
<
NativeSampleFormat
::
float32_t
>(
336
source
,
sourceFormat
,
destination
);
337
default
:
338
Q_UNREACHABLE_RETURN
();
339
}
340
}
341
342
NativeSampleFormat
bestNativeSampleFormat
(
const
QAudioFormat
&
fmt
,
343
QSpan
<
const
NativeSampleFormat
>
supportedNativeFormats
)
344
{
345
Q_ASSERT
(!
supportedNativeFormats
.
empty
());
346
347
auto
resolveCandidate
= [&](
QSpan
<
const
NativeSampleFormat
>
candidates
) {
348
auto
it
=
std
::
find_first_of
(
candidates
.
begin
(),
candidates
.
end
(),
349
supportedNativeFormats
.
begin
(),
supportedNativeFormats
.
end
());
350
351
if
(
it
!=
candidates
.
end
())
352
return
*
it
;
353
354
qFatal
() <<
"No candidate for conversion found. That should not happen"
;
355
Q_UNREACHABLE_RETURN
(
NativeSampleFormat
::
float32_t
);
356
};
357
358
// heuristics:
359
// select the best format that does not involve precision
360
// if that does not yield a format, find a format with the least loss of precision.
361
switch
(
fmt
.
sampleFormat
()) {
362
case
QAudioFormat
::
SampleFormat
::
UInt8
: {
363
constexpr
auto
candidates
=
std
::
array
{
364
NativeSampleFormat
::
uint8_t
,
NativeSampleFormat
::
int16_t
,
365
NativeSampleFormat
::
int24_t_3b
,
NativeSampleFormat
::
int24_t_4b_low
,
366
NativeSampleFormat
::
int32_t
,
NativeSampleFormat
::
float32_t
,
367
};
368
369
return
resolveCandidate
(
candidates
);
370
}
371
case
QAudioFormat
::
SampleFormat
::
Int16
: {
372
constexpr
auto
candidates
=
std
::
array
{
373
NativeSampleFormat
::
int16_t
,
NativeSampleFormat
::
int24_t_3b
,
374
NativeSampleFormat
::
int24_t_4b_low
,
NativeSampleFormat
::
int32_t
,
375
NativeSampleFormat
::
float32_t
,
NativeSampleFormat
::
uint8_t
,
376
};
377
378
return
resolveCandidate
(
candidates
);
379
}
380
case
QAudioFormat
::
SampleFormat
::
Int32
: {
381
constexpr
auto
candidates
=
std
::
array
{
382
NativeSampleFormat
::
int32_t
,
NativeSampleFormat
::
float32_t
,
383
NativeSampleFormat
::
int24_t_3b
,
NativeSampleFormat
::
int24_t_4b_low
,
384
NativeSampleFormat
::
int16_t
,
NativeSampleFormat
::
uint8_t
,
385
386
};
387
388
return
resolveCandidate
(
candidates
);
389
}
390
case
QAudioFormat
::
SampleFormat
::
Float
: {
391
constexpr
auto
candidates
=
std
::
array
{
392
NativeSampleFormat
::
float32_t
,
NativeSampleFormat
::
int24_t_3b
,
393
NativeSampleFormat
::
int24_t_4b_low
,
NativeSampleFormat
::
int32_t
,
394
NativeSampleFormat
::
int16_t
,
NativeSampleFormat
::
uint8_t
,
395
};
396
397
return
resolveCandidate
(
candidates
);
398
}
399
case
QAudioFormat
::
SampleFormat
::
Unknown
:
400
case
QAudioFormat
::
SampleFormat
::
NSampleFormats
: {
401
qFatal
() <<
"bestNativeSampleFormat called with invalid argument"
<<
fmt
.
sampleFormat
();
402
Q_UNREACHABLE_RETURN
(
NativeSampleFormat
::
int32_t
);
403
}
404
405
default
: {
406
Q_UNREACHABLE_RETURN
(
NativeSampleFormat
::
int32_t
);
407
}
408
}
409
}
410
411
NativeSampleFormat
toNativeSampleFormat
(
QAudioFormat
::
SampleFormat
fmt
)
412
{
413
switch
(
fmt
) {
414
case
QAudioFormat
::
SampleFormat
::
Float
:
415
return
NativeSampleFormat
::
float32_t
;
416
case
QAudioFormat
::
SampleFormat
::
UInt8
:
417
return
NativeSampleFormat
::
uint8_t
;
418
case
QAudioFormat
::
SampleFormat
::
Int16
:
419
return
NativeSampleFormat
::
int16_t
;
420
case
QAudioFormat
::
SampleFormat
::
Int32
:
421
return
NativeSampleFormat
::
int32_t
;
422
default
:
423
Q_UNREACHABLE_RETURN
({});
424
}
425
}
426
427
QAudioFormat
::
SampleFormat
bestSampleFormat
(
const
NativeSampleFormat
fmt
)
428
{
429
switch
(
fmt
) {
430
case
NativeSampleFormat
::
uint8_t
:
431
return
QAudioFormat
::
SampleFormat
::
UInt8
;
432
case
NativeSampleFormat
::
int16_t
:
433
return
QAudioFormat
::
SampleFormat
::
Int16
;
434
case
NativeSampleFormat
::
int32_t
:
435
case
NativeSampleFormat
::
int24_t_3b
:
436
case
NativeSampleFormat
::
int24_t_4b_low
:
437
return
QAudioFormat
::
SampleFormat
::
Int32
;
438
case
NativeSampleFormat
::
float32_t
:
439
return
QAudioFormat
::
SampleFormat
::
Float
;
440
default
:
441
Q_UNREACHABLE_RETURN
({});
442
}
443
}
444
445
std
::
optional
<
float
>
sanitizeVolume
(
float
volume
,
float
lastValue
)
446
{
447
constexpr
float
epsilon
= 1.f / (1 << 22);
// good enough for 22bit resolution
448
449
// multiplication is optimized for 0 and 1
450
// values which are sufficiently close to 0 and 1 can be considered as 0 or 1
451
if
(
volume
<
epsilon
)
452
volume
= 0;
453
else
if
(
volume
> (1.f -
epsilon
))
454
volume
= 1.f;
455
456
// similar to qFuzzyCompare, but with better heuristics for volume
457
if
(
std
::
abs
(
volume
-
lastValue
) <
epsilon
)
458
return
std
::
nullopt
;
459
460
return
volume
;
461
}
462
463
void
fillSilence
(
QSpan
<
std
::
byte
>
buffer
,
464
NativeSampleFormat
sampleFormat
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
465
{
466
namespace
ranges
=
QtMultimediaPrivate
::
ranges
;
467
ranges
::
fill
(
buffer
,
468
sampleFormat
==
NativeSampleFormat
::
uint8_t
?
std
::
byte
{ 0x80 } :
std
::
byte
{ 0 });
469
}
470
471
void
fillSilence
(
QSpan
<
std
::
byte
>
buffer
,
QAudioFormat
fmt
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
472
{
473
fillSilence
(
buffer
,
toNativeSampleFormat
(
fmt
.
sampleFormat
()));
474
}
475
476
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
477
478
namespace
{
479
480
struct
CatmullRomBasis
481
{
482
explicit
CatmullRomBasis
(
float
t
)
483
{
484
Q_ASSERT
(
t
>= 0.0f &&
t
< 1.0f);
485
const
float
t2
=
t
*
t
;
486
const
float
t3
=
t2
*
t
;
487
c0
= -0.5f *
t3
+
t2
- 0.5f *
t
;
488
c1
= 1.5f *
t3
- 2.5f *
t2
+ 1.0f;
489
c2
= -1.5f *
t3
+ 2.0f *
t2
+ 0.5f *
t
;
490
c3
= 0.5f *
t3
- 0.5f *
t2
;
491
}
492
493
float
interpolate
(
float
pm1
,
float
p0
,
float
p1
,
float
p2
)
const
494
{
495
return
(
c0
*
pm1
+
c1
*
p0
) + (
c2
*
p1
+
c3
*
p2
);
496
}
497
498
private
:
499
float
c0
,
c1
,
c2
,
c3
;
500
};
501
502
}
// namespace
503
504
QByteArray
resampleAudioCatmullRom
(
QSpan
<
const
float
>
input
,
int
nChannels
,
int
inputRate
,
505
int
outputRate
)
506
{
507
Q_ASSERT
(
nChannels
>= 1);
508
Q_ASSERT
(
inputRate
> 0);
509
Q_ASSERT
(
outputRate
> 0);
510
511
if
(
input
.
isEmpty
() ||
nChannels
<= 0)
512
return
{};
513
514
if
(
inputRate
==
outputRate
) {
515
return
QByteArray
(
reinterpret_cast
<
const
char
*>(
input
.
data
()),
516
qsizetype
(
input
.
size
()) *
qsizetype
(
sizeof
(
float
)));
517
}
518
519
const
qsizetype
inputFrames
=
input
.
size
() /
nChannels
;
520
if
(
inputFrames
== 0)
521
return
{};
522
523
const
double
resampleRatio
=
double
(
outputRate
) /
double
(
inputRate
);
524
525
// Number of output frames
526
const
qsizetype
outputFrames
=
std
::
lround
(
inputFrames
*
resampleRatio
);
527
if
(
outputFrames
<= 0)
528
return
{};
529
530
QByteArray
out
;
531
out
.
resizeForOverwrite
(
outputFrames
*
nChannels
*
qsizetype
(
sizeof
(
float
)));
532
QSpan
<
float
>
dst
(
reinterpret_cast
<
float
*>(
out
.
data
()),
outputFrames
*
nChannels
);
533
534
using
namespace
QtMultimediaPrivate
;
535
536
// Catmull-Rom: needs p0,p1,p2,p3 where output samples between p1 and p2
537
// p[i] = clamp(i, 0, inputFrames-1)
538
const
float
increment
= 1.f /
float
(
resampleRatio
);
539
540
qsizetype
iPos
= 0;
541
float
fPos
= 0.0f;
542
543
for
(
qsizetype
outFrame
= 0;
outFrame
<
outputFrames
; ++
outFrame
) {
544
const
qsizetype
i1
=
iPos
;
545
const
float
t
=
fPos
;
546
547
// Clamp frame indices
548
const
qsizetype
i0
=
std
::
max
(
qsizetype
(0),
i1
- 1);
549
const
qsizetype
i2
=
std
::
min
(
i1
+ 1,
inputFrames
- 1);
550
const
qsizetype
i3
=
std
::
min
(
i1
+ 2,
inputFrames
- 1);
551
552
const
CatmullRomBasis
basis
(
t
);
553
554
auto
frameOut
=
take
(
dst
,
nChannels
);
555
for
(
int
ch
= 0;
ch
<
nChannels
; ++
ch
) {
556
const
float
pm1
=
input
[
i0
*
nChannels
+
ch
];
557
const
float
p0
=
input
[
i1
*
nChannels
+
ch
];
558
const
float
p1
=
input
[
i2
*
nChannels
+
ch
];
559
const
float
p2
=
input
[
i3
*
nChannels
+
ch
];
560
frameOut
[
ch
] =
basis
.
interpolate
(
pm1
,
p0
,
p1
,
p2
);
561
}
562
dst
=
drop
(
dst
,
nChannels
);
563
564
fPos
+=
increment
;
565
while
(
fPos
>= 1.0f) {
566
fPos
-= 1.0f;
567
iPos
+= 1;
568
}
569
}
570
571
return
out
;
572
}
573
574
CatmullRomInterpolator
::
CatmullRomInterpolator
(
int
nChannels
,
int
inputRate
,
int
outputRate
)
575
:
m_nChannels
(
nChannels
),
m_ratio
(
float
(
inputRate
) /
float
(
outputRate
))
576
{
577
Q_ASSERT
(
nChannels
>= 1);
578
Q_ASSERT
(
inputRate
> 0);
579
Q_ASSERT
(
outputRate
> 0);
580
581
m_history
.
assign
(3 *
nChannels
, 0.0f);
582
}
583
584
void
CatmullRomInterpolator
::
reset
()
585
{
586
m_iPos
= 0;
587
m_fPos
= 0.0f;
588
m_inputFramesConsumed
= 0;
589
namespace
ranges
=
QtMultimediaPrivate
::
ranges
;
590
ranges
::
fill
(
m_history
, 0.0f);
591
}
592
593
void
CatmullRomInterpolator
::
advancePosition
()
594
{
595
m_fPos
+=
m_ratio
;
596
while
(
m_fPos
>= 1.0f) {
597
m_fPos
-= 1.0f;
598
m_iPos
+= 1;
599
}
600
}
601
602
auto
CatmullRomInterpolator
::
process
(
QSpan
<
const
float
>
input
,
603
QSpan
<
float
>
output
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
604
->
ResampleResult
605
{
606
using
namespace
QtMultimediaPrivate
;
607
608
Q_PRESUME
(
m_nChannels
>= 1);
609
const
qsizetype
nChannels
=
m_nChannels
;
610
const
qsizetype
inputFrames
=
input
.
size
() /
nChannels
;
611
const
qsizetype
maxOutputFrames
=
output
.
size
() /
nChannels
;
612
613
// Flush mode: empty input signals end-of-stream
614
if
(
inputFrames
== 0) {
615
qsizetype
outputFramePos
= 0;
616
const
qsizetype
lastAvailableFrame
=
m_inputFramesConsumed
- 1;
617
618
while
(
outputFramePos
<
maxOutputFrames
) {
619
if
(
m_iPos
>
lastAvailableFrame
)
620
break
;
621
622
const
qsizetype
pm1FrameIdx
=
std
::
max
(
qsizetype
(0),
m_iPos
- 1);
623
const
qsizetype
p1FrameIdx
=
std
::
min
(
m_iPos
+ 1,
lastAvailableFrame
);
624
const
qsizetype
p2FrameIdx
=
std
::
min
(
m_iPos
+ 2,
lastAvailableFrame
);
625
626
const
CatmullRomBasis
basis
(
m_fPos
);
627
628
for
(
qsizetype
ch
= 0;
ch
<
nChannels
; ++
ch
) {
629
auto
fetch
= [&](
qsizetype
absoluteFrameIdx
) ->
float
{
630
if
(
absoluteFrameIdx
<
m_inputFramesConsumed
) {
631
const
qsizetype
historySampleIdx
=
632
absoluteFrameIdx
- (
m_inputFramesConsumed
- 3);
633
if
(
historySampleIdx
>= 0)
634
return
m_history
[
historySampleIdx
*
nChannels
+
ch
];
635
return
m_history
[
ch
];
// clamp to oldest history
636
}
637
return
m_history
[
ch
];
// beyond available data, clamp
638
};
639
640
const
float
pm1
=
fetch
(
pm1FrameIdx
);
641
const
float
p0
=
fetch
(
m_iPos
);
642
const
float
p1
=
fetch
(
p1FrameIdx
);
643
const
float
p2
=
fetch
(
p2FrameIdx
);
644
645
output
[
outputFramePos
*
nChannels
+
ch
] =
646
basis
.
interpolate
(
pm1
,
p0
,
p1
,
p2
);
647
}
648
649
advancePosition
();
650
++
outputFramePos
;
651
}
652
653
return
ResampleResult
{
654
input
,
655
take
(
output
,
outputFramePos
*
nChannels
),
656
};
657
}
658
659
qsizetype
outputFramePos
= 0;
660
qsizetype
maxConsumedFrames
= 0;
661
662
while
(
outputFramePos
<
maxOutputFrames
) {
663
if
(
m_iPos
+ 2 >=
m_inputFramesConsumed
+
inputFrames
)
664
break
;
665
666
// Clamp frame indices relative to available data
667
const
qsizetype
pm1FrameIdx
=
std
::
max
(
qsizetype
(0),
m_iPos
- 1);
668
const
qsizetype
p1FrameIdx
=
669
std
::
min
(
m_iPos
+ 1,
m_inputFramesConsumed
+
inputFrames
- 1);
670
const
qsizetype
p2FrameIdx
=
671
std
::
min
(
m_iPos
+ 2,
m_inputFramesConsumed
+
inputFrames
- 1);
672
673
const
CatmullRomBasis
basis
(
m_fPos
);
674
675
for
(
qsizetype
ch
= 0;
ch
<
nChannels
; ++
ch
) {
676
auto
fetch
= [&](
qsizetype
absoluteFrameIdx
) ->
float
{
677
if
(
absoluteFrameIdx
>=
m_inputFramesConsumed
)
678
return
input
[(
absoluteFrameIdx
-
m_inputFramesConsumed
) *
nChannels
+
ch
];
679
const
qsizetype
historySampleIdx
=
680
absoluteFrameIdx
- (
m_inputFramesConsumed
- 3);
681
if
(
historySampleIdx
>= 0)
682
return
m_history
[
historySampleIdx
*
nChannels
+
ch
];
683
return
m_history
[
ch
];
// clamp to oldest history (stream start)
684
};
685
686
const
float
pm1
=
fetch
(
pm1FrameIdx
);
687
const
float
p0
=
fetch
(
m_iPos
);
688
const
float
p1
=
fetch
(
p1FrameIdx
);
689
const
float
p2
=
fetch
(
p2FrameIdx
);
690
691
output
[
outputFramePos
*
nChannels
+
ch
] =
692
basis
.
interpolate
(
pm1
,
p0
,
p1
,
p2
);
693
}
694
695
maxConsumedFrames
=
696
std
::
max
(
maxConsumedFrames
,
697
m_iPos
+ 3 -
m_inputFramesConsumed
);
698
advancePosition
();
699
++
outputFramePos
;
700
}
701
702
// Update history with last 3 frames of consumed portion
703
qsizetype
framesToCache
=
std
::
min
(
maxConsumedFrames
,
inputFrames
);
704
// If the main loop produced no output but there is input (m_iPos lags
705
// behind m_inputFramesConsumed at a chunk boundary), consume all input into history so
706
// the caller makes progress. The flush path interpolates using history.
707
if
(
framesToCache
== 0 &&
inputFrames
> 0)
708
framesToCache
=
inputFrames
;
709
namespace
ranges
=
QtMultimediaPrivate
::
ranges
;
710
if
(
framesToCache
>= 3) {
711
const
qsizetype
last3FramesSampleOffset
= (
framesToCache
- 3) *
nChannels
;
712
ranges
::
copy
(
take
(
drop
(
input
,
last3FramesSampleOffset
), 3 *
nChannels
),
713
m_history
.
data
());
714
}
else
if
(
framesToCache
> 0) {
715
const
qsizetype
newCacheSampleCount
=
framesToCache
*
nChannels
;
716
const
qsizetype
retainedSampleCount
= (3 -
framesToCache
) *
nChannels
;
717
auto
history
=
QSpan
(
m_history
);
718
ranges
::
copy
(
take
(
drop
(
history
,
newCacheSampleCount
),
retainedSampleCount
),
719
history
.
begin
());
720
ranges
::
copy
(
take
(
input
,
newCacheSampleCount
),
721
drop
(
history
,
retainedSampleCount
).
begin
());
722
}
723
724
m_inputFramesConsumed
+=
framesToCache
;
725
726
const
qsizetype
unconsumedInputFrames
=
inputFrames
-
framesToCache
;
727
return
ResampleResult
{
728
take
(
drop
(
input
,
framesToCache
*
nChannels
),
unconsumedInputFrames
*
nChannels
),
729
take
(
output
,
outputFramePos
*
nChannels
),
730
};
731
}
732
733
void
upmixMonoToStereo
(
QSpan
<
float
>
out
,
QSpan
<
const
float
>
in
,
734
UpmixScaling
scaling
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
735
{
736
constexpr
float
kEqualPowerGain
(
M_SQRT1_2
);
// 1/sqrt(2)
737
Q_ASSERT
(
out
.
size
() ==
in
.
size
() * 2);
738
739
const
qsizetype
frames
=
in
.
size
();
740
const
float
gain
= (
scaling
==
UpmixScaling
::
EqualPower
) ?
kEqualPowerGain
: 1.0f;
741
742
for
(
qsizetype
i
= 0;
i
<
frames
; ++
i
) {
743
const
float
s
=
in
[
i
] *
gain
;
744
out
[
i
* 2 + 0] =
s
;
745
out
[
i
* 2 + 1] =
s
;
746
}
747
}
748
749
QByteArray
upmixMonoToStereo
(
QSpan
<
const
float
>
input
,
UpmixScaling
scaling
)
750
{
751
if
(
input
.
empty
())
752
return
{};
753
754
const
qsizetype
frames
=
input
.
size
();
755
QByteArray
out
{
756
2 *
frames
*
qsizetype
(
sizeof
(
float
)),
757
Qt
::
Initialization
::
Uninitialized
,
758
};
759
760
upmixMonoToStereo
(
QSpan
<
float
>(
reinterpret_cast
<
float
*>(
out
.
data
()), 2 *
frames
),
761
input
,
scaling
);
762
return
out
;
763
}
764
765
void
downmixStereoToMono
(
QSpan
<
float
>
out
,
QSpan
<
const
float
>
in
,
766
DownmixScaling
scaling
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
767
{
768
constexpr
float
kEqualPowerGain
(
M_SQRT1_2
);
// 1/sqrt(2)
769
770
Q_ASSERT
(
in
.
size
() % 2 == 0);
771
Q_ASSERT
(
out
.
size
() ==
in
.
size
() / 2);
772
773
const
qsizetype
frames
=
in
.
size
() / 2;
774
const
float
gain
= (
scaling
==
DownmixScaling
::
KeepPower
) ?
kEqualPowerGain
: 0.5f;
775
776
for
(
qsizetype
i
= 0;
i
<
frames
; ++
i
)
777
out
[
i
] = (
in
[
i
* 2 + 0] +
in
[
i
* 2 + 1]) *
gain
;
778
}
779
780
QByteArray
downmixStereoToMono
(
QSpan
<
const
float
>
input
,
DownmixScaling
scaling
)
781
{
782
if
(
input
.
empty
())
783
return
{};
784
785
Q_ASSERT
(
input
.
size
() % 2 == 0);
786
787
const
qsizetype
frames
=
input
.
size
() / 2;
788
QByteArray
out
{
789
frames
*
qsizetype
(
sizeof
(
float
)),
790
Qt
::
Initialization
::
Uninitialized
,
791
};
792
793
downmixStereoToMono
(
QSpan
<
float
>(
reinterpret_cast
<
float
*>(
out
.
data
()),
frames
),
794
input
,
scaling
);
795
return
out
;
796
}
797
798
}
// namespace QAudioHelperInternal
799
800
#
undef
QT_MM_RESTRICT
801
802
// clang-format off
803
QT_MM_MAKE_STRING_RESOLVER
(
QAudioHelperInternal
::
NativeSampleFormat
,
QtMultimediaPrivate
::
EnumName
,
804
(QAudioHelperInternal::NativeSampleFormat::uint8_t,
"uint8_t"
)
805
(QAudioHelperInternal::NativeSampleFormat::int16_t,
"int16_t"
)
806
(QAudioHelperInternal::NativeSampleFormat::int32_t,
"int32_t"
)
807
(QAudioHelperInternal::NativeSampleFormat::int24_t_3b,
"int24_t_3b"
)
808
(QAudioHelperInternal::NativeSampleFormat::int24_t_4b_low,
"int24_t_4b_low"
)
809
(QAudioHelperInternal::NativeSampleFormat::float32_t,
"float32_t"
)
810
);
811
// clang-format on
812
813
QT_MM_DEFINE_QDEBUG_ENUM
(
QAudioHelperInternal
::
NativeSampleFormat
);
814
815
QT_END_NAMESPACE
QAudioHelperInternal
Definition
qaudiohelpers.cpp:17
QAudioHelperInternal::NativeSampleFormat
NativeSampleFormat
Definition
qaudiohelpers_p.h:40
std
[33]
Definition
src_corelib_tools_qhash.cpp:421
QT_MM_MAKE_STRING_RESOLVER
QT_MM_MAKE_STRING_RESOLVER(QAudioHelperInternal::NativeSampleFormat, QtMultimediaPrivate::EnumName,(QAudioHelperInternal::NativeSampleFormat::uint8_t, "uint8_t")(QAudioHelperInternal::NativeSampleFormat::int16_t, "int16_t")(QAudioHelperInternal::NativeSampleFormat::int32_t, "int32_t")(QAudioHelperInternal::NativeSampleFormat::int24_t_3b, "int24_t_3b")(QAudioHelperInternal::NativeSampleFormat::int24_t_4b_low, "int24_t_4b_low")(QAudioHelperInternal::NativeSampleFormat::float32_t, "float32_t"))
QT_MM_RESTRICT
#define QT_MM_RESTRICT
Definition
qaudiohelpers.cpp:24
QT_MM_DEFINE_QDEBUG_ENUM
QT_MM_DEFINE_QDEBUG_ENUM(QAudioHelperInternal::NativeSampleFormat)
qtmultimedia
src
multimedia
audio
qaudiohelpers.cpp
Generated on
for Qt by
1.18.0