天天看點

Android10.0CarAudioZone(三)

前言

我們前面兩篇分析了CarAudioZone相關的聲音以及音頻焦點,基本控制流就差不多了,今天繼續看下關于CarAudioZone相關的資料流。

正文

資料流這塊與CarAudioZone的關系是什麼呢,資料流底層是一個bus的概念,那麼什麼是bus,是谷歌專為car弄得一套devices(這裡的device概念是framework層的),即AUDIO_DEVICE_OUT_BUS。它和普通的device有什麼差別呢,我們知道device的加載時通過audiopolicy來初始化的,在AudioPolicyService啟動後會通過AudioPolicyManager來load audio_policy_configuration.xml,而在audio_policy_configuration.xml裡定義了framework層所有的device。普通的device的type一定是不同的比如AUDIO_DEVICE_OUT_EARPIECE或者AUDIO_DEVICE_OUT_WIRED_HEADSET等,而使用bus的方式所有的device的type必須都是AUDIO_DEVICE_OUT_BUS,而且他們的address一定不為空,并且daddress的命名規則必須是busx_xxx或者bus00x_xxx,其中bus後面跟着的x必須是int,也就是這樣bus1_xxx或者bus001_xxx.那麼為什麼這麼命名呢,就進入了今天的正題。

CarAudioService的啟動過程這裡就不多說了,啟動後加載了init,在init中通過AudioManager的getDevices(AudioManager.GET_DEVICES_OUTPUTS)擷取了所有的用于輸出的device,然後把這些device中type是BUS的過濾出來,是以說想使用bus的這套邏輯首先type必須是AUDIO_DEVICE_OUT_BUS。拿到這些type是AUDIO_DEVICE_OUT_BUS的device後重新建構了CarAudioDeviceInfo。然後便進入了setupDynamicRouting,從這個方法名稱也可以看出設定動态路由,在setupDynamicRouting中我們之前的兩篇分析了Volume和AudioFocus部分。但其中夾雜了兩行代碼

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

我們進入CarAudioDynamicRouting看看

CarAudioDynamicRouting(CarAudioZone[] carAudioZones) {
        mCarAudioZones = carAudioZones;
    }
           

構造方法不說了,繼續看下dynamicRouting.setupAudioDynamicRouting(builder)

void setupAudioDynamicRouting(AudioPolicy.Builder builder) {
        for (CarAudioZone zone : mCarAudioZones) {
            for (CarVolumeGroup group : zone.getVolumeGroups()) {
                setupAudioDynamicRoutingForGroup(group, builder);
            }
        }
    }
           

兩個for循環,外層是mCarAudioZones即我們傳入的mCarAudioZones的循環,我們繼續看内層循環,我們還記得在分析Android10.0CarAudioZone(一)的時候說過CarVolumeGroup,每個CarAudioZone中包含多個CarVolumeGroup,這裡拿出每一個CarVolumeGroup,和builder(audiopolicy的bulider)傳遞給setupAudioDynamicRoutingForGroup(個人吐槽下覺得這個寫的還有優化的空間)

