Add support for setting CSRCs on audio and video senders

This is a modified version of
https://webrtc-review.googlesource.com/c/src/+/392940 to avoid breaking
downstream dependencies.

With this change, CSRCs can be added to video packets sent via
RTPSenderVideo::SendEncodedImage. This is implemented by keeping a list
of CSRCs in the calling class RtpVideoSender, which is included in all
calls to SendEncodedImage. Similarly, a list of CSRCs for audio packets
is kept on ChannelSend and added to calls to SendRtpAudio. CSRCs are
also propagated to frame transformers for audio and video.

Bug: b/410811496
Change-Id: I728934f8c190120211672e2d6dc5940bc8f83838
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/395301
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Jonas Oreland <jonaso@webrtc.org>
Commit-Queue: Helmer Nylén <helmern@google.com>
Cr-Commit-Position: refs/heads/main@{#44907}
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
index 2077d97..06ebdd2 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -163,6 +163,9 @@
   // Muting, Volume and Level.
   void SetInputMute(bool enable) override;
 
+  // CSRCs.
+  void SetCsrcs(ArrayView<const uint32_t> csrcs) override;
+
   // Stats.
   ANAStats GetANAStatistics() const override;
 
@@ -320,6 +323,8 @@
   mutable Mutex bitrate_accountant_mutex_;
   AudioBitrateAccountant bitrate_accountant_
       RTC_GUARDED_BY(bitrate_accountant_mutex_);
+
+  std::vector<uint32_t> csrcs_ RTC_GUARDED_BY(encoder_queue_checker_);
 };
 
 const int kTelephoneEventAttenuationdB = 10;
@@ -393,12 +398,11 @@
     frame_transformer_delegate_->Transform(
         frameType, payloadType, rtp_timestamp + rtp_rtcp_->StartTimestamp(),
         payloadData, payloadSize, absolute_capture_timestamp_ms,
-        rtp_rtcp_->SSRC(), mime_type.str(), audio_level_dbov);
+        rtp_rtcp_->SSRC(), mime_type.str(), audio_level_dbov, csrcs_);
     return 0;
   }
   return SendRtpAudio(frameType, payloadType, rtp_timestamp, payload,
-                      absolute_capture_timestamp_ms, /*csrcs=*/{},
-                      audio_level_dbov);
+                      absolute_capture_timestamp_ms, csrcs_, audio_level_dbov);
 }
 
 int32_t ChannelSend::SendRtpAudio(AudioFrameType frameType,
@@ -694,6 +698,17 @@
   return input_mute_;
 }
 
