二.音视频编辑-媒体组合-播放

引言

当涉及到音视频编辑时,媒体资源的提取和组合是至关重要的环节。在iOS平台上,AVFoundation框架提供了丰富而强大的功能,使得媒体资源的操作变得轻松而高效。从原始的媒体中提取片段,然后将它们巧妙地组合成一个完整的作品,这是音视频编辑过程中的常见任务之一。在这篇博客中,我们将深入探讨iOS AVFoundation框架中的媒体组合功能,探索其如何为开发者提供丰富的工具和技术,帮助他们实现创意无限的音视频编辑项目。

概述

媒体组合类关系

上图是关于媒体功能中的核心类,以及类直接的关系图。有关资源组合的功能就源于AVAsset的子类AVComposition。一个组合就是将多种媒体资源组合成一个自定义的临时排列,再将这个临时排列视为一个可呈现的独立媒体项目。就比如AVAsset对象,组合相当于包含了一个或多个给定类型的媒体轨道的容器。AVComposition中的轨道都是AVAssetTrack的子类AVCompositionTrack。一个组合轨道本身由一个或多个媒体片段组成,由AVCompositionTrackSegment类定义,代表这个组合中的实际媒体区域。

组合后的对象关系如下:

组合排列

AVComposition和AVCompositionTrack都是不可变对象,提供对资源的只读操作。这些对象提供了一个合适的接口让应用程序的一部分可以进行播放或处理。不过,当创建自己的组合时,就需要使用AVMutableComposition和AVMutableCompositionTrack所提供的可变子类。这些对象提供的类接口需要操作轨道和轨道分段,这样我们就可以创建所需的临时排列了。

基础方法

这个基础的实例会将两个视频片段中的前5秒内容提取出来,并按照组合视频轨道的顺序进行排序。还会从MP3文件中奖音频轨道整合到视频中,期间会用到Core Media框架中定义的CMTime数据类型作为时间格式,相关内容可以查看其它博客。

        let composition = AVMutableComposition()var videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!var audioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)

上面的示例创建了一个AVMutableComposition并用它的addMutableTrackWithMediaType:preferredTrackID:方法添加了两个轨道对象。当创建组合轨道时,开发者必须指明它所能支持的媒体类型,并给出一个轨道标识符。设置preferredTrackID:参数为CMPersistentTrackID,这是一个32位的整数值。虽然我们可以传递任意标识符作为参数,这个标识符在我们之后需要返回轨道时会用到,不过一般来说都是赋给它一个kCMPersistentTrackID_Invalid常量。这个有着奇怪名字的常量的意思是我们需要创建一个合适轨道ID的任务委托给框架,标识符会以1..n排列。

现在我们已经实现了一个组合资源:

组合状态

下一步就是将独立的媒体片段插入到组合的轨道中。

        //1.创建资源let goldenGateAsset = AVURLAsset(url: URL(string: "1")!, options: nil)let teaGardenAsset = AVURLAsset(url: URL(string: "2")!, options: nil)let soundTrackAsset = AVURLAsset(url: URL(string: "3")!, options: nil)//2.定义插入点var cursorTime = CMTime.zero//3.定义片段时长let videoDuration = CMTime(value: 5, timescale: 1)let videoTimeRange = CMTimeRange(start: cursorTime, duration: videoDuration)//4.提取资源中的视频轨道并插入到组合中的视频轨道let goldenGateAssetTrack = goldenGateAsset.tracks(withMediaType: .video).first!do {try videoTrack.insertTimeRange(videoTimeRange, of: goldenGateAssetTrack, at: cursorTime)} catch {print("Error inserting time range: \(error)")}//5.调整插入时间cursorTime = CMTimeAdd(cursorTime, videoDuration)    //6.提取资源中的视频轨道并插入到组合中的视频轨道let teaGardenAssetTrack = teaGardenAsset.tracks(withMediaType: .video).first!do {try videoTrack.insertTimeRange(videoTimeRange, of: teaGardenAssetTrack, at: cursorTime)} catch {print("Error inserting time range: \(error)")}//7.调整插入时间和时长cursorTime = CMTime.zerolet audioDuration = composition.durationlet audioTimeRange = CMTimeRangeMake(start: cursorTime, duration: audioDuration)//8.提取音频轨道并插入到组合中的音频轨道let soundTrackAssetTrack = soundTrackAsset.tracks(withMediaType: .audio).first!do {try audioTrack?.insertTimeRange(audioTimeRange, of: soundTrackAssetTrack, at: cursorTime)} catch {print("Error inserting time range: \(error)")}
  1. 首先我们创建了3个AVAsset资源,当然这里面是模拟创建的,其中前2个表示视频,第3个表示音频。
  2. 定义了资源的插入时间点。
  3. 定义每个视频片段资源的插入时长。
  4. 提取第1个视频资源的视频轨道,默认视频资源只有一个视频轨道,插入到组合的视频轨道。
  5. 调整下一个视频资源的插入时间为上一个视频资源的结束时间点。
  6. 同样获取第2个视频资源的视频轨道,插入到组合的视频轨道。
  7. 调整插入时间为0,并设置音频的时长。
  8. 提取音频资源的音频轨道并插入到组合的音频轨道中。

