1、背景

在Android做过自定义Camera的朋友应该都知道,我们可以通过public void onPreviewFrame(byte[] data, Camera camera)回调中获取摄像头采集到的每一帧的数据,但是这个byte[] data的数据格式YUV的,并不能直接给我们进行使用,那么该通过什么样的方法对这个YUV数据进行处理呢?

2、YUV数据格式介绍

首先我们来了解什么是YUV数据,当然这方面的文章有很多,在这里我就不详细的介绍了,大家可以看下这篇文章 :

一文读懂 YUV 的采样与格式

在这里我们主要用到的是YUV数据格式是NV21(yuv420sp)和I420(yuv420p),它们都是 4:2:0的格式,唯一的区别就是它们的YUV数据排列不一样,

NV21的排列是YYYYYYYY VUVU =>YUV420SP,

而I420的排列是YYYYYYYY UU VV =>YUV420P。

其实我们知道的NV21和I420的数据格式和数据的排列,我们就可以根据排列方式对其进行一些操作,但是它的效率并不是很高,如果只是简单的操作单一的YUV数据,那么倒没有太大影响。但是如果要运用于直播推流的话,要保证推流视频的帧率,那么对YUV数据处理的耗时就相当的重要。

Libyuv库的介绍

其实对于YUV数据的处理,Google已经开源了一个叫做libyuv的库专门用于YUV数据的处理。

什么是libyuv

libyuv是Google开源的实现各种YUV与RGB之间相互转换、旋转、缩放的库。它是跨平台的,可在Windows、Linux、Mac、Android等操作系统,x86、x64、arm架构上进行编译运行,支持SSE、AVX、NEON等SIMD指令加速。

Android上如何使用Libyuv

libyuv并不能直接为Android开发直接进行使用,需要对它进行编译的操作。在这里介绍的是使用Android Studio的Cmake的方式进行libyuv的编译操作,首先从官方网站Libyuv上下载libyuv库,下载的目录结构如下

如果无法下载的话,也可以从我文章最后的demo中去进行拷贝。新键Android项目,并且创建的时候勾选项include C++ Support,也就是改android项目支持C,C++的编译,如果对于Android Stuido如何支持C,C++编译不清楚的,请自行百度谷歌,这里就不多细说。

项目创建之后将下载的libyuv库直接拷贝到src/main/cpp目录下

修改CMakeLists.txt文件,并在src/main/cpp下创建YuvJni.cpp文件,CMakeLists.txt修改如下

cmake_minimum_required(VERSION 3.4.1)
include_directories(src/main/cpp/libyuv/include)
add_subdirectory(src/main/cpp/libyuv ./build)
aux_source_directory(src/main/cpp SRC_FILE)
add_library(yuvutil SHARED ${SRC_FILE})
find_library(log-lib log)
target_link_libraries(yuvutil ${log-lib} yuv)

创建文件YuvUtil.java,在这里我添加了三个方法进行yuv数据的操作

public class YuvUtil {static {System.loadLibrary("yuvutil");}/*** YUV数据的基本的处理** @param src        原始数据* @param width      原始的宽* @param height     原始的高* @param dst        输出数据* @param dst_width  输出的宽* @param dst_height 输出的高* @param mode       压缩模式。这里为0,1,2,3 速度由快到慢,质量由低到高,一般用0就好了,因为0的速度最快* @param degree     旋转的角度,90,180和270三种* @param isMirror   是否镜像,一般只有270的时候才需要镜像**/public static native void compressYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int mode, int degree, boolean isMirror);/*** yuv数据的裁剪操作** @param src        原始数据* @param width      原始的宽* @param height     原始的高* @param dst        输出数据* @param dst_width  输出的宽* @param dst_height 输出的高* @param left       裁剪的x的开始位置,必须为偶数,否则显示会有问题* @param top        裁剪的y的开始位置,必须为偶数,否则显示会有问题**/public static native void cropYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int left, int top);/*** 将I420转化为NV21** @param i420Src 原始I420数据* @param nv21Src 转化后的NV21数据* @param width   输出的宽* @param width   输出的高**/public static native void yuvI420ToNV21(byte[] i420Src, byte[] nv21Src, int width, int height);
}

同时在前面创建的YuvJni.cpp文件中添加对应的方法

extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_compressYUV(JNIEnv *env, jclass type,jbyteArray src_, jint width,jint height, jbyteArray dst_,jint dst_width, jint dst_height,jint mode, jint degree,jboolean isMirror) {
}extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_cropYUV(JNIEnv *env, jclass type, jbyteArray src_, jint width,jint height, jbyteArray dst_, jint dst_width, jint dst_height,jint left, jint top) {
}extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_yuvI420ToNV21(JNIEnv *env, jclass type, jbyteArray i420Src,jbyteArray nv21Src,jint width, jint height) {}

3、使用Libyuv库进行YUV数据的操作

接下来就是要libyuv对yuv数据进行缩放,旋转,镜像,裁剪等操作。

在libyuv的实际使用过程中,更多的是用于直播推流前对Camera采集到的YUV数据进行处理的操作。

对如今,Camera的预览一般采用的是1080p,并且摄像头采集到的数据是旋转之后的,一般来说后置摄像头旋转了90度,前置摄像头旋转了270度并且水平镜像。在下面的例子中,就对Camera返回的yuv数据进行相关的处理操作。

NV21转化为I420

对于如何获取Camera返回的YUV数据,不是本篇文章的重点,不了解的请自行百度谷歌。

因为Camera返回的YUV数据只能是NV21和YV12两种,而libyuv的缩放旋转镜像的操作需要的是I420的数据格式,那么第一步就是将NV21(例子中Camera返回数据格式设置的是NV21)转化为I420了。

方法如下:

#include "libyuv.h"
void nv21ToI420(jbyte *src_nv21_data, jint width, jint height, jbyte *src_i420_data) {jint src_y_size = width * height;jint src_u_size = (width >> 1) * (height >> 1);jbyte *src_nv21_y_data = src_nv21_data;jbyte *src_nv21_vu_data = src_nv21_data + src_y_size;jbyte *src_i420_y_data = src_i420_data;jbyte *src_i420_u_data = src_i420_data + src_y_size;jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;libyuv::NV21ToI420((const uint8 *) src_nv21_y_data, width,(const uint8 *) src_nv21_vu_data, width,(uint8 *) src_i420_y_data, width,(uint8 *) src_i420_u_data, width >> 1,(uint8 *) src_i420_v_data, width >> 1,width, height);
}

首先我们必须得先导入libyuv(#include "libyuv.h"),在这里我们用到的是libyuv::NV21ToI420方法,我们来看下它传参

// Convert NV21 to I420.  Same as NV12 but u and v pointers swapped.
LIBYUV_API
int NV21ToI420(const uint8* src_y,int src_stride_y,const uint8* src_vu,int src_stride_vu,uint8* dst_y,int dst_stride_y,uint8* dst_u,int dst_stride_u,uint8* dst_v,int dst_stride_v,int width,int height) {return X420ToI420(src_y, src_stride_y, src_stride_y, src_vu, src_stride_vu,dst_y, dst_stride_y, dst_v, dst_stride_v, dst_u,dst_stride_u, width, height);
}

首先第一个参数src_y指的是NV21数据中的Y的数据,我们知道NV21的数据格式是YYYYYYYY VUVU,同时NV21的数据大小是widthheight3/2,可以知道Y的数据大小是widthheight,而V和U均为widthheight/4。

第二个参数src_stride_y表示的是Y的数组行间距,在这里很容易知道是width。

以此类推src_vu和src_stride_vu也可以相对应的知道了。对于后面的参数dst_y,dst_stride_y,dst_u,dst_stride_u,dst_v ,dst_stride_v表示分别表示的是输出的I420数据的YUV三个分量的数据,最后的width和height也就是我们设置的Camera的预览的width和height了。

3.2 I420数据的缩放和旋转

经过上面的NV21转化为I420操作之后,我们就可以对I420数据进行后续的缩放和旋转的操作,它们的传参跟上面的NV21ToI420是类似的,这里就不具体的介绍了。

缩放的方法

void scaleI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint dst_width,jint dst_height, jint mode) {jint src_i420_y_size = width * height;jint src_i420_u_size = (width >> 1) * (height >> 1);jbyte *src_i420_y_data = src_i420_data;jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;jint dst_i420_y_size = dst_width * dst_height;jint dst_i420_u_size = (dst_width >> 1) * (dst_height >> 1);jbyte *dst_i420_y_data = dst_i420_data;jbyte *dst_i420_u_data = dst_i420_data + dst_i420_y_size;jbyte *dst_i420_v_data = dst_i420_data + dst_i420_y_size + dst_i420_u_size;libyuv::I420Scale((const uint8 *) src_i420_y_data, width,(const uint8 *) src_i420_u_data, width >> 1,(const uint8 *) src_i420_v_data, width >> 1,width, height,(uint8 *) dst_i420_y_data, dst_width,(uint8 *) dst_i420_u_data, dst_width >> 1,(uint8 *) dst_i420_v_data, dst_width >> 1,dst_width, dst_height,(libyuv::FilterMode) mode);
}

