音频播放+音频采样(绘制音波)

news2024/11/14 15:13:44

引言

在 iOS 平台中,实现音频播放有多种方式。AVAudioPlayer 是一个专门用于播放音频数据的类,易于使用,适合处理简单的音频播放需求。而 AVPlayer 则是一种更通用的播放器,既能播放视频资源,也能处理音频内容,非常适合流媒体和多媒体应用。

然而,当我们需要实现更复杂的音频功能,比如音频节点的连接、实时音频处理,或是其他更精细的音频控制时,AVAudioEngine 就成为了一个不可或缺的工具。AVAudioEngine 提供了一个高度可扩展的音频处理架构,能够满足各种高级音频需求。

在最近的一个项目中,我需要实现一个播放背景音乐并显示音乐波形的功能。为了满足对音频播放的精确控制以及实时音频数据的获取,我采用了 AVAudioEngine 结合 AVAudioPlayerNode 的方式来实现。接下来,我将详细介绍如何使用这些工具来完成这个任务。

音频播放和采样

播放

首先创建了一个继承自NSObject的PHAudioPlayer类,我们定义了四个属性代码如下:

    /// 音频地址
    private var audioURL: URL?
    /// 音频引擎
    private var audioEngine = AVAudioEngine()
    /// 播放器节点
    private var audioPlayerNode = AVAudioPlayerNode()
    
    private var audioFile: AVAudioFile?

一个自定义的初始化方法,传递音频的URL。

    init(audioURL: URL? = nil) {
        super.init()
        self.audioURL = audioURL
        self.setupAudioEngine()
        self.playAudioFile(url: self.audioURL!)
    }

首先设置音频引擎,并添加同步捕捉音频数据:

    private func setupAudioEngine() {
        // 加载音频文件
        guard let audioURL = audioURL else {
            CSAssert(false, "音频地址为空")
            return
        }
        do {
            let mainMixer = audioEngine.mainMixerNode
            audioEngine.attach(audioPlayerNode)
            audioEngine.connect(audioPlayerNode, to: audioEngine.mainMixerNode, format: nil)
            // 捕获音频数据
            mainMixer.installTap(onBus: 0, bufferSize: 1024, format: mainMixer.outputFormat(forBus: 0)) {[weak self] buffer, time in
                guard let self = self else { return }
                self.handleAudioSampleData(buffer: buffer)
            }
            
            try audioEngine.start()
        } catch {
            CSLog.error(module: "PHAudioPlayer", "加载音频文件失败")
        }
    }

开始播放

    /// 播放
    func playAudioFile(url: URL) {
        do {
            audioFile = try AVAudioFile(forReading: url)
            guard let audioFile = audioFile else { return }
            
            let length = AVAudioFrameCount(audioFile.length)
            audioPlayerNode.scheduleFile(audioFile, at: nil, completionHandler: {
                self.playFinished()
            })
            
            audioPlayerNode.play()
        } catch {
            print("Error loading audio file: \(error.localizedDescription)")
        }
    }

处理音频数据

在mainMixer的回调中开始处理音频的样本数据,我们单独创建了一个方法代码如下:

    /// 处理音频样本数据
    private func handleAudioSampleData(buffer:AVAudioPCMBuffer) {
        guard let channelData = buffer.floatChannelData?[0] else { return }
        let frameLength = Int(buffer.frameLength)
        // 获取音频样本数据
        let samples = stride(from: 0, to: frameLength, by: buffer.stride).map { channelData[$0] }
        /// 计算振幅
        let amplitude = calculateRMS(samples: samples)
        // 使用样本数据绘制波形
        DispatchQueue.main.async {
            self.delegate?.audioPlayer(self, amplitude: amplitude)
        }
    }
    private func calculateRMS(samples: [Float]) -> Float {
        let squareSum = samples.reduce(0.0) { $0 + $1 * $1 }
        return sqrt(squareSum / Float(samples.count))
    }

销毁

监听到播放完成之后,手动进行了循环播放,并创建一个主动的销毁方法,当页面退出时主动调用。

    /// 播放完成
    func playFinished() {
        playAudioFile(url: audioURL!)
    }
    
    /// 销毁
    func destroy() {
        audioEngine.stop()
        audioEngine.reset()
    }

音频波形图绘制

波形绘制采用了贝塞尔曲线+图层遮罩的方式来实现,底部图层采用了渐变图层这样的波形会有一个顶部颜色和底部颜色不同的样式。具体代码如下:

class SVLPAudioVolumeView: UIView,PHAudioPlayerDelegate {

