Android音视频—YUV格式深入浅出

文章参考:

  1. 图文详解YUV420数据格式
  2. YUV主要采样格式理解
  3. YUV格式详解
  4. 百度百科和维基百科

概述

本文基于Android音视频开发时需要的,对基础视频流YUV格式的认识。主要描述对YUV的基本认识YUV格式的区别Android音视频开发时常用到的YUV格式处理,转换,显示方法等。YUV格式的认识很多引用和参考上述博文,做了一些总结,也包括一些个人的理解,还有许多开发时遇到的功能或者问题的总结。

一、什么是YUV?

YUV是一种颜色编码格式,可以说YUV流媒体是原始流数据,大部分的视频领域都在使用。他与RGB类似,但RGB更多的用于渲染时,而YUV则用在数据传输,因为它占用更少的频宽。当然,实时通讯为了降低带宽都会采用H264/H265编码。从字面意思理解,YUV的含义:Y代表亮度信息(灰度),UV分别代表色彩信息。YUV的常用名称有许多,如YUV422这是大部分镜头出来的数据,还有许多(yuv420,yuv444等)。


YUV的 planar和packed的差别?####

这是yuv格式的两大类

  • planar格式:连续存储所有像素点Y,然后是所有像素点U,接着是V

  • packed格式:所有像素点的YUV信息连续交错存储

      比如:
          YUV420P:YYYYYYYY UU VV 
          YUV420: YUV YUV YUV 
    

YUV,YCbCr,YPbPr写法的含义

它们分别代表在不同领域时使用的名称,总的大类都是一致的。主流上所说的YUV即是YCbCr

  • YCbCr:其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量
  • YPbPr:他和YCbCr的区别在于YCbCr是数字系统的标识,YPbPr是模拟系统的标识。

怎么理解YUV后面的三个数字呢?

数字代表yuv信息在像素点中的分布状况,为了维持人的肉眼观感,通常需要每个像素点保存8bit的亮度,每2x2个点保存至少一个Cb和Cr值,如下所示(要理解它的排列就要知道,它在量化8bit之后,每个像素占用大小,可以参考文章:图文详解YUV420数据格式,它里面的描述图很好理解):

  1. YUV444采样,每个Y对应一组UV,8bit量化,每个像素占用3个字节。

    • 四个像素点: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
    • 存放码流: Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3
  2. YUV422采样,每2个Y对应一组UV,由两个水平方向相邻的像素组成的宏像素需要占用4字节内存,亮度2个字节,两个色度各1个字节。

    • 四个像素点: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
    • 存放码流: Y0 U0 Y1 V1 Y2 U2 Y3 V3
  3. YUV411采样,每4个Y对应一组UV,由4个水平方向相邻的像素组成的宏像素需要占用6字节内存,亮度4个字节,两个色度各1个字节。

    • 四个像素点: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
    • 存放码流: Y0 U0 Y1 Y2 V2 Y3
  4. YUV420采样,每4个Y对应一组UV,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。,亮度4个字节,两个色度各1个字节。

    • 四个像素点: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
      [Y5 U5 V5] [Y6 U6 V6] [Y7 U7 V7] [Y8 U8 V8]
    • 存放码流: Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8

YUV中stride跨距的含义?

