test(agent): dump_stream 增加 --save,可导出裸 H.264 码流供外部解码器验证
目的 安卓主控方案的唯一高风险未知项是「agent 发出的码流能否被 MediaCodec 解码」。 要验证它,需要一份脱离 QUIC/自定义帧头的**裸 Annex-B 码流文件**,供手机端 或任何标准解码器直接读取。 改动 - 新增 --save <路径>:把各帧载荷按到达顺序拼接成 Annex-B 文件 - 存的是**载荷**而非完整帧:外部解码器只认标准 Annex-B,不认识本项目的 长度前缀与 bincode 帧头,故不能存 encode_video_frame 的输出 - 保存时打印字节数与 IDR 数量,便于快速判断样本是否可用 不做的事 不引入独立的码流解析库,IDR 统计复用文件内已有的起始码扫描逻辑, 避免为一次性诊断工具增加依赖。 验证 cargo build --workspace 退出码 0;cargo test --workspace --exclude linux-agent 全绿。 实际使用:导出 90 帧、437823 字节、含 2 个 IDR,该文件已用于安卓 MediaCodec 兼容性验证(vivo S6 / Exynos 硬解,87/89 帧解出)。
This commit is contained in:
parent
6e83bda8b8
commit
1837658c85
@ -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<u8> = 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(())
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user