当前位置: 首页 > 知识库问答 >
问题:

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

谢奕
2023-03-14

我想播放一些音量lvl调整到耳朵aka的音频。“电话呼叫模式”。为此,我使用了著名的和普遍建议的

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

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

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

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

// 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"
}

请注意,在上面的代码段中,没有关于当前播放状态的标志/信息,只有apps state/config

我想自己管理这些模式,并决定将使用哪个音频输出,或者可能有任何其他方法来强制播放所有音频(< code>AudioTrack 、< code>SoundPool 、< code>MediaPlayer 、< code>ExoPlayer等。)带耳友好的调节音量?

编辑:刚刚注意到,当模式自动切换到<code>mode_NORMAL</code>时,我将开始播放<code>STREAM_VOICE_CALL</code>它将自动切换到通信模式</code>(有一些小但显著的延迟),并在完成音频后再次重置…这是系统总体上的一些新的未记录行为,变得非常不友好、有漏洞和不清晰的API。。。

编辑2:这看起来像相关的问题

我注意到Android 12设备上的MediaSession应用程序(例如音乐播放器)在通知上有一个新选项,用于在连接有线/ bt耳机/耳机时选择扬声器/耳机,但我根本没有使用会话API。奖励问题:是否有API?

共有1个答案

公西宏峻
2023-03-14

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

6 秒自动切换模式是 Android 12 中的一项新功能,仅当 (mode == AudioSystem.MODE_IN_COMMUNICATION) 时才有效(请查看与 MSG_CHECK_MODE_FOR_UID 标志相关的流程)。这应该有助于MODE_IN_COMMUNICATION设置为AudioManager并在应用程序退出后离开,这弄乱了全局/系统级音频路由。还有一个全新的AudioManager.OnModeChangedListener,当模式(自动)更改时调用

而<code>SetSpeakerPhone</code>被证明是不推荐使用的,即使这在文档中没有标记……我们有了一个新方法<code>setCommunicationDevice(AudioDeviceInfo),在它的描述中,我们有关于<code〉startBluetoothSco()</code>、<code>stopBluetoothSco()</code>和<code>SetSpeakorPhone(boolean)</code>的信息。我正在使用这三种方法,现在在Android 12上,我正在迭代getAvailableCommunicationDevices(),比较每个项目的类型,如果需要,我会调用setCommunicationDevice(targetAudioDeviceInfo)。我现在根本不切换音频模式,而是保持模式_NORMAL。我的所有流都是AudioManager。STREAM_VOICE_CALL类型(如适用)

用于内置耳机音频播放,又名。我们正在使用的“耳友好模式”

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耳机盒-当刚与设备连接/配对时,我的所有配件都被识别为AudioDeviceInfo。TYPE_BLUETOOTH_A2DPTYPE(getCommunicationDevice())。我确实想要SCO,在A2DP连接后的几秒钟内,它没有列在getAvailableCommunicationDevices()中,因此我留下了一些倒计时计时器,它检查(间隔2秒)并等待AudioDeviceInfo。键入_ BLUETOOTH_

 类似资料:
  • 定义自己的索引模式 加载到 Elasticsearch 的每组数据都有一个索引模式(Index Pattern)。 在上一节中,为莎士比亚数据集创建了名为 shakespeare 的索引,为 accounts 数据集创建了名为 bank 的索引。一个 _索引模式_ 是可以匹配多个索引的带可选通配符的字符串。例如一般在通用日志记录中,一个典型的索引名称一般包含类似 YYYY.MM.DD 格式的日期信

  • 使用JavaScript 可以从多个方面增强表单字段的易用性。其中,最常见的一种方式就是在用户填写完当前字段时,自动将焦点切换到下一个字段。通常,在自动切换焦点之前,必须知道用户已经输入了既定长度的数据(例如电话号码)。例如,美国的电话号码通常会分为三部分:区号、局号和另外4 位数字。为取得完整的电话号码,很多网页中都会提供下列3 个文本框: <input type="text" name="te

  • 服务模式切换比较麻烦,需要您的Kubernetes支持,目前我们使用的是istio的方案,也就是说您需要在你的kubernetes上安装istio的相关服务,并且在我们的模版管理将istio所需要的几个模版配置上。才能开启此功能。 如果您没有安装Istio,可跳过此章。 依赖 在"模版管理"菜单找到Gateway、VritualService、InitContainer、IstioProxy这几个

  • 好了,现在我们总结一下模式间切换的方法 其它模式 普通模式 Esc   普通模式 插入模式 i 在光标前插入 I 在行首插入 a 在光标后插入 A 在行末插入 o 在当前行之下新建行 O 在当前行之上新建行 r 替换当前字符 R 从当前字符开始替换 普通模式 命令模式 :   普通模式 可视模式 v 可视模式 V 可视行模式 Ctrl+v 可视块模式