Add dropped frames metric on the receive side

Reported to UMA and logged for at the end of the call.

Bug: webrtc:8355
Change-Id: I4ef31bf9e55feaba9cf28be5cb4fcfae929c7179
Reviewed-on: https://webrtc-review.googlesource.com/53760
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22132}
diff --git a/modules/video_coding/packet_buffer.cc b/modules/video_coding/packet_buffer.cc
index 741c11e..4a8904e 100644
--- a/modules/video_coding/packet_buffer.cc
+++ b/modules/video_coding/packet_buffer.cc
@@ -48,6 +48,7 @@
       data_buffer_(start_buffer_size),
       sequence_buffer_(start_buffer_size),
       received_frame_callback_(received_frame_callback),
+      unique_frames_seen_(0),
       sps_pps_idr_is_h264_keyframe_(
           field_trial::IsEnabled("WebRTC-SpsPpsIdrIsH264Keyframe")) {
   RTC_DCHECK_LE(start_buffer_size, max_buffer_size);
@@ -65,6 +66,8 @@
   {
     rtc::CritScope lock(&crit_);
 
+    OnTimestampReceived(packet->timestamp);
+
     uint16_t seq_num = packet->seqNum;
     size_t index = seq_num % size_;
 
@@ -207,6 +210,11 @@
   return last_received_keyframe_packet_ms_;
 }
 
+int PacketBuffer::GetUniqueFramesSeen() const {
+  rtc::CritScope lock(&crit_);
+  return unique_frames_seen_;
+}
+
 bool PacketBuffer::ExpandBufferSize() {
   if (size_ == max_size_) {
     RTC_LOG(LS_WARNING) << "PacketBuffer is already at max size (" << max_size_
@@ -484,5 +492,18 @@
   }
 }
 
+void PacketBuffer::OnTimestampReceived(uint32_t rtp_timestamp) {
+  const size_t kMaxTimestampsHistory = 1000;
+  if (rtp_timestamps_history_set_.insert(rtp_timestamp).second) {
+    rtp_timestamps_history_queue_.push(rtp_timestamp);
+    ++unique_frames_seen_;
+    if (rtp_timestamps_history_set_.size() > kMaxTimestampsHistory) {
+      uint32_t discarded_timestamp = rtp_timestamps_history_queue_.front();
+      rtp_timestamps_history_set_.erase(discarded_timestamp);
+      rtp_timestamps_history_queue_.pop();
+    }
+  }
+}
+
 }  // namespace video_coding
 }  // namespace webrtc
diff --git a/modules/video_coding/packet_buffer.h b/modules/video_coding/packet_buffer.h
index c1ca7b8..869a81c 100644
--- a/modules/video_coding/packet_buffer.h
+++ b/modules/video_coding/packet_buffer.h
@@ -12,6 +12,7 @@
 #define MODULES_VIDEO_CODING_PACKET_BUFFER_H_
 
 #include <memory>
+#include <queue>
 #include <set>
 #include <vector>
 
@@ -61,6 +62,9 @@
   rtc::Optional<int64_t> LastReceivedPacketMs() const;
   rtc::Optional<int64_t> LastReceivedKeyframePacketMs() const;
 
+  // Returns number of different frames seen in the packet buffer
+  int GetUniqueFramesSeen() const;
+
   int AddRef() const;
   int Release() const;
 
@@ -126,6 +130,10 @@
   void UpdateMissingPackets(uint16_t seq_num)
       RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
 
+  // Counts unique received timestamps and updates |unique_frames_seen_|.
+  void OnTimestampReceived(uint32_t rtp_timestamp)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+
   rtc::CriticalSection crit_;
 
   // Buffer size_ and max_size_ must always be a power of two.
@@ -156,6 +164,8 @@
   rtc::Optional<int64_t> last_received_keyframe_packet_ms_
       RTC_GUARDED_BY(crit_);
 
+  int unique_frames_seen_ RTC_GUARDED_BY(crit_);
+
   rtc::Optional<uint16_t> newest_inserted_seq_num_ RTC_GUARDED_BY(crit_);
   std::set<uint16_t, DescendingSeqNumComp<uint16_t>> missing_packets_
       RTC_GUARDED_BY(crit_);
@@ -164,6 +174,11 @@
   // RTP timestamp to treat the corresponding frame as a keyframe.
   const bool sps_pps_idr_is_h264_keyframe_;
 
+  // Stores several last seen unique timestamps for quick search.
+  std::set<uint32_t> rtp_timestamps_history_set_ RTC_GUARDED_BY(crit_);
+  // Stores the same unique timestamps in the order of insertion.
+  std::queue<uint32_t> rtp_timestamps_history_queue_ RTC_GUARDED_BY(crit_);
+
   mutable volatile int ref_count_ = 0;
 };
 
