diff --git a/crates/wzp-video/src/decoder.rs b/crates/wzp-video/src/decoder.rs index a0cf6fc..b6d5e2d 100644 --- a/crates/wzp-video/src/decoder.rs +++ b/crates/wzp-video/src/decoder.rs @@ -12,4 +12,9 @@ pub trait VideoDecoder: Send { /// Returns `Ok(Some(frame))` when a frame is ready, `Ok(None)` if more /// data is needed (e.g., for reordering), or an error. fn decode(&mut self, access_unit: &[u8]) -> Result, VideoError>; + + /// Compact implementation-specific state useful for field diagnostics. + fn debug_snapshot(&self) -> Option { + None + } } diff --git a/crates/wzp-video/src/mediacodec.rs b/crates/wzp-video/src/mediacodec.rs index f60f64d..b6e22a5 100644 --- a/crates/wzp-video/src/mediacodec.rs +++ b/crates/wzp-video/src/mediacodec.rs @@ -359,6 +359,17 @@ impl VideoDecoder for MediaCodecDecoder { Err(VideoError::NotInitialized) } } + + fn debug_snapshot(&self) -> Option { + #[cfg(target_os = "android")] + { + media_codec_debug_snapshot(self.codec.as_ref()) + } + #[cfg(not(target_os = "android"))] + { + None + } + } } // ============================================================================ @@ -847,6 +858,17 @@ impl VideoDecoder for MediaCodecHevcDecoder { Err(VideoError::NotInitialized) } } + + fn debug_snapshot(&self) -> Option { + #[cfg(target_os = "android")] + { + media_codec_debug_snapshot(self.codec.as_ref()) + } + #[cfg(not(target_os = "android"))] + { + None + } + } } /// Android MediaCodec AV1 decoder. @@ -987,6 +1009,35 @@ impl VideoDecoder for MediaCodecAv1Decoder { Err(VideoError::NotInitialized) } } + + fn debug_snapshot(&self) -> Option { + #[cfg(target_os = "android")] + { + media_codec_debug_snapshot(self.codec.as_ref()) + } + #[cfg(not(target_os = "android"))] + { + None + } + } +} + +#[cfg(target_os = "android")] +fn media_codec_debug_snapshot(codec: Option<&MediaCodec>) -> Option { + let codec = codec?; + let format = codec.output_format(); + Some(format!( + "color_format={:?} width={:?} height={:?} stride={:?} slice_height={:?} crop=({:?},{:?},{:?},{:?})", + format.i32("color-format"), + format.i32("width"), + format.i32("height"), + format.i32("stride"), + format.i32("slice-height"), + format.i32("crop-left"), + format.i32("crop-top"), + format.i32("crop-right"), + format.i32("crop-bottom"), + )) } #[cfg(target_os = "android")] diff --git a/desktop/src-tauri/src/engine.rs b/desktop/src-tauri/src/engine.rs index a77dbde..4c220c3 100644 --- a/desktop/src-tauri/src/engine.rs +++ b/desktop/src-tauri/src/engine.rs @@ -177,6 +177,10 @@ fn i420_sample(data: &[u8], width: u32, height: u32) -> serde_json::Value { }) } +fn should_log_video_sample(frame_no: u64, is_keyframe: bool) -> bool { + frame_no <= 5 || is_keyframe || frame_no % 30 == 0 +} + /// Resolve a quality string from the UI to a QualityProfile. /// Returns None for "auto" (use default adaptive behavior). fn resolve_quality(quality: &str) -> Option { @@ -1321,7 +1325,7 @@ impl CallEngine { }), ); } - if video_reassembled_samples <= 5 { + if should_log_video_sample(video_reassembled_samples, is_kf) { crate::emit_call_debug( &recv_app, "video:reassembled_frame", @@ -1401,6 +1405,8 @@ impl CallEngine { "yuv_bytes": yuv_frame.data.len(), "jpeg_ok": jpeg_ok, "platform": "android", + "source_is_keyframe": is_kf, + "decoder_debug": dec.debug_snapshot(), "i420_sample": i420_sample( &yuv_frame.data, yuv_frame.width, @@ -1409,7 +1415,7 @@ impl CallEngine { }), ); } - if video_decoded_samples <= 5 { + if should_log_video_sample(video_decoded_samples, is_kf) { crate::emit_call_debug( &recv_app, "video:decoded_frame_sample", @@ -1422,6 +1428,8 @@ impl CallEngine { "yuv_bytes": yuv_frame.data.len(), "jpeg_ok": jpeg_ok, "platform": "android", + "source_is_keyframe": is_kf, + "decoder_debug": dec.debug_snapshot(), "i420_sample": i420_sample( &yuv_frame.data, yuv_frame.width, @@ -2608,7 +2616,7 @@ impl CallEngine { }), ); } - if video_reassembled_samples <= 5 { + if should_log_video_sample(video_reassembled_samples, is_kf) { crate::emit_call_debug( &recv_app, "video:reassembled_frame", @@ -2692,6 +2700,8 @@ impl CallEngine { "yuv_bytes": yuv_frame.data.len(), "jpeg_ok": jpeg_ok, "platform": "desktop", + "source_is_keyframe": is_kf, + "decoder_debug": dec.debug_snapshot(), "i420_sample": i420_sample( &yuv_frame.data, yuv_frame.width, @@ -2700,7 +2710,7 @@ impl CallEngine { }), ); } - if video_decoded_samples <= 5 { + if should_log_video_sample(video_decoded_samples, is_kf) { crate::emit_call_debug( &recv_app, "video:decoded_frame_sample", @@ -2713,6 +2723,8 @@ impl CallEngine { "yuv_bytes": yuv_frame.data.len(), "jpeg_ok": jpeg_ok, "platform": "desktop", + "source_is_keyframe": is_kf, + "decoder_debug": dec.debug_snapshot(), "i420_sample": i420_sample( &yuv_frame.data, yuv_frame.width,