值得注意的是,这边有一个缩放的模式选择 (libyuv::FilterMode),它的值分别有0,1,2,3四种,代表不同的缩放模式,在我实际的使用过程中,0的缩放速度是最快的,且远远快与其他的3种,并且就缩放的效果来看,以我的肉眼观察,看不出有什么区别,这里为了保证速度,一般用FilterMode.kFilterNone就好了。

typedef enum FilterMode {kFilterNone = 0,      // Point sample; Fastest.kFilterLinear = 1,    // Filter horizontally only.kFilterBilinear = 2,  // Faster than box, but lower quality scaling down.kFilterBox = 3        // Highest quality.
} FilterModeEnum;

旋转的方法如下,不过在这里要注意的是,因为Camera输出的数据是需要进行90度或者是270的旋转,那么要注意的就是旋转之后width和height也就相反了。

void rotateI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint degree) {jint src_i420_y_size = width * height;jint src_i420_u_size = (width >> 1) * (height >> 1);jbyte *src_i420_y_data = src_i420_data;jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;jbyte *dst_i420_y_data = dst_i420_data;jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;if (degree == libyuv::kRotate90 || degree == libyuv::kRotate270) {libyuv::I420Rotate((const uint8 *) src_i420_y_data, width,(const uint8 *) src_i420_u_data, width >> 1,(const uint8 *) src_i420_v_data, width >> 1,(uint8 *) dst_i420_y_data, height,(uint8 *) dst_i420_u_data, height >> 1,(uint8 *) dst_i420_v_data, height >> 1,width, height,(libyuv::RotationMode) degree);}
}

libyuv其他的一些操作

libyuv的操作不仅仅是上面的这些,它还有镜像,裁剪的一些操作,同时还有一些其他数据格式的转化和对于的操作。包括rgba与yuv数据的转化等。在文章中,镜像和裁剪的操作就不加以叙述了,在demo之中我已经加入了进去了。

最后

最近做直播推流,小视频的录制中才接触到的libyuv库的使用,网上也有一些相关的文章。

但是大多不是很详细,要么文章中的方法使用过程中有各种各样的问题,要么就是方法不够全面和具体。这篇文章也主要是做了一些总结。

最后贴上demo的Github地址:https://github.com/hzl123456/LibyuvDemo

作者:这真不是玩笑

链接:https://www.jianshu.com/p/bd0feaf4c0f9

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

一文读懂 YUV 的采样与格式

OpenGL 之 GPUImage 源码分析

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

