四.音视频编辑-音频混合-概述

引言

当我们在前两篇博客中成功地构建了一个媒体组合,并且略过了音频部分时,我们意识到了我们需要对这个项目进行更详细的探讨。在本篇博客中,我们将会展示如何创建一个包含视频轨道、配音音频轨道以及背景音频轨道的完整媒体组合。更进一步,我们将探讨当两个音频轨道同时竞争空间时的音频混合方案。

构建双音轨组合

在前面博客的基础上我们需要进行一些调整以构建新的媒体组合。

资源选择

首先是资源选择器的调整,原本的资源选择器只支持单一的视频媒体资源选择,现在我们将其分为三组,分别为视频资源,配音音频资源,背景音乐资源。

代码如下:

修改数据源为二维数组

    /// 数据
    var dataArray:[[PHResource]] = [[PHResource]]()

初始化数据

    override func initData() {
        super.initData()
        //添加视频资源
        var videoArray = [PHResource]()
        let resource_breckiehill = PHResource(resource_name: "01_nebula", resource_ext: "mp4", resource_type: .video)
        videoArray.append(resource_breckiehill)
        let resource_dkglitch = PHResource(resource_name: "04_quasar", resource_ext: "mp4", resource_type: .video)
        videoArray.append(resource_dkglitch)
        dataArray.append(videoArray)
        //添加音频资源
        var audioArray = [PHResource]()
        let resource_john_kennedy = PHResource(resource_name: "John F. Kennedy", resource_ext: "m4a", resource_type: .audio)
        audioArray.append(resource_john_kennedy)
        let resource_ronald_reagen = PHResource(resource_name: "Ronald Reagan", resource_ext: "m4a", resource_type: .audio)
        audioArray.append(resource_ronald_reagen)
        dataArray.append(audioArray)
        //添加背景音乐资源
        var bgmArray = [PHResource]()
        let resource_keep_going = PHResource(resource_name: "02 Keep Going", resource_ext: "m4a", resource_type: .music)
        bgmArray.append(resource_keep_going)
        let resource_star_gazing = PHResource(resource_name: "01 Star Gazing", resource_ext: "m4a", resource_type: .music)
        bgmArray.append(resource_star_gazing)
        dataArray.append(bgmArray)

列表改为分组样式,显示结果如下图:

资源选择器

资源编辑

资源编辑页面需要将三个轨道都显示到页面上,为此我们需要创建三个不同的横向列表来显示不同的媒体资源轨道。

    /// 数据
    var timeLine = PHTimeLine()
    /// 视频编辑视图
    var videoCollectionView:UICollectionView?
    /// 音频编辑视图
    var audioCollectionView:UICollectionView?
    /// 背景音乐编辑视图
    var musicCollectionView:UICollectionView?
    func addCollectionView() {
        addVideoCollectionView()
        addAudioCollectionView()
        addMusicCollectionView()
    }
    
    func addVideoCollectionView() {
        self.videoCollectionView = buildCollectionView(offsetY: 50.0)
    }
    
    func addAudioCollectionView() {
        let offsetY = CGRectGetMaxY(self.videoCollectionView?.frame ?? CGRect.zero) + 8.0
        self.audioCollectionView = buildCollectionView(offsetY: offsetY)
    }
    
    func addMusicCollectionView() {
        let offsetY = CGRectGetMaxY(self.audioCollectionView?.frame ?? CGRect.zero) + 8.0
        self.musicCollectionView = buildCollectionView(offsetY: offsetY)
    }
    
    /// 创建编辑视图
    /// - Parameter :
    /// - offsetY: 偏移量
    ///
    /// - Returns: UICollectionView
    
    func buildCollectionView(offsetY:CGFloat) -> UICollectionView {
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .horizontal
        flowLayout.itemSize = item_size
        flowLayout.minimumInteritemSpacing = 0.0
        flowLayout.minimumLineSpacing = 0.0
        let collectionView = UICollectionView(frame: CGRect(x: 0.0, y: offsetY, width: self.bounds.size.width, height: item_size.height), collectionViewLayout: flowLayout)
        collectionView.contentInset = UIEdgeInsets(top: 0.0, left: self.bounds.width*0.5, bottom: 0.0, right: self.bounds.width*0.5)
        collectionView.delegate = self
        collectionView.dataSource = self
        self.addSubview(collectionView)
        collectionView.register(PHEditorCell.self, forCellWithReuseIdentifier: NSStringFromClass(PHEditorCell.self))
        return collectionView
    }

每个collectionView加载timeline中的不同数据。

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        var cell:UICollectionViewCell?
        var mediaItem:PHMediaItem?
        var backViewColor:UIColor = .red
        if collectionView == self.videoCollectionView {
            mediaItem = timeLine.videoItmes[indexPath.section]
            backViewColor = .red
        }
        if collectionView == self.audioCollectionView {
            mediaItem = timeLine.audioItems[indexPath.section]
            backViewColor = .blue
        }
        if collectionView == self.musicCollectionView {
            mediaItem = timeLine.musicItem
            backViewColor = .green
        }
        
        if let editorCell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(PHEditorCell.self), for: indexPath) as? PHEditorCell {
            if let mediaItem = mediaItem {
                editorCell.titleLabel.text = mediaItem.title
                editorCell.backViewColor = backViewColor
                cell = editorCell
            }
        }
        if cell == nil {
            assert(false, "cell is nil")
        }
        return cell!
    }
    

