【Android】Audio音頻輸出通道切換 - 藍(lán)牙、外放

手機(jī)音頻的輸出有外放(Speaker)谷浅、聽筒(Telephone Receiver)箫老、有線耳機(jī)(WiredHeadset)怪蔑、藍(lán)牙音箱(Bluetooth A2DP)等輸出設(shè)備歉糜。在平時卵酪,電話免提戳玫、插拔耳機(jī)熙掺、連接斷開藍(lán)牙設(shè)備等操作系統(tǒng)都會自動切換Audio音頻到相應(yīng)的輸出設(shè)備上。比如電話免提就是從聽筒切換到外放揚(yáng)聲器咕宿,插入耳機(jī)就是從外放切換到耳機(jī)币绩。

場景需求

Android系統(tǒng)自動切換的這些策略,并不能全部滿足我們的產(chǎn)品需求府阀,比如音樂App需要對聽歌時拔出耳機(jī)的操作進(jìn)行阻止(暫停播放)缆镣,防止突然切換到外放導(dǎo)致尷尬。

最近項(xiàng)目需求希望即使在連接藍(lán)牙音箱的情況下试浙,仍舊使用手機(jī)外放播放音頻董瞻。這就需要強(qiáng)制切換Audio輸出通道,打破系統(tǒng)原有的策略田巴。

查閱資料钠糊,看到了Android中可以通過AudioManager查詢、切換當(dāng)前Audio輸出通道壹哺,并且在Audio輸出發(fā)生變化時抄伍,捕獲并處理這種變化。

首先提醒下大家管宵,使用下面的方法時截珍,需要添加權(quán)限:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

Audio輸出狀態(tài)查詢

AudioManager 提供的下列方法可以用來查詢當(dāng)前Audio輸出的狀態(tài):

  • isBluetoothA2dpOn():檢查A2DPAudio音頻輸出是否通過藍(lán)牙耳機(jī)攀甚;

  • isSpeakerphoneOn():檢查揚(yáng)聲器是否打開;

  • isWiredHeadsetOn():檢查線控耳機(jī)是否連著岗喉;注意這個方法只是用來判斷耳機(jī)是否是插入狀態(tài)秋度,并不能用它的結(jié)果來判定當(dāng)前的Audio是通過耳機(jī)輸出的,這還依賴于其他條件沈堡。

  • setSpeakerphoneOn(boolean on):直接選擇外放揚(yáng)聲器發(fā)聲静陈;

  • setBluetoothScoOn(boolean on):要求使用藍(lán)牙SCO耳機(jī)進(jìn)行通訊;

此處根據(jù)這篇文章簡單地介紹一下藍(lán)牙耳機(jī)的兩種鏈路诞丽,A2DP及SCO鲸拥。android的api表明:

  • A2DP:是一種單向的高品質(zhì)音頻數(shù)據(jù)傳輸鏈路,通常用于播放立體聲音樂僧免;
  • SCO: 則是一種雙向的音頻數(shù)據(jù)的傳輸鏈路刑赶,該鏈路只支持8K及16K單聲道的音頻數(shù)據(jù),只能用于普通語音的傳輸懂衩,若用于播放音樂那就只能呵呵了撞叨。

兩者的主要區(qū)別是:A2DP只能播放,默認(rèn)是打開的浊洞,而SCO既能錄音也能播放牵敷,默認(rèn)是關(guān)閉的。 如果要錄音肯定要打開sco啦法希,因此調(diào)用上面的setBluetoothScoOn(boolean on)就可以通過藍(lán)牙耳機(jī)錄音枷餐、播放音頻了,錄完苫亦、播放完記得要關(guān)閉毛肋。

另外,在Android系統(tǒng)中通過AudioManager.setMode()方法來管理播放模式屋剑。在setMode()方法中有以下幾種對應(yīng)不同的播放模式:

  • MODE_NORMAL : 普通模式润匙,既不是鈴聲模式也不是通話模式
  • MODE_RINGTONE : 鈴聲模式
  • MODE_IN_CALL : 通話模式
  • MODE_IN_COMMUNICATION : 通信模式,包括音/視頻,VoIP通話.(3.0加入的唉匾,與通話模式類似)

