工作问题接触到图像这一块,需要对手机摄像头采集的原始帧做Rotate或者scale,但无奈对此的了解少之又少,于是网上搜了一顿,完事后将最近所学总结一下,以方便之后的人别踩太多坑。
     
  首先想要了解YUV为何物,请猛戳:https://msdn.microsoft.com/en-us/library/aa904813(VS.80).aspx
 
  上面的链接中,微软已经写的很详细了,国内大部分文章都是翻译这篇文章的,如果还有疑问的同学可以参考下面这些大神的博客:

  看完上面的文章应该都会有所了解和认识了,因为在Android API <= 20(Android5.0之前的版本)中Google支持的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    |
--------------------------------------------------------------
16x4像素的YV12排列
  
  知道了YUV值的结构,我们就可以任性的对此图像做Rotate,scale等等。这里我以480*270 (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;
}

  上面的代码中可以看到我调用了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 __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

  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);
}

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

  

  

  

【Android】YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别的更多相关文章

  1. 【Android】直播必备之YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别

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

  2. Android学习第二天-android常用命令

    上一篇文章中,我们重点讲解了adb的常用命令,下面我们一起来看看其它常用的命令 2 android 2.1 查看机器上所有已经安装的Android版本和AVD设备 2.1.1查看机器上已经安装的AVD ...

  3. Android中常用的五种数据存储方式

    第一种: 使用SharedPreferences存储数据 适用范围: 保存少量的数据,且这些数据的格式非常简单:字符串型.基本类型的值.比如应用程序的各种配置信息(如是否打开音效.是否使用震动效果.小 ...

  4. 转--Android实用的代码片段 常用代码总结

    这篇文章主要介绍了Android实用的代码片段 常用代码总结,需要的朋友可以参考下     1:查看是否有存储卡插入 复制代码 代码如下: String status=Environment.getE ...

  5. 【转】Android LCD(二):LCD常用接口原理篇

    关键词:android LCD TFT TTL(RGB)  LVDS  EDP MIPI  TTL-LVDS  TTL-EDP 平台信息:内核:linux2.6/linux3.0系统:android/ ...

  6. Android support library支持包常用控件介绍(一)

    谷歌官方推出Material Design 设计理念已经有段时间了,为支持更方便的实现Material Design设计效果,官方给出了Android support design library 支 ...

  7. Android support library支持包常用控件介绍(二)

    谷歌官方推出Material Design 设计理念已经有段时间了,为支持更方便的实现 Material Design设计效果,官方给出了Android support design library ...

  8. Android常用的四种布局(或者说是五种)

    一.FrameLayout(帧布局): 显示特点:所有的子控件默认显示在FrameLayout的左上角,会重叠在一起显示. 常用属性: layout_gravity(设置给子控件,调整控件在容器内的重 ...

  9. 想玩 Android 开发板?这些常用命令你不知不行!

    2019-04-19 关键字:Android机顶盒常用命令.Linux命令 笔者早年间从事 Android 机顶盒开发工作,那会刚毕业,技术也比较菜,工作过程中遇到过不少困难,不过所幸当时就有做笔记的 ...

随机推荐

  1. (转载)JAVA敏捷开发环境搭建

    整个软件项目分为四个环境 开发本地环境.开发环境.测试环境.IDC环境.和传统C++开发不一样的模式是多了第一个开发本地环境.这是为什么呢,因为目前大部分开发人员还是比较熟悉windows下开发.对于 ...

  2. UNET学习笔记3 - 网络系统的概念

    服务器和 HOST 在Unity游戏里,一个游戏一般有一个服务器和多个客户端组成,但也可以没有服务器,用某一个客户端来同时做服务器用,这种就叫Host 在Host上的客户端叫Local Client, ...

  3. [转载]并发编程之Operation Queue和GCD

    并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...

  4. Android 高仿微信支付密码输入控件

    像微信支付密码控件,在app中是一个多么司空见惯的功能.最近,项目需要这个功能,于是乎就实现这个功能. 老样子,投篮需要找准角度,变成需要理清思路.对于这个"小而美"的控件,我们思 ...

  5. jQuery Ready 与 Window onload 的区别(转)

    “我们都知道,很多时候,在页面加载完后都需要做一些相应的初始化动作.例如,运行某些js特效,设置表单等等.怎么知道页面加载完了呢?一 般情况下都是设置body标签的onload监听window的loa ...

  6. 使用WMI和性能计数器监控远程服务器权限设置

    应用场景:在web服务器中,通过.NET编码使用WMI查询远程服务器的一些硬件配置信息,使用性能计数器查询远程机器的运行时资源使用情况.在网上没有找到相关的东西,特记录与大家共享. 将web服务器和所 ...

  7. asp.net identity 2.2.0 在MVC下的角色启用和基本使用(一)

    基本环境:asp.net 4.5.2 第一步:在App_Start文件夹中的IdentityConfig.cs中添加角色控制器. 在namespace xxx内(即最后一个“}”前面)添加 角色控制类 ...

  8. Java语法糖3:泛型

    泛型初探 在泛型(Generic type或Generics)出现之前,是这么写代码的: public static void main(String[] args) { List list = ne ...

  9. 自己实现一个Native方法的调用

    JNI 开始本篇的内容之前,首先要讲一下JNI.Java很好,使用的人很多.应用极广,但是Java不是完美的.Java的不足体现在运行速度要比传统的C++慢上许多之外,还有Java无法直接访问到操作系 ...

  10. 上层建筑——DOM元素的特性与属性(dojo/dom-attr)

    上一篇返本求源中,我们从DOM基础的角度出发,总结了特性与属性的关系.本文中,我们来看看dojo框架是如何处理特性与属性的.dojo框架中特性的处理位于dojo/dom-attr模块属性的处理为与do ...