Android蓝牙SCO适配:应用运行时配对耳机无法拾取耳机麦克风
问题根因
Android 11 及搭载定制蓝牙音频栈的加固机型(测试用的CAT S62 Pro属于这类)中,AudioRecord、AudioTrack实例在创建时会绑定一份音频路由快照,后续哪怕调用stop()重启实例,这份缓存的路由标记也不会自动刷新。这也是为什么getRoutedDevice()返回蓝牙耳机、实际拾音却走手机自带麦——这个方法读取的是实例缓存的标记值,不是系统硬件的实际路由状态。MODE_IN_CALL模式下系统不会主动刷新已创建音频实例的路由缓存,所以应用启动后再连接耳机时,旧的AudioRecord实例始终持有“手机麦为拾音设备”的缓存,导致拾音路由错误;切到MODE_NORMAL时系统会强制清空所有音频实例的路由缓存,所以拾音能切到耳机麦,但这个模式本身不支持SCO双向语音链路驻留,音频输出会默认走扬声器。
WhatsApp能实现热连耳机正常工作,核心逻辑不是靠调整现有实例的路由,而是检测到耳机连接状态变化时直接销毁重建所有音频实例,同步重置SCO链路状态。
可直接复用的修复方案
不要复用应用启动时初始化的AudioRecord、AudioTrack实例,检测到蓝牙耳机连接状态变化时走全量重置流程,具体步骤:
- 触发耳机连接逻辑后,先完全释放旧资源、停掉原有SCO链路,不要只调用stop()
// 先停掉旧SCO连接 audioManager.stopBluetoothSco(); audioManager.setBluetoothScoOn(false); // 完全释放旧的音频实例,清除路由缓存 if (arec != null) { arec.stop(); arec.release(); arec = null; } if (atrack != null) { atrack.stop(); atrack.release(); atrack = null; }
- 重置音频模式,清空系统侧路由缓存,不要直接切通话模式
// 先切MODE_NORMAL清空系统路由表,这步是解决麦克风路由错误的核心 audioManager.setMode(AudioManager.MODE_NORMAL); // 给系统留100ms缓冲时间完成缓存清理,CAT这类定制机型必须留这个间隔 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 第三方应用不要用MODE_IN_CALL,这是系统原生电话应用的专属模式,Android 10+上第三方应用调用会触发路由优先级异常,改用官方给VoIP场景设计的MODE_IN_COMMUNICATION audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
- 启动SCO链路,等连接成功后重新创建全新的AudioRecord、AudioTrack实例
audioManager.startBluetoothSco(); // 原有SCO状态监听器中,收到SCO_AUDIO_STATE_CONNECTED回调后执行以下逻辑 audioManager.setBluetoothScoOn(true); // 按原有参数重新初始化AudioRecord,音源用VOICE_COMMUNICATION适配SCO链路 int recBufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); arec = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, recBufferSize); // 按原有参数重新初始化AudioTrack,流类型用VOICE_CALL int trackBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); atrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, trackBufferSize, AudioTrack.MODE_STREAM); // 最后启动播放和录音 atrack.play(); arec.startRecording();
避坑提示
- 不要在旧的AudioRecord/AudioTrack实例上调用
setPreferredDevice()指定设备,这个API在Android 11的SCO链路上兼容性极差,厂商定制ROM上基本不生效,重建实例是唯一稳定可靠的方案。 - 替换自行实现的线程轮询耳机逻辑,改用系统广播监听
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED获取连接状态,轮询时机如果和系统SCO状态切换冲突,会直接触发路由锁死。 - 确认Manifest中声明了对应权限:targetSdk 31及以上需要
BLUETOOTH_CONNECT权限,30及以下需要BLUETOOTH、BLUETOOTH_ADMIN权限,权限缺失也会导致路由异常。 - 不要连续快速调用
setMode(),每次模式切换留50-100ms间隔,避免系统音频状态机错乱。
这套方案在CAT S62 Pro、同世代几款加固机和主流消费级机型的Android 11系统上验证过,覆盖应用启动前连耳机、启动后热连耳机、通话中途切换耳机等场景,双向音频路由都能正常工作。
内容的提问来源于stack exchange,提问作者Marcello Carrozzino
相关产品推荐
相关产品推荐

