一个小玩具:NDK编译FFmpeg的例子
FFmpeg NDK编译 和最简单的APK
准备
硬件:
一台电脑,实验在Lenovo T430上
一个Android设备,实验在 三星S3/A7
编译环境:
Ubuntu 14.04 (ant\java等命令必须支持)
工具包:
NDK: https://dl.google.com/android/ndk/android-ndk32-r10b-linux-x86_64.tar.bz2
SDK:https://dl.google.com/android/adt/adt-bundle-linux-x86_64-20140702.zip
Ffmpeg: http://ffmpeg.org/releases/ffmpeg-2.7.2.tar.bz2
步骤
1. 配置编译FFmpeg, 生成库文件libxxx.so和include头文件
2. 用eclipse生成一个最简单APK,load静态卡,调用native方法
3.将第1步的libxxx.so文件,由ndk-build工具重新编译
4. 引用第2步的库文件,编写自己的jni call native函数
1. 配置编译FFmpeg, 生成库文件libxxx.so和include头文件
tar -xvjf ffmpeg-2.7.2.tar.bz2
cd ffmpeg-2.7.2
gedit my-make.sh
将下面的代码拷贝到my-make.sh中
#!/bin/bash
NDK=/opt/adrd-stuff/android-ndk-r10b
SYSROOT=$NDK/platforms/android-19/arch-arm
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffserver \
--enable-cross-compile \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one
make -j8
make install
## end of my-make.sh
然后:
chmod +x my-make.sh
mkdir android/arm -p
./my-make.sh
最后就可以在android/arm下面:
~~~~~~~~~~~/android/arm$ ls
bin include lib share
2. 用eclipse生成一个最简单APK,load静态库,调用native方法
在android eclipse开发环境中,新建一个最简单的android application。
此处关节是添加文件FFmpegNative.java
└── src
└── com
└── az
└── ffmpegapp
├── FFmpegNative.java
└── MainActivity.java
package com.az.ffmpegapp;
publicclass FFmpegNative {
static{
System.loadLibrary("avutil-54");
System.loadLibrary("avcodec-56");
System.loadLibrary("swresample-1");
System.loadLibrary("avformat-56");
System.loadLibrary("swscale-3");
System.loadLibrary("avfilter-5");
System.loadLibrary("ffmpeg_codec");
}
publicstaticnativeint avcodec_find_decoder(int codecID);
}
确保在bin目录下生成了
bin/classes/com/az/ffmpegapp/FFmpegNative.class
3.将第1步的libxxx.so文件,由ndk-build工具重新编译
(1) 在FFmpegApp下面,新建目录jni
(2)jni下面新建目录ffmpeg,把第1大步生成的文件拷贝到此目录下
ls ffmpeg/
bin include lib share
(3)编写Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec-56-prebuilt
LOCAL_SRC_FILES := ffmpeg/lib/libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE :=avdevice-56-prebuilt
LOCAL_SRC_FILES :=ffmpeg/lib/libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE :=avfilter-5-prebuilt
LOCAL_SRC_FILES :=ffmpeg/lib/libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE :=avformat-56-prebuilt
LOCAL_SRC_FILES :=ffmpeg/lib/libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avutil-54-prebuilt
LOCAL_SRC_FILES :=ffmpeg/lib/libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avswresample-1-prebuilt
LOCAL_SRC_FILES :=ffmpeg/lib/libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swscale-3-prebuilt
LOCAL_SRC_FILES :=ffmpeg/lib/libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)
#end of Andoid.mk
(4)运行ndk-build
此时生成了文件在../libs/armeabi下面:
tree ../libs/
../libs/
├── android-support-v4.jar
├── android-support-v7-appcompat.jar
└── armeabi
├── libavcodec-56.so
├── libavdevice-56.so
├── libavfilter-5.so
├── libavformat-56.so
├── libavutil-54.so
├── libswresample-1.so
└── libswscale-3.so
4. 引用第2步的库文件,编写自己的jni call native函数
(1)注意到第2大步里面生成的bin/classes/com/az/ffmpegapp/FFmpegNative.class
cd bin
javah -classpath classes/ com.az.ffmpegapp.FFmpegNative
生成文件:com_az_ffmpegapp_FFmpegNative.h
(2) 拷贝到jni下,实现这个头文件里面的方法,更换一个名字native_avcodec_find_decoder(文字最后附录一个完整c文件FFmpegNative.c)
#include <libavcodec/avcodec.h>
JNIEXPORT jint JNICALL native_avcodec_find_decoder(JNIEnv * e, jclass jc, jint codecID)
{
LOGI("%s called\n",__func__);
AVCodec *codec = NULL;
/*register all formats and codecs */
av_register_all();
codec= avcodec_find_decoder(codecID);
if(codec != NULL) {
return 0;
} else {
return -1;
}
}
(3)编辑Android.mk,在前面的基础上,最后面添加:
include $(CLEAR_VARS)
LOCAL_MODULE :=ffmpeg_codec
LOCAL_SRC_FILES :=FFmpegNative.c
LOCAL_LDLIBS := -llog #-ljnigraphics -lz -landroid
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg/include
LOCAL_SHARED_LIBRARIES:= \
avcodec-56-prebuilt \
avdevice-56-prebuilt \
avfilter-5-prebuilt \
avformat-56-prebuilt \
avutil-54-prebuilt
include $(BUILD_SHARED_LIBRARY)
(4)ndk-build
tree ../libs/
../libs/
├── android-support-v4.jar
├── android-support-v7-appcompat.jar
└── armeabi
├── libavcodec-56.so
├── libavdevice-56.so
├── libavfilter-5.so
├── libavformat-56.so
├── libavutil-54.so
├── libffmpeg_codec.so
├── libswresample-1.so
└── libswscale-3.so
libffmpeg_codec.so 就是新生成的库。
(5)Eclipse重新编译生成APK
由于在MainActivity.java有下面的打印语句:
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (FFmpegNative.avcodec_find_decoder(28)==0)
Log.d("MainActivity","Find decoder 28");
else
Log.d("MainActivity","Not Find");
}
JNI native里面有调试语句:
JNIEXPORT jint JNICALL native_avcodec_find_decoder(JNIEnv * e, jclass jc, jint codecID)
{
#if
LOGI("%s called\n",__func__);
在logcat可以有下面的打印:
D/MainActivity(12786): Find decoder 28
I/SubCommand(12786): native_avcodec_find_decoder called
注意:
NDK版本:
32bits小机用:android-ndk32-r10b-linux-x86_64.tar.bz2
64bits小机用:android-ndk64-r10b-linux-x86_64.tar.bz2
64bits编译出来的APK在S3上面运行,会有闪退的现象,
E/dalvikvm(31393): dlopen("/data/app-lib/com.az.ffmpegapp-1/libavformat-56.so") failed: dlopen failed: cannot locate symbol "atof" referenced by "libavformat-56.so"...
W/dalvikvm(31393): Exception Ljava/lang/UnsatisfiedLinkError; thrown while initializing Lcom/az/ffmpegapp/FFmpegNative;
E/AndroidRuntime(31393): Process: com.az.ffmpegapp, PID: 31393
E/AndroidRuntime(31393): at com.az.ffmpegapp.FFmpegNative.<clinit>(FFmpegNative.java:7)
E/AndroidRuntime(31393): at com.az.ffmpegapp.MainActivity.onCreate(MainActivity.java:15)
W/ActivityManager( 834): Force finishing activity com.az.ffmpegapp/.MainActivity
附录文件:
FFmpegNative.c
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include <android/log.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <assert.h> /* Header for class com_az_ffmpegapk_FFmpegNative */ #ifndef _Included_com_az_ffmpegapk_FFmpegNative #define _Included_com_az_ffmpegapk_FFmpegNative #define LOG_TAG "SubCommand" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOG_DEBUG(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #ifdef __cplusplus extern "C" { #endif /* * Class: com_az_ffmpegapk_FFmpegNative * Method: avcodec_find_decoder * Signature: (I)I */ #include <libavcodec/avcodec.h> JNIEXPORT jint JNICALL native_avcodec_find_decoder(JNIEnv * e, jclass jc, jint codecID) { #if 1 LOGI("%s called\n",__func__); AVCodec *codec = NULL; /*register all formats and codecs */ av_register_all(); codec= avcodec_find_decoder(codecID); if(codec != NULL) { return 0; } else { return -1; } #endif } #define JNIREG_CLASS "com/az/ffmpegapp/FFmpegNative" //class name to be registered /** * Table of methods associated with a single class. */ static JNINativeMethod gMethods[] = { { "avcodec_find_decoder", "(I)I", (void*)native_avcodec_find_decoder } }; /* * Register several native methods for one class. */ static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } /* * Register native methods for all classes we know about. */ static int registerNatives(JNIEnv* env) { if (!registerNativeMethods(env, JNIREG_CLASS, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))) return JNI_FALSE; return JNI_TRUE; } /* * Set some test stuff up. * * Returns the JNI version on success, -1 on failure. */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; LOGI("--------------------------------------------------------------------\n"); LOGI("OnLoad: %s:%s\n", __DATE__, __TIME__); LOGI("--------------------------------------------------------------------\n"); if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) { return -1; } /* success -- return valid version number */ result = JNI_VERSION_1_6; return result; } #ifdef __cplusplus } #endif #endif
jni/Android.mk
jni$ cat Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := avcodec-56-prebuilt LOCAL_SRC_FILES := ffmpeg/lib/libavcodec-56.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=avdevice-56-prebuilt LOCAL_SRC_FILES :=ffmpeg/lib/libavdevice-56.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=avfilter-5-prebuilt LOCAL_SRC_FILES :=ffmpeg/lib/libavfilter-5.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=avformat-56-prebuilt LOCAL_SRC_FILES :=ffmpeg/lib/libavformat-56.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avutil-54-prebuilt LOCAL_SRC_FILES :=ffmpeg/lib/libavutil-54.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := avswresample-1-prebuilt LOCAL_SRC_FILES :=ffmpeg/lib/libswresample-1.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := swscale-3-prebuilt LOCAL_SRC_FILES :=ffmpeg/lib/libswscale-3.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE :=ffmpeg_codec LOCAL_SRC_FILES :=FFmpegNative.c LOCAL_LDLIBS := -llog #-ljnigraphics -lz -landroid LOCAL_C_INCLUDES += $(LOCAL_PATH)/ffmpeg/include LOCAL_SHARED_LIBRARIES:= \ avcodec-56-prebuilt \ avdevice-56-prebuilt \ avfilter-5-prebuilt \ avformat-56-prebuilt \ avutil-54-prebuilt include $(BUILD_SHARED_LIBRARY)
src/com/az/ffmpegapp/FFmpegNative.java
package com.az.ffmpegapp; public class FFmpegNative { static{ System.loadLibrary("avutil-54"); System.loadLibrary("avcodec-56"); System.loadLibrary("swresample-1"); System.loadLibrary("avformat-56"); System.loadLibrary("swscale-3"); System.loadLibrary("avfilter-5"); System.loadLibrary("ffmpeg_codec"); } public static native int avcodec_find_decoder(int codecID); }
src/com/az/ffmpegapp/MainActivity.java
package com.az.ffmpegapp; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (FFmpegNative.avcodec_find_decoder(28)==0) Log.d("MainActivity","Find decoder 28"); else Log.d("MainActivity","Not Find"); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }