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