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

更多相关文章

  1. Android中使用SAX方式解析XML文件
  2. 布局中文件中【控件间距参数详解以及单位选择】
  3. android 模拟器手机如何添加文件到sd卡
  4. Android轻量级存储源码分析
  5. 混淆Android(安卓)JAR包的方法
  6. Android数据存储方式
  7. Android命令monkey测试
  8. android 命令行安装apk
  9. NPM 和webpack 的基础使用

随机推荐

  1. 为什么要引入ContentProvider
  2. Android PullToRefresh 分析之四、扩展Re
  3. Android程序跳过登录界面直接进入主界面(
  4. Android中的Drawable资源——Transition
  5. Android常用实例—Alert Dialog的使用
  6. Android(安卓)圆形ImageView
  7. Android UI系统控件进阶(四)—网格视图控件
  8. 在Windows系统上安装与使用Android NDK r
  9. Android系统移植(二)-按键移植
  10. android广播指定权限