【iOS ARKit】播放3D音频
3D音频
在前面系列中,我们了解如何定位追踪用户(实际是定位用户的移动设备)的位置与方向,然后通过摄像机的投影矩阵将虚拟物体投影到用户移动设备屏幕。如果用户移动了,则通过VIO 和 IMU更新用户的位置与方向信息,更新投影矩阵,这样就可以把虚拟物体固定在空间的某点上(这个点就是锚点),从而达到以假乱真的视觉体验。
3D音效处理的目的是让用户进一步相信AR 应用虚拟生成的数字世界是真实的,营造沉浸的AR体验。事实上,3D音效在电影、电视、电子游戏中被广泛应用,但在AR 场景中,3D声音的处理有其特别之处,类似于电影采用的技术并不能很好地解决 AR中3D音效的问题。
在电影院中,观众的位置是固定的,因此可以通过在影院的四周都加装上音响设备,通过设计不同位置音响设备上声音的大小和延迟,就能给观众营造逼真的3D 声音效果。经过大量的研究与努力,人们根据人耳的结构与声音的传播特性开发出了很多技术,可以只用两个音响或者耳机就能模拟出3D音效,这种技术叫双耳声(Binaural Sound),它的技术原理如图11-2所示。
在图11-2中,从声源发出来的声音会直接传播到左耳和右耳,但因为左耳离声源近,所以声音会先到达左耳再到达右耳,由于在传播过程中的衰减,左耳听到的声音要比右耳大,这是直接的声音信号,大脑会接收到两只耳朵传过来的信号。同时,从声源发出的声音也会被周围的物体反射,这些反射与直接信号相比有一定的延迟并且音量更小,这些是间接的声音信号。大脑会采集到直接信号与所有的间接信号并比较从左耳与右耳采集的信号,经过分析计算,从而达到定位声源的效果。在了解大脑的工作模式后,就可以通过算法控制两个音响或者耳机的音量与延迟来达到模拟3D声源的效果,让大脑产生出虚拟的3D声场效果。
3D声场原理
3D声场,也称为三维音频、虚拟3D音频、双耳音频等,它是根据人耳对声音信号的感知特性,使用信号处理的方法对到达两耳的声音信号进行模拟,以重建复杂的空间声场。
通俗地说就是把耳朵以外的世界看作一个系统,对任意一个声音源,在耳膜处接收到信号后,三维声场重建就是把两个耳朵接收到的声音尽可能准确地模拟出来,让人产生听到三维音频的感觉。
如前所述,当人耳在接收到声源发出的声音时,人的耳廓、耳道、头盖骨、肩部等对声波的折射、绕射和衍射及鼓膜接收到的信息会被大脑所接收,大脑通过经验对声音的方位进行判断。与大脑工作原理类似,在计算机中通过信号处理的数学方法,构建头部相关传输函数(HeadRelated Transfer Functions, HRTF),根据多组的滤波器计算人耳接收到的声源的“位置信息”。
目前3D声场重建技术口冬比校成戴们不时加送之上术都假设用户是静止的(或者说与用户位置无关),而在 AR应用中,情况却有很大不同,AR应用的用户是随时移动的,这意味着用户周围的3D声音也需要调整,这一特殊情况导致目前的3D声场重建技术在AR应用时失效。
RealityKit 中的 3D 音效
ARKit 通过世界跟踪功能定位声源位置,然后根据用户与声源的相对位置和方向自动混音,将3D音频技术带人 AR中。在 AR场景中放置一个声源,当用户接近或远离时,声音音量大小会自动增加或减弱,当用户围绕声源旋转时,声音也会呈现沉浸式的3D效果。在 RealityKit 中,使用3D音效的典型代码如代码如下所示,稍后我们将对代码进行详细解析。
//
// Audio3DView.swift
// ARKitDeamo
//
// Created by zhaoquan du on 2024/3/22.
//import SwiftUI
import ARKit
import RealityKit
import Combinestruct Audio3DView: View {var body: some View {Audio3DViewContainer().navigationTitle("3D音频").edgesIgnoringSafeArea(.all)}
}struct Audio3DViewContainer:UIViewRepresentable {func makeUIView(context: Context) -> some ARView {let arView = ARView(frame: .zero)let config = ARWorldTrackingConfiguration()config.planeDetection = .horizontal//createPlane(arView: arView)arView.session.run(config)arView.createAudioPlane()return arView}func updateUIView(_ uiView: UIViewType, context: Context) {}static var audioEvent : Cancellable!func createPlane(arView:ARView){let planAnchor = AnchorEntity(plane: .horizontal)let boxMesh = MeshResource.generateBox(size: 0.2)let boxMaterial = SimpleMaterial(color: .red, isMetallic: true)let boxEntity = ModelEntity(mesh: boxMesh, materials: [boxMaterial])guard let audio = try? AudioFileResource.load(named: "fox.mp3",in: .main, inputMode: .spatial,loadingStrategy: .preload,shouldLoop: false) else {return}let audioControler = boxEntity.prepareAudio(audio)audioControler.play()boxEntity.generateCollisionShapes(recursive: false)planAnchor.addChild(boxEntity)arView.scene.addAnchor(planAnchor)arView.installGestures(for: boxEntity)Audio3DViewContainer.audioEvent = arView.scene.subscribe(to: AudioEvents.PlaybackCompleted.self) { event inprint("音频播放完毕")}}
}
var audioEvent : Cancellable!
extension ARView{func createAudioPlane(){do{let planeAnchor = AnchorEntity(plane:.horizontal)let boxMesh = MeshResource.generateBox(size: 0.2)let boxMaterial = SimpleMaterial(color:.red,isMetallic: true)let boxEntity = ModelEntity(mesh:boxMesh,materials:[boxMaterial])let audio = try AudioFileResource.load(named:"fox.mp3",in:.main,inputMode: .spatial,loadingStrategy: .preload,shouldLoop: false)boxEntity.playAudio(audio)let audioController = boxEntity.prepareAudio(audio)audioController.play()boxEntity.generateCollisionShapes(recursive: false)planeAnchor.addChild(boxEntity)self.scene.addAnchor(planeAnchor)self.installGestures(.all,for:boxEntity)audioEvent = self.scene.subscribe(to: AudioEvents.PlaybackCompleted.self){ event inprint("音频播放完毕")}}catch{print("Error Loading audio file")}}
}#Preview {Audio3DView()
}
编译运行 AR 应用,使用耳机(注意耳机上的左右耳塞勿戴反,一般会标有I.和 R字样)或者双通道音响体验3D音效,在检测到的平面放置虛拟立方体对象后,移动手机或者旋转手机朝向,体验在 AR场景中声源定位的效果。
从代码清单 11-3中我们可以看出,在 RealityKit 中使用3D音频分为3步:
(1) 加载音频。
(2)设置音频播放参数。
(3)将音频放置到 AR 场景中并播放。
下面我们针对这3个步骤进行详细学习。在 RealityKit 中使用音频,必须将音频加载为 AudioResource(或者其子类 AudioFileResource)类型对象才能正确播放,通常使用 AudioFileResource 类将音频从文件系统或者 URL 加载到内存中,该类有4个方法,可以同步/异步从文件/URL 中加载音频,如表11-2所示。
表 11-2 AudioFileResource 类加载音频方法
方法 | 描述 |
load (named: String, in: Bundle?,inputMode: AudioResource. InputMode,loadingStrategy: AudioFileResource. LoadingStrategy,shouldLoop: Bool)—> AudioFileResource | 同步从程序 Bundle 中加载音频 |
loadAsync(named: String,in: Bundle?, inputMode: AudioResource. InputMode,loadingStrategy: AudioFileResource. LoadingStrategy,shouldLoop: Bool)—> LoadRequest < AudioFileResource > | 异步从程序 Bundle 中加载音频 |
load ( contentsOf: URL, withName: String?, inputMode: AudioResource. InputMode,loadingStrategy: AudioFileResource. LoadingStrategy,shouldLoop: Bool) -> AudioFileResource | 同步从 URL 中加载音频 |
load Async(contentsOf: URL, withName: String?, inputMode: AudioResource. InputMode,loadingStrategy: AudioFileResource. LoadingStrategy,shouldLoop: Bool) -> LoadRequest < AudioFileResource > | 异步从 URL 中加载音频 |
在 RealityKit 中加载音频与加载模型一样,每一种同步加载方法都有对应的异步加载方法。加载方法中的参数因加载方法不同而不同,基本的参数及其意义如表11-3所示。
表11-3 加载音频方法中各参数的意义
描述 | 方法 |
named | 从 Bundle 中加载时文件路径与名称 |
contentsOf | 从 URL 中加载时的 URL 地址 |
withName | 从 URL 中加载时的音频名称 |
in | 从程序 Bundle 中加载时音频所在 Bundle 名称 |
shouldLoop | 布尔值,是否循环播放 |
loadingStrategy | AudioFileResource. LoadingStrategy 枚举类型,指定加载音频时的策略,共有两个枚举值:preload (预加载音频,在使用之前将音频加载到内存中)、stream(流媒体编码,边加载边播放)。通常在使用时,preload 适合短小、内存占用少,播放频度高的音频,而 stream适合较长、播放频度低的音频 |
inputMode | AudioResource. InputMode 枚举类型,指定3D音频类型,共有3个枚举值:nonSpatial(不使用3D音效)、spatial(使用空间音效)、ambient(环境音效,声音不会随距离发生音量变化,但声音可以反映方向变化,如用户围绕声源转动时,音效会发生变化) |
在加载音频完成后,可以通过实体对象的 prepareAudio(_:)方法获取一个 AudioPlaybackController 类型的音频控制器,利用该控制器可以使用其 play()、pause()、stop()方法控制音频的播放,可以通过isPlaying 属性获取音频的播放状态,还可以设置音频的播放增益(gain)、速度(speed)、混音(reverb SendL.evel),及衰減(fade()方法)和音频播放完后的回调(completionHandler),可以满足音频使用的各类个性化需求。
通常在 AR中使用3D,音频,需要将音频绑定到实体对象(Entity)上,当实体对象放置在场景中时,实体对象所在的空间位置即为声源位置,RealityKit 会根据用户设备所在空间位置与声源位置进行3D音效模拟,营造沉浸式的声场效果。
利用 AudioPlaybackController 类可以很方便地控制音频的播放,而且可以重复地进行暂停、播放等操作,但如果只需要一次性地播放,也可以不使用该类,而直接使用boxEntity. playAudio(audio),这种方法更简洁,当音频播放完后即结束,特别适合3D物体音效模拟,如子弹击中柽物时的音频播放。
在使用 AudioPlaybackController 类控制音频播放时,可以通过其 completionHandler 属性设置音频播放完后的回调函数。除此之外,也可以通过订阅 AudioEvents 事件进行后续处理,目前,音频只有一个AudioEvents. PlaybackCompleted 事件,即音频播放完毕事件。
在订阅 AudioEvents事件时有两点需要注意,一是保存事件订阅的引用,不然无法捕获事件,具体可参阅第2章相关内容:三是只有当 shouldLoop 设置为 false(即不循环播放)时,才会触发 AudioEvents.PlaybackCompleted 事件。
相关文章:

