diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc index c7e11981ed..d918056743 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 #include #include "absl/algorithm/container.h" @@ -441,41 +442,112 @@ void RtcpTransceiverImpl::SchedulePeriodicCompoundPackets(int64_t delay_ms) { }); } -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 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(rtcp::ReceiverReport::kMaxNumberOfReportBlocks, + report_blocks.end() - report_block_it); + std::vector 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 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 @@ void RtcpTransceiverImpl::SendPeriodicCompoundPacket() { auto send_packet = [this](rtc::ArrayView 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 @@ void RtcpTransceiverImpl::SendImmediateFeedback( 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 @@ void RtcpTransceiverImpl::SendImmediateFeedback( } std::vector 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 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 b03db7d786..1339ba1e67 100644 --- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h +++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h @@ -104,14 +104,24 @@ class RtcpTransceiverImpl { 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 CreateReportBlocks(Timestamp now); + // Generate Report Blocks to be send in Sender or Receiver Reports. + std::vector 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 cad361cce0..b957173493 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 @@ namespace { using ::testing::_; using ::testing::ElementsAre; +using ::testing::Ge; using ::testing::NiceMock; using ::testing::Return; using ::testing::SizeIs; @@ -946,6 +947,61 @@ TEST(RtcpTransceiverImplTest, EXPECT_EQ(CompactNtpRttToMs(report_blocks[1].delay_since_last_sr()), 100); } +TEST(RtcpTransceiverImplTest, MaySendMultipleReceiverReportInSinglePacket) { + std::vector 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(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;