IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 音频设备管理 -> 正文阅读

[移动开发]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;
}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:57:45  更:2022-07-03 11:00:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 18:28:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码