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

添加SEI单元至编码帧后遇FFmpeg截断错误求助

H.264 SEI单元截断错误排查与修复

问题描述

编写编码器在每个编码帧后添加SEI单元,参考BSAnalyzer结构(SEI位于每一帧之后),但使用FFmpeg播放生成的文件时出现如下截断错误:

[NULL @ 000001cd901df1c0] SEI type 5 size 179843 truncated at 179776
[h264 @ 000001cd96eae780] SEI type 5 size 179843 truncated at 179774
[NULL @ 000001cd901df1c0] SEI type 5 size 179843 truncated at 179776
[h264 @ 000001cd970789c0] SEI type 5 size 179843 truncated at 179774
...and more

错误原因分析

错误核心是SEI payload size的编码逻辑完全错误。H.264规定SEI的payload size采用EBSP可变长度整数编码:

  • 用若干个0xFF字节表示超出255的部分
  • 最后一个字节为剩余值(小于0xFF)
  • 所有字节的总和等于实际payload大小(UUID + 用户数据的总长度)

你的代码在计算payload size编码长度和写入时,错误地用0xFF - 剩余值作为最后一个字节,导致FFmpeg解析时计算出的payload size远大于实际数据长度,从而触发截断错误。

修复后的代码

修正后的calc_sei_size函数

static const uint8_t START_MARKER[4] = {0x0, 0x0, 0x0, 0x1};
static const uint8_t UUID[16] = {
    0x81, 0x39, 0xe9, 0xbd, 0xa6, 0x09, 0x48, 0xb7,
    0x76, 0x2c, 0xc8, 0x20, 0xd9, 0x68, 0xcc, 0xcf};

static const uint8_t SEI_TYPE[1] = {0x06};
static const uint8_t PAYLOAD_TYPE[1] = {0x05};

int calc_sei_size(size_t user_data_size)
{
    int new_buffer_size = 0;
    new_buffer_size += sizeof(START_MARKER); // NAL起始标记
    new_buffer_size += sizeof(SEI_TYPE);     // NAL类型(0x06)
    new_buffer_size += sizeof(PAYLOAD_TYPE); // SEI payload type(0x05)

    int payload_size = user_data_size + sizeof(UUID); // 实际payload大小:UUID + 用户数据
    int temp = payload_size;
    int size_bytes = 0;
    // 计算payload size的编码字节数
    while (temp > 0)
    {
        size_bytes++;
        if (temp > 0xFF)
        {
            temp -= 0xFF;
        }
        else
        {
            temp = 0;
        }
    }
    new_buffer_size += size_bytes; // 加上payload size的编码字节数
    new_buffer_size += sizeof(UUID); // UUID长度
    new_buffer_size += user_data_size; // 用户数据长度
    return new_buffer_size;
}

修正后的sei_write函数

void sei_write(uint8_t *user_data, size_t user_data_size)
{
    int newSize = calc_sei_size(user_data_size);
    uint8_t *buffer = (uint8_t *)calloc(sizeof(uint8_t), newSize);
    if (!buffer) return; // 增加内存分配失败判断

    int offset = 0;
    memcpy(&buffer[offset], START_MARKER, sizeof(START_MARKER)); // 复制NAL起始标记
    offset += sizeof(START_MARKER);
    memcpy(&buffer[offset], SEI_TYPE, sizeof(SEI_TYPE)); // 复制NAL类型(0x06)
    offset += sizeof(SEI_TYPE);
    memcpy(&buffer[offset], PAYLOAD_TYPE, sizeof(PAYLOAD_TYPE)); // 复制payload type(0x05)
    offset += sizeof(PAYLOAD_TYPE);

    // 正确编码payload size
    int payload_size = user_data_size + sizeof(UUID);
    int temp = payload_size;
    while (temp > 0xFF)
    {
        buffer[offset++] = 0xFF;
        temp -= 0xFF;
    }
    buffer[offset++] = temp; // 写入剩余值,而非0xFF - temp

    memcpy(&buffer[offset], UUID, sizeof(UUID)); // 复制UUID
    offset += sizeof(UUID);
    memcpy(&buffer[offset], user_data, user_data_size); // 复制用户数据

    DoSomeWithBuffer(buffer, newSize);
    free(buffer); // 释放内存,避免泄漏
}

额外注意事项

  1. 内存泄漏修复:原代码未释放calloc分配的内存,修复后添加free(buffer)
  2. EBSP转换:如果用户数据中包含0x000002或0x000003序列,需要插入0x03进行EBSP转换,否则可能导致NALU解析失败
  3. Payload Type编码:如果未来使用大于255的payload type,需要按照EBSP规则编码(当前type=5,单字节没问题)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 09:00:56