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

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数据格式

转载于:https://my.oschina.net/HuJifeng/blog/152705

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值