基于FFmpeg的短视频编辑工具Cut

3,572 阅读5分钟
原文链接: www.jianshu.com

前言

最近在学习FFmpeg和音视频的相关知识,为了加强对FFmpeg的认识和了解,于是撸了一个短视频编辑软件Cut

效果图先行:

5.gif
5.gif

技术点

启动页优化

但启动app的时候会有一个短暂的黑屏或者白屏。为什么呢? 是因为在App启动时,系统会执行3个Task:

1、 加载并启动app
2、在app启动后,立即展示空白的window
3、创建app进程

一旦app进程完成了第一次绘制,系统进程就会用main activity替换已经展示的background window。之后用户才可以使用app。

这个空白的window就是导致白屏或者黑屏的罪魁祸首。怎么解决呢? 1.定义透明的主题,parent中的AppTheme为APP的主题


<style name="Theme.AppStartLoadTranslucent" parent="AppTheme">  
       <item name="android:windowIsTranslucent">true</item>  
       <item name="android:windowNoTitle">true</item>  
   </style>  

2。


<!-- 启动界面 -->  
        <activity  
            android:name=".ui.LaunchActivity"  
            android:launchMode="singleTask"  
            android:theme="@style/Theme.AppStartLoadTranslucent">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  
  
                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity> 

启动页优化原理

增量更新和全量更新

在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.01.0.11.0.2,最新版本为1.0.3.
差分包patch是:1.0.21.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命令

在项目中,使用的命令有:改变视频的速度,改变视频的分辨率,视频和视频的连接,视频和图片的合成,视频的剪辑。

改变视频的速度

点击分镜,会弹出一个popup可以选择分镜播放的速度

002.png
002.png
/**
     * 改变视频的速度的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。

003.png
003.png

       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(" ");

视频和图片的合成

把三个分镜头合成一个视频后,可以对视频进行涂鸦,帖子,添加文本等操作。

005.png
005.png
 
      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版本存在很多的不足,比如:

  1. 速度的变换范围少
  2. 合成视频画质差
  3. FFmpeg对一些功能,比如:在overlay做叠加,用scale缩放,改变速度功能较慢 。
  4. 不能添加滤镜

后记

这个项目将会一直会维护下去,完善所能知道的一些不足的地方,还请大家多多指导和多提意见,互相学习,感谢。

Thanks

FFmpeg
glide
butterknife
BaseRecyclerViewAdapterHelper
okhttp
bspatchlibrary
ffmpeglibrary
circular-progress-button
material-dialogs
Zhaoss
视频裁剪