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

Android 12如何通过听筒播放音频?AudioManager设置不生效问题

问题根因

这不是模拟器bug,是Android 12(API Level 31)对音频路由策略的官方变更导致原有逻辑失效,核心原因有两个:

  • Android 12及以上修改音频输出路径需要新增MODIFY_AUDIO_ROUTING危险权限,仅声明权限不动态申请无法操作音频路由
  • PeerJS底层依赖的WebRTC内核默认会抢占音频路由优先级,主动切换到扬声器输出,会覆盖直接对AudioManager的属性设置
修复方案

1. 补充权限声明与动态申请

首先在AndroidManifest.xml中添加所需权限:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Android 12及以上新增必需权限 -->
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />

在初始化音频功能前,动态申请MODIFY_AUDIO_ROUTING权限,该权限属于Android 12新增的危险权限,必须用户授权后才能生效。

2. 适配Android 12新音频路由API

Android 12开始废弃了直接修改isSpeakerphoneOn的路由控制逻辑,改用setCommunicationDevice接口控制通话音频输出设备,替换原有代码如下:

private fun setAudioOutput(useSpeaker: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        val targetDevice = if (useSpeaker) {
            // 找扬声器设备
            am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).firstOrNull {
                it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
            }
        } else {
            // 找听筒设备
            am.getDevices(AudioManager.GET_DEVICES_OUTPUTS).firstOrNull {
                it.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
            }
        }
        targetDevice?.let {
            am.setCommunicationDevice(it)
        }
    } else {
        // 低版本沿用原有逻辑
        am.isSpeakerphoneOn = useSpeaker
    }
}

private fun initAudio(){
    am = getSystemService(AUDIO_SERVICE) as AudioManager
    volumeControlStream = AudioManager.STREAM_VOICE_CALL
    am.mode = AudioManager.MODE_IN_COMMUNICATION
    // 切换到听筒输出
    setAudioOutput(false)
}

private fun toggleSpeakerMode(){
    val currentIsSpeaker = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        am.communicationDevice?.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
    } else {
        am.isSpeakerphoneOn
    }
    setAudioOutput(!currentIsSpeaker)
}

3. 禁用WebRTC默认音频路由抢占

PeerJS底层的WebRTC内核默认会自动调整音频路由,需要在WebView初始化时注入JS禁用该逻辑:

webView.evaluateJavascript("""
    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        const originalGetUserMedia = navigator.mediaDevices.getUserMedia;
        navigator.mediaDevices.getUserMedia = function(constraints) {
            return originalGetUserMedia.call(this, constraints).then(stream => {
                stream.getAudioTracks().forEach(track => {
                    track.applyConstraints({
                        autoGainControl: false,
                        echoCancellation: true,
                        noiseSuppression: true
                    });
                });
                return stream;
            });
        }
    }
""".trimIndent(), null)
注意事项
  • Android 12及以上不要同时混用setCommunicationDevice和isSpeakerphoneOn接口,避免路由逻辑冲突
  • 若修改后模拟器仍异常可以用真机验证,少数Android 12模拟器镜像存在音频路由模拟不完善的问题,但绝大多数同类问题都是API适配和权限问题导致

内容的提问来源于stack exchange,提问作者Sujith S Manjavana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 01:27:07