diff --git a/modules/video_coding/video_packet_buffer_unittest.cc b/modules/video_coding/video_packet_buffer_unittest.cc
index 8936493..db29b8b 100644
--- a/modules/video_coding/video_packet_buffer_unittest.cc
+++ b/modules/video_coding/video_packet_buffer_unittest.cc
@@ -54,14 +54,16 @@
   enum IsFirst { kFirst, kNotFirst };
   enum IsLast { kLast, kNotLast };
 
-  bool Insert(uint16_t seq_num,           // packet sequence number
-              IsKeyFrame keyframe,        // is keyframe
-              IsFirst first,              // is first packet of frame
-              IsLast last,                // is last packet of frame
-              int data_size = 0,          // size of data
-              uint8_t* data = nullptr) {  // data pointer
+  bool Insert(uint16_t seq_num,             // packet sequence number
+              IsKeyFrame keyframe,          // is keyframe
+              IsFirst first,                // is first packet of frame
+              IsLast last,                  // is last packet of frame
+              int data_size = 0,            // size of data
+              uint8_t* data = nullptr,      // data pointer
+              uint32_t timestamp = 123u) {  // rtp timestamp
     VCMPacket packet;
     packet.codec = kVideoCodecGeneric;
+    packet.timestamp = timestamp;
     packet.seqNum = seq_num;
     packet.frameType =
         keyframe == kKeyFrame ? kVideoFrameKey : kVideoFrameDelta;
@@ -195,6 +197,64 @@
   EXPECT_EQ(20UL, frames_from_callback_.begin()->second->size());
 }
 
+TEST_F(TestPacketBuffer, CountsUniqueFrames) {
+  const uint16_t seq_num = Rand();
+
+  ASSERT_EQ(0, packet_buffer_->GetUniqueFramesSeen());
+
+  EXPECT_TRUE(Insert(seq_num, kKeyFrame, kFirst, kNotLast, 0, nullptr, 100));
+  ASSERT_EQ(1, packet_buffer_->GetUniqueFramesSeen());
+  // Still the same frame.
+  EXPECT_TRUE(
+      Insert(seq_num + 1, kKeyFrame, kNotFirst, kLast, 0, nullptr, 100));
+  ASSERT_EQ(1, packet_buffer_->GetUniqueFramesSeen());
+
+  // Second frame.
+  EXPECT_TRUE(
+      Insert(seq_num + 2, kKeyFrame, kFirst, kNotLast, 0, nullptr, 200));
+  ASSERT_EQ(2, packet_buffer_->GetUniqueFramesSeen());
+  EXPECT_TRUE(
+      Insert(seq_num + 3, kKeyFrame, kNotFirst, kLast, 0, nullptr, 200));
+  ASSERT_EQ(2, packet_buffer_->GetUniqueFramesSeen());
+
+  // Old packet.
+  EXPECT_TRUE(
+      Insert(seq_num + 1, kKeyFrame, kNotFirst, kLast, 0, nullptr, 100));
+  ASSERT_EQ(2, packet_buffer_->GetUniqueFramesSeen());
+
+  // Missing middle packet.
+  EXPECT_TRUE(
+      Insert(seq_num + 4, kKeyFrame, kFirst, kNotLast, 0, nullptr, 300));
+  EXPECT_TRUE(
+      Insert(seq_num + 6, kKeyFrame, kNotFirst, kLast, 0, nullptr, 300));
+  ASSERT_EQ(3, packet_buffer_->GetUniqueFramesSeen());
+}
+
+TEST_F(TestPacketBuffer, HasHistoryOfUniqueFrames) {
+  const int kNumFrames = 1500;
+  const int kRequiredHistoryLength = 1000;
+  const uint16_t seq_num = Rand();
+  const uint32_t timestamp = 0xFFFFFFF0;  // Large enough to cause wrap-around.
+
+  for (int i = 0; i < kNumFrames; ++i) {
+    EXPECT_TRUE(Insert(seq_num + i, kKeyFrame, kFirst, kNotLast, 0, nullptr,
+                       timestamp + 10 * i));
+  }
+  ASSERT_EQ(kNumFrames, packet_buffer_->GetUniqueFramesSeen());
+
+  // Old packets within history should not affect number of seen unique frames.
+  for (int i = kNumFrames - kRequiredHistoryLength; i < kNumFrames; ++i) {
+    EXPECT_TRUE(Insert(seq_num + i, kKeyFrame, kFirst, kNotLast, 0, nullptr,
+                       timestamp + 10 * i));
+  }
+  ASSERT_EQ(kNumFrames, packet_buffer_->GetUniqueFramesSeen());
+
+  // Very old packets should be treated as unique.
+  EXPECT_TRUE(
+      Insert(seq_num, kKeyFrame, kFirst, kNotLast, 0, nullptr, timestamp));
+  ASSERT_EQ(kNumFrames + 1, packet_buffer_->GetUniqueFramesSeen());
+}
+
 TEST_F(TestPacketBuffer, ExpandBuffer) {
   const uint16_t seq_num = Rand();
 
diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc
index b54e2a0..ffb925d 100644
--- a/video/receive_statistics_proxy.cc
+++ b/video/receive_statistics_proxy.cc
@@ -136,6 +136,16 @@
               << stream_duration_sec << "\n";
   }
 
+  RTC_LOG(LS_INFO) << "Frames decoded " << stats_.frames_decoded;
+
+  if (num_unique_frames_) {
+    int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded;
+    RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DroppedFrames.Receiver",
+                              num_dropped_frames);
+    RTC_LOG(LS_INFO) << "WebRTC.Video.DroppedFrames.Receiver "
+                     << num_dropped_frames;
+  }
+
   if (first_report_block_time_ms_ != -1 &&
       ((clock_->TimeInMilliseconds() - first_report_block_time_ms_) / 1000) >=
           metrics::kMinRunTimeInSeconds) {
@@ -579,6 +589,11 @@
   delay_counter_.Add(target_delay_ms + avg_rtt_ms_ / 2);
 }
 
