1 /*
2  *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc.voiceengine;
12 
13 import static android.media.AudioManager.MODE_IN_CALL;
14 import static android.media.AudioManager.MODE_IN_COMMUNICATION;
15 import static android.media.AudioManager.MODE_NORMAL;
16 import static android.media.AudioManager.MODE_RINGTONE;
17 
18 import android.content.Context;
19 import android.content.pm.PackageManager;
20 import android.media.AudioDeviceInfo;
21 import android.media.AudioManager;
22 import android.os.Build;
23 import java.lang.Thread;
24 import java.util.Arrays;
25 import java.util.List;
26 import org.webrtc.ContextUtils;
27 import org.webrtc.Logging;
28 
29 public final class WebRtcAudioUtils {
30   private static final String TAG = "WebRtcAudioUtils";
31 
32   // List of devices where we have seen issues (e.g. bad audio quality) using
33   // the low latency output mode in combination with OpenSL ES.
34   // The device name is given by Build.MODEL.
35   private static final String[] BLACKLISTED_OPEN_SL_ES_MODELS = new String[] {
36       // It is recommended to maintain a list of blacklisted models outside
37       // this package and instead call
38       // WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true)
39       // from the client for devices where OpenSL ES shall be disabled.
40   };
41 
42   // List of devices where it has been verified that the built-in effect
43   // bad and where it makes sense to avoid using it and instead rely on the
44   // native WebRTC version instead. The device name is given by Build.MODEL.
45   private static final String[] BLACKLISTED_AEC_MODELS = new String[] {
46       // It is recommended to maintain a list of blacklisted models outside
47       // this package and instead call setWebRtcBasedAcousticEchoCanceler(true)
48       // from the client for devices where the built-in AEC shall be disabled.
49   };
50   private static final String[] BLACKLISTED_NS_MODELS = new String[] {
51     // It is recommended to maintain a list of blacklisted models outside
52     // this package and instead call setWebRtcBasedNoiseSuppressor(true)
53     // from the client for devices where the built-in NS shall be disabled.
54   };
55 
56   // Use 16kHz as the default sample rate. A higher sample rate might prevent
57   // us from supporting communication mode on some older (e.g. ICS) devices.
58   private static final int DEFAULT_SAMPLE_RATE_HZ = 16000;
59   private static int defaultSampleRateHz = DEFAULT_SAMPLE_RATE_HZ;
60   // Set to true if setDefaultSampleRateHz() has been called.
61   private static boolean isDefaultSampleRateOverridden;
62 
63   // By default, utilize hardware based audio effects for AEC and NS when
64   // available.
65   private static boolean useWebRtcBasedAcousticEchoCanceler;
66   private static boolean useWebRtcBasedNoiseSuppressor;
67 
68   // Call these methods if any hardware based effect shall be replaced by a
69   // software based version provided by the WebRTC stack instead.
70   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
71   @SuppressWarnings("NoSynchronizedMethodCheck")
setWebRtcBasedAcousticEchoCanceler(boolean enable)72   public static synchronized void setWebRtcBasedAcousticEchoCanceler(boolean enable) {
73     useWebRtcBasedAcousticEchoCanceler = enable;
74   }
75 
76     // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
77   @SuppressWarnings("NoSynchronizedMethodCheck")
setWebRtcBasedNoiseSuppressor(boolean enable)78   public static synchronized void setWebRtcBasedNoiseSuppressor(boolean enable) {
79     useWebRtcBasedNoiseSuppressor = enable;
80   }
81 
82   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
83   @SuppressWarnings("NoSynchronizedMethodCheck")
setWebRtcBasedAutomaticGainControl(boolean enable)84   public static synchronized void setWebRtcBasedAutomaticGainControl(boolean enable) {
85     // TODO(henrika): deprecated; remove when no longer used by any client.
86     Logging.w(TAG, "setWebRtcBasedAutomaticGainControl() is deprecated");
87   }
88 
89   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
90   @SuppressWarnings("NoSynchronizedMethodCheck")
useWebRtcBasedAcousticEchoCanceler()91   public static synchronized boolean useWebRtcBasedAcousticEchoCanceler() {
92     if (useWebRtcBasedAcousticEchoCanceler) {
93       Logging.w(TAG, "Overriding default behavior; now using WebRTC AEC!");
94     }
95     return useWebRtcBasedAcousticEchoCanceler;
96   }
97 
98   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
99   @SuppressWarnings("NoSynchronizedMethodCheck")
useWebRtcBasedNoiseSuppressor()100   public static synchronized boolean useWebRtcBasedNoiseSuppressor() {
101     if (useWebRtcBasedNoiseSuppressor) {
102       Logging.w(TAG, "Overriding default behavior; now using WebRTC NS!");
103     }
104     return useWebRtcBasedNoiseSuppressor;
105   }
106 
107   // TODO(henrika): deprecated; remove when no longer used by any client.
108   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
109   @SuppressWarnings("NoSynchronizedMethodCheck")
useWebRtcBasedAutomaticGainControl()110   public static synchronized boolean useWebRtcBasedAutomaticGainControl() {
111     // Always return true here to avoid trying to use any built-in AGC.
112     return true;
113   }
114 
115   // Returns true if the device supports an audio effect (AEC or NS).
116   // Four conditions must be fulfilled if functions are to return true:
117   // 1) the platform must support the built-in (HW) effect,
118   // 2) explicit use (override) of a WebRTC based version must not be set,
119   // 3) the device must not be blacklisted for use of the effect, and
120   // 4) the UUID of the effect must be approved (some UUIDs can be excluded).
isAcousticEchoCancelerSupported()121   public static boolean isAcousticEchoCancelerSupported() {
122     return WebRtcAudioEffects.canUseAcousticEchoCanceler();
123   }
isNoiseSuppressorSupported()124   public static boolean isNoiseSuppressorSupported() {
125     return WebRtcAudioEffects.canUseNoiseSuppressor();
126   }
127   // TODO(henrika): deprecated; remove when no longer used by any client.
isAutomaticGainControlSupported()128   public static boolean isAutomaticGainControlSupported() {
129     // Always return false here to avoid trying to use any built-in AGC.
130     return false;
131   }
132 
133   // Call this method if the default handling of querying the native sample
134   // rate shall be overridden. Can be useful on some devices where the
135   // available Android APIs are known to return invalid results.
136   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
137   @SuppressWarnings("NoSynchronizedMethodCheck")
setDefaultSampleRateHz(int sampleRateHz)138   public static synchronized void setDefaultSampleRateHz(int sampleRateHz) {
139     isDefaultSampleRateOverridden = true;
140     defaultSampleRateHz = sampleRateHz;
141   }
142 
143   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
144   @SuppressWarnings("NoSynchronizedMethodCheck")
isDefaultSampleRateOverridden()145   public static synchronized boolean isDefaultSampleRateOverridden() {
146     return isDefaultSampleRateOverridden;
147   }
148 
149   // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
150   @SuppressWarnings("NoSynchronizedMethodCheck")
getDefaultSampleRateHz()151   public static synchronized int getDefaultSampleRateHz() {
152     return defaultSampleRateHz;
153   }
154 
getBlackListedModelsForAecUsage()155   public static List<String> getBlackListedModelsForAecUsage() {
156     return Arrays.asList(WebRtcAudioUtils.BLACKLISTED_AEC_MODELS);
157   }
158 
getBlackListedModelsForNsUsage()159   public static List<String> getBlackListedModelsForNsUsage() {
160     return Arrays.asList(WebRtcAudioUtils.BLACKLISTED_NS_MODELS);
161   }
162 
163   // Helper method for building a string of thread information.
getThreadInfo()164   public static String getThreadInfo() {
165     return "@[name=" + Thread.currentThread().getName() + ", id=" + Thread.currentThread().getId()
166         + "]";
167   }
168 
169   // Returns true if we're running on emulator.
runningOnEmulator()170   public static boolean runningOnEmulator() {
171     return Build.HARDWARE.equals("goldfish") && Build.BRAND.startsWith("generic_");
172   }
173 
174   // Returns true if the device is blacklisted for OpenSL ES usage.
deviceIsBlacklistedForOpenSLESUsage()175   public static boolean deviceIsBlacklistedForOpenSLESUsage() {
176     List<String> blackListedModels = Arrays.asList(BLACKLISTED_OPEN_SL_ES_MODELS);
177     return blackListedModels.contains(Build.MODEL);
178   }
179 
180   // Information about the current build, taken from system properties.
logDeviceInfo(String tag)181   static void logDeviceInfo(String tag) {
182     Logging.d(tag, "Android SDK: " + Build.VERSION.SDK_INT + ", "
183             + "Release: " + Build.VERSION.RELEASE + ", "
184             + "Brand: " + Build.BRAND + ", "
185             + "Device: " + Build.DEVICE + ", "
186             + "Id: " + Build.ID + ", "
187             + "Hardware: " + Build.HARDWARE + ", "
188             + "Manufacturer: " + Build.MANUFACTURER + ", "
189             + "Model: " + Build.MODEL + ", "
190             + "Product: " + Build.PRODUCT);
191   }
192 
193   // Logs information about the current audio state. The idea is to call this
194   // method when errors are detected to log under what conditions the error
195   // occurred. Hopefully it will provide clues to what might be the root cause.
logAudioState(String tag)196   static void logAudioState(String tag) {
197     logDeviceInfo(tag);
198     final Context context = ContextUtils.getApplicationContext();
199     final AudioManager audioManager =
200         (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
201     logAudioStateBasic(tag, audioManager);
202     logAudioStateVolume(tag, audioManager);
203     logAudioDeviceInfo(tag, audioManager);
204   }
205 
206   // Reports basic audio statistics.
logAudioStateBasic(String tag, AudioManager audioManager)207   private static void logAudioStateBasic(String tag, AudioManager audioManager) {
208     Logging.d(tag, "Audio State: "
209             + "audio mode: " + modeToString(audioManager.getMode()) + ", "
210             + "has mic: " + hasMicrophone() + ", "
211             + "mic muted: " + audioManager.isMicrophoneMute() + ", "
212             + "music active: " + audioManager.isMusicActive() + ", "
213             + "speakerphone: " + audioManager.isSpeakerphoneOn() + ", "
214             + "BT SCO: " + audioManager.isBluetoothScoOn());
215   }
216 
isVolumeFixed(AudioManager audioManager)217   private static boolean isVolumeFixed(AudioManager audioManager) {
218     if (Build.VERSION.SDK_INT < 21) {
219       return false;
220     }
221     return audioManager.isVolumeFixed();
222   }
223 
224   // Adds volume information for all possible stream types.
logAudioStateVolume(String tag, AudioManager audioManager)225   private static void logAudioStateVolume(String tag, AudioManager audioManager) {
226     final int[] streams = {
227         AudioManager.STREAM_VOICE_CALL,
228         AudioManager.STREAM_MUSIC,
229         AudioManager.STREAM_RING,
230         AudioManager.STREAM_ALARM,
231         AudioManager.STREAM_NOTIFICATION,
232         AudioManager.STREAM_SYSTEM
233     };
234     Logging.d(tag, "Audio State: ");
235     // Some devices may not have volume controls and might use a fixed volume.
236     boolean fixedVolume = isVolumeFixed(audioManager);
237     Logging.d(tag, "  fixed volume=" + fixedVolume);
238     if (!fixedVolume) {
239       for (int stream : streams) {
240         StringBuilder info = new StringBuilder();
241         info.append("  " + streamTypeToString(stream) + ": ");
242         info.append("volume=").append(audioManager.getStreamVolume(stream));
243         info.append(", max=").append(audioManager.getStreamMaxVolume(stream));
244         logIsStreamMute(tag, audioManager, stream, info);
245         Logging.d(tag, info.toString());
246       }
247     }
248   }
249 
logIsStreamMute( String tag, AudioManager audioManager, int stream, StringBuilder info)250   private static void logIsStreamMute(
251       String tag, AudioManager audioManager, int stream, StringBuilder info) {
252     if (Build.VERSION.SDK_INT >= 23) {
253       info.append(", muted=").append(audioManager.isStreamMute(stream));
254     }
255   }
256 
logAudioDeviceInfo(String tag, AudioManager audioManager)257   private static void logAudioDeviceInfo(String tag, AudioManager audioManager) {
258     if (Build.VERSION.SDK_INT < 23) {
259       return;
260     }
261     final AudioDeviceInfo[] devices =
262         audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
263     if (devices.length == 0) {
264       return;
265     }
266     Logging.d(tag, "Audio Devices: ");
267     for (AudioDeviceInfo device : devices) {
268       StringBuilder info = new StringBuilder();
269       info.append("  ").append(deviceTypeToString(device.getType()));
270       info.append(device.isSource() ? "(in): " : "(out): ");
271       // An empty array indicates that the device supports arbitrary channel counts.
272       if (device.getChannelCounts().length > 0) {
273         info.append("channels=").append(Arrays.toString(device.getChannelCounts()));
274         info.append(", ");
275       }
276       if (device.getEncodings().length > 0) {
277         // Examples: ENCODING_PCM_16BIT = 2, ENCODING_PCM_FLOAT = 4.
278         info.append("encodings=").append(Arrays.toString(device.getEncodings()));
279         info.append(", ");
280       }
281       if (device.getSampleRates().length > 0) {
282         info.append("sample rates=").append(Arrays.toString(device.getSampleRates()));
283         info.append(", ");
284       }
285       info.append("id=").append(device.getId());
286       Logging.d(tag, info.toString());
287     }
288   }
289 
290   // Converts media.AudioManager modes into local string representation.
modeToString(int mode)291   static String modeToString(int mode) {
292     switch (mode) {
293       case MODE_IN_CALL:
294         return "MODE_IN_CALL";
295       case MODE_IN_COMMUNICATION:
296         return "MODE_IN_COMMUNICATION";
297       case MODE_NORMAL:
298         return "MODE_NORMAL";
299       case MODE_RINGTONE:
300         return "MODE_RINGTONE";
301       default:
302         return "MODE_INVALID";
303     }
304   }
305 
streamTypeToString(int stream)306   private static String streamTypeToString(int stream) {
307     switch(stream) {
308       case AudioManager.STREAM_VOICE_CALL:
309         return "STREAM_VOICE_CALL";
310       case AudioManager.STREAM_MUSIC:
311         return "STREAM_MUSIC";
312       case AudioManager.STREAM_RING:
313         return "STREAM_RING";
314       case AudioManager.STREAM_ALARM:
315         return "STREAM_ALARM";
316       case AudioManager.STREAM_NOTIFICATION:
317         return "STREAM_NOTIFICATION";
318       case AudioManager.STREAM_SYSTEM:
319         return "STREAM_SYSTEM";
320       default:
321         return "STREAM_INVALID";
322     }
323   }
324 
325   // Converts AudioDeviceInfo types to local string representation.
deviceTypeToString(int type)326   private static String deviceTypeToString(int type) {
327     switch (type) {
328       case AudioDeviceInfo.TYPE_UNKNOWN:
329         return "TYPE_UNKNOWN";
330       case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
331         return "TYPE_BUILTIN_EARPIECE";
332       case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
333         return "TYPE_BUILTIN_SPEAKER";
334       case AudioDeviceInfo.TYPE_WIRED_HEADSET:
335         return "TYPE_WIRED_HEADSET";
336       case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
337         return "TYPE_WIRED_HEADPHONES";
338       case AudioDeviceInfo.TYPE_LINE_ANALOG:
339         return "TYPE_LINE_ANALOG";
340       case AudioDeviceInfo.TYPE_LINE_DIGITAL:
341         return "TYPE_LINE_DIGITAL";
342       case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
343         return "TYPE_BLUETOOTH_SCO";
344       case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
345         return "TYPE_BLUETOOTH_A2DP";
346       case AudioDeviceInfo.TYPE_HDMI:
347         return "TYPE_HDMI";
348       case AudioDeviceInfo.TYPE_HDMI_ARC:
349         return "TYPE_HDMI_ARC";
350       case AudioDeviceInfo.TYPE_USB_DEVICE:
351         return "TYPE_USB_DEVICE";
352       case AudioDeviceInfo.TYPE_USB_ACCESSORY:
353         return "TYPE_USB_ACCESSORY";
354       case AudioDeviceInfo.TYPE_DOCK:
355         return "TYPE_DOCK";
356       case AudioDeviceInfo.TYPE_FM:
357         return "TYPE_FM";
358       case AudioDeviceInfo.TYPE_BUILTIN_MIC:
359         return "TYPE_BUILTIN_MIC";
360       case AudioDeviceInfo.TYPE_FM_TUNER:
361         return "TYPE_FM_TUNER";
362       case AudioDeviceInfo.TYPE_TV_TUNER:
363         return "TYPE_TV_TUNER";
364       case AudioDeviceInfo.TYPE_TELEPHONY:
365         return "TYPE_TELEPHONY";
366       case AudioDeviceInfo.TYPE_AUX_LINE:
367         return "TYPE_AUX_LINE";
368       case AudioDeviceInfo.TYPE_IP:
369         return "TYPE_IP";
370       case AudioDeviceInfo.TYPE_BUS:
371         return "TYPE_BUS";
372       case AudioDeviceInfo.TYPE_USB_HEADSET:
373         return "TYPE_USB_HEADSET";
374       default:
375         return "TYPE_UNKNOWN";
376     }
377   }
378 
379   // Returns true if the device can record audio via a microphone.
hasMicrophone()380   private static boolean hasMicrophone() {
381     return ContextUtils.getApplicationContext().getPackageManager().hasSystemFeature(
382         PackageManager.FEATURE_MICROPHONE);
383   }
384 }
385