From e1cd3ad4f5488a7cd1fb7f617d7dfbb8d3e28850 Mon Sep 17 00:00:00 2001 From: Sergey Silkin Date: Fri, 21 Jan 2022 11:35:04 +0100 Subject: [PATCH] Switch encoder on init failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently if encoder initialization fails WebRTC doesn't send any video. This CL adds functionality that changes encoder type in such case and restores the video. If encoder selector is available we switch to encoder it recommends. Otherwise, VP8 is used as the default fallback encoder. Bug: webrtc:13572 Change-Id: Ifcdf707a575711f5ff81f9451caf30140c9171dc Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/246960 Reviewed-by: Philip Eliasson Reviewed-by: Erik Språng Reviewed-by: Rasmus Brandt Commit-Queue: Sergey Silkin Cr-Commit-Position: refs/heads/main@{#35761} --- api/video/video_stream_encoder_settings.h | 16 +-- media/engine/webrtc_video_engine.cc | 69 +++-------- media/engine/webrtc_video_engine.h | 12 +- media/engine/webrtc_video_engine_unittest.cc | 117 +++++-------------- video/video_stream_encoder.cc | 76 ++++++++---- video/video_stream_encoder.h | 5 + video/video_stream_encoder_unittest.cc | 114 +++++++++++++++--- 7 files changed, 213 insertions(+), 196 deletions(-) diff --git a/api/video/video_stream_encoder_settings.h b/api/video/video_stream_encoder_settings.h index 743524b352..3aee5b7050 100644 --- a/api/video/video_stream_encoder_settings.h +++ b/api/video/video_stream_encoder_settings.h @@ -23,19 +23,13 @@ class EncoderSwitchRequestCallback { public: virtual ~EncoderSwitchRequestCallback() {} - struct Config { - std::string codec_name; - absl::optional param; - absl::optional value; - }; - - // Requests that encoder fallback is performed. + // Requests switch to next negotiated encoder. virtual void RequestEncoderFallback() = 0; - // Requests that a switch to a specific encoder is performed. - virtual void RequestEncoderSwitch(const Config& conf) = 0; - - virtual void RequestEncoderSwitch(const SdpVideoFormat& format) = 0; + // Requests switch to a specific encoder. If the encoder is not available and + // `allow_default_fallback` is `true` the default fallback is invoked. + virtual void RequestEncoderSwitch(const SdpVideoFormat& format, + bool allow_default_fallback) = 0; }; struct VideoStreamEncoderSettings { diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index b67f4df0d2..940105817d 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -899,56 +899,13 @@ void WebRtcVideoChannel::RequestEncoderFallback() { } void WebRtcVideoChannel::RequestEncoderSwitch( - const EncoderSwitchRequestCallback::Config& conf) { + const webrtc::SdpVideoFormat& format, + bool allow_default_fallback) { if (!worker_thread_->IsCurrent()) { - worker_thread_->PostTask(ToQueuedTask( - task_safety_, [this, conf] { RequestEncoderSwitch(conf); })); - return; - } - - RTC_DCHECK_RUN_ON(&thread_checker_); - - if (!allow_codec_switching_) { - RTC_LOG(LS_INFO) << "Encoder switch requested but codec switching has" - " not been enabled yet."; - requested_encoder_switch_ = conf; - return; - } - - for (const 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."; -} - -void WebRtcVideoChannel::RequestEncoderSwitch( - const webrtc::SdpVideoFormat& format) { - if (!worker_thread_->IsCurrent()) { - worker_thread_->PostTask(ToQueuedTask( - task_safety_, [this, format] { RequestEncoderSwitch(format); })); + worker_thread_->PostTask( + ToQueuedTask(task_safety_, [this, format, allow_default_fallback] { + RequestEncoderSwitch(format, allow_default_fallback); + })); return; } @@ -974,8 +931,13 @@ void WebRtcVideoChannel::RequestEncoderSwitch( } } - RTC_LOG(LS_WARNING) << "Encoder switch failed: SdpVideoFormat " - << format.ToString() << " not negotiated."; + RTC_LOG(LS_WARNING) << "Failed to switch encoder to: " << format.ToString() + << ". Is default fallback allowed: " + << allow_default_fallback; + + if (allow_default_fallback) { + RequestEncoderFallback(); + } } bool WebRtcVideoChannel::ApplyChangedParams( @@ -1956,11 +1918,6 @@ void WebRtcVideoChannel::SetVideoCodecSwitchingEnabled(bool enabled) { allow_codec_switching_ = enabled; if (allow_codec_switching_) { RTC_LOG(LS_INFO) << "Encoder switching enabled."; - if (requested_encoder_switch_) { - RTC_LOG(LS_INFO) << "Executing cached video encoder switch request."; - RequestEncoderSwitch(*requested_encoder_switch_); - requested_encoder_switch_.reset(); - } } } diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h index 72ed7bf0b3..940985d9f8 100644 --- a/media/engine/webrtc_video_engine.h +++ b/media/engine/webrtc_video_engine.h @@ -224,11 +224,8 @@ class WebRtcVideoChannel : public VideoMediaChannel, // Implements webrtc::EncoderSwitchRequestCallback. void RequestEncoderFallback() override; - - // TODO(bugs.webrtc.org/11341) : Remove this version of RequestEncoderSwitch. - void RequestEncoderSwitch( - const EncoderSwitchRequestCallback::Config& conf) override; - void RequestEncoderSwitch(const webrtc::SdpVideoFormat& format) override; + void RequestEncoderSwitch(const webrtc::SdpVideoFormat& format, + bool allow_default_fallback) override; void SetRecordableEncodedFrameCallback( uint32_t ssrc, @@ -638,9 +635,10 @@ class WebRtcVideoChannel : public VideoMediaChannel, std::unique_ptr unknown_ssrc_packet_buffer_ RTC_GUARDED_BY(thread_checker_); + // TODO(bugs.webrtc.org/11341): Remove this and relevant PC API. Presence + // of multiple negotiated codecs allows generic encoder fallback on failures. + // Presence of EncoderSelector allows switching to specific encoders. bool allow_codec_switching_ = false; - absl::optional - requested_encoder_switch_; }; class EncoderStreamFactory diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc index 91329e60d9..a1aa88de7b 100644 --- a/media/engine/webrtc_video_engine_unittest.cc +++ b/media/engine/webrtc_video_engine_unittest.cc @@ -2405,116 +2405,59 @@ TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderFallback) { 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"; - +TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchDefaultFallback) { 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); - + parameters.codecs.push_back(GetEngineCodec("VP9")); + parameters.codecs.push_back(GetEngineCodec("VP8")); EXPECT_TRUE(channel_->SetSendParameters(parameters)); - channel_->SetVideoCodecSwitchingEnabled(true); VideoCodec codec; ASSERT_TRUE(channel_->GetSendCodec(&codec)); - EXPECT_THAT(codec.name, Eq("VP9")); + EXPECT_EQ("VP9", codec.name); // 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); + channel_->RequestEncoderSwitch(webrtc::SdpVideoFormat("UnavailableCodec"), + /*allow_default_fallback=*/true); 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); + // Requested encoder is not available. Default fallback is allowed. Switch to + // the next negotiated codec, VP8. ASSERT_TRUE(channel_->GetSendCodec(&codec)); - EXPECT_THAT(codec.name, Eq("VP9")); - EXPECT_THAT(codec.params, Contains(Pair(kParam, kPong))); + EXPECT_EQ("VP8", codec.name); } -TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchIncorrectParam) { - const std::string kParam = "the-param"; - const std::string kPing = "ping"; - const std::string kPong = "pong"; +TEST_F(WebRtcVideoChannelBaseTest, RequestEncoderSwitchStrictPreference) { + VideoCodec vp9 = GetEngineCodec("VP9"); + vp9.params["profile-id"] = "0"; cricket::VideoSendParameters parameters; - VideoCodec vp9 = GetEngineCodec("VP9"); - vp9.params[kParam] = kPong; + parameters.codecs.push_back(GetEngineCodec("VP8")); parameters.codecs.push_back(vp9); - - VideoCodec vp8 = GetEngineCodec("VP8"); - vp8.params[kParam] = kPing; - parameters.codecs.push_back(vp8); - - EXPECT_TRUE(channel_->SetSendParameters(parameters)); - channel_->SetVideoCodecSwitchingEnabled(true); - - 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))); -} - -TEST_F(WebRtcVideoChannelBaseTest, - RequestEncoderSwitchWithConfigBeforeEnabling) { - 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")); + EXPECT_EQ("VP8", codec.name); - webrtc::EncoderSwitchRequestCallback::Config conf{"VP8", kParam, kPing}; - channel_->RequestEncoderSwitch(conf); - - // Enable codec switching after it has been requested. - channel_->SetVideoCodecSwitchingEnabled(true); - - // RequestEncoderSwitch will post a task to the worker thread (which is also - // the current thread), hence the ProcessMessages call. + channel_->RequestEncoderSwitch( + webrtc::SdpVideoFormat("VP9", {{"profile-id", "1"}}), + /*allow_default_fallback=*/false); rtc::Thread::Current()->ProcessMessages(30); + + // VP9 profile_id=1 is not available. Default fallback is not allowed. Switch + // is not performed. ASSERT_TRUE(channel_->GetSendCodec(&codec)); - EXPECT_THAT(codec.name, Eq("VP8")); - EXPECT_THAT(codec.params, Contains(Pair(kParam, kPing))); + EXPECT_EQ("VP8", codec.name); + + channel_->RequestEncoderSwitch( + webrtc::SdpVideoFormat("VP9", {{"profile-id", "0"}}), + /*allow_default_fallback=*/false); + rtc::Thread::Current()->ProcessMessages(30); + + // VP9 profile_id=0 is available. Switch encoder. + ASSERT_TRUE(channel_->GetSendCodec(&codec)); + EXPECT_EQ("VP9", codec.name); } TEST_F(WebRtcVideoChannelBaseTest, SendCodecIsMovedToFrontInRtpParameters) { diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc index 40c46ae118..28cd8a7bba 100644 --- a/video/video_stream_encoder.cc +++ b/video/video_stream_encoder.cc @@ -28,6 +28,7 @@ #include "api/video/video_bitrate_allocator_factory.h" #include "api/video/video_codec_constants.h" #include "api/video/video_layers_allocation.h" +#include "api/video_codecs/sdp_video_format.h" #include "api/video_codecs/video_encoder.h" #include "call/adaptation/resource_adaptation_processor.h" #include "call/adaptation/video_stream_adapter.h" @@ -65,6 +66,11 @@ const int64_t kPendingFrameTimeoutMs = 1000; constexpr char kFrameDropperFieldTrial[] = "WebRTC-FrameDropper"; +// TODO(bugs.webrtc.org/13572): Remove this kill switch after deploying the +// feature. +constexpr char kSwitchEncoderOnInitializationFailuresFieldTrial[] = + "WebRTC-SwitchEncoderOnInitializationFailures"; + const size_t kDefaultPayloadSize = 1440; const int64_t kParameterUpdateIntervalMs = 1000; @@ -660,6 +666,8 @@ VideoStreamEncoder::VideoStreamEncoder( !field_trial::IsEnabled("WebRTC-DefaultBitrateLimitsKillSwitch")), qp_parsing_allowed_( !field_trial::IsEnabled("WebRTC-QpParsingKillSwitch")), + switch_encoder_on_init_failures_(!field_trial::IsDisabled( + kSwitchEncoderOnInitializationFailuresFieldTrial)), encoder_queue_(std::move(encoder_queue)) { TRACE_EVENT0("webrtc", "VideoStreamEncoder::VideoStreamEncoder"); RTC_DCHECK_RUN_ON(worker_queue_); @@ -1133,7 +1141,6 @@ void VideoStreamEncoder::ReconfigureEncoder() { // Encoder creation block is split in two since EncoderInfo needed to start // CPU adaptation with the correct settings should be polled after // encoder_->InitEncode(). - bool success = true; if (encoder_reset_required) { ReleaseEncoder(); const size_t max_data_payload_length = max_data_payload_length_ > 0 @@ -1148,7 +1155,6 @@ void VideoStreamEncoder::ReconfigureEncoder() { << CodecTypeToPayloadString(send_codec_.codecType) << " (" << send_codec_.codecType << ")"; ReleaseEncoder(); - success = false; } else { encoder_initialized_ = true; encoder_->RegisterEncodeCompleteCallback(this); @@ -1167,7 +1173,7 @@ void VideoStreamEncoder::ReconfigureEncoder() { // Inform dependents of updated encoder settings. OnEncoderSettingsChanged(); - if (success) { + if (encoder_initialized_) { RTC_LOG(LS_VERBOSE) << " max bitrate " << codec.maxBitrate << " start bitrate " << codec.startBitrate << " max frame rate " << codec.maxFramerate @@ -1253,6 +1259,49 @@ void VideoStreamEncoder::ReconfigureEncoder() { stream_resource_manager_.ConfigureQualityScaler(info); stream_resource_manager_.ConfigureBandwidthQualityScaler(info); + + if (!encoder_initialized_) { + RTC_LOG(LS_WARNING) << "Failed to initialize " + << CodecTypeToPayloadString(codec.codecType) + << " encoder." + << "switch_encoder_on_init_failures: " + << switch_encoder_on_init_failures_; + + if (switch_encoder_on_init_failures_) { + RequestEncoderSwitch(); + } + } +} + +void VideoStreamEncoder::RequestEncoderSwitch() { + bool is_encoder_switching_supported = + settings_.encoder_switch_request_callback != nullptr; + bool is_encoder_selector_available = encoder_selector_ != nullptr; + + RTC_LOG(LS_INFO) << "RequestEncoderSwitch." + << " is_encoder_selector_available: " + << is_encoder_selector_available + << " is_encoder_switching_supported: " + << is_encoder_switching_supported; + + if (!is_encoder_switching_supported) { + return; + } + + // If encoder selector is available, switch to the encoder it prefers. + // Otherwise try switching to VP8 (default WebRTC codec). + absl::optional preferred_fallback_encoder; + if (is_encoder_selector_available) { + preferred_fallback_encoder = encoder_selector_->OnEncoderBroken(); + } + + if (!preferred_fallback_encoder) { + preferred_fallback_encoder = + SdpVideoFormat(CodecTypeToPayloadString(kVideoCodecVP8)); + } + + settings_.encoder_switch_request_callback->RequestEncoderSwitch( + *preferred_fallback_encoder, /*allow_default_fallback=*/true); } void VideoStreamEncoder::OnEncoderSettingsChanged() { @@ -1651,7 +1700,7 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame, // If the encoder fail we can't continue to encode frames. When this happens // the WebrtcVideoSender is notified and the whole VideoSendStream is // recreated. - if (encoder_failed_) + if (encoder_failed_ || !encoder_initialized_) return; // It's possible that EncodeVideoFrame can be called after we've completed @@ -1789,21 +1838,7 @@ 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_switch_request_callback) { - if (encoder_selector_) { - if (auto encoder = encoder_selector_->OnEncoderBroken()) { - settings_.encoder_switch_request_callback->RequestEncoderSwitch( - *encoder); - } - } else { - encoder_failed_ = true; - settings_.encoder_switch_request_callback->RequestEncoderFallback(); - } - } else { - RTC_LOG(LS_ERROR) - << "Encoder failed but no encoder fallback callback is registered"; - } + RequestEncoderSwitch(); } else { RTC_LOG(LS_ERROR) << "Failed to encode frame. Error code: " << encode_status; @@ -2059,7 +2094,8 @@ void VideoStreamEncoder::OnBitrateUpdated(DataRate target_bitrate, if (!video_is_suspended && settings_.encoder_switch_request_callback && encoder_selector_) { if (auto encoder = encoder_selector_->OnAvailableBitrate(link_allocation)) { - settings_.encoder_switch_request_callback->RequestEncoderSwitch(*encoder); + settings_.encoder_switch_request_callback->RequestEncoderSwitch( + *encoder, /*allow_default_fallback=*/false); } } diff --git a/video/video_stream_encoder.h b/video/video_stream_encoder.h index da4747f555..b0a4661c7b 100644 --- a/video/video_stream_encoder.h +++ b/video/video_stream_encoder.h @@ -245,6 +245,8 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface, int64_t time_when_posted_in_ms) RTC_RUN_ON(&encoder_queue_); + void RequestEncoderSwitch() RTC_RUN_ON(&encoder_queue_); + TaskQueueBase* const worker_queue_; const uint32_t number_of_cores_; @@ -429,6 +431,9 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface, QpParser qp_parser_; const bool qp_parsing_allowed_; + // Enables encoder switching on initialization failures. + bool switch_encoder_on_init_failures_; + // Public methods are proxied to the task queues. The queues must be destroyed // first to make sure no tasks run that use other members. rtc::TaskQueue encoder_queue_; diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc index d2b2f3bb35..3a81ec25ba 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -7465,10 +7465,10 @@ TEST_F(VideoStreamEncoderTest, EncoderRatesPropagatedOnReconfigure) { struct MockEncoderSwitchRequestCallback : public EncoderSwitchRequestCallback { MOCK_METHOD(void, RequestEncoderFallback, (), (override)); - MOCK_METHOD(void, RequestEncoderSwitch, (const Config& conf), (override)); MOCK_METHOD(void, RequestEncoderSwitch, - (const webrtc::SdpVideoFormat& format), + (const webrtc::SdpVideoFormat& format, + bool allow_default_fallback), (override)); }; @@ -7482,14 +7482,14 @@ TEST_F(VideoStreamEncoderTest, EncoderSelectorCurrentEncoderIsSignaled) { // Reset encoder for new configuration to take effect. ConfigureEncoder(video_encoder_config_.Copy()); - EXPECT_CALL(encoder_selector, OnCurrentEncoder(_)); + EXPECT_CALL(encoder_selector, OnCurrentEncoder); video_source_.IncomingCapturedFrame( CreateFrame(kDontCare, kDontCare, kDontCare)); AdvanceTime(TimeDelta::Zero()); video_stream_encoder_->Stop(); - // The encoders produces by the VideoEncoderProxyFactory have a pointer back + // The encoders produced by the VideoEncoderProxyFactory have a pointer back // to it's factory, so in order for the encoder instance in the // `video_stream_encoder_` to be destroyed before the `encoder_factory` we // reset the `video_stream_encoder_` here. @@ -7510,11 +7510,11 @@ TEST_F(VideoStreamEncoderTest, EncoderSelectorBitrateSwitch) { // Reset encoder for new configuration to take effect. ConfigureEncoder(video_encoder_config_.Copy()); - ON_CALL(encoder_selector, OnAvailableBitrate(_)) + ON_CALL(encoder_selector, OnAvailableBitrate) .WillByDefault(Return(SdpVideoFormat("AV1"))); EXPECT_CALL(switch_callback, - RequestEncoderSwitch(Matcher( - Field(&SdpVideoFormat::name, "AV1")))); + RequestEncoderSwitch(Field(&SdpVideoFormat::name, "AV1"), + /*allow_default_fallback=*/false)); video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( /*target_bitrate=*/DataRate::KilobitsPerSec(50), @@ -7556,18 +7556,16 @@ TEST_F(VideoStreamEncoderTest, EncoderSelectorBrokenEncoderSwitch) { /*round_trip_time_ms=*/0, /*cwnd_reduce_ratio=*/0); - ON_CALL(video_encoder, Encode(_, _)) + ON_CALL(video_encoder, Encode) .WillByDefault(Return(WEBRTC_VIDEO_CODEC_ENCODER_FAILURE)); - ON_CALL(encoder_selector, OnEncoderBroken()) + ON_CALL(encoder_selector, OnEncoderBroken) .WillByDefault(Return(SdpVideoFormat("AV2"))); rtc::Event encode_attempted; EXPECT_CALL(switch_callback, - RequestEncoderSwitch(Matcher(_))) - .WillOnce([&encode_attempted](const SdpVideoFormat& format) { - EXPECT_EQ(format.name, "AV2"); - encode_attempted.Set(); - }); + RequestEncoderSwitch(Field(&SdpVideoFormat::name, "AV2"), + /*allow_default_fallback=*/true)) + .WillOnce([&encode_attempted]() { encode_attempted.Set(); }); video_source_.IncomingCapturedFrame(CreateFrame(1, kDontCare, kDontCare)); encode_attempted.Wait(3000); @@ -7576,7 +7574,93 @@ TEST_F(VideoStreamEncoderTest, EncoderSelectorBrokenEncoderSwitch) { video_stream_encoder_->Stop(); - // The encoders produces by the VideoEncoderProxyFactory have a pointer back + // The encoders produced by the VideoEncoderProxyFactory have a pointer back + // to it's factory, so in order for the encoder instance in the + // `video_stream_encoder_` to be destroyed before the `encoder_factory` we + // reset the `video_stream_encoder_` here. + video_stream_encoder_.reset(); +} + +TEST_F(VideoStreamEncoderTest, SwitchEncoderOnInitFailureWithEncoderSelector) { + NiceMock video_encoder; + NiceMock encoder_selector; + StrictMock switch_callback; + video_send_config_.encoder_settings.encoder_switch_request_callback = + &switch_callback; + auto encoder_factory = std::make_unique( + &video_encoder, &encoder_selector); + video_send_config_.encoder_settings.encoder_factory = encoder_factory.get(); + + // Reset encoder for new configuration to take effect. + ConfigureEncoder(video_encoder_config_.Copy()); + + video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( + kTargetBitrate, kTargetBitrate, kTargetBitrate, /*fraction_lost=*/0, + /*round_trip_time_ms=*/0, + /*cwnd_reduce_ratio=*/0); + ASSERT_EQ(0, sink_.number_of_reconfigurations()); + + ON_CALL(video_encoder, InitEncode(_, _)) + .WillByDefault(Return(WEBRTC_VIDEO_CODEC_ENCODER_FAILURE)); + ON_CALL(encoder_selector, OnEncoderBroken) + .WillByDefault(Return(SdpVideoFormat("AV2"))); + + rtc::Event encode_attempted; + EXPECT_CALL(switch_callback, + RequestEncoderSwitch(Field(&SdpVideoFormat::name, "AV2"), + /*allow_default_fallback=*/true)) + .WillOnce([&encode_attempted]() { encode_attempted.Set(); }); + + video_source_.IncomingCapturedFrame(CreateFrame(1, nullptr)); + encode_attempted.Wait(3000); + + AdvanceTime(TimeDelta::Zero()); + + video_stream_encoder_->Stop(); + + // The encoders produced by the VideoEncoderProxyFactory have a pointer back + // to it's factory, so in order for the encoder instance in the + // `video_stream_encoder_` to be destroyed before the `encoder_factory` we + // reset the `video_stream_encoder_` here. + video_stream_encoder_.reset(); +} + +TEST_F(VideoStreamEncoderTest, + SwitchEncoderOnInitFailureWithoutEncoderSelector) { + NiceMock video_encoder; + StrictMock switch_callback; + video_send_config_.encoder_settings.encoder_switch_request_callback = + &switch_callback; + auto encoder_factory = std::make_unique( + &video_encoder, /*encoder_selector=*/nullptr); + video_send_config_.encoder_settings.encoder_factory = encoder_factory.get(); + + // Reset encoder for new configuration to take effect. + ConfigureEncoder(video_encoder_config_.Copy()); + + video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( + kTargetBitrate, kTargetBitrate, kTargetBitrate, /*fraction_lost=*/0, + /*round_trip_time_ms=*/0, + /*cwnd_reduce_ratio=*/0); + ASSERT_EQ(0, sink_.number_of_reconfigurations()); + + ON_CALL(video_encoder, InitEncode(_, _)) + .WillByDefault(Return(WEBRTC_VIDEO_CODEC_ENCODER_FAILURE)); + + rtc::Event encode_attempted; + EXPECT_CALL(switch_callback, + RequestEncoderSwitch(Field(&SdpVideoFormat::name, "VP8"), + /*allow_default_fallback=*/true)) + .WillOnce([&encode_attempted]() { encode_attempted.Set(); }); + + video_source_.IncomingCapturedFrame(CreateFrame(1, nullptr)); + encode_attempted.Wait(3000); + + AdvanceTime(TimeDelta::Zero()); + + video_stream_encoder_->Stop(); + + // The encoders produced by the VideoEncoderProxyFactory have a pointer back // to it's factory, so in order for the encoder instance in the // `video_stream_encoder_` to be destroyed before the `encoder_factory` we // reset the `video_stream_encoder_` here.