《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!
“纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!
链接地址:H.264/AVC视频编解码技术详解
GitHub代码地址:点击这里
一、帧内预测模式在码流中的表示
在前面的博文:【H.264/AVC视频编解码技术详解】十二、解析H.264码流的宏块结构(下):H.264帧内编码宏块的预测结构中,我们解析了H.264码流中的宏块结构,并且分析了其中的语法元素mb_pred()。mb_pred()包含了帧内预测宏块的预测数据,其定义为:
其中,表示帧内预测模式的语法元素有:
- prev_intra4x4_pred_mode_flag和prev_intra8x8_pred_mode_flag:表示帧内预测模式预测标识。如果该标识位为1,表示帧内预测模式的预测值就是实际的模式,否则就需要另外传递实际的帧内预测模式。
- rem_intra4x4_pred_mode和rem_intra8x8_pred_modeg:表示额外传递的实际帧内预测模式。
- intra_chroma_pred_mode:表示色度分量的预测模式,取值范围为[0,3],分别代表DC、水平、垂直和平面模式。
二、计算实际的帧内预测模式
以4×4模式的亮度信号为例,该部分的解析方式定义在标准文档的8.3.1.1节。
1. 计算相邻块的位置等参数
帧内预测模式的计算也利用了相邻块的模式相关性计算,计算所需的相邻块需要的是子块上方和左方的块的信息。
计算相邻块的预测模式首先要得到相邻块的位置参数。
1.1 计算相邻块的位置
计算亮度4×4相邻块的方式在标准文档的6.4.10.4节指定:
该过程的输入数据为当前亮度4×4块在宏块内的索引序号luma4x4BlkIdx;
输出数据为:
- 相邻块A所属宏块地址mbAddrA:当前宏块或者左方宏块,根据luma4x4BlkIdx的不同选择;
- 相邻块A的子块索引luma4x4BlkIdxA:左邻域子块在mbAddrA宏块内的索引;
- 相邻块B所属宏块地址mbAddrB:当前宏块或者上方宏块,根据luma4x4BlkIdx的不同选择;
- 相邻块B的子块索引luma4x4BlkIdxB:上邻域子块在mbAddrB宏块内的索引;
左方相邻块信息mbAddrA、luma4x4BlkIdxA和右侧相信块信息mbAddrB、luma4x4BlkIdxB的计算方法:通过子块位置的
坐标差值(xD,yD)。
多数情况下,H.264中的一个block都以4×4像素为大小,宽高相等,因此上图中ABCD四个邻块的坐标差值分别为:
- 邻块A:(-1, 0)
- 邻块B:(0, -1)
- 邻块C:(4, -1)
- 邻块D:(-1,-1)
1.2 获取相邻块参考像素的位置
当前块的位置可以认为是当前块左上角像素对于整个宏块的相对位置(x,y),该部分在6.4.3中指定。计算中使用了InverseRasterScan函数,该函数是个分段函数,在标准文档中定义为公式(5-7)。
由此,可以得到参考像素对于当前宏块的相对位置:
(xN,yN) = (x + xD, y + yD);
1.3 通过参考像素的位置的相对位置获取相邻块的参数
我们已经可以获取相邻块相对于当前宏块的位置(xN,yN),通过(xN,yN)获取相邻块参数的方法定义与6.4.11.1节(只考虑帧编码情况)。通过(xN,yN)的取值情况,可以判断参考像素所在宏块的位置以及其他信息:
- xN < 0, yN < 0:参考像素位于当前宏块的左上角,宏块位置为mbAddrD。
- xN < 0, 0 <= yN < 16:参考像素位于当前宏块左侧,宏块位置为AmbAddrA。
- 0 <= xN < 16, yN < 0:参考像素位于当前宏块上方,宏块位置为mbAddrB。
- 0 <= xN < 16, 0 <= yN < 16:参考像素位于当前宏块内部,宏块位置即为当前宏块。
- xN >= 16, yN < 0:参考像素位于当前宏块右上方,宏块位置为mbAddrC。
- xN >= 16, 0 <= yN < 16:参考像素位于当前宏块右方,实际上不可获得。
- yN >=16:参考像素位于当前宏块的下一行,实际上不可获得。
从参考像素相对当前块位置(xN,yN)计算宏块位置,可以计算参考像素在参考宏块中的相对位置(xW,yW)。计算方法为:
xW = (xN + 16) % 16;
yW = (yN + 16) % 16;
1.4 获取指定的相邻块在所在宏块中的索引
如果参考像素所在的宏块实际上不可获得,则整个宏块索引皆定义为不可得,否则计算方式定义在6.4.12.1节中,即对于像素(x,y):
luma4x4BlkIdx = 8 * (y/8) + 4 * (x/8) + 2 * ((y%8)/4) + ((x%8)/4)
2. 推导flag数据dcPredModePredictedFlag
当满足以下条件的任意一种时,dcPredModePredictedFlag的值被设为1。
- 宏块AmbAddrA不可获得;
- 宏块AmbAddrB不可获得;
- 宏块AmbAddrA可获得,但在标识位constrained_intra_pred_flag限定为1的情况下以帧间模式进行编码;
- 宏块AmbAddrA可获得,但在标识位constrained_intra_pred_flag限定为1的情况下以帧间模式进行编码;
在其他情况下,dcPredModePredictedFlag的值被设为0。
3. 推导出相邻块的帧内预测模式intra4x4PredMode
这一步骤的目的是推导出相邻块A和B的帧内预测模式。而实际上有时候,如某个相邻块不可获得那么其预测模式也会有相应的替代方法。具体来说:
- 如果dcPredModePredictedFlag被设为1,或者相邻块A或B所在的宏块不是Intra 4×4或8×8模式,那么A或者B的预测模式intra4x4(8x8)PredModeA/B的值设为2;
-
若不满足前款条件,则继续判断:
- 若相邻块A或B所在的宏块按照Intra4x4模式编码,那么其预测模式intra4x4(8x8)PredModeA/B设为Intra4x4PredMode[ luma4x4BlkIdxN ];
- 如果按照Intra8x8模式进行编码,其预测模式设为Intra8x8PredMode[ luma4x4BlkIdxN >> 2 ]。
4. 从相邻块的帧内预测模式得出当前块的预测模式
现在我们已经获得的信息有:
- 左方相邻块A的预测模式;
- 上方相邻块B的预测模式;
- 码流中读取的语法元素prev_intra4x4_pred_mode_flag;
从上述数据中获取当前块的预测模式的方式在文档8.3.1.1中如下表示:
predIntra4x4PredMode = Min( intraMxMPredModeA, intraMxMPredModeB )
if( prev_intra4x4_pred_mode_flag[ luma4x4BlkIdx ] )
Intra4x4PredMode[ luma4x4BlkIdx ] = predIntra4x4PredMode
else
if( rem_intra4x4_pred_mode[ luma4x4BlkIdx ] < predIntra4x4PredMode )
Intra4x4PredMode[ luma4x4BlkIdx ] = rem_intra4x4_pred_mode[ luma4x4BlkIdx ]
else
Intra4x4PredMode[ luma4x4BlkIdx ] = rem_intra4x4_pred_mode[ luma4x4BlkIdx ] + 1
- 从左侧和上方相邻块的预测模式中选取较小的一个作为预先定义模式。
- 判断码流中读取的标志位prev_intra4x4_pred_mode_flag,如果该标志位为1,则预先定义模式就是当前块的预测模式;
- 如果标志位prev_intra4x4_pred_mode_flag为0,则根据码流中解析出的语法元素 rem_intra4x4_pred_mode判断。如果rem_intra4x4_pred_mode的值小于预定义模式的值则选用rem_intra4x4_pred_mode;如果大于等于预定义模式,则当前块的预测模式设为rem_intra4x4_pred_mode + 1。
5. 总结
其实标准文档中定义的繁杂无比的解析方式,简而言之可以分为两步:
- 获取左侧和上方子块的预测模式,并选择较小的那个作为当前的模式预测值;
- 码流中指定了要不要用这个预测值。如果用,那么这个预测值就是当前块的帧内预测模式;否则就从后续读取的预测模式中计算。
三、获取预测数据
该部分主要实现的功能是通过参考数据和帧内预测模式,获取当前子块的预测块。此部分定义在标准文档的8.3.1.2节。该步骤通过当前块的预测模式以及相邻块的参考像素值,输出当前块的一个预测块数据。
对于某一个4×4像素块,其参考像素包括13个像素点。如果(x,y)表示参考像素点相对于像素块左上像素的相对位置,则x和y的取值分别为:x=-1, y=(-1, 3);x=(0, 7), y=-1。以图表示如下所示:
根据当前块在图像中的位置(xO,yO)与参考像素的相对位置,可获取参考像素的绝对位置(xN,yN),计算方式为(xN,yN)=(xO+x, yO+y)。并参考上一章的方法可以获取参考像素所属的宏块地址mbAddrN号及宏块内相对位置(xW,yW)。
获取13个参考像素的像素值,首先判断某个像素值是否有效/可以获得。当下列4个条件满足任意一个,那么该像素便被判定为无效/不可获得:
- 宏块mbAddrN不可获得;
- 宏块mbAddrN为帧间预测模式,且标识位constrained_intra_pred_flag为1;
- 宏块mbAddrN为SI类型,,且标识位constrained_intra_pred_flag为1,且当前宏块不是SI类型;
- x大于3,且块索引luma4x4BlkIdx为3或11;(这一条说明了什么意思?视频教程中有详细解释)
如果上述条件皆不满足,那么参考像素p(x,y)被判定为有效,即可以用作4x4帧内预测。在宏块编号mbAddrN及块内相对位置(xW,yW)均已获取之后,再由mbAddrN获取宏块在图像中的位置即可获取p(x,y)的像素值。需要注意的一个特殊情况是:若像素p(3,-1)可获得,而p(4,-1)、p(5,-1)、p(6,-1)、p(7,-1)都不可获得,那么p(4,-1)、p(5,-1)、p(6,-1)、p(7,-1)这4个无效点都将替换为p(3,-1)的值参与预测运算。
四、九种帧内预测模式
在获取了13个预测像素点之后,再根据第2节计算出的预测模式,便可以获取相应的预测块数据。该部分在标准文档的8.3.1.2.1到8.3.1.2.9节定义。
模式0:垂直模式
当前块的帧内预测模式为0时,使用垂直模式生成预测块。该模式只有预测像素p(0,-1)~p(3,-1)有效的条件下可用。预测块的计算方法为:
pred4x4L[ x, y ] = p[ x, −1 ], with x, y = 0..3
模式1:水平模式
当前块的帧内预测模式为1时,使用水平模式生成预测块。该模式只有预测像素p(-1,0)~p(-1,3)的条件下可用。预测块的计算方法为:
pred4x4L[ x, y ] = p[ −1, y ], with x,y = 0..3
模式2:DC模式
当前块的帧内预测模式为2时,使用DC模式生成预测块。DC模式下预测块的所有16个像素点取值一致,计算需要根据预测像素有效性分为一下不同情况:
- 如果上方相邻像素p(0,-1)~p(3,-1)和左方相邻像素p(-1,0)~p(-1,3)这8个点都有效,预测块的计算方法为
pred4x4L[x,y] = (p[0,−1] + p[1,−1] + p[2,−1] + p[3,−1] + p[−1,0] + p[−1,1] + p[−1,2] + p[−1,3] + 4) >> 3
- 如果上方相邻像素p(0,-1)~p(3,-1)无效,而左方相邻像素p(-1,0)~p(-1,3)有效,预测块的计算方法为
pred4x4L[ x, y ] = ( p[ −1, 0 ] + p[ −1, 1 ] + p[ −1, 2 ] + p[ −1, 3 ] + 2 ) >> 2
- 如果左方相邻像素p(-1,0)~p(-1,3)无效,而上方相邻像素p(0,-1)~p(3,-1)有效,预测块的计算方法为
pred4x4L[ x, y ] = ( p[ 0, −1 ] + p[ 1, −1 ] + p[ 2, −1 ] + p[ 3, −1 ] + 2 ) >> 2
- 如果上述8个相邻像素均无效,预测块的计算方法为:
pred4x4L[ x, y ] = ( 1 << ( BitDepthY − 1 ) )
模式3:左下对角线模式
当前块的帧内预测模式为3时,使用左下对角线模式生成预测块。该模式只有预测像素p(0,-1)~p(7,-1)有效的条件下可用。预测块的计算方法为:
- 当(x,y)=(3,3),即计算一个4×4像素块最右下方的一个点时:
pred4x4L[3,3] = ( p[ 6, −1 ] + 3 * p[ 7, −1 ] + 2 ) >> 2
- 其他情况:
pred4x4L[ x, y ] = ( p[ x + y, −1 ] + 2 * p[ x + y + 1, −1 ] + p[ x + y + 2, −1 ] + 2 ) >> 2
模式4:右下对角线模式
当前块的帧内预测模式为4时,使用左下对角线模式生成预测块。该模式只有上方预测像素p(0,-1)~p(3,-1)、左方预测像素p(-1,0)~p(-1,3)和左上角对角像素点p(-1,-1)有效的条件下可用。预测块的计算方法为:
- x > y :
pred4x4L[ x, y ] = ( p[ x − y − 2, −1] + 2 * p[ x − y − 1, −1 ] + p[ x − y, −1 ] + 2 ) >> 2
- x < y :
pred4x4L[ x, y ] = ( p[ −1, y − x − 2 ] + 2 * p[ −1, y − x − 1 ] + p[ −1, y − x ] + 2 ) >> 2
- x == y:
pred4x4L[ x, y ] = ( p[ 0, −1 ] + 2 * p[ −1, −1 ] + p[ −1, 0 ] + 2 ) >> 2
模式5:右垂直模式
当前块的帧内预测模式为5时,使用右垂直模式生成预测块。该模式只有上方预测像素p(0,-1)~p(3,-1)、左方预测像素p(-1,0)~p(-1,3)和左上角对角像素点p(-1,-1)有效的条件下可用。预测块的计算方法为:
首先计算中间变量:zVR = 2 * x - y,根据zVR取值
- 若zVR等于0/2/4/6:
pred4x4L[ x, y ] = ( p[ x − ( y >> 1 ) − 1, −1 ] + p[ x − ( y >> 1 ), −1 ] + 1 ) >> 1
- 若zVR等于1/3/5:
pred4x4L[ x, y ] = ( p[ x − ( y >> 1 ) − 2, −1] + 2 * p[ x − ( y >> 1 ) − 1, −1 ] + p[ x − ( y >> 1 ), −1 ] + 2 ) >> 2
- 若zVR等于-1:
pred4x4L[ x, y ] = ( p[ −1, 0 ] + 2 * p[ −1, −1 ] + p[ 0, −1 ] + 2 ) >> 2
- 其他情况,即zVR等于-2或-3:
pred4x4L[ x, y ] = ( p[ −1, y − 1 ] + 2 * p[ −1, y − 2 ] + p[ −1, y − 3 ] + 2 ) >> 2
模式6:下水平模式
当前块的帧内预测模式为6时,使用下水平模式生成预测块。该模式只有上方预测像素p(0,-1)~p(3,-1)、左方预测像素p(-1,0)~p(-1,3)和左上角对角像素点p(-1,-1)有效的条件下可用。预测块的计算方法为:
首先计算中间变量:zHD = 2 * y - x,根据zHD的取值
- 若zHD等于0/2/4/6:
pred4x4L[ x, y ] = ( p[ −1, y − ( x >> 1 ) − 1 ] + p[ −1, y − ( x >> 1 ) ] + 1 ) >> 1
- 若zHD等于1/3/5:
pred4x4L[ x, y ] = ( p[ −1, y − ( x >> 1 ) − 2 ] + 2 * p[ −1, y − ( x >> 1 ) − 1 ] + p[ −1, y − ( x >> 1 ) ] + 2 ) >> 2
- 若zHD等于-1:
pred4x4L[ x, y ] = ( p[ −1, 0 ] + 2 * p[ −1, −1 ] + p[ 0, −1 ] + 2 ) >> 2
- 其他情况,即zHD等于-2或-3:
pred4x4L[ x, y ] = ( p[ x − 1, −1 ] + 2 * p[ x − 2, −1 ] + p[ x − 3, −1 ] + 2 ) >> 2
模式7:左垂直模式
当前块的帧内预测模式为7时,使用左垂直模式生成预测块。该模式只有预测像素p(0,-1)~p(7,-1)有效的条件下可用。预测块的计算方法为:
- 若y等于0或2;
pred4x4L[ x, y ] = ( p[ x + ( y >> 1 ), −1 ] + p[ x + ( y >> 1 ) + 1, −1 ] + 1) >> 1
- 若y等于1或3:
pred4x4L[ x, y ] = ( p[ x + ( y >> 1 ), −1 ] + 2 * p[ x + ( y >> 1 ) + 1, −1 ] + p[ x + ( y >> 1 ) + 2, −1 ] + 2 ) >> 2
模式8:上水平模式
当前块的帧内预测模式为8时,使用上水平模式生成预测块。该模式只有预测像素p(-1,0)~p(-1,3)的条件下可用。预测块的计算方法为:
首先计算中间变量zHU:zHU = x + 2 * y;
- 若zHU等于0/2/4:
pred4x4L[ x, y ] = ( p[ −1, y + ( x >> 1 ) ] + p[ −1, y + ( x >> 1 ) + 1 ] + 1 ) >> 1
- 若zHU等于1或3:
pred4x4L[ x, y ] = ( p[ −1, y + ( x >> 1 ) ] + 2 * p[ −1, y + ( x >> 1 ) + 1 ] + p[ −1, y + ( x >> 1 ) + 2 ] + 2 ) >> 2
- 若zHU等于5:
pred4x4L[ x, y ] = ( p[ −1, 2 ] + 3 * p[ −1, 3 ] + 2 ) >> 2
- 其他情况,即zHU大于5:
pred4x4L[ x, y ] = p[ −1, 3 ]
五、亮度分量16×16模式的帧内预测方法
针对16×16模式亮度分量的帧内预测方法,定义于标准文档的8.3.3节。对于16×16模式的亮度块,共计需要33个参考像素点,分别为当前宏块的左侧16个点、上方16个点和左上角的一个像素点。类似于4×4模式,这33个参考像素相对于当前块相对位置为:
- p(-1, -1):左上角对角像素;
- p(-1,0)~p(-1,15):左侧相邻像素;
- p(0,-1)~p(15,-1):上方相邻像素;
获取这33个预测像素点的方法也类似于4×4模式预测像素的获取,主要步骤如获取所在宏块的地址、所在宏块左上角像素的地址、参考像素点相对于整个宏块的地址等。
整个16×16块的预测模式不需要专门推导,因为在宏块模式中就已经指定了。共定义4种预测模式:
模式0:垂直模式
只有预测像素p(0,-1)~p(15,-1)有效时才可以使用,计算方法为:
predL[ x, y ] = p[ x, −1 ], with x, y = 0..15
模式1:水平模式
只有预测像素p(-1,0)~p(-1,15)有效时才可以使用,计算方法为:
predL[ x, y ] = p[ −1, y ], with x, y = 0..15
模式2:DC模式
16×16模式的DC预测模式同4×4模式的DC预测方法类似,判断左侧16个像素和上方16个像素的有效性,将其中有效部分的均值作为整个预测块的像素值。
模式3:平面模式
平面模式是相对于其他模式的一种比较独特的预测模式,只有在上述33个预测像素点均有效时才可以使用。计算方法为:
predL[ x, y ] = Clip1Y( ( a + b * ( x − 7 ) + c * ( y − 7 ) + 16 ) >> 5 ), with x, y = 0..15
上式中的a/b/c为中间变量,计算方式为:
a = 16 * ( p[ −1, 15 ] + p[ 15, −1 ] )
b = ( 5 * H + 32 ) >> 6
c = ( 5 * V + 32 ) >> 6
六、色度分量的帧内预测模式
色度信息的帧内预测在标准文档的8.3.4节。其原理与亮度分量的预测模式基本一致。