Android autoMotive 多区音频-路由策略

1. 说明:

  1. 本文只针对audiopolicy选择设备的逻辑,旨在搞清楚一件事情,audiopolicy怎么能选到前区或后区对应的设备,音量以及audfocus相关逻辑暂不涉及
  2. 网上有一些介绍分区音频初始化的文章,这里不再重新分析,只列出设备路由的关键点。
  3. 可以先看官方介绍: ​​​​​​​https://source.android.com/devices/automotive/audio?hl=zh-cn#multi-zone

2. 原理

先说下AudioPolicyservice选择设备的逻辑,再描述carAudioSevice怎么利用这个机制实现前后区APP选择不同的播放设备

一般而言,设备选择由audiopolicyservice自动完成,不需要app干预,经典路由逻辑如下:

streamtype->strategy->devices->output

后面streamtype不够用,android引入attribute, 路由逻辑变成

attribute->stratey->devices->output

这个也是手机上最常见的设备路由逻辑。 这部分逻辑是固定的,开机后不能修改,后面Android为了应对汽车,AudioplayCapture等使用场景增加了动态audiopolicy。 用户可以动态register路由策略,并且优先级高于经典路由策略。

2.1 动态路由介绍

比较关键的几个API:

AudioPolicy:  动态audiopolicy, 包括路由,audiofocus,音量等相关处理逻辑
AudioMix:  	路策略由,包含多个路由规则和目标设备,如果是播放场景,代表符合路由规则的AudioTrack应该路由到目标设备
AudioMixingRule:路由规则,具体规则如下:

frameworks/base/media/java/android/media/audiopolicy/AudioMixingRule.java

    public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
 
    public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
  
    public static final int RULE_MATCH_UID = 0x1 << 2;
   
    public static final int RULE_MATCH_USERID = 0x1 << 3;

    private final static int RULE_EXCLUSION_MASK = 0x8000;
   
    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
 
    public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;

    public static final int RULE_EXCLUDE_UID =
            RULE_EXCLUSION_MASK | RULE_MATCH_UID;

    public static final int RULE_EXCLUDE_USERID =
            RULE_EXCLUSION_MASK | RULE_MATCH_USERID;

动态路由提供usage, uid, userid, capture source等匹配规则来选者设备,而这些信息在AudioTrack,或者AudioRecord的构造中都能拿到。

通过AudioPolicyMix选设备和output的逻辑如下

status_t AudioPolicyMixCollection::getOutputForAttr(
        const audio_attributes_t& attributes, uid_t uid,
        audio_output_flags_t flags,
        sp<AudioPolicyMix> &primaryMix,
        std::vector<sp<AudioPolicyMix>> *secondaryMixes)
{
    ALOGV("getOutputForAttr() querying %zu mixes:", size());
    primaryMix.clear();
    for (size_t i = 0; i < size(); i++) {
        sp<AudioPolicyMix> policyMix = itemAt(i);
        const bool primaryOutputMix = !is_mix_loopback_render(policyMix->mRouteFlags);
        if (!primaryOutputMix && (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ)) {
            // AAudio does not support MMAP_NO_IRQ loopback render, and there is no way with
            // the current MmapStreamInterface::start to reject a specific client added to a shared
            // mmap stream.
            // As a result all MMAP_NOIRQ requests have to be rejected when an loopback render
            // policy is present. That ensures no shared mmap stream is used when an loopback
            // render policy is registered.
            ALOGD("%s: Rejecting MMAP_NOIRQ request due to LOOPBACK|RENDER mix present.", __func__);
            return INVALID_OPERATION;
        }

        if (primaryOutputMix && primaryMix != nullptr) {
            ALOGV("%s: Skiping %zu: Primary output already found", __func__, i);
            continue; // Primary output already found
        }

        switch (mixMatch(policyMix.get(), i, attributes, uid)) {
            case MixMatchStatus::INVALID_MIX:
                // The mix has contradictory rules, ignore it
                // TODO: reject invalid mix at registration
                continue;
            case MixMatchStatus::NO_MATCH:
                ALOGV("%s: Mix %zu: does not match", __func__, i);
                continue; // skip the mix
            case MixMatchStatus::MATCH:;
        }

        if (primaryOutputMix) {
            primaryMix = policyMix;
            ALOGV("%s: Mix %zu: set primary desc", __func__, i);
        } else {
            ALOGV("%s: Add a secondary desc %zu", __func__, i);
            if (secondaryMixes != nullptr) {
                secondaryMixes->push_back(policyMix);
            }
        }
    }
    return NO_ERROR;
}

