ref:f7bd16c7e722547dbbc79cfb0ef1d4a219326087

test(linux): log what pacing the guest's audio costs

Pacing trades a little delay for even delivery, so the log says how much: the time each frame waits and how much audio is queued behind it, every five seconds at debug level. On the desktop guest it holds 2.6 ms per frame with 5 ms queued, steady, which is half a frame of wait and one frame in hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPNw4PCgkEfhyCjQT19wsb
SHA: f7bd16c7e722547dbbc79cfb0ef1d4a219326087
Author: Cole Christensen <cole.christensen@gmail.com>
Date: 2026-09-17 22:07
Parents: 0df5aa9
1 files changed +20 -3
Type
src/platform/linux/qemu/audio.cpp +20 −3
@@ -279,9 +279,10 @@
* @param output Output the mixer is attached to.
* @param mixer Attached mixer.
*/
mic_t(std::shared_ptr<audio_output_t> output, std::shared_ptr<audio_mixer_t> mixer, std::uint32_t frame_size, std::chrono::nanoseconds frame_duration):
mic_t(std::shared_ptr<audio_output_t> output, std::shared_ptr<audio_mixer_t> mixer, std::uint32_t frame_size, std::uint32_t sample_rate, std::chrono::nanoseconds frame_duration):
output {std::move(output)},
mixer {std::move(mixer)},
frame_size {frame_size},
sample_rate {sample_rate},
frame_duration {frame_duration} {
}
@@ -340,14 +341,30 @@
return;
}
if (now < next_frame) {
std::this_thread::sleep_for(next_frame - now);
const auto wait = next_frame - now;
waited += wait;
std::this_thread::sleep_for(wait);
}
next_frame += frame_duration;
// what pacing costs: how long frames wait and how much audio is queued behind them
if (++paced_frames % report_every == 0) {
const auto queued = mixer->buffered_frames() * std::chrono::nanoseconds {std::chrono::seconds {1}} / sample_rate;
BOOST_LOG(debug) << "qemu: audio pacing held "sv
<< std::chrono::duration_cast<std::chrono::microseconds>(waited).count() / report_every
<< " us per frame over the last "sv << report_every << ", with "sv
<< std::chrono::duration_cast<std::chrono::microseconds>(queued).count() / 1000.0 << " ms queued"sv;
waited = {};
}
}
std::shared_ptr<audio_output_t> output; ///< Keeps the listener registered.
std::shared_ptr<audio_mixer_t> mixer; ///< Converts and buffers the guest audio.
std::uint32_t frame_size; ///< Frames handed out per read.
std::uint32_t sample_rate; ///< Frames per second of stream time.
std::chrono::nanoseconds waited {}; ///< Time spent waiting since the last report.
std::uint64_t paced_frames {0}; ///< Frames handed out through the schedule.
static constexpr std::uint64_t report_every = 1000; ///< Frames between reports, 5 s at 5 ms each.
std::chrono::nanoseconds frame_duration; ///< How long one frame of samples plays for.
std::chrono::steady_clock::time_point next_frame {}; ///< When the next frame is due out.
};
@@ -401,7 +418,7 @@
BOOST_LOG(info) << "qemu: streaming guest audio as "sv << channels << " channel(s) at "sv << sample_rate << " Hz"sv << (continuous ? ", continuous"sv : ""sv);
auto mixer = output->attach({mapping, (std::size_t) channels}, sample_rate, frame_size, continuous);
const auto frame_duration = std::chrono::nanoseconds {std::chrono::seconds {1}} * frame_size / sample_rate;
return std::make_unique<mic_t>(output, std::move(mixer), frame_size, frame_duration);
return std::make_unique<mic_t>(output, std::move(mixer), frame_size, sample_rate, frame_duration);
}
/**