In RtcpTransceiver support sending more than one rtcp receiver report per packet

Bug: webrtc:8239
Change-Id: I7670b8c10fbcfad7238afecd96edd55ad65dd3aa
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/249792
Reviewed-by: Emil Lundmark <lndmrk@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35913}
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
index c7e1198..d918056 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
@@ -10,6 +10,7 @@
 
 #include "modules/rtp_rtcp/source/rtcp_transceiver_impl.h"
 
+#include <algorithm>
 #include <utility>
 
 #include "absl/algorithm/container.h"
@@ -441,41 +442,112 @@
       });
 }
 
-void RtcpTransceiverImpl::CreateCompoundPacket(PacketSender* sender) {
-  RTC_DCHECK(sender->IsEmpty());
-  const uint32_t sender_ssrc = config_.feedback_ssrc;
-  Timestamp now = config_.clock->CurrentTime();
-  rtcp::ReceiverReport receiver_report;
-  receiver_report.SetSenderSsrc(sender_ssrc);
-  receiver_report.SetReportBlocks(CreateReportBlocks(now));
-  if (config_.rtcp_mode == RtcpMode::kCompound ||
-      !receiver_report.report_blocks().empty()) {
-    sender->AppendPacket(receiver_report);
+void RtcpTransceiverImpl::FillReports(Timestamp now,
+                                      size_t reserved_bytes,
+                                      PacketSender& rtcp_sender) {
+  // Sender/receiver reports should be first in the RTCP packet.
+  RTC_DCHECK(rtcp_sender.IsEmpty());
+
+  size_t available_bytes = config_.max_packet_size;
+  if (reserved_bytes > available_bytes) {
+    // Because reserved_bytes is unsigned, substracting would underflow and will
+    // not produce desired result.
+    available_bytes = 0;
+  } else {
+    available_bytes -= reserved_bytes;
   }
 
-  if (!config_.cname.empty() && !sender->IsEmpty()) {
-    rtcp::Sdes sdes;
-    bool added = sdes.AddCName(config_.feedback_ssrc, config_.cname);
-    RTC_DCHECK(added) << "Failed to add cname " << config_.cname
-                      << " to rtcp sdes packet.";
-    sender->AppendPacket(sdes);
+  static constexpr size_t kReceiverReportSizeBytes = 8;
+  static constexpr size_t kFullReceiverReportSizeBytes =
+      kReceiverReportSizeBytes +
+      rtcp::ReceiverReport::kMaxNumberOfReportBlocks *
+          rtcp::ReportBlock::kLength;
+  size_t max_full_receiver_reports =
+      available_bytes / kFullReceiverReportSizeBytes;
+  size_t max_report_blocks = max_full_receiver_reports *
+                             rtcp::ReceiverReport::kMaxNumberOfReportBlocks;
+  size_t available_bytes_for_last_receiver_report =
+      available_bytes -
+      max_full_receiver_reports * kFullReceiverReportSizeBytes;
+  if (available_bytes_for_last_receiver_report >= kReceiverReportSizeBytes) {
+    max_report_blocks +=
+        (available_bytes_for_last_receiver_report - kReceiverReportSizeBytes) /
+        rtcp::ReportBlock::kLength;
   }
-  if (remb_) {
+
+  std::vector<rtcp::ReportBlock> report_blocks =
+      CreateReportBlocks(now, max_report_blocks);
+
+  size_t num_receiver_reports =
+      (report_blocks.size() + rtcp::ReceiverReport::kMaxNumberOfReportBlocks -
+       1) /
+      rtcp::ReceiverReport::kMaxNumberOfReportBlocks;
+
+  // In compund mode each RTCP packet has to start with a sender or receiver
+  // report.
+  if (config_.rtcp_mode == RtcpMode::kCompound && num_receiver_reports == 0) {
+    num_receiver_reports = 1;
+  }
+
+  auto report_block_it = report_blocks.begin();
+  for (size_t i = 0; i < num_receiver_reports; ++i) {
+    rtcp::ReceiverReport receiver_report;
+    receiver_report.SetSenderSsrc(config_.feedback_ssrc);
+    size_t num_blocks =
+        std::min<size_t>(rtcp::ReceiverReport::kMaxNumberOfReportBlocks,
+                         report_blocks.end() - report_block_it);
+    std::vector<rtcp::ReportBlock> sub_blocks(report_block_it,
+                                              report_block_it + num_blocks);
+    receiver_report.SetReportBlocks(std::move(sub_blocks));
+    report_block_it += num_blocks;
+    rtcp_sender.AppendPacket(receiver_report);
+  }
+  // All report blocks should be attached at this point.
+  RTC_DCHECK_EQ(report_blocks.end() - report_block_it, 0);
+}
+
+void RtcpTransceiverImpl::CreateCompoundPacket(Timestamp now,
+                                               size_t reserved_bytes,
+                                               PacketSender& sender) {
+  RTC_DCHECK(sender.IsEmpty());
+  absl::optional<rtcp::Sdes> sdes;
+  if (!config_.cname.empty()) {
+    sdes.emplace();
+    bool added = sdes->AddCName(config_.feedback_ssrc, config_.cname);
+    RTC_DCHECK(added) << "Failed to add CNAME " << config_.cname
+                      << " to RTCP SDES packet.";
+    reserved_bytes += sdes->BlockLength();
+  }
+  if (remb_.has_value()) {
+    reserved_bytes += remb_->BlockLength();
+  }
+  if (config_.non_sender_rtt_measurement) {
+    // 4 bytes for common RTCP header + 4 bytes for the ExtenedReports header.
+    reserved_bytes += (4 + 4 + rtcp::Rrtr::kLength);
+  }
+
+  FillReports(now, reserved_bytes, sender);
+  const uint32_t sender_ssrc = config_.feedback_ssrc;
+
+  if (sdes.has_value() && !sender.IsEmpty()) {
+    sender.AppendPacket(*sdes);
+  }
+  if (remb_.has_value()) {
     remb_->SetSenderSsrc(sender_ssrc);
-    sender->AppendPacket(*remb_);
+    sender.AppendPacket(*remb_);
   }
   // TODO(bugs.webrtc.org/8239): Do not send rrtr if this packet starts with
   // SenderReport instead of ReceiverReport
   // when RtcpTransceiver supports rtp senders.
   if (config_.non_sender_rtt_measurement) {
     rtcp::ExtendedReports xr;
+    xr.SetSenderSsrc(sender_ssrc);
 
     rtcp::Rrtr rrtr;
     rrtr.SetNtp(config_.clock->ConvertTimestampToNtpTime(now));
     xr.SetRrtr(rrtr);
 
-    xr.SetSenderSsrc(sender_ssrc);
-    sender->AppendPacket(xr);
+    sender.AppendPacket(xr);
   }
 }
 
@@ -483,8 +555,9 @@
   auto send_packet = [this](rtc::ArrayView<const uint8_t> packet) {
     config_.outgoing_transport->SendRtcp(packet.data(), packet.size());
   };
+  Timestamp now = config_.clock->CurrentTime();
   PacketSender sender(send_packet, config_.max_packet_size);
-  CreateCompoundPacket(&sender);
+  CreateCompoundPacket(now, /*reserved_bytes=*/0, sender);
   sender.Send();
 }
 
@@ -510,8 +583,11 @@
   PacketSender sender(send_packet, config_.max_packet_size);
   // Compound mode requires every sent rtcp packet to be compound, i.e. start
   // with a sender or receiver report.
-  if (config_.rtcp_mode == RtcpMode::kCompound)
-    CreateCompoundPacket(&sender);
+  if (config_.rtcp_mode == RtcpMode::kCompound) {
+    Timestamp now = config_.clock->CurrentTime();
+    CreateCompoundPacket(now, /*reserved_bytes=*/rtcp_packet.BlockLength(),
+                         sender);
+  }
 
   sender.AppendPacket(rtcp_packet);
   sender.Send();
@@ -522,14 +598,12 @@
 }
 
 std::vector<rtcp::ReportBlock> RtcpTransceiverImpl::CreateReportBlocks(
-    Timestamp now) {
+    Timestamp now,
+    size_t num_max_blocks) {
   if (!config_.receive_statistics)
     return {};
-  // TODO(danilchap): Support sending more than
-  // `ReceiverReport::kMaxNumberOfReportBlocks` per compound rtcp packet.
   std::vector<rtcp::ReportBlock> report_blocks =
-      config_.receive_statistics->RtcpReportBlocks(
-          rtcp::ReceiverReport::kMaxNumberOfReportBlocks);
+      config_.receive_statistics->RtcpReportBlocks(num_max_blocks);
   uint32_t last_sr = 0;
   uint32_t last_delay = 0;
   for (rtcp::ReportBlock& report_block : report_blocks) {
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
index b03db7d..1339ba1 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
@@ -104,14 +104,24 @@
 
   void ReschedulePeriodicCompoundPackets();
   void SchedulePeriodicCompoundPackets(int64_t delay_ms);
+  // Appends RTCP receiver reports with attached report blocks to the `sender`.
+  // Uses up to `config_.max_packet_size - reserved_bytes`
+  void FillReports(Timestamp now,
+                   size_t reserved_bytes,
+                   PacketSender& rtcp_sender);
+
   // Creates compound RTCP packet, as defined in
   // https://tools.ietf.org/html/rfc5506#section-2
-  void CreateCompoundPacket(PacketSender* sender);
+  void CreateCompoundPacket(Timestamp now,
+                            size_t reserved_bytes,
+                            PacketSender& rtcp_sender);
+
   // Sends RTCP packets.
   void SendPeriodicCompoundPacket();
   void SendImmediateFeedback(const rtcp::RtcpPacket& rtcp_packet);
-  // Generate Report Blocks to be send in Sender or Receiver Report.
-  std::vector<rtcp::ReportBlock> CreateReportBlocks(Timestamp now);
+  // Generate Report Blocks to be send in Sender or Receiver Reports.
+  std::vector<rtcp::ReportBlock> CreateReportBlocks(Timestamp now,
+                                                    size_t num_max_blocks);
 
   const RtcpTransceiverConfig config_;
 
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
index cad361c..b957173 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
@@ -40,6 +40,7 @@
 
 using ::testing::_;
 using ::testing::ElementsAre;
+using ::testing::Ge;
 using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::SizeIs;
@@ -946,6 +947,61 @@
   EXPECT_EQ(CompactNtpRttToMs(report_blocks[1].delay_since_last_sr()), 100);
 }
 
+TEST(RtcpTransceiverImplTest, MaySendMultipleReceiverReportInSinglePacket) {
+  std::vector<ReportBlock> statistics_report_blocks(40);
+  MockReceiveStatisticsProvider receive_statistics;
+  EXPECT_CALL(receive_statistics, RtcpReportBlocks(/*max_blocks=*/Ge(40u)))
+      .WillOnce(Return(statistics_report_blocks));
+
+  SimulatedClock clock(0);
+  RtcpTransceiverConfig config = DefaultTestConfig();
+  config.clock = &clock;
+  RtcpPacketParser rtcp_parser;
+  RtcpParserTransport transport(&rtcp_parser);
+  config.outgoing_transport = &transport;
+  config.receive_statistics = &receive_statistics;
+  RtcpTransceiverImpl rtcp_transceiver(config);
+
+  // Trigger ReceiverReports.
+  rtcp_transceiver.SendCompoundPacket();
+
+  // Expect a single RTCP packet with multiple receiver reports in it.
+  EXPECT_EQ(transport.num_packets(), 1);
+  // Receiver report may contain up to 31 report blocks, thus 2 reports are
+  // needed to carry 40 blocks: 31 in the first, 9 in the last.
+  EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 2);
+  // RtcpParser remembers just the last receiver report, thus can't check number
+  // of blocks in the first receiver report.
+  EXPECT_THAT(rtcp_parser.receiver_report()->report_blocks(), SizeIs(9));
+}
+
+TEST(RtcpTransceiverImplTest, AttachMaxNumberOfReportBlocksToCompoundPacket) {
+  MockReceiveStatisticsProvider receive_statistics;
+  EXPECT_CALL(receive_statistics, RtcpReportBlocks)
+      .WillOnce([](size_t max_blocks) {
+        return std::vector<ReportBlock>(max_blocks);
+      });
+  SimulatedClock clock(0);
+  RtcpTransceiverConfig config = DefaultTestConfig();
+  config.clock = &clock;
+  config.rtcp_mode = RtcpMode::kCompound;
+  RtcpPacketParser rtcp_parser;
+  RtcpParserTransport transport(&rtcp_parser);
+  config.outgoing_transport = &transport;
+  config.receive_statistics = &receive_statistics;
+  RtcpTransceiverImpl rtcp_transceiver(config);
+
+  EXPECT_EQ(transport.num_packets(), 0);
+  // Send some fast feedback message. Because of compound mode, report blocks
+  // should be attached.
+  rtcp_transceiver.SendPictureLossIndication(/*ssrc=*/123);
+
+  // Expect single RTCP packet with multiple receiver reports and a PLI.
+  EXPECT_EQ(transport.num_packets(), 1);
+  EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 1);
+  EXPECT_EQ(rtcp_parser.pli()->num_packets(), 1);
+}
+
 TEST(RtcpTransceiverImplTest, SendsNack) {
   const uint32_t kSenderSsrc = 1234;
   const uint32_t kRemoteSsrc = 4321;