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

sna*_*msm 12 android android-audiomanager android-12

我想播放一些音频,音量级别调整为耳朵的大小。“通话模式”。为此,我使用众所周知且普遍建议的

audioManager.setMode(audioNormalState ?
            AudioManager.MODE_NORMAL : AudioManager.MODE_IN_COMMUNICATION);
Run Code Online (Sandbox Code Playgroud)

问题是我并不总是在模式切换后播放音频,我必须等待并且不确定多久,甚至可能是几分钟。我做了一些循环记录,MODE_IN_COMMUNICATION只要用户在某些搭载 Android 9 的摩托罗拉上的应用程序中处于“电话通话模式”,模式就会保留,但在配备Android 12 的 Pixel 3 上,6 秒后模式会自动切换回MODE_NORMAL当没有播放任何内容时。没有执行额外的代码(如某些侦听器),没有额外的(系统)日志。当我在切换到MODE_IN_COMMUNICATION模式后 1 秒开始播放音频时,只要播放音频(甚至超过 6 秒),它就不会自动切换,但在完成模式后也会自动切换到MODE_NORMAL.

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

目前使用的切换音频模式+配置的方法如下,值得注意的是,该setSpeakerphoneOn方法在 Android 12 上也并不总是有效。至少在打开时不起作用MODE_NORMAL,这是现在默认的自动切换回模式,也是isSpeakerphoneOnfalse一个开始,但我所有的音频源实际上都大声播放......

// 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"
}
Run Code Online (Sandbox Code Playgroud)

请注意,在上面的代码片段中没有有关当前播放状态的标志/信息,只有应用程序状态/配置

我想自己管理这些模式并决定使用哪个音频输出,或者也许有其他方法可以强制播放所有音频(AudioTrackSoundPoolMediaPlayerExoPlayer),并调整耳朵友好的音量?

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

edit2:这看起来像相关问题

附言。我注意到MediaSessionAndroid 12 设备上的应用程序(例如音乐播放器)有一个新选项,Notification可以在连接有线/BT 耳机/耳机时直接选择扬声器/耳机,但我根本不使用会话 API。额外问题:有相应的 API 吗?

sna*_*msm 7

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

6秒自动切换模式是Android 12中的新功能,仅起作用if (mode == AudioSystem.MODE_IN_COMMUNICATION)(查看与MSG_CHECK_MODE_FOR_UIDflag相关的流程)。这应该有助于在应用程序退出后MODE_IN_COMMUNICATION设置AudioManager和离开,这会扰乱全局/系统级音频路由。还有一个全新的AudioManager.OnModeChangedListener称为模式(自动)更改时

事实setSpeakerphoneOn证明已被弃用,即使这没有在文档中标记...我们有新方法setCommunicationDevice(AudioDeviceInfo),在其描述中我们有关于startBluetoothSco()和弃用stopBluetoothSco()的信息setSpeakerphoneOn(boolean)。我正在使用所有三种方法,现在在 Android 12 上我正在迭代getAvailableCommunicationDevices(),比较每个项目的类型,如果需要找到我正在调用的类型setCommunicationDevice(targetAudioDeviceInfo)。我现在根本不切换音频模式,保持在MODE_NORMAL. 我的所有流都是AudioManager.STREAM_VOICE_CALL类型(如果适用)

用于内置耳机音频播放。我们使用的“耳朵友好模式”

if (earpieceMode) {
    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    audioManager.setSpeakerphoneOn(false); // call AFTER setMode
}
Run Code Online (Sandbox Code Playgroud)

这在 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!!");
}
Run Code Online (Sandbox Code Playgroud)

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