在Android中使用FFmpeg(android studio環(huán)境)

1.首先我們需要一個已經(jīng)編譯好的libffmpeg.so文件。(怎么編譯是個大坑液南,可以參考windows環(huán)境下編譯android中使用的FFmpeg壳猜,也可以用網(wǎng)上下載的現(xiàn)成的,本文相關(guān)的github項目里也有滑凉。)统扳,當(dāng)然也要下載好ffmpeg的源碼喘帚,一會要用到。

2.打開你的Android工程,在 src/main/ 目錄下新建 jni 目錄咒钟。并將libffmpeg.so文件丟進去吹由。

3.創(chuàng)建FFmpegKit.java。寫入如下代碼

package codepig.ffmpegcldemo;

import android.os.AsyncTask;

public class FFmpegKit {
    public interface KitInterface{
        void onStart();
        void onProgress(int progress);
        void onEnd(int result);
    }

    static{
        System.loadLibrary("ffmpeg");
        System.loadLibrary("ffmpeginvoke");
    }

    public static int execute(String[] commands){
        return run(commands);
    }

    public static void execute(String[] commands, final KitInterface kitIntenrface){
        new AsyncTask<String[],Integer,Integer>(){
            @Override
            protected void onPreExecute() {
                if(kitIntenrface != null){
                    kitIntenrface.onStart();
                }
            }
            @Override
            protected Integer doInBackground(String[]... params) {
                return run(params[0]);
            }
            @Override
            protected void onProgressUpdate(Integer... values) {
                if(kitIntenrface != null){
                    kitIntenrface.onProgress(values[0]);
                }
            }
            @Override
            protected void onPostExecute(Integer integer) {
                if(kitIntenrface != null){
                    kitIntenrface.onEnd(integer);
                }
            }
        }.execute(commands);
    }

    public native static int run(String[] commands);
}

這個是用來調(diào)用ffmpeg可執(zhí)行文件的朱嘴。

4.在終端中切到src/main/java文件夾下倾鲫,輸入:

javah codepig.ffmpegcldemo.FFmpegKit

(這里注意你自己的文件的實際位置)
然后就會在該目錄生成 codepig_ffmpegecldemo_FFmpegKit.h 文件,將這個文件移動到 jni 目錄。

5.復(fù)制FFmpeg源碼文件 ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c萍嬉,cmdutils.c, cmdutils.h, cmdutils_common_opts.h 到j(luò)ni目錄下蛀序。
在 jni 目錄新建文件 Android.mk Application.mk codepig_ffmpegcldemo_FFmpegKit.c社搅。

6.編輯ffmpeg.c,把

int main(int argc, char **argv)

改名為

int run(int argc, char **argv)

編輯ffmpeg.h, 在文件末尾添加函數(shù)申明:

int run(int argc, char **argv)

7.編輯cmdutils.c中的exit_program函數(shù),刪掉函數(shù)中原來的內(nèi)容, 添加 return ret次企;并修改函數(shù)的返回類型為int昧廷。
長這樣:

int exit_program(int ret)
{
    return ret;
}

編輯cmdutils.h中exit_program的申明被碗,把返回類型修改為int奏属。
長這樣:

int exit_program(int ret);

8.在 codepig_ffmpegcldemo_FFmpegKit.c 中實現(xiàn) codepig_ffmpegcldemo_FFmpegKit.h 中的方法。

#include <stdio.h>
#include "codepig_ffmpegcldemo_FFmpegKit.h"
#include "ffmpeg.h"
#include "logjam.h"

JNIEXPORT jint JNICALL Java_codepig_ffmpegcldemo_FFmpegKit_run
(JNIEnv *env, jclass obj, jobjectArray commands){
    //FFmpeg av_log() callback
    int argc = (*env)->GetArrayLength(env, commands);
    char *argv[argc];

    LOGD("Kit argc %d\n", argc);
    int i;
    for (i = 0; i < argc; i++) {
        jstring js = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
        argv[i] = (char*) (*env)->GetStringUTFChars(env, js, 0);
        LOGD("Kit argv %s\n", argv[i]);
    }
    return run(argc, argv);
}

9.編輯Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := libffmpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeginvoke
LOCAL_SRC_FILES := codepig_ffmpegcldemo_FFmpegKit.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c
LOCAL_C_INCLUDES := F:/demo/ffmpeg-3.0
LOCAL_LDLIBS := -llog -lz -ldl
LOCAL_SHARED_LIBRARIES := ffmpeg
include $(BUILD_SHARED_LIBRARY)

其中LOCAL_C_INCLUDES的值為ffmpeg源碼文件夾地址

10.編輯Application.mk 文件

APP_ABI := armeabi-v7a
APP_PLATFORM := android-17

其中APP_ABI的值是支持的cpu類型悼做。要支持多種cpu的話焙贷,可以把類型寫一起用空格隔開,比如

APP_ABI := armeabi-v7a x86

11.在終端中定位到j(luò)ni目錄贿堰,執(zhí)行ndk -build
成功后就會在libs文件夾生成相應(yīng)的libffmpeg.so和libffmpeginvoke.so文件辙芍。這些so文件就是最終我們用來調(diào)用的FFmpeg可執(zhí)行文件。
如果出現(xiàn)如下錯誤提示

Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.

則在Application.mk文件中添加

APP_BUILD_SCRIPT := Android.mk

如果出現(xiàn)如下提示

Android NDK: Your Android application project path contains spaces: 'F:/qzd android/Android/workspace/'
Android NDK: The Android NDK build cannot work here. Please move your project to a different location.

那大約是因為項目所在文件夾名稱有空格羹与。改名就好了(感覺好弱雞)

