当前位置: 首页 > news >正文

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

引言

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

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

推入过渡效果:

            // 推入过渡效果let identityTransform = CGAffineTransform.identitylet videoWidth = videoComposition.renderSize.widthlet fromeTransform = CGAffineTransform(translationX: -videoWidth, y: 0)let toTransform = CGAffineTransform(translationX: videoWidth, y: 0)fromeLayerInstruction.setTransformRamp(fromStart: identityTransform, toEnd: fromeTransform, timeRange: videoCompositionInstruction.timeRange)toLayerInstruction.setTransformRamp(fromStart: toTransform, toEnd: identityTransform, timeRange: videoCompositionInstruction.timeRange)

擦除过渡效果:

            // 擦除过渡效果let width = videoComposition.renderSize.widthlet height = videoComposition.renderSize.heightlet startRect = CGRect(x: 0, y: 0, width: width, height: height)let endRect = CGRect(x: width, y: height, width: width, height: 0.0)fromeLayerInstruction.setCropRectangleRamp(fromStartCropRectangle: startRect, toEndCropRectangle: endRect, timeRange: videoCompositionInstruction.timeRange)

播放

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

相关文章:

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

引言 在上一篇博客中,我们已经介绍了创建视频过渡的实现方案,步骤非常繁琐,在生成AVMutableVideoCompositionInstruction和AVMutableVideoCompositionLayerInstruction的计算也十分复杂,但其实还有一个创建视频组合的捷径。不过我…...

Android11 InputManagerService启动流程分析

InputManagerService在systemserver进程中被启动 //frameworks\base\services\java\com\android\server\SystemServer.java t.traceBegin("StartInputManagerService"); inputManager new InputManagerService(context);//1 t.traceEnd(); //省略 //注册服务 Servi…...

【计算机网络篇】数据链路层(8)共享式以太网的退避算法和信道利用率

文章目录 🛸共享式以太网的退避算法🥚截断二进制指数算法 🍔共享式以太网的信道利用率 🛸共享式以太网的退避算法 在使用CSMA/CD协议的共享总线以太网中,正在发送帧的站点一边发送帧一边检测碰撞,当检测到…...

wordpress主题 7B2 PRO主题5.4.2免授权直接安装

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 WordPress 资讯、资源、社交、商城、圈子、导航等多功能商用主题:B2 PRO 其设计风格专业且时尚,功能十分强大,包括多栏布局、自定义页面、强大的主…...

Dubbo基本使用

Dubbo基本使用 1.项目介绍2.开发步骤2.1 启动注册中心2.2 初始化项目2.3 添加 Maven 依赖2.3.1 父pom.xml2.3.1 consumer模块和provider模块pom.xml 2.4 定义服务接口2.5 定义服务端的实现2.6 配置服务端 Yaml 配置文件2.7 配置消费端 Yaml 配置文件2.8 基于 Spring 配置服务端…...

JS解密之新js加密实战(二)

前言 上次发了一篇关于新加密的,只解了前边两层,这中间家里各种事情因素影响,没有继续进一步研究,今天百忙之中抽空发布第二篇,关于其中的一小段加密片段,我认为分割成多个小片段是更容易被理解的。逻辑相…...

tsconfig 备忘清单

前言 ❝ Nealyang/blog0 使用 ts 已多年,但是貌似对于 tsconfig 总是记忆不清,每次都是 cv 历史项目,所以写了这篇备忘录,希望能帮助到大家。 本文总结整理自 Matt Pocock 的一篇文章3,加以个人理解,并做了…...

jmeter后置处理器提取到的参数因为换行符导致json解析错误

现象: {"message":"JSON parse error: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Ill…...

栈与队列的实现

前言 本次博客将要实现一下栈和队列,好吧 他们两个既可以使用动态数组也可以使用链表来实现 本次会有详细的讲解 栈的实现 栈的基础知识 什么是栈呢? 栈的性质是后进先出 来画个图来理解 当然可不可以出一个进一个呢,当然可以了 比如…...

线性集合:ArrayList,LinkedList,Vector/Stack

共同点:都是线性集合 ArrayList ArrayList 底层是基于数组实现的,并且实现了动态扩容(当需要添加新元素时,如果 elementData 数组已满,则会自动扩容,新的容量将是原来的 1.5 倍),来…...

llama3 发布!大语言模型新选择 | 开源日报 No.251

meta-llama/llama Stars: 53.0k License: NOASSERTION llama 是用于 Llama 模型推理的代码。 提供了预训练和微调的 Llama 语言模型,参数范围从 7B 到 70B。可以通过下载脚本获取模型权重和 tokenizer。支持在本地快速运行推理,并提供不同规格的模型并…...

SpringBoot 具体是做什么的?

Spring Boot是一个用于构建独立的、生产级别的、基于Spring框架的应用程序的开源框架。它的目标是简化Spring应用程序的开发和部署过程,通过提供一种快速、便捷的方式来创建Spring应用程序,同时保持Spring的灵活性和强大特性。 1. 简化Spring应用程序开…...

Debian常用命令

Debian是一个开源的Unix-like操作系统,提供了大量的软件包供用户安装和使用。在Debian系统中,命令行界面(CLI)是用户与系统进行交互的重要工具。以下是Debian中一些常用的命令及其详细解释: 文件和目录操作命令&#x…...

常见的前端框架

常用的前端框架有以下几种: 模型 React:由Facebook开发的一款前端框架,采用虚拟DOM的概念,可高效地更新页面。Vue.js:一款轻量级的前端框架,易学易用,支持组件化开发和双向数据绑定。AngularJ…...

初学者如何选择ARM开发硬件?

在开始前我有一些资料,是我根据网友给的问题精心整理了一份「ARM的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!如果你没有ARM开发经验&#xff0…...

Mysql 多表查询,内外连接

内连接: 隐式内连接 使用sql语句直接进行多表查询 select 字段列表 from 表1 , 表2 where 条件 … ; 显式内连接 将‘,’改为 inner join 连接两个表的 on select 字段列表 from 表1 [ inner ] join 表2 on 连接条件 … ; select emp.id, emp.name, …...

【C语言】函数

目录 一、函数的概念 二、库函数 2.1 ❥ 标准库 2.2 ❥ 库函数的使用方法 三、自定义函数 四、形参和实参 4.1 ❥ 实参(实际参数) 4.2 ❥ 形参(形式参数) 五、return语句 六、函数的调用 6.1 ❥ 传值调用 6.2 ❥ 传址调…...

【LeetCode】每日一题 2024_5_13 腐烂的橘子(经典多源 BFS)

文章目录 LeetCode?启动!!!题目:找出不同元素数目差数组题目描述代码与解题思路 每天进步一点点 LeetCode?启动!!! 好久没写每日一题题解了,今天重新起航 干…...

【Linux系统编程】第十七弹---进程理解

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、进程的基本概念 2、描述进程-PCB 2.1、什么是PCB 2.2、为什么要有PCB 3、task_ struct 3.1、启动进程 3.2、创建进程…...

【网络安全入门】你必须要有的学习工具(附安装包)零基础入门到进阶,看这一篇就够了!

工欲善其事必先利其器 在新入门网络安全的小伙伴而言。这些工具你必须要有所了解。本文我们简单说说这些网络安全工具吧! Web安全类 Web类工具主要是通过各种扫描工具,发现web站点存在的各种漏洞如sql注入、xss等。从而获取系统权限,常用的…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

在rocky linux 9.5上在线安装 docker

前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...