ffmpeg开发之旅(1):视频直播YUV颜色格式完全解析

96
裂缝中的阳光dg
2017.05.14 23:37* 字数 3562

在AndroidAPI <= 20(Android5.0之前的版本)中Google支持的CameraPreview Callback的YUV常用格式有两种:一个是NV21,一个是YV12。如果我们需要对Camera采集的图像进行编码等,必须要对其进一步处理,比如格式转换、旋转等操作,否则会出现一些花屏、叠影等问题。

1.  YUV简介

YUV是一种亮度信号Y和色度信号U、V是分离的色彩空间,它主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。使用YUV的优点有两个:

(1)  彩色YUV图像转黑白YUV图像转换非常简单,这一特性用在于电视信号上;

(2)  YUV是数据总尺寸小于RGB格式;

2.  YUV与RGB区别

YUV的存储中与RGB格式最大不同在于,RGB格式每个点的数据是连继保存在一起的。即R,G,B是前后不间隔的保存在2-4byte空间中。而YUV的数据中为了节约空间,U,V分量空间会减小。每一个点的Y分量独立保存,但连续几个点的U,V分量是保存在一起的,通常人的肉眼察觉不出。

3.  YUV格式分析

YUV格式分为两种类型:Packed类型和Planar类型。其中,Packed类型是将YUV分量存在在同一个数组中,每个像素点的Y、U、V是连续交错存储的;Planar类型是将YUV分量分别存放到三个独立的数组中,且先连续存储所有像素点的Y,紧接着存储所有像素点的U,最后是所有像素点的V。

(1)  YUV采样格式

YUV码流的存储格式与采样方式密切相关,目前主流的采样方式有如下三种:YUV444、YUV422、YUV420,其中,YUV444采样是每一个Y对应一组UV分量,每个像素(YUV)占32Bits;YUV422采样是每两个Y共用一组UV分量,每个像素占16bits(Y占8bits、UV分量占8bits);YUV420采样是每四个Y共用一组UV分量,每个像素(YUV)占16bits或者12bits。通常,YUV A:B:C的意思一般是指基于4个象素来讲,其中Y采样了A次,U采样了B次,V采样了C次。假设以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量,三种采样格式表示如下图:

a) YUV444:YUV444即表示Y、U、V所占比为4:4:4,这种采样方式的色度值UV不会较少采样,Y、U、V分量各占一个字节,连同Alpha通道一个字节,YUV444每个像素占4字节,也就是说这个格式实质就是24bpp的RGB格式。采样示例:

如果原始数据四个像素是:A0Y0 U0 V0 ,A1 Y1 U1 V1,A2 Y2 U2 V2,A3 Y3 U3 V3

经过4:4:4采样后,数据仍为:A0Y0 U0 V0 ,A1 Y1 U1 V1,A2 Y2 U2 V2,A3 Y3 U3 V3

b) YUV422:YUV422即表示Y、U、V所占比为4:2:2,这种采样方式的色度值UV分量采样减半,比如第一个像素采样为Y、U,第二个像素采样Y、V,依此类推…YUV422每个像素占2个字节。采样示例:

如果原始数据四个像素是:Y0U0 V0 ,Y1 U1 V1,Y2 U2 V2,Y3 U3 V3

经过4:2:2采样后,数据变成:Y0U0 ,Y1 V1 ,Y2 U2,Y3 V3

c)YUV420:YUV420采样并不意味没有V分量,0的意思是U、V分量隔行才采样一次,比如第一行采样为4:2:0,第二行采样4:0:2,依此类推…YUV采样(每个像素)占用16bits或12bits。总之除了4:4:4采样,其余采样后信号重新还原显示后,会丢失部分UV数据,只能用相临的数据补齐,但人眼对UV不敏感,因此总体感觉损失不大。

(2)  YUV420采样分析

由于CameraPrevieCallback实时采集的视频帧格式为YV12或者NV21,它们都属于YUV420采样格式,接下来我们对这种格式进行一个简单的分析。YUV420格式所采样的采样格式为4:2:0,即4个Y分量共用一组UV分量,它所占内存为16bits/pixel或12Bits/Pixel(像素),而我们要研究的YV12和NV21每个像素的占内存为12bit,其中每个像素由一组YUV构成。该颜色格式对于每个像素Y、U、V分别占内存大小为Y=8bit=1Byte、U=2bit=1/4(Byte)、V=2bit=1/4(Byte),即一个像素中Y、U、V的比例为4:1:1。假设原始帧图像为640x480像素,它所占用的内存空间大小为:

640*480*(Y+Y/4+Y/4) =640*480*(1+1/4+1/4)*(1 Byte) = 640*480*(3/2)字节=450KB

