Add RtpVideoSender::SendVideoLayersAllocation

This adds a method to allow VideoLayersAllocation to be sent using the header extension RtpVideoLayersAllocationExtension.

Bug: webrtc:12000
Change-Id: Iafdc1e16911c57ca55d7cc0559a0b45774211e92
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/187495
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32397}
diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc
index 5e36693..8294891 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video.cc
@@ -34,6 +34,7 @@
 #include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h"
 #include "modules/rtp_rtcp/source/rtp_header_extensions.h"
 #include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h"
 #include "modules/rtp_rtcp/source/time_util.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/experiments/field_trial_parser.h"
@@ -129,6 +130,18 @@
              : absl::nullopt;
 }
 
+// Some packets can be skipped and the stream can still be decoded. Those
+// packets are less likely to be retransmitted if they are lost.
+bool PacketWillLikelyBeRequestedForRestransmitionIfLost(
+    const RTPVideoHeader& video_header) {
+  return IsBaseLayer(video_header) &&
+         !(video_header.generic.has_value()
+               ? absl::c_linear_search(
+                     video_header.generic->decode_target_indications,
+                     DecodeTargetIndication::kDiscardable)
+               : false);
+}
+
 }  // namespace
 
 RTPSenderVideo::RTPSenderVideo(const Config& config)
@@ -140,6 +153,7 @@
               : (kRetransmitBaseLayer | kConditionallyRetransmitHigherLayers)),
       last_rotation_(kVideoRotation_0),
       transmit_color_space_next_frame_(false),
+      send_allocation_(false),
       current_playout_delay_{-1, -1},
       playout_delay_pending_(false),
       forced_playout_delay_(LoadVideoPlayoutDelayOverride(config.field_trials)),
@@ -223,11 +237,15 @@
     frame_transformer_delegate_->SetVideoStructureUnderLock(video_structure);
     return;
   }
-  // Lock is being held by SetVideoStructure() caller.
-  SetVideoStructureUnderLock(video_structure);
+  SetVideoStructureInternal(video_structure);
 }
 
-void RTPSenderVideo::SetVideoStructureUnderLock(
+void RTPSenderVideo::SetVideoStructureAfterTransformation(
+    const FrameDependencyStructure* video_structure) {
+  SetVideoStructureInternal(video_structure);
+}
+
+void RTPSenderVideo::SetVideoStructureInternal(
     const FrameDependencyStructure* video_structure) {
   RTC_DCHECK_RUNS_SERIALIZED(&send_checker_);
   if (video_structure == nullptr) {
@@ -257,6 +275,28 @@
   video_structure_->structure_id = structure_id;
 }
 
+void RTPSenderVideo::SetVideoLayersAllocation(
+    VideoLayersAllocation allocation) {
+  if (frame_transformer_delegate_) {
+    frame_transformer_delegate_->SetVideoLayersAllocationUnderLock(
+        std::move(allocation));
+    return;
+  }
+  SetVideoLayersAllocationInternal(std::move(allocation));
+}
+
+void RTPSenderVideo::SetVideoLayersAllocationAfterTransformation(
+    VideoLayersAllocation allocation) {
+  SetVideoLayersAllocationInternal(std::move(allocation));
+}
+
+void RTPSenderVideo::SetVideoLayersAllocationInternal(
+    VideoLayersAllocation allocation) {
+  RTC_DCHECK_RUNS_SERIALIZED(&send_checker_);
+  allocation_ = std::move(allocation);
+  send_allocation_ = true;
+}
+
 void RTPSenderVideo::AddRtpHeaderExtensions(
     const RTPVideoHeader& video_header,
     const absl::optional<AbsoluteCaptureTime>& absolute_capture_time,
@@ -387,6 +427,18 @@
           generic_descriptor);
     }
   }
+
+  if (first_packet && send_allocation_) {
+    if (video_header.frame_type == VideoFrameType::kVideoFrameKey) {
+      packet->SetExtension<RtpVideoLayersAllocationExtension>(
+          allocation_.value());
+    } else if (PacketWillLikelyBeRequestedForRestransmitionIfLost(
+                   video_header)) {
+      VideoLayersAllocation allocation = allocation_.value();
+      allocation.resolution_and_frame_rate_is_valid = false;
+      packet->SetExtension<RtpVideoLayersAllocationExtension>(allocation);
+    }
+  }
 }
 
 bool RTPSenderVideo::SendVideo(
@@ -417,10 +469,15 @@
   }
 
   MaybeUpdateCurrentPlayoutDelay(video_header);
