Kevin Su bio photo

Kevin Su

patience, persevere, and enjoy

Email Github Stackoverflow

关于JNI和NDK的内容可以说是Android开发进阶必须学习的内容.

第一个问题,为什么需要学习NDK呢?

这是因为通过Java语言编译出来的二进制码需要在runtime时再次编译成机器码.所以效率对比起C/C++就没有那么高效,另外一个原因,因为Java的代码很容易被反编译,即使通过混淆可以使得代码阅读难度加大,但是对于牛逼的人来说,还是有办法搞定的.所以这两种情况下,就需要把效率型,和保密型的代码用C/C++来实现.这就是NDK的用处.

第二个问题,什么是NDK呢?

官方文档中显示:
The NDK is a toolset that allows you to implement parts of your app using native-code languages such as C and C++.
NDK就是允许你是使用如C/C++这样的语言来实现你的app的工具集(交叉编译工具).能够在linux平台编译出在arm平台下执行的二进制库文件.

这里需要讲解一下,几个概念:

交叉编译:在一个系统平台上可以产生出另外一个系统平台上可以执行的代码.例如在windows x86系统上编译出的库文件可以在arm平台上运行的代码.所以也就是一般我们拿到的x86上的源码程序,需要通过交叉编译后,才能生成嵌入式设备(一般是手机)上可以用的库文件.

库文件:C代码被编译成库文件后,链接之后才能执行.库文件分为动态库静态库.

unix环境下的动态库以.so为后缀的文件,而windows环境下以.dll为后缀的文件;  
.a为后缀的文件是静态库的拓展名
第三个问题,什么是JNI?

JNI(java native interface):Java本地接口,是Java和C/C++代码交流的接口.

--JNI规范: C语言和Java语言交流需要一个适配器,中间件,即JNI,JNI提供一种规范.  
--C语言中调用JAVA方法:可以让我们在C代码中找到Java代码class中的方法,并且调用该方法.  
--JAVA语言中调用C语言方法:同事也可以在Java代码中,将一个C语言的方法映射到Java的某个方法上

所以综上所述,NDK的开发流程,就是:
1. 编写(拿别人)C/C++代码
2. 然后写Java调用C的接口文件
3. 然后通过编译,生成可用的so文件
4. 最后在Android中,加载该so文件,然后调用编写好的接口方法


###以FFMPEG为示例,记录FFMPEG移植Android的过程.

上面主要是解释原理的东西.其实本来应该拿Google中的helloJni来作为演示实例.但是我是因为工程中需要用到FFMPEG来对视频进行压缩,才开始接触NDK的.所以以下内容主要是记录我编译FFMPEG的过程以及遇到的问题.

需求:把一个Android录制好的mp4文件,进行压缩.ffmpeg对于视频的操作的功能,岂止这么少.经过我google,发现其实对一个录制好的视频要进行压缩,其实本质的意义就是对一个视频文件解码,在编码的过程.

关于编译流程,网上有很多文章.我借鉴了以下文章,最终成功编译成功.
1. Mac环境下Android Studio移植FFmpeg
2. Mac下为Android Studio编译Ffmpeg(一)ndk部分
3. Android移植ffmpeg.so实现屏幕录制功能(上)
4. 雷霄骅(leixiaohua1020)的专栏

我在这个编译的过程中,因为自己的C基础基本为0,所以根本找不到哪些方法才是我需要的方法,但是通过命令行来实现.这个网友通过修改FFMPEG的main方法,实现传入命令的方式,调用其中的方法.

结果: 最终能够成功的对一个视频进行压缩.测试一个一分钟的视频(大小14M左右)的视频,经过50秒的时间,压缩成2.3M左右.
最后其实还是放弃了,因为这种方法不现实,完整的ffmpeg的so文件有十几M,这只是一个框架下的.另外就是压缩的时间实在太长了.所以要通过这个方法来对视频进行压缩是不现实的.

参考了一下QQ,微信,instagram的做法,应该是通过获取Camera投影到SurfaceView的流以及获取麦克风的音频流,将这两个流合并成一个MP4文件,这样就不用经历两次解码和编码的过程,还可以像instagram和qq一样,把多段视频合成一段视频.

几点注意:

  1. 对于文章中首先对FFMPEG进行编译,根据需求对各个平台,生成多个平台下的多个so库文件,后面又在NDK中通过ndk-build的方式生成多个平台下的ffmpeg的so库文件.我认为其实是可以通过一次编译就实现的.但是因为需要在Android.mk文件中,把所有的.c文件都加入编译列表中,导致这种Android.mk文件太复杂,所以经过头一次的编译,生成我们需要的几个so文件.后面再编译只需要加入这几个so文件便可.

参考的文章:
Android 如何藉由JNI來使用C/C++程式
Android 开发 之 JNI入门 - NDK从入门到精通