Reporting audio device underrun counter

Bug: webrtc:10884
Change-Id: I35636fcbc1e2a19a89242379cdff6ec5c12fd21a
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/149200
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Commit-Queue: Alex Narest <alexnarest@google.com>
Cr-Commit-Position: refs/heads/master@{#28874}
diff --git a/api/stats_types.cc b/api/stats_types.cc
index 441522e..7dcbd13 100644
--- a/api/stats_types.cc
+++ b/api/stats_types.cc
@@ -653,6 +653,8 @@
       return "googTypingNoiseState";
     case kStatsValueNameWritable:
       return "googWritable";
+    case kStatsValueNameAudioDeviceUnderrunCounter:
+      return "googAudioDeviceUnderrunCounter";
   }
 
   return nullptr;
diff --git a/api/stats_types.h b/api/stats_types.h
index 5b8ad4f..71bf164 100644
--- a/api/stats_types.h
+++ b/api/stats_types.h
@@ -238,6 +238,7 @@
     kStatsValueNameTransportType,
     kStatsValueNameTypingNoiseState,
     kStatsValueNameWritable,
+    kStatsValueNameAudioDeviceUnderrunCounter,
   };
 
   class IdBase : public rtc::RefCountInterface {
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 2909126..b0b0b88 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -687,6 +687,7 @@
   std::vector<VoiceReceiverInfo> receivers;
   RtpCodecParametersMap send_codecs;
   RtpCodecParametersMap receive_codecs;
+  int32_t device_underrun_count = 0;
 };
 
 struct VideoMediaInfo {
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 7e62bc6..07be793 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -2267,6 +2267,7 @@
     info->receive_codecs.insert(
         std::make_pair(codec_params.payload_type, std::move(codec_params)));
   }
+  info->device_underrun_count = engine_->adm()->GetPlayoutUnderrunCount();
 
   return true;
 }
diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc
index 91fcfeb..9556e5f 100644
--- a/media/engine/webrtc_voice_engine_unittest.cc
+++ b/media/engine/webrtc_voice_engine_unittest.cc
@@ -2243,6 +2243,7 @@
 
   // Check stats for the added streams.
   {
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     cricket::VoiceMediaInfo info;
     EXPECT_EQ(true, channel_->GetStats(&info));
 
@@ -2262,6 +2263,7 @@
   {
     cricket::VoiceMediaInfo info;
     EXPECT_TRUE(channel_->RemoveRecvStream(kSsrcY));
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     EXPECT_EQ(true, channel_->GetStats(&info));
     EXPECT_EQ(static_cast<size_t>(arraysize(kSsrcs4)), info.senders.size());
     EXPECT_EQ(0u, info.receivers.size());
@@ -2273,6 +2275,7 @@
     cricket::VoiceMediaInfo info;
     DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
     SetAudioReceiveStreamStats();
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     EXPECT_EQ(true, channel_->GetStats(&info));
     EXPECT_EQ(static_cast<size_t>(arraysize(kSsrcs4)), info.senders.size());
     EXPECT_EQ(1u, info.receivers.size());
@@ -2431,6 +2434,7 @@
 
   // Check stats for the added streams.
   {
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     cricket::VoiceMediaInfo info;
     EXPECT_EQ(true, channel_->GetStats(&info));
 
@@ -2446,6 +2450,7 @@
   {
     cricket::VoiceMediaInfo info;
     SetSend(true);
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     EXPECT_EQ(true, channel_->GetStats(&info));
     VerifyVoiceSenderInfo(info.senders[0], true);
     VerifyVoiceSendRecvCodecs(info);
@@ -2455,6 +2460,7 @@
   {
     cricket::VoiceMediaInfo info;
     EXPECT_TRUE(channel_->RemoveRecvStream(kSsrcY));
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     EXPECT_EQ(true, channel_->GetStats(&info));
     EXPECT_EQ(1u, info.senders.size());
     EXPECT_EQ(0u, info.receivers.size());
@@ -2466,6 +2472,7 @@
     cricket::VoiceMediaInfo info;
     DeliverPacket(kPcmuFrame, sizeof(kPcmuFrame));
     SetAudioReceiveStreamStats();
+    EXPECT_CALL(adm_, GetPlayoutUnderrunCount()).WillOnce(Return(0));
     EXPECT_EQ(true, channel_->GetStats(&info));
     EXPECT_EQ(1u, info.senders.size());
     EXPECT_EQ(1u, info.receivers.size());
diff --git a/modules/audio_device/audio_device_data_observer.cc b/modules/audio_device/audio_device_data_observer.cc
index 877d8d9..e81670e 100644
--- a/modules/audio_device/audio_device_data_observer.cc
+++ b/modules/audio_device/audio_device_data_observer.cc
@@ -261,6 +261,9 @@
   int32_t EnableBuiltInNS(bool enable) override {
     return impl_->EnableBuiltInNS(enable);
   }
+  int32_t GetPlayoutUnderrunCount() const override {
+    return impl_->GetPlayoutUnderrunCount();
+  }
 // Only supported on iOS.
 #if defined(WEBRTC_IOS)
   int GetPlayoutAudioParameters(AudioParameters* params) const override {
diff --git a/modules/audio_device/audio_device_generic.cc b/modules/audio_device/audio_device_generic.cc
index 13d359c..7b8cfd1 100644
--- a/modules/audio_device/audio_device_generic.cc
+++ b/modules/audio_device/audio_device_generic.cc
@@ -44,6 +44,11 @@
   return -1;
 }
 
+int32_t AudioDeviceGeneric::GetPlayoutUnderrunCount() const {
+  RTC_LOG_F(LS_ERROR) << "Not supported on this platform";
+  return -1;
+}
+
 #if defined(WEBRTC_IOS)
 int AudioDeviceGeneric::GetPlayoutAudioParameters(
     AudioParameters* params) const {
diff --git a/modules/audio_device/audio_device_generic.h b/modules/audio_device/audio_device_generic.h
index 7d3c83e..41e24eb 100644
--- a/modules/audio_device/audio_device_generic.h
+++ b/modules/audio_device/audio_device_generic.h
@@ -125,6 +125,9 @@
   virtual int32_t EnableBuiltInAGC(bool enable);
   virtual int32_t EnableBuiltInNS(bool enable);
 
+  // Play underrun count.
+  virtual int32_t GetPlayoutUnderrunCount() const;
+
 // iOS only.
 // TODO(henrika): add Android support.
 #if defined(WEBRTC_IOS)
diff --git a/modules/audio_device/audio_device_impl.cc b/modules/audio_device/audio_device_impl.cc
index aaba49a..95f9f03 100644
--- a/modules/audio_device/audio_device_impl.cc
+++ b/modules/audio_device/audio_device_impl.cc
@@ -910,6 +910,14 @@
   return ok;
 }
 
+int32_t AudioDeviceModuleImpl::GetPlayoutUnderrunCount() const {
+  RTC_LOG(INFO) << __FUNCTION__;
+  CHECKinitialized_();
+  int32_t underrunCount = audio_device_->GetPlayoutUnderrunCount();
+  RTC_LOG(INFO) << "output: " << underrunCount;
+  return underrunCount;
+}
+
 #if defined(WEBRTC_IOS)
 int AudioDeviceModuleImpl::GetPlayoutAudioParameters(
     AudioParameters* params) const {
diff --git a/modules/audio_device/audio_device_impl.h b/modules/audio_device/audio_device_impl.h
index 5a76595..45f73dc 100644
--- a/modules/audio_device/audio_device_impl.h
+++ b/modules/audio_device/audio_device_impl.h
@@ -137,6 +137,9 @@
   bool BuiltInNSIsAvailable() const override;
   int32_t EnableBuiltInNS(bool enable) override;
 
+  // Play underrun count.
+  int32_t GetPlayoutUnderrunCount() const override;
+
 #if defined(WEBRTC_IOS)
   int GetPlayoutAudioParameters(AudioParameters* params) const override;
   int GetRecordAudioParameters(AudioParameters* params) const override;
diff --git a/modules/audio_device/include/audio_device.h b/modules/audio_device/include/audio_device.h
index 04d53a8..42ba203 100644
--- a/modules/audio_device/include/audio_device.h
+++ b/modules/audio_device/include/audio_device.h
@@ -146,6 +146,10 @@
   virtual int32_t EnableBuiltInAGC(bool enable) = 0;
   virtual int32_t EnableBuiltInNS(bool enable) = 0;
 
+  // Play underrun count. Only supported on Android.
+  // TODO(alexnarest): Make it abstract after upstream projects support it.
+  virtual int32_t GetPlayoutUnderrunCount() const { return -1; }
+
 // Only supported on iOS.
 #if defined(WEBRTC_IOS)
   virtual int GetPlayoutAudioParameters(AudioParameters* params) const = 0;
diff --git a/modules/audio_device/include/audio_device_default.h b/modules/audio_device/include/audio_device_default.h
index 8b052fb..3779d6f 100644
--- a/modules/audio_device/include/audio_device_default.h
+++ b/modules/audio_device/include/audio_device_default.h
@@ -114,6 +114,8 @@
   bool BuiltInNSIsAvailable() const override { return false; }
   int32_t EnableBuiltInNS(bool enable) override { return -1; }
 
+  int32_t GetPlayoutUnderrunCount() const override { return -1; }
+
 #if defined(WEBRTC_IOS)
   int GetPlayoutAudioParameters(AudioParameters* params) const override {
     return -1;
diff --git a/modules/audio_device/include/mock_audio_device.h b/modules/audio_device/include/mock_audio_device.h
index 011886f..8f9d9b6 100644
--- a/modules/audio_device/include/mock_audio_device.h
+++ b/modules/audio_device/include/mock_audio_device.h
@@ -91,6 +91,7 @@
   MOCK_METHOD1(EnableBuiltInAEC, int32_t(bool enable));
   MOCK_METHOD1(EnableBuiltInAGC, int32_t(bool enable));
   MOCK_METHOD1(EnableBuiltInNS, int32_t(bool enable));
+  MOCK_CONST_METHOD0(GetPlayoutUnderrunCount, int32_t());
 #if defined(WEBRTC_IOS)
   MOCK_CONST_METHOD1(GetPlayoutAudioParameters, int(AudioParameters* params));
   MOCK_CONST_METHOD1(GetRecordAudioParameters, int(AudioParameters* params));
diff --git a/pc/stats_collector.cc b/pc/stats_collector.cc
index 37b4b4b..260f601 100644
--- a/pc/stats_collector.cc
+++ b/pc/stats_collector.cc
@@ -636,6 +636,14 @@
   return report;
 }
 
+StatsReport* StatsCollector::PrepareADMReport() {
+  RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
+  StatsReport::Id id(StatsReport::NewTypedId(
+      StatsReport::kStatsReportTypeSession, pc_->session_id()));
+  StatsReport* report = reports_.FindOrAddNew(id);
+  return report;
+}
+
 bool StatsCollector::IsValidTrack(const std::string& track_id) {
   return reports_.Find(StatsReport::NewTypedId(
              StatsReport::kStatsReportTypeTrack, track_id)) != nullptr;
@@ -956,6 +964,12 @@
   void ExtractStats(StatsCollector* collector) const override {
     ExtractSenderReceiverStats(collector, voice_media_info.receivers,
                                voice_media_info.senders);
+    if (voice_media_info.device_underrun_count == -2 ||
+        voice_media_info.device_underrun_count > 0) {
+      StatsReport* report = collector->PrepareADMReport();
+      report->AddInt(StatsReport::kStatsValueNameAudioDeviceUnderrunCounter,
+                     voice_media_info.device_underrun_count);
+    }
   }
 
   bool HasRemoteAudio() const override {
diff --git a/pc/stats_collector.h b/pc/stats_collector.h
index 569f1a6..fa9d587 100644
--- a/pc/stats_collector.h
+++ b/pc/stats_collector.h
@@ -84,6 +84,8 @@
                              const StatsReport::Id& transport_id,
                              StatsReport::Direction direction);
 
+  StatsReport* PrepareADMReport();
+
   // A track is invalid if there is no report data for it.
   bool IsValidTrack(const std::string& track_id);
 
diff --git a/pc/test/fake_audio_capture_module.h b/pc/test/fake_audio_capture_module.h
index 433fda0..0af3810 100644
--- a/pc/test/fake_audio_capture_module.h
+++ b/pc/test/fake_audio_capture_module.h
@@ -128,6 +128,8 @@
   int32_t EnableBuiltInAGC(bool enable) override { return -1; }
   bool BuiltInNSIsAvailable() const override { return false; }
   int32_t EnableBuiltInNS(bool enable) override { return -1; }
+
+  int32_t GetPlayoutUnderrunCount() const override { return -1; }
 #if defined(WEBRTC_IOS)
   int GetPlayoutAudioParameters(
       webrtc::AudioParameters* params) const override {
diff --git a/sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java b/sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java
index 16a6fcf..a00aec0 100644
--- a/sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java
+++ b/sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java
@@ -342,6 +342,19 @@
     return audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
   }
 
+  @CalledByNative
+  private int GetPlayoutUnderrunCount() {
+    if (Build.VERSION.SDK_INT >= 24) {
+      if (audioTrack != null) {
+        return audioTrack.getUnderrunCount();
+      } else {
+        return -1;
+      }
+    } else {
+      return -2;
+    }
+  }
+
   private void logMainParameters() {
     Logging.d(TAG,
         "AudioTrack: "
diff --git a/sdk/android/src/jni/audio_device/audio_device_module.cc b/sdk/android/src/jni/audio_device/audio_device_module.cc
index 0fcff9b..0008e7e 100644
--- a/sdk/android/src/jni/audio_device/audio_device_module.cc
+++ b/sdk/android/src/jni/audio_device/audio_device_module.cc
@@ -584,6 +584,12 @@
     return result;
   }
 
+  int32_t GetPlayoutUnderrunCount() const override {
+    if (!initialized_)
+      return -1;
+    return output_->GetPlayoutUnderrunCount();
+  }
+
   int32_t AttachAudioBuffer() {
     RTC_LOG(INFO) << __FUNCTION__;
     output_->AttachAudioBuffer(audio_device_buffer_.get());
diff --git a/sdk/android/src/jni/audio_device/audio_device_module.h b/sdk/android/src/jni/audio_device/audio_device_module.h
index 34979fe..1918336 100644
--- a/sdk/android/src/jni/audio_device/audio_device_module.h
+++ b/sdk/android/src/jni/audio_device/audio_device_module.h
@@ -65,6 +65,7 @@
   virtual absl::optional<uint32_t> MaxSpeakerVolume() const = 0;
   virtual absl::optional<uint32_t> MinSpeakerVolume() const = 0;
   virtual void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) = 0;
+  virtual int GetPlayoutUnderrunCount() = 0;
 };
 
 // Extract an android.media.AudioManager from an android.content.Context.
diff --git a/sdk/android/src/jni/audio_device/audio_track_jni.cc b/sdk/android/src/jni/audio_device/audio_track_jni.cc
index 856e18a..12e9fbf 100644
--- a/sdk/android/src/jni/audio_device/audio_track_jni.cc
+++ b/sdk/android/src/jni/audio_device/audio_track_jni.cc
@@ -169,6 +169,10 @@
   return volume;
 }
 
+int AudioTrackJni::GetPlayoutUnderrunCount() {
+  return Java_WebRtcAudioTrack_GetPlayoutUnderrunCount(env_, j_audio_track_);
+}
+
 // TODO(henrika): possibly add stereo support.
 void AudioTrackJni::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) {
   RTC_LOG(INFO) << "AttachAudioBuffer";
diff --git a/sdk/android/src/jni/audio_device/audio_track_jni.h b/sdk/android/src/jni/audio_device/audio_track_jni.h
index 2a76845..c7d0600 100644
--- a/sdk/android/src/jni/audio_device/audio_track_jni.h
+++ b/sdk/android/src/jni/audio_device/audio_track_jni.h
@@ -65,6 +65,7 @@
   absl::optional<uint32_t> SpeakerVolume() const override;
   absl::optional<uint32_t> MaxSpeakerVolume() const override;
   absl::optional<uint32_t> MinSpeakerVolume() const override;
+  int GetPlayoutUnderrunCount() override;
 
   void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override;
 
diff --git a/sdk/android/src/jni/audio_device/opensles_player.h b/sdk/android/src/jni/audio_device/opensles_player.h
index 4b8a0aa..a2a49f9 100644
--- a/sdk/android/src/jni/audio_device/opensles_player.h
+++ b/sdk/android/src/jni/audio_device/opensles_player.h
@@ -82,6 +82,8 @@
 
   void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override;
 
+  int GetPlayoutUnderrunCount() override { return -1; }
+
  private:
   // These callback methods are called when data is required for playout.
   // They are both called from an internal "OpenSL ES thread" which is not
diff --git a/sdk/objc/native/src/audio/audio_device_module_ios.h b/sdk/objc/native/src/audio/audio_device_module_ios.h
index 6e28c32..625eec2 100644
--- a/sdk/objc/native/src/audio/audio_device_module_ios.h
+++ b/sdk/objc/native/src/audio/audio_device_module_ios.h
@@ -125,6 +125,8 @@
   bool BuiltInNSIsAvailable() const override;
   int32_t EnableBuiltInNS(bool enable) override;
 
+  int32_t GetPlayoutUnderrunCount() const override;
+
 #if defined(WEBRTC_IOS)
   int GetPlayoutAudioParameters(AudioParameters* params) const override;
   int GetRecordAudioParameters(AudioParameters* params) const override;
diff --git a/sdk/objc/native/src/audio/audio_device_module_ios.mm b/sdk/objc/native/src/audio/audio_device_module_ios.mm
index e82a4e7..74d2965 100644
--- a/sdk/objc/native/src/audio/audio_device_module_ios.mm
+++ b/sdk/objc/native/src/audio/audio_device_module_ios.mm
@@ -642,6 +642,14 @@
     return ok;
   }
 
+  int32_t AudioDeviceModuleIOS::GetPlayoutUnderrunCount() const {
+    RTC_LOG(INFO) << __FUNCTION__;
+    CHECKinitialized_();
+    int32_t ok = audio_device_->GetPlayoutUnderrunCount();
+    RTC_LOG(INFO) << "output: " << ok;
+    return ok;
+  }
+
 #if defined(WEBRTC_IOS)
   int AudioDeviceModuleIOS::GetPlayoutAudioParameters(
       AudioParameters* params) const {