47AudioRenderer::AudioRenderer(
const TimeController &tc, QAudioOutput *output,
48 QAudioBufferOutput *bufferOutput,
bool pitchCompensation)
51 m_bufferOutput(bufferOutput),
52 m_pitchCompensation(pitchCompensation)
56 connect(output, &QAudioOutput::deviceChanged,
this, &AudioRenderer::onDeviceChanged);
57 connect(output, &QAudioOutput::volumeChanged,
this, &AudioRenderer::updateVolume);
58 connect(output, &QAudioOutput::mutedChanged,
this, &AudioRenderer::updateVolume);
120 if (!m_ioDevice || !m_audioFrameConverter)
125 auto firstFrameFlagGuard = qScopeGuard([&]() { m_firstFrameToSink =
false; });
128 m_bufferedData.offset, SteadyClock::now() };
130 if (!m_bufferedData.isValid()) {
131 if (!frame.isValid()) {
132 if (std::exchange(m_drained,
true))
135 const auto time = bufferLoadingTime(syncStamp);
137 qCDebug(qLcAudioRenderer) <<
"Draining AudioRenderer, time:" << time;
139 return { time.count() == 0, time };
143 m_audioFrameConverter->convert(frame.avFrame()),
147 if (m_bufferedData.isValid()) {
149 auto syncGuard = qScopeGuard([&]() { updateSynchronization(syncStamp, frame); });
151 const auto bytesWritten = m_ioDevice->write(m_bufferedData.data(), m_bufferedData.size());
153 m_bufferedData.offset += bytesWritten;
155 if (m_bufferedData.size() <= 0) {
161 const auto remainingDuration = durationForBytes(m_bufferedData.size());
164 std::min(remainingDuration + DurationBias, m_timings.actualBufferDuration / 2) };
175 if (frame.isValid()) {
176 Q_ASSERT(m_bufferOutputResampler);
179 QAudioBuffer buffer = m_bufferOutputResampler->resample(frame.avFrame());
180 emit m_bufferOutput->audioBufferReceived(buffer);
182 emit m_bufferOutput->audioBufferReceived({});
213 if (!m_pitchCompensation || qFuzzyCompare(playbackRate(), 1.0f)) {
214 m_audioFrameConverter = makeTrivialAudioFrameConverter(frame, m_sinkFormat, playbackRate());
216 m_audioFrameConverter =
217 makePitchShiftingAudioFrameConverter(frame, m_sinkFormat, playbackRate());
242 if (m_deviceChanged) {
244 m_audioFrameConverter.reset();
247 if (m_bufferOutput) {
248 if (m_bufferOutputChanged) {
249 m_bufferOutputChanged =
false;
250 m_bufferOutputResampler.reset();
253 if (!m_bufferOutputResampler) {
256 outputFormat = audioFormatFromFrame(frame);
257 m_bufferOutputResampler = createResampler(frame, outputFormat);
264 if (!m_sinkFormat.isValid()) {
265 m_sinkFormat = audioFormatFromFrame(frame);
266 m_sinkFormat.setChannelConfig(m_output->device().channelConfiguration());
271 m_sink = std::make_unique<QAudioSink>(m_output->device(), m_sinkFormat);
273 m_sink->setBufferSize(m_sinkFormat.bytesForDuration(DesiredBufferTime.count()));
274 m_ioDevice = m_sink->start();
275 m_firstFrameToSink =
true;
277 connect(m_sink.get(), &QAudioSink::stateChanged,
this,
278 &AudioRenderer::onAudioSinkStateChanged);
280 m_timings.actualBufferDuration = durationForBytes(m_sink->bufferSize());
281 m_timings.maxSoundDelay = qMin(MaxDesiredBufferTime,
282 m_timings.actualBufferDuration - MinDesiredFreeBufferTime);
283 m_timings.minSoundDelay = MinDesiredBufferTime;
285 Q_ASSERT(DurationBias < m_timings.minSoundDelay
286 && m_timings.maxSoundDelay < m_timings.actualBufferDuration);
289 if (!m_audioFrameConverter)
290 initAudioFrameConverter(frame);
295 if (!frame.isValid())
300 const auto bufferLoadingTime =
this->bufferLoadingTime(stamp);
301 const auto currentFrameDelay = frameDelay(frame, stamp.timePoint);
302 const auto writtenTime = durationForBytes(stamp.bufferBytesWritten);
303 const auto soundDelay = currentFrameDelay + bufferLoadingTime - writtenTime;
305 auto synchronize = [&](microseconds fixedDelay, microseconds targetSoundDelay) {
308 changeRendererTime(fixedDelay - targetSoundDelay);
309 if (qLcAudioRenderer().isDebugEnabled()) {
311 qCDebug(qLcAudioRenderer)
312 <<
"Change rendering time:"
313 <<
"\n First frame:" << m_firstFrameToSink
314 <<
"\n Delay (frame+buffer-written):" << currentFrameDelay <<
"+"
315 << bufferLoadingTime <<
"-"
316 << writtenTime <<
"="
318 <<
"\n Fixed delay:" << fixedDelay
319 <<
"\n Target delay:" << targetSoundDelay
320 <<
"\n Buffer durations (min/max/limit):" << m_timings.minSoundDelay
321 << m_timings.maxSoundDelay
322 << m_timings.actualBufferDuration
323 <<
"\n Audio sink state:" << stamp.audioSinkState;
328 const auto loadingType = soundDelay > m_timings.maxSoundDelay ? BufferLoadingInfo::High
329 : soundDelay < m_timings.minSoundDelay ? BufferLoadingInfo::Low
330 : BufferLoadingInfo::Moderate;
332 if (loadingType != m_bufferLoadingInfo.type) {
336 m_bufferLoadingInfo = { loadingType, stamp.timePoint, soundDelay };
341 const auto shouldHandleIdle = stamp.audioSinkState == QAudio::IdleState && !isHigh;
343 auto &fixedDelay = m_bufferLoadingInfo.delay;
345 fixedDelay = shouldHandleIdle ? soundDelay
346 : isHigh ? qMin(soundDelay, fixedDelay)
347 : qMax(soundDelay, fixedDelay);
349 if (stamp.timePoint - m_bufferLoadingInfo.timePoint > BufferLoadingMeasureTime
350 || (m_firstFrameToSink && isHigh) || shouldHandleIdle) {
351 const auto targetDelay = isHigh
352 ? (m_timings.maxSoundDelay + m_timings.minSoundDelay) / 2
353 : m_timings.minSoundDelay + DurationBias;
355 synchronize(fixedDelay, targetDelay);
356 m_bufferLoadingInfo = { BufferLoadingInfo::Moderate, stamp.timePoint, targetDelay };