Flash中实现语音变声(下)
http://vivimice.blog.163.com/blog/static/16100120111111428742/
工作 2011-02-11 15:41:25 阅读188 评论12 字号:大中小 订阅
上次我们说到了,语音变声的算法,最简单的就是利用FFT将音频信号变换到频域,然后将频谱向高频方向伸展或者向低频方向收缩即可实现。本文主要关注上述算法在Flash(ActionScript 3)中实现的性能评估以及优化方式。
众所周知,AS3是一种编译为ABC字节码然后运行于AVM2上的语言,具备OO的特性。ABC很简洁,AVM2是一种轻量级的虚拟机,AS3书写起来非常的友好,不过执行的效率并不突出,尤其是FFT这种运算密集型的算法(尽管FFT已经将复杂度从O(n^2)简化到了O(nlogn)了)。
首先考虑直接使用纯AS3实现一个(FFT的实现改写自FFT.java),语音数据直接读取44100Hz/16Bit/Mono的声音文件(PCM格式),可以设置的参数包括了帧长度、帧重叠区长度、频谱伸缩系数。
纯AS实现的效率并不理想,按照每帧2048点,帧重叠1024点(重叠率50%),处理时间大概是输入数据长度的10倍以上,也就是说1秒的音频需要大概需要10秒的时间来进行处理,而且占掉整个FlashPlayer的CPU时间,如果要预留时间给图形渲染线程的话,时间要更长。
首先尝试进行代码的优化,例如减少OO的程度,优化循环,减少重复的计算,精度要求不高的地方使用查表法等等,不过这样的优化只提高了5%左右的效率,而且代码的可读性和可维护性急剧下降。
然后我想到了Adobe的Alchemy项目,这是一个利用gcc/lvvm来将C代码编译为ABC字节码的工具链,虽然我之前对其能提高效率深表怀疑——虽然是C代码,不过这和JNI等机制不同,Alchemy会将C代码编译成中间代码,然后在一个AS实现的自动机上运行,都是在AVM2上执行的,而并不是向JNI一样是本地代码(从某种角度上来说,中间代码的效率应该更低,因为它们相当于是在一个运行在AVM2上的自动机里面运行)。不过事实证明我的理解是对的,但是怀疑是不必要的。
首先去Adobe网站上下载了Alchemy工具链(http://labs.adobe.com/technologies/alchemy/),找了台Debian主机装上,编译成SWC代码是没啥问题,不过在控制台里面死活运行不起来,后来发现Adobe没有提供Linux下的ADL,找了个AIR SDK中的ADL过来,也运行不起来,非得要X。我是在Windows下开发,SSH远程到主机编译运行的,实在不理解控制台的ADL为啥非得要X(后来知道了,得发生错误时用X而不是stderr显示错误堆栈……),没办法,又装了个vncserver。
对了,中间发现了若干诡异的问题,参考了http://savagelook.com/blog/actionscript3/adobe-alchemy以后就解决了。
之前写88代码那么多年,C的基础一点都没有丢掉,手到擒来,实现很快就写好了,首先直接利用控制台和ALSA API直接在主机上调试听效果,保险起见,还用Valgrind查了下内存错误。最后参考了下Alchemy的C API,将它编译成SWC,再在Flash工程中引用。
效率相当不错啊,提高得相当的多。再加上由于是语音信号,采样率可以适当降低到11050Hz,这样信息量也减少到了1/4,这样下来,15秒的数据,在我的机器上,只需要3~4秒就可以处理完成,这还利用了Alchemy的异步调用机制,变调的算法是在另外的线程中执行的,不会影响绘图渲染线程的工作。如果是可以独占的话,效率会更高(不过考虑到用户体验,最后没有采用独占的方式)
不过不知道是我代码的问题还是Alchemy工具链目前不稳定的问题,变声的例程在调用若干次以后,Alchemy生成的自动机会自己挂掉,看起来像是内存访问违例。不过由于被编译过了,根本看不出来是哪里的问题。这个问题过了很久都没有解决,查不到端倪。后来只能够将SWC外面再包一层SWF,然后嵌入的调用方里面。调用方每次调用的时候都用Loader重新加载,使用完了以后都unload+gc掉,这样保证每次调用的时候,自动机(包括所有寄存器的状态和内存等等)都会重新生成并且初始化一次(默认情况下,如果直接引用SWC的话,自动机只会在库init的时候自动初始化一次)
其实我之前对于Alchemy的怀疑是有道理的:Alchemy实际上是利用gcc/lvvm将C代码编译为一种中间代码,然后让这部分代码在Alchemy提供的一个自动机上面跑。这个自动机再在AVM2上面运行,不过由于gcc的优化力量(编译的时候使用-O3参数),使得代码的优化空间被充分利用起来,所以效率反而比纯AS3实现的要高。
同样的逻辑,使用Alchemy工具链实现还是使用纯AS3实现,需要考虑下代码的优化空间。如果是运算密集型,并且有充分的优化空间,可以考虑用Alchemy实现,否之还是纯AS实现效率高。
打个比方,要在屏幕上绘制很多的精灵,这个必然是纯AS的效率高(因为Alchemy也是走AS来调用绘图的API的),一个纯粹的累加循环,那也是纯AS的效率要高,因为纯粹的循环语句翻译成ABC也就区区几行指令,要是再在自动机里面运行就得不偿失了。
至此,整个使用Flash来实现语音变声的过程就已经叙述完毕了。这几篇文章主要讨论了语音变声的基本原理、算法实现和Flash中具体实现的技巧和优化方法。文中难免有很多错误,欢迎大家一起指正和探讨!