Add RTCStats for keyFramesEncoded, keyFramesDecoded.

This implements the correspondingly named JavaScript fields defined in
https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict* and
https://w3c.github.io/webrtc-stats/#outboundrtpstats-dict*.

Bug: webrtc:7066
Change-Id: I431045bca80bf5faf27132c54f59c1f723c92952
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/143683
Commit-Queue: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28404}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 0c864cd..a178d54 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -442,6 +442,7 @@
   // TODO(hbos): Collect and populate this value. https://bugs.webrtc.org/7065
   RTCStatsMember<double> gap_discard_rate;
   RTCStatsMember<uint32_t> frames_decoded;
+  RTCStatsMember<uint32_t> key_frames_decoded;
   // https://henbos.github.io/webrtc-provisional-stats/#dom-rtcinboundrtpstreamstats-contenttype
   RTCStatsMember<std::string> content_type;
 };
@@ -466,6 +467,7 @@
   // TODO(hbos): Collect and populate this value. https://bugs.webrtc.org/7066
   RTCStatsMember<double> target_bitrate;
   RTCStatsMember<uint32_t> frames_encoded;
+  RTCStatsMember<uint32_t> key_frames_encoded;
   RTCStatsMember<double> total_encode_time;
   RTCStatsMember<uint64_t> total_encoded_bytes_target;
   // TODO(https://crbug.com/webrtc/10635): This is only implemented for video;
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index e98da00..8a97022 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -564,6 +564,7 @@
   int avg_encode_ms = 0;
   int encode_usage_percent = 0;
   uint32_t frames_encoded = 0;
+  uint32_t key_frames_encoded = 0;
   // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodetime
   uint64_t total_encode_time_ms = 0;
   // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodedbytestarget
@@ -597,6 +598,7 @@
   int framerate_render_output = 0;
   uint32_t frames_received = 0;
   uint32_t frames_decoded = 0;
+  uint32_t key_frames_decoded = 0;
   uint32_t frames_rendered = 0;
   absl::optional<uint64_t> qp_sum;
   int64_t interframe_delay_max_ms = -1;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index b95b40e..4561c7f 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -2280,6 +2280,13 @@
   info.avg_encode_ms = stats.avg_encode_time_ms;
   info.encode_usage_percent = stats.encode_usage_percent;
   info.frames_encoded = stats.frames_encoded;
+  // TODO(bugs.webrtc.org/9547): Populate individual outbound-rtp stats objects
+  // for each simulcast stream, instead of accumulating all keyframes encoded
+  // over all simulcast streams in the same outbound-rtp stats object.
+  info.key_frames_encoded = 0;
+  for (const auto& kv : stats.substreams) {
+    info.key_frames_encoded += kv.second.frame_counts.key_frames;
+  }
   info.total_encode_time_ms = stats.total_encode_time_ms;
   info.total_encoded_bytes_target = stats.total_encoded_bytes_target;
   info.qp_sum = stats.qp_sum;
@@ -2747,6 +2754,7 @@
   info.frames_received =
       stats.frame_counts.key_frames + stats.frame_counts.delta_frames;
   info.frames_decoded = stats.frames_decoded;
+  info.key_frames_decoded = stats.frame_counts.key_frames;
   info.frames_rendered = stats.frames_rendered;
   info.qp_sum = stats.qp_sum;
   info.last_packet_received_timestamp_ms =
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index bb989f4..bedda07 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -4938,6 +4938,21 @@
   EXPECT_EQ(stats.frames_encoded, info.senders[0].frames_encoded);
 }
 
+TEST_F(WebRtcVideoChannelTest, GetStatsReportsKeyFramesEncoded) {
+  FakeVideoSendStream* stream = AddSendStream();
+  webrtc::VideoSendStream::Stats stats;
+  stats.substreams[123].frame_counts.key_frames = 10;
+  stats.substreams[456].frame_counts.key_frames = 87;
+  stream->SetStats(stats);
+
+  cricket::VideoMediaInfo info;
+  ASSERT_TRUE(channel_->GetStats(&info));
+  // TODO(bugs.webrtc.org/9547): Populate individual outbound-rtp stats objects
+  // for each simulcast stream, instead of accumulating all keyframes encoded
+  // over all simulcast streams in the same outbound-rtp stats object.
+  EXPECT_EQ(97u, info.senders[0].key_frames_encoded);
+}
+
 TEST_F(WebRtcVideoChannelTest, GetStatsReportsQpSum) {
   FakeVideoSendStream* stream = AddSendStream();
   webrtc::VideoSendStream::Stats stats;
@@ -5095,6 +5110,8 @@
             info.receivers[0].frames_received);
   EXPECT_EQ(stats.frames_rendered, info.receivers[0].frames_rendered);
   EXPECT_EQ(stats.frames_decoded, info.receivers[0].frames_decoded);
