上篇文章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. Premiere Pro CC问题集,不断更新

    1.Premiere Pro CC不好用? 是的.原因如下: 1.1 Adobe公司不注重用户体验,不注重工作流程,导致这款软件的用户体验很差,设计也很烂.对比Adobe公司当年用户体验最好的软件 F ...

  2. Solr安装(Tomcat)

    Solr安装(Tomcat)   安装环境 Windows 7 64bit Apache-tomcat-8.0.9-windows-x64 Solr-4.9.0 JDK 1.8.0_05 64bit ...

  3. des和Rijndael加密

    ------------IV的作用: 为了保证数据的安全,.NET基类库中提供的私钥算法类使用称作密码块链(CBC,Cipher Block Chaining)的链模式,算法使用一个密钥和一个初始化向 ...

  4. 序列化(ObjectOutputStream和ObjectInputStream)(切记:out是输出到本地中,in是输入到程序中)

    注意:序列化自定义类必须实现一个接口Serializable,在c#中序列化自定义类是使用特性也就是[Serializable] //要实现序列化的类 public class Student imp ...

  5. PLinq

    PLinq(Linq的并行计算) 上面介绍完了For和ForEach的并行计算盛宴,微软也没忘记在Linq中加入并行计算.下面介绍Linq中的并行计算. 4.0中在System.Linq命名空间下加入 ...

  6. Ubuntu下的截图工具

    转载自:http://os.yesky.com/88/8733088.shtml 相信大家对于屏幕截图(或称抓图)应该不会陌生,在Windows平台上,我们可以使用许多第三方的专业抓图软件如SnagI ...

  7. Windows 下让 Python 多个版本共存(支持 pip)

    转载自 http://blog.kgzx.net/index.php/archives/40/ 因为类库兼容性的关系,写实际项目时都是用 Python 2,但自己试验性的写点小东西还是喜欢用 Pyth ...

  8. 玩转python之测试一个对象是否是类字符串

    提到类型测试,我首先想到python中“鸭子类型”的特点,所谓鸭子类型,即如果它走路像鸭子,叫声也像鸭子, 那么对于我们的应用而言,就可以认为它是鸭子了!这一切都是为了功能复用. 我们总是需要测试一个 ...

  9. ToolBox Analysis & Design

    话说小菜过做已近3年,虽出身PHP后项目大多涉及.net,系统也做得比较繁杂,从常见的CMS,企业OA,ERP,也涉及到电商系统的开发定制,爬虫,工具不一而足,其中web系统居多. 由于表现良好,时常 ...

  10. 取xml文件转成List<T>对象的两种方法

    读取xml文件转成List<T>对象的两种方法(附源码)   读取xml文件转成List<T>对象的两种方法(附源码) 读取xml文件,是项目中经常要用到的,所以就总结一下,最 ...