【Android】直播必備之YUV使用總結 —— 常用的幾種格式:NV21/NV12/YV12/YUV420P的區別

轉載自 gitzzp 的博客 【Android】直播必備之YUV使用總結 —— 常用的幾種格式:NV21/NV12/YV12/YUV420P的區別
轉自:http://www.cnblogs.com/raomengyang/p/5582270.html

  因工作方面接觸到圖像處理這一塊,需要對手機攝像頭採集的原始幀做Rotate或者Scale,但無奈對此的瞭解少之又少,於是網上搜了一頓,完事後將最近所學總結一下,以方便之後的人別踩太多坑。

  首先想要了解YUV爲何物:請猛戳我  

  上面的鏈接中微軟已經寫的很詳細了,國內大部分文章都是翻譯這篇文章的,如果還有疑問的同學可以參考下面這些大神的博客:


  看完上面的文章應該都會有所瞭解和認識了,因爲在Android SDK <= 20(Android5.0Google支持的Camera Preview Callback的YUV常用格式有兩種:NV21 / YV12,在此針對這兩種格式做分析。

NV21:

引用一段微軟的敘述:

4:2:0 Formats, 12 Bits per Pixel

Four 4:2:0 12-bpp formats are recommended, with the following FOURCC codes:

  • IMC2
  • IMC4
  • YV12
  • NV12
    In all of these formats, the chroma channels are subsampled by a factor of two in both the horizontal and vertical dimensions.
YV12

All of the Y samples appear first in memory as an array of unsigned char values. This array is followed immediately by all of the V (Cr) samples. The stride of the V plane is half the stride of the Y plane, and the V plane contains half as many lines as the Y plane. The V plane is followed immediately by all of the U (Cb) samples, with the same stride and number of lines as the V plane (Figure 12).
Figure 12:
YV12 memory layout

NV12

All of the Y samples are found first in memory as an array of unsigned char values with an even number of lines. The Y plane is followed immediately by an array of unsigned char values that contains packed U (Cb) and V (Cr) samples, as shown in Figure 13. When the combined U-V array is addressed as an array of little-endian WORD values, the LSBs contain the U values, and the MSBs contain the V values. NV12 is the preferred 4:2:0 pixel format for DirectX VA. It is expected to be an intermediate-term requirement for DirectX VA accelerators supporting 4:2:0 video.
Figure 13:
NV12 memory layout

  從上可知YV12和NV12所佔內存是12bits/Pixel,因爲每個Y就是一個像素點,注意着色加粗的敘述,YUV值在內存中是按照數組的形式存放的,而由於YV12和NV21都是屬於planar格式,也就是Y值和UV值是獨立採樣的:

In a planar format, the Y, U, and V components are stored as three separate planes.

  既然Y、U、V值都是獨立的,那就意味着我們可以分別處理相應的值,比如在YV12中,排列方式是這樣的,每4個Y共用一對UV值,而U、V值又是按照如下格式排列
下面是YV12格式中寬爲16,高爲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
V第一行: V0 V1 V2 V3
U第一行: U0 U1 U2 U3
V第二行: V4 V5 V6 V7
U第二行: U4 U5 U6 U7

  既然知道了YUV值的結構,我們就可以任性的對此圖像做Rotate,scale等等。這裏我以480x270 (16:9)的一張原始幀圖像舉例,貼出部分代碼示例:
任意設定的一個帶有onPreviewFrame的類,CameraPreviewFrame.java:

/**
 * 獲取preview的原始幀:
 * 
 * 這裏有個前提,因爲Android camera preview默認格式爲NV21的,所以需要
 * 調用setPreviewFormat()方法設置爲我們需要的格式
 * 
 */

@Override
public void onPreviewFrame(byte[] data, Camera camera) {// 假設這裏的data爲480x270原始幀

    String SRC_FRAME_WIDTH = 480;
    String SRC_FRAME_HEIGHT = 270;

    String DES_FRAME_WIDTH = 480;
    String DES_FRAME_HEIGHT = 270;
    // 此處將data數組保存在了指定的路徑,保存類型爲jpeg格式,但是普通的圖片瀏
    // 覽器是無法打開的,需要使用RawView等專業的工具打開。
    saveImageData(data);

    // 定義與原始幀大小一樣的outputData,因爲YUV420所佔內存是12Bits/Pixel,
     // 每個Y爲一個像素8bit=1Byte,U=2bit=1/4(Byte),V=2bit=1/4(Byte),
     // Y值數量爲480*270,則U=V=480*270*(1/4)
    byte[] outputData = new byte[DES_FRAME_WIDTH * DES_FRAME_HEIGHT * 3 / 2]; 
    // call the JNI method to rotate frame data clockwise 90 degrees
    YuvUtil.DealYV12(data, outputData, SRC_FRAME_WIDTH, SRC_FRAME_HEIGHT, 90);
    saveImageData(outputData);

}

}

// save image to sdcard path: Pictures/MyTestImage/

public void saveImageData(byte[] imageData) {
File imageFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (imageFile == null) {
return;
}

    try {
        FileOutputStream fos = new FileOutputStream(imageFile);
        fos.write(imageData);
        fos.close();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
        Log.e(TAG, "File not found: " + e.getMessage());

    } catch (IOException e) {
        e.printStackTrace();
        Log.e(TAG, "Error accessing file: " + e.getMessage());
    }
}

