WAAPI+Python使用中的相关问题和学习记录
首先鸣谢:溪夜大佬的博客:https://blog.audiokinetic.com/zh/everyone-can-use-waapi-overview/
本文环境:
Wwise 2019.1.9.7221
python 3.6.2(Anaconda3)
UE4 4.24.3
防伪刮刮乐:原文地址:https://www.cnblogs.com/lingchuL/p/14364085.html
1. 配置环境
自己平时都是python 3.6.3裸奔,这次想试试Anaconda3,不得不说,有一个管理环境的工具确实要方便一些。
不要使用python 3.8!因为目前(2021.2),WAAPI的源码无法完全兼容python3.8。虽然pypl上写着自己支持3.8和3.9,但就是会因为time.block这个方法被3.8舍弃而没法import waapi-client。
1. Anaconda3的安装:https://segmentfault.com/a/1190000022797661
2. 配置 Anaconda 并在本地环境中通过 pip 添加 waapi-client:
https://zhuanlan.zhihu.com/p/25198543
3. 更新pip
默认的pip应该都是9.0.1的,但就安装不了waapi-client。要先更新一下pip。
但是最tm离谱的是,用pip install --upgrade pip来更新的话,很有可能因为网络原因反而继续提示你用--upgrade来更新pip。十足的鸡生蛋。根据这篇文章,使用如下命令:
python -m pip install --upgrade pip -i https://pypi.douban.com/simple
大概是镜像吧。
二、使用中的记录
1. pprint的作用
https://blog.csdn.net/qq_24185239/article/details/80977556
总的来说,pprint打印json的排版比print的更好看。通过 from pprint import pprint 来使用pprint。
2. WAAPI是什么?
如果你有阅读一点Wwise中对于WAAPI的介绍,你应该知道AK官方提供了几种方式来连接到Wwise:WAMP、HTTP POST和Wwise插件。
我这就只会记录WAMP,毕竟它最nb。
你可能很想知道Wwise的WAMP到底是什么,总之,先记住WAMP就类似于一种网络连接,理论上它可以连通你的程序和另一台电脑的Wwise,但它默认情况下只负责连接你的电脑和你自己电脑里的Wwise。如果你有使用过Unity/Unreal和Wwise进行实时调试,应该也会对上述过程有所理解(这一切就类似于,在Profiler视图中点击的Connect)。可以粗暴地想象为Wwise是个网站,而这个网站默认情况下就建在你的电脑里。总之,WAMP就是这样一个和Wwise通信的方式,而WAAPI就是Wwise官方推出的、钦定的,和Wwise通信使用的一系列方法、语法和规则。
2.1 连接建立
先来看下和Wwise通信的代码在python中大致模样。感觉首先知道代码大概长什么样,每部分在干什么,然后再去学WAAPI里那些细节的规则会更高效。
这部分大概说来我就是翻译了一下waapi-client库在其github页面中readme的部分内容,其余部分可以通过原文学习:https://github.com/audiokinetic/waapi-client-python
总的来说,先要引入WaapiClient。记忆角度而言的话:刚才说了,WAMP就相当于你的电脑/你自己编写的程序去连接Wwise。所以你现在在写的python代码/程序相当于就是Client(客户端)。Wwise就是那个服务器。
from waapi import WaapiClient
然后,有两种方法进行“客户端”的建立。
(1)
with WaapiClient() as client:
然后,就可以使用client进行后续的和Wwise的通信。但官方表示,这种方式会导致,client在with程序块之后会自动关闭。所以如果你希望,只是定义一个client,并且随时进行通信,可以使用第二种建立方式。
(2)
client=WaapiClient()
然后你就可以随时使用client进行通信了。
如果你有一定python基础,建议你看看WwapiClient()类的源码,还是比较清晰的:https://github.com/audiokinetic/waapi-client-python/blob/0d746223f56be6749dfaf0d78a6f10ffd2904c23/waapi/client/client.py#L30
如果你没有基础,那就继续看我的记录吧。
2.2 有了Client后要做什么
首先默认你拥有最基本的“面向对象”的编程知识,如果你是一名纯音效设计师没有学过编程,你可以考虑去一些python教程中学习一下面向对象的基本知识。
client有一些方法,如果你没有基础,但又跳过了上面这段话的建议。我稍微说下,client就相当于游戏里一个角色,这对游戏音效设计师的你来说应该很熟悉吧?然后这个角色可能有很多技能,在面向对象的编程世界里,我们就把“角色、角色的技能”称为“对象、对象的方法”。
大致方法列举如下,再次推荐浏览源码,注释非常清晰。
2.2.1 call(self, _uri, *args, **kwargs)
这个方法,用来进行远程程序调用(Remote Procedure Call, RPC),这六个字在Wwise官网也能见到。
self,*args,**kwargs都是很多方法都会有的东西。而_uri则是远程程序的URI,它的所需类型为str。不用太在意URI原意是什么,因为其实这个位置上我们只需要指定WAAPI为我们提供的远程程序就行了。比如如果要获取wwise的基本信息,我们会使用名为ak.wwise.core.getInfo的这个远程程序!你可能还是一脸懵逼,但这部分暂时还不用太过在意!后文会详细地介绍它。总之你就记住,官方WAAPI已经提供了一大堆诸如刚才提到的getInfo的远程程序,然后这些程序都有各自的功能!
虽然说*args,**kwargs是所有程序都会有的东西,但WAAPI里有的远程程序可能需要额外的参数,这些参数在编写代码时候也有着各自的规定!具体的规定对每个远程程序而言都是不一样的。比如刚才的getInfo,就不需要任何参数。这部分也会在之后介绍!现在只需要记住后面这两个东西之后还有一出戏份就好了!因为我们现在还是在介绍client的方法本身。
2.2.2 subscribe(self, _uri, callback_or_handler=None, *args, **kwargs)
没骗你吧?self,*args,**kwargs都是很多方法都会有的东西。而_uri也和刚才的意思一样。
这个方法,用来进行订阅主题(Subscribe to a topic)。是不是很不明所以,但官方就是这么叫它的,可能这就是程序员的浪漫吧(不是)。
这个方法配合它的远程程序会比较好懂,所以在这里先举个例子吧:比如,这个方法有一个配套远程程序叫做ak.wwise.core.audio.imported,看这串名字都看出来了吧,它是一个,会在你在wwise导入文件后通知你的程序。也就是说,你使用subscribe连接到这个audio.imported的远程程序。然后远程程序会开始持续检测你是否导入文件,并且会在检测到导入操作后,以回传的方式通知你。而回传的接口,就是你在callback_or_handler中给它的一个函数句柄。
源码中明确提醒,callback会从另一个线程返回,所以需要你的程序自行处理线程之间的冲突/竞争条件问题。所以再次建议强行读到这里的人还是稍微了解下面向对象和多线程编程后,再来学习WAAPI。
值得一记的是所谓的callback_or_handler,总之只要是可以调用的东西就可以了。比如,函数,对象方法,甚至是实现了__call__方法的类实例,特别地,还有lambda函式。lambda的用法可以粗暴地如下记忆:函数名=lambda 参数列表:函数表达式。比如,test=lambda a,b:print(a+b),这样,就可以用test(1,2)之类的函数表达了。是不是超没用?反正我觉得挺装杯的,知道就行了。
还值得一提的是,subscribe的返回值类型是一个Wwise官方自己定义的抽象类,叫做EventHandler,有基础的话也可以去看看它的源码。但是我们只要知道几点这个类有用的方法就行了。
unsubscribe:用于随时取消订阅。
bind:用于绑定一个新的接受返回通知的callback_or_handler。其实,官方源码也写了,之所以写了这个抽象类,就是为了方便我们随时切换绑定的接受返回通知函数。
以上可能有一点杂乱了,但是比对着下面这段代码应该就会立刻思路清晰!
from waapi import WaapiClient # 连接Wwise (使用默认URL,也就是说会连接到自己电脑上的Wwise) client = WaapiClient() # 使用client的call方法,进行远程程序调用,调用的是叫做getInfo的远程程序,然后远程程序会把它运行的结果返回给result。 result = client.call("ak.wwise.core.getInfo") # 使用client的subscribe方法,进行订阅主题,订阅的是object.created的远程程序,这个程序会在有wwise对象被创建时返回通知。handler用于持续地控制这个订阅。 handler = client.subscribe( "ak.wwise.core.object.created", lambda object: print("Object created: " + str(object)) ) # 随时都可以切换接收通知的函数 def my_callback(object): print("Different callback: " + str(object)) handler.bind(my_callback) # 使用handler的方法unsubscribe取消订阅 handler.unsubscribe() # 使用client的方法disconnect断开和Wwise的连接 client.disconnect()
2.3 远程程序详细介绍
首先,其实Wwise自身提供的文档介绍非常详细:https://www.audiokinetic.com/zh/library/edge/?source=SDK&id=waapi_index.html
用一个结构相对比较简单且有特殊结构的远程程序来举例,它就是ak.wwise.core.object.get。
调用这个程序,需要给予Wwise两大类参数:Arguments和Options。首先,前者的Wwise文档如图。
根据表格最下方的标注,带星号的项目都是必要的参数。对这里的get而言,就是from参数必须。(因为transform本身不是必须的,所以虽然它下面有一些transform.select是必须的,但总的来说并不是必须的。)
还有就是,中间的Type列中,会有一些行内容为 one of、any of,其实这就是在说,下面几行的都可以。比如from,既可以用id指定,也可以用path指定,你会发现每一个都对应了下面带星号的Name。
一定要注意中间Type为每个参数指定的类型,按照类型来书写参数才不会出错。
而对于Options,则没有什么必须提供的参数。并且其书写规则与上面Arguments表的书写规则一致。
2.4 把至此为止的知识结合起来
然后,在写代码的时候,需要注意,call方法只能调用Functions的远程函数,而subscribe只能调用Topics的远程函数!
而2.3中提到的这一大堆参数,在书写时,官方有几种格式可供选择。但推荐使用类似于以下的格式。
args={ "from": { "id": [ "{A076AA65-B71A-45BB-8841-5A20C52CE727}", "{2028C899-8300-4667-ADD0-ED10467BD91E}", "{24979032-B170-43E3-A2E4-469E0193E2C3}" ] } }
总之就是类似于python中的字典的形式。细心的话会发现这里只有args,那说好的kwargs呢,其实官方实现了一个能将输入参数合并的内部方法,刚才说的“几种格式可供选择”也是得益于此。
然后,调用call的时候,就是形如以下代码:
client.call("ak.wwise.core.object.get",args)
而调用subscribe,则是形如以下形式的代码:
handler=client.subscribe("ak.wwise.core.audio.imported",callbackFunction,options)
其中,handler是一个句柄,方便我们后续进行前文说的bind和unsubscribe之类的操作,而callbackFunction则是你给远程程序提供的返回值处理函数,options则为你给远程程序提供的参数。而远程程序,此处的imported会以publish的json形式返回它的监测结果。源码也提到了,你的callbackFunction可以定义为:
def callbackFuntion(*args,**kwargs):
以便接收任何类型的publish返回数据。
这里再顺便记录一下*args和**kwargs,*args是把参数打包成tuple给函数调用,而**kwargs则是把参数打包成dict给函数调用。
下图应该比较直观:
而publish就会被kwargs转化为字典形式。如果没看懂,没关系,下文马上就要开始记录对publish的处理方式。如何从众多的publish返回信息中挑选我们需要的信息。
(第二部分未完待续)
3 实践中的坑
3.1 每次与Wwise互动时,应该使用同一个client?还是每次创建一个client?
最开始我是为每次互动创建一个client,然后发现,当同时创建一个client用于远程程序调用,并再创建一个client用于订阅主题时,wwise使用的asyncio模块会报错。
最后,在waapi的WaapiClient()类的源码中,看见这么一段注释。
这样看来,wwise是不鼓励使用多个client的,而是鼓励应该在用一个在一个client内,进行多次订阅,或者多次远程程序调用。然后再用waapi提供的方法获得当前总共有多少订阅。
最后结果也是,我使用了全局的client,在需要的时候disconnect,waapi就正常地与wwise通信了。