这样我们的组合就构建完成了:

完成的组合

使用示例

下面我们将着色创建一个视频编辑的应用程序,接下来的博客也将围绕这个程序不断的添加和完善视频编辑的功能。

项目介绍

应用程序将包含两个不同的部分,一个是视频播放器,我们只需在之前博客的视频播放器中稍作改动,一个是可以选择媒体和允许媒体排列组合的视频编辑部分,重点会放在视频编辑的部分。

播放器

播放器和视频播放相关博客的播放器大致相同,只是原来传入播放器的是视频地址,而现在传入的需要是一个完整的AVPlayerItem。因此需要重写了init方法,并且添加另一个用于替换当前播放AVPlayerItem的方法。

init方法:

    override init() {super.init()self.player = AVPlayer(playerItem: playerItem)if let player = player {playerView = PHPlayerView(player: player)}addObserverForPlayerItem()}/// 自定义初始化方法////// - Parameters:///   - playerItem: AVPlayerIteminit(playerItem: AVPlayerItem? = nil) {super.init()self.playerItem = playerItemself.player = AVPlayer(playerItem: playerItem)if let player = player {playerView = PHPlayerView(player: player)}addObserverForPlayerItem()}

替换当前AVPlayerItem方法:

    /// AVPlayer的同名方法,替换当前播的资源////// - Parameters:///   - playerItem: AVPlayerItemfunc replaceCurrentItem(playerItem:AVPlayerItem?) {guard let player = self.player else { return }self.playerItem = playerItemplayer.replaceCurrentItem(with: playerItem)addObserverForPlayerItem()}/// 为AVPlayerItem添加监听func addObserverForPlayerItem() {guard let playerItem = playerItem else { return }playerItem.addObserver(self, forKeyPath: status_keypath, context: &playerItemContext)}

另外我们将播放进度的监听由原来的0.5改为了1/60秒,因为我们需要使用它来同步动画,而不仅仅是显示当前时间。

    /// 监听播放进度func addPlayerItemTimeObserver() {guard let player = player else { return }let interval = CMTimeMakeWithSeconds(1/60.0, preferredTimescale: Int32(NSEC_PER_SEC))let queue = DispatchQueue.maintimeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: queue, using: {[weak self] time inguard let self = self else { return }guard let playerItem = self.playerItem else { return }guard let delegate = self.delegate else { return }let currentTime = CMTimeGetSeconds(time)let duration = CMTimeGetSeconds(playerItem.duration)delegate.setCuttentTime(time: currentTime, duration: duration)})}
编辑器

编辑器由两部分构成,媒体资源选择器,和媒体资源编辑区域。

博客的示例项目中,我们只获取了视频媒体资源,音频媒体资源的做法与视频完全相同,只是传入的mediaType为audio。

媒体资源选择器为一个简单的列表,点击加号后会根据所选择的媒体资源创建一个PHMediaItem,PHMediaItem是一个基类,它的子类又分为PHVideoItem和PHAudioItem,后续的功能我们也许会用到PHAudioItem,但目前我们只需要使用PHVideoItem即可。

媒体资源选择器

媒体资源编辑器就是页面除播放器以外的下半部分,有显示媒体选择器的按钮,和控制播放器播放和暂停的按钮,以及一个显示媒体剪辑状态的时间轴区域。

媒体编辑器

创建组合

我们的核心任务就是通过页面上的一些操作来创建一个媒体组合,首先声明一个PHComposition协议,协议中定义了两个方法分别用来生成组合的可播放版本和可导出版本。