首先需要解释primaryOutputMix和secondaryMixes的含义, primaryOutputMix表示当前AudioPolixyMix当作主输出, secondaryMixes表示当前AudioPolicyMix当前第二个输出。

AudioPolicyMix有一个flag,指定render,loopback, loopback用于audioplaybackcatpure,比如录屏等场景,render代表播放,同时指定render和loopback表示同时播放和回环
如果只有render标记的话,或者只有loopback标记的话,只需要一个播放线程,就是AudioPolicyMix绑定的播放线程,这个线程就是PrimaryOutputMix(就他一个,他就是primary)
如果同时有render和loopback标记的话, 那么audioPolicyMix只负责loopback部分,render的部分由经典的路由策略决定,按照attribute选设备和播放线程,那么audiopolicymix绑定的播放线程就是secondaryMix。然后在updateSecondaryOutputs里把render线程的数据和loopback线程的数据链接起来。

针对分区场景,只制定了render flag,所以这里如有能通过audiotrack的attributes和uid match到合适的AudioPolicyMix的话,这里就返回primaryOutputMix

补充一点,secondaryOutputs的机制跟duplicate实现的作用类似,但是duplicate在播放时有三个播放线程,
而secondaryOutput机制只需要两个线程,连外,duplicate机制在播放时调节音量会同时影响后面两个播放线程的音量大小。 而secondaryoutput这种方式的话,可以让另一个播放线程的音量fixed保持不变。这块的机制如果感兴趣可以后面单位介绍。

2.2 automotive多区音频

在汽车领域,围绕多个用户同时与平台互动并且每个用户都希望使用单独媒体的需求,出现了一系列新的用例。例如,后座上的乘客在后座显示屏上观看 YouTube 视频时,司机可以在驾驶舱中播放音乐。多区音频通过允许不同的音频源在车辆的不同音频区同时进行播放来实现此目的。下面是个汽车多媒体软件框架示意图:
在这里插入图片描述

汽车上定义了carAudioContext, 根据context路由到不同的播放device,cardAudioContext跟audiotrack中Attribute usages是对应的,可以从carAudioContext转换到AttributeUsages,下面是对应的关系表:

CarAudioContext	          			关联的 AttributeUsages
  	MUSIC	              			UNKNOWN, GAME, MEDIA
	NAVIGATION           			ASSISTANCE_NAVIGATION_GUIDANCE
	VOICE_COMMAND	   				ASSISTANT, ASSISTANCE_ACCESSIBILITY
	CALL_RING	               		NOTIFICATION_RINGTONE
	CALL                    		VOICE_COMMUNICATION, VOICE_COMMUNICATION_SIGNALING
	ALARM	                        ALARM
	NOTIFICATION	               	NOTIFICATION, NOTIFICATION_*
	SYSTEM_SOUND	             	ASSISTANCE_SONIFICATION
	EMERGENCY                    	EMERGENCY
	SAFETY	                       	SAFETY
	VEHICLE_STATUS	         		VEHICLE_STATUS
	ANNOUNCEMENT                  	ANNOUNCEMENT

通过car_audio_configuration.xml来定义context到device的路由规则

<audioZoneConfiguration version="2.0">
       <zone name="primary zone" isPrimary="true">
           <volumeGroups>
               <group>
                   <device address="bus0_media_out">
                       <context context="music"/>
                       <context context="announcement"/>
                   </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"/>
                       <context context="emergency"/>
                       <context context="safety"/>
                       <context context="vehicle_status"/>
                   </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>
       </zone>
        <zone name="rear seat zone" audioZoneId="1">
           <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"/>
                       <context context="emergency"/>
                       <context context="safety"/>
                       <context context="vehicle_status"/>
                       <context context="announcement"/>
                   </device>
               </group>
           </volumeGroups>
    </zones>
</audioZoneConfiguration>

这个配置文件定义了两个zones, 每个zones里分别定义context选择设备的逻辑

2.3 实现

