iOS音视频编辑系列-视频过渡动画

在上一篇的结尾我们提到,现在两个视频之间是没有过渡动画的,一般的视频编辑软件都会提供一些片段之间的过渡动画,这一篇我们也来简单给两个视频加上几个过渡动画。

相关类

在视频合成的基础上,本节内容又会增加几个新的类,而且理解上比较有挑战。

AVVideoComposition

这个类和AVComposition完全没有关系,不是继承至avasset,所有不能直接播放,主要回来管理视频渲染相关的内容。比如render的size,scale等。当然还有一个比较重要也就是我们这节需要用到的instructions属性,该属性是一个AVMutableVideoCompositionInstruction对象的数组。这些对象存储了渲染过程中的一些指令。

AVMutableVideoCompositionInstruction && AVMutableVideoCompositionLayerInstruction

AVMutableVideoCompositionInstruction类存储了一个视频的过渡动画相关信息,其中比较重要的是layerInstructions,这是一个存储AVMutableVideoCompositionLayerInstruction对象的数组,一个AVMutableVideoCompositionLayerInstruction对象表示一个具体的转场效果。

具体编码

基于上一节的代码,我们给01和02视频之间添加一些过渡动画。

step0 创建两个videotrack,

1
2
3
4
5
6
/// compposition
let composition = AVMutableComposition.init()
/// video track
let videoTrack01 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let videoTrack02 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)

step1 给videotrack插入视频片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//将01.mp4插入videoTrack01
let track01 = asset01.tracks(withMediaType: .video).first
do {
try videoTrack01?.insertTimeRange(videoTimeRange, of: track01!, at: cursorTime)
} catch let error as NSError {
print("error when adding video to mix = \(error)")
}
//将02.mp4插入videotrack02
cursorTime = CMTimeAdd(cursorTime, videoDuration)
//这里需要预留转场动画的时间
cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
let track02 = asset02.tracks(withMediaType: .video).first
do {
try videoTrack02?.insertTimeRange(videoTimeRange, of: track02!, at: cursorTime)
} catch let error as NSError {
print("error when adding video to mix = \(error)")
}

step2 获取默认过渡对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let videoComposition = AVVideoComposition.init(propertiesOf: composition)
let compositionInstructions = videoComposition.instructions
var fromLayer : AVMutableVideoCompositionLayerInstruction?
var toLayer : AVMutableVideoCompositionLayerInstruction?
let fromTimeRange : CMTimeRange?
var toTimeRange : CMTimeRange?
let instruction01 : AVMutableVideoCompositionInstruction?
var instruction02 : AVMutableVideoCompositionInstruction?
// 01.mp4的默认转场
if let first = compositionInstructions[0] as? AVMutableVideoCompositionInstruction {
instruction01 = first
fromLayer = first.layerInstructions.first! as! AVMutableVideoCompositionLayerInstruction
fromTimeRange = first.timeRange
}
// 02.mp4的默认转场
if let second = compositionInstructions[1] as? AVMutableVideoCompositionInstruction {
instruction02 = second
fromLayer = second.layerInstructions.first! as! AVMutableVideoCompositionLayerInstruction
toLayer = second.layerInstructions.last! as! AVMutableVideoCompositionLayerInstruction
toTimeRange = second.timeRange
}

step3 自定义过渡效果

默认转场是没有添加任何效果的,这里我们来加一些自己的过渡效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 渐隐
fromLayer!.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: toTimeRange!)
toLayer?.setOpacityRamp(fromStartOpacity: 0.0, toEndOpacity: 1.0, timeRange: toTimeRange!)
if let fl = fromLayer,let tl = toLayer {
instruction02?.layerInstructions = [fl,tl]
}
//擦除
let videoWidth = videoComposition.renderSize.width
let videoHeight = videoComposition.renderSize.height
let startRect = CGRect(x: 0, y: 0, width: videoWidth, height: videoHeight)
let endRect = CGRect(x: 0, y: videoHeight, width: videoWidth, height: 0)
fromLayer?.setCropRectangleRamp(fromStartCropRectangle: startRect, toEndCropRectangle: endRect, timeRange: toTimeRange!)
if let fl = fromLayer,let tl = toLayer {
instruction02?.layerInstructions = [fl,tl]
}
//渐入
let identityTransform = CGAffineTransform.identity
let videoWidth = videoComposition.renderSize.width
let fromTransform = CGAffineTransform.init().translatedBy(x: -videoWidth, y: 0)
let toTransform = CGAffineTransform.init().translatedBy(x: videoWidth, y: 0)
fromLayer?.setTransformRamp(fromStart: identityTransform, toEnd: fromTransform, timeRange: toTimeRange!)
toLayer?.setTransformRamp(fromStart: toTransform, toEnd: identityTransform, timeRange: toTimeRange!)
if let fl = fromLayer,let tl = toLayer {
instruction02?.layerInstructions = [fl,tl]
}

step4 预览

1
2
//将videoComposition赋值playerItem,其他和上一节一致。
self.playerItem?.videoComposition = videoComposition

step5 导出

1
2
//将videoComposition赋值exportSession,其他和上一节一致。
exportSession?.videoComposition = videoComposition

经过以上步骤,简单的视频过渡效果就完成了,这里只是实现了几个比较基础的效果,更多内容可以后续在扩展。