diff --git a/api/video/video_stream_encoder_settings.h b/api/video/video_stream_encoder_settings.h index 9a9889a084..4997327971 100644 --- a/api/video/video_stream_encoder_settings.h +++ b/api/video/video_stream_encoder_settings.h @@ -11,16 +11,29 @@ #ifndef API_VIDEO_VIDEO_STREAM_ENCODER_SETTINGS_H_ #define API_VIDEO_VIDEO_STREAM_ENCODER_SETTINGS_H_ +#include + #include "api/video/video_bitrate_allocator_factory.h" #include "api/video_codecs/video_encoder.h" #include "api/video_codecs/video_encoder_factory.h" namespace webrtc { -class EncoderFailureCallback { +class EncoderSwitchRequestCallback { public: - virtual ~EncoderFailureCallback() {} - virtual void OnEncoderFailure() = 0; + virtual ~EncoderSwitchRequestCallback() {} + + struct Config { + std::string codec_name; + absl::optional param; + absl::optional value; + }; + + // Requests that encoder fallback is performed. + virtual void RequestEncoderFallback() = 0; + + // Requests that a switch to a specific encoder is performed. + virtual void RequestEncoderSwitch(const Config& conf) = 0; }; struct VideoStreamEncoderSettings { @@ -35,8 +48,8 @@ struct VideoStreamEncoderSettings { // Ownership stays with WebrtcVideoEngine (delegated from PeerConnection). VideoEncoderFactory* encoder_factory = nullptr; - // Notifies the WebRtcVideoChannel that the currently used encoder is broken. - EncoderFailureCallback* encoder_failure_callback = nullptr; + // Requests the WebRtcVideoChannel to perform a codec switch. + EncoderSwitchRequestCallback* encoder_switch_request_callback = nullptr; // Ownership stays with WebrtcVideoEngine (delegated from PeerConnection). VideoBitrateAllocatorFactory* bitrate_allocator_factory = nullptr; diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index 2a1f65dbc3..f1eca04e5d 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -32,6 +32,7 @@ #include "media/engine/webrtc_voice_engine.h" #include "rtc_base/copy_on_write_buffer.h" #include "rtc_base/experiments/field_trial_parser.h" +#include "rtc_base/experiments/field_trial_units.h" #include "rtc_base/logging.h" #include "rtc_base/strings/string_builder.h" #include "rtc_base/time_utils.h" @@ -738,7 +739,7 @@ bool WebRtcVideoChannel::SetSendParameters(const VideoSendParameters& params) { return ApplyChangedParams(changed_params); } -void WebRtcVideoChannel::OnEncoderFailure() { +void WebRtcVideoChannel::RequestEncoderFallback() { invoker_.AsyncInvoke( RTC_FROM_HERE, worker_thread_, [this] { RTC_DCHECK_RUN_ON(&thread_checker_); @@ -756,6 +757,45 @@ void WebRtcVideoChannel::OnEncoderFailure() { }); } +void WebRtcVideoChannel::RequestEncoderSwitch( + const EncoderSwitchRequestCallback::Config& conf) { + invoker_.AsyncInvoke(RTC_FROM_HERE, worker_thread_, [this, conf] { + RTC_DCHECK_RUN_ON(&thread_checker_); + + for (VideoCodecSettings codec_setting : negotiated_codecs_) { + if (codec_setting.codec.name == conf.codec_name) { + if (conf.param) { + auto it = codec_setting.codec.params.find(*conf.param); + + if (it == codec_setting.codec.params.end()) { + continue; + } + + if (conf.value && it->second != *conf.value) { + continue; + } + } + + if (send_codec_ == codec_setting) { + // Already using this codec, no switch required. + return; + } + + ChangedSendParameters params; + params.send_codec = codec_setting; + ApplyChangedParams(params); + return; + } + } + + RTC_LOG(LS_WARNING) << "Requested encoder with codec_name:" + << conf.codec_name + << ", param:" << conf.param.value_or("none") + << " and value:" << conf.value.value_or("none") + << "not found. No switch performed."; + }); +} + bool WebRtcVideoChannel::ApplyChangedParams( const ChangedSendParameters& changed_params) { RTC_DCHECK_RUN_ON(&thread_checker_); @@ -1172,7 +1212,7 @@ bool WebRtcVideoChannel::AddSendStream(const StreamParams& sp) { config.encoder_settings.encoder_factory = encoder_factory_; config.encoder_settings.bitrate_allocator_factory = bitrate_allocator_factory_; - config.encoder_settings.encoder_failure_callback = this; + config.encoder_settings.encoder_switch_request_callback = this; config.crypto_options = crypto_options_; config.rtp.extmap_allow_mixed = ExtmapAllowMixed(); config.rtcp_report_interval_ms = video_config_.rtcp_report_interval_ms; diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h index 1bd8edd56b..3c6b86a3c9 100644 --- a/media/engine/webrtc_video_engine.h +++ b/media/engine/webrtc_video_engine.h @@ -122,7 +122,7 @@ class WebRtcVideoEngine : public VideoEngineInterface { class WebRtcVideoChannel : public VideoMediaChannel, public webrtc::Transport, - public webrtc::EncoderFailureCallback { + public webrtc::EncoderSwitchRequestCallback { public: WebRtcVideoChannel( webrtc::Call* call, @@ -221,8 +221,10 @@ class WebRtcVideoChannel : public VideoMediaChannel, // This method does nothing unless unknown_ssrc_packet_buffer_ is configured. void BackfillBufferedPackets(rtc::ArrayView ssrcs); - // Implements webrtc::EncoderFailureCallback. - void OnEncoderFailure() override; + // Implements webrtc::EncoderSwitchRequestCallback. + void RequestEncoderFallback() override; + void RequestEncoderSwitch( + const EncoderSwitchRequestCallback::Config& conf) override; private: class WebRtcVideoReceiveStream; diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc index e7949fec43..aac6631473 100644 --- a/media/engine/webrtc_video_engine_unittest.cc +++ b/media/engine/webrtc_video_engine_unittest.cc @@ -61,8 +61,11 @@ #include "test/gmock.h" #include "test/rtp_header_parser.h" +using ::testing::Contains; +using ::testing::Eq; using ::testing::Field; using ::testing::IsEmpty; +using ::testing::Pair; using ::testing::SizeIs; using webrtc::BitrateConstraints; using webrtc::RtpExtension; @@ -2112,7 +2115,7 @@ TEST_F(WebRtcVideoChannelBaseTest, TwoStreamsSendAndReceive) { TwoStreamsSendAndReceive(codec); } -TEST_F(WebRtcVideoChannelBaseTest, OnEncoderFailure) { +TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderFallback) { cricket::VideoSendParameters parameters; parameters.codecs.push_back(GetEngineCodec("VP9")); parameters.codecs.push_back(GetEngineCodec("VP8")); @@ -2122,20 +2125,95 @@ TEST_F(WebRtcVideoChannelBaseTest, OnEncoderFailure) { ASSERT_TRUE(channel_->GetSendCodec(&codec)); EXPECT_EQ("VP9", codec.name); - // OnEncoderFailure will post a task to the worker thread (which is also + // RequestEncoderFallback will post a task to the worker thread (which is also // the current thread), hence the ProcessMessages call. - channel_->OnEncoderFailure(); + channel_->RequestEncoderFallback(); rtc::Thread::Current()->ProcessMessages(30); ASSERT_TRUE(channel_->GetSendCodec(&codec)); EXPECT_EQ("VP8", codec.name); // No other codec to fall back to, keep using VP8. - channel_->OnEncoderFailure(); + channel_->RequestEncoderFallback(); rtc::Thread::Current()->ProcessMessages(30); ASSERT_TRUE(channel_->GetSendCodec(&codec)); EXPECT_EQ("VP8", codec.name); } +TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchWithConfig) { + const std::string kParam = "the-param"; + const std::string kPing = "ping"; + const std::string kPong = "pong"; + + cricket::VideoSendParameters parameters; + VideoCodec vp9 = GetEngineCodec("VP9"); + vp9.params[kParam] = kPong; + parameters.codecs.push_back(vp9); + + VideoCodec vp8 = GetEngineCodec("VP8"); + vp8.params[kParam] = kPing; + parameters.codecs.push_back(vp8); + + EXPECT_TRUE(channel_->SetSendParameters(parameters)); + + VideoCodec codec; + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_THAT(codec.name, Eq("VP9")); + + // RequestEncoderSwitch will post a task to the worker thread (which is also + // the current thread), hence the ProcessMessages call. + webrtc::EncoderSwitchRequestCallback::Config conf1{"VP8", kParam, kPing}; + channel_->RequestEncoderSwitch(conf1); + rtc::Thread::Current()->ProcessMessages(30); + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_THAT(codec.name, Eq("VP8")); + EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing))); + + webrtc::EncoderSwitchRequestCallback::Config conf2{"VP9", kParam, kPong}; + channel_->RequestEncoderSwitch(conf2); + rtc::Thread::Current()->ProcessMessages(30); + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_THAT(codec.name, Eq("VP9")); + EXPECT_THAT(codec.params, Contains(Pair(kParam, kPong))); +} + +TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchIncorrectParam) { + const std::string kParam = "the-param"; + const std::string kPing = "ping"; + const std::string kPong = "pong"; + + cricket::VideoSendParameters parameters; + VideoCodec vp9 = GetEngineCodec("VP9"); + vp9.params[kParam] = kPong; + parameters.codecs.push_back(vp9); + + VideoCodec vp8 = GetEngineCodec("VP8"); + vp8.params[kParam] = kPing; + parameters.codecs.push_back(vp8); + + EXPECT_TRUE(channel_->SetSendParameters(parameters)); + + VideoCodec codec; + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_THAT(codec.name, Eq("VP9")); + + // RequestEncoderSwitch will post a task to the worker thread (which is also + // the current thread), hence the ProcessMessages call. + webrtc::EncoderSwitchRequestCallback::Config conf1{"VP8", kParam, kPing}; + channel_->RequestEncoderSwitch(conf1); + rtc::Thread::Current()->ProcessMessages(30); + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_THAT(codec.name, Eq("VP8")); + EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing))); + + // Incorrect conf2.value, expect no codec switch. + webrtc::EncoderSwitchRequestCallback::Config conf2{"VP9", kParam, kPing}; + channel_->RequestEncoderSwitch(conf2); + rtc::Thread::Current()->ProcessMessages(30); + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_THAT(codec.name, Eq("VP8")); + EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing))); +} + class WebRtcVideoChannelTest : public WebRtcVideoEngineTest { public: WebRtcVideoChannelTest() : WebRtcVideoChannelTest("") {} diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc index e6e627ce46..8b576e8c75 100644 --- a/video/video_stream_encoder.cc +++ b/video/video_stream_encoder.cc @@ -526,7 +526,9 @@ VideoStreamEncoder::VideoStreamEncoder( next_frame_id_(0), encoder_queue_(task_queue_factory->CreateTaskQueue( "EncoderQueue", - TaskQueueFactory::Priority::NORMAL)) { + TaskQueueFactory::Priority::NORMAL)), + encoder_switch_experiment_(ParseEncoderSwitchFieldTrial()), + encoder_switch_requested_(false) { RTC_DCHECK(encoder_stats_observer); RTC_DCHECK(overuse_detector_); RTC_DCHECK_GE(number_of_cores, 1); @@ -720,6 +722,19 @@ GetEncoderBitrateLimits(const VideoEncoder::EncoderInfo& encoder_info, // "soft" reconfiguration. void VideoStreamEncoder::ReconfigureEncoder() { RTC_DCHECK(pending_encoder_reconfiguration_); + + if (encoder_switch_experiment_.IsPixelCountBelowThreshold( + last_frame_info_->width * last_frame_info_->height) && + !encoder_switch_requested_ && settings_.encoder_switch_request_callback) { + EncoderSwitchRequestCallback::Config conf; + conf.codec_name = encoder_switch_experiment_.to_codec; + conf.param = encoder_switch_experiment_.to_param; + conf.value = encoder_switch_experiment_.to_value; + settings_.encoder_switch_request_callback->RequestEncoderSwitch(conf); + + encoder_switch_requested_ = true; + } + std::vector streams = encoder_config_.video_stream_factory->CreateEncoderStreams( last_frame_info_->width, last_frame_info_->height, encoder_config_); @@ -841,6 +856,8 @@ void VideoStreamEncoder::ReconfigureEncoder() { } send_codec_ = codec; + encoder_switch_experiment_.SetCodec(send_codec_.codecType); + // Keep the same encoder, as long as the video_format is unchanged. // Encoder creation block is split in two since EncoderInfo needed to start // CPU adaptation with the correct settings should be polled after @@ -1484,9 +1501,9 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame, if (encode_status == WEBRTC_VIDEO_CODEC_ENCODER_FAILURE) { RTC_LOG(LS_ERROR) << "Encoder failed, failing encoder format: " << encoder_config_.video_format.ToString(); - if (settings_.encoder_failure_callback) { + if (settings_.encoder_switch_request_callback) { encoder_failed_ = true; - settings_.encoder_failure_callback->OnEncoderFailure(); + settings_.encoder_switch_request_callback->RequestEncoderFallback(); } else { RTC_LOG(LS_ERROR) << "Encoder failed but no encoder fallback callback is registered"; @@ -1721,6 +1738,18 @@ void VideoStreamEncoder::OnBitrateUpdated(DataRate target_bitrate, return; } RTC_DCHECK_RUN_ON(&encoder_queue_); + + if (encoder_switch_experiment_.IsBitrateBelowThreshold(target_bitrate) && + settings_.encoder_switch_request_callback && !encoder_switch_requested_) { + EncoderSwitchRequestCallback::Config conf; + conf.codec_name = encoder_switch_experiment_.to_codec; + conf.param = encoder_switch_experiment_.to_param; + conf.value = encoder_switch_experiment_.to_value; + settings_.encoder_switch_request_callback->RequestEncoderSwitch(conf); + + encoder_switch_requested_ = true; + } + RTC_DCHECK(sink_) << "sink_ must be set before the encoder is active."; RTC_LOG(LS_VERBOSE) << "OnBitrateUpdated, bitrate " << target_bitrate.bps() @@ -2232,4 +2261,110 @@ std::string VideoStreamEncoder::AdaptCounter::ToString( return ss.Release(); } +bool VideoStreamEncoder::EncoderSwitchExperiment::IsBitrateBelowThreshold( + const DataRate& target_bitrate) { + DataRate rate = + DataRate::kbps(bitrate_filter.Apply(1.0, target_bitrate.kbps())); + return current_thresholds.bitrate && rate < *current_thresholds.bitrate; +} + +bool VideoStreamEncoder::EncoderSwitchExperiment::IsPixelCountBelowThreshold( + int pixel_count) const { + return current_thresholds.pixel_count && + pixel_count < *current_thresholds.pixel_count; +} + +void VideoStreamEncoder::EncoderSwitchExperiment::SetCodec( + VideoCodecType codec) { + auto it = codec_thresholds.find(codec); + if (it == codec_thresholds.end()) { + current_thresholds = {}; + } else { + current_thresholds = it->second; + } +} + +VideoStreamEncoder::EncoderSwitchExperiment +VideoStreamEncoder::ParseEncoderSwitchFieldTrial() const { + EncoderSwitchExperiment result; + + // Each "codec threshold" have the format + // ";;", and are separated by the "|" + // character. + webrtc::FieldTrialOptional codec_thresholds_string{ + "codec_thresholds"}; + webrtc::FieldTrialOptional to_codec{"to_codec"}; + webrtc::FieldTrialOptional to_param{"to_param"}; + webrtc::FieldTrialOptional to_value{"to_value"}; + webrtc::FieldTrialOptional window{"window"}; + + webrtc::ParseFieldTrial( + {&codec_thresholds_string, &to_codec, &to_param, &to_value, &window}, + webrtc::field_trial::FindFullName( + "WebRTC-NetworkCondition-EncoderSwitch")); + + if (!codec_thresholds_string || !to_codec || !window) { + return {}; + } + + result.bitrate_filter.Reset(1.0 - 1.0 / *window); + result.to_codec = *to_codec; + result.to_param = to_param.GetOptional(); + result.to_value = to_value.GetOptional(); + + std::vector codecs_thresholds; + if (rtc::split(*codec_thresholds_string, '|', &codecs_thresholds) == 0) { + return {}; + } + + for (const std::string& codec_threshold : codecs_thresholds) { + std::vector thresholds_split; + if (rtc::split(codec_threshold, ';', &thresholds_split) != 3) { + return {}; + } + + VideoCodecType codec = PayloadStringToCodecType(thresholds_split[0]); + int bitrate_kbps; + rtc::FromString(thresholds_split[1], &bitrate_kbps); + int pixel_count; + rtc::FromString(thresholds_split[2], &pixel_count); + + if (bitrate_kbps > 0) { + result.codec_thresholds[codec].bitrate = DataRate::kbps(bitrate_kbps); + } + + if (pixel_count > 0) { + result.codec_thresholds[codec].pixel_count = pixel_count; + } + + if (!result.codec_thresholds[codec].bitrate && + !result.codec_thresholds[codec].pixel_count) { + return {}; + } + } + + rtc::StringBuilder ss; + ss << "Successfully parsed WebRTC-NetworkCondition-EncoderSwitch field " + "trial." + << " to_codec:" << result.to_codec + << " to_param:" << result.to_param.value_or("") + << " to_value:" << result.to_value.value_or("") + << " codec_thresholds:"; + + for (auto kv : result.codec_thresholds) { + std::string codec_name = CodecTypeToPayloadString(kv.first); + std::string bitrate = kv.second.bitrate + ? std::to_string(kv.second.bitrate->kbps()) + : ""; + std::string pixels = kv.second.pixel_count + ? std::to_string(*kv.second.pixel_count) + : ""; + ss << " (" << codec_name << ":" << bitrate << ":" << pixels << ")"; + } + + RTC_LOG(LS_INFO) << ss.str(); + + return result; +} + } // namespace webrtc diff --git a/video/video_stream_encoder.h b/video/video_stream_encoder.h index a00d2c6d5a..4db92f5fa6 100644 --- a/video/video_stream_encoder.h +++ b/video/video_stream_encoder.h @@ -17,6 +17,7 @@ #include #include +#include "api/units/data_rate.h" #include "api/video/video_bitrate_allocator.h" #include "api/video/video_rotation.h" #include "api/video/video_sink_interface.h" @@ -32,6 +33,7 @@ #include "rtc_base/experiments/balanced_degradation_settings.h" #include "rtc_base/experiments/quality_scaler_settings.h" #include "rtc_base/experiments/rate_control_settings.h" +#include "rtc_base/numerics/exp_filter.h" #include "rtc_base/race_checker.h" #include "rtc_base/rate_statistics.h" #include "rtc_base/synchronization/sequence_checker.h" @@ -390,6 +392,42 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface, // destroyed first to make sure no tasks are run that use other members. rtc::TaskQueue encoder_queue_; + struct EncoderSwitchExperiment { + struct Thresholds { + absl::optional bitrate; + absl::optional pixel_count; + }; + + // Codec --> switching thresholds + std::map codec_thresholds; + + // To smooth out the target bitrate so that we don't trigger a switch + // too easily. + rtc::ExpFilter bitrate_filter{1.0}; + + // Codec/implementation to switch to + std::string to_codec; + absl::optional to_param; + absl::optional to_value; + + // Thresholds for the currently used codecs. + Thresholds current_thresholds; + + // Updates the |bitrate_filter|, so not const. + bool IsBitrateBelowThreshold(const DataRate& target_bitrate); + bool IsPixelCountBelowThreshold(int pixel_count) const; + void SetCodec(VideoCodecType codec); + }; + + EncoderSwitchExperiment ParseEncoderSwitchFieldTrial() const; + + EncoderSwitchExperiment encoder_switch_experiment_ + RTC_GUARDED_BY(&encoder_queue_); + + // An encoder switch is only requested once, this variable is used to keep + // track of whether a request has been made or not. + bool encoder_switch_requested_ RTC_GUARDED_BY(&encoder_queue_); + RTC_DISALLOW_COPY_AND_ASSIGN(VideoStreamEncoder); }; diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc index e037bf1d3d..6f19edcbb1 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -49,6 +49,9 @@ namespace webrtc { using ScaleReason = AdaptationObserverInterface::AdaptReason; using ::testing::_; +using ::testing::AllOf; +using ::testing::Field; +using ::testing::StrictMock; namespace { const int kMinPixelsPerFrame = 320 * 180; @@ -4787,4 +4790,89 @@ TEST_F(VideoStreamEncoderTest, BandwidthAllocationLowerBound) { video_stream_encoder_->Stop(); } +struct MockEncoderSwitchRequestCallback : public EncoderSwitchRequestCallback { + MOCK_METHOD0(RequestEncoderFallback, void()); + MOCK_METHOD1(RequestEncoderSwitch, void(const Config& conf)); +}; + +TEST_F(VideoStreamEncoderTest, BitrateEncoderSwitch) { + constexpr int kDontCare = 100; + + StrictMock switch_callback; + video_send_config_.encoder_settings.encoder_switch_request_callback = + &switch_callback; + VideoEncoderConfig encoder_config = video_encoder_config_.Copy(); + encoder_config.codec_type = kVideoCodecVP8; + webrtc::test::ScopedFieldTrials field_trial( + "WebRTC-NetworkCondition-EncoderSwitch/" + "codec_thresholds:VP8;100;-1|H264;-1;30000," + "to_codec:AV1,to_param:ping,to_value:pong,window:2.0/"); + + // Reset encoder for new configuration to take effect. + ConfigureEncoder(std::move(encoder_config)); + + // Send one frame to trigger ReconfigureEncoder. + video_source_.IncomingCapturedFrame( + CreateFrame(kDontCare, kDontCare, kDontCare)); + + using Config = EncoderSwitchRequestCallback::Config; + EXPECT_CALL(switch_callback, + RequestEncoderSwitch(AllOf(Field(&Config::codec_name, "AV1"), + Field(&Config::param, "ping"), + Field(&Config::value, "pong")))); + + video_stream_encoder_->OnBitrateUpdated( + /*target_bitrate=*/DataRate::kbps(50), + /*stable_target_bitrate=*/DataRate::kbps(kDontCare), + /*link_allocation=*/DataRate::kbps(kDontCare), + /*fraction_lost=*/0, + /*rtt_ms=*/0); + + video_stream_encoder_->Stop(); +} + +TEST_F(VideoStreamEncoderTest, ResolutionEncoderSwitch) { + constexpr int kSufficientBitrateToNotDrop = 1000; + constexpr int kHighRes = 500; + constexpr int kLowRes = 100; + + StrictMock switch_callback; + video_send_config_.encoder_settings.encoder_switch_request_callback = + &switch_callback; + webrtc::test::ScopedFieldTrials field_trial( + "WebRTC-NetworkCondition-EncoderSwitch/" + "codec_thresholds:VP8;120;-1|H264;-1;30000," + "to_codec:AV1,to_param:ping,to_value:pong,window:2.0/"); + VideoEncoderConfig encoder_config = video_encoder_config_.Copy(); + encoder_config.codec_type = kVideoCodecH264; + + // Reset encoder for new configuration to take effect. + ConfigureEncoder(std::move(encoder_config)); + + // The VideoStreamEncoder needs some bitrate before it can start encoding, + // setting some bitrate so that subsequent calls to WaitForEncodedFrame does + // not fail. + video_stream_encoder_->OnBitrateUpdated( + /*target_bitrate=*/DataRate::kbps(kSufficientBitrateToNotDrop), + /*stable_target_bitrate=*/DataRate::kbps(kSufficientBitrateToNotDrop), + /*link_allocation=*/DataRate::kbps(kSufficientBitrateToNotDrop), + /*fraction_lost=*/0, + /*rtt_ms=*/0); + + // Send one frame to trigger ReconfigureEncoder. + video_source_.IncomingCapturedFrame(CreateFrame(1, kHighRes, kHighRes)); + WaitForEncodedFrame(1); + + using Config = EncoderSwitchRequestCallback::Config; + EXPECT_CALL(switch_callback, + RequestEncoderSwitch(AllOf(Field(&Config::codec_name, "AV1"), + Field(&Config::param, "ping"), + Field(&Config::value, "pong")))); + + video_source_.IncomingCapturedFrame(CreateFrame(2, kLowRes, kLowRes)); + WaitForEncodedFrame(2); + + video_stream_encoder_->Stop(); +} + } // namespace webrtc