美文网首页Android进阶FFmpegAndroid FFMPEG
在Android中使用FFmpeg(android studio

在Android中使用FFmpeg(android studio

作者: 书柜里的松鼠 | 来源:发表于2017-04-13 11:11 被阅读16327次

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)

相关文章

网友评论

  • 她来自上界:楼主你好,配置Android.mk时,“其中LOCAL_C_INCLUDES的值为ffmpeg源码文件夹地址”,中的ffmpeg源码文件夹地址指的是什么意思?在哪里获取呢?
    她来自上界:@书柜里的松鼠 好的谢谢
    书柜里的松鼠:@小古筝 源码可以在ffmpeg官网下载。这个地址是你本地存放这个源码文件的地址。
  • 750cbd165bd0:支持转amr吗? 我执行下面这条报错
    ffmpeg -i a.amr -acodec amr_wb -ac 1 -ar 16000 -ab 23850 16k-23850.amr

    08-15 11:33:03.625 21328-21345/me.v2 E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: me.v2, PID: 21328
    java.lang.RuntimeException: An error occurred while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:309)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
    Caused by: java.lang.UnsatisfiedLinkError: No implementation found for int me.v2.FFmpegKit.run(java.lang.String[]) (tried Java_me_v2_FFmpegKit_run and Java_me_v2_FFmpegKit_run___3Ljava_lang_String_2)
    at me.v2.FFmpegKit.run(Native Method)
    at me.v2.FFmpegKit$1.doInBackground(FFmpegKit.java:31)
    at me.v2.FFmpegKit$1.doInBackground(FFmpegKit.java:22)
    at android.os.AsyncTask$2.call(AsyncTask.java:295)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
  • TimFei:你好,请问能否减少添加水印的转换时间,简单测试了下,一个30秒的视频花了1分钟。
    TimFei:@书柜里的松鼠 ffmpeg -i videoUrl -i imgUrl -b:v 1500K -filter_complex overlay=main_w-overlay_w-10 : main_h-overlay_h-10 -codec:a outputUrl
    TimFei:@书柜里的松鼠 你好,请问下我想要添加其他指令的话,是否直接在 commands 数组中增加指令就行了?添加了一个指令想设置码率 "-b:v 1500K" 后,执行后直接奔溃了
    书柜里的松鼠:@TimFei 这个估计不能,他给吧整个视频重新渲染一遍,手机的性能也就这样了。
  • d1e98c55f393:博主关于执行进度 ,完成,和错误的回调。能否详细说下 。
  • 1b5238b5ffbd:编译到后面报错,INSTALL libavdevice/libavdevice.a
    /bin/sh: ranlib/usr/local/lib/libavdevice.a: No such file or directory
    make: *** [install-libavdevice-static] Error 127 请问有遇到吗 ??
  • 76bdf4554e1b:你好!想请教一下,您写的这个项目里面没有写视频编解码有关的,是吗?
    书柜里的松鼠:@76bdf4554e1b 不是。这个你可以在编译的时候自己配置。
    76bdf4554e1b:@书柜里的松鼠 那编译出来的jni文件夹下的so文件里是包含了ffmpeg所有功能的吗?
    书柜里的松鼠:@76bdf4554e1b还没
  • 76bdf4554e1b:你好,我想请问一下,我再用mingw输入./build_android.sh编译的时候,提示build_android.sh cannot execute binary file,这是怎么回事,应该怎么办呢?
    76bdf4554e1b:@书柜里的松鼠 嗯呐!是文件格式的问题。我这边文件都经过了公司的加密,没想到会因为这个编译受阻。十分感谢了
    书柜里的松鼠:看上去像是文件格式的问题,不过具体也不是很清楚。
  • b2c39779d626:记得网上的 教程 都有个 坑,那就是 第一次执行 ffmpeg 命令 没问题,再执行下,就会 闪退
    书柜里的松鼠:这个问题写在我的另一个文章里了:https://www.jianshu.com/p/ace06cfb84a8
  • b0c0a8a8937a:是直接在FFmpeg的源码下 搜索 ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c,cmdutils.c, cmdutils.h, cmdutils_common_opts.h 这几个文件 直接复制么
    书柜里的松鼠:是的。作用的话看文件名就看出大概的用处了。具体的话就说来话长了,也没有通读过。
  • b0c0a8a8937a:请问大神 ffmpeg.h, ffmpeg.c, ffmpeg_opt.c, ffmpeg_filter.c,cmdutils.c, cmdutils.h, cmdutils_common_opts.h 这几个文件都是干嘛用的 能简单的说下么
  • 谢尔顿:你好,我用你的的时候,ndk-build的时候confi.h找不到,希望大神能帮帮忙?
    cd34b24acc5e:麻烦问一下,config.h生成在哪里了?
    谢尔顿:@dom4jj 嗯嗯解决了:blush: 谢谢
    b0c0a8a8937a:自己编译源码的时候会生成confi.h文件 拷贝到jni目录下 就可以了
  • 龙_9b58:关于多路视频合成有尝试过么?我这边老是出问题
    书柜里的松鼠:@龙_9b58 如果只是视频的叠加的话用filter_complex滤镜有可以了,如果涉及到两个视频的声音也要合并在一起的话貌似会复杂点,暂时还没试过。
    龙_9b58:@书柜里的松鼠 相当于画中画一样 一个画面同时多个视频一起播放也是这个意思 有时间尝试么?841347983 可以聊聊
    书柜里的松鼠:@龙_9b58 多路视频什么意思,一个画面同时多个视频?这个还没试过。
  • 倬倬爸:想问一下,你直接把ffmpeg.c拷贝过来用,编译不会报错吗?你make install ffmpeg的时候,生成的include文件里面有libm.h和inernal.h这几个文件吗?
    倬倬爸:@书柜里的松鼠 你不是要使用ffmpeg.c里的main函数作为调用入口吗?编译ffmpeg.c需要依赖libavutil/libm.h和libavformat/inernal.h等几个头文件。我在ffmpeg 3.2和3.3分支上编译ffmpeg,make install以后,install生成的include文件夹里都没有这几个文件,只能手动搬过来。
    书柜里的松鼠:@倬倬爸 项目无关是指不相关什么具体项目,通用的。
    书柜里的松鼠:@倬倬爸 ffmpeg.c文件是项目无关的。后两个文件记不得了。回头看看。
  • 逸云天:楼主你好,三星SCH-N719上loadLibray会崩,cpu架构是armeabi-v7a,在其他手机armeabi-v7a的手机上都能正常使用。其他功能的so在三星SCH-N719上也正常。可能就是ffmpeg的so自身哪里设置的不正确,所以问问编译过程中有什么特殊注意的吗
    谢谢楼主
    书柜里的松鼠:手边没有三星这个处理器的机器,不是太清楚。我的那个编译的.sh文件后面有几个其他类型cpu的选项,你可以试试。
    逸云天:另外。我试了下用楼主分享的demo,也会出这个错
    逸云天:我是按照楼主的步骤自己编译的
  • 3a6072fa9328:大神,我最近在研究视频转gif,直接用了你的ffmpegCLDemo,可以运行,真是非常开心啊。不知道您是否知道视频转gif的命名是什么
    3a6072fa9328:找到了,ffmpeg -i test.asf -vframes 30 -y -f gif a.gif;万分感谢
  • 吧主:A very good article, Keep up.
  • 大芒果er:config.h文件找不到,有什么解决办法么,还是必须自己去编译一遍ffmpeg,求解答啊
    书柜里的松鼠: @大芒果er 这个文件是编译时生成的,具体可以看我那篇关于编译的文章
  • piao先生1920:感谢博主文章,欢迎大家加群588296572一起讨论技术
  • 逸云天:楼主有使用过ffmpeg实现音频变声的功能吗?比如男声变女声、老人声等等
    书柜里的松鼠:@逸云天 没
  • 逸云天:楼主,我直接用了你源码包和jni下的文件,ndk-build的时候报错:In file included from D:\android_demo\ffmpeg\sourceCode\ffmpeg-3.2.4\libavcodec/mathops.h:39:
    D:\android_demo\ffmpeg\sourceCode\ffmpeg-3.2.4\libavcodec/arm/mathops.h:95:26: error: unrecognized
    instruction mnemonic
    "it le \n\t"
    楼主遇到过吗?
    逸云天:@书柜里的松鼠 看错误 不支持某个命令?是环境问题吗
    逸云天:@书柜里的松鼠 jni下面的文件我改了,源码包里面的需要改吗?
    书柜里的松鼠:@逸云天 许多文件是项目相关的,目录不一样,不能直接用。要先改
  • 逸云天:楼主你好,config.h文件会在编译.so文件的时候生成是指在编辑FFmpeg的时候吗?我遇到了提示找不到config.h文件的问题,求帮助
    书柜里的松鼠: @逸云天 编译ffmpeg文件的时候
  • eec52a6bd072:合成视频特别慢,请问下大神 有没有办法提高合成效率
    书柜里的松鼠:@玛格纳斯 你是说用其他工具先压缩还是直接用ffmpeg压缩?
    玛格纳斯:@书柜里的松鼠 将视频的先压缩一下。。这个是我做到了的
    书柜里的松鼠: @我的薇恩会冲锋 这个不知道,我也很困扰。
  • 815cf78a3caa:楼主,为什么我用上面加水印的那段命令,生成的视频质量下降很多,可以调节么?
    书柜里的松鼠: @紫夜金麟_c3a5 输出我没有配置分辨率、码率等信息,加上就好了。
  • Galaxy北爱:直接用你的so可以么,移植到我的demo中,包名 需要和你的一致么?
    书柜里的松鼠: @Galaxy北爱 一般是路径问题。如果不是,光这么一说我也猜不出来的。
    Galaxy北爱:@书柜里的松鼠 编译出现'libavutil/avconfig.h 找不到 怎么回事呢
    书柜里的松鼠: @Galaxy北爱 jni目录下那个可以直接用。那个是用来编译libs目录下的那两个so的。项目无关。
    libs下那两个是要根据项目编译的,你需要重新编译。
  • MarchCd:没有提示找不到config.h文件吗?文件中并没有这个文件
    书柜里的松鼠: @MarchCd 那就不是很清楚了。没在mac环境试过。
    MarchCd:@书柜里的松鼠 我是Mac环境,之前编译过多个so的版本,现在用你的build_android.sh编译单个so,但是出现了很多问题,开始报jni找不到,后来我把--enable-jni注释掉,又有
    line 31: --enable-mediacodec: command not found
    Makefile:2: config.mak: No such file or directory
    Makefile:67: /common.mak: No such file or directory
    Makefile:114: /libavutil/Makefile: No such file or directory
    Makefile:114: /library.mak: No such file or directory
    Makefile:116: /doc/Makefile: No such file or directory
    Makefile:206: /tests/Makefile: No such file or directory
    这样的问题,请问你知道是什么原因吗。Mac , ffmpeg 331
    书柜里的松鼠: @MarchCd config.h文件会在编译.so文件的时候生成。

本文标题:在Android中使用FFmpeg(android studio

本文链接:https://www.haomeiwen.com/subject/xcfbattx.html