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"
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;
20using namespace std::chrono;
21
22namespace {
23constexpr auto DesiredBufferTime = 110000us;
24constexpr auto MinDesiredBufferTime = 22000us;
25constexpr auto MaxDesiredBufferTime = 64000us;
26constexpr auto MinDesiredFreeBufferTime = 10000us;
27
28// It might be changed with #ifdef, as on Linux, QPulseAudioSink has quite unstable timings,
29// and it needs much more time to make sure that the buffer is overloaded.
30constexpr auto BufferLoadingMeasureTime = 400ms;
31
32constexpr auto DurationBias = 2ms; // avoids extra timer events
33
34qreal sampleRateFactor() {
35 // Test purposes:
36 //
37 // The env var describes a factor for the sample rate of
38 // audio data that we feed to the audio sink.
39 //
40 // In some cases audio sink might consume data slightly slower or faster than expected;
41 // even though the synchronization in the audio renderer is supposed to handle it,
42 // it makes sense to experiment with QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR != 1.
43 //
44 // Set QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR > 1 (e.g. 1.01 - 1.1) to test high buffer loading
45 // or try compensating too fast data consumption by the audio sink.
46 // Set QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR < 1 to test low buffer loading
47 // or try compensating too slow data consumption by the audio sink.
48
49
50 static const qreal result = []() {
51 const auto sampleRateFactorStr = qEnvironmentVariable("QT_MEDIA_PLAYER_AUDIO_SAMPLE_RATE_FACTOR");
52 bool ok = false;
53 const auto result = sampleRateFactorStr.toDouble(&ok);
54 return ok ? result : 1.;
55 }();
56
57 return result;
58}
59} // namespace
60
62 : Renderer(tc), m_output(output)
63{
64 if (output) {
65 // TODO: implement the signals in QPlatformAudioOutput and connect to them, QTBUG-112294
69 }
70}
71
76
81
83{
84 if (m_sink)
85 m_sink->setVolume(m_output->isMuted() ? 0.f : m_output->volume());
86}
87
89{
90 m_deviceChanged = true;
91}
92
94{
95 if (frame.isValid())
96 updateOutput(frame.codec());
97
98 if (!m_ioDevice || !m_resampler)
99 return {};
100
101 Q_ASSERT(m_sink);
102
103 auto firstFrameFlagGuard = qScopeGuard([&]() { m_firstFrame = false; });
104
105 const SynchronizationStamp syncStamp{ m_sink->state(), m_sink->bytesFree(),
106 m_bufferedData.offset, Clock::now() };
107
108 if (!m_bufferedData.isValid()) {
109 if (!frame.isValid()) {
110 if (std::exchange(m_drained, true))
111 return {};
112
113 const auto time = bufferLoadingTime(syncStamp);
114
115 qCDebug(qLcAudioRenderer) << "Draining AudioRenderer, time:" << time;
116
117 return { time.count() == 0, time };
118 }
119
120 m_bufferedData = { m_resampler->resample(frame.avFrame()) };
121 }
122
123 if (m_bufferedData.isValid()) {
124 // synchronize after "QIODevice::write" to deliver audio data to the sink ASAP.
125 auto syncGuard = qScopeGuard([&]() { updateSynchronization(syncStamp, frame); });
126
127 const auto bytesWritten = m_ioDevice->write(m_bufferedData.data(), m_bufferedData.size());
128
129 m_bufferedData.offset += bytesWritten;
130
131 if (m_bufferedData.size() <= 0) {
132 m_bufferedData = {};
133
134 return {};
135 }
136
137 const auto remainingDuration = durationForBytes(m_bufferedData.size());
138
139 return { false,
140 std::min(remainingDuration + DurationBias, m_timings.actualBufferDuration / 2) };
141 }
142
143 return {};
144}
145
147{
148 m_resampler.reset();
149}
150
152{
153 constexpr auto MaxFixableInterval = 50; // ms
154
155 const auto interval = Renderer::timerInterval();
156
157 if (m_firstFrame || !m_sink || m_sink->state() != QAudio::IdleState
158 || interval > MaxFixableInterval)
159 return interval;
160
161 return 0;
162}
163
165{
166 m_firstFrame = true;
168}
169
171{
172 // We recreate resampler whenever format is changed
173
174 /* AVSampleFormat requiredFormat =
175 QFFmpegMediaFormatInfo::avSampleFormat(m_format.sampleFormat());
176
177 #if QT_FFMPEG_OLD_CHANNEL_LAYOUT
178 qCDebug(qLcAudioRenderer) << "init resampler" << requiredFormat
179 << codec->stream()->codecpar->channels;
180 #else
181 qCDebug(qLcAudioRenderer) << "init resampler" << requiredFormat
182 << codec->stream()->codecpar->ch_layout.nb_channels;
183 #endif
184 */
185
186 auto resamplerFormat = m_format;
187 resamplerFormat.setSampleRate(
188 qRound(m_format.sampleRate() / playbackRate() * sampleRateFactor()));
189 m_resampler = std::make_unique<QFFmpegResampler>(codec, resamplerFormat);
190}
191
193{
194 qCDebug(qLcAudioRenderer) << "Free audio output";
195 if (m_sink) {
196 m_sink->reset();
197
198 // TODO: inestigate if it's enough to reset the sink without deleting
199 m_sink.reset();
200 }
201
202 m_ioDevice = nullptr;
203
204 m_bufferedData = {};
205 m_deviceChanged = false;
206 m_timings = {};
207 m_bufferLoadingInfo = {};
208}
209
211{
212 if (m_deviceChanged) {
213 freeOutput();
214 m_format = {};
215 m_resampler.reset();
216 }
217
218 if (!m_output)
219 return;
220
221 if (!m_format.isValid()) {
222 m_format =
224 m_format.setChannelConfig(m_output->device().channelConfiguration());
225 }
226
227 if (!m_sink) {
228 // Insert a delay here to test time offset synchronization, e.g. QThread::sleep(1)
229 m_sink = std::make_unique<QAudioSink>(m_output->device(), m_format);
230 updateVolume();
231 m_sink->setBufferSize(m_format.bytesForDuration(DesiredBufferTime.count()));
232 m_ioDevice = m_sink->start();
233 m_firstFrame = true;
234
235 connect(m_sink.get(), &QAudioSink::stateChanged, this,
237
238 m_timings.actualBufferDuration = durationForBytes(m_sink->bufferSize());
239 m_timings.maxSoundDelay = qMin(MaxDesiredBufferTime,
240 m_timings.actualBufferDuration - MinDesiredFreeBufferTime);
241 m_timings.minSoundDelay = MinDesiredBufferTime;
242
243 Q_ASSERT(DurationBias < m_timings.minSoundDelay
244 && m_timings.maxSoundDelay < m_timings.actualBufferDuration);
245 }
246
247 if (!m_resampler) {
249 }
250}
251
253{
254 if (!frame.isValid())
255 return;
256
257 Q_ASSERT(m_sink);
258
259 const auto bufferLoadingTime = this->bufferLoadingTime(stamp);
260 const auto currentFrameDelay = frameDelay(frame, stamp.timePoint);
261 const auto writtenTime = durationForBytes(stamp.bufferBytesWritten);
262 const auto soundDelay = currentFrameDelay + bufferLoadingTime - writtenTime;
263
264 auto synchronize = [&](microseconds fixedDelay, microseconds targetSoundDelay) {
265 // TODO: investigate if we need sample compensation here
266
267 changeRendererTime(fixedDelay - targetSoundDelay);
268 if (qLcAudioRenderer().isDebugEnabled()) {
269 // clang-format off
270 qCDebug(qLcAudioRenderer)
271 << "Change rendering time:"
272 << "\n First frame:" << m_firstFrame
273 << "\n Delay (frame+buffer-written):" << currentFrameDelay << "+"
274 << bufferLoadingTime << "-"
275 << writtenTime << "="
276 << soundDelay
277 << "\n Fixed delay:" << fixedDelay
278 << "\n Target delay:" << targetSoundDelay
279 << "\n Buffer durations (min/max/limit):" << m_timings.minSoundDelay
280 << m_timings.maxSoundDelay
281 << m_timings.actualBufferDuration
282 << "\n Audio sink state:" << stamp.audioSinkState;
283 // clang-format on
284 }
285 };
286
287 const auto loadingType = soundDelay > m_timings.maxSoundDelay ? BufferLoadingInfo::High
288 : soundDelay < m_timings.minSoundDelay ? BufferLoadingInfo::Low
290
291 if (loadingType != m_bufferLoadingInfo.type) {
292 // qCDebug(qLcAudioRenderer) << "Change buffer loading type:" <<
293 // m_bufferLoadingInfo.type
294 // << "->" << loadingType << "soundDelay:" << soundDelay;
295 m_bufferLoadingInfo = { loadingType, stamp.timePoint, soundDelay };
296 }
297
298 if (loadingType != BufferLoadingInfo::Moderate) {
299 const auto isHigh = loadingType == BufferLoadingInfo::High;
300 const auto shouldHandleIdle = stamp.audioSinkState == QAudio::IdleState && !isHigh;
301
302 auto &fixedDelay = m_bufferLoadingInfo.delay;
303
304 fixedDelay = shouldHandleIdle ? soundDelay
305 : isHigh ? qMin(soundDelay, fixedDelay)
306 : qMax(soundDelay, fixedDelay);
307
308 if (stamp.timePoint - m_bufferLoadingInfo.timePoint > BufferLoadingMeasureTime
309 || (m_firstFrame && isHigh) || shouldHandleIdle) {
310 const auto targetDelay = isHigh
311 ? (m_timings.maxSoundDelay + m_timings.minSoundDelay) / 2
312 : m_timings.minSoundDelay + DurationBias;
313
314 synchronize(fixedDelay, targetDelay);
315 m_bufferLoadingInfo = { BufferLoadingInfo::Moderate, stamp.timePoint, targetDelay };
316 }
317 }
318}
319
320microseconds AudioRenderer::bufferLoadingTime(const SynchronizationStamp &syncStamp) const
321{
322 Q_ASSERT(m_sink);
323
324 if (syncStamp.audioSinkState == QAudio::IdleState)
325 return microseconds(0);
326
327 const auto bytes = qMax(m_sink->bufferSize() - syncStamp.audioSinkBytesFree, 0);
328
329#ifdef Q_OS_ANDROID
330 // The hack has been added due to QAndroidAudioSink issues (QTBUG-118609).
331 // The method QAndroidAudioSink::bytesFree returns 0 or bufferSize, intermediate values are not
332 // available now; to be fixed.
333 if (bytes == 0)
334 return m_timings.minSoundDelay + MinDesiredBufferTime;
335#endif
336
337 return durationForBytes(bytes);
338}
339
345
347{
348 return microseconds(m_format.durationForBytes(static_cast<qint32>(bytes)));
349}
350
351} // namespace QFFmpeg
352
354
355#include "moc_qffmpegaudiorenderer_p.cpp"
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)
void stateChanged(QAudio::State state)
This signal is emitted when the device state has changed.
static QAudioFormat audioFormatFromCodecParameters(AVCodecParameters *codecPar)
Microseconds durationForBytes(qsizetype bytes) const
RenderingResult renderInternal(Frame frame) override
int timerInterval() const override
AudioRenderer(const TimeController &tc, QAudioOutput *output)
void updateSynchronization(const SynchronizationStamp &stamp, const Frame &frame)
void onAudioSinkStateChanged(QAudio::State state)
void setOutput(QAudioOutput *output)
Microseconds bufferLoadingTime(const SynchronizationStamp &syncStamp) const
void updateOutput(const Codec *codec)
void initResempler(const Codec *codec)
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.
QMediaFormat::AudioCodec codec
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLsizei GLuint GLint * bytesWritten
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)
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]