编译FFmpeg+x264移植到安卓(二)

将编译后的ffmpeg移植到Android

上篇文章讲了如何 在Linux 环境中 编译FFmpeg 和 x264 并生成安卓支持的静态库 so, 而这些静态库 so 文件是不能直接依赖使用,而是要工程搭配CmakList 来使用,而本篇文章就是来介绍如何 生成 动态so,让项目直接依赖。

注意:需要将x264的so 文件也拷进来

准备

  • 在任何地方新建一个jni的文件夹。

  • 将我们刚刚编译的ffmpeg/android/armv7-a/include下的所有文件拷贝进入jni文件夹。

  • 在jni下新建一个prebuilt的文件夹,将ffmpeg/android/armv7-a/lib下的so文件全部拷贝到prebuilt之下。然后将ffmpeg/fftools文件下的如下文件拷贝进入jni

    cmdutils.c

    cmdutils.h

    config.h

    ffmpeg_filter.c

    ffmpeg_hw.c

    ffmpeg_opt.c

    ffmpeg.c

    ffmpeg.h

和源码位置同级 新建一个文件夹buildForAndroid, 然后在这个文件夹下 创建一个固定名称,jni,不能是其它名字。

修改ffmpeg文件

修改刚刚拷贝的ffmpeg.c文件,找到int main(int argc, char **argv)函数,将其替换为int run(int argc, char **argv)

在修改后的run(int argc, char **argv) 末尾(retrun 之前)加上如上如下代码:

nb_filtergraphs = 0;

progress_avio = NULL;

input_streams = NULL;

nb_input_streams = 0;

input_files = NULL;

nb_input_files = 0;

output_streams = NULL;

nb_output_streams = 0;

output_files = NULL;

nb_output_files = 0;

并在ffmpeg.h文件末尾加上

int run(int argc, char **argv);

再次打开ffmpeg.c文件,注释掉run(int argc, char **argv)下的所有exit_program函数,大致代码如下:

int run(int argc, char **argv)

{

int i, ret;

BenchmarkTimeStamps ti;

init_dynload();

register_exit(ffmpeg_cleanup);

setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */

av_log_set_flags(AV_LOG_SKIP_REPEATED);

parse_loglevel(argc, argv, options);

if(argc>1 && !strcmp(argv[1], "-d")){

run_as_daemon=1;

av_log_set_callback(log_callback_null);

argc--;

argv++;

}

#if CONFIG_AVDEVICE

avdevice_register_all();

#endif

avformat_network_init();

show_banner(argc, argv, options);

/* parse options and open all input/output files */

ret = ffmpeg_parse_options(argc, argv);

if (ret < 0);

// exit_program(1);

if (nb_output_files <= 0 && nb_input_files == 0) {

show_usage();

av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);

// exit_program(1);

}

/* file converter / grab */

if (nb_output_files <= 0) {

av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");

// exit_program(1);

}

//     if (nb_input_files == 0) {

//         av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n");

//         exit_program(1);

//     }

for (i = 0; i < nb_output_files; i++) {

if (strcmp(output_files[i]->ctx->oformat->name, "rtp"))

want_sdp = 0;

}

current_time = ti = get_benchmark_time_stamps();

if (transcode() < 0);

// exit_program(1);

if (do_benchmark) {

int64_t utime, stime, rtime;

current_time = get_benchmark_time_stamps();

utime = current_time.user_usec - ti.user_usec;

stime = current_time.sys_usec  - ti.sys_usec;

rtime = current_time.real_usec - ti.real_usec;

av_log(NULL, AV_LOG_INFO,

"bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n",

utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0);

}

av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",

decode_error_stat[0], decode_error_stat[1]);

if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1]);

// exit_program(69);

// exit_program(received_nb_signals ? 255 : main_return_code);

nb_filtergraphs = 0;

progress_avio = NULL;

input_streams = NULL;

nb_input_streams = 0;

input_files = NULL;

nb_input_files = 0;

output_streams = NULL;

nb_output_streams = 0;

output_files = NULL;

nb_output_files = 0;

return main_return_code;

}

新建ffmpeg-invoke

在jni文件下新建一个ffmpeg-invoke.cpp的c++文件,将如下代码写入

#include <jni.h>

#include <string.h>

#include "android/log.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ffmpeg-invoke", __VA_ARGS__)

extern "C"{

#include "ffmpeg.h"

#include "libavcodec/jni.h"

}

extern "C"

JNIEXPORT jint JNICALL

Java_com_coder_ffmpeg_jni_FFmpegCmd_run(JNIEnv *env, jclass type, jint cmdLen,

jobjectArray cmd) {

//set java vm

JavaVM *jvm = NULL;

env->GetJavaVM(&jvm);

av_jni_set_java_vm(jvm, NULL);

char *argCmd[cmdLen] ;

jstring buf[cmdLen];

for (int i = 0; i < cmdLen; ++i) {

buf[i] = static_cast<jstring>(env->GetObjectArrayElement(cmd, i));

char *string = const_cast<char *>(env->GetStringUTFChars(buf[i], JNI_FALSE));

argCmd[i] = string;

LOGD("argCmd=%s",argCmd[i]);

}

int retCode = run(cmdLen, argCmd);

LOGD("ffmpeg-invoke: retCode=%d",retCode);

return retCode;

}

