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}
This commit is contained in:
Danil Chapovalov
2022-02-04 16:27:30 +01:00
committed by WebRTC LUCI CQ
parent 38b80a1d44
commit 8c0aaae664
3 changed files with 171 additions and 31 deletions

View File

@ -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::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<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 @@ void RtcpTransceiverImpl::SendPeriodicCompoundPacket() {
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 @@ 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<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) {

View File

@ -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<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_;

View File

@ -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<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;