You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 19:27:23