From 2db1778d38064855f79ff76420de211e4c283540 Mon Sep 17 00:00:00 2001 From: henrika Date: Wed, 29 Nov 2017 13:34:08 +0100 Subject: [PATCH] Adds extended audio state logs to Android audio. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTRY=TRUE Bug: webrtc:8583 Change-Id: I2e9cb9354cc77c597a308b1f6c519c015a263842 Reviewed-on: https://webrtc-review.googlesource.com/25826 Commit-Queue: Henrik Andreassson Reviewed-by: Sami Kalliomäki Cr-Commit-Position: refs/heads/master@{#20934} --- .../voiceengine/WebRtcAudioManager.java | 8 +- .../webrtc/voiceengine/WebRtcAudioRecord.java | 6 +- .../webrtc/voiceengine/WebRtcAudioTrack.java | 4 + .../webrtc/voiceengine/WebRtcAudioUtils.java | 204 +++++++++++++++++- 4 files changed, 213 insertions(+), 9 deletions(-) diff --git a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioManager.java b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioManager.java index 12b2fad129..08979aa6fa 100644 --- a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioManager.java +++ b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioManager.java @@ -89,11 +89,6 @@ public class WebRtcAudioManager { private static final int DEFAULT_FRAME_PER_BUFFER = 256; - // List of possible audio modes. - private static final String[] AUDIO_MODES = new String[] { - "MODE_NORMAL", "MODE_RINGTONE", "MODE_IN_CALL", "MODE_IN_COMMUNICATION", - }; - // Private utility class that periodically checks and logs the volume level // of the audio stream that is currently controlled by the volume control. // A timer triggers logs once every 30 seconds and the timer's associated @@ -189,7 +184,8 @@ public class WebRtcAudioManager { if (initialized) { return true; } - Logging.d(TAG, "audio mode is: " + AUDIO_MODES[audioManager.getMode()]); + Logging.d(TAG, "audio mode is: " + + WebRtcAudioUtils.modeToString(audioManager.getMode())); initialized = true; volumeLogger.start(); return true; diff --git a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java index f9bdc5d212..73cf35d7cd 100644 --- a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java +++ b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java @@ -11,7 +11,6 @@ package org.webrtc.voiceengine; import android.annotation.TargetApi; -import android.content.Context; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder.AudioSource; @@ -19,7 +18,6 @@ import android.os.Process; import java.lang.System; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; -import org.webrtc.ContextUtils; import org.webrtc.Logging; import org.webrtc.ThreadUtils; @@ -252,6 +250,7 @@ public class WebRtcAudioRecord { audioThread.stopThread(); if (!ThreadUtils.joinUninterruptibly(audioThread, AUDIO_RECORD_THREAD_JOIN_TIMEOUT_MS)) { Logging.e(TAG, "Join of AudioRecordJavaThread timed out"); + WebRtcAudioUtils.logAudioState(TAG); } audioThread = null; if (effects != null) { @@ -320,6 +319,7 @@ public class WebRtcAudioRecord { private void reportWebRtcAudioRecordInitError(String errorMessage) { Logging.e(TAG, "Init recording error: " + errorMessage); + WebRtcAudioUtils.logAudioState(TAG); if (errorCallback != null) { errorCallback.onWebRtcAudioRecordInitError(errorMessage); } @@ -328,6 +328,7 @@ public class WebRtcAudioRecord { private void reportWebRtcAudioRecordStartError( AudioRecordStartErrorCode errorCode, String errorMessage) { Logging.e(TAG, "Start recording error: " + errorCode + ". " + errorMessage); + WebRtcAudioUtils.logAudioState(TAG); if (errorCallback != null) { errorCallback.onWebRtcAudioRecordStartError(errorCode, errorMessage); } @@ -335,6 +336,7 @@ public class WebRtcAudioRecord { private void reportWebRtcAudioRecordError(String errorMessage) { Logging.e(TAG, "Run-time recording error: " + errorMessage); + WebRtcAudioUtils.logAudioState(TAG); if (errorCallback != null) { errorCallback.onWebRtcAudioRecordError(errorMessage); } diff --git a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java index ae7a16f8f0..1d2f4afe2e 100644 --- a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java +++ b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java @@ -339,6 +339,7 @@ public class WebRtcAudioTrack { audioThread.interrupt(); if (!ThreadUtils.joinUninterruptibly(audioThread, AUDIO_TRACK_THREAD_JOIN_TIMEOUT_MS)) { Logging.e(TAG, "Join of AudioTrackThread timed out."); + WebRtcAudioUtils.logAudioState(TAG); } Logging.d(TAG, "AudioTrackThread has now been stopped."); audioThread = null; @@ -491,6 +492,7 @@ public class WebRtcAudioTrack { private void reportWebRtcAudioTrackInitError(String errorMessage) { Logging.e(TAG, "Init playout error: " + errorMessage); + WebRtcAudioUtils.logAudioState(TAG); if (errorCallback != null) { errorCallbackOld.onWebRtcAudioTrackInitError(errorMessage); } @@ -502,6 +504,7 @@ public class WebRtcAudioTrack { private void reportWebRtcAudioTrackStartError( AudioTrackStartErrorCode errorCode, String errorMessage) { Logging.e(TAG, "Start playout error: " + errorCode + ". " + errorMessage); + WebRtcAudioUtils.logAudioState(TAG); if (errorCallback != null) { errorCallbackOld.onWebRtcAudioTrackStartError(errorMessage); } @@ -512,6 +515,7 @@ public class WebRtcAudioTrack { private void reportWebRtcAudioTrackError(String errorMessage) { Logging.e(TAG, "Run-time playback error: " + errorMessage); + WebRtcAudioUtils.logAudioState(TAG); if (errorCallback != null) { errorCallbackOld.onWebRtcAudioTrackError(errorMessage); } diff --git a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java index 3d66923a01..da3e1f0281 100644 --- a/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java +++ b/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java @@ -10,13 +10,25 @@ package org.webrtc.voiceengine; +import static android.media.AudioManager.MODE_IN_CALL; +import static android.media.AudioManager.MODE_IN_COMMUNICATION; +import static android.media.AudioManager.MODE_NORMAL; +import static android.media.AudioManager.MODE_RINGTONE; + +import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageManager; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.AudioRecordingConfiguration; +import android.media.MediaRecorder.AudioSource; import android.os.Build; import android.os.Process; import java.lang.Thread; import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import org.webrtc.ContextUtils; import org.webrtc.Logging; public final class WebRtcAudioUtils { @@ -196,7 +208,7 @@ public final class WebRtcAudioUtils { } // Information about the current build, taken from system properties. - public static void logDeviceInfo(String tag) { + static void logDeviceInfo(String tag) { Logging.d(tag, "Android SDK: " + Build.VERSION.SDK_INT + ", " + "Release: " + Build.VERSION.RELEASE + ", " + "Brand: " + Build.BRAND + ", " @@ -207,4 +219,194 @@ public final class WebRtcAudioUtils { + "Model: " + Build.MODEL + ", " + "Product: " + Build.PRODUCT); } + + // Logs information about the current audio state. The idea is to call this + // method when errors are detected to log under what conditions the error + // occurred. Hopefully it will provide clues to what might be the root cause. + static void logAudioState(String tag) { + logDeviceInfo(tag); + final Context context = ContextUtils.getApplicationContext(); + final AudioManager audioManager = + (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + logAudioStateBasic(tag, audioManager); + logAudioStateVolume(tag, audioManager); + logAudioDeviceInfo(tag, audioManager); + } + + // Reports basic audio statistics. + private static void logAudioStateBasic(String tag, AudioManager audioManager) { + Logging.d(tag, "Audio State: " + + "audio mode: " + modeToString(audioManager.getMode()) + ", " + + "has mic: " + hasMicrophone() + ", " + + "mic muted: " + audioManager.isMicrophoneMute() + ", " + + "music active: " + audioManager.isMusicActive() + ", " + + "speakerphone: " + audioManager.isSpeakerphoneOn() + ", " + + "BT SCO: " + audioManager.isBluetoothScoOn()); + } + + // Adds volume information for all possible stream types. + private static void logAudioStateVolume(String tag, AudioManager audioManager) { + final int[] streams = { + AudioManager.STREAM_VOICE_CALL, + AudioManager.STREAM_MUSIC, + AudioManager.STREAM_RING, + AudioManager.STREAM_ALARM, + AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_SYSTEM + }; + Logging.d(tag, "Audio State: "); + boolean fixedVolume = false; + if (WebRtcAudioUtils.runningOnLollipopOrHigher()) { + fixedVolume = audioManager.isVolumeFixed(); + // Some devices may not have volume controls and might use a fixed volume. + Logging.d(tag, " fixed volume=" + fixedVolume); + } + if (!fixedVolume) { + for (int stream : streams) { + StringBuilder info = new StringBuilder(); + info.append(" " + streamTypeToString(stream) + ": "); + info.append("volume=").append(audioManager.getStreamVolume(stream)); + info.append(", max=").append(audioManager.getStreamMaxVolume(stream)); + logIsStreamMute(tag, audioManager, stream, info); + Logging.d(tag, info.toString()); + } + } + } + + @TargetApi(23) + private static void logIsStreamMute( + String tag, AudioManager audioManager, int stream, StringBuilder info) { + if (WebRtcAudioUtils.runningOnMarshmallowOrHigher()) { + info.append(", muted=").append(audioManager.isStreamMute(stream)); + } + } + + @TargetApi(23) + private static void logAudioDeviceInfo(String tag, AudioManager audioManager) { + if (!WebRtcAudioUtils.runningOnMarshmallowOrHigher()) { + return; + } + final AudioDeviceInfo[] devices = + audioManager.getDevices(AudioManager.GET_DEVICES_ALL); + if (devices.length == 0) { + return; + } + Logging.d(tag, "Audio Devices: "); + for (AudioDeviceInfo device : devices) { + StringBuilder info = new StringBuilder(); + info.append(" ").append(deviceTypeToString(device.getType())); + info.append(device.isSource() ? "(in): " : "(out): "); + // An empty array indicates that the device supports arbitrary channel counts. + if (device.getChannelCounts().length > 0) { + info.append("channels=").append(Arrays.toString(device.getChannelCounts())); + info.append(", "); + } + if (device.getEncodings().length > 0) { + // Examples: ENCODING_PCM_16BIT = 2, ENCODING_PCM_FLOAT = 4. + info.append("encodings=").append(Arrays.toString(device.getEncodings())); + info.append(", "); + } + if (device.getSampleRates().length > 0) { + info.append("sample rates=").append(Arrays.toString(device.getSampleRates())); + info.append(", "); + } + info.append("id=").append(device.getId()); + Logging.d(tag, info.toString()); + } + } + + // Converts media.AudioManager modes into local string representation. + static String modeToString(int mode) { + switch (mode) { + case MODE_IN_CALL: + return "MODE_IN_CALL"; + case MODE_IN_COMMUNICATION: + return "MODE_IN_COMMUNICATION"; + case MODE_NORMAL: + return "MODE_NORMAL"; + case MODE_RINGTONE: + return "MODE_RINGTONE"; + default: + return "MODE_INVALID"; + } + } + + private static String streamTypeToString(int stream) { + switch(stream) { + case AudioManager.STREAM_VOICE_CALL: + return "STREAM_VOICE_CALL"; + case AudioManager.STREAM_MUSIC: + return "STREAM_MUSIC"; + case AudioManager.STREAM_RING: + return "STREAM_RING"; + case AudioManager.STREAM_ALARM: + return "STREAM_ALARM"; + case AudioManager.STREAM_NOTIFICATION: + return "STREAM_NOTIFICATION"; + case AudioManager.STREAM_SYSTEM: + return "STREAM_SYSTEM"; + default: + return "STREAM_INVALID"; + } + } + + // Converts AudioDeviceInfo types to local string representation. + private static String deviceTypeToString(int type) { + switch (type) { + case AudioDeviceInfo.TYPE_UNKNOWN: + return "TYPE_UNKNOWN"; + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + return "TYPE_BUILTIN_EARPIECE"; + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + return "TYPE_BUILTIN_SPEAKER"; + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + return "TYPE_WIRED_HEADSET"; + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + return "TYPE_WIRED_HEADPHONES"; + case AudioDeviceInfo.TYPE_LINE_ANALOG: + return "TYPE_LINE_ANALOG"; + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + return "TYPE_LINE_DIGITAL"; + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + return "TYPE_BLUETOOTH_SCO"; + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + return "TYPE_BLUETOOTH_A2DP"; + case AudioDeviceInfo.TYPE_HDMI: + return "TYPE_HDMI"; + case AudioDeviceInfo.TYPE_HDMI_ARC: + return "TYPE_HDMI_ARC"; + case AudioDeviceInfo.TYPE_USB_DEVICE: + return "TYPE_USB_DEVICE"; + case AudioDeviceInfo.TYPE_USB_ACCESSORY: + return "TYPE_USB_ACCESSORY"; + case AudioDeviceInfo.TYPE_DOCK: + return "TYPE_DOCK"; + case AudioDeviceInfo.TYPE_FM: + return "TYPE_FM"; + case AudioDeviceInfo.TYPE_BUILTIN_MIC: + return "TYPE_BUILTIN_MIC"; + case AudioDeviceInfo.TYPE_FM_TUNER: + return "TYPE_FM_TUNER"; + case AudioDeviceInfo.TYPE_TV_TUNER: + return "TYPE_TV_TUNER"; + case AudioDeviceInfo.TYPE_TELEPHONY: + return "TYPE_TELEPHONY"; + case AudioDeviceInfo.TYPE_AUX_LINE: + return "TYPE_AUX_LINE"; + case AudioDeviceInfo.TYPE_IP: + return "TYPE_IP"; + case AudioDeviceInfo.TYPE_BUS: + return "TYPE_BUS"; + case AudioDeviceInfo.TYPE_USB_HEADSET: + return "TYPE_USB_HEADSET"; + default: + return "TYPE_UNKNOWN"; + } + } + + // Returns true if the device can record audio via a microphone. + private static boolean hasMicrophone() { + return ContextUtils.getApplicationContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_MICROPHONE); + } }