47AudioRenderer::AudioRenderer(
const PlaybackEngineObjectID &id,
const TimeController &tc,
48 QAudioOutput *output, QAudioBufferOutput *bufferOutput,
49 bool pitchCompensation)
52 m_bufferOutput(bufferOutput),
53 m_pitchCompensation(pitchCompensation)
129 updateOutputs(frame);
133 const RenderingResult result = pushFrameToOutput(frame);
135 if (m_sessionCtx.lastFramePushDone)
136 pushFrameToBufferOutput(frame);
139 m_sessionCtx.lastFramePushDone = result.done;
146 if (!m_ioDevice || !m_audioFrameConverter)
151 auto firstFrameFlagGuard = qScopeGuard([&]() { m_firstFrameToSink =
false; });
154 m_sessionCtx.bufferedData.offset, SteadyClock::now() };
156 if (!m_sessionCtx.bufferedData.isValid()) {
157 if (!frame.isValid()) {
158 if (std::exchange(m_sessionCtx.drained,
true))
161 const auto time = bufferLoadingTime(syncStamp);
163 qCDebug(qLcAudioRenderer) <<
"Draining AudioRenderer, time:" << time;
165 return { time.count() == 0, time };
168 m_sessionCtx.bufferedData = {
169 m_audioFrameConverter->convert(frame.avFrame()),
173 if (m_sessionCtx.bufferedData.isValid()) {
175 auto syncGuard = qScopeGuard([&]() { updateSynchronization(syncStamp, frame); });
177 const auto bytesWritten = m_ioDevice->write(m_sessionCtx.bufferedData.data(),
178 m_sessionCtx.bufferedData.size());
180 m_sessionCtx.bufferedData.offset += bytesWritten;
182 if (m_sessionCtx.bufferedData.size() <= 0) {
183 m_sessionCtx.bufferedData = {};
188 const auto remainingDuration = durationForBytes(m_sessionCtx.bufferedData.size());
191 std::min(remainingDuration + DurationBias, m_timings.actualBufferDuration / 2) };
202 if (frame.isValid()) {
203 Q_ASSERT(m_bufferOutputResampler);
206 QAudioBuffer buffer = m_bufferOutputResampler->resample(frame.avFrame());
207 emit m_bufferOutput->audioBufferReceived(buffer);
209 emit m_bufferOutput->audioBufferReceived({});
249 if (!m_pitchCompensation || QtPrivate::fuzzyCompare(playbackRate(), 1.0f)) {
250 m_audioFrameConverter = makeTrivialAudioFrameConverter(frame, m_sinkFormat, playbackRate());
252 m_audioFrameConverter =
253 makePitchShiftingAudioFrameConverter(frame, m_sinkFormat, playbackRate());
278 if (m_deviceChanged) {
280 m_audioFrameConverter.reset();
283 if (m_bufferOutput) {
284 if (m_bufferOutputChanged) {
285 m_bufferOutputChanged =
false;
286 m_bufferOutputResampler.reset();
289 if (!m_bufferOutputResampler) {
292 outputFormat = audioFormatFromFrame(frame);
293 m_bufferOutputResampler = createResampler(frame, outputFormat);
300 if (!m_sinkFormat.isValid()) {
301 m_sinkFormat = audioFormatFromFrame(frame);
302 m_sinkFormat.setChannelConfig(m_output->device().channelConfiguration());
307 m_sink = std::make_unique<QAudioSink>(m_output->device(), m_sinkFormat);
309 m_sink->setBufferSize(m_sinkFormat.bytesForDuration(DesiredBufferTime.count()));
311 connect(m_sink.get(), &QAudioSink::stateChanged,
this,
312 &AudioRenderer::onAudioSinkStateChanged);
314 m_timings.actualBufferDuration = durationForBytes(m_sink->bufferSize());
315 m_timings.maxSoundDelay = qMin(MaxDesiredBufferTime,
316 m_timings.actualBufferDuration - MinDesiredFreeBufferTime);
317 m_timings.minSoundDelay = MinDesiredBufferTime;
319 Q_ASSERT(DurationBias < m_timings.minSoundDelay
320 && m_timings.maxSoundDelay < m_timings.actualBufferDuration);
324 m_ioDevice = m_sink->start();
325 m_firstFrameToSink =
true;
328 if (!m_audioFrameConverter)
329 initAudioFrameConverter(frame);
334 if (!frame.isValid())
339 const auto bufferLoadingTime =
this->bufferLoadingTime(stamp);
340 const auto currentFrameDelay = frameDelay(frame, stamp.timePoint);
341 const auto writtenTime = durationForBytes(stamp.bufferBytesWritten);
342 const auto soundDelay = currentFrameDelay + bufferLoadingTime - writtenTime;
344 auto synchronize = [&](microseconds fixedDelay, microseconds targetSoundDelay) {
347 changeRendererTime(fixedDelay - targetSoundDelay);
348 if (qLcAudioRenderer().isDebugEnabled()) {
350 qCDebug(qLcAudioRenderer)
351 <<
"Change rendering time:"
352 <<
"\n First frame:" << m_firstFrameToSink
353 <<
"\n Delay (frame+buffer-written):" << currentFrameDelay <<
"+"
354 << bufferLoadingTime <<
"-"
355 << writtenTime <<
"="
357 <<
"\n Fixed delay:" << fixedDelay
358 <<
"\n Target delay:" << targetSoundDelay
359 <<
"\n Buffer durations (min/max/limit):" << m_timings.minSoundDelay
360 << m_timings.maxSoundDelay
361 << m_timings.actualBufferDuration
362 <<
"\n Audio sink state:" << stamp.audioSinkState;
367 const auto loadingType = soundDelay > m_timings.maxSoundDelay ? BufferLoadingInfo::High
368 : soundDelay < m_timings.minSoundDelay ? BufferLoadingInfo::Low
369 : BufferLoadingInfo::Moderate;
371 if (loadingType != m_bufferLoadingInfo.type) {
375 m_bufferLoadingInfo = { loadingType, stamp.timePoint, soundDelay };
380 const auto shouldHandleIdle = stamp.audioSinkState == QAudio::IdleState && !isHigh;
382 auto &fixedDelay = m_bufferLoadingInfo.delay;
384 fixedDelay = shouldHandleIdle ? soundDelay
385 : isHigh ? qMin(soundDelay, fixedDelay)
386 : qMax(soundDelay, fixedDelay);
388 if (stamp.timePoint - m_bufferLoadingInfo.timePoint > BufferLoadingMeasureTime
389 || (m_firstFrameToSink && isHigh) || shouldHandleIdle) {
390 const auto targetDelay = isHigh
391 ? (m_timings.maxSoundDelay + m_timings.minSoundDelay) / 2
392 : m_timings.minSoundDelay + DurationBias;
394 synchronize(fixedDelay, targetDelay);
395 m_bufferLoadingInfo = { BufferLoadingInfo::Moderate, stamp.timePoint, targetDelay };