FFmpeg

Android音视频---YUV格式深入浅出(一)

2019-10-31  本文已影响0人  ZebraWei

版权声明:本文为卫伟学习总结文章,转载请注明出处!
近期由于项目需要,开始接触视频像素格式,因此在这里做一个小结;
像素格式描述了像素数据存储所用的格式,定义了像素在内存中编码方式。RGB和YUV为两种经常使用的像素格式。

1.RGB格式

一般较为熟悉,RGB图像具有三个通道R、G、B,分别对应红、绿、蓝三个分量,由三个分量的值决定颜色;通常,会给RGB图像加一个通道alpha,即透明度,于是共有四个分量共同控制颜色。

2.YUV格式

YUV是一种颜色编码格式,可以说YUV流媒体是原始数据,大部分的视频领域都在使用。它与RGB类似,但RGB更多的用于渲染时,而YUV则用于数据传输,因为它占用更少的频宽。当然,实时通讯为了降低带宽都会采用H264/H265编码。从字面理解,YUV的含义:Y代表亮点信息(灰度),UV分别代表色彩信息。
YUV像素格式来源与RGB像素格式,通过公司运算,YUV三分量可以还原RGB,YUV转RGB的公式如下:

 R = Y + 1.403V
 G = Y - 0.344U - 0.714V
 B = Y + 1.770U

一般,将RGB和YUV的范围均限制在[0,255]间,则有如下转换公式:

 R = Y + 1.403(V - 128)
 G = Y - 0.344(U - 128) - 0.714(V - 128)
 B = Y + 1.770(U - 128)
2.1.YUV采样

YUV相比于RGB格式最大的好处是可以做到保持图像质量降低不明显的前提下,减小文件大小。YUV格式之所以能够做到,是因为进行了采样操作。
YUV码流的存储格式与其采样方式密切相关,主流的采样方式有三种:YUV 4:4:4(YUV444),YUV 4:2:2(YUV422),YUV 4:2:0(YUV420)
若以以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量,则这三种采样方式如下:

即:
2.2.YUV存储格式

YUV存储可以分为两种:packd(打包) 和 planar(平面);

常见的像素格式

1.YUV422:YUYV、YVYU、UYVY、V

这四种格式每一种又可以分为2类(packed和planar),以YUYV为例,一个6*4的图像的存储方式如下:
Y Y Y Y Y Y                   
Y Y Y Y Y Y                  
Y Y Y Y Y Y                   
Y Y Y Y Y Y                    
U U U U U U                  Y U Y V Y U Y V Y U Y V
U U U U U U                  Y U Y V Y U Y V Y U Y V
V V V V V V                  Y U Y V Y U Y V Y U Y V
V V V V V V                  Y U Y V Y U Y V Y U Y V
- Planar -                          - Packed - 

2.YUV420

YUV420p: I420、YV12
YUV420sp: NV12、NV21
同样,对于一个6*4的图像,这四种像素格式的存储方式如下:
Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y
Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y
Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y
Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y      Y Y Y Y Y Y
U U U U U U      V V V V V V      U V U V U V      V U V U V U
V V V V V V      U U U U U U      U V U V U V      V U V U V U
 - I420 -          - YV12 -         - NV12 -         - NV21 -

注:

3.Android I420转NV21,NV21转I420

/**
 * Nv21:
 * YYYYYYYY
 * YYYYYYYY
 * YYYYYYYY
 * YYYYYYYY`------`     ````
 * VUVU
 * VUVU
 * VUVU
 * VUVU
 * <p>
 * 
 * YUV420P
 * YYYYYYYY
 * YYYYYYYY
 * YYYYYYYY
 * YYYYYYYY`------`     ````
 * UVUV
 * UVUV
 * UVUV
 * UVUV
 * <p> 
 * 
 * I420:
 * YYYYYYYY
 * YYYYYYYY
 * YYYYYYYY
 * YYYYYYYY
 * UUUU
 * UUUU
 * VVVV
 * VVVV
 */