其中,1个Y分量占内存1个字节(Byte),因此经过计算可知一帧640x480像素的YV12或NV21的图片所占内存大小为450KB,这也解释了我们在对Camera采集的YV12或NV21格式数据进行编码时,需要开辟一个大小为[widt*height*3/2]的字节数组作为缓存的原因。由于内存存储的最小单位为字节,Y、U、V分别占用内存空间为(以YV12类型为例,Plannar):

Y分量:(640*480)个字节,内存存储范围为0~ 640*480字节

V(Cr)分量:(640*480*(1/4))个字节,存储范围为640*480~ (1+1/4)*640*480字节

U(Cb)分量:(640*480*(1/4))个字节,存储范围为5/4*640*480~640*480*3/2字节

4  I420、YV12、NV12、NV21区别

(1)YUV420SP、YUV420P:属于YUV420格式。对于所有YUV420格式图像,它们的Y值排列完全相同,因为只有Y的图像是灰度图像。YUV420P中Y,U,V三个分量都是平面格式,分为I420(标准YUV420,YYYYYYYYUU VV)和YV12两种;YUV420SP中Y分量为平面格式,UV打包格式,分为NV12与NV21两种。需要注意的是,YUV格式的存放方式永远是先排列完Y分量,再排序U或V分量,不同的采样只是Y或V分量的排列格式和顺序不同。

(2)YV12、I420:属于YUV420P格式,每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。它是一种Plane模式,将Y、U、V分量分独立的三个plane依次存储,如下图所示。I420、YV12的Y值排序完全相同,只是U、V平面的位置不同,存储空间结构如下:

YV12 :亮度(行×列) +U(行×列/4) + V(行×列/4)

I420 :亮度(行×列) +V(行×列/4) + U(行×列/4)

举例:Y0Y1Y2Y3U0 V0(I420)、Y0Y1Y2Y3V0U0(YV12)。

(3)NV21、NV12(YUV420SP):NV21、NV12使用two-plane模式,即Y和UV分为两个Plane,但UV为交错存储,而不是分为三个plane。它们的采样格式为4:2:0,每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。

NV21、NV12的区别在于Y值排序完全相同,U和V交错排序,不同在于UV顺序:

* NV12存储方式:Y0Y1Y2Y3U0V0

* NV21存储方式:Y0Y1Y2Y3V0U0

总结:

I420:YYYYYYYY  UU VV    =>YUV420P(Plane模式)

YV12:YYYYYYYY  VV UU    =>YUV420P(Plane模式)

NV12:YYYYYYYY  UVUV    =>YUV420SP(2个Plannar,Y为平面模式,UV为打包模式)

NV21:YYYYYYYY  VUVU    =>YUV420SP(2个Plannar,Y为平面模式,UV为打包模式)

5. YUV格式数据的旋转、变换(根据存储方式处理,所有像素的Y排列在前面)

在使用MediaCodec对图像帧进行硬编码时,编码格式中的颜色参数一般为COLOR_FormatYUV420SemiPlanar或COLOR_FormatYUV420Planar。前者为半平面类型,存储格式为YYYYYYYUVUV;后者为Packet类型,存储格式为YYYYYYYYUU VV。因此,如果预览格式设置为NV21,MediaCodec编码颜色格式为COLOR_FormatYUV420SemiPlanar,就需要将NV21第二个平面里V和U的位置,才能够在编码后出现花屏或叠影。如果预览格式设置为YV12,MediaCodec编码颜色格式为COLOR_FormatYUV420Planar,就需要将YV12的第二个平面V和第三个平面U进行位置交换。

NV21(YYYYYYYYVUVUV)  ---- COLOR_FormatYUV420Planar(YYYYYYYYY UU VV)

NV21(YYYYYYYYVUVUV)  ----COLOR_FormatYUV420SemiPlanar(YYYYYYYY UVUV)

YV12(YYYYYYYY VV UU)  ----- COLOR_FormatYUV420Planar (YYYYYYYYY UUVV)

(1)  RGB与YUV互相转换

a)      RGB转YUV

Y = 0.299R + 0.587G + 0.114B

U'= (BY)*0.565

V'= (RY)*0.713

b)      YUV转RGB

R = Y + 1.403V'

G = Y - 0.344U' - 0.714V'

B = Y + 1.770U'

其中,RGB取值范围均为0~255,Y=0~255,U=-122~+122,V=-157~+157

(2)  NV21转COLOR_FormatYUV420Planar(I420)

存储格式:YYYYYYYY  VUVU(NV21) ~YYYYYYYY  UU VV(I420)

[java]view plaincopy

/**将NV21转换为I420

* @param nv21bytes 旋转后的nv21格式数据

* @param i420bytes 转换后的i420格式数据

*/

publicstaticvoidswapNV21toI420(byte[]nv21bytes,byte[] i420bytes,

intwidth,intheight) {

System.arraycopy(nv21bytes,0, i420bytes,0, width * height);// Y分量

for(inti = width * height; i < nv21bytes.length; i +=2) {

i420bytes[i]= nv21bytes[i +1];// U分量

i420bytes[i+1] = nv21bytes[i];// V分量

}

}