+void ChannelSend::SetCsrcs(ArrayView<const uint32_t> csrcs) {
+  RTC_DCHECK_RUN_ON(&worker_thread_checker_);
+  std::vector<uint32_t> csrcs_copy(
+      csrcs.begin(),
+      csrcs.begin() + std::min<size_t>(csrcs.size(), kRtpCsrcSize));
+  encoder_queue_->PostTask([this, csrcs = std::move(csrcs_copy)]() mutable {
+    RTC_DCHECK_RUN_ON(&encoder_queue_checker_);
+    csrcs_ = csrcs;
+  });
+}
+
 bool ChannelSend::SendTelephoneEventOutband(int event, int duration_ms) {
   RTC_DCHECK_RUN_ON(&worker_thread_checker_);
   RTC_DCHECK_LE(0, event);
diff --git a/audio/channel_send.h b/audio/channel_send.h
index 8991fc0..0ed544a 100644
--- a/audio/channel_send.h
+++ b/audio/channel_send.h
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "absl/strings/string_view.h"
+#include "api/array_view.h"
 #include "api/audio/audio_frame.h"
 #include "api/audio_codecs/audio_encoder.h"
 #include "api/audio_codecs/audio_format.h"
@@ -90,6 +91,10 @@
   virtual void OnBitrateAllocation(BitrateAllocationUpdate update) = 0;
   virtual int GetTargetBitrate() const = 0;
   virtual void SetInputMute(bool muted) = 0;
+  // Sets the list of CSRCs to be included in the RTP header. If more than
+  // kRtpCsrcSize CSRCs are provided, only the first kRtpCsrcSize elements are
+  // kept.
+  virtual void SetCsrcs(ArrayView<const uint32_t> csrcs) = 0;
 
   virtual void ProcessAndEncodeAudio(
       std::unique_ptr<AudioFrame> audio_frame) = 0;
diff --git a/audio/channel_send_frame_transformer_delegate.cc b/audio/channel_send_frame_transformer_delegate.cc
index f1a8b4e..d82b0f5 100644
--- a/audio/channel_send_frame_transformer_delegate.cc
+++ b/audio/channel_send_frame_transformer_delegate.cc
@@ -10,11 +10,25 @@
 
 #include "audio/channel_send_frame_transformer_delegate.h"
 
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
 #include <utility>
 #include <vector>
 
+#include "api/array_view.h"
+#include "api/frame_transformer_interface.h"
+#include "api/scoped_refptr.h"
+#include "api/sequence_checker.h"
+#include "api/task_queue/task_queue_base.h"
 #include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
+#include "modules/audio_coding/include/audio_coding_module_typedefs.h"
+#include "rtc_base/buffer.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/synchronization/mutex.h"
 
 namespace webrtc {
 namespace {
@@ -183,14 +197,15 @@
     size_t payload_size,
     int64_t absolute_capture_timestamp_ms,
     uint32_t ssrc,
-    const std::string& codec_mimetype,
-    std::optional<uint8_t> audio_level_dbov) {
+    const std::string& codec_mime_type,
+    std::optional<uint8_t> audio_level_dbov,
+    const std::vector<uint32_t>& csrcs) {
   {
     MutexLock lock(&send_lock_);
     if (short_circuit_) {
       send_frame_callback_(frame_type, payload_type, rtp_timestamp,
                            ArrayView<const uint8_t>(payload_data, payload_size),
-                           absolute_capture_timestamp_ms, /*csrcs=*/{},
+                           absolute_capture_timestamp_ms, csrcs,
                            audio_level_dbov);
       return;
     }
@@ -198,8 +213,7 @@
   frame_transformer_->Transform(
       std::make_unique<TransformableOutgoingAudioFrame>(
           frame_type, payload_type, rtp_timestamp, payload_data, payload_size,
-          absolute_capture_timestamp_ms, ssrc,
-          /*csrcs=*/std::vector<uint32_t>(), codec_mimetype,
+          absolute_capture_timestamp_ms, ssrc, csrcs, codec_mime_type,
           /*sequence_number=*/std::nullopt, audio_level_dbov));
 }
 
diff --git a/audio/channel_send_frame_transformer_delegate.h b/audio/channel_send_frame_transformer_delegate.h
index eae9cac..f1a6d20 100644
--- a/audio/channel_send_frame_transformer_delegate.h
+++ b/audio/channel_send_frame_transformer_delegate.h
@@ -11,15 +11,21 @@
 #ifndef AUDIO_CHANNEL_SEND_FRAME_TRANSFORMER_DELEGATE_H_
 #define AUDIO_CHANNEL_SEND_FRAME_TRANSFORMER_DELEGATE_H_
 
+#include <cstddef>
+#include <cstdint>
+#include <functional>
 #include <memory>
+#include <optional>
 #include <string>
+#include <vector>
 
+#include "api/array_view.h"
 #include "api/frame_transformer_interface.h"
-#include "api/sequence_checker.h"
+#include "api/scoped_refptr.h"
 #include "api/task_queue/task_queue_base.h"
 #include "modules/audio_coding/include/audio_coding_module_typedefs.h"
-#include "rtc_base/buffer.h"
 #include "rtc_base/synchronization/mutex.h"
+#include "rtc_base/thread_annotations.h"
 
 namespace webrtc {
 
@@ -62,7 +68,8 @@
                  int64_t absolute_capture_timestamp_ms,
                  uint32_t ssrc,
                  const std::string& codec_mime_type,
-                 std::optional<uint8_t> audio_level_dbov);
+                 std::optional<uint8_t> audio_level_dbov,
+                 const std::vector<uint32_t>& csrcs = {});
 
   // Implements TransformedFrameCallback. Can be called on any thread.
   void OnTransformedFrame(
diff --git a/audio/channel_send_frame_transformer_delegate_unittest.cc b/audio/channel_send_frame_transformer_delegate_unittest.cc
index 2eac45c..f0d498b 100644
--- a/audio/channel_send_frame_transformer_delegate_unittest.cc
+++ b/audio/channel_send_frame_transformer_delegate_unittest.cc
@@ -23,6 +23,7 @@
 #include "api/scoped_refptr.h"
 #include "api/test/mock_frame_transformer.h"
 #include "api/test/mock_transformable_audio_frame.h"
+#include "api/units/timestamp.h"
 #include "modules/audio_coding/include/audio_coding_module_typedefs.h"
 #include "rtc_base/task_queue_for_test.h"
 #include "test/gmock.h"
@@ -200,6 +201,59 @@
   channel_queue.WaitForPreviouslyPostedTasks();
 }
 
+// Test that CSRCs are propagated correctly from the Transform call to the frame
+// transformer.
+TEST(ChannelSendFrameTransformerDelegateTest,
+     TransformForwardsCsrcsViaFrameTransformer) {
+  TaskQueueForTest channel_queue("channel_queue");
+  scoped_refptr<MockFrameTransformer> mock_frame_transformer =
+      make_ref_counted<NiceMock<MockFrameTransformer>>();
+  MockChannelSend mock_channel;
+  scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
+      make_ref_counted<ChannelSendFrameTransformerDelegate>(
+          mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
+  scoped_refptr<TransformedFrameCallback> callback;
+  EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameCallback)
+      .WillOnce(SaveArg<0>(&callback));
+  delegate->Init();
+  ASSERT_TRUE(callback);
+
+  std::vector<uint32_t> csrcs = {123, 234, 345, 456};
+  EXPECT_CALL(mock_channel,
+              SendFrame(_, _, _, _, _, ElementsAreArray(csrcs), _));
+  ON_CALL(*mock_frame_transformer, Transform)
+      .WillByDefault(
+          [&callback](std::unique_ptr<TransformableFrameInterface> frame) {
+            callback->OnTransformedFrame(std::move(frame));
+          });
+  delegate->Transform(
+      AudioFrameType::kEmptyFrame, 0, 0, mock_data, sizeof(mock_data), 0,
+      /*ssrc=*/0, /*mimeType=*/"audio/opus", /*audio_level_dbov=*/31, csrcs);
+  channel_queue.WaitForPreviouslyPostedTasks();
+}
+
+// Test that CSRCs are propagated correctly from the Transform call to the send
+// frame callback when short circuiting is enabled.
+TEST(ChannelSendFrameTransformerDelegateTest,
+     TransformForwardsCsrcsViaShortCircuiting) {
+  TaskQueueForTest channel_queue("channel_queue");
+  scoped_refptr<MockFrameTransformer> mock_frame_transformer =
+      make_ref_counted<testing::NiceMock<MockFrameTransformer>>();
+  MockChannelSend mock_channel;
+  scoped_refptr<ChannelSendFrameTransformerDelegate> delegate =
+      make_ref_counted<ChannelSendFrameTransformerDelegate>(
+          mock_channel.callback(), mock_frame_transformer, channel_queue.Get());
+
+  std::vector<uint32_t> csrcs = {123, 234, 345, 456};
+  delegate->StartShortCircuiting();
+  EXPECT_CALL(mock_channel,
+              SendFrame(_, _, _, _, _, ElementsAreArray(csrcs), _));
+  delegate->Transform(
+      AudioFrameType::kEmptyFrame, 0, 0, mock_data, sizeof(mock_data), 0,
+      /*ssrc=*/0, /*mimeType=*/"audio/opus", /*audio_level_dbov=*/31, csrcs);
+  channel_queue.WaitForPreviouslyPostedTasks();
+}
+
 // Test that if the delegate receives a transformed frame after it has been
 // reset, it does not run the SendFrameCallback, as the channel is destroyed
 // after resetting the delegate.
diff --git a/audio/channel_send_unittest.cc b/audio/channel_send_unittest.cc
index 7636ee3..2e8bfb6 100644
--- a/audio/channel_send_unittest.cc
+++ b/audio/channel_send_unittest.cc
@@ -409,6 +409,117 @@
   ProcessNextFrame();
 }
 
+TEST_F(ChannelSendTest, ConfiguredCsrcsAreIncludedInRtpPackets) {
+  channel_->StartSend();
+  std::vector<uint32_t> expected_csrcs = {1, 2, 3};
+  channel_->SetCsrcs(expected_csrcs);
+
+  std::vector<uint32_t> csrcs;
+  auto send_rtp = [&](ArrayView<const uint8_t> data,
+                      const PacketOptions& /* options */) {
+    RtpPacketReceived packet;
+    packet.Parse(data);
+    csrcs = packet.Csrcs();
+    return true;
+  };
+
+  EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(send_rtp));
+  ProcessNextFrame();
+  ProcessNextFrame();
+
+  EXPECT_EQ(csrcs, expected_csrcs);
+}
+
+// Creates a frame with the given CSRCs where other values are copied from the
+// template.
+std::unique_ptr<TransformableAudioFrameInterface> CreateMockFrameWithCsrcs(
+    const TransformableAudioFrameInterface* frame_template,
+    const std::vector<uint32_t>& csrcs) {
+  std::unique_ptr<MockTransformableAudioFrame> mock_frame =
+      std::make_unique<MockTransformableAudioFrame>();
+  EXPECT_CALL(*mock_frame, GetContributingSources)
+      .WillRepeatedly(Return(csrcs));
+
+  std::vector<uint8_t> frame_data = std::vector(
+      frame_template->GetData().begin(), frame_template->GetData().end());
+  ON_CALL(*mock_frame, GetData).WillByDefault(Return(frame_data));
+
+  ON_CALL(*mock_frame, GetTimestamp)
+      .WillByDefault(Return(frame_template->GetTimestamp()));
+  ON_CALL(*mock_frame, GetPayloadType)
+      .WillByDefault(Return(frame_template->GetPayloadType()));
+  ON_CALL(*mock_frame, GetSsrc)
+      .WillByDefault(Return(frame_template->GetSsrc()));
+  ON_CALL(*mock_frame, GetMimeType)
+      .WillByDefault(Return(frame_template->GetMimeType()));
+  ON_CALL(*mock_frame, SequenceNumber)
+      .WillByDefault(Return(frame_template->SequenceNumber()));
+  ON_CALL(*mock_frame, GetDirection)
+      .WillByDefault(Return(frame_template->GetDirection()));
+  ON_CALL(*mock_frame, AbsoluteCaptureTimestamp)
+      .WillByDefault(Return(frame_template->AbsoluteCaptureTimestamp()));
+  ON_CALL(*mock_frame, Type).WillByDefault(Return(frame_template->Type()));
+  ON_CALL(*mock_frame, AudioLevel)
+      .WillByDefault(Return(frame_template->AudioLevel()));
+  ON_CALL(*mock_frame, ReceiveTime)
+      .WillByDefault(Return(frame_template->ReceiveTime()));
+  ON_CALL(*mock_frame, CaptureTime)
+      .WillByDefault(Return(frame_template->CaptureTime()));
+  ON_CALL(*mock_frame, SenderCaptureTimeOffset)
+      .WillByDefault(Return(frame_template->SenderCaptureTimeOffset()));
+  return mock_frame;
+}
+
+TEST_F(ChannelSendTest, FrameTransformerTakesPrecedenceOverSetCsrcs) {
+  scoped_refptr<MockFrameTransformer> mock_frame_transformer =
+      make_ref_counted<MockFrameTransformer>();
+  scoped_refptr<TransformedFrameCallback> callback;
+  EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameCallback)
+      .WillOnce(SaveArg<0>(&callback));
+  EXPECT_CALL(*mock_frame_transformer, UnregisterTransformedFrameCallback);
+  channel_->SetEncoderToPacketizerFrameTransformer(mock_frame_transformer);
+
+  // Configure the mock frame transformer to return a frame with different CSRCs
+  // than it is provided.
+  std::vector<uint32_t> csrcs_provided_to_frame_transformer;
+  std::vector<uint32_t> csrcs_output_by_frame_transformer = {1, 2, 3};
+  EXPECT_CALL(*mock_frame_transformer, Transform)
+      .WillRepeatedly(
+          Invoke([&](std::unique_ptr<TransformableFrameInterface> frame) {
+            auto audio_frame =
+                static_cast<TransformableAudioFrameInterface*>(frame.get());
+            csrcs_provided_to_frame_transformer.assign(
+                audio_frame->GetContributingSources().begin(),
+                audio_frame->GetContributingSources().end());
+            callback->OnTransformedFrame(CreateMockFrameWithCsrcs(
+                audio_frame, csrcs_output_by_frame_transformer));
+          }));
+
+  std::vector<uint32_t> set_csrcs = {4, 5, 6};
+  channel_->SetCsrcs(set_csrcs);
+  channel_->StartSend();
+
+  std::vector<uint32_t> sent_csrcs;
+  auto send_rtp = [&](ArrayView<const uint8_t> data,
+                      const PacketOptions& /* options */) {
+    RtpPacketReceived packet;
+    packet.Parse(data);
+    sent_csrcs = packet.Csrcs();
+    return true;
+  };
+
+  EXPECT_CALL(transport_, SendRtp).WillRepeatedly(Invoke(send_rtp));
+  ProcessNextFrame();
+  ProcessNextFrame();
+
+  EXPECT_EQ(csrcs_provided_to_frame_transformer, set_csrcs)
+      << "The CSRCs configured in ChannelSend should be passed to the frame "
+         "transformer.";
+  EXPECT_EQ(sent_csrcs, csrcs_output_by_frame_transformer)
+      << "CSRCs provided by the frame transformer should propagate to the RTP "
+         "packet.";
+}
+
 }  // namespace
 }  // namespace voe
 }  // namespace webrtc
