美文网首页Android TechAndroid开发经验谈Android知识
基于FFmpeg的短视频编辑工具Cut App

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

作者: maimingliang | 来源:发表于2017-08-01 22:55 被阅读1534次

    前言

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

    效果图先行:

    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
    /**
         * 改变视频的速度的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
    
           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
     
          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
    视频裁剪

    相关文章

      网友评论

      本文标题:基于FFmpeg的短视频编辑工具Cut App

      本文链接:https://www.haomeiwen.com/subject/rszclxtx.html