xiaozongpeng 发表于 2023-1-9 14:31

怎样给Unity项目引入Apple的PHASE,从而实现SpatialAudio?

写这篇文章,主要是为了吐槽Apple给Unity开发的PHASE这个插件的。
需要说明一下,PHASE不支持杜比音效(但是PHASE可以将普通双声道分解成5.1 声道、7.1 声道,这就有了空间化的基础),不支持头部追踪,如果想实现头部追踪,需要借助其他插件配合PHASE,比如
下载该PHASE仓库,然后mac上build(生成.a给unity的plugins, 以及生成tgz压缩文件) (需要先安装python3以及npm, npm负责打包成tgz, python3负责编译成.a)之后打开PHASE的unity demo工程,发现可以正常执行。
遂将其引入项目中。将某个场景中添加多个PHASESource作为环境音,比如峡谷风声,瀑布声音。 然后在生成角色Player的时候给Player挂上PHASEListener。
没想到居然遇到了问题,有时候可以正常播放声音,有时候会有报错提示(Failed to play sound event: xxx)并且听不到声音。仔细观察错误堆栈,没啥结论。偶然想到可能是因为player置后与场景的加载,这就会导致PHASEListener滞后于PHASESource,如果将PHASEListener先于PHASESource加载,那么可能就可以正常听到声音了。在demo工程中尝试了一下,果然如此。为什么开源的demo工程可以正常工作呢?是因为demo是一个静态环境,提前已经挂载好了PHASESource和PHASEListener,不会有这个PHASEListener滞后的情况。
此时就不得不提Unity和PHASE设计哲学的区别了。Unity是AudioSource不管有没有AudioListener都会正常播放声音,只不过没有AudioListener会听不到声音。而PHASESource则必须在PHASEListener存在得情况下才能正常工作,同时还有一个吊轨的地方,disable PHASEListener之后发现依然可以听到声音。而且整个开源PHASE工程就没有开启Issue(意味着遇到问题别烦我),从这里就可以看出,Apple开源这个PHASE插件其实只是为了开源,压根没想过和Unity做一个很好的设计哲学上的兼容,以及后续的工程维护,简单答疑。
所以为了让PHASEListener先于PHASESource执行,就先用一个临时的PHASElistener工作,当Player加载完成的时候用Player上的正式的PHASEListener顶替临时的PHASEListener,也就是disable临时的,激活正式的(老的PHASEListener和新的PHASEListener位置不一样)。但是还是遇到问题了,发现整个声音效果的混音,音量大小,cull distance等效果全部是基于老的PHASEListener的。为了尝试解决这个问题,在demo上测试发现动态替换PHASEListener之后,好像重新激活一下PHASESource就可以正常工作了。在项目中试了,发现还是不行。就开始改变策略,始终用一个PHASEListener,不在中途动态替换只是动态修改PHASEListener的位置,也就是在Player加载完成之后将PHASEListener挂载到Player上,否则就在一个固定位置上挂载着。该方案暂时通过了。
但是在切换场景到最早的登陆场景之后又遇到了新的问题(其实就是注销操作会返回到登陆场景),会发现上一个场景的声音有残留。百思不得其解,看开源出来的从C#代码会在OnDestroy的时候销毁声音,所以理论上声音应该消失的。在PHASESource的可疑的地方输出日志,主要在标注的CompletionHandler函数中发现remove _sourceId的时候不太正常(这里源码remove逻辑有问题)。在demo工程中建立一个切换场景的demo, 观察日志多次发现原来是切换场景之后PHASEListener被销毁了,导致PHASEUpdate没被调用导致的。于是将PHASEEngine相关的代码从PHASEListener中剥离出来成单独的文件,即剥离engine的start,engine的stop, engine的update。


将该mono的生命周期和游戏保持同步。这样就解决了切换场景导致PHASEListener销毁,声音残留的问题。
继续推进遇到了新的问题,发现切换到非登录场景(PHASListener不被销毁),然后再切回到环境音场景发现听不到声音了。怀疑是SpatialMixer的cull distance影响的,将其全部改成0发现这种情况下可以听到声音但是效果全错了,音量的远近效果之类的全过了。所以问题应该就出在cull distance这里。想不到什么对策,只能委托中间人问问Apple的PHASE的技术人员,Apple给出的回复是,尝试engine.stop然后engine.start的重置大法。我在项目中场景切换的时候进行重置操作,发现问题解决了。然后我又尝试将这种修改PHASEListener位置的方案替换为最原始的那种动态替换PHASEListener的方案,同时重置engine依然可以正常执行。果然重置大法好啊。之前也有过重置的想法,但是不知道要怎么重置,归根结底是因为不知道底层PHASEListener, PHASESource和PHASEEngine之间到底是什么关系。
还遇到一个问题是切换到其他平台比如pc平台然后打包player会有问题,是因为PHASE相关的程序集只在editor和ios/tvos/macox等平台可以使用,自然在打包pc的时候这些PHASE代码就会失效。也就是说PHASE的c#代码没有用平台宏区分各种平台。

总结一下这个开源工程的槽点:a)和unity的audio的设计哲学不一样,要适应不同的设计哲学b)动态替换listener不方便而且没有相关democ)切换场景导致listener销毁声音会残留,也无demo d)代码逻辑错误,比如PHASESource.cs中的remove _sourceId的逻辑。e)engine和listener混杂到一起,层次不清晰。 f)没有issue导致遇到问题很难解决 g)代码没有宏区分

refs:
(4 条消息) 会丢锅的coder 的想法: 不得不吐槽下,apple给unity开发的PHASE… - 知乎 (zhihu.com)
会丢锅的coder 的想法: 怎么使用Apple提供给Unity的插件?比如空… - 知乎 (zhihu.com)
(4 条消息) 会丢锅的coder 的想法: 关于空间音频?就是让你感觉声音不是从耳… - 知乎 (zhihu.com)
【WWDC21 10079】使用 PHASE 探索几何感知的音频 - 小专栏 (xiaozhuanlan.com)
【WWDC21 10265】将你的应用沉浸在空间音频中 - 小专栏 (xiaozhuanlan.com)
【WWDC22 10065/10151/10064/11093】即插即用:将 Apple frameworks 添加到您的 Unity 游戏项目 - 小专栏 (xiaozhuanlan.com)
页: [1]
查看完整版本: 怎样给Unity项目引入Apple的PHASE,从而实现SpatialAudio?