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.audio.CarAudioUtils.getAudioDeviceInfo; 21 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR; 22 23 import android.car.builtin.util.Slogf; 24 import android.media.AudioAttributes; 25 import android.media.AudioDeviceInfo; 26 import android.media.AudioFormat; 27 import android.media.AudioManager; 28 import android.media.audiopolicy.AudioMix; 29 import android.media.audiopolicy.AudioMixingRule; 30 import android.media.audiopolicy.AudioPolicy; 31 import android.util.Log; 32 import android.util.SparseArray; 33 34 import com.android.car.CarLog; 35 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 36 37 import java.util.Arrays; 38 import java.util.List; 39 40 /** 41 * Builds dynamic audio routing in a car from audio zone configuration. 42 */ 43 final class CarAudioDynamicRouting { 44 // For legacy stream type based volume control. 45 // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned. 46 static final int[] STREAM_TYPES = new int[] { 47 AudioManager.STREAM_MUSIC, 48 AudioManager.STREAM_ALARM, 49 AudioManager.STREAM_RING 50 }; 51 static final int[] STREAM_TYPE_USAGES = new int[] { 52 USAGE_MEDIA, 53 AudioAttributes.USAGE_ALARM, 54 AudioAttributes.USAGE_NOTIFICATION_RINGTONE 55 }; 56 setupAudioDynamicRouting(CarAudioContext carAudioContext, AudioManagerWrapper audioManager, AudioPolicy.Builder builder, SparseArray<CarAudioZone> carAudioZones)57 static void setupAudioDynamicRouting(CarAudioContext carAudioContext, 58 AudioManagerWrapper audioManager, AudioPolicy.Builder builder, 59 SparseArray<CarAudioZone> carAudioZones) { 60 for (int i = 0; i < carAudioZones.size(); i++) { 61 CarAudioZone zone = carAudioZones.valueAt(i); 62 List<CarAudioZoneConfig> zoneConfigs = zone.getAllCarAudioZoneConfigs(); 63 CarAudioZoneConfig defaultConfig = null; 64 boolean foundSelected = false; 65 for (int configIndex = 0; configIndex < zoneConfigs.size(); configIndex++) { 66 CarAudioZoneConfig config = zoneConfigs.get(configIndex); 67 // Default config will be added at the end 68 if (config.isDefault()) { 69 defaultConfig = config; 70 continue; 71 } 72 if (!config.isSelected() || !config.isActive()) { 73 continue; 74 } 75 foundSelected = true; 76 setupAudioDynamicRoutingForZoneConfig(builder, config, carAudioContext, 77 audioManager); 78 } 79 // Always setup default configuration at the end, so that zone routing has a backup for 80 // routing in case the dynamic device disconnects. 81 if (defaultConfig.isSelected()) { 82 foundSelected = true; 83 } 84 85 if (!foundSelected) { 86 throw new IllegalStateException("Selected configuration for zone " + zone.getId() 87 + " was not available"); 88 } 89 90 // Default configuration should always be available, in case the dynamic 91 // device disappears the default configuration will be selected 92 setupAudioDynamicRoutingForZoneConfig(builder, defaultConfig, carAudioContext, 93 audioManager); 94 } 95 } 96 setupAudioDynamicRoutingForZoneConfig(AudioPolicy.Builder builder, CarAudioZoneConfig zoneConfig, CarAudioContext carAudioContext, AudioManagerWrapper audioManager)97 private static void setupAudioDynamicRoutingForZoneConfig(AudioPolicy.Builder builder, 98 CarAudioZoneConfig zoneConfig, CarAudioContext carAudioContext, 99 AudioManagerWrapper audioManager) { 100 CarVolumeGroup[] volumeGroups = zoneConfig.getVolumeGroups(); 101 for (int index = 0; index < volumeGroups.length; index++) { 102 setupAudioDynamicRoutingForGroup(builder, volumeGroups[index], carAudioContext, 103 audioManager); 104 } 105 } 106 107 /** 108 * Enumerates all physical buses in a given volume group and attach the mixing rules. 109 * 110 * @param builder {@link AudioPolicy.Builder} to attach the mixing rules 111 * @param group {@link CarVolumeGroup} instance to enumerate the buses with 112 * @param carAudioContext car audio context 113 * @param audioManager audio manager to find audio configuration for the passed in info 114 */ setupAudioDynamicRoutingForGroup(AudioPolicy.Builder builder, CarVolumeGroup group, CarAudioContext carAudioContext, AudioManagerWrapper audioManager)115 private static void setupAudioDynamicRoutingForGroup(AudioPolicy.Builder builder, 116 CarVolumeGroup group, CarAudioContext carAudioContext, 117 AudioManagerWrapper audioManager) { 118 // Note that one can not register audio mix for same bus more than once. 119 List<String> addresses = group.getAddresses(); 120 for (int index = 0; index < addresses.size(); index++) { 121 String address = addresses.get(index); 122 boolean hasContext = false; 123 CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForAddress(address); 124 if (!info.canBeRoutedWithDynamicPolicyMix()) { 125 if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) { 126 Slogf.d(CarLog.TAG_AUDIO, "Address: %s AudioContext: %s cannot be routed with " 127 + "Dynamic Policy Mixing", address, carAudioContext); 128 } 129 continue; 130 } 131 AudioFormat mixFormat = createMixFormatFromDevice(info); 132 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 133 List<Integer> contextIdsForAddress = group.getContextsForAddress(address); 134 for (int contextIndex = 0; contextIndex < contextIdsForAddress.size(); contextIndex++) { 135 @CarAudioContext.AudioContext int contextId = 136 contextIdsForAddress.get(contextIndex); 137 hasContext = true; 138 AudioAttributes[] allAudioAttributes = 139 carAudioContext.getAudioAttributesForContext(contextId); 140 for (int attrIndex = 0; attrIndex < allAudioAttributes.length; attrIndex++) { 141 AudioAttributes attributes = allAudioAttributes[attrIndex]; 142 mixingRuleBuilder.addRule(attributes, 143 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 144 } 145 if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) { 146 Slogf.d(CarLog.TAG_AUDIO, "Address: %s AudioContext: %s sampleRate: %d " 147 + "channels: %d attributes: %s", address, carAudioContext, 148 info.getSampleRate(), info.getChannelCount(), 149 Arrays.toString(allAudioAttributes)); 150 } 151 } 152 if (hasContext) { 153 AudioDeviceInfo audioDeviceInfo = 154 getAudioDeviceInfo(info.getAudioDevice(), audioManager); 155 // It's a valid case that an audio output address is defined in 156 // audio_policy_configuration and no context is assigned to it. 157 // In such case, do not build a policy mix with zero rules. 158 addMix(builder, audioDeviceInfo, mixFormat, mixingRuleBuilder); 159 } 160 } 161 } 162 163 @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR) CarAudioDynamicRouting()164 private CarAudioDynamicRouting() { 165 throw new UnsupportedOperationException("contains only static methods"); 166 } 167 setupAudioDynamicRoutingForMirrorDevice( AudioPolicy.Builder mirrorPolicyBuilder, List<CarAudioDeviceInfo> audioDeviceInfos, AudioManagerWrapper audioManager)168 public static void setupAudioDynamicRoutingForMirrorDevice( 169 AudioPolicy.Builder mirrorPolicyBuilder, List<CarAudioDeviceInfo> audioDeviceInfos, 170 AudioManagerWrapper audioManager) { 171 for (int index = 0; index < audioDeviceInfos.size(); index++) { 172 AudioFormat mixFormat = createMixFormatFromDevice(audioDeviceInfos.get(index)); 173 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 174 mixingRuleBuilder.addRule(CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA), 175 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 176 AudioDeviceInfo info = getAudioDeviceInfo( 177 audioDeviceInfos.get(index).getAudioDevice(), audioManager); 178 179 addMix(mirrorPolicyBuilder, info, mixFormat, mixingRuleBuilder); 180 } 181 } 182 createMixFormatFromDevice(CarAudioDeviceInfo mirrorDevice)183 private static AudioFormat createMixFormatFromDevice(CarAudioDeviceInfo mirrorDevice) { 184 AudioFormat mixFormat = new AudioFormat.Builder() 185 .setSampleRate(mirrorDevice.getSampleRate()) 186 .setEncoding(mirrorDevice.getEncodingFormat()) 187 .setChannelMask(mirrorDevice.getChannelCount()) 188 .build(); 189 return mixFormat; 190 } 191 addMix(AudioPolicy.Builder builder, AudioDeviceInfo deviceInfo, AudioFormat mixFormat, AudioMixingRule.Builder mixingRuleBuilder)192 private static void addMix(AudioPolicy.Builder builder, AudioDeviceInfo deviceInfo, 193 AudioFormat mixFormat, AudioMixingRule.Builder mixingRuleBuilder) { 194 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build()) 195 .setFormat(mixFormat) 196 .setDevice(deviceInfo) 197 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 198 .build(); 199 builder.addMix(audioMix); 200 } 201 } 202