12.在build.gradle文件中修改下庫文件地址的指向

android {
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/libs']
            jni.srcDirs=[]
        }
    }
}

現(xiàn)在終于可以在android中使用ffmpeg庫了故硅。

13.舉個栗子
(以下例子里的videoUrl是原始視頻文件地址,imageUrl是水印圖片地址纵搁,musicUrl是音頻mp3地址吃衅,outputUrl是最終輸出視頻地址。)

(1) 給視頻添加圖片水犹谟:

Runnable compoundRun=new Runnable() {
            @Override
            public void run() {
                String[] commands = new String[10];
                commands[0] = "ffmpeg";
                commands[1] = "-i";
                commands[2] = videoUrl;
                commands[3] = "-i";
                commands[4] = imageUrl;
                commands[5] = "-filter_complex";
                commands[6] = "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2";
                commands[7] = "-codec:a";
                commands[8] = "copy";
                commands[9] = outputUrl;

                FFmpegKit.execute(commands, new FFmpegKit.KitInterface() {
                    @Override
                    public void onStart() {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行開始執(zhí)行了...");
                    }

                    @Override
                    public void onProgress(int progress) {
                        Log.d("FFmpegLog LOGCAT","done com"+"FFmpeg 命令行執(zhí)行進度..."+progress);
                    }

                    @Override
                    public void onEnd(int result) {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行執(zhí)行完成...");
                    }
                });
            }
        };
        ThreadPoolUtils.execute(compoundRun);

(2) 合成音頻視頻

Runnable compoundRun=new Runnable() {
            @Override
            public void run() {
                String[] commands = new String[6];
                commands[0] = "ffmpeg";
                commands[1] = "-i";
                commands[2] = videoUrl;
                commands[3] = "-i";
                commands[4] = musicUrl;
                commands[5] = outputUrl;

                FFmpegKit.execute(commands, new FFmpegKit.KitInterface() {
                    @Override
                    public void onStart() {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行開始執(zhí)行了...");
                    }

                    @Override
                    public void onProgress(int progress) {
                        Log.d("FFmpegLog LOGCAT","done com"+"FFmpeg 命令行執(zhí)行進度..."+progress);
                    }

                    @Override
                    public void onEnd(int result) {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行執(zhí)行完成...");
//                        getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//                        Message msg = new Message();
//                        msg.what = 1;
//                        mHandler.sendMessage(msg);
                    }
                });
            }
        };
        ThreadPoolUtils.execute(compoundRun);

(3)把這兩個命令寫一起

Runnable compoundRun=new Runnable() {
            @Override
            public void run() {
                String[] commands = new String[11];
                commands[0] = "ffmpeg";
                commands[1] = "-i";
                commands[2] = videoUrl;
                commands[3] = "-i";
                commands[4] = imageUrl;
                commands[5] = "-filter_complex";
                commands[6] = "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2";
                commands[7] = "-i";
                commands[8] = musicUrl;
                commands[9] = "-y";
                commands[10] = outputUrl;

                FFmpegKit.execute(commands, new FFmpegKit.KitInterface() {
                    @Override
                    public void onStart() {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行開始執(zhí)行了...");
                    }

                    @Override
                    public void onProgress(int progress) {
                        Log.d("FFmpegLog LOGCAT","done com"+"FFmpeg 命令行執(zhí)行進度..."+progress);
                    }

                    @Override
                    public void onEnd(int result) {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行執(zhí)行完成...");
//                        getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                    }
                });
            }
        };
        ThreadPoolUtils.execute(compoundRun);

附:ffmpeg命令格式簡要說明
基本形式:

ffmpeg -i inputFile -參數(shù)名 參數(shù)值 …… outputFile;

頭尾不變徘层,中間的參數(shù)順序無所謂。但是一個操作的參數(shù)必須寫一起利职。
參數(shù)是以 -參數(shù)名+參數(shù)值 這樣的成對形式賦值趣效。


參考:
ffmpeg參數(shù)解釋
FFmpeg常用命令大全,并簡單封裝
Android最簡單的基于FFmpeg的例子(四)---以命令行的形式來使用ffmpeg
本文github項目:
ffmpegCLDemo
(誠邀各位多去點幾個star)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猪贪,一起剝皮案震驚了整個濱河市跷敬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌热押,老刑警劉巖西傀,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斤寇,死亡現(xiàn)場離奇詭異,居然都是意外死亡拥褂,警方通過查閱死者的電腦和手機娘锁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饺鹃,“玉大人致盟,你說我怎么就攤上這事∮任浚” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵雷蹂,是天一觀的道長伟端。 經(jīng)常有香客問我,道長匪煌,這世上最難降的妖魔是什么责蝠? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮萎庭,結(jié)果婚禮上霜医,老公的妹妹穿的比我還像新娘。我一直安慰自己驳规,他們只是感情好肴敛,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吗购,像睡著了一般医男。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捻勉,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天镀梭,我揣著相機與錄音,去河邊找鬼踱启。 笑死报账,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埠偿。 我是一名探鬼主播透罢,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冠蒋!你這毒婦竟也來了琐凭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤浊服,失蹤者是張志新(化名)和其女友劉穎统屈,沒想到半個月后胚吁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡愁憔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年腕扶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吨掌。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡半抱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膜宋,到底是詐尸還是另有隱情窿侈,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布秋茫,位于F島的核電站史简,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肛著。R本人自食惡果不足惜圆兵,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枢贿。 院中可真熱鬧殉农,春花似錦、人聲如沸局荚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耀态。三九已至聪建,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茫陆,已是汗流浹背金麸。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留簿盅,地道東北人挥下。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像桨醋,于是被迫代替她去往敵國和親棚瘟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容