CarAudioservice会解析car_audio_configuration文件,生成AudioPolicy注册到audipolicyservice:

  /**
     * Enumerates all physical buses in a given volume group and attach the mixing rules.
     * @param group {@link CarVolumeGroup} instance to enumerate the buses with
     * @param builder {@link AudioPolicy.Builder} to attach the mixing rules
     */
    private static void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
            AudioPolicy.Builder builder) {
        // Note that one can not register audio mix for same bus more than once.
        for (String address : group.getAddresses()) {
            boolean hasContext = false;
            CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForAddress(address);
            AudioFormat mixFormat = new AudioFormat.Builder()
                    .setSampleRate(info.getSampleRate())
                    .setEncoding(info.getEncodingFormat())
                    .setChannelMask(info.getChannelCount())
                    .build();
            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
            for (int carAudioContext : group.getContextsForAddress(address)) {
                hasContext = true;
                int[] usages = CarAudioContext.getUsagesForContext(carAudioContext);
                for (int usage : usages) {
                    AudioAttributes attributes = buildAttributesWithUsage(usage);
                    mixingRuleBuilder.addRule(attributes,
                            AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
                }
                if (Log.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
                    Slog.d(CarLog.TAG_AUDIO, String.format(
                            "Address: %s AudioContext: %s sampleRate: %d channels: %d usages: %s",
                            address, carAudioContext, info.getSampleRate(), info.getChannelCount(),
                            Arrays.toString(usages)));
                }
            }
            if (hasContext) {
                // It's a valid case that an audio output address 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);
            }
        }
    }

这里将carAudioContex转到到usage,然后生成了一个AudioMix, AudioMix添加了RULE_MATCH_ATTRIBUTE_USAGE这一个规则,注意AudioMix中没有任何zone的信息。可以看到"music" 这个context会同时关联到bus0_media_out和bus100_rear_seat。在实际播放时应该先定位APP属于那个zone, 然后根据zone里的路由策略来选择设备,这一步是在那里完成的呢?

在 Android 10 中,向CarAudioManager 引入了一系列隐藏 API,使应用可以查询并设置音频可用区和焦点。

int[] getAudioZoneIds();
int getZoneIdForUid(int uid);
boolean setZoneIdForUid(int zoneId, int uid);
boolean clearZoneIdForUid(int uid);
int getZoneIdForDisplay(Display display);

其中 setZoneIdForUid(int zoneId, int uid)用于为应用设置zoneID,我看看下它的实现,同样我们只关注设备选择这部分逻辑

   public boolean setZoneIdForUid(int zoneId, int uid) {
        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
        requireDynamicRouting();
        synchronized (mImplLock) {
            checkAudioZoneIdLocked(zoneId);
            Slog.i(CarLog.TAG_AUDIO, "setZoneIdForUid Calling uid "
                    + uid + " mapped to : "
                    + zoneId);

            // If occupant mapping exist uid routing can not be used
            requiredOccupantZoneMappingDisabledLocked();

            // Figure out if anything is currently holding focus,
            // This will change the focus to transient loss while we are switching zones
            Integer currentZoneId = mUidToZoneMap.get(uid);
            ArrayList<AudioFocusInfo> currentFocusHoldersForUid = new ArrayList<>();
            ArrayList<AudioFocusInfo> currentFocusLosersForUid = new ArrayList<>();
            if (currentZoneId != null) {
                currentFocusHoldersForUid = mFocusHandler.getAudioFocusHoldersForUid(uid,
                        currentZoneId.intValue());
                currentFocusLosersForUid = mFocusHandler.getAudioFocusLosersForUid(uid,
                        currentZoneId.intValue());
                if (!currentFocusHoldersForUid.isEmpty() || !currentFocusLosersForUid.isEmpty()) {
                    // Order matters here: Remove the focus losers first
                    // then do the current holder to prevent loser from popping up while
                    // the focus is being remove for current holders
                    // Remove focus for current focus losers
                    mFocusHandler.transientlyLoseInFocusInZone(currentFocusLosersForUid,
                            currentZoneId.intValue());
                    // Remove focus for current holders
                    mFocusHandler.transientlyLoseInFocusInZone(currentFocusHoldersForUid,
                            currentZoneId.intValue());
                }
            }

            // if the current uid is in the list
            // remove it from the list

            if (checkAndRemoveUidLocked(uid)) {
                if (setZoneIdForUidNoCheckLocked(zoneId, uid)) {
                    // Order matters here: Regain focus for
                    // Previously lost focus holders then regain
                    // focus for holders that had it last
                    // Regain focus for the focus losers from previous zone
                    if (!currentFocusLosersForUid.isEmpty()) {
                        regainAudioFocusLocked(currentFocusLosersForUid, zoneId);
                    }
                    // Regain focus for the focus holders from previous zone
                    if (!currentFocusHoldersForUid.isEmpty()) {
                        regainAudioFocusLocked(currentFocusHoldersForUid, zoneId);
                    }
                    return true;
                }
            }
            return false;
        }
    }

    private boolean setZoneIdForUidNoCheckLocked(int zoneId, int uid) {
        Slog.d(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Calling uid "
                + uid + " mapped to " + zoneId);
        //Request to add uid device affinity
        List<AudioDeviceInfo> deviceInfos = getCarAudioZoneLocked(zoneId).getAudioDeviceInfos();
        if (mAudioPolicy.setUidDeviceAffinity(uid, deviceInfos)) {
            // TODO do not store uid mapping here instead use the uid
            //  device affinity in audio policy when available
            mUidToZoneMap.put(uid, zoneId);
            return true;
        }
        Slog.w(CarLog.TAG_AUDIO, "setZoneIdForUidNoCheck Failed set device affinity for uid "
                + uid + " in zone " + zoneId);
        return false;
    }

最终调到底层AudioPolicyMixCollection::setUidDeviceAffinities,这个函数的输入为uid, devices, uid表示对应的app,devices表示app偏好的devices, 注意这里只是偏好,因为传下来的是zones支持的所有设备。

status_t AudioPolicyMixCollection::setUidDeviceAffinities(uid_t uid,
        const AudioDeviceTypeAddrVector& devices) {
    // verify feasibility: for each player mix: if it already contains a
    //    "match uid" rule for this uid, return an error
    //    (adding a uid-device affinity would result in contradictory rules)
    for (size_t i = 0; i < size(); i++) {
        const AudioPolicyMix* mix = itemAt(i).get();
        if (!mix->isDeviceAffinityCompatible()) {
            continue;
        }
        if (mix->hasUidRule(true /*match*/, uid)) {
            return INVALID_OPERATION;
        }
    }

    // remove existing rules for this uid
    removeUidDeviceAffinities(uid);

    // for each player mix:
    //   IF    device is not a target for the mix,
    //     AND it doesn't have a "match uid" rule
    //   THEN add a rule to exclude the uid
    for (size_t i = 0; i < size(); i++) {
        const AudioPolicyMix *mix = itemAt(i).get();
        if (!mix->isDeviceAffinityCompatible()) {
            continue;
        }
        // check if this mix goes to a device in the list of devices
        bool deviceMatch = false;
        const AudioDeviceTypeAddr mixDevice(mix->mDeviceType, mix->mDeviceAddress.string());
        for (size_t j = 0; j < devices.size(); j++) {
            if (mixDevice.equals(devices[j])) {
                deviceMatch = true;
                break;
            }
        }
        if (!deviceMatch && !mix->hasMatchUidRule()) {
            // this mix doesn't go to one of the listed devices for the given uid,
            // and it's not already restricting the mix on a uid,
            // modify its rules to exclude the uid
            if (!mix->hasUidRule(false /*match*/, uid)) {
                // no need to do it again if uid is already excluded
                mix->setExcludeUid(uid);
            }
        }
    }

    return NO_ERROR;
}

假设这个uid对应后区的APP, 则devices为"bus100_rear_seat“。
到这里做一个遍历,把所有的mix取出来,如果有Mix中device与输入的device能匹配上则跳过, 对于后区的AudioPoliyMix, 应为它对应的设备为“bus100_rear_seat”,与输入是匹配的,devicematch=true, 跳过这个AudioPolicyMix, 对于前区的AudioPolicyMix, 匹配不到输入的device,对其调用mix->setExcludeUid,代表这个AudioPolicyMix要排除掉当前UID

这个函数的本意是对后区的AudioPolicyMix添加UID match规则,实现却是对前区的audiopolicyMix添加了排除规则, 绕了一圈。为什么要这么绕一圈呢?

这跟AudioPolicyMix 的mixMatch规则有关,前面提到过,一个AudioPolicyMix可以包含多个rules, 只要有一个规则能match上就能匹配成功。在前后区都有RULE_MATCH_ATTRIBUTE_USAGE规则的情况下,给后区添加一个RULE_MATCH_UID规则,还是区分不了从前区还是从后区播放。 但是给前区添加一个exlude规则就可以区分了

AudioPolicyMixCollection::MixMatchStatus AudioPolicyMixCollection::mixMatch(
        const AudioMix* mix, size_t mixIndex, const audio_attributes_t& attributes, uid_t uid) {

    if (mix->mMixType == MIX_TYPE_PLAYERS) {
        // Loopback render mixes are created from a public API and thus restricted
        // to non sensible audio that have not opted out.
        if (is_mix_loopback_render(mix->mRouteFlags)) {
            if (!(attributes.usage == AUDIO_USAGE_UNKNOWN ||
                  attributes.usage == AUDIO_USAGE_MEDIA ||
                  attributes.usage == AUDIO_USAGE_GAME ||
                  attributes.usage == AUDIO_USAGE_VOICE_COMMUNICATION)) {
                return MixMatchStatus::NO_MATCH;
            }
            auto hasFlag = [](auto flags, auto flag) { return (flags & flag) == flag; };
            if (hasFlag(attributes.flags, AUDIO_FLAG_NO_SYSTEM_CAPTURE)) {
                return MixMatchStatus::NO_MATCH;
            }

            if (attributes.usage == AUDIO_USAGE_VOICE_COMMUNICATION) {
                if (!mix->mVoiceCommunicationCaptureAllowed) {
                    return MixMatchStatus::NO_MATCH;
                }
            } else if (!mix->mAllowPrivilegedMediaPlaybackCapture &&
                hasFlag(attributes.flags, AUDIO_FLAG_NO_MEDIA_PROJECTION)) {
                return MixMatchStatus::NO_MATCH;
            }
        }

        int userId = (int) multiuser_get_user_id(uid);

        // TODO if adding more player rules (currently only 2), make rule handling "generic"
        //      as there is no difference in the treatment of usage- or uid-based rules
        bool hasUsageMatchRules = false;
        bool hasUsageExcludeRules = false;
        bool usageMatchFound = false;
        bool usageExclusionFound = false;

        bool hasUidMatchRules = false;
        bool hasUidExcludeRules = false;
        bool uidMatchFound = false;
        bool uidExclusionFound = false;

        bool hasUserIdExcludeRules = false;
        bool userIdExclusionFound = false;
        bool hasUserIdMatchRules = false;
        bool userIdMatchFound = false;


        bool hasAddrMatch = false;

        // iterate over all mix criteria to list what rules this mix contains
        for (size_t j = 0; j < mix->mCriteria.size(); j++) {
            ALOGV(" getOutputForAttr: mix %zu: inspecting mix criteria %zu of %zu",
                    mixIndex, j, mix->mCriteria.size());

            // if there is an address match, prioritize that match
            if (strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 &&
                    strncmp(attributes.tags + strlen("addr="),
                            mix->mDeviceAddress.string(),
                            AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) {
                hasAddrMatch = true;
                break;
            }

            switch (mix->mCriteria[j].mRule) {
            case RULE_MATCH_ATTRIBUTE_USAGE:
                ALOGV("\tmix has RULE_MATCH_ATTRIBUTE_USAGE for usage %d",
                                            mix->mCriteria[j].mValue.mUsage);
                hasUsageMatchRules = true;
                if (mix->mCriteria[j].mValue.mUsage == attributes.usage) {
                    // found one match against all allowed usages
                    usageMatchFound = true;
                }
                break;
            case RULE_EXCLUDE_ATTRIBUTE_USAGE:
                ALOGV("\tmix has RULE_EXCLUDE_ATTRIBUTE_USAGE for usage %d",
                        mix->mCriteria[j].mValue.mUsage);
                hasUsageExcludeRules = true;
                if (mix->mCriteria[j].mValue.mUsage == attributes.usage) {
                    // found this usage is to be excluded
                    usageExclusionFound = true;
                }
                break;
            case RULE_MATCH_UID:
                ALOGV("\tmix has RULE_MATCH_UID for uid %d", mix->mCriteria[j].mValue.mUid);
                hasUidMatchRules = true;
                if (mix->mCriteria[j].mValue.mUid == uid) {
                    // found one UID match against all allowed UIDs
                    uidMatchFound = true;
                }
                break;
            case RULE_EXCLUDE_UID:
                ALOGV("\tmix has RULE_EXCLUDE_UID for uid %d", mix->mCriteria[j].mValue.mUid);
                hasUidExcludeRules = true;
                if (mix->mCriteria[j].mValue.mUid == uid) {
                    // found this UID is to be excluded
                    uidExclusionFound = true;
                }
                break;
            case RULE_MATCH_USERID:
                ALOGV("\tmix has RULE_MATCH_USERID for userId %d",
                    mix->mCriteria[j].mValue.mUserId);
                hasUserIdMatchRules = true;
                if (mix->mCriteria[j].mValue.mUserId == userId) {
                    // found one userId match against all allowed userIds
                    userIdMatchFound = true;
                }
                break;
            case RULE_EXCLUDE_USERID:
                ALOGV("\tmix has RULE_EXCLUDE_USERID for userId %d",
                    mix->mCriteria[j].mValue.mUserId);
                hasUserIdExcludeRules = true;
                if (mix->mCriteria[j].mValue.mUserId == userId) {
                    // found this userId is to be excluded
                    userIdExclusionFound = true;
                }
                break;
            default:
                break;
            }

            // consistency checks: for each "dimension" of rules (usage, uid...), we can
            // only have MATCH rules, or EXCLUDE rules in each dimension, not a combination
            if (hasUsageMatchRules && hasUsageExcludeRules) {
                ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_ATTRIBUTE_USAGE"
                        " and RULE_EXCLUDE_ATTRIBUTE_USAGE in mix %zu", mixIndex);
                return MixMatchStatus::INVALID_MIX;
            }
            if (hasUidMatchRules && hasUidExcludeRules) {
                ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_UID"
                        " and RULE_EXCLUDE_UID in mix %zu", mixIndex);
                return MixMatchStatus::INVALID_MIX;
            }
            if (hasUserIdMatchRules && hasUserIdExcludeRules) {
                ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_USERID"
                        " and RULE_EXCLUDE_USERID in mix %zu", mixIndex);
                    return MixMatchStatus::INVALID_MIX;
            }

            if ((hasUsageExcludeRules && usageExclusionFound)
                    || (hasUidExcludeRules && uidExclusionFound)
                    || (hasUserIdExcludeRules && userIdExclusionFound)) {
                break; // stop iterating on criteria because an exclusion was found (will fail)
            }
        }//iterate on mix criteria

        // determine if exiting on success (or implicit failure as desc is 0)
        if (hasAddrMatch ||
                !((hasUsageExcludeRules && usageExclusionFound) ||
                  (hasUsageMatchRules && !usageMatchFound)  ||
                  (hasUidExcludeRules && uidExclusionFound) ||
                  (hasUidMatchRules && !uidMatchFound) ||
                  (hasUserIdExcludeRules && userIdExclusionFound) ||
                  (hasUserIdMatchRules && !userIdMatchFound))) {
            ALOGV("\tgetOutputForAttr will use mix %zu", mixIndex);
            return MixMatchStatus::MATCH;
        }

    } else if (mix->mMixType == MIX_TYPE_RECORDERS) {
        if (attributes.usage == AUDIO_USAGE_VIRTUAL_SOURCE &&
                strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 &&
                strncmp(attributes.tags + strlen("addr="),
                        mix->mDeviceAddress.string(),
                        AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) {
            return MixMatchStatus::MATCH;
        }
    }
    return MixMatchStatus::NO_MATCH;
}

