H.264 硬解码播放链路 Bug 修复记录
H.264 硬解码播放链路 Bug 修复记录
概述
在 TXW82x 平台 H.264 硬解码播放功能开发过程中,定位并修复了 H.264 文件解复用(h264_file_msi)与硬件解码(h264_decode_msi)链路上的 3 个关键 Bug。这些 Bug 共同导致解码输出花屏、解码器崩溃或完全不输出。
涉及文件
| 文件 | 路径 | 角色 |
|---|---|---|
h264_file_msi.c |
sdk/app/h264_demux/ |
H.264 文件解复用器,从 SD 卡读取 NAL 流,封装 framebuff 发出 |
h264_decode_msi.c |
sdk/app/decode/ |
H.264 硬件解码 MSI 组件,消费 framebuff 送入硬件解码 |
sample_h264_esplayer.c |
project/app/ |
H.264 ES 流播放 Sample,作为参考实现 |
stream_define.h |
sdk/app/algorithm/stream_frame/ |
fb_h264_s 结构定义 |
Bug 1:start_len 未动态设置
现象
解码器在 h264_decode_msi.c 中通过以下代码计算 NAL 有效载荷的偏移和长度:
1 | uint32_t dst = (uint32_t) rfb->data + h264_priv->start_len; |
当 start_len 被硬编码为固定值(如 4)时,如果实际文件使用 3 字节起始码(00 00 01),则:
dst多偏移了 1 字节,指向错误位置dst_len少算了 1 字节,长度不正确- 后续
h264_rom_memcpy()拷贝错误数据送入硬件解码器,导致花屏或解码器异常
根因
H.264 Annex-B 格式的起始码(start code)有两种长度:
- 3 字节:
0x00 0x00 0x01 - 4 字节:
0x00 0x00 0x00 0x01
fb_h264_s.start_len 字段用于记录起始码长度,告诉解码器需要跳过多少字节才能到达 NAL 单元的真实数据。之前代码中未根据实际扫描到的起始码动态赋值,而是使用了固定值。
修复
h264_file_msi.c — 帧发送路径
1 | // 旧:start_len 可能为固定值或未正确设置 |
其中 frame_info.frame_start_len 在扫描 NAL 边界时由 h264_find_start_code() 动态确定(返回 3 或 4)。
sample_h264_esplayer.c — Sample 参考实现
1 | // 旧:固定值或缺失 |
其中 code_len 在扫描起始码时根据字节序列动态判定。
h264_parse_fb_priv() 中的连带修复
该函数在解析内存中的 NAL 缓冲时,原先使用 nal_header_offset 赋值 start_len:
1 | // 旧:nal_header_offset = found_offset + start_len |
注:
found_offset是起始码在缓冲中的偏移,start_len(局部变量)是起始码长度(3 或 4)。当缓冲头部无填充字节时(found_offset == 0),两者数值相同,Bug 不易暴露;但存在前导数据时则计算错误。
Bug 2:fb->len 未设置导致 memcpy 失败
现象
解码器在 h264_decode_msi.c 中执行:
1 | h264_rom_memcpy(rom_ptr, (uint8_t *) dst, dst_len); |
其中 dst_len = rfb->len - h264_priv->start_len。当 fb->len 为 0 或未赋值时,dst_len 计算为负值(类型隐式转换后为极大值),导致:
h264_rom_memcpy拷贝超出实际数据范围的无效内存- 硬件解码器接收到错误的 NAL 数据
- 解码超时或崩溃
根因
在创建 framebuff 时,fb->len 字段未被正确设置为 NAL 单元的实际长度。fb->len 是 MSI 管道中下游组件判断数据有效长度的唯一依据。
修复
h264_file_msi.c — 帧发送路径
1 | // 旧:fb->len 未赋值或赋值为 0 |
sample_h264_esplayer.c — Sample 参考实现
1 | fb->data = STREAM_MALLOC(nal_size); |
Bug 3:解码端 dst / dst_len 计算
现象
这是 Bug 1 和 Bug 2 的消费端表现。即使前两项已修复,如果 start_len 和 fb->len 正确,此计算本身是正确的。但该代码对输入的正确性高度敏感:
1 | uint32_t dst = (uint32_t) rfb->data + h264_priv->start_len; |
逻辑说明
| 变量 | 含义 |
|---|---|
rfb->data |
framebuff 数据指针,指向包含起始码的完整 NAL 数据 |
rfb->len |
NAL 数据总长度(包含起始码) |
h264_priv->start_len |
起始码长度(3 或 4),跳过起始码到达 NAL type 字节 |
dst |
指向 NAL type 字节(即有效载荷起点) |
dst_len |
剩余有效数据长度(NAL type + payload) |
数据流校验关系
1 | rfb->data ──▶ [00 00 00 01][67 XX YY ...] |
修复依赖
- ✅ Bug 1 修复后 →
start_len正确(3 或 4) - ✅ Bug 2 修复后 →
rfb->len正确 - → 此计算自然正确
修复验证
测试条件
| 项目 | 值 |
|---|---|
| 平台 | TXW82x (C-Sky CK804DF) |
| 测试文件 | H.264 基线编码文件,包含 I 帧 + P 帧 |
| 起始码类型 | 混合:部分文件使用 3 字节、部分使用 4 字节 |
| 输出分辨率 | 320×240 |
| 验证方式 | LCD 屏幕目测 + os_printf 日志 |
验证结果
| 测试项 | 修复前 | 修复后 |
|---|---|---|
| 3 字节起始码文件 | 花屏/无输出 | 正常播放 |
| 4 字节起始码文件 | 偶发花屏 | 正常播放 |
| I 帧 + P 帧解码 | 首帧后卡死 | 连续播放 |
| 文件循环播放 | 第 2 轮崩溃 | 稳定循环 |
| 快速切换文件 | 内存拷贝错误 | 切换正常 |
经验总结
fb_h264_s.start_len是起始码长度,不是 NAL header 偏移。 虽然在某些场景下两者数值相同,但语义必须准确,否则遇到变长起始码或缓冲前导数据时会出错。fb->len是 MSI 管道的核心契约。 上游生产者必须准确设置 framebuff 的数据长度,下游消费者依赖此值进行内存操作。遗漏设置会导致消费者读取越界或长度异常。H.264 Annex-B 起始码长度是动态的(3 或 4 字节)。 所有涉及 NAL 数据扫描和传递的环节都必须感知这一动态性,硬编码 4 字节是最常见的隐蔽 Bug 来源。
端到端校验: 在
h264_file_msi(生产者)→jpg_decode_msg_msi(帧解析)→h264_decode_msi(解码器)→R_VIDEO_P0(LCD 显示)的完整链路中,每个环节都应校验fb->len和start_len的合理性,避免异常数据传递到硬件层。