美文网首页audio
Android 10.0 CarAudioService分析(一

Android 10.0 CarAudioService分析(一

作者: 棒棒0_0 | 来源:发表于2022-03-26 15:01 被阅读0次

1. 构造函数
关于CarAudioService的启动过程,我们就不在这里描述了,首先看CarAudioService的构造函数,Android10.0与Android9.0相比,这里多了mUidToZoneMap,这是一个Map的集合,主要是与音区相关。

    public CarAudioService(Context context) {
        mContext = context;
        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
        mPersistMasterMuteState = mContext.getResources().getBoolean(
                R.bool.audioPersistMasterMuteState);
        mUidToZoneMap = new HashMap<>();
    }

2. init()函数
构造函数结束之后,就是init()函数,init函数里面,首先是通过AudioManager.getDevices获取到输出设备,并存储在deviceInfos 数组中,然后根据type类型是否是TYPE_BUS来对deviceInfos数组进行筛选,把筛选的结果放在busToCarAudioDeviceInfo,拿到busToCarAudioDeviceInfo之后,就调用setupDynamicRouting方法,进行动态路由的选择。
流程图如下:

CarAudioService.png
    /**
     * Dynamic routing and volume groups are set only if
     * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
     */
    @Override
    public void init() {
        synchronized (mImplLock) {
            if (mUseDynamicRouting) {
                // Enumerate all output bus device ports
                AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
                        AudioManager.GET_DEVICES_OUTPUTS);
                if (deviceInfos.length == 0) {
                    Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
                    return;
                }
                SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
                for (AudioDeviceInfo info : deviceInfos) {
                    Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
                            info.getId(), info.getAddress(), info.getType()));
                    if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
                        final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
                        // See also the audio_policy_configuration.xml,
                        // the bus number should be no less than zero.
                        if (carInfo.getBusNumber() >= 0) {
                            busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
                            Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
                        }
                    }
                }
                setupDynamicRouting(busToCarAudioDeviceInfo);
            } else {
                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
                setupLegacyVolumeChangedListener();
            }

            // Restore master mute state if applicable
            if (mPersistMasterMuteState) {
                boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
                        VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
                setMasterMute(storedMasterMute, 0);
            }
        }
    }

2.1 setupDynamicRouting()函数
setupDynamicRouting做的事情比较多,我们分布进行梳理
1.读取CarAudio的配置
2.创建CarAudioZonesHelper,同步当前音量
3.通过usage创建动态路由

    private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
        //创建AudioPolicy
        final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
        builder.setLooper(Looper.getMainLooper());
        //读取Car Audio的配置
        mCarAudioConfigurationPath = getAudioConfigurationPath();
        if (mCarAudioConfigurationPath != null) {
            try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
                //创建CarAudioZonesHelper 
                CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
                        busToCarAudioDeviceInfo);
                mCarAudioZones = zonesHelper.loadAudioZones();
            } catch (IOException | XmlPullParserException e) {
                throw new RuntimeException("Failed to parse audio zone configuration", e);
            }
        } else {
            // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
            final IAudioControl audioControl = getAudioControl();
            if (audioControl == null) {
                throw new RuntimeException(
                        "Dynamic routing requested but audioControl HAL not available");
            }
            CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
                    R.xml.car_volume_groups, busToCarAudioDeviceInfo, audioControl);
            mCarAudioZones = legacyHelper.loadAudioZones();
        }
        for (CarAudioZone zone : mCarAudioZones) {
            if (!zone.validateVolumeGroups()) {
                throw new RuntimeException("Invalid volume groups configuration");
            }
            // Ensure HAL gets our initial value
            zone.synchronizeCurrentGainIndex();
            Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
        }

        // Setup dynamic routing rules by usage
        final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
        dynamicRouting.setupAudioDynamicRouting(builder);

        // Attach the {@link AudioPolicyVolumeCallback}
        builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);

        if (sUseCarAudioFocus) {
            // Configure our AudioPolicy to handle focus events.
            // This gives us the ability to decide which audio focus requests to accept and bypasses
            // the framework ducking logic.
            mFocusHandler = new CarZonesAudioFocus(mAudioManager,
                    mContext.getPackageManager(),
                    mCarAudioZones);
            builder.setAudioPolicyFocusListener(mFocusHandler);
            builder.setIsAudioFocusPolicy(true);
        }

        mAudioPolicy = builder.build();
        if (sUseCarAudioFocus) {
            // Connect the AudioPolicy and the focus listener
            mFocusHandler.setOwningPolicy(this, mAudioPolicy);
        }

        int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
        if (r != AudioManager.SUCCESS) {
            throw new RuntimeException("registerAudioPolicy failed " + r);
        }
    }

