文档章节

每周总结20130814——Android NDK环境的搭建和使用,YUV420SP格式图像的处理

Pupa
 Pupa
发布于 2013/08/14 20:54
字数 2295
阅读 1313
收藏 10

Windows下搭建Android NDK开发环境

更新:比较新的版本的Android NDK都自带基本的GNU工具链,所以不用安装庞大的cygwin或者MSYS了,直接解压NDK然后在Eclipse里配置编译器就可以了。

————————————————————————————————————

Android NDK需要使用Linux下的make、gdb等开发工具,因此要安装一个模拟的Linux环境。这里选择最常用的cygwin。MSYS应该也可以,不过没有亲自试过,留给有求证精神并鄙视cygwin的庞大和缓慢的Coder去验证!

cygwin有自己的安装器,相当于Linux发行版下的包管理器,用来管理软件。打开后选择从网络安装,选择一个合适的镜像,偷懒的话直接把Devel分类下的软件全部选上,点击下一步后这个包管理器会自己解决各种乱七八糟的依赖关系,给你下载安装几个G的软件包。如果有洁癖或者网络不给力,可以自己慢慢选择要装哪些软件,这样装的东西会少很多。顺便吐槽一句,cygwin的这个图形化包管理器体验真是渣,快装完的时候有个选项没看清,手贱点了一下上一步,我再次点击下一步的时候它就给我卸载又重新安装配置了一遍,于是又多花了十几分钟。。。有没有省事点的像aptitude这样牛气哄哄的工具?

装完cygwin后还要简单地配置一下。首先请下载最新版本的Android NDK并解压,解压后的路径名不能包含空格!这是NDK自己的硬性规定,不然就等着出错吧……这些准备好后打开cygwin terminal。这货虽然不如Linux下各种功能强大的Terminal,但比Windows的cmd顺眼多了。首先看看你的home目录准备好了没有,方法很简单,输入echo ~即可。如果输出的目录类似于/home/xxx,那就没问题了;如果输出是Windows用户的home目录,那就打开Windows环境变量设置,删掉home变量(我以为会对Windows系统有影响的,删掉后发现没有可见的变化……),然后在cygwin根目录的home目录下建立一个与你当前登录Windows的用户名同名的文件夹。重启cygwin terminal后,再次输入echo ~检查,可以看到cygwin已经将home目录重定向到你新建的文件夹。

接下来把/etc/defaults/etc/skel目录下的.bash_profile、.bashrc、.inputrc这几个文件复制到自己的home目录下,然后在.bash_profile文件结尾添加类似的两句:

NDK=/cygdrive/d/android-ndk/android-ndk-r9/ ##这里是ndk的目录,我是注释,你看不见我!    
export NDK

这几句是给cygwin设置环境变量。/cygdrive/d/对应Windows的D盘,其他盘依此类推。熟悉Linux环境的朋友用terminal可以轻松完成这几步,不熟悉也没关系,默默地复制粘贴文件然后用记事本打开文件编辑吧,哈哈~

做完上面的几步,NDK环境基本搞定,接下来可以简单测试一下。在terminal中执行cd $NDK/samples/hello-jni/,然后再执行../../ndk-build,如果看到这样的

那么恭喜你,你的环境搭建完成~

Eclipse环境下使用Android NDK

环境搭建好后我还惆怅了一阵子,Google就给了hello-jni这么一个破例子,NDK到底怎么使?继续查了很多资料终于有了眉目。在hello-jni工程的jni目录下,有Android.mk这样一个文件,这其实就是一个Makefile文件。这个文件可谓麻雀虽小,五脏俱全,该有的都有了,可以成为很好的范本和copy对象。接下来在Eclipse下打开我们自己的Android工程,已有的或者新建的都行。在Project Explorer下右键选中工程打开Properties,然后找到Builders。这里列举了一个Android工程在编译时用到的一些工具。接下来我们选中New,选择Program类型,接下来就是具体的设置了。贴图以示清白:

Name可以随便取,具体的Location就要按照自己的安装路径设置。最后的Arguments比较长,我的是这样的--login -c "cd '${project_loc}' && $NDK/ndk-build NDK_DEBUG=1",其中NDK_DEBUG=1是设置编译出来的so文件是Debug版本,不需要的可以去掉这一句。

接下来在Android工程目录下新建一个文件夹jni,这个文件夹主要是存放mk文件和C/CPP的代码文件。于是别犹豫,赶紧把hello-jni给的Android.mk文件复制进去。

