AudioManager 自动切换自己的模式 + 不尊重 setSpeakerphoneOn()

AudioManager auto switching own mode + not respecting setSpeakerphoneOn()

我想播放一些音量 lvl 适合耳朵的音频。 “phone 通话模式”。为此,我使用了众所周知且普遍建议的

audioManager.setMode(audioNormalState ?
            AudioManager.MODE_NORMAL : AudioManager.MODE_IN_COMMUNICATION);

问题是我不是总是在模式切换后才播放音频,我必须等待并且不能确定多长时间,甚至可能是几分钟。我做了一些循环记录和 MODE_IN_COMMUNICATION mode is kept as long as the user is in "phone call mode" in my app on some Motorola with Android 9, but on Pixel 3 with Android 12 after 6 seconds mode is auto-switching back to MODE_NORMAL 当没有播放时 。没有执行额外的代码(比如一些监听器),没有额外的(系统)日志。当我在切换到 MODE_IN_COMMUNICATION 模式后 1 秒开始播放音频时,只要播放音频(甚至超过 6 秒)它就不会自动切换,但在完成模式后立即也会自动切换到 MODE_NORMAL.

我的应用程序发出某种实时语音呼叫(命令),但也可以发出一些信号模式的“嘟嘟声”,还有一个历史记录功能,可以提供所有按时间顺序排列的发声动作,以便再次播放为了。如果这只是语音,那么仅在通话期间切换到 MODE_IN_COMMUNICATION 并返回可能就足够了,但是如何处理非常重要的 SoundPool 叮当声,我是否也必须为它们切换模式? (或者对于历史播放,这是一个混合)AFAIK 模式切换并不快(在某些设备上什至几秒钟),所以我可能会为数百毫秒的短信号应用一些显着的延迟(没办法,每毫秒都是至关重要!)或者即使在“phone 呼叫模式”下,当模式没有“足够快”地改变(用户不会高兴)时,我也冒着大声播放 signal/voice 的风险。我依赖固定设置(但根据应用程序状态和设置进行配置)MODE_IN_COMMUNICATION,它一直工作到 Android 12...(可以确认 new/wrong Pixels 和 Samsungs 上的行为)

当前使用的切换音频模式的方法+下面的配置,值得注意的是setSpeakerphoneOn方法也并不总是在Android 12上工作。至少在MODE_NORMAL上不会,这是现在默认的自动切换回模式,isSpeakerphoneOn 在第一次启动时也是 false,但实际上我所有的音频源都在大声播放...

// forceAudioNormalState = true only when app exit!
public static void resolveLoudState(AudioManager audioManager, boolean forceAudioNormalState) {
    boolean silentPhoneCallMode = isPhoneCallModeEnabled(); // phone call GUI, only ear-friendly volume!!
    boolean silentHeadset = HeadsetPlugReceiver.isHeadsetPlugged &&
            !HeadsetPlugReceiver.forceSpeakerWhenHeadsetOn; // headset plugged, but "muted", force speaker
    boolean silentBluetooth = BluetoothController.isAudioDeviceConnected() &&
            !audioManager.isBluetoothScoOn(); // bt headset plugged, but "muted", force speaker

    boolean loud = true; // by default
    if (silentPhoneCallMode || silentHeadset || silentBluetooth) loud = false;

    String log = String.format("resolveLoudState play loud: %s," +
                    " silentPhoneCallMode: %s, silentHeadset: %s, silentBluetooth: %s",
            loud, silentPhoneCallMode, silentHeadset, silentBluetooth);
    Timber.i(log);

    audioManager.setMode(forceAudioNormalState ?
            AudioManager.MODE_NORMAL : AudioManager.MODE_IN_COMMUNICATION);
    audioManager.setSpeakerphoneOn(loud);
    // even if deprecated this still works! fake wired headset on even for bt
    audioManager.setWiredHeadsetOn(!loud && (silentHeadset || silentBluetooth));
    // not loud and any headset connected and "muted"
}

