Fork me on GitHub

H.264/AVC编解码技术及JM源码分析(三)——宏块访问

作为基础,对其他宏块/子块的访问是编解码进行预测时所必不可少的操作,除此以外在许多别的地方也会用到,比如在对系数进行熵编码时。因此本文主要讲解源码中关于宏块访问的知识,这部分代码主要在mb_access.c中。

mb_is_available()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Boolean mb_is_available(int mbAddr, Macroblock *currMB)
{
VideoParameters *p_Vid = currMB->p_Vid;
//首先检测地址是否超出当前宏块或不在画面内
if ((mbAddr < 0) || (mbAddr > ((int)p_Vid->PicSizeInMbs - 1)))
return FALSE;

// the following line checks both: slice number and if the mb has been decoded
// 然后检测当前宏块是否在做去块效应滤波
if (!currMB->DeblockCall)
{
//如果不是则最后判断当前宏块和待检测宏块是否属于同一个slice
if (p_Vid->mb_data[mbAddr].slice_nr != currMB->slice_nr)
return FALSE;
}

return TRUE;
}

该函数用于检测宏块是否可用,实际上是被其他函数所调用的基础函数。输入参数是待检测宏块的地址和当前宏块,其中宏块地址是按光栅扫描的顺序的序号,即从左到右、从上到下,从0开始编号。主要的条件是宏待检测块需要在画面范围内且在当前宏块位置之前,同时和当前宏块要属于相同slice。因为一般情况下来自不同slice的宏块是不能被使用的,但是如果当前宏块是在做去块效应滤波的话可以忽略该限制。

CheckAvailabilityOfNeighbors()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void CheckAvailabilityOfNeighbors(Macroblock *currMB)
{
VideoParameters *p_Vid = currMB->p_Vid;
const int mb_nr = currMB->mbAddrX;
BlockPos *PicPos = p_Vid->PicPos;

// mark all neighbors as unavailable
// 预设为不可用
currMB->mb_up = NULL;
currMB->mb_left = NULL;

if (p_Vid->mb_aff_frame_flag)
{
//忽略一段
}
else
{
//赋值为周围4个宏块的地址
currMB->mbAddrA = mb_nr - 1;
currMB->mbAddrB = mb_nr - p_Vid->PicWidthInMbs;
currMB->mbAddrC = mb_nr - p_Vid->PicWidthInMbs + 1;
currMB->mbAddrD = mb_nr - p_Vid->PicWidthInMbs - 1;

//PicPos数组保存了一帧内所有宏块的坐标(以宏块大小为单位),通过宏块地址进行查找,左上角坐标为(0,0)
currMB->mbAvailA = (byte) (mb_is_available(currMB->mbAddrA, currMB) && ((PicPos[mb_nr ].x)!=0));
currMB->mbAvailB = (byte) (mb_is_available(currMB->mbAddrB, currMB));
currMB->mbAvailC = (byte) (mb_is_available(currMB->mbAddrC, currMB) && ((PicPos[mb_nr + 1].x)!=0));
currMB->mbAvailD = (byte) (mb_is_available(currMB->mbAddrD, currMB) && ((PicPos[mb_nr ].x)!=0));
}

if (currMB->mbAvailA) currMB->mb_left = &(p_Vid->mb_data[currMB->mbAddrA]);
if (currMB->mbAvailB) currMB->mb_up = &(p_Vid->mb_data[currMB->mbAddrB]);
}

此函数检测当前宏块的左(A)、上(B)、右上(C)、左上(D)的相邻宏块是否可用,其中左、右上、左上宏块还需要判断是否超出图像的左/右边界,因此后面多出一句判断x坐标的语句。但比较奇怪的是右上邻块应该判断的是右边界但是这里却判断了左边界,暂时未解。

getNonAffNeighbour()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
void getNonAffNeighbour(Macroblock *currMB, int xN, int yN, int mb_size[2], PixelPos *pix)
{
BlockPos *PicPos = currMB->p_Vid->PicPos;
if (xN < 0)
{
//xN和yN均为负值表明是左上的邻块中的像素
if (yN < 0)
{
pix->mb_addr = currMB->mbAddrD;
pix->available = currMB->mbAvailD;
}
//左邻块,yN不能超出一个宏块的距离,否则就不是邻块了
else if ((yN >= 0)&&(yN < mb_size[1]))
{
pix->mb_addr = currMB->mbAddrA;
pix->available = currMB->mbAvailA;
}
//否则不可访问
else
{
pix->available = FALSE;
}
}
else if ((xN >= 0)&&(xN < mb_size[0]))
{
//上邻块
if (yN<0)
{
pix->mb_addr = currMB->mbAddrB;
pix->available = currMB->mbAvailB;
}
//xN和yN都在一个宏块距离内,且是正值,显然就是当前宏块内的像素
else if (((yN >= 0)&&(yN < mb_size[1])))
{
pix->mb_addr = currMB->mbAddrX;
pix->available = TRUE;
}
else
{
pix->available = FALSE;
}
}
//右上邻块
else if ((xN >= mb_size[0])&&(yN < 0))
{
pix->mb_addr = currMB->mbAddrC;
pix->available = currMB->mbAvailC;
}
//否则不可用
else
{
pix->available = FALSE;
}

if (pix->available || currMB->DeblockCall)
{
//计算该像素在其所属宏块内的相对坐标
pix->x = (short) (xN & (mb_size[0] - 1));
pix->y = (short) (yN & (mb_size[1] - 1));
//计算该像素在帧内的绝对坐标
pix->pos_x = (short) (pix->x + PicPos[ pix->mb_addr ].x * mb_size[0]);
pix->pos_y = (short) (pix->y + PicPos[ pix->mb_addr ].y * mb_size[1]);
}
}

此函数作用是在非帧场自适应模式下获取相邻(或当前)宏块内某个像素在帧内的绝对坐标(以像素为单位)以及在其所属宏块内的相对坐标(以像素为单位)。参数中xN和yN是相邻像素以当前宏块(currMB)的左上角像素为原点的相对坐标。mb_size是宏块大小,亮度宏块两个元素均为16,色度宏块两个元素均为8。pix就是用来保存获取到的像素信息的。

get4x4Neighbour()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void get4x4Neighbour (Macroblock *currMB, int block_x, int block_y, int mb_size[2], PixelPos *pix)
{
//非帧场自适应模式下实际上就是调用上一个函数
currMB->p_Vid->getNeighbour(currMB, block_x, block_y, mb_size, pix);

if (pix->available)
{
//因为是4x4块的原因,所有坐标要除以4
pix->x >>= 2;
pix->y >>= 2;
pix->pos_x >>= 2;
pix->pos_y >>= 2;
}
}

此函数与上一个函数相似,作用是获取某个相邻(或当前)4x4块在帧内的绝对坐标(以4x4块为单位)以及在其所属宏块内的相对坐标(以4x4块为单位)。参数block_x和block_y是4x4块内某个像素以当前宏块(currMB)的左上角像素为原点的相对坐标(以像素为单位),其余参数含义与上一函数相同。该函数多用于判断左邻和上邻4x4块的可用性。这里与上一个函数的区别要细细钻研!