diff --git a/api/stats_types.cc b/api/stats_types.cc index 441522e051..7dcbd134a1 100644 --- a/api/stats_types.cc +++ b/api/stats_types.cc @@ -653,6 +653,8 @@ const char* StatsReport::Value::display_name() const { 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 5b8ad4fb23..71bf164a89 100644 --- a/api/stats_types.h +++ b/api/stats_types.h @@ -238,6 +238,7 @@ class StatsReport { kStatsValueNameTransportType, kStatsValueNameTypingNoiseState, kStatsValueNameWritable, + kStatsValueNameAudioDeviceUnderrunCounter, }; class IdBase : public rtc::RefCountInterface { diff --git a/media/base/media_channel.h b/media/base/media_channel.h index 2909126794..b0b0b88cee 100644 --- a/media/base/media_channel.h +++ b/media/base/media_channel.h @@ -687,6 +687,7 @@ struct VoiceMediaInfo { std::vector 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 7e62bc64ac..07be79333d 100644 --- a/media/engine/webrtc_voice_engine.cc +++ b/media/engine/webrtc_voice_engine.cc @@ -2267,6 +2267,7 @@ bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info) { 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 91fcfeb549..9556e5f662 100644 --- a/media/engine/webrtc_voice_engine_unittest.cc +++ b/media/engine/webrtc_voice_engine_unittest.cc @@ -2243,6 +2243,7 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStatsWithMultipleSendStreams) { // 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 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStatsWithMultipleSendStreams) { { 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(arraysize(kSsrcs4)), info.senders.size()); EXPECT_EQ(0u, info.receivers.size()); @@ -2273,6 +2275,7 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStatsWithMultipleSendStreams) { 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(arraysize(kSsrcs4)), info.senders.size()); EXPECT_EQ(1u, info.receivers.size()); @@ -2431,6 +2434,7 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStats) { // 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 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStats) { { 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 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStats) { { 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 @@ TEST_F(WebRtcVoiceEngineTestFake, GetStats) { 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 877d8d96dd..e81670ef00 100644 --- a/modules/audio_device/audio_device_data_observer.cc +++ b/modules/audio_device/audio_device_data_observer.cc @@ -261,6 +261,9 @@ class ADMWrapper : public AudioDeviceModule, public AudioTransport { 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 13d359c9c4..7b8cfd1734 100644 --- a/modules/audio_device/audio_device_generic.cc +++ b/modules/audio_device/audio_device_generic.cc @@ -44,6 +44,11 @@ int32_t AudioDeviceGeneric::EnableBuiltInNS(bool enable) { 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 7d3c83e119..41e24eb3b0 100644 --- a/modules/audio_device/audio_device_generic.h +++ b/modules/audio_device/audio_device_generic.h @@ -125,6 +125,9 @@ class AudioDeviceGeneric { 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 aaba49a46e..95f9f03993 100644 --- a/modules/audio_device/audio_device_impl.cc +++ b/modules/audio_device/audio_device_impl.cc @@ -910,6 +910,14 @@ int32_t AudioDeviceModuleImpl::EnableBuiltInNS(bool enable) { 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 5a765957bd..45f73dcd65 100644 --- a/modules/audio_device/audio_device_impl.h +++ b/modules/audio_device/audio_device_impl.h @@ -137,6 +137,9 @@ class AudioDeviceModuleImpl : public AudioDeviceModuleForTest { 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 04d53a81de..42ba2037cc 100644 --- a/modules/audio_device/include/audio_device.h +++ b/modules/audio_device/include/audio_device.h @@ -146,6 +146,10 @@ class AudioDeviceModule : public rtc::RefCountInterface { 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 8b052fb340..3779d6fb3b 100644 --- a/modules/audio_device/include/audio_device_default.h +++ b/modules/audio_device/include/audio_device_default.h @@ -114,6 +114,8 @@ class AudioDeviceModuleDefault : public T { 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 011886fc72..8f9d9b61db 100644 --- a/modules/audio_device/include/mock_audio_device.h +++ b/modules/audio_device/include/mock_audio_device.h @@ -91,6 +91,7 @@ class MockAudioDeviceModule : public AudioDeviceModule { 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 37b4b4b3d9..260f601a23 100644 --- a/pc/stats_collector.cc +++ b/pc/stats_collector.cc @@ -636,6 +636,14 @@ StatsReport* StatsCollector::PrepareReport(bool local, 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 @@ class VoiceMediaChannelStatsGatherer final : public MediaChannelStatsGatherer { 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 569f1a6b96..fa9d587a67 100644 --- a/pc/stats_collector.h +++ b/pc/stats_collector.h @@ -84,6 +84,8 @@ class StatsCollector { 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 433fda0362..0af3810290 100644 --- a/pc/test/fake_audio_capture_module.h +++ b/pc/test/fake_audio_capture_module.h @@ -128,6 +128,8 @@ class FakeAudioCaptureModule : public webrtc::AudioDeviceModule, 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 16a6fcf55a..a00aec01ed 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 @@ class WebRtcAudioTrack { 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 0fcff9b50b..0008e7ef9d 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 @@ class AndroidAudioDeviceModule : public AudioDeviceModule { 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 34979fe933..1918336c5a 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 @@ class AudioOutput { virtual absl::optional MaxSpeakerVolume() const = 0; virtual absl::optional 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 856e18abbe..12e9fbf834 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 @@ absl::optional AudioTrackJni::SpeakerVolume() const { 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 2a7684510b..c7d060033f 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 @@ class AudioTrackJni : public AudioOutput { absl::optional SpeakerVolume() const override; absl::optional MaxSpeakerVolume() const override; absl::optional 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 4b8a0aaf2f..a2a49f986f 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 @@ class OpenSLESPlayer : public AudioOutput { 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 6e28c32dd4..625eec284e 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 @@ class AudioDeviceModuleIOS : public AudioDeviceModule { 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 e82a4e7c20..74d29651dd 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 @@ AudioDeviceModuleIOS::AudioDeviceModuleIOS() 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 {