type
status
date
slug
summary
tags
category
icon

一、下载源码

创建一个叫 FFmpegAndroid 的目录,下载 libx264源码ffmpeg源码,然后在 FFmpegAndroid 文件夹下建立一个 bulid 文件夹,用于存放编译脚本和输出

二、编译 FFmpeg

编译 x264 编码器

先在 build 文件夹下建立 setting.sh, 用于申明一些公用的环境变量,比如 $NDK$CPU...
然后建立 libx264 的编译脚本 build_x264.shlibx264 是一个开源的H.264编码器,据说是最好的视频有损编码器。ffmpeg 默认不自带,但是支持 x264 作为第三方编码器编译。
build_x264.sh
./config 内的# 注释必须在运行的时候去掉
写完之后就可以编译 x264 库了,编译之前还有一点要注意的是,默认编译出来的文件后缀并不是 *.so,这 Android 是识别不了的,需要对 x264 源码里面的 config 做如下修改:
修改成
别忘了给 build_x264.sh 和 setting.sh 赋予可执行权限 (chmod +x build_x264.sh setting.sh)
修改完后就可以执行脚本命令了
等待一段时间后,build 文件夹目录下应该有个 lib 目录(build 脚本里面 prefix 指定的目录),里面存放了 x264 的静态库
这里为什么编译成静态库而不是动态库呢?静态库可以把内容编译到待会儿要编译 ffmpeg 的so库里去,不需要单独加载 libx264.so 了,如果你硬要编译成动态库也可以,加载 ffmpeg.so 的时候加载 libx264.so 就可以
至此,x264编码器编译完毕

编译 FFmpeg

同样在 build 文件夹下建立编译脚本 build_ffmpeg.sh,编译 ffmpeg 比编译 x264 略微麻烦点,首先肯定不能全功能编译,那还不如直接去网上找一个编译好的,要自己定制哪些组件需要,哪些组件不需要
FFmpeg它主要含有以下几个核心库:
  • libavcodec-提供了更加全面的编解码实现的合集
  • libavformat-提供了更加全面的音视频容器格式的封装和解析以及所支持的协议
  • libavutil-提供了一些公共函数
  • libavfilter-提供音视频的过滤器,如视频加水印、音频变声等
  • libavdevice-提供支持众多设备数据的输入与输出,如读取摄像头数据、屏幕录制
  • libswresample,libavresample-提供音频的重采样工具
  • libswscale-提供对视频图像进行色彩转换、缩放以及像素格式转换,如图像的YUV转换
  • libpostproc-多媒体后处理器
如果不修改什么配置,直接编译的话,我发现 libavcodec.so 有 7.8MB,我可以在这方面下手,指定 decoderencoder,因为我需要的是视频压缩,所以编码器(encoder)我就只需要 x264(视频编码) 和 aac(音频编码),至于解码器,挑几个常用的就可以了
查看编码器和解码器种类,可以通过 ./config --list-decoders 或 ./config --list-encoers 命令实现(ffmpeg目录下) ./config 内的# 注释必须在运行的时候去掉
这次编译不用静态库的原因是,静态库链接是有顺序要求的,这里模块太多,我也不知道哪个模块依赖哪个模块,所以直接上动态库
脚本写完后,就可以 run 了,编译时间有点久,可以学学我的某个同学,一编译就起来泡泡妹子,有说有笑。
编译完成后你的目录应该是下面那个样子:
后面的版本号不一样没关系,这由 ffmpeg 版本决定的
库编译完了,这些 so 库就是在 Android 可用的动态库,接下来就可以准备 JNI 编程了

三、在 Android 里使用 FFmpeg

