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 204 // Adds volume information for all possible stream types. logAudioStateVolume(String tag, AudioManager audioManager)205 private static void logAudioStateVolume(String tag, AudioManager audioManager) { 206 final int[] streams = {AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_MUSIC, 207 AudioManager.STREAM_RING, AudioManager.STREAM_ALARM, AudioManager.STREAM_NOTIFICATION, 208 AudioManager.STREAM_SYSTEM}; 209 Logging.d(tag, "Audio State: "); 210 // Some devices may not have volume controls and might use a fixed volume. 211 boolean fixedVolume = audioManager.isVolumeFixed(); 212 Logging.d(tag, " fixed volume=" + fixedVolume); 213 if (!fixedVolume) { 214 for (int stream : streams) { 215 StringBuilder info = new StringBuilder(); 216 info.append(" " + streamTypeToString(stream) + ": "); 217 info.append("volume=").append(audioManager.getStreamVolume(stream)); 218 info.append(", max=").append(audioManager.getStreamMaxVolume(stream)); 219 logIsStreamMute(tag, audioManager, stream, info); 220 Logging.d(tag, info.toString()); 221 } 222 } 223 } 224 logIsStreamMute( String tag, AudioManager audioManager, int stream, StringBuilder info)225 private static void logIsStreamMute( 226 String tag, AudioManager audioManager, int stream, StringBuilder info) { 227 if (Build.VERSION.SDK_INT >= 23) { 228 info.append(", muted=").append(audioManager.isStreamMute(stream)); 229 } 230 } 231 logAudioDeviceInfo(String tag, AudioManager audioManager)232 private static void logAudioDeviceInfo(String tag, AudioManager audioManager) { 233 if (Build.VERSION.SDK_INT < 23) { 234 return; 235 } 236 final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL); 237 if (devices.length == 0) { 238 return; 239 } 240 Logging.d(tag, "Audio Devices: "); 241 for (AudioDeviceInfo device : devices) { 242 StringBuilder info = new StringBuilder(); 243 info.append(" ").append(deviceTypeToString(device.getType())); 244 info.append(device.isSource() ? "(in): " : "(out): "); 245 // An empty array indicates that the device supports arbitrary channel counts. 246 if (device.getChannelCounts().length > 0) { 247 info.append("channels=").append(Arrays.toString(device.getChannelCounts())); 248 info.append(", "); 249 } 250 if (device.getEncodings().length > 0) { 251 // Examples: ENCODING_PCM_16BIT = 2, ENCODING_PCM_FLOAT = 4. 252 info.append("encodings=").append(Arrays.toString(device.getEncodings())); 253 info.append(", "); 254 } 255 if (device.getSampleRates().length > 0) { 256 info.append("sample rates=").append(Arrays.toString(device.getSampleRates())); 257 info.append(", "); 258 } 259 info.append("id=").append(device.getId()); 260 Logging.d(tag, info.toString()); 261 } 262 } 263 264 // Converts media.AudioManager modes into local string representation. modeToString(int mode)265 static String modeToString(int mode) { 266 switch (mode) { 267 case MODE_IN_CALL: 268 return "MODE_IN_CALL"; 269 case MODE_IN_COMMUNICATION: 270 return "MODE_IN_COMMUNICATION"; 271 case MODE_NORMAL: 272 return "MODE_NORMAL"; 273 case MODE_RINGTONE: 274 return "MODE_RINGTONE"; 275 default: 276 return "MODE_INVALID"; 277 } 278 } 279 streamTypeToString(int stream)280 private static String streamTypeToString(int stream) { 281 switch (stream) { 282 case AudioManager.STREAM_VOICE_CALL: 283 return "STREAM_VOICE_CALL"; 284 case AudioManager.STREAM_MUSIC: 285 return "STREAM_MUSIC"; 286 case AudioManager.STREAM_RING: 287 return "STREAM_RING"; 288 case AudioManager.STREAM_ALARM: 289 return "STREAM_ALARM"; 290 case AudioManager.STREAM_NOTIFICATION: 291 return "STREAM_NOTIFICATION"; 292 case AudioManager.STREAM_SYSTEM: 293 return "STREAM_SYSTEM"; 294 default: 295 return "STREAM_INVALID"; 296 } 297 } 298 299 // Returns true if the device can record audio via a microphone. hasMicrophone(Context context)300 private static boolean hasMicrophone(Context context) { 301 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE); 302 } 303 } 304