From efbe75361705198cab3f4eb3c12368fd3b7930ef Mon Sep 17 00:00:00 2001 From: Fredrik Hernqvist Date: Fri, 13 Jan 2023 16:42:36 +0100 Subject: [PATCH] Add RTCAudioPlayoutStats to GetStats(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is done by allowing implementations of AudioDeviceModule to implement the GetStats() method. The default implementation returns nullopt, in which case RTCAudioPlayoutStats will not be visible in the stats. Bug: webrtc:14653 Change-Id: I8e4aa6f1b8fcfa47a30f633d28a4013191752e20 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/290563 Reviewed-by: Henrik Boström Commit-Queue: Fredrik Hernqvist Reviewed-by: Henrik Andreassson Reviewed-by: Olga Sharonova Cr-Commit-Position: refs/heads/main@{#39115} --- api/stats/rtcstats_objects.h | 16 ++++++++++++ media/BUILD.gn | 2 ++ media/base/fake_media_engine.cc | 5 ++++ media/base/fake_media_engine.h | 2 ++ media/base/media_engine.h | 3 +++ media/engine/webrtc_voice_engine.cc | 5 ++++ media/engine/webrtc_voice_engine.h | 3 +++ modules/audio_device/BUILD.gn | 1 + modules/audio_device/include/audio_device.h | 15 +++++++++++ pc/BUILD.gn | 4 +++ pc/peer_connection.cc | 8 ++++++ pc/peer_connection.h | 2 ++ pc/peer_connection_internal.h | 4 +++ pc/rtc_stats_collector.cc | 29 +++++++++++++++++++++ pc/rtc_stats_collector.h | 6 +++++ pc/rtc_stats_collector_unittest.cc | 26 ++++++++++++++++++ pc/test/fake_peer_connection_base.h | 5 ++++ pc/test/fake_peer_connection_for_stats.h | 11 ++++++++ pc/test/mock_peer_connection_internal.h | 5 ++++ stats/rtcstats_objects.cc | 23 ++++++++++++++++ 20 files changed, 175 insertions(+) diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h index 47bca78b74..96114dfa72 100644 --- a/api/stats/rtcstats_objects.h +++ b/api/stats/rtcstats_objects.h @@ -699,6 +699,22 @@ class RTC_EXPORT RTCTransportStats final : public RTCStats { RTCStatsMember ice_state; }; +// https://w3c.github.io/webrtc-stats/#playoutstats-dict* +class RTC_EXPORT RTCAudioPlayoutStats final : public RTCStats { + public: + WEBRTC_RTCSTATS_DECL(); + + RTCAudioPlayoutStats(const std::string& id, Timestamp timestamp); + RTCAudioPlayoutStats(const RTCAudioPlayoutStats& other); + ~RTCAudioPlayoutStats() override; + + RTCStatsMember synthesized_samples_duration; + RTCStatsMember synthesized_samples_events; + RTCStatsMember total_samples_duration; + RTCStatsMember total_playout_delay; + RTCStatsMember total_samples_count; +}; + } // namespace webrtc #endif // API_STATS_RTCSTATS_OBJECTS_H_ diff --git a/media/BUILD.gn b/media/BUILD.gn index 4d8c67eead..4ab0b114db 100644 --- a/media/BUILD.gn +++ b/media/BUILD.gn @@ -90,6 +90,7 @@ rtc_library("rtc_media_base") { "../call:video_stream_api", "../common_video", "../modules/async_audio_processing", + "../modules/audio_device", "../modules/audio_processing:audio_processing_statistics", "../modules/rtp_rtcp:rtp_rtcp_format", "../rtc_base", @@ -749,6 +750,7 @@ if (rtc_include_tests) { absl_deps = [ "//third_party/abseil-cpp/absl/algorithm:container", "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", ] sources = [ "base/fake_frame_source.cc", diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc index 68e0dd07e0..d4d24cda85 100644 --- a/media/base/fake_media_engine.cc +++ b/media/base/fake_media_engine.cc @@ -15,6 +15,7 @@ #include "absl/algorithm/container.h" #include "absl/strings/match.h" +#include "absl/types/optional.h" #include "rtc_base/checks.h" namespace cricket { @@ -490,6 +491,10 @@ bool FakeVoiceEngine::StartAecDump(webrtc::FileWrapper file, int64_t max_size_bytes) { return false; } +absl::optional +FakeVoiceEngine::GetAudioDeviceStats() { + return absl::nullopt; +} void FakeVoiceEngine::StopAecDump() {} std::vector diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h index 82795a504f..082c7f37f9 100644 --- a/media/base/fake_media_engine.h +++ b/media/base/fake_media_engine.h @@ -537,6 +537,8 @@ class FakeVoiceEngine : public VoiceEngineInterface { int GetInputLevel(); bool StartAecDump(webrtc::FileWrapper file, int64_t max_size_bytes) override; void StopAecDump() override; + absl::optional GetAudioDeviceStats() + override; std::vector GetRtpHeaderExtensions() const override; void SetRtpHeaderExtensions( diff --git a/media/base/media_engine.h b/media/base/media_engine.h index 521b8241e5..cc7563a85e 100644 --- a/media/base/media_engine.h +++ b/media/base/media_engine.h @@ -116,6 +116,9 @@ class VoiceEngineInterface : public RtpHeaderExtensionQueryInterface { // Stops recording AEC dump. virtual void StopAecDump() = 0; + + virtual absl::optional + GetAudioDeviceStats() = 0; }; class VideoEngineInterface : public RtpHeaderExtensionQueryInterface { diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc index c425916114..78861b21d6 100644 --- a/media/engine/webrtc_voice_engine.cc +++ b/media/engine/webrtc_voice_engine.cc @@ -626,6 +626,11 @@ void WebRtcVoiceEngine::StopAecDump() { } } +absl::optional +WebRtcVoiceEngine::GetAudioDeviceStats() { + return adm()->GetStats(); +} + webrtc::AudioDeviceModule* WebRtcVoiceEngine::adm() { RTC_DCHECK_RUN_ON(&worker_thread_checker_); RTC_DCHECK(adm_); diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h index 25f8e3dc1e..96e07cd9ff 100644 --- a/media/engine/webrtc_voice_engine.h +++ b/media/engine/webrtc_voice_engine.h @@ -88,6 +88,9 @@ class WebRtcVoiceEngine final : public VoiceEngineInterface { // Stops AEC dump. void StopAecDump() override; + absl::optional GetAudioDeviceStats() + override; + private: // Every option that is "set" will be applied. Every option not "set" will be // ignored. This allows us to selectively turn on and off different options diff --git a/modules/audio_device/BUILD.gn b/modules/audio_device/BUILD.gn index 6c7292aba1..4a6a0ab41c 100644 --- a/modules/audio_device/BUILD.gn +++ b/modules/audio_device/BUILD.gn @@ -54,6 +54,7 @@ rtc_source_set("audio_device_api") { "../../rtc_base:refcount", "../../rtc_base:stringutils", ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] } rtc_library("audio_device_buffer") { diff --git a/modules/audio_device/include/audio_device.h b/modules/audio_device/include/audio_device.h index f82029eb51..936ee6cb04 100644 --- a/modules/audio_device/include/audio_device.h +++ b/modules/audio_device/include/audio_device.h @@ -11,6 +11,7 @@ #ifndef MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_H_ #define MODULES_AUDIO_DEVICE_INCLUDE_AUDIO_DEVICE_H_ +#include "absl/types/optional.h" #include "api/scoped_refptr.h" #include "api/task_queue/task_queue_factory.h" #include "modules/audio_device/include/audio_device_defines.h" @@ -41,6 +42,16 @@ class AudioDeviceModule : public rtc::RefCountInterface { kDefaultDevice = -2 }; + struct Stats { + // The fields below correspond to similarly-named fields in the WebRTC stats + // spec. https://w3c.github.io/webrtc-stats/#playoutstats-dict* + double synthesized_samples_duration_s = 0; + uint64_t synthesized_samples_events = 0; + double total_samples_duration_s = 0; + double total_playout_delay_s = 0; + uint64_t total_samples_count = 0; + }; + public: // Creates a default ADM for usage in production code. static rtc::scoped_refptr Create( @@ -150,6 +161,10 @@ class AudioDeviceModule : public rtc::RefCountInterface { // TODO(alexnarest): Make it abstract after upstream projects support it. virtual int32_t GetPlayoutUnderrunCount() const { return -1; } + // Used to generate RTC stats. If not implemented, RTCAudioPlayoutStats will + // not be present in the stats. + virtual absl::optional GetStats() const { return absl::nullopt; } + // Only supported on iOS. #if defined(WEBRTC_IOS) virtual int GetPlayoutAudioParameters(AudioParameters* params) const = 0; diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 2ecf4b230d..176f8c5acb 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -1015,7 +1015,10 @@ rtc_source_set("peer_connection_internal") { ":sctp_data_channel", "../api:libjingle_peerconnection_api", "../call:call_interfaces", + "../modules/audio_device", ] + + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] } rtc_source_set("rtc_stats_collector") { @@ -1060,6 +1063,7 @@ rtc_source_set("rtc_stats_collector") { "../media:media_channel", "../media:media_channel_impl", "../media:rtc_media_base", + "../modules/audio_device", "../modules/audio_processing:audio_processing_statistics", "../modules/rtp_rtcp:rtp_rtcp_format", "../p2p:rtc_p2p", diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index 622870cfca..5633be352e 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc @@ -22,6 +22,7 @@ #include "absl/algorithm/container.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "api/jsep_ice_candidate.h" #include "api/rtp_parameters.h" #include "api/rtp_transceiver_direction.h" @@ -2500,6 +2501,13 @@ Call::Stats PeerConnection::GetCallStats() { } } +absl::optional PeerConnection::GetAudioDeviceStats() { + if (context_->media_engine()) { + return context_->media_engine()->voice().GetAudioDeviceStats(); + } + return absl::nullopt; +} + bool PeerConnection::SetupDataChannelTransport_n(const std::string& mid) { DataChannelTransportInterface* transport = transport_controller_->GetDataChannelTransport(mid); diff --git a/pc/peer_connection.h b/pc/peer_connection.h index 39a240da4f..a4b21f0e01 100644 --- a/pc/peer_connection.h +++ b/pc/peer_connection.h @@ -298,6 +298,8 @@ class PeerConnection : public PeerConnectionInternal, const std::set& transport_names) override; Call::Stats GetCallStats() override; + absl::optional GetAudioDeviceStats() override; + bool GetLocalCertificate( const std::string& transport_name, rtc::scoped_refptr* certificate) override; diff --git a/pc/peer_connection_internal.h b/pc/peer_connection_internal.h index ecf8fbfc83..1085ff94b1 100644 --- a/pc/peer_connection_internal.h +++ b/pc/peer_connection_internal.h @@ -17,8 +17,10 @@ #include #include +#include "absl/types/optional.h" #include "api/peer_connection_interface.h" #include "call/call.h" +#include "modules/audio_device/include/audio_device.h" #include "pc/jsep_transport_controller.h" #include "pc/peer_connection_message_handler.h" #include "pc/rtp_transceiver.h" @@ -162,6 +164,8 @@ class PeerConnectionInternal : public PeerConnectionInterface, virtual Call::Stats GetCallStats() = 0; + virtual absl::optional GetAudioDeviceStats() = 0; + virtual bool GetLocalCertificate( const std::string& transport_name, rtc::scoped_refptr* certificate) = 0; diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc index 6a45b58eb5..508661ec8a 100644 --- a/pc/rtc_stats_collector.cc +++ b/pc/rtc_stats_collector.cc @@ -37,6 +37,7 @@ #include "common_video/include/quality_limitation_reason.h" #include "media/base/media_channel.h" #include "media/base/media_channel_impl.h" +#include "modules/audio_device/include/audio_device.h" #include "modules/audio_processing/include/audio_processing_statistics.h" #include "modules/rtp_rtcp/include/report_block_data.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" @@ -513,6 +514,21 @@ std::unique_ptr CreateInboundAudioStreamStats( return inbound_audio; } +std::unique_ptr CreateAudioPlayoutStats( + const AudioDeviceModule::Stats& audio_device_stats, + webrtc::Timestamp timestamp) { + auto stats = std::make_unique( + /*id=*/"AP", timestamp); + stats->synthesized_samples_duration = + audio_device_stats.synthesized_samples_duration_s; + stats->synthesized_samples_events = + audio_device_stats.synthesized_samples_events; + stats->total_samples_count = audio_device_stats.total_samples_count; + stats->total_samples_duration = audio_device_stats.total_samples_duration_s; + stats->total_playout_delay = audio_device_stats.total_playout_delay_s; + return stats; +} + std::unique_ptr CreateRemoteOutboundAudioStreamStats( const cricket::VoiceReceiverInfo& voice_receiver_info, @@ -1486,6 +1502,7 @@ void RTCStatsCollector::ProducePartialResultsOnSignalingThreadImpl( ProduceMediaStreamTrackStats_s(timestamp, partial_report); ProduceMediaSourceStats_s(timestamp, partial_report); ProducePeerConnectionStats_s(timestamp, partial_report); + ProduceAudioPlayoutStats_s(timestamp, partial_report); } void RTCStatsCollector::ProducePartialResultsOnNetworkThread( @@ -1935,6 +1952,17 @@ void RTCStatsCollector::ProducePeerConnectionStats_s( report->AddStats(std::move(stats)); } +void RTCStatsCollector::ProduceAudioPlayoutStats_s( + Timestamp timestamp, + RTCStatsReport* report) const { + RTC_DCHECK_RUN_ON(signaling_thread_); + rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls; + + if (audio_device_stats_) { + report->AddStats(CreateAudioPlayoutStats(*audio_device_stats_, timestamp)); + } +} + void RTCStatsCollector::ProduceRTPStreamStats_n( Timestamp timestamp, const std::vector& transceiver_stats_infos, @@ -2447,6 +2475,7 @@ void RTCStatsCollector::PrepareTransceiverStatsInfosAndCallStats_s_w_n() { } call_stats_ = pc_->GetCallStats(); + audio_device_stats_ = pc_->GetAudioDeviceStats(); }); } diff --git a/pc/rtc_stats_collector.h b/pc/rtc_stats_collector.h index be366140c2..bdfe781a5a 100644 --- a/pc/rtc_stats_collector.h +++ b/pc/rtc_stats_collector.h @@ -29,6 +29,7 @@ #include "api/stats/rtcstats_objects.h" #include "call/call.h" #include "media/base/media_channel.h" +#include "modules/audio_device/include/audio_device.h" #include "pc/data_channel_utils.h" #include "pc/peer_connection_internal.h" #include "pc/rtp_receiver.h" @@ -204,6 +205,9 @@ class RTCStatsCollector : public rtc::RefCountInterface, // Produces `RTCPeerConnectionStats`. void ProducePeerConnectionStats_s(Timestamp timestamp, RTCStatsReport* report) const; + // Produces `RTCAudioPlayoutStats`. + void ProduceAudioPlayoutStats_s(Timestamp timestamp, + RTCStatsReport* report) const; // Produces `RTCInboundRTPStreamStats`, `RTCOutboundRTPStreamStats`, // `RTCRemoteInboundRtpStreamStats`, `RTCRemoteOutboundRtpStreamStats` and any // referenced `RTCCodecStats`. This has to be invoked after transport stats @@ -298,6 +302,8 @@ class RTCStatsCollector : public rtc::RefCountInterface, Call::Stats call_stats_; + absl::optional audio_device_stats_; + // A timestamp, in microseconds, that is based on a timer that is // monotonically increasing. That is, even if the system clock is modified the // difference between the timer and this timestamp is how fresh the cached diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc index b75a15f322..cd56ee0d04 100644 --- a/pc/rtc_stats_collector_unittest.cc +++ b/pc/rtc_stats_collector_unittest.cc @@ -42,6 +42,7 @@ #include "api/video_codecs/scalability_mode.h" #include "common_video/include/quality_limitation_reason.h" #include "media/base/media_channel.h" +#include "modules/audio_device/include/audio_device.h" #include "modules/audio_processing/include/audio_processing_statistics.h" #include "modules/rtp_rtcp/include/report_block_data.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" @@ -2705,6 +2706,31 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRTPStreamStats_Video) { EXPECT_TRUE(report->Get(*expected_video.codec_id)); } +TEST_F(RTCStatsCollectorTest, CollectRTCAudioPlayoutStats) { + AudioDeviceModule::Stats audio_device_stats; + audio_device_stats.synthesized_samples_duration_s = 1; + audio_device_stats.synthesized_samples_events = 2; + audio_device_stats.total_samples_count = 3; + audio_device_stats.total_samples_duration_s = 4; + audio_device_stats.total_playout_delay_s = 5; + pc_->SetAudioDeviceStats(audio_device_stats); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + auto stats_of_track_type = report->GetStatsOfType(); + ASSERT_EQ(1U, stats_of_track_type.size()); + + RTCAudioPlayoutStats expected_stats("AP", report->timestamp()); + expected_stats.synthesized_samples_duration = 1; + expected_stats.synthesized_samples_events = 2; + expected_stats.total_samples_count = 3; + expected_stats.total_samples_duration = 4; + expected_stats.total_playout_delay = 5; + + ASSERT_TRUE(report->Get(expected_stats.id())); + EXPECT_EQ(report->Get(expected_stats.id())->cast_to(), + expected_stats); +} + TEST_F(RTCStatsCollectorTest, CollectGoogTimingFrameInfo) { cricket::VideoMediaInfo video_media_info; diff --git a/pc/test/fake_peer_connection_base.h b/pc/test/fake_peer_connection_base.h index fa2d0c2828..18b8824c28 100644 --- a/pc/test/fake_peer_connection_base.h +++ b/pc/test/fake_peer_connection_base.h @@ -17,6 +17,7 @@ #include #include +#include "absl/types/optional.h" #include "api/field_trials_view.h" #include "api/sctp_transport_interface.h" #include "pc/peer_connection_internal.h" @@ -273,6 +274,10 @@ class FakePeerConnectionBase : public PeerConnectionInternal { Call::Stats GetCallStats() override { return Call::Stats(); } + absl::optional GetAudioDeviceStats() override { + return absl::nullopt; + } + bool GetLocalCertificate( const std::string& transport_name, rtc::scoped_refptr* certificate) override { diff --git a/pc/test/fake_peer_connection_for_stats.h b/pc/test/fake_peer_connection_for_stats.h index be9d3a3f53..b771d45a0b 100644 --- a/pc/test/fake_peer_connection_for_stats.h +++ b/pc/test/fake_peer_connection_for_stats.h @@ -328,6 +328,11 @@ class FakePeerConnectionForStats : public FakePeerConnectionBase { void SetCallStats(const Call::Stats& call_stats) { call_stats_ = call_stats; } + void SetAudioDeviceStats( + absl::optional audio_device_stats) { + audio_device_stats_ = audio_device_stats; + } + void SetLocalCertificate( const std::string& transport_name, rtc::scoped_refptr certificate) { @@ -410,6 +415,10 @@ class FakePeerConnectionForStats : public FakePeerConnectionBase { Call::Stats GetCallStats() override { return call_stats_; } + absl::optional GetAudioDeviceStats() override { + return audio_device_stats_; + } + bool GetLocalCertificate( const std::string& transport_name, rtc::scoped_refptr* certificate) override { @@ -490,6 +499,8 @@ class FakePeerConnectionForStats : public FakePeerConnectionBase { Call::Stats call_stats_; + absl::optional audio_device_stats_; + std::map> local_certificates_by_transport_; std::map> diff --git a/pc/test/mock_peer_connection_internal.h b/pc/test/mock_peer_connection_internal.h index 967f9b605e..23ecc93e43 100644 --- a/pc/test/mock_peer_connection_internal.h +++ b/pc/test/mock_peer_connection_internal.h @@ -17,6 +17,7 @@ #include #include +#include "modules/audio_device/include/audio_device.h" #include "pc/peer_connection_internal.h" #include "test/gmock.h" @@ -302,6 +303,10 @@ class MockPeerConnectionInternal : public PeerConnectionInternal { (const std::set&), (override)); MOCK_METHOD(Call::Stats, GetCallStats, (), (override)); + MOCK_METHOD(absl::optional, + GetAudioDeviceStats, + (), + (override)); MOCK_METHOD(bool, GetLocalCertificate, (const std::string&, rtc::scoped_refptr*), diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc index 5051475750..4b216bd0dd 100644 --- a/stats/rtcstats_objects.cc +++ b/stats/rtcstats_objects.cc @@ -895,4 +895,27 @@ RTCTransportStats::RTCTransportStats(const RTCTransportStats& other) = default; RTCTransportStats::~RTCTransportStats() {} +RTCAudioPlayoutStats::RTCAudioPlayoutStats(const std::string& id, + Timestamp timestamp) + : RTCStats(std::move(id), timestamp), + synthesized_samples_duration("synthesizedSamplesDuration"), + synthesized_samples_events("synthesizedSamplesEvents"), + total_samples_duration("totalSamplesDuration"), + total_playout_delay("totalPlayoutDelay"), + total_samples_count("totalSamplesCount") {} + +RTCAudioPlayoutStats::RTCAudioPlayoutStats(const RTCAudioPlayoutStats& other) = + default; + +RTCAudioPlayoutStats::~RTCAudioPlayoutStats() {} + +// clang-format off +WEBRTC_RTCSTATS_IMPL(RTCAudioPlayoutStats, RTCStats, "audio-playout", + &synthesized_samples_duration, + &synthesized_samples_events, + &total_samples_duration, + &total_playout_delay, + &total_samples_count) +// clang-format on + } // namespace webrtc