最终的实现的页面显示效果如下:

混合音频

这次组合媒体看起来还不错,场景切换也十分清晰,但仔细感受我们会发现在音频上有一些小瑕疵,首先一个比较严重的问题是我们的两个音频轨道发生了冲突,在开始播放时几乎听不见画外音,背景音乐的音量已经完全覆盖了它。与其让着两个音频轨道发生冲突,倒不如使用一种名为闪避的处理方案,在画外音持续的时间内将背景音乐的音量调低,并保持这个音量直到画外音结束之后再恢复到原来的音量。

另外一个小问题时当播放结束时音乐戛然而止,如果声音可以渐渐减小,会带来更好的用户体验。

框架提供了一个AVAudioMix类处理上面的问题,它是用来在组合的音频轨道中进行自定义音频的处理。

AVAudioMix所具有的音频处理方法是由它的输入参数集定义的,它的参数是AVAudioMixInputParameters类型的对象。AVAudioMixInputParameters的实例关联组合中的单独音频轨道,并在添加到音频混合时定义基于轨道的处理方法。AVAudioMix和其相关联的AVAudioMixInputParameters集合都是不可变对象,意味着它们适用于为AVPlayerItem和AVAssetExportSession之类的客户端提供相关数据,不过它们不能操作其状态。当我们需要创建一个自定义音频混合时,需要改用它们在AVMutableAudioMix和AVMutableAudioMixInputParameters中的可变子类。

AVAudioMix及其相关类的示意图如下:

AVAudioMix及其相关类

自动调节音量

当一个组合资源播放或导出时,默认行为是以最大音量或正常音量。只有一个单音轨道时这样的方法才可能比较容易接受,不过当一个组合资源包含多个音频源时就会出现问题。对于多音频轨道的情况,每个声音都在争夺空间,这就不可避免会导致一些声音可能无法被听到。

AV Foundation把音量定义为了一个标准化的浮点型数值,数值范围从0.0~1.0。音频轨道的默认音量为1.0,不过可以使用AVMutableAudioMixInputParameters实例修改这个值。这个对象允许在一个指定时间点或给定的时间范围自动调节音量。

AVMutableAudioMixInputParameters提供了两个方法来调节音量:

1.在指定时间点立即调节音量。音量在音轨持续时间内会保持不变,直到有另一个音量调节出现。

setVolume(_ volume: Float, at time: CMTime)

volume:表示目标音量。

time:起始时间点。

2.在一个给定时间范围内平滑地将音量从一个值调节到另外一个值。当需要在一个时间范围内调整音量时,音量会立即变为指定值的初始值音量并在持续时间内逐渐调整为指定的结束值。

setVolumeRamp(fromStartVolume startVolume: Float, toEndVolume endVolume: Float, timeRange: CMTimeRange)

starVolume:起始音量。

endVolume:目标音量。

timeRange:变化持续时间范围。

简单示例

