Improves audio logs of native audio layers on Android

BUG=webrtc:6592,webrtc:6580

Review-Url: https://codereview.webrtc.org/2447683002
Cr-Commit-Position: refs/heads/master@{#14798}
diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java
index 939caf7..3bda070 100644
--- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java
+++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioRecord.java
@@ -194,26 +194,16 @@
       Logging.e(TAG, "Failed to create a new AudioRecord instance");
       return -1;
     }
-    Logging.d(TAG, "AudioRecord "
-            + "session ID: " + audioRecord.getAudioSessionId() + ", "
-            + "audio format: " + audioRecord.getAudioFormat() + ", "
-            + "channels: " + audioRecord.getChannelCount() + ", "
-            + "sample rate: " + audioRecord.getSampleRate());
     if (effects != null) {
       effects.enable(audioRecord.getAudioSessionId());
     }
-    // TODO(phoglund): put back audioRecord.getBufferSizeInFrames when
-    // all known downstream users supports M.
-    // if (WebRtcAudioUtils.runningOnMOrHigher()) {
-    // Returns the frame count of the native AudioRecord buffer. This is
-    // greater than or equal to the bufferSizeInBytes converted to frame
-    // units. The native frame count may be enlarged to accommodate the
-    // requirements of the source on creation or if the AudioRecord is
-    // subsequently rerouted.
-
-    // Logging.d(TAG, "bufferSizeInFrames: "
-    //     + audioRecord.getBufferSizeInFrames());
-    //}
+    // Verify that all audio parameters are valid and correct.
+    if (!areParametersValid(sampleRate, channels)) {
+      Logging.e(TAG, "At least one audio record parameter is invalid.");
+      return -1;
+    }
+    logMainParameters();
+    logMainParametersExtended();
     return framesPerBuffer;
   }
 
@@ -252,6 +242,30 @@
     return true;
   }
 
+  // Verifies that the audio record is using correct parameters, i.e., that the
+  // created instance uses the parameters that we asked for.
+  private boolean areParametersValid(int sampleRate, int channels) {
+    return (audioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT
+        && audioRecord.getChannelConfiguration() == AudioFormat.CHANNEL_IN_MONO
+        && audioRecord.getAudioSource() == AudioSource.VOICE_COMMUNICATION
+        && audioRecord.getSampleRate() == sampleRate && audioRecord.getChannelCount() == channels);
+  }
+
+  private void logMainParameters() {
+    Logging.d(TAG, "AudioRecord: "
+            + "session ID: " + audioRecord.getAudioSessionId() + ", "
+            + "channels: " + audioRecord.getChannelCount() + ", "
+            + "sample rate: " + audioRecord.getSampleRate());
+  }
+
+  private void logMainParametersExtended() {
+    if (WebRtcAudioUtils.runningOnMarshmallowOrHigher()) {
+      Logging.d(TAG, "AudioRecord: "
+              // The frame count of the native AudioRecord buffer.
+              + "buffer size in frames: " + audioRecord.getBufferSizeInFrames());
+    }
+  }
+
   // Helper method which throws an exception  when an assertion has failed.
   private static void assertTrue(boolean condition) {
     if (!condition) {
diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java
index c287431..7af1af0 100644
--- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java
+++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioTrack.java
@@ -12,7 +12,6 @@
 
 import org.webrtc.Logging;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -130,7 +129,6 @@
       audioTrack.flush();
     }
 
-    @TargetApi(21)
     private int writeOnLollipop(AudioTrack audioTrack, ByteBuffer byteBuffer, int sizeInBytes) {
       return audioTrack.write(byteBuffer, sizeInBytes, AudioTrack.WRITE_BLOCKING);
     }
@@ -214,6 +212,13 @@
       Logging.e(TAG, "Initialization of audio track failed.");
       return false;
     }
+    // Verify that all audio parameters are valid and correct.
+    if (!areParametersValid(sampleRate, channels)) {
+      Logging.e(TAG, "At least one audio track parameter is invalid.");
+      return false;
+    }
+    logMainParameters();
+    logMainParametersExtended();
     return true;
   }
 
@@ -233,6 +238,7 @@
   private boolean stopPlayout() {
     Logging.d(TAG, "stopPlayout");
     assertTrue(audioThread != null);
+    logUnderrunCount();
     audioThread.joinThread();
     audioThread = null;
     if (audioTrack != null) {
@@ -242,14 +248,14 @@
     return true;
   }
 
-  /** Get max possible volume index for a phone call audio stream. */
+  // Get max possible volume index for a phone call audio stream.
   private int getStreamMaxVolume() {
     Logging.d(TAG, "getStreamMaxVolume");
     assertTrue(audioManager != null);
     return audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
   }
 
-  /** Set current volume level for a phone call audio stream. */
+  // Set current volume level for a phone call audio stream.
   private boolean setStreamVolume(int volume) {
     Logging.d(TAG, "setStreamVolume(" + volume + ")");
     assertTrue(audioManager != null);
@@ -261,7 +267,6 @@
     return true;
   }
 
-  @TargetApi(21)
   private boolean isVolumeFixed() {
     if (!WebRtcAudioUtils.runningOnLollipopOrHigher())
       return false;
@@ -275,7 +280,52 @@
     return audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
   }
 
-  /** Helper method which throws an exception  when an assertion has failed. */
+  // Verifies that the audio track is using correct parameters, i.e., that the
+  // created track uses the parameters that we asked for.
+  private boolean areParametersValid(int sampleRate, int channels) {
+    final int streamType = audioTrack.getStreamType();
+    return (audioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT
+        && audioTrack.getChannelConfiguration() == AudioFormat.CHANNEL_OUT_MONO
+        && streamType == AudioManager.STREAM_VOICE_CALL && audioTrack.getSampleRate() == sampleRate
+        && sampleRate == audioTrack.getNativeOutputSampleRate(streamType)
+        && audioTrack.getChannelCount() == channels);
+  }
+
+  private void logMainParameters() {
+    Logging.d(TAG, "AudioTrack: "
+            + "session ID: " + audioTrack.getAudioSessionId() + ", "
+            + "channels: " + audioTrack.getChannelCount() + ", "
+            + "sample rate: " + audioTrack.getSampleRate() + ", "
+            // Gain (>=1.0) expressed as linear multiplier on sample values.
+            + "max gain: " + audioTrack.getMaxVolume());
+  }
+
+  private void logMainParametersExtended() {
+    if (WebRtcAudioUtils.runningOnMarshmallowOrHigher()) {
+      Logging.d(TAG, "AudioTrack: "
+              // The effective size of the AudioTrack buffer that the app writes to.
+              + "buffer size in frames: " + audioTrack.getBufferSizeInFrames());
+    }
+    if (WebRtcAudioUtils.runningOnNougatOrHigher()) {
+      Logging.d(TAG, "AudioTrack: "
+              // Maximum size of the AudioTrack buffer in frames.
+              + "buffer capacity in frames: " + audioTrack.getBufferCapacityInFrames());
+    }
+  }
+
+  // Prints the number of underrun occurrences in the application-level write
+  // buffer since the AudioTrack was created. An underrun occurs if the app does
+  // not write audio data quickly enough, causing the buffer to underflow and a
+  // potential audio glitch.
+  // TODO(henrika): keep track of this value in the field and possibly add new
+  // UMA stat if needed.
+  private void logUnderrunCount() {
+    if (WebRtcAudioUtils.runningOnNougatOrHigher()) {
+      Logging.d(TAG, "underrun count: " + audioTrack.getUnderrunCount());
+    }
+  }
+
+  // Helper method which throws an exception  when an assertion has failed.
   private static void assertTrue(boolean condition) {
     if (!condition) {
       throw new AssertionError("Expected condition to be true");
diff --git a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java
index 677569c..4983d1d 100644
--- a/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java
+++ b/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/WebRtcAudioUtils.java
@@ -159,6 +159,11 @@
     return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
   }
 
+  public static boolean runningOnNougatOrHigher() {
+    // API Level 24.
+    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+  }
+
   // Helper method for building a string of thread information.
   public static String getThreadInfo() {
     return "@[name=" + Thread.currentThread().getName() + ", id=" + Thread.currentThread().getId()