将 FFmpeg 移植到 Android

Home PDF

原文链接(CSDN)


缘起

对于尚未了解 FFmpeg 的同学,可以先去看看 FFmpeg 常用基本命令,仔细研究其功能——如音视频合成、播放各类编码、视频截取、多图合成视频与音频混流、格式转换等。它能为很多应用场景带来强大且有趣的功能。

以“配音秀”类应用为例,配音不仅趣味十足,也往往能让产品更具吸引力。我们计划开发一个配音模块,进而看中了 FFmpeg,尝试将其移植到 Android 平台。前后花了 3~4 天,尝试了多个版本,网上不少文章都没能成功,直到发现一篇名为《android 使用ffmpeg 并调用接口》的教程,才最终搞定。实测表明,只有 FFmpeg 1.2 搭配 ndk-r9 的组合才能成功移植。


思路

FFmpeg 是一个用 C 语言编写的项目,其中包含一个 main() 函数。我们的目标是:

  1. 使用 Android NDK 编译出 libffmpeg.so
  2. 借助该库编译并修改 ffmpeg.c 文件,将原本的 main() 函数改名为 video_merge(int argc, char **argv),这样就能从 JNI 里直接调用它来完成视频合成等操作。

例如,可通过类似以下方式来实现视频合成(对应命令行 ffmpeg -i src1 -i src2 -y output):

video_merge(5, argv); // 其中 argv 模拟命令行参数

环境

在动手之前,建议先参考一些相关教程,碰到问题再回来比照本文,避免走太多弯路。


修改接口与 Android.mk

在为 FFmpeg 编写 JNI 接口时,需要编写 Android.mk 文件来链接库,进而生成可用的 .so 文件。一些示例的 Android.mk 在不同环境下可能无法直接运行成功。其作用是告诉 NDK 哪些源文件需要编译、链接到哪个库等信息。

我采用了一种“两次编译、再链接”的方法:

  1. 先编译得到一个名为 myffmpeg 的共享库。
  2. 在另一个模块 ffmpeg-jni 中,再将 myffmpeg 链接进去,最终生成所需的 .so

另外,需要将编译好的 libffmpeg.so 放置在 jni 目录下,以保证链接时能够找到它。


调试 FFmpeg

移植 FFmpeg 之后,为了通过 JNI 调用函数,往往需要在 C 层调试。如果能像在命令行一样看到详细的日志输出,就能更轻松地定位问题。

在 Eclipse 下,按住 Ctrl 点击类似 av_log 的调用位置,可以追踪到 ffmpeg/libavutil/log.cav_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,即可避免重复调用时的内存问题。


可能遇到的问题

在配音秀的具体实现中,常见的做法是:

  1. 提前下载好原始视频、字幕,以及“已去除需配音片段”的音频文件。
  2. 录制用户声音,合成时仅需将录音与静音片段合并即可。
  3. 若对本地合成时间不满意,可选择将音视频数据上传到服务器,由服务器端进行合成,完成后再下载。

在 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,欢迎在评论区分享经验或问题,互相交流与学习。


Back 2014.03.10 Donate