    /// 缓存点个数
    private let bufferSize = 200
    /// 缓冲区
    private var buffer = [Float]()
    /// 渐变
    private var gradientView = CLGradientView(startColor: .red, endColor: .green, direction: .topToBottom)
    /// shaplayer
    private var shapeLayer = CAShapeLayer()
    /// path
    private var path = UIBezierPath()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        // 加bufferSize个0
        for _ in 0..<bufferSize {
            buffer.append(0.0)
        }
        addSubview(gradientView)
        gradientView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        self.layer.mask = shapeLayer
        shapeLayer.fillColor = UIColor.cyan.cgColor
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.lineWidth = 2.0
        shapeLayer.lineJoin = .round
        shapeLayer.lineCap = .round
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    /// 音量振幅
    func audioPlayer(_ player: PHAudioPlayer, amplitude: Float) {
        if buffer.count < bufferSize {
            buffer.append(amplitude)
        } else {
            buffer.removeFirst()
            buffer.append(amplitude)
        }
        updatePath()
    }
    
    /// 更新path
    func updatePath() {
        path.removeAllPoints()
        let width = bounds.width / CGFloat(bufferSize - 1)
        let height = bounds.height
        path.move(to: CGPoint(x: 0, y: height))
        for (index, value) in buffer.enumerated() {
            let x = CGFloat(index) * width
            let y = height - CGFloat(value) * height
            path.addLine(to: CGPoint(x: x, y: y))
        }
        path.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
        path.close()
        shapeLayer.path = path.cgPath
    }
    
    
}

结语

在本篇博客中,我们探讨了一种更为复杂但功能强大的音频播放方式,即使用 AVAudioEngine 实现音频的播放与处理。虽然相较于 AVAudioPlayer 和 AVPlayer,这种方式的实现稍显复杂,但它为我们提供了更灵活的音频处理能力,特别是在实时获取音频样本数据和绘制音频波形图方面。

通过利用 AVAudioEngine 的实时处理功能,我们能够精确地获取音频样本中的音量信息,并基于此动态绘制音频的波形图。这种方法不仅展现了 AVAudioEngine 的强大功能,也为开发者提供了实现复杂音频需求的有效途径。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux:Linux线程池

目录 线程池的概念 线程池的优点 线程池的应用场景 线程池的实现 线程池演示 线程池的概念 线程池是一种线程使用模式。 线程过多会带来调度开销&#xff0c;进而影响缓存局部和整体性能&#xff0c;而线程池维护着多个线程&#xff0c;等待着监督管理者分配可并发执行的…

长视频生成研究的挑战、方法与前景

人工智能咨询培训老师叶梓 转载标明出处 长视频生成面临的主要挑战包括如何在有限的计算资源下生成长时间、高一致性、内容丰富且多样化的视频序列。另外现有研究中对于“长视频”的定义并不统一&#xff0c;这给研究的标准化和比较带来了困难。来自西安电子科技大学、上海交通…

Window 安装Gogs教程

1、下载 下载地址&#xff1a;https://gogs.io/docs/installation/install_from_binary.html(请自行科学上网 选择Windows amd64(64位)或者386(32位) 2、安装 2.1 将压缩文件放到目标文件夹 2.2 创建数据库 在本地数据库或者其他目标数据库新建查询执行下列SQL语句 找到go…

taskBus的设计局限和吞吐能力测试

在前文中&#xff0c;我们介绍了EPDR技术的起源&#xff0c;以及使用该技术驱动的业余软件无线电平台专栏。已有玩家通过踩坑证明&#xff0c;进程管道交换数据时间延迟大&#xff08;10ms&#xff09;&#xff0c;构造时间敏感系统难。除非采用传统的紧耦合设计及更大的颗粒度…

尚品汇-选中状态缓存变更、删除缓存购物车(三十八)

目录&#xff1a; &#xff08;1&#xff09;选中状态的变更 &#xff08;2&#xff09;删除购物车 &#xff08;3&#xff09;流程总结 &#xff08;1&#xff09;选中状态的变更 用户每次勾选购物车的多选框&#xff0c;都要把当前状态保存起来。由于可能会涉及更频繁的操…

基于AT89C51单片机的可手动定时控制的智能窗帘设计

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/89469560?spm=1001.2014.3001.5503 C 源码+仿真图+毕业设计+实物制作步骤+11 摘要 I abstract II 第1章 绪论 1 1.1 背景及意义 1 1.2 国内外发展现状 1 1.3 设计思想及基…

ChatGPT等大模型高效调参大法——PEFT库的算法简介

随着ChatGPT等大模型&#xff08;Large Language Model&#xff09;的爆火&#xff0c;而且目前业界已经发现只有当模型的参数量达到100亿规模以上时&#xff0c;才能出现一些在小模型无法得到的涌现能力&#xff0c;比如 in_context learing 和 chain of thougt。深度学习似乎…

