H.264编码格式详解:Annex-B vs AVCC
📋 概述
H.264视频编码标准在不同容器格式中采用两种不同的封装方式:
- Annex-B格式:使用起始码标识NALU边界
- AVCC格式:使用长度前缀标识NALU大小
理解这两种格式的区别对于视频处理、流媒体传输和系统集成至关重要。
🔍 Annex-B格式
基本特征
- 标识方式:使用起始码标识NALU边界
- 标准格式:H.264标准定义的原始格式
- 流媒体友好:适合实时传输和流媒体应用
数据结构
[起始码][NALU数据][起始码][NALU数据][起始码][NALU数据]...
起始码类型
类型 | 字节序列 | 说明 |
---|---|---|
4字节起始码 | 00 00 00 01 | 标准起始码,兼容性最好 |
3字节起始码 | 00 00 01 | 短起始码,节省空间 |
示例数据
# SPS (Sequence Parameter Set)
00 00 00 01 67 42 00 1E 9A 74 40 00 00 03 00 40 00 00 0F 03 C6 0C 44 80# PPS (Picture Parameter Set)
00 00 00 01 68 CE 3C 80# IDR帧
00 00 00 01 65 B8 40 22 9E 3C 59 7B E1 08 40 00 00 03 00 40 00 00 0F 03 A6 0C 44 80
优势
- ✅ 标准兼容性好
- ✅ 流媒体播放器支持度高
- ✅ 实时传输效率高
- ✅ 无需额外解析长度信息
劣势
- ❌ 数据大小略大(每个NALU多3-4字节起始码)
- ❌ 不支持随机访问优化
📦 AVCC格式
基本特征
- 标识方式:使用长度前缀标识NALU大小
- 容器优化:针对MP4容器格式优化
- 随机访问友好:支持快速定位和跳转
数据结构
[4字节长度][NALU数据][4字节长度][NALU数据][4字节长度][NALU数据]...
长度前缀解析
- 字节序:大端序(Big-Endian)
- 格式:4字节无符号整数
- 计算:
长度 = (字节0 << 24) | (字节1 << 16) | (字节2 << 8) | 字节3
示例数据
# 长度前缀:0x1A = 26字节
00 00 00 1A 67 42 00 1E 9A 74 40 00 00 03 00 40 00 00 0F 03 C6 0C 44 80# 长度前缀:0x0C = 12字节
00 00 00 0C 68 CE 3C 80# 长度前缀:0x1F = 31字节
00 00 00 1F 65 B8 40 22 9E 3C 59 7B E1 08 40 00 00 03 00 40 00 00 0F 03 A6 0C 44 80
优势
- ✅ 支持随机访问和快速跳转
- ✅ 容器格式优化
- ✅ 数据组织更紧凑
- ✅ 适合本地存储和播放
劣势
- ❌ 需要解析长度前缀
- ❌ 某些播放器兼容性较差
- ❌ 实时传输效率略低
🌐 RTSP传输格式
标准要求
RTSP传输H.264视频时,必须使用Annex-B格式,这是由RFC 6184标准强制规定的。
原因分析
- 流媒体特性:RTSP是实时流媒体协议,需要支持随机接入
- 起始码识别:Annex-B的起始码便于接收端快速定位NALU边界
- 标准兼容:符合H.264标准定义,兼容性最好
- 实时解析:接收端无需预先知道数据长度,可以边接收边解析
传输过程
H.264 Annex-B → RTP封装 → 网络传输 → RTP解封装 → H.264 Annex-B
🔧 格式转换
AVCC → Annex-B转换
std::string ConvertToAnnexB(const uint8_t* data, int size) {std::string annex_b_data;annex_b_data.reserve(size + 1024); // 预分配空间int i = 0;while (i < size - 4) { // 至少需要4字节长度前缀// 读取4字节长度前缀(大端序)uint32_t nalu_length = (data[i] << 24) | (data[i+1] << 16) | (data[i+2] << 8) | data[i+3];// 检查长度是否合理if (nalu_length == 0 || nalu_length > size - i - 4) {break; // 长度无效,跳过剩余数据}// 添加Annex-B起始码annex_b_data.push_back(0x00);annex_b_data.push_back(0x00);annex_b_data.push_back(0x00);annex_b_data.push_back(0x01);// 添加NALU数据annex_b_data.append(reinterpret_cast<const char*>(data + i + 4), nalu_length);// 移动到下一个NALUi += 4 + nalu_length;}return annex_b_data;
}
转换要点
- 长度解析:正确读取大端序长度值
- 边界检查:确保数据完整性
- 起始码添加:为每个NALU添加标准起始码
- 数据复制:保持NALU数据内容不变
🎯 应用场景
Annex-B格式适用
- 实时流媒体:RTSP、HLS、DASH
- 广播系统:DVB、ATSC
- 网络传输:RTP封装
- TS容器:MPEG-2 Transport Stream
AVCC格式适用
- 本地存储:MP4、MOV、3GP文件
- 点播系统:支持随机访问
- 编辑软件:视频编辑和后期处理
- 移动设备:iOS、Android原生支持
📊 性能对比
特性 | Annex-B | AVCC |
---|---|---|
解析速度 | 快(直接查找起始码) | 中等(需要读取长度) |
内存占用 | 稍高(起始码开销) | 较低(无额外开销) |
兼容性 | 优秀 | 良好 |
随机访问 | 困难 | 容易 |
流媒体 | 优秀 | 中等 |
🔧 实际应用
CyberRT兼容性
- 期望格式:Annex-B格式
- MP4处理:需要自动转换为Annex-B
- TS处理:直接使用原始数据
- 播放器支持:确保H.264数据能被正确解析
格式检测和转换
// 获取H.264数据并进行格式转换
std::string h264_data;
if (IsMP4Format()) {// MP4格式:转换为Annex-B格式h264_data = ConvertToAnnexB(packet_->data, packet_->size);
} else {// 非MP4格式(如TS):直接使用原始数据h264_data = std::string(reinterpret_cast<const char*>(packet_->data), packet_->size);
}
📝 总结
H.264在TS和MP4中的编码格式差异主要体现在NALU边界标识方式上:
- TS文件使用起始码标识,兼容性好,适合流媒体
- MP4文件使用长度前缀,支持随机访问,适合本地存储
- RTSP传输强制使用Annex-B格式,确保实时流媒体兼容性
在实际应用中,需要根据目标播放器的要求进行相应的格式转换,确保H.264数据能被正确解析和播放。特别是从MP4文件提取H.264数据并通过RTSP传输时,必须先将AVCC格式转换为Annex-B格式。