+void ReceiveStatisticsProxy::OnUniqueFramesCounted(int num_unique_frames) {
+  rtc::CritScope lock(&crit_);
+  num_unique_frames_.emplace(num_unique_frames);
+}
+
 void ReceiveStatisticsProxy::OnTimingFrameInfoUpdated(
     const TimingFrameInfo& info) {
   rtc::CritScope lock(&crit_);
diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h
index bdeee86..836bb4b 100644
--- a/video/receive_statistics_proxy.h
+++ b/video/receive_statistics_proxy.h
@@ -60,6 +60,8 @@
   void OnPreDecode(const EncodedImage& encoded_image,
                    const CodecSpecificInfo* codec_specific_info);
 
+  void OnUniqueFramesCounted(int num_unique_frames);
+
   // Indicates video stream has been paused (no incoming packets).
   void OnStreamInactive();
 
@@ -188,6 +190,7 @@
   // called from const GetStats().
   mutable rtc::MovingMaxCounter<TimingFrameInfo> timing_frame_info_counter_
       RTC_GUARDED_BY(&crit_);
+  rtc::Optional<int> num_unique_frames_ RTC_GUARDED_BY(crit_);
 };
 
 }  // namespace webrtc
diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc
index 7a06f1c..e01e85f 100644
--- a/video/rtp_video_stream_receiver.cc
+++ b/video/rtp_video_stream_receiver.cc
@@ -589,6 +589,10 @@
                                                : RtcpMode::kOff);
 }
 
+int RtpVideoStreamReceiver::GetUniqueFramesSeen() const {
+  return packet_buffer_->GetUniqueFramesSeen();
+}
+
 void RtpVideoStreamReceiver::StartReceive() {
   RTC_DCHECK_CALLED_SEQUENTIALLY(&worker_task_checker_);
   receiving_ = true;
diff --git a/video/rtp_video_stream_receiver.h b/video/rtp_video_stream_receiver.h
index aa5b781..71cb5db 100644
--- a/video/rtp_video_stream_receiver.h
+++ b/video/rtp_video_stream_receiver.h
@@ -97,6 +97,9 @@
 
   void SignalNetworkState(NetworkState state);
 
+  // Returns number of different frames seen in the packet buffer.
+  int GetUniqueFramesSeen() const;
+
   // Implements RtpPacketSinkInterface.
   void OnRtpPacket(const RtpPacketReceived& packet) override;
 
diff --git a/video/video_receive_stream.cc b/video/video_receive_stream.cc
index dcffc9f..9a572ca 100644
--- a/video/video_receive_stream.cc
+++ b/video/video_receive_stream.cc
@@ -234,6 +234,9 @@
   RTC_DCHECK_CALLED_SEQUENTIALLY(&worker_sequence_checker_);
   rtp_video_stream_receiver_.StopReceive();
 
+  stats_proxy_.OnUniqueFramesCounted(
+      rtp_video_stream_receiver_.GetUniqueFramesSeen());
+
   frame_buffer_->Stop();
   call_stats_->DeregisterStatsObserver(this);
   call_stats_->DeregisterStatsObserver(&rtp_video_stream_receiver_);