Android libyuv应用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
来源:互联网 发布:mac x ov10.11 编辑:程序博客网 时间:2024/05/21 18:10
项目中接触到图像处理这部分,需求是将手机摄像头采集的原始帧进行 Rotate (旋转)、Scale(拉伸)和 format convert(格式转换),无奈对此的了解甚少于是网上查阅资料恶补了一顿,完事后将最近所学总结一下以方便之后的人别踩太多。
首先想要了解YUV为何物,请猛戳:Video Rendering with 8-Bit YUV Formats 链接中微软已经写的很详细了,国内大部分文章都是翻译这篇文章的,如果还有疑问的同学可以参考下面这些大神的博客:
最简单的基于FFmpeg的libswscale的示例(YUV转RGB)
图文详解YUV420数据格式
- ANDROID 高性能图形处理 之 一. RENDERSCRIPT
从上面的文章中应该都会对YUV有所了解和认识了。需要注意的是,在 Android SDK <= 20 Android5.0 LOLLIPOP 版本中 Google 支持的 Camera Preview Callback 的YUV常用格式有两种:
NV21
YV12
先贴一段微软的叙述:
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 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.
从上可知 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.
在 planar 的格式中, Y, U, V 值是单独存储在三个分离的平面中的。
既然 Y、U、V 值都是独立的,那就意味着我们可以分别处理相应的值,比如在YV12中,排列方式如下表所示,每4个 Y 共用一对 UV 值,而 U、V 值又是按照横排排列(下面是 YV12 格式中,宽为16,高为4像素的排列)。
YV12 中 16 x 4 像素排列
了解了 YUV 值的结构我们就可以任性的对此图像做 Rotate,scale等等。这里我以480*270 (16:9)的一张原始帧图像举例,贴出部分代码示例:
CameraPreviewFrame.java:
/*** 获取preview的原始帧 * 这里有个前提,因为Android camera preview默认格式为NV21的,所以需要* 调用setPreviewFormat()方法设置为我们需要的格式*/@Overridepublic 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格式,但是普通的图片浏 // 览器是无法打开的,需要使用RawViewer等专业的工具打开。 // 定义与原始帧大小一样的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), "MyYuvImage"); 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;}
上面的代码中可以看到我调用了JNI的方法YuvUtil.RotateYV12()
YuvUtil.java
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);}
对应的Jni的C代码如下:
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 __cplusplusextern "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
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 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*/JNIEXPORT void JNICALL Java_com_example_jni_YuvUtil_ReleaseYV12(JNIEnv *env , jclass jcls) {free(output_src_data);free(input_src_data);}
RawViewer
一个查看YUV原始帧文件的工具,可以根据自定义的宽高、YUV格式显示出当前YUV的图像,对分析当前视频帧的结构和数据类型还是挺有帮助的。
Demo源码
- Android libyuv应用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- Android YUV使用总结之Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- 【Android】YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- 【Android】直播必备之YUV使用总结 —— 常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- Android直播必备之YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- YUV格式学习:YUV420P、YV12、NV12、NV21格式转换成RGB24
- YUV格式学习:YUV420P、YV12、NV12、NV21格式转换成RGB24
- NV12 和 NV21的区别
- YV12,I420,YUV420P的区别
- YV12,I420,YUV420P的区别 .
- YV12,I420,YUV420P的区别
- YV12、I420、YUV420P的区别
- YV12,I420,YUV420P的区别
- YV12,I420,YUV420P的区别
- YV12,I420,YUV420P的区别
- YV12,I420,YUV420P的区别
- YV12,I420,YUV420P的区别
- g++ 编译c++11
- 项目源码解读 baseControl
- 高性能异步web框架Sanic文档【三】
- 聚类的基本知识
- asp如何在线打开pdf文件
- Android libyuv应用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别
- 项目源码解读dimensionControl
- Angularjs 传递k-v模式的参数
- 高性能异步web框架Sanic文档【四】
- 华为上机题目之数组比较
- JZ2440:通过nfs挂载文件系统不成功
- 数据结构-顺序表
- iOS开发 之 消息转发机制
- 从嗤之以鼻到“奇迹” 前淘宝工程师详解12306技术