需注意的是 Java_com_coder_ffmpeg_jni_FFmpegCmd_run 中 com_coder_ffmpeg_jni表示FFmpegCmd所在你Android项目的位置。

新建Android.mk

在jni文件夹下,新建Android.mk的文件,将如下代码拷贝,但是需要注意的是将 LOCAL_C_INCLUDES路径替换成你源码所在位置。

// 将此处的路径改为你ffmepg源码所在位置

LOCAL_C_INCLUDES := /home/anjoiner/Documents/AnJoiner/ffmpeg

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE :=  libavdevice

LOCAL_SRC_FILES := prebuilt/libavdevice.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE :=  libavutil

LOCAL_SRC_FILES := prebuilt/libavutil.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE :=  libswresample

LOCAL_SRC_FILES := prebuilt/libswresample.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE :=  libswscale

LOCAL_SRC_FILES := prebuilt/libswscale.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libavcodec

LOCAL_SRC_FILES := prebuilt/libavcodec.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libavformat

LOCAL_SRC_FILES := prebuilt/libavformat.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libavfilter

LOCAL_SRC_FILES := prebuilt/libavfilter.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := libpostproc

LOCAL_SRC_FILES := prebuilt/libpostproc.so

include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := ffmpeg-invoke

LOCAL_SRC_FILES :=ffmpeg-invoke.cpp \

cmdutils.c \

ffmpeg_filter.c \

ffmpeg_opt.c \

ffmpeg_hw.c \

ffmpeg.c

// 将此处的路径改为你ffmepg源码所在位置

LOCAL_C_INCLUDES := /home/anjoiner/Documents/AnJoiner/ffmpeg

LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib

LOCAL_SHARED_LIBRARIES := libavdevice libavcodec libavfilter libavformat libavutil libswresample libswscale libpostproc

include $(BUILD_SHARED_LIBRARY)

新建Application.mk

在jni文件夹下,新建Application.mk并将如下代码拷贝进入

APP_ABI := armeabi-v7a

APP_PLATFORM := android-21

APP_OPTIM := release

APP_STL := stlport_static

到此基本上的配置已经完成,然后在jni文件下运行ndk-build

/home/anjoiner/Documents/AnJoiner/build/jni# /home/anjoiner/Documents/AnJoiner/ffmpeg/ndk/ndk-build

当出现如下代码就表示成功了

[armeabi-v7a] SharedLibrary  : libffmpeg-invoke.so

[armeabi-v7a] Install        : libffmpeg-invoke.so => libs/armeabi-v7a/libffmpeg-invoke.so

[armeabi-v7a] Install        : libpostproc.so => libs/armeabi-v7a/libpostproc.so

[armeabi-v7a] Install        : libswresample.so => libs/armeabi-v7a/libswresample.so

[armeabi-v7a] Install        : libswscale.so => libs/armeabi-v7a/libswscale.so

测试

新建一个支持C++项目,注意包名需是上述ava_com_coder_ffmpeg_jni_FFmpegCmd_run中的com.coder.ffmpeg。

在jni的同级目录下,会生成一个libs的目录,将这个目录下的armeabi-v7a文件拷贝到你所在的Android项目中的app/src/main/jniLibs下

新建一个jni的文件目录,在此目录下新建一个FFmpegCmd

/**

* @author: AnJoiner

* @datetime: 19-7-30

*/

public class FFmpegCmd {

static {

System.loadLibrary("avdevice");

System.loadLibrary("avutil");

System.loadLibrary("avcodec");

System.loadLibrary("swresample");

System.loadLibrary("avformat");

System.loadLibrary("swscale");

System.loadLibrary("avfilter");

System.loadLibrary("postproc");

System.loadLibrary("ffmpeg-invoke");

}

private static native int run(int cmdLen, String[] cmd);

public static int runCmd(String[] cmd){

return run(cmd.length,cmd);

}

}

拷贝进来run方法名会出现红色,不用管他,

在我们的MainActivity中进行调用,此方法是将音频进行剪切

public class MainActivity extends AppCompatActivity {

// Used to load the 'native-lib' library on application startup.

static {

System.loadLibrary("native-lib");

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)

!= PackageManager.PERMISSION_GRANTED) {

ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},

100);

}

// Example of a call to a native method

TextView tv = findViewById(R.id.sample_text);

tv.setText(stringFromJNI());

findViewById(R.id.btn_test).setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

ffmpegTest();

}

});

}

/**

* A native method that is implemented by the 'native-lib' native library,

* which is packaged with this application.

*/

public native String stringFromJNI();

private void ffmpegTest() {

new Thread() {

@Override

public void run() {

long startTime = System.currentTimeMillis();

String input =

Environment.getExternalStorageDirectory().getPath() + File.separator +

"DCIM" + File.separator + "test.mp3";

String output =

Environment.getExternalStorageDirectory().getPath() + File.separator +

"DCIM" + File.separator + "output.mp3";

String cmd = "ffmpeg -y -i %s -vn -acodec copy -ss %s -t %s %s";

String result = String.format(cmd, input, "00:00:30", "00:00:40", output);

FFmpegCmd.runCmd(result.split(" "));

Log.d("FFmpegTest", "run: 耗时:" + (System.currentTimeMillis() - startTime));

}

}.start();

}

参考:https://zhuanlan.zhihu.com/p/76462890

(0)

相关推荐