private void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
            AudioPolicy.Builder builder) {
        // Note that one can not register audio mix for same bus more than once.
        for (int busNumber : group.getBusNumbers()) {
            boolean hasContext = false;
            CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForBus(busNumber);
            AudioFormat mixFormat = new AudioFormat.Builder()
                    .setSampleRate(info.getSampleRate())
                    .setEncoding(info.getEncodingFormat())
                    .setChannelMask(info.getChannelCount())
                    .build();
            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
            for (int contextNumber : group.getContextsForBus(busNumber)) {
                hasContext = true;
                int[] usages = getUsagesForContext(contextNumber);
                for (int usage : usages) {
                    mixingRuleBuilder.addRule(
                            new AudioAttributes.Builder().setUsage(usage).build(),
                            AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
                }
                Log.d(CarLog.TAG_AUDIO, "Bus number: " + busNumber
                        + " contextNumber: " + contextNumber
                        + " sampleRate: " + info.getSampleRate()
                        + " channels: " + info.getChannelCount()
                        + " usages: " + Arrays.toString(usages));
            }
            if (hasContext) {
                // It's a valid case that an audio output bus is defined in
                // audio_policy_configuration and no context is assigned to it.
                // In such case, do not build a policy mix with zero rules.
                AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
                        .setFormat(mixFormat)
                        .setDevice(info.getAudioDeviceInfo())
                        .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
                        .build();
                builder.addMix(audioMix);
            }
        }
    }
           

又是各種for循環,我們一點一點分析,先看最外層的for,其中group.getBusNumbers()是什麼呢?

int[] getBusNumbers() {
        final int[] busNumbers = new int[mBusToCarAudioDeviceInfo.size()];
        for (int i = 0; i < busNumbers.length; i++) {
            busNumbers[i] = mBusToCarAudioDeviceInfo.keyAt(i);
        }
        return busNumbers;
    }
           

mBusToCarAudioDeviceInfo就是我們在CarVolumeGroup的bind的時候建構的

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

這裡不說了,想看的可參照Android10.0CarAudioZone(一),拿到busNumber後就可以得到CarAudioDeviceInfo,拿到info後又建構了一個AudioFormat。AudioFormat就不說了,接下來就是建立AudioMixingRule,然後便進入contextNumbers的一個循環,而contextNumbers是從mContextToBus根據bus取出的contextNUmbers

int[] getContextsForBus(int busNumber) {
        List<Integer> contextNumbers = new ArrayList<>();
        for (int i = 0; i < mContextToBus.size(); i++) {
            int value = mContextToBus.valueAt(i);
            if (value == busNumber) {
                contextNumbers.add(mContextToBus.keyAt(i));
            }
        }
        //list轉成了int數組
        return contextNumbers.stream().mapToInt(i -> i).toArray();
    }
           

拿到了每個device下的contextNumbers數組後,在根據每個contextNumber取了一個usage的數組,即getUsagesForContext(contextNumber)

private int[] getUsagesForContext(int contextNumber) {
        final List<Integer> usages = new ArrayList<>();
        for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
            if (CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
                usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
            }
        }
        return usages.stream().mapToInt(i -> i).toArray();
    }
           

我們看下USAGE_TO_CONTEXT中usage和contextNumber的對應關系如下:

static {
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
                ContextNumber.CALL);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
                ContextNumber.NOTIFICATION);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
                ContextNumber.NOTIFICATION);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
                ContextNumber.NOTIFICATION);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
                ContextNumber.VOICE_COMMAND);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
                ContextNumber.NAVIGATION);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
                ContextNumber.SYSTEM_SOUND);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
    }
           

拿到usage後便 mixingRuleBuilder.addRule

public Builder addRule(AudioAttributes attrToMatch, int rule)
                throws IllegalArgumentException {
            if (!isValidAttributesSystemApiRule(rule)) {
                throw new IllegalArgumentException("Illegal rule value " + rule);
            }
            return checkAddRuleObjInternal(rule, attrToMatch);
        }
           

先看下這個isValidAttributesSystemApiRule,我們傳入的是 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE

private static boolean isValidAttributesSystemApiRule(int rule) {
        // API rules only expose the RULE_MATCH_* rules
        switch (rule) {
            case RULE_MATCH_ATTRIBUTE_USAGE:
            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                return true;
            default:
                return false;
        }
    }
           

,即return checkAddRuleObjInternal。我們繼續看下checkAddRuleObjInternal

private Builder checkAddRuleObjInternal(int rule, Object property)
                throws IllegalArgumentException {
            if (property == null) {
                throw new IllegalArgumentException("Illegal null argument for mixing rule");
            }
            if (!isValidRule(rule)) {
                throw new IllegalArgumentException("Illegal rule value " + rule);
            }
            final int match_rule = rule & ~RULE_EXCLUSION_MASK;
            if (isAudioAttributeRule(match_rule)) {
                if (!(property instanceof AudioAttributes)) {
                    throw new IllegalArgumentException("Invalid AudioAttributes argument");
                }
                return addRuleInternal((AudioAttributes) property, null, rule);
            } else {
                // implies integer match rule
                if (!(property instanceof Integer)) {
                    throw new IllegalArgumentException("Invalid Integer argument");
                }
                return addRuleInternal(null, (Integer) property, rule);
            }
        }
           

又是一個isValidRule的判斷,這裡傳回時true,