【iOS ARKit】播放3D音频
3D音频 在前面系列中,我们了解如何定位追踪用户(实际是定位用户的移动设备)的位置与方向,然后通过摄像机的投影矩阵将虚拟物体投影到用户移动设备屏幕。如果用户移动了,则通过VIO 和 IMU更新用户的位置与方向信息&…...
ES学习日记(四)-------插件head安装和一些配套插件下载
前言 接上节,第三方插件选择了时间久,功能丰富,长得丑的head,head 插件在ES 5版本以前开箱即用非常简单,ES 5版本以后需要运行在node环境下,所以我们要先准备一下环境 一.安装Git 不装了,明儿再说,看会儿手机准备下班!!!!!!!!!...
flask+uwsgi+云服务器 部署服务端
参考:使用uwsgi部署flask 报错 “找不到Python应用程序,请检查启动日志以查找错误” 或者: no python application found, check your startup logs for errors debug 过程:查到Python uWSGI 安装配置 里面说,先写测…...

linux学习之路 -- 普通用户添加进sudoer列表
在Linux系统里,很多的操作普通用户是不能执行的,所以我们需要对普通用户进行提权操作,可我们会发现,一开始没有配置的话,是无法的提权操作的,下面我将介绍普通用户该如何配置sudoer列表。 首先以root 的身…...

【分类评估指标,精确率,召回率,】from sklearn.metrics import classification_report
from: https://zhuanlan.zhihu.com/p/368196647 多分类 from sklearn.metrics import classification_report y_true [0, 1, 2, 2, 2] y_pred [0, 0, 2, 2, 1] target_names [class 0, class 1, class 2] # print(classification_report(y_true, y_pred, targe…...

element-ui autocomplete 组件源码分享
紧接着 input 组件的源码,分享带输入建议的 autocomplete 组件,在 element-ui 官方文档上,没有这个组件的 api 目录,它的 api 是和 input 组件的 api 在一起的,看完源码之后发现,源码当中 autocomplete 组件…...
视觉SLAM理论与实践的学习链接汇总
仅供学习,在此感谢所有乐于分享知识的大佬们~ 一、 ORB_SLAM理论 视觉SLAM 前端 后端 回环 建图 1、 前端视觉里程计 1.1 特征点法 一文带你搞懂相机内参外参(Intrinsics & Extrinsics)-知乎 VSLAM 笔记——我们如何通过图像来计算位姿的变化ÿ…...

极光笔记|极光消息推送服务的云原生实践
摘要 极光始终秉承“以开发者为中心”的战略导向,极光推送(JPush)是国内领先的消息推送服务。极光推送(JPush)本质上是一种软件付费应用程序,结合当前主流云厂商基础施设,逐渐演进成了云上SaaS…...
高效八股文背诵方法
往往到了找工作高峰期,经常会出现八股文很多 难以背诵 的苦恼,下面在下结合情况,列举了几点自认为可以的背诵方法: 1. **大声朗读**: - 对于Java核心概念和重要理论,先大声朗读,这不仅可以帮…...

