使用FFmpeg解码Live555 RTSP分片HEVC流仅解码1/3帧的问题
排查FFmpeg解码HEVC分片流的拼接逻辑问题
针对你遇到的分片HEVC流仅1/3解码成功的问题,结合HEVC分片(FU-A/FU-B)的编码规范,以下是核心排查点和修正方向:
一、常见拼接逻辑错误点
1. NALU分片头部解析错误
HEVC的分片单元(FU-A/FU-B)有两层头部:
- 第一层是原始NALU头(1字节):
F(1位) + NRI(2位) + Type(5位),其中Type为49(FU-A)或50(FU-B)。 - 第二层是FU头(1字节):
S(1位,起始标记) + E(1位,结束标记) + R(1位,保留) + OriginalType(5位)。
错误场景:
- 直接将FU的Type当作原始NALU类型,未提取FU头中的
OriginalType。 - 未保留原始NALU头的
F和NRI位,导致拼接后的NALU头不符合规范。
2. 帧边界判断错误
同一帧的所有分片必须通过S和E标记关联:
S=1表示当前分片是帧的起始分片。E=1表示当前分片是帧的结束分片。
错误场景:
- 未在
S=1时清空拼接缓冲区,导致残留上一帧数据。 - 未在
E=1时立即将拼接完成的NALU送入解码器,或提前送入未完成的帧数据。
3. 时间戳同步问题
Live555发送的同一帧所有分片共享相同的PTS/DTS。
错误场景:
- 为每个分片单独设置时间戳,导致解码器将同一帧的不同分片识别为独立帧,进而丢弃或乱序。
- 未将起始分片的时间戳继承给整个拼接后的帧。
4. 缓冲区管理错误
错误场景:
- 拼接时未跳过FU头(前2字节),直接将整个分片数据写入缓冲区。
- 结束分片处理后未及时清空缓冲区,导致下一帧拼接混入旧数据。
二、正确的分片拼接逻辑示例
// 假设已初始化拼接缓冲区(如std::vector<uint8_t> nalu_buffer) // data: 当前分片的原始数据,size: 分片长度,current_pts/dts: 当前帧的时间戳 uint8_t nal_type = data[0] & 0x1F; if (nal_type == 49 || nal_type == 50) { // FU-A/FU-B uint8_t fu_header = data[1]; bool is_start = (fu_header & 0x80) != 0; bool is_end = (fu_header & 0x40) != 0; uint8_t original_nal_type = fu_header & 0x3F; if (is_start) { // 起始分片:清空缓冲区,写入修正后的NALU头 nalu_buffer.clear(); uint8_t new_nal_header = (data[0] & 0xC0) | original_nal_type; // 保留F+NRI,替换Type nalu_buffer.push_back(new_nal_header); // 写入FU负载(跳过前2字节的头部) nalu_buffer.insert(nalu_buffer.end(), data + 2, data + size); } else { // 中间/结束分片:直接追加负载 nalu_buffer.insert(nalu_buffer.end(), data + 2, data + size); } if (is_end) { // 结束分片:打包成AVPacket送入解码器 AVPacket pkt; av_init_packet(&pkt); pkt.data = nalu_buffer.data(); pkt.size = nalu_buffer.size(); pkt.pts = current_pts; pkt.dts = current_dts; pkt.stream_index = 0; int ret = avcodec_send_packet(codec_ctx, &pkt); if (ret >= 0) { AVFrame *frame = av_frame_alloc(); while (avcodec_receive_frame(codec_ctx, frame) == 0) { // 处理解码后的帧 av_frame_unref(frame); } av_frame_free(&frame); } // 清空缓冲区准备下一轮拼接 nalu_buffer.clear(); } } else { // 非分片NALU,直接送入解码器 AVPacket pkt; av_init_packet(&pkt); pkt.data = data; pkt.size = size; pkt.pts = current_pts; pkt.dts = current_dts; avcodec_send_packet(codec_ctx, &pkt); // 处理解码帧逻辑... }
三、排查验证步骤
- 打印分片标记:输出每个分片的
nal_type、S/E标记、original_nal_type,确认分片的起始/结束是否与帧类型(I/P帧)对应。 - 对比NALU结构:将拼接后的完整NALU与非分片HEVC流的NALU对比,检查头部和负载长度是否一致。
- 验证解码器输入:打印送入解码器的AVPacket的
size、pts值,确认无重复或不完整的包。
内容的提问来源于stack exchange,提问作者ios dev
相关产品推荐
相关产品推荐