在設(shè)置播放模式的時候孕讳,需要考慮流類型,我在這里使用的流類型是 STREAM_MUSIC 巍膘,所以切換播放設(shè)備的時候就需要設(shè)置為MODE_IN_COMMUNICATION 模式而不是 MODE_NORMAL 模式卫病。可以參考這個問題典徘。

解決問題

使用以下方法切換音頻Audio輸出蟀苛,參考Android : Switching audio between Bluetooth and Phone Speaker is inconsistent

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

/**
 * 切換到外放
 */
public void changeToSpeaker(){
    //注意此處,藍(lán)牙未斷開時使用MODE_IN_COMMUNICATION而不是MODE_NORMAL
    mAudioManager.setMode(bluetoothIsConnected ? AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_NORMAL);    
    mAudioManager.stopBluetoothSco();
    mAudioManager.setBluetoothScoOn(false);
    mAudioManager.setSpeakerphoneOn(true);
}

/**
 * 切換到藍(lán)牙音箱
 */
public void changeToHeadset(){
    mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    mAudioManager.startBluetoothSco();
    mAudioManager.setBluetoothScoOn(true);
    mAudioManager.setSpeakerphoneOn(false);
}

/************************************************************/
//注意:以下兩個方法還未驗(yàn)證
/************************************************************/

/**
 * 切換到耳機(jī)模式
 */
public void changeToHeadset(){
    mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    mAudioManager.stopBluetoothSco();
    mAudioManager.setBluetoothScoOn(false);
    mAudioManager.setSpeakerphoneOn(false);
}

/**
 * 切換到聽筒
 */
public void changeToReceiver(){
    audioManager.setSpeakerphoneOn(false);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
        audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    } else {
        audioManager.setMode(AudioManager.MODE_IN_CALL);
    }
}

直接切換輸出通道的方法我們已經(jīng)知道了逮诲。剩下需要解決的問題是帜平,當(dāng)藍(lán)牙設(shè)備斷開幽告、連接的時候,我們希望可以自動切換到用戶原本設(shè)置的輸出通道上裆甩,比如在藍(lán)牙未連接時冗锁,用戶設(shè)置的是希望通過藍(lán)牙播報,所以應(yīng)該在藍(lán)牙一旦連接以后嗤栓,就把音頻切換到藍(lán)牙設(shè)備上冻河。

下面我們就看看如何監(jiān)聽藍(lán)牙設(shè)備的連接狀態(tài)。
監(jiān)聽藍(lán)牙連接狀態(tài)
首先注意使用前需要以下權(quán)限:

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />

根據(jù)這篇文章茉帅,我們發(fā)現(xiàn)可以使用 AudioManager.ACTION_AUDIO_BECOMING_NOISY 這個Intent Action來監(jiān)聽藍(lán)牙斷開叨叙、耳機(jī)插拔的廣播,但是測試發(fā)現(xiàn)堪澎,它也只能收到藍(lán)牙斷開的廣播擂错,無法接收到藍(lán)牙連接的廣播,所以不是我們想要的樱蛤。

進(jìn)一步找到這篇文章:關(guān)于藍(lán)牙開發(fā)钮呀,必須注意的廣播,總結(jié)了以下藍(lán)牙廣播昨凡。

/**
 * 有注釋的廣播爽醋,藍(lán)牙連接時都會用到
 */
intentFilter.addAction(BluetoothDevice.ACTION_FOUND); //搜索藍(lán)壓設(shè)備,每搜到一個設(shè)備發(fā)送一條廣播
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); //配對開始時便脊,配對成功時
intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); //配對時子房,發(fā)起連接
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); //配對結(jié)束時,斷開連接
intentFilter.addAction(PAIRING_REQUEST); //配對請求(Android.bluetooth.device.action.PAIRING_REQUEST)

intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //開始搜索
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //搜索結(jié)束就轧。重新搜索時,會先終止搜索
intentFilter.addAction(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); //本機(jī)開啟田度、關(guān)閉藍(lán)牙開關(guān) 
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); //藍(lán)牙設(shè)備連接或斷開
intentFilter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); //更改藍(lán)牙名稱妒御,打開藍(lán)牙時,可能會調(diào)用多次
intentFilter.addAction(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intentFilter.addAction(BluetoothAdapter.ACTION_REQUEST_ENABLE);
intentFilter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); //搜索模式改變

我們發(fā)現(xiàn)了BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGEDBluetoothAdapter.ACTION_STATE_CHANGED 這兩個Intent廣播镇饺。

那么這兩個廣播Intent的區(qū)別是什么呢乎莉?只用其中一個可以嗎?查看Google文檔發(fā)現(xiàn)

  • BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED :指的是本地藍(lán)牙適配器的連接狀態(tài)的發(fā)生改變(比如沒有關(guān)閉本機(jī)藍(lán)牙開關(guān)時奸笤,另外一個配對設(shè)備自己把連接斷開)

  • BluetoothAdapter.ACTION_STATE_CHANGED :指的是本地藍(lán)牙適配器的狀態(tài)已更改惋啃。 例如,藍(lán)牙開關(guān)打開或關(guān)閉监右。

換句話說边灭,一個是用于連接狀態(tài)的變化,另一個用于藍(lán)牙適配器本身的狀態(tài)變化健盒。經(jīng)過測試發(fā)現(xiàn)绒瘦,如果只使用BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED 監(jiān)聽廣播称簿,則會接收不到“主動關(guān)閉本機(jī)藍(lán)牙開關(guān)”的廣播事件。但只是用BluetoothAdapter.ACTION_STATE_CHANGED 的話惰帽,很明顯這時候藍(lán)牙設(shè)備并未真正配對憨降。

動態(tài)注冊藍(lán)牙連接、斷開廣播的方式如下:

  • 動態(tài)注冊廣播
BluetoothConnectionReceiver audioNoisyReceiver = new BluetoothConnectionReceiver();

//藍(lán)牙狀態(tài)廣播監(jiān)聽
IntentFilter audioFilter = new IntentFilter();
audioFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
audioFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(audioNoisyReceiver, audioFilter);

之后该酗,我們就可以根據(jù)上面切換音頻輸出通道的代碼來實(shí)現(xiàn)藍(lán)牙設(shè)備連接授药、斷開以后強(qiáng)制打破操作系統(tǒng)原有的輸出通道切換策略,來實(shí)現(xiàn)我們自己想要的切換功能了呜魄。

參考資料:

1悔叽、Android中的Audio播放:控制Audio輸出通道切換
2、Android音樂播放模式切換-外放耕赘、聽筒骄蝇、耳機(jī)
3、Android : Switching audio between Bluetooth and Phone Speaker is inconsistent
4操骡、Listening to bluetooth connections

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末九火,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子册招,更是在濱河造成了極大的恐慌岔激,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件是掰,死亡現(xiàn)場離奇詭異虑鼎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)键痛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門炫彩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人絮短,你說我怎么就攤上這事江兢。” “怎么了丁频?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵杉允,是天一觀的道長。 經(jīng)常有香客問我席里,道長叔磷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任奖磁,我火速辦了婚禮改基,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咖为。我一直安慰自己寥裂,他們只是感情好嵌洼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著封恰,像睡著了一般麻养。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诺舔,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天鳖昌,我揣著相機(jī)與錄音,去河邊找鬼低飒。 笑死许昨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的褥赊。 我是一名探鬼主播糕档,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拌喉!你這毒婦竟也來了速那?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤尿背,失蹤者是張志新(化名)和其女友劉穎端仰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體田藐,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荔烧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了汽久。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹤竭。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖景醇,靈堂內(nèi)的尸體忽然破棺而出臀稚,到底是詐尸還是另有隱情,我是刑警寧澤啡直,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站苍碟,受9級特大地震影響酒觅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜微峰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一舷丹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜓肆,春花似錦颜凯、人聲如沸谋币。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕾额。三九已至,卻和暖如春彼城,著一層夾襖步出監(jiān)牢的瞬間诅蝶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工募壕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留调炬,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓舱馅,卻偏偏與公主長得像缰泡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子代嗤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容