1. 程式人生 > >如何在嵌入式Linux上開發一個語音通訊解決方案

如何在嵌入式Linux上開發一個語音通訊解決方案

開發一個語音通訊解決方案是一個軟體專案。既然是軟體專案,就要有相應的計劃:有多少功能,安排多少軟體工程師去做,這些工程師在這一領域的經驗如何,是否需要培訓,要多長時間做完,中間有幾個主要的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,哪些做的不錯,哪些還可以做的更好。