您的位置:首页 > 移动开发 > Android开发

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);
#endif
1
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)
#endif
1
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;
#endif
1
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_H
1
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_H
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
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
}
#endif
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
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.mp4
1
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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: