天天看點

Flash中實作語音變聲(下)

上次我們說到了,語音變聲的算法,最簡單的就是利用FFT将音頻信号變換到頻域,然後将頻譜向高頻方向伸展或者向低頻方向收縮即可實作。本文主要關注上述算法在Flash(ActionScript 3)中實作的性能評估以及優化方式。

衆所周知,AS3是一種編譯為ABC位元組碼然後運作于AVM2上的語言,具備OO的特性。ABC很簡潔,AVM2是一種輕量級的虛拟機,AS3書寫起來非常的友好,不過執行的效率并不突出,尤其是FFT這種運算密集型的算法(盡管FFT已經将複雜度從O(n^2)簡化到了O(nlogn)了)。

純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上的自動機裡面運作)。不過事實證明我的了解是對的,但是懷疑是不必要的。

之前寫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中具體實作的技巧和優化方法。文中難免有很多錯誤,歡迎大家一起指正和探讨!

繼續閱讀