You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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);
    // 处理解码帧逻辑...
}

三、排查验证步骤

  1. 打印分片标记:输出每个分片的nal_type、S/E标记、original_nal_type,确认分片的起始/结束是否与帧类型(I/P帧)对应。
  2. 对比NALU结构:将拼接后的完整NALU与非分片HEVC流的NALU对比,检查头部和负载长度是否一致。
  3. 验证解码器输入:打印送入解码器的AVPacket的size、pts值,确认无重复或不完整的包。

内容的提问来源于stack exchange,提问作者ios dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:00:59