@@ -279,12 +279,9 @@
* @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::uint32_t sample_rate, std::chrono::nanoseconds frame_duration):
mic_t(std::shared_ptr<audio_output_t> output, std::shared_ptr<audio_mixer_t> mixer):
output {std::move(output)},
mixer {std::move(mixer)},
frame_size {frame_size},
sample_rate {sample_rate},
frame_duration {frame_duration} {
mixer {std::move(mixer)} {
}
~mic_t() override {
@@ -304,7 +301,6 @@
platf::capture_e sample(std::vector<float> &frame_buffer) override {
switch (mixer->read(frame_buffer, sample_timeout)) {
case read_status_e::ok:
pace();
return platf::capture_e::ok;
case read_status_e::timeout:
return platf::capture_e::timeout;
@@ -316,57 +312,8 @@
}
private:
/**
* @brief Hand out frames at the rate they play, not the rate the guest wrote them.
* @details A guest writes its audio in whatever quantum its own stack uses, usually 10 ms or more,
* so frames reach Sunshine in bursts: two or three at once, then nothing for a while. The samples
* are right either way, which is why only a player hears the difference -- its jitter buffer is fed
* unevenly and crackles. A sound card paces a real capture; this paces ours.
*
* The schedule follows the frames handed out, and restarts whenever it has fallen more than a frame
* behind, so a stall doesn't make the frames after it race out.
*/
void pace() {
// A guest whose clock runs a shade fast would build a backlog that only the buffer overflowing
// would clear, turning smooth delivery into added delay. Handing frames out as fast as they come
// while more than a couple are waiting keeps the delay where it started.
if (mixer->buffered_frames() > 2 * frame_size) {
next_frame = {};
return;
}
const auto now = std::chrono::steady_clock::now();
if (next_frame == std::chrono::steady_clock::time_point {} || now - next_frame > frame_duration) {
next_frame = now + frame_duration;
return;
}
if (now < next_frame) {
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.
};
/**
@@ -417,8 +364,10 @@
}
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);
// frames go out as soon as the guest has written them: a client's jitter buffer uses every
// millisecond of head start it gets, and holding them back to an even cadence measurably doubled
// the packets it had to conceal on a jittery link
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, sample_rate, frame_duration);
return std::make_unique<mic_t>(output, std::move(mixer));
}
/**