NDK部分
1、下载ndk
这里就一笔带过了。
2、解压ndk
不要解压,文件权限会出错。执行之,会自动解压,然后mv到想放的地方。我放到了”/usr/local/bin/android-ndk-r10d”(此目录之后用$NDK_DIR指代)。
3、下载Ffmpeg
我下的是2.5.3版本。
4、解压Ffmpeg
解压Ffmpeg到$NDK_DIR/sources/ffmpeg-2.5.3。
5、修改Ffmpeg编译配置
在ffmpeg-2.5.3目录下把configure文件中的这几行,目的是去掉默认生成的库名字libavcodec.so.55最后那个”55″的版本号。
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
6、编译Ffmpeg
在ffmpeg-2.5.3目录下创建文件build_android.sh。
注意前三行要按照自己的路径正确配置。
#!/bin/bash
NDK=/usr/local/android-ndk-r10d
SYSROOT=$NDK/platforms/android-15/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
保存后执行
1
2
|
sudo
chmod
+x build_android.sh
.
/build_android
.sh
|
编译会花上一段时间。
7、查看编译结果
编译完成后$NDK_DIR/sources/ffmpeg-2.5.3下面会多出一个android目录,里面就是我们想要的编译好的库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
arm
android//arm:
Android.mk include lib
android//arm/include:
libavcodec libavfilter libavformat libavutil libswresample libswscale
android//arm/include/libavcodec:
avcodec.h avfft.h dv_profile.h dxva2.h old_codec_ids.h vaapi.h vda.h vdpau.h version.h vorbis_parser.h xvmc.h
android//arm/include/libavfilter:
asrc_abuffer.h avcodec.h avfilter.h avfiltergraph.h buffersink.h buffersrc.h version.h
android//arm/include/libavformat:
avformat.h avio.h version.h
android//arm/include/libavutil:
adler32.h avstring.h cast5.h downmix_info.h hash.h macros.h opt.h replaygain.h time.h
aes.h avutil.h channel_layout.h error.h hmac.h mathematics.h parseutils.h ripemd.h timecode.h
attributes.h base64.h common.h eval.h imgutils.h md5.h pixdesc.h samplefmt.h timestamp.h
audio_fifo.h blowfish.h cpu.h ffversion.h intfloat.h mem.h pixelutils.h sha.h version.h
audioconvert.h bprint.h crc.h fifo.h intreadwrite.h motion_vector.h pixfmt.h sha512.h xtea.h
avassert.h bswap.h dict.h file.h lfg.h murmur3.h random_seed.h stereo3d.h
avconfig.h buffer.h display.h frame.h log.h old_pix_fmts.h rational.h threadmessage.h
android//arm/include/libswresample:
swresample.h version.h
android//arm/include/libswscale:
swscale.h version.h
android//arm/lib:
libavcodec-56.so libavfilter-5.so libavformat-56.so libavutil-54.so libswresample-1.so libswscale-3.so pkgconfig
libavcodec.so libavfilter.so libavformat.so libavutil.so libswresample.so libswscale.so
android//arm/lib/pkgconfig:
libavcodec.pc libavfilter.pc libavformat.pc libavutil.pc libswresample.pc libswscale.pc
|
其中libavcodec.so、libavfilter.so、libavformat.so、libavutil.so、libswresample.so、libswscale.so都是软链,没有用,可以删掉。
8、给Ffmpeg库写Android.mk使其可用
创建$NDK_DIR/sources/ffmpeg-2.5.3/android/arm/Android.mk文件,内容如下:
要注意其中.so前面的数字应该改成你的ffmpeg版本编译出来的数字。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= lib/libavcodec-56.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= lib/libavformat-56.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= lib/libswscale-3.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= lib/libavutil-54.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= lib/libavfilter-5.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= lib/libswresample-1.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
|
至此ndk配置完毕,后面是配置Android Studio的部分。
Android Studio部分
Android Studio和Eclipse不太一样,它有一定的自动生成Android.mk并自动搞定JNI的能力。
但目前还并不足以让我们使用起来Ffmpeg库。
因此我们的思路是禁用掉Android Studio自动ndk-build的功能,手动编译我们的C代码达到目的。
首先当然要新建一个Android Studio项目。
我们使用$ROOT_DIR指代项目根目录。
1、Android Studio配置ndk路径
$ROOT_DIR/local.properties原先只配置了sdk。
1
|
sdk.dir=/usr/local/bin/android-sdk-macosx
|
给它增加一行ndk的配置
1
2
|
sdk.dir=/usr/local/bin/android-sdk-macosx
ndk.dir=/usr/local/bin/android-ndk-r10d
|
2、配置build.gradle
项目里面有两个build.gradle,一个在根目录下,一个在$ROOT_DIR/app/src下,我们要修改的是后者。
A>添加这一段以禁用自动ndk-build。
1
|
sourceSets.main.jni.srcDirs = []
|
B>添加这一段让它知道用库
1
2
3
4
5
|
ndk {
abiFilter "armeabi"
moduleName "ovsplayer"
ldLibs "log", "z", "m", "jnigraphics", "android"
}
|
修改后的build.gradle是这样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
android {
compileSdkVersion 21
buildToolsVersion "21.1.1"
sourceSets.main.jni.srcDirs = [] // 禁用自动执行ndk-build
defaultConfig {
applicationId "com.example.chengang.myapplication"
minSdkVersion 15
targetSdkVersion 21
versionCode 1
versionName "1.0"
ndk {
abiFilter "armeabi"
moduleName "ovsplayer" // 这个是C文件的名字
ldLibs "log", "z", "m", "jnigraphics", "android"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
|
3、生成头文件
执行命令,注意路径要根据自己的情况更改。
javah -d jni -classpath ..\..\build\intermediates\classes\debug com.example.nativeapp.app.MainActivity
会生成这个文件$ROOT_DIR/app/src/main/jni/com_example_chengang_myapplication_MainActivity.h
4、编写C文件
$ROOT_DIR/app/src/main/jni/ovsplayer.c内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include <stdio.h>
#include <stdlib.h>
#include "com_example_chengang_myapplication_MainActivity.h"
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
JNIEXPORT jstring JNICALL Java_com_example_chengang_myapplication_MainActivity_getStringFromNative
(JNIEnv * env , jobject obj)
{
const
char
*url =
"/mnt/sdcard/1.mp4"
;
av_register_all();
AVFormatContext *pFormatCtx = NULL;
int
ret = avformat_open_input(&pFormatCtx, url, NULL, NULL);
ret = avformat_find_stream_info(input_context, NULL);
int
streamNum = input_context->nb_streams;
char
wd[512];
sprintf
(wd,
"AVCODEC VERSION %u\n, streamNum[%d]"
, avcodec_version()
, streamNum
);
return
(*env)->NewStringUTF(env, wd);
}
|
5、编写Java文件
$ROOT_DIR/app/src/main/java/com/example/chengang/myapplication/MainActivity.java内容如下。
其中getStringFromNative()方法是我们实现的,打印了Ffmpeg库的版本号(我编译的这个是3673444)和视频文件的信息出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package
com.example.chengang.myapplication;
import
android.support.v7.app.ActionBarActivity;
import
android.os.Bundle;
import
android.view.Menu;
import
android.view.MenuItem;
import
android.widget.TextView;
public
class
MainActivity
extends
ActionBarActivity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView view = (TextView) findViewById(R.id.mytext);
view.setText(
this
.getStringFromNative());
}
@Override
public
boolean
onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return
true
;
}
@Override
public
boolean
onOptionsItemSelected(MenuItem item) {
int
id = item.getItemId();
if
(id == R.id.action_settings) {
return
true
;
}
return
super
.onOptionsItemSelected(item);
}
public
native
String getStringFromNative();
static
{
System.loadLibrary(
"swresample-1"
);
System.loadLibrary(
"avutil-54"
);
System.loadLibrary(
"avformat-56"
);
System.loadLibrary(
"avcodec-56"
);
System.loadLibrary(
"swscale-3"
);
System.loadLibrary(
"ovsplayer"
);
}
}
|
附上布局文件是这样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
android:layout_height
=
"match_parent"
android:paddingLeft
=
"@dimen/activity_horizontal_margin"
android:paddingRight
=
"@dimen/activity_horizontal_margin"
android:paddingTop
=
"@dimen/activity_vertical_margin"
android:paddingBottom
=
"@dimen/activity_vertical_margin"
tools:context
=
".MainActivity"
>
<
TextView
android:id
=
"@+id/mytext"
android:text
=
"@string/hello_world"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
/>
</
RelativeLayout
>
|
6、编写项目Android.mk
Android.mk放到$ROOT_DIR/app/build/intermediates/ndk/debug/下。
注意其中的绝对路径”/Users/chengang/Code/Android/MyApplication4″是咱们的$ROOT_DIR。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ovsplayer
LOCAL_LDLIBS := \
-lm \
-ljnigraphics \
-landroid \
-llog \
-lz \
LOCAL_SHARED_LIBRARIES := libavformat libavcodec libswscale libavutil
LOCAL_SRC_FILES := \
/Users/chengang/Code/Android/MyApplication4/app/src/main/jni/ovsplayer.c \
LOCAL_C_INCLUDES += /Users/chengang/Code/Android/MyApplication4/app/src/main/jni
LOCAL_C_INCLUDES += /Users/chengang/Code/Android/MyApplication4/app/src/debug/jni
include $(BUILD_SHARED_LIBRARY)
$(call import-module,ffmpeg-2.5.3/android/arm)
|
简单说下LOCAL_LDLIBS和LOCAL_SHARED_LIBRARIES的区别,前者链接系统库,后者链接第三方库。
并不能换着用。
7、编译C代码
在终端下执行这个命令编译C代码。
/usr/local/bin/android-ndk-r10d/ndk-build NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=/Users/chengang/Code/Android/MyApplication4/app/build/intermediates/ndk/debug/Android.mk APP_PLATFORM=android-21 NDK_OUT=/Users/chengang/Code/Android/MyApplication4/app/build/intermediates/ndk/debug/obj NDK_LIBS_OUT=/Users/chengang/Code/Android/MyApplication4/app/build/intermediates/ndk/debug/lib APP_ABI=armeabi
8、运行项目
回到Android Studio中Ctrl+R运行项目。
会看到手机上打印出Ffmpeg库的版本号(我编译的这个是3673444)和视频文件的信息出来。
Ffmpeg库已经可以调用了,然后继续写自己的逻辑就好了。
9、另外
另外说两个C代码中可能遇见的错误:
A>如果你通过标准库想得到文件大小,又写错了文件名。安卓上,在不存在的文件的句柄上执行fseek会报如下错误:”could not disable core file generation.”;
B>如果avformat_open_input()返回-1330794744了。那有两种可能,一是你忘记调用av_register_all()了,二是编译Ffmpeg的时候没有编译进对应的DEMUXER或者指定了disable-everything。