在安卓中使用 FFmpegKit 剪切视频并添加文字水印

129 阅读6分钟

在安卓中用到的三方库:github.com/arthenica/f…

这个库很强大,支持很多平台,每个平台都有各自的分支代码,用了一段时间,稳定性挺好的,

找到安卓下的分支:FFmpegKit for Android FFmpegKit Android 版

引入项目:

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.arthenica:ffmpeg-kit-full:6.0-2'
}

每个平台下,又分为多个库,每个库包含不同的功能,因为功能越丰富,导入到项目中编译的包体积越大,尽量选择适合自己功能的库进行使用。


📦 FFmpeg 编译配置选项(库依赖分类)

配置项说明包含的库
min最小化构建-
min-gpl最小化构建并启用 GPL 库vid.stab, x264, x265, xvidcore
https启用 HTTPS 支持(使用非-GPL 依赖)gmp, gnutls
https-gpl启用 HTTPS 支持(使用 GPL 兼容依赖)gmp, gnutls, vid.stab, x264, x265, xvidcore
audio启用音频相关编码器/解码器lame, libilbc, libvorbis, opencore-amr, opus, shine, soxr, speex, twolame, vo-amrwbenc
video启用视频相关编码器/解码器dav1d, fontconfig, freetype, fribidi, kvazaar, libass, libiconv, libtheora, libvpx, libwebp, snappy, zimg
full启用所有非 GPL 第三方库dav1d, fontconfig, freetype, fribidi, gmp, gnutls, kvazaar, libass, libiconv, libilbc, libtheora, libvorbis, libvpx, libwebp, libxml2, opencore-amr, opus, shine, snappy, soxr, speex, twolame, vo-amrwbenc, zimg
full-gpl启用所有库(包括 GPL)dav1d, fontconfig, freetype, fribidi, gmp, gnutls, kvazaar, lame, libass, libiconv, libilbc, libtheora, libvorbis, libvpx, libwebp, libxml2, opencore-amr, opus, shine, snappy, soxr, speex, twolame, vid.stab, vo-amrwbenc, x264, x265, xvidcore, zimg

比如你如果只需要保存 rtsp 视频流 和 推流的话,只需要导入 min-gpl即可:

 implementation 'com.arthenica:ffmpeg-kit-min-gpl:6.0-2'

如果你想要更多,比如添加水印,就涉及到 FFmpeg 滤镜相关功能,就需要引入full-gpl

 implementation 'com.arthenica:ffmpeg-kit-full-gpl:6.0-2'

具体需要什么功能,可以进去看说明,说明没有涉及到的,并且你不想用全功能库,你也可以一个一个试试,也许就能满足你。

正文开始


由说明可知,使用方式为:

import com.arthenica.ffmpegkit.FFmpegKit;

FFmpegSession session = FFmpegKit.execute("-i file1.mp4 -c:v mpeg4 file2.mp4");
if (ReturnCode.isSuccess(session.getReturnCode())) {

    // SUCCESS

} else if (ReturnCode.isCancel(session.getReturnCode())) {

    // CANCEL

} else {

    // FAILURE
    Log.d(TAG, String.format("Command failed with state %s and rc %s.%s", session.getState(), session.getReturnCode(), session.getFailStackTrace()));

}

或者异步调用:

FFmpegKit.executeAsync("-i file1.mp4 -c:v mpeg4 file2.mp4", new FFmpegSessionCompleteCallback() {

    @Override
    public void apply(FFmpegSession session) {
        SessionState state = session.getState();
        ReturnCode returnCode = session.getReturnCode();

        // CALLED WHEN SESSION IS EXECUTED

        Log.d(TAG, String.format("FFmpeg process exited with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace()));
    }
}, new LogCallback() {

    @Override
    public void apply(com.arthenica.ffmpegkit.Log log) {

        // CALLED WHEN SESSION PRINTS LOGS

    }
}, new StatisticsCallback() {

    @Override
    public void apply(Statistics statistics) {

        // CALLED WHEN SESSION GENERATES STATISTICS

    }
});

与正常 ffmpeg 命令不同的是,在传入命令时,前面不需要加 "ffmpeg" 关键字,只需传入后面的具体命令即可,加上会报错哦!

关键代码:

搞了好久才凑齐的正确代码,这东西真不能听 AI 的一面之辞,不然就被 AI 一条路领到黑,

    private static String buildWatermarkCommand(VideoFile file, VideoTimeRange timeRange, String outputPath, String fontPath) {
        FFmpegKitConfig.setFontconfigConfigurationPath(fontPath);
      
        String drawtextFilter = String.format(
                "drawtext=text='%s':fontfile=%s:fontcolor=white:fontsize=20:x=0:y=30",
                DEFAULT_WATERMARK, fontPath
        );

        List<String> commandList = new ArrayList<>();
        commandList.add("-ss");                              // 指定输入文件的开始时间,格式:HH:MM:SS
        commandList.add(timeRange.startTime);
        commandList.add("-i");                               // 输入文件路径
        commandList.add(file.filePath);
        commandList.add("-t");                               // 指定持续时间,格式:HH:MM:SS
        commandList.add(timeRange.durationStr);
        commandList.add("-vf");                              // 视频滤镜,用于添加水印文字
        commandList.add(drawtextFilter);
        commandList.add("-c:v");                             // 视频编码器设置
        commandList.add(VIDEO_CODEC);                        // 使用H.264软件编码器
        commandList.add("-preset");                          // 编码速度预设
        commandList.add(VIDEO_PRESET);                       // ultrafast:最快编码速度,文件稍大
        commandList.add("-crf");                             // 恒定质量因子(0-51,越小质量越好)
        commandList.add(String.valueOf(VIDEO_CRF));          // 23:平衡质量和文件大小的推荐值
        commandList.add("-c:a");                             // 音频编码器设置
        commandList.add("copy");                             // 直接复制音频流,不重新编码
        commandList.add("-r");
        commandList.add("20");                               // 每秒20帧
        commandList.add("-avoid_negative_ts");               // 避免负时间戳问题
        commandList.add("make_zero");                        // 将负时间戳调整为0
        commandList.add(outputPath);                         // 输出文件路径

        return String.join(" ", commandList);
    }

简单介绍下命令作用:对一段现有的视频文件进行剪辑,-ss指定开始时间,比如要剪切的原视频时长为两分钟,所以开始时间到结束时间就是:00:00:00 - 00:02:00 , 假设要剪辑中间一分钟的视频,那么 -ss指定的开始时间为:00:00:30 , -t持续时间就是:00:01:00 , 截取的时间段为:00:00:30 - 00:01:30 ,-c:v设置编码器,一般 H.264 就够了,如果设置其他的编码器,要看你的设备支持不支持了,-r设置帧率,如果你想要剪切的视频大小小一点,一方面就可以通过降低帧率,另一方面就可以降低码率来实现(上述命令码率未指定默认按原视频码率)。

如果你指定的时间范围,超过原视频时长会报错,这很正常,只是报错内容可能看不懂,这是一个问题点!

设置 FFmpegKitConfig.setFontconfigConfigurationPath的作用是 github.com/arthenica/f… 参考第四条:ffmpeg 需要有效的 fontconfig 配置才能在使用 drawtext filter 时渲染文本。

这里指定一个存在的字体路径即可 比如:/system/fonts/NotoSansCJK-Regular.ttc

注意:有的字体不支持中文,写入中文水印的时候会乱码!


视频处理性能测试(A133,Android 10)

测试环境

  • 设备平台:A133
  • 操作系统:Android 10
  • 测试内容:视频加水印 vs 无水印处理
  • 加水印帧率:15fps
  • 视频格式:H.264 (宽高:720x576)(位率:512Kbps)

性能对比数据

视频时长处理方式耗时(ms)耗时(秒)输出文件大小
30s加水印52145.211.5M
30s加水印(15帧)38133.811.5M
30s不加水印1680.170.9M
60s加水印1201312.012.5M
60s加水印(15帧)73927.392.5M
60s不加水印2080.211.5M
120s加水印2200622.015.03M
120s加水印(15帧)1840418.405.03M
120s不加水印2770.282.8M
240s加水印5054750.5510.57M
240s加水印(15帧)3785737.8610.57M
240s不加水印4480.455.24M

在 A133 平台上,加水印操作是性能瓶颈,视频重编码操作对cpu要求比较高。

补充

在低端设备使用FFmpeg处理视频显然是不推荐的,更是不明智的,除非无可选择,可以选择的替代方式为在手机或者在服务器进行视频处理操作,这样速度和体验感更好。

在手机处理发现 不设置 FFmpegKitConfig.setFontconfigConfigurationPath也可以正常添加水印,这个操作应该是可有可无的!或者在特殊地方才会使用到它,有待发现。

开发中发现,只有full-gpl库,才能进行加水印重编码操作,其他库均不行,没办法,只有一个加水印操作也要引入全功能,apk包体积大概增加13M左右,而且这只是在应用只支持arm64-v8a的情况下,多一个就翻一倍!

而且full-gpl:6.0-2最低要求sdk 24,如果你的应用之前最小sdk 小于23,然后改为24时你会发现,包体积大小剧增,这是因为minSdkVersion >= 23 默认不压缩so 大小 ,导致apk体积会变大,大于23 就在清单文件的application标签下设置 android:extractNativeLibs="true" 压缩so ,就正常了。