Codeforces Round 841 (Div. 2) C. Even Subarrays
题目 思路: #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e9, maxm 4e4 5; co…...

用 SpringBoot+Redis 解决海量重复提交问题
1前言 在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何…...

前端基础知识html
一.基础标签 1.<h1>-<h6>:定义标题,h最大,h最小 2.<font>:定义文本的字体,尺寸,颜色 3.<b>:定义粗体文本 4.<i>:定义斜体文本 5.<u>:定义文本下…...

网络原理-传输层-UDP报文结构
本文介绍UDP报文 有很多友友搞不清楚UDP报文的详细结构还有TCP的详细结构,所以专门分开来讲 以免弄混. 首先我们先看一下整个UDP结构,让大家有一个全方面的认识 下面我们来详细解释UDP报 16位源端口号(本机):就是2字节大小,16个二进制位. 16位目的端口号(目的机):也是2字节…...
TCP/IP参考模型(四层及其解析)
文章目录 1、什么是TCP/IP2、四层协议2.1 应用层(应用程序协议)2.2 传输层(源端口↔️目的端口)2.3 网络层(主机↔️主机)2.4 网络接口层(主机↔️网络层) 总结 1、什么是TCP/IP TC…...

2024第六届环境科学与可再生能源国际会议能源 (ESRE 2024) 即将召开!
2024第六届环境科学与可再生能源国际会议 能源 (ESRE 2024) 即将举行 2024 年 6 月 28 日至 30 日在德国法兰克福举行。ESRE 2024 年 旨在为研究人员、从业人员和专业人士提供一个论坛 从工业界、学术界和政府到研究和 发展,环境科学领域的专…...
CentOS配置docker外部访问
CoreOS 官方文档提供的方法 官方文档:https://coreos.com/os/docs/latest/customizing-docker.html 新建 /etc/systemd/system/docker-tcp.socket 文件 [Unit] DescriptionDocker Socket for the API[Socket] # ListenStream127.0.0.1:2375 ListenStre…...
面试前端八股文十问十答第二期
面试前端八股文十问十答第二期 作者:程序员小白条,个人博客 相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新! ⭐点赞⭐收藏⭐不迷路!⭐ 1)从输入URL到页面加载的全过程…...

【漏洞复现】大华综合安防监控管理平台 Digital Surveillance System系统存在RCE漏洞
免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…...

ssm网上订餐管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目采用线性算法
一、源码特点 ssm 网上订餐管理系统是一套完善的信息系统,结合springMVC框架完成本系统,对理解JSP java编程开发语言有帮助系统采用SSM框架(MVC模式开发),系统具有完整的源代码和数据库,系统主要采用B/S模…...
python 进程之由浅入深
进程测试 import osimport time while True:time.sleep(0.5)print("hahaha")print("self", os.getpid()) #获取自己的进程idprint("parent",os.getppid()) #parent 获取父进程的id互斥锁 # """ # 当多个进程共享一个数据时…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...