FFmpeg,如何插入SEI自定义数据
FFmpeg,如何插入SEI自定义数据
一、什么是SEI?
SEI(Supplemental Enhancement Information,补充增强信息)是H.264/H.265视频编码标准中的一种元数据载体,它允许在视频流中嵌入额外的信息,如时间戳、人脸框、设备信息等增强信息。
与视频帧数据不同,SEI信息不是解码必需的,但可以用于增强播放体验或传递辅助信息。
二、AVPacket中插入SEI
在ffmpeg中,H264裸流的数据,一个 AVPacket 可能包含多个 NALU,它们之间通过起始码 0x00 00 00 01 或 0x00 00 01 分隔。
一个AVPacket中的关键帧数据包含SPS、PPS、IDR,而SEI的插入位置在PPS之后,IDR之前,如下图所示。
三、如何构建一个SEI NALU
1、SEI NALU格式
SEI NALU由起始码(0x00 00 00 01)、nal head(0x06)、payload type(0x05)、UVLC编码字节数、rbsp_data组成。其中rbsp_data由uuid(16字节)、payload、结束标记(0x80)组成,具体格式如下图所示。
说明 | 示例值或长度 | |
Start Code | 起始码(Annex-B 格式) | 0x00000001 |
NAL Header | NAL 类型为 SEI | 0x06 |
SEI Payload Type | 固定为 5,表示未注册用户数据 | 0x05 |
SEI Payload Size | 整个 payload 长度(字节) | 如 0x2F(47) |
UUID | 16 字节,唯一标识数据类型 | 如 dc45e9bd... |
Payload Content | 自定义数据内容(如字符串) | 如 "hello" |
RBSP Trailing Bits | 固定结尾对齐字节 | 0x80 |
2、UVLC编码字节数
UVLC(Unsigned Variable Length Coding)是H.264/H.265标准中用于编码SEI的payload_size和payload_type的压缩算法,其核心特点是:
- 无符号整数编码:仅处理非负整数
- 前缀码结构:通过0xFF标记实现变长
- 自描述性:解码器无需预知长度即可解析
若值 < 0xFF:用1字节直接表示
value = 100 → 0x64
若值 ≥ 0xFF:
- 第1字节固定为0xFF
- 剩余值递归编码(value -= 255)
value = 300 → 0xFF 0x2D (300 = 255 + 45)
value = 550 → 0xFF 0xFF 0x28 (550 = 255 + 255 + 40)
四、参考信息
1、NALU类型
NALU 的类型,共 32 种(0-31),常见类型如下表:
NALU 类型 | 说明 | |
0 | 未指定 | 保留,不使用 |
1 | 非 IDR 图像的片(Slice) | P 帧或 B 帧的 Slice 数据 |
2 | 数据分区 A | 用于分片编码,存放重要的运动信息 |
3 | 数据分区 B | 存放次要的运动信息 |
4 | 数据分区 C | 存放残差数据 |
5 | IDR 图像的片(Slice) | 立即刷新图像(关键帧)的 Slice 数据,解码时需清空参考帧缓冲区 |
6 | SEI(补充增强信息) | 包含额外信息(如时间戳、用户数据),不影响基本解码 |
7 | SPS(序列参数集) | 包含视频序列的全局参数(如分辨率、profile 等) |
8 | PPS(图像参数集) | 包含图像级参数(如量化参数、熵编码方式) |
9 | 访问单元分隔符 | 标记视频帧的开始 |
10 | 序列结束符 | 标记视频序列的结束 |
11 | 流结束符 | 标记整个码流的结束 |
12 | 填充数据 | 用于增加码流长度(如测试场景) |
13-23 | 保留 | 用于 H.264 的扩展功能 |
24-31 | 未指定 | 通常用于 RTP 等网络协议的封装 |
2. RBSP(Raw Byte Sequence Payload)
RBSP 是 NALU 的负载数据,包含 VCL 层的压缩信息(如 Slice 数据、参数集内容)。它由SODB(String of Data Bits) 经过处理后得到:
SODB:VCL 层输出的原始比特流(如预测残差、运动矢量等);
RBSP:在 SODB 末尾添加停止位(1 个 "1" 比特后跟若干 "0" 比特),使其字节对齐,形成 RBSP。
3、关键 NALU 类型详解
3.1 SPS(序列参数集,nal_unit_type=7)
SPS 是视频序列的全局配置,包含影响整个序列的参数,解析时需优先处理。常见参数如下:
// SPS参数示例(部分关键参数)
profile_idc // 编码profile(如Baseline=66,Main=77,High=100)
level_idc // 编码level(如3.0=30,3.1=31)
seq_parameter_set_id // SPS的ID(用于关联PPS)
chroma_format_idc // 色度格式(如1=4:2:0,2=4:2:2,3=4:4:4)
bit_depth_luma_minus8 // 亮度位深度(通常为8)
bit_depth_chroma_minus8 // 色度位深度(通常为8)
log2_max_frame_num_minus4 // 最大帧号的对数(用于计算帧号范围)
pic_order_cnt_type // 图像顺序计数类型(0-2,控制POC的计算方式)
max_num_ref_frames // 最大参考帧数量
pic_width_in_mbs_minus1 // 视频宽度(以宏块为单位,实际宽度=(值+1)*16)
pic_height_in_map_units_minus1 // 视频高度(以宏块为单位)
frame_mbs_only_flag // 是否仅帧编码(0=支持帧/场混合,1=仅帧)
3.2 PPS(图像参数集,nal_unit_type=8)
PPS 定义单幅图像的参数,依赖于 SPS,常见参数如下:
// PPS参数示例(部分关键参数)
pic_parameter_set_id // PPS的ID
seq_parameter_set_id // 关联的SPS的ID
entropy_coding_mode_flag // 熵编码方式(0=CAVLC,1=CABAC)
num_ref_idx_l0_default_active_minus1 // 默认的前向参考帧列表长度
num_ref_idx_l1_default_active_minus1 // 默认的后向参考帧列表长度
weighted_pred_flag // 是否使用加权预测(对P帧)
weighted_bipred_idc // 双向预测加权模式(0-2)
pic_init_qp_minus26 // 初始量化参数(QP=值+26)
deblocking_filter_control_present_flag // 是否存在去块滤波控制信息
3.3. IDR Slice(即时解码刷新,nal_unit_type=5)
IDR Slice 是一种特殊的 I Slice,属于关键帧:
解码 IDR Slice 时,解码器会清空所有参考帧缓冲区,确保后续帧的解码不依赖之前的错误帧,从而终止错误传播。
IDR Slice 必须包含完整的帧内预测信息,不依赖其他帧。
3.4. 非 IDR Slice(nal_unit_type=1)
包括 P Slice 和 B Slice:
P Slice:依赖前向参考帧(已解码的 I/P 帧)进行预测;
B Slice:依赖双向参考帧(前向和后向的 I/P 帧)进行预测,压缩效率更高。
3.5. SEI(补充增强信息,nal_unit_type=6)
携带与解码无关的辅助信息,常见类型:
时间戳信息(如 NTP 时间);
用户数据(如字幕、水印);
场景切换标记;
码流统计信息。
具体实现方式参见:
https://gitee.com/hanshuang741852/mem-push-streamhttps://gitee.com/hanshuang741852/mem-push-stream