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 <QtCore/qdebug.h>
7#include <QtMultimedia/private/qaudio_qspan_support_p.h>
8#include <QtMultimedia/private/qmultimedia_enum_to_string_converter_p.h>
9
10#include <limits>
11
12QT_BEGIN_NAMESPACE
13
14namespace QAudioHelperInternal
15{
16
17#if defined(Q_CC_GNU)
18# define QT_MM_RESTRICT __restrict__
19#elif defined(Q_CC_MSVC)
20# define QT_MM_RESTRICT __restrict
21#else
22# define QT_MM_RESTRICT /*__restrict__*/
23#endif
24
25namespace {
26
27template<class T>
28inline T applyVolumeOnSample(T sample, float factor)
29{
30 if constexpr (std::is_signed_v<T>) {
31 return sample * factor;
32 } else {
33 using SignedT = std::make_signed_t<T>;
34 // Unsigned samples are biased around 0x80/0x8000
35 constexpr T offset = SignedT(1) << (std::numeric_limits<T>::digits - 1);
36 return offset + (SignedT(sample - offset) * factor);
37 }
38}
39
40template<class T>
41void adjustSamples(float factor,
42 const void *QT_MM_RESTRICT src,
43 void *QT_MM_RESTRICT dst,
44 int samples)
45{
46 const T *pSrc = (const T *)src;
47 T *pDst = (T *)dst;
48
49 for (int i = 0; i < samples; i++)
50 pDst[i] = applyVolumeOnSample(pSrc[i], factor);
51}
52
53} // namespace
54
55void qMultiplySamples(float factor,
56 const QAudioFormat &format,
57 const void *src,
58 void *dest,
59 int len) noexcept QT_MM_NONBLOCKING
60{
61 const int samplesCount = len / qMax(a: 1, b: format.bytesPerSample());
62
63 auto clamp = [](float arg) {
64 float realVolume = std::clamp<float>(val: arg, lo: 0.f, hi: 1.f);
65 return realVolume;
66 };
67
68 switch (format.sampleFormat()) {
69 case QAudioFormat::UInt8:
70 return QAudioHelperInternal::adjustSamples<quint8>(factor: clamp(factor), src, dst: dest, samples: samplesCount);
71 case QAudioFormat::Int16:
72 return QAudioHelperInternal::adjustSamples<qint16>(factor: clamp(factor), src, dst: dest, samples: samplesCount);
73 case QAudioFormat::Int32:
74 return QAudioHelperInternal::adjustSamples<qint32>(factor: clamp(factor), src, dst: dest, samples: samplesCount);
75 case QAudioFormat::Float:
76 return QAudioHelperInternal::adjustSamples<float>(factor, src, dst: dest, samples: samplesCount);
77 default:
78 Q_UNREACHABLE_RETURN();
79 }
80}
81
82void applyVolume(float volume,
83 const QAudioFormat &format,
84 QSpan<const std::byte> source,
85 QSpan<std::byte> destination) noexcept QT_MM_NONBLOCKING
86{
87 Q_ASSERT(source.size() == destination.size());
88
89 if (Q_LIKELY(volume == 1.f)) {
90 if (source.data() != destination.data())
91 std::copy(first: source.begin(), last: source.end(), result: destination.begin());
92 } else if (volume == 0) {
93 std::byte zero =
94 format.sampleFormat() == QAudioFormat::UInt8 ? std::byte{ 0x80 } : std::byte{ 0 };
95
96 std::fill(first: destination.begin(), last: destination.begin() + source.size(), value: zero);
97 } else {
98 QAudioHelperInternal::qMultiplySamples(factor: volume, format, src: source.data(), dest: destination.data(),
99 len: source.size());
100 }
101}
102
103namespace {
104
105template <NativeSampleFormat Format>
106Q_ALWAYS_INLINE constexpr int32_t toInt32(QSpan<const std::byte> source) noexcept QT_MM_NONBLOCKING
107{
108 switch (Format) {
109 case NativeSampleFormat::uint8_t: {
110 constexpr int32_t offset = (1 << 7);
111 uint8_t smp = uint8_t(source.front());
112 int32_t val = (int32_t(smp) - offset) << 24;
113 return val;
114 }
115 case NativeSampleFormat::int16_t: {
116 union CastUnion {
117 std::int16_t i16;
118 std::byte b[2];
119 } castUnion = {};
120
121 std::copy_n(source.data(), 2, castUnion.b);
122
123 int32_t val = castUnion.i16 << 16;
124 return val;
125 }
126 case NativeSampleFormat::int24_t_3b: {
127 union CastUnion {
128 std::int32_t i32;
129 std::byte b[4];
130 } castUnion = {};
131
132 std::copy_n(source.data(), 3, castUnion.b);
133
134 int32_t val = castUnion.i32 << 8;
135 return val;
136 }
137 case NativeSampleFormat::int24_t_4b_low: {
138 union CastUnion {
139 std::int32_t i32;
140 std::byte b[4];
141 } castUnion = {};
142
143 std::copy_n(source.data(), 4, castUnion.b);
144 int32_t val = castUnion.i32 << 8;
145 return val;
146 }
147 case NativeSampleFormat::float32_t: {
148 union CastUnion {
149 float f32;
150 std::byte b[4];
151 } castUnion = {};
152
153 std::copy_n(source.data(), 4, castUnion.b);
154 constexpr double range = std::numeric_limits<int32_t>::max();
155 int32_t val = (castUnion.f32 * range);
156 return val;
157 }
158 case NativeSampleFormat::int32_t: {
159 union CastUnion {
160 std::int32_t i32;
161 std::byte b[4];
162 } castUnion = {};
163
164 std::copy_n(source.data(), 4, castUnion.b);
165 return castUnion.i32;
166 }
167
168 default:
169 Q_UNREACHABLE_RETURN({});
170 }
171}
172
173template <NativeSampleFormat Format>
174Q_ALWAYS_INLINE constexpr void storeSampleWithFormat(QSpan<std::byte> destination,
175 int32_t value) noexcept QT_MM_NONBLOCKING
176{
177 switch (Format) {
178 case NativeSampleFormat::uint8_t: {
179 value = value >> 24;
180 constexpr int32_t offset = 1 << 7;
181 uint8_t sampleValue = value + offset;
182 std::copy_n(first: reinterpret_cast<const std::byte *>(&sampleValue), n: 1, result: destination.data());
183 return;
184 }
185 case NativeSampleFormat::int16_t: {
186 value = value >> 16;
187 int16_t sampleValue = value;
188 std::copy_n(first: reinterpret_cast<const std::byte *>(&sampleValue), n: 2, result: destination.data());
189 return;
190 }
191
192 case NativeSampleFormat::int24_t_3b: {
193 int32_t sampleValue = value >> 8;
194 std::copy_n(first: reinterpret_cast<const std::byte *>(&sampleValue), n: 3, result: destination.data());
195 return;
196 }
197
198 case NativeSampleFormat::int24_t_4b_low: {
199 int32_t sampleValue = (value >> 8) & 0x00ffffff;
200 std::copy_n(first: reinterpret_cast<const std::byte *>(&sampleValue), n: 4, result: destination.data());
201 return;
202 }
203 case NativeSampleFormat::int32_t: {
204 std::copy_n(first: reinterpret_cast<const std::byte *>(&value), n: 4, result: destination.data());
205 return;
206 }
207 case NativeSampleFormat::float32_t: {
208 constexpr double normalizationFactor = 1.0 / std::numeric_limits<int32_t>::max();
209 float sampleValue = float(double(value) * normalizationFactor);
210 std::copy_n(first: reinterpret_cast<const std::byte *>(&sampleValue), n: 4, result: destination.data());
211 return;
212 }
213 default:
214 Q_UNREACHABLE_RETURN();
215 }
216}
217
218template <NativeSampleFormat sourceFormat, NativeSampleFormat destinationFormat>
219struct WordConverter
220{
221 void operator()(QSpan<const std::byte> source, QSpan<std::byte> destination)
222 {
223 if constexpr (sourceFormat == destinationFormat) {
224 std::copy(first: source.begin(), last: source.end(), result: destination.begin());
225 } else {
226 constexpr size_t bytesPerSampleSource = bytesPerSample(fmt: sourceFormat);
227 constexpr size_t bytesPerSampleDest = bytesPerSample(fmt: destinationFormat);
228
229 using namespace QtMultimediaPrivate;
230 while (!source.isEmpty()) {
231 if (size_t(source.size()) >= 4 * bytesPerSampleSource) {
232 // unroll by 4.
233 // should help the compiler to vectorize, or at least avoid pipeline stalls.
234
235 auto sourceSampleRange1 = take(span: source, n: bytesPerSampleSource);
236 source = drop(span: source, n: bytesPerSampleSource);
237 auto sourceSampleRange2 = take(span: source, n: bytesPerSampleSource);
238 source = drop(span: source, n: bytesPerSampleSource);
239 auto sourceSampleRange3 = take(span: source, n: bytesPerSampleSource);
240 source = drop(span: source, n: bytesPerSampleSource);
241 auto sourceSampleRange4 = take(span: source, n: bytesPerSampleSource);
242 source = drop(span: source, n: bytesPerSampleSource);
243
244 int32_t value1 = toInt32<sourceFormat>(sourceSampleRange1);
245 int32_t value2 = toInt32<sourceFormat>(sourceSampleRange2);
246 int32_t value3 = toInt32<sourceFormat>(sourceSampleRange3);
247 int32_t value4 = toInt32<sourceFormat>(sourceSampleRange4);
248
249 auto destSampleRange1 = take(span: destination, n: bytesPerSampleDest);
250 destination = drop(span: destination, n: bytesPerSampleDest);
251 auto destSampleRange2 = take(span: destination, n: bytesPerSampleDest);
252 destination = drop(span: destination, n: bytesPerSampleDest);
253 auto destSampleRange3 = take(span: destination, n: bytesPerSampleDest);
254 destination = drop(span: destination, n: bytesPerSampleDest);
255 auto destSampleRange4 = take(span: destination, n: bytesPerSampleDest);
256 destination = drop(span: destination, n: bytesPerSampleDest);
257
258 storeSampleWithFormat<destinationFormat>(destSampleRange1, value1);
259 storeSampleWithFormat<destinationFormat>(destSampleRange2, value2);
260 storeSampleWithFormat<destinationFormat>(destSampleRange3, value3);
261 storeSampleWithFormat<destinationFormat>(destSampleRange4, value4);
262 } else {
263 auto sourceSampleRange = take(span: source, n: bytesPerSampleSource);
264 int32_t value = toInt32<sourceFormat>(sourceSampleRange);
265
266 auto destSampleRange = take(span: destination, n: bytesPerSampleDest);
267 storeSampleWithFormat<destinationFormat>(destSampleRange, value);
268
269 source = drop(span: source, n: bytesPerSampleSource);
270 destination = drop(span: destination, n: bytesPerSampleDest);
271 }
272 }
273 }
274 }
275};
276
277template <NativeSampleFormat destinationFormat>
278void convertSampleFormatWithDestinationFormat(QSpan<const std::byte> source,
279 NativeSampleFormat sourceFormat,
280 QSpan<std::byte> destination) noexcept QT_MM_NONBLOCKING
281{
282 switch (sourceFormat) {
283 case NativeSampleFormat::uint8_t:
284 return WordConverter<NativeSampleFormat::uint8_t, destinationFormat>()(source, destination);
285 case NativeSampleFormat::int16_t:
286 return WordConverter<NativeSampleFormat::int16_t, destinationFormat>()(source, destination);
287 case NativeSampleFormat::int32_t:
288 return WordConverter<NativeSampleFormat::int32_t, destinationFormat>()(source, destination);
289 case NativeSampleFormat::int24_t_3b:
290 return WordConverter<NativeSampleFormat::int24_t_3b, destinationFormat>()(source,
291 destination);
292 case NativeSampleFormat::int24_t_4b_low:
293 return WordConverter<NativeSampleFormat::int24_t_4b_low, destinationFormat>()(source,
294 destination);
295 case NativeSampleFormat::float32_t:
296 return WordConverter<NativeSampleFormat::float32_t, destinationFormat>()(source,
297 destination);
298 default:
299 Q_UNREACHABLE_RETURN();
300 }
301}
302
303} // namespace
304
305void convertSampleFormat(QSpan<const std::byte> source, NativeSampleFormat sourceFormat,
306 QSpan<std::byte> destination,
307 NativeSampleFormat destinationFormat) noexcept QT_MM_NONBLOCKING
308{
309 using namespace QtMultimediaPrivate;
310
311 size_t bytesPerSampleSource = bytesPerSample(fmt: sourceFormat);
312 size_t bytesPerSampleDest = bytesPerSample(fmt: destinationFormat);
313
314 Q_ASSERT(source.size() * bytesPerSampleDest == destination.size() * bytesPerSampleSource);
315
316 switch (destinationFormat) {
317 case NativeSampleFormat::uint8_t:
318 return convertSampleFormatWithDestinationFormat<NativeSampleFormat::uint8_t>(
319 source, sourceFormat, destination);
320 case NativeSampleFormat::int16_t:
321 return convertSampleFormatWithDestinationFormat<NativeSampleFormat::int16_t>(
322 source, sourceFormat, destination);
323 case NativeSampleFormat::int32_t:
324 return convertSampleFormatWithDestinationFormat<NativeSampleFormat::int32_t>(
325 source, sourceFormat, destination);
326 case NativeSampleFormat::int24_t_3b:
327 return convertSampleFormatWithDestinationFormat<NativeSampleFormat::int24_t_3b>(
328 source, sourceFormat, destination);
329 case NativeSampleFormat::int24_t_4b_low:
330 return convertSampleFormatWithDestinationFormat<NativeSampleFormat::int24_t_4b_low>(
331 source, sourceFormat, destination);
332 case NativeSampleFormat::float32_t:
333 return convertSampleFormatWithDestinationFormat<NativeSampleFormat::float32_t>(
334 source, sourceFormat, destination);
335 default:
336 Q_UNREACHABLE_RETURN();
337 }
338}
339
340NativeSampleFormat bestNativeSampleFormat(const QAudioFormat &fmt,
341 QSpan<const NativeSampleFormat> supportedNativeFormats)
342{
343 Q_ASSERT(!supportedNativeFormats.empty());
344
345 auto resolveCandidate = [&](QSpan<const NativeSampleFormat> candidates) {
346 auto it = std::find_first_of(first1: candidates.begin(), last1: candidates.end(),
347 first2: supportedNativeFormats.begin(), last2: supportedNativeFormats.end());
348
349 if (it != candidates.end())
350 return *it;
351
352 qFatal() << "No candidate for conversion found. That should not happen";
353 Q_UNREACHABLE_RETURN(NativeSampleFormat::float32_t);
354 };
355
356 // heuristics:
357 // select the best format that does not involve precision
358 // if that does not yield a format, find a format with the least loss of precision.
359 switch (fmt.sampleFormat()) {
360 case QAudioFormat::SampleFormat::UInt8: {
361 constexpr auto candidates = std::array{
362 NativeSampleFormat::uint8_t, NativeSampleFormat::int16_t,
363 NativeSampleFormat::int24_t_3b, NativeSampleFormat::int24_t_4b_low,
364 NativeSampleFormat::int32_t, NativeSampleFormat::float32_t,
365 };
366
367 return resolveCandidate(candidates);
368 }
369 case QAudioFormat::SampleFormat::Int16: {
370 constexpr auto candidates = std::array{
371 NativeSampleFormat::int16_t, NativeSampleFormat::int24_t_3b,
372 NativeSampleFormat::int24_t_4b_low, NativeSampleFormat::int32_t,
373 NativeSampleFormat::float32_t, NativeSampleFormat::uint8_t,
374 };
375
376 return resolveCandidate(candidates);
377 }
378 case QAudioFormat::SampleFormat::Int32: {
379 constexpr auto candidates = std::array{
380 NativeSampleFormat::int32_t, NativeSampleFormat::float32_t,
381 NativeSampleFormat::int24_t_3b, NativeSampleFormat::int24_t_4b_low,
382 NativeSampleFormat::int16_t, NativeSampleFormat::uint8_t,
383
384 };
385
386 return resolveCandidate(candidates);
387 }
388 case QAudioFormat::SampleFormat::Float: {
389 constexpr auto candidates = std::array{
390 NativeSampleFormat::float32_t, NativeSampleFormat::int24_t_3b,
391 NativeSampleFormat::int24_t_4b_low, NativeSampleFormat::int32_t,
392 NativeSampleFormat::int16_t, NativeSampleFormat::uint8_t,
393 };
394
395 return resolveCandidate(candidates);
396 }
397 case QAudioFormat::SampleFormat::Unknown:
398 case QAudioFormat::SampleFormat::NSampleFormats: {
399 qFatal() << "bestNativeSampleFormat called with invalid argument" << fmt.sampleFormat();
400 Q_UNREACHABLE_RETURN(NativeSampleFormat::int32_t);
401 }
402
403 default: {
404 Q_UNREACHABLE_RETURN(NativeSampleFormat::int32_t);
405 }
406 }
407}
408
409NativeSampleFormat toNativeSampleFormat(QAudioFormat::SampleFormat fmt)
410{
411 switch (fmt) {
412 case QAudioFormat::SampleFormat::Float:
413 return NativeSampleFormat::float32_t;
414 case QAudioFormat::SampleFormat::UInt8:
415 return NativeSampleFormat::uint8_t;
416 case QAudioFormat::SampleFormat::Int16:
417 return NativeSampleFormat::int16_t;
418 case QAudioFormat::SampleFormat::Int32:
419 return NativeSampleFormat::int32_t;
420 default:
421 Q_UNREACHABLE_RETURN({});
422 }
423}
424
425QAudioFormat::SampleFormat bestSampleFormat(const NativeSampleFormat fmt)
426{
427 switch (fmt) {
428 case NativeSampleFormat::uint8_t:
429 return QAudioFormat::SampleFormat::UInt8;
430 case NativeSampleFormat::int16_t:
431 return QAudioFormat::SampleFormat::Int16;
432 case NativeSampleFormat::int32_t:
433 case NativeSampleFormat::int24_t_3b:
434 case NativeSampleFormat::int24_t_4b_low:
435 return QAudioFormat::SampleFormat::Int32;
436 case NativeSampleFormat::float32_t:
437 return QAudioFormat::SampleFormat::Float;
438 default:
439 Q_UNREACHABLE_RETURN({});
440 }
441}
442
443std::optional<float> sanitizeVolume(float volume, float lastValue)
444{
445 constexpr float epsilon = 1.f / (1 << 22); // good enough for 22bit resolution
446
447 // multiplication is optimized for 0 and 1
448 // values which are sufficiently close to 0 and 1 can be considered as 0 or 1
449 if (volume < epsilon)
450 volume = 0;
451 else if (volume > (1.f - epsilon))
452 volume = 1.f;
453
454 // similar to qFuzzyCompare, but with better heuristics for volume
455 if (std::abs(x: volume - lastValue) < epsilon)
456 return std::nullopt;
457
458 return volume;
459}
460
461} // namespace QAudioHelperInternal
462
463#undef QT_MM_RESTRICT
464
465// clang-format off
466QT_MM_MAKE_STRING_RESOLVER( QAudioHelperInternal::NativeSampleFormat, QtMultimediaPrivate::EnumName,
467 (QAudioHelperInternal::NativeSampleFormat::uint8_t, "uint8_t")
468 (QAudioHelperInternal::NativeSampleFormat::int16_t, "int16_t")
469 (QAudioHelperInternal::NativeSampleFormat::int32_t, "int32_t")
470 (QAudioHelperInternal::NativeSampleFormat::int24_t_3b, "int24_t_3b")
471 (QAudioHelperInternal::NativeSampleFormat::int24_t_4b_low, "int24_t_4b_low")
472 (QAudioHelperInternal::NativeSampleFormat::float32_t, "float32_t")
473 );
474// clang-format on
475
476QT_MM_DEFINE_QDEBUG_ENUM(QAudioHelperInternal::NativeSampleFormat);
477
478QT_END_NAMESPACE
479

source code of qtmultimedia/src/multimedia/audio/qaudiohelpers.cpp