-  if (video_header.frame_type == VideoFrameType::kVideoFrameKey &&
-      !IsNoopDelay(current_playout_delay_)) {
-    // Force playout delay on key-frames, if set.
-    playout_delay_pending_ = true;
+  if (video_header.frame_type == VideoFrameType::kVideoFrameKey) {
+    if (!IsNoopDelay(current_playout_delay_)) {
+      // Force playout delay on key-frames, if set.
+      playout_delay_pending_ = true;
+    }
+    if (allocation_) {
+      // Send the bitrate allocation on every key frame.
+      send_allocation_ = true;
+    }
   }
 
   if (video_structure_ != nullptr && video_header.generic) {
@@ -638,15 +695,11 @@
   }
 
   if (video_header.frame_type == VideoFrameType::kVideoFrameKey ||
-      (IsBaseLayer(video_header) &&
-       !(video_header.generic.has_value()
-             ? absl::c_linear_search(
-                   video_header.generic->decode_target_indications,
-                   DecodeTargetIndication::kDiscardable)
-             : false))) {
-    // This frame has guaranteed delivery, no need to populate playout
+      PacketWillLikelyBeRequestedForRestransmitionIfLost(video_header)) {
+    // This frame will likely be delivered, no need to populate playout
     // delay extensions until it changes again.
     playout_delay_pending_ = false;
+    send_allocation_ = false;
   }
 
   TRACE_EVENT_ASYNC_END1("webrtc", "Video", capture_time_ms, "timestamp",
diff --git a/modules/rtp_rtcp/source/rtp_sender_video.h b/modules/rtp_rtcp/source/rtp_sender_video.h
index 7e70ef2..3f431df 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video.h
+++ b/modules/rtp_rtcp/source/rtp_sender_video.h
@@ -24,6 +24,7 @@
 #include "api/transport/rtp/dependency_descriptor.h"
 #include "api/video/video_codec_type.h"
 #include "api/video/video_frame_type.h"
+#include "api/video/video_layers_allocation.h"
 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "modules/rtp_rtcp/source/absolute_capture_time_sender.h"
 #include "modules/rtp_rtcp/source/active_decode_targets_helper.h"
@@ -117,9 +118,22 @@
   // All calls to SendVideo after this call must use video_header compatible
   // with the video_structure.
   void SetVideoStructure(const FrameDependencyStructure* video_structure);
-  void SetVideoStructureUnderLock(
+  // Should only be used by a RTPSenderVideoFrameTransformerDelegate and exists
+  // to ensure correct syncronization.
+  void SetVideoStructureAfterTransformation(
       const FrameDependencyStructure* video_structure);
 
+  // Sets current active VideoLayersAllocation. The allocation will be sent
+  // using the rtp video layers allocation extension. The allocation will be
+  // sent in full on every key frame. The allocation will be sent once on a
+  // none discardable delta frame per call to this method and will not contain
+  // resolution and frame rate.
+  void SetVideoLayersAllocation(VideoLayersAllocation allocation);
+  // Should only be used by a RTPSenderVideoFrameTransformerDelegate and exists
+  // to ensure correct syncronization.
+  void SetVideoLayersAllocationAfterTransformation(
+      VideoLayersAllocation allocation);
+
   // Returns the current packetization overhead rate, in bps. Note that this is
   // the payload overhead, eg the VP8 payload headers, not the RTP headers
   // or extension/
@@ -145,6 +159,10 @@
     int64_t last_frame_time_ms;
   };
 
+  void SetVideoStructureInternal(
+      const FrameDependencyStructure* video_structure);
+  void SetVideoLayersAllocationInternal(VideoLayersAllocation allocation);
+
   void AddRtpHeaderExtensions(
       const RTPVideoHeader& video_header,
       const absl::optional<AbsoluteCaptureTime>& absolute_capture_time,
@@ -181,10 +199,14 @@
   bool transmit_color_space_next_frame_ RTC_GUARDED_BY(send_checker_);
   std::unique_ptr<FrameDependencyStructure> video_structure_
       RTC_GUARDED_BY(send_checker_);
+  absl::optional<VideoLayersAllocation> allocation_
+      RTC_GUARDED_BY(send_checker_);
+  // Flag indicating if we should send |allocation_|.
+  bool send_allocation_ RTC_GUARDED_BY(send_checker_);
 
   // Current target playout delay.
   VideoPlayoutDelay current_playout_delay_ RTC_GUARDED_BY(send_checker_);
-  // Flag indicating if we need to propagate |current_playout_delay_| in order
+  // Flag indicating if we need to send |current_playout_delay_| in order
   // to guarantee it gets delivered.
   bool playout_delay_pending_;
   // Set by the field trial WebRTC-ForceSendPlayoutDelay to override the playout
diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc
index 786e467..074b640 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc
@@ -162,7 +162,14 @@
     const FrameDependencyStructure* video_structure) {
   MutexLock lock(&sender_lock_);
   RTC_CHECK(sender_);
-  sender_->SetVideoStructureUnderLock(video_structure);
+  sender_->SetVideoStructureAfterTransformation(video_structure);
+}
+
+void RTPSenderVideoFrameTransformerDelegate::SetVideoLayersAllocationUnderLock(
+    VideoLayersAllocation allocation) {
+  MutexLock lock(&sender_lock_);
+  RTC_CHECK(sender_);
+  sender_->SetVideoLayersAllocationAfterTransformation(std::move(allocation));
 }
 
 void RTPSenderVideoFrameTransformerDelegate::Reset() {
diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h
index a14ce3a..8573869 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h
+++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h
@@ -16,6 +16,7 @@
 #include "api/frame_transformer_interface.h"
 #include "api/scoped_refptr.h"
 #include "api/task_queue/task_queue_base.h"
+#include "api/video/video_layers_allocation.h"
 #include "rtc_base/synchronization/mutex.h"
 
 namespace webrtc {
@@ -51,10 +52,16 @@
   // Delegates the call to RTPSendVideo::SendVideo on the |encoder_queue_|.
   void SendVideo(std::unique_ptr<TransformableFrameInterface> frame) const;
 
-  // Delegates the call to RTPSendVideo::SendVideo under |sender_lock_|.
+  // Delegates the call to RTPSendVideo::SetVideoStructureAfterTransformation
+  // under |sender_lock_|.
   void SetVideoStructureUnderLock(
       const FrameDependencyStructure* video_structure);
 
+  // Delegates the call to
+  // RTPSendVideo::SetVideoLayersAllocationAfterTransformation under
+  // |sender_lock_|.
+  void SetVideoLayersAllocationUnderLock(VideoLayersAllocation allocation);
+
   // Unregisters and releases the |frame_transformer_| reference, and resets
   // |sender_| under lock. Called from RTPSenderVideo destructor to prevent the
   // |sender_| to dangle.
diff --git a/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc b/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc
index 9d4f81f..e415bad 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc
@@ -33,6 +33,7 @@
 #include "modules/rtp_rtcp/source/rtp_header_extensions.h"
 #include "modules/rtp_rtcp/source/rtp_packet_received.h"
 #include "modules/rtp_rtcp/source/rtp_rtcp_impl2.h"
+#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h"
 #include "modules/rtp_rtcp/source/time_util.h"
 #include "rtc_base/arraysize.h"
 #include "rtc_base/rate_limiter.h"
@@ -66,7 +67,8 @@
   kVideoRotationExtensionId,
   kVideoTimingExtensionId,
   kAbsoluteCaptureTimeExtensionId,
-  kPlayoutDelayExtensionId
+  kPlayoutDelayExtensionId,
+  kVideoLayersAllocationExtensionId,
 };
 
 constexpr int kPayload = 100;
@@ -98,6 +100,8 @@
         kAbsoluteCaptureTimeExtensionId);
     receivers_extensions_.Register<PlayoutDelayLimits>(
         kPlayoutDelayExtensionId);
+    receivers_extensions_.Register<RtpVideoLayersAllocationExtension>(
+        kVideoLayersAllocationExtensionId);
   }
 
   bool SendRtp(const uint8_t* data,
@@ -821,6 +825,152 @@
   UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed(1);
 }
 
+TEST_P(RtpSenderVideoTest, VideoLayersAllocationWithResolutionSentOnKeyFrames) {
+  const size_t kFrameSize = 100;
+  uint8_t kFrame[kFrameSize];
+  rtp_module_->RegisterRtpHeaderExtension(
+      RtpVideoLayersAllocationExtension::kUri,
+      kVideoLayersAllocationExtensionId);
+
+  VideoLayersAllocation allocation;
+  VideoLayersAllocation::SpatialLayer layer;
+  layer.width = 360;
+  layer.height = 180;
+  layer.target_bitrate_per_temporal_layer.push_back(
+      DataRate::KilobitsPerSec(50));
+  allocation.resolution_and_frame_rate_is_valid = true;
+  allocation.active_spatial_layers.push_back(layer);
+  rtp_sender_video_->SetVideoLayersAllocation(allocation);
+
+  RTPVideoHeader hdr;
+  hdr.frame_type = VideoFrameType::kVideoFrameKey;
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+
+  VideoLayersAllocation sent_allocation;
+  EXPECT_TRUE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+  EXPECT_THAT(sent_allocation.active_spatial_layers, ElementsAre(layer));
+
+  // Next key frame also have the allocation.
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+  EXPECT_TRUE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+}
+
+TEST_P(RtpSenderVideoTest,
+       VideoLayersAllocationWithoutResolutionSentOnDeltaFrames) {
+  const size_t kFrameSize = 100;
+  uint8_t kFrame[kFrameSize];
+  rtp_module_->RegisterRtpHeaderExtension(
+      RtpVideoLayersAllocationExtension::kUri,
+      kVideoLayersAllocationExtensionId);
+
+  VideoLayersAllocation allocation;
+  VideoLayersAllocation::SpatialLayer layer;
+  layer.width = 360;
+  layer.height = 180;
+  allocation.resolution_and_frame_rate_is_valid = true;
+  layer.target_bitrate_per_temporal_layer.push_back(
+      DataRate::KilobitsPerSec(50));
+
+  allocation.active_spatial_layers.push_back(layer);
+  rtp_sender_video_->SetVideoLayersAllocation(allocation);
+
+  RTPVideoHeader hdr;
+  hdr.frame_type = VideoFrameType::kVideoFrameDelta;
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+  VideoLayersAllocation sent_allocation;
+  EXPECT_TRUE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+  ASSERT_THAT(sent_allocation.active_spatial_layers, SizeIs(1));
+  EXPECT_FALSE(sent_allocation.resolution_and_frame_rate_is_valid);
+  EXPECT_THAT(sent_allocation.active_spatial_layers[0]
+                  .target_bitrate_per_temporal_layer,
+              SizeIs(1));
+}
+
+TEST_P(RtpSenderVideoTest, VideoLayersAllocationSentOnDeltaFramesOnlyOnUpdate) {
+  const size_t kFrameSize = 100;
+  uint8_t kFrame[kFrameSize];
+  rtp_module_->RegisterRtpHeaderExtension(
+      RtpVideoLayersAllocationExtension::kUri,
+      kVideoLayersAllocationExtensionId);
+
+  VideoLayersAllocation allocation;
+  VideoLayersAllocation::SpatialLayer layer;
+  layer.target_bitrate_per_temporal_layer.push_back(
+      DataRate::KilobitsPerSec(50));
+  allocation.active_spatial_layers.push_back(layer);
+  rtp_sender_video_->SetVideoLayersAllocation(allocation);
+
+  RTPVideoHeader hdr;
+  hdr.frame_type = VideoFrameType::kVideoFrameDelta;
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+
+  VideoLayersAllocation sent_allocation;
+  EXPECT_TRUE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+  EXPECT_THAT(sent_allocation.active_spatial_layers, SizeIs(1));
+
+  // VideoLayersAllocation not sent on the next delta frame.
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+  EXPECT_FALSE(transport_.last_sent_packet()
+                   .HasExtension<RtpVideoLayersAllocationExtension>());
+
+  // Update allocation. VideoLayesAllocation should be sent on the next frame.
+  rtp_sender_video_->SetVideoLayersAllocation(allocation);
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+  EXPECT_TRUE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+}
+
+TEST_P(RtpSenderVideoTest, VideoLayersAllocationNotSentOnHigherTemporalLayers) {
+  const size_t kFrameSize = 100;
+  uint8_t kFrame[kFrameSize];
+  rtp_module_->RegisterRtpHeaderExtension(
+      RtpVideoLayersAllocationExtension::kUri,
+      kVideoLayersAllocationExtensionId);
+
+  VideoLayersAllocation allocation;
+  VideoLayersAllocation::SpatialLayer layer;
+  layer.target_bitrate_per_temporal_layer.push_back(
+      DataRate::KilobitsPerSec(50));
+  allocation.active_spatial_layers.push_back(layer);
+  rtp_sender_video_->SetVideoLayersAllocation(allocation);
+
+  RTPVideoHeader hdr;
+  hdr.frame_type = VideoFrameType::kVideoFrameDelta;
+  hdr.codec = VideoCodecType::kVideoCodecVP8;
+  auto& vp8_header = hdr.video_type_header.emplace<RTPVideoHeaderVP8>();
+  vp8_header.temporalIdx = 1;
+
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+  VideoLayersAllocation sent_allocation;
+  EXPECT_FALSE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+
+  // Send a delta frame on tl0.
+  vp8_header.temporalIdx = 0;
+  rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr,
+                               kDefaultExpectedRetransmissionTimeMs);
+  EXPECT_TRUE(
+      transport_.last_sent_packet()
+          .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation));
+}
+
 TEST_P(RtpSenderVideoTest, AbsoluteCaptureTime) {
   constexpr int64_t kAbsoluteCaptureTimestampMs = 12345678;
   uint8_t kFrame[kMaxPacketLength];
diff --git a/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc b/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc
index bbec45a..dbaa36b 100644
--- a/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc
+++ b/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc
@@ -148,6 +148,7 @@
   rtc::BitBuffer reader(data.data(), data.size());
   if (!allocation)
     return false;
+  allocation->active_spatial_layers.clear();
 
   uint32_t val;
   // NS: