Support setting a list of CSRCs in RtpEncodingParameters

This is a reupload of
https://webrtc-review.googlesource.com/c/src/+/392980.

CSRCs are commonly used to indicate which participants have contributed
to a media packet. This change makes it possible to specify the CSRCs
for both audio and video tracks.

As demonstrated in the integration test, the CSRCs can be set either
when adding a track to a peer connection or via a SetParameters call on
an existing sender.

Bug: b/410811496
Change-Id: I3f8f0b1e22680abb7d83ec27ac8025414dcacaae
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/395800
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Helmer Nylén <helmern@google.com>
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44909}
diff --git a/api/rtp_parameters.h b/api/rtp_parameters.h
index fb711a7..3bb65d8 100644
--- a/api/rtp_parameters.h
+++ b/api/rtp_parameters.h
@@ -477,6 +477,14 @@
   // unset SSRC acts as a "wildcard" SSRC.
   std::optional<uint32_t> ssrc;
 
+  // The list of CSRCs to be included in the RTP header. Defaults to an empty
+  // list. At most 15 CSRCs can be specified, and they must be the same for all
+  // encodings in an RtpParameters struct.
+  //
+  // If this field is set, the list is replaced with the specified values.
+  // Otherwise, it is left unchanged. Specify an empty vector to clear the list.
+  std::optional<std::vector<uint32_t>> csrcs;
+
   // The relative bitrate priority of this encoding. Currently this is
   // implemented for the entire rtp sender by using the value of the first
   // encoding parameter.
@@ -561,7 +569,8 @@
   std::optional<RtpCodec> codec;
 
   bool operator==(const RtpEncodingParameters& o) const {
-    return ssrc == o.ssrc && bitrate_priority == o.bitrate_priority &&
+    return ssrc == o.ssrc && csrcs == o.csrcs &&
+           bitrate_priority == o.bitrate_priority &&
            network_priority == o.network_priority &&
            max_bitrate_bps == o.max_bitrate_bps &&
            min_bitrate_bps == o.min_bitrate_bps &&
diff --git a/api/transport/rtp/BUILD.gn b/api/transport/rtp/BUILD.gn
index 9982346..9facb7a 100644
--- a/api/transport/rtp/BUILD.gn
+++ b/api/transport/rtp/BUILD.gn
@@ -16,6 +16,7 @@
     "../../../api/units:time_delta",
     "../../../api/units:timestamp",
     "../../../rtc_base:checks",
+    "//third_party/abseil-cpp/absl/strings:str_format",
   ]
 }
 
diff --git a/api/transport/rtp/DEPS b/api/transport/rtp/DEPS
new file mode 100644
index 0000000..97daa2b
--- /dev/null
+++ b/api/transport/rtp/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+  "rtp_source\.h": [
+    "+absl/strings/str_format.h",
+  ],
+}
diff --git a/api/transport/rtp/rtp_source.h b/api/transport/rtp/rtp_source.h
index 4732044..3f73fdc 100644
--- a/api/transport/rtp/rtp_source.h
+++ b/api/transport/rtp/rtp_source.h
@@ -15,6 +15,7 @@
 
 #include <optional>
 
+#include "absl/strings/str_format.h"
 #include "api/rtp_headers.h"
 #include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
@@ -84,6 +85,13 @@
     return extensions_.local_capture_clock_offset;
   }
 
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const RtpSource& source) {
+    absl::Format(&sink, "{ source_type: %s, source_id: %u }",
+                 source.source_type() == RtpSourceType::CSRC ? "CSRC" : "SSRC",
+                 source.source_id());
+  }
+
   bool operator==(const RtpSource& o) const {
     return timestamp_ == o.timestamp() && source_id_ == o.source_id() &&
            source_type_ == o.source_type() &&
diff --git a/media/BUILD.gn b/media/BUILD.gn
index f6359d4..203a55b 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -245,6 +245,7 @@
     "../api:audio_options_api",
     "../api:field_trials_view",
     "../api:rtc_error",
+    "../api:rtp_headers",
     "../api:rtp_parameters",
     "../api:rtp_transceiver_direction",
     "../api:scoped_refptr",
diff --git a/media/base/media_engine.cc b/media/base/media_engine.cc
index 3a3cc36..b81a8f4 100644
--- a/media/base/media_engine.cc
+++ b/media/base/media_engine.cc
@@ -23,6 +23,7 @@
 #include "api/array_view.h"
 #include "api/field_trials_view.h"
 #include "api/rtc_error.h"
+#include "api/rtp_headers.h"
 #include "api/rtp_parameters.h"
 #include "api/rtp_transceiver_direction.h"
 #include "api/video/video_codec_constants.h"
@@ -217,6 +218,20 @@
                              "different encodings.");
       }
     }
