理解Cabbage框架的基础设计

Cabbage是基于AVFoundation封装的音视频处理框架。它将AVFoundation中零散的API通过适当的抽象,达到了易用、易扩展的目的。该篇是学习该框架系列文章中的一篇,主要研究其中的数据结构。

AVFoundation

AVFoundation作为Cabbage的基础,了解其数据结构有助有后续的深入。对于这部分的熟练程度,决定了后面理解Cabbage的难易。下面通过类图的方式,和大家一起学习。

数据源

AVAsset是一个抽象类,代表了一个媒体资源,如一个视频或一个音频。通过该类,我们可以获取一个媒体资源的相关信息,如时长、轨道等。由于是一个抽象类,所以我们不会直接使用该类。

AVURLAssetAVAsset的一个子类,负责从指定 URL 中加载媒体资源。最常使用的也就是它。AVAsset提供的有一个便利构造+assetWithURL:,实际上返回的就是AVURLAsset的实例。

我们通过 URL 初始化一个AVAsset对象后,其大部分属性值并没有准备好,若访问了一个未准备好的属性(比如时长)会触发AVAsset去更新这些值,默认这个行为是会阻塞当前线程的。为了避免阻塞,我们需要通过AVAsynchronousKeyValueLoading协议去异步加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"duration"];

[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
    NSError *error = nil;
    AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"duration" error:&error];
    switch (tracksStatus) {
        case AVKeyValueStatusLoaded:
            [self updateUserInterfaceForDuration];
            break;
        case AVKeyValueStatusFailed:
            [self reportError:error forAsset:asset];
            break;
        case AVKeyValueStatusCancelled:
            // Do whatever is appropriate for cancelation.
            break;
   }
}];

AVComposition代表了一个媒体作品,它是由其他媒体资源组合而成。是不可变对象。在进行视频编辑时,我们使用它的可变子类AVMutableComposition。比如,资源级别的操作: -w445

轨道级别的操作: -w429 在插入轨道后,会返回一个AVMutableCompositionTrack对象,我们可以利用它编辑该轨道: -w313 可以看到在编辑轨道时,要求的数据也是轨道类型AVAssetTrack。它代表了一个音轨或一个视频轨。

导出、播放以及生成快照

有了数据,就可以进行接下来的操作了。比如导出、播放以及生成快照。依然通过类图的方式学习一下。

先看中间的AVAssetExportSession。它负责将一个AVAsset进行导出,在指定路径下生成媒体文件。在导出之前我们可以设置导出的质量、文件格式以及是否为网络传输进行优化等。导出过程中,可以通过progress获取导出进度,以及通过-cancelExport方法取消。

AVPlayerItem代表了一个播放源,可以送入AVPlayer进行播放。也许你会有个疑问:既然AVAsset已经可以指定一个媒体文件了,为什么不能直接使用它进行播放?这是因为为了支持,同一个媒体文件可以同时被多个播放器进行播放。AVPlayerItem存储了多个播放状态。 Playing the same asset in different ways

AVAssetImageGenerator是一个视频快照生成器,用于截取视频中特定时间(序列)的快照(序列)。

如果你想在这些过程中对视频或音频做自定义操作,那么就需要视频处理类(右边)和音频处理类(左边)的帮助了。

AVAudioMix是一个混音器,负责对音频进行调节。其中,一个AVAudioMixInputParameters具体负责一条音轨,通过trackID进行关联。默认情况下,我们可以使用它的可变子类进行音量调节,支持突变和渐变。若需要更复杂的操作,可以提供audioTapProcessor

AVVideoComposition是一个视频混合器配置,记录者视频画面合成的参数。比如设置帧的持续时长、画布的大小等。

AVVideoCompositionInstruction代表了对一个时间段内的进一步调节,通过timeRange进行关联。比如设置该时间段内画布的背景色;对于一个时间段,可能需要处理多个视频轨,这里通过AVVideoCompositionLayerInstruction代表对每一个视频轨的处理,通过trackID进行关联。可通过其可变子类AVMutableVideoCompositionLayerInstruction设置transformopacity或者crop

AVVideoCompositionCoreAnimationTool是对CoreAnimation的支持,可以将CALayer及其支持的动画引入到视频画面的合成当中来。使用它有两种方式,一是将Layer内容绘制到独立的轨道中参与画面合成;二是将视频帧绘制到Layer上,再使用Layer的内容生成视频帧。需要注意的是:这种方式只适用于导出,播放时需要使用AVSynchronizedLayer

AVVideoCompositing是一个视频混合器,它是一个协议,对视频画面的混合流程进行了规范。你可以提供自己的实现,接管多个轨道视频画面的混合。在需要进行混合时,它会收到-startVideoCompositionRequest:消息,并传递一个AVAsynchronousVideoCompositionRequestrequest 参数,通过这个参数,读取画布大小,轨道 id,每个轨道对应的视频画面,然后通过finishXXX完成一次混合。

小结

好了,视频编辑涉及到的类及其职责暂时到这里。总体来说,AVFoundation所支持的数据源单一,比较容易理解。但是在表示处理过程时,相对细致和复杂,毕竟这个过程就毕竟复杂。这里用一副图来进行一个小结:

Cabbage

Cabbage在数据的组织上进行了更高层次的抽象,体现了Swift面向协议的特点。

