開發一個語音通信解決方案是一個軟件項目。既然是軟件項目,就要有相應的計划:有多少功能,安排多少軟件工程師去做,這些工程師在這一領域的經驗如何,是否需要培訓,要多長時間做完,中間有幾個主要的milestone等。我們曾經四個人花了近一年時間開發了一個語音解決方案,成功通過驗收,各項關鍵指標(語音質量、單向時延)均達到運營商要求。當時是在芯片公司,在公司自己的芯片上做語音解決方案,增加芯片的賣點,增強芯片競爭力。我們做語音數據面實現,同時提供API。用戶在上層控制協議(例如SIP)中調用API,從而實現語音通信功能。下面聊聊我們當時是怎么一步步做出來的。
1,討論需求和軟件架構
第一步是討論需求,看要實現哪些功能,並對功能排一個優先級。討論下來前處理(回聲消除、噪聲抑制、增益控制)、tone generation、重采樣、codec(g.711、g.722、g.729)、RTP、RTCP 、jitter buffer等是第一優先級功能,VAD、CNG、SID、PLC等是第二優先級功能。同時制定了幾個關鍵的milestone點:討論需求和軟件架構、成功打通第一個basic call、完成第一優先級功能產品基本能用、完成第二優先級功能攻克關鍵指標、全面測試通過驗收。需求討論后就要看用一個什么樣的軟件架構去實現這個解決方案。由於在Linux上開發,Linux上關於聲音的架構是ALSA,討論下來我們基於ALSA來做,這樣可以縮短開發周期,在driver上僅僅是調試,在user space里用好ALSA-Lib等。同時確定了需要三個thread。Capture thread每隔10ms運行一次,靠ALSA獲取采集到的語音PCM數據,然后做前處理編碼打包等發到網絡上。Play thread也是每隔10ms運行一次,從jitter buffer中獲取10ms的碼流,然后解碼成PCM等再經由ALSA送給audio device播放出來。Packet receive thread用來從網絡上收包,收到后解析並放進jitter buffer中。由於jitter buffer會被兩個thread訪問,需要加保護。這樣語音數據面的軟件架構圖如下:
這些都確定后我們就開始向第一個Milestone進軍了。
2,成功打通第一個basic call
這一階段的目標就是成功打通第一個basic call。在每個階段的開始我們都討論一下大家的分工。共四個人,一個同學負責搭軟件框架以及做API給上層協議調用。一個同學對ALSA比較熟悉,由他負責調ALSA,分兩個階段:第一階段是把從ALSA得到的MIC的PCM數據再經由ALSA送給speaker播放出來,即在PCM側形成Loopback。如果從MIC講的話到speaker正確播放出來,ALSA的調試就完成了。第二階段是加上最簡單的codec G.711,形成codec loopback,即從ALSA得到的MIC的PCM數據用G.711編碼得到碼流,然后用G.711解碼得到PCM數據再經由ALSA送給speaker播放出來。我主要負責網絡側,包括RTP的實現以及UDP收發包的實現等。也是通過loopback的方式調試,即將特殊字符(如全’a’)作為RTP的payload打包(codec用G.711),然后通過UDP socket發給目標地址,在目標地址板子上用UDP socket收包,收到后解析RTP得到payload,這個payload與發送端的payload 相比較,如果完全相同則說明RTP的實現UDP收發包的實現是正確的。由於jitter buffer相對比較復雜,這一階段暫時不用jitter buffer,用固定的緩沖buffer來替代,但是jitter buffer的設計開始做起來。一個同學負責上層協議,也就是SIP,用一個開源的實現(上層協議不是我們的重點,主要是配合底層實現的),把基本功能調試好就可以了。
經過大家的努力,這一階段的目標如期實現。當成功打通第一個basic call的時刻,大家別提多高興啦。畢竟這幾乎是從零開始做方案,這種經歷還是難得的。我們還特地出去吃了一頓大餐,慶祝basic call成功打通。
3,完成第一優先級功能,產品基本能用
這一階段的目標就是完成第一優先級功能,產品基本能用。依舊先分好工。先前做框架和API的做重采樣、tone generation和codec(G.722/G.729)。先前調試ALSA的負責前處理(回聲消除、噪聲抑制、增益控制)。他們主要是先驗證算法再調試算法使其在我們的方案里能很好的運行,先保證能用,后面再優化。我還是負責網絡側,完成jitter buffer和RTCP。做SIP的同學在這一階段使功能更完善。這一階段相對於上一階段難度加大了,我們每個人在做的過程中都或多或少的遇到了一些困難。以我自己為例,在做jitter buffer的過程中就遇到了不少困難,主要是在一些細節上設計時想的不全,導致在良好網絡下音質有時也不好。這些問題后來都一一被解決,同時也更新了設計文檔。在大家的努力下,這一階段也如期完工。老板來驗收后對目標完成表示滿意,又帶着大家出去吃了一頓大餐。
4,完成第二優先級功能,攻克關鍵指標
這一階段的目標就是完成第二優先級功能,攻克關鍵指標。關鍵指標有各種場景下的打電話時CPU load、各種網絡環境下的語音質量(MOS值)、單向時延(one way delay)、連續打多少通電話不出錯(bulk call)、單通電話最大能持續多長時間(long call)等。先前做重采樣等的同學做第二優先級功能(VAD、CNG、SID、PLC等)。先前做前處理的做CPU load優化,使打電話時全部模塊所占的CPU load控制在一個合理的范圍內,當然是越小越好,做的很好就是我們方案的一個賣點。我主要負責語音質量、bulk call、long call等。做SIP的同學依舊完善SIP的功能。這一階段使越來越難越來越有挑戰了。優化算法load的同學需要測出各個算法的load,然后一一優化,盡可能降到最小。語音質量把我折騰的夠嗆。單向時延也是,儀器測出來的是一個總的值,我要找到各個模塊引入的delay是多少,然后看怎么減小。bulk call和long call都是做起來非常頭疼傷腦細胞的,一般都是晚上或者周末測,第二天早上或者下周一早上看結果,如果有問題就分析看怎么解決。
在這一階段不少問題都是大家一起討論看怎么解決的,畢竟這些問題都是很難的。 我們沒被困難打趴,還是在規定時間內搞定了。我們離最終目標越來越近了!
5,全面測試,通過驗收
在前面幾個階段中,把一個功能做完后會對這個功能相關的進行測試,確保功能完好,但是由於趕進度,並沒有做全面性的測試。現在各項功能全部完備,關鍵指標也已全部達標,是時候全面測試確保產品質量了。首先是由我們開發人員自己測,我們覺得質量可以了再交由測試人員測。我們自己測時先由一個人寫測試用例,然后大家一起review討論,確保盡量全的覆蓋,還有就是異常case要盡量多的考慮。測試用例review后大家分工測,發現了些問題,由於代碼除了算法全是我們自己寫的,對代碼機制非常熟悉,發現的問題很快就解決了(算法都是非常成熟的,一般不會出問題)。交給測試人員后共測試了三輪,也發現了一些問題(測試人員的測試用例會更多一些,他們的測試也會更專業一些,一定要由他們把關產品質量),也都很快解決了。最終順利的通過了驗收。老板和我們都特別高興,雖然我們的方案是免費提供給客戶,但是這增強了我們芯片的競爭力,可以提高芯片的出貨量。對我們個人而言也是一次很難得的一個方案從無到成功完成的經歷,畢竟工作中這種經歷太少了。
最后我們也進行了總結,有哪些lesson learnt,哪些做的不錯,哪些還可以做的更好。