我们来使用上面的知识来实现一下下面的小示例,8秒的音频资源,开始播放时默认音量为1.0,当播放到2秒的时候开始设置音量平滑的减小到3秒时音量为0.4,而到第5秒音量直接调整为0.6。

        // 创建一个音频轨道
        let composition = AVMutableComposition()
        let compositionTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
       
        // 设置时间
        let zeroSeconds = CMTime.zero
        let twoSeconds = CMTime(value: 2, timescale: 1)
        let threeSeconds = CMTime(value: 3, timescale: 1)
        let fiveSeconds = CMTime(value: 5, timescale: 1)
        
        // 创建parameters
        let parameters = AVMutableAudioMixInputParameters(track: compositionTrack)
        
        // 设置初始音量 (即使不设置默认也是最大音量1.0)
        parameters.setVolume(1.0, at: zeroSeconds)
        // 2s时音量开始平滑减小3s减小至0.4
        parameters.setVolumeRamp(fromStartVolume: 1.0, toEndVolume: 0.4, timeRange: CMTimeRange(start: twoSeconds, end: threeSeconds))
        // 5s时直接设置音量为0.6
        parameters.setVolume(0.6, at: fiveSeconds)
        
        // 创建audioMix
        let audioMix = AVMutableAudioMix()
        audioMix.inputParameters = [parameters]
  1. 首先需要有一个音频轨道。
  2. 定义设置音量变化的时间。
  3. 创建一个新的与要操作的轨道关联的AVMutableAudioMixInputParameters实例。
  4. 默认音量为1.0,在2s时设置音量平滑过渡到0.4持续时间为1s,在5秒时直接设置音量为0.6。
  5. 定义好所有参数后就可以创建AVMutableAudioMix了,将参数添加到数组中,并将数组赋给音频混合对象的inputParameters属性。

示例中创建了一个全格式的音频混合,可以被设置为AVPlayerItem或AVAssetExportSession的audioMix属性进行播放或导出。

结语

在本文中,我们深入探讨了使用AVFoundation进行音频混合的相关类和方案。我们介绍了核心概念,包括AVAudioMix,AVMutableAudioMix,AVAudioMixInputParameters,AVMutableAudioMixInputParameters等类的使用场景。

通过提供一个简单的实例,我们演示了如何在实际项目中应用这些技术。

希望本文能为您提供清晰的指导,并激发您在自己的应用程序中尝试音频混合的想法。在下一篇博客中,我们将继续探讨如何将这些概念整合到实际项目中,为您展示更多高级技术和实用技巧。

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

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

相关文章

游泳耳机哪个牌子好?体验与口碑兼顾的4大游泳耳机汇总!

最近的天气越来越炎热了,许多人选择游泳作为一种既能锻炼身体又能享受清凉的活动。而随着科技的发展,越来越多的运动爱好者希望在游泳时也能享受到音乐的乐趣。因此,游泳耳机应运而生,成为市场上的热门产品。然而,面对…

项目中的解耦小能手-观察者模式

目录 1.使用场景 2.什么是观察模式 3.观察者模式结构图 4.代码实现案例 4.1 subject代码实现 4.2 Observer类代码实现 5. 回顾总结 1.使用场景 当一个对象的改变需要同事改变其他对象的时候,如:订单中心-下单成功需要通知库存、物流和积分去做相应…

交流回馈老化测试负载优点和应用

交流回馈老化测试负载是用于模拟真实环境下设备运行状态的测试工具,通过对设备进行长时间的连续工作,以检测其性能的稳定性和可靠性。这种测试负载具有许多优点,并且在实际应用中有着广泛的用途。 在实际应用中,设备往往需要在各种…

Flask实战

