Android 音频设备管理
一、简介
在即使语音或者实时视频通话中,时常需要提供以下功能:
-
手动切换听筒或者扬声器; -
连接蓝牙时,音频需转到蓝牙; -
连接有线耳机时,音频需转到有线耳机; Android实现以上功能需要用到以下几个类: -
AudioManager:提供对音量和振铃模式控制的访问; -
BluetoothManager:蓝牙管理器,用于获取BluetoothAdapter蓝牙本地适配器的实例,并进行整体蓝牙管理; -
BluetoothAdapter:蓝牙本地适配器,用于管理蓝牙的搜索、连接等功能; -
BluetoothHeadset:蓝牙免提的配置文件,需要又听又说则需要使用此配置类。
蓝牙配置文件还有两个:
BluetoothA2dp:蓝牙立体声音传输配置文件,需要只听可以使用此配置类。
BluetoothHealth:蓝牙健康设备配置文件,需要与健康设备(如心率检测仪、血糖仪、温度计、台秤等)进行通信,则可以使用此配置类。
这两个蓝牙配置文件,这里用不上,所以就简单介绍下,有兴趣的可以自行学习。
二、相关类用法介绍
1. AudioManager
1)获取此类实例
通过Context中的getSystemService接口获取
private AudioManager mAudioManager = null;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
2)设置音频模式
AudioManager中的setMode方法,用于设置当前的音频的播放模式。以下是几种常用音频模式:
MODE_NORMAL : 普通模式,既不是铃声模式也不是通话模式MODE_RINGTONE : 铃声模式MODE_IN_CALL : 通话模式MODE_IN_COMMUNICATION : 通信模式,包括音/视频,VoIP通话.(3.0加入的,与通话模式类似)
即时语言一般使用MODE_IN_COMMUNICATION 通信模式,Android 3.0之前使用MODE_IN_CALL 通话模式。实际开发过程中,有一些机型在3.0之后也需要使用MODE_IN_CALL 通话模式,但只是少数。
if(mAudioManager != null){
mAudioManager.setMOde(AudioManager.MODE_IN_COMMUNICATION);
}
在使用完后,需要恢复原来的模式,这里我默认恢复到MODE_NORMAL普通模式。
if(mAudioManager != null){
mAudioManager.setMOde(AudioManager.MODE_NORMAL);
}
最好保证在同个进程中调用setMode方法。如果在不同进程中调用setMode方法,系统会将上一个调用setMode的进程的蓝牙SCO数据断开,就会导致蓝牙数据断连。
3)切换听筒、扬声器
AudioManager中的setSpeakerphoneOn方法,用于设置扬声器是否打开。
if(mAudioManager != null){
mAudioManager.setSpeakerphoneOn(false);
}
if(mAudioManager != null){
mAudioManager.setSpeakerphoneOn(true);
}
并且可以使用isSpeakerphoneOn方法判断当前扬声器是否打开
if(mAudioManager != null){
if(mAudioManager.isSpeakerphoneOn()){
}else{
}
}
切换音频到有线耳机的操作跟切换听筒的操作一致,只需调用setSpeakerphoneOn(false); 即可
2. BluetoothAdapter
1)获取此类实例
- sdk版本大于等于18时,使用BluetoothManager获取BluetoothAdapter
- sdk版本小于18时,使用BluetoothAdapter.getDefaultAdapter()获取
private BluetoothAdapter mBluetoothAdapter = null;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager != null) {
mBluetoothAdapter = mBluetoothManager.getAdapter();
}
} else {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
2)判断本地蓝牙适配器是否打开
BluetoothAdapter 中的isEnabled方法,用于判断手机本地的蓝牙适配器是否打开。如果返回值为false,则代表手机本地的适配器没有打开,也就不可能有蓝牙连接。
if(mBluetoothAdapter != null && mBluetoothAdapter.isEnable()){
}else{
}
3. BluetoothHeadset
1)获取此类实例
- 首先需要进行蓝牙协议切换,调用BluetoothAdapter 的getProfileProxy方法,将蓝牙协议切换为BluetoothProfile.HEADSET协议。getProfileProxy方法需要传入的参数:当前上下文Context,蓝牙协议监听回调函数BluetoothProfile.ServiceListener,需要切换的蓝牙协议BluetoothProfile.HEADSET;
- 接着在蓝牙协议切换成功的监听回调函数中赋值;
private BluetoothHeadset mBluetoothHeadset = null;
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.getProfileProxy(mContext, mBluetoothListener, BluetoothProfile.HEADSET);
}
private BluetoothProfile.ServiceListener mBluetoothListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile bluetoothProfile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) bluetoothProfile;
}
}
@Override
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
2)切换蓝牙耳机(HEADSET协议)
一般在收到蓝牙协议切换成功后,才进行蓝牙耳机的切换。也就是在上一点的回调函数中获取HEADSET协议的配置后,就进行蓝牙耳机的切换。
切换蓝牙耳机使用AudioManager中三个方法:setSpeakerphoneOn、startBluetooth、ScosetBluetoothScoOn。
private void switchBluetoothEarphone() {
if (mAudioManager == null || mBluetoothAdapter == null || mBluetoothHeadset == null) {
return;
}
if(!mBluetoothAdapter.isEnabled()){
return;
}
mAudioManager.setSpeakerphoneOn(false);
mAudioManager.startBluetoothSco();
mAudioManager.setBluetoothScoOn(true);
}
若在蓝牙耳机使用途中,想要强制切换到听筒或扬声器时,需要先将蓝牙的SCO连接关闭,再切换听筒或扬声器。
private void switchSpeakerphone() {
if (mAudioManager == null) {
return;
}
if (mBluetoothAdapter != null && mBluetoothHeadset != null) {
mAudioManager.setBluetoothScoOn(false);
mAudioManager.stopBluetoothSco();
}
mAudioManager.setSpeakerphoneOn(true);
}
private void switchEarphone() {
if (mAudioManager == null) {
return;
}
if (mBluetoothAdapter != null && mBluetoothHeadset != null) {
mAudioManager.setBluetoothScoOn(false);
mAudioManager.stopBluetoothSco();
}
mAudioManager.setSpeakerphoneOn(false);
}
3)关闭此类实例
在使用完BluetoothHeadSet时,需要调用 BluetoothAdapter的closeProfileProxy方法将协议关闭。协议成功关闭后,依旧会走之前注册的回调函数的onServiceDisconnected方法。
if (mBluetoothAdapter != null && mBluetoothHeadset != null) {
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
}
三、监听耳机的连接与断连
系统在耳机状态改变时会发送相应的广播信号。所以监听耳机的插入拔出或者连接断连,需要注册一个广播BroadcastReceiver。广播的使用如下代码:
private void registerBluetoothReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
if (mContext != null) {
mContext.registerReceiver(mBluetoothReceiver, filter);
}
}
private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
String action = intent.getAction();
BluetoothDevice dev;
if (action == null) {
return;
}
switch(action){
}
}
};
1. 监听有线耳机的插入与拔出
1)注册广播
filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
2)监听广播
AudioManager.ACTION_HEADSET_PLUG广播Intent中包含有参数state,表明有线耳机设备是否插入耳机孔。
case AudioManager.ACTION_HEADSET_PLUG: {
if (intent.hasExtra("state")) {
int state = intent.getIntExtra("state", 0);
if (state == 1) {
switchPlugInEarphones();
} else if (state == 0) {
switchEarphone();
}
}
break;
}
在开发中发现,只要手机插入过一次有线耳机,就算当前没有拔插有线耳机,后面重新注册广播时,会收到一次耳机拔出的广播。重启手机后,这种现象就会消失,但不可能每次拔插耳机就重启手机。
2. 监听蓝牙耳机的连接与断连
1)注册广播
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
2)监听广播
- BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED 广播intent包含当前的连接设备信息dev,以及当前的连接状态state。切换协议后,在蓝牙中途连接或者
- AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED 广播intent包含当前切换SCO链路的状态,切换成功会回调SCO_AUDIO_STATE_CONNECTED状态,如果切换失败或者断开,则会回调SCO_AUDIO_STATE_DISCONNECTED状态。
case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: {
dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
Log.i(TAG, "Headset STATE: " + state);
Log.i(TAG, "BluetoothDevice: " + dev.getName() + ", " + dev.getAddress());
switch (state) {
case BluetoothHeadset.STATE_CONNECTING://连接中
break;
case BluetoothHeadset.STATE_CONNECTED://已连接
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled() && mBluetoothHeadset == null) {
mBluetoothAdapter.getProfileProxy(mContext, mBluetoothListener, BluetoothProfile.HEADSET);
}
break;
case BluetoothHeadset.STATE_DISCONNECTED://断开
if (mBluetoothAdapter != null && mBluetoothHeadset != null) {
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
}
break;
case BluetoothHeadset.STATE_DISCONNECTING://断开中
break;
}
break;
}
case AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED: {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
switch (state) {
case AudioManager.SCO_AUDIO_STATE_CONNECTING: {
break;
}
case AudioManager.SCO_AUDIO_STATE_CONNECTED: {
break;
}
case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: {
break;
}
case AudioManager.SCO_AUDIO_STATE_ERROR: {
break;
}
default:
break;
}
break;
}
3. 判断当前连接的是有线耳机还是蓝牙耳机
- AudioManager的isWiredHeadsetOn方法可以判断是否连接着有线耳机,使用isBluetoothScoOn和isBluetoothA2dpOn方法可以判断是否连接着蓝牙耳机。
- 新版本的话推荐使用AudioManager.GET_DEVICES_OUTPUTS去获取当前连接的设备信息,设备信息保存在AudioDeviceInfo中。
private boolean isConnectPlugInEarphone() {
if (mAudioManager == null) {
return false;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return mAudioManager.isWiredHeadsetOn();
} else {
AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (int i = 0; i < devices.length; i++) {
AudioDeviceInfo device = devices[i];
if (device.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
|| device.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
return true;
}
}
}
return false;
}
private boolean isConnectBluetooth() {
if (mAudioManager == null) {
return false;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return mAudioManager.isBluetoothScoOn() || mAudioManager.isBluetoothA2dpOn();
} else {
AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (int i = 0; i < devices.length; i++) {
AudioDeviceInfo device = devices[i];
if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
|| device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
return true;
}
}
}
return false;
}
|