(一)句法元素的分层结构
在 H.264 定义的码流中,句法元素被组织成有层次的结构,分别描述各个层次的信息,如下图所示
在H.264 中,句法元素共被组织成 序列、图像、片、宏块、子宏块五个层次。
在这样的结构中,每一层的头部和它的数据部分形成管理与被管理的强依赖关系,头部的句法元素是该层数据的核心,而一旦头部丢失,数据部分的信息几乎不可能再被正确解码出来,尤其在序列层及图像层。
在 H.264 中,分层结构最大的不同是取消了序列层和图像层,并将原本属于序列和图像头部的大部分句法元素游离出来形成序列和图像两级参数集,其余的部分则放入片层。参数集是一个独立的数据单位,不依赖于参数集外的其他句法元素。由于参数集是独立的,可以被多次重发或者采用特殊技术加以保护。
复杂通信中的码流中可能出现的数据单位:
IDR: 一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。IDR 图像一定是 I 图像,但 I 图像不一定是 IDR 图像。
(二)NAL层句法
NAL&VCL: H.264 的功能分为两层,即视频编码层(VCL)和网络提取层(NAL,Network Abstraction Layer)。VCL 数据即编码处理的输出,它表示被压缩编码后的视频数据序列。在 VCL 数据传输或存储之前,这些编码的 VCL 数据,先被映射或封装进 NAL 单元中。
每个 NAL 单元包括一个原始字节序列负荷(RBSP)、一组对应于视频编码数据的 NAL 头信息。NAL 单元序列的结构如下:
RBSP的类型:
RBSP 类型 | 描 述 |
参数集 PS | 序列的全局参数,如图像尺寸、视频格式等等 |
增强信息 SEI | 视频序列解码的增强信息 |
图像定界符 PD | 视频图像的边界 |
编码片 | 片的头信息和数据 |
数据分割 | DP 片层的数据,用于错误恢复解码 |
序列结束符 | 表明下一图像为 IDR 图像 |
流结束符 | 表明该流中已没有图像 |
填充数据 | 哑元数据,用于填充字节 |
PS:
包括序列参数集 SPS 和图像参数集 PPS
SPS 包含的是针对一连续编码视频序列的参数,如标识符 seq_parameter_set_id、帧数及 POC 的约束、参考帧数目、解码图像尺寸和帧场编码模式选择标识等等。
PPS对应的是一个序列中某一幅图像或者某几幅图像,其参数如标识符 pic_parameter_set_id、可选的 seq_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等等。
数据分割:组成片的编码数据存放在 3 个独立的 DP(数据分割,A、B、C)中,各自包含一个编码片的子集。分割A包含片头和片中每个宏块头数据。分割B包含帧内和 SI 片宏块的编码残差数据。分割 C包含帧间宏块的编码残差数据。每个分割可放在独立的 NAL 单元并独立传输。
NAL 层句法 :
nal_unit( NumBytesInNALunit ) {
// forbidden_zero_bit 等于 0
forbidden_zero_bit
// nal_ref_idc 指示当前 NAL 的优先级。取值范围为 0-3, 值越高,表示当前 NAL 越重要,需要优先受到保护。H.264 规定如果当前 NAL 是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的数据单位时,本句法元素必须大于 0。
nal_ref_idc
// nal_unit_type 指明当前 NAL unit 的类型
nal_unit_type
NumBytesInRBSP = 0
/* rbsp_byte[i] RBSP 的第 i 个字节。RBSP 指原始字节载荷,它是 NAL 单元的数据部分的封装格式,封装的数据来自 SODB(原始数据比特流)。SODB 是编码后的原始数据,SODB 经封装为 RBSP 后放入 NAL 的数据部分。下面介绍一个 RBSP 的生成顺序。
从 SODB 到 RBSP 的生成过程:
- 如果 SODB 内容是空的,生成的 RBSP 也是空的
- 否则,RBSP 由如下的方式生成:
1) RBSP 的第一个字节直接取自 SODB 的第 1 到 8 个比特,(RBSP 字节内的比特按照从左到右对应为从高到低的顺序排列,most significant),以此类推,RBSP 其余的每个字节都直接取自 SODB的相应比特。RBSP 的最后一个字节包含 SODB 的最后几个比特,及如下的 rbsp_trailing_bits()
2) rbsp_trailing_bits()的第一个比特是 1,接下来填充 0,直到字节对齐。(填充 0 的目的也是为了字节对齐)
3) 最后添加若干个 cabac_zero_word(其值等于 0x0000)
*/
for(i = 1; i < NumBytesInNALunit; i++ ) {
if( i + 2 < NumBytesInNALunit && next_bits( 24 ) = = 0x000003 ) {
rbsp_byte[ NumBytesInRBSP++ ]
rbsp_byte[ NumBytesInRBSP++ ]
i += 2
//emulation_prevention_three_byte NAL 内部为防止与起始码竞争而引入的填充字节 ,值为 0x03。
emulation_prevention_three_byte
} else
rbsp_byte[ NumBytesInRBSP++ ]
}
}
seq_parameter_set_rbsp( ) {
// profile_idc level_idc 指明所用 profile、level
profile_idc
// constraint_set0_flag 等于 1 时表示必须遵从附录 A.2.1 所指明的所有制约条件。等于 0 时表示不必遵从所有条件。
constraint_set0_flag
// constraint_set1_flag 等于 1 时表示必须遵从附录 A.2.2 所指明的所有制约条件。等于 0 时表示不必遵从所有条件。
constraint_set1_flag
// constraint_set2_flag 等于 1 时表示必须遵从附录 A.2.3 所指明的所有制约条件。等于 0 时表示不必遵从所有条件。
constraint_set2_flag
// reserved_zero_5bits 在目前的标准中本句法元素必须等于 0,其他的值保留做将来用,解码器应该忽略本句法元素的值。
reserved_zero_5bits /* equal to 0 */
level_idc
// seq_parameter_set_id 指明本序列参数集的 id 号,这个 id 号将被 picture 参数集引用,本句法元素的值应该在[0,31]。
seq_parameter_set_id
// log2_max_frame_num_minus4 这个句法元素主要是为读取另一个句法元素 frame_num 服务的,frame_num 是最重要的句法元素之一,它标识所属图像的解码顺序 。这个句法元素同时也指明了 frame_num 的所能达到的最大值: MaxFrameNum = 2*exp( log2_max_frame_num_minus4 + 4 )
log2_max_frame_num_minus4
// pic_order_cnt_type 指明了 poc (picture order count) 的编码方法,poc 标识图像的播放顺序。由poc 可以由 frame-num 通过映射关系计算得来,也可以索性由编码器显式地传送。
pic_order_cnt_type
if( pic_order_cnt_type == 0 )
// log2_max_pic_order_cnt_lsb_minus4 指明了变量 MaxPicOrderCntLsb 的值: MaxPicOrderCntLsb = pow(2, (log2_max_pic_order_cnt_lsb_minus4 + 4) )
log2_max_pic_order_cnt_lsb_minus4
else if( pic_order_cnt_type == 1 ) {
// delta_pic_order_always_zero_flag 等于 1 时,句法元素 delta_pic_order_cnt[0]和 delta_pic_order_cnt[1]
不在片头出现,并且它们的值默认为 0; 本句法元素等于 0 时,上述的两个句法元素将在片头出现。
delta_pic_order_always_zero_flag
// offset_for_non_ref_pic 被用来计算非参考帧或场的 POC,本句法元素的值应该在[pow(-2, 31) , pow(2, 31) – 1]。
offset_for_non_ref_pic
// offset_for_top_to_bottom_field 被用来计算帧的底场的 POC, 本句法元素的值应该在[pow(-2, 31) , pow(2, 31) – 1]。
offset_for_top_to_bottom_field
// num_ref_frames_in_pic_order_cnt_cycle 被用来解码POC, 本句法元素的值应该在[0,255]。
num_ref_frames_in_pic_order_cnt_cycle
// offset_for_ref__frame[i] 用于解码 POC,本句法元素对循环num_ref_frames_in_pic_order_cycle 中的每一个元素指定一个偏移。
for( i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ )
offset_for_ref_frame[ i ]
}
// num_ref_frames 指定参考帧队列可能达到的最大长度,解码器依照这个句法元素的值开辟存储区,这个存储区用于存放已解码的参考帧,H.264 规定最多可用 16 个参考帧,本句法元素的值最大为 16。值得注意的是这个长度以帧为单位,如果在场模式下,应该相应地扩展一倍。
num_ref_frames
// gaps_in_frame_num_value_allowed_flag 这个句法元素等于 1 时,表示允许句法元素 frame_num 可以不连续。当传输信道堵塞严重时,编码器来不及将编码后的图像全部发出,这时允许丢弃若干帧图像。
gaps_in_frame_num_value_allowed_flag
// pic_width_in_mbs_minus1 本句法元素加 1 后指明图像宽度,以宏块为单位: PicWidthInMbs = pic_width_in_mbs_minus1 + 1 通过这个句法元素解码器可以计算得到亮度分量以像素为单位的图像宽度: PicWidthInSamplesL = PicWidthInMbs * 16
pic_width_in_mbs_minus1
// pic_height_in_map_units_minus1 本句法元素加 1 后指明图像高度: PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1
pic_height_in_map_units_minus1
// frame_mbs_only_flag 本句法元素等于 0 时表示本序列中所有图像的编码模式都是帧,没有其他编码模式存在;本句法元素等于 1 时 ,表示本序列中图像的编码模式可能是帧,也可能是场或帧场自适应,某个图像具体是哪一种要由其他句法元素决定。
frame_mbs_only_flag
// mb_adaptive_frame_field_flag 指明本序列是否属于帧场自适应模式。mb_adaptive_frame_field_flag等于1时表明在本序列中的图像如果不是场模式就是帧场自适应模式,等于0时表示本序列中的图像如果不是场模式就是帧模式。。表 列举了一个序列中可能出现的编码模式:
if( !frame_mbs_only_flag )
mb_adaptive_frame_field_flag
// direct_8x8_inference_flag 用于指明 B 片的直接和 skip 模式下运动矢量的预测方法。
direct_8x8_inference_flag
// frame_cropping_flag 用于指明解码器是否要将图像裁剪后输出,如果是的话,后面紧跟着的四个句法元素分别指出左右、上下裁剪的宽度。
frame_cropping_flag
if( frame_cropping_flag ) {
frame_crop_left_offset
frame_crop_right_offset
frame_crop_top_offset
frame_crop_bottom_offset
}
// vui_parameters_present_flag 指明 vui 子结构是否出现在码流中,vui 用以表征视频格式等额外信息。
vui_parameters_present_flag
if( vui_parameters_present_flag )
vui_parameters( )
rbsp_trailing_bits( )
}