diff --git a/audio/mock_voe_channel_proxy.h b/audio/mock_voe_channel_proxy.h
index 0038f3a..cd2927d 100644
--- a/audio/mock_voe_channel_proxy.h
+++ b/audio/mock_voe_channel_proxy.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "absl/strings/string_view.h"
+#include "api/array_view.h"
 #include "api/audio/audio_frame.h"
 #include "api/audio/audio_mixer.h"
 #include "api/audio_codecs/audio_encoder.h"
@@ -200,6 +201,7 @@
               RegisterPacketOverhead,
               (int packet_byte_overhead),
               (override));
+  MOCK_METHOD(void, SetCsrcs, (ArrayView<const uint32_t> csrcs), (override));
 };
 }  // namespace test
 }  // namespace webrtc
diff --git a/call/BUILD.gn b/call/BUILD.gn
index aa7c506..0e553ea 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -401,6 +401,7 @@
   ]
   deps = [
     ":rtp_interfaces",
+    "../api:array_view",
     "../api:frame_transformer_interface",
     "../api:rtp_parameters",
     "../api:rtp_sender_interface",
diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
index 4943443..dfd9566 100644
--- a/call/rtp_video_sender.cc
+++ b/call/rtp_video_sender.cc
@@ -625,7 +625,7 @@
           encoded_image,
           params_[simulcast_index].GetRtpVideoHeader(
               encoded_image, codec_specific_info, frame_id),
-          expected_retransmission_time);
+          expected_retransmission_time, csrcs_);
   if (frame_count_observer_) {
     FrameCounts& counts = frame_counts_[simulcast_index];
     if (encoded_image._frameType == VideoFrameType::kVideoFrameKey) {
@@ -1031,6 +1031,12 @@
                                    rtp_config_.max_packet_size);
 }
 