public class I420NV21Test {

public static byte[] I420ToNV21(byte[] data, int width, int height) {
    byte[] ret = new byte[data.length];
    int total = width * height;
    
    ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);
    ByteBuffer bufferV = ByteBuffer.wrap(ret, total ,total/4);
    ByteBuffer bufferU = ByteBuffer.wrap(ret, total + total / 4, total /4);
    
    bufferY.put(data, 0, total);
    for(int i = 0; i < total /4; i+=1) {
        bufferV.put(data[total+i]);
        bufferU.put(data[i+total+total/4]);
    }
    return ret; 
}      
/**
 * nv21转I420
 * @param data 
 * @param width
 * @param height
 * @return
 */
public static byte[] nv21ToI420(byte[] data, int width, int height) {  
    byte[] ret = new byte[data.length];  
    int total = width * height;  

    ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);  
    ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4);  
    ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4);  

    bufferY.put(data, 0, total);  
    for (int i=total; i<data.length; i+=2) {  
        bufferV.put(data[i]);  
        bufferU.put(data[i+1]);  
    }  

    return ret;  
}

/*
* @param data Nv21数据
* @param dstData I420(YUV420)数据
* @param w 宽度
* @param h 长度
*/
public static byte[] Nv21ToI420(byte[] data, int w, int h) {
   byte[] dstData = new byte[data.length];  
   int size = w * h;
   // Y
   System.arraycopy(data, 0, dstData, 0, size);
   
   int sizef = size/4;
   for (int i = 0; i < sizef; i++) {
       dstData[size + i] = data[size + (i<<1) + 1]; //U
       dstData[size + (size>>2) + i] = data[size + (i<<1)]; //V
   }
   return dstData;
}

/**
* 将Nv21数据转换为Yuv420SP数据
* @param data Nv21数据
* @param dstData Yuv420sp数据
* @param w 宽度
* @param h 高度
*/
public static byte[] Nv21ToYuv420SP(byte[] data, int w, int h) {
   byte[] dstData = new byte[data.length]; 
   int size = w * h;
   // Y
   System.arraycopy(data, 0, dstData, 0, size);

   for (int i = 0; i < size / 4; i++) {
       dstData[size + i * 2] = data[size + i * 2 + 1]; //U
       dstData[size + i * 2 + 1] = data[size + i * 2]; //V
   }
   return dstData;
}

public static void main(String[] args) {
    //模拟nv21 数据    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2]
    //模拟I420 数据    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]

     byte[] data=new byte[30];
     for(int i=0;i<20;i++){
         data[i]=1;
     }

     for(int i=0;i<10;i++){
         if(i%2==0){
             data[i+20]=3;
         }else{
             data[i+20]=2;
         }
     }
     
     System.out.println("NV21 数据  --> "+Arrays.toString(data));
     byte[] i420data=nv21ToI420(data,5,4);
     System.out.println("I420 数据  --> "+Arrays.toString(i420data));
     byte[] nv21data=I420ToNV21(data,5,4);
     System.out.println("NV21 数据  --> "+Arrays.toString(nv21data));
     System.out.println("\n");
     byte[] i420data1=Nv21ToI420(data,5,4);
     System.out.println("方式二 I420 数据  --> "+Arrays.toString(i420data1));
     byte[] yuv420sp = Nv21ToYuv420SP(data,5,4);
     System.out.println("YUV420SP 数据  --> "+Arrays.toString(yuv420sp));
}

一张图片的大小为widthheight, YUV420图片数据的大小就是widthheight3/2
NV21数据格式
width
height个字节存的是每个像素的Y分量,后面的widthheight/2字节是VUVUVUVU…
如: YYYY…YYVUVUVUVU…
I420 是数据格式:
width
height个字节还是每个像素的Y分量,接下来的widthheight/4字节是U分量,最后的widthheight/4字节是V分量
如: YYYY…YYUUUUVVVV…
模拟数据测试结果如下:
其中: 1表示是Y, 2表示是U ,3表示是V

上一篇 下一篇

猜你喜欢

热点阅读