跨距的由来,因为CPU存储和读取必须是2的密次方,故而很多分辨率的yuv格式通常会有一个stride,比如某个720*536的YUV420SP视频,它的stride是768,那么中间48就是跨距。通常如果自己去解析,可以通过偏移裁取,如果采用第三方库,一般都会有传入跨距的值。

  • 在Android中,setPreviewFormat中就有标注YV12的跨距计算方式:
    {@link android.graphics.ImageFormat#YV12}. For camera callback data,
     * it can be assumed that the stride of the Y and UV data is the
     * smallest possible that meets the alignment requirements. That is, if
     * the preview size is <var>width x height</var>, then the following
     * equations describe the buffer index for the beginning of row
     * <var>y</var> for the Y plane and row <var>c</var> for the U and V
     * planes:
     *
     * <pre>{@code
     * yStride   = (int) ceil(width / 16.0) * 16;
     * uvStride  = (int) ceil( (yStride / 2) / 16.0) * 16;
     * ySize     = yStride * height;
     * uvSize    = uvStride * height / 2;
     * yRowIndex = yStride * y;
     * uRowIndex = ySize + uvSize + uvStride * c;
     * vRowIndex = ySize + uvStride * c;
     * size      = ySize + uvSize * 2;
     * }

二、一些常见YUV格式的区别

1. YUV422—包含如:YUYV、UYVY、YUV422P

YUV422,大多数的Android机 sensor出来的视频流都是这个格式

  • YUYV:

    image
  • YVYU:

    image
  • YUV422P:2个Y对应一对UV,即Y00 Y01对应U00 V00

    image

2. YUV420—包含如:YV12,YU12、NV12、NV21、YUV420SP、I420

在Android Camera框架中,setPreviewFormat中可传入的格式,API给出的2个可供选择的格式分别是ImageFormat.NV21和ImageFormat.YV12

  • YV12YU12都属于YUV420p,其中Y\U\V分别对应一个plane,区别在于UV的位置对调,下面是YU12的存储示意图:

    image
  • NV12和NV21,其中NV12就是我们Android常见的YUV420SP,他们不像上一个YV12,有3个plane,而是由Y和UV分别两个Plane组成,UV交替排列,U在前的是NV12,V在前为NV21.

    image
  • I420:或表示为IYUV,数码摄像机专用表示法.

    • 一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Byte,RGB32的size=width×heigth×4 Byte,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Byte。 在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB24转化为IYUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下:
    • YV12 : 亮度(行×列) + V(行×列/4) + U(行×列/4)
    • I420 : 亮度(行×列) +U(行×列/4) + V(行×列/4)
      可以看出,YV12和I420基本上是一样的,就是UV的顺序不同。(摘自百度百科I420)

三、Android中常用YUV格式认识和处理

1. android Camera中用到的yuv格式。

  • setPreviewFormat中可以指定通常是NV12和YV12

    • YV12toYUV420SP:

        public static byte[] YV12toYUV420SP(final byte[] input, final byte[] output, final int width, final int height) {
            final int frameSize = width * height;
            final int qFrameSize = frameSize / 4;
            System.arraycopy(input, 0, output, 0, frameSize); // Y
            for (int i = 0; i < qFrameSize; i++) {
                output[frameSize + i * 2 + 1] = input[frameSize + i + qFrameSize]; // Cr (V)
                output[frameSize + i * 2] = input[frameSize + i]; // Cb (U)
            }
            return output;
        }
      
    • rotateYUV240SP:

        public static void rotateYUV240SP(byte[] src, byte[] des, int width, int height) {
            int wh = width * height;
            //旋转Y
            int k = 0;
            for (int i = 0; i < width; i++) {
                for (int j = 0; j < height; j++) {
                    des[k] = src[width * j + i];
                    k++;
                }
            }
            for (int i = 0; i < width / 2; i++) {
                for (int j = 0; j < height / 2; j++) {
                    des[k] = src[wh + width / 2 * j + i];
                    des[k + width * height / 4] = src[wh * 5 / 4 + width / 2 * j + i];
                    k++;
                }
            }
        }
      

2. Android硬编码对YUV格式的要求

  • 部分Android硬件平台对stride也有要求,比如MTK

      // Note: the stride of resolution must be set as 16x for hard encoding with some chip like MTK
      // Since Y component is quadruple size as U and V component, the stride must be set as 32x
      if (!useSoftEncoder && vOutWidth % 32 != 0 || vOutHeight % 32 != 0) {
          if (vmci.getName().contains("MTK")) {
              throw new AssertionError("MTK encoding revolution stride must be 32x");
          }
      }
    
  • AndroidH264银编码时传入的MediaFormat.KEY_COLOR_FORMAT注意事项

    • 首先时写法,不同的CPU支持编码的yuv格式不同,需要主动查询符合支持的列表

        // choose the video encoder by name.
        private MediaCodecInfo chooseVideoEncoder(String name) {
            int nbCodecs = MediaCodecList.getCodecCount();
            for (int i = 0; i < nbCodecs; i++) {
                MediaCodecInfo mci = MediaCodecList.getCodecInfoAt(i);
                if (!mci.isEncoder()) {
                    continue;
                }
                String[] types = mci.getSupportedTypes();
                for (int j = 0; j < types.length; j++) {
                    if (types[j].equalsIgnoreCase(VCODEC)) {
                        LogUtils.i(TAG, String.format("vencoder %s types: %s", mci.getName(), types[j]));
                        if (name == null) {
                            return mci;
                        }
                        if (mci.getName().contains(name)) {
                            return mci;
                        }
                    }
                }
            }
            return null;
        }
      

3. Android MediaFormat.KEY_COLOR_FORMAT代表的含义

通过查看android.media.MediaCodecInfo.CodecCapabilities,常用的YUV ColorFormat项如下,我们能发现大部分是隐藏API,反而推荐我们去使用COLOR_FormatYUV420Flexible, COLOR_FormatYUV422Flexible等,接下来解释下为什么会这样

/** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
    public static final int COLOR_FormatYUV411Planar            = 17;
    /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
    public static final int COLOR_FormatYUV411PackedPlanar      = 18;
    /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
    public static final int COLOR_FormatYUV420Planar            = 19;
    /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
    public static final int COLOR_FormatYUV420PackedPlanar      = 20;
    /** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
    public static final int COLOR_FormatYUV420SemiPlanar        = 21;

    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatYUV422Planar            = 22;
    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatYUV422PackedPlanar      = 23;
    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatYUV422SemiPlanar        = 24;

    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatYCbYCr                  = 25;
    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatYCrYCb                  = 26;
    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatCbYCrY                  = 27;
    /** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
    public static final int COLOR_FormatCrYCbY                  = 28;

    /** @deprecated Use {@link #COLOR_FormatYUV444Flexible}. */
    public static final int COLOR_FormatYUV444Interleaved       = 29;
  • COLOR_FormatYUV420Flexible

首先看注解:

    /**
     * Flexible 12 bits per pixel, subsampled YUV color format with 8-bit chroma and luma
     * components.
     * <p>
     * Chroma planes are subsampled by 2 both horizontally and vertically.
     * Use this format with {@link Image}.
     * This format corresponds to {@link android.graphics.ImageFormat#YUV_420_888},
     * and can represent the {@link #COLOR_FormatYUV411Planar},
     * {@link #COLOR_FormatYUV411PackedPlanar}, {@link #COLOR_FormatYUV420Planar},
     * {@link #COLOR_FormatYUV420PackedPlanar}, {@link #COLOR_FormatYUV420SemiPlanar}
     * and {@link #COLOR_FormatYUV420PackedSemiPlanar} formats.
     *
     * @see Image#getFormat
     */
    
    它大体意思是,哥们,我是个万能钥匙,对应了ImageFormat中的YUV_420_888,可以代替
    COLOR_FormatYUV411PackedPlanar,COLOR_FormatYUV420Planar,COLOR_FormatYUV420PackedPlanar
    以及COLOR_FormatYUV420SemiPlanar,COLOR_FormatYUV420PackedSemiPlanar使用

好吧,通常编码时查看支持列表有它都可以传入,那我们来看看它可替代的这些format的含义:

  1. COLOR_FormatYUV411PackedPlanar: YUV411,每4个连续的Y分量公用一个UV分量,并且Y分量和UV分量打包到同一个平面,用的不多。
  2. COLOR_FormatYUV420Planar:YUV420P,每2x2像素公用一个UV空间,Y分量空间-->U分量平面-->V分量平面
  3. COLOR_FormatYUV420PackedPlanar:YUV420 packet每2X2像素公用一个UV分量,并且将YUV打包到一个平面
  4. COLOR_FormatYUV420SemiPlanar:YUV420SP,即上述的NV12
  5. COLOR_FormatYUV420PackedSemiPlanar:Y分量空间-->V分量平面-->U分量平面,与COLOR_FormatYUV420Planar uv相反
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容