在你的手机上使用FFmpeg转码、剪辑———音视频编辑器的第一个正式版
前言
大概是2024年4月初的时候,我开始上手捣鼓Android上的FFmpeg编程,当时就萌生了开发一款基于FFmpeg的开源的音视频编辑器的想法。虽然市面上的音视频处理的软件已经非常多了,但是我认为自己开发一款能在手机上充分发挥FFmpeg的价值的软件还是一件非常酷的事情。我24年末开始在掘金写写一些和开发音视频编辑器相关的技术文章,记录我的开发过程和开发过程中学到的一些东西。现在,我完成了音视频编辑器的第一个正式版,代码在GitHub上开源了,这篇文章是对我这个项目的一次总结和介绍。
应用功能介绍
FFmpeg是著名的开源多媒体框架,人们日常生活中的音视频播放、直播、视频聊天等涉及到音视频处理的场景大都可以看见FFmpeg库的身影。音视频编辑器是一款基于FFmpeg的Android应用,我希望用户可以通过这款软件在Android手机上体验FFmpeg所有的强大的音视频处理功能。当前版本的音视频编辑器支持执行ffmpeg命令行、视频格式转换、视频时长与画面剪裁、视频变速、提取音频和视频消音、获取音视频信息等功能,后面的版本会陆续添加新的功能。
在手机上执行ffmpeg命令行
FFmpeg旗下有ffmpeg、ffplay、ffprobe三个命令行工具。ffmpeg主要用于音视频转码、格式转换等各种音视频处理,对于非开发者用户而言,ffmpeg就是FFmpeg的“主体”。ffplay用于音视频播放,ffprobe用于分析音视频的各类信息。
音视频编辑器支持用户在手机上直接执行ffmpeg命令行,更具体地说,我其实是将ffmpeg命令行工具移植到Android手机上。
本应用的ffmpeg命令行分为辅助编辑和自主编辑两种模式。辅助编辑模型下用户可以通过文件夹界面选择输入文件,填写输出文件的文件名,选择输出文件的文件类型,填写命令行参数就可以启动一个命令行了。辅助编辑模式还提供了常用参数模板帮助用户填写参数信息。
自主编辑就是让用户自己完全编写一个ffmpeg命令行。自主编辑模式的界面只有一个编辑框和确定按钮。
界面操作功能
在手机上编辑命令行还是比较麻烦的,而且相比命令行,大多数用户还是更喜欢界面操作。因此本应用还提供了界面操作的音视频处理功能,详情如下表。
| 功能名称 | 功能详情 |
|---|---|
| 视频格式转换功能 | 用于重新设置视频封装类型、视频编码类型、音频编码类型、视频分辨率、视频码率、视频帧率、音频码率、音频采样率。 |
| 视频时长剪裁 | 用于裁剪用户截取指定的视频片段,有快速剪裁和精准剪裁两种模式。快速剪裁的执行速度快,截取的时间点可能没那么准。精准剪裁的执行速度较慢,截取的时间点会更准确。 |
| 视频画面剪裁 | 用于裁剪用户指定的视频画面。 |
| 调整视频比例 | 用于重新设置视频比例和视频背景颜色。 |
| 视频变速 | 用于设置视频播放速度。 |
| 提取音频 | 用于提取视频中的音频。 |
| 视频消音 | 用于消除视频中的音频部分。 |
其它功能
任务中心中可以看见任务信息列表。按照任务的状态分为正在执行、等待执行、取消执行、执行失败、执行结束五个信息列表。正在执行列表可以看见的正在执行的任务进度和任务日志。执行结束列表可以点击播放已完成任务的输出视频和任务日志。
本应用还提供了文件夹功能,用户可以浏览设备上的视频、音频和其它文件,可以获取文件信息和尝试打开文件。
在本应用的设置中心可以设置应用语言(简体中文/英文)、设置目标文件夹(默认所有输出文件在该文件夹下)、获取应用信息和隐私政策、与开发者的联系方式。
应用的设计
音视频编辑器当前的所有代码都在GitHub上开源了,下面我简要地介绍一下这款软件的设计。
应用的架构
我在开发这款项目的时候计划基于MVVM模型去开发,即Model-View-ViewModel模型。Model管理应用的核心数据和业务逻辑,View负责界面展示,与用户直接交互;ViewModel是连接 View 和 Model 的“桥梁”,处理来自Model的数据,来自View的事件。不过我到后面就是怎么方便怎么开发了,没有完全遵循MVVM模型。UI是Activity+Compose函数。Model数据源有三个。一个是本机视频、音频和文件,音视频文件信息由ContentResolver组件获取,一般的文件信息就通过File获取。一个是当前启动的任务(task),由TasksService存储和管理相关信息。一个是历史上已完成、已取消、失败的任务信息,由Room负责持久化存储和查询。
UI
本应用的界面使用Jetpack Compose开发而成,采用单Activity多Compose函数架构,界面都是由一个Activity承载的,不同的界面内容由不同的Compose函数实现。根据我的个人体验,使用Jetpack Compose开发界面比传统的Android View效率更高一些。每个主要的Compose函数都有自己一个ViewModel类去管理和临时存储页面状态。
图片
本应用需要展示本机视频封面,现在使用的是Android自带的API MediaMetadataRetriever尝试获取视频图片。为了避免加载大量图片带来卡顿和内存不足,还设置了缓存队列。先检查队列是否有需要的图片,没有就加载,新加载的图片(Bitmap)要进入队列。队列最大长度为100,超过100队列中的第一张图片要离开队列并释放资源。用户离开应用时,所有的图片资源都会被释放。
val thumbnailsMaxNum=100
val thumbnailBitmapArray=ArrayList<Pair<String,Bitmap>>()
// ViewModel被清除时调用此方法
override fun onCleared() {
super.onCleared()
releaseBitmaps() // 在ViewModel清除时释放资源
}
private fun releaseBitmaps() {
for (pair in thumbnailBitmapArray) {
if (!pair.second.isRecycled) {
pair.second.recycle()
}
}
thumbnailBitmapArray.clear() // 清空列表
//println("ViewModel中的所有Bitmap资源已释放并清空列表。")
}
视频
既然都进行FFmpeg开发了,理论上我们可以基于FFmpeg开发一款视频播放器,不过想自己开发出一款好用又可靠的视频播放器成本还是蛮高的。我最后选择了Media3 ExoPlayer做了本应用的播放器,它的功能很丰富,就是支持格式不是很多。
任务的实现和管理
本应用的音视频处理功能由一个一个任务(Task)去实现。所有的Task交由统一的一个TasksService去管理,Task的创建、执行、销毁都交由TasksService完成。
Task的信息都会放在应用的数据库中存储,本应用使用Android官方推出的ORM框架Room来实现数据库的增删改查功能。
日志
ffmpeg命令行执行的时候会输出日志,本应用会保留每个任务的日志,用户可以点击查看日志。日志保存在应用的私有目录中,用户可以选择清空所有日志。
除了处理音视频的日志外,本应用还会存储应用的最新的报错、崩溃日志,用户可以查看这些日志并将报错信息提供给我,帮助我改善应用。
进程和线程
本应用将音视频任务放在另一个独立的进程中执行,这样做的好处是隔离崩溃风险、在独立进程中可以获得更多内存、未来可以开多个进程并发执行任务(现在只能执行单个任务)。
//在独立进程中开启一个新的service执行任务
<service
android:name=".services.FFmpegService"
android:enabled="true"
android:exported="true"
android:process=":ffmpeg"
>
</service>
获取任务信息进度、更新任务队列等工作放在子线程中执行。
其它的异步和并发的操作则由轻量级的协程完成。
权限
本应用的读写文件操作需要申请Android的文件存储管理权限。Android 13及以上还会申请通知权限来向用户通知任务进度。
用户可以在设置界面管理权限。
信息收集和应用更新
本应用不会主动收集用户信息,用户可以通过表单、邮件和加入群聊提供建议和反馈报错信息。由于我没有服务器,所以应用的更新需要依靠用户自己去Github或者我提供的网盘地址重新下载更新。
支持的Android版本
理论上支持Android 8~Android 14的版本,我只在Android 10的虚拟机和Android 14的真机上测试过。只支持arm64-v8a和x86的CPU架构。目前还没有适配Android 15的16K Page Size。
后面的计划
1.支持libmp3lame、libx265和libvpx
2.适配Android 15的16K Page Size