Add RequestKeyFrame with Pli to RtcpTransceiver

Add support for reduced size mode.

Bug: webrtc:8239
Change-Id: I1d646f0d7848af6632c9204ce5b96ae24cfc0ad3
Reviewed-on: https://webrtc-review.googlesource.com/23681
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20812}
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver.cc b/modules/rtp_rtcp/source/rtcp_transceiver.cc
index fa104f1..7c668b9 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver.cc
@@ -90,4 +90,18 @@
   });
 }
 
+void RtcpTransceiver::RequestKeyFrame(std::vector<uint32_t> ssrcs) {
+  // TODO(danilchap): Replace with lambda with move capture when available.
+  struct RequestKeyFrameClosure {
+    void operator()() {
+      if (ptr)
+        ptr->RequestKeyFrame(ssrcs);
+    }
+
+    rtc::WeakPtr<RtcpTransceiverImpl> ptr;
+    std::vector<uint32_t> ssrcs;
+  };
+  task_queue_->PostTask(RequestKeyFrameClosure{ptr_, std::move(ssrcs)});
+}
+
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver.h b/modules/rtp_rtcp/source/rtcp_transceiver.h
index 5a9998b..4e571ee 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver.h
+++ b/modules/rtp_rtcp/source/rtcp_transceiver.h
@@ -44,6 +44,8 @@
   // Stops sending REMB in following compound packets.
   void UnsetRemb();
 
+  void RequestKeyFrame(std::vector<uint32_t> ssrcs);
+
  private:
   rtc::TaskQueue* const task_queue_;
   std::unique_ptr<RtcpTransceiverImpl> rtcp_transceiver_;
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_config.cc b/modules/rtp_rtcp/source/rtcp_transceiver_config.cc
index 9256d24..d6313ae 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_config.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_config.cc
@@ -48,6 +48,11 @@
     RTC_LOG(LS_ERROR) << debug_id << "outgoing transport must be set";
     return false;
   }
+  if (initial_report_delay_ms < 0) {
+    RTC_LOG(LS_ERROR) << debug_id << "delay " << initial_report_delay_ms
+                      << "ms before first report shouldn't be negative.";
+    return false;
+  }
   if (report_period_ms <= 0) {
     RTC_LOG(LS_ERROR) << debug_id << "period " << report_period_ms
                       << "ms between reports should be positive.";
@@ -58,6 +63,10 @@
                       << "missing task queue for periodic compound packets";
     return false;
   }
