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