前面已经把 FFmpeg 各个核心库编译出来了,但是我肯定不会在里面直接用核心库内的函数来用,ffmpeg 本来是一个在 pc 端的命令,命令里面可以填写各种参数,比如 ffmpeg -i a.mp4 -c:v x264 -c:a aac b.mp4,就是把 a.mp4 用 x264(视频)、aac(音频) 编码成 b.mp4
ffmpeg 是由 ffmpeg.c 编译出来的,想要在 Android 里面用 ffmpeg 命令,只要修改 ffmpeg.c 里面的 main 函数,比如修改成 int run_ffmpeg_command(int args, char **argv),然后用 JNI 暴露给 java 调用,就可以在 Android 使用 ffmpeg 命令了
在 FFmpegAndroid 建立一个 Android 工程,然后新建一个 ffmpeg 的 lib module 对于 NDK 开发,AndroidStudio 2.2 以后就有较好的支持,直接修改支持库的 build.gradle 文件
这样 lib module 就支持 c++ 了,方便吧!比以前的 Android.mk 不知道方便多少
然后在模块的 src/main 下面新建一个 cpp 目录,用于存放 c++ 代码,从ffmpeg拷贝以下文件:
然后在 CMakeList.txt 里面配置这些文件,好让 AndroidStudio 认识它们
刷新下 gradle,就可以写 c++ 代码了。先看下 ffmpeg.c 这个文件,原先的指令其实调用的就是 main 函数,我们先把 main 函数改成自己自定义的函数 run_ffmpeg_command:
改了以后,我们就可以调用 run_ffmpeg_command 然后传入参数,相当于在 pc 执行 ffmpeg 命令。不过现在还不能执行,这是个坑点,仔细看 run_ffmpeg_command 函数,在程序结束的时候,或者中途出现错误的时候,都会调用 exit_program(int),这个函数:
exit_program(int) 函数是什么,跳过去看一下发现里面就是清理资源然后 exit(int),这里就要注意这个 exit 函数了,除非我们是多进程方式调用 run_ffmpeg_command,如果我们在 app 的进程调用,执行了 exit 就会结束 app 的进程!
这不是我想看到的,最好的方法是另开一个进程调用,但是这样就涉及到了进程间的通信问题,麻烦,不想写!反正只是跑一个压缩指令嘛,直接改 ffmpeg.c,首先把 exit(int) 函数给注释掉,然后返回一个 code,run_ffmpeg_command 函数里面只要涉及到 exit_program(int) 函数调用的地方都写成 return exit_program(int),不过要注意,有如下几个坑点:

修改 ffmpeg.c 坑点一

调试的时候发现 return exit_program(int); 语句并不会结束当前函数并返回,而是继续往下执行了,当时一脸楞逼,我艹!!这是什么鬼??为什么我 return 了没有用?找了半天后才发现是 exit_program(int) 这个函数声明的锅!看下面这个函数的声明:
函数后面有个奇怪的 av_noreturn 声明,网上查了一下才知道,这个是给编译器的注解,这货的锅,去掉就好了。

修改 ffmpeg.c 坑点二

其实 exit_program(int) 这个函数不只是在 run_ffmpeg_command 里面调用,其它各种函数里面都有,如果都要修改的话必须一层一层的 return (C语言里面没有异常啊),很麻烦,但是如果没有改好的话就很容易 crash,这是个要解决的问题,首先 run_ffmpeg_command 里面的 exit_program 都要改成 return 方式
然后因为最终目的是压缩视频,参数集是固定的,所以不用考虑编码不支持,或参数匹配不到的情况,只需要考虑文件读写的问题,就是输入文件不存在的时候,或者输出路径不合法的时候,不能让程序异常退出,而是返回错误码,这个需要改 ffmpeg_opt.c 这个文件 ffmpeg_opt.c
目前我项目中就只改了这几个函数内的 exit_program,测试可行,也可以参考本项目的代码,链接在文末
最后就是暴露 run_ffmpeg_command 方法给 java 调用了,这个和普通的 JNI 编程一样,建一个 native 的方法,创建 cpp 代码。。。没啥东西,直接上代码
FFmpegNativeBridge
ffmpeg-lib.c
运行前先需要把 ffmpeg 编译出来的一堆 so 库放到 jniLibs 内,不然运行的时候会出现动态库无法加载的异常。最后就可以在 Android 内用 ffmpeg 的命令了:
关于这些参数,可以去查 FFmpeg 的官网,本项目源码地址Github
探索 Android RippleDrawable作为背景是如何绘制到View外的如何优雅地在Android上实现iOS的图片预览
Loading...