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
qffmpegaudioframeconverter.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/qaudiobuffer_support_p.h>
7#include <QtFFmpegMediaPluginImpl/private/qffmpegframe_p.h>
8#include <QtFFmpegMediaPluginImpl/private/qffmpegresampler_p.h>
9#include <QtFFmpegMediaPluginImpl/private/qffmpegmediaformatinfo_p.h>
10
11#if defined(Q_CC_MSVC) && defined(QT_MM_OPTIMIZE_DEBUG)
12# pragma optimize("s", on)
13#endif
14
15// TODO: namespace 3p library to prevent odr violations
16#include <signalsmith-stretch.h>
17
19
20namespace QFFmpeg {
21
22namespace {
23
24qreal sampleRateFactor()
25{
26 // Test purposes:
27 //
28 // The env var describes a factor for the sample rate of
29 // audio data that we feed to the audio sink.
30 //
31 // In some cases audio sink might consume data slightly slower or faster than expected;
32 // even though the synchronization in the audio renderer is supposed to handle it,
33 // it makes sense to experiment with QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR != 1.
34 //
35 // Set QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR > 1 (e.g. 1.01 - 1.1) to test high buffer
36 // loading
37 // or try compensating too fast data consumption by the audio sink.
38 // Set QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR < 1 to test low buffer loading
39 // or try compensating too slow data consumption by the audio sink.
40
41 static const qreal result = []() {
42 const auto sampleRateFactorStr =
43 qEnvironmentVariable("QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR");
44 bool ok = false;
45 const auto result = sampleRateFactorStr.toDouble(&ok);
46 return ok ? result : 1.;
47 }();
48
49 return result;
50}
51
52struct TrivialAudioFrameConverter : AbstractAudioFrameConverter
53{
54 explicit TrivialAudioFrameConverter(const Frame &frame, QAudioFormat outputFormat,
55 float playbackRate)
56 {
57 int sampleRate = qRound(outputFormat.sampleRate() / playbackRate * sampleRateFactor());
58 outputFormat.setSampleRate(sampleRate);
59 m_converter = createResampler(frame, outputFormat);
60 }
61
62 QAudioBuffer convert(AVFrame *frame) override
63 {
64 if (!m_converter)
65 return { };
66
67 return m_converter->resample(frame);
68 }
69
70private:
71 std::unique_ptr<QFFmpegResampler> m_converter;
72};
73
74struct PitchShiftingAudioFrameConverter : AbstractAudioFrameConverter
75{
76 explicit PitchShiftingAudioFrameConverter(const Frame &frame, QAudioFormat outputFormat,
77 float playbackRate)
78 : m_playbackRate{ playbackRate }
79 {
80 const QAudioFormat mediaFormat = QFFmpegMediaFormatInfo::audioFormatFromCodecParameters(
81 *frame.codecContext()->stream()->codecpar);
82
83 const QAudioFormat floatFormat = [&] {
84 QAudioFormat ret = mediaFormat;
85 ret.setSampleFormat(QAudioFormat::SampleFormat::Float);
86 return ret;
87 }();
88
89 m_toPCMDecoder = createResampler(frame, floatFormat); // this is *not* a resampler
90 m_stretcher.reset();
91 m_pendingFractionalFrames = 0.f;
92 m_stretcher.presetDefault(mediaFormat.channelCount(), outputFormat.sampleRate());
93
94 const QAudioFormat pitchCompensatedFormat = [&] {
95 QAudioFormat ret = floatFormat;
96 ret.setSampleRate(outputFormat.sampleRate());
97 return ret;
98 }();
99 m_toOutputFormatConverter = QFFmpegResampler::createFromInputFormat(pitchCompensatedFormat, outputFormat,
100 frame.startTime().get());
101 }
102
103 QAudioBuffer convert(AVFrame *frame) override
104 {
105 if (!m_toPCMDecoder || !m_toOutputFormatConverter)
106 return { };
107
108 using namespace QtPrivate;
109
110 // convert to pcm buffer
111 QAudioBuffer wordConverted = m_toPCMDecoder->resample(frame);
112
113 // compute stretch amount
114 int mediaFrameCount = wordConverted.frameCount();
115 float expectedNumberOfFrames = mediaFrameCount / m_playbackRate + m_pendingFractionalFrames;
116 int numberOfFullExpectedFrames = qFloor(expectedNumberOfFrames);
117 m_pendingFractionalFrames = expectedNumberOfFrames - numberOfFullExpectedFrames;
118
119 auto timeStretcherOutput = QAudioBuffer{
120 numberOfFullExpectedFrames,
121 wordConverted.format(),
122 };
123
124 // stretch
125 m_stretcher.process(
126 QAudioBufferDeinterleaveAdaptor<const float>{
127 wordConverted,
128 },
129 mediaFrameCount,
130 QAudioBufferDeinterleaveAdaptor<float>{
131 timeStretcherOutput,
132 },
133 numberOfFullExpectedFrames);
134
135 // convert to audio output format
136 QAudioBuffer outputBuffer = m_toOutputFormatConverter->resample(
137 timeStretcherOutput.constData<const char>(), timeStretcherOutput.byteCount());
138
139 return outputBuffer;
140 }
141
142private:
143 std::unique_ptr<QFFmpegResampler> m_toPCMDecoder;
144 signalsmith::stretch::SignalsmithStretch<float> m_stretcher;
145 std::unique_ptr<QFFmpegResampler> m_toOutputFormatConverter;
146 float m_playbackRate;
147 float m_pendingFractionalFrames = 0.f;
148};
149
150} // namespace
151
153
155 const QAudioFormat &outputFormat)
156{
157 return QFFmpegResampler::createFromCodecContext(frame.codecContext(), outputFormat, frame.startTime().get());
158}
159
161makeTrivialAudioFrameConverter(const Frame &frame, QAudioFormat outputFormat, float playbackRate)
162{
163 return std::make_unique<TrivialAudioFrameConverter>(frame, outputFormat, playbackRate);
164}
165
167makePitchShiftingAudioFrameConverter(const Frame &frame, QAudioFormat outputFormat,
168 float playbackRate)
169{
170 return std::make_unique<PitchShiftingAudioFrameConverter>(frame, outputFormat, playbackRate);
171}
172
173} // namespace QFFmpeg
174
175QT_END_NAMESPACE
std::unique_ptr< QFFmpegResampler > createResampler(const Frame &frame, const QAudioFormat &outputFormat)
std::unique_ptr< AbstractAudioFrameConverter > makePitchShiftingAudioFrameConverter(const Frame &frame, QAudioFormat outputFormat, float playbackRate)
std::conditional_t< QT_FFMPEG_AVIO_WRITE_CONST, const uint8_t *, uint8_t * > AvioWriteBufferType
std::unique_ptr< AbstractAudioFrameConverter > makeTrivialAudioFrameConverter(const Frame &frame, QAudioFormat outputFormat, float playbackRate)
Combined button and popup list for selecting options.