Android 10.0 CarAudioService分析(一)

1. 構(gòu)造函數(shù)
關(guān)于CarAudioService的啟動過程,我們就不在這里描述了,首先看CarAudioService的構(gòu)造函數(shù)茄唐,Android10.0與Android9.0相比孝扛,這里多了mUidToZoneMap,這是一個(gè)Map的集合乎芳,主要是與音區(qū)相關(guān)遵蚜。

    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()函數(shù)
構(gòu)造函數(shù)結(jié)束之后,就是init()函數(shù)奈惑,init函數(shù)里面吭净,首先是通過AudioManager.getDevices獲取到輸出設(shè)備,并存儲在deviceInfos 數(shù)組中肴甸,然后根據(jù)type類型是否是TYPE_BUS來對deviceInfos數(shù)組進(jìn)行篩選寂殉,把篩選的結(jié)果放在busToCarAudioDeviceInfo,拿到busToCarAudioDeviceInfo之后原在,就調(diào)用setupDynamicRouting方法友扰,進(jìn)行動態(tài)路由的選擇彤叉。
流程圖如下:

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()函數(shù)
setupDynamicRouting做的事情比較多村怪,我們分布進(jìn)行梳理
1.讀取CarAudio的配置
2.創(chuàng)建CarAudioZonesHelper秽浇,同步當(dāng)前音量
3.通過usage創(chuàng)建動態(tài)路由

    private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
        //創(chuàng)建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)) {
                //創(chuàng)建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甚负,找到之后則會返回路徑柬焕,然后后面根據(jù)path進(jìn)行解析,然后會創(chuàng)建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,后面會有專門的章節(jié)分析該配置文件

<?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()的構(gòu)造函數(shù)比較簡單腊敲,創(chuàng)建完畢之后击喂,調(diào)用loadAudioZones()函數(shù),這里主要是對傳入的path進(jìn)行解析
解析過程如下:
1.先找name和isPrimary碰辅,在CarAudioZone中isPrimary只能有一個(gè)懂昂,如果isPrimary為true,則id是CarAudioManager.PRIMARY_AUDIO_ZONE没宾,否則id是mNextSecondaryZoneId凌彬,因?yàn)閙NextSecondaryZoneId初始為1,也就是說primary為true的id=0,其他的從1開始累加
2.根據(jù)zone標(biāo)簽循衰,我們知道最終會創(chuàng)建多少個(gè)CarAudioZone铲敛。每個(gè)CarAudioZone包含一個(gè)VolumeGroups的集合,而集合的size是由<group>的數(shù)量決定会钝,每個(gè)<group>下的device以及他們下面的所有context組成了CarVolumeGroup伐蒋,每個(gè)CarVolumeGroup包含了一個(gè)contextNumber和busNumber組成的map,以及busNumber和CarAudioDeviceInfo組成的map迁酸,每個(gè)device下的音量最大先鱼,最小,當(dāng)前音量奸鬓,默認(rèn)音量都是一樣的焙畔,每個(gè)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]);
    }

上面根據(jù)配置文件串远,解析出CarAudioZone宏多,我們看CarAudioZone里面都有什么,通過CarAudioZone的構(gòu)造函數(shù)來看澡罚,CarAudioZone中會有一個(gè)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的時(shí)候傳入的從0開始累加的一個(gè)數(shù)伸但,也就是list的索引
mStoredGainIndex 是數(shù)據(jù)庫存儲的值

    /**
     * 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有個(gè)map關(guān)系,有了
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函數(shù)中更胖,因?yàn)間roup下的音量都是一個(gè)步長,所以步長只賦值一次。mContextToBus把contextNumber和busNumber存入map函喉,mBusToCarAudioDeviceInfo則是把busNumber和info保存下來,這樣就可以找到context與device之間的對應(yīng)關(guān)系荣月,一個(gè)device下不管有多少個(gè)context管呵,volume對應(yīng)的都是一次賦值。

    /**
     * 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;
        }
    }

本篇文章先分析到這里哺窄,接下來我們會分析動態(tài)路由部分,也就是setupAudioDynamicRouting()

        // Setup dynamic routing rules by usage
        final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
        dynamicRouting.setupAudioDynamicRouting(builder);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捐下,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子萌业,更是在濱河造成了極大的恐慌坷襟,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件生年,死亡現(xiàn)場離奇詭異婴程,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)抱婉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門档叔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蒸绩,你說我怎么就攤上這事衙四。” “怎么了患亿?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵传蹈,是天一觀的道長。 經(jīng)常有香客問我,道長爱态,這世上最難降的妖魔是什么鲜戒? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮表锻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乞娄。我一直安慰自己瞬逊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布仪或。 她就那樣靜靜地躺著确镊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪范删。 梳的紋絲不亂的頭發(fā)上蕾域,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼旨巷。 笑死巨缘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的采呐。 我是一名探鬼主播若锁,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼斧吐!你這毒婦竟也來了又固?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤煤率,失蹤者是張志新(化名)和其女友劉穎仰冠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝶糯,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洋只,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昼捍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片木张。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖端三,靈堂內(nèi)的尸體忽然破棺而出舷礼,到底是詐尸還是另有隱情,我是刑警寧澤郊闯,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布妻献,位于F島的核電站,受9級特大地震影響团赁,放射性物質(zhì)發(fā)生泄漏育拨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一欢摄、第九天 我趴在偏房一處隱蔽的房頂上張望熬丧。 院中可真熱鬧,春花似錦怀挠、人聲如沸析蝴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闷畸。三九已至,卻和暖如春吞滞,著一層夾襖步出監(jiān)牢的瞬間佑菩,已是汗流浹背盾沫。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殿漠,地道東北人赴精。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像绞幌,于是被迫代替她去往敵國和親祖娘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容