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

视频直播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 :亮度(行×列) +V(行×列/4) + U(行×列/4)

I420 :亮度(行×列) +U(行×列/4) + V(行×列/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。前者为半平面类型,存储格式为YYYYYYYYUU VV;后者为Packet类型,存储格式为YYYYYYYUVUV。因此,如果预览格式设置为NV21,MediaCodec编码颜色格式为COLOR_FormatYUV420SemiPlanar,就需要将NV21第二个平面里V和U的位置,才能够在编码后出现花屏或叠影。如果预览格式设置为YV12,MediaCodec编码颜色格式为COLOR_FormatYUV420Planar,就需要将YV12的第二个平面V和第三个平面U进行位置交换。

(1) COLOR_FormatYUV420Planar,值为19
特点:每2X2像素共用一个UV空间,即4个Y共用一个UV,其中,Y分量空间后面跟U分量,然后为V分量平面。物理像素和数据矩阵分布如下:

(2) COLOR_FormatYUV420PackedPlanar,值为20
特点:每2x2像素共用一个UV分量,并且将YUV打包到一个平面;

(3) COLOR_FormatYUV420SemiPlanar,值为21
特点:每2x2像素共用一个UV空间,Y分量后跟UV分量,其被交错打包到一个平面中;


(4) Camera采集YUV与编码器的YUV格式转换关系
YV12(YYYYYYYY VV UU)  ----- COLOR_FormatYUV420Planar (YYYYYYYYY UU VV)
NV21(YYYYYYYY VU VU)  ---- COLOR_FormatYUV420Planar(YYYYYYYYY UU VV)
NV21(YYYYYYYY VU VU)  ----COLOR_FormatYUV420SemiPlanar(YYYYYYYY UVUV)
NV21(YYYYYYYY VU VU)  ----COLOR_FormatYUV420PackedPlanar(YUYYUY YVYYVY)
 

6. 代码实现

(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)

 

/**将NV21转换为I420
* @param nv21bytes 旋转后的nv21格式数据
* @param i420bytes 转换后的i420格式数据
*/
public static void swapNV21toI420(byte[]nv21bytes, byte[] i420bytes,
intwidth, int height) {
    int yLength = width * height;
    int uLength = width * height / 4;
    System.arraycopy(nv21bytes,0, i420bytes, 0, yLength); // Y分量
    for(int i = 0; i <yLength/4; i++) {
        // U分量
        i420bytes[yLength + i] = nv21bytes[yLength + 2*i + 1];
        // V分量
        i420bytes[yLength + uLength + i] = nv21bytes[yLength + 2*i];
    }
}

 

(3)   YV12转NV21

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

 

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);             //Y
    for (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度

 

public synchronized static byte[]YUV420spRotate90ForBack(byte[] src, int width,
int height) {
byte[]dest = new byte[width * height * 3 / 2];
intwh = width * height;
//旋转Y
intk = 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度

 

/**
* 将后置摄像头采集的YUV图像帧旋转180度
*/
public byte[]YUV420spRotate180ForBack(byte[] src, int width,
int height){
byte[]dest = new byte[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(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度)

 

/**
* 将后置摄像头采集的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;
//旋转Y
intk = 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/AndroidRecordMp4


 欢迎大家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

(7)    MediaCodecInfo颜色格式说明:http://blog.csdn.net/jumper511/article/details/21719313

 

展开阅读全文
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值