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

引言

在上一篇博客中,我们已经介绍了创建视频过渡的实现方案,步骤非常繁琐,在生成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 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,分成三个部分,添加视频到组合轨道,添加音频到组合轨道,添加背景音乐到组合轨道。

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上。

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

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

创建AVVideoComposition的实现如下:

    /// 创建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
    }
  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.mfbz.cn/a/609041.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单片机智能灯控制系统源程序仿真原理图与论文全套资料

目录 1、设计描述 2、仿真图 3、程序 4、资料内容 资料下载地址:单片机智能灯控制系统源程序仿真原理图与论文全套资料下载 1、设计描述 设计了一款智能控制系统。 AT89C51LCD1602DS1302按键LED组成了这样一个完整的设计。 P2.0-P2.3 4个LED等代表庭院内的4…

Mock.js 问题记录

文章目录 Mock.js 问题记录1. 浮点数范围限制对小数不起效2. increment 全局共用 Mock.js 问题记录 最新写网页的时候引入了 Mock.js 来生成模拟数据; Mock使用起来很方便,具体可以参考 官网 很快就能上手, 但是这个项目最近一次提交还是在2…

Windows 跨服务器进行 MYSQL备份脚本

Windows 服务器进行 MYSQL备份的脚本,使用该脚本前,请先测试一下 1、新建一个文本文档 2、将下面代码放入文本文档中,保存退出 echo off :: 命令窗口名 title mysql-bak:: 参数定义 set "Y%date:~,4%" set "m%date:~5,2%&qu…

公司服务器内网OA网站如何实现外网访问?

目前很多公司会用windows自带的IIS搭建局域网ftp服务器,并搭建WEB服务办公网站。公司内部OA服务器,在公司内网是可以正常访问的,如何将公司内部的OA服务器映射到internet网络,让不在公司的企业员工可以正常访问到内部的OA服务器&a…

你用什么笔记软件记录自己的成长过程?

大家好,这里是大话硬件。祝大家新年好! 前两天我们在群里谈到记笔记的软件,其中有人记日记一开始是使用手写,后面改为电子笔记软件。作为一个知识型的博主,在笔记软件方面属于深度用户,有些笔记软件会员充到了几年后,在多年的使用中,总结了一些方法。 基于上次聊到的…

未授权访问:Jenkins未授权访问漏洞

目录 1、漏洞原理 2、环境搭建 3、未授权访问 4、利用未授权访问写入webshell 防御手段 今天继续学习各种未授权访问的知识和相关的实操实验,一共有好多篇,内容主要是参考先知社区的一位大佬的关于未授权访问的好文章,还有其他大佬总结好…

Visual Studio编译QT工程

1、安装QT 2、安装VS 3、选择扩展和更新 4、搜索Qt Visual Studio Tools,安装或卸载 5、安装成功后工具栏显示Qt VS Tools 6、配置Qt VS Tools:打开Qt VS Tools的下拉菜单,选择Qt Versions 7、选择qt qmake.exe 的路径

【知识碎片】2024_05_09

本篇记录了关于C语言的一些题目(puts,printf函数的返回值,getchar,跳出多重循环),和一道关于位运算的代码[整数转换]。 C语言碎片知识 如下程序的功能是( ) #…

通过编写dockerfile部署python项目

docker命令总览 docker通过dockerfile构建镜像常用命令 # 创建镜像(进入dockerfile所在的路径) docker build -t my_image:1.0 .# 查看镜像 docker images# 创建容器 docker run -dit --restartalways -p 9700:9700 --name my_container my_image:1.0 #…

互动科技如何强化法治教育基地体验?

近年来,多媒体互动技术正日益融入我们生活的各个角落,法治教育领域亦不例外。步入法治教育基地,我们不难发现,众多创新的多媒体互动装置如雨后春笋般涌现,这些装置凭借前沿的科技手段,不仅极大地丰富了法制…

电机控制系列模块解析(20)—— MTPA

一、MTPA MTPA 是 "Maximum Torque Per Ampere" 的缩写,意为“最大转矩电流比”。在电机控制系统中,特别是永磁同步电机(PMSM)或其它永磁电机的控制策略中,MTPA 控制旨在实现电机在给定负载条件下&#xff…

利用ansible playbook部署LNMP架构

接:ansible批量运维管理-CSDN博客 由于host01主机环境不纯净,决定弃用host01主机,编写剧本时要确保环境纯洁 (只做实验用途一台控制) [rootansible-server ~]# vim /etc/ansible/hosts [webserver] host02 1、在a…

盲盒一番赏小程序:探索未知,开启神秘宝藏之旅

开启神秘之门,探索未知的乐趣 在繁忙的生活中,我们渴望一丝丝未知带来的惊喜与乐趣。盲盒一番赏小程序,正是为了满足您这种探索未知的欲望而诞生。它不仅仅是一个购物平台,更是一个充满神秘与惊喜的宝藏世界。 精选好物&#xf…

AI视频教程下载:给企业管理层和商业精英的ChatGpt课程

课程内容大纲: 1-引言 2-面向初学者的生成性人工智能 3-与ChatGPT一起学习提示101 详细介绍了如何使用ChatGPT的六种沟通模式,并提供了各种实际应用场景和示例: **Q&A模式(问题与答案模式)**: - 这…

如何在mac电脑安装 Android SDK

1、在 Mac 电脑上安装 Android SDK 的步骤如下: 前往 Android 开发者网站下载 Android SDK 打开 Android 开发者网站 (https://developer.android.com/studio) 打开下载好的 Android SDK 安装包 2、解压 Android SDK 安装包 打开下载好的 Android SDK 安装包 将 android-…

Kubernetes学习-集群搭建篇(二) 部署Node服务,启动JNI网络插件

目录 1. 前言 2. 部署Node服务 2.1. 前置环境安装 2.2. 将Node服务加入集群 3. 部署JNI网络插件 4. 测试集群 5. 总结 1. 前言 我们在上一篇文章完成了Matster结点的部署,这次我们接着来部署Node服务,注意,我Node服务是部署在另外两台…

0509_IO4

练习1&#xff1a; 创建一对父子进程&#xff1a; 父进程负责向文件中写入 长方形的长和宽 子进程负责读取文件中的长宽信息后&#xff0c;计算长方形的面积 1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <sys/types.h>…

kafka系列三:生产与消费实践之旅

在本篇技术博客中&#xff0c;我们将深入探索Apache Kafka 0.10.0.2版本中的消息生产与消费机制。Kafka作为一个分布式消息队列系统&#xff0c;以其高效的吞吐量、低延迟和高可扩展性&#xff0c;在大数据处理和实时数据流处理领域扮演着至关重要的角色。了解如何在这一特定版…

如何安全高效地进行分公司文件下发?

确保分公司文件下发过程中的保密性和安全性&#xff0c;是企业信息安全管理的重要组成部分。以下是一些关键步骤和最佳实践&#xff1a; 权限管理&#xff1a;确保只有授权的人员可以访问文件。使用权限管理系统来控制谁可以查看、编辑或下载文件。 加密传输&#xff1a;在文…

前端面试题 | 常考题整理

本文为面试中出现的高频次考题&#xff0c;具体还是要看所有题。 目录 css 1、☆介绍下 BFC 及其应用 3、☆浮动清除 17、☆说几个未知宽高元素水平垂直居中方法 js 9、☆箭头函数与普通函数的区别是什么&#xff1f;构造函数可以使用 new 生成实例&#xff0c;那么箭头…
最新文章