上一篇文章介绍了音频分区的相关 API 接口,这里我们来看一下 CarAudioManager 中对应接口的实现。通过前面的文章我们知道,对应 CarAudioManager 中接口的实现都是在 CarAudioService 完成的。
一、获取分区列表
1、CarAudioService
源码位置:/packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
getAudioZoneIds
private CarAudioZone[] mCarAudioZones;
@Override
public @NonNull int[] getAudioZoneIds() {
// 权限检查
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
// 动态路由检查
requireDynamicRouting();
synchronized (mImplLock) {
// 获取音频区域ID
return Arrays.stream(mCarAudioZones).mapToInt(CarAudioZone::getId).toArray();
}
}
该方法返回一个整型数组,其中包含了所有可用音频区域的ID。这些 ID 可用于标识不同的音频输出区域,例如前排、后排等。这里主要通过遍历 mCarAudioZones 集合,将每个 CarAudioZone 对象的 ID 提取出来,并转换为整型数组。而 mCarAudioZones 正是前面通过解析 xml 文件得到的音频分区数组。
二、用户关联分区
1、getZoneIdForUid
@Override
public int getZoneIdForUid(int uid) {
// 权限检查
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
// 确保动态路由功能被启用
requireDynamicRouting();
synchronized (mImplLock) {
// 从mUidToZoneMap缓存中获取zoneId
if (mUidToZoneMap.containsKey(uid)) {
return mUidToZoneMap.get(uid);
}
// 获取用户ID
int userId = UserHandle.getUserId(uid);
// 根据用户ID获取zoneId
return getZoneIdForUserIdLocked(userId);
}
}
该方法用于获取与特定用户标识 (uid) 关联的音频区域标识 (zoneId),这里的 mUidToZoneMap 正是前面 setZoneIdForUid() 函数设置用户关系是用来存储 uid 与 zoneId 映射关系的数组。
2、clearZoneIdForUid
@Override
public boolean clearZoneIdForUid(int uid) {
// 权限检查
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
// 确保动态路由功能被启用
requireDynamicRouting();
synchronized (mImplLock) {
// 确保占用者区域映射功能被禁用
requiredOccupantZoneMappingDisabledLocked();
// 检查并移除与 uid 关联的 zoneId
return checkAndRemoveUidLocked(uid);
}
}
该方法用于清除与特定用户标识 (uid) 关联的音频区域标识 (zoneId)。
checkAndRemoveUidLocked
private AudioPolicy mAudioPolicy;
private boolean checkAndRemoveUidLocked(int uid) {
Integer zoneId = mUidToZoneMap.get(uid);
if (zoneId != null) {
if (mAudioPolicy.removeUidDeviceAffinity(uid)) {
// 当音频策略可用时,使用uid设备亲和性
mUidToZoneMap.remove(uid);
return true;
}
// 移除失败
return false;
}
return true;
}
这里就与设置流程相似了,通过 AudioPolicy 中的 removeUidDeviceAffinity 函数移除设备亲和性,最终移除音频混合策略中的对应规则,这里就不做详细分析了。
三、音量设置相关
1、setGroupVolume
@Override
public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
synchronized (mImplLock) {
// 权限检查
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
// 回调音量变化
callbackGroupVolumeChange(zoneId, groupId, flags);
// 用于基于传统流类型的音量控制
if (!mUseDynamicRouting) {
mAudioManager.setStreamVolume(CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
return;
}
// 动态路由音量控制(音量增益)
CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
group.setCurrentGainIndex(index);
}
}
这里是不是比较熟悉,前面对传统流类型的音量控制和动态路由音量控制都进行了详细分析,对于车载音频分区功能来说肯定是使用了动态路由,所以不会走传统流类型音量控制的分支。通过 getCarVolumeGroup() 方法获取 CarVolumeGroup 对象,然后通过 setCurrentGainIndex() 方法设置特定分组的音量增益索引。
getCarVolumeGroup
private CarVolumeGroup getCarVolumeGroup(int zoneId, int groupId) {
Objects.requireNonNull(mCarAudioZones);
// 参数范围检查
Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1, "zoneId out of range: " + zoneId);
return mCarAudioZones[zoneId].getVolumeGroup(groupId);
}
- mCarAudioZones:存储了每个音频区域 (zoneId) 对应的 CarAudioZone 对象,及前面 xml 文件解析的内容。
- CarAudioZone:代表特定音频区域的音频控制。
该方法用于获取特定音频区域 (zoneId) 和特定分组 (groupId) 的 CarVolumeGroup 对象,该对象代表特定分组的音量控制。对于音量的设置和获取这里就不做过多的介绍了,详细内容可以去看前面的文章。
2、getVolumeGroupCount
@Override
public int getVolumeGroupCount(int zoneId) {
synchronized (mImplLock) {
……
return mCarAudioZones[zoneId].getVolumeGroupCount();
}
}
对于获取可用音量分组数量的接口也比较简单,就是返回 mCarAudioZones 的大小。
四、音量分组相关
1、getVolumeGroupIdForUsage
@Override
public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
// 对于传统流类型音量控制获取音量分组ID
if (!mUseDynamicRouting) {
for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPE_USAGES.length; i++) {
if (usage == CarAudioDynamicRouting.STREAM_TYPE_USAGES[i]) {
return i;
}
}
return INVALID_VOLUME_GROUP_ID;
}
// 确保 zoneId 在有效范围内
Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1, "zoneId out of range: " + zoneId);
// 根据usage获取对应的音频上下文
@AudioContext int audioContext = CarAudioContext.getContextForUsage(usage);
// 通过上下文获取对应的音量分组ID
return getVolumeGroupIdForAudioContextLocked(zoneId, audioContext);
}
}
该方法就是获取特定音频区域 (zoneId) 和特定用途 (usage) 对应的音量分组 (volume group) 的 ID。 CarAudioContext 负责管理 group 和 usage 的对应关系。
getVolumeGroupIdForAudioContextLocked
private int getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext) {
CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
for (int i = 0; i < groups.length; i++) {
int[] groupAudioContexts = groups[i].getContexts();
for (int groupAudioContext : groupAudioContexts) {
if (audioContext == groupAudioContext) {
return i;
}
}
}
return INVALID_VOLUME_GROUP_ID;
}
该方法通过音频区域 (zoneId) 和音频上下文 (audioContext) 获取对应的音量分组 (volume group) 的 ID。
2、getUsagesForVolumeGroupId
@Override
public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
synchronized (mImplLock) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
// 用于基于传统流类型的音量控制
if (!mUseDynamicRouting) {
return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
}
// 获取特定音频区域和音量分组的 CarVolumeGroup 对象。
CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
// 获取特定音量分组支持的所有音频上下文
Set<Integer> contexts = Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
final List<Integer> usages = new ArrayList<>();
for (@AudioContext int context : contexts) {
// 根据音频上下文获取对应的usages
int[] usagesForContext = CarAudioContext.getUsagesForContext(context);
for (@AudioAttributes.AttributeUsage int usage : usagesForContext) {
usages.add(usage);
}
}
return usages.stream().mapToInt(i -> i).toArray();
}
}
这里同样利用 context 获取 group 中的对应的所有 usage。
五、设备信息相关
1、getOutputDeviceAddressForUsage
@Override
public String getOutputDeviceAddressForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
// 确保当前环境支持动态路由
requireDynamicRouting();
// 参数有效性检查
Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1, "zoneId (" + zoneId + ")");
// 获取音频上下文
int contextForUsage = CarAudioContext.getContextForUsage(usage);
// 音频上下文有效性检查
Preconditions.checkArgument(contextForUsage != CarAudioContext.INVALID, "Invalid audio attribute usage %d", usage);
// 获取输出设备地址
return mCarAudioZones[zoneId].getAddressForContext(contextForUsage);
}
该方法用于获取特定音频区域 (zoneId) 和特定用途 (usage) 对应的输出设备地址。最终结果也是返回 xml 文件解析出来的内容。
2、getInputDevicesForZoneId
@Override
public @NonNull List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
……
for (CarAudioZone zone : mCarAudioZones) {
if (zone.getId() == zoneId) {
// 取特定音频区域的所有输入设备的属性
return zone.getInputAudioDevices();
}
}
throw new IllegalArgumentException("zoneId does not exist" + zoneId);
}
该方法用于获取特定音频区域 (zoneId) 的所有输入设备。同样是遍历 mCarAudioZones 列表,获取对应输入设备的信息。