五.音视频编辑-音频混合-应用

引言

音频混合技术是一项强大的工具,可以为应用程序增添丰富的功能和用户体验。在前一篇博客中,我们深入探讨了AVFoundation框架中的音频混合基础知识和实现方法。现在,让我们进一步探索,看看如何将这些技术应用到实际项目中。

在本文中,我们将介绍一个实际应用场景,并展示如何利用音频混合技术来实现特定的功能。我们将从问题背景开始,阐述为什么需要音频混合,然后逐步介绍我们的解决方案。通过一个实际案例,我们将演示如何使用AVFoundation框架中的音频混合功能来实现我们的目标。

实现

下面我们就开始将音频混合应用到项目当中,来解决声音突兀,以及两个音频轨道争夺空间导致配音音频无法被听见的问题。

目标

首先我们需要在有配音音频的时候将背景音乐的音量尽量减小比如设置为0.1,当配音音频结束后背景音乐的音量恢复到1.0。而当资源播放即将结束的时候将音频逐渐减小到0.0。

数据

首先创建一个新的类用来描述音频的音量变化,包括音频的起始音量,结束音量,变化起始时间及持续时长。

PHVolumeAutomation:

import UIKit
import CoreMedia

class PHVolumeAutomation: NSObject {
    
    /// 开始音量
    var startVolume:Float = 0.0
    /// 结束音量
    var endVolume:Float = 0.0
    /// timeRange
    var timeRange:CMTimeRange = CMTimeRange.zero
    
    /// 自定义初始化
    /// - Parameters:
    /// - startVolume: 开始音量
    /// - endVolume: 结束音量
    /// - timeRange: timeRange
    init(startVolume:Float,endVolume:Float,timeRange:CMTimeRange) {
        self.startVolume = startVolume
        self.endVolume = endVolume
        self.timeRange = timeRange
    }

}

给PHMusicItem类增加特有属性,用来保存音量变化的数据。

import UIKit

class PHMusicItem: PHMediaItem {
    /// 音频调节 数组
    var volumeAutomations = [PHVolumeAutomation]()

}

在添加音频轨道数据以及背景音乐轨道数据时来调用构建音量自动闪避的数据。

        resourcePickerView.addMediaItemBlock = { [weak self] mediaItem in
            guard let self = self else { return }
            if mediaItem.isKind(of: PHVideoItem.self) {
                /// 视频
                self.timeLine.videoItmes.append(mediaItem as! PHVideoItem)
                self.videoCollectionView?.reloadData()
            } else if mediaItem.isKind(of: PHAudioItem.self) {
                /// 音频
                self.timeLine.audioItems.append(mediaItem as! PHAudioItem)
                self.audioCollectionView?.reloadData()
                self.buildVolumeData()
            } else if mediaItem.isKind(of: PHMusicItem.self) {
                /// 音乐
                self.timeLine.musicItem = mediaItem as? PHMusicItem
                self.musicCollectionView?.reloadData()
                self.buildVolumeData()
            }
        }

buildVolumeData方法实现如下:

    /// 构建 音量调节数据
    func buildVolumeData() {
        let volumeBuilder = PHVolumeAutomationBuilder()
        guard let volumeAutomations = volumeBuilder.buildVolumeData(timeLine: timeLine) else { return  }
        timeLine.musicItem?.volumeAutomations = volumeAutomations
    }

其中volumeBuilder是我们自定义的一个构建器,代码实现如下:

//  音频音量调节数据模型构建者

import UIKit
import CoreMedia

class PHVolumeAutomationBuilder: NSObject {
    
