Add RTCAudioPlayoutStats to GetStats().
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 <hbos@webrtc.org>
Commit-Queue: Fredrik Hernqvist <fhernqvist@google.com>
Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
Reviewed-by: Olga Sharonova <olka@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39115}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 47bca78..96114df 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -699,6 +699,22 @@
RTCStatsMember<std::string> 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<double> synthesized_samples_duration;
+ RTCStatsMember<uint64_t> synthesized_samples_events;
+ RTCStatsMember<double> total_samples_duration;
+ RTCStatsMember<double> total_playout_delay;
+ RTCStatsMember<uint64_t> total_samples_count;
+};
+
} // namespace webrtc
#endif // API_STATS_RTCSTATS_OBJECTS_H_
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 4d8c67e..4ab0b11 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -90,6 +90,7 @@
"../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 @@
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 68e0dd0..d4d24cd 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 @@
int64_t max_size_bytes) {
return false;
}
+absl::optional<webrtc::AudioDeviceModule::Stats>
+FakeVoiceEngine::GetAudioDeviceStats() {
+ return absl::nullopt;
+}
void FakeVoiceEngine::StopAecDump() {}
std::vector<webrtc::RtpHeaderExtensionCapability>
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index 82795a5..082c7f3 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -537,6 +537,8 @@
int GetInputLevel();
bool StartAecDump(webrtc::FileWrapper file, int64_t max_size_bytes) override;
void StopAecDump() override;
+ absl::optional<webrtc::AudioDeviceModule::Stats> GetAudioDeviceStats()
+ override;
std::vector<webrtc::RtpHeaderExtensionCapability> GetRtpHeaderExtensions()
const override;
void SetRtpHeaderExtensions(
diff --git a/media/base/media_engine.h b/media/base/media_engine.h
index 521b824..cc7563a 100644
--- a/media/base/media_engine.h
+++ b/media/base/media_engine.h
@@ -116,6 +116,9 @@
// Stops recording AEC dump.
virtual void StopAecDump() = 0;
+
+ virtual absl::optional<webrtc::AudioDeviceModule::Stats>
+ GetAudioDeviceStats() = 0;
};
class VideoEngineInterface : public RtpHeaderExtensionQueryInterface {
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index c425916..78861b2 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -626,6 +626,11 @@
}
}
+absl::optional<webrtc::AudioDeviceModule::Stats>
+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 25f8e3d..96e07cd 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -88,6 +88,9 @@
// Stops AEC dump.
void StopAecDump() override;
+ absl::optional<webrtc::AudioDeviceModule::Stats> 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 6c7292a..4a6a0ab 100644
--- a/modules/audio_device/BUILD.gn
+++ b/modules/audio_device/BUILD.gn
@@ -54,6 +54,7 @@
"../../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 f82029e..936ee6c 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 @@
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<AudioDeviceModule> Create(
@@ -150,6 +161,10 @@
// 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<Stats> 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 2ecf4b2..176f8c5 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -1015,7 +1015,10 @@
":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 @@
"../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 622870c..5633be3 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 @@
}
}
+absl::optional<AudioDeviceModule::Stats> 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 39a240d..a4b21f0 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -298,6 +298,8 @@
const std::set<std::string>& transport_names) override;
Call::Stats GetCallStats() override;
+ absl::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() override;
+
bool GetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) override;
diff --git a/pc/peer_connection_internal.h b/pc/peer_connection_internal.h
index ecf8fbf..1085ff9 100644
--- a/pc/peer_connection_internal.h
+++ b/pc/peer_connection_internal.h
@@ -17,8 +17,10 @@
#include <string>
#include <vector>
+#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 @@
virtual Call::Stats GetCallStats() = 0;
+ virtual absl::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() = 0;
+
virtual bool GetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) = 0;
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 6a45b58..508661e 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 @@
return inbound_audio;
}
+std::unique_ptr<RTCAudioPlayoutStats> CreateAudioPlayoutStats(
+ const AudioDeviceModule::Stats& audio_device_stats,
+ webrtc::Timestamp timestamp) {
+ auto stats = std::make_unique<RTCAudioPlayoutStats>(
+ /*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<RTCRemoteOutboundRtpStreamStats>
CreateRemoteOutboundAudioStreamStats(
const cricket::VoiceReceiverInfo& voice_receiver_info,
@@ -1486,6 +1502,7 @@
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 @@
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<RtpTransceiverStatsInfo>& transceiver_stats_infos,
@@ -2447,6 +2475,7 @@
}
call_stats_ = pc_->GetCallStats();
+ audio_device_stats_ = pc_->GetAudioDeviceStats();
});
}
diff --git a/pc/rtc_stats_collector.h b/pc/rtc_stats_collector.h
index be36614..bdfe781 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 @@
// 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 @@
Call::Stats call_stats_;
+ absl::optional<AudioDeviceModule::Stats> 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 b75a15f..cd56ee0 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 @@
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<const RTCStatsReport> report = stats_->GetStatsReport();
+ auto stats_of_track_type = report->GetStatsOfType<RTCAudioPlayoutStats>();
+ 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<RTCAudioPlayoutStats>(),
+ 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 fa2d0c2..18b8824 100644
--- a/pc/test/fake_peer_connection_base.h
+++ b/pc/test/fake_peer_connection_base.h
@@ -17,6 +17,7 @@
#include <string>
#include <vector>
+#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 @@
Call::Stats GetCallStats() override { return Call::Stats(); }
+ absl::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() override {
+ return absl::nullopt;
+ }
+
bool GetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) override {
diff --git a/pc/test/fake_peer_connection_for_stats.h b/pc/test/fake_peer_connection_for_stats.h
index be9d3a3..b771d45 100644
--- a/pc/test/fake_peer_connection_for_stats.h
+++ b/pc/test/fake_peer_connection_for_stats.h
@@ -328,6 +328,11 @@
void SetCallStats(const Call::Stats& call_stats) { call_stats_ = call_stats; }
+ void SetAudioDeviceStats(
+ absl::optional<AudioDeviceModule::Stats> audio_device_stats) {
+ audio_device_stats_ = audio_device_stats;
+ }
+
void SetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate> certificate) {
@@ -410,6 +415,10 @@
Call::Stats GetCallStats() override { return call_stats_; }
+ absl::optional<AudioDeviceModule::Stats> GetAudioDeviceStats() override {
+ return audio_device_stats_;
+ }
+
bool GetLocalCertificate(
const std::string& transport_name,
rtc::scoped_refptr<rtc::RTCCertificate>* certificate) override {
@@ -490,6 +499,8 @@
Call::Stats call_stats_;
+ absl::optional<AudioDeviceModule::Stats> audio_device_stats_;
+
std::map<std::string, rtc::scoped_refptr<rtc::RTCCertificate>>
local_certificates_by_transport_;
std::map<std::string, std::unique_ptr<rtc::SSLCertChain>>
diff --git a/pc/test/mock_peer_connection_internal.h b/pc/test/mock_peer_connection_internal.h
index 967f9b6..23ecc93 100644
--- a/pc/test/mock_peer_connection_internal.h
+++ b/pc/test/mock_peer_connection_internal.h
@@ -17,6 +17,7 @@
#include <string>
#include <vector>
+#include "modules/audio_device/include/audio_device.h"
#include "pc/peer_connection_internal.h"
#include "test/gmock.h"
@@ -302,6 +303,10 @@
(const std::set<std::string>&),
(override));
MOCK_METHOD(Call::Stats, GetCallStats, (), (override));
+ MOCK_METHOD(absl::optional<AudioDeviceModule::Stats>,
+ GetAudioDeviceStats,
+ (),
+ (override));
MOCK_METHOD(bool,
GetLocalCertificate,
(const std::string&, rtc::scoped_refptr<rtc::RTCCertificate>*),
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 5051475..4b216bd 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -895,4 +895,27 @@
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