当前位置: 首页 > news >正文

【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
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 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;
http://www.dtcms.com/a/536702.html

相关文章:

  • 2026版基于python的旅游景点推荐系统
  • 直白理解 NTRU 公钥加密系统
  • 如何在物联网产品应用串行psram
  • 1.2、实战准备:AI安全研究环境搭建与工具链
  • 鞋材加工东莞网站建设山西网页制作
  • 网站建设基本流程包括哪几个步骤工装设计方案网站
  • 苏州手机网站建设报价logo设计网站平台
  • 数据驱动AI落地:交通运维与仓储管理的智能化破局方法论与技术实践
  • 多功能雷达行为辨识与预测技术研究
  • Rust面试题及详细答案120道(115-120)-- 对比其他语言
  • LeetCode算法学习之移除元素
  • allWebPlugin中间件IE特别版发布
  • 前端八股之HTTP
  • rust笔记
  • 西安招聘网站建设多多鱼网页设计代码
  • 中国团队开发出有效的钙钛矿电池缓冲液
  • 使用mybatis-plus,实现将排序时,字段值为NULL的数据排在最后
  • Websocket两台服务器之间的通信
  • 网站技能培训班有哪些做网站用什么软件ps字体
  • 摩根大通将支持比特币和以太坊作为抵押品
  • 绿园区住房和城乡建设局网站c2c平台是什么意思
  • Web前端开发:用JavaScript阻止表单提交
  • 从 TCP 粘包到线程池:一起了解用 QRunnable 重构 Qt 高并发网络通信架构
  • Blender入门学习07 - 形态键
  • 网站推广常用方法包括二手车 东莞网站建设
  • LUMI 大模型分拣机器人应用 和 Lumi视觉标定
  • 开源项目分享:Gitee热榜项目 2025-10-27 日榜
  • [Dify 实战] 封闭插件开发到发布:本地编写、Remote调试与上线全流程(Python)
  • ARM《5》_系统移植(在开发板上运行linux程序)
  • 长沙网络营销公司排名郑州seo外包