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