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

引言

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

代码实现如下:

import UIKit
import AVFoundationclass PHTransitionComposition: NSObject,PHComposition {/// 组合轨道var composition:AVMutableComposition!/// 视频轨道var videoComposition:AVMutableVideoComposition?/// 音频混合var audioMix:AVMutableAudioMix?init(composition: AVMutableComposition!, videoComposition: AVMutableVideoComposition?, audioMix: AVMutableAudioMix?) {self.composition = compositionself.videoComposition = videoCompositionself.audioMix = audioMix}func makePlayerItem() -> AVPlayerItem? {let playerItem = AVPlayerItem(asset: composition.copy() as! AVAsset)playerItem.videoComposition = videoCompositionplayerItem.audioMix = audioMixreturn playerItem}func makeAssetExportSession() -> AVAssetExportSession? {let exportSession = AVAssetExportSession(asset: composition.copy() as! AVAsset, presetName: AVAssetExportPresetHighestQuality)exportSession?.videoComposition = videoCompositionexportSession?.audioMix = audioMixreturn exportSession!}}

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

PHTransitionCompositionBuilder

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

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()// 创建AVVideoCompositionlet videoComposition = buildVideoComposition()// 添加音频到组合轨道addAudioCompositionTrack()// 添加背景音乐到组合轨道let audioMix = addMusicCompositionTrack()return PHTransitionComposition(composition: composition, videoComposition: videoComposition, audioMix: audioMix)}/// 添加视频到组合轨道func addVideoCompositionTrack() {....}/// 创建AVVideoCompositionfunc buildVideoComposition() -> AVMutableVideoComposition? {....}/// 添加音频到组合轨道func addAudioCompositionTrack() {let _ = addCompositionTrack(mediaType: .audio, mediaItems: timeLine.audioItems)}/// 添加背景音乐到组合轨道func addMusicCompositionTrack() -> AVMutableAudioMix?{// 添加背景音乐var audioMix:AVMutableAudioMix? = nilif timeLine.musicItem != nil {let musicCompositionTrack =  addCompositionTrack(mediaType: .audio, mediaItems: [timeLine.musicItem!])let musicAudioMix = buildAudioMixWithTrack(track: musicCompositionTrack)audioMix = musicAudioMix}return audioMix}/// 私有方法-添加媒体资源轨道/// - Parameters:///  - mediaType: 媒体类型///  - mediaItems: 媒体媒体资源数组///  - Returns: 返回一个AVCompositionTrackprivate func addCompositionTrack(mediaType:AVMediaType,mediaItems:[PHMediaItem]?) -> AVMutableCompositionTrack? {if PHIsEmpty(array: mediaItems) {return nil}let trackID = kCMPersistentTrackID_Invalidguard let compositionTrack = composition.addMutableTrack(withMediaType: mediaType, preferredTrackID: trackID) else { return nil }//设置起始时间var cursorTime = CMTime.zeroguard 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上。

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

    /// 添加视频到组合轨道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.zerolet videoItems = timeLine.videoItmesfor (index,item) in videoItems.enumerated() {let trackIndex = index % 2let 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")}// 更新cursorTimecursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)cursorTime = CMTimeSubtract(cursorTime, transitionDuration)}}
  1. 首先从当前的AVMutableComposition中创建两个新的AVMutableCompositionTrack对象,两者都是.video类型,并添加到videoTracks数组中,提供所需的A-B轨道排列。
  2. 遍历视频资源,将视频资源交替插入到AB两个轨道中。
  3. 计算cursorTime,需要考虑到减去动画过程的时间,因为这段时间两个视频媒体是重叠的。

创建AVVideoComposition的实现如下:

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

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

播放

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

 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熟悉,一定要使用能够满足组合视频轨道中最大视频维度的尺寸值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/9346.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Github 学习使用随手记

1. GitHub 中使用仓库 在 GitHub 上,每个项目都由一个唯一的仓库来管理。仓库名是项目的标识,用于区分不同的项目。 仓库名由以下部分组成: 用户名或组织名:这是项目的创建者或所有者的用户名或组织名。项目名:这是…

Java基于Spring Boot框架的校园外卖服务系统设计与实现(附源码,说明文档)

博主介绍:✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇&#x1f3…

typescript 命名空间、装饰器

1、命名空间 命名空间:在代码量较大的情况下,为了避免各种变量命名的冲突,可将相似功能的函数、类、接口等放置到命名空间内。同Java的包.Net的命名空间一样,typescript 的命名空间可以将代码包裹起来,只对外暴露需要在…

第二篇【AI与传奇开心果系列】Python的AI技术点库案例示例:详解AI工业应用算法原理

AI与传奇开心果系列博文 系列博文目录Python的AI技术点库案例示例系列 博文目录前言一、AI工业应用算法原理介绍二、机器学习在工业领域的应用算法示例代码三、深度学习算法在工业领域应用示例代码四、强化学习在工业领域应用示例代码五、自然语言处理在工业领域应用示例代码六…

9. Django Admin后台系统

9. Admin后台系统 Admin后台系统也称为网站后台管理系统, 主要对网站的信息进行管理, 如文字, 图片, 影音和其他日常使用的文件的发布, 更新, 删除等操作, 也包括功能信息的统计和管理, 如用户信息, 订单信息和访客信息等. 简单来说, 它是对网站数据库和文件进行快速操作和管…

Hive多字节分隔符处理

Hive多字节分隔符处理 1.Hive分隔符默认规则 2.问题与需求 3.解决方案 (1)替换分隔符 (2)RegeSerDe正则加载 (3)自定义InputFormat

SAP-ABAP-ALV报表

1、功能介绍 什么是ALV报表 2、开发步骤 事物码: SE38创建报表程序SE93生成TCODESE43SE91消息号报表类型:简单报表、复杂报表、报表树 报表组成:查询界面(选择屏幕)、展示界面、功能按钮 基本流程: 重点:数据放在内表里,临时表,放在内存里。 3、开发实战 按标题…

科技早报|商务部回应美撤销部分企业对华为出口许可证;百度公关副总裁璩静道歉;微软关闭旗下四家游戏工作室 | 最新快讯

商务部回应美撤销部分企业对华为出口许可证 5月8日,据环球网报道,拜登政府已于7日当天进一步收紧了对华为的出口限制,撤销了美国芯片企业高通和英特尔公司向华为出售半导体的许可证。这一行动也得到了美国商务部的证实。其表示已“撤销了对华…

【Linux系统编程】31.pthread_detach、线程属性

目录 pthread_detach 参数pthread 返回值 测试代码1 测试结果 pthread_attr_init 参数attr 返回值 pthread_attr_destroy 参数attr 返回值 pthread_attr_setdetachstate 参数attr 参数detachstate 返回值 测试代码2 测试结果 线程使用注意事项 pthread_deta…

使用React高阶组件

高阶组件个人理解其实就是抽离公共逻辑,高阶组件英语全称为 Higher-Order Components,简称 HOC,所谓高阶组件,是 React中一种复用逻辑的技巧。高阶组件是一个函数,高阶组件作为一个函数,接收你传入的组件&a…

百度地图API 快速入门

一、创建一个应用 创建成功可以在应用程序中查看到自己的ak密钥 二、基本使用 2.1 显示地图 在static下创建demo1.html &#xff08;将密钥换成自己的就可以显示地图了&#xff09; 示例&#xff1a; <!DOCTYPE html> <html> <head><meta name"…

顺序表的实现(迈入数据结构的大门)(2)

目录 顺序表的头插(SLPushFront) 此时&#xff1a;我们有两个思路&#xff08;数组移位&#xff09; 顺序表的头删(学会思维的变换)(SLPopFront) 顺序表的尾插(SLPushBack) 有尾插就有尾删 既然头与尾部的插入与删除都有&#xff0c;那必然少不了指定位置的插入删除 查找…

特征提取与深度神经网络(二)

关键点/角点检测 2011论文-ORB关键点检测&#xff0c;比SIFT与SURF速度更快。 ORB算法可以看出两个部分组成&#xff1a;快速关键点定位BRIEF描述子生成 Fast关键点检测&#xff1a; 选择当前像素点P&#xff0c;阈值T&#xff0c;周围16个像素点&#xff0c;超过连续N12个像素…

Flutter笔记:Widgets Easier组件库 - 使用标签(Tag)

Flutter笔记 Widgets Easier组件库 - 使用标签&#xff08;Tag&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this …

电路板维修【一】

最近喜欢上了电路板维修&#xff0c;经常看很多博主的维修视频&#xff0c;觉得还是颇有收获的&#xff08;维修板子原来有方法可循&#xff09;&#xff0c;于是做笔记如下&#xff1a; 一.【修了半天也没找到问题&#xff0c;原来是检查方向错了&#xff0c;变频油烟机板维修…

LifeCycle之ProcessLifeCycleOwner

问题&#xff1a;想要知道应用程序当前处在前台、后台、或从后台回到前台&#xff0c;想要知道应用的状态&#xff0c; LifeCycle提供了ProcessLifeCycleOwner的类&#xff0c;方便我们知道整个应用程序的生命周期情况 ProcessLifeCycleOwner 使用方法 1.首先添加依赖 imple…

ai电话机器人的操作难吗?智能电销系统部署

人工智能出现的目的&#xff0c;就是造福方便人类的生活&#xff0c;电话机器人的使用只需要通上电&#xff0c;登录后台在后台中导入客户的电话号码并设定好工作时间即可&#xff0c;其余的只需要完全交给机器人来做就行了&#xff0c;什么时候需要知道客户的筛选结果&#xf…

如何设置内网打印机端口网络穿透到公网

打印机是当前公司企业办公输出纸质文件处理过程中必不可少的工具设备。出差在外&#xff0c;我们经常会面对需要远程使用公司内部打印机复印或打印各种文件资料的情况&#xff0c;或不在家又需要远程访问家里打印机进行打印的情况。这时候&#xff0c;就必须学会远程打印的方法…

C++类和对象(三) 缺省值 | static成员 | 内部类

前言&#xff1a; 这是关于类和对象的最后一篇文章&#xff0c;当然还是基础篇的最后一篇&#xff0c;因为类的三大特性继承&#xff0c;封装和多态都还没有讲&#xff0c;少年&#xff0c;慢慢来。 缺省值&#xff1a; 之前讲过&#xff0c;在C11的新标准中&#xff0c;支持为…

vue3 - 图灵

目录 vue3简介整体上认识vue3项目创建Vue3工程使用官方脚手架创建Vue工程[推荐] 主要⼯程结构 数据双向绑定vue2语法的双向绑定简单表单双向绑定复杂表单双向绑定 CompositionAPI替代OptionsAPICompositionAPI简单不带双向绑定写法CompositionAPI简单带双向绑定写法setup简写⽅…