    /// 根据timeline构建音量调节数据
    /// - Parameters:
    /// - timeline: timeline
    /// - Returns: 返回音量调节数据
    func buildVolumeData(timeLine:PHTimeLine) -> [PHVolumeAutomation]? {
        var volumeAutomations = [PHVolumeAutomation]()
        guard let musicItem = timeLine.musicItem else { return nil}
        // 闪避配音音频
        if PHIsEmpty(array: timeLine.audioItems) {
            return nil
        }
        // 变化持续时间
        let transitionDuration = CMTime(value: 1, timescale: 1)
        // 配音开始时间
        let voiceStartTime = CMTime.zero
        let startAutoVolumeAutomation = PHVolumeAutomation(startVolume: 0.1, endVolume: 0.1, timeRange: CMTimeRange(start: voiceStartTime, duration: transitionDuration))
        timeLine.musicItem?.volumeAutomations.append(startAutoVolumeAutomation)
        volumeAutomations.append(startAutoVolumeAutomation)
        // 配音结束时间
        // 计算总时长
        var voiceEndTime = CMTime.zero
        for audioItem in timeLine.audioItems {
            voiceEndTime = CMTimeAdd(voiceEndTime, audioItem.timeRange.duration)
        }
        voiceEndTime = CMTimeSubtract(voiceEndTime, transitionDuration)
        let endAutoVolumeAutomation = PHVolumeAutomation(startVolume: 0.1, endVolume: 1.0, timeRange: CMTimeRange(start: voiceEndTime, duration: transitionDuration))
        volumeAutomations.append(endAutoVolumeAutomation)
        
        // 结尾逐渐减小
        let endTime = musicItem.timeRange.end
        let startTime = CMTimeSubtract(endTime, CMTime(value: 2, timescale: 1))
        let endVolumeAutomation = PHVolumeAutomation(startVolume: 1.0, endVolume: 0.0, timeRange: CMTimeRange(start: startTime, end: endTime))
        volumeAutomations.append(endVolumeAutomation)
        return volumeAutomations
    }

}

上面的代码看起来内容很多,实际上都是CMTime的数据处理和计算,在前面我们已经说过音视频编辑的部分CMTime相关的知识非常重要,使用十分频繁。

AVAudioMix

将AVAudioMix应用到视频播放和导出中,需要对PHBaseComposition和PHBaseCompositionBuilder中的代码进行调整,我们将之前的两个类保留。

重新创建PHAudioMixComposition类用来创建包含音频混合的可播放和可导出版本,实现如下:

import UIKit
import AVFoundation

class PHAudioMixComposition: NSObject,PHComposition {
    
    /// 私有composition
    private var compostion:AVComposition?
    /// 音频混合
    private var audioMix:AVAudioMix?
    
    /// 自定义初始化
    /// - Parameters:
    ///  - compostion: AVComposition
    ///  - audioMix: AVAudioMix
    init(compostion: AVComposition? = nil,audioMix:AVAudioMix? = nil) {
        self.compostion = compostion
        self.audioMix = audioMix
    }
    
    func makePlayerItem() -> AVPlayerItem? {
        if let compostion = compostion {
            let playerItem =  AVPlayerItem(asset: compostion.copy() as! AVAsset)
            playerItem.audioMix = audioMix
            return playerItem
        }
        return nil
    }
    
    func makeAssetExportSession() -> AVAssetExportSession? {
        var assetExportSession:AVAssetExportSession? = nil
        if let compostion = compostion {
            let prset = AVAssetExportPresetHighestQuality
            assetExportSession = AVAssetExportSession(asset: compostion.copy() as! AVAsset, presetName: prset)
            assetExportSession?.audioMix = audioMix
        }
        return assetExportSession
    }
}

代码几乎和原来的实现相同,只是新增了AVAudioMix属性,并在播放和导出的时候进行了应用。

创建创建PHAudioMixCompositionBuilder类实现PHCompositionBuilder中定义的接口:

import UIKit
import AVFoundation

class PHAudioMixCompositionBuilder: NSObject, PHCompositionBuilder{
    
    /// 时间线
    var timeLine:PHTimeLine!
    /// composition
    private var composition = AVMutableComposition()
    
    init(timeLine: PHTimeLine!) {
        self.timeLine = timeLine
    }
    
