需要下载的源码
https://github.com/tanersener/mobile-ffmpeg
https://github.com/tanersener/FFmpeg
https://github.com/tanersener/x264
考虑源码较大,github不好下载成功,可以从gitee镜像仓库下载
https://gitee.com/catface7/tanersener-mobile-ffmpeg
https://gitee.com/catface7/tanersener-FFmpeg
https://gitee.com/catface7/tanersener-x264
FFmpeg源码下载后解压到mpbile-ffmpeg/src/ffmpeg目录下
x264源码下载后解压到mobile-ffmpeg/src/x264目录下
编译
配置sdk、ndk环境
export ANDROID_HOME=/opt/sdk
export ANDROID_NDK_ROOT=/opt/ndk/android-ndk-r21b-linux-x86_64/android-ndk-r21b
执行编译命令./android.sh
此时编译出的so不支持x264编码,表现为不支持preset、crf等指令参数;不支持字幕添加,表现为不支持subtitles、FontName等指令参数
执行编译命令./android.sh --enable-gpl --enable-x264 --enable-fontconfig --enable-libass
编译后的so支持x264编码和字幕添加能力,可排除指定平台,如下:
./android.sh --enable-gpl --disable-arm-v7a-neon --disable-x86 --disable-x86-64 --enable-x264 --enable-fontconfig --enable-libass
失败记录
aclocal-1.16: 未找到命令
详细错误
命令行错误日志:
Building mobile-ffmpeg library for Android
Architectures: arm-v7a
Libraries: android-zlib, cpu-features, fontconfig, freetype, fribidi, libass, libiconv, x264
Building arm-v7a platform on API level 24
fribidi: failed
build.log错误日志:
CDPATH="${ZSH_VERSION+.}:" && cd . && /bin/bash /opt/pj4as/tanersener-mobile-ffmpeg/src/fribidi/missing aclocal-1.16 -I m4
/opt/pj4as/tanersener-mobile-ffmpeg/src/fribidi/missing: 行 81: aclocal-1.16: 未找到命令
WARNING: 'aclocal-1.16' is missing on your system.
You should only need it if you modified 'acinclude.m4' or
'configure.ac' or m4 files included by 'configure.ac'.
The 'aclocal' program is part of the GNU Automake package:
<https://www.gnu.org/software/automake>
It also requires GNU Autoconf, GNU m4 and Perl in order to run:
<https://www.gnu.org/software/autoconf>
<https://www.gnu.org/software/m4/>
<https://www.perl.org/>
Makefile:442: recipe for target 'aclocal.m4' failed
make: *** [aclocal.m4] Error 127
解决办法-安装automake
进入root权限
sudo passwd root
su
下载地址http://ftp.gnu.org/gnu/automake/
分别执行命令:
./configure
make
make install
参考https://github.com/itCatface/catface_app或者https://gitee.com/catface7/catface_app项目的
app_ffmpeg_demo模块,以下为使用示例
音频文件裁剪
// 方法说明
/**
* 裁剪音频文件
*
* @param filepath 文件路径
* @param saveFilepath 裁剪后文件存储路径
* @param coverSaveFilepath 是否覆盖已存在文件(默认不覆盖)
* @param startTime 截取开始时间戳-00:00:30
* @param duration 截取时长-00:03:00
* @param callback 结果回调
*/
public void runCutAudio(String filepath, String saveFilepath, boolean coverSaveFilepath, String startTime, String duration, ExecuteCallback callback)
// 调用示例
FFmpegUtils.getInstance().runCutAudio("/sdcard/wav.wav", "/sdcard/dest_cut_audio_" + System.currentTimeMillis() + ".wav", "00:00:02", "00:00:03", new ExecuteCallback() {
@Override
public void apply(long executionId, int returnCode) {
Log.i(TAG, "apply: cut audio finish:" + returnCode);
}
});
视频文件裁剪
// 方法说明
/**
* 裁剪视频文件
*
* @param filepath 文件路径
* @param saveFilepath 裁剪后文件存储路径
* @param coverSaveFilepath 是否覆盖已存在文件(默认不覆盖)
* @param startTime 截取开始时间戳-00:00:30
* @param duration 截取时长-00:03:00
* @param callback 结果回调
*/
public void runCutVideo(String filepath, String saveFilepath, boolean coverSaveFilepath, String startTime, String duration, ExecuteCallback callback)
// 调用示例
FFmpegUtils.getInstance().runCutVideo("/sdcard/5m.mp4", "/sdcard/dest_cut_video_" + System.currentTimeMillis() + ".mp4", "00:00:01", "00:00:03", new ExecuteCallback() {
@Override
public void apply(long executionId, int returnCode) {
Log.i(TAG, "apply: cut video finish:" + returnCode);
}
});
获取媒体文件时长
// 方法说明
/**
* 获取视频文件时长
*
* @param filepath 视频文件绝对路径
* @return 时长(ms)
*/
public long getVideoDuration(String filepath)
// 调用示例
long duration = FFmpegUtils.getInstance().getVideoDuration("/sdcard/5m.mp4");
添加字幕
// Application中注册字体
/* 注册字幕字体 */
SubtitleFont fontTljt = new SubtitleFont(R.raw.tljt, "tljt", "叶根友特隶简体"); // "叶根友特隶简体"为ttf文件打开的第一行文本
SubtitleFont fontKxjt = new SubtitleFont(R.raw.kxjt, "kxjt", "叶根友空心简体");
List<SubtitleFont> fonts = new ArrayList<>();
fonts.add(fontTljt);
fonts.add(fontKxjt);
FFmpegUtils.getInstance().initRegisterFonts(this, fonts);
// 方法说明
/**
* 添加字幕
*
* @param videoFilepath 视频文件路径
* @param subtitleFilepath 字幕文件路径
* @param saveFilepath 合成后视频文件存储路径
* @param coverSaveFilepath 是否覆盖已存在文件(默认覆盖)
* @param fontName 字体名
* @param fontSize 字体大小
* @param preset 合成速度,空间换取时间(默认ultrafast)
* @param crf 合成质量,0-51递减,18-25基本无损(默认25)
* @param statisticsCallback 进度回调
* @param executeCallback 结果回调
*/
public void compressSubtitle(String videoFilepath, String subtitleFilepath, String saveFilepath, boolean coverSaveFilepath, String fontName, String fontSize, String preset, String crf, StatisticsCallback statisticsCallback, ExecuteCallback executeCallback)
// 调用示例
findViewById(R.id.btCompressSubtitle).setOnClickListener(v -> {
long startTime = System.currentTimeMillis();
String videoFilepath = "/sdcard/6s.mp4";
String subtitleFilepath = "/sdcard/6s.srt";
mDuration = FFmpegUtils.getInstance().getVideoDuration(videoFilepath); // mDuration为当前文件时长
String fontName = ((EditText) findViewById(R.id.etSubtitleFontName)).getText().toString().trim();
String fontSize = ((EditText) findViewById(R.id.etSubtitleFontSize)).getText().toString().trim();
String saveFilepath = "/sdcard/dest_compress_" + System.currentTimeMillis() + ".mp4";
FFmpegUtils.getInstance().compressSubtitle(videoFilepath, subtitleFilepath, saveFilepath, fontName, fontSize, new StatisticsCallback() {
@Override
public void apply(Statistics statistics) {
if (mDuration == 0) return;
// String progress = statistics.getTime() / 1_000 / mDuration + "";
String progress = new BigDecimal(statistics.getTime() / 1_000).multiply(new BigDecimal(100)).divide(new BigDecimal(mDuration), 0, BigDecimal.ROUND_HALF_UP).toString();
String msg = String.format("subtitle compress progress:" + progress + "-frame: %d, time: %d, quality: %s", statistics.getVideoFrameNumber(), statistics.getTime(), statistics.getVideoQuality() + "-duration:" + mDuration);
Log.d(TAG, msg);
}
}, new ExecuteCallback() {
@Override
public void apply(long executionId, int returnCode) {
Log.i(TAG, "subtitle compress finish:" + returnCode + "-time used(ms):" + (System.currentTimeMillis() - startTime) + "-save filepath:" + saveFilepath + "-fontName:" + fontName + "-fontSize:" + fontSize);
}
});
});
取消ffmpeg操作
FFmpegUtils.getInstance().cancel()
同步/异步执行命令
// 异步
long excutionId = FFmpeg.executeAsync(cmd, ExecuteCallback);
// 同步-ret0成功255用户取消其他失败
int ret = FFmpeg.execute(cmd);