Android Java调用ffmpeg命令
2017-03-17 16:04
323 查看
0. 前言
ffmpeg命令很强大,但是在Android工程中无法执行可执行文件ffmpeg,即无法使用ffmpeg。本文介绍把ffmpeg改造成库文件,然后通过JNI调用它,即可实现在Java中使用ffmpeg命令。
PS:
本工程依赖于前文Android 编译FFmpeg x264。
1. ffmpeg
1.1 main to run
(1)ffmpeg.h进入ffmpeg源代码,修改ffmpeg.h,在文件中添加一下代码:
#ifdef FFMPEG_RUN_LIB int run(int argc, char** argv); #endif1
2
3
1
2
3
(2)ffmpeg.c
修改ffmpeg.c
把
int main(int argc, char** argv)1
1
替换为
#ifdef FFMPEG_RUN_LIB int run(int argc, char **argv) #else int main(int argc, char** argv) #endif1
2
3
4
5
1
2
3
4
5
1.2 ffmpeg cleanup
ffmpeg 在清理阶段虽然把各个变量释放掉了,但是并没有将其置为null,会出现问题。 修改ffmpeg_cleanup函数,具体修改方法就是当调用av_freep函数后,在把变量设置为NULL。部分代码如下:
2. 修复直接退出进程
如果只完成上面修改的话,在执行ffmpeg命令会直接结束,因为原始工程作为一个进程,运行结束会进行垃圾回收,以及结束进程。解决方法:
找到cmdutils文件,用longjmp替换exit方法
具体操作如下:
(1)修改cmdutils.c
在文件include代码块底部添加一下代码:
#ifdef FFMPEG_RUN_LIB #include <setjmp.h> extern jmp_buf jmp_exit; #endif1
2
3
4
1
2
3
4
找到并修改exit_program函数:
void exit_program(int ret) { if (program_exit) program_exit(ret); #ifdef FFMPEG_RUN_LIB av_log(NULL, AV_LOG_INFO, "exit_program code : %d\n", ret); longjmp(jmp_exit, ret); #else exit(ret); #endif }1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
(2)修改ffmpeg.c
把
exit()1
1
函数替换为
exit_program()1
1
(3) 添加ffmpeg_cmd文件
ffmpeg_cmd.h
// // Created by Taylor Guo on 16/6/24. // #ifndef FFMPEG_BUILD_LIB_FFMPEG_MAIN_H #define FFMPEG_BUILD_LIB_FFMPEG_MAIN_H int run_cmd(int argc, char** argv); #endif //FFMPEG_BUILD_LIB_FFMPEG_MAIN_H1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
ffmpeg_cmd.c
// // Created by Taylor Guo on 16/6/24. // #include <setjmp.h> #include "ffmpeg_cmd.h" #include "ffmpeg.h" #include "cmdutils.h" jmp_buf jmp_exit; int run_cmd(int argc, char** argv) { int res = 0; if(res = setjmp(jmp_exit)) { return res; } res = run(argc, argv); return res; }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
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
3. JNI接口
3.1 JAVA
(1)加载库(2)函数接口
public class FFmpegCmd { public static final String TAG = "FFmpegUtils"; public static final int R_SUCCESS = 0; public static final int R_FAILED = -1; private static final String STR_DEBUG_PARAM = "-d"; public static boolean mEnableDebug = false; static { System.loadLibrary("ffmpeg"); System.loadLibrary("ffmpeg_cmd"); } public native static int run(String[] cmd); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
3.2 Native
(1)ffmpeg_cmd_wrapper.h// // Created by Taylor Guo on 16/6/24. // #ifndef FFMPEG_BUILD_LIB_FFMPEG_MAIN_H #define FFMPEG_BUILD_LIB_FFMPEG_MAIN_H #include"jni.h" #ifdef __cplusplus extern "C" { #endif /* * Class: org_wi_androidffmpeg_FFmpegCmd * Method: run * Signature: ([Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_org_wi_androidffmpeg_FFmpegCmd_run (JNIEnv *env, jclass obj, jobjectArray commands); #ifdef __cplusplus } #endif #endif //FFMPEG_BUILD_LIB_FFMPEG_MAIN_H1
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
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
(2)ffmpeg_cmd_wrapper.c
// // Created by Taylor Guo on 16/6/24. // #include "ffmpeg_cmd.h" #include "ffmpeg_cmd_wrapper.h" #include "jni.h" #ifdef __cplusplus extern "C" { #endif JNIEXPORT jint JNICALL Java_org_wi_androidffmpeg_FFmpegCmd_run (JNIEnv *env, jclass obj, jobjectArray commands) { int argc = (*env)->GetArrayLength(env, commands); char *argv[argc]; jstring jstr[argc]; int i = 0;; for (i = 0; i < argc; i++) { jstr[i] = (jstring)(*env)->GetObjectArrayElement(env, commands, i); argv[i] = (char *) (*env)->GetStringUTFChars(env, jstr[i], 0); //CGE_LOG_INFO("argv[%d] : %s", i, argv[i]); } int status = run_cmd(argc, argv); for (i = 0; i < argc; ++i) { (*env)->ReleaseStringUTFChars(env, jstr[i], argv[i]); } return status; } #ifdef __cplusplus } #endif1
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
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
5. 编译
修改Android.mk文件,在文件末尾添加:############################### #libffmpeg_main ############################### include $(CLEAR_VARS) FFMPEG_ROOT=../ffmpeg LOCAL_C_INCLUDES := $(FFMPEG_ROOT) \ LOCAL_MODULE := ffmpeg_cmd LOCAL_SRC_FILES := \ ffmpeg_cmd.c \ ffmpeg_cmd_wrapper.c \ $(FFMPEG_ROOT)/cmdutils.c \ $(FFMPEG_ROOT)/ffmpeg.c \ $(FFMPEG_ROOT)/ffmpeg_opt.c \ $(FFMPEG_ROOT)/ffmpeg_filter.c LOCAL_LDLIBS := -llog -lz -ldl LOCAL_SHARED_LIBRARIES := libffmpeg LOCAL_CFLAGS := -march=armv7-a -mfloat-abi=softfp -mfpu=neon -O3 -ffast-math -funroll-loops -DFFMPEG_RUN_LIB -DLOG_TAG=\"FFMPEG\" include $(BUILD_SHARED_LIBRARY)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
6. ffmpeg命令测试代码
(1)这里写一个简单的程序用于测试ffmpeg命令,具体功能是:通过调用ffmpeg命令实现音视频混合,ffmpeg对应命令为:
ffmpeg -i test.mp4 -i test.mp3 -vcodec copy -acodec aac -map 0:v:0 -map 1:a:0 -shortest mix_test.mp41
1
程序如下:
/** * Muxing video stream and audio stream. * This interface is quite complex which is only for adding audio effect. * * @param srcVideoName Input video file name. * @param fvVolume Input video volume, should not be negative, default is 1.0f. * @param srcAudioName Input audio file name. * @param faVolume Input audio volume, should not be negative, default is 1.0f. * @param desVideoName Output video file name. * @param callback Completion callback. * * @return Negative : Failed * else : Success. */ public static int mixAV(final String srcVideoName, final float fvVolume, final String srcAudioName, final float faVolume, final String desVideoName, final OnCompletionListener callback) { if (srcAudioName == null || srcAudioName.length() <= 0 || srcVideoName == null || srcVideoName.length() <= 0 || desVideoName == null || desVideoName.length() <= 0) { return R_FAILED; } Runnable runnable = new Runnable() { @Override public void run() { ArrayList<String> cmds = new ArrayList<String>(); cmds.add("ffmpeg"); cmds.add("-i"); cmds.add(srcVideoName); cmds.add("-i"); cmds.add(srcAudioName); //Copy Video Stream cmds.add("-c:v"); cmds.add("copy"); cmds.add("-map"); cmds.add("0:v:0"); //Deal With Audio Stream cmds.add("-strict"); cmds.add("-2"); if (fvVolume <= 0.001f) { //Replace audio stream cmds.add("-c:a"); cmds.add("aac"); cmds.add("-map"); cmds.add("1:a:0"); cmds.add("-shortest"); if (faVolume < 0.99 || faVolume > 1.01) { cmds.add("-vol"); cmds.add(String.valueOf((int) (faVolume * 100))); } } else if (fvVolume > 0.001f && faVolume > 0.001f){ //Merge audio streams cmds.add("-filter_complex"); cmds.add(String.format("[0:a]aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo,volume=%f[a0]; " + "[1:a]aformat=sample_fmts=fltp:sample_rates=48000:channel_layouts=stereo,volume=%f[a1];" + "[a0][a1]amix=inputs=2:duration=first[aout]", fvVolume, faVolume)); cmds.add("-map"); cmds.add("[aout]"); } else { Log.w(TAG, String.format(Locale.getDefault(), "Illigal volume : SrcVideo = %.2f, SrcAudio = %.2f",fvVolume, faVolume)); if (callback != null) { callback.onCompletion(R_FAILED); } return; } cmds.add("-f"); cmds.add("mp4"); cmds.add("-y"); cmds.add("-movflags"); cmds.add("faststart"); cmds.add(desVideoName); if (mEnableDebug) { cmds.add(STR_DEBUG_PARAM); } String[] commands = cmds.toArray(new String[cmds.size()]); int result = FFmpegCmd.run(commands); if (callback != null) { callback.onCompletion(result); } } }; new Thread(runnable).start(); return R_SUCCESS; } public interface OnCompletionListener { void onCompletion(int result); }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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
(2)Activity:
package org.wi.androidffmpeg; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import java.io.File; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void run(View view) { Log.d("MainActivity", "MIX AV..."); FFmpegCmd.setEnableDebug(true); String folder = Environment.getExternalStorageDirectory().getPath(); if (folder == null || folder.length() <=0) { return; } folder += "/libCGE"; FFmpegCmd.mixAV(folder + "/MediaResource/test.mp4", 1.0f, folder + "/MediaResource/test.mp3", 0.7f, folder + "/new_mix.mp4", new FFmpegCmd.OnCompletionListener() { @Override public void onCompletion(int result) { Log.d("MainActivity", "MIX AV Finish : " + result); } }); } }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
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
(3)运行结果:
7. 项目源码
AndroidFFmpeg相关文章推荐
- Android Java调用ffmpeg命令
- Android Java 调用linux 命令
- Android开发:java程序调用linux命令(高级)
- [Android] 解析android framework下利用app_process来调用java写的命令及示例
- Android 集成 FFmpeg (二) 以命令方式调用 FFmpeg
- android java获得root权限调用linux命令
- android java获得root权限调用linux命令
- 搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)
- 02_JNI中Java代码调用C代码,Android中使用log库打印日志,javah命令的使用,Android.mk文件的编写,交叉编译
- Android 集成 FFmpeg (二) 以命令方式调用 FFmpeg
- Android开发:java程序调用linux命令(高级)
- 执行文件下载Java 调用 FFMPEG 命令时用 url 作为输入源,Linux 下出现 “no such file or directory” 问题的解决
- java调用android aapt反编译命令获取应用程序包名和应用名
- Java 调用FFMPEG命令进行视频格式转换 (windows环境)
- Java调用linux命令ffmpeg视频转码
- android java代码调用linux命令续
- Java 调用 FFMPEG 命令时用 url 作为输入源,Linux 下出现 “no such file or directory” 问题的解决
- android java程序中调用shell命令
- java中调用adb shell 命令启动android应用程序
- android java代码调用linux命令续