Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qffmpegaudiorenderer.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#include "qaudiosink.h"
6#include "qaudiooutput.h"
7#include "private/qplatformaudiooutput_p.h"
8#include <QtCore/qloggingcategory.h>
9
10#include "qffmpegresampler_p.h"
12
14
15static Q_LOGGING_CATEGORY(qLcAudioRenderer, "qt.multimedia.ffmpeg.audiorenderer");
16
17namespace QFFmpeg {
18
19using namespace std::chrono_literals;
20
21namespace {
22constexpr auto AudioSinkBufferTime = 100000us;
23constexpr auto MinDesiredBufferTime = AudioSinkBufferTime / 10;
24constexpr auto MaxDesiredBufferTime = 6 * AudioSinkBufferTime / 10;
25constexpr auto SampleCompenationOffset = AudioSinkBufferTime / 10;
26
27// actual playback rate chang during the soft compensation
28constexpr qreal CompensationAngleFactor = 0.01;
29
30constexpr auto DurationBias = 2ms; // avoids extra timer events
31} // namespace
32
34 : Renderer(tc, MinDesiredBufferTime), m_output(output)
35{
36 if (output) {
37 // TODO: implement the signals in QPlatformAudioOutput and connect to them, QTBUG-112294
41 }
42}
43
45{
46 setOutputInternal(m_output, output, [this](QAudioOutput *) { onDeviceChanged(); });
47}
48
50{
51 freeOutput();
52}
53
55{
56 if (m_sink)
57 m_sink->setVolume(m_output->isMuted() ? 0.f : m_output->volume());
58}
59
61{
62 m_deviceChanged = true;
63}
64
66{
67 if (frame.isValid())
68 updateOutput(frame.codec());
69
70 if (!m_sink || !m_resampler || !m_ioDevice)
71 return {};
72
73 if (!m_bufferedData.isValid()) {
74 if (!frame.isValid())
75 return {};
76
78 m_bufferedData = m_resampler->resample(frame.avFrame());
79 m_bufferWritten = 0;
80 }
81
82 if (m_bufferedData.isValid()) {
83 auto bytesWritten = m_ioDevice->write(m_bufferedData.constData<char>() + m_bufferWritten,
84 m_bufferedData.byteCount() - m_bufferWritten);
85
86 m_bufferWritten += bytesWritten;
87
88 if (m_bufferWritten >= m_bufferedData.byteCount()) {
89 m_bufferedData = {};
90 m_bufferWritten = 0;
91
92 return {};
93 }
94
95 const auto remainingDuration = std::chrono::microseconds(
96 m_format.durationForBytes(m_bufferedData.byteCount() - m_bufferWritten));
97
98 return { false, std::min(remainingDuration + DurationBias, AudioSinkBufferTime / 2) };
99 }
100
101 return {};
102}
103
105{
106 m_resampler.reset();
107}
108
110{
111 // We recreate resampler whenever format is changed
112
113 /* AVSampleFormat requiredFormat =
114 QFFmpegMediaFormatInfo::avSampleFormat(m_format.sampleFormat());
115
116 #if QT_FFMPEG_OLD_CHANNEL_LAYOUT
117 qCDebug(qLcAudioRenderer) << "init resampler" << requiredFormat
118 << codec->stream()->codecpar->channels;
119 #else
120 qCDebug(qLcAudioRenderer) << "init resampler" << requiredFormat
121 << codec->stream()->codecpar->ch_layout.nb_channels;
122 #endif
123 */
124
125 // Test purposes:
126 // Set PlaybackRateDeviation > 1 (e.g. 1.01) to test high buffer loading
127 // or PlaybackRateDeviation < 1 to test low buffer loading.
128 constexpr qreal PlaybackRateDeviation = 1.;
129
130 auto resamplerFormat = m_format;
131 resamplerFormat.setSampleRate(
132 qRound(m_format.sampleRate() / playbackRate() * PlaybackRateDeviation));
133 m_resampler = std::make_unique<Resampler>(codec, resamplerFormat);
134}
135
137{
138 qCDebug(qLcAudioRenderer) << "Free audio output";
139 if (m_sink) {
140 m_sink->reset();
141
142 // TODO: inestigate if it's enough to reset the sink without deleting
143 m_sink.reset();
144 }
145
146 m_ioDevice = nullptr;
147
148 m_bufferedData = {};
149 m_bufferWritten = 0;
150 m_deviceChanged = false;
151}
152
154{
155 if (m_deviceChanged) {
156 freeOutput();
157 m_format = {};
158 m_resampler.reset();
159 }
160
161 if (!m_output) {
162 return;
163 }
164
165 if (!m_format.isValid()) {
166 m_format =
168 m_format.setChannelConfig(m_output->device().channelConfiguration());
169 }
170
171 if (!m_sink) {
172 // Insert a delay here to test time offset synchronization, e.g. QThread::sleep(1)
173 m_sink = std::make_unique<QAudioSink>(m_output->device(), m_format);
174 updateVolume();
175 m_sink->setBufferSize(m_format.bytesForDuration(AudioSinkBufferTime.count()));
176 m_ioDevice = m_sink->start();
177 }
178
179 if (!m_resampler) {
181 }
182}
183
185{
186 // Currently we use "soft" compensation with a positive/negative delta
187 // for slight increasing/decreasing of the sound delay (roughly equal to buffer loading on a
188 // normal playng) if it's out of the range [min; max]. If the delay more than
189 // AudioSinkBufferTime we synchronize rendering time to ensure free buffer size and than
190 // decrease it more with "soft" synchronization.
191 //
192 // TODO:
193 // 1. Probably, use "hard" compensation (inject silence) on the start and after pause
194 // 2. Make a delay as much stable as possible. For this aim, we should make
195 // CompensationAngleFactor flexible, and all the time to the narrower range of desired
196 // QAudioSink loading time.
197
198 Q_ASSERT(m_resampler);
199 Q_ASSERT(currentFrame.isValid());
200
201 const auto bufferLoadingTime = currentBufferLoadingTime();
202 const auto currentFrameDelay = frameDelay(currentFrame);
203 auto soundDelay = currentFrameDelay + bufferLoadingTime;
204
205 const auto activeCompensationDelta = m_resampler->activeSampleCompensationDelta();
206
207 if (soundDelay > AudioSinkBufferTime) {
208 const auto targetSoundDelay = (AudioSinkBufferTime + MaxDesiredBufferTime) / 2;
209 changeRendererTime(soundDelay - targetSoundDelay);
210 qCDebug(qLcAudioRenderer) << "Change rendering time: Audio time offset."
211 << "Prev sound delay:" << soundDelay.count()
212 << "Target sound delay:" << targetSoundDelay.count()
213 << "New actual sound delay:"
214 << (frameDelay(currentFrame) + bufferLoadingTime).count();
215
216 soundDelay = targetSoundDelay;
217 }
218
219 constexpr auto AvgDesiredBufferTime = (MinDesiredBufferTime + MaxDesiredBufferTime) / 2;
220
221 std::optional<int> newCompensationSign;
222 if (soundDelay < MinDesiredBufferTime && activeCompensationDelta <= 0)
223 newCompensationSign = 1;
224 else if (soundDelay > MaxDesiredBufferTime && activeCompensationDelta >= 0)
225 newCompensationSign = -1;
226 else if ((soundDelay <= AvgDesiredBufferTime && activeCompensationDelta < 0)
227 || (soundDelay >= AvgDesiredBufferTime && activeCompensationDelta > 0))
228 newCompensationSign = 0;
229
230 // qDebug() << soundDelay.count() << bufferLoadingTime.count();
231
232 if (newCompensationSign) {
233 const auto target = *newCompensationSign == 0 ? soundDelay
234 : *newCompensationSign > 0 ? MinDesiredBufferTime + SampleCompenationOffset
235 : MaxDesiredBufferTime - SampleCompenationOffset;
236 const auto delta = m_format.sampleRate() * (target - soundDelay) / 1s;
237 const auto interval = std::abs(delta) / CompensationAngleFactor;
238
239 qDebug(qLcAudioRenderer) << "Set audio sample compensation. Delta (samples and us):"
240 << delta << (target - soundDelay).count()
241 << "PrevDelta:" << activeCompensationDelta
242 << "Interval:" << interval
243 << "SampleRate:" << m_format.sampleRate()
244 << "Delay(us):" << soundDelay.count()
245 << "SamplesProcessed:" << m_resampler->samplesProcessed();
246
247 m_resampler->setSampleCompensation(static_cast<qint32>(delta),
248 static_cast<quint32>(interval));
249 }
250}
251
252std::chrono::microseconds AudioRenderer::currentBufferLoadingTime() const
253{
254 Q_ASSERT(m_sink);
255
256 return AudioSinkBufferTime * qMax(m_sink->bufferSize() - m_sink->bytesFree(), 0)
257 / m_sink->bufferSize();
258}
259
260} // namespace QFFmpeg
261
263
264#include "moc_qffmpegaudiorenderer_p.cpp"
bool isValid() const noexcept
Returns true if this is a valid buffer.
const T * constData() const
qsizetype byteCount() const noexcept
Returns the size of this buffer, in bytes.
QAudioFormat::ChannelConfig channelConfiguration() const
Returns the channel configuration of the device.
Q_MULTIMEDIA_EXPORT void setChannelConfig(ChannelConfig config) noexcept
Sets the channel configuration to config.
constexpr void setSampleRate(int sampleRate) noexcept
Sets the sample rate to samplerate in Hertz.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
Q_MULTIMEDIA_EXPORT qint32 bytesForDuration(qint64 microseconds) const
Returns the number of bytes required for this audio format for microseconds.
Q_MULTIMEDIA_EXPORT qint64 durationForBytes(qint32 byteCount) const
Returns the number of microseconds represented by bytes in this format.
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
\qmltype AudioOutput \instantiates QAudioOutput
void deviceChanged()
bool isMuted() const
void mutedChanged(bool muted)
QAudioDevice device
\qmlproperty AudioDevice QtMultimedia::AudioOutput::device
float volume
\qmlproperty real QtMultimedia::AudioOutput::volume
void volumeChanged(float volume)
static QAudioFormat audioFormatFromCodecParameters(AVCodecParameters *codecPar)
void updateSynchronization(const Frame &currentFrame)
RenderingResult renderInternal(Frame frame) override
std::chrono::microseconds currentBufferLoadingTime() const
AudioRenderer(const TimeController &tc, QAudioOutput *output)
void setOutput(QAudioOutput *output)
void updateOutput(const Codec *codec)
void initResempler(const Codec *codec)
float playbackRate() const
std::chrono::microseconds frameDelay(const Frame &frame) const
void setOutputInternal(QPointer< Output > &actual, Output *desired, ChangeHandler &&changeHandler)
void changeRendererTime(std::chrono::microseconds offset)
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
Combined button and popup list for selecting options.
QMediaFormat::AudioCodec codec
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
#define qDebug
[1]
Definition qlogging.h:160
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLenum GLsizei count
GLenum target
const GLfloat * tc
GLdouble s
[6]
Definition qopenglext.h:235
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
unsigned int quint32
Definition qtypes.h:45
int qint32
Definition qtypes.h:44
double qreal
Definition qtypes.h:92
QT_BEGIN_NAMESPACE typedef uchar * output
QFrame frame
[0]
bool isValid() const