x264中遍历一帧图像的YUV所有像素
由于实现算法需要获取当前编码帧的所有亮度和色度的图像,通过调试代码发现了一些跟通常理解不一致的像素存储方式,主要是色度。在此前阅读编码器的过程中没有关注色度像素的需要,本次在获取色度像素时发现跟亮度像素的获取有较大差别。
实现位置
代码的实现位置位于 encoder/encoder.c->slices_write(x264_t *h) 函数中。传入函数的参数只有一个x264_t 的结构体 h。在编码器的整个编码过程中结构体x264_t几乎贯穿所有过程,其中保存了所有的编码所需要的信息。在结构体x264_t中有一个x264_frame_t *fenc,其中存储的是当前编码帧。
获取亮度像素
获取亮度像素较为简单,主要过程如下:
- 通过fenc->i_stride[0]获取亮度stride;
- 通过fenc->i_lines[0]获取图像帧的高,这个值一般等于图像高度;
- 通过h->fenc->plane[0]获取亮度像素指针;
- 最后通过像素指针的遍历即可获取所有像素。
获取色度像素
获取色度像素是本篇文章的核心,其过程跟亮度像素的获取有很大区别。
首先介绍下fenc中几个重要的变量,在编码器输入为420YUV_416x240时,对应变量的值为:
- i_stride:长度为3,i_stride[0]=i_stride[1]=512,i_stride[2]=0;
- i_width:长度为3,i_width[0]=416,i_width[1]=208,i_width[2]=0;
- i_lines:长度为3,i_lines[0]=240,i_lines[1]=120,i_lines[2]=0;
- planes:长度为3的指针数组,planes[0]和planes[1]有值,planes[2]为空。
从i_stride[2]=0,i_width[2]=0,i_lines[2]=0,planes[2]为空可以看出,所有的像素都存储在planes[0]和planes[1]中,又因为planes[0]存储的是亮度像素,故色度像素的U和V都存储在planes[1]中。
起初认为U和V是类似于下图中的存储方式并列排放,但通过调试验证发现并不对。
最后通过不断尝试,发现在planes[1]中U和V的像素是交替存储的。
实现代码
int img_width = h->param.i_width;
int img_height = h->param.i_height;
int img_stride = h->fenc->i_stride[0]; //luma
const uint8_t *img_plane = h->fenc->plane[0]; //luma
const uint8_t *img_plane1 = h->fenc->plane[1]; //chroma
int loca;
int loca1;
for(int mb_y = 0; mb_y < h->mb.i_mb_height; mb_y++)
{
for(int mb_x = 0; mb_x < h->mb.i_mb_width; mb_x++)
{
loca = mb_x * 16 + mb_y * 16 * img_stride;
loca1 = mb_x * 8 + mb_y * 8 * img_stride;
uint8_t pix = 0; //Y
uint8_t pix1 =0; //U
uint8_t pix2 =0; //V
for(int idx_y = 0; idx_y < 16; idx_y++)
{
for(int idx_x = 0; idx_x < 16; idx_x++)
{
pix = img_plane[loca + idx_x + idx_y * img_stride];
if (idx_x < 8 && idx_y < 8)
{
pix1 = img_plane1[loca1 + idx_x * 2 + idx_y * img_stride];
pix2 = img_plane1[loca1 + idx_x * 2 + idx_y * img_stride + 1];
}
}
}
}
}
获取YUV的方式如代码所示,由于一些原因写的是以宏块为单位分别获取的。本篇文章的核心在于UV的获取方式。