使用libyuv对YUV数据进行缩放,旋转,镜像,裁剪等操作相关推荐

  1. 音视频入门-10-使用libyuv对YUV数据进行缩放、旋转、镜像、裁剪、混合

    音视频入门文章目录 libyuv libyuv 是 Google 开源的实现各种 YUV 与 RGB 之间相互转换.旋转.缩放等的库.它是跨平台的,可在 Windows.Linux.Mac.Andro ...

  2. 在android项目上集成libyuv库以及使用libyuv库完成camera的缩放,旋转,翻转,裁剪操作

    目录 一.下拉google官方的libyuv库代码 二.在android项目中集成libyuv库 1.环境配置 2.拷贝libyuv源码文件 ​编辑3.配置cmake libyuv相关的链接编译等 三 ...

  3. Android 端处理 YUV 数据 - Libyuv 的编译与使用

    在 Android 系统上, Camera 输出的图像一般为 NV21(YUV420SP 系列) 格式, 当我们想进行录像处理时, 会面临两个问题 问题 1 图像的旋转问题 后置镜头: 需要旋转 90 ...

  4. 【Android RTMP】x264 图像数据编码 ( NV21 格式中的 YUV 数据排列 | Y 灰度数据拷贝 | U 色彩值数据拷贝 | V 饱和度数据拷贝 | 图像编码操作 )

    文章目录 安卓直播推流专栏博客总结 一. NV21 图像数据中的 YUV 数据简介 二.向 x264 编码图片 三. 提取 NV21 数据中的灰度数据 Y 四. 提取 NV21 数据中的饱和度数据 U ...

  5. 【Android Camera2】玩转图像数据 -- NV21图像旋转,镜像,转rgba代码分析,性能优化

    [Android Camera2]玩转图像数据 业务场景介绍 NV21数据旋转 逐像素遍历法 NV21数据镜像 逐像素遍历法 中心翻转法 NV21转RGB/RGBA数据 逐像素遍历法 NV21组合操作 ...

  6. android yuv旋转,针对移动端摄像头yuv旋转、裁剪、镜像、格式转换算法的实现

    存在问题 移动端录像在yuv数据上存在如下问题: 无论android还是ios都不能直接从摄像头取出颜色空间为i420的数据,所以在编码前需要进行格式转换. 而且由于所取图像得分辨率必须是摄像头所提供 ...

  7. [Python从零到壹] 三十八.图像处理基础篇之图像几何变换(平移缩放旋转)

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  8. 【使用D3D11直接渲染YUV数据】

    使用D3D11直接渲染YUV数据 最初学习D3D11时,采取的是直接渲染RGB数据的方式,因为采集的时候采出来的是YUV420格式,需要利用libyuv库把YUVI420转成RGB格式.但是在实际项目 ...

  9. R语言使用magick包的image_rotate函数、image_flip函数、image_flop函数对图像进行缩放旋转、镜像、翻转(Rotate or mirror the image)

    R语言使用magick包的image_rotate函数.image_flip函数.image_flop函数对图像进行缩放旋转.镜像.翻转(Rotate or mirror the image) 目录

  10. R语言使用Rtsne包进行TSNE分析:通过数据类型筛选数值数据、scale函数进行数据标准化缩放、提取TSNE分析结果合并到原dataframe中(tSNE with Rtsne package)

    R语言使用Rtsne包进行TSNE分析:通过数据类型筛选数值数据.scale函数进行数据标准化缩放.提取TSNE分析结果合并到原dataframe中(tSNE with Rtsne package) ...

最新文章

  1. vue2.0通过Axios导出excel文件(解决乱码问题)
  2. uwsgi模式_nginx+uwsgi 和nginx+gunicorn区别、如何部署
  3. spark 算子使用类变量_自己工作中超全spark性能优化总结
  4. win32 ipv6 bind 10014问题
  5. Leetcode题目:Valid Anagram
  6. chrome91 后 SameSite by default cookies 不对外开放 解决方案
  7. java取当前日期_java如何获取系统的当前时间
  8. SMPL: A Skinned Multi-Person Linear Model
  9. 如何从我的虚拟环境中更新pip本身?
  10. 计算机二级公共基础知识易错点汇总
  11. 关于Aws SNS的使用 小结
  12. 竞选计算机课代表稿子,竞选课代表发言稿(精选7篇)
  13. 不同计算机打印机共享,不同系统之间设置打印机共享
  14. 如何发送gmail邮件_如何从R和Gmail发送电子邮件
  15. Java从数据库中读取Blob对象图片并显示的方法
  16. 进军欧罗巴:中国区块链企业何以敲开欧洲市场的大门
  17. deepin更新失败_deepin V20 20200826升级失败
  18. 人一生要读的100首古诗
  19. 服务器性能指标图英文翻译,技术性能指标,technic performance guildline,在线英语词典,英文翻译,专业英语...
  20. 前端导出excel自定义样式(行高除外)

热门文章

  1. STCMCU各系列里程碑的进化
  2. 二维码红包系统源代码
  3. android 自定义emoji表情包,emoji表情制作
  4. 华为U2000北向接口开发中遇到的坑——获取HW_MSTPInventory管理对象失败
  5. java读取movielens数据txt
  6. 产品经理们终极面试宝典
  7. 使用JPA @OneToMany关联时,@ JoinColumn和mappedBy有什么区别
  8. ansys--workbench(导入solidworks)受力分析
  9. C语言 | 延时函数(Delay)
  10. tp 数据库查询排序_ThinkPHP对查询的数据随机排序