(3)  YV12转NV21

存储格式:YYYYYYYY VV UU(YV12) ---- YYYYYYYY VUVU(NV21)

[java]view plaincopy

privatevoid YV12toNV21(finalbyte[] input,finalbyte[] output,

finalint width,finalintheight) {

finalintframeSize =width * height;

finalintqFrameSize =frameSize /4;

finalinttempFrameSize= frameSize *5/4;

System.arraycopy(input,0, output,0, frameSize);//Y

for(inti =0; i

output[frameSize + i *2] = input[frameSize + i];// Cb(U)

output[frameSize + i *2+1] = input[tempFrameSize + i];// Cr(V)

}

}

(4)  YUV420sp(NV21)旋转90度

[java]view plaincopy

publicsynchronizedstaticbyte[]YUV420spRotate90ForBack(byte[] src,intwidth,

intheight) {

byte[]dest =newbyte[width * height *3/2];

intwh = width * height;

//旋转Y

intk =0;

for(inti =0; i < width; i++) {

for(intj = height -1; j >=0; j--) {

dest[k]= src[width * j + i];// dest[k] = src[]

k++;

}

}

//选择U、V分量

for(inti =0; i < width; i +=2) {

for(intj = height /2-1; j >=0; j--) {

dest[k]= src[wh + width * j + i];

dest[k+1] = src[wh + width * j + i +1];

k+=2;

}

}

returndest;

}

分析:以一幅分辨率为8x4图像为例,由上面分析可知,该图像占32个像素,每个像素对应一组YUV,每四个Y对应一组,且Y分量大小为32,U、V分量均为8。YUV图像顺时针旋转90度各分量分布和存储表现如下:

(5)  YUV420sp(NV21)顺时针旋转180度

[java]view plaincopy

/**

* 将后置摄像头采集的YUV图像帧旋转180度

*/

publicbyte[]YUV420spRotate180ForBack(byte[] src,intwidth,

intheight){

byte[]dest =newbyte[width * height *3/2];

intwh = width * height;

//旋转Y

intk =0;

for(inti=wh-1;i>=0;i--){

dest[k]= src[i];

k++;

}

//选择U、V分量

for(intj = wh *3/2-1; j >=wh  ; j-=2) {

dest[k]= src[j-1];

dest[k+1]= src[j];

k+=2;

}

returndest;

}

分析:以一幅分辨率为8x4图像为例,由上面分析可知,该图像占32个像素,每个像素对应一组YUV,每四个Y对应一组,且Y分量大小为32,U、V分量均为8。YUV图像顺时针旋转180度各分量分布和内存存储表现如下:

(6)  YUV420sp(NV21)顺时针旋转270度(逆时针90度)

[java]view plaincopy

/**

* 将后置摄像头采集的YUV图像帧旋转270度,即逆时针旋转90度

*/

publicsynchronizedstaticbyte[]YUV420spRotate270ForBack(byte[] src,intwidth,

intheight){

byte[]dest =newbyte[width * height *3/2];

intwh = width * height;

//旋转Y

intk =0;

for(inti = width-1; i >=0; i--) {

for(intj = height -1; j >=0; j--) {

dest[k]= src[width * j + i];

k++;

}

}

//选择U、V分量

for(inti = width-1; i >=0; i -=2) {

for(intj = height /2-1; j >=0; j--) {

dest[k]= src[wh + width * j + i-1];

dest[k+1] = src[wh + width * j + i];

k+=2;

}

}

returndest;

}

分析:以一幅分辨率为8x4图像为例,由上面分析可知,该图像占32个像素,每个像素对应一组YUV,每四个Y对应一组,且Y分量大小为32,U、V分量均为8。YUV图像顺时针旋转270度各分量分布和内存存储表现如下

码字不易,转载请声明出处:http://blog.csdn.Net/andrexpert/article/details/69267043

原始图像传感器采集图像:

最终处理效果如下所示,分别为旋转90、180、270度:

(1) 旋转 90度

(2)旋转180度

(3)选择270度

关于资料与Demo:

(1)    YV12,I420,YUV420P的区别:http://blog.chinaunix.net/uid-28458801-id-4638708.html

(2)    【Android】YUV使用总结:http://www.cnblogs.com/raomengyang/p/5793096.html

(3)    图文详解YUV420数据格式:http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html

(4)    Android视频采集编码颜色格式选择:http://blog.csdn.net/yuxiatongzhi/article/details/48708639

(5)    YUV格式分析:http://www.cnblogs.com/armlinux/archive/2012/02/15/2396763.html

(6)    官方文档:https://msdn.microsoft.com/en-us/library/aa904813(VS.80).aspx

日记本
Web note ad 1