import UIKit
import AVFoundationprotocol PHComposition {/// 协议方法-生成AVPlayerItem////// - Returns: 返回一个可播放的AVPlayerItemfunc makePlayerItem() -> AVPlayerItem?/// 协议方法-生成AVAssetExportSession////// - Returns: 返回一个可导出的AVAssetExportSessionfunc makeAssetExportSession() -> AVAssetExportSession?}

创建一个遵循PHComposition协议的类,并提供协议方法的实现。

//  负责创建 视频的可播放资源和可导出资源import UIKit
import AVFoundationclass PHBaseComposition: NSObject,PHComposition {//只读compositionprivate var compostion:AVComposition?//自定义初始化init(compostion: AVComposition? = nil) {self.compostion = compostion}//MARK: PHComposition - 生成 AVPlayerItemfunc makePlayerItem() -> AVPlayerItem? {if let compostion = compostion {let playerItem =  AVPlayerItem(asset: compostion)return playerItem}return nil}//MARK: PHComposition - 生成 AVAssetExportSessionfunc makeAssetExportSession() -> AVAssetExportSession? {return nil}
}

创建一个组合的构建器,同样我们创建一个协议,负责来创建遵循PHComposition协议的对象。

import UIKitprotocol PHCompositionBuilder {/// 协议方法-生成一个遵循PHComposition协议的对象////// - Returns: 返回一个最新PHComposition协议的对象func buildComposition() -> PHComposition?
}

这个协议的具体方法由PHBaseCompositionBuilder来实现,代码如下。

import UIKit
import AVFoundationclass PHBaseCompositionBuilder: NSObject,PHCompositionBuilder {/// 时间线var timeLine:PHTimeLine!/// compositionprivate var composition = AVMutableComposition()init(timeLine: PHTimeLine!) {self.timeLine = timeLine}//MARK: PHCompositionBuilder - 生成 PHCompositionfunc buildComposition() -> PHComposition? {addCompositionTrack(mediaType: .video, mediaItems: timeLine.videoItmes)return PHBaseComposition(compostion: self.composition)}/// 私有方法-添加媒体资源轨道/// - Parameters:///   - mediaType: 媒体类型///   - mediaItems: 媒体媒体资源数组/// - Returns: 返回一个可播放的AVPlayerItemprivate func addCompositionTrack(mediaType:AVMediaType,mediaItems:[PHMediaItem]?) {if PHIsEmpty(array: mediaItems) {return}let trackID = kCMPersistentTrackID_Invalidguard let compositionTrack = composition.addMutableTrack(withMediaType: mediaType, preferredTrackID: trackID) else { return }//设置起始时间var cursorTime = CMTime.zeroguard let mediaItems = mediaItems else { return }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)}}
}
  1. PHBaseCompositionBuilder在初始化的时候会默认创建一个AVMutableComposition对象用于媒体编辑操作。
  2. 获取timeLine对象中的所有视频资源,调用addCompositionTrack方法进行拼接。
  3. addCompositionTrack方法中首先判断了传入的资源数组是否为空。
  4. 当媒体资源数组不为空的时候,从composition中获取对应的媒体轨道。
  5. 设置起始时间,遍历媒体资源数组,从每个资源中获取对应的媒体轨道并添加到组合媒体轨道中。
  6. 修改下一个媒体资源的插入起始时间。

实现播放组合媒体

选择媒体资源

点击页面上的加号按钮,显示媒体选择列表,点击列表后会将选择的媒体资源创建为PHMediaItem并添加到当前的timeLine对应的资源数组下。

    //MARK: 显示选择视频视图@objc func showItemPickerView() {let resourcePickerView = PHResourcePickerView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height * 0.5, width: 150.0, height: 200.0))resourcePickerView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1.0)resourcePickerView.layer.masksToBounds = trueresourcePickerView.layer.cornerRadius = 5.0resourcePickerView.layer.borderColor = UIColor.white.cgColorresourcePickerView.layer.borderWidth = 1.0resourcePickerView.showDialog()resourcePickerView.addMediaItemBlock = { [weak self] mediaItem inguard let self = self else { return }if var videoItmes = timeLine.videoItmes {videoItmes.append(mediaItem as! PHVideoItem)timeLine.videoItmes = videoItmes} else {var videoItmes = [PHVideoItem]()videoItmes.append(mediaItem as! PHVideoItem)timeLine.videoItmes = videoItmes}self.collectionView?.reloadData()self.needReplay = true}}
点击播放按钮

点击播放按钮后判断是否有可播放资源,再进行播放。播放分为两种情况,从头开始播放和暂停后的继续播放。

    //MARK: 播放按钮点击@objc func playerButtonOnlick(button:UIButton) {if PHIsEmpty(array: timeLine.videoItmes) {playerButton.isSelected = falsereturn}button.isSelected = !button.isSelected//回调guard let delegate = self.delegate else { return }if button.isSelected {if needReplay {player()needReplay = false} else {delegate.play()}} else {delegate.pause()}}func player() {guard let delegate = self.delegate else { return }let compositionBuilder = PHBaseCompositionBuilder(timeLine: timeLine)let composition = compositionBuilder.buildComposition()let playerItem = composition?.makePlayerItem()delegate.replaceCurrentItem(playerItem: playerItem)}
同步播放进度

PHEditorView视频编辑器遵循了PHControlDelegate协议,这里我们只关注setCuttentTime和playbackComplete方法。

setCuttentTime方法用来同步编辑器时间轴的进度。

playbackComplete用来同步播放按钮的状态。

extension PHEditorView:PHControlDelegate{func playpause(currentTime: TimeInterval) {}func playstart(duration: TimeInterval) {}func setCuttentTime(time: TimeInterval, duration: TimeInterval) {let origin_offsetX = -UIScreen.main.bounds.width * 0.5self.collectionView?.contentOffset = CGPointMake(origin_offsetX + time * item_size.width, 0.0)}func playbackComplete() {self.playerButton.isSelected = false}}

结语

在示例项目中,我们仅仅涉及了视频媒体资源,并默认这些资源都是单轨道的。然而,在实际的应用开发中,我们可能会面对更加复杂的情况,涉及到多种类型的媒体资源,以及多轨道的组合。iOS AVFoundation框架为我们提供了强大的工具和灵活的接口,让我们能够处理各种各样的媒体资源,并将它们巧妙地组合成为精彩纷呈的作品。通过深入理解和灵活运用AVFoundation框架,我们可以实现更加复杂和令人惊叹的音视频编辑应用,为用户带来全新的体验和享受。在今后的开发过程中,让我们继续探索和挖掘AVFoundation框架的潜力,创造出更加优秀和创新的音视频编辑应用!

项目地址:PHEditorPlayer: AV Foundation 音视频编辑

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

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

相关文章

dell灵越5439升级记录(2024年4月5日)

1、内存 5439为单个内存插槽,网上那些写两个的都是乱写的,型号是ddr3L。原来是4G,换成国产全新三星颗粒寨条,8g 1600hz。 淘宝价:47元。 2、CPU和显卡 如果和我一样在系统里面找不到GT740M的独显,那这一步…

PC版复古珠宝饰品网站模板 基于pbootcms的首饰类源码下载

PbootCMS复古珠宝饰品网站模板:PCWAP双端同步,数据即时共享,轻松打造专业饰品首饰平台 本模板基于PbootCMS内核开发,专为饰品首饰网站、复古珠宝饰品网站等企业量身定制。同时,其他行业同样适用,只需替换文…

160 Linux C++ 通讯架构实战14,epoll 反应堆模型

到这里,我们需要整理一下之前学习的epoll模型,并根据之前的epoll模型,提出弊端,进而整理epoll反应堆模型,进一步深刻理解,这是因为epoll实在是太重要了。 复习之前的epoll的整体流程以及思路。 参考之前写…

环信IM集成教程——Web端UIKit快速集成与消息发送

写在前面: 千呼万唤始出来,环信Web端终于出UIKit了!🎉🎉🎉 文档地址:https://doc.easemob.com/uikit/chatuikit/web/chatuikit_overview.html 环信单群聊 UIKit 是基于环信即时通讯云 IM SDK 开…

JAVA毕业设计132—基于Java+Springboot+Vue的自习室座位预约小程序管理系统(源代码+数据库)

毕设所有选题: https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的自习室座位预约小程序管理系统(源代码数据库)132 一、系统介绍 本项目前后端分离带小程序,分为管理员、用户两种角色 1、用户: 注…

echart 仪表盘实现指针的渐变色及添加图片

需求: 在仪表盘中设置指针为渐变色,并在仪表盘中间添加图片。 实现重点: 1、仪表盘指针渐变色的实现 渐变色通过设置pointer的itemStyle属性内的color实现,重点是echart版本,这个原本使用4.8.0的版本不起作用&#xff…

利用nginx-http-flv-module实现三种直播

目录 一、说明 二、目标 三、实现 四、直播地址 一、说明 此文在《流媒体服务器的搭建(支持hls)》《搭建nginx-http-flv-module直播系统》之后编写,很多详细内容需要参考它。 流媒体服务器的搭建(支持hls)

MongoDB 启动异常

Failed to start up WiredTiger under any compatibility version. 解决方案: 删除WiredTiger.lock 和 mongod.lock两个文件,在重新启动。回重新生成新的文件。

Kotlin学习日志(一)TextView、Button、Toast的使用(1)

android:layout_width“wrap_content” android:layout_height“wrap_content”/> import kotlinx.android.synthetic.main.activity_main.* 这句话的意思是引进Kotlin的的控件变量自动映射功能,接下来只要是这个activity_main.xml文件中的控件,我…

汽车网络安全管理

汽车网络安全管理 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事&#xff0c…

SpringBoot快速入门笔记(4)

文章目录 一、Vue框架1、前端环境准备2、简介3、快速开始4、事件绑定 二、Vue组件化开发1、NPM2、Vue Cli3、组件化开发4、SayHello自定义组件5、Movie自定义组件 一、Vue框架 1、前端环境准备 编码工具:VSCode 依赖管理:NPM 项目构建:VueCl…

(ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类

文章目录 相关论文摘要引言类别嵌入局限性——问题1普通ZSL模型局限性——问题2自动属性注释过程——对应问题1深度语义-视觉对齐(DSVA)模型——对应问题2 基于遥感多模态相似性的自动属性标注属性词汇表构造使用CLIP模型自动标注属性对CLIP模型进行训练…

【Arthas案例】某应用依赖两个GAV-classifier不同的snakeyaml.jar,引起NoSuchMethodError

多个不同的GAV-classifier依赖冲突,引起NoSuchMethodError Maven依赖的三坐标体系GAV(G-groupId,A-artifactId,V-version) classifier通常用于区分从同一POM构建的具有不同内容的构件物(artifact)。它是可选的&#xf…

51之矩阵键盘

目录 1.矩阵键盘简介 2.获取矩阵键盘键码值 3.矩阵键盘实现密码锁 1.矩阵键盘简介 矩阵键盘就是一个基于独立按键的Plus版本,它的原理图就是下面这样: 和独立按键就像表兄弟一样,为什么这么说呢?因为这个矩阵键盘上可以找到很多…

java——文件上传

一、文件上传——简介 文件上传的简介:文件上传是指将本地计算机中的文件传输到网络上的服务器或另一台计算机上的过程。在 Web 开发中,文件上传通常指的是将用户通过 Web 页面提交的文件(如图像、文档、音频、视频等)传输到服务器…

大数据实验统计-1、Hadoop安装及使用;2、HDFS编程实践;3、HBase编程实践;4、MapReduce编程实践

大数据实验统计 1、Hadoop安装及使用; 一.实验内容 Hadoop安装使用: 1)在PC机上以伪分布式模式安装Hadoop; 2)访问Web界面查看Hadoop信息。 二.实验目的 1、熟悉Hadoop的安装流程。 2、…

