在linux上开发ffmpeg需要安装的dev库
sudo apt-get install libavcodec-dev
sudo apt-get install libavformat-dev
sudo apt-get install libavfilter-dev
sudo apt-get install libjpeg-dev
sudo apt-get install libswscale-dev
sws_scale将yuv转换为rgb
输入m_pFrame存储的是yuv格式,输出到videoFrame是rgb格式,转换代码如下,先创建格式为AV_PIX_FMT_RGB24的swsContext,然后分配内存,再调用sws_scale完成转换。
videoFrame.width = m_pFrame->width;
videoFrame.height = m_pFrame->height;
int linesize[4];
uint8_t* data[4];
struct SwsContext *swsContext = NULL;
swsContext = sws_getContext(m_pFrame->width, m_pFrame->height, AVPixelFormat(m_pFrame->format),
videoFrame.width, videoFrame.height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, NULL, NULL, NULL);
av_image_alloc(data, linesize, videoFrame.width, videoFrame.height, AV_PIX_FMT_RGB24, 1);
sws_scale(swsContext, m_pFrame->data, m_pFrame->linesize, 0, m_pFrame->height, data, linesize);
videoFrame.lineSize = linesize[0];
videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height);
memcpy(videoFrame.frameData.data(), data[0], videoFrame.frameData.size());
sws_freeContext(swsContext);
avpicture_free((AVPicture *)data);
代码分析
- 首先获取SwsContext
- av_image_alloc根据width, height, pix_fmt初始化linesize[4], data[4]
- sws_scale色彩转换,完成yuv到rgb的转换
- 将data[0]的数据全部copy到framedata.data()
说明:data[0]中放的是rgb数据,非平面数据,所以data[1],data[2],data[3]都是没有数据的。
linesize[0]的值就是rgb数据的总size。
- 对于packed格式的数据(例如RGB24),会存到data[0]里面。
- 对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]…(YUV420P中data[0]存Y,data[1]存U,data[2]存V)
YUV背景知识
YUV Formats分成两个格式
- 紧缩格式(packed formats):将Y、U、V值存储成Macro Pixels数组,和RGB的存放方式类似。
- 平面格式(planar formats):将Y、U、V的三个分量分别存放在不同的矩阵中。
常用的YUV格式
- YUV 4:4:4采样,每一个Y对应一组UV分量,一个YUV占8+8+8 = 24bits 3个字节。
- YUV4:2:2采样,每两个Y共用一组UV分量,一个YUV占8+4+4 = 16bits 2个字节。
- YUV4:2:0采样,每四个Y共用一组UV分量,一个YUV占8+2+2 = 12bits 1.5个字节。
4:4:4表示每个像素采样YUV三个分量
- 4:2:2表示2:1的水平取样,垂直完全采样。
- 4:2:0表示2:1的水平取样,垂直2:1采样。
- 4:1:1表示4:1的水平取样,垂直完全采样。
YUV4:2:0并不是说只有U(即Cb),V(即Cr)一定为0,而是指U:V互相援引,时见时隐,也就是说对于每一个行,只有一个U或者V分量,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0…以此类推。
我们最常见的YUV420P和YUV420SP都是基于4:2:0采样的,所以如果图片的宽为width,高为heigth,在内存中占的空间为width*height * 3 / 2,其中前width * height的空间存放Y分量,接着width * height / 4存放U分量,最后width * height / 4存放V分量。
YUV420P又叫plane平面模式,Y , U , V分别在不同平面,也就是有三个平面,它是YUV标准格式4:2:0,主要分为:YU12和YV12。
YV12排列举例
- 2*2图像, 4个Y: YYYYVU;
- 4*4图像, 16个Y: YYYYYYYYYYYYYYYYVVVVUUUU
YU12格式(I420)
- YU12:亮度(行×列) + U(行×列/4) + V(行×列/4)
- YU12: YYYYYYYY UUVV => YUV420P
- YV12: YYYYYYYY VVUU => YUV420P
YUV420SP格式的图像阵列,首先是所有Y值,然后是UV或者VU交替存储,NV12和NV21属于YUV420SP格式,是一种two-plane模式,即Y和UV分为两个plane,但是UV(CbCr)为交错存储,而不是分为三个平面。
android手机从摄像头采集的预览数据一般都是NV21,存储顺序是先存Y,再VU交替存储,NV21存储顺序是先存Y值,再VU交替存储:YYYYVUVUVU,以
4 X 4 图片为例子,占用内存为 4 X 4 X 3 / 2 = 24 个字节
yuv viewer工具
-
https://github.com/liuyang1/yuv-viewer.git
-
需要注意的是Cb,Cr在计算过程中是会出现负数的,但是从-128到127这些数值都用一个字节表示,读取的时候就映射0到255这个区间,成为了无符号的值,所以要减去128,才能参与公式计算。
-
由于 U(Cb)、V(Cr) 取值范围是 [﹣128, 127],对应的浮点数表示为 [﹣0.5, 0.5];而在存储时,为了方便存储,跟Y数据一样,统一用一个(无符号)字节表示, 即取值范围是 [0, 255],对应的浮点数表示为:[0, 1],所以在读取是,需要 将 U(Cb)、V(Cr)的浮点值 [0, 1] 减去 128(0.5) 使其变为 :[﹣0.5, 0.5]。
常见错误
- 没有对计算得到的RGB数值进行判断,导致强制类型转换成unsigned char之后数据溢出了,从而导致图像上某些点变成红色和蓝色。