上篇文章Android libyuv使用系列(一)Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别中我们了解了YUV相关的知识,而本篇文章我会介绍libyuv是什么,以及如何使用libyuv进行相应的图像数据处理。

当我们在 Android 中处理 Image 时,常因为 Java 性能和效率问题导致达不到我们期望的效果,例如进行Camera 采集视频流的原始帧时我们需要每秒能够获取足够的帧率才能流畅的显示出来,这也是为什么美颜 SDK 和图像识别等这类 SDK 都是基于 C / C++ 的原因之一。语言的特性也是关键因素点,所以常常会在 Java 中调用 C / C++ 的 API 来进行相关操作。

因最近工作需求是替代 Camera 的原始打视频流,数据源是 Bitmap 格式的,如果使用 Java 的方法来进行Bitmap 的旋转,转换为 YUV 类型的 NV21 、YV12 数据的话,那么少说也要 15FPS 的视频就尴尬的变成了5FPS的PPT幻灯片了。关于YUV的各种格式区别请见我的博客:直播必备之YUV使用总结 —— Android常用的几种格式:NV21/NV12/YV12/YUV420P的区别,而Google提供了一套Image处理的开源库[libyuv](git clone https://chromium.googlesource.com/libyuv/libyuv)(科学上网),可高效的对各类Image进行Rotate(旋转)、Scale(拉伸)和Convert(格式转换)等操作。

libyuv官方说明

libyuv is an open source project that includes YUV scaling and conversion functionality.

  • Scale YUV to prepare content for compression, with point, bilinear or box filter.
  • Convert to YUV from webcam formats.
  • Convert from YUV to formats for rendering/effects.
  • Rotate by 90/180/270 degrees to adjust for mobile devices in portrait mode.
  • Optimized for SSE2/SSSE3/AVX2 on x86/x64.
  • Optimized for Neon on Arm.
  • Optimized for DSP R2 on Mips.

简单来讲,libyuv 就是一个具有可以对 YUV 进行拉伸和转换等操作的工具库。

几个重要的功能:

  • 可以使用 point,bilinear 或 box 三种类型的压缩方法进行YUV的拉伸
  • 旋转 90/180/270 的角度以适配设备的竖屏模式
  • 可将 webcam 转换为 YUV
  • 还有一些列的平台性能优化等等

大概了解了libyuv的功能后,我们来看看普通方式和libyuv之间的差距。

系统环境

我的硬件环境是Macbook Pro和PC,硬件环境如下:

Hardware Macbook Pro Retina, 13-inch, Early 2015 PC
OS MacOS Sierra 10.12 Windows 10
CPU 2.7 GHz Intel Core i5 i5 6500
RAM 8 GB 1867 MHz DDR3 16G 2400MHz DDR4
HDD 128 SSD 256 SSD

我们使用一张XXX的Bitmap来做一下对比测试,看看不同的系统环境下,效果如何。

Bitmap和YUV的转换

数据源是 Bitmap,项目中会涉及以下几种格式:

Bitmap YUV
ARGB_8888 NV21 (YUV420SP)
RGB_565 YV12 (YUV420P)

StackOverFlow上有网友给出了手动转换BitmapToYuv的方式:

  /**
* Bitmap转换成Drawable
* Bitmap bm = xxx; //xxx根据你的情况获取
* BitmapDrawable bd = new BitmapDrawable(getResource(), bm);
* 因为BtimapDrawable是Drawable的子类,最终直接使用bd对象即可。
*/
public static byte[] getNV21(int inputWidth, int inputHeight, Bitmap srcBitmap) {
int[] argb = new int[inputWidth * inputHeight];
if (null != srcBitmap) {
try {
srcBitmap.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
} catch (Exception e) {
e.printStackTrace();
return null;
}
// byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2];
// encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
if (null != srcBitmap && !srcBitmap.isRecycled()) {
srcBitmap.recycle();
srcBitmap = null;
}
return colorconvertRGB_IYUV_I420(argb, inputWidth, inputHeight);
} else return null;
} private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
final int frameSize = width * height;
int yIndex = 0;
int uvIndex = frameSize; int a, R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) { a = (argb[index] & 0xff000000) >> 24; // a is not used obviously
R = (argb[index] & 0xff0000) >> 16;
G = (argb[index] & 0xff00) >> 8;
B = (argb[index] & 0xff) >> 0; // well known RGB to YUV algorithm
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; /* NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2 meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every otherpixel AND every other scanline.*/
yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (j % 2 == 0 && index % 2 == 0) {
yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
}
index++;
}
}
} public static byte[] colorconvertRGB_IYUV_I420(int[] aRGB, int width, int height) {
final int frameSize = width * height;
final int chromasize = frameSize / 4; int yIndex = 0;
int uIndex = frameSize;
int vIndex = frameSize + chromasize;
byte[] yuv = new byte[width * height * 3 / 2]; int a, R, G, B, Y, U, V;
int index = 0;
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
//a = (aRGB[index] & 0xff000000) >> 24; //not using it right now
R = (aRGB[index] & 0xff0000) >> 16;
G = (aRGB[index] & 0xff00) >> 8;
B = (aRGB[index] & 0xff) >> 0; Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; yuv[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); if (j % 2 == 0 && index % 2 == 0) {
yuv[vIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));
yuv[uIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));
}
index++;
}
}
return yuv;
}