基础协议

  • TimeRangeProvider 定义了时间范围

视频相关

  • VideoCompositionTrackProvider 定义了一个视频作品中视频轨的来源
  • VideoCompositionProvider 定义了图像数据的处理节点,它只有一个方法applyEffect(to:at:renderSize:),接收源图像数据,并返回一个图像数据。但是这里的命名实在不理解 😂。
  • VideoProvider并没有定义新的功能,只是将TimeRangeProviderVideoCompositionTrackProviderVideoCompositionProvider三者累加。
  • TransitionableVideoProviderVideoProvider基础上继续累加,并添加了获取视频转场信息的能力。
  • VideoTransition 定义了视频转场的必要信息以及转场时画面渲染规范。
  • VideoConfigurationProtocol 也定义了图像数据的处理节点,和VideoCompositionProvider不同的是,它接收的参数多一个timeRange参数

音频相关

  • AudioCompositionTrackProvider定义了一个视频作品中音频轨的来源
  • AudioMixProvider定义了声音数据的处理节点,这里的命名依然不理解 😂。
  • AudioProvider并没有定义新的功能,只是将TimeRangeProviderAudioCompositionTrackProviderAudioMixProvider三者累加。
  • TransitionableAudioProviderAudioProvider基础上继续累加,并添加了获取音频转场信息的能力。
  • AudioTransition定义了音频转场的必要信息以及转场时声音渲染规范。
  • AudioProcessingNode定义了音频处理节点
  • AudioConfigurationProtocolAudioProcessingNodeNSCopying的累加,并没有添加其他功能

数据源

这里的数据源是指用于合成最终视频的原始资源媒体文件。AVFoundation中只支持AVAsset类型的资源,经过这里的抽象,Cabbage支持了图片等多种类型的原始资源。

首先,ResourceTrackInfoProvider协议定义了一个ResourceTrackInfo的来源。ResourceTrackInfo描述了从原始资源的track中选取的片段,以及在最终作品中的播放时长。

令我不解的是,这个协议还提供了另外一个获取图片的接口image(at:renderSize:)。似乎有点混乱。

其次,Resource是原始资源媒体的抽象基类,遵循ResourceTrackInfoProvider协议。它定义了原始资源的基本属性(如:时长、选中范围、缩放的时长、分辨率)和资源加载相关的方法。

基于这个抽象基类,其具体的子类代表不同的资源类型。如:

ImageResource 单帧图片类型

根据图片来源不同,又可以通过不同子类进行加载。如:

  • PHAssetImageResource 代表了来源于Phots框架中图片。
  • AVAssetReaderImageResource 代表了来源于AVAssetReader读取的图片,它支持AVVideoComposition配置。
  • AVAssetReverseImageResource 也代表了来源于AVAssetReader读取的图片,但是是倒放。

AVAssetTrackResource 多帧图片类型

这是AVFoundation中默认支持的类型。在此之上,根据图片来源不同,又通过不同子类进行加载。如:

  • PHAssetTrackResource 代表了PHImageManagerrequestAVAsset的方式。
  • PHAssetLivePhotoResource代表了PHImageManagerrequestLivePhoto的方式。

总体来说,Cabbage中组织数据源的方式就是这样,用一张类图来小结下:

中间状态存储

从原始资源到最终播放或导出的视频,中间可能需要多次修改相关配置,这些配置需要有地方存储,下面这些对象都是于此相关的。

TrackItem 表示可编辑的一段资源。该段资源的配置也存储在这里。比如视频画面的填充模式、渲染大小、transform、透明度以及自定义的遵循VideoConfigurationProtocol协议的配置列表,这些信息通过VideoConfiguration类存储,被TrackItem引用。音频支持音量配置以及自定义的遵循AudioConfigurationProtocol协议的配置列表。另外视频的转场信息(VideoTransition)和音频的转场信息(AudioTransition)也都存储在TrackItem类中。

Timeline是最终视频的代表,它由所有视频、音频的片段、浮层、配音等组合而成。还可以设置最终视频的画布大小以及背景颜色。

生产线

所有的资源及配置准备好后,会通过Timeline传入CompositionGenerator进行统一装配。形成最终的播放、导出以及快照实例。

前面在AVFoundation一节中提到过AVVideoCompositing协议,它是视频混合器。Cabbage实现了自己的混合器VideoCompositor,内部通过CoreImage对视频进行混合。实现自己的混合器是支持除AVAsset之外资源中的重要一环。

为了更方便的支持VideoCompositorCabbage还实现了自己的VideoCompositionInstructionVideoCompositionLayerInstruction分别对标AVFoundation中的AVVideoCompositionInstructionAVVideoCompositionLayerInstruction

音频处理方面,Cabbage实现了自己的MTAudioProcessingTap,通过AudioProcessingTapHolder进行管理。并通过AudioProcessingChainAudioProcessingNode支持了链式处理。

小结

可以看到,Cabbage从开始的数据源表示,到最后的合成处理设计的相对复杂。当然带来的好处也是显而易见的,就是容易扩展。下面附上相关类的总览图: cabbage-structure

总结

该篇对AVFoundationCabbage中视频编辑相关的类和职责进行了简单梳理,这对于后续的分析大有裨益。之后继续探索相关细节,共勉!

参考

  1. AVFoundation Programming Guide
  2. Cabbage