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;