Add nonstandard x-google-per-layer-pli fmtp for enabling per-layer keyFrames in response to PLIs
which needs to be added to the remote codecs a=fmtp:
This also forces SimulcastCastEncoderAdapter to avoid issues with codecs that have native simulcast capability but do require synchronized keyframes.
This parameter allows for large-scale experimentation and A/B testing
whether the new behavior has advantages. It is to be considered
transitional and may be removed again in the future.
BUG=webrtc:10107
Change-Id: I81f496c987b2fed7ff3089efb746e7e89e89c033
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/333560
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41805}
diff --git a/docs/native-code/sdp-ext/fmtp-x-google-per-layer-pli.md b/docs/native-code/sdp-ext/fmtp-x-google-per-layer-pli.md
new file mode 100644
index 0000000..6823d65
--- /dev/null
+++ b/docs/native-code/sdp-ext/fmtp-x-google-per-layer-pli.md
@@ -0,0 +1,36 @@
+# x-google-per-layer-pli FMTP parameter
+
+The x-google-per-layer-pli FMTP parameter is a format specific parameter
+that can be added to a remote description in the `a=fmtp:` line:
+ a=fmtp:96 x-google-per-layer-pli=1
+
+When using simulcast with more than a single SSRC, it will change how the
+simulcast encoder reacts to Picture Loss Indication (PLI) and Full Intra
+Request (FIR) RTCP feedback.
+
+When the parameter value is 1, a PLI requests the generation of a key frame
+for the spatial layer associated with the SSRC of the media source and a
+FIR does the same for the SSRC value of the media sender.
+
+When the value is 0 or the parameter is missing, a keyframe is generated
+on all spatial layers for backward compability.
+
+## Experimentation
+
+This parameter does allow for large-scale A/B testing and opt-in to the
+new behavior. For multiparty calls enabling it should reduce the number of
+keyframes sent by the client and number of key frames received by the
+receivers which results in a better bandwidth utilization.
+
+This parameter is experimental and may be removed again in the future.
+
+## IANA considerations
+
+Since the current behavior of reacting to a PLI for a specific SSRC with
+key frames on all spatial layers can be considered an implementation bug
+this parameter is not registered with the IANA.
+
+If experimentation shows that the current behavior is better for some
+codecs like VP8 which can share encoding parameters with synchronous
+keyframes a standardized variant of this parameter shall be registered
+with the IANA.
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 2530e5a..e208e4f 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -37,11 +37,15 @@
]
deps = [
+ ":media_constants",
"../api/video_codecs:video_codecs_api",
"../rtc_base:checks",
"../rtc_base:stringutils",
]
- absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+ absl_deps = [
+ "//third_party/abseil-cpp/absl/algorithm:container",
+ "//third_party/abseil-cpp/absl/types:optional",
+ ]
}
rtc_library("rtc_media_base") {
@@ -337,6 +341,7 @@
]
deps = [
":rtc_media_base",
+ ":rtc_sdp_video_format_utils",
"../api:fec_controller_api",
"../api:field_trials_view",
"../api:scoped_refptr",
diff --git a/media/base/media_constants.cc b/media/base/media_constants.cc
index 1a7561a..c61ce14 100644
--- a/media/base/media_constants.cc
+++ b/media/base/media_constants.cc
@@ -96,6 +96,8 @@
const char kCodecParamMinBitrate[] = "x-google-min-bitrate";
const char kCodecParamStartBitrate[] = "x-google-start-bitrate";
const char kCodecParamMaxQuantization[] = "x-google-max-quantization";
+const char kCodecParamPerLayerPictureLossIndication[] =
+ "x-google-per-layer-pli";
const char kComfortNoiseCodecName[] = "CN";
diff --git a/media/base/media_constants.h b/media/base/media_constants.h
index d5af17e..7e245fc 100644
--- a/media/base/media_constants.h
+++ b/media/base/media_constants.h
@@ -64,6 +64,7 @@
extern const char kCodecParamUseDtx[];
extern const char kCodecParamMaxAverageBitrate[];
extern const char kCodecParamMaxPlaybackRate[];
+extern const char kCodecParamPerLayerPictureLossIndication[];
extern const char kParamValueTrue[];
// Parameters are stored as parameter/value pairs. For parameters who do not
diff --git a/media/base/sdp_video_format_utils.cc b/media/base/sdp_video_format_utils.cc
index 2b756ba..81ddb7b 100644
--- a/media/base/sdp_video_format_utils.cc
+++ b/media/base/sdp_video_format_utils.cc
@@ -12,12 +12,15 @@
#include <cstring>
#include <map>
+#include <string>
#include <utility>
#include "api/video_codecs/h264_profile_level_id.h"
#ifdef RTC_ENABLE_H265
#include "api/video_codecs/h265_profile_tier_level.h"
#endif
+#include "absl/algorithm/container.h"
+#include "media/base/media_constants.h"
#include "rtc_base/checks.h"
#include "rtc_base/string_to_number.h"
@@ -177,4 +180,13 @@
: absl::nullopt;
}
+bool SupportsPerLayerPictureLossIndication(const CodecParameterMap& params) {
+ return absl::c_find_if(
+ params, [](const std::pair<std::string, std::string>& kv) {
+ return kv.first ==
+ cricket::kCodecParamPerLayerPictureLossIndication &&
+ kv.second == "1";
+ }) != params.end();
+}
+
} // namespace webrtc
diff --git a/media/base/sdp_video_format_utils.h b/media/base/sdp_video_format_utils.h
index 931caa8..e6a4ca7 100644
--- a/media/base/sdp_video_format_utils.h
+++ b/media/base/sdp_video_format_utils.h
@@ -57,6 +57,10 @@
// blocks but the returned value is in total number of pixels.
absl::optional<int> ParseSdpForVPxMaxFrameSize(const CodecParameterMap& params);
+// Determines whether the non-standard x-google-per-layer-pli fmtp is present
+// in the parameters and has a value of "1".
+bool SupportsPerLayerPictureLossIndication(const CodecParameterMap& params);
+
} // namespace webrtc
#endif // MEDIA_BASE_SDP_VIDEO_FORMAT_UTILS_H_
diff --git a/media/base/sdp_video_format_utils_unittest.cc b/media/base/sdp_video_format_utils_unittest.cc
index 9f505c1..04327d3 100644
--- a/media/base/sdp_video_format_utils_unittest.cc
+++ b/media/base/sdp_video_format_utils_unittest.cc
@@ -24,6 +24,9 @@
const char kVPxFmtpMaxFrameRate[] = "max-fr";
// Max frame size for VP8 and VP9 video.
const char kVPxFmtpMaxFrameSize[] = "max-fs";
+// Nonstandard per-layer PLI for video.
+const char kCodecParamPerLayerPictureLossIndication[] =
+ "x-google-per-layer-pli";
} // namespace
TEST(SdpVideoFormatUtilsTest, TestH264GenerateProfileLevelIdForAnswerEmpty) {
@@ -141,4 +144,15 @@
EXPECT_EQ(ParseSdpForVPxMaxFrameSize(params), 3840 * 2160);
}
+TEST(SdpVideoFormatUtilsTest, PerLayerPictureLossIndication) {
+ CodecParameterMap params;
+ EXPECT_FALSE(SupportsPerLayerPictureLossIndication(params));
+ params[kCodecParamPerLayerPictureLossIndication] = "wrong";
+ EXPECT_FALSE(SupportsPerLayerPictureLossIndication(params));
+ params[kCodecParamPerLayerPictureLossIndication] = "0";
+ EXPECT_FALSE(SupportsPerLayerPictureLossIndication(params));
+ params[kCodecParamPerLayerPictureLossIndication] = "1";
+ EXPECT_TRUE(SupportsPerLayerPictureLossIndication(params));
+}
+
} // namespace webrtc
diff --git a/media/engine/simulcast_encoder_adapter.cc b/media/engine/simulcast_encoder_adapter.cc
index 642fe31..e9de53d 100644
--- a/media/engine/simulcast_encoder_adapter.cc
+++ b/media/engine/simulcast_encoder_adapter.cc
@@ -30,6 +30,7 @@
#include "api/video_codecs/video_encoder_factory.h"
#include "api/video_codecs/video_encoder_software_fallback_wrapper.h"
#include "media/base/media_constants.h"
+#include "media/base/sdp_video_format_utils.h"
#include "media/base/video_common.h"
#include "modules/video_coding/include/video_error_codes.h"
#include "modules/video_coding/include/video_error_codes_utils.h"
@@ -269,7 +270,8 @@
RateControlSettings::ParseFromKeyValueConfig(&field_trials)
.Vp8BoostBaseLayerQuality()),
prefer_temporal_support_on_base_layer_(field_trials.IsEnabled(
- "WebRTC-Video-PreferTemporalSupportOnBaseLayer")) {
+ "WebRTC-Video-PreferTemporalSupportOnBaseLayer")),
+ per_layer_pli_(SupportsPerLayerPictureLossIndication(format.parameters)) {
RTC_DCHECK(primary_factory);
// The adapter is typically created on the worker thread, but operated on
@@ -358,9 +360,12 @@
// If we only have a single active layer it is better to create an encoder
// with only one configured layer than creating it with all-but-one disabled
// layers because that way we control scaling.
+ // The use of the nonstandard x-google-per-layer-pli fmtp parameter also
+ // forces the use of SEA with separate encoders to support per-layer
+ // handling of PLIs.
bool separate_encoders_needed =
!encoder_context->encoder().GetEncoderInfo().supports_simulcast ||
- active_streams_count == 1;
+ active_streams_count == 1 || per_layer_pli_;
RTC_LOG(LS_INFO) << "[SEA] InitEncode: total_streams_count: "
<< total_streams_count_
<< ", active_streams_count: " << active_streams_count
diff --git a/media/engine/simulcast_encoder_adapter.h b/media/engine/simulcast_encoder_adapter.h
index 553a6a0..83ebb4e 100644
--- a/media/engine/simulcast_encoder_adapter.h
+++ b/media/engine/simulcast_encoder_adapter.h
@@ -191,6 +191,7 @@
const absl::optional<unsigned int> experimental_boosted_screenshare_qp_;
const bool boost_base_layer_quality_;
const bool prefer_temporal_support_on_base_layer_;
+ const bool per_layer_pli_;
const SimulcastEncoderAdapterEncoderInfoSettings encoder_info_override_;
};
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
index ee31db2..63f44fb 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -87,7 +87,6 @@
} // namespace
constexpr size_t RTCPReceiver::RegisteredSsrcs::kMediaSsrcIndex;
-constexpr size_t RTCPReceiver::RegisteredSsrcs::kMaxSsrcs;
RTCPReceiver::RegisteredSsrcs::RegisteredSsrcs(
bool disable_sequence_checker,
@@ -105,7 +104,7 @@
}
}
// Ensure that the RegisteredSsrcs can inline the SSRCs.
- RTC_DCHECK_LE(ssrcs_.size(), RTCPReceiver::RegisteredSsrcs::kMaxSsrcs);
+ RTC_DCHECK_LE(ssrcs_.size(), kMaxSimulcastStreams);
}
bool RTCPReceiver::RegisteredSsrcs::contains(uint32_t ssrc) const {
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.h b/modules/rtp_rtcp/source/rtcp_receiver.h
index a6175d0..20e314b 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.h
+++ b/modules/rtp_rtcp/source/rtcp_receiver.h
@@ -179,7 +179,6 @@
class RegisteredSsrcs {
public:
static constexpr size_t kMediaSsrcIndex = 0;
- static constexpr size_t kMaxSsrcs = 3;
// Initializes the set of registered local SSRCS by extracting them from the
// provided `config`. The `disable_sequence_checker` flag is a workaround
// to be able to use a sequence checker without breaking downstream
@@ -194,7 +193,7 @@
private:
RTC_NO_UNIQUE_ADDRESS CustomSequenceChecker packet_sequence_checker_;
- absl::InlinedVector<uint32_t, kMaxSsrcs> ssrcs_
+ absl::InlinedVector<uint32_t, kMaxSimulcastStreams> ssrcs_
RTC_GUARDED_BY(packet_sequence_checker_);
};
diff --git a/video/BUILD.gn b/video/BUILD.gn
index e4e1f27..8dd07d7 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -119,6 +119,7 @@
"../call:video_stream_api",
"../common_video",
"../media:media_constants",
+ "../media:rtc_sdp_video_format_utils",
"../modules:module_api",
"../modules:module_api_public",
"../modules/pacing",
diff --git a/video/encoder_rtcp_feedback.cc b/video/encoder_rtcp_feedback.cc
index ebba41e..c9f3f73 100644
--- a/video/encoder_rtcp_feedback.cc
+++ b/video/encoder_rtcp_feedback.cc
@@ -26,6 +26,7 @@
EncoderRtcpFeedback::EncoderRtcpFeedback(
Clock* clock,
+ bool per_layer_keyframes,
const std::vector<uint32_t>& ssrcs,
VideoStreamEncoderInterface* encoder,
std::function<std::vector<RtpSequenceNumberMap::Info>(
@@ -33,9 +34,11 @@
const std::vector<uint16_t>& seq_nums)> get_packet_infos)
: clock_(clock),
ssrcs_(ssrcs),
+ per_layer_keyframes_(per_layer_keyframes),
get_packet_infos_(std::move(get_packet_infos)),
video_stream_encoder_(encoder),
- time_last_packet_delivery_queue_(Timestamp::Zero()),
+ time_last_packet_delivery_queue_(per_layer_keyframes ? ssrcs.size() : 1,
+ Timestamp::Zero()),
min_keyframe_send_interval_(
TimeDelta::Millis(KeyframeIntervalSettings::ParseFromFieldTrials()
.MinKeyframeSendIntervalMs()
@@ -49,14 +52,32 @@
RTC_DCHECK_RUN_ON(&packet_delivery_queue_);
RTC_DCHECK(std::find(ssrcs_.begin(), ssrcs_.end(), ssrc) != ssrcs_.end());
+ auto it = std::find(ssrcs_.begin(), ssrcs_.end(), ssrc);
+ if (it == ssrcs_.end()) {
+ RTC_LOG(LS_WARNING) << "SSRC " << ssrc << " not found.";
+ return;
+ }
+ size_t ssrc_index =
+ per_layer_keyframes_ ? std::distance(ssrcs_.begin(), it) : 0;
+ RTC_CHECK_LE(ssrc_index, time_last_packet_delivery_queue_.size());
const Timestamp now = clock_->CurrentTime();
- if (time_last_packet_delivery_queue_ + min_keyframe_send_interval_ > now)
+ if (time_last_packet_delivery_queue_[ssrc_index] +
+ min_keyframe_send_interval_ >
+ now)
return;
- time_last_packet_delivery_queue_ = now;
+ time_last_packet_delivery_queue_[ssrc_index] = now;
- // Always produce key frame for all streams.
- video_stream_encoder_->SendKeyFrame();
+ std::vector<VideoFrameType> layers(ssrcs_.size(),
+ VideoFrameType::kVideoFrameDelta);
+ if (!per_layer_keyframes_) {
+ // Always produce key frame for all streams.
+ video_stream_encoder_->SendKeyFrame();
+ } else {
+ // Determine on which layer we ask for key frames.
+ layers[ssrc_index] = VideoFrameType::kVideoFrameKey;
+ video_stream_encoder_->SendKeyFrame(layers);
+ }
}
void EncoderRtcpFeedback::OnReceivedLossNotification(
diff --git a/video/encoder_rtcp_feedback.h b/video/encoder_rtcp_feedback.h
index c66a945..4b92bd2 100644
--- a/video/encoder_rtcp_feedback.h
+++ b/video/encoder_rtcp_feedback.h
@@ -33,6 +33,7 @@
public:
EncoderRtcpFeedback(
Clock* clock,
+ bool per_layer_keyframes,
const std::vector<uint32_t>& ssrcs,
VideoStreamEncoderInterface* encoder,
std::function<std::vector<RtpSequenceNumberMap::Info>(
@@ -51,6 +52,7 @@
private:
Clock* const clock_;
const std::vector<uint32_t> ssrcs_;
+ const bool per_layer_keyframes_;
const std::function<std::vector<RtpSequenceNumberMap::Info>(
uint32_t ssrc,
const std::vector<uint16_t>& seq_nums)>
@@ -58,7 +60,7 @@
VideoStreamEncoderInterface* const video_stream_encoder_;
RTC_NO_UNIQUE_ADDRESS SequenceChecker packet_delivery_queue_;
- Timestamp time_last_packet_delivery_queue_
+ std::vector<Timestamp> time_last_packet_delivery_queue_
RTC_GUARDED_BY(packet_delivery_queue_);
const TimeDelta min_keyframe_send_interval_;
diff --git a/video/encoder_rtcp_feedback_unittest.cc b/video/encoder_rtcp_feedback_unittest.cc
index f1ac65d..fdece62 100644
--- a/video/encoder_rtcp_feedback_unittest.cc
+++ b/video/encoder_rtcp_feedback_unittest.cc
@@ -17,34 +17,46 @@
#include "video/test/mock_video_stream_encoder.h"
using ::testing::_;
+using ::testing::ElementsAre;
namespace webrtc {
-class VieKeyRequestTest : public ::testing::Test {
+class VideoEncoderFeedbackKeyframeTestBase : public ::testing::Test {
public:
- VieKeyRequestTest()
+ VideoEncoderFeedbackKeyframeTestBase(bool per_layer_pli_handling,
+ std::vector<uint32_t> ssrcs)
: simulated_clock_(123456789),
encoder_(),
- encoder_rtcp_feedback_(
- &simulated_clock_,
- std::vector<uint32_t>(1, VieKeyRequestTest::kSsrc),
- &encoder_,
- nullptr) {}
+ encoder_rtcp_feedback_(&simulated_clock_,
+ per_layer_pli_handling,
+ ssrcs,
+ &encoder_,
+ nullptr) {}
protected:
- const uint32_t kSsrc = 1234;
+ static const uint32_t kSsrc = 1234;
+ static const uint32_t kOtherSsrc = 4321;
SimulatedClock simulated_clock_;
::testing::StrictMock<MockVideoStreamEncoder> encoder_;
EncoderRtcpFeedback encoder_rtcp_feedback_;
};
-TEST_F(VieKeyRequestTest, CreateAndTriggerRequests) {
+class VideoEncoderFeedbackKeyframeTest
+ : public VideoEncoderFeedbackKeyframeTestBase {
+ public:
+ VideoEncoderFeedbackKeyframeTest()
+ : VideoEncoderFeedbackKeyframeTestBase(
+ /*per_layer_pli_handling=*/false,
+ {VideoEncoderFeedbackKeyframeTestBase::kSsrc}) {}
+};
+
+TEST_F(VideoEncoderFeedbackKeyframeTest, CreateAndTriggerRequests) {
EXPECT_CALL(encoder_, SendKeyFrame(_)).Times(1);
encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
}
-TEST_F(VieKeyRequestTest, TooManyOnReceivedIntraFrameRequest) {
+TEST_F(VideoEncoderFeedbackKeyframeTest, TooManyOnReceivedIntraFrameRequest) {
EXPECT_CALL(encoder_, SendKeyFrame(_)).Times(1);
encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
@@ -58,4 +70,61 @@
encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
}
+class VideoEncoderFeedbackKeyframePerLayerPliTest
+ : public VideoEncoderFeedbackKeyframeTestBase {
+ public:
+ VideoEncoderFeedbackKeyframePerLayerPliTest()
+ : VideoEncoderFeedbackKeyframeTestBase(
+ /*per_layer_pli_handling=*/true,
+ {VideoEncoderFeedbackKeyframeTestBase::kSsrc,
+ VideoEncoderFeedbackKeyframeTestBase::kOtherSsrc}) {}
+};
+
+TEST_F(VideoEncoderFeedbackKeyframePerLayerPliTest, CreateAndTriggerRequests) {
+ EXPECT_CALL(encoder_,
+ SendKeyFrame(ElementsAre(VideoFrameType::kVideoFrameKey,
+ VideoFrameType::kVideoFrameDelta)))
+ .Times(1);
+ EXPECT_CALL(encoder_,
+ SendKeyFrame(ElementsAre(VideoFrameType::kVideoFrameDelta,
+ VideoFrameType::kVideoFrameKey)))
+ .Times(1);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kOtherSsrc);
+}
+
+TEST_F(VideoEncoderFeedbackKeyframePerLayerPliTest,
+ TooManyOnReceivedIntraFrameRequest) {
+ EXPECT_CALL(encoder_,
+ SendKeyFrame(ElementsAre(VideoFrameType::kVideoFrameKey,
+ VideoFrameType::kVideoFrameDelta)))
+ .Times(1);
+ EXPECT_CALL(encoder_,
+ SendKeyFrame(ElementsAre(VideoFrameType::kVideoFrameDelta,
+ VideoFrameType::kVideoFrameKey)))
+ .Times(1);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kOtherSsrc);
+ simulated_clock_.AdvanceTimeMilliseconds(10);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kOtherSsrc);
+
+ EXPECT_CALL(encoder_,
+ SendKeyFrame(ElementsAre(VideoFrameType::kVideoFrameKey,
+ VideoFrameType::kVideoFrameDelta)))
+ .Times(1);
+ EXPECT_CALL(encoder_,
+ SendKeyFrame(ElementsAre(VideoFrameType::kVideoFrameDelta,
+ VideoFrameType::kVideoFrameKey)))
+ .Times(1);
+ simulated_clock_.AdvanceTimeMilliseconds(300);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kOtherSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kOtherSsrc);
+ encoder_rtcp_feedback_.OnReceivedIntraFrameRequest(kOtherSsrc);
+}
+
} // namespace webrtc
diff --git a/video/video_send_stream_impl.cc b/video/video_send_stream_impl.cc
index ec63b51..f5005b2 100644
--- a/video/video_send_stream_impl.cc
+++ b/video/video_send_stream_impl.cc
@@ -52,6 +52,8 @@
#include "call/rtp_config.h"
#include "call/rtp_transport_controller_send_interface.h"
#include "call/video_send_stream.h"
+#include "media/base/media_constants.h"
+#include "media/base/sdp_video_format_utils.h"
#include "modules/pacing/pacing_controller.h"
#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@@ -419,6 +421,8 @@
config_.encoder_selector)),
encoder_feedback_(
clock,
+ SupportsPerLayerPictureLossIndication(
+ encoder_config.video_format.parameters),
config_.rtp.ssrcs,
video_stream_encoder_.get(),
[this](uint32_t ssrc, const std::vector<uint16_t>& seq_nums) {