Android(安卓)Java调用ffmpeg命令
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_LIBint run(int argc, char** argv);#endif
(2)ffmpeg.c
修改ffmpeg.c
把
int main(int argc, char** argv)
替换为
#ifdef FFMPEG_RUN_LIBint run(int argc, char **argv)#elseint 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 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_Hint 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 #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 __cplusplusextern "C" {#endif/* * Class: org_wi_androidffmpeg_FFmpegCmd * Method: run * Signature: ([Ljava/lang/String;)I */JNIEXPORT jintJNICALL 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 __cplusplusextern "C" {#endifJNIEXPORT jintJNICALL 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=../ffmpegLOCAL_C_INCLUDES := $(FFMPEG_ROOT) \LOCAL_MODULE := ffmpeg_cmdLOCAL_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.cLOCAL_LDLIBS := -llog -lz -ldlLOCAL_SHARED_LIBRARIES := libffmpegLOCAL_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 cmds = new ArrayList(); 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. 项目源码
AndroidFFmpeg
更多相关文章
- Android中使用SAX方式解析XML文件
- 布局中文件中【控件间距参数详解以及单位选择】
- android 模拟器手机如何添加文件到sd卡
- Android轻量级存储源码分析
- 混淆Android(安卓)JAR包的方法
- Android数据存储方式
- Android命令monkey测试
- android 命令行安装apk
- NPM 和webpack 的基础使用