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.audio; 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.annotation.TargetApi; 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.media.AudioDeviceInfo; 22 import android.media.AudioFormat; 23 import android.media.AudioManager; 24 import android.media.MediaRecorder.AudioSource; 25 import android.os.Build; 26 import java.lang.Thread; 27 import java.util.Arrays; 28 import org.webrtc.Logging; 29 30 final class WebRtcAudioUtils { 31 private static final String TAG = "WebRtcAudioUtilsExternal"; 32 33 // Helper method for building a string of thread information. getThreadInfo()34 public static String getThreadInfo() { 35 return "@[name=" + Thread.currentThread().getName() + ", id=" + Thread.currentThread().getId() 36 + "]"; 37 } 38 39 // Returns true if we're running on emulator. runningOnEmulator()40 public static boolean runningOnEmulator() { 41 return Build.HARDWARE.equals("goldfish") && Build.BRAND.startsWith("generic_"); 42 } 43 44 // Information about the current build, taken from system properties. logDeviceInfo(String tag)45 static void logDeviceInfo(String tag) { 46 Logging.d(tag, 47 "Android SDK: " + Build.VERSION.SDK_INT + ", " 48 + "Release: " + Build.VERSION.RELEASE + ", " 49 + "Brand: " + Build.BRAND + ", " 50 + "Device: " + Build.DEVICE + ", " 51 + "Id: " + Build.ID + ", " 52 + "Hardware: " + Build.HARDWARE + ", " 53 + "Manufacturer: " + Build.MANUFACTURER + ", " 54 + "Model: " + Build.MODEL + ", " 55 + "Product: " + Build.PRODUCT); 56 } 57 58 // Logs information about the current audio state. The idea is to call this 59 // method when errors are detected to log under what conditions the error 60 // occurred. Hopefully it will provide clues to what might be the root cause. logAudioState(String tag, Context context, AudioManager audioManager)61 static void logAudioState(String tag, Context context, AudioManager audioManager) { 62 logDeviceInfo(tag); 63 logAudioStateBasic(tag, context, audioManager); 64 logAudioStateVolume(tag, audioManager); 65 logAudioDeviceInfo(tag, audioManager); 66 } 67 68 // Converts AudioDeviceInfo types to local string representation. deviceTypeToString(int type)69 static String deviceTypeToString(int type) { 70 switch (type) { 71 case AudioDeviceInfo.TYPE_UNKNOWN: 72 return "TYPE_UNKNOWN"; 73 case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: 74 return "TYPE_BUILTIN_EARPIECE"; 75 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: 76 return "TYPE_BUILTIN_SPEAKER"; 77 case AudioDeviceInfo.TYPE_WIRED_HEADSET: 78 return "TYPE_WIRED_HEADSET"; 79 case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: 80 return "TYPE_WIRED_HEADPHONES"; 81 case AudioDeviceInfo.TYPE_LINE_ANALOG: 82 return "TYPE_LINE_ANALOG"; 83 case AudioDeviceInfo.TYPE_LINE_DIGITAL: 84 return "TYPE_LINE_DIGITAL"; 85 case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: 86 return "TYPE_BLUETOOTH_SCO"; 87 case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: 88 return "TYPE_BLUETOOTH_A2DP"; 89 case AudioDeviceInfo.TYPE_HDMI: 90 return "TYPE_HDMI"; 91 case AudioDeviceInfo.TYPE_HDMI_ARC: 92 return "TYPE_HDMI_ARC"; 93 case AudioDeviceInfo.TYPE_USB_DEVICE: 94 return "TYPE_USB_DEVICE"; 95 case AudioDeviceInfo.TYPE_USB_ACCESSORY: 96 return "TYPE_USB_ACCESSORY"; 97 case AudioDeviceInfo.TYPE_DOCK: 98 return "TYPE_DOCK"; 99 case AudioDeviceInfo.TYPE_FM: 100 return "TYPE_FM"; 101 case AudioDeviceInfo.TYPE_BUILTIN_MIC: 102 return "TYPE_BUILTIN_MIC"; 103 case AudioDeviceInfo.TYPE_FM_TUNER: 104 return "TYPE_FM_TUNER"; 105 case AudioDeviceInfo.TYPE_TV_TUNER: 106 return "TYPE_TV_TUNER"; 107 case AudioDeviceInfo.TYPE_TELEPHONY: 108 return "TYPE_TELEPHONY"; 109 case AudioDeviceInfo.TYPE_AUX_LINE: 110 return "TYPE_AUX_LINE"; 111 case AudioDeviceInfo.TYPE_IP: 112 return "TYPE_IP"; 113 case AudioDeviceInfo.TYPE_BUS: 114 return "TYPE_BUS"; 115 case AudioDeviceInfo.TYPE_USB_HEADSET: 116 return "TYPE_USB_HEADSET"; 117 default: 118 return "TYPE_UNKNOWN"; 119 } 120 } 121 122 @TargetApi(Build.VERSION_CODES.N) audioSourceToString(int source)123 public static String audioSourceToString(int source) { 124 // AudioSource.UNPROCESSED requires API level 29. Use local define instead. 125 final int VOICE_PERFORMANCE = 10; 126 switch (source) { 127 case AudioSource.DEFAULT: 128 return "DEFAULT"; 129 case AudioSource.MIC: 130 return "MIC"; 131 case AudioSource.VOICE_UPLINK: 132 return "VOICE_UPLINK"; 133 case AudioSource.VOICE_DOWNLINK: 134 return "VOICE_DOWNLINK"; 135 case AudioSource.VOICE_CALL: 136 return "VOICE_CALL"; 137 case AudioSource.CAMCORDER: 138 return "CAMCORDER"; 139 case AudioSource.VOICE_RECOGNITION: 140 return "VOICE_RECOGNITION"; 141 case AudioSource.VOICE_COMMUNICATION: 142 return "VOICE_COMMUNICATION"; 143 case AudioSource.UNPROCESSED: 144 return "UNPROCESSED"; 145 case VOICE_PERFORMANCE: 146 return "VOICE_PERFORMANCE"; 147 default: 148 return "INVALID"; 149 } 150 } 151 channelMaskToString(int mask)152 public static String channelMaskToString(int mask) { 153 // For input or AudioRecord, the mask should be AudioFormat#CHANNEL_IN_MONO or 154 // AudioFormat#CHANNEL_IN_STEREO. AudioFormat#CHANNEL_IN_MONO is guaranteed to work on all 155 // devices. 156 switch (mask) { 157 case AudioFormat.CHANNEL_IN_STEREO: 158 return "IN_STEREO"; 159 case AudioFormat.CHANNEL_IN_MONO: 160 return "IN_MONO"; 161 default: 162 return "INVALID"; 163 } 164 } 165 166 @TargetApi(Build.VERSION_CODES.N) audioEncodingToString(int enc)167 public static String audioEncodingToString(int enc) { 168 switch (enc) { 169 case AudioFormat.ENCODING_INVALID: 170 return "INVALID"; 171 case AudioFormat.ENCODING_PCM_16BIT: 172 return "PCM_16BIT"; 173 case AudioFormat.ENCODING_PCM_8BIT: 174 return "PCM_8BIT"; 175 case AudioFormat.ENCODING_PCM_FLOAT: 176 return "PCM_FLOAT"; 177 case AudioFormat.ENCODING_AC3: 178 return "AC3"; 179 case AudioFormat.ENCODING_E_AC3: 180 return "AC3"; 181 case AudioFormat.ENCODING_DTS: 182 return "DTS"; 183 case AudioFormat.ENCODING_DTS_HD: 184 return "DTS_HD"; 185 case AudioFormat.ENCODING_MP3: 186 return "MP3"; 187 default: 188 return "Invalid encoding: " + enc; 189 } 190 } 191 192 // Reports basic audio statistics. logAudioStateBasic(String tag, Context context, AudioManager audioManager)193 private static void logAudioStateBasic(String tag, Context context, AudioManager audioManager) { 194 Logging.d(tag, 195 "Audio State: " 196 + "audio mode: " + modeToString(audioManager.getMode()) + ", " 197 + "has mic: " + hasMicrophone(context) + ", " 198 + "mic muted: " + audioManager.isMicrophoneMute() + ", " 199 + "music active: " + audioManager.isMusicActive() + ", " 200 + "speakerphone: " + audioManager.isSpeakerphoneOn() + ", " 201 + "BT SCO: " + audioManager.isBluetoothScoOn()); 202 } 203 isVolumeFixed(AudioManager audioManager)204 private static boolean isVolumeFixed(AudioManager audioManager) { 205 if (Build.VERSION.SDK_INT < 21) { 206 return false; 207 } 208 return audioManager.isVolumeFixed(); 209 } 210 211 // Adds volume information for all possible stream types. logAudioStateVolume(String tag, AudioManager audioManager)212 private static void logAudioStateVolume(String tag, AudioManager audioManager) { 213 final int[] streams = {AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_MUSIC, 214 AudioManager.STREAM_RING, AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION, 215 AudioManager.STREAM_SYSTEM}; 216 Logging.d(tag, "Audio State: "); 217 // Some devices may not have volume controls and might use a fixed volume. 218 boolean fixedVolume = isVolumeFixed(audioManager); 219 Logging.d(tag, " fixed volume=" + fixedVolume); 220 if (!fixedVolume) { 221 for (int stream : streams) { 222 StringBuilder info = new StringBuilder(); 223 info.append(" " + streamTypeToString(stream) + ": "); 224 info.append("volume=").append(audioManager.getStreamVolume(stream)); 225 info.append(", max=").append(audioManager.getStreamMaxVolume(stream)); 226 logIsStreamMute(tag, audioManager, stream, info); 227 Logging.d(tag, info.toString()); 228 } 229 } 230 } 231 logIsStreamMute( String tag, AudioManager audioManager, int stream, StringBuilder info)232 private static void logIsStreamMute( 233 String tag, AudioManager audioManager, int stream, StringBuilder info) { 234 if (Build.VERSION.SDK_INT >= 23) { 235 info.append(", muted=").append(audioManager.isStreamMute(stream)); 236 } 237 } 238 logAudioDeviceInfo(String tag, AudioManager audioManager)239 private static void logAudioDeviceInfo(String tag, AudioManager audioManager) { 240 if (Build.VERSION.SDK_INT < 23) { 241 return; 242 } 243 final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); 244 if (devices.length == 0) { 245 return; 246 } 247 Logging.d(tag, "Audio Devices: "); 248 for (AudioDeviceInfo device : devices) { 249 StringBuilder info = new StringBuilder(); 250 info.append(" ").append(deviceTypeToString(device.getType())); 251 info.append(device.isSource() ? "(in): " : "(out): "); 252 // An empty array indicates that the device supports arbitrary channel counts. 253 if (device.getChannelCounts().length > 0) { 254 info.append("channels=").append(Arrays.toString(device.getChannelCounts())); 255 info.append(", "); 256 } 257 if (device.getEncodings().length > 0) { 258 // Examples: ENCODING_PCM_16BIT = 2, ENCODING_PCM_FLOAT = 4. 259 info.append("encodings=").append(Arrays.toString(device.getEncodings())); 260 info.append(", "); 261 } 262 if (device.getSampleRates().length > 0) { 263 info.append("sample rates=").append(Arrays.toString(device.getSampleRates())); 264 info.append(", "); 265 } 266 info.append("id=").append(device.getId()); 267 Logging.d(tag, info.toString()); 268 } 269 } 270 271 // Converts media.AudioManager modes into local string representation. modeToString(int mode)272 static String modeToString(int mode) { 273 switch (mode) { 274 case MODE_IN_CALL: 275 return "MODE_IN_CALL"; 276 case MODE_IN_COMMUNICATION: 277 return "MODE_IN_COMMUNICATION"; 278 case MODE_NORMAL: 279 return "MODE_NORMAL"; 280 case MODE_RINGTONE: 281 return "MODE_RINGTONE"; 282 default: 283 return "MODE_INVALID"; 284 } 285 } 286 streamTypeToString(int stream)287 private static String streamTypeToString(int stream) { 288 switch (stream) { 289 case AudioManager.STREAM_VOICE_CALL: 290 return "STREAM_VOICE_CALL"; 291 case AudioManager.STREAM_MUSIC: 292 return "STREAM_MUSIC"; 293 case AudioManager.STREAM_RING: 294 return "STREAM_RING"; 295 case AudioManager.STREAM_ALARM: 296 return "STREAM_ALARM"; 297 case AudioManager.STREAM_NOTIFICATION: 298 return "STREAM_NOTIFICATION"; 299 case AudioManager.STREAM_SYSTEM: 300 return "STREAM_SYSTEM"; 301 default: 302 return "STREAM_INVALID"; 303 } 304 } 305 306 // Returns true if the device can record audio via a microphone. hasMicrophone(Context context)307 private static boolean hasMicrophone(Context context) { 308 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE); 309 } 310 } 311