Android 10.0 CarAudioService分析(一)

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 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 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,后面会有专门的章节分析该配置文件






    
        
            
                
                    
                        
                    
                    
                        
                    
                    
                        
                    
                    
                        
                    
                
                
                    
                        
                    
                    
                        
                    
                
                
                    
                        
                    
                
                
                    
                        
                    
                
            
            
                
            
            
        
        
            
                
                    
                        
                        
                        
                        
                        
                        
                        
                        
                    
                
            
            
                
            
        
    

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是由的数量决定,每个下的device以及他们下面的所有context组成了CarVolumeGroup,每个CarVolumeGroup包含了一个contextNumber和busNumber组成的map,以及busNumber和CarAudioDeviceInfo组成的map,每个device下的音量最大,最小,当前音量,默认音量都是一样的,每个group下的所有音量的步长都是一样的。

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

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

    CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {
        List 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 mVolumeGroups;
    private final List 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分析(一))