• 七.音视频编辑-创建视频过渡-应用


    引言

    在上一篇博客中,我们已经介绍了创建视频过渡的实现方案,步骤非常繁琐,在生成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

    代码实现如下:

    1. import UIKit
    2. import AVFoundation
    3. class PHTransitionComposition: NSObject,PHComposition {
    4. /// 组合轨道
    5. var composition:AVMutableComposition!
    6. /// 视频轨道
    7. var videoComposition:AVMutableVideoComposition?
    8. /// 音频混合
    9. var audioMix:AVMutableAudioMix?
    10. init(composition: AVMutableComposition!, videoComposition: AVMutableVideoComposition?, audioMix: AVMutableAudioMix?) {
    11. self.composition = composition
    12. self.videoComposition = videoComposition
    13. self.audioMix = audioMix
    14. }
    15. func makePlayerItem() -> AVPlayerItem? {
    16. let playerItem = AVPlayerItem(asset: composition.copy() as! AVAsset)
    17. playerItem.videoComposition = videoComposition
    18. playerItem.audioMix = audioMix
    19. return playerItem
    20. }
    21. func makeAssetExportSession() -> AVAssetExportSession? {
    22. let exportSession = AVAssetExportSession(asset: composition.copy() as! AVAsset, presetName: AVAssetExportPresetHighestQuality)
    23. exportSession?.videoComposition = videoComposition
    24. exportSession?.audioMix = audioMix
    25. return exportSession!
    26. }
    27. }

    代码较以前的类相比多了一个AVMutableVideoComposition属性,用来实现视频的过渡效果

    PHTransitionCompositionBuilder

    该类中的代码和以往一样主要目的是构建PHComposition,分成三个部分,添加视频到组合轨道,添加音频到组合轨道,添加背景音乐到组合轨道。

    1. let defaultTransitionDuration = CMTime(value: 2, timescale: 1)
    2. class PHTransitionCompositionBuilder: NSObject,PHCompositionBuilder {
    3. /// 资源模型
    4. var timeLine:PHTimeLine!
    5. /// 组合轨道
    6. let composition = AVMutableComposition()
    7. init(timeLine: PHTimeLine!) {
    8. self.timeLine = timeLine
    9. }
    10. func buildComposition() -> PHComposition? {
    11. // 添加视频到组合轨道
    12. addVideoCompositionTrack()
    13. // 创建AVVideoComposition
    14. let videoComposition = buildVideoComposition()
    15. // 添加音频到组合轨道
    16. addAudioCompositionTrack()
    17. // 添加背景音乐到组合轨道
    18. let audioMix = addMusicCompositionTrack()
    19. return PHTransitionComposition(composition: composition, videoComposition: videoComposition, audioMix: audioMix)
    20. }
    21. /// 添加视频到组合轨道
    22. func addVideoCompositionTrack() {
    23. ....
    24. }
    25. /// 创建AVVideoComposition
    26. func buildVideoComposition() -> AVMutableVideoComposition? {
    27. ....
    28. }
    29. /// 添加音频到组合轨道
    30. func addAudioCompositionTrack() {
    31. let _ = addCompositionTrack(mediaType: .audio, mediaItems: timeLine.audioItems)
    32. }
    33. /// 添加背景音乐到组合轨道
    34. func addMusicCompositionTrack() -> AVMutableAudioMix?{
    35. // 添加背景音乐
    36. var audioMix:AVMutableAudioMix? = nil
    37. if timeLine.musicItem != nil {
    38. let musicCompositionTrack = addCompositionTrack(mediaType: .audio, mediaItems: [timeLine.musicItem!])
    39. let musicAudioMix = buildAudioMixWithTrack(track: musicCompositionTrack)
    40. audioMix = musicAudioMix
    41. }
    42. return audioMix
    43. }
    44. /// 私有方法-添加媒体资源轨道
    45. /// - Parameters:
    46. /// - mediaType: 媒体类型
    47. /// - mediaItems: 媒体媒体资源数组
    48. /// - Returns: 返回一个AVCompositionTrack
    49. private func addCompositionTrack(mediaType:AVMediaType,mediaItems:[PHMediaItem]?) -> AVMutableCompositionTrack? {
    50. if PHIsEmpty(array: mediaItems) {
    51. return nil
    52. }
    53. let trackID = kCMPersistentTrackID_Invalid
    54. guard let compositionTrack = composition.addMutableTrack(withMediaType: mediaType, preferredTrackID: trackID) else { return nil }
    55. //设置起始时间
    56. var cursorTime = CMTime.zero
    57. guard let mediaItems = mediaItems else { return nil }
    58. for item in mediaItems {
    59. //这里默认时间都是从0开始
    60. guard let asset = item.asset else { continue }
    61. guard let assetTrack = asset.tracks(withMediaType: mediaType).first else { continue }
    62. do {
    63. try compositionTrack.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime)
    64. } catch {
    65. print("addCompositionTrack error")
    66. }
    67. cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)
    68. }
    69. return compositionTrack
    70. }
    71. /// 创建音频混合器
    72. /// - Parameters:
    73. /// - musicTrack: 音乐轨道
    74. func buildAudioMixWithTrack(track:AVMutableCompositionTrack?) -> AVMutableAudioMix? {
    75. guard let track = track else { return nil }
    76. guard let musicItem = timeLine.musicItem else { return nil }
    77. let audioMix = AVMutableAudioMix()
    78. let audioMixParam = AVMutableAudioMixInputParameters(track: track)
    79. for volumeAutomaition in musicItem.volumeAutomations {
    80. audioMixParam.setVolumeRamp(fromStartVolume: volumeAutomaition.startVolume, toEndVolume: volumeAutomaition.endVolume, timeRange: volumeAutomaition.timeRange)
    81. }
    82. audioMix.inputParameters = [audioMixParam]
    83. return audioMix
    84. }
    85. }

    关于添加音频和背景音乐以及创建AVAudioMix的部分,我们在前面的博客中已经进行过介绍,在这里就不再重复解释了,把重点放到添加视频到组合轨道以及创建AVVideoComposition上。

    下面看一下添加视频到组合轨道的实现:

    1. /// 添加视频到组合轨道
    2. func addVideoCompositionTrack() {
    3. let compositionTrackA = self.composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    4. let compositionTrackB = self.composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    5. let videoTracks = [compositionTrackA,compositionTrackB]
    6. let transitionDuration = defaultTransitionDuration
    7. // 初始时间
    8. var cursorTime = CMTime.zero
    9. let videoItems = timeLine.videoItmes
    10. for (index,item) in videoItems.enumerated() {
    11. let trackIndex = index % 2
    12. let currentTrack = videoTracks[trackIndex]
    13. guard let asset = item.asset else { continue }
    14. guard let assetTrack = asset.tracks(withMediaType: .video).first else { continue }
    15. do {
    16. try currentTrack?.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime)
    17. } catch {
    18. print("insertTimeRange error")
    19. }
    20. // 更新cursorTime
    21. cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)
    22. cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
    23. }
    24. }
    1. 首先从当前的AVMutableComposition中创建两个新的AVMutableCompositionTrack对象,两者都是.video类型,并添加到videoTracks数组中,提供所需的A-B轨道排列。
    2. 遍历视频资源,将视频资源交替插入到AB两个轨道中。
    3. 计算cursorTime,需要考虑到减去动画过程的时间,因为这段时间两个视频媒体是重叠的。

    创建AVVideoComposition的实现如下:

    1. /// 创建AVVideoComposition
    2. func buildVideoComposition() -> AVMutableVideoComposition? {
    3. let videoComposition = AVMutableVideoComposition(propertiesOf: self.composition)
    4. videoComposition.renderSize = CGSize(width: 1920, height: 1080)
    5. /// 获取instructions
    6. let instructions = videoComposition.instructions
    7. var layerInstructionIndex = 1
    8. for instruction in instructions {
    9. guard let videoCompositionInstruction = instruction as? AVMutableVideoCompositionInstruction else { continue }
    10. let layerInstructions = videoCompositionInstruction.layerInstructions
    11. // 判断是否有两个layerInstructions
    12. guard layerInstructions.count == 2 else { continue }
    13. // 创建过渡效果
    14. let fromeLayerInstruction = layerInstructions[1 - layerInstructionIndex] as! AVMutableVideoCompositionLayerInstruction
    15. let toLayerInstruction = layerInstructions[layerInstructionIndex] as! AVMutableVideoCompositionLayerInstruction
    16. // 设置动画
    17. fromeLayerInstruction.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: videoCompositionInstruction.timeRange)
    18. layerInstructionIndex = layerInstructionIndex == 1 ? 0 : 1
    19. }
    20. return videoComposition
    21. }
    1. 使用init(propertiesOf asset: AVAsset)创建一个新的AVVideoComposition实例,这个方法会自动创建所需的组合对象和层指令。并设置renderSize、renderScale、frameDuration熟悉为相应的值。(renderSize需要满足组合视频轨道中最大视频维度的尺寸值)
    2. 遍历videoComposition的instructions属性,判断videoCompositionInstruction的layerInstructions个数等于2的情况提取重叠的两个AVMutableVideoCompositionLayerInstruction。
    3. 为两个AVMutableVideoCompositionLayerInstruction添加一个渐变的过渡动画。
    4. 返回AVVideoComposition实例。

    除了上面列出的最简单的渐隐过渡方式,还支持很多其它的过渡方式。

    推入过渡效果:

    1. // 推入过渡效果
    2. let identityTransform = CGAffineTransform.identity
    3. let videoWidth = videoComposition.renderSize.width
    4. let fromeTransform = CGAffineTransform(translationX: -videoWidth, y: 0)
    5. let toTransform = CGAffineTransform(translationX: videoWidth, y: 0)
    6. fromeLayerInstruction.setTransformRamp(fromStart: identityTransform, toEnd: fromeTransform, timeRange: videoCompositionInstruction.timeRange)
    7. toLayerInstruction.setTransformRamp(fromStart: toTransform, toEnd: identityTransform, timeRange: videoCompositionInstruction.timeRange)

    擦除过渡效果:

    1. // 擦除过渡效果
    2. let width = videoComposition.renderSize.width
    3. let height = videoComposition.renderSize.height
    4. let startRect = CGRect(x: 0, y: 0, width: width, height: height)
    5. let endRect = CGRect(x: width, y: height, width: width, height: 0.0)
    6. fromeLayerInstruction.setCropRectangleRamp(fromStartCropRectangle: startRect, toEndCropRectangle: endRect, timeRange: videoCompositionInstruction.timeRange)

    播放

    创建PHTransitionCompositionBuilder实例,构建视频的可播放版本,进行播放。

    1. func player() {
    2. guard let delegate = self.delegate else { return }
    3. let compositionBuilder = PHTransitionCompositionBuilder(timeLine: timeLine)
    4. let composition = compositionBuilder.buildComposition()
    5. let playerItem = composition?.makePlayerItem()
    6. delegate.replaceCurrentItem(playerItem: playerItem)
    7. }

    结语

    代码中我们把工作的重点集中到了A-B轨道的方式交替添加视频资源,以及构建AVMutableVideoComposition并创建过渡效果上面。但事实上我们还需要注意到播放进度的显示,创建过渡后两个视频之间会有重叠部分,在呈现的时候需要减去重叠时间。

    需要注意其它轨道的长度需要小于视频组合轨道的轨道长度。

    另外最需要注意的是AVMutableVideoComposition的renderSize熟悉,一定要使用能够满足组合视频轨道中最大视频维度的尺寸值。

  • 相关阅读:
    Hadoop集群搭建
    详解VQVAE:Neural Discrete Representation Learning
    Java项目如何实现限流?
    【教3妹学算法】按照频率将数组升序排序
    IPWorks WebSockets Delphi版
    风险洞察之事件总线的探索与演进
    Java面试题-线程
    Docker V24 及 Docker Compose V2 的安装及使用
    SQL标识列实现自动编号的步骤和技巧以及优势
    宋明的结局揭示了什么
  • 原文地址:https://blog.csdn.net/weixin_39339407/article/details/138607037