说明

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

  首先想要了解YUV为何物: https://msdn.microsoft.com/en-us/library/aa904813  

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


  看完上面的文章应该都会有所了解和认识了,因为在Android SDK <= 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:

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:

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

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

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 开发者必备的小工具

    Photo from https://www.airpair.com 在做Android 开发过程中,会遇到一些小的问题,虽然自己动手也能解决,但是有了一些小工具,解决这些问题就得心应手了,今天就为大 ...

  3. Android高工必备技能

    转载:http://www.jianshu.com/p/d791bbede02c Step 1. 玩转RxJava 使用RxJava处理异步极其方便,各种操作符可以对数据做流水线式操作,再加上与Ret ...

  4. Android开发者必备的42个链接

    http://mobile.51cto.com/ahot-426035.htm Android开发者必备的42个链接 下面收集了42个帮助大家学习Android的内容链接,部分内容是面向初学者的,帮助 ...

  5. Android 开发必备

    Android 开发必备 http://www.androiddevtools.cn/ 收集整理Android开发所需的Android SDK.开发中用到的工具.Android开发教程.Android ...

  6. Android直播实现 Android端推流、播放

    最近想实现一个Android直播,但是对于这方面的资料都比较零碎,一开始是打算用ffmpeg来实现编码推流,在搜集资料期间,找到了几个强大的开源库,直接避免了jni的代码,集成后只用少量的java代码 ...

  7. 【Android 直播软件开发:音视频硬解码篇】

    开篇 炙手可热,望而生畏的音视频开发 时至今日,短视频App可谓是如日中天,一片兴兴向荣.随着短视频的兴起,音视频开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,让许许多多 ...

  8. 6本Android开发必备图书

    学习一样新事物或许有多种方式,报培训班,看视频,向高手请教等等,但一本好书往往可以让你少走很多弯路,事半功倍.今天与大家分享一些Android开发值得一读的书籍,希望对新手安卓开发者们有所帮助. 1. ...

  9. [转] Android开发者必备的42个链接

    下面收集了42个帮助大家学习Android的内容链接,部分内容是面向初学者的,帮助大家从头开始学习Android开发,其他则面向较高级的开发者.希望推荐的这些内容对你有帮助. 官方网站 1.谷歌And ...

随机推荐

  1. C语言dll文件的说明以及生成、使用方法

    最近在搞一些小项目,由于要涉及到跟其它语言进行交互,动态链接库变成了不二的选择.为此也查阅了很多资料,将动态链接库的相关知识在此做一个整理. 一.动态链接库概述 动态链接库(Dynamic Link ...

  2. final关键字(final是最终的)

    final关键字(final是最终的) 1.final修饰特点 a.修饰类,类不能被继承 b.修饰变量,变量就变成了常量, 修饰基本数据类:final int num = 10; 修饰引用数据类型变量 ...

  3. SQL SERVER 2016 AlwaysOn 无域集群+负载均衡搭建与简测

    之前和很多群友聊天发现对2016的无域和负载均衡满心期待,毕竟可以简单搭建而且可以不适用第三方负载均衡器,SQL自己可以负载了.windows2016已经可以下载使用了,那么这回终于可以揭开令人憧憬向 ...

  4. 大话JS面向对象之开篇万物皆对象------(ATM取款机引发的深思)

    一,总体概要 OO(面向对象)概念的提出是软件开发工程发展的一次革命,多年来我们借助它使得很多大型应用程序得以顺利实现.如果您还没有掌握并使用OO进行程序设计和开发,那么您无疑还停留在软件开发的石器时 ...

  5. MySQL 对比数据库表结构

    200 ? "200px" : this.width)!important;} --> 介绍 本章主要介绍怎样对比数据库的表结构的差异,这里主要介绍使用mysqldiff工具 ...

  6. MapReduce剖析笔记之二:Job提交的过程

    上一节以WordCount分析了MapReduce的基本执行流程,但并没有从框架上进行分析,这一部分工作在后续慢慢补充.这一节,先剖析一下作业提交过程. 在分析之前,我们先进行一下粗略的思考,如果要我 ...

  7. ReactNative与NativeScript对比报告

    综合这段时间对ReactNative(下称RN)和NativeScript(下称NS)的简单学习了解,分别从不同方面分析对比二者的优缺点. 页面结构 NS一个页面的目录结构: RN的一个页面一般就是一 ...

  8. [SDK2.2]SQL Azure (13) Azure的两种关系型数据库服务:SQL Azure与SQL Server VM的不同

    <Windows Azure Platform 系列文章目录> 如果熟悉Windows Azure平台的用户不难发现,对于SQL Server数据库来说,微软提供了两种服务,分别是: -W ...

  9. TODO:小程序集成WeUI

    TODO:小程序集成WeUI WeUI 为微信 Web 服务量身设计.WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信 Web 开发量身设计,可以令用户的使用感知更加统一. ...

  10. 前端构建工具gulpjs的使用介绍及技巧

    gulpjs是一个前端构建工具,与gruntjs相比,gulpjs无需写一大堆繁杂的配置参数,API也非常简单,学习起来很容易,而且gulpjs使用的是nodejs中stream来读取和操作数据,其速 ...