diff --git a/crates/wzp-video/src/mediacodec.rs b/crates/wzp-video/src/mediacodec.rs index 8e1d43b..d0d3b1b 100644 --- a/crates/wzp-video/src/mediacodec.rs +++ b/crates/wzp-video/src/mediacodec.rs @@ -992,8 +992,13 @@ fn extract_vps_sps_pps(annex_b: &[u8]) -> HevcParameterSets { /// (4-byte start codes `0x00 0x00 0x00 0x01`). #[allow(dead_code)] fn avcc_to_annexb(data: &[u8]) -> Vec { + if starts_with_annex_b_start_code(data) { + return data.to_vec(); + } + let mut out = Vec::with_capacity(data.len() + data.len() / 4); let mut offset = 0; + let mut saw_nal = false; while offset + 4 <= data.len() { let nal_len = u32::from_be_bytes([ data[offset], @@ -1003,15 +1008,20 @@ fn avcc_to_annexb(data: &[u8]) -> Vec { ]) as usize; offset += 4; if offset + nal_len > data.len() { - break; + return if saw_nal { out } else { data.to_vec() }; } out.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); out.extend_from_slice(&data[offset..offset + nal_len]); offset += nal_len; + saw_nal = true; } out } +fn starts_with_annex_b_start_code(data: &[u8]) -> bool { + data.starts_with(&[0x00, 0x00, 0x01]) || data.starts_with(&[0x00, 0x00, 0x00, 0x01]) +} + /// Parse an Annex-B access unit and return the first SPS and PPS found. #[allow(dead_code)] fn extract_sps_pps(annex_b: &[u8]) -> (Option>, Option>) { @@ -1163,6 +1173,16 @@ mod tests { assert_eq!(annex_b, expected); } + #[test] + fn avcc_to_annexb_passes_through_annexb() { + let annex_b = vec![ + 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xC0, 0x1E, + 0x00, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x21, + ]; + + assert_eq!(avcc_to_annexb(&annex_b), annex_b); + } + #[test] fn hevc_mediacodec_encoder_returns_not_initialized_on_non_android() { let enc = MediaCodecHevcEncoder::new(1280, 720, 2_000_000); diff --git a/desktop/src-tauri/src/engine.rs b/desktop/src-tauri/src/engine.rs index 68a7679..4591894 100644 --- a/desktop/src-tauri/src/engine.rs +++ b/desktop/src-tauri/src/engine.rs @@ -1184,6 +1184,7 @@ impl CallEngine { let mut video_decoder_codec: Option = None; let mut video_first_recv_logged = false; let mut video_first_reassembled_logged = false; + let mut video_reassembled_samples: u64 = 0; let mut video_first_decoded_logged = false; let mut video_decoder_buffering_count: u64 = 0; @@ -1217,6 +1218,7 @@ impl CallEngine { if let Some((codec_id, is_kf, frame)) = video_reassembler.push(&pkt) { + video_reassembled_samples += 1; if !video_first_reassembled_logged { video_first_reassembled_logged = true; crate::emit_call_debug( @@ -1231,6 +1233,20 @@ impl CallEngine { }), ); } + if video_reassembled_samples <= 5 { + crate::emit_call_debug( + &recv_app, + "video:reassembled_frame", + serde_json::json!({ + "t_ms": recv_t0.elapsed().as_millis() as u64, + "codec": format!("{:?}", codec_id), + "is_keyframe": is_kf, + "frame_bytes": frame.len(), + "frame_no": video_reassembled_samples, + "platform": "android", + }), + ); + } if video_decoder_codec != Some(codec_id) { crate::emit_call_debug( &recv_app, @@ -1778,6 +1794,7 @@ impl CallEngine { let mut first_camera_frame_logged = false; let mut camera_frames: u64 = 0; let mut empty_encodes: u64 = 0; + let mut encoded_frame_samples: u64 = 0; let mut wait_ticks: u64 = 0; encoder.request_keyframe(); crate::emit_call_debug( @@ -1894,6 +1911,26 @@ impl CallEngine { let pkts = wzp_video::transport::packetize_video_frame( &encoded, vid_codec, is_keyframe, &mut seq, ts_ms, ); + if encoded_frame_samples < 5 { + encoded_frame_samples += 1; + let packet_payload_bytes: usize = + pkts.iter().map(|pkt| pkt.payload.len()).sum(); + crate::emit_call_debug( + &vid_app, + "video:encoded_frame", + serde_json::json!({ + "t_ms": vid_t0.elapsed().as_millis() as u64, + "codec": format!("{:?}", vid_codec), + "camera_frames": camera_frames, + "encoded_bytes": encoded.len(), + "packet_payload_bytes": packet_payload_bytes, + "packets": pkts.len(), + "is_keyframe": is_keyframe, + "sample_no": encoded_frame_samples, + "platform": "android", + }), + ); + } if !first_send_logged && !pkts.is_empty() { first_send_logged = true; crate::emit_call_debug( @@ -1904,6 +1941,8 @@ impl CallEngine { "codec": format!("{:?}", vid_codec), "packets": pkts.len(), "first_pkt_bytes": pkts[0].payload.len(), + "last_pkt_bytes": pkts.last().map(|pkt| pkt.payload.len()).unwrap_or(0), + "encoded_bytes": encoded.len(), "is_keyframe": is_keyframe, }), ); @@ -2389,6 +2428,7 @@ impl CallEngine { let mut video_decoder_codec: Option = None; let mut video_first_recv_logged_desktop = false; let mut video_first_reassembled_logged = false; + let mut video_reassembled_samples: u64 = 0; let mut video_first_decoded_logged = false; let mut video_decoder_buffering_count: u64 = 0; let mut decoded_frames: u64 = 0; @@ -2427,6 +2467,7 @@ impl CallEngine { if let Some((codec_id, is_kf, frame)) = video_reassembler.push(&pkt) { + video_reassembled_samples += 1; if !video_first_reassembled_logged { video_first_reassembled_logged = true; crate::emit_call_debug( @@ -2441,6 +2482,20 @@ impl CallEngine { }), ); } + if video_reassembled_samples <= 5 { + crate::emit_call_debug( + &recv_app, + "video:reassembled_frame", + serde_json::json!({ + "t_ms": recv_t0.elapsed().as_millis() as u64, + "codec": format!("{:?}", codec_id), + "is_keyframe": is_kf, + "frame_bytes": frame.len(), + "frame_no": video_reassembled_samples, + "platform": "desktop", + }), + ); + } // Lazy-init or switch decoder on codec change. if video_decoder_codec != Some(codec_id) { crate::emit_call_debug( @@ -2838,6 +2893,7 @@ impl CallEngine { let mut first_camera_frame_logged = false; let mut camera_frames: u64 = 0; let mut empty_encodes: u64 = 0; + let mut encoded_frame_samples: u64 = 0; let mut wait_ticks: u64 = 0; encoder.request_keyframe(); crate::emit_call_debug( @@ -2954,6 +3010,26 @@ impl CallEngine { let pkts = wzp_video::transport::packetize_video_frame( &encoded, vid_codec, is_keyframe, &mut seq, ts_ms, ); + if encoded_frame_samples < 5 { + encoded_frame_samples += 1; + let packet_payload_bytes: usize = + pkts.iter().map(|pkt| pkt.payload.len()).sum(); + crate::emit_call_debug( + &vid_app, + "video:encoded_frame", + serde_json::json!({ + "t_ms": vid_t0.elapsed().as_millis() as u64, + "codec": format!("{:?}", vid_codec), + "camera_frames": camera_frames, + "encoded_bytes": encoded.len(), + "packet_payload_bytes": packet_payload_bytes, + "packets": pkts.len(), + "is_keyframe": is_keyframe, + "sample_no": encoded_frame_samples, + "platform": "desktop", + }), + ); + } if !first_send_logged && !pkts.is_empty() { first_send_logged = true; crate::emit_call_debug( @@ -2964,6 +3040,8 @@ impl CallEngine { "codec": format!("{:?}", vid_codec), "packets": pkts.len(), "first_pkt_bytes": pkts[0].payload.len(), + "last_pkt_bytes": pkts.last().map(|pkt| pkt.payload.len()).unwrap_or(0), + "encoded_bytes": encoded.len(), "is_keyframe": is_keyframe, }), ); diff --git a/desktop/src/style.css b/desktop/src/style.css index 47c25ce..bb8fc07 100644 --- a/desktop/src/style.css +++ b/desktop/src/style.css @@ -258,7 +258,7 @@ body { border-top: 1px solid var(--surface2); padding: 0 16px; padding-bottom: env(safe-area-inset-bottom, 8px); - z-index: 50; + z-index: 70; animation: drawerUp 0.25s ease-out; box-shadow: 0 -4px 20px rgba(0,0,0,0.4); } @@ -324,7 +324,7 @@ body { right: 0; bottom: 96px; /* leave room for voice drawer */ background: #000; - z-index: 50; + z-index: 40; overflow: hidden; } .vd-remote-stage {