我工作的頭幾年是在通信設備商做通信設備上的語音軟件開發,主要是follow ITU-T/3GPP/RFC等SPEC寫代碼,相對封閉,沒怎么接觸開源代碼。后來到芯片公司做終端上的voice engine,開始接觸音頻類的開源代碼,先是ITU-T/3GPP的各種codec,后來是各種完整的解決方案。剛開始做voice engine的時候,GIPS還沒被Google收購,更加沒有webRTC的開源,那時騰訊QQ上的語音方案還沒自研(用的是GIPS的方案)。到現在用過的音頻類開源代碼個數雖然沒具體統計過,至少也有十幾個吧,例如webRTC/PJSIP/FFMPEG。今天我們就聊聊音頻類開源代碼這點事。
音頻類開源代碼眾多,我根據自己的使用以及理解將它們分成三類,如下表:
算法類主要有ITU-T/3GPP codec reference code等,音頻的算法主要是數字信號處理,技術門檻是很高的,ITU-T/3GPP等組織為了普及自己制定的codec,就開源了codec的軟件實現方便大家使用,大家用時的主要工作就是優化代碼,使其在具體的平台上流暢運行。解決方案類主要有webRTC/PJSIP等,包括了從采集、編解碼、傳輸到播放的整個過程,並且對不同平台(Linux/Android/iOS)進行了適配。好多公司(尤其互聯網公司)基於這樣的開源實現做二次開發,形成自己的產品。這類開源實現中也用其他的開源代碼,主要是算法類的,比如用ITU-T/3GPP各種codec的reference code,再比如PJSIP就用了webRTC里的AEC的開源實現。介於算法和解決方案之間的既不是單純的算法,也沒有形成一套完整的解決方案,比如FFMPEG,它除了各種算法之外,還有各種音頻格式的封裝和解封裝等,但它沒有形成完整的解決方案,而是提供API給其他模塊用。
音頻類代碼的開源,不管是對行業對公司還是對從業的個人都是受益頗多。對行業而言,主要是降低了技術的門檻,促進了技術的普及,大家都來做了,也促進了行業的繁榮。要是沒有webRTC的開源,短時間內肯定不會有音視頻直播APP的集中涌現,更加不會有2016年全民玩直播的熱潮。
對公司而言,主要有以下益處:
1,基於解決方案類的開源實現做產品,極大地縮短了產品的開發周期,讓產品盡早的投放市場,獲得先機和較好的市場份額。
2,音頻處理中復雜的算法,比如AEC,都是有很高的技術門檻的,一般不可能在短時間內做出一個性能很好的軟件實現。以前只有大公司或者專業的算法公司才做這些算法,一般公司都是去買算法庫用到自己的產品里。現在這些算法開源了,可以拿來用,節省了一筆開支。
3,基於解決方案類開源實現做產品,就要對里面的機制技巧等搞清楚吃透,然后才能做二次開發。這樣就逐漸的提高了整個團隊的技術能力和競爭力。
對個人而言,主要有以下益處:
1,一些算法實現,如果只有文檔,可能理解的不是很深刻,有了代碼實現后就可以去調試,更好的理解算法。
2,著名的解決方案類開源實現都是技術大牛寫出來的,里面有很多的技巧和設計的思想,把這些都理解了后就無形中提高了自己的編程架構等能力。大家做東西一般都不是從零開始的,而是先學習他人的,理解消化后再根據自己開發產品的需要做改進。以我自己為例,以前對jitter buffer只有一個基本的概念,並不知道如何實現,后來學習了開源里的實現,理解消化后並做了一定的改進用到了自己開發的產品里。
音頻類開源代碼也有自己的一些特點:
1,對算法類開源實現而言,尤其是各種codec的reference code,一般都不能直接使用(主要是因為CPU load 高),而是優化后才能使用。
2,對解決方案類開源實現而言,一般會適配各種平台各種協議,因而是包羅萬象的,有的還增加了適配層,這些處理增加了代碼的復雜度,加大了代碼走讀的難度。同時這些代碼都是大牛寫的,技巧性很強,也加大了代碼走讀的難度。我記得剛開始讀PJSIP代碼時特別暈,好多抽象出的適配層,到處是callback函數,而且這些callback函數的名稱都一樣,只有加log才知道這個函數在哪調的。后來代碼熟悉了知道這些callback函數在哪調才好很多。
就我自己而言,既在產品開發中用過算法類開源實現,也有基於解決方案類開源實現開發產品。我的看法是算法和模塊可以用開源的,但是整個解決方案最好不要用開源的,即使剛開始為了趕進度用開源的,后面有時間了也要自己重寫。因為解決方案類開源是保羅萬象的,而我們自己開發的產品平台和協議等都是明確的,這樣可以減少代碼的復雜度冗余度,也有利於后期的維護(我看到一些基於解決方案類開源做的產品越到后面越難維護,不得不重寫)。同時開源實現中好多會用非常復雜的數據結構,有時還有多層的封裝,代碼走讀性不夠友好。我喜歡的代碼風格是簡潔明了,能用簡單的數據結構就不用復雜的,即使是剛畢業的學生也能很快看懂,也有利於后期的維護。記得在開發過的一款產品里前人用雙向鏈表(buffer動態分配不固定)來存儲從網絡上收到的語音包,通常情況下都沒有問題,但是網絡狀況多變,好多場景是設計時想不到的,只有出問題了才意識到。而且出問題基本上都是指針飛了crash,如果沒有很好的定位crash的手段,再加上log又不是實時的,不知道crash在什么地方,會很痛苦。當時就填了好多這樣的坑。后來我們用循環數組替換了雙向鏈表,存放語音包的buffer是一塊連續的空間,代碼好理解,也很少出crash的問題,即使出crash問題也能很快定位解決。所以我覺得能用數組解決問題的就不要用鏈表,數組(循環數組/ring buffer)是我這么多年用的最多的數據結構,而鏈表是很少用的。