【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 Combine

struct 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 in
            print("音频播放完毕")
        }
        
    }
}
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 in
                print("音频播放完毕")
            }
        }
        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 事件。

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

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

相关文章

STM32-01基于HAL库(CubeMX+MDK+Proteus)仿真开发环境搭建(LED点亮测试实例)

STM32-01基于HAL库&#xff08;CubeMXMDKProteus&#xff09;仿真开发环境搭建&#xff08;LED点亮测试实例&#xff09; 一、 开发工具版本列表二、安装过程三、实例测试&#xff08;点亮单个LED&#xff09;0、功能需求分析1、Proteus绘制电路原理图2、STMCubeMX 配置引脚及模…

MVC与MVVM:两种前端架构模式对比

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

sensitive-word-admin v1.3.0 发布 如何支持敏感词控台分布式部署?

拓展阅读 sensitive-word-admin v1.3.0 发布 如何支持分布式部署&#xff1f; sensitive-word-admin 敏感词控台 v1.2.0 版本开源 sensitive-word 基于 DFA 算法实现的高性能敏感词工具介绍 更多技术交流 业务背景 如果我们的敏感词部署之后&#xff0c;不会变化&#xff0c;那…

华为升级FIT AP示例(通过AC的命令行)

升级FIT AP示例&#xff08;通过AC的命令行&#xff09; 前提条件 从官网下载升级目标版本对应的系统软件包&#xff0c;保存在PC本地。如果下载的文件是压缩文件&#xff0c;则需要解压缩出系统软件包。 AP已在WAC上线。 背景信息 升级的过程是先将系统软件包传到设备上&…

视频无水印爬虫采集工具|抖音视频批量下载软件|可导出视频分享链接

全新视频无水印爬虫采集工具&#xff0c;助力您快速获取所需视频&#xff01; 视频无水印爬虫采集工具&#xff0c;为用户提供了强大的视频采集和下载功能。它可以批量提取关键词相关的视频&#xff0c;同时支持单独视频的提取和下载&#xff0c;操作简便&#xff0c;使用方便。…

WPF---1.入门学习

学习来源 布局 wpf布局原则 一个窗口中只能包含一个元素 不应显示设置元素尺寸 不应使用坐标设置元素的位置 可以嵌套布局容器 StackPanel-->表单条件查找布局 DataGrid wpf布局容器 StackPanel: 水平或垂直排列元素&#xff0c;Orientation属性分别: Horizontal / Vertic…

java项目加载lib下的jar包

一、选择项目结构 二、点击模块-->依赖-->加号 三、选择lib下的jar包 四、加载成功 完成 ps&#xff1a;部署直接部署&#xff08;jar包加载就行&#xff09;

SpringBoot集成Solr全文检索

SrpingBoot 集成 Solr 实现全文检索 一、核心路线 使用 Docker 镜像部署 Solr 8.11.3 版本服务使用 ik 分词器用于处理中文分词使用 spring-boot-starter-data-solr 实现增删改查配置用户名密码认证使用 poi 和 pdfbox 组件进行文本内容读取文章最上方有源码和 ik 分词器资源…

Matlab|基于模型预测控制(MPC)的微电网调度优化的研究

目录 1 主要内容 2 程序难点及问题说明 3 部分程序 4 下载链接 1 主要内容 该程序分为两部分&#xff0c;日前优化部分——该程序首先根据《电力系统云储能研究框架与基础模型》上面方法&#xff0c;根据每个居民的实际需要得到响应储能充放电功率&#xff0c;优化得到整…

前端学习之css基本网格布局

网格布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>网格布局</title><style>.a{/* grid网格布局 */display: grid;width: 400px;height: 400px;border: 1px solid red;/* 设置当前…

华为数通方向HCIP-DataCom H12-821题库(多选题:181-200)

第181题 在BGP中Community属性为可选过渡属性,是一种路由标记,用于简化路由策略的执行。它分为自定义团体属性和公共团体属性,那么以下属于公共团体属性的是哪些项? A、No_Export_Subconfed B、No_Advertise C、No_Export D、Internet 【正确答案】ABCD 【答案解析】 第18…

OpenCV 形态学处理函数

四、形态学处理&#xff08;膨胀&#xff0c;腐蚀&#xff0c;开闭运算&#xff09;_getstructuringelement()函数作用-CSDN博客 数字图像处理(c opencv)&#xff1a;形态学图像处理-morphologyEx函数实现腐蚀膨胀、开闭运算、击中-击不中变换、形态学梯度、顶帽黑帽变换 - 知乎…

LeetCode Python - 74. 搜索二维矩阵

目录 题目描述解法方法一&#xff1a;二分查找方法二&#xff1a;从左下角或右上角搜索 运行结果方法一方法二 题目描述 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给…

电脑如何更新AMD独立显卡驱动?安装官方驱动的方法来了!

前言 有小伙伴在电脑上安装了独立显卡之后&#xff0c;总会用驱动人生或者驱动精灵等软件给独立显卡安装驱动。这种安装方法并不能说是错的&#xff0c;反正能用就行。 安装官方驱动的办法其实很简单&#xff0c;现在独立显卡一共就那么几家&#xff0c;最常见的显卡就是Nvidi…

技术周刊 117 期:Visual Copilot、INP、Kimi 支持 200 万字上下文、Grok 开源、Figure 01、Open Sora 开源

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;金骏眉 大家好&#xff0c;我是童欧巴。老规矩&#xff0c;咱们先来看技术资讯。 技术资讯 前端 VitePress (早就应该) 1.0 发布MistCSS&#xff0c;只使用 CSS 来…

2024-03-25 作业

作业要求&#xff1a; 整理思维导图定义自己的命名空间&#xff0c;其中有string类型的变量&#xff0c;再定义两个函数&#xff0c;一个函数完成字符串的输入&#xff0c;一个函数完成求字符串长度&#xff0c;再定义一个全局函数完成对该字符串的反转有以下定义&#xff0c;说…

Vue复习

1. MVVM 模型 ● Model&#xff08;模型&#xff09;&#xff1a;表示应用程序中的数据模型。它代表着应用程序中的业务逻辑和状态。 ● View&#xff08;视图&#xff09;&#xff1a;表示应用程序的用户界面。它是用户与应用程序交互的方式。 ● ViewModel&#xff08;视图模…

家政服务管理平台设计与实现|SpringBoot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

Kotlin协程CoroutineScope命名空间CoroutineName,Kotlin

Kotlin协程CoroutineScope命名空间CoroutineName&#xff0c;Kotlin import kotlinx.coroutines.*fun main(args: Array<String>) {val myName CoroutineName("fly")runBlocking {CoroutineScope(Dispatchers.IO).launch {repeat(3) {val name coroutineCont…

后端常问面经之并发

volatile 关键字 volatile关键字是如何保证内存可见性的&#xff1f;底层是怎么实现的&#xff1f; "观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现&#xff0c;加入volatile关键字时&#xff0c;会多出一个lock前缀指令”lock前缀指令实际上相…