美文网首页直插
【Android】直播必备之YUV使用总结 —— Android

【Android】直播必备之YUV使用总结 —— Android

作者: 60916fc63567 | 来源:发表于2016-12-19 14:37 被阅读1081次

    说明

    因工作方面接触到图像处理这一块,需要对手机摄像头采集的原始帧做Rotate或者Scale,但无奈对此的了解少之又少,于是网上搜了一顿,完事后将最近所学总结一下,以方便之后的人别踩太多坑。

    首先想要了解YUV为何物:请猛戳我

    上面的链接中微软已经写的很详细了,国内大部分文章都是翻译这篇文章的,如果还有疑问的同学可以参考下面这些大神的博客:

    最简单的基于FFmpeg的libswscale的示例(YUV转RGB)

    图文详解YUV420数据格式

    ANDROID 高性能图形处理

    Android摄像头开发:实时摄像头视频预览帧的编码问题(二)


    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).

    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 unsignedcharvalues 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-endianWORDvalues, 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.

    从上可知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    |

    --------------------------------------------------------------

    16x4像素的YV12排列

    知道了YUV值的结构,我们就可以任性的对此图像做Rotate,scale等等。这里我以480*270 (16:9)的一张原始帧图像举例,贴出部分代码示例:

    随便设定的一个带有onPreviewFrame的类,CameraPreviewFrame.java:

    /*** 获取preview的原始帧:

    *

    * 这里有个前提,因为Android camera preview默认格式为NV21的,所以需要

    * 调用setPreviewFormat()方法设置为我们需要的格式

    **/@OverridepublicvoidonPreviewFrame(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 =newbyte[DES_FRAME_WIDTH * DES_FRAME_HEIGHT * 3 / 2];//call the JNI method to rotate frame data clockwise 90 degreesYuvUtil.DealYV12(data, outputData, SRC_FRAME_WIDTH, SRC_FRAME_HEIGHT, 90);

    saveImageData(outputData);

    }

    }//save image to sdcard path: Pictures/MyTestImage/publicvoidsaveImageData(byte[] imageData) {

    File imageFile=getOutputMediaFile(MEDIA_TYPE_IMAGE);if(imageFile ==null) {return;

    }try{

    FileOutputStream fos=newFileOutputStream(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());

    }

    }publicstaticFile getOutputMediaFile(inttype) {

    File imageFileDir=newFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyTestImage");if(!imageFileDir.exists()) {if(!imageFileDir.mkdirs()) {

    Log.e(TAG,"can't makedir for imagefile");returnnull;

    }

    }//Create a media file nameString timeStamp =newSimpleDateFormat("yyyyMMdd_HHmmss").format(newDate());

    File imageFile;if(type ==MEDIA_TYPE_IMAGE) {

    imageFile=newFile(imageFileDir.getPath() + File.separator +                    "IMG_" + timeStamp + ".jpg");

    }elseif(type ==MEDIA_TYPE_VIDEO) {

    imageFile=newFile(imageFileDir.getPath() + File.separator +                    "VID_" + timeStamp + ".mp4");

    }else{returnnull;

    }returnimageFile;

    }

    上面的代码中可以看到我调用了Jni的方法:YuvUtil.RotateYV12();

    YuvUtil.Java

    publicclassYuvUtil {//初始化,为data分配相应大小的内存publicstaticnativevoidinitYV12(intlength,intscale_length);publicstaticnativevoidDealYV12(byte[] src_data,byte[] dst_data,intwidth,intheight,introtation);

    }

    对应的Jni的C代码如下:

    com_example_jni_YuvUtil.h

    com_example_jni_YuvUtil.c

    #include"com_example_jni_YuvUtil.h"#include#include#include#include#defineTAG "jni-log-jni"//这个是自定义的LOG的标识#defineLOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)//定义LOGD类型#defineLOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)//定义LOGI类型#defineLOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)//定义LOGW类型#defineLOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)//定义LOGE类型#defineLOGF(...) __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;intsrc_data_width, src_data_height, len_src;/** Class:    com_example_jni_YuvUtil*/JNIEXPORTvoidJNICALL 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));

    }

    JNIEXPORTvoidJNICALL 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 planememcpy(src_y_data, input_src_data , (len_src *2/3));//get u planememcpy(src_u_data, input_src_data + (len_src *2/3), len_src /6);//get v planememcpy(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*/JNIEXPORTvoidJNICALL Java_com_example_jni_YuvUtil_ReleaseYV12

    (JNIEnv*env , jclass jcls) {free(output_src_data);free(input_src_data);

    }

    注意:以上代码不是完全的,只是用于说明而已,如果需要更多的操作还请各位朋友自己完善,因为没怎么写过这类博客,很多地方很乱,表述的不清楚,有问题的朋友可以问我。

    源网页地址:http://blog.csdn.net/zxccxzzxz/article/details/49529787

    相关文章

      网友评论

        本文标题:【Android】直播必备之YUV使用总结 —— Android

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