在Android工程下新建一个Java文件TestJni.java:

public class TestJni {    
    public native void test();    
   
    static {    
        System.loadLibrary("testjni");    
    }    
}

然后把Android.mk文件的LOCAL_MODULE对应的值改成testjni。LOCAL_SRC_FILES这个选项是指所用到的C/CPP代码文件。Jni的C/CPP代码有其固定格式,得先用javah工具生成头文件,然后按照头文件的函数声明来写具体代码。关于javah命令的使用,可以查阅jni的有关资料,这里不再赘述。这里有篇很实用的文章,提到的错误基本人人都会犯一遍:《用javah 导出类的头文件, 常见的错误及正确的使用方法》。

正确生成头文件,然后按照头文件中的声明格式写C/CPP文件,把头文件和C/CPP文件添加到Android工程的jni目录里,最后点Run As Android Application,这些代码就会被编译成so文件,在Java代码中通过native方法可以方便调用。

  

YUV420SP格式图像的旋转及矩形区域的截取

YUV420SP,即所谓的NV21格式,是Android系统中摄像设备的预览数据的默认格式。Android API中有个方法Camera.Parameters.setPreviewFormat(int pixel_format),用来设置预览数据的格式。尝试过设置其他如jpeg、png等更省事的格式,但在运行中都会报错,仔细查阅API文档,有这么一句:It is strongly recommended that either NV21 or YV12 is used, since they are supported by all camera devices.这才知道不同Android设备支持的格式可能不一样,但NV21和YV12是所有设备都支持的格式。接下来将以默认的格式NV21为例。

在ARGB颜色模式下的图像操作都非常轻松,因为每个像素的颜色都用一个32位的整型数表示,按空间顺序排列,非常符合人的正常思维。但NV21格式的图像数据就有点绕了,把Y和UV放到了两个平面上,UV的采样频率的总和还只有Y的一半,平均下来一个像素占用12位。NV21格式的具体介绍可以从下面的参考文章找到,这里不再赘述,总之这个格式折腾了我好久…

下面是一段在YUV420SP格式下截取矩形图像区域的代码,逻辑简单明了,但细节处容易出错。仅供参考:

void getTargetRect(byte[] src, byte[] dst, int srcW, int srcH, int startW,    
            int startH, int dstW, int dstH) {    
&#160;&#160;&#160;&#160;&#160;&#160;&#160; if (src == null || dst == null || srcW < 1 || srcH < 1 || startW < 0    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; || startH < 0 || dstW < 1 || dstH < 1 || startW + dstW > srcW    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; || startH + dstH > srcH)    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; return;

&#160;&#160;&#160;&#160;&#160;&#160;&#160; int srcFrameSize = (srcW * srcH * 3) >> 1;    
&#160;&#160;&#160;&#160;&#160;&#160;&#160; int dstFrameSize = (dstW * dstH * 3) >> 1;

&#160;&#160;&#160;&#160;&#160;&#160;&#160; for (int j = startH; j < startH + dstH; ++j) {    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int jOffset = j - startH;    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int srcUv = srcFrameSize + (j >> 1) * srcW;    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int dstUv = dstFrameSize + (jOffset >> 1) * dstW;    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; for (int i = startW; i < startW + dstW; i += 2) {    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int iOffset = i - startW;    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; dst[jOffset * dstW + iOffset] = src[j * srcW + i];    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; dst[jOffset * dstW + iOffset] = src[j * srcW + i + 1];    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if ((jOffset & 1)==0) {    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; dst[dstUv + iOffset] = src[srcUv + i];    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; dst[dstUv + iOffset + 1] = src[srcUv + i + 1];    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }    
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }    
&#160;&#160;&#160;&#160;&#160;&#160;&#160; }    
&#160;&#160;&#160; }

有关图像旋转,参考文章[2]已经给出实现代码,需要注意的问题是旋转的方向,旋转的方向可以通过改变循环系数的增减方向来改变。

有意义的参考文章:

[1]原YUV格式的解析 Android NV21 视频采集

[2]图文详解YUV420数据格式

© 著作权归作者所有

共有 人打赏支持
Pupa

Pupa

粉丝 1
博文 6
码字总数 5105
作品 1
朝阳
程序员
私信 提问
加载中

评论(6)

Pupa
Pupa

引用来自“小_精灵”的评论

