關於安卓音頻的經驗

謹以此文總結我在蘇州的一個月,希望看到的人能夠不再踩這些坑。項目背景是一個通過耳機接口和手機相連的通信設備,編程聯調。通信原理就是播放音樂和錄音,通過音頻波形來負載信號,當然這種聲音基本沒法聽。之所以採用這種通信方式,是因爲手機接收信息的方式有限,數據線接口不一,而唯一比較通用的就是耳機口和藍牙,藍牙的功耗比較大,對小設備不適合,所以採用了耳機口。

初到蘇州,我的任務是解碼,設備會以曼徹斯特編碼發送數據,在手機端以一定的採樣率錄音就是一串點,如果在excel中用折線連起來就是一個波形,一個波形的上升沿表示bit0,下降沿表示bit1,當然每個bit還必須有一定的寬度。這個大約用了我幾天的時間,主要是開始瞭解需求,以及對各種波形的熟悉過程。不同的手機的波形差別很大,有的噪音比較大,還需要做一些去噪處理。我想通信領域的朋友或許對這個瞭解的更深吧,包括糾錯機制什麼的,我都不太瞭解,只是用最簡單的區間統計來找bit,這個方法目前來看還算穩定。

接着是手機發送信號的時序問題,之前約定手機端先發送幾個脈衝信號激活設備,然後纔開始發送命令,但是手機端的信號用示波器看,經常不是很準確,比較頭疼,主要是安卓手機的上層用java,對底層的控制力太弱了,後來採用的解決辦法就是脈衝和命令放在一個音頻數據塊裏發,中間的間隔就發送0電平信號,當然硬件那邊也要配合,這樣就初步解決了時序問題。

手機端接受數據,就是從耳機口錄音,但是打開錄音設備似乎有延遲(我對安卓不懂,並且之前代碼是別人寫的),所以採用的方式就是提前打開錄音,用另一個線程不停地去抓錄音數據,抓過來就扔掉,直到有通信過程,纔開始真正的解析數據。要控制好時間段,避免抓取到太多的無用數據,這是通過控制每次通信的信息量來保證的,例如一次傳輸不超過100 byte。

設備想通過耳機口和手機通信,最重要的就是要模擬一個耳機,好比你拿個火柴棍插耳機口裏,手機是不會和火柴棍通信的。這包括兩點:

第一,設備要能像普通耳機一樣,這樣播放音頻數據的時候,手機纔會把信號傳遞到耳機口,這其實是一些電路方面的知識,我也不太懂,只是聽說手機檢測耳機的三種方式是:電壓、電流、彈簧片。反正就是在耳機插頭要連接上合適的電阻和電容,這個都由硬件工程師解決了。

第二,設備要模擬一個帶有MIC的耳機,而不僅僅是普通耳機,這樣錄音的時候,纔會從耳機口錄音,否則會用手機本身的MIC錄,不走耳機口了。這個問題到最後了才發現,所以解決的不是很完美,耳機的MIC分爲國內和國外兩種制式,一般國外的偏多一些,有的手機似乎內部可以自動識別耳機的模式,有的則是固定的,爲了適應不同手機,我們的設備做了自動切換模式,但是需要用戶協作,不是全自動的。


接下來就是安卓的各種坑了,準確的說,是安卓的audio相關的坑,真是坑爹啊。

1 如果查看安卓文檔,你會發現寫的挺全,但是一沒有示例,二沒有詳細解釋,對於一些常量,點進去,只有一個數值,對於我這種對音頻沒了解過的人,只能是邊猜邊試,並且很多api你覺得似乎對你有用,但是往往都加了deprecated標記,無奈。

2 播放音樂數據使用的AudioTrack,文檔大致意思如下:分爲stream和static兩種方式,static會預先寫入native層,對於需要反覆播放又較短的數據用static,對於較大數據用stream模式。我猜大部分做應用的人都是使用了stream模式,並且只是播放音樂,即使失真一點用戶也聽不出什麼,可是對於通信來說一點失真都不行。

