将 FFmpeg 移植到 Android
缘起
对于尚未了解 FFmpeg 的同学,可以先去看看 FFmpeg 常用基本命令,仔细研究其功能——如音视频合成、播放各类编码、视频截取、多图合成视频与音频混流、格式转换等。它能为很多应用场景带来强大且有趣的功能。
以“配音秀”类应用为例,配音不仅趣味十足,也往往能让产品更具吸引力。我们计划开发一个配音模块,进而看中了 FFmpeg,尝试将其移植到 Android 平台。前后花了 3~4 天,尝试了多个版本,网上不少文章都没能成功,直到发现一篇名为《android 使用ffmpeg 并调用接口》的教程,才最终搞定。实测表明,只有 FFmpeg 1.2 搭配 ndk-r9 的组合才能成功移植。
思路
FFmpeg 是一个用 C 语言编写的项目,其中包含一个 main()
函数。我们的目标是:
- 使用 Android NDK 编译出
libffmpeg.so
。 - 借助该库编译并修改
ffmpeg.c
文件,将原本的main()
函数改名为video_merge(int argc, char **argv)
,这样就能从 JNI 里直接调用它来完成视频合成等操作。
例如,可通过类似以下方式来实现视频合成(对应命令行 ffmpeg -i src1 -i src2 -y output
):
video_merge(5, argv); // 其中 argv 模拟命令行参数
环境
- 操作系统:Ubuntu 12.04
- FFmpeg 版本:1.2
- NDK 版本:ndk-r9
在动手之前,建议先参考一些相关教程,碰到问题再回来比照本文,避免走太多弯路。
修改接口与 Android.mk
在为 FFmpeg 编写 JNI 接口时,需要编写 Android.mk
文件来链接库,进而生成可用的 .so
文件。一些示例的 Android.mk
在不同环境下可能无法直接运行成功。其作用是告诉 NDK 哪些源文件需要编译、链接到哪个库等信息。
我采用了一种“两次编译、再链接”的方法:
- 先编译得到一个名为
myffmpeg
的共享库。 - 在另一个模块
ffmpeg-jni
中,再将myffmpeg
链接进去,最终生成所需的.so
。
另外,需要将编译好的 libffmpeg.so
放置在 jni
目录下,以保证链接时能够找到它。
调试 FFmpeg
移植 FFmpeg 之后,为了通过 JNI 调用函数,往往需要在 C 层调试。如果能像在命令行一样看到详细的日志输出,就能更轻松地定位问题。
在 Eclipse 下,按住 Ctrl 点击类似 av_log
的调用位置,可以追踪到 ffmpeg/libavutil/log.c
里 av_log_default_callback
函数的实现。它会调用 Android 的 __android_log_print
打印到 Logcat。通过查看这些输出,就能获知 FFmpeg 的内部状态,用来排查合成失败、不支持特定编码等问题。
有时,FFmpeg 会抛出异常导致 App 崩溃,可以使用以下命令来定位:
adb shell logcat | ndk-stack -sym obj/local/armeabi
如果 FFmpeg 原始的 main()
最后有一个 exit(0)
,请记得注释掉,否则会导致应用退出。
内存泄漏与 Service 方案
合成完成后,若再次调用时出现 "INVALID HEAP ADDRESS IN dlfree ffmpeg"
错误,多半是 FFmpeg 内存释放不完全导致。一个折中办法是将合成过程放在一个单独的 Service
里,合成完毕后杀掉该 Service,以清理资源。
<!-- AndroidManifest.xml -->
<service android:name=".FFmpegService" />
通过注册 Receiver
等方式,在合成完成后自行结束 Service,即可避免重复调用时的内存问题。
可能遇到的问题
- AAC 文件播放异常
有些机型(如小米 2s)可能无法通过默认的MediaPlayer
播放 AAC 编码的音频。 - 编码器支持不足
如果要支持 AMR-NB、MP3 等,需要在编译 FFmpeg 时手动启用相应的选项。如果编译脚本无法找到相关库或头文件,会报错终止。 - 合成速度
合成一个 10 秒的 1280×720 视频并混合音频,可能要花费几十秒到一分钟不等。用户体验上,或许先让用户试听再决定是否最终合成会更好。
在配音秀的具体实现中,常见的做法是:
- 提前下载好原始视频、字幕,以及“已去除需配音片段”的音频文件。
- 录制用户声音,合成时仅需将录音与静音片段合并即可。
- 若对本地合成时间不满意,可选择将音视频数据上传到服务器,由服务器端进行合成,完成后再下载。
在 Eclipse 中使用 NDK
不一定要在命令行输入 ndk-build
。只需在 Eclipse 中右键项目,选择 Android Tools → Add Native Support,之后每次点击“Run”都会自动执行 ndk-build
。
一键生成 JNI 头文件
编写 JNI 函数头文件比较繁琐,可以通过 javah
命令自动生成。
在 Eclipse 里,可以配置为一个外部工具,用类似如下命令来生成头文件:
javah -jni -classpath bin/classes -d jni com.example.ffmpeg.MyFFmpeg
执行后,会在 jni
目录下产生类似 com_example_ffmpeg_MyFFmpeg.h
的文件,然后只需在你的 C 代码中 #include
并实现对应函数即可。
小结
FFmpeg 在 Android 上的移植涉及多方知识,包括 NDK 环境配置、C/C++ 编译链接、JNI 调用、音视频编解码等。若遇到无法合成、不支持某些格式或链接报错等问题,需要仔细排查配置与日志输出。希望本文能够帮助你避开一些坑。如果你也在使用 FFmpeg,欢迎在评论区分享经验或问题,互相交流与学习。