1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.audio; 17 18 import static android.media.AudioAttributes.USAGE_MEDIA; 19 20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR; 21 22 import android.car.builtin.util.Slogf; 23 import android.media.AudioAttributes; 24 import android.media.AudioFormat; 25 import android.media.AudioManager; 26 import android.media.audiopolicy.AudioMix; 27 import android.media.audiopolicy.AudioMixingRule; 28 import android.media.audiopolicy.AudioPolicy; 29 import android.util.Log; 30 import android.util.SparseArray; 31 32 import com.android.car.CarLog; 33 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 34 35 import java.util.Arrays; 36 import java.util.List; 37 38 /** 39 * Builds dynamic audio routing in a car from audio zone configuration. 40 */ 41 final class CarAudioDynamicRouting { 42 // For legacy stream type based volume control. 43 // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned. 44 static final int[] STREAM_TYPES = new int[] { 45 AudioManager.STREAM_MUSIC, 46 AudioManager.STREAM_ALARM, 47 AudioManager.STREAM_RING 48 }; 49 static final int[] STREAM_TYPE_USAGES = new int[] { 50 USAGE_MEDIA, 51 AudioAttributes.USAGE_ALARM, 52 AudioAttributes.USAGE_NOTIFICATION_RINGTONE 53 }; 54 setupAudioDynamicRouting(AudioPolicy.Builder builder, SparseArray<CarAudioZone> carAudioZones, CarAudioContext carAudioContext)55 static void setupAudioDynamicRouting(AudioPolicy.Builder builder, 56 SparseArray<CarAudioZone> carAudioZones, CarAudioContext carAudioContext) { 57 for (int i = 0; i < carAudioZones.size(); i++) { 58 List<CarAudioZoneConfig> zoneConfigs = 59 carAudioZones.valueAt(i).getAllCarAudioZoneConfigs(); 60 for (int configIndex = 0; configIndex < zoneConfigs.size(); configIndex++) { 61 setupAudioDynamicRoutingForZoneConfig(builder, zoneConfigs.get(configIndex), 62 carAudioContext); 63 } 64 } 65 } 66 setupAudioDynamicRoutingForZoneConfig(AudioPolicy.Builder builder, CarAudioZoneConfig zoneConfig, CarAudioContext carAudioContext)67 private static void setupAudioDynamicRoutingForZoneConfig(AudioPolicy.Builder builder, 68 CarAudioZoneConfig zoneConfig, CarAudioContext carAudioContext) { 69 CarVolumeGroup[] volumeGroups = zoneConfig.getVolumeGroups(); 70 for (int index = 0; index < volumeGroups.length; index++) { 71 setupAudioDynamicRoutingForGroup(builder, volumeGroups[index], carAudioContext); 72 } 73 } 74 75 /** 76 * Enumerates all physical buses in a given volume group and attach the mixing rules. 77 * @param builder {@link AudioPolicy.Builder} to attach the mixing rules 78 * @param group {@link CarVolumeGroup} instance to enumerate the buses with 79 * @param carAudioContext car audio context 80 */ setupAudioDynamicRoutingForGroup(AudioPolicy.Builder builder, CarVolumeGroup group, CarAudioContext carAudioContext)81 private static void setupAudioDynamicRoutingForGroup(AudioPolicy.Builder builder, 82 CarVolumeGroup group, CarAudioContext carAudioContext) { 83 // Note that one can not register audio mix for same bus more than once. 84 List<String> addresses = group.getAddresses(); 85 for (int index = 0; index < addresses.size(); index++) { 86 String address = addresses.get(index); 87 boolean hasContext = false; 88 CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForAddress(address); 89 if (!info.canBeRoutedWithDynamicPolicyMix()) { 90 if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) { 91 Slogf.d(CarLog.TAG_AUDIO, "Address: %s AudioContext: %s cannot be routed with " 92 + "Dynamic Policy Mixing", address, carAudioContext); 93 } 94 continue; 95 } 96 AudioFormat mixFormat = createMixFormatFromDevice(info); 97 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 98 List<Integer> contextIdsForAddress = group.getContextsForAddress(address); 99 for (int contextIndex = 0; contextIndex < contextIdsForAddress.size(); contextIndex++) { 100 @CarAudioContext.AudioContext int contextId = 101 contextIdsForAddress.get(contextIndex); 102 hasContext = true; 103 AudioAttributes[] allAudioAttributes = 104 carAudioContext.getAudioAttributesForContext(contextId); 105 for (int attrIndex = 0; attrIndex < allAudioAttributes.length; attrIndex++) { 106 AudioAttributes attributes = allAudioAttributes[attrIndex]; 107 mixingRuleBuilder.addRule(attributes, 108 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 109 } 110 if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) { 111 Slogf.d(CarLog.TAG_AUDIO, "Address: %s AudioContext: %s sampleRate: %d " 112 + "channels: %d attributes: %s", address, carAudioContext, 113 info.getSampleRate(), info.getChannelCount(), 114 Arrays.toString(allAudioAttributes)); 115 } 116 } 117 if (hasContext) { 118 // It's a valid case that an audio output address is defined in 119 // audio_policy_configuration and no context is assigned to it. 120 // In such case, do not build a policy mix with zero rules. 121 addMix(builder, info, mixFormat, mixingRuleBuilder); 122 } 123 } 124 } 125 126 @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR) CarAudioDynamicRouting()127 private CarAudioDynamicRouting() { 128 throw new UnsupportedOperationException("contains only static methods"); 129 } 130 setupAudioDynamicRoutingForMirrorDevice( AudioPolicy.Builder mirrorPolicyBuilder, List<CarAudioDeviceInfo> audioDeviceInfos)131 public static void setupAudioDynamicRoutingForMirrorDevice( 132 AudioPolicy.Builder mirrorPolicyBuilder, List<CarAudioDeviceInfo> audioDeviceInfos) { 133 for (int index = 0; index < audioDeviceInfos.size(); index++) { 134 AudioFormat mixFormat = createMixFormatFromDevice(audioDeviceInfos.get(index)); 135 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 136 mixingRuleBuilder.addRule(CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA), 137 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 138 139 addMix(mirrorPolicyBuilder, audioDeviceInfos.get(index), mixFormat, mixingRuleBuilder); 140 } 141 } 142 createMixFormatFromDevice(CarAudioDeviceInfo mirrorDevice)143 private static AudioFormat createMixFormatFromDevice(CarAudioDeviceInfo mirrorDevice) { 144 AudioFormat mixFormat = new AudioFormat.Builder() 145 .setSampleRate(mirrorDevice.getSampleRate()) 146 .setEncoding(mirrorDevice.getEncodingFormat()) 147 .setChannelMask(mirrorDevice.getChannelCount()) 148 .build(); 149 return mixFormat; 150 } 151 addMix(AudioPolicy.Builder mirrorPolicyBuilder, CarAudioDeviceInfo mirrorDevice, AudioFormat mixFormat, AudioMixingRule.Builder mixingRuleBuilder)152 private static void addMix(AudioPolicy.Builder mirrorPolicyBuilder, 153 CarAudioDeviceInfo mirrorDevice, AudioFormat mixFormat, 154 AudioMixingRule.Builder mixingRuleBuilder) { 155 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build()) 156 .setFormat(mixFormat) 157 .setDevice(mirrorDevice.getAudioDeviceInfo()) 158 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 159 .build(); 160 mirrorPolicyBuilder.addMix(audioMix); 161 } 162 } 163