你好,getTargetRect(byte[] src, byte[] dst, int srcW, int srcH, int startW, int startH, int dstW, int dstH) 怎么调用,我是新手,试了这个出现IndexOutOfBoundsException.
Src:源YUV数据; dst存放目标Yuv的数据,长度为目标长*宽
SrcW,SrcH:源图片宽、高
startW,startH: 截图起点左、上坐标
dstW, dstH: 截图的宽、高
这样调用出现IndexOutOfBoundsException,麻烦指导,非常感谢!

src和dst数组是各自的宽*高*1.5的大小,然后宽高要设置成偶数,我代码里没有处理奇数的情况
小_精灵
byte[] dst的长度设为长*宽*1.5也不行
小_精灵
你好,getTargetRect(byte[] src, byte[] dst, int srcW, int srcH, int startW, int startH, int dstW, int dstH) 怎么调用,我是新手,试了这个出现IndexOutOfBoundsException.
Src:源YUV数据; dst存放目标Yuv的数据,长度为目标长*宽
SrcW,SrcH:源图片宽、高
startW,startH: 截图起点左、上坐标
dstW, dstH: 截图的宽、高
这样调用出现IndexOutOfBoundsException,麻烦指导,非常感谢!
Pupa
Pupa

引用来自“viwii”的评论

我试过,msys+mingw可以编 译成功

呵呵,有空就把cygwin砍了换msys,cygwin快把我D盘占满了
viwii
viwii
我试过,msys+mingw可以编 译成功
王珏大大
王珏大大
挺详细,正准备学呢
【Android音视频】Android—YUV格式深入浅出

文章目录 Android音视频—YUV格式深入浅出 Android音视频—YUV格式深入浅出 文章参考: 图文详解YUV420数据格式 YUV主要采样格式理解 YUV格式详解 百度百科和维基百科 概述 本文基于Android...

sslinp
2018/11/28
0
0
使用 Android NDK 重用现有的 C 代码

开始之前 首先,了解 Android 原生开发工具包(NDK)的动机之一是得以利用开源项目,大多数项目都是用 C 语言编写的。完成本教程后,您将了解到如何创建 Java 本地接口(JNI)库,它使用 C ...

IBMdW
2012/01/05
952
0
使用 Android NDK 重用现有的 C 代码

开始之前 首先,了解 Android 本机开发人员工具包(NDK)的动机之一是得以利用开源项目,大多数项目都是用 C 语言编写的。完成本教程后,您将了解到如何创建 Java 本地接口(JNI)库,它使用...

IBMdW
2011/05/12
2.1K
0
【jni 编程】—— NDK环境搭建

在《站在巨人的肩膀上,谈app的创新性》一文中 http://my.oschina.net/liusicong/blog/311971,我提到过构建app技术壁垒的必要性。在构建技术壁垒时,我们往往需要调用许多库函数,例如:图像...

刘小米
2014/09/10
0
0
Ubuntu下android学习——(1)开发环境的搭建

强调:你使用的是ubuntu,不是windows,你学习的是Android开发,不是怎么配环境 一、Android简介 Android是基于Linux内核的软件平台和操作系统。 Android构架主要由3部分组成,linux内核层,...

OrionBox
2012/07/21
0
1

没有更多内容

加载失败,请刷新页面

加载更多

Confluence 6 升级中的一些常见问题

升级的时候遇到了问题了吗? 如果你想尝试重新进行升级的话,你需要首先重新恢复老的备份。不要尝试再次对 Confluence 进行升级或者在升级失败后重新启动老的 Confluence。 在升级过程中的一...

honeymoose
今天
2
0
C++随笔(四)Nuget打包

首先把自己编译好的包全部准备到一个文件夹 像这样 接下来新建一个文本文档,后缀名叫.nuspec 填写内容 <?xml version="1.0"?><package xmlns="http://schemas.microsoft.com/packaging/201......

Pulsar-V
今天
2
0
再谈使用开源软件搭建数据分析平台

三年前,我写了这篇博客使用开源软件快速搭建数据分析平台, 当时收到了许多的反馈,有50个点赞和300+的收藏。到现在我还能收到一些关于dataplay2的问题。在过去的三年,开源社区和新技术的发...

naughty
今天
5
0
Python3的日期和时间

python 中处理日期时间数据通常使用datetime和time库 因为这两个库中的一些功能有些重复,所以,首先我们来比较一下这两个库的区别,这可以帮助我们在适当的情况下时候合适的库。 在Python文...

编程老陆
今天
2
0
分布式面试整理

并发和并行 并行是两个任务同时进行,而并发呢,则是一会做一个任务一会又切换做另一个任务。 临界区 临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用,但是每一次,只能有...

群星纪元
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部