美文网首页
(3)框架设计

(3)框架设计

作者: 酱肉包啊 | 来源:发表于2021-03-04 09:54 被阅读0次

    最初下载了几个比较好用的编辑软件体验,大都功能比较完善,体验下来发现分为两大模块

    • 自定义编辑模式
    • 模板编辑模式

    自定义编辑模式,这里面包含的功能比较多,大部分的实现都能找到单独的例子,难点在于怎么把所有的这些功能组合起来,如何编辑,如何预览。

    • 视频拼接
    • 视频裁剪
    • 视频排序
    • 视频分割
    • 视频旋转
    • 视频变速
    • 删除视频
    • 调整画幅
    • 调整音量
    • 画中画
    • 滤镜
    • 视频转场
    • 视频倒播
    • 背景音乐
    • 配乐
    • 字幕
    • 保存草稿
    • 重做功能
    • 导出视频

    模板编辑模式,即为用户提供「模板视频」,用户只需要选择视频或者图片,便可创作出与「模板视频」有同样编辑特效的同款视频,实现「一键编辑」。

    1、技术选型

    在iOS平台上,能够实现上面的自定义编辑功能的技术有不少。比如AVFoundation、GPUImage、FFMPEG,本文主要基于AVFoundation来实现。

    主要考虑的几个点:

    1、使用AVFoundation可以很方便预览,使用AVPlayer来做编辑预览

    2、使用AVFoundation性能有保障,后续做优化比较容易,接口比较完善,方便拓展

    基础的编辑功能,比如拼接、排序、裁剪等等功能使用AVMutableComposition很容易就能实现。难么转场、滤镜、画中画这些怎么实现呢。使用AVMutableVideoComposition 可以指定视频的渲染尺寸、缩放比例、帧率等参数,转场动画可以通过添加AVVideoCompositionLayerInstruction指令实现。滤镜功能得针对每一帧视频来做处理,这里我使用AVVideoCompositing来处理。我们可以获取到每一帧的图片,在有多条轨道的时候,还能同时获取多个轨道的帧图片,我们可以在这做转场,滤镜,画中画这些功能。

    2、框架设计

    视频编辑主要分为以下几个模块

    • 资源缓存(缓存当前视频编辑的资源,包括视频路径,图片、音乐、录音、视频帧图片)
    • 编辑描述 (对视频编辑操作的描述,可以导出为JSON文件,也可以从JSON文件还原到编辑状态)
    • 预览构建 (构建视频预览、处理编辑描述形成能够播放的视频资源)
    • 视频预览 (播放预览构建的资源)
    • 导出构建 (构建视频导出、处理编辑描述形成能够导出的视频资源)
    • UI

    下图是整体的一个流程


    20210303210256.png

    在视频编辑的时候,每次编辑都是对编辑描述模块的一个修改,后续所有的预览和导出都依赖于对编辑描述的解析。从视频编辑描述我们可以生成用来播放和导出的资源组合(AVMutableComposition),那么我们怎么来描述视频编辑呢。

    首先我们看下视频编辑之后的资源组合是怎么样的:

    编辑轨道示意图.png
    • 时间线 :首先有个时间线的概念,我们所有的资源都是位于时间上的一段,每个资源都处于时间线上,所有操作也都是位于时间下上的一段时间。时间线的长度也就代表了最终视频合成的长度。

    • 轨道:以时间线为坐标系的容器,容器内存放的是每个时间点需要的内容素材及编辑功能,可以分为视频轨道,音频轨道,画中画轨道、文字轨道

    然后我们对上面图的编辑模型做描述

    • 我们创建一个时间长度为100s的视频,拥有五条轨道,视频轨道包含转场描述、滤镜描述

    • 视频轨道分两部分,一部分是基本视频,一部分是画中画视频

      • 基础视频:包含5个视频段,分别位于两个视频轨道,类型都是video,所在位置分别为[0--30]、[42--55]、[75--100]、[25--45]、[60--78]
      • 画中画:只包含一个视频段,类型是pip,所在位置是[8--92]
    • 图片轨道:包含两个图片,类型是image,位置为:[28--45]、[62--90]

    • 音频轨道:实例中只包含一条视频轨道,一个视频段,位置为[0--100],实际中可以跟视频一样有多条轨道,多个视频段

    • 转场、滤镜描述:在这里也可以把转场滤镜当作完整的轨道,拥有类型和位置描述,跟视频段一样

    有了上述的描述,我们可以将描述转换为相应的代码:

    时间线:

    我们创建一个时间线的描述类:FXTimelineDescribe,它将拥有下面这些属性,用来存放各个轨道描述文件

    CMTime duration; //时间线总时长
    NSMutableArray<FXVideoDescribe *> *videoArray;      //视频轨道视频段描述数组
    NSMutableArray<FXPIPVideoDescribe *> *pipVideoArray;   //画中画
    NSMutableArray<FXAudioDescribe *> *audioArray;   //音频(视频资源中自带的音频)
    NSMutableArray<FXTransitionDescribe *> *transitionArray;  //转场描述
    NSMutableArray<FXMusicDescribe *>*musicArray;   //音乐描述
    NSMutableArray *filterArray;   //滤镜描述
    NSMutableArray *titleArray;   //字幕描述
    NSMutableArray *overlayArray;   //水印描述
    
    轨道描述:

    根据类型不同,我们创建不同类型的轨道描述:

    typedef NS_ENUM(NSUInteger, FXDescribeType) {
        FXDescribeTypeNone,
        FXDescribeTypeVideo,    //视频
        FXDescribeTypeAudio,    //音频
        FXDescribeTypeTransition,    //转场
        FXDescribeTypeTitle,    //字幕
        FXDescribeTypePip,      //画中画
        FXDescribeTypeMusic,    //音乐
        FXDescribeTypeRecord    //配音
    };
    

    然后抽出轨道描述相同的部分作为轨道内容描述的基类:FXDescribe

    @interface FXDescribe : NSObject
    
    @property (nonatomic, assign) CMTime startTime;    //开始时间
    
    @property (nonatomic, assign) CMTime duration;     //持续时间
     
    @property (nonatomic, assign) CMTimeRange sourceRange;    //在源资源中的位置
    
    @property (nonatomic, assign) CGFloat scale;    //变速的倍速
    
    @property (nonatomic, assign) FXDescribeType desType;    //类型
    
    - (NSDictionary *)objectDictionary;
    
    - (void)setObjectWithDic:(NSDictionary *)dic;
    
    @end
    

    其中添加了一个sourceRange属性,用来描述当前资源在源资源中的位置,比如视频段截取自某一个视频的一部分。

    其中有两个方法需要子类重写的

    1、(NSDictionary *)objectDictionary;

    2、(void)setObjectWithDic:(NSDictionary *)dic;

    1、(NSDictionary *)objectDictionary;

    用来将描述转换为字典,后续组合起来转换为描述JSON,子类需要重写这个方法,然后将子类新增的属性也添加进来

    2、(void)setObjectWithDic:(NSDictionary *)dic

    这部分相当于数模转换,在这里没有使用自动数模转化(比如用YYModel),因为要处理一些比较特殊的数据,还有部分资源的查找。

    接下来我们看下视频轨道的视频段是怎么描述的:

    我们创建FXVideoDescribe,继承自:FXDescribe,我们需要添加视频特有的一些描述属性

    @property (nonatomic, assign) NSInteger videoIndex;         //视频段编号,后续用来做视频排序
    
    @property (nonatomic, assign) BOOL reverse;               //视频反转
     
    @property (nonatomic, readonly) FXRotation rotate;        //视频旋转,支持90、180、270度的旋转
    
    @property (nonatomic, strong) FXVideoItem *videoItem;      //视频资源,包含真实的视频音频轨道,帧缩略图资源等
    
    @property (nonatomic, assign) BOOL mute;    //是否静音
    

    跟视频段描述相似,其他类型的描述添加各自需要的属性。

    最终把时间线转换为字典:(我们添加两段视频,添加一个转场特效,然后将第一个视频分割为两段)我们来看下生成的描述字典

    {
        audioTrack =     (
        );
        defaultNaturalSizeHeight = 1080;
        defaultNaturalSizeWidth = 608;
        duration = "30.182";
        lengthTimeScale = 30;
        mainVideoVolume = 100;
        pipVideoVolume = 100;
        transitionTrack =     (
                    {
                backVideoIndex = 1;
                desType = 3;
                duration = 2;
                preVideoIndex = 0;
                scale = 1;
                sourceRangeDuration = nan;
                sourceRangeStart = nan;
                startTime = 0;
                transType = 1;
            }
        );
        videoTrack =     (
                    {
                desType = 1;
                duration = "5.471666666666667";
                filterType = 2;
                mute = 0;
                reverse = 0;
                rotate = 0;
                scale = 1;
                sourceRangeDuration = "5.471666666666667";
                sourceRangeStart = 0;
                startTime = 0;
                videoIndex = 0;
                videoItem = "file:///var/mobile/Media/PhotoData/CPLAssets/group115/480C781E-4B41-48A3-B367-484F5C693464.MP4";
            },
                    {
                desType = 1;
                duration = "8.389333333333333";
                filterType = 2;
                mute = 0;
                reverse = 0;
                rotate = 0;
                scale = 1;
                sourceRangeDuration = "8.389333333333333";
                sourceRangeStart = "5.471666666666667";
                startTime = "3.471666666666667";
                videoIndex = 1;
                videoItem = "file:///var/mobile/Media/PhotoData/CPLAssets/group115/480C781E-4B41-48A3-B367-484F5C693464.MP4";
            },
                    {
                desType = 1;
                duration = "18.321";
                filterType = 2;
                mute = 0;
                reverse = 0;
                rotate = 0;
                scale = 1;
                sourceRangeDuration = "18.321";
                sourceRangeStart = 0;
                startTime = "11.861";
                videoIndex = 2;
                videoItem = "file:///var/mobile/Media/PhotoData/CPLAssets/group259/98D7CEA4-69EA-4EB8-924C-FB99DCDBBDD7.MP4";
            }
        );
    }
    

    转换为JSON 字符串:

    {"pipVideoVolume":"100","mainVideoVolume":"100","transitionTrack":[{"scale":1,"desType":3,"backVideoIndex":"1","sourceRangeDuration":"nan","duration":"2","preVideoIndex":"0","startTime":"0","sourceRangeStart":"nan","transType":"1"}],"defaultNaturalSizeWidth":"608","duration":"30.182","audioTrack":[],"lengthTimeScale":"30","videoTrack":[{"scale":1,"reverse":"0","rotate":"0","desType":1,"sourceRangeStart":"0","sourceRangeDuration":"5.471666666666667","filterType":"2","videoIndex":"0","videoItem":"file:///var/mobile/Media/PhotoData/CPLAssets/group115/480C781E-4B41-48A3-B367-484F5C693464.MP4","duration":"5.471666666666667","mute":"0","startTime":"0"},{"scale":1,"reverse":"0","rotate":"0","desType":1,"sourceRangeStart":"5.471666666666667","sourceRangeDuration":"8.389333333333333","filterType":"2","videoIndex":"1","videoItem":"file:///var/mobile/Media/PhotoData/CPLAssets/group115/480C781E-4B41-48A3-B367-484F5C693464.MP4","duration":"8.389333333333333","mute":"0","startTime":"3.471666666666667"},{"scale":1,"reverse":"0","rotate":"0","desType":1,"sourceRangeStart":"0","sourceRangeDuration":"18.321","filterType":"2","videoIndex":"2","videoItem":"file:///var/mobile/Media/PhotoData/CPLAssets/group259/98D7CEA4-69EA-4EB8-924C-FB99DCDBBDD7.MP4","duration":"18.321","mute":"0","startTime":"11.861"}],"defaultNaturalSizeHeight":"1080"}

    至此我们的视频编辑描述部分就完成了,我们可以根据描述文件反推会相应的描述模型。因此,我们可以将每次修改之后的描述JSON文件存储起来,作为修改的一个状态,用来做撤销和重做操作(也可以使用NSUndoManager来实现撤销重做,但是相对来说比较复杂一点)。

    有了编辑描述之后,我们就可以根据编辑描述来构建UI,构建播放、导出模块。最终的结构如下图

    视频编辑流程1.png

    相关文章

      网友评论

          本文标题:(3)框架设计

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