from flask import Flask appFlask(__name__)点击Flask同时点击键盘ctrl即可查看Flask的默认初始化函数 def __init__(self,import_name: str,static_url_path: str | None None,static_folder: str | os.PathLike[str] | None "static",static_host: str | None …

产品心理学:为什么管钱的都是女生?

大家发现了吗?大部分公司女财务居多,而在家庭中,多数也是女生管钱。 为什么管钱的都是女生?答案文尾揭晓。 问题的答案,要从一个心理学名词“过度自信偏差”说起 用人话说,就是“迷之自信” 过度自信的例…

【剪映专业版】11音频的全流程剪辑操作

视频课程:B站有知公开课【剪映电脑版教程】 1.音乐素材 可能包含人声,音乐素材普遍比较长,几十秒到几分钟。要点击倒三角才会出现分类。 点击下载箭头下载素材;点击加号将素材增加到轨道;时间指示器在哪个地方&#…

Python | Leetcode Python题解之第35题搜索插入位置

题目&#xff1a; 题解&#xff1a; class Solution:def searchInsert(self, nums: List[int], target: int) -> int:left, right 0, len(nums) #采用左闭右开区间[left,right)while left < right: # 右开所以不能有,区间不存在mid left (right - left)//2 # 防止溢出…

UE5增强输入系统 Enhanced Input

关键字&#xff1a; Enhanced Input 、 输入、映射、事件、鼠标、键盘、键鼠、动作、Trigger、触发器、 疑问&#xff1a; 新输入系统怎么做一个基础的案例&#xff1f;Trigger修改器中每个项都是什么功能&#xff1f;InputAction和InputMappingContext中都有修改器&#xff…

Python基础02-掌握HTTP API的秘诀

在下面文案基础上扩展&#xff0c;写一篇技术博客&#xff0c;标题要有吸引力&#xff1f; 标题&#xff1a; 在Python中&#xff0c;使用HTTP API已成为一种常见的操作。本文将深入探讨如何使用Python的requests库与HTTP API进行交互。我们将学习如何发送GET和POST请求、处理…

消息队列选型(RabbitMq、RocketMq、Kafaka)

文章目录 前言RabbitMq优点缺点 RocketMq优点缺点 Kafaka优点缺点 总结 前言 当引入消息队列时&#xff0c;常见的选择包括ActiveMQ、Kafka、RabbitMQ和RocketMQ。然而&#xff0c;近年来&#xff0c;ActiveMQ的活跃度已经下降&#xff0c;很多公司已经不再使用这款消息队列中…

TSINGSEE青犀算法中台消防通道堵塞/占压AI检测算法的介绍及应用

消防通道是建筑物内用于紧急疏散的通道&#xff0c;其畅通无阻对于保障人员生命安全至关重要。然而&#xff0c;由于各种原因&#xff0c;消防通道经常会被杂物、车辆等堵塞&#xff0c;一旦发生火灾等紧急情况&#xff0c;后果不堪设想。为了有效解决这一问题&#xff0c;我们…

【氮化镓】GaN HEMT失效物理和可靠性

概述: 本文是一篇关于AlGaN/GaN基高电子迁移率晶体管(HEMTs)的失效物理和可靠性研究的综述文章,发表在2013年10月的《IEEE Transactions on Electron Devices》上。文章由Enrico Zanoni等人撰写,主要关注了影响栅极边缘和肖特基结的失效机制,并探讨了提高这些器件可靠性…

文档加密软件哪个好用?为什么迅软DSE加密软件更受用户青睐?

通过对文档内容进行加密处理&#xff0c;以确保其安全性和保密性。文档加密软件采用加密算法对文档进行加密处理&#xff0c;在加密过程中&#xff0c;文档加密软件会将文档的原始内容转换为一种不可读的形式&#xff0c;即加密后的文档。这个加密过程是通过应用特定的加密算法…

SQVI创建以及生成程序

SAP数据快速查询工具&#xff1a;Sqvi-QuickView 项目实施&运维阶段&#xff0c;为了快速获取一些透明表数据&#xff0c;一开始接触项目肯定会通过大量的数据表查找&#xff0c;然后线下通过EXCEL通过VLOOKUP进行数据关联&#xff0c;这种方式在关联数据较少的情况比较适应…

spring boot获取请求参数并响应

获取请求参数并响应&#xff1a; 响应&#xff1a; 在Controller类或方法上加上ResponseBody注解&#xff0c;可以将方法返回值直接响应&#xff0c;如果返回值是实体对象或者集合&#xff0c;将转换为json格式响应。如下例&#xff1a; RestControllerResponseBodyControll…

Linux最常用的40个基本命令

目录 Linux基本命令命令1&#xff1a;ls &#xff08;查看指定目录中有哪些内容&#xff09;ls / 相当于查看根目录中的内容&#xff0c;相当于查看我的电脑ls -l&#xff08;小写l&#xff0c;或者使用ll&#xff09;详细查看目录下所有内容ls /usr/lib&#xff08;ls目录名称…

Java | Leetcode Java题解之第38题外观数列

题目&#xff1a; 题解&#xff1a; class Solution {public String countAndSay(int n) {String[] arr {"","1","11","21","1211","111221","312211","13112221","1113213211",…

基于springboot的网上二手商城的设计与实现

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

盛情邀请 | 4月24-26日成都工博会科东软件邀您共赴蓉城

2024成都国际工业博览会(简称“成都工博会”)将于2024年4月24日-26日在中国西部国际博览城隆重揭幕。立足未来工业的高站位&#xff0c;精确聚焦中国智能制造&#xff0c;为上下游企业搭建高效的供需对接桥梁&#xff0c;促进制造业向数字化网络化智能化转型&#xff0c;推动西…

CTF竞赛:一场网络安全技术的盛宴

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…