    func buildComposition() -> PHComposition? {
        // 添加视频轨道
        let _ = addCompositionTrack(mediaType: .video, mediaItems: timeLine.videoItmes)
        // 添加音频轨道
        let _ = addCompositionTrack(mediaType: .audio, mediaItems: timeLine.audioItems)
        // 添加背景音乐
        var audioMix:AVAudioMix? = nil
        if timeLine.musicItem != nil {
          let musicCompositionTrack =  addCompositionTrack(mediaType: .audio, mediaItems: [timeLine.musicItem!])
            let musicAudioMix = buildAudioMixWithTrack(track: musicCompositionTrack)
            audioMix = musicAudioMix
        }
        return PHAudioMixComposition(compostion: self.composition, audioMix: 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?) -> AVAudioMix? {
        return nil
    }

}

这里面的代码和上一篇博客中PHBaseCompositionBuilder的代码也是几乎相同,不同地方是为背景音乐轨道创建了一个AVAudioMix,接下来我们来看一下buildAudioMixWithTrack方法的实现:

    /// 创建音频混合器
    /// - Parameters:
    ///  - musicTrack: 音乐轨道
    func buildAudioMixWithTrack(track:AVMutableCompositionTrack?) -> AVAudioMix? {
        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
    }

图形呈现

截止到上面的代码,其实关于音频混合的功能就已经完成。只需要在点击播放构建播放资源时将原来的PHBaseCompositionBuilder替换为音频混合的PHAudioMixCompositionBuilder即可,代码如下:

    func player() {
        guard let delegate = self.delegate else { return }
        let compositionBuilder = PHAudioMixCompositionBuilder(timeLine: timeLine)
        let composition = compositionBuilder.buildComposition()
        let playerItem = composition?.makePlayerItem()
        delegate.replaceCurrentItem(playerItem: playerItem)
    }

再次播放媒体组合,会发现当存在独白音频时,背景音乐的音量非常小,当独白结束后背景音乐音量开始增大到1,而当组合播放即将结束时,背景音乐的音量又会逐渐减小至0。

我们采用一种更直观的方式,将背景音乐的音量大小使用图像呈现出来。

定义一个PHMusicEditorCell继承自PHEditorCell,采用CAShaperLayer+UIBeizierPath方式来呈现音量。代码如下:

// 背景音乐编辑cell

import UIKit
import CoreMedia

class PHMusicEditorCell: PHEditorCell {
    
    let shapLayer = CAShapeLayer()
    let path = UIBezierPath()
    
    var timeLine:PHTimeLine? {
        didSet {
            setNeedsDisplay()
        }
    }
    
