【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);
...
        @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.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: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定义中

system\media\audio\include\system\audio_policy.h

复制代码
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;
相关推荐
gfdgd xi5 小时前
Wine运行器3.4.0——虚拟机安装工具支持设置UEFI启动
android·windows·python·ubuntu·架构
shaominjin1235 小时前
OpenCV 4.1.2 SDK 静态库作用与功能详解
android·c++·人工智能·opencv·计算机视觉·中间件
东坡肘子6 小时前
Swift 官方发布 Android SDK | 肘子的 Swift 周报 #0108
android·swiftui·swift
Storm-Shadow13 小时前
Android OpenGLES视频剪辑示例源码
android·opengles·视频滤镜
双桥wow13 小时前
android 堆栈打印
android
Jonathan Star15 小时前
用Python轻松提取视频音频并去除静音片段
开发语言·python·音视频
给大佬递杯卡布奇诺15 小时前
FFmpeg 基本数据结构 AVInputFormat 分析
数据结构·c++·ffmpeg·音视频
给大佬递杯卡布奇诺15 小时前
FFmpeg 基本数据结构 AVCodecContext分析
数据结构·c++·ffmpeg·音视频
爱学习的大牛12318 小时前
使用C++开发Android .so库的优势与实践指南
android·.so·1024程序员节