上篇文章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. [译]内存中的Java数组是怎么样的

    (文章翻译自What does a Java array look like in memory?) Java中的数组存储了两个中的一个类型:原始类型的类或则是引用类型(比如指针) 当一个对象通过Ne ...

  2. leetcode[73] Set Matrix Zeroes 将矩阵置零

    给定一个矩阵,把零值所在的行和列都置为零.例如: 1 2 3 1 3 1 1 1 操作之后变为 1 3 0 0 0 1 1 方法1: 赋值另存一个m*n的矩阵,在原矩阵为零的值相应置新的矩阵行和列为零 ...

  3. 安装Windows操作系统的驱动程序(驱动精灵版) - 进阶者系列 - 学习者系列文章

    安装完操作系统之后,就需要安装对应的驱动程序了.下面就简要介绍下驱动程序的安装.以驱动精灵驱动安装软件为例. 1.  下载驱动精灵. 从http://www.drivergenius.com/网站下载 ...

  4. sql2005还原超长sql脚本,还原超大脚本文件

    原文:sql2005还原超长sql脚本,还原超大脚本文件 从外网把数据库用导出脚本的方式导出来了,280M的样子,导是导出来了,但是在本机执行sql脚本的时候,直接就是out of memory,之前 ...

  5. mediawiki在windows下的安装

    mediawiki在windows下的安装 对于刚接触wiki的朋友们来说,配置一个服务器环境,安装并运行mediawiki是一件很麻烦的事情,在这里,我尽量用通俗易懂的语言,介绍mw(mediawi ...

  6. [转载+实践理解]Android动画---如何正确使用平移动画(关于fillBefore和fillAfter的一点说明)(转载)

    红色部分为自己的实践理解 如何实现将View向上平移自身高度一半的距离? TranslateAnimation translate = new TranslateAnimation( Animatio ...

  7. 不是技术牛人,如何拿到国内IT巨头的Offer【转】

    转:http://blog.csdn.net/lsldd/article/details/13506263 不久前,byvoid面阿里星计划的面试结果截图泄漏,引起无数IT屌丝的羡慕敬仰.看看这些牛人 ...

  8. [转] iOS ABI Function Call Guide

    source: apple ARMv6 Function Calling Conventions When functions (routines) call other functions (sub ...

  9. PHP实现一个简单url路由功能

    如果一个页面的内容呈现,需要根据url上传递的参数来进行渲染.很多时候可能是这样子写:xxx.com/xx?c=x&m=x& t=..,而我们看到的url往往是这样子的(以新浪微游戏的 ...

  10. cocos2dx android lua文件设置问题

    cocos2d-x版本: cocos2d-2.1rc0-x-2.1.2 通常我们在文件AppDelegate.cpp的applicationDidFinishLaunching()函数中设置lua代码 ...