Add support for RFC8888 feedback to loss event log plot

Bug: webrtc:436707095
Change-Id: I622d5ffa3d1027c601f81610c1c41c50465bc324
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/403560
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45310}
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.cc b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
index 2395046..916ace1 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.cc
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.cc
@@ -616,8 +616,11 @@
   plots_.RegisterPlot("simulated_goog_cc", [this](Plot* plot) {
     this->CreateGoogCcSimulationGraph(plot);
   });
+  plots_.RegisterPlot("outgoing_loss", [this](Plot* plot) {
+    this->CreateOutgoingLossRateGraph(plot);
+  });
   plots_.RegisterPlot("outgoing_twcc_loss", [this](Plot* plot) {
-    this->CreateOutgoingTWCCLossRateGraph(plot);
+    this->CreateOutgoingLossRateGraph(plot);
   });
   plots_.RegisterPlot("outgoing_ecn_feedback", [this](Plot* plot) {
     this->CreateOutgoingEcnFeedbackGraph(plot);
@@ -1627,7 +1630,68 @@
                           kTopMargin);
 }
 
-void EventLogAnalyzer::CreateOutgoingTWCCLossRateGraph(Plot* plot) const {
+void EventLogAnalyzer::CreateOutgoingLossRateGraph(Plot* plot) const {
+  struct PacketLossPerFeedback {
+    Timestamp timestamp;              // Time when this feedback was received.
+    int num_packets_in_feedback = 0;  // Includes lost packets.
+    int num_lost_packets = 0;         // In this specific feedback.
+    int num_reordered_packets = 0;    // Packets received in this feedback, but
+                                      // was previously reported as lost.
+    int num_missing_feedback =
+        0;  // Packets missing feedback between this report and the previous.
+  };
+
+  class LossFeedbackBuilder {
+   public:
+    void AddPacket(uint16_t sequence_number, TimeDelta arrival_time_delta) {
+      last_unwrapped_sequence_number_ =
+          sequence_number_unwrapper_.Unwrap(sequence_number);
+      if (!first_sequence_number_.has_value()) {
+        first_sequence_number_ = last_unwrapped_sequence_number_;
+      }
+      ++num_packets_;
+      if (arrival_time_delta.IsInfinite()) {
+        lost_sequence_numbers_.insert(last_unwrapped_sequence_number_);
+      } else {
+        num_reordered_packets_ += previous_lost_sequence_numbers_.count(
+            last_unwrapped_sequence_number_);
+      }
+    }
+
+    void Update(PacketLossPerFeedback& feedback) {
+      feedback.num_packets_in_feedback += num_packets_;
+      feedback.num_lost_packets += lost_sequence_numbers_.size();
+      feedback.num_reordered_packets += num_reordered_packets_;
+      if (first_sequence_number_.has_value() &&
+          previous_feedback_highest_seq_number_.has_value()) {
+        feedback.num_missing_feedback +=
+            *first_sequence_number_ - *previous_feedback_highest_seq_number_ -
+            1;
+      }
+
+      // Prepare for next feedback.
+      first_sequence_number_ = std::nullopt;
+      previous_lost_sequence_numbers_.insert(lost_sequence_numbers_.begin(),
+                                             lost_sequence_numbers_.end());
+      previous_feedback_highest_seq_number_ = last_unwrapped_sequence_number_;
+      lost_sequence_numbers_.clear();
+      num_reordered_packets_ = 0;
+      num_packets_ = 0;
+    }
+
+   private:
+    int64_t last_unwrapped_sequence_number_ = 0;
+    int num_reordered_packets_ = 0;
+    int num_packets_ = 0;
+    std::optional<int64_t> first_sequence_number_;
+
+    std::unordered_set<int64_t> lost_sequence_numbers_;
+    std::unordered_set<int64_t> previous_lost_sequence_numbers_;
+    std::optional<int64_t> previous_feedback_highest_seq_number_;
+
+    RtpSequenceNumberUnwrapper sequence_number_unwrapper_;
+  };
+
   TimeSeries loss_rate_series("Loss rate (from packet feedback)",
                               LineStyle::kLine, PointStyle::kHighlight);
   TimeSeries reordered_packets_between_feedback(
@@ -1637,6 +1701,47 @@
                                       LineStyle::kLine, PointStyle::kHighlight);
   TimeSeries missing_feedback_series("Missing feedback", LineStyle::kNone,
                                      PointStyle::kHighlight);
+
+  std::vector<PacketLossPerFeedback> loss_per_feedback;
+
+  if (!parsed_log_.congestion_feedback(kIncomingPacket).empty()) {
+    plot->SetTitle("Outgoing loss rate (from CCFB)");
+
+    std::map</*ssrc*/ uint32_t, LossFeedbackBuilder> per_ssrc_builder;
+    for (const LoggedRtcpCongestionControlFeedback& feedback :
+         parsed_log_.congestion_feedback(kIncomingPacket)) {
+      const rtcp::CongestionControlFeedback& transport_feedback =
+          feedback.congestion_feedback;
+
+      PacketLossPerFeedback packet_loss_per_feedback = {
+          .timestamp = feedback.log_time()};
+      for (const rtcp::CongestionControlFeedback::PacketInfo& packet :
+           transport_feedback.packets()) {
+        per_ssrc_builder[packet.ssrc].AddPacket(packet.sequence_number,
+                                                packet.arrival_time_offset);
+      }
+      for (auto& [ssrc, builder] : per_ssrc_builder) {
+        builder.Update(packet_loss_per_feedback);
+      }
+      loss_per_feedback.push_back(packet_loss_per_feedback);
+    }
+  } else if (!parsed_log_.transport_feedbacks(kIncomingPacket).empty()) {
+    plot->SetTitle("Outgoing loss rate (from TWCC)");
+
+    LossFeedbackBuilder builder;
+    for (const LoggedRtcpPacketTransportFeedback& feedback :
+         parsed_log_.transport_feedbacks(kIncomingPacket)) {
+      feedback.transport_feedback.ForAllPackets(
+          [&](uint16_t sequence_number, TimeDelta receive_time_delta) {
+            builder.AddPacket(sequence_number, receive_time_delta);
+          });
+      PacketLossPerFeedback packet_loss_per_feedback = {
+          .timestamp = feedback.log_time()};
+      builder.Update(packet_loss_per_feedback);
+      loss_per_feedback.push_back(packet_loss_per_feedback);
+    }
+  }
+
   PacketLossSummary window_summary;
   Timestamp last_observation_receive_time = Timestamp::Zero();
 
@@ -1644,66 +1749,37 @@
   constexpr TimeDelta kObservationDuration = TimeDelta::Millis(250);
   constexpr uint32_t kObservationWindowSize = 20;
   std::deque<PacketLossSummary> observations;
-  SeqNumUnwrapper<uint16_t> unwrapper;
-  int64_t last_acked = 1;
-  if (!parsed_log_.transport_feedbacks(kIncomingPacket).empty()) {
-    last_acked =
-        unwrapper.Unwrap(parsed_log_.transport_feedbacks(kIncomingPacket)[0]
-                             .transport_feedback.GetBaseSequence());
-  }
-  std::unordered_set<int64_t> previous_feedback_lost_sequence_numbers;
-  size_t previous_feedback_size = 0;
-  for (auto& feedback : parsed_log_.transport_feedbacks(kIncomingPacket)) {
-    const rtcp::TransportFeedback& transport_feedback =
-        feedback.transport_feedback;
-    int64_t base_seq_num =
-        unwrapper.Unwrap(transport_feedback.GetBaseSequence());
-    // Collect packets that do not have feedback, which are from the last acked
-    // packet, to the current base packet.
-    for (int64_t seq_num = last_acked; seq_num < base_seq_num; ++seq_num) {
+  int previous_feedback_size = 0;
+  for (const PacketLossPerFeedback& feedback : loss_per_feedback) {
+    for (int64_t num = 0; num < feedback.num_missing_feedback; ++num) {
       missing_feedback_series.points.emplace_back(
-          config_.GetCallTimeSec(feedback.timestamp),
-          100 + seq_num - last_acked);
+          config_.GetCallTimeSec(feedback.timestamp), 100 + num);
     }
-    last_acked = base_seq_num + transport_feedback.GetPacketStatusCount();
-
-    int num_reordered_packets = 0;
-    std::unordered_set<int64_t> lost_sequence_numbers;
-    transport_feedback.ForAllPackets(
-        [&](uint16_t sequence_number, TimeDelta receive_time_delta) {
-          int64_t unwrapped_seq = unwrapper.Unwrap(sequence_number);
-          if (receive_time_delta.IsInfinite()) {
-            lost_sequence_numbers.insert(unwrapped_seq);
-          } else {
-            num_reordered_packets +=
-                previous_feedback_lost_sequence_numbers.count(unwrapped_seq);
-          }
-        });
 
     // Compute loss rate from the transport feedback.
-    float loss_rate =
-        static_cast<float>(lost_sequence_numbers.size() * 100.0 /
-                           transport_feedback.GetPacketStatusCount());
+    float loss_rate = static_cast<float>(feedback.num_lost_packets * 100.0 /
+                                         feedback.num_packets_in_feedback);
 
     loss_rate_series.points.emplace_back(
         config_.GetCallTimeSec(feedback.timestamp), loss_rate);
     float reordered_rate =
         previous_feedback_size == 0
             ? 0
-            : static_cast<float>(num_reordered_packets * 100.0 /
+            : static_cast<float>(feedback.num_reordered_packets * 100.0 /
                                  previous_feedback_size);
+    previous_feedback_size = feedback.num_packets_in_feedback;
     reordered_packets_between_feedback.points.emplace_back(
         config_.GetCallTimeSec(feedback.timestamp), reordered_rate);
 
     // Compute loss rate in a window of kObservationWindowSize.
     if (window_summary.num_packets == 0) {
-      window_summary.base_time = feedback.log_time();
+      window_summary.base_time = feedback.timestamp;
     }
-    window_summary.num_packets += transport_feedback.GetPacketStatusCount();
+    window_summary.num_packets += feedback.num_packets_in_feedback;
     window_summary.num_lost_packets +=
-        lost_sequence_numbers.size() - num_reordered_packets;
+        feedback.num_lost_packets - feedback.num_reordered_packets;
 
-    const Timestamp last_received_time = feedback.log_time();
+    const Timestamp last_received_time = feedback.timestamp;
     const TimeDelta observation_duration =
         window_summary.base_time == Timestamp::Zero()
             ? TimeDelta::Zero()
@@ -1732,8 +1808,6 @@
       }
       window_summary = PacketLossSummary();
     }
-    std::swap(previous_feedback_lost_sequence_numbers, lost_sequence_numbers);
-    previous_feedback_size = transport_feedback.GetPacketStatusCount();
   }
   // Add the data set to the plot.
   plot->AppendTimeSeriesIfNotEmpty(std::move(loss_rate_series));
@@ -1746,7 +1820,6 @@
                  "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 100, "Loss rate (percent)", kBottomMargin,
                           kTopMargin);
-  plot->SetTitle("Outgoing loss rate (from TWCC feedback)");
 }
 
 void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) const {
@@ -2231,8 +2304,8 @@
   }
 
   // TODO(qingsi): There can be a large number of candidate pairs generated by
-  // certain calls and the frontend cannot render the chart in this case due to
-  // the failure of generating a palette with the same number of colors.
+  // certain calls and the frontend cannot render the chart in this case due
+  // to the failure of generating a palette with the same number of colors.
   for (auto& kv : configs_by_cp_id) {
     plot->AppendTimeSeries(std::move(kv.second));
   }
diff --git a/rtc_tools/rtc_event_log_visualizer/analyzer.h b/rtc_tools/rtc_event_log_visualizer/analyzer.h
index 134a45f..1055821 100644
--- a/rtc_tools/rtc_event_log_visualizer/analyzer.h
+++ b/rtc_tools/rtc_event_log_visualizer/analyzer.h
@@ -116,7 +116,7 @@
   void CreateBitrateAllocationGraph(PacketDirection direction,
                                     Plot* plot) const;
 
-  void CreateOutgoingTWCCLossRateGraph(Plot* plot) const;
+  void CreateOutgoingLossRateGraph(Plot* plot) const;
   void CreateOutgoingEcnFeedbackGraph(Plot* plot) const;
   void CreateIncomingEcnFeedbackGraph(Plot* plot) const;
   void CreateGoogCcSimulationGraph(Plot* plot) const;