2.1 static模式發送兩次信號的問題
如果用static模式,那麼大致的用法是
track.write...
track.reloadStaticData...
track.play...
但是第一次write的時候,不能調用reload,否則第一次會發送兩個信號,所以必須設定個標記位,判斷一下這個track是否是第一次write,是不是很坑爹啊?而你看reloadStaticData的文檔說明,只是告訴你這個函數是rewind到數據的開始。

2.2 static模式和stream模式發送的信號不一致
從安卓頂層到耳機口真的是包了一層又一層,我沒法刨根到底,但是我只能說,從示波器上看,某些手機上兩種模式發送相同數據,顯示的波形差異很大,所以需要上層針對不同手機型號做適配,不知道是哪一層,對數據做了修改。

2.3 static模式跑一段時間,出現“TrackBase::getBuffer out of range”的錯誤,以及各種詭異現象
如果你在logCat裏面看到了這個錯誤信息像洪水一樣爆發出來,永無止境,那麼恭喜你,你也踩到了這個大坑,根據我查到的各種資料,這個問題是安卓底層的一個bug,至今無人解決,具體有幾個鏈接比較重要:
上面的解釋大概就是安卓底層把共享內存給寫廢了,沒用多久就溢出。
如果我可以只使用stream該多好啊,那就可以繞過這個大坑,可惜因爲2.2提到的原因,繞不過去,最後採用了一個妥協的方法,目前看暫時算是解決了問題:既然需要運行一段時間出錯,那我就用幾次就release,然後重新new一個新對象。

2.4 拔掉設備,手機發出連續的滴滴聲(蜂鳴聲,etc)
本來正常通信,只會發出一聲短暫的滴(高頻信號),但是測試中,模擬正常通信突然拔出設備,有時候會發出連續的滴滴聲,似乎程序進入了死循環,而且聲音明顯和正常信號不同。經分析,是使用過static模式,再切換到stream模式造成的。只使用stream模式就不會有這個問題,這個問題多見於安卓版本4.0.4(更低版本的可能也有,不過沒做大量測試)。

2.5 系統音效(杜比、米音,etc)
某些系統會“自作聰明”的替你加一些效果,改善你的聽音樂質量,例如降噪、AGC、杜比音效等等,對於一個播放音樂的用戶來說,聲音有更強的效果確實不錯,但是對於通信卻是致命的,用示波器看,波形被修改的體無完膚,目前能夠想到的解決辦法就是提醒用戶關閉這些選項,也是一份任重道遠的活啊,不同手機的配置都不一樣。

3 錄音主要是AudioRecord這個類,這個類似乎沒什麼問題。唯一需要注意的是硬件必需模擬成帶MIC的,否則會錄到外面。

3.1 錄音設備的打開關閉有延時???
這個目前還不是完全確定,因爲前期代碼很亂,硬件也不行,得出的結論我發現很多時候都是錯誤的,現在採用一直打開錄音的方式,所以無論是否有延遲,都沒有關係了。就是如果錄音數據會存儲在一個buffer裏,如果你不及時取走,會出overrun的信息,不過這個沒關係。

3.2 錄音效果不好
這個現象也是極少的手機中會出現,經查,是波形被系統修改了,最後改成VOICE_RECOGNITION,解決了,但是並不是所有手機都支持VOICE_RECOGNITION,有的手機上來就初始化失敗,據說andriod官方白皮書要求廠商對VOICE_RECOGNITION的音頻不要做特殊處理,但是支持規範的廠商有多少還真不知道。

最後,發現一個手機(華爲P6)的波形無法解析,修改了一下解碼算法,也算是解決了。再看另一組iOS的開發人員,那叫一個爽啊,一共就幾款蘋果手機需要適配,而其實這幾款都差不多,想想安卓的這些坑,真的坑爹啊。


發佈了99 篇原創文章 · 獲贊 98 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章