前言
最近接到一個需求,需要在播放聲音時適配華為藍芽手環,這個手環不同於其他手環,將手環主機從腕帶上取出時,就變成了藍芽耳機,可以接聽電話。如圖所示:
這裡為了便於理解,定義兩個概念:- 手環模式:未將主機從腕帶上取出狀態
- 耳機模式:將主機從腕帶上取出狀態
定位問題
遇到的問題
手環模式下,手機連線手環藍芽後,聲音由聽筒播放。
為什麼會從聽筒中播放?
從手機設定介面觀察到,該手環的音訊選項在處於手環模式和耳機模式時有不同的狀態,當處於手環模式時,通話音訊處於開啟狀態,媒體音訊處於關閉狀態,如圖:
當處於耳機模式時,通話音訊和媒體音訊均為開啟狀態 由此我們可以得到一個重要的線索,如果系統設定可以判斷出音訊狀態,我們也可以通過程式碼判斷當前藍芽裝置的通話音訊和媒體音訊的狀態。接下來我們看程式碼,在原有的實現中,當有藍芽裝置連線時,會收到連線的廣播,然後我們通過如下程式碼設定將聲音通過藍芽耳機播放
private void chooseBluetooth() {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.startBluetoothSco(); // 開啟 SCO 型別藍芽鏈路
audioManager.setBluetoothScoOn(true); // 開啟 SCO 型別藍芽鏈路
audioManager.setSpeakerphoneOn(false); // 關閉揚聲器
}
複製程式碼
audioManager
即 AudioManager
物件,這裡簡單介紹一下 audioManager.startBluetoothSco(); 這行程式碼的意思是開啟 SCO 型別藍芽鏈路。在藍芽通訊中,共有兩種通訊鏈路:
- 同步鏈路 (Synchronous Connection Oriented)
- 非同步鏈路 (Asynchronous Connectionless)
同步鏈路 (SCO) 連線為對稱連線,利用保留時隙傳送資料包。連線建立後,主裝置和從裝置可以不被選中就傳送SCO資料包。SCO資料包既可以傳送話音,也可以傳送資料,但在傳送資料時,只用於重發被損壞的那部分的資料。主要用來傳輸對時間要求很高的資料通訊
非同步鏈路(ACL)就是定向傳送資料包,它既支援對稱連線,也支援不對稱連線(既可以一對一,也可以一對多)。主裝置負責控制鏈路頻寬,並決定微微網中的每個從裝置可以佔用多少頻寬和連線的對稱性。從裝置只有被選中時才能傳送資料。ACL鏈路也支援接收主裝置發給微微網中所有從裝置的廣播訊息。
那麼為什麼在手環模式下聲音會從聽筒中播放?這裡做出一個猜想,在手環模式下,Android 系統並沒有主動開啟媒體音訊,當我們的程式執行 audioManager.startBluetoothSco();
時執行失敗,導致無法開啟藍芽鏈路,之後我們又設定了 audioManager.setSpeakerphoneOn(false);
關閉揚聲器,聲音自然就從聽筒中播放。
解決方案
大致思路就是在呼叫 chooseBluetooth()
之前判斷系統是否開啟了媒體音訊,如果開啟了則進行藍芽播放,否則依然使用揚聲器播放,具體判斷程式碼如下:
boolean isA2dpOn = audioManager.isBluetoothA2dpOn();
複製程式碼