47AudioRenderer::AudioRenderer(
const PlaybackEngineObjectID &id,
const TimeController &tc,
48 QAudioOutput *output, QAudioBufferOutput *bufferOutput,
49 bool pitchCompensation)
52 m_bufferOutput(bufferOutput),
53 m_pitchCompensation(pitchCompensation)
57 connect(output, &QAudioOutput::deviceChanged,
this, &AudioRenderer::onDeviceChanged);
58 connect(output, &QAudioOutput::volumeChanged,
this, &AudioRenderer::updateVolume);
59 connect(output, &QAudioOutput::mutedChanged,
this, &AudioRenderer::updateVolume);
121 if (!m_ioDevice || !m_audioFrameConverter)
126 auto firstFrameFlagGuard = qScopeGuard([&]() { m_firstFrameToSink =
false; });
129 m_bufferedData.offset, SteadyClock::now() };
131 if (!m_bufferedData.isValid()) {
132 if (!frame.isValid()) {
133 if (std::exchange(m_drained,
true))
136 const auto time = bufferLoadingTime(syncStamp);
138 qCDebug(qLcAudioRenderer) <<
"Draining AudioRenderer, time:" << time;
140 return { time.count() == 0, time };
144 m_audioFrameConverter->convert(frame.avFrame()),
148 if (m_bufferedData.isValid()) {
150 auto syncGuard = qScopeGuard([&]() { updateSynchronization(syncStamp, frame); });
152 const auto bytesWritten = m_ioDevice->write(m_bufferedData.data(), m_bufferedData.size());
154 m_bufferedData.offset += bytesWritten;
156 if (m_bufferedData.size() <= 0) {
162 const auto remainingDuration = durationForBytes(m_bufferedData.size());
165 std::min(remainingDuration + DurationBias, m_timings.actualBufferDuration / 2) };
176 if (frame.isValid()) {
177 Q_ASSERT(m_bufferOutputResampler);
180 QAudioBuffer buffer = m_bufferOutputResampler->resample(frame.avFrame());
181 emit m_bufferOutput->audioBufferReceived(buffer);
183 emit m_bufferOutput->audioBufferReceived({});
223 if (!m_pitchCompensation || qFuzzyCompare(playbackRate(), 1.0f)) {
224 m_audioFrameConverter = makeTrivialAudioFrameConverter(frame, m_sinkFormat, playbackRate());
226 m_audioFrameConverter =
227 makePitchShiftingAudioFrameConverter(frame, m_sinkFormat, playbackRate());
252 if (m_deviceChanged) {
254 m_audioFrameConverter.reset();
257 if (m_bufferOutput) {
258 if (m_bufferOutputChanged) {
259 m_bufferOutputChanged =
false;
260 m_bufferOutputResampler.reset();
263 if (!m_bufferOutputResampler) {
266 outputFormat = audioFormatFromFrame(frame);
267 m_bufferOutputResampler = createResampler(frame, outputFormat);
274 if (!m_sinkFormat.isValid()) {
275 m_sinkFormat = audioFormatFromFrame(frame);
276 m_sinkFormat.setChannelConfig(m_output->device().channelConfiguration());
281 m_sink = std::make_unique<QAudioSink>(m_output->device(), m_sinkFormat);
283 m_sink->setBufferSize(m_sinkFormat.bytesForDuration(DesiredBufferTime.count()));
284 m_ioDevice = m_sink->start();
285 m_firstFrameToSink =
true;
287 connect(m_sink.get(), &QAudioSink::stateChanged,
this,
288 &AudioRenderer::onAudioSinkStateChanged);
290 m_timings.actualBufferDuration = durationForBytes(m_sink->bufferSize());
291 m_timings.maxSoundDelay = qMin(MaxDesiredBufferTime,
292 m_timings.actualBufferDuration - MinDesiredFreeBufferTime);
293 m_timings.minSoundDelay = MinDesiredBufferTime;
295 Q_ASSERT(DurationBias < m_timings.minSoundDelay
296 && m_timings.maxSoundDelay < m_timings.actualBufferDuration);
299 if (!m_audioFrameConverter)
300 initAudioFrameConverter(frame);
305 if (!frame.isValid())
310 const auto bufferLoadingTime =
this->bufferLoadingTime(stamp);
311 const auto currentFrameDelay = frameDelay(frame, stamp.timePoint);
312 const auto writtenTime = durationForBytes(stamp.bufferBytesWritten);
313 const auto soundDelay = currentFrameDelay + bufferLoadingTime - writtenTime;
315 auto synchronize = [&](microseconds fixedDelay, microseconds targetSoundDelay) {
318 changeRendererTime(fixedDelay - targetSoundDelay);
319 if (qLcAudioRenderer().isDebugEnabled()) {
321 qCDebug(qLcAudioRenderer)
322 <<
"Change rendering time:"
323 <<
"\n First frame:" << m_firstFrameToSink
324 <<
"\n Delay (frame+buffer-written):" << currentFrameDelay <<
"+"
325 << bufferLoadingTime <<
"-"
326 << writtenTime <<
"="
328 <<
"\n Fixed delay:" << fixedDelay
329 <<
"\n Target delay:" << targetSoundDelay
330 <<
"\n Buffer durations (min/max/limit):" << m_timings.minSoundDelay
331 << m_timings.maxSoundDelay
332 << m_timings.actualBufferDuration
333 <<
"\n Audio sink state:" << stamp.audioSinkState;
338 const auto loadingType = soundDelay > m_timings.maxSoundDelay ? BufferLoadingInfo::High
339 : soundDelay < m_timings.minSoundDelay ? BufferLoadingInfo::Low
340 : BufferLoadingInfo::Moderate;
342 if (loadingType != m_bufferLoadingInfo.type) {
346 m_bufferLoadingInfo = { loadingType, stamp.timePoint, soundDelay };
351 const auto shouldHandleIdle = stamp.audioSinkState == QAudio::IdleState && !isHigh;
353 auto &fixedDelay = m_bufferLoadingInfo.delay;
355 fixedDelay = shouldHandleIdle ? soundDelay
356 : isHigh ? qMin(soundDelay, fixedDelay)
357 : qMax(soundDelay, fixedDelay);
359 if (stamp.timePoint - m_bufferLoadingInfo.timePoint > BufferLoadingMeasureTime
360 || (m_firstFrameToSink && isHigh) || shouldHandleIdle) {
361 const auto targetDelay = isHigh
362 ? (m_timings.maxSoundDelay + m_timings.minSoundDelay) / 2
363 : m_timings.minSoundDelay + DurationBias;
365 synchronize(fixedDelay, targetDelay);
366 m_bufferLoadingInfo = { BufferLoadingInfo::Moderate, stamp.timePoint, targetDelay };