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
我想自己管理这些模式并决定使用哪个音频输出,或者可能有任何其他方式强制播放所有音频(AudioTrack
,SoundPool
, 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),然后当出现在列表中或只是关闭计时器时,我将切换到这种类型
我想播放一些音量 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
我想自己管理这些模式并决定使用哪个音频输出,或者可能有任何其他方式强制播放所有音频(AudioTrack
,SoundPool
, 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),然后当出现在列表中或只是关闭计时器时,我将切换到这种类型