(3)框架设计

2021-03-04  本文已影响0人  酱肉包啊

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

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

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

1、技术选型

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

主要考虑的几个点:

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

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

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

2、框架设计

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

下图是整体的一个流程


20210303210256.png

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

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

编辑轨道示意图.png

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

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

时间线:

我们创建一个时间线的描述类: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
上一篇下一篇

猜你喜欢

热点阅读