赞
踩
视频直播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) 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 :亮度(行×列) +V(行×列/4) + U(行×列/4)
I420 :亮度(行×列) +U(行×列/4) + V(行×列/4)
举例:Y0Y1Y2Y3 U0 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存储方式: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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。