在Android中使用FFmpeg(android studio环境)

1.首先我们需要一个已经编译好的libffmpeg.so文件。(怎么编译是个大坑,可以参考windows环境下编译android中使用的FFmpeg,也可以用网上下载的现成的,本文相关的github项目里也有。),当然也要下载好ffmpeg的源码,一会要用到。

2.打开你的Android工程,在 src/main/ 目录下新建 jni 目录。并将libffmpeg.so文件丢进去。

3.创建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);
}

这个是用来调用ffmpeg可执行文件的。

4.在终端中切到src/main/java文件夹下,输入:

javah codepig.ffmpegcldemo.FFmpegKit

(这里注意你自己的文件的实际位置)
然后就会在该目录生成 codepig_ffmpegecldemo_FFmpegKit.h 文件,将这个文件移动到 jni 目录。

5.复制FFmpeg源码文件 ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c,cmdutils.c, cmdutils.h, cmdutils_common_opts.h 到jni目录下。
在 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, 在文件末尾添加函数申明:

int run(int argc, char **argv)

7.编辑cmdutils.c中的exit_program函数,删掉函数中原来的内容, 添加 return ret;并修改函数的返回类型为int。
长这样:

int exit_program(int ret)
{
    return ret;
}

编辑cmdutils.h中exit_program的申明,把返回类型修改为int。
长这样:

int exit_program(int ret);

8.在 codepig_ffmpegcldemo_FFmpegKit.c 中实现 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.在终端中定位到jni目录,执行ndk -build
成功后就会在libs文件夹生成相应的libffmpeg.so和libffmpeginvoke.so文件。这些so文件就是最终我们用来调用的FFmpeg可执行文件。
如果出现如下错误提示

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

如果出现如下提示

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=[]
        }
    }
}

现在终于可以在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 命令行开始执行了...");
                    }

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

                    @Override
                    public void onEnd(int result) {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行执行完成...");
                    }
                });
            }
        };
        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 命令行开始执行了...");
                    }

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

                    @Override
                    public void onEnd(int result) {
                        Log.d("FFmpegLog LOGCAT","FFmpeg 命令行执行完成...");
//                        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 命令行开始执行了...");
                    }

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

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

附:ffmpeg命令格式简要说明
基本形式:

ffmpeg -i inputFile -参数名 参数值 …… outputFile;

头尾不变,中间的参数顺序无所谓。但是一个操作的参数必须写一起。
参数是以 -参数名+参数值 这样的成对形式赋值。


参考:
ffmpeg参数解释
FFmpeg常用命令大全,并简单封装
Android最简单的基于FFmpeg的例子(四)---以命令行的形式来使用ffmpeg
本文github项目:
ffmpegCLDemo
(诚邀各位多去点几个star)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容