请注意,在上面的代码片段中,没有关于当前播放状态的 flag/information,只有应用程序 state/config

我想自己管理这些模式并决定使用哪个音频输出,或者可能有任何其他方式强制播放所有音频(AudioTrackSoundPool , MediaPlayer, ExoPlayer 等) 是否有亲耳调节音量?

编辑:刚刚注意到当模式自动切换到 MODE_NORMAL 并且我将开始播放 STREAM_VOICE_CALL 时它会自动切换到 MODE_IN_COMMUNICATION(有一些小但显着的延迟)并且完成音频后再次重置...这是整个系统的一些新的未记录的行为,变得非常不友好,有漏洞且不清楚API...

edit2: this looks like related issue

PS。我注意到 Android 12 设备上的 MediaSession 应用程序(例如音乐播放器)直接在 Notification 上有一个新选项,用于在 wired/bt [= 时选择 speaker/headphones 62=] 已连接,但我根本没有使用会话 API。奖励问题:是否有 API?

找到了我自己的问题的一些答案,与社区分享

6 秒 auto-switch 模式是 new feature in Android 12, which works only if (mode == AudioSystem.MODE_IN_COMMUNICATION) (check out flow related to MSG_CHECK_MODE_FOR_UID flag). This should help for MODE_IN_COMMUNICATION set to AudioManager and left after app exit, this was messing with global/system-level audio routing. There is also a brand new AudioManager.OnModeChangedListener 在模式(自动)更改时调用

setSpeakerphoneOn 结果被弃用了,即使这没有在文档中标记...我们有新方法 setCommunicationDevice(AudioDeviceInfo) and in its description we have info about startBluetoothSco(), stopBluetoothSco() and setSpeakerphoneOn(boolean) deprecation. I'm using all three methods and now on Android 12 I'm iterating through getAvailableCommunicationDevices(),比较每个项目的类型,如果找到所需的类型,我'打电话 setCommunicationDevice(targetAudioDeviceInfo)。我现在根本不切换音频模式,保持 MODE_NORMAL。我所有的流都是 AudioManager.STREAM_VOICE_CALL 类型(如果适用)

用于 built-in 听筒音频播放。我们使用的是“ear-friendly 模式”

if (earpieceMode) {
    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    audioManager.setSpeakerphoneOn(false); // call AFTER setMode
}

在 Android 12 上(在多次扬声器状态切换后)不能可靠地工作。现在我使用下面的代码(综合片段)

ArrayList<Integer> targetTypes = new ArrayList<>();
//add types according to needs, may be few in order of importance
if (bluetoothScoConnected) {
    targetTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
} else if (wiredHeadsetConnected) {
    if (isUsbHeadset) {
        targetTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET);
        targetTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE);
        targetTypes.add(AudioDeviceInfo.TYPE_USB_ACCESSORY);
    } else {
        targetTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADSET);
        targetTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES);
    }
} else if (earpieceMode) {
    targetTypes.add(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
} else { // play out loud
    targetTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
}

Boolean result = null;
List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices();

outer:
for (Integer targetType : targetTypes) {
    for (AudioDeviceInfo device : devices) {
        if (device.getType() == targetType) {
            result = audioManager.setCommunicationDevice(device);
            Log.i("AUDIO_MANAGER", "setCommunicationDevice type:" + targetType + " result:" + result);
            break outer;
        }
    }
}

if (result == null) {
    Log.i("AUDIO_MANAGER", "setCommunicationDevice targetType NOT FOUND!!");
}

值得一提的蓝牙 SCO 耳机盒 - 当刚 connected/paired 与设备一起使用时,我的所有配件都被识别为 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP 类型 (getCommunicationDevice())。我确实想要 SCO,它在 A2DP 连接后的几秒钟内未在 getAvailableCommunicationDevices() 中列出,所以我要留下一些倒数计时器,它会检查(间隔 2s)并等待 AudioDeviceInfo.TYPE_BLUETOOTH_SCO 几秒钟(我设置了 16),然后当出现在列表中或只是关闭计时器时,我将切换到这种类型