2.1.1 getAudioConfigurationPath()
首先加载vendor/etc下的car_audio_configuration.xml,如果没有则会加载system/etc下的car_audio_configuration.xml,找到之后则会返回路径,然后后面根据path进行解析,然后会创建CarAudioZonesHelper

    // CarAudioService reads configuration from the following paths respectively.
    // If the first one is found, all others are ignored.
    // If no one is found, it fallbacks to car_volume_groups.xml resource file.
    private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
            "/vendor/etc/car_audio_configuration.xml",
            "/system/etc/car_audio_configuration.xml"
    };

    /**
     * Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
     * @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
     */
    @Nullable
    private String getAudioConfigurationPath() {
        for (String path : AUDIO_CONFIGURATION_PATHS) {
            File configuration = new File(path);
            if (configuration.exists()) {
                return path;
            }
        }
        return null;
    }

下面我们看一下car_audio_configuration.xml,后面会有专门的章节分析该配置文件

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<!--
  Defines the audio configuration in a car, including
    - Audio zones
    - Display to audio zone mappings
    - Context to audio bus mappings
    - Volume groups
  in the car environment.
-->
<carAudioConfiguration version="1">
    <zones>
        <zone name="primary zone" isPrimary="true">
            <volumeGroups>
                <group>
                    <device address="bus0_media_out">
                        <context context="music"/>
                    </device>
                    <device address="bus3_call_ring_out">
                        <context context="call_ring"/>
                    </device>
                    <device address="bus6_notification_out">
                        <context context="notification"/>
                    </device>
                    <device address="bus7_system_sound_out">
                        <context context="system_sound"/>
                    </device>
                </group>
                <group>
                    <device address="bus1_navigation_out">
                        <context context="navigation"/>
                    </device>
                    <device address="bus2_voice_command_out">
                        <context context="voice_command"/>
                    </device>
                </group>
                <group>
                    <device address="bus4_call_out">
                        <context context="call"/>
                    </device>
                </group>
                <group>
                    <device address="bus5_alarm_out">
                        <context context="alarm"/>
                    </device>
                </group>
            </volumeGroups>
            <displays>
                <display port="0"/>
            </displays>
            <!-- to specify displays associated with this audio zone, use the following tags
                <displays>
                    <display port="1"/>
                    <display port="2"/>
                </displays>
                where port is the physical port of the display (See DisplayAddress.Phyisical)
            -->
        </zone>
        <zone name="rear seat zone">
            <volumeGroups>
                <group>
                    <device address="bus100_rear_seat">
                        <context context="music"/>
                        <context context="navigation"/>
                        <context context="voice_command"/>
                        <context context="call_ring"/>
                        <context context="call"/>
                        <context context="alarm"/>
                        <context context="notification"/>
                        <context context="system_sound"/>
                    </device>
                </group>
            </volumeGroups>
            <displays>
                <display port="1"/>
            </displays>
        </zone>
    </zones>
</carAudioConfiguration>

2.1.2 CarAudioZonesHelper()
CarAudioZonesHelper()的构造函数比较简单,创建完毕之后,调用loadAudioZones()函数,这里主要是对传入的path进行解析
解析过程如下:
1.先找name和isPrimary,在CarAudioZone中isPrimary只能有一个,如果isPrimary为true,则id是CarAudioManager.PRIMARY_AUDIO_ZONE,否则id是mNextSecondaryZoneId,因为mNextSecondaryZoneId初始为1,也就是说primary为true的id=0,其他的从1开始累加
2.根据zone标签,我们知道最终会创建多少个CarAudioZone。每个CarAudioZone包含一个VolumeGroups的集合,而集合的size是由<group>的数量决定,每个<group>下的device以及他们下面的所有context组成了CarVolumeGroup,每个CarVolumeGroup包含了一个contextNumber和busNumber组成的map,以及busNumber和CarAudioDeviceInfo组成的map,每个device下的音量最大,最小,当前音量,默认音量都是一样的,每个group下的所有音量的步长都是一样的。

    CarAudioZonesHelper(Context context, @NonNull InputStream inputStream,
            @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
        mContext = context;
        mInputStream = inputStream;
        mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;

        mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
        mPortIds = new HashSet<>();
    }

    CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {
        List<CarAudioZone> carAudioZones = new ArrayList<>();
        parseCarAudioZones(carAudioZones, mInputStream);
        return carAudioZones.toArray(new CarAudioZone[0]);
    }

上面根据配置文件,解析出CarAudioZone,我们看CarAudioZone里面都有什么,通过CarAudioZone的构造函数来看,CarAudioZone中会有一个CarVolumeGroups,这是CarVolumeGroup的集合

    private final int mId;
    private final String mName;
    private final List<CarVolumeGroup> mVolumeGroups;
    private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;

    CarAudioZone(int id, String name) {
        mId = id;
        mName = name;
        mVolumeGroups = new ArrayList<>();
        mPhysicalDisplayAddresses = new ArrayList<>();
    }