+
+    if (rtp_parameters.encodings[i].csrcs.has_value() &&
+        rtp_parameters.encodings[i].csrcs.value().size() > kRtpCsrcSize) {
+      LOG_AND_RETURN_ERROR(
+          RTCErrorType::INVALID_RANGE,
+          "Attempted to set more than the maximum allowed number of CSRCs.")
+    }
+
+    if (i > 0 && rtp_parameters.encodings[i - 1].csrcs !=
+                     rtp_parameters.encodings[i].csrcs) {
+      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION,
+                           "Attempted to set different CSRCs for different "
+                           "encodings.");
+    }
   }
 
   if (has_scale_resolution_down_to &&
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index e7bca15..7f3b7d8 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -2153,6 +2153,9 @@
       new_send_state = true;
     }
   }
+  bool new_csrcs =
+      (new_parameters.encodings[0].csrcs.has_value() &&
+       new_parameters.encodings[0].csrcs != rtp_parameters_.encodings[0].csrcs);
 
   rtp_parameters_ = new_parameters;
   // Codecs are currently handled at the WebRtcVideoSendChannel level.
@@ -2167,6 +2170,9 @@
       stream_->SetSource(source_, GetDegradationPreference());
     }
   }
+  if (stream_ && new_csrcs) {
+    stream_->SetCsrcs(rtp_parameters_.encodings[0].csrcs.value());
+  }
   // Check if a key frame was requested via setParameters.
   std::vector<std::string> key_frames_requested_by_rid;
   for (const auto& encoding : rtp_parameters_.encodings) {
@@ -2669,6 +2675,10 @@
     stream_ = call_->CreateVideoSendStream(std::move(config),
                                            parameters_.encoder_config.Copy());
   }
+  if (!rtp_parameters_.encodings.empty() &&
+      rtp_parameters_.encodings[0].csrcs.has_value()) {
+    stream_->SetCsrcs(rtp_parameters_.encodings[0].csrcs.value());
+  }
 
   parameters_.encoder_config.encoder_specific_settings = nullptr;
 
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 08d3e7f..3690ffb 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -1100,16 +1100,23 @@
     double old_priority = rtp_parameters_.encodings[0].bitrate_priority;
     Priority old_dscp = rtp_parameters_.encodings[0].network_priority;
     bool old_adaptive_ptime = rtp_parameters_.encodings[0].adaptive_ptime;
+    std::optional<std::vector<uint32_t>> old_csrcs =
+        rtp_parameters_.encodings[0].csrcs;
+
     rtp_parameters_ = parameters;
     config_.bitrate_priority = rtp_parameters_.encodings[0].bitrate_priority;
     config_.has_dscp =
         (rtp_parameters_.encodings[0].network_priority != Priority::kLow);
+    if (rtp_parameters_.encodings[0].csrcs.has_value()) {
+      config_.rtp.csrcs = rtp_parameters_.encodings[0].csrcs.value();
+    }
 
     bool reconfigure_send_stream =
         (rtp_parameters_.encodings[0].max_bitrate_bps != old_rtp_max_bitrate) ||
         (rtp_parameters_.encodings[0].bitrate_priority != old_priority) ||
         (rtp_parameters_.encodings[0].network_priority != old_dscp) ||
-        (rtp_parameters_.encodings[0].adaptive_ptime != old_adaptive_ptime);
+        (rtp_parameters_.encodings[0].adaptive_ptime != old_adaptive_ptime) ||
+        (rtp_parameters_.encodings[0].csrcs != old_csrcs);
     if (rtp_parameters_.encodings[0].max_bitrate_bps != old_rtp_max_bitrate) {
       // Update the bitrate range.
       if (send_rate) {
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 76d0833..7ce2609 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -1288,6 +1288,120 @@
             callee_receivers[1]->streams()[0]);
 }
 
