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_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. 项目源码

AndroidFFmpeg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章