六. NAL Unit格式解析

在有序字节流格式的H.264码流中,我们可以根据前缀起始码0x 00 00 01或0x 00 00 01获取到一个完整的NAL Unit所包含的的字节数据。H.264码流中的每一个NAL Unit的作用并不是相同的,而是根据不同的类型起不同的作用。因此将H.264的码流解析为NAL Unit之后,下一步将是对NAL Unit中的数据进行解析。

1. NAL Unit结构

H.264标准中规定的一个NAL Unit的结构如下图: 

一个NAL Unit都是由一个NAL Header和一个NAL Body组成。对于基本版本的H.264标准(不考虑SVC和MVC扩展),一个NAL Header的长度固定为1,即8bit。这8bit的含义分别为:

  • forbidden_zero_bit:每一个NAL Header的第一个bit,规定必须为0;
  • nal_ref_idc:第2和3位,主要表示NAL的优先级。当该值为正时,表示当前NAL Unit中包含了SPS、PPS和作为参考帧的Slice等重要数据。
  • nal_unit_type:表示NAL Unit的类型,包括VCL层和非VCL层的多种数据类型。常见的nal_unit_type取值有:7表示SPS,8表示PPS,5表示IDR帧,1表示非IDR帧等。

2. NAL Unit的有效负载数据及其封装

在NAL Header之后,NAL Unit的其余部分,即NAL Body包含了有效负载数据的封装。从NAL Body到实际的语法元素的码流共3层封装:

(1). 第一层:EBSP——扩展字节序列载荷

EBSP全称为Extended Byte String Payload,等同于NAL Body的数据本身。在EBSP中包含了一个特殊的字节0x03,表示防止竞争校验字节

  • emulation_prevention_three_byte:设置该值的目的是为了防止NAL Body内部出现于NAL Unit起始码0x 00 00 01或0x 00 00 00 01冲突。

当内部的连续4字节数据出现了下列情况时:

  • 0x 00 00 00
  • 0x 00 00 01
  • 0x 00 00 02
  • 0x 00 00 03

在两个0字节之后会插入值为3的一个字节,形成下列情况:

  • 0x 00 00 03 00
  • 0x 00 00 03 01
  • 0x 00 00 03 02
  • 0x 00 00 03 03

在进行解析时需要将附加的03字节去掉,得到RBSP数据。

(2). 第二层:RBSP——原始字节序列载荷

RBSP全称为Raw Byte Sequence Payload,相当于NAL Body去掉emulation_prevention_three_byte之后的数据,是对原始的语法元素码流进一步处理后产生的数据。相比于原始的语法元素码流,RBSP在末尾添加了rbsp_trailing_bits()部分,其主要目的是字节对齐。每个rbsp_trailing_bits()包括一个1bit和若干个0bit,0bit的个数不定,以实现字节的对齐。

(3). 第三层:SODB——数据字节流

SODB全称为String Of Data Bits,表示H.264的语法元素编码完成后的实际的原始二进制码流。SODB通常不能保证字节对其。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的 C++ 代码示例,用于解析 HEVC 流中的 NAL 头和切片类型: ```cpp #include <iostream> #include <fstream> #include <cstring> using namespace std; // NAL 头结构体 struct NALHeader { int forbidden_zero_bit; int nal_unit_type; int nuh_layer_id; int nuh_temporal_id_plus1; }; // 切片类型枚举 enum SliceType { B_SLICE = 0, P_SLICE = 1, I_SLICE = 2, IDR_SLICE = 3, SP_SLICE = 4, SI_SLICE = 5, P_SLICE_BLA = 6, P_SLICE_BLA_RET = 7, P_SLICE_BLA_N_LP = 8, P_SLICE_IDR = 9, B_SLICE_BLA = 10, B_SLICE_BLA_RET = 11, B_SLICE_BLA_N_LP = 12, IDR_SLICE_BLA = 13, IDR_SLICE_BLA_RET = 14, IDR_SLICE_BLA_N_LP = 15, }; int main() { ifstream ifs("sample.hevc", ios::binary); // 打开 HEVC 流文件 if (!ifs) { cerr << "Error opening file." << endl; return -1; } char buffer[1024]; int nal_unit_type; SliceType slice_type; while (ifs.read(buffer, sizeof(buffer))) { for (int i = 0; i < ifs.gcount(); ) { // 查找 NAL 头起始位置 while (i < ifs.gcount() - 3 && !(buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01)) { i++; } if (i >= ifs.gcount() - 3) { break; } // 解析 NALNALHeader nal_header; nal_header.forbidden_zero_bit = (buffer[i+3] >> 7) & 0x01; nal_header.nal_unit_type = (buffer[i+3] >> 1) & 0x3f; nal_header.nuh_layer_id = (buffer[i+4] >> 1) & 0x3f; nal_header.nuh_temporal_id_plus1 = buffer[i+5] & 0x07; // 获取切片类型 if (nal_header.nal_unit_type >= 16 && nal_header.nal_unit_type <= 21) { slice_type = static_cast<SliceType>(nal_header.nal_unit_type - 16); } else if (nal_header.nal_unit_type >= 32 && nal_header.nal_unit_type <= 34) { slice_type = static_cast<SliceType>(nal_header.nal_unit_type - 24); } else { slice_type = static_cast<SliceType>(-1); } // 输出 NAL 头和切片类型 cout << "NAL Header: forbidden_zero_bit=" << nal_header.forbidden_zero_bit << ", nal_unit_type=" << nal_header.nal_unit_type << ", nuh_layer_id=" << nal_header.nuh_layer_id << ", nuh_temporal_id_plus1=" << nal_header.nuh_temporal_id_plus1 << endl; cout << "Slice Type: " << slice_type << endl; // 移动指针到下一个 NAL 头 i += 3; } } ifs.close(); return 0; } ``` 代码中使用了 ifstream 类来打开 HEVC 流文件,然后读取数据并解析 NAL 头和切片类型。NAL 头包含了 forbidden_zero_bit、nal_unit_type、nuh_layer_id 和 nuh_temporal_id_plus1 等字段。切片类型用枚举类型 SliceType 表示。在代码中,我们使用了查找 0x00 0x00 0x01 的方式来定位 NAL 头的起始位置。这种方式并不是 HEVC 标准规定的方法,在实际应用中需要根据标准规范来实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值