Excel如何快速的定位到某一列和快速知道当前列

Excel如何快速的定位到某一列和快速知道当前列 背景快速找到某一列---660列快速知道当前列 背景 由于某一次做excel数据太大需要快速知道某一列是多少列和快速定位到某一列对此写了这个 快速找到某一列—660列 SUBSTITUTE(ADDRESS(1, 660, 4), "1", ""…

实现MySQL的主从复制基础

目录 1 MySQL实现主从复制的原理 1.1 实现主从复制的规则 1.2 如何实现主从复制 2 MySQL 实现主从复制实践 2.1 实验环境 2.2 my.cnf 配置添加 2.2.1 配置MSTER 端配置文件 2.2.2 配置SLAVE 端配置文件 2.2.3 三台MySQL服务器重启服务 2.3 创建用于复制的用户 2.4 保证三台主机…

Android实战:过root检测

在启动这个app时&#xff0c;我们会看到一个提示&#xff0c;表示设备处于root环境。如下图所示&#xff1a; 为了过掉到这个root检测&#xff0c;我们可以通过直接Hook Toast.show()方法&#xff0c;并打印调用堆栈信息来实现定位关键代码。以下是相关的Frida脚本代码&#…

esxi 安装 精简版win10

镜像来源&#xff1a;[【不忘初心】Windows10 22H2 (19045.4780) X64 无更新 纯净[深度精简版]1.27G](https://www.pc528.net/22h2s.html) 提供下载地址&#xff1a;https://www.123pan.cn/s/lYtRVv-Wmuf3?提取码:GaD4 先把下载esd 转成iso安装 把下载的esd 重命名为install…

如何使用ssm实现学生宿舍管理

TOC ssm094学生宿舍管理jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。这样…

YOLOv5改进 | 融合改进 | C3融合EffectiveSE-Convolutional【完整代码 + 小白必备】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录&#xff1a; 《YOLOv5入门 改…

如何用comate快速生成一个剩菜好帮手

想法 上班后不想吃饭店的饭菜&#xff0c;时长想自己做一些饭菜&#xff0c;买完菜后却经常放到冰箱中&#xff0c;剩下的菜有无法一下子处理&#xff0c;单纯扔掉有些可惜&#xff0c;但是基于冰箱中的剩菜如何能做出一顿像样的饭菜一致困扰着我&#xff0c;查市面上的程序有…

在不修改应用数据源的情况下,如何确保应用程序能够正常访问adg切换后的主库?

在不修改应用数据源的情况下&#xff0c;如何确保应用程序能够正常访问adg切换后的主库&#xff1f; oracle12c rac测试通过&#xff1a; 1.修改原主库的scanip为某个临时ip&#xff0c;新主库的scanip修改为原生产 2.修改新主库的service_names&#xff1a;dgorcl为原生产的…

学习2d直线拟合

直线拟合算法&#xff08;续&#xff1a;加权最小二乘&#xff09;_加权拟合直线法-CSDN博客 直线拟合算法_相位拟合直线-CSDN博客 特别感谢博主无私分享 博文中提到的参考资料《机器视觉算法与应用&#xff08;双语版&#xff09;》[德] 斯蒂格&#xff08;Steger C&#x…

GPT-4o语音功能潜在风险分析与技术挑战

引言 近年来&#xff0c;随着大语言模型&#xff08;LLM&#xff09;技术的飞速发展&#xff0c;人工智能的能力在语音处理领域也取得了显著进展。OpenAI推出的GPT系列模型正成为人工智能领域的标杆。然而&#xff0c;在最新的GPT-4o版本中&#xff0c;尽管语音功能具备广阔的…

vue3 多文件下载zip压缩包

vue3多文件下载zip文件包 效果图 代码块 在这里插入代码片 <template><div><el-button type"primary" click"downLoadClick">下载文件zip</el-button></div> </template><script setup lang"ts"> i…

Springsecurity 自定义AuthenticationManager

一、认证流程 1、当用户提交了一个他的凭证(用户名、密码) AbstractAuthenticationProcessingFilter 将会创建一个凭证信息&#xff0c;最终&#xff0c;该请求会被UsernamePasswordAuthenticationFilter 拦截将请求中用户名和密码&#xff0c;封装为 Authentication 对象&…

4个学生党必备好用 AI 学术论文写作工具

随着人工智能技术的不断进步&#xff0c;AI论文写作工具已成为研究人员和学生的得力助手。学姐今天将介绍4个市面上广受好评的免费AI论文写作工具&#xff0c;它们能帮助用户高效地完成从论文大纲到最终校对的各个阶段。 一、梅子AI论文 梅子AI提供快速论文撰写功能&#xff…