Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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"
8#include "private/qplatformaudiooutput_p.h"
9#include <QtCore/qloggingcategory.h>
10
11#include "qffmpegresampler_p.h"
13
15
16Q_STATIC_LOGGING_CATEGORY(qLcAudioRenderer, "qt.multimedia.ffmpeg.audiorenderer");
17
18namespace QFFmpeg {
19
20using namespace std::chrono_literals;
21using namespace std::chrono;
22
23namespace {
24constexpr auto DesiredBufferTime = 110000us;
25constexpr auto MinDesiredBufferTime = 22000us;
26constexpr auto MaxDesiredBufferTime = 64000us;
27constexpr auto MinDesiredFreeBufferTime = 10000us;
28
29// It might be changed with #ifdef, as on Linux, QPulseAudioSink has quite unstable timings,
30// and it needs much more time to make sure that the buffer is overloaded.
31constexpr auto BufferLoadingMeasureTime = 400ms;
32
33constexpr auto DurationBias = 2ms; // avoids extra timer events
34
35qreal sampleRateFactor() {
36 // Test purposes:
37 //
38 // The env var describes a factor for the sample rate of
39 // audio data that we feed to the audio sink.
40 //
41 // In some cases audio sink might consume data slightly slower or faster than expected;
42 // even though the synchronization in the audio renderer is supposed to handle it,
43 // it makes sense to experiment with QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR != 1.
44 //
45 // Set QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR > 1 (e.g. 1.01 - 1.1) to test high buffer loading
46 // or try compensating too fast data consumption by the audio sink.
47 // Set QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR < 1 to test low buffer loading
48 // or try compensating too slow data consumption by the audio sink.
49
50
51 static const qreal result = []() {
52 const auto sampleRateFactorStr = qEnvironmentVariable("QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR");
53 bool ok = false;
54 const auto result = sampleRateFactorStr.toDouble(&ok);
55 return ok ? result : 1.;
56 }();
57
58 return result;
59}
60
61QAudioFormat audioFormatFromFrame(const Frame &frame)
62{
64 frame.codec()->stream()->codecpar);
65}
66
67std::unique_ptr<QFFmpegResampler> createResampler(const Frame &frame,
68 const QAudioFormat &outputFormat)
69{
70 return std::make_unique<QFFmpegResampler>(frame.codec(), outputFormat, frame.pts());
71}
72
73} // namespace
74
76 QAudioBufferOutput *bufferOutput)
77 : Renderer(tc), m_output(output), m_bufferOutput(bufferOutput)
78{
79 if (output) {
80 // TODO: implement the signals in QPlatformAudioOutput and connect to them, QTBUG-112294
84 }
85}
86
91
93{
94 setOutputInternal(m_bufferOutput, bufferOutput,
95 [this](QAudioBufferOutput *) { m_bufferOutputChanged = true; });
96}
97
102
104{
105 if (m_sink)
106 m_sink->setVolume(m_output->isMuted() ? 0.f : m_output->volume());
107}
108
110{
111 m_deviceChanged = true;
112}
113
115{
116 if (frame.isValid())
118
119 // push to sink first in order not to waste time on resampling
120 // for QAudioBufferOutput
122
123 if (m_lastFramePushDone)
125 // else // skip pushing the same data to QAudioBufferOutput
126
127 m_lastFramePushDone = result.done;
128
129 return result;
130}
131
133{
134 if (!m_ioDevice || !m_resampler)
135 return {};
136
137 Q_ASSERT(m_sink);
138
139 auto firstFrameFlagGuard = qScopeGuard([&]() { m_firstFrameToSink = false; });
140
141 const SynchronizationStamp syncStamp{ m_sink->state(), m_sink->bytesFree(),
142 m_bufferedData.offset, Clock::now() };
143
144 if (!m_bufferedData.isValid()) {
145 if (!frame.isValid()) {
146 if (std::exchange(m_drained, true))
147 return {};
148
149 const auto time = bufferLoadingTime(syncStamp);
150
151 qCDebug(qLcAudioRenderer) << "Draining AudioRenderer, time:" << time;
152
153 return { time.count() == 0, time };
154 }
155
156 m_bufferedData = { m_resampler->resample(frame.avFrame()) };
157 }
158
159 if (m_bufferedData.isValid()) {
160 // synchronize after "QIODevice::write" to deliver audio data to the sink ASAP.
161 auto syncGuard = qScopeGuard([&]() { updateSynchronization(syncStamp, frame); });
162
163 const auto bytesWritten = m_ioDevice->write(m_bufferedData.data(), m_bufferedData.size());
164
165 m_bufferedData.offset += bytesWritten;
166
167 if (m_bufferedData.size() <= 0) {
168 m_bufferedData = {};
169
170 return {};
171 }
172
173 const auto remainingDuration = durationForBytes(m_bufferedData.size());
174
175 return { false,
176 std::min(remainingDuration + DurationBias, m_timings.actualBufferDuration / 2) };
177 }
178
179 return {};
180}
181
183{
184 if (!m_bufferOutput)
185 return;
186
187 Q_ASSERT(m_bufferOutputResampler);
188
189 if (frame.isValid()) {
190 // TODO: get buffer from m_bufferedData if resample formats are equal
191 QAudioBuffer buffer = m_resampler->resample(frame.avFrame());
192 emit m_bufferOutput->audioBufferReceived(buffer);
193 } else {
194 emit m_bufferOutput->audioBufferReceived({});
195 }
196}
197
199{
200 m_resampler.reset();
201}
202
204{
205 constexpr auto MaxFixableInterval = 50; // ms
206
207 const auto interval = Renderer::timerInterval();
208
209 if (m_firstFrameToSink || !m_sink || m_sink->state() != QAudio::IdleState
210 || interval > MaxFixableInterval)
211 return interval;
212
213 return 0;
214}
215
217{
218 m_firstFrameToSink = true;
220}
221
223{
224 // We recreate resampler whenever format is changed
225
226 auto resamplerFormat = m_sinkFormat;
227 resamplerFormat.setSampleRate(
228 qRound(m_sinkFormat.sampleRate() / playbackRate() * sampleRateFactor()));
229 m_resampler = createResampler(frame, resamplerFormat);
230}
231
233{
234 qCDebug(qLcAudioRenderer) << "Free audio output";
235 if (m_sink) {
236 m_sink->reset();
237
238 // TODO: inestigate if it's enough to reset the sink without deleting
239 m_sink.reset();
240 }
241
242 m_ioDevice = nullptr;
243
244 m_bufferedData = {};
245 m_deviceChanged = false;
246 m_sinkFormat = {};
247 m_timings = {};
248 m_bufferLoadingInfo = {};
249}
250
252{
253 if (m_deviceChanged) {
254 freeOutput();
255 m_resampler.reset();
256 }
257
258 if (m_bufferOutput) {
259 if (m_bufferOutputChanged) {
260 m_bufferOutputChanged = false;
261 m_bufferOutputResampler.reset();
262 }
263
264 if (!m_bufferOutputResampler) {
265 QAudioFormat outputFormat = m_bufferOutput->format();
266 if (!outputFormat.isValid())
267 outputFormat = audioFormatFromFrame(frame);
268 m_bufferOutputResampler = createResampler(frame, outputFormat);
269 }
270 }
271
272 if (!m_output)
273 return;
274
275 if (!m_sinkFormat.isValid()) {
276 m_sinkFormat = audioFormatFromFrame(frame);
277 m_sinkFormat.setChannelConfig(m_output->device().channelConfiguration());
278 }
279
280 if (!m_sink) {
281 // Insert a delay here to test time offset synchronization, e.g. QThread::sleep(1)
282 m_sink = std::make_unique<QAudioSink>(m_output->device(), m_sinkFormat);
283 updateVolume();
284 m_sink->setBufferSize(m_sinkFormat.bytesForDuration(DesiredBufferTime.count()));
285 m_ioDevice = m_sink->start();
286 m_firstFrameToSink = true;
287
288 connect(m_sink.get(), &QAudioSink::stateChanged, this,
290
291 m_timings.actualBufferDuration = durationForBytes(m_sink->bufferSize());
292 m_timings.maxSoundDelay = qMin(MaxDesiredBufferTime,
293 m_timings.actualBufferDuration - MinDesiredFreeBufferTime);
294 m_timings.minSoundDelay = MinDesiredBufferTime;
295
296 Q_ASSERT(DurationBias < m_timings.minSoundDelay
297 && m_timings.maxSoundDelay < m_timings.actualBufferDuration);
298 }
299
300 if (!m_resampler)
302}
303
305{
306 if (!frame.isValid())
307 return;
308
309 Q_ASSERT(m_sink);
310
311 const auto bufferLoadingTime = this->bufferLoadingTime(stamp);
312 const auto currentFrameDelay = frameDelay(frame, stamp.timePoint);
313 const auto writtenTime = durationForBytes(stamp.bufferBytesWritten);
314 const auto soundDelay = currentFrameDelay + bufferLoadingTime - writtenTime;
315
316 auto synchronize = [&](microseconds fixedDelay, microseconds targetSoundDelay) {
317 // TODO: investigate if we need sample compensation here
318
319 changeRendererTime(fixedDelay - targetSoundDelay);
320 if (qLcAudioRenderer().isDebugEnabled()) {
321 // clang-format off
322 qCDebug(qLcAudioRenderer)
323 << "Change rendering time:"
324 << "\n First frame:" << m_firstFrameToSink
325 << "\n Delay (frame+buffer-written):" << currentFrameDelay << "+"
326 << bufferLoadingTime << "-"
327 << writtenTime << "="
328 << soundDelay
329 << "\n Fixed delay:" << fixedDelay
330 << "\n Target delay:" << targetSoundDelay
331 << "\n Buffer durations (min/max/limit):" << m_timings.minSoundDelay
332 << m_timings.maxSoundDelay
333 << m_timings.actualBufferDuration
334 << "\n Audio sink state:" << stamp.audioSinkState;
335 // clang-format on
336 }
337 };
338
339 const auto loadingType = soundDelay > m_timings.maxSoundDelay ? BufferLoadingInfo::High
340 : soundDelay < m_timings.minSoundDelay ? BufferLoadingInfo::Low
342
343 if (loadingType != m_bufferLoadingInfo.type) {
344 // qCDebug(qLcAudioRenderer) << "Change buffer loading type:" <<
345 // m_bufferLoadingInfo.type
346 // << "->" << loadingType << "soundDelay:" << soundDelay;
347 m_bufferLoadingInfo = { loadingType, stamp.timePoint, soundDelay };
348 }
349
350 if (loadingType != BufferLoadingInfo::Moderate) {
351 const auto isHigh = loadingType == BufferLoadingInfo::High;
352 const auto shouldHandleIdle = stamp.audioSinkState == QAudio::IdleState && !isHigh;
353
354 auto &fixedDelay = m_bufferLoadingInfo.delay;
355
356 fixedDelay = shouldHandleIdle ? soundDelay
357 : isHigh ? qMin(soundDelay, fixedDelay)
358 : qMax(soundDelay, fixedDelay);
359
360 if (stamp.timePoint - m_bufferLoadingInfo.timePoint > BufferLoadingMeasureTime
361 || (m_firstFrameToSink && isHigh) || shouldHandleIdle) {
362 const auto targetDelay = isHigh
363 ? (m_timings.maxSoundDelay + m_timings.minSoundDelay) / 2
364 : m_timings.minSoundDelay + DurationBias;
365
366 synchronize(fixedDelay, targetDelay);
367 m_bufferLoadingInfo = { BufferLoadingInfo::Moderate, stamp.timePoint, targetDelay };
368 }
369 }
370}
371
372microseconds AudioRenderer::bufferLoadingTime(const SynchronizationStamp &syncStamp) const
373{
374 Q_ASSERT(m_sink);
375
376 if (syncStamp.audioSinkState == QAudio::IdleState)
377 return microseconds(0);
378
379 const auto bytes = qMax(m_sink->bufferSize() - syncStamp.audioSinkBytesFree, 0);
380
381#ifdef Q_OS_ANDROID
382 // The hack has been added due to QAndroidAudioSink issues (QTBUG-118609).
383 // The method QAndroidAudioSink::bytesFree returns 0 or bufferSize, intermediate values are not
384 // available now; to be fixed.
385 if (bytes == 0)
386 return m_timings.minSoundDelay + MinDesiredBufferTime;
387#endif
388
389 return durationForBytes(bytes);
390}
391
397
399{
400 return microseconds(m_sinkFormat.durationForBytes(static_cast<qint32>(bytes)));
401}
402
403} // namespace QFFmpeg
404
406
407#include "moc_qffmpegaudiorenderer_p.cpp"
\inmodule QtMultimedia
QAudioFormat format() const
Gets the audio format specified in the constructor.
void audioBufferReceived(const QAudioBuffer &buffer)
Signals that a new audio buffer has been received from \l QMediaPlayer.
\inmodule QtMultimedia
QAudioFormat::ChannelConfig channelConfiguration() const
Returns the channel configuration of the device.
The QAudioFormat class stores audio stream parameter information.
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)
void stateChanged(QAudio::State state)
This signal is emitted when the device state has changed.
static QAudioFormat audioFormatFromCodecParameters(AVCodecParameters *codecPar)
AudioRenderer(const TimeController &tc, QAudioOutput *output, QAudioBufferOutput *bufferOutput)
RenderingResult pushFrameToOutput(const Frame &frame)
Microseconds durationForBytes(qsizetype bytes) const
RenderingResult renderInternal(Frame frame) override
void updateOutputs(const Frame &frame)
int timerInterval() const override
void initResempler(const Frame &frame)
void updateSynchronization(const SynchronizationStamp &stamp, const Frame &frame)
void onAudioSinkStateChanged(QAudio::State state)
void setOutput(QAudioOutput *output)
void pushFrameToBufferOutput(const Frame &frame)
Microseconds bufferLoadingTime(const SynchronizationStamp &syncStamp) const
void scheduleNextStep(bool allowDoImmediatelly=true)
int timerInterval() const override
void onPauseChanged() override
float playbackRate() const
std::chrono::microseconds frameDelay(const Frame &frame, TimePoint timePoint=Clock::now()) 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:2960
else opt state
[0]
State
Definition qaudio.h:29
@ IdleState
Definition qaudio.h:29
Combined button and popup list for selecting options.
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
QT_BEGIN_NAMESPACE constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:19
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:21
GLenum GLsizei GLuint GLint * bytesWritten
GLenum GLuint buffer
GLdouble s
[6]
Definition qopenglext.h:235
const GLfloat * tc
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
#define emit
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
double qreal
Definition qtypes.h:187
QT_BEGIN_NAMESPACE typedef uchar * output
QFrame frame
[0]