【Android Audio】安卓音频中Surround mode切换流程
Surround mode
Surround mode的切换用来控制数字音频设备的输出格式。同时动态切换数字音频输出设备的profile,让apk感知到当前输出设备的能力级发生改变,apk需要动态的切换设备支持的音频流格式给到系统。
Surround mode的切换用来控制数字音频设备指:
- HDMI OUT
- HDMI ARC
- HDMI EARC
1.安卓原生AudioManager的API(setEncodedSurroundMode)
AudioManager.setEncodedSurroundMode
1.1. mode定义
/*** The surround sound formats are available for use if they are detected. This is the default* mode.*/public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0;/*** The surround sound formats are NEVER available, even if they are detected by the hardware.* Those formats will not be reported.*/public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;/*** The surround sound formats are ALWAYS available, even if they are not detected by the* hardware. Those formats will be reported as part of the HDMI output capability.* Applications are then free to use either PCM or encoded output.*/public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2;/*** Surround sound formats are available according to the choice of user, even if they are not* detected by the hardware. Those formats will be reported as part of the HDMI output* capability. Applications are then free to use either PCM or encoded output.*/public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3;
ENCODED_SURROUND_OUTPUT_AUTO
- 数字输出设备的profile根据该设备上报的能力级决定
ENCODED_SURROUND_OUTPUT_NEVER
- 数字输出设备的profile只有PCM的profile,其他的profile强制删除
ENCODED_SURROUND_OUTPUT_ALWAYS
- 数字输出设备的profile中强制加上所有格式
ENCODED_SURROUND_OUTPUT_MANUAL
- 数字输出设备的profile中只有用户UI手动配置的格式
1.2. 数据库定义
Surround mode的安卓数据库字段是ENCODED_SURROUND_OUTPUT (encoded_surround_output)
Surround mode的manual菜单中的各个format 保存在ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS(encoded_surround_output_enabled_formats)
1.3. setEncodedSurroundMode接口实现
在AudioService的接口实现中,仅仅对数据库字段进行更新,实际逻辑处理在数据库监听ENCODED_SURROUND_OUTPUT字段的OnChange函数
public boolean setEncodedSurroundMode(@AudioManager.EncodedSurroundOutputMode int mode) {
...mSettings.putGlobalInt(mContentResolver,Settings.Global.ENCODED_SURROUND_OUTPUT,toEncodedSurroundSetting(mode));
...
1.4. 数据库监听
frameworks\base\services\core\java\com\android\server\audio\AudioService.java
注册需要监听的数据库字段,任一数据库字段发生改变时系统会回调onChange函数
public class AudioService extends IAudioService.Stub
...private SettingsObserver mSettingsObserver;private class SettingsObserver extends ContentObserver {SettingsObserver() {mContentResolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.ENCODED_SURROUND_OUTPUT), false, this);mContentResolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS), false, this);
...@Overridepublic void onChange(boolean selfChange) {
...updateEncodedSurroundOutput(); // 检查mode是否更新sendEnabledSurroundFormats(mContentResolver, mSurroundModeChanged); // 检查Manual模式的Format状态是否更新
...}private void updateEncodedSurroundOutput() {int newSurroundMode = mSettings.getGlobalInt(mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);// Did it change?if (mEncodedSurroundMode != newSurroundMode) {// Send to AudioPolicyManagersendEncodedSurroundMode(newSurroundMode, "SettingsObserver"); // 通过setForceuse 到AudioPolicyManagermDeviceBroker.toggleHdmiIfConnected_Async(); // reconnect HDMI设备mEncodedSurroundMode = newSurroundMode;
...
1.5. setForceuse的处理流程
将数据库值转换为ForceUse的值,然后设置到AudioPolicyManager
private void sendEncodedSurroundMode(int encodedSurroundMode, String eventSource) {// initialize to guaranteed bad valueint forceSetting = AudioSystem.NUM_FORCE_CONFIG;switch (encodedSurroundMode) {case Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO:forceSetting = AudioSystem.FORCE_NONE;break;case Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER:forceSetting = AudioSystem.FORCE_ENCODED_SURROUND_NEVER;break;case Settings.Global.ENCODED_SURROUND_OUTPUT_ALWAYS:forceSetting = AudioSystem.FORCE_ENCODED_SURROUND_ALWAYS;break;case Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL:forceSetting = AudioSystem.FORCE_ENCODED_SURROUND_MANUAL;break;default:Log.e(TAG, "updateSurroundSoundSettings: illegal value "+ encodedSurroundMode);break;}if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) {Log.d(TAG, "sendEncodedSurroundMode forceSetting:" + forceSetting);mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting,eventSource);}}
1.6. reconnect 设备动态修改profile的处理流程
frameworks\base\services\core\java\com\android\server\audio\AudioDeviceInventory.java
如果当前系统中有hdmiDevices相关设备,那么系统onToggleHdmi对设备进行reconnect,使得AudioPolicyManager重新刷新对应的设备的profile
/*package*/ void onToggleHdmi() {final int[] hdmiDevices = { AudioSystem.DEVICE_OUT_HDMI,AudioSystem.DEVICE_OUT_HDMI_ARC, AudioSystem.DEVICE_OUT_HDMI_EARC };synchronized (mDevicesLock) {for (DeviceInfo di : mConnectedDevices.values()) {boolean isHdmiDevice = Arrays.stream(hdmiDevices).anyMatch(device ->device == di.mDeviceType);if (isHdmiDevice) {
...// Toggle HDMI to retrigger broadcast with proper formats.setWiredDeviceConnectionState(ada,AudioSystem.DEVICE_STATE_UNAVAILABLE, "onToggleHdmi"); // disconnectsetWiredDeviceConnectionState(ada,AudioSystem.DEVICE_STATE_AVAILABLE, "onToggleHdmi"); // reconnect
根据ForceUse的值来决定要加载数字设备对应的format
void AudioPolicyManager::modifySurroundFormats(const sp<DeviceDescriptor>& devDesc, FormatVector *formatsPtr) {std::unordered_set<audio_format_t> enforcedSurround(devDesc->encodedFormats().begin(), devDesc->encodedFormats().end());std::unordered_set<audio_format_t> allSurround; // A flat set of all known surround formatsfor (const auto& pair : mConfig->getSurroundFormats()) {allSurround.insert(pair.first);for (const auto& subformat : pair.second) allSurround.insert(subformat);}audio_policy_forced_cfg_t forceUse = mEngine->getForceUse(AUDIO_POLICY_FORCE_FOR_ENCODED_SURROUND);ALOGD("%s: forced use = %d", __FUNCTION__, forceUse);// This is the resulting set of formats depending on the surround mode:// 'all surround' = allSurround// 'enforced surround' = enforcedSurround [may include IEC69137 which isn't raw surround fmt]// 'non-surround' = not in 'all surround' and not in 'enforced surround'// 'manual surround' = mManualSurroundFormats// AUTO: formats v 'enforced surround'// ALWAYS: formats v 'all surround' v 'enforced surround'// NEVER: formats ^ 'non-surround'// MANUAL: formats ^ ('non-surround' v 'manual surround' v (IEC69137 ^ 'enforced surround'))std::unordered_set<audio_format_t> formatSet;if (forceUse == AUDIO_POLICY_FORCE_ENCODED_SURROUND_MANUAL|| forceUse == AUDIO_POLICY_FORCE_ENCODED_SURROUND_NEVER) {// formatSet is (formats ^ 'non-surround')for (auto formatIter = formatsPtr->begin(); formatIter != formatsPtr->end(); ++formatIter) {if (allSurround.count(*formatIter) == 0 && enforcedSurround.count(*formatIter) == 0) {formatSet.insert(*formatIter);}}} else {formatSet.insert(formatsPtr->begin(), formatsPtr->end());}formatsPtr->clear(); // Re-filled from the formatSet at the end.if (forceUse == AUDIO_POLICY_FORCE_ENCODED_SURROUND_MANUAL) {formatSet.insert(mManualSurroundFormats.begin(), mManualSurroundFormats.end());// Enable IEC61937 when in MANUAL mode if it's enforced for this device.if (enforcedSurround.count(AUDIO_FORMAT_IEC61937) != 0) {formatSet.insert(AUDIO_FORMAT_IEC61937);}} else if (forceUse != AUDIO_POLICY_FORCE_ENCODED_SURROUND_NEVER) { // AUTO or ALWAYSif (forceUse == AUDIO_POLICY_FORCE_ENCODED_SURROUND_ALWAYS) {formatSet.insert(allSurround.begin(), allSurround.end());}formatSet.insert(enforcedSurround.begin(), enforcedSurround.end());}for (const auto& format : formatSet) {formatsPtr->push_back(format);}
}
1.7. Manual模式中setSurroundFormatEnabled设置Format开关
AudioManager.setSurroundFormatEnabled
在AudioService的接口实现中,仅仅对数据库字段进行更新,实际逻辑处理在数据库监听ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS字段的OnChange函数
public boolean setSurroundFormatEnabled(int audioFormat, boolean enabled) {
...mSettings.putGlobalString(mContentResolver,Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,TextUtils.join(",", enabledFormats));
...
}
在OnChange中调用sendEnabledSurroundFormats函数来检查format是否更新,最后通过onEnableSurroundFormats函数告知AudioPolicyManager
private void onEnableSurroundFormats(ArrayList<Integer> enabledSurroundFormats) {// Set surround format enabled accordingly.for (int surroundFormat : AudioFormat.SURROUND_SOUND_ENCODING) {boolean enabled = enabledSurroundFormats.contains(surroundFormat);int ret = AudioSystem.setSurroundFormatEnabled(surroundFormat, enabled);Log.i(TAG, "enable surround format:" + surroundFormat + " " + enabled + " " + ret);}}
在AudioPolicyManager的setSurroundFormatEnabled中再进行reconnect设备,更新对应的profile。最终format保存在mManualSurroundFormats中
status_t AudioPolicyManager::setSurroundFormatEnabled(audio_format_t audioFormat, bool enabled) {DeviceVector hdmiOutputDevices = mAvailableOutputDevices.getDevicesFromTypes({AUDIO_DEVICE_OUT_HDMI, AUDIO_DEVICE_OUT_HDMI_ARC, AUDIO_DEVICE_OUT_HDMI_EARC});for (size_t i = 0; i < hdmiOutputDevices.size(); i++) {// Simulate reconnection to update enabled surround sound formats.String8 address = String8(hdmiOutputDevices[i]->address().c_str());std::string name = hdmiOutputDevices[i]->getName();status_t status = setDeviceConnectionStateInt(hdmiOutputDevices[i]->type(),AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,address.c_str(),name.c_str(),AUDIO_FORMAT_DEFAULT);if (status != NO_ERROR) {continue;}status = setDeviceConnectionStateInt(hdmiOutputDevices[i]->type(),AUDIO_POLICY_DEVICE_STATE_AVAILABLE,address.c_str(),name.c_str(),AUDIO_FORMAT_DEFAULT);
...
1.8、Surround mode修改之后如何通知到apk
1.8.1、AudioService的connect发送粘性广播
在设置Surround mode之后做onToggleHdmi会进行connect和disconnect,同时调用sendDeviceConnectionIntent发送粘性广播AudioManager.ACTION_HDMI_AUDIO_PLUG。
private void sendDeviceConnectionIntent(int device, int state, String address,String deviceName) {
...case AudioSystem.DEVICE_OUT_HDMI:case AudioSystem.DEVICE_OUT_HDMI_ARC:case AudioSystem.DEVICE_OUT_HDMI_EARC:configureHdmiPlugIntent(intent, state);break;
...intent.putExtra(CONNECT_INTENT_KEY_STATE, state);intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);final long ident = Binder.clearCallingIdentity();try {mDeviceBroker.broadcastStickyIntentToCurrentProfileGroup(intent); // 发送粘性广播
...
private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
...intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
...
1.8.2、AudioPolicyManager回调onAudioPortListUpdate
status_t AudioPolicyManager::setDeviceConnectionStateInt(const sp<DeviceDescriptor> &device,audio_policy_dev_state_t state)
...mpClientInterface->onAudioPortListUpdate();
...
在每次disconnect和connect调用中会回调客户端注册的onAudioPortListUpdate函数,告知客户端音频设备发生了改变。
2. 调试
2.1 安卓数据库的值
串口敲cmd:
- 获取当前Surround mode值:
settings get global encoded_surround_output
对应值定义如下:
public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0;public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2;public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3;
- 获取当前Surround mode Manual的format值:
settings get global encoded_surround_output_enabled_formats
对应值定义如下:
frameworks/base/media/java/android/media/AudioFormat.java
public static final int ENCODING_INVALID = 0;public static final int ENCODING_DEFAULT = 1;public static final int ENCODING_PCM_16BIT = 2;public static final int ENCODING_PCM_8BIT = 3;public static final int ENCODING_PCM_FLOAT = 4;public static final int ENCODING_AC3 = 5;public static final int ENCODING_E_AC3 = 6;public static final int ENCODING_DTS = 7;public static final int ENCODING_DTS_HD = 8;public static final int ENCODING_MP3 = 9;public static final int ENCODING_AAC_LC = 10;public static final int ENCODING_AAC_HE_V1 = 11;public static final int ENCODING_AAC_HE_V2 = 12;public static final int ENCODING_IEC61937 = 13;public static final int ENCODING_DOLBY_TRUEHD = 14;public static final int ENCODING_AAC_ELD = 15;public static final int ENCODING_AAC_XHE = 16;public static final int ENCODING_AC4 = 17;public static final int ENCODING_E_AC3_JOC = 18;public static final int ENCODING_DOLBY_MAT = 19;public static final int ENCODING_OPUS = 20;
2.2 AudioPolicyManager的dump信息
dumpsys media.audio_policy的最前面可以看到当前Surround mode的值,同时也能看到hdmi设备的profile是否正确
Force use for encoded surround output: 13
...- Available output devices:Device 2:- id: 29- tag name: HDMI Out- type: AUDIO_DEVICE_OUT_AUX_DIGITAL|AUDIO_DEVICE_OUT_HDMI- supported encapsulation modes: 0 - supported encapsulation metadata types: 0 - Profiles:Profile 0:[dynamic format][dynamic channels][dynamic rates]Profile 1:[dynamic format]- format: AUDIO_FORMAT_PCM_16_BIT- sampling rates:32000, 44100, 48000- channel masks:0x0003Profile 2:[dynamic channels]- format: AUDIO_FORMAT_PCM_16_BIT- sampling rates:48000- channel masks:0x0003Profile 3:[dynamic format]- format: AUDIO_FORMAT_AC3- sampling rates:32000, 44100, 48000- channel masks:0x0003Profile 4:[dynamic format]- format: AUDIO_FORMAT_IEC61937- sampling rates:8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 128000, 176400, 192000- channel masks:0x0001, 0x0003, 0x0007, 0x000f, 0x0033, 0x0037, 0x003f, 0x0103, 0x0107, 0x063f
对应的枚举值对应关系在audio_policy_forced_cfg_t定义中
system\media\audio\include\system\audio_policy.h
typedef enum {AUDIO_POLICY_FORCE_NONE = 0, // Auto
...AUDIO_POLICY_FORCE_ENCODED_SURROUND_NEVER = 13, // PCMAUDIO_POLICY_FORCE_ENCODED_SURROUND_ALWAYS = 14, // AlwaysAUDIO_POLICY_FORCE_ENCODED_SURROUND_MANUAL = 15, // Manual
...
} audio_policy_forced_cfg_t;
