簡(jiǎn)介: 手機(jī)音頻的輸出分有外放(Speaker)、聽筒(telephone Receiver)、有線耳機(jī)(WiredHeadset)、藍(lán)牙耳機(jī)(Bluetooth A2DP)等輸出。電話免提、插拔耳機(jī)、連接斷開藍(lán)牙設(shè)備等操作系統(tǒng)都會(huì)自動(dòng)切換Audio音頻到相應(yīng)的輸出設(shè)備上。例如,電話免提就是從聽筒切換到外放揚(yáng)聲器,插入耳機(jī)就是從外放切換到耳機(jī)。
APP 場(chǎng)景需求:
比如音樂app正在播放音樂,這時(shí)我們打開自有攜帶對(duì)講功能的app,需要關(guān)閉音樂播放。打開app中的
音頻播放,此時(shí)要選擇音頻的模式為通話模式的音頻通道(根據(jù)插入的設(shè)備情況):
- 沒有外設(shè):選擇外放的通話模式
- 先接入有線耳機(jī),再接入藍(lán)牙耳機(jī)或者先接入藍(lán)牙耳機(jī)、再接入有線耳機(jī):以最后一次接入設(shè)備的狀態(tài)為準(zhǔn)。選擇對(duì)應(yīng)通話模式的音頻通道
- 音頻播放途中,有線耳機(jī)斷開:需要監(jiān)聽Audio外設(shè)監(jiān)聽狀態(tài),來(lái)做對(duì)應(yīng)的處理
- 音頻播放途中,藍(lán)牙耳機(jī)斷開:需要監(jiān)聽Audio外設(shè)監(jiān)聽狀態(tài),來(lái)做對(duì)應(yīng)的處理
- 退出音頻播放:需要關(guān)閉音頻焦點(diǎn),并釋放音頻通道
Audio輸出狀態(tài)查詢
note: <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> 首先需要添加權(quán)限
AudioManager 提供的下列方法可以用來(lái)查詢當(dāng)前Audio輸出的狀態(tài):
isBluetoothA2dpOn():檢查A2DPAudio音頻輸出是否通過(guò)藍(lán)牙耳機(jī);isSpeakerphoneOn():檢查揚(yáng)聲器是否打開;-
isWiredHeadsetOn():檢查線控耳機(jī)是否連著;note :這個(gè)方法只是用來(lái)判斷耳機(jī)是否是插入狀態(tài)。
setSpeakerphoneOn(boolean on):直接選擇外放揚(yáng)聲器發(fā)聲-
setBluetoothScoOn(boolean on):要求使用藍(lán)牙SCO耳機(jī)進(jìn)行通訊;這里簡(jiǎn)單介紹一下藍(lán)牙耳機(jī)的兩種鏈路:A2DP,SCO。A2DP:是一種單向的高品質(zhì)音頻數(shù)據(jù)傳輸鏈路,通常用于播放立體聲音樂;SCO: 則是一種雙向的音頻數(shù)據(jù)的傳輸鏈路,該鏈路只支持8K及16K單聲道的音頻數(shù)據(jù),只能用于普通語(yǔ)音的傳輸。兩者的主要區(qū)別是:A2DP只能播放,默認(rèn)是打開的,而SCO既能錄音也能播放,默認(rèn)是關(guān)閉的。 如果要錄音肯定要打開sco啦,因此調(diào)用上面的
setBluetoothScoOn(boolean on)就可以通過(guò)藍(lán)牙耳機(jī)錄音、播放音頻了,錄完、播放完記得要關(guān)閉。
Audio 播放模式
Android系統(tǒng)通過(guò)AudioManager.setModel()來(lái)管理音頻模式。如下幾種模式:
-
MODE_NORMAL: 普通模式,既不是鈴聲模式也不是通話模式 -
MODE_RINGTONE: 鈴聲模式 -
MODE_IN_CALL: 通話模式 -
MODE_IN_COMMUNICATION: 通信模式,包括音/視頻,VoIP通話.(3.0加入的,與通話模式類似),因?yàn)閍pp場(chǎng)景需要用到對(duì)講功能,所以選擇音頻模式為通信模式MODE_IN_COMMUNICATION)
在設(shè)置播放模式的時(shí)候,需要考慮流類型 STREAM_MUSIC ,所以切換播放設(shè)備的時(shí)候就需要設(shè)置為MODE_IN_COMMUNICATION 模式而不是 MODE_NORMAL 模式。
好了,了解了以上音頻通道相關(guān)的知識(shí)后。我們來(lái)對(duì)需求做對(duì)應(yīng)的解決。
使用如下方法來(lái)解決切換音頻輸出:
public class AudioUtils {
private static int lastModel = -10;
/**
* 音頻外放
*/
public static void changeToSpeaker(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
//注意此處,藍(lán)牙未斷開時(shí)使用MODE_IN_COMMUNICATION而不是MODE_NORMAL
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
audioManager.setSpeakerphoneOn(true);
}
/**
* 切換到藍(lán)牙音箱
*/
public static void changeToHeadset(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
audioManager.setSpeakerphoneOn(false);
}
/**
* 切換到聽筒
*/
public static void changeToReceiver(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
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);
}
}
public static void dispose(Context context, AudioManager.OnAudioFocusChangeListener focusRequest) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(lastModel);
if (audioManager.isBluetoothScoOn()) {
audioManager.setBluetoothScoOn(false);
audioManager.stopBluetoothSco();
}
audioManager.unloadSoundEffects();
if (null != focusRequest) {
audioManager.abandonAudioFocus(focusRequest);
}
}
public static void getModel(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
lastModel = audioManager.getMode();
}
public static void changeToNomal(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setMode(AudioManager.MODE_NORMAL);
}
public static boolean isWiredHeadsetOn(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
return audioManager.isWiredHeadsetOn();
}
public static boolean isBluetoothA2dpOn(Context context) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
return audioManager.isBluetoothA2dpOn();
}
/**
* context 傳入的是MicroContext.getApplication()
* @param context
*/
public static void choiceAudioModel(Context context) {
if (isWiredHeadsetOn(context)) {
changeToReceiver(context);
} else if (isBluetoothA2dpOn(context)) {
changeToHeadset(context);
} else {
changeToSpeaker(context);
}
}
public static void pauseMusic(Context context, AudioManager.OnAudioFocusChangeListener focusRequest) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.requestAudioFocus(focusRequest, AudioManager.STREAM_MUSIC, AUDIOFOCUS_GAIN);
}
}
接下來(lái)是當(dāng)有線耳機(jī)/藍(lán)牙設(shè)備斷開、連接的時(shí)候,我們希望可以自動(dòng)切換到用戶原本設(shè)置的輸出通道上,比如在藍(lán)牙未連接時(shí),用戶設(shè)置的是手機(jī)外放;藍(lán)牙一旦連接以后,就把音頻切換到藍(lán)牙設(shè)備上。
下面我們就看看如何監(jiān)聽藍(lán)牙設(shè)備的連接狀態(tài)。
首先需要使用Android的權(quán)限如下:
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
然后使用Intent.ACTION_HEADSET_PLUG,BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,AudioManager.ACTION_AUDIO_BECOMING_NOISY來(lái)監(jiān)聽有線耳機(jī)、藍(lán)牙耳機(jī)連接狀態(tài)。
-
BluetoothAdapter.ACTION_STATE_CHANGED:指的是本地藍(lán)牙適配器的狀態(tài)已更改。 例如,藍(lán)牙開關(guān)打開或關(guān)閉。 - AudioManager.ACTION_AUDIO_BECOMING_NOISY這個(gè)Intent Action來(lái)監(jiān)聽藍(lán)牙斷開、耳機(jī)插拔的廣播
動(dòng)態(tài)注冊(cè)監(jiān)聽廣播:
public class HeadsetPlugReceiver extends BroadcastReceiver {
private static final String TAG = "HeadsetPlugReceiver";
private AudioManager audioManager;
@Override
public void onReceive(Context context, Intent intent) {
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
String action = intent.getAction();
if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
int state = intent.getIntExtra("state", 0);
if (state == 0) { // 耳機(jī)拔出
AudioUtils.changeToSpeaker(MicroContext.getApplication());
} else if (state == 1) { // 耳機(jī)插入
AudioUtils.changeToReceiver(MicroContext.getApplication());
}
} else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_DISCONNECTED);
updateBluetoothIndication(state);
} else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {
AudioUtils.changeToSpeaker(MicroContext.getApplication());
}
}
public void updateBluetoothIndication(int bluetoothHeadsetState) {
if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
L.i(TAG, "BluetoothProfile.STATE_CONNECTED");
if (audioManager == null) {
return;
} else {
AudioUtils.changeToHeadset(MicroContext.getApplication());
}
} else {
if (audioManager == null) {
return;
} else {
AudioUtils.changeToSpeaker(MicroContext.getApplication());
}
}
}
}
private HeadsetPlugReceiver mHeadsetPlugReceiver;
@Override
protected void onResume() {
super.onResume();
registerHeadSetPlugListener();
AudioUtils.pauseMusic(MicroContext.getApplication(),afChangeListener);
isFront = true;
L.e("Current ClassName:", getClass().getSimpleName());
}
@Override
protected void onPause() {
super.onPause();
isFront = false;
if (null != mHeadsetPlugReceiver) {
unregisterReceiver(mHeadsetPlugReceiver);
}
AudioUtils.dispose(MicroContext.getApplication(),afChangeListener);
}
這樣我們就能根據(jù)上面切換音頻輸出通道的代碼來(lái)實(shí)現(xiàn)音頻外設(shè)連接、斷開以后強(qiáng)制打破操作系統(tǒng)原有的輸出通道切換策略,來(lái)實(shí)現(xiàn)我們自己想要的切換功能了。
這里有一點(diǎn)需要注意的是:當(dāng)如果有其他音樂軟件在播放音樂時(shí),打開自有的app,需要關(guān)閉其他音樂。
代碼如下:
private AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// Stop playback
} else if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback or Raise it back to normal
}
}
};
AudioUtils.pauseMusic(MicroContext.getApplication(),afChangeListener);
當(dāng)使用藍(lán)牙通道進(jìn)行音頻通訊的時(shí)候,關(guān)閉音頻時(shí)就需要把音頻關(guān)閉掉,然后把音頻模式切回原來(lái)的模式。
AudioUtils.dispose(MicroContext.getApplication(),afChangeListener);
dispose里面的代碼,請(qǐng)看AudioUtils的源碼即可。