1 /* 2 * Copyright (C) 2016 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 17 package com.android.car; 18 19 import android.car.settings.CarSettings; 20 import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag; 21 import android.media.AudioAttributes; 22 import android.media.AudioManager; 23 import android.util.Log; 24 import android.util.SparseArray; 25 26 import java.util.Arrays; 27 28 public class VolumeUtils { 29 private static final String TAG = "VolumeUtils"; 30 31 public static final int[] LOGICAL_STREAMS = { 32 AudioManager.STREAM_VOICE_CALL, 33 AudioManager.STREAM_SYSTEM, 34 AudioManager.STREAM_RING, 35 AudioManager.STREAM_MUSIC, 36 AudioManager.STREAM_ALARM, 37 AudioManager.STREAM_NOTIFICATION, 38 AudioManager.STREAM_DTMF, 39 }; 40 41 public static final int[] CAR_AUDIO_CONTEXT = { 42 VehicleAudioContextFlag.MUSIC_FLAG, 43 VehicleAudioContextFlag.NAVIGATION_FLAG, 44 VehicleAudioContextFlag.VOICE_COMMAND_FLAG, 45 VehicleAudioContextFlag.CALL_FLAG, 46 VehicleAudioContextFlag.ALARM_FLAG, 47 VehicleAudioContextFlag.NOTIFICATION_FLAG, 48 VehicleAudioContextFlag.UNKNOWN_FLAG, 49 VehicleAudioContextFlag.SAFETY_ALERT_FLAG, 50 VehicleAudioContextFlag.CD_ROM_FLAG, 51 VehicleAudioContextFlag.AUX_AUDIO_FLAG, 52 VehicleAudioContextFlag.SYSTEM_SOUND_FLAG, 53 VehicleAudioContextFlag.RADIO_FLAG 54 }; 55 56 public static final SparseArray<String> CAR_AUDIO_CONTEXT_SETTINGS = new SparseArray<>(); 57 static { 58 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.UNKNOWN_FLAG, 59 CarSettings.Global.KEY_VOLUME_MUSIC); 60 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.MUSIC_FLAG, 61 CarSettings.Global.KEY_VOLUME_MUSIC); 62 CAR_AUDIO_CONTEXT_SETTINGS.put( 63 VehicleAudioContextFlag.NAVIGATION_FLAG, 64 CarSettings.Global.KEY_VOLUME_NAVIGATION); 65 CAR_AUDIO_CONTEXT_SETTINGS.put( 66 VehicleAudioContextFlag.VOICE_COMMAND_FLAG, 67 CarSettings.Global.KEY_VOLUME_VOICE_COMMAND); 68 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CALL_FLAG, 69 CarSettings.Global.KEY_VOLUME_CALL); 70 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.ALARM_FLAG, 71 CarSettings.Global.KEY_VOLUME_ALARM); 72 CAR_AUDIO_CONTEXT_SETTINGS.put( 73 VehicleAudioContextFlag.NOTIFICATION_FLAG, 74 CarSettings.Global.KEY_VOLUME_NOTIFICATION); 75 CAR_AUDIO_CONTEXT_SETTINGS.put( 76 VehicleAudioContextFlag.SAFETY_ALERT_FLAG, 77 CarSettings.Global.KEY_VOLUME_SAFETY_ALERT); 78 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CD_ROM_FLAG, 79 CarSettings.Global.KEY_VOLUME_CD_ROM); 80 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.AUX_AUDIO_FLAG, 81 CarSettings.Global.KEY_VOLUME_AUX); 82 CAR_AUDIO_CONTEXT_SETTINGS.put( 83 VehicleAudioContextFlag.SYSTEM_SOUND_FLAG, 84 CarSettings.Global.KEY_VOLUME_SYSTEM_SOUND); 85 CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.RADIO_FLAG, 86 CarSettings.Global.KEY_VOLUME_RADIO); 87 } 88 89 public static String streamToName(int stream) { 90 switch (stream) { 91 case AudioManager.STREAM_ALARM: return "Alarm"; 92 case AudioManager.STREAM_MUSIC: return "Music"; 93 case AudioManager.STREAM_NOTIFICATION: return "Notification"; 94 case AudioManager.STREAM_RING: return "Ring"; 95 case AudioManager.STREAM_VOICE_CALL: return "Call"; 96 case AudioManager.STREAM_SYSTEM: return "System"; 97 case AudioManager.STREAM_DTMF: return "DTMF"; 98 default: return "Unknown"; 99 } 100 } 101 102 public static int androidStreamToCarContext(int logicalAndroidStream) { 103 switch (logicalAndroidStream) { 104 case AudioManager.STREAM_VOICE_CALL: 105 return VehicleAudioContextFlag.CALL_FLAG; 106 case AudioManager.STREAM_SYSTEM: 107 return VehicleAudioContextFlag.SYSTEM_SOUND_FLAG; 108 case AudioManager.STREAM_RING: 109 return VehicleAudioContextFlag.NOTIFICATION_FLAG; 110 case AudioManager.STREAM_MUSIC: 111 return VehicleAudioContextFlag.MUSIC_FLAG; 112 case AudioManager.STREAM_ALARM: 113 return VehicleAudioContextFlag.ALARM_FLAG; 114 case AudioManager.STREAM_NOTIFICATION: 115 return VehicleAudioContextFlag.NOTIFICATION_FLAG; 116 case AudioManager.STREAM_DTMF: 117 return VehicleAudioContextFlag.SYSTEM_SOUND_FLAG; 118 default: 119 return VehicleAudioContextFlag.UNKNOWN_FLAG; 120 } 121 } 122 123 public static int carContextToAndroidStream(int carContext) { 124 switch (carContext) { 125 case VehicleAudioContextFlag.CALL_FLAG: 126 return AudioManager.STREAM_VOICE_CALL; 127 case VehicleAudioContextFlag.SYSTEM_SOUND_FLAG: 128 return AudioManager.STREAM_SYSTEM; 129 case VehicleAudioContextFlag.NOTIFICATION_FLAG: 130 return AudioManager.STREAM_NOTIFICATION; 131 case VehicleAudioContextFlag.MUSIC_FLAG: 132 return AudioManager.STREAM_MUSIC; 133 case VehicleAudioContextFlag.ALARM_FLAG: 134 return AudioManager.STREAM_ALARM; 135 default: 136 return AudioManager.STREAM_MUSIC; 137 } 138 } 139 140 public static int androidStreamToCarUsage(int logicalAndroidStream) { 141 return CarAudioAttributesUtil.getCarUsageFromAudioAttributes( 142 new AudioAttributes.Builder() 143 .setLegacyStreamType(logicalAndroidStream).build()); 144 } 145 146 private final SparseArray<Float[]> mStreamAmplLookup = new SparseArray<>(7); 147 148 private static final float LN_10 = 2.302585093f; 149 // From cs/#android/frameworks/av/media/libmedia/AudioSystem.cpp 150 private static final float DB_PER_STEP = -.5f; 151 152 private final AudioManager mAudioManager; 153 154 public VolumeUtils(AudioManager audioManager) { 155 mAudioManager = audioManager; 156 for(int i : LOGICAL_STREAMS) { 157 initStreamLookup(i); 158 } 159 } 160 161 private void initStreamLookup(int streamType) { 162 int maxIndex = mAudioManager.getStreamMaxVolume(streamType); 163 Float[] amplList = new Float[maxIndex + 1]; 164 165 for (int i = 0; i <= maxIndex; i++) { 166 amplList[i] = volIndexToAmpl(i, maxIndex); 167 } 168 Log.d(TAG, streamToName(streamType) + ": " + Arrays.toString(amplList)); 169 mStreamAmplLookup.put(streamType, amplList); 170 } 171 172 173 public static int closestIndex(float desired, Float[] list) { 174 float min = Float.MAX_VALUE; 175 int closestIndex = 0; 176 177 for (int i = 0; i < list.length; i++) { 178 float diff = Math.abs(list[i] - desired); 179 if (diff < min) { 180 min = diff; 181 closestIndex = i; 182 } 183 } 184 return closestIndex; 185 } 186 187 public void adjustStreamVol(int stream, int desired, int actual, int maxIndex) { 188 float gain = getTrackGain(desired, actual, maxIndex); 189 int index = closestIndex(gain, mStreamAmplLookup.get(stream)); 190 if (index == mAudioManager.getStreamVolume(stream)) { 191 return; 192 } else { 193 mAudioManager.setStreamVolume(stream, index, 0 /*don't show UI*/); 194 } 195 } 196 197 /** 198 * Returns the gain which, when applied to an a stream with volume 199 * actualVolIndex, will make the output volume equivalent to a stream with a gain of 200 * 1.0 playing on a stream with volume desiredVolIndex. 201 * 202 * Computing this is non-trivial because the gain is applied on a linear scale while the volume 203 * indices map to a log (dB) scale. 204 * 205 * The computation is copied from cs/#android/frameworks/av/media/libmedia/AudioSystem.cpp 206 */ 207 float getTrackGain(int desiredVolIndex, int actualVolIndex, int maxIndex) { 208 if (desiredVolIndex == actualVolIndex) { 209 return 1.0f; 210 } 211 return volIndexToAmpl(desiredVolIndex, maxIndex) 212 / volIndexToAmpl(actualVolIndex, maxIndex); 213 } 214 215 /** 216 * Returns the amplitude corresponding to volIndex. Guaranteed to return a non-negative value. 217 */ 218 private float volIndexToAmpl(int volIndex, int maxIndex) { 219 // Normalize volIndex to be in the range [0, 100]. 220 int volume = (int) ((float) volIndex / maxIndex * 100.0f); 221 return logToLinear(volumeToDecibels(volume)); 222 } 223 224 /** 225 * volume is in the range [0, 100]. 226 */ 227 private static float volumeToDecibels(int volume) { 228 return (100 - volume) * DB_PER_STEP; 229 } 230 231 /** 232 * Corresponds to the function linearToLog in AudioSystem.cpp. 233 */ 234 private static float logToLinear(float decibels) { 235 return decibels < 0.0f ? (float) Math.exp(decibels * LN_10 / 20.0f) : 1.0f; 236 } 237 } 238