Add per frame decode time histograms for 4k/HD and VP9/H264
Add new histograms
WebRTC.Video.DecodeTimePerFrameInMs.[codec].[resolution].[decoder]
These histograms are more explicit than the existing histogram
WebRTC.VideoDecodTimeMs, since they allow to see performance per
codec/resolution/decoder and also contain per frame statistics instead
of an average decode time.
There's a killswitch, WebRTC-DecodeTimeHistogramsKillSwitch, that can be
used to disable the histograms.
Bug: chromium:1007526
Change-Id: I9f75127b4bc5341e9f406c64ed91164564290b26
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/157881
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Commit-Queue: Johannes Kron <kron@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29572}
diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc
index 42ab7e1..d8bde94 100644
--- a/video/receive_statistics_proxy.cc
+++ b/video/receive_statistics_proxy.cc
@@ -20,6 +20,7 @@
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/clock.h"
+#include "system_wrappers/include/field_trial.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
@@ -85,6 +86,8 @@
: clock_(clock),
config_(*config),
start_ms_(clock->TimeInMilliseconds()),
+ enable_decode_time_histograms_(
+ !field_trial::IsEnabled("WebRTC-DecodeTimeHistogramsKillSwitch")),
last_sample_time_(clock->TimeInMilliseconds()),
fps_threshold_(kLowFpsThreshold,
kHighFpsThreshold,
@@ -553,6 +556,61 @@
stats_.network_frame_rate = static_cast<int>(framerate);
}
+void ReceiveStatisticsProxy::UpdateDecodeTimeHistograms(
+ int width,
+ int height,
+ int decode_time_ms) const {
+ bool is_4k = (width == 3840 || width == 4096) && height == 2160;
+ bool is_hd = width == 1920 && height == 1080;
+ // Only update histograms for 4k/HD and VP9/H264.
+ if ((is_4k || is_hd) && (last_codec_type_ == kVideoCodecVP9 ||
+ last_codec_type_ == kVideoCodecH264)) {
+ const std::string kDecodeTimeUmaPrefix =
+ "WebRTC.Video.DecodeTimePerFrameInMs.";
+
+ // Each histogram needs its own line for it to not be reused in the wrong
+ // way when the format changes.
+ if (last_codec_type_ == kVideoCodecVP9) {
+ bool is_sw_decoder =
+ stats_.decoder_implementation_name.compare(0, 6, "libvpx") == 0;
+ if (is_4k) {
+ if (is_sw_decoder)
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Sw",
+ decode_time_ms);
+ else
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Hw",
+ decode_time_ms);
+ } else {
+ if (is_sw_decoder)
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Sw",
+ decode_time_ms);
+ else
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Hw",
+ decode_time_ms);
+ }
+ } else {
+ bool is_sw_decoder =
+ stats_.decoder_implementation_name.compare(0, 6, "FFmpeg") == 0;
+ if (is_4k) {
+ if (is_sw_decoder)
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Sw",
+ decode_time_ms);
+ else
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Hw",
+ decode_time_ms);
+
+ } else {
+ if (is_sw_decoder)
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Sw",
+ decode_time_ms);
+ else
+ RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Hw",
+ decode_time_ms);
+ }
+ }
+ }
+}
+
VideoReceiveStream::Stats ReceiveStatisticsProxy::GetStats() const {
rtc::CritScope lock(&crit_);
// Get current frame rates here, as only updating them on new frames prevents
@@ -697,6 +755,10 @@
decode_time_counter_.Add(decode_time_ms);
stats_.decode_ms = decode_time_ms;
stats_.total_decode_time_ms += decode_time_ms;
+ if (enable_decode_time_histograms_) {
+ UpdateDecodeTimeHistograms(frame.width(), frame.height(), decode_time_ms);
+ }
+
last_content_type_ = content_type;
decode_fps_estimator_.Update(1, now_ms);
if (last_decoded_frame_time_ms_) {
diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h
index 9c3a117..40608a8 100644
--- a/video/receive_statistics_proxy.h
+++ b/video/receive_statistics_proxy.h
@@ -128,6 +128,11 @@
void UpdateFramerate(int64_t now_ms) const
RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+ void UpdateDecodeTimeHistograms(int width,
+ int height,
+ int decode_time_ms) const
+ RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+
Clock* const clock_;
// Ownership of this object lies with the owner of the ReceiveStatisticsProxy
// instance. Lifetime is guaranteed to outlive |this|.
@@ -138,6 +143,7 @@
// then no longer store a pointer to the object).
const VideoReceiveStream::Config& config_;
const int64_t start_ms_;
+ const bool enable_decode_time_histograms_;
rtc::CriticalSection crit_;
int64_t last_sample_time_ RTC_GUARDED_BY(crit_);
diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc
index fffc42c..66adb83 100644
--- a/video/receive_statistics_proxy_unittest.cc
+++ b/video/receive_statistics_proxy_unittest.cc
@@ -16,12 +16,14 @@
#include <tuple>
#include <utility>
+#include "absl/types/optional.h"
#include "api/scoped_refptr.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "api/video/video_frame_buffer.h"
#include "api/video/video_rotation.h"
#include "system_wrappers/include/metrics.h"
+#include "test/field_trial.h"
#include "test/gtest.h"
namespace webrtc {
@@ -1548,4 +1550,142 @@
"WebRTC.Video.InterframeDelayInMs.ExperimentGroup0"));
}
}
+
+class DecodeTimeHistogramsKillswitch {
+ public:
+ explicit DecodeTimeHistogramsKillswitch(bool disable_histograms)
+ : field_trial_(disable_histograms
+ ? "WebRTC-DecodeTimeHistogramsKillSwitch/Enabled/"
+ : "") {}
+
+ private:
+ webrtc::test::ScopedFieldTrials field_trial_;
+};
+
+class ReceiveStatisticsProxyTestWithDecodeTimeHistograms
+ : public DecodeTimeHistogramsKillswitch,
+ public ::testing::WithParamInterface<
+ std::tuple<bool, int, int, int, VideoCodecType, std::string>>,
+ public ReceiveStatisticsProxyTest {
+ public:
+ ReceiveStatisticsProxyTestWithDecodeTimeHistograms()
+ : DecodeTimeHistogramsKillswitch(std::get<0>(GetParam())) {}
+
+ protected:
+ const std::string kUmaPrefix = "WebRTC.Video.DecodeTimePerFrameInMs.";
+ const int expected_number_of_samples_ = {std::get<1>(GetParam())};
+ const int width_ = {std::get<2>(GetParam())};
+ const int height_ = {std::get<3>(GetParam())};
+ const VideoCodecType codec_type_ = {std::get<4>(GetParam())};
+ const std::string implementation_name_ = {std::get<5>(GetParam())};
+ const std::string uma_histogram_name_ =
+ kUmaPrefix + (codec_type_ == kVideoCodecVP9 ? "Vp9." : "H264.") +
+ (height_ == 2160 ? "4k." : "Hd.") +
+ (implementation_name_.compare("ExternalDecoder") == 0 ? "Hw" : "Sw");
+};
+
+TEST_P(ReceiveStatisticsProxyTestWithDecodeTimeHistograms,
+ DecodeTimeHistogramsUpdated) {
+ constexpr int kNumberOfFrames = 10;
+ constexpr int kDecodeTimeMs = 7;
+ constexpr int kFrameDurationMs = 1000 / 60;
+
+ webrtc::VideoFrame frame = CreateFrame(width_, height_);
+
+ statistics_proxy_->OnDecoderImplementationName(implementation_name_.c_str());
+ statistics_proxy_->OnPreDecode(codec_type_, /*qp=*/0);
+
+ for (int i = 0; i < kNumberOfFrames; ++i) {
+ statistics_proxy_->OnDecodedFrame(frame, /*qp=*/absl::nullopt,
+ kDecodeTimeMs,
+ VideoContentType::UNSPECIFIED);
+ fake_clock_.AdvanceTimeMilliseconds(kFrameDurationMs);
+ }
+
+ EXPECT_EQ(expected_number_of_samples_,
+ metrics::NumSamples(uma_histogram_name_));
+ EXPECT_EQ(expected_number_of_samples_,
+ metrics::NumEvents(uma_histogram_name_, kDecodeTimeMs));
+}
+
+const auto kVp94kHw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/3840,
+ /*height=*/2160,
+ kVideoCodecVP9,
+ /*implementation=*/"ExternalDecoder");
+const auto kVp94kSw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/3840,
+ /*height=*/2160,
+ kVideoCodecVP9,
+ /*implementation=*/"libvpx");
+const auto kVp9HdHw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/1920,
+ /*height=*/1080,
+ kVideoCodecVP9,
+ /*implementation=*/"ExternalDecoder");
+const auto kVp9HdSw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/1920,
+ /*height=*/1080,
+ kVideoCodecVP9,
+ /*implementation=*/"libvpx");
+const auto kH2644kHw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/3840,
+ /*height=*/2160,
+ kVideoCodecH264,
+ /*implementation=*/"ExternalDecoder");
+const auto kH2644kSw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/3840,
+ /*height=*/2160,
+ kVideoCodecH264,
+ /*implementation=*/"FFmpeg");
+const auto kH264HdHw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/1920,
+ /*height=*/1080,
+ kVideoCodecH264,
+ /*implementation=*/"ExternalDecoder");
+const auto kH264HdSw = std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/1920,
+ /*height=*/1080,
+ kVideoCodecH264,
+ /*implementation=*/"FFmpeg");
+
+INSTANTIATE_TEST_SUITE_P(AllHistogramsPopulated,
+ ReceiveStatisticsProxyTestWithDecodeTimeHistograms,
+ ::testing::Values(kVp94kHw,
+ kVp94kSw,
+ kVp9HdHw,
+ kVp9HdSw,
+ kH2644kHw,
+ kH2644kSw,
+ kH264HdHw,
+ kH264HdSw));
+
+const auto kKillswitchDisabled =
+ std::make_tuple(/*killswitch=*/false,
+ /*expected_number_of_samples=*/10,
+ /*width=*/1920,
+ /*height=*/1080,
+ kVideoCodecVP9,
+ /*implementation=*/"libvpx");
+const auto kKillswitchEnabled =
+ std::make_tuple(/*killswitch=*/true,
+ /*expected_number_of_samples=*/0,
+ /*width=*/1920,
+ /*height=*/1080,
+ kVideoCodecVP9,
+ /*implementation=*/"libvpx");
+
+INSTANTIATE_TEST_SUITE_P(KillswitchEffective,
+ ReceiveStatisticsProxyTestWithDecodeTimeHistograms,
+ ::testing::Values(kKillswitchDisabled,
+ kKillswitchEnabled));
+
} // namespace webrtc