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);
...
@Override
public 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 AudioPolicyManager
sendEncodedSurroundMode(newSurroundMode, "SettingsObserver"); // 通过setForceuse 到AudioPolicyManager
mDeviceBroker.toggleHdmiIfConnected_Async(); // reconnect HDMI设备
mEncodedSurroundMode = newSurroundMode;
...
1.5. setForceuse的处理流程
将数据库值转换为ForceUse的值,然后设置到AudioPolicyManager
private void sendEncodedSurroundMode(int encodedSurroundMode, String eventSource) {
// initialize to guaranteed bad value
int 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"); // disconnect
setWiredDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE, "onToggleHdmi"); // reconnect
AudioPolicyManager::setDeviceConnectionStateInt AudioPolicyManager::checkOutputsForDevice AudioPolicyManager::openOutputWithProfileAndDevice AudioPolicyManager::updateAudioProfiles AudioPolicyManager::modifySurroundFormats
根据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 formats
for (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 ALWAYS
if (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.javapublic 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:0x0003
Profile 2:[dynamic channels]
- format: AUDIO_FORMAT_PCM_16_BIT
- sampling rates:48000
- channel masks:0x0003
Profile 3:[dynamic format]
- format: AUDIO_FORMAT_AC3
- sampling rates:32000, 44100, 48000
- channel masks:0x0003
Profile 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定义中
typedef enum {
AUDIO_POLICY_FORCE_NONE = 0, // Auto
...
AUDIO_POLICY_FORCE_ENCODED_SURROUND_NEVER = 13, // PCM
AUDIO_POLICY_FORCE_ENCODED_SURROUND_ALWAYS = 14, // Always
AUDIO_POLICY_FORCE_ENCODED_SURROUND_MANUAL = 15, // Manual
...
} audio_policy_forced_cfg_t;