test(android): report renderer stats even when the plane is silent
ci / web (pull_request) Successful in 1m2s
apple / swift (pull_request) Successful in 1m16s
ci / docs-site (pull_request) Successful in 1m16s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 1m30s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m53s
android / android (pull_request) Successful in 4m7s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m30s
ci / rust (pull_request) Canceled after 4m44s

The renderer now reports once a second regardless of traffic — frames in,
samples decoded, peak level, frames written, underruns, short bytes.

The first version reported only after a frame arrived, which made the single
most diagnostic state unreportable: an idle plane and a dead renderer looked
identical (both silent). That cost a debugging round on real hardware, where the
absence of any line had to be triangulated against usbfs interface claims and
`dumpsys input` to work out which of the two it was.

The peak is of the decoded PCM, and it is the discriminator that matters: frames
arriving with peak=0 means the host's capture is hearing silence — a routing
problem upstream — whereas a non-zero peak means real signal is reaching the pad
and anything still wrong is downstream of the write.
This commit is contained in:
enricobuehler
2026-08-03 10:01:05 +02:00
parent 4fd240deab
commit 6fed1510ba
+36
View File
@@ -537,10 +537,34 @@ fn pump(
) {
let mut mixer = QuadMixer::new();
let mut streams: [Option<KindStream>; 2] = [None, None];
// Periodic accounting. Without it the only way to tell "the host is sending nothing" from
// "frames arrive but render silently" is to guess, and those two have completely different
// causes — one is host-side routing, the other is here.
let mut frames_in = 0u64;
let mut samples_in = 0u64;
let mut peak = 0i32;
let mut last_report = std::time::Instant::now();
let mut pcm: Vec<i16> = Vec::with_capacity(MAX_FRAME_SAMPLES * 2);
let mut out: Vec<i16> = Vec::with_capacity(MAX_BUFFER_FRAMES * PAD_CHANNELS);
while !stop.load(Ordering::Relaxed) {
// Report BEFORE the frame gate. Silence on the plane is a legitimate — and highly
// diagnostic — state: it means the host's capture hears nothing, which is a routing
// problem upstream rather than anything here. Reporting only when a frame arrives makes
// that state indistinguishable from the renderer being dead.
if last_report.elapsed() >= Duration::from_secs(1) {
let st = playback.stats();
log::info!(
"pad audio: {frames_in} frames in, {samples_in} samples, peak={peak}, \
{} written, {} underruns, {} short",
playback.frames_written(),
st.underruns,
st.short_bytes
);
last_report = std::time::Instant::now();
peak = 0;
}
let Some(frame) = client.next_pad_audio(Duration::from_millis(10)) else {
continue;
};
@@ -556,6 +580,7 @@ fn pump(
continue;
}
frames_in += 1;
let k = usize::from(frame.kind).min(1);
let st = match &mut streams[k] {
Some(s) => s,
@@ -589,6 +614,17 @@ fn pump(
match st.dec.decode(&frame.opus, &mut pcm, false) {
Ok(n) => {
st.frame_samples = n;
samples_in += n as u64;
// Peak of what actually decoded: distinguishes "frames arriving but silent"
// (a host-side routing problem) from "frames arriving with signal that is not
// reaching the actuators" (a problem here).
peak = peak.max(
pcm[..n * 2]
.iter()
.map(|s| i32::from(s.abs()))
.max()
.unwrap_or(0),
);
mixer.push(frame.kind, &pcm[..n * 2]);
}
Err(e) => log::debug!("pad audio: opus decode failed: {e}"),