使用libyuv对YUV数据进行缩放、裁剪等操作
使用libyuv对YUV数据进行缩放、裁剪等操作
- 背景
为了局部录制手机屏幕画面,可以通过MediaProjection获取整个手机屏幕的画面,但是要如何截取指定区域的画面,并录制成视频呢?
- YUV数据格式
由于视频编码要求的数据通常是YUV。因此需要将MediaProjection获取的画面数据转换成YUV格式的数据。
首先我们来了解YUV数据有哪些格式?
YUV格式有两大类:planar和packed。
对于planar的YUV格式,Y、U、V数据是分别连续存储的,例如:先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
1、采样方式
YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0。这里用三个图来直观的表示采集的方式,以黑点表示采样该像素点的Y分量,以空心圆圈表示该像素点的UV分量。
其中:
-
- YUV 4:4:4采样,每一个Y对应一组UV分量
- YUV 4:2:2采样,每两个Y共用一组UV分量
- YUV 4:2:0采样,每四个Y共用一组UV分量
2、存储方式
-
- YUV422P(属于YUV422)
YUV422P也属于YUV422的一种,它是一种plane模式,即平面模式,并不是将YUV数据交错存储,而是先存放所有的Y分量,然后存储所有的U(Cb)分量,最后存储所有的V(Cr)分量,如上图所示,其每一个像素点的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即两个Y共用一个UV。比如,对于像素点Y00,Y01,其Cb、Cr的值均为Cb00,Cr00。
-
- YV12、YU12格式(属于YUV420)
YV12和YU12(也叫I420)都属于YUV420格式,是一种plane模式。将Y、U、V分量分别打包,依次存储。该格式4个Y分量共用一组UV分量。上图是YV12的存储方式,先存储所有Y,然后存储所有V,最后存储所有U分量。而YU12是先所有Y分量,然后所有U,最后存储所有V分量。
-
- NV12、NV21格式(属于YUV420)
NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV交错存储。上图是NV12存储格式,先存Y值,再UV交替存储。NV21则是先存Y值,再VU交替存储。
综上所述:YUV420分为YUV420P和Y420SP两种,YUV420P分为YU12和YV12。YUV420SP分为NV12和NV21。
I420(YU12): YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP
- Libyuv库介绍
Google开源的专门用于YUV数据处理的库,目前已经集成到ffmpeg中。用于实现YUV与RGB之间相互转换,YUV旋转、缩放、裁剪等处理。
- RGB转化为I420
首先,需要将RGB转换成I420格式。其中sdl_ARGBToI420用到的是libyuv库的ABGRToI420方法,由于libyuv表示的排列顺序和Bitmap的RGBA表示的顺序是反向的,RGBAToI420方法转换后的颜色是错的,实际要调用ABGRToI420才能得到正确的结果。 我们来看一下它的传参:
rgbBuf:用于待转换的rgb数据。
srcStride:argb数据每一行的大小,如果是argb_8888格式的话这个值为wX4,argb4444的话值为wX2。
dstY:用于保存y分量数据。
dstStrideY:值为w*h。
dstU:用于保存u分量数据。
dstStrideUV:值为(w)/2。
dstV,:用于保存v分量数据。
dstStrideUV:值为(w)/2。
Width:位图宽度
Height:位图高度
上面的w和h分别对应width和height。
- I420转换成NV21
I420转换成NV21实际调用的是ConvertFromI420方法。它的第一个参数是srcY指的是I420数据中的Y分量。前面说过,I420的数据格式是YYYYUV,同时I420的数据大小是(width * height)* 3 /2,可以知道Y的数据大小是w*h,而u、v均为w*h/4。第二个参数标识Y分量的行间距,这里根据格式可知是像素位图宽度width。srcU、srcV分别是I420的UV分量dstBuf则是转换后的NV21数据。最后的width,height是位图的宽度和高度。
- I420数据裁剪
数据裁剪实际调用的是libyuv的ConvertToI420方法。裁剪需要传递的参数是裁剪的起始点坐标,即上面的lelf和top值,需要注意的是,left和top值需要为偶数,否则显示回有问题。然后需要传递裁剪的大小,即dst_width和height。width和height则是原始yuv的宽度和高度。
裁剪后尝试过两种放大的处理,一种是缩放处理,一种是填充黑点处理,缩放处理会导致变形,黑点处理则是在裁剪区域外填充黑色像素点。
4、yuv缩放处理
缩放的实现如上所示,实际调用I420Scale方法,传参为缩放前的yuv数据和缩放后的yuv数据以及相应的宽高。
5、未选中区域填充黑色
填充黑色是通过I420Copy方法实现的,首先需要初始化一帧黑色的yuv数据,然后定位到yuv复制的起始位置。