+void RtpVideoSender::SetCsrcs(ArrayView<const uint32_t> csrcs) {
+  MutexLock lock(&mutex_);
+  csrcs_.assign(csrcs.begin(),
+                csrcs.begin() + std::min<size_t>(csrcs.size(), kRtpCsrcSize));
+}
+
 DataRate RtpVideoSender::CalculateOverheadRate(DataRate data_rate,
                                                DataSize packet_size,
                                                DataSize overhead_per_packet,
diff --git a/call/rtp_video_sender.h b/call/rtp_video_sender.h
index 6fce841..86a5338 100644
--- a/call/rtp_video_sender.h
+++ b/call/rtp_video_sender.h
@@ -152,6 +152,12 @@
   void SetEncodingData(size_t width, size_t height, size_t num_temporal_layers)
       RTC_LOCKS_EXCLUDED(mutex_) override;
 
+  // Sets the list of CSRCs to be included in every packet. If more than
+  // kRtpCsrcSize CSRCs are provided, only the first kRtpCsrcSize elements are
+  // kept.
+  void SetCsrcs(ArrayView<const uint32_t> csrcs)
+      RTC_LOCKS_EXCLUDED(mutex_) override;
+
   std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos(
       uint32_t ssrc,
       ArrayView<const uint16_t> sequence_numbers) const
@@ -201,6 +207,9 @@
   const RtpConfig rtp_config_;
   RtpTransportControllerSendInterface* const transport_;
 
+  // The list of CSRCs to be included when sending an encoded image.
+  std::vector<uint32_t> csrcs_ RTC_GUARDED_BY(mutex_);
+
   // When using the generic descriptor we want all simulcast streams to share
   // one frame id space (so that the SFU can switch stream without having to
   // rewrite the frame id), therefore `shared_frame_id` has to live in a place
diff --git a/call/rtp_video_sender_interface.h b/call/rtp_video_sender_interface.h
index 069a2a8..70c0800 100644
--- a/call/rtp_video_sender_interface.h
+++ b/call/rtp_video_sender_interface.h
@@ -55,6 +55,7 @@
   virtual void SetEncodingData(size_t width,
                                size_t height,
                                size_t num_temporal_layers) = 0;
+  virtual void SetCsrcs(ArrayView<const uint32_t> csrcs) = 0;
   virtual std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos(
       uint32_t ssrc,
       ArrayView<const uint16_t> sequence_numbers) const = 0;
diff --git a/media/BUILD.gn b/media/BUILD.gn
index f4a761a..7f92e45 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -806,6 +806,7 @@
       ":rtp_utils",
       ":stream_params",
       ":video_common",
+      "../api:array_view",
       "../api:audio_options_api",
       "../api:call_api",
       "../api:fec_controller_api",
diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc
index 96662d1..8503b0d 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video.cc
@@ -804,17 +804,18 @@
                                       uint32_t rtp_timestamp,
                                       const EncodedImage& encoded_image,
                                       RTPVideoHeader video_header,
-                                      TimeDelta expected_retransmission_time) {
+                                      TimeDelta expected_retransmission_time,
+                                      const std::vector<uint32_t>& csrcs) {
   if (frame_transformer_delegate_) {
     // The frame will be sent async once transformed.
     return frame_transformer_delegate_->TransformFrame(
         payload_type, codec_type, rtp_timestamp, encoded_image, video_header,
-        expected_retransmission_time);
+        expected_retransmission_time, csrcs);
   }
   return SendVideo(payload_type, codec_type, rtp_timestamp,
                    encoded_image.CaptureTime(), encoded_image,
                    encoded_image.size(), video_header,
-                   expected_retransmission_time, /*csrcs=*/{});
+                   expected_retransmission_time, csrcs);
 }
 
 DataRate RTPSenderVideo::PostEncodeOverhead() const {
diff --git a/modules/rtp_rtcp/source/rtp_sender_video.h b/modules/rtp_rtcp/source/rtp_sender_video.h
index 86943db..8e30021 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video.h
+++ b/modules/rtp_rtcp/source/rtp_sender_video.h
@@ -116,7 +116,8 @@
                         uint32_t rtp_timestamp,
                         const EncodedImage& encoded_image,
                         RTPVideoHeader video_header,
-                        TimeDelta expected_retransmission_time);
+                        TimeDelta expected_retransmission_time,
+                        const std::vector<uint32_t>& csrcs = {});
 
   // Configures video structures produced by encoder to send using the
   // dependency descriptor rtp header extension. Next call to SendVideo should
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 2d08bd6..7f9a80e 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
@@ -180,22 +180,21 @@
     uint32_t rtp_timestamp,
     const EncodedImage& encoded_image,
     RTPVideoHeader video_header,
