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 {
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.UNKNOWN_FLAG, CarSettings.Global.KEY_VOLUME_MUSIC)58         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.UNKNOWN_FLAG,
59                 CarSettings.Global.KEY_VOLUME_MUSIC);
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.MUSIC_FLAG, CarSettings.Global.KEY_VOLUME_MUSIC)60         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.MUSIC_FLAG,
61                 CarSettings.Global.KEY_VOLUME_MUSIC);
CAR_AUDIO_CONTEXT_SETTINGS.put( VehicleAudioContextFlag.NAVIGATION_FLAG, CarSettings.Global.KEY_VOLUME_NAVIGATION)62         CAR_AUDIO_CONTEXT_SETTINGS.put(
63                 VehicleAudioContextFlag.NAVIGATION_FLAG,
64                 CarSettings.Global.KEY_VOLUME_NAVIGATION);
CAR_AUDIO_CONTEXT_SETTINGS.put( VehicleAudioContextFlag.VOICE_COMMAND_FLAG, CarSettings.Global.KEY_VOLUME_VOICE_COMMAND)65         CAR_AUDIO_CONTEXT_SETTINGS.put(
66                 VehicleAudioContextFlag.VOICE_COMMAND_FLAG,
67                 CarSettings.Global.KEY_VOLUME_VOICE_COMMAND);
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CALL_FLAG, CarSettings.Global.KEY_VOLUME_CALL)68         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CALL_FLAG,
69                 CarSettings.Global.KEY_VOLUME_CALL);
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.ALARM_FLAG, CarSettings.Global.KEY_VOLUME_ALARM)70         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.ALARM_FLAG,
71                 CarSettings.Global.KEY_VOLUME_ALARM);
CAR_AUDIO_CONTEXT_SETTINGS.put( VehicleAudioContextFlag.NOTIFICATION_FLAG, CarSettings.Global.KEY_VOLUME_NOTIFICATION)72         CAR_AUDIO_CONTEXT_SETTINGS.put(
73                 VehicleAudioContextFlag.NOTIFICATION_FLAG,
74                 CarSettings.Global.KEY_VOLUME_NOTIFICATION);
CAR_AUDIO_CONTEXT_SETTINGS.put( VehicleAudioContextFlag.SAFETY_ALERT_FLAG, CarSettings.Global.KEY_VOLUME_SAFETY_ALERT)75         CAR_AUDIO_CONTEXT_SETTINGS.put(
76                 VehicleAudioContextFlag.SAFETY_ALERT_FLAG,
77                 CarSettings.Global.KEY_VOLUME_SAFETY_ALERT);
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CD_ROM_FLAG, CarSettings.Global.KEY_VOLUME_CD_ROM)78         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.CD_ROM_FLAG,
79                 CarSettings.Global.KEY_VOLUME_CD_ROM);
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.AUX_AUDIO_FLAG, CarSettings.Global.KEY_VOLUME_AUX)80         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.AUX_AUDIO_FLAG,
81                 CarSettings.Global.KEY_VOLUME_AUX);
CAR_AUDIO_CONTEXT_SETTINGS.put( VehicleAudioContextFlag.SYSTEM_SOUND_FLAG, CarSettings.Global.KEY_VOLUME_SYSTEM_SOUND)82         CAR_AUDIO_CONTEXT_SETTINGS.put(
83                 VehicleAudioContextFlag.SYSTEM_SOUND_FLAG,
84                 CarSettings.Global.KEY_VOLUME_SYSTEM_SOUND);
CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.RADIO_FLAG, CarSettings.Global.KEY_VOLUME_RADIO)85         CAR_AUDIO_CONTEXT_SETTINGS.put(VehicleAudioContextFlag.RADIO_FLAG,
86                 CarSettings.Global.KEY_VOLUME_RADIO);
87     }
88 
streamToName(int stream)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 
androidStreamToCarContext(int logicalAndroidStream)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 
carContextToAndroidStream(int carContext)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 
androidStreamToCarUsage(int logicalAndroidStream)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 
VolumeUtils(AudioManager audioManager)154     public VolumeUtils(AudioManager audioManager) {
155         mAudioManager = audioManager;
156         for(int i : LOGICAL_STREAMS) {
157             initStreamLookup(i);
158         }
159     }
160 
initStreamLookup(int streamType)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 
closestIndex(float desired, Float[] list)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 
adjustStreamVol(int stream, int desired, int actual, int maxIndex)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      */
getTrackGain(int desiredVolIndex, int actualVolIndex, int maxIndex)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      */
volIndexToAmpl(int volIndex, int maxIndex)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      */
volumeToDecibels(int volume)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      */
logToLinear(float decibels)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