/* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/video_codecs/video_decoder_software_fallback_wrapper.h" #include #include "absl/types/optional.h" #include "api/video/encoded_image.h" #include "api/video/video_frame.h" #include "api/video_codecs/video_decoder.h" #include "modules/video_coding/codecs/vp8/include/vp8.h" #include "modules/video_coding/include/video_codec_interface.h" #include "modules/video_coding/include/video_error_codes.h" #include "rtc_base/checks.h" #include "test/field_trial.h" #include "test/gtest.h" namespace webrtc { class VideoDecoderSoftwareFallbackWrapperTest : public ::testing::Test { protected: VideoDecoderSoftwareFallbackWrapperTest() : VideoDecoderSoftwareFallbackWrapperTest("") {} explicit VideoDecoderSoftwareFallbackWrapperTest( const std::string& field_trials) : override_field_trials_(field_trials), fake_decoder_(new CountingFakeDecoder()), fallback_wrapper_(CreateVideoDecoderSoftwareFallbackWrapper( std::unique_ptr(VP8Decoder::Create()), std::unique_ptr(fake_decoder_))) {} class CountingFakeDecoder : public VideoDecoder { public: bool Configure(const Settings& settings) override { ++configure_count_; return configure_return_value_; } int32_t Decode(const EncodedImage& input_image, bool missing_frames, int64_t render_time_ms) override { ++decode_count_; return decode_return_code_; } int32_t RegisterDecodeCompleteCallback( DecodedImageCallback* callback) override { decode_complete_callback_ = callback; return WEBRTC_VIDEO_CODEC_OK; } int32_t Release() override { ++release_count_; return WEBRTC_VIDEO_CODEC_OK; } const char* ImplementationName() const override { return "fake-decoder"; } int configure_count_ = 0; int decode_count_ = 0; bool configure_return_value_ = true; int32_t decode_return_code_ = WEBRTC_VIDEO_CODEC_OK; DecodedImageCallback* decode_complete_callback_ = nullptr; int release_count_ = 0; int reset_count_ = 0; }; test::ScopedFieldTrials override_field_trials_; // `fake_decoder_` is owned and released by `fallback_wrapper_`. CountingFakeDecoder* fake_decoder_; std::unique_ptr fallback_wrapper_; }; TEST_F(VideoDecoderSoftwareFallbackWrapperTest, InitializesDecoder) { fallback_wrapper_->Configure({}); EXPECT_EQ(1, fake_decoder_->configure_count_); EncodedImage encoded_image; encoded_image._frameType = VideoFrameType::kVideoFrameKey; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(1, fake_decoder_->configure_count_) << "Initialized decoder should not be reinitialized."; EXPECT_EQ(1, fake_decoder_->decode_count_); } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, UsesFallbackDecoderAfterAnyInitDecodeFailure) { fake_decoder_->configure_return_value_ = false; fallback_wrapper_->Configure({}); EXPECT_EQ(1, fake_decoder_->configure_count_); EncodedImage encoded_image; encoded_image._frameType = VideoFrameType::kVideoFrameKey; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(1, fake_decoder_->configure_count_) << "Should not have attempted reinitializing the fallback decoder on " "keyframe."; // Unfortunately faking a VP8 frame is hard. Rely on no Decode -> using SW // decoder. EXPECT_EQ(0, fake_decoder_->decode_count_) << "Decoder used even though no InitDecode had succeeded."; } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, IsSoftwareFallbackSticky) { fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; EncodedImage encoded_image; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(1, fake_decoder_->decode_count_); // Software fallback should be sticky, fake_decoder_ shouldn't be used. encoded_image._frameType = VideoFrameType::kVideoFrameKey; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(1, fake_decoder_->decode_count_) << "Decoder shouldn't be used after failure."; // fake_decoder_ should have only been initialized once during the test. EXPECT_EQ(1, fake_decoder_->configure_count_); } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, DoesNotFallbackOnEveryError) { fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_ERROR; EncodedImage encoded_image; EXPECT_EQ(fake_decoder_->decode_return_code_, fallback_wrapper_->Decode(encoded_image, false, -1)); EXPECT_EQ(1, fake_decoder_->decode_count_); fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(2, fake_decoder_->decode_count_) << "Decoder should be active even though previous decode failed."; } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, UsesHwDecoderAfterReinit) { fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; EncodedImage encoded_image; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(1, fake_decoder_->decode_count_); fallback_wrapper_->Release(); fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_OK; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(2, fake_decoder_->decode_count_) << "Should not be using fallback after reinit."; } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, ForwardsReleaseCall) { fallback_wrapper_->Configure({}); fallback_wrapper_->Release(); EXPECT_EQ(1, fake_decoder_->release_count_); fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; EncodedImage encoded_image; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(2, fake_decoder_->release_count_) << "Decoder should be released during fallback."; fallback_wrapper_->Release(); EXPECT_EQ(2, fake_decoder_->release_count_); } // TODO(pbos): Fake a VP8 frame well enough to actually receive a callback from // the software decoder. TEST_F(VideoDecoderSoftwareFallbackWrapperTest, ForwardsRegisterDecodeCompleteCallback) { class FakeDecodedImageCallback : public DecodedImageCallback { int32_t Decoded(VideoFrame& decodedImage) override { return 0; } int32_t Decoded(webrtc::VideoFrame& decodedImage, int64_t decode_time_ms) override { RTC_DCHECK_NOTREACHED(); return -1; } void Decoded(webrtc::VideoFrame& decodedImage, absl::optional decode_time_ms, absl::optional qp) override { RTC_DCHECK_NOTREACHED(); } } callback; fallback_wrapper_->Configure({}); fallback_wrapper_->RegisterDecodeCompleteCallback(&callback); EXPECT_EQ(&callback, fake_decoder_->decode_complete_callback_); } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, ReportsFallbackImplementationName) { fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; EncodedImage encoded_image; fallback_wrapper_->Decode(encoded_image, false, -1); // Hard coded expected value since libvpx is the software implementation name // for VP8. Change accordingly if the underlying implementation does. EXPECT_STREQ("libvpx (fallback from: fake-decoder)", fallback_wrapper_->ImplementationName()); fallback_wrapper_->Release(); } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, FallbacksOnTooManyErrors) { fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_ERROR; EncodedImage encoded_image; encoded_image._frameType = VideoFrameType::kVideoFrameKey; // Doesn't fallback from a single error. fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_STREQ("fake-decoder", fallback_wrapper_->ImplementationName()); // However, many frames with the same error, fallback should happen. const int kNumFramesToEncode = 10; for (int i = 0; i < kNumFramesToEncode; ++i) { fallback_wrapper_->Decode(encoded_image, false, -1); } // Hard coded expected value since libvpx is the software implementation name // for VP8. Change accordingly if the underlying implementation does. EXPECT_STREQ("libvpx (fallback from: fake-decoder)", fallback_wrapper_->ImplementationName()); fallback_wrapper_->Release(); } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, DoesNotFallbackOnDeltaFramesErrors) { fallback_wrapper_->Configure({}); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_ERROR; EncodedImage encoded_image; encoded_image._frameType = VideoFrameType::kVideoFrameDelta; // Many decoded frames with the same error const int kNumFramesToEncode = 10; for (int i = 0; i < kNumFramesToEncode; ++i) { fallback_wrapper_->Decode(encoded_image, false, -1); } EXPECT_STREQ("fake-decoder", fallback_wrapper_->ImplementationName()); fallback_wrapper_->Release(); } TEST_F(VideoDecoderSoftwareFallbackWrapperTest, DoesNotFallbacksOnNonConsequtiveErrors) { fallback_wrapper_->Configure({}); EncodedImage encoded_image; encoded_image._frameType = VideoFrameType::kVideoFrameKey; const int kNumFramesToEncode = 10; for (int i = 0; i < kNumFramesToEncode; ++i) { // Interleaved errors and successful decodes. fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_ERROR; fallback_wrapper_->Decode(encoded_image, false, -1); fake_decoder_->decode_return_code_ = WEBRTC_VIDEO_CODEC_OK; fallback_wrapper_->Decode(encoded_image, false, -1); } EXPECT_STREQ("fake-decoder", fallback_wrapper_->ImplementationName()); fallback_wrapper_->Release(); } class ForcedSoftwareDecoderFallbackTest : public VideoDecoderSoftwareFallbackWrapperTest { public: ForcedSoftwareDecoderFallbackTest() : VideoDecoderSoftwareFallbackWrapperTest( "WebRTC-Video-ForcedSwDecoderFallback/Enabled/") { fake_decoder_ = new CountingFakeDecoder(); sw_fallback_decoder_ = new CountingFakeDecoder(); fallback_wrapper_ = CreateVideoDecoderSoftwareFallbackWrapper( std::unique_ptr(sw_fallback_decoder_), std::unique_ptr(fake_decoder_)); } CountingFakeDecoder* sw_fallback_decoder_; }; TEST_F(ForcedSoftwareDecoderFallbackTest, UsesForcedFallback) { fallback_wrapper_->Configure({}); EXPECT_EQ(1, sw_fallback_decoder_->configure_count_); EncodedImage encoded_image; encoded_image._frameType = VideoFrameType::kVideoFrameKey; fallback_wrapper_->Decode(encoded_image, false, -1); EXPECT_EQ(1, sw_fallback_decoder_->configure_count_); EXPECT_EQ(1, sw_fallback_decoder_->decode_count_); fallback_wrapper_->Release(); EXPECT_EQ(1, sw_fallback_decoder_->release_count_); // Only fallback decoder should have been used. EXPECT_EQ(0, fake_decoder_->configure_count_); EXPECT_EQ(0, fake_decoder_->decode_count_); EXPECT_EQ(0, fake_decoder_->release_count_); } } // namespace webrtc