关键就在这一段:

   if (hasAddrMatch ||
                !((hasUsageExcludeRules && usageExclusionFound) ||
                  (hasUsageMatchRules && !usageMatchFound)  ||
                  (hasUidExcludeRules && uidExclusionFound) ||
                  (hasUidMatchRules && !uidMatchFound) ||
                  (hasUserIdExcludeRules && userIdExclusionFound) ||
                  (hasUserIdMatchRules && !userIdMatchFound))) {
            ALOGV("\tgetOutputForAttr will use mix %zu", mixIndex);
            return MixMatchStatus::MATCH;

hasAddrMatch 对应audioPlaybackCaputre的场景,对车载这里都是false. 对前区因为添加了uid exlude规则,所以 (hasUidExcludeRules && uidExclusionFound) =1, 整个表达式为0, 返回的是NO_MATCH, 对后区hasUidMatchRules =1,usageMatchFound=1,(hasUsageMatchRules && !usageMatchFound) =0, 其他值都=0, 所以整个表达式=1, 返回MATCH。

3总结

CarAudioService通过context和UID, 利用AudioPolicyService的dynamic audiopolicy机制来定于路由规则,可以让前后zone的APP,以及同一个zone的不同context的播放选到不同的设备。

另外:
因为我自己在看代码时,被UID exlude这部分逻辑绕了一下,所有重点介绍了这部分,省略了AudioPolicyManager::registerPolicyMixes以及AudioPolicyMixCollection::getOutputForAttr相关的逻辑,这两部分逻辑也比较绕,有时间再介绍下

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值