Files
wz-phone/crates/wzp-android/src/stats.rs
Siavash Sameni 4af7c5f94c
Some checks failed
Build Release Binaries / build-amd64 (push) Failing after 3m56s
fix: AudioRing cursor desync + capture thread use-after-free
AudioRing (reader-detects-lap architecture):
- Writer NEVER touches read_pos — fixes SPSC invariant violation
- Reader self-corrects when lapped (snaps read_pos forward)
- Power-of-2 capacity (16384 = 341ms) with bitmask indexing
- Added overflow_count and underrun_count diagnostics
- Wired ring health into engine stats and periodic logging

Capture thread use-after-free (drain latch):
- Added CountDownLatch(2) to AudioPipeline
- Audio threads count down after exiting their loops
- teardown() awaits latch (200ms timeout) before destroy()
- Guarantees no in-flight JNI calls when native handle is freed
- stopAudio() no longer nulls pipeline (teardown handles it)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:28:34 +04:00

74 lines
2.4 KiB
Rust

//! Call statistics for the Android engine.
/// State of the call.
/// Serializes as integer for easy parsing on the Kotlin side:
/// 0=Idle, 1=Connecting, 2=Active, 3=Reconnecting, 4=Closed
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum CallState {
#[default]
Idle,
Connecting,
Active,
Reconnecting,
Closed,
}
impl serde::Serialize for CallState {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let n: u8 = match self {
CallState::Idle => 0,
CallState::Connecting => 1,
CallState::Active => 2,
CallState::Reconnecting => 3,
CallState::Closed => 4,
};
serializer.serialize_u8(n)
}
}
/// Aggregated call statistics, serializable for JNI bridge.
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct CallStats {
/// Current call state.
pub state: CallState,
/// Call duration in seconds.
pub duration_secs: f64,
/// Current quality tier (0=GOOD, 1=DEGRADED, 2=CATASTROPHIC).
pub quality_tier: u8,
/// Observed packet loss percentage.
pub loss_pct: f32,
/// Smoothed round-trip time in milliseconds.
pub rtt_ms: u32,
/// Jitter in milliseconds.
pub jitter_ms: u32,
/// Current jitter buffer depth in packets.
pub jitter_buffer_depth: usize,
/// Total frames encoded since call start.
pub frames_encoded: u64,
/// Total frames decoded since call start.
pub frames_decoded: u64,
/// Number of playout underruns (buffer empty when audio needed).
pub underruns: u64,
/// Frames recovered by FEC.
pub fec_recovered: u64,
/// Playout ring overflow count (reader was lapped by writer).
pub playout_overflows: u64,
/// Playout ring underrun count (reader found empty buffer).
pub playout_underruns: u64,
/// Capture ring overflow count.
pub capture_overflows: u64,
/// Current mic audio level (RMS of i16 samples, 0-32767).
pub audio_level: u32,
/// Number of participants in the room (from last RoomUpdate).
pub room_participant_count: u32,
/// Participant list (fingerprint + optional alias) serialized as JSON array.
pub room_participants: Vec<RoomMember>,
}
/// A room member entry, serialized into the stats JSON.
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct RoomMember {
pub fingerprint: String,
pub alias: Option<String>,
}