+  if (rtcp_mode != RtcpMode::kCompound && rtcp_mode != RtcpMode::kReducedSize) {
+    RTC_LOG(LS_ERROR) << debug_id << "unsupported rtcp mode";
+    return false;
+  }
   // TODO(danilchap): Remove or update the warning when RtcpTransceiver supports
   // send-only sessions.
   if (receive_statistics == nullptr)
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_config.h b/modules/rtp_rtcp/source/rtcp_transceiver_config.h
index 79f18c8..07f4c51 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_config.h
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_config.h
@@ -13,6 +13,7 @@
 
 #include <string>
 
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "rtc_base/task_queue.h"
 
 namespace webrtc {
@@ -51,6 +52,11 @@
   // Rtcp report block generator for outgoing receiver reports.
   ReceiveStatisticsProvider* receive_statistics = nullptr;
 
+  // Configures if sending should
+  //  enforce compound packets: https://tools.ietf.org/html/rfc4585#section-3.1
+  //  or allow reduced size packets: https://tools.ietf.org/html/rfc5506
+  // Receiving accepts both compound and reduced-size packets.
+  RtcpMode rtcp_mode = RtcpMode::kCompound;
   //
   // Tuning parameters.
   //
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
index 2c6eb8d..52bc489 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
@@ -17,6 +17,7 @@
 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "modules/rtp_rtcp/source/rtcp_packet.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/pli.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/report_block.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/sdes.h"
@@ -28,11 +29,13 @@
 #include "rtc_base/timeutils.h"
 
 namespace webrtc {
-namespace {
 
 // Helper to put several RTCP packets into lower layer datagram composing
 // Compound or Reduced-Size RTCP packet, as defined by RFC 5506 section 2.
-class PacketSender : public rtcp::RtcpPacket::PacketReadyCallback {
+// TODO(danilchap): When in compound mode and packets are so many that several
+// compound RTCP packets need to be generated, ensure each packet is compound.
+class RtcpTransceiverImpl::PacketSender
+    : public rtcp::RtcpPacket::PacketReadyCallback {
  public:
   PacketSender(Transport* transport, size_t max_packet_size)
       : transport_(transport), max_packet_size_(max_packet_size) {
@@ -56,6 +59,8 @@
     }
   }
 
+  bool IsEmpty() const { return index_ == 0; }
+
  private:
   // Implements RtcpPacket::PacketReadyCallback
   void OnPacketReady(uint8_t* data, size_t length) override {
@@ -68,8 +73,6 @@
   uint8_t buffer_[IP_PACKET_SIZE];
 };
 
-}  // namespace
-
 RtcpTransceiverImpl::RtcpTransceiverImpl(const RtcpTransceiverConfig& config)
     : config_(config), ptr_factory_(this) {
   RTC_CHECK(config_.Validate());
@@ -94,12 +97,8 @@
 }
 
 void RtcpTransceiverImpl::SendCompoundPacket() {
-  SendPacket();
-  if (config_.schedule_periodic_compound_packets) {
-    // Stop existent send task.
-    ptr_factory_.InvalidateWeakPtrs();
-    SchedulePeriodicCompoundPackets(config_.report_period_ms);
-  }
+  SendPeriodicCompoundPacket();
+  ReschedulePeriodicCompoundPackets();
 }
 
 void RtcpTransceiverImpl::SetRemb(int bitrate_bps,
@@ -117,6 +116,27 @@
   remb_.reset();
 }
 
+void RtcpTransceiverImpl::RequestKeyFrame(
+    rtc::ArrayView<const uint32_t> ssrcs) {
+  RTC_DCHECK(!ssrcs.empty());
+  const uint32_t sender_ssrc = config_.feedback_ssrc;
+  PacketSender sender(config_.outgoing_transport, config_.max_packet_size);
+  if (config_.rtcp_mode == RtcpMode::kCompound)
+    CreateCompoundPacket(&sender);
+
+  for (uint32_t media_ssrc : ssrcs) {
+    rtcp::Pli pli;
+    pli.SetSenderSsrc(sender_ssrc);
+    pli.SetMediaSsrc(media_ssrc);
+    sender.AppendPacket(pli);
+  }
+
+  sender.Send();
+
+  if (config_.rtcp_mode == RtcpMode::kCompound)
+    ReschedulePeriodicCompoundPackets();
+}
+
 void RtcpTransceiverImpl::HandleReceivedPacket(
     const rtcp::CommonHeader& rtcp_packet_header,
     int64_t now_us) {
@@ -134,17 +154,25 @@
   }
 }
 
+void RtcpTransceiverImpl::ReschedulePeriodicCompoundPackets() {
+  if (!config_.schedule_periodic_compound_packets)
+    return;
+  // Stop existent send task.
+  ptr_factory_.InvalidateWeakPtrs();
+  SchedulePeriodicCompoundPackets(config_.report_period_ms);
+}
+
 void RtcpTransceiverImpl::SchedulePeriodicCompoundPackets(int64_t delay_ms) {
-  class SendPeriodicCompoundPacket : public rtc::QueuedTask {
+  class SendPeriodicCompoundPacketTask : public rtc::QueuedTask {
    public:
-    SendPeriodicCompoundPacket(rtc::TaskQueue* task_queue,
-                               rtc::WeakPtr<RtcpTransceiverImpl> ptr)
+    SendPeriodicCompoundPacketTask(rtc::TaskQueue* task_queue,
+                                   rtc::WeakPtr<RtcpTransceiverImpl> ptr)
         : task_queue_(task_queue), ptr_(std::move(ptr)) {}
     bool Run() override {
       RTC_DCHECK(task_queue_->IsCurrent());
       if (!ptr_)
         return true;
-      ptr_->SendPacket();
+      ptr_->SendPeriodicCompoundPacket();
       task_queue_->PostDelayedTask(rtc::WrapUnique(this),
                                    ptr_->config_.report_period_ms);
       return false;
@@ -157,7 +185,7 @@
 
   RTC_DCHECK(config_.schedule_periodic_compound_packets);
 
-  auto task = rtc::MakeUnique<SendPeriodicCompoundPacket>(
+  auto task = rtc::MakeUnique<SendPeriodicCompoundPacketTask>(
       config_.task_queue, ptr_factory_.GetWeakPtr());
   if (delay_ms > 0)
     config_.task_queue->PostDelayedTask(std::move(task), delay_ms);
@@ -165,27 +193,30 @@
     config_.task_queue->PostTask(std::move(task));
 }
 
-void RtcpTransceiverImpl::SendPacket() {
-  PacketSender sender(config_.outgoing_transport, config_.max_packet_size);
+void RtcpTransceiverImpl::CreateCompoundPacket(PacketSender* sender) {
+  RTC_DCHECK(sender->IsEmpty());
   const uint32_t sender_ssrc = config_.feedback_ssrc;
-
   rtcp::ReceiverReport receiver_report;
   receiver_report.SetSenderSsrc(sender_ssrc);
   receiver_report.SetReportBlocks(CreateReportBlocks());
-  sender.AppendPacket(receiver_report);
+  sender->AppendPacket(receiver_report);
 
   if (!config_.cname.empty()) {
     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);
+    sender->AppendPacket(sdes);
   }
   if (remb_) {
     remb_->SetSenderSsrc(sender_ssrc);
-    sender.AppendPacket(*remb_);
+    sender->AppendPacket(*remb_);
   }
+}
 
+void RtcpTransceiverImpl::SendPeriodicCompoundPacket() {
+  PacketSender sender(config_.outgoing_transport, config_.max_packet_size);
+  CreateCompoundPacket(&sender);
   sender.Send();
 }
 
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
index 689ed1e..aed850b 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
@@ -48,7 +48,10 @@
   // Stops sending REMB in following compound packets.
   void UnsetRemb();
 
+  void RequestKeyFrame(rtc::ArrayView<const uint32_t> ssrcs);
+
  private:
+  class PacketSender;
   struct SenderReportTimes {
     int64_t local_received_time_us;
     NtpTime remote_sent_time;
@@ -57,9 +60,13 @@
   void HandleReceivedPacket(const rtcp::CommonHeader& rtcp_packet_header,
                             int64_t now_us);
 
+  void ReschedulePeriodicCompoundPackets();
   void SchedulePeriodicCompoundPackets(int64_t delay_ms);
+  // Creates compound RTCP packet, as defined in
+  // https://tools.ietf.org/html/rfc5506#section-2
+  void CreateCompoundPacket(PacketSender* sender);
   // Sends RTCP packets.
-  void SendPacket();
+  void SendPeriodicCompoundPacket();
   // Generate Report Blocks to be send in Sender or Receiver Report.
   std::vector<rtcp::ReportBlock> CreateReportBlocks();
 
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
index b144ec4..e5f7a65 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
@@ -447,4 +447,69 @@
   EXPECT_EQ(CompactNtpRttToMs(report_blocks[1].delay_since_last_sr()), 100);
 }
 
+TEST(RtcpTransceiverImplTest, RequestKeyFrameWithPictureLossIndication) {
+  const uint32_t kSenderSsrc = 1234;
+  const uint32_t kRemoteSsrcs[] = {4321, 5321};
+  MockTransport outgoing_transport;
+  RtcpTransceiverConfig config;
+  config.feedback_ssrc = kSenderSsrc;
+  config.schedule_periodic_compound_packets = false;
+  config.outgoing_transport = &outgoing_transport;
+  RtcpTransceiverImpl rtcp_transceiver(config);
+  RtcpPacketParser rtcp_parser;
+  EXPECT_CALL(outgoing_transport, SendRtcp(_, _))
+      .WillOnce(Invoke(&rtcp_parser, &RtcpPacketParser::Parse));
+
+  rtcp_transceiver.RequestKeyFrame(kRemoteSsrcs);
+
+  // Expect a pli packet per ssrc in the sent single compound packet.
+  EXPECT_EQ(rtcp_parser.pli()->num_packets(), 2);
+  EXPECT_EQ(rtcp_parser.pli()->sender_ssrc(), kSenderSsrc);
+  // test::RtcpPacketParser overwrites first pli packet with second one.
+  EXPECT_EQ(rtcp_parser.pli()->media_ssrc(), kRemoteSsrcs[1]);
+}
+
+TEST(RtcpTransceiverImplTest, KeyFrameRequestCreatesCompoundPacket) {
+  const uint32_t kRemoteSsrcs[] = {4321};
+  MockTransport outgoing_transport;
+  RtcpTransceiverConfig config;
+  // Turn periodic off to ensure sent rtcp packet is explicitly requested.
+  config.schedule_periodic_compound_packets = false;
+  config.outgoing_transport = &outgoing_transport;
+
+  config.rtcp_mode = webrtc::RtcpMode::kCompound;
+
+  RtcpTransceiverImpl rtcp_transceiver(config);
+  RtcpPacketParser rtcp_parser;
+  EXPECT_CALL(outgoing_transport, SendRtcp(_, _))
+      .WillOnce(Invoke(&rtcp_parser, &RtcpPacketParser::Parse));
+
+  rtcp_transceiver.RequestKeyFrame(kRemoteSsrcs);
+
+  // Test sent packet is compound by expecting presense of receiver report.
+  EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 1);
+}
+
+TEST(RtcpTransceiverImplTest, KeyFrameRequestCreatesReducedSizePacket) {
+  const uint32_t kRemoteSsrcs[] = {4321};
+  MockTransport outgoing_transport;
+  RtcpTransceiverConfig config;
+  // Turn periodic off to ensure sent rtcp packet is explicitly requested.
+  config.schedule_periodic_compound_packets = false;
+  config.outgoing_transport = &outgoing_transport;
+
+  config.rtcp_mode = webrtc::RtcpMode::kReducedSize;
+
+  RtcpTransceiverImpl rtcp_transceiver(config);
+  RtcpPacketParser rtcp_parser;
+  // Expect some rtcp packet is triggered by the RequestKeyFrame.
+  EXPECT_CALL(outgoing_transport, SendRtcp(_, _))
+      .WillOnce(Invoke(&rtcp_parser, &RtcpPacketParser::Parse));
+
+  rtcp_transceiver.RequestKeyFrame(kRemoteSsrcs);
+
+  // Test sent packet is reduced size by expecting absense of receiver report.
+  EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 0);
+}
+
 }  // namespace