引言
在上一篇博客中,我们已经介绍了创建视频过渡的实现方案,步骤非常繁琐,在生成AVMutableVideoCompositionInstruction和AVMutableVideoCompositionLayerInstruction的计算也十分复杂,但其实还有一个创建视频组合的捷径。不过我们还是需要理解上一篇博客中我们所讨论的步骤,只有理解了那些步骤,才能发现学习这些对应的使用变得容易许多。
创建组合的捷径
AVVideoComposition定义了一个十分便捷的初始化方法init(propertiesOf asset: AVAsset),我们可以将AVCompostion作为参数来创建一个AVVideoComposition。该方法会为我们创建一个带有如下配置的AVVideoComposition对象:
- instructions 属性包含一组完整的基于组合视频轨道(以及其中包含的片段空间布局)的组合和层指令。
- renderSize 属性被设置为AVComposition对象的naturalSize,或者如果没有设置,则使用能够满足组合视频轨道中最大视频维度的尺寸值。
- frameDuration 设置为组合视频轨道中最大nominalFrameRate的值。如果所有轨道的nominalFrameRate值都为0,则frameDuration设置成默认1/30秒(30FPS)。
- renderScale 始终设置为1.0。
创建视频过渡
下面我们开始着手创建视频过渡,和之前的实现方案一样,先创建一个遵循PHComposition协议名为PHTransitionComposition的类,以及遵循PHCompositionBuilder协议名为PHTransitionCompositionBuilder的类。
PHTransitionComposition负责构建视频的可播放和可导出版本。
PHTransitionCompositionBuilder负责构建PHTransitionComposition,里面会创建用于视频编辑的AVMutableComposition,AVMutableAudioMix以及AVMutableVideoComposition。
PHTransitionComposition
代码实现如下:
Swift
import UIKit
import AVFoundation
class PHTransitionComposition: NSObject,PHComposition {
/// 组合轨道
var composition:AVMutableComposition!
/// 视频轨道
var videoComposition:AVMutableVideoComposition?
/// 音频混合
var audioMix:AVMutableAudioMix?
init(composition: AVMutableComposition!, videoComposition: AVMutableVideoComposition?, audioMix: AVMutableAudioMix?) {
self.composition = composition
self.videoComposition = videoComposition
self.audioMix = audioMix
}
func makePlayerItem() -> AVPlayerItem? {
let playerItem = AVPlayerItem(asset: composition.copy() as! AVAsset)
playerItem.videoComposition = videoComposition
playerItem.audioMix = audioMix
return playerItem
}
func makeAssetExportSession() -> AVAssetExportSession? {
let exportSession = AVAssetExportSession(asset: composition.copy() as! AVAsset, presetName: AVAssetExportPresetHighestQuality)
exportSession?.videoComposition = videoComposition
exportSession?.audioMix = audioMix
return exportSession!
}
}
代码较以前的类相比多了一个AVMutableVideoComposition属性,用来实现视频的过渡效果。
PHTransitionCompositionBuilder
该类中的代码和以往一样主要目的是构建PHComposition,分成三个部分,添加视频到组合轨道,添加音频到组合轨道,添加背景音乐到组合轨道。
Swift
let defaultTransitionDuration = CMTime(value: 2, timescale: 1)
class PHTransitionCompositionBuilder: NSObject,PHCompositionBuilder {
/// 资源模型
var timeLine:PHTimeLine!
/// 组合轨道
let composition = AVMutableComposition()
init(timeLine: PHTimeLine!) {
self.timeLine = timeLine
}
func buildComposition() -> PHComposition? {
// 添加视频到组合轨道
addVideoCompositionTrack()
// 创建AVVideoComposition
let videoComposition = buildVideoComposition()
// 添加音频到组合轨道
addAudioCompositionTrack()
// 添加背景音乐到组合轨道
let audioMix = addMusicCompositionTrack()
return PHTransitionComposition(composition: composition, videoComposition: videoComposition, audioMix: audioMix)
}
/// 添加视频到组合轨道
func addVideoCompositionTrack() {
....
}
/// 创建AVVideoComposition
func buildVideoComposition() -> AVMutableVideoComposition? {
....
}
/// 添加音频到组合轨道
func addAudioCompositionTrack() {
let _ = addCompositionTrack(mediaType: .audio, mediaItems: timeLine.audioItems)
}
/// 添加背景音乐到组合轨道
func addMusicCompositionTrack() -> AVMutableAudioMix?{
// 添加背景音乐
var audioMix:AVMutableAudioMix? = nil
if timeLine.musicItem != nil {
let musicCompositionTrack = addCompositionTrack(mediaType: .audio, mediaItems: [timeLine.musicItem!])
let musicAudioMix = buildAudioMixWithTrack(track: musicCompositionTrack)
audioMix = musicAudioMix
}
return audioMix
}
/// 私有方法-添加媒体资源轨道
/// - Parameters:
/// - mediaType: 媒体类型
/// - mediaItems: 媒体媒体资源数组
/// - Returns: 返回一个AVCompositionTrack
private func addCompositionTrack(mediaType:AVMediaType,mediaItems:[PHMediaItem]?) -> AVMutableCompositionTrack? {
if PHIsEmpty(array: mediaItems) {
return nil
}
let trackID = kCMPersistentTrackID_Invalid
guard let compositionTrack = composition.addMutableTrack(withMediaType: mediaType, preferredTrackID: trackID) else { return nil }
//设置起始时间
var cursorTime = CMTime.zero
guard let mediaItems = mediaItems else { return nil }
for item in mediaItems {
//这里默认时间都是从0开始
guard let asset = item.asset else { continue }
guard let assetTrack = asset.tracks(withMediaType: mediaType).first else { continue }
do {
try compositionTrack.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime)
} catch {
print("addCompositionTrack error")
}
cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)
}
return compositionTrack
}
/// 创建音频混合器
/// - Parameters:
/// - musicTrack: 音乐轨道
func buildAudioMixWithTrack(track:AVMutableCompositionTrack?) -> AVMutableAudioMix? {
guard let track = track else { return nil }
guard let musicItem = timeLine.musicItem else { return nil }
let audioMix = AVMutableAudioMix()
let audioMixParam = AVMutableAudioMixInputParameters(track: track)
for volumeAutomaition in musicItem.volumeAutomations {
audioMixParam.setVolumeRamp(fromStartVolume: volumeAutomaition.startVolume, toEndVolume: volumeAutomaition.endVolume, timeRange: volumeAutomaition.timeRange)
}
audioMix.inputParameters = [audioMixParam]
return audioMix
}
}
关于添加音频和背景音乐以及创建AVAudioMix的部分,我们在前面的博客中已经进行过介绍,在这里就不再重复解释了,把重点放到添加视频到组合轨道以及创建AVVideoComposition上。
下面看一下添加视频到组合轨道的实现:
Swift
/// 添加视频到组合轨道
func addVideoCompositionTrack() {
let compositionTrackA = self.composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let compositionTrackB = self.composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let videoTracks = [compositionTrackA,compositionTrackB]
let transitionDuration = defaultTransitionDuration
// 初始时间
var cursorTime = CMTime.zero
let videoItems = timeLine.videoItmes
for (index,item) in videoItems.enumerated() {
let trackIndex = index % 2
let currentTrack = videoTracks[trackIndex]
guard let asset = item.asset else { continue }
guard let assetTrack = asset.tracks(withMediaType: .video).first else { continue }
do {
try currentTrack?.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime)
} catch {
print("insertTimeRange error")
}
// 更新cursorTime
cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)
cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
}
}
- 首先从当前的AVMutableComposition中创建两个新的AVMutableCompositionTrack对象,两者都是.video类型,并添加到videoTracks数组中,提供所需的A-B轨道排列。
- 遍历视频资源,将视频资源交替插入到AB两个轨道中。
- 计算cursorTime,需要考虑到减去动画过程的时间,因为这段时间两个视频媒体是重叠的。
创建AVVideoComposition的实现如下:
Swift
/// 创建AVVideoComposition
func buildVideoComposition() -> AVMutableVideoComposition? {
let videoComposition = AVMutableVideoComposition(propertiesOf: self.composition)
videoComposition.renderSize = CGSize(width: 1920, height: 1080)
/// 获取instructions
let instructions = videoComposition.instructions
var layerInstructionIndex = 1
for instruction in instructions {
guard let videoCompositionInstruction = instruction as? AVMutableVideoCompositionInstruction else { continue }
let layerInstructions = videoCompositionInstruction.layerInstructions
// 判断是否有两个layerInstructions
guard layerInstructions.count == 2 else { continue }
// 创建过渡效果
let fromeLayerInstruction = layerInstructions[1 - layerInstructionIndex] as! AVMutableVideoCompositionLayerInstruction
let toLayerInstruction = layerInstructions[layerInstructionIndex] as! AVMutableVideoCompositionLayerInstruction
// 设置动画
fromeLayerInstruction.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: videoCompositionInstruction.timeRange)
layerInstructionIndex = layerInstructionIndex == 1 ? 0 : 1
}
return videoComposition
}
- 使用init(propertiesOf asset: AVAsset)创建一个新的AVVideoComposition实例,这个方法会自动创建所需的组合对象和层指令。并设置renderSize、renderScale、frameDuration熟悉为相应的值。(renderSize需要满足组合视频轨道中最大视频维度的尺寸值)
- 遍历videoComposition的instructions属性,判断videoCompositionInstruction的layerInstructions个数等于2的情况提取重叠的两个AVMutableVideoCompositionLayerInstruction。
- 为两个AVMutableVideoCompositionLayerInstruction添加一个渐变的过渡动画。
- 返回AVVideoComposition实例。
除了上面列出的最简单的渐隐过渡方式,还支持很多其它的过渡方式。
播放
创建PHTransitionCompositionBuilder实例,构建视频的可播放版本,进行播放。
Swift
func player() {
guard let delegate = self.delegate else { return }
let compositionBuilder = PHTransitionCompositionBuilder(timeLine: timeLine)
let composition = compositionBuilder.buildComposition()
let playerItem = composition?.makePlayerItem()
delegate.replaceCurrentItem(playerItem: playerItem)
}
结语
代码中我们把工作的重点集中到了A-B轨道的方式交替添加视频资源,以及构建AVMutableVideoComposition并创建过渡效果上面。但事实上我们还需要注意到播放进度的显示,创建过渡后两个视频之间会有重叠部分,在呈现的时候需要减去重叠时间。
另外最需要注意的是AVMutableVideoComposition的renderSize熟悉,一定要使用能够满足组合视频轨道中最大视频维度的尺寸值。