From 6fed1510babb230fb598f1e098c3e20590738ed7 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 3 Aug 2026 10:01:05 +0200 Subject: [PATCH] test(android): report renderer stats even when the plane is silent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- clients/android/native/src/pad_audio.rs | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/clients/android/native/src/pad_audio.rs b/clients/android/native/src/pad_audio.rs index b979c748..1417522c 100644 --- a/clients/android/native/src/pad_audio.rs +++ b/clients/android/native/src/pad_audio.rs @@ -537,10 +537,34 @@ fn pump( ) { let mut mixer = QuadMixer::new(); let mut streams: [Option; 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 = Vec::with_capacity(MAX_FRAME_SAMPLES * 2); let mut out: Vec = 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}"),