【Rust】生命周期

Rust 生命周期机制是与所有权机制同等重要的资源管理机制。 之所以引入这个概念主要是应对复杂类型系统中资源管理的问题。 引用是对待复杂类型时必不可少的机制,毕竟复杂类型的数据不能被处理器轻易地复制和计算。 但引用往往导致极其复杂的资源管理问题&#x…

归并排序解读

在算法领域中,排序算法一直是一个核心话题。归并排序(Merge Sort)作为一种典型的分治思想应用,以其稳定、高效的特点受到了广泛的关注和应用。本文将深入探讨归并排序的原理、实现方式,以及它在实际应用中的价值。 一…

KeyguardClockSwitch的父类

KeyguardClockSwitch 定义在KeyguardStatusView中, mClockView findViewById(R.id.keyguard_clock_container);KeyguardClockSwitch的父类为: Class Name: LinearLayout Class Name: KeyguardStatusView Class Name: NotificationPanelView Class Name: Notificat…

如何在iPhone上恢复永久删除的照片?

2007 年,Apple Inc. 推出了这款震撼人心的智能手机,后来被称为 iPhone。您会惊讶地发现,迄今为止,Apple Inc. 已售罄 7 亿台 iPhone 设备。根据 2023 年 8 月的一项调查数据,95% 的智能手机利润都落入了苹果公司的口袋…