最近在学习FFmpeg和音视频的相关知识,为了加强对FFmpeg的认识和了解,于是撸了一个短视频编辑软件Cut。
效果图先行:
但启动app的时候会有一个短暂的黑屏或者白屏。为什么呢? 是因为在App启动时,系统会执行3个Task:
1、 加载并启动app
2、在app启动后,立即展示空白的window
3、创建app进程
一旦app进程完成了第一次绘制,系统进程就会用main activity替换已经展示的background window。之后用户才可以使用app。
这个空白的window就是导致白屏或者黑屏的罪魁祸首。怎么解决呢? 1.定义透明的主题,parent中的AppTheme为APP的主题
启动页优化原理
增量更新和全量更新
在App用了增量更新。
增量更新:增量更新是指在进行更新操作时,只更新需要改变的地方,不需要更新或者已经更新过的地方则不会重复更新,增量更新与全量更新相对。
使用的是bsdiff、 在bspatch中还会用到bzip2.
增量更新的流程:下载差分包,手机上的apk和差很包合并形成新的apk,然后再次安装。
DownloadUtil.get().download(appPath, savePath, saveName,new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess(File file) {
if(file != null){
mProgressDialog.dismiss();
LogUtil.e("tag", "---path = " + file.getAbsolutePath());
if(update_type == 1){
//获取当前应用的apk文件/data/app/app
String oldFile = Utils.getSourceApkPath(LaunchActivity.this, getPackageName());
//2.合并得到最新版本的APK文件
String newApkPath = MApplication.VIDEO_PATH+"meger.apk";
//下载差分包的地址
String patchFileAbsolutePath = file.getAbsolutePath();
LogUtil.e(TAG, "oldfile:"+oldFile);
LogUtil.e(TAG, "newfile:"+newApkPath);
LogUtil.e(TAG, "patchfile:"+patchFileAbsolutePath);
//jni调用baspatch old.APK 和 差分包 合成新的apk
BspatchNDK.bspatch(oldFile, newApkPath, patchFileAbsolutePath);
//再次安装
Utils.installApk(LaunchActivity.this,newApkPath);
}else if(update_type == 2){
Utils.installApk(LaunchActivity.this,file);
}
}
}
@Override
public void onDownloading(int progress) {
mProgressDialog.setProgress(progress);
}
@Override
public void onDownloadFailed() {
mProgressDialog.dismiss();
}
});
这里会有一个问题?这个差分包,是什么版本和新版本的差分包?我这里是这样处理的:假如市场发布了1.0.0
、1.0.1
、1.0.2
,最新版本为1.0.3
.
差分包patch是:1.0.2
和1.0.3
生成的差分包。
因此:当且仅有版本为1.0.2
(前一个版本),才能进行增量更新,1.0.2
之前的(前一个版本之前的)都需要全量更新。所以在代码中有这样的一段判断:
if (MApplication.getUpgradeinfo().versionCode - Utils.getVerCode(this) == 1) {//前一个版本
//增量更新
//有新版本
hasNewVersion = true;
update_type = 1;
apkUrl = MApplication.QINIU_ADDRESS + "diff-"+MApplication.getUpgradeinfo().versionCode+".patch";
}else if(MApplication.getUpgradeinfo().versionCode - Utils.getVerCode(this) > 1){
//全量更新
hasNewVersion = true;
apkUrl = MApplication.getUpgradeinfo().apkUrl;
update_type = 2;
} else {
update_type = 0;
hasNewVersion = false;
toHome3Second();
}
差分包怎么生成?下载了bsdiff,调用命令即可:(我这里是 Mac OS下执行的)
bsdiff old.apk new.apk diff.patch
然后然后就是差分包和旧的apk在Android如何合成的问题了。因为如何在Android使用bspacth,还得需要如何把bapacth引入Android Studio。 所以新开了一篇文章介绍,可以看这里。
ffmpeg命令行使用
FFmpeg的使用整个项目的重点,大部分的功能都需要它。而在之前的一篇文章中有介绍如何编译FFmpeg并且引入Android Studio 使用, 如何在Android 中使用FFmpeg命令。
ffmpeg命令
在项目中,使用的命令有:改变视频的速度,改变视频的分辨率,视频和视频的连接,视频和图片的合成,视频的剪辑。
【免费分享】音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击788280672加群免费领取~
点击分镜,会弹出一个popup可以选择分镜播放的速度
/**
* 改变视频的速度的ffmpeg命令 atempo【0.5,2】
* ffmpeg -i input.mkv -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" output.mkv
* @param videoPath 输入录像
* @param outPath 输出路径
* @param speed 速度
* @return
*/
private String getSpeedCommandStr(String videoPath, float speed, String outPath) {
if (TextUtils.isEmpty(videoPath) || TextUtils.isEmpty(outPath)) {
return null;
}
String filter = String.format(Locale.getDefault(), "[0:v]setpts=%f*PTS[v];[0:a]atempo=%f[a]", 1/speed, speed);
StringBuilder sb = new StringBuilder("ffmpeg");
sb.append(" -i");
sb.append(" "+videoPath);
sb.append(" -filter_complex");
sb.append(" "+filter);
sb.append(" -map");
sb.append(" [v]");
sb.append(" -map");
sb.append(" [a]");
sb.append(" -b:v 3000k -g 25");
sb.append(" -y");
sb.append(" "+outPath);
LogUtil.d(TAG,"------- cmd = " + sb.toString());
return sb.toString();
}
在项目中共有3个分镜头,最后需要把这三个分镜合成一个完整的视频:
/**
* 合成视频命令
* ffmpeg -f concat -i filelist.txt -c copy output.mkv
* @param path
* @return-vcodec libx264
*/
private String getComplexVideoCmd(String fileList,String path) {
StringBuilder builder = new StringBuilder();
builder.append("ffmpeg -f concat -safe 0 -i ");
builder.append(fileList);
// builder.append(" -b:v 4000K -b:a 96K ");
// builder.append("-profile:v baseline -preset ultrafast ");
// builder.append(" -b:v 1500K -b:a 48K -f mp4 ");
builder.append(" ");
builder.append(path);
LogUtil.d(TAG,"----- 合成视频命令 = " + builder.toString());
return builder.toString();
}
在视频连接的时候会有一个坑,因为在fileList.txt里面写入的路径是绝对路径,在使用ffmpeg 命令连接视频的时候,会报 Operation not permitted
的错误。加上-safe 0
就可以解决了。
改变视频的分辨率
每一分镜的视频来源有可能是录制的,也可能是选择本地视频剪辑一部分的,因此分辨率和码率都会各部相同,就对每一分镜统一成相同分辨率和码率,如果不统一,不然会在视频连接的生成的视频会丢帧的厉害。
/**
* 改变视频分辨率的命令
* @param videoPath
* @return
*/
private String getChangeVideoSizeCmd(String videoPath,String outPath) {
if (TextUtils.isEmpty(videoPath) || TextUtils.isEmpty(outPath)) {
return null;
}
StringBuilder builder = new StringBuilder();
builder.append("ffmpeg -y -i ");
builder.append(videoPath);
builder.append(" -vf scale=1080:1920 -r 25 ");
builder.append(outPath);
LogUtil.e(TAG, "----- 改变视频size 命令 = " + builder.toString());
return builder.toString();
}
对本地的视频剪辑出其中的一部分 ,现在固定3s。
StringBuilder builder = new StringBuilder();
builder.append("ffmpeg -ss ");
builder.append(start);
builder.append(" -t ");
builder.append(duration);
builder.append(" -i ");
builder.append(inputFile);
// builder.append(" -vcodec copy -acodec copy -b:v 4000K -b:a 96K -f mp4 ");
builder.append(" -vcodec copy -acodec copy ");
builder.append(MApplication.VIDEO_PATH);
builder.append(outputName);
LogUtil.e(TAG,"------------ 剪辑视频ffmpeg 命令 = " +builder.toString());
final String[] command = builder.toString().split(" ");
把三个分镜头合成一个视频后,可以对视频进行涂鸦,帖子,添加文本等操作。
StringBuilder sb = new StringBuilder();
sb.append("ffmpeg");
sb.append(" -y -i");
sb.append(" "+path);
sb.append(" -i");
sb.append(" "+imagePath);
sb.append(" -filter_complex overlay ");
sb.append(mergeVideo);
String[] cmds = sb.toString().split(" ");
LogUtil.d(TAG, "----- overlay 命令 " + sb.toString());
不足
在这个项目中,完成初期的预想,加深对FFmpeg认识和了解。但是1.0
版本存在很多的不足,比如:
后记
这个项目将会一直会维护下去,完善所能知道的一些不足的地方,还请大家多多指导和多提意见,互相学习,感谢。
Thanks
FFmpeg
glide
butterknife
BaseRecyclerViewAdapterHelper
okhttp
bspatchlibrary
ffmpeglibrary
circular-progress-button
material-dialogs
Zhaoss
视频裁剪
作者:maimingliang
原文 基于FFmpeg的短视频编辑工具Cut - 掘金