上面的方式如果在不苛求性能的情况下是可以满足使用的,然而每秒也就能够达到5~8FPS的水平(与设备的硬件配置也有关系),显然达不到我的需求。那么使用libyuv后的结果如何呢?别着急,我们先看看如何编译libyuv。

获取libyuv

libyuv git clone下来后,我们可以看到结构目录如下:

libyuv给出了三个平台的MakeFile文件,可以Build出Windows / Mac OS / Linux三种平台的资源包。因为我使用的是Android,这里以Android.mk为例:

# This is the Android makefile for libyuv for both platform and NDK.
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_CPP_EXTENSION := .cc LOCAL_SRC_FILES := \
source/compare.cc \
source/compare_common.cc \
source/compare_neon64.cc \
source/compare_gcc.cc \
source/convert.cc \
source/convert_argb.cc \
source/convert_from.cc \
source/convert_from_argb.cc \
source/convert_to_argb.cc \
source/convert_to_i420.cc \
source/cpu_id.cc \
source/planar_functions.cc \
source/rotate.cc \
source/rotate_argb.cc \
source/rotate_mips.cc \
source/rotate_neon64.cc \
source/row_any.cc \
source/row_common.cc \
source/row_mips.cc \
source/row_neon64.cc \
source/row_gcc.cc \
source/scale.cc \
source/scale_any.cc \
source/scale_argb.cc \
source/scale_common.cc \
source/scale_mips.cc \
source/scale_neon64.cc \
source/scale_gcc.cc \
source/video_common.cc # TODO(fbarchard): Enable mjpeg encoder.
# source/mjpeg_decoder.cc
# source/convert_jpeg.cc
# source/mjpeg_validate.cc ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS += -DLIBYUV_NEON
LOCAL_SRC_FILES += \
source/compare_neon.cc.neon \
source/rotate_neon.cc.neon \
source/row_neon.cc.neon \
source/scale_neon.cc.neon
endif LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include LOCAL_MODULE := libyuv_static
LOCAL_MODULE_TAGS := optional include $(BUILD_STATIC_LIBRARY)

使用NDK编译libyuv

常用两种编译方式:

  • 直接引入源码

通过Gradle使用脚本的方式代替手动编译。在构建项目的同时将libyuv编译引入,通过Gradle来构建编译,具体方法是在app层级的build.gradle中加入对应的Build Task,指定相关路径,同时构建项目和编译。

  • 预先手动将libyuv编译成动态库so文件,放入对应的jniLibs目录下

使用ndk-build命令进行编译,每次执行ndk-build之前都需要ndk-build clean一遍才行,不然不会将新的改动编译进去。

如果使用JNI并且要在 c / c++ 层使用libyuv的话,上述两种方式都需要在项目中的 Android.mk 文件中加入 libyuv 的引用,如:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_LDFLAGS += -ljnigraphics
LOCAL_SHARED_LIBRARIES := libyuv
LOCAL_MODULE := yuv_utils
LOCAL_SRC_FILES := com_rayclear_jni_YuvUtils.c
include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS)
LOCAL_MODULE := yuv
LOCAL_SRC_FILES := $(LOCAL_PATH)/libyuv.so
include $(PREBUILT_SHARED_LIBRARY)

到这里libyuv的编译工作就基本完成了,准备工作做完后,在需要使用的Activity或者Application初始化的时候添加如下代码进行引入:

public class MyApplication extends Application {

    /**
* so文件默认前缀带lib,在此引用时需要去掉"lib"和后缀".so"
* */
static {
System.loadLibrary("yuv_utils");
System.loadLibrary("yuv");
} private static Context sContext; @Override
public void onCreate() {
super.onCreate();
initContext();
} private void initContext() {
sContext = getApplicationContext();
} public static Context getContext() {
return sContext;
}
}

性能对比

先上图看看区别:

使用Java进行Bitmap转换为YUV时,一张1440 x 900 的Bitmap耗时大概35 ~ 45ms左右,而使用libyuv则花费14~22 ms左右,性能提升一倍,而更暴力的来了,如果同时进行拉伸缩放和格式转换,例如1440 x 90 —> 480 x 270,可以实现 5 ~ 13 ms,性能提升了3 ~ 6倍。这意味着1000 ms可以满足我们不低于25FPS的需求。

Rawviewer查看YUV文件

上篇文章中提供了RawViewer的下载地址,但是具体的使用方式还没说,在Demo中有方法FileUtil.saveYuvToSdCardStorage(dstYuv)用于保存YUV文件(.jpeg为后缀的)到存储中,从设备中取到这个文件使用RawViewer打开,打开前先进行RawViewer的参数配置,否则可能会闪退。我们预先设定分辨率及格式后打开即可。如下图所示:

Demo源码

简单的Demo结果因为有事拖拖拉拉搞了一下午,不得不对着产率低下感到头疼啊。Demo源码在我的GitHub仓库,这篇文章中介绍的不详细的地方可以从项目中看看实现即可,后续有时间我会将这个库做个简单的工具类封装。有问题的朋友请随时留言指错或者提问,如果觉得对你有帮助的话请顺手点个Star,谢谢大家的支持!

Android libyuv应用系列(二)libyuv的使用的更多相关文章

  1. 【Android】资源系列(二) -- 文件原样保留的资源assets和res/raw文件夹

    这两个文件夹都能够存放文件.而在打包的时候被原样保留. 那用这两个文件夹可以做什么事呢? 1.放一个apk,要用的时候调出来.免得去下载server下载. 2.放一个sql,当app数据库非常大的时候 ...

  2. ANDROID Porting系列二、配置一个新产品

    ANDROID Porting系列二.配置一个新产品 详细说明 下面的步骤描述了如何配置新的移动设备和产品的makefile运行android. 1.         目录//vendor/创建一个公 ...

  3. Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能

    Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...

  4. (android高仿系列)今日头条 --新闻阅读器 (二)

    高仿今日头条 --- 第一篇:(android高仿系列)今日头条 --新闻阅读器 (一)    上次,已经完毕了头部新闻分类栏目的拖动效果. 这篇文章是继续去完好APP 今日头条  这个新闻阅读器的其 ...

  5. 【圣诞特献】Web 前端开发精华文章推荐【系列二十一】

    <Web 前端开发精华文章推荐>2013年第九期(总第二十一期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和  ...

  6. Android提升篇系列:Activity recreate(Activity 重新创建/自我恢复)机制(一)

    注:本文中的recreate是指当内存不足时,Activity被回收,但再次来到此Activity时,系统重新恢复的过程.例如:当Activity A到Activity B时,如果内存不足,A被回收, ...

  7. (android高仿系列)今日头条 --新闻阅读器 (三) 完结 、总结 篇

    从写第一篇今日头条高仿系列开始,到现在已经过去了1个多月了,其实大体都做好了,就是迟迟没有放出来,因为我觉得,做这个东西也是有个过程的,我想把这个模仿中一步一步学习的过程,按照自己的思路写下来,在根据 ...

  8. Android之Activity系列总结(一)--Activity概览

    Activity 本文内容 创建 Activity 实现用户界面 在清单文件中声明 Activity 启动 Activity 启动 Activity 以获得结果 结束 Activity 管理 Acti ...

  9. Android Camera开发系列(下)——自定义Camera实现拍照查看图片等功能

    Android Camera开发系列(下)--自定义Camera实现拍照查看图片等功能 Android Camera开发系列(上)--Camera的基本调用与实现拍照功能以及获取拍照图片加载大图片 上 ...

随机推荐

  1. 有return如果是try catch finally运行命令

    背景: 昨天一个朋友出去采访,遇到这样的问题:"C#  catch那里return.finally也弄它运行?" 个人总结实践: 1.无论有木有出现异常.finally块中代码都会 ...

  2. leetcode第17题--4Sum

    Problem:Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + ...

  3. ssis的script task作业失败(调用外部dll)

    原文 ssis的script task作业失败 我的ssis作业包里用了一个script task,会查询一个http的页面接口,获取json数据后解析然后做后续处理,其中解析json引用了本地目录下 ...

  4. VS多平台开发

    Xamarin技术文档------VS多平台开发   此技术业余时间研究,仅供大家学习参考,不涉及深入研究,有一定开发基础的人员,应该都能较快上手. 一.简介 Xamarin始创于2011年,旨在使移 ...

  5. SpringMVC视图

    SpringMVC视图机制详解[附带源码分析] 目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一 ...

  6. WCF、Web API、WCF REST、Web Service 区别

    Web Service It is based on SOAP and return data in XML form. It support only HTTP protocol. It is no ...

  7. Web缓存(Varnish方案)

    Web缓存(Varnish方案) 转载 http://www.s135.com/post/313/ arnish是一款高性能的开源HTTP加速器,挪威最大的在线报纸 Verdens Gang (htt ...

  8. DTD

    DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块. 它使用一系列的合法元素来定义文档结构. DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用. 内部的 DOCTYPE 声明 ...

  9. Bootstrap3.0学习第八轮

    Bootstrap3.0学习第八轮(工具Class)   前言 阅读之前您也可以到Bootstrap3.0入门学习系列导航中进行查看http://www.cnblogs.com/aehyok/p/34 ...

  10. [转]Even when one byte matters

    Source:http://kernelbof.blogspot.jp/2009/07/even-when-one-byte-matters.html Common Vulnerabilities a ...