-    TimeDelta expected_retransmission_time) {
+    TimeDelta expected_retransmission_time,
+    const std::vector<uint32_t>& csrcs) {
   {
     MutexLock lock(&sender_lock_);
     if (short_circuit_) {
       sender_->SendVideo(payload_type, codec_type, rtp_timestamp,
                          encoded_image.CaptureTime(),
                          *encoded_image.GetEncodedData(), encoded_image.size(),
-                         video_header, expected_retransmission_time,
-                         /*csrcs=*/{});
+                         video_header, expected_retransmission_time, csrcs);
       return true;
     }
   }
   frame_transformer_->Transform(std::make_unique<TransformableVideoSenderFrame>(
       encoded_image, video_header, payload_type, codec_type, rtp_timestamp,
-      expected_retransmission_time, ssrc_,
-      /*csrcs=*/std::vector<uint32_t>()));
+      expected_retransmission_time, ssrc_, csrcs));
   return true;
 }
 
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 4302fb7..2e84e1f 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
@@ -77,7 +77,8 @@
                       uint32_t rtp_timestamp,
                       const EncodedImage& encoded_image,
                       RTPVideoHeader video_header,
-                      TimeDelta expected_retransmission_time);
+                      TimeDelta expected_retransmission_time,
+                      const std::vector<uint32_t>& csrcs = {});
 
   // Implements TransformedFrameCallback. Can be called on any thread. Posts
   // the transformed frame to be sent on the `encoder_queue_`.
diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate_unittest.cc b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate_unittest.cc
index af3083b..48c6d09 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate_unittest.cc
@@ -42,6 +42,7 @@
 namespace {
 
 using ::testing::_;
+using ::testing::ElementsAreArray;
 using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::SaveArg;
@@ -136,13 +137,22 @@
   auto delegate = make_ref_counted<RTPSenderVideoFrameTransformerDelegate>(
       &test_sender_, frame_transformer_,
       /*ssrc=*/1111, time_controller_.CreateTaskQueueFactory().get());
+  VideoFrameMetadata metadata;
+  EXPECT_CALL(*frame_transformer_, Transform)
+      .WillOnce([&](std::unique_ptr<TransformableFrameInterface> frame) {
+        metadata = static_cast<TransformableVideoFrameInterface*>(frame.get())
+                       ->Metadata();
+      });
 
+  std::vector<uint32_t> csrcs = {1, 2, 3};
   EncodedImage encoded_image;
-  EXPECT_CALL(*frame_transformer_, Transform);
   delegate->TransformFrame(
       /*payload_type=*/1, VideoCodecType::kVideoCodecVP8, /*rtp_timestamp=*/2,
       encoded_image, RTPVideoHeader(),
-      /*expected_retransmission_time=*/TimeDelta::Millis(10));
+      /*expected_retransmission_time=*/TimeDelta::Millis(10), csrcs);
+
+  EXPECT_EQ(metadata.GetSsrc(), 1111U);
+  EXPECT_THAT(metadata.GetCsrcs(), ElementsAreArray(csrcs));
 }
 
 TEST_F(RtpSenderVideoFrameTransformerDelegateTest,
@@ -319,17 +329,19 @@
 
   delegate->StartShortCircuiting();
 
+  std::vector<uint32_t> csrcs = {1, 2, 3};
   // Will not call the actual transformer.
   EXPECT_CALL(*frame_transformer_, Transform).Times(0);
   // Will pass the frame straight to the reciever.
-  EXPECT_CALL(test_sender_, SendVideo);
+  EXPECT_CALL(test_sender_,
+              SendVideo(_, _, _, _, _, _, _, _, ElementsAreArray(csrcs)));
 
   EncodedImage encoded_image;
   encoded_image.SetEncodedData(EncodedImageBuffer::Create(1));
   delegate->TransformFrame(
       /*payload_type=*/1, VideoCodecType::kVideoCodecVP8, /*rtp_timestamp=*/2,
       encoded_image, RTPVideoHeader(),
-      /*expected_retransmission_time=*/TimeDelta::Millis(10));
+      /*expected_retransmission_time=*/TimeDelta::Millis(10), csrcs);
 }
 
 }  // namespace
