转载自 gitzzp 的博客 【Android】直播必备之YUV使用总结 —— 常用的几种格式:NV21/NV12/YV12/YUV420P的区别
转自:http://www.cnblogs.com/raomengyang/p/5582270.html

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

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

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

  • 最简单的基于FFmpeg的libswscale的示例(YUV转RGB)
  • 图文详解YUV420数据格式
  • ANDROID 高性能图形处理
  • Android摄像头开发:实时摄像头视频预览帧的编码问题(二)

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

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

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

  1. 计算机管理格式化硬盘,计算机管理必备知识|格式化硬盘,究竟用哪一种格式好...

    我们有时候需要将硬盘进行一个格式化的操作,不要以为格式化就是简单的将文件进行删除,其实格式化还是有很多的知识点是我们不知道的,比如我们一般非专业的人要想将硬盘格式化的时候,究竟是要使用哪一种格式呢?这 ...

  2. android好看的配色方案,APP界面常用的五种颜色搭配

    众所周知,每一种颜色带给用户的视觉感受也是不同的.现在人们对手机的依赖程度,就能看到手机中APP的发展前景,那今天就跟大家聊聊如何通过颜色搭配的不同来进行移动端APP界面的布局和排版设计. 移动端UI ...

  3. Android平台RTMP推送模块如何对接NV21、YV12、RGB、YUV等编码前数据

    前言 我们在对接Android平台摄像头或者屏幕采集.编码打包推送场景的时候,随着采集设备的不同,出来的数据也是多样化的,比如NV21.YV12.RGB.YUV等,更有图像数据甚至是翻转或者倒置的,如 ...

  4. Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)

    Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer) (码字不易,转载请声明出处:http://blog.csdn.net/andrexp ...

  5. Android 直播 APP实现直播流程

    Android 直播 APP实现直播流程 直播本质 1. 主播端采集音视频 2. 视频处理(美颜,水印) 3. 视频编解码 视频编码框架 视频编码技术 压缩方式 视频编解码和压缩时的关键知识点 4. ...

  6. Android直播软件搭建中实用的录制编辑方案有哪些

    Android直播软件搭建中实用的录制编辑方案有哪些 经大量数据显示,直播已经发展成为一种全民参与.共享和生产的文化现象.它的火爆不仅丰富了大众的艺术审美水平和精神文化,而且也影响了一代人的世界观.人 ...

  7. Android直播开发之旅(25):使用AES算法加密多媒体文件(+RSA+MD5+Base64)

    文章目录 1. AES算法 1.1 AES加密过程 1.1.1 字节代替(SubBytes) 1.1.2 行移位(ShiftRows) 1.1.3 列混合(MixColumns) 1.1.4 加轮密钥 ...

  8. Android开发必备(干货源码放送大)

    Android源码大放送(实战开发必备) 文件夹 PATH 列表 │  javaapk.com文件列表生成工具.bat │  使用说明.txt │  免费下载更多源码.url │  目录列表.txt ...

  9. 开发工具总结(7)之多年珍藏的Android开发必备网站和工具

    今天早上在简书上瞎逛,看到了这个,干货很多,这肯定是出自一个经验丰富的程序员之手,作为小小白,学习路上难免有需要帮助的和通过一些捷径来提高开发效率,所以收藏了这篇文章,同时也增加了一些自己平时收藏的内 ...

最新文章

  1. java生成Https证书,及证书导入的步骤和过程
  2. html如何创建学生信息表,创建学生对象并且能访问网页
  3. 20 岁发表 SCI 的学霸,梦想用算法改变世界
  4. Oracle数据库多结点相关配置
  5. linux卸载minicoda2,MiniConda2下载 MiniConda python 2.7 v4.3.30.2 Linux 64位 官方免费版(附安装步骤) 下载-脚本之家...
  6. 可以批量转modis投影_SNAP批量处理Sentinel2数据
  7. 利用Python爬虫刷新某网站访问量
  8. 用计算机模拟病毒,计算机模拟揭露HIV病毒体内传播细节,有望为治疗提供新途径...
  9. ARP 协议 理解
  10. mysql所有选修课程都及格_Day37:MySQL 数据库 ---(7)
  11. 统计字段中出现字符串的次数
  12. class文件反编译成java文件
  13. 价格奥秘-在超市遇见亚当斯密--第十章 便宜鸡蛋会创造更多的就业机会?
  14. iOS常见的加密方法有哪些
  15. 固态硬盘(SSD)——NAND闪存芯片(颗粒)QLC、SLC、MLC、TLC
  16. win10开启快速启动,关机时电源键一直亮着无法正常关机。。。
  17. 2020年3大免费又好用的BI工具软件
  18. 标准的镜头质量评测方法——MTF(Modulation Transfer Function)
  19. 下拉菜单和文本框结合
  20. 003云数据中心基础原理笔记

热门文章

  1. 项目2-2软文推广页面
  2. Qt实战案例(41)——利用QWinTaskbarButton和QWinTaskbarProgress类实现任务栏进度条的显示
  3. Python 学习——图像简单处理
  4. 2022年福建省安全保护服务人员(初级保安员)考试练习题及答案
  5. 在线进行C语言编译,在线C语言编译及考试系统.doc
  6. xp系统查找哪台计算机,WinXP如何查看电脑使用记录?查看电脑使用痕迹的方法...
  7. 爬虫进阶-- 字体反爬终极解析
  8. 淘宝高管写给找工作的我们
  9. 记录我与欣旺达的校招经历
  10. php字符串副职_PHP字符串