private static boolean isValidRule(int rule) {
        final int match_rule = rule & ~RULE_EXCLUSION_MASK;
        switch (match_rule) {
            case RULE_MATCH_ATTRIBUTE_USAGE:
            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
            case RULE_MATCH_UID:
                return true;
            default:
                return false;
        }
    }
           

繼續又做了一個isAudioAttributeRule判斷

private static boolean isAudioAttributeRule(int match_rule) {
        switch(match_rule) {
            case RULE_MATCH_ATTRIBUTE_USAGE:
            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                return true;
            default:
                return false;
        }
    }
           

還是return true,進入 addRuleInternal((AudioAttributes) property, null, rule);這個方法

private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
                throws IllegalArgumentException {
            // as rules are added to the Builder, we verify they are consistent with the type
            // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
            //mTargetMixType 預設是MIX_TYPE_INVALID
            if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
            //又判斷這個,此處為true
                if (isPlayerRule(rule)) {
                    mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
                } else {
                    mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
                }
            } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
                    || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
            {
                throw new IllegalArgumentException("Incompatible rule for mix");
            }
            synchronized (mCriteria) {
                Iterator<AudioMixMatchCriterion> crIterator = mCriteria.iterator();
                final int match_rule = rule & ~RULE_EXCLUSION_MASK;
                //第一調用crIterator 的size是0,是以不走while
                while (crIterator.hasNext()) {
                    final AudioMixMatchCriterion criterion = crIterator.next();

                    if ((criterion.mRule & ~RULE_EXCLUSION_MASK) != match_rule) {
                        continue; // The two rules are not of the same type
                    }
                    switch (match_rule) {
                        case RULE_MATCH_ATTRIBUTE_USAGE:
                            // "usage"-based rule
                            if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
                                if (criterion.mRule == rule) {
                                    // rule already exists, we're done
                                    return this;
                                } else {
                                    // criterion already exists with a another rule,
                                    // it is incompatible
                                    throw new IllegalArgumentException("Contradictory rule exists"
                                            + " for " + attrToMatch);
                                }
                            }
                            break;
                        case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                            // "capture preset"-base rule
                            if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
                                if (criterion.mRule == rule) {
                                    // rule already exists, we're done
                                    return this;
                                } else {
                                    // criterion already exists with a another rule,
                                    // it is incompatible
                                    throw new IllegalArgumentException("Contradictory rule exists"
                                            + " for " + attrToMatch);
                                }
                            }
                            break;
                        case RULE_MATCH_UID:
                            // "usage"-based rule
                            if (criterion.mIntProp == intProp.intValue()) {
                                if (criterion.mRule == rule) {
                                    // rule already exists, we're done
                                    return this;
                                } else {
                                    // criterion already exists with a another rule,
                                    // it is incompatible
                                    throw new IllegalArgumentException("Contradictory rule exists"
                                            + " for UID " + intProp);
                                }
                            }
                            break;
                    }
                }
                // rule didn't exist, add it
                switch (match_rule) {
                    case RULE_MATCH_ATTRIBUTE_USAGE:
                    case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
                      //将我們傳入的usage組成的audioAttribute和rule存入mCriteria中。
                        mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
                        break;
                    case RULE_MATCH_UID:
                        mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
                        break;
                    default:
                        throw new IllegalStateException("Unreachable code in addRuleInternal()");
                }
            }
            return this;
        }
           

到此 addRule的過程就結束了,我們簡單總結一下**,首先通過傳入的mCarAudioZones周遊其中的每個CarAudioZone,每個CarAudioZone又包含一個CarVolumeGroup集合,周遊CarVolumeGroup裡的每個group,每個group又包含一個devices的集合(CarAudioDeviceInfo的map,這個map的key是busNumber),每個device又包含一個context的集合(contextNumber和busNumber組成的map),說的有點亂,但我們一定要把這些關系捋清楚。這樣層層循環後我們拿到了contextNumber後通過map拿到usage數組,歸根揭底就是把Audio Attribute的usage數組和context以及CarAudioDeviceInfo都關聯上**。最終的關聯我們又回到CarAudioDynamicRouting的 setupAudioDynamicRoutingForGroup中

