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
(2)ffmpeg.c
修改ffmpeg.c
把
int main(int argc, char** argv)
替换为
#ifdef FFMPEG_RUN_LIB
int run(int argc, char **argv)
#else
int main(int argc, char** argv)
#endif
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
找到并修改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
}
(2)修改ffmpeg.c
把
exit()
函数替换为
exit_program()
(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
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;
}
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);
}
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
(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
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)
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
程序如下:
/**
* 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);
}
(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);
}
});
}
}
(3)运行结果:
7. 项目源码
作者:xiyanlgu 发表于2017/2/23 17:45:19 原文链接
阅读:40 评论:0 查看评论