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

来源:互联网 发布:淘宝买家秀招聘 编辑:程序博客网 时间:2024/05/23 18:47
转载至: http://blog.csdn.NET/andrexpert/article/details/69267043

视频直播YUV颜色格式完全解析

                       –解决MediaCodec与Camera颜色空间不匹配导致的花屏、叠影等问题

 

 作者:

    蒋东国

 时间:

     2017年4月5日 星期三                                              

 应用来源:

     zbj 测试:华为nova

 博客地址:

   http://blog.csdn.NET/andrexpert/article/details/69267043  

      

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

(2)  YV12I420:属于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)

举例:Y0Y1Y2Y3 U0 V0(I420)、Y0Y1Y2Y3V0U0(YV12)。


(3)NV21NV12(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存储方式:Y0Y1Y2Y3 U0V0

* NV21存储方式:Y0Y1Y2Y3  V0U0



总结:

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 plain copy
print?
  1. /**将NV21转换为I420 
  2. * @param nv21bytes 旋转后的nv21格式数据 
  3. * @param i420bytes 转换后的i420格式数据 
  4. */  
  5. public static void swapNV21toI420(byte[]nv21bytes, byte[] i420bytes,  
  6. intwidth, int height) {  
  7. System.arraycopy(nv21bytes,0, i420bytes, 0, width * height); // Y分量  
  8. for(int i = width * height; i < nv21bytes.length; i += 2) {  
  9. i420bytes[i]= nv21bytes[i + 1]; // U分量  
  10. i420bytes[i+ 1] = nv21bytes[i]; // V分量  
  11. }  
  12. }  
/**将NV21转换为I420* @param nv21bytes 旋转后的nv21格式数据* @param i420bytes 转换后的i420格式数据*/public static void swapNV21toI420(byte[]nv21bytes, byte[] i420bytes,intwidth, int height) {System.arraycopy(nv21bytes,0, i420bytes, 0, width * height); // Y分量for(int i = 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 plain copy
print?
  1. privatevoid YV12toNV21(final byte[] input, final byte[] output,  
  2.                                           finalint width, final int height) {  
  3. final int frameSize =width * height;  
  4. final int qFrameSize =frameSize / 4;  
  5. final int tempFrameSize= frameSize * 5 / 4;  
  6. System.arraycopy(input,0, output, 0, frameSize);             //Y  
  7. for (int i = 0; i <qFrameSize; i++) {  
  8. output[frameSize + i *2] = input[frameSize + i];         // Cb(U)  
  9. output[frameSize + i *2 + 1] = input[tempFrameSize + i];  // Cr(V)  
  10. }  
  11. }  
privatevoid YV12toNV21(final byte[] input, final byte[] output,                                          finalint width, final int height) {final int frameSize =width * height;final int qFrameSize =frameSize / 4;final int tempFrameSize= frameSize * 5 / 4;System.arraycopy(input,0, output, 0, frameSize);             //Yfor (int i = 0; i <qFrameSize; 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 plain copy
print?
  1. public synchronized static byte[]YUV420spRotate90ForBack(byte[] src, int width,  
  2. int height) {  
  3. byte[]dest = new byte[width * height * 3 / 2];  
  4. intwh = width * height;  
  5. //旋转Y  
  6. intk = 0;  
  7. for(int i = 0; i < width; i++) {  
  8. for(int j = height - 1; j >= 0; j–) {  
  9. dest[k]= src[width * j + i];  // dest[k] = src[]  
  10. k++;  
  11. }  
  12. }  
  13. //选择U、V分量  
  14. for(int i = 0; i < width; i += 2) {  
  15. for(int j = height / 2 - 1; j >= 0; j–) {  
  16. dest[k]= src[wh + width * j + i];  
  17. dest[k+ 1] = src[wh + width * j + i + 1];  
  18. k+= 2;  
  19. }  
  20. }  
  21. returndest;  
  22. }  
public synchronized static byte[]YUV420spRotate90ForBack(byte[] src, int width,int height) {byte[]dest = new byte[width * height * 3 / 2];intwh = width * height;//旋转Yintk = 0;for(int i = 0; i < width; i++) {for(int j = height - 1; j >= 0; j--) {dest[k]= src[width * j + i];  // dest[k] = src[]k++;}}//选择U、V分量for(int i = 0; i < width; i += 2) {for(int j = 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 plain copy
print?
  1. /** 
  2. * 将后置摄像头采集的YUV图像帧旋转180度 
  3. */  
  4. public byte[]YUV420spRotate180ForBack(byte[] src, int width,  
  5. int height){  
  6. byte[]dest = new byte[width * height * 3 / 2];  
  7. intwh = width * height;  
  8. //旋转Y  
  9. intk = 0;  
  10. for(inti=wh-1;i>=0;i–){  
  11. dest[k]= src[i];  
  12. k++;  
  13. }  
  14. //选择U、V分量  
  15. for(int j = wh *3/2-1; j >=wh   ; j-=2) {  
  16. dest[k]= src[j-1];  
  17. dest[k+1]= src[j];  
  18. k+=2;  
  19. }  
  20. returndest;  
  21. }  
/*** 将后置摄像头采集的YUV图像帧旋转180度*/public byte[]YUV420spRotate180ForBack(byte[] src, int width,int height){byte[]dest = new byte[width * height * 3 / 2];intwh = width * height;//旋转Yintk = 0;for(inti=wh-1;i>=0;i--){dest[k]= src[i];k++;}//选择U、V分量for(int j = 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 plain copy
print?
  1. /** 
  2. * 将后置摄像头采集的YUV图像帧旋转270度,即逆时针旋转90度 
  3. */  
  4. public synchronized static byte[]YUV420spRotate270ForBack(byte[] src, int width,  
  5. int height){  
  6. byte[]dest = new byte[width * height * 3 / 2];  
  7. intwh = width * height;  
  8. //旋转Y  
  9. intk = 0;  
  10. for(int i = width-1; i >=0 ; i–) {  
  11. for(int j = height - 1; j >= 0; j–) {  
  12. dest[k]= src[width * j + i];  
  13. k++;  
  14. }  
  15. }  
  16. //选择U、V分量  
  17. for(int i = width-1; i >=0 ; i -= 2) {  
  18. for(int j = height / 2 - 1; j >= 0; j–) {  
  19. dest[k]= src[wh + width * j + i-1];  
  20. dest[k+ 1] = src[wh + width * j + i];  
  21. k+= 2;  
  22. }  
  23. }  
  24. returndest;  
  25. }  
/*** 将后置摄像头采集的YUV图像帧旋转270度,即逆时针旋转90度*/public synchronized static byte[]YUV420spRotate270ForBack(byte[] src, int width,int height){byte[]dest = new byte[width * height * 3 / 2];intwh = width * height;//旋转Yintk = 0;for(int i = width-1; i >=0 ; i--) {for(int j = height - 1; j >= 0; j--) {dest[k]= src[width * j + i];k++;}}//选择U、V分量for(int i = width-1; i >=0 ; i -= 2) {for(int j = 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度



GitHub项目地址:https://github.com/jiangdongguo/MediaCodecDemo 欢迎大家star~

关于资料与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


阅读全文
0 0
原创粉丝点击