if (hasContext) {
                // It's a valid case that an audio output bus is defined in
                // audio_policy_configuration and no context is assigned to it.
                // In such case, do not build a policy mix with zero rules.
                AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
                        .setFormat(mixFormat)
                        .setDevice(info.getAudioDeviceInfo())
                        .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
                        .build();
                builder.addMix(audioMix);
            }
           

AudioMix 包含了有usage數組的mixingRuleBuilder和AudioDeviceInfo,這樣device與usage數組便對應到了一起,我們知道原生的Android是通過stream在AudioPolicyManager的Engine中選擇的device,而在Car的這套邏輯中通過usage和device在上層(java)就配置好了。這裡的builder就是audioPolicy,addMix是簡單的将audioMix傳遞到了audiopolicy中

public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
            if (mix == null) {
                throw new IllegalArgumentException("Illegal null AudioMix argument");
            }
            mMixes.add(mix);
            return this;
        }
           

mMixes即

ArrayList<AudioMix> mMixes
           

我們繼續看下,就又回到了CarAudioService的setupDynamicRouting中

開始了audiopolicy的bulid

public AudioPolicy build() {
            if (mStatusListener != null) {
                // the AudioPolicy status listener includes updates on each mix activity state
                for (AudioMix mix : mMixes) {
                    mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY;
                }
            }
            if (mIsFocusPolicy && mFocusListener == null) {
                throw new IllegalStateException("Cannot be a focus policy without "
                        + "an AudioPolicyFocusListener");
            }
            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
                    mFocusListener, mStatusListener, mIsFocusPolicy, mIsTestFocusPolicy,
                    mVolCb, mProjection);
        }
    }
           

建立了一個AudioPolicyConfig然後用來建構AudioPolicy,這裡也是簡單看下AudioPolicyConfig的構造函數吧

AudioPolicyConfig(ArrayList<AudioMix> mixes) {
        mMixes = mixes;
    }
           

簡單過了,再看下AudioPolicy的構造函數

private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper,
            AudioPolicyFocusListener fl, AudioPolicyStatusListener sl,
            boolean isFocusPolicy, boolean isTestFocusPolicy,
            AudioPolicyVolumeCallback vc, @Nullable MediaProjection projection) {
        mConfig = config;
        mStatus = POLICY_STATUS_UNREGISTERED;
        mContext = context;
        if (looper == null) {
            looper = Looper.getMainLooper();
        }
        if (looper != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
            Log.e(TAG, "No event handler due to looper without a thread");
        }
        mFocusListener = fl;
        mStatusListener = sl;
        mIsFocusPolicy = isFocusPolicy;
        mIsTestFocusPolicy = isTestFocusPolicy;
        mVolCb = vc;
        mProjection = projection;
    }
           

也是一些指派,具體看下,**其中mConfig是我們剛才new的,mFocusListener 是mFocusHandler即我們之前new的 CarZonesAudioFocus,mStatusListener這裡為null,mIsFocusPolicy我們之前設定為了true,mIsTestFocusPolicy是false,mVolCb 即mAudioPolicyVolumeCallback,mProjection這裡也為null。**到這裡我們AudioPolicy也build結束了。剩下來的就是

将AudioPolicy注冊下去了,我們下篇在講

總結

在CarAudioService的初始化過程中,通過car_audio_configuration.xml做了volume 、audiofocus以及device的多分區的配置,如何讓我們的應用對應到不同的分區上,主要是通過uid處理的(這塊邏輯後面說)。今天我們主要分析的是devic的動态路由,通過周遊mCarAudioZones中的每個CarAudioZone,然後再周遊每個CarAudioZone中的CarVolumeGroup集合,周遊CarVolumeGroup裡的每個group,再周遊每個group中的devices的集合(CarAudioDeviceInfo的map,這個map的key是busNumber),最後周遊device中的一個context的集合(contextNumber和busNumber組成的map),把所有conrextNumber對應的usage全部拿出來放到usage數組中。最後把這個usage數組以及他對應的外層的deviceInfo一起存入AudioMix中,再被add到AudioPolicy中,這塊歸根揭底就是把Audio Attribute的usage數組和CarAudioDeviceInfo關聯上。這樣我們最終通過mAudioManager.registerAudioPolicy(mAudioPolicy)注冊下去。

如有問題,歡迎大家随時溝通~