+// Used by the CSRC tests below.
+MATCHER_P(IsCsrcWithId, csrc, "") {
+  return arg.source_type() == RtpSourceType::CSRC && arg.source_id() == csrc;
+}
+
+// Test that CSRCs can be set upon adding tracks and that they are received by
+// the other side.
+TEST_F(PeerConnectionIntegrationTestUnifiedPlan, EndToEndCallForwardsCsrcs) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+
+  constexpr uint32_t kAudioCsrc = 1234;
+  constexpr uint32_t kVideoCsrc = 5678;
+  RtpEncodingParameters audio_encoding;
+  RtpEncodingParameters video_encoding;
+  audio_encoding.csrcs = {kAudioCsrc};
+  video_encoding.csrcs = {kVideoCsrc};
+  ASSERT_THAT(caller()->pc()->AddTrack(caller()->CreateLocalAudioTrack(), {},
+                                       {audio_encoding}),
+              IsRtcOk());
+  ASSERT_THAT(caller()->pc()->AddTrack(caller()->CreateLocalVideoTrack(), {},
+                                       {video_encoding}),
+              IsRtcOk());
+
+  caller()->CreateAndSetAndSignalOffer();
+
+  // Wait for some packets to arrive.
+  ASSERT_THAT(
+      WaitUntil([&] { return SignalingStateStable(); }, ::testing::IsTrue()),
+      IsRtcOk());
+  ASSERT_THAT(WaitUntil(
+                  [&] {
+                    return callee()->audio_frames_received() > 0 &&
+                           callee()->min_video_frames_received_per_track() > 0;
+                  },
+                  ::testing::IsTrue(), {.timeout = kMaxWaitForFrames}),
+              IsRtcOk());
+
+  std::vector<scoped_refptr<RtpReceiverInterface>> audio_receivers =
+      callee()->GetReceiversOfType(webrtc::MediaType::AUDIO);
+  ASSERT_EQ(audio_receivers.size(), 1u);
+  std::vector<scoped_refptr<RtpReceiverInterface>> video_receivers =
+      callee()->GetReceiversOfType(webrtc::MediaType::VIDEO);
+  ASSERT_EQ(video_receivers.size(), 1u);
+
+  EXPECT_THAT(audio_receivers[0]->GetSources(),
+              Contains(IsCsrcWithId(kAudioCsrc)));
+  EXPECT_THAT(video_receivers[0]->GetSources(),
+              Contains(IsCsrcWithId(kVideoCsrc)));
+}
+
+// Test that CSRCs can be updated on active tracks.
+TEST_F(PeerConnectionIntegrationTestUnifiedPlan, EndToEndCallCanUpdateCsrcs) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+
+  RtpEncodingParameters audio_encoding;
+  RtpEncodingParameters video_encoding;
+  audio_encoding.csrcs = {1234};
+  video_encoding.csrcs = {5678};
+
+  scoped_refptr<RtpSenderInterface> audio_sender =
+      caller()
+          ->pc()
+          ->AddTrack(caller()->CreateLocalAudioTrack(), {}, {audio_encoding})
+          .MoveValue();
+  scoped_refptr<RtpSenderInterface> video_sender =
+      caller()
+          ->pc()
+          ->AddTrack(caller()->CreateLocalVideoTrack(), {}, {video_encoding})
+          .MoveValue();
+
+  caller()->CreateAndSetAndSignalOffer();
+
+  // Wait for some packets to arrive.
+  ASSERT_THAT(
+      WaitUntil([&] { return SignalingStateStable(); }, ::testing::IsTrue()),
+      IsRtcOk());
+  ASSERT_THAT(WaitUntil(
+                  [&] {
+                    return callee()->audio_frames_received() > 0 &&
+                           callee()->min_video_frames_received_per_track() > 0;
+                  },
+                  ::testing::IsTrue(), {.timeout = kMaxWaitForFrames}),
+              IsRtcOk());
+
+  // Update the CSRCs.
+  constexpr uint32_t kUpdatedAudioCsrc = 4321;
+  constexpr uint32_t kUpdatedVideoCsrc = 8765;
+  RtpParameters audio_parameters = audio_sender->GetParameters();
+  RtpParameters video_parameters = video_sender->GetParameters();
+  audio_parameters.encodings[0].csrcs = {kUpdatedAudioCsrc};
+  video_parameters.encodings[0].csrcs = {kUpdatedVideoCsrc};
+  ASSERT_THAT(audio_sender->SetParameters(audio_parameters), IsRtcOk());
+  ASSERT_THAT(video_sender->SetParameters(video_parameters), IsRtcOk());
+
+  std::vector<scoped_refptr<RtpReceiverInterface>> audio_receivers =
+      callee()->GetReceiversOfType(webrtc::MediaType::AUDIO);
+  ASSERT_EQ(audio_receivers.size(), 1u);
+  std::vector<scoped_refptr<RtpReceiverInterface>> video_receivers =
+      callee()->GetReceiversOfType(webrtc::MediaType::VIDEO);
+  ASSERT_EQ(video_receivers.size(), 1u);
+
+  // Ensure that the new CSRCs are used.
+  EXPECT_THAT(WaitUntil([&] { return audio_receivers[0]->GetSources(); },
+                        Contains(IsCsrcWithId(kUpdatedAudioCsrc)),
+                        {.timeout = kMaxWaitForFrames}),
+              IsRtcOk());
+  EXPECT_THAT(WaitUntil([&] { return video_receivers[0]->GetSources(); },
+                        Contains(IsCsrcWithId(kUpdatedVideoCsrc)),
+                        {.timeout = kMaxWaitForFrames}),
+              IsRtcOk());
+}
+
 // Test that if two video tracks are sent (from caller to callee, in this test),
 // they're transmitted correctly end-to-end.
 TEST_P(PeerConnectionIntegrationTest, EndToEndCallWithTwoVideoTracks) {