H.264 定义
field:帧的交替行的集合。一帧由两个场组成,一个顶场(top field)和一个底场 (bottom field)。
frame:一个帧包含一个亮度(Y)样本数组和两个对应的色度(CrCb)样本数组。一个帧由两个 field 组成,一个顶场(top field)和一个底场(bottom field)。
- top field:组成一帧的两个场之一。顶场的每一行在空间上位于底场的相应行的正上方。
- bottom field:组成一帧的两个场之一。底场的每一行在空间上位于顶场的对应行的正下方。
picture:一个 field 或一个 frame 的统称。
视频信号可按完整的帧序列渐进地采样,或者是被隔行采样为隔行场序列。在交错视频序列中,两个场构成一幅视频帧,一个场包括一个完整视频帧的奇数行或者是偶数行。这种采样方法地优点是,在相同地数据率下,它在每秒钟发送地场数可以是相同渐进序列帧数的两倍,同时能给出更平滑的运动视觉效果。
slice group:picture 的 macroblock 或 macroblock pair 的子集。将 picture 分割 slice group 是对 picture 进行分区。分区由宏块到切片组映射指定(map[macroblock][slicegroup]
)。
slice:在特定 slice group 内的光栅扫描中 连续排序 的整数个宏块或宏块对。
光栅扫描(RasterScan) 是指从左往右,由上往下,先扫描完一行,再移至下一行起始位置继续扫描,H.264使用的主要就是光栅扫描顺序。
连续排序指的是在同一行连续或跨行时上一行的尾和当前行的头连续,即在扫描的顺序中连续。
- I slice:使用来自同一 slice 内的解码样本的预测进行解码的 slice,且不是 SI slice 的 slice。
- P slice:使用来自同一 slice 内的解码样本的帧内预测 或 来自前置解码的参考图片的帧间预测来解码的 slice,最多使用一个运动矢量和参考索引来预测每个块的样本值。
- B slice:使用来自同一 slice 内的解码样本的帧内预测或来自先前解码的参考图片的帧间预测来解码的切片,使用最多两个运动矢量和参考索引来预测每个块的样本值。
- SI slice (switching I slice):使用来自同一 slice 内的解码样本的预测和预测样本的量化来编码的 slice。可以对 SI slice 进行编码,使 SI slice 的解码样本的构造与 SP slice 相同。
- SP slice (switching P slice):使用来自前置解码的参考图片的帧间预测进行编码的切片,最多使用一个运动矢量和参考索引来预测每个块的样本值。可以对 SP slice 进行编码,使 SP slice 的解码样本的构造与另一个 SP 切片或 SI 切片相同。
macroblock :picture 中的一个16x16的像素块,包含一个 16x16 的亮度样本块和两个相应的色度样本块(色度样本块的数量视采样模式而定)。
- field macroblock :一个包含的 samples 来自单个 field 的 macroblock。一个 coded field 的 macroblocks 都是 field macroblocks 。当使用 macroblock-adaptive frame/field (宏块自适应帧/场)解码时,一个 coded frame 的 macroblocks 可能是 field macroblocks 。
- frame macroblock :一个包含的 samples 来自一个 coded frame 中两个 fields 的 macroblock 。当不使用 macroblock-adaptive frame/field (宏块自适应帧/场)解码时,coded frame 的所有宏块都是 frame macroblock 。当使用 macroblock-adaptive frame/field (宏块自适应帧/场)解码时,编码帧的一些宏块可能是frame macroblock,也可能是 field macroblocks。
macroblock pair :picture 中一对垂直连续宏块。
- field macroblock pair :解码为两个场宏块的宏块对。
- frame macroblock pair :解码为两个帧宏块的宏块对。
macroblock to slice group map :一种将 picture 的 **macroblock ** 映射到 slice group 的方法。macroblock 到 slice group 的映射由一个数字列表组成,每个编码宏块一个,指定每个编码宏块所属的 slice group。
skipped macroblock :没有数据被编码的 macroblock ,且指示该 macroblock 在解码时跳过。这个指示可能对几个宏块是共同的。
macroblock partition :由 帧间预测的宏块划分 产生的亮度样本块和两个对应的色度样本块。
sub-macroblock :一个宏块的四分之一样本,即一个8x8的亮度块和对应的两个色度块。
sub-macroblock partition :由 帧间预测的子宏块划分 产生的亮度样本块和两个对应的色度样本块。
top macroblock of a macroblock pair :宏块对的顶部宏块,简称 top macroblock。包含 macroblock pair 的 top row (顶行)的样本(亮度样本和色度样本)。
- field macroblock pair ,top macroblock 表示 frame 的 top field 的 samples。
- frame macroblock pair ,top macroblock 表示 macroblock pair 上半部分 的 samples。
coded field:field 的编码表示。
coded frame:frame 的编码表示。
coded picture:picture 的编码表示。coded picture 可以是一个 coded field 或是 一个 coded frame 。Coded picture 是主编码图片(primary coded picture)或冗余编码图片(redundant coded picture)的一个总称,但不能同时指两者。
coded video sequence:a sequence of access units,由按解码顺序排序的 IDR 访问单元(IDR access unit)及其后跟的零个或多个非 IDR 访问单元(non-IDR access units)组成。
access unit:一组 包含 primary coded picture 的 NAL 单元。除了 primary coded picture 之外,一个 access unit 也可能包含一个或多个 redundant coded pictures 或 其他不包含 slices 或 slices data partitions 的 coded picture。access unit 的解码结果是一个 decoded picture 。
- primary coded picture:primary picture 的编码表示(符合H.264标准的可被正确解码的 bistream)。primary coded picture 包含一张 picture 的所有宏块。
-
redundant coded picture :primary picture 的 redundant picture 的编码表示 。redundant 即冗余,用于增强 primary picture的错误恢复能力。
- redundant coded picture 通常覆盖 primary picture 的一部分,因此不需要包含 primary coded picture 中的所有宏块。
- 当 primary coded picture 的 bitstream 在传输中出错时(即 bitstream 不符合H.264标准),才对 redundant coded picture 进行解码,并通过 redundant picture 恢复 primary picture。
- 通常只有 IDR picture 附有 redundant picture,因为 IDR picture 的损坏可能会破坏整个GOP。
- 顺便说一下,每个 primary coded picture 可能有多达127个 redundant pictures。因为单个access unit 最多可包含128张图片。
IDR access unit:primary coded picture 是 IDR picture 的访问单元。
IDR picture: slices 全部是 I slice 或 SI slice 类型的 coded picture 。在解码 IDR 图片后,解码进程将所有参考图片标记为"unused for reference"。在对 IDR 图片进行解码之后,可以对解码顺序中的所有后续 coded picture 进行解码,而无需从在 IDR 图片之前解码的任何图片进行帧间预测。每个 coded video sequence 的第一张图片是 IDR picture。
IDR(instantaneous decoding refresh):瞬时解码刷新。
random access : 随机访问:在比特流的开始点以外的点开始解码过程的行为流。
reference field:当 coded field 采用 P, SP 和 B slices 编码 或者 coded frame 采用 field macroblocks 编码时,reference field 可被用于帧间预测。
reference frame:当 coded frame 采用 P, SP 和 B slices 编码时,reference field 可被用于帧间预测。
reference picture:nal_ref_idc 不等于 0 的 picture (reference picture 表示当前 picture 被别的 coded picture 引用)。reference picture 包含的样本可用于在解码顺序的后续图片的解码过程中进行帧间预测。
reference index:reference picture 的索引列表。
syntax element:语法元素:比特流中表示的数据元素。
syntax structure:语法结构:零个或多个语法元素以特定的顺序一起出现在比特流中。
picture parameter set :一种语法结构,包含适用于零个或多个完整编码图片的语法元素,由每个 slice header 中的 pic_parameter_set_id 确定。
sequence parameter set :一种语法结构,包含适用于零个或多个完整编码视频序列的语法元素,由每个 slice header 中的 pic_parameter_set_id
的 picture parameter set 的 seq_parameter_set_id
确定。
NAL Unit :一种语法结构,包含指示要遵循的数据类型(nal unit type 指示 RBSP的数据类型)和包含该数据的 RBSP 形式的字节数据(必要时增加防竞争字节)。
RBSP(raw byte sequence payload):一种语法结构,包含整数个字节,封装在 NAL 单元中。 RBSP 要么是空的,要么是表示一个或多个语法元素的 SODB,后跟一个 RBSP stop bit,然后是零个或多个等于 0 的后续位。
RBSP stop bit:RBSP 中位于 SODB 后的一个等于 1 的 bit 。可以通过从 RBSP 的末尾搜索 RBSP 的 stop bit 来识别 RBSP 中的 SODB 的结束。RBSP stop bit 是 RBSP 中的最后一个非零位。
SODB(string of data bits):表示一个或多个语法元素的若干位序列,SODB 存在于 RBSP 内,在 RBSP stop bit 之前。在 SODB 中,最左边的位被认为是第一个和最高有效位,最右边的位被认为是最后一个和最低有效位。
NAL unit
nal_unit( NumBytesInNALunit ) {
forbidden_zero_bit /* 1 bit */
nal_ref_idc /* 2 bits */
nal_unit_type /* 5 bits */
NumBytesInRBSP = 0
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 /* equal to 0x03 (跳过防竞争字节)*/
} else {
rbsp_byte[ NumBytesInRBSP++ ]
}
}
}
-
forbidden_zero_bit
:应该等于 0 -
nal_ref_idc
:不等于 0 意味着 NAL unit 的内容为一个 sequence parameter set 或 一个 picture parameter set 或 一个 refrence picture 的 slice 或者 一个 reference picture 的 slice data partition 。 -
nal_unit_type
:指示 NAL unit 中的 RBSP data structure。- VCL NAL units 的 nal_unit_type 等于 1 to 5 。剩余的 NAL units 为 non-VCL NAL units 。
RBSP
Slice layer without partitioning syntax:包含 slice_data( )
的所有类别。
slice_layer_without_partitioning_rbsp( ) {
slice_header( )
slice_data( ) /* all categories of slice_data( ) syntax */
rbsp_slice_trailing_bits( )
}
slice data partitioning:一种基于与每个语法元素相关联的类别将所选语法元素 划分 为语法结构的方法。 当使用 slice data partitioning 时,单个 slice 的编码数据被分成三个单独的分区:partition A包含类别 2 的所有语法元素,partition B 包含类别 3 的所有语法元素,partition C 包含类别 4 的所有语法元素。如果语法元素未区分类别,则所有 partiion 都包含该语法元素。partition 的区别在于 slice_data( )
中的 macroblock_layer( )
中的 residual( )
。详见:T-REC-H.264-200305-S!!PDF-E.pdf 中的 7.3.4 Slice data syntax - 7.3.5.3.2 Residual block CABAC syntax
其中,C 列 表示 Category (类别)。
slice_data( ) {
...
do {
... /* all */
... /* category 2 */
if( moreDataFlag ) {
...
macroblock_layer( ) /* category 2, 3, 4 */
}
...
} while( moreDataFlag )
}
macroblock_layer( ) {
... /* all */
... /* category 2 */
if( CodedBlockPatternLuma > 0 | | CodedBlockPatternChroma > 0
|| MbPartPredMode( mb_type, 0 ) = = Intra_16x16 ) {
residual( ) /* category 3, 4 */
}
}
residual( ) {
... /* category 3 */
... /* category 3, 4 */
}
Slice data partition A RBSP semantics:slice_data( ) 含类别 2 的 slice_data( ) 的语法元素。Slice data partition A RBSP syntax:
slice_data_partition_a_layer_rbsp( ) {
slice_header( )
slice_id
slice_data( ) /* only category 2 parts of slice_data( ) syntax */
rbsp_slice_trailing_bits( )
}
Slice data partition B RBSP semantics:slice_data( ) 含类别 3 的语法元素。Slice data partition B RBSP syntax:
slice_data_partition_b_layer_rbsp( ) {
slice_id
if( redundant_pic_cnt_present_flag )
redundant_pic_cnt
slice_data( ) /* only category 3 parts of slice_data( ) syntax */
rbsp_slice_trailing_bits( )
}
Slice data partition C RBSP semantics:slice_data( ) 含类别 4 的语法元素。Slice data partition C RBSP syntax:
slice_data_partition_c_layer_rbsp( ) {
slice_id
if( redundant_pic_cnt_present_flag )
redundant_pic_cnt
slice_data( ) /* only category 4 parts of slice_data( ) syntax */
rbsp_slice_trailing_bits( )
}
slice header:coded slice 的 header(包含 slice 的相关信息)。
slice header syntax elements
-
first_mb_in_slice
: 指定 slice 中第一个宏块的地址。当 Annex A 中规定不允许任意 slice 顺序时,first_mb_in_slice 的值不得小于当前 picture 中解码顺序位于当前切片之前的任何其他切片的 first_mb_in_slice 的值。slice 的第一个宏块地址导出如下。- 如果 MbaffFrameFlag 等于 0 ,
first_mb_in_slice
是 slice 中第一个宏块的宏块地址,first_mb_in_slice 的范围为0 to PicSizeInMbs - 1
- 如果 MbaffFrameFlag 等于 1 ,
first_mb_in_slice * 2
是 slice 中第一个宏块的宏块地址,这是 slice 中第一个宏块对的 top macroblock,first_mb_in_slice
的范围为0 to PicSizeInMbs/2 - 1
- 如果 MbaffFrameFlag 等于 0 ,
-
slice_type
:
pic_parameter_set_id
:指定使用的 picture parameter set。-
frame_num
:用作 picture 的标识符,应由 bitstream 中的log2_max_frame_num_minus4 + 4
bits 进行表示,frame_num 的约束如下:- 定义变量
PrevRefFrameNum
:- 如果当前 picture 是 IDR picture,则
PrevRefFrameNum = 0
- 否则,PrevRefFrameNum 顾名思义:previous reference frame
frame_num
。
- 如果当前 picture 是 IDR picture,则
-
frame_num
的值约束如下:-
frame_num = 0
: picture 是 IDR picture 。 -
frame_num == PrevRefFrameNum
, 以下三个条件为 true 时成立- 当前图片和前面的参考图片在解码顺序上属于连续的访问单元。
- 当前图片和前面的参考图片是具有相反奇偶性的参考场
- 以下任意条件为真
- 前面的参考图片是 IDR 图片
- 前面的参考图片包括一个
memory_management_control_operation
语法元素等于 5。(当前面的参考图片包括等于5的memory_management_control_operation
语法元素时,PrevRefFrameNum
等于0。) - 在前面的参考图片之前有一个 primary coded picture,并且在前面的参考图片之前的 primary coded picture 的 frame_num 不等于 PrevRefFrameNum 。
- 在前面的参考图片之前有一个 primary coded picture,并且在前面的参考图片之前的 primary coded picture 不是参考图片 。
-
frame_num != PrevRefFrameNum
- 非
used for short-term reference
的 field 或 frame 的 frame_num 为UnusedShortTermFrameNum = ( PrevRefFrameNum + 1 ) % MaxFrameNum
。 - frame_num 的值约束如下:
- 如果
gaps_in_frame_num_value_allowed_flag
等于 0,则当前图片的 frame_num 的值应等于( PrevRefFrameNum + 1 ) % MaxFrameNum
- 如果
gaps_in_frame_num_value_allowed_flag
等于 1,则:- 如果
frame_num > PrevRefFrameNum
,则在以下任一条件为真的解码顺序中,比特流中不应有任何非参考图片在前一个参考图片之后和当前图片之前进行解码。- 非参考图片的 frame_num 的值小于 PrevRefFrameNum。
- 非参考图片的 frame_num 的值大于当前图片的 frame_num 的值。
- 否则
frame_num < PrevRefFrameNum
,在以下两个条件都为真的情况下,比特流中不应有任何非参考图片在前一个参考图片之后和当前图片之前的解码顺序。- 非参考图片的 frame_num 的值小于 PrevRefFrameNum。
- 非参考图片的 frame_num 的值大于当前图片的 frame_num 的值。
- 如果
- 如果
- 非
-
- 定义变量
field_pic_flag
:等于 1 时指示 slice 是 coded field 的 slice。bottom_field_flag
:等于 1 时 指示 slice 是 coded bottom field 的 slice 。idr_pic_id identifies
:指示是 IDR picture 的 slice 。
其中, pic_parameter_set_id
, frame_num
, field_pic_flag
, bottom_field_flag
, idr_pic_id
, pic_order_cnt_lsb
, delta_pic_order_cnt_bottom
, delta_pic_order_cnt[ 0 ]
, delta_pic_order_cnt[ 1 ]
, sp_for_switch_flag
, 和 slice_group_change_cycle
的值在同一个 coded picture 的所有 slice header 中应相同。
slice_id:标识与 data partition 关联的 slice。每个 slice 在 所属的 coded picture 内具有唯一的 slice_id。当 Annex A 中规定不允许任意切片顺序时,在解码顺序中,编码图像的第一个切片的 slice_id 应等于 0,并且对于coded picture 的每个后续切片, slice_id 的值应以 1 进行递增。
H.264 视频分层结构
在H.264中,语法元素被组织成五个层次:
- 序列 (sequence)
- 图像 (picture => frame or filed)
- 分片 (slice)
- 宏块 (macroblock)
- 子块 (sub-block)
- access unit: A set of NAL units always containing a primary coded picture.
- coded video sequence: A sequence of access units that consists.
- in decoding order, of an instantaneous decoding refresh (IDR) access unit followed by zero or more non-IDR access units including all subsequent access units up to but not including any subsequent IDR access unit.
- IDR access unit: An access unit in which the primary coded picture is an IDR picture.
- IDR picture: A coded picture containing only slices with I or SI slice types that causes a "reset" in the decoding process
H.264 码流分层结构
NAL unit 和 coded picture 的顺序以及与访问单元的关联
在 primary coded picture 的最后一个 VCL NAL unit 之后,首个以下的任何 NAL unit 指定新访问单元的开始:
- access unit delimiter NAL unit (when present)
- sequence parameter set NAL unit (when present)
- picture parameter set NAL unit (when present)
- SEI NAL unit (when present)
- NAL units with nal_unit_type in the range of 13 to 18, inclusive
- first VCL NAL unit of a primary coded picture (always present)
访问单元内的 coded picture 和非 VCL NAL 单元的顺序应遵守以下约束:
- 当存在 access unit delimiter(间隔符) NAL unit 时,它应该是第一个 NAL 单元。任何访问单元中最多有一个 access unit delimiter NAL unit 。
- 当存在任何 SEI NAL units 时,它们应位于 primary coded picture 之前。
- 当存在包含 buffering period SEI message 的 SEI NAL unit 时,buffering period SEI message 应是该访问单元中第一个 SEI NAL unit 的第一个 SEI message payload 。
- primary coded picture 应在相应的 redundant coded pictures 之前。
- 当存在 redundant coded pictures 时,它们应按redundant_pic_cnt 值的升序排列。
- 当 end of sequence NAL unit 出现时,它应跟随在 primary coded picture 和所有 redundant coded pictures(如果有)之后。
- 当end of stream NAL unit 存在时,它应是最后一个 NAL unit。
- nal_unit_type 等于 0、12 或在 19 到 31 范围内(含)的 NAL unit 不得位于 primary coded picture 的第一个 VCL NAL 之前。
- Sequence parameter set NAL units 或 picture parameter set NAL units 可能存在于访问单元中。
H.264 封装格式
Annexb 格式
Annexb 格式主要用于实时播放(直播流),每一个NAL单元前面都有一个 StartCode (起始码)。
一共有两种起始码 start_code
-
0x000001
3 字节,用在单帧多 slice(即单帧多个NALU)之间间隔。 -
0x00000001
4 字节,用在帧之间,或者SPS等之前。
防字节竞争处理:用起始码定位NALU边界存在一个问题,即NALU中可能存在与起始码相同的数据。为了防止这个问题,在构建NALU时,需要在数据中的0x000000,0x000001,0x000002,0x000003中插入防竞争字节(Emulation Prevention Bytes)0x03 。
AVCC 格式
AVCC 格式主要用于存储(如存储在硬盘的文件:FLV、MP4、MKV通常用AVCC格式来存储。),AVCC格式不使用起始码作为NALU的分界,AVCC在每个NALU前都加上一个大端格式的前缀(1、2、4字节,代表NALU长度)。
在解析 AVCC 格式的时候需要将指定的前缀字节数的值保存在一个头部对象中,这个都通常称为 sequence header 。同时,SPS 和 PPS 数据也需要保存在 sequence header 中。(这些数据的存储和传输是文件容器的任务(即 FLV 中的 AVC Sequence header),超出了本文的范畴)。
虽然 AVCC 格式不使用起始码,但防竞争字节还是有的。因此在构建 NALU 时,同样在数据中的0x000000,0x000001,0x000002,0x000003中插入防竞争字节(Emulation Prevention Bytes) 0x03 。
RTP 格式
RTP 格式主要用于网络传输(例如:RTC)。而在IP网络中,当要传输的IP报文大小超过 最大传输单元MTU 时就会产生IP分片情况。若交给底层协议拆包容易出问题,因此此时需要主动拆分NALU再打包成RTP包后发送。
RTP 的 NALU :
Single NAL Unit Packet: payload 中仅包含一个 NAL 单元。 NAL header 的 type 等于原始 NAL 单元类型。
Aggregation packet:用于将多个 NAL 单元聚合为单个 RTP payload 的数据包类型。
Fragmentation unit: 用于将单个 NAL 单元分段到多个 RTP 数据包上。
Type Packet Type name
-------------------------------------------------
0 undefined
1-23 NAL unit Single NAL unit packet per H.264
24 STAP-A Single-time aggregation packet
25 STAP-B Single-time aggregation packet
26 MTAP16 Multi-time aggregation packet
27 MTAP24 Multi-time aggregation packet
28 FU-A Fragmentation unit
29 FU-B Fragmentation unit
30-31 undefined
详见:rfc3984
思考
为什么需要多总格式?
媒体可分为本地文件和直播流:
如果是本地文件,则我们只需要读取一次SPS,PPS的信息,然后就可以一直进行解码,所以将SP,PPS等信息放到文件的头部,打开文件,先读取这些信息初始化解码器,就可以顺利的解码。如 FLV,MP4,MKV 等常用的本地存储方式采用 AVCC 封装 NALU。
如果是直播流,如果将SPS,PPS放到头部,那么中途播放的用户无法接收到SPS ,PPS信息,导致无法初始化解码器,所以必须每隔一段时间发送一次SPS,PPS等信息,一般是放在IDR帧之前进行发送,如 mpeg-ts 直播流采用 Annex-B 结构封装 NALU。
H.264 in FLV
在 NAL 中没有 frame 的概念,NAL 中包含的图像数据其实是 frame 的 Slice,一个 或 多个 Slice 构成 一个 frame。
H.264 在 FLV 中的 Frame 实际是一个包含 AVCPacket 的 FLV Video Tag 包含了一个 或 多个 NALU (即一个 或 多个 Slice)。比如 I Frame 由一个 或 多个 I Slice 构成。
FLV Video Tag 中的 AVCPacket 是使用 AVCC 格式封装的(AVCC在每个NALU前都加上一个大端格式的前缀代表NALU长度),因此可以知道一个 AVCPacket 中每个 NALU 的长度,从而解出每个 NALU 中的 Slice,从而得出一帧。