下面我们看一下CarVolumeGroup里面都有什么
mZoneId就是前面分析的zoneId
mId是我们每次mVolumeGroups .add的时候传入的从0开始累加的一个数,也就是list的索引
mStoredGainIndex 是数据库存储的值

    /**
     * Constructs a {@link CarVolumeGroup} instance
     * @param context {@link Context} instance
     * @param zoneId Audio zone this volume group belongs to
     * @param id ID of this volume group
     */
    CarVolumeGroup(Context context, int zoneId, int id) {
        mContentResolver = context.getContentResolver();
        mZoneId = zoneId;
        mId = id;
        mStoredGainIndex = Settings.Global.getInt(mContentResolver,
                CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1);
    }

busNumber和context在CarAudioZonesHelper有个map关系,有了
busNumber还可以通过mBusToCarAudioDeviceInfo.get(busNumber)找到CarAudioDeviceInfo。这样我们就可以做CarVolumeGroup的bind了

    static {
        CONTEXT_NAME_MAP = new HashMap<>();
        CONTEXT_NAME_MAP.put("music", ContextNumber.MUSIC);
        CONTEXT_NAME_MAP.put("navigation", ContextNumber.NAVIGATION);
        CONTEXT_NAME_MAP.put("voice_command", ContextNumber.VOICE_COMMAND);
        CONTEXT_NAME_MAP.put("call_ring", ContextNumber.CALL_RING);
        CONTEXT_NAME_MAP.put("call", ContextNumber.CALL);
        CONTEXT_NAME_MAP.put("alarm", ContextNumber.ALARM);
        CONTEXT_NAME_MAP.put("notification", ContextNumber.NOTIFICATION);
        CONTEXT_NAME_MAP.put("system_sound", ContextNumber.SYSTEM_SOUND);
    }

    private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
            throws XmlPullParserException, IOException {
        final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
                String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
                parseVolumeGroupContexts(parser, group,
                        CarAudioDeviceInfo.parseDeviceAddress(address));
            } else {
                skip(parser);
            }
        }
        return group;
    }

    private void parseVolumeGroupContexts(
            XmlPullParser parser, CarVolumeGroup group, int busNumber)
            throws XmlPullParserException, IOException {
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            if (TAG_CONTEXT.equals(parser.getName())) {
                group.bind(
                        parseContextNumber(parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)),
                        busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
            }
            // Always skip to upper level since we're at the lowest.
            skip(parser);
        }
    }

在bind函数中,因为group下的音量都是一个步长,所以步长只赋值一次。mContextToBus把contextNumber和busNumber存入map,mBusToCarAudioDeviceInfo则是把busNumber和info保存下来,这样就可以找到context与device之间的对应关系,一个device下不管有多少个context,volume对应的都是一次赋值。

    /**
     * Binds the context number to physical bus number and audio device port information.
     * Because this may change the groups min/max values, thus invalidating an index computed from
     * a gain before this call, all calls to this function must happen at startup before any
     * set/getGainIndex calls.
     *
     * @param contextNumber Context number as defined in audio control HAL
     * @param busNumber Physical bus number for the audio device port
     * @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
     */
    void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
        if (mBusToCarAudioDeviceInfo.size() == 0) {
            mStepSize = info.getAudioGain().stepValue();
        } else {
            Preconditions.checkArgument(
                    info.getAudioGain().stepValue() == mStepSize,
                    "Gain controls within one group must have same step value");
        }

        mContextToBus.put(contextNumber, busNumber);
        mBusToCarAudioDeviceInfo.put(busNumber, info);

        if (info.getDefaultGain() > mDefaultGain) {
            // We're arbitrarily selecting the highest bus default gain as the group's default.
            mDefaultGain = info.getDefaultGain();
        }
        if (info.getMaxGain() > mMaxGain) {
            mMaxGain = info.getMaxGain();
        }
        if (info.getMinGain() < mMinGain) {
            mMinGain = info.getMinGain();
        }
        if (mStoredGainIndex < getMinGainIndex() || mStoredGainIndex > getMaxGainIndex()) {
            // We expected to load a value from last boot, but if we didn't (perhaps this is the
            // first boot ever?), then use the highest "default" we've seen to initialize
            // ourselves.
            mCurrentGainIndex = getIndexForGain(mDefaultGain);
        } else {
            // Just use the gain index we stored last time the gain was set (presumably during our
            // last boot cycle).
            mCurrentGainIndex = mStoredGainIndex;
        }
    }

本篇文章先分析到这里,接下来我们会分析动态路由部分,也就是setupAudioDynamicRouting()

        // Setup dynamic routing rules by usage
        final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
        dynamicRouting.setupAudioDynamicRouting(builder);

相关文章

网友评论

    本文标题:Android 10.0 CarAudioService分析(一

    本文链接:https://www.haomeiwen.com/subject/hfnjjrtx.html