diff --git a/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc b/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc
index 479527e..c279ee5 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc
@@ -1617,6 +1617,55 @@
                                      kDefaultExpectedRetransmissionTime);
 }
 
+TEST_F(RtpSenderVideoTest, SendEncodedImageIncludesProvidedCsrcs) {
+  std::vector<uint32_t> expected_csrcs = {1, 2, 3};
+  std::unique_ptr<EncodedImage> encoded_image = CreateDefaultEncodedImage();
+  RTPVideoHeader video_header;
+  video_header.frame_type = VideoFrameType::kVideoFrameKey;
+
+  ASSERT_TRUE(rtp_sender_video_->SendEncodedImage(
+      0, kType, kTimestamp, *encoded_image, video_header,
+      kDefaultExpectedRetransmissionTime, expected_csrcs));
+
+  ASSERT_GT(transport_.packets_sent(), 0);
+  std::vector<uint32_t> csrcs = transport_.last_sent_packet().Csrcs();
+  EXPECT_EQ(csrcs, expected_csrcs);
+}
+
+TEST_F(RtpSenderVideoWithFrameTransformerTest,
+       SendEncodedImageIncludesProvidedCsrcs) {
+  auto mock_frame_transformer =
+      make_ref_counted<NiceMock<MockFrameTransformer>>();
+  scoped_refptr<TransformedFrameCallback> callback;
+  EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameSinkCallback)
+      .WillOnce(SaveArg<0>(&callback));
+  std::unique_ptr<RTPSenderVideo> rtp_sender_video =
+      CreateSenderWithFrameTransformer(mock_frame_transformer);
+  ASSERT_TRUE(callback);
+  ON_CALL(*mock_frame_transformer, Transform)
+      .WillByDefault(
+          [&callback](std::unique_ptr<TransformableFrameInterface> frame) {
+            callback->OnTransformedFrame(std::move(frame));
+          });
+
+  auto encoded_image = CreateDefaultEncodedImage();
+  std::vector<uint32_t> expected_csrcs = {1, 2, 3};
+  RTPVideoHeader video_header;
+  video_header.frame_type = VideoFrameType::kVideoFrameKey;
+  auto encoder_queue = time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
+      "encoder_queue", TaskQueueFactory::Priority::NORMAL);
+  encoder_queue->PostTask([&] {
+    rtp_sender_video->SendEncodedImage(
+        kPayloadType, kType, kTimestamp, *encoded_image, video_header,
+        kDefaultExpectedRetransmissionTime, expected_csrcs);
+  });
+  time_controller_.AdvanceTime(TimeDelta::Zero());
+
+  ASSERT_GT(transport_.packets_sent(), 0);
+  std::vector<uint32_t> csrcs = transport_.last_sent_packet().Csrcs();
+  EXPECT_EQ(csrcs, expected_csrcs);
+}
+
 #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
 TEST_F(RtpSenderVideoWithFrameTransformerTest, ValidPayloadTypes) {
   auto mock_frame_transformer =
diff --git a/video/video_send_stream_impl_unittest.cc b/video/video_send_stream_impl_unittest.cc
index f0023f1..00b45f4 100644
--- a/video/video_send_stream_impl_unittest.cc
+++ b/video/video_send_stream_impl_unittest.cc
@@ -124,6 +124,7 @@
   MOCK_METHOD(uint32_t, GetPayloadBitrateBps, (), (const, override));
   MOCK_METHOD(uint32_t, GetProtectionBitrateBps, (), (const, override));
   MOCK_METHOD(void, SetEncodingData, (size_t, size_t, size_t), (override));
+  MOCK_METHOD(void, SetCsrcs, (ArrayView<const uint32_t> csrcs), (override));
   MOCK_METHOD(std::vector<RtpSequenceNumberMap::Info>,
               GetSentRtpPacketInfos,
               (uint32_t ssrc, ArrayView<const uint16_t> sequence_numbers),