diff --git a/agent/src/bin/dump_stream.rs b/agent/src/bin/dump_stream.rs index 9d61853..0101a0b 100644 --- a/agent/src/bin/dump_stream.rs +++ b/agent/src/bin/dump_stream.rs @@ -130,6 +130,10 @@ async fn main() -> Result<()> { .map(|s| s.parse()) .transpose()? .unwrap_or(30); + // --save <路径>:把**裸 H.264 码流**(各帧载荷按到达顺序直接拼接) + // 写成 Annex-B 文件,供安卓 MediaCodec 等外部解码器验证兼容性。 + // 注意存的是载荷而非完整帧(不含自定义帧头),因为外部解码器只认标准 Annex-B。 + let save_path = arg("--save"); let tls = rustls::ClientConfig::builder() .dangerous() @@ -177,6 +181,8 @@ async fn main() -> Result<()> { let mut decoder = Decoder::new()?; let mut sent_kfreq = false; + // 累积裸码流,用于 --save + let mut saved: Vec = Vec::new(); println!("\n序号 seq kf 载荷字节 NAL 结构 解码结果"); println!("{}", "-".repeat(72)); @@ -198,6 +204,11 @@ async fn main() -> Result<()> { let types = nal_types(&payload); let is_idr = types.contains(&5); + // 需要在 save 里也保留这一帧时,先记录到缓冲 + if save_path.is_some() { + saved.extend_from_slice(&payload); + } + // 交叉校验:帧头 keyframe 标志必须与码流实际 NAL 类型一致。 // 这条不一致意味着时间戳/序号契约或编码器标志有 bug,比画面花屏更难排查。 if hdr.keyframe != is_idr { @@ -267,6 +278,39 @@ async fn main() -> Result<()> { } } + // 导出裸码流文件(Annex-B 拼接),供外部解码器验证 + if let Some(p) = save_path.as_ref() { + std::fs::write(p, &saved)?; + let idr_cnt = { + // 粗略统计 IDR 数量:扫起始码后的 NAL 类型 + let mut n = 0; + let mut i = 0usize; + while i + 3 < saved.len() { + let sc = if saved[i..].starts_with(&[0, 0, 0, 1]) { + 4 + } else if saved[i..].starts_with(&[0, 0, 1]) { + 3 + } else { + i += 1; + continue; + }; + match saved.get(i + sc) { + Some(&b) if b & 0x1F == 5 => { + n += 1; + i += sc + 1; + } + Some(_) => i += sc + 1, + None => break, + } + } + n + }; + println!( + "\n[dump] 已保存裸码流: {p}({} 字节,含 {idr_cnt} 个 IDR)", + saved.len() + ); + } + let _ = conn.close(0u32.into(), b"done"); Ok(()) }