    override func setupView() {
        super.setupView()
        shapLayer.strokeColor = UIColor.red.cgColor
        shapLayer.fillColor = UIColor.clear.cgColor
        shapLayer.lineWidth = 6.0
        contentView.layer.addSublayer(shapLayer)
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        path.removeAllPoints()
        guard let timeLine = timeLine else {
            return
        }
        if let musicItem = timeLine.musicItem {
            let volumeAutomations = musicItem.volumeAutomations
            if volumeAutomations.count > 0 {
                var pathY = 0.0
                path.move(to: CGPoint(x: 0, y: pathY))
                let height = rect.height
                let width = rect.width
                for volumeAutomation in volumeAutomations {
                    let start = volumeAutomation.timeRange.start.seconds / musicItem.timeRange.duration.seconds * width
                    let end = CGFloat(volumeAutomation.timeRange.end.seconds / musicItem.timeRange.duration.seconds) * width
                    let startVolume = CGFloat(volumeAutomation.startVolume) * height
                    let endVolume = CGFloat(volumeAutomation.endVolume) * height
                    /// y不变 x 变
                    path.addLine(to: CGPoint(x: start, y: pathY))
                    pathY = height - startVolume
                    path.addLine(to: CGPoint(x: start, y: pathY))
                    pathY = height - endVolume
                    path.addLine(to: CGPoint(x: end, y: pathY))
                }
                shapLayer.path = path.cgPath
            }
        }
    }
}
  1. 定义shaplayer和path以及timeLine,shaplayer和path用于呈现图像,timeLine用于从中获取音量随时间轴变化的数据。
  2. 在给timeLine进行赋值时,触发draw方法进行重绘。
  3. 重写draw方法,根据musicItem中的volumeAutomations进行绘制。这里面的内容和AVAudioMix没有任何关联,就不过多介绍。

呈现结果如下。

结语

通过本文,我们深入探讨了音频混合技术在实际应用中的应用。我们从一个具体的应用场景出发,逐步介绍了如何利用AVFoundation框架中的音频混合功能来解决特定问题。

希望通过本文的阅读,您对音频混合技术有了更深入的了解,并能够将其应用到您自己的项目中。音频混合不仅可以为您的应用增添新的功能和特色,还可以提升用户体验,为用户带来更加丰富的视听享受。

下面一篇博客我们会介绍到关于媒体编辑最复杂的部分,创建视频过渡效果。

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

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

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

相关文章

Django项目无法安装python-ldap依赖解决方案

最近工作中安排了一个Python web项目,使用Pycharm从git拉取代码后,配置号Python的解释器和pip后,Pycharm自动下载安装项目所需的依赖,但是有一个依赖django-auth-ldap4.1.0安装始终失败,最初的异常信息提示是&#xff…

Java客户端如何直接调用es的API

Java客户端如何直接调用es的API 一. 问题二. withJson 前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 一. 问题 今天做项目的时候,想要直接通过java客户端调用es的api…

解决Django中调页面时出现“Did you forget to register or load this tag”报错

解决Django中调页面时出现“Did you forget to register or load this tag?”报错 1.问题收录 2.分析问题 在HTML文件中,{{title}},{{lanyy}},django 默认规定的语法,用{{}}包起来的变量叫做模板变量。 django渲染模板时会将大…

2.1K Star微软开源的高质量 iot库

功能描述 该项目是一个开源的 .NET Core 实现,旨在帮助开发者构建适用于物联网(IoT)设备和场景的应用程序。它提供了与传感器、显示器和输入设备等相互作用所需的 GPIO 引脚、串口等硬件的接口。该仓库包含 System.Device.Gpio 库以及针对各种板卡(如 Ra…

论文笔记:Time-LLM: Time Series Forecasting by Reprogramming Large Language Models

iclr 2024 reviewer 评分 3888 1 方法 提出了 Time-LLM, 是一个通用的大模型重编程(LLM Reprogramming)框架将 LLM 轻松用于一般时间序列预测,而无需对大语言模型本身做任何训练 为什么需要时序数据和文本数据对齐:时…

vi, vim,data,wc,系统常用命令-读书笔记(十)

vi 文本编辑器 基本上 vi 共分为三种模式,分别是“一般指令模式”、“编辑模式”与“命令行命令模式”。这三种模式的作用分别是: 一般指令模式(command mode)以 vi 打开一个文件就直接进入一般指令模式了(这是默认的…

分类预测 | Matlab实现CNN-LSTM-SAM-Attention卷积长短期记忆神经网络融合空间注意力机制的数据分类预测

分类预测 | Matlab实现CNN-LSTM-SAM-Attention卷积长短期记忆神经网络融合空间注意力机制的数据分类预测 目录 分类预测 | Matlab实现CNN-LSTM-SAM-Attention卷积长短期记忆神经网络融合空间注意力机制的数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Mat…

frp改造Windows笔记本实现家庭版免费内网穿透

文章目录 前言frp原理Windows服务端IP检验IP固定软件下载端口放行端口映射开机启动 NAS客户端端口查询软件下载端口检验穿透测试自启设置 Ubuntu客户端软件下载后台启动 后记 前言 之前一直用花生壳远程控制一个服务器,但最近内网的网络策略似乎发生了变化&#xf…

java中的异常机制

异常原理分析 在我们编写一个代码的时候必然会出现这样那样的问题,这些问题可能是明显的,也有可能是不明显的。从理论上来说,一个程序员应当具备处理代码出现的大多数问题的能力,但是这并不意味着程序员要思考处理所有的代码问题。…

硬盘日常使用中的注意事项

硬盘是计算机中的重要存储设备,负责存储大量的数据。为了确保数据的完整性和硬盘的寿命,日常使用中需要注意以下几点: 避免震动和撞击:硬盘在工作时,内部的磁盘正在高速旋转,任何轻微的震动或撞击都可能导致磁盘损坏或数据丢失。因此,使用硬盘时应确保计算机放置稳定,避…

Python图像处理【24】面部变形(face morphing)

面部变形 0. 前言1. 网格变形算法2. 实现面部变形小结系列链接 0. 前言 面部变形 (face morphing) 的目的是在计算图像中两个面部之间的平均值,它并不是计算两张图像的平均值,而是计算面部区域的平均值。可以将该过程分解为以下两步: 对齐两…

Docker容器化部署(企业版)

大家好,webfunny前端监控埋点系统,已经正式发布了webfunny的官方镜像: Webfunny镜像目录:https://hub.docker.com/r/webfunny/webfunny_monitor_cluster/tags 部署前提是你的服务器已经安装了Docker环境,没有安装doc…

车载电子电器架构 —— 售后诊断开发

车载电子电器架构 —— 售后诊断开发 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己…

OpenCV——Niblack局部阈值二值化方法

目录 一、Niblack算法1、算法概述2、参考文献二、代码实现三、结果展示OpenCV——Niblack局部阈值二值化方法由CSDN点云侠原创,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、Niblack算法 1、算法概述 Niblack 算法是一种典型的局部阈值…

SpringMVC深解--一起学习吧之架构

SpringMVC的工作原理主要基于请求驱动,它采用了前端控制器模式来进行设计。以下是SpringMVC工作原理的详细解释: 请求接收与分发: 当用户发送一个请求到Web服务器时,这个请求首先会被SpringMVC的前端控制器(Dispatche…

VUE识别图片文字OCR(tesseract.js)

效果:1&#xff1a; 效果图2&#xff1a; 一、安装tesseract.js npm i tesseract.js 二、静态页面实现 <template><div><div style"marginTop:100px"><input change"handleChage" type"file" id"image-input"…

【数据结构项目】通讯录

个人主页点这里~ 原文件在gitee里~ 通讯录的实现 基于动态顺序表实现通讯录项目1、功能要求2、代码实现file.hfile.cList.hList.ctest.c 基于动态顺序表实现通讯录项目 准备&#xff1a;结构体、动态内存管理、顺序表、文件操作 1、功能要求 ①能够存储100个人的通讯信息 ②…

Xilinx 7系列FPGA全局时钟缓冲器基本单元

Global Clock Buffer Primitives&#xff08;全局时钟缓冲器基本单元&#xff09;在FPGA&#xff08;现场可编程门阵列&#xff09;和其他数字系统中扮演着至关重要的角色。这些基本单元被设计用于处理、分配和增强时钟信号&#xff0c;以确保系统中的各个组件都能以精确和同步…

【Hadoop】- YARN架构[7]

前言 Yarn架构是一个用于管理和调度Hadoop集群资源的系统。它是Hadoop生态系统的一部分&#xff0c;主要用于解决Hadoop中的资源管理问题。 通过使用Yarn架构&#xff0c;Hadoop集群中的不同应用程序可以共享集群资源&#xff0c;并根据需要动态分配和回收资源。这种灵活的资…

4.21java聊天室项目小结

基本完成了用户的登录注册功能&#xff0c;可以实现用户账号登录和邮箱登录功能&#xff0c;忘记密码通过邮箱发送验证码找回&#xff0c;注册账号功能&#xff0c;并传递给客户端更新数据库的表内容 注册功能&#xff1a; 注册成功后密码进行MD5加密并通过服务器保存到数据库…