+  EXPECT_EQ(rtc::checked_cast<unsigned int>(stats.frame_counts.key_frames),
+            info.receivers[0].key_frames_decoded);
   EXPECT_EQ(stats.qp_sum, info.receivers[0].qp_sum);
 }
 
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index c368ec0..42d6c51 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -2708,6 +2708,12 @@
   for (const auto& stat : outbound_stream_stats) {
     ASSERT_TRUE(stat->bytes_sent.is_defined());
     EXPECT_LT(0u, *stat->bytes_sent);
+    if (*stat->kind == "video") {
+      ASSERT_TRUE(stat->key_frames_encoded.is_defined());
+      EXPECT_GT(*stat->key_frames_encoded, 0u);
+      ASSERT_TRUE(stat->frames_encoded.is_defined());
+      EXPECT_GE(*stat->frames_encoded, *stat->key_frames_encoded);
+    }
     ASSERT_TRUE(stat->track_id.is_defined());
     const auto* track_stat =
         caller_report->GetAs<webrtc::RTCMediaStreamTrackStats>(*stat->track_id);
@@ -2726,6 +2732,12 @@
   for (const auto& stat : inbound_stream_stats) {
     ASSERT_TRUE(stat->bytes_received.is_defined());
     EXPECT_LT(0u, *stat->bytes_received);
+    if (*stat->kind == "video") {
+      ASSERT_TRUE(stat->key_frames_decoded.is_defined());
+      EXPECT_GT(*stat->key_frames_decoded, 0u);
+      ASSERT_TRUE(stat->frames_decoded.is_defined());
+      EXPECT_GE(*stat->frames_decoded, *stat->key_frames_decoded);
+    }
     ASSERT_TRUE(stat->track_id.is_defined());
     const auto* track_stat =
         callee_report->GetAs<webrtc::RTCMediaStreamTrackStats>(*stat->track_id);
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 5e0986d..fb85092 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -309,6 +309,7 @@
   inbound_video->nack_count =
       static_cast<uint32_t>(video_receiver_info.nacks_sent);
   inbound_video->frames_decoded = video_receiver_info.frames_decoded;
+  inbound_video->key_frames_decoded = video_receiver_info.key_frames_decoded;
   if (video_receiver_info.qp_sum)
     inbound_video->qp_sum = *video_receiver_info.qp_sum;
   if (video_receiver_info.last_packet_received_timestamp_ms) {
@@ -378,6 +379,7 @@
   if (video_sender_info.qp_sum)
     outbound_video->qp_sum = *video_sender_info.qp_sum;
   outbound_video->frames_encoded = video_sender_info.frames_encoded;
+  outbound_video->key_frames_encoded = video_sender_info.key_frames_encoded;
   outbound_video->total_encode_time =
       static_cast<double>(video_sender_info.total_encode_time_ms) /
       rtc::kNumMillisecsPerSec;
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 11500d6..2f0e78b 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -1801,6 +1801,7 @@
   video_media_info.receivers[0].plis_sent = 6;
   video_media_info.receivers[0].nacks_sent = 7;
   video_media_info.receivers[0].frames_decoded = 8;
+  video_media_info.receivers[0].key_frames_decoded = 3;
   video_media_info.receivers[0].qp_sum = absl::nullopt;
   video_media_info.receivers[0].last_packet_received_timestamp_ms =
       absl::nullopt;
@@ -1837,6 +1838,7 @@
   expected_video.bytes_received = 3;
   expected_video.packets_lost = 42;
   expected_video.frames_decoded = 8;
+  expected_video.key_frames_decoded = 3;
   // |expected_video.qp_sum| should be undefined.
   // |expected_video.last_packet_received_timestamp| should be undefined.
   // |expected_video.content_type| should be undefined.
@@ -1938,6 +1940,7 @@
   video_media_info.senders[0].retransmitted_bytes_sent = 60;
   video_media_info.senders[0].codec_payload_type = 42;
   video_media_info.senders[0].frames_encoded = 8;
+  video_media_info.senders[0].key_frames_encoded = 3;
   video_media_info.senders[0].total_encode_time_ms = 9000;
   video_media_info.senders[0].total_encoded_bytes_target = 1234;
   video_media_info.senders[0].total_packet_send_delay_ms = 10000;
@@ -1985,6 +1988,7 @@
   expected_video.bytes_sent = 6;
   expected_video.retransmitted_bytes_sent = 60;
   expected_video.frames_encoded = 8;
+  expected_video.key_frames_encoded = 3;
   expected_video.total_encode_time = 9.0;
   expected_video.total_encoded_bytes_target = 1234;
   expected_video.total_packet_send_delay = 10.0;
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index 4f10a1d..79d6faf 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -804,11 +804,13 @@
     if (inbound_stream.media_type.is_defined() &&
         *inbound_stream.media_type == "video") {
       verifier.TestMemberIsDefined(inbound_stream.frames_decoded);
+      verifier.TestMemberIsDefined(inbound_stream.key_frames_decoded);
       // The integration test is not set up to test screen share; don't require
       // this to be present.
       verifier.MarkMemberTested(inbound_stream.content_type, true);
     } else {
       verifier.TestMemberIsUndefined(inbound_stream.frames_decoded);
+      verifier.TestMemberIsUndefined(inbound_stream.key_frames_decoded);
       verifier.TestMemberIsUndefined(inbound_stream.content_type);
     }
     return verifier.ExpectAllMembersSuccessfullyTested();
@@ -838,6 +840,7 @@
     if (outbound_stream.media_type.is_defined() &&
         *outbound_stream.media_type == "video") {
       verifier.TestMemberIsDefined(outbound_stream.frames_encoded);
+      verifier.TestMemberIsDefined(outbound_stream.key_frames_encoded);
       verifier.TestMemberIsNonNegative<double>(
           outbound_stream.total_encode_time);
       verifier.TestMemberIsNonNegative<uint64_t>(
@@ -850,6 +853,7 @@
       verifier.MarkMemberTested(outbound_stream.content_type, true);
     } else {
       verifier.TestMemberIsUndefined(outbound_stream.frames_encoded);
+      verifier.TestMemberIsUndefined(outbound_stream.key_frames_encoded);
       verifier.TestMemberIsUndefined(outbound_stream.total_encode_time);
       verifier.TestMemberIsUndefined(
           outbound_stream.total_encoded_bytes_target);
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 43c1d23..2b62990 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -614,6 +614,7 @@
     &gap_loss_rate,
     &gap_discard_rate,
     &frames_decoded,
+    &key_frames_decoded,
     &content_type)
 // clang-format on
 
@@ -643,6 +644,7 @@
       gap_loss_rate("gapLossRate"),
       gap_discard_rate("gapDiscardRate"),
       frames_decoded("framesDecoded"),
+      key_frames_decoded("keyFramesDecoded"),
       content_type("contentType") {}
 
 RTCInboundRTPStreamStats::RTCInboundRTPStreamStats(
@@ -667,6 +669,7 @@
       gap_loss_rate(other.gap_loss_rate),
       gap_discard_rate(other.gap_discard_rate),
       frames_decoded(other.frames_decoded),
+      key_frames_decoded(other.key_frames_decoded),
       content_type(other.content_type) {}
 
 RTCInboundRTPStreamStats::~RTCInboundRTPStreamStats() {}
@@ -681,6 +684,7 @@
     &retransmitted_bytes_sent,
     &target_bitrate,
     &frames_encoded,
+    &key_frames_encoded,
     &total_encode_time,
     &total_encoded_bytes_target,
     &total_packet_send_delay,
@@ -702,6 +706,7 @@
       retransmitted_bytes_sent("retransmittedBytesSent"),
       target_bitrate("targetBitrate"),
       frames_encoded("framesEncoded"),
+      key_frames_encoded("keyFramesEncoded"),
       total_encode_time("totalEncodeTime"),
       total_encoded_bytes_target("totalEncodedBytesTarget"),
       total_packet_send_delay("totalPacketSendDelay"),
@@ -718,6 +723,7 @@
       retransmitted_bytes_sent(other.retransmitted_bytes_sent),
       target_bitrate(other.target_bitrate),
       frames_encoded(other.frames_encoded),
+      key_frames_encoded(other.key_frames_encoded),
       total_encode_time(other.total_encode_time),
       total_encoded_bytes_target(other.total_encoded_bytes_target),
       total_packet_send_delay(other.total_packet_send_delay),