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
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
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
14QT_BEGIN_NAMESPACE
15
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
27namespace {
28
29template<class T>
30inline 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
42template<class T>
43void 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
58 const QAudioFormat &format,
59 const void *src,
60 void *dest,
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:
73 case QAudioFormat::Int16:
75 case QAudioFormat::Int32:
77 case QAudioFormat::Float:
79 default:
81 }
82}
83
85 const QAudioFormat &format,
86 QSpan<const std::byte> source,
88{
90
91 if (Q_LIKELY(volume == 1.f)) {
92 if (source.data() != destination.data())
94 } else if (volume == 0) {
95 std::byte zero =
96 format.sampleFormat() == QAudioFormat::UInt8 ? std::byte{ 0x80 } : std::byte{ 0 };
97
99 } else {
101 source.size());
102 }
103}
104
105namespace {
106
107template <NativeSampleFormat Format>
109{
110 switch (Format) {
112 constexpr int32_t offset = (1 << 7);
114 int32_t val = (int32_t(smp) - offset) << 24;
115 return val;
116 }
118 union CastUnion {
119 std::int16_t i16;
120 std::byte b[2];
121 } castUnion = {};
122
124
125 int32_t val = castUnion.i16 << 16;
126 return val;
127 }
129 union CastUnion {
130 std::int32_t i32;
131 std::byte b[4];
132 } castUnion = {};
133
135
136 int32_t val = castUnion.i32 << 8;
137 return val;
138 }
140 union CastUnion {
141 std::int32_t i32;
142 std::byte b[4];
143 } castUnion = {};
144
146 int32_t val = castUnion.i32 << 8;
147 return val;
148 }
150 union CastUnion {
151 float f32;
152 std::byte b[4];
153 } castUnion = {};
154
156 constexpr double range = std::numeric_limits<int32_t>::max();
158 return val;
159 }
161 union CastUnion {
162 std::int32_t i32;
163 std::byte b[4];
164 } castUnion = {};
165
167 return castUnion.i32;
168 }
169
170 default:
172 }
173}
174
175template <NativeSampleFormat Format>
178{
179 switch (Format) {
181 value = value >> 24;
182 constexpr int32_t offset = 1 << 7;
184 std::copy_n(reinterpret_cast<const std::byte *>(&sampleValue), 1, destination.data());
185 return;
186 }
188 value = value >> 16;
190 std::copy_n(reinterpret_cast<const std::byte *>(&sampleValue), 2, destination.data());
191 return;
192 }
193
196 std::copy_n(reinterpret_cast<const std::byte *>(&sampleValue), 3, destination.data());
197 return;
198 }
199
201 int32_t sampleValue = (value >> 8) & 0x00ffffff;
202 std::copy_n(reinterpret_cast<const std::byte *>(&sampleValue), 4, destination.data());
203 return;
204 }
206 std::copy_n(reinterpret_cast<const std::byte *>(&value), 4, destination.data());
207 return;
208 }
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:
217 }
218}
219
221struct WordConverter
222{
224 {
225 if constexpr (sourceFormat == destinationFormat) {
227 } else {
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
245
250
259
264 } else {
267
270
273 }
274 }
275 }
276 }
277};
278
283{
284 switch (sourceFormat) {
300 default:
302 }
303}
304
305} // namespace
306
310{
311 using namespace QtMultimediaPrivate;
312
315
317
318 switch (destinationFormat) {
337 default:
339 }
340}
341
344{
346
350
351 if (it != candidates.end())
352 return *it;
353
354 qFatal() << "No candidate for conversion found. That should not happen";
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()) {
363 constexpr auto candidates = std::array{
367 };
368
370 }
372 constexpr auto candidates = std::array{
376 };
377
379 }
381 constexpr auto candidates = std::array{
385
386 };
387
389 }
391 constexpr auto candidates = std::array{
395 };
396
398 }
401 qFatal() << "bestNativeSampleFormat called with invalid argument" << fmt.sampleFormat();
403 }
404
405 default: {
407 }
408 }
409}
410
426
444
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
469
474
475////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
476
477namespace {
478
479struct CatmullRomBasis
480{
481 explicit CatmullRomBasis(float t)
482 {
483 Q_ASSERT(t >= 0.0f && t < 1.0f);
484 const float t2 = t * t;
485 const float t3 = t2 * t;
486 c0 = -0.5f * t3 + t2 - 0.5f * t;
487 c1 = 1.5f * t3 - 2.5f * t2 + 1.0f;
488 c2 = -1.5f * t3 + 2.0f * t2 + 0.5f * t;
489 c3 = 0.5f * t3 - 0.5f * t2;
490 }
491
492 float interpolate(float pm1, float p0, float p1, float p2) const
493 {
494 return (c0 * pm1 + c1 * p0) + (c2 * p1 + c3 * p2);
495 }
496
497private:
498 float c0, c1, c2, c3;
499};
500
501} // namespace
502
504 int outputRate)
505{
506 Q_ASSERT(nChannels >= 1);
507 Q_ASSERT(inputRate > 0);
508 Q_ASSERT(outputRate > 0);
509
510 if (input.isEmpty() || nChannels <= 0)
511 return {};
512
513 if (inputRate == outputRate) {
514 return QByteArray(reinterpret_cast<const char *>(input.data()),
515 qsizetype(input.size()) * qsizetype(sizeof(float)));
516 }
517
519 if (inputFrames == 0)
520 return {};
521
522 // Number of output frames
523 const qsizetype outputFrames =
524 qsizetype(double(inputFrames) * double(outputRate) / double(inputRate) + 0.5);
525 if (outputFrames <= 0)
526 return {};
527
530 QSpan<float> dst(reinterpret_cast<float *>(out.data()), outputFrames * nChannels);
531
532 using namespace QtMultimediaPrivate;
533
534 // Catmull-Rom: needs p0,p1,p2,p3 where output samples between p1 and p2
535 // p[i] = clamp(i, 0, inputFrames-1)
536 const float increment = float(inputRate) / float(outputRate);
537
538 qsizetype iPos = 0;
539 float fPos = 0.0f;
540
542 const qsizetype i1 = iPos;
543 const float t = fPos;
544
545 // Clamp frame indices
546 const qsizetype i0 = std::max(qsizetype(0), i1 - 1);
547 const qsizetype i2 = std::min(i1 + 1, inputFrames - 1);
548 const qsizetype i3 = std::min(i1 + 2, inputFrames - 1);
549
550 const CatmullRomBasis basis(t);
551
552 auto frameOut = take(dst, nChannels);
553 for (int ch = 0; ch < nChannels; ++ch) {
554 const float pm1 = input[i0 * nChannels + ch];
555 const float p0 = input[i1 * nChannels + ch];
556 const float p1 = input[i2 * nChannels + ch];
557 const float p2 = input[i3 * nChannels + ch];
559 }
560 dst = drop(dst, nChannels);
561
562 fPos += increment;
563 while (fPos >= 1.0f) {
564 fPos -= 1.0f;
565 iPos += 1;
566 }
567 }
568
569 return out;
570}
571
581
583{
584 m_iPos = 0;
585 m_fPos = 0.0f;
587 namespace ranges = QtMultimediaPrivate::ranges;
588 ranges::fill(m_history, 0.0f);
589}
590
592{
593 m_fPos += m_ratio;
594 while (m_fPos >= 1.0f) {
595 m_fPos -= 1.0f;
596 m_iPos += 1;
597 }
598}
599
603{
604 using namespace QtMultimediaPrivate;
605
610
611 // Flush mode: empty input signals end-of-stream
612 if (inputFrames == 0) {
615
618 break;
619
620 const qsizetype pm1FrameIdx = std::max(qsizetype(0), m_iPos - 1);
623
625
626 for (qsizetype ch = 0; ch < nChannels; ++ch) {
627 auto fetch = [&](qsizetype absoluteFrameIdx) -> float {
631 if (historySampleIdx >= 0)
633 return m_history[ch]; // clamp to oldest history
634 }
635 return m_history[ch]; // beyond available data, clamp
636 };
637
638 const float pm1 = fetch(pm1FrameIdx);
639 const float p0 = fetch(m_iPos);
640 const float p1 = fetch(p1FrameIdx);
641 const float p2 = fetch(p2FrameIdx);
642
645 }
646
649 }
650
651 return ResampleResult{
652 input,
654 };
655 }
656
659
662 break;
663
664 // Clamp frame indices relative to available data
665 const qsizetype pm1FrameIdx = std::max(qsizetype(0), m_iPos - 1);
666 const qsizetype p1FrameIdx =
668 const qsizetype p2FrameIdx =
670
672
673 for (qsizetype ch = 0; ch < nChannels; ++ch) {
674 auto fetch = [&](qsizetype absoluteFrameIdx) -> float {
679 if (historySampleIdx >= 0)
681 return m_history[ch]; // clamp to oldest history (stream start)
682 };
683
684 const float pm1 = fetch(pm1FrameIdx);
685 const float p0 = fetch(m_iPos);
686 const float p1 = fetch(p1FrameIdx);
687 const float p2 = fetch(p2FrameIdx);
688
691 }
692
698 }
699
700 // Update history with last 3 frames of consumed portion
702 // If the main loop produced no output but there is input (m_iPos lags
703 // behind m_inputFramesConsumed at a chunk boundary), consume all input into history so
704 // the caller makes progress. The flush path interpolates using history.
705 if (framesToCache == 0 && inputFrames > 0)
707 namespace ranges = QtMultimediaPrivate::ranges;
708 if (framesToCache >= 3) {
711 m_history.data());
712 } else if (framesToCache > 0) {
715 auto history = QSpan(m_history);
717 history.begin());
720 }
721
723
725 return ResampleResult{
728 };
729}
730
731void upmixMonoToStereo(QSpan<float> out, QSpan<const float> in,
733{
734 constexpr float kEqualPowerGain(M_SQRT1_2); // 1/sqrt(2)
735 Q_ASSERT(out.size() == in.size() * 2);
736
737 const qsizetype frames = in.size();
738 const float gain = (scaling == UpmixScaling::EqualPower) ? kEqualPowerGain : 1.0f;
739
740 for (qsizetype i = 0; i < frames; ++i) {
741 const float s = in[i] * gain;
742 out[i * 2 + 0] = s;
743 out[i * 2 + 1] = s;
744 }
745}
746
748{
749 if (input.empty())
750 return {};
751
752 const qsizetype frames = input.size();
754 2 * frames * qsizetype(sizeof(float)),
756 };
757
758 upmixMonoToStereo(QSpan<float>(reinterpret_cast<float *>(out.data()), 2 * frames),
759 input, scaling);
760 return out;
761}
762
763void downmixStereoToMono(QSpan<float> out, QSpan<const float> in,
765{
766 constexpr float kEqualPowerGain(M_SQRT1_2); // 1/sqrt(2)
767
768 Q_ASSERT(in.size() % 2 == 0);
769 Q_ASSERT(out.size() == in.size() / 2);
770
771 const qsizetype frames = in.size() / 2;
772 const float gain = (scaling == DownmixScaling::KeepPower) ? kEqualPowerGain : 0.5f;
773
774 for (qsizetype i = 0; i < frames; ++i)
775 out[i] = (in[i * 2 + 0] + in[i * 2 + 1]) * gain;
776}
777
779{
780 if (input.empty())
781 return {};
782
783 Q_ASSERT(input.size() % 2 == 0);
784
785 const qsizetype frames = input.size() / 2;
787 frames * qsizetype(sizeof(float)),
789 };
790
791 downmixStereoToMono(QSpan<float>(reinterpret_cast<float *>(out.data()), frames),
792 input, scaling);
793 return out;
794}
795
796} // namespace QAudioHelperInternal
797
798#undef QT_MM_RESTRICT
799
800// clang-format off
802 (QAudioHelperInternal::NativeSampleFormat::uint8_t, "uint8_t")
803 (QAudioHelperInternal::NativeSampleFormat::int16_t, "int16_t")
804 (QAudioHelperInternal::NativeSampleFormat::int32_t, "int32_t")
805 (QAudioHelperInternal::NativeSampleFormat::int24_t_3b, "int24_t_3b")
806 (QAudioHelperInternal::NativeSampleFormat::int24_t_4b_low, "int24_t_4b_low")
807 (QAudioHelperInternal::NativeSampleFormat::float32_t, "float32_t")
808 );
809// clang-format on
810
812
813QT_END_NAMESPACE
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"))
#define QT_MM_RESTRICT
QT_MM_DEFINE_QDEBUG_ENUM(QAudioHelperInternal::NativeSampleFormat)