Android 音频设备管理

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;
/*
* sdk版本大于等于18时,使用BluetoothManager获取BluetoothAdapter
* 小于18使用BluetoothAdapter.getDefaultAdapter()获取
*/
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)获取此类实例

  • 首先需要进行蓝牙协议切换,调用BluetoothAdaptergetProfileProxy方法,将蓝牙协议切换为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) {
        // 蓝牙协议获取成功时,回调此接口

        // 判断是否为Headset协议
        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);

    // 打开蓝牙SCO连接
    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时,需要调用 BluetoothAdaptercloseProfileProxy方法将协议关闭。协议成功关闭后,依旧会走之前注册的回调函数的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); // BluetoothHeadset连接状态
    filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);// 蓝牙SCO的连接状态
    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){
          	// 判断这个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); // BluetoothHeadset连接状态
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);// 蓝牙SCO的连接状态

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;
}
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Android音频策略是指安卓操作系统对于设备音频功能的管理和控制策略。它包括了音频的输入、输出、路由和控制等方面。 首先,Android音频策略规定了音频输入的管理方式。通过底层API,Android可以管理和控制设备的麦克风输入。它允许应用程序访问和使用麦克风数据,进行录音或语音识别等功能。同时,音频策略还考虑了不同应用程序之间的优先级,确保高优先级的应用程序能够独占麦克风输入。 其次,音频策略还涉及音频输出的路由和管理。通过音频路由,Android可以将音频输出到不同的设备,如扬声器、耳机或蓝牙耳机。这使得用户可以根据需求选择合适的音频输出方式。此外,音频策略还允许应用程序控制音量和音调等参数,以满足用户的个性化需求。 此外,Android音频策略还考虑了多媒体的播放和通信功能。它允许多个应用程序同时使用音频功能,如同时播放背景音乐和接听电话。通过合理的资源管理和使用优先级,音频策略可以确保不同应用程序之间的协调和平衡。 最后,安卓音频策略还考虑了能耗和性能方面的因素。它通过动态调整设备的工作状态和功耗来优化音频体验。例如,在没有音频输出时,安卓可以自动关闭耳机插孔以节省电量。同时,它还可以通过优化音频处理算法和资源管理,减少对系统性能的影响。 总结来说,Android音频策略通过管理和控制音频输入、输出、路由和控制等方面,提供了全面的音频功能管理和优化能力,以提供良好的音频体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值