美文网首页FFmpeg
Android音视频---YUV格式深入浅出(一)

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

作者: ZebraWei | 来源:发表于2019-10-31 13:50 被阅读0次

    版权声明:本文为卫伟学习总结文章,转载请注明出处!
    近期由于项目需要,开始接触视频像素格式,因此在这里做一个小结;
    像素格式描述了像素数据存储所用的格式,定义了像素在内存中编码方式。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分量,则这三种采样方式如下:

    即:
    • YUV 4:4:4采样,每一个Y对应一组UV
    • YUV 4:2:2采样,每两个Y共用一组UV分量
    • YUV 4:2:0采样,每四个Y公用一组UV分量
    2.2.YUV存储格式

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

    • packed: Y、U、V分量穿插着排列,三个分量存在一个Byte型数组里;
    • planar: Y、U、V分量分别存在三个Byte型数组中;

    常见的像素格式

    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 -
    

    注:

    • I420、YV12三个分量均为平面格式,即分别存在三个Byte型数组中;
    • NV12、NV21的存储格式为Y平面,UV打包,即Y信息存储在一个数组中,UV信息存储在一个矩阵中。

    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

    相关文章

      网友评论

        本文标题:Android音视频---YUV格式深入浅出(一)

        本文链接:https://www.haomeiwen.com/subject/yaizvctx.html