public static File getOutputMediaFile(int type) {
File imageFileDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), “MyTestImage”);

    if (!imageFileDir.exists()) {
        if (!imageFileDir.mkdirs()) {
            Log.e(TAG, "can't makedir for imagefile");
            return null;
        }
    }
    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File imageFile;
    if (type == MEDIA_TYPE_IMAGE) {
        imageFile = new File(imageFileDir.getPath() + File.separator +
                "IMG_" + timeStamp + ".jpg");
    } else if (type == MEDIA_TYPE_VIDEO) {
        imageFile = new File(imageFileDir.getPath() + File.separator +
                "VID_" + timeStamp + ".mp4");
    } else {
        return null;
    }
    return imageFile;

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

上面的代碼中可以看到調用了JNI的方法:YuvUtil.RotateYV12()

public class YuvUtil {
    // 初始化,爲data分配相應大小的內存
    public static native void initYV12(int length, int scale_length);
public static native void DealYV12(byte[] src_data, byte[] dst_data, int width, int height, int rotation);

}

  • 1
  • 2
  • 3
  • 4
  • 5

com_example_jni_YuvUtil.h

/* DO NOT EDIT THIS FILE - it is machine generated */
 #include <jni.h>
/* Header for class _Included_com_example_jni_YuvUtil */

#ifndef _Included_com_example_jni_YuvUtil
#define _Included_com_example_jni_YuvUtil
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: com_example_jni_YuvUtil
  • Method: initYV12
  • Signature: (II)V
    */
    JNIEXPORT void JNICALL Java_com_example_jni_YuvUtil_initYV12
    (JNIEnv *, jclass, jint, jint);

/*

  • Class: com_example_jni_YuvUtil
  • Method: DealYV12
  • Signature: ([B[BIIIII)V
    */
    JNIEXPORT void JNICALL Java_com_example_jni_YuvUtil_DealYV12
    (JNIEnv *, jclass, jbyteArray, jbyteArray, jint, jint, jint, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

com_example_jni_YuvUtil.c

#include "com_example_jni_YuvUtil.h"
 #include <android/log.h>
 #include <string.h>
 #include <jni.h>
 #include <stdlib.h>

#define TAG “jni-log-jni” // 這個是自定義的LOG的標識
#define LOGD(…) __android_log_print(ANDROID_LOG_DEBUG,TAG ,VA_ARGS) // 定義LOGD類型
#define LOGI(…) __android_log_print(ANDROID_LOG_INFO,TAG ,VA_ARGS) // 定義LOGI類型
#define LOGW(…) __android_log_print(ANDROID_LOG_WARN,TAG ,VA_ARGS) // 定義LOGW類型
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR,TAG ,VA_ARGS) // 定義LOGE類型
#define LOGF(…) __android_log_print(ANDROID_LOG_FATAL,TAG ,VA_ARGS) // 定義LOGF類型

char *input_src_data, *output_src_data, *src_y_data,
*src_u_data, *src_v_data, *dst_y_data, *dst_v_data;
int src_data_width, src_data_height, len_src;

/*

  • Class: com_example_jni_YuvUtil
    */
    JNIEXPORT void JNICALL Java_com_example_jni_YuvUtil_initYV12
    (JNIEnv *env, jclass jcls, jint length, jint scaleDataLength) {
    len_src = length;
    len_scale = scaleDataLength;
    LOGD("########## len_src = %d, len_scale = %d \n", len_src, len_scale);

input_src_data = malloc(sizeof(char) * len_src);
LOGD("########## input_src_data = %d \n", input_src_data);

src_y_data = malloc(sizeof(char) * (len_src * 2 / 3));
src_u_data = malloc(sizeof(char) * (len_src / 6));
src_v_data = malloc(sizeof(char) * (len_src / 6));

dst_y_data = malloc(sizeof(char) * (len_src * 2 / 3));
dst_u_data = malloc(sizeof(char) * (len_src / 6));
dst_v_data = malloc(sizeof(char) * (len_src / 6));

}

JNIEXPORT void JNICALL Java_com_example_jni_YuvUtil_DealYV12
(JNIEnv *env, jclass jcls, jbyteArray src_data,
jbyteArray dst_data, jint width, jint height, jint rotation, jint dst_width, jint dst_height) {
src_data_width = width;
src_data_height = height;

// 將src_data的數據傳給input_src_data
(env)->GetByteArrayRegion (env, src_data, 0, len_src, (jbyte)(input_src_data));

/以下三個memcpy分別將Y、U、V值從src_data中提取出來,將YUV值分別scale或者rotate,則可得到對應格式的圖像數據/
// get y plane
memcpy(src_y_data, input_src_data , (len_src * 2 /3));
// get u plane
memcpy(src_u_data, input_src_data + (len_src * 2 / 3), len_src / 6);
// get v plane
memcpy(src_v_data, input_src_data + (len_src * 5 / 6 ), len_src / 6);
/獲取yuv三個值的數據可以做相應操作/
// …
// …

// 例:將Y值置爲0,則得到沒有灰度的圖像;
memset(input_src_data + src_data_width * src_data_height, 0, src_data_width * src_data_height);

// 將input_src_data的數據返回給dst_data輸出
// output to the dst_data
(env)->SetByteArrayRegion (env, dst_data, 0, len_src, (jbyte)(input_src_data));

}

/**

  • free memory
    */
    JNIEXPORT void JNICALL Java_com_example_jni_YuvUtil_ReleaseYV12
    (JNIEnv *env , jclass jcls) {
    free(output_src_data);
    free(input_src_data);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

注意:以上代碼不是完全的,只是用於說明而已,如果需要更多的操作還請各位朋友自己完善,因爲沒怎麼寫過這類博客代碼很亂,如有表述的不清楚和有問題的地方,大家可以給我留言。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章