前言
最近接到一个需求,需要在播放声音时适配华为蓝牙手环,这个手环不同于其他手环,将手环主机从腕带上取出时,就变成了蓝牙耳机,可以接听电话。如图所示:

- 手环模式:未将主机从腕带上取出状态
- 耳机模式:将主机从腕带上取出状态
定位问题
遇到的问题
手环模式下,手机连接手环蓝牙后,声音由听筒播放。
为什么会从听筒中播放?
从手机设置界面观察到,该手环的音频选项在处于手环模式和耳机模式时有不同的状态,当处于手环模式时,通话音频处于开启状态,媒体音频处于关闭状态,如图:


接下来我们看代码,在原有的实现中,当有蓝牙设备连接时,会收到连接的广播,然后我们通过如下代码设置将声音通过蓝牙耳机播放
private void chooseBluetooth() {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.startBluetoothSco(); // 打开 SCO 类型蓝牙链路
audioManager.setBluetoothScoOn(true); // 打开 SCO 类型蓝牙链路
audioManager.setSpeakerphoneOn(false); // 关闭扬声器
}
audioManager
即 AudioManager
对象,这里简单介绍一下 audioManager.startBluetoothSco(); 这行代码的意思是打开 SCO 类型蓝牙链路。在蓝牙通信中,共有两种通信链路:
- 同步链路 (Synchronous Connection Oriented)
- 异步链路 (Asynchronous Connectionless)
同步链路 (SCO) 连接为对称连接,利用保留时隙传送数据包。连接建立后,主设备和从设备可以不被选中就发送SCO数据包。SCO数据包既可以传送话音,也可以传送数据,但在传送数据时,只用于重发被损坏的那部分的数据。主要用来传输对时间要求很高的数据通信
异步链路(ACL)就是定向发送数据包,它既支持对称连接,也支持不对称连接(既可以一对一,也可以一对多)。主设备负责控制链路带宽,并决定微微网中的每个从设备可以占用多少带宽和连接的对称性。从设备只有被选中时才能传送数据。ACL链路也支持接收主设备发给微微网中所有从设备的广播消息。
那么为什么在手环模式下声音会从听筒中播放?这里做出一个猜想,在手环模式下,Android 系统并没有主动打开媒体音频,当我们的程序执行 audioManager.startBluetoothSco();
时执行失败,导致无法打开蓝牙链路,之后我们又设置了 audioManager.setSpeakerphoneOn(false);
关闭扬声器,声音自然就从听筒中播放。
解决方案
大致思路就是在调用 chooseBluetooth()
之前判断系统是否打开了媒体音频,如果打开了则进行蓝牙播放,否则依然使用扬声器播放,具体判断代码如下:
boolean isA2dpOn = audioManager.isBluetoothA2dpOn();