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

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