美文网首页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官网下载。这个地址是你本地存放这个源码文件的地址。
      • 方不同_6c42:支持转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:@书柜里的松鼠 嗯呐!是文件格式的问题。我这边文件都经过了公司的加密,没想到会因为这个编译受阻。十分感谢了
        书柜里的松鼠:看上去像是文件格式的问题,不过具体也不是很清楚。
      • 桥下红药:记得网上的 教程 都有个 坑,那就是 第一次执行 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