二、H264编码原理——SPS
1 SPS基本概念
SPS(Sequence Paramater Set)又称作序列参数集。对应的是针对一段连续编码视频序列的参数。包含 帧数、POC的约束、参考帧数目、解码图像尺寸和帧场编码模式选择标识等信息。
-
SPS NALU的
nal_unit_type
为 7. -
可以获取到视频流的帧率、长宽。
-
通过
pic_order_cnt_type
可以知道帧显示POC如何计算。
1.1 SPS每个字段含义
1.1.1 profile_idc
标识当前H.264码流的profile。我们知道,H.264中定义了三种常用的档次profile:
基准档次:baseline profile;
主要档次:main profile;
扩展档次:extended profile;
在H.264的SPS中,第一个字节表示profile_idc,根据profile_idc的值可以确定码流符合哪一种档次。判断规律为:
profile_idc = 66 → baseline profile;
profile_idc = 77 → main profile;
profile_idc = 88 → extended profile;
在新版的标准中,还包括了High、High 10、High 4:2:2、High 4:4:4、High 10 Intra、High 4:2:2 Intra、High 4:4:4 Intra、CAVLC 4:4:4 Intra等,每一种都由不同的profile_idc表示。
另外,constraint_set0_flag ~ constraint_set5_flag是在编码的档次方面对码流增加的其他一些额外限制性条件。
1.1.2 level_idc
标识当前码流的Level。编码的Level定义了某种条件下的最大视频分辨率、最大视频帧率等参数,码流所遵从的level由level_idc指定。
1.1.3 seq_parameter_set_id
表示当前的序列参数集的id。通过该id值,图像参数集pps可以引用其代表的sps中的参数。
1.1.4 log2_max_frame_num_minus4
用于计算MaxFrameNum的值。
计算公式为MaxFrameNum = 2^(log2_max_frame_num_minus4 + 4)
MaxFrameNum是frame_num的上限值,frame_num是图像序号的一种表示方法,在帧间编码中常用作一种参考帧标记的手段。
1.1.5 pic_order_cnt_type
表示解码picture order count(POC)的方法。POC是另一种计量图像序号的方式,与frame_num有着不同的计算方法。该语法元素的取值为0、1或2。
1.1.6 log2_max_pic_order_cnt_lsb_minus4
用于计算MaxPicOrderCntLsb的值,该值表示POC的上限。
计算方法为:MaxPicOrderCntLsb = 2^(log2_max_pic_order_cnt_lsb_minus4 + 4)
该变量在 pic_order_cnt_type = 0 时使用。
1.1.7 max_num_ref_frames
用于表示参考帧B帧的最大数目(连续B帧的数量)
1.1.8 gaps_in_frame_num_value_allowed_flag
标识位,说明frame_num中是否允许不连续的值。标识位为0表示不允许、标识位为1表示允许。
-
frame_num 可以不连续。当传输信道堵塞严重时,编码器来不及将编码后的图像全部发出,这时允许丢弃若干帧图像。在正常情况下每一帧图像都有依次连续的 frame_num 值,解码器检查到如果 frame_num 不连续,便能确定有图像被编码器丢弃。这时,解码器必须启动错误掩藏的机制来近似地恢复这些图像,因为这些图像有可能被后续图像用作参考帧。
-
不允许 frame_num 不连续,即编码器在任何情况下都不能丢弃图像。这时,H.264 允许解码器可以不去检查 frame_num 的连续性以减少计算量。这种情况下如果依然发生 frame_num 不连续,表示在传输中发生丢包,解码器会通过其他机制检测到丢包的发生,然后启动错误掩藏的恢复图像。
1.1.9 pic_width_in_mbs_minus1
用于计算图像的宽度。单位为宏块个数,因此图像的实际宽度为:
只有帧编码的情况下(frame_mbs_only_flag == 1):frame_width = 16 × (pic_width_in_mbs_minus1 + 1);
1.1.10 pic_height_in_map_units_minus1
用于计算图像的高度。
只有帧编码的情况下(frame_mbs_only_flag == 1):frame_height = 16 × (pic_height_in_map_units_minus1 + 1);
1.1.11 frame_mbs_only_flag
如果该标识位为1时,所有宏块都采用帧编码。
如果该标识位为0时,宏块可能是帧编码或者场编码。如果宏块可能是场编码,POC计算会涉及到 TopFieldOrderCnt
和 BottomFieldOrderCnt
1.1.12 mb_adaptive_frame_field_flag
标识位,说明是否采用了宏块级的帧场自适应编码。当该标识位为0时,不存在帧编码和场编码之间的切换;当标识位为1时,宏块可能在帧编码和场编码模式之间进行选择。
1.1.13 direct_8x8_inference_flag
标识位,用于B_Skip、B_Direct模式运动矢量的推导计算。
1.1.14 frame_cropping_flag
标识位,说明是否需要对输出的图像帧进行裁剪。
1.1.15 vui_parameters_present_flag
标识位,说明SPS中是否存在VUI信息。
2 SPS计算视频流帧率
视频帧率 = time_scale / (2 * num_units_in_tick)
3 SPS计算视频长宽
当视频宽度和视频高度均为16的整数倍时,且frame_mbs_only_flag字段值为1。此时视频宽高的计算式子简化如下:
width = (pic_width_in_mbs_minus1+1)*16;
height = (pic_height_in_map_units_minus1+1)*16;
4 关于POC的计算
-
pic_order_cnt_type == 0
表示把POC的高位和低位是分开的。低位就是slice中的pic_order_cnt_lsb
,高位则需要我们编码器自行计数求得。(POC求得方式还跟帧编码和场编码有关) -
pic_order_cnt_type == 1
依赖frame_num求解POC -
pic_order_cnt_type == 2
表示解码顺序和视频显示顺序是一样(视频中没有B帧或者最多只能有一个B帧)
4.1 第一种情况
pic_order_cnt_type == 0 情况:
在 H.264 视频编码中,当处理隔行扫描视频时,每个帧由一个顶场(top field)和一个底场(bottom field)组成。每个场都有自己的 POC(Picture Order Count),用于标识该场的显示顺序。
这里还要分两种情况,帧编码和场编码(根据 frame_mbs_only_flag
标识符)
如果 frame_mbs_only_flag == 0
宏块则可能是场编码或者帧编码,场编码就会分顶场TopFiledPOC和底场BottomFieldPOC
如果 frame_mbs_only_flag == 1
宏块则是帧编码,当前图像的POC = TopFiledPOC
为什么POC分高有效位(POCMsb)和低有效位(POCLsb):
答:由于POC会随着帧数增多而逐渐增大,如果直接编码POC,消耗的bit数回越来越大,因此,把POC分为低有效位(POCLsb)和高有效位(POCMsb)两部分。编码时只传输POCLsb,而POCMsb由前面帧计算得到。
prevPicOrderCntMsb
和prevPicOrderCntLsb
如何计算:
-
如果当前图像为IDR帧,prePicOrderCntMsb = prevPicOrderCntLsb = 0(由此可以计算得到IDR帧的高位和低位都是等于0)
-
如果当前图像非IDR帧,分三种情况:
- 前一个参考图像 mmco(memory_management_control_operation)= 5,并且前一个参考图像不为底场, 则prevPicOrderCntMsb=0;prevPicOrderCntLsb等于前一参考图像的TopfieldPOC;
- 前一个参考图像 mmco(memory_management_control_operation)=5,并且前一个参考图像是底场,则prevPicOrderCntMsb=prevPicOrderCntLsb=0;
- 前一个参考图像 mmco(memory_management_control_operation)不等于5,prevPicOrderCntMsb等于前一个参考图像的PicOrderCntMsb,prevPicOrderCntLsb等于前一个参考图像的pic_order_cnt_lsb
memory_management_control_operation 从slice header中获取。
如何计算 PicOrderCntMsb
:
/**
* @brief: 计算当前图片的高位POC,也就是 PicOrderCntMsb
* @param PrevPicOrderCntLsb:前一张图片的低位POC
* @param PrevPicOrderCntMsb:前一张图片的高位POC
* @param MaxPicOrderCntLsb:低位POC的最大值,查看 log2_max_pic_order_cnt_lsb_minus4 计算
*/
if( img->pic_order_cnt_lsb < img->PrevPicOrderCntLsb &&
( img->PrevPicOrderCntLsb - img->pic_order_cnt_lsb ) >= ( MaxPicOrderCntLsb / 2 ) )
img->PicOrderCntMsb = img->PrevPicOrderCntMsb + MaxPicOrderCntLsb;
else if ( img->pic_order_cnt_lsb > img->PrevPicOrderCntLsb &&
( img->pic_order_cnt_lsb - img->PrevPicOrderCntLsb ) > ( MaxPicOrderCntLsb / 2 ) )
img->PicOrderCntMsb = img->PrevPicOrderCntMsb - MaxPicOrderCntLsb;
else
img->PicOrderCntMsb = img->PrevPicOrderCntMsb;
计算当前图像TopFiledPOC
和BottomFieldPOC
:
/**
* @brief: 如果当前图像不是底场(顶场或帧格式),TopFieldOrderCnt 计算公式如下
*/
TopFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb
/**
* @brief: 如果当前图像不是顶场,BottomFieldOrderCnt 计算公式如下
* @param delta_pic_order_cnt_bottom: 从slice header中解析
* @param pic_order_cnt_lsb: 从slice header中解析
* @note: 一般底场的 pic_order_cnt_lsb 是顶场 pic_order_cnt_lsb + 1
*/
if( !field_pic_flag ) //帧格式
BottomFieldOrderCnt = TopFieldOrderCnt + delta_pic_order_cnt_bottom
else // 场格式
BottomFieldOrderCnt = PicOrderCntMsb + pic_order_cnt_lsb
帧编码举例,求解POC:
场编码举例,求解POC:
其实不难看出,帧编码的情况下,POC是递增2,因为帧编码不分顶场和底场。场编码情况下,POC是连续递增,底场通常是顶场加一。
4.3 第三种情况
pic_order_cnt_type == 2
比如我读取rtsp码流中,没有B帧,则frame_num就是即是解码顺序,也是显示顺序。
如果有一个B帧,则需要另外求解。