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

引言

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

构建双音轨组合

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

资源选择

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

代码如下:

修改数据源为二维数组

Swift 复制代码
    /// 数据
    var dataArray:[[PHResource]] = [[PHResource]]()

初始化数据

Swift 复制代码
    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)

列表改为分组样式,显示结果如下图:
资源选择器

资源编辑

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

Swift 复制代码
    /// 数据
    var timeLine = PHTimeLine()
    /// 视频编辑视图
    var videoCollectionView:UICollectionView?
    /// 音频编辑视图
    var audioCollectionView:UICollectionView?
    /// 背景音乐编辑视图
    var musicCollectionView:UICollectionView?
Swift 复制代码
    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中的不同数据。

Swift 复制代码
    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。

Swift 复制代码
        // 创建一个音频轨道
        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等类的使用场景。

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

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

相关推荐
Mr.简锋1 小时前
opencv视频读写
人工智能·opencv·音视频
春末的南方城市2 小时前
开源音乐分离器Audio Decomposition:可实现盲源音频分离,无需外部乐器分离库,从头开始制作。将音乐转换为五线谱的程序
人工智能·计算机视觉·aigc·音视频
Hali_Botebie3 小时前
采样率22050,那么CHUNK_SIZE 一次传输的音频数据大小设置多少合适?unity接收后出现卡顿的问题的思路
音视频
风之馨技术录3 小时前
智谱AI清影升级:引领AI视频进入音效新时代
人工智能·音视频
晚点吧4 小时前
视频横屏转竖屏播放-使用人脸识别+目标跟踪实现
人工智能·目标跟踪·音视频
EasyCVR4 小时前
ISUP协议视频平台EasyCVR视频设备轨迹回放平台智慧农业视频远程监控管理方案
服务器·网络·数据库·音视频
cuijiecheng20186 小时前
音视频入门基础:MPEG2-TS专题(3)——TS Header简介
音视频
学编程的小程13 小时前
【安全通信】告别信息泄露:搭建你的开源视频聊天系统briefing
安全·开源·音视频
芯视音赖工14 小时前
传统型视频展台方案分享
音视频
melonbo14 小时前
音频采样数据格式
音视频