| /* |
| * Copyright (c) 2020 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 "video/video_stream_decoder_impl.h" |
| |
| #include <vector> |
| |
| #include "api/video/i420_buffer.h" |
| #include "api/video_codecs/video_decoder.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| #include "test/time_controller/simulated_time_controller.h" |
| |
| namespace webrtc { |
| namespace { |
| using ::testing::_; |
| using ::testing::ByMove; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| |
| class MockVideoStreamDecoderCallbacks |
| : public VideoStreamDecoderInterface::Callbacks { |
| public: |
| MOCK_METHOD(void, OnNonDecodableState, (), (override)); |
| MOCK_METHOD(void, OnContinuousUntil, (int64_t frame_id), (override)); |
| MOCK_METHOD( |
| void, |
| OnDecodedFrame, |
| (VideoFrame frame, |
| const VideoStreamDecoderInterface::Callbacks::FrameInfo& frame_info), |
| (override)); |
| }; |
| |
| class StubVideoDecoder : public VideoDecoder { |
| public: |
| StubVideoDecoder() { ON_CALL(*this, Configure).WillByDefault(Return(true)); } |
| |
| MOCK_METHOD(bool, Configure, (const Settings&), (override)); |
| |
| int32_t Decode(const EncodedImage& input_image, |
| bool missing_frames, |
| int64_t render_time_ms) override { |
| int32_t ret_code = DecodeCall(input_image, missing_frames, render_time_ms); |
| if (ret_code == WEBRTC_VIDEO_CODEC_OK || |
| ret_code == WEBRTC_VIDEO_CODEC_OK_REQUEST_KEYFRAME) { |
| VideoFrame frame = VideoFrame::Builder() |
| .set_video_frame_buffer(I420Buffer::Create(1, 1)) |
| .build(); |
| callback_->Decoded(frame); |
| } |
| return ret_code; |
| } |
| |
| MOCK_METHOD(int32_t, |
| DecodeCall, |
| (const EncodedImage& input_image, |
| bool missing_frames, |
| int64_t render_time_ms), |
| ()); |
| |
| int32_t Release() override { return 0; } |
| |
| int32_t RegisterDecodeCompleteCallback( |
| DecodedImageCallback* callback) override { |
| callback_ = callback; |
| return 0; |
| } |
| |
| private: |
| DecodedImageCallback* callback_; |
| }; |
| |
| class WrappedVideoDecoder : public VideoDecoder { |
| public: |
| explicit WrappedVideoDecoder(StubVideoDecoder* decoder) : decoder_(decoder) {} |
| |
| bool Configure(const Settings& settings) override { |
| return decoder_->Configure(settings); |
| } |
| int32_t Decode(const EncodedImage& input_image, |
| bool missing_frames, |
| int64_t render_time_ms) override { |
| return decoder_->Decode(input_image, missing_frames, render_time_ms); |
| } |
| int32_t Release() override { return decoder_->Release(); } |
| |
| int32_t RegisterDecodeCompleteCallback( |
| DecodedImageCallback* callback) override { |
| return decoder_->RegisterDecodeCompleteCallback(callback); |
| } |
| |
| private: |
| StubVideoDecoder* decoder_; |
| }; |
| |
| class FakeVideoDecoderFactory : public VideoDecoderFactory { |
| public: |
| std::vector<SdpVideoFormat> GetSupportedFormats() const override { |
| return {}; |
| } |
| std::unique_ptr<VideoDecoder> CreateVideoDecoder( |
| const SdpVideoFormat& format) override { |
| if (format.name == "VP8") { |
| return std::make_unique<WrappedVideoDecoder>(&vp8_decoder_); |
| } |
| |
| if (format.name == "AV1") { |
| return std::make_unique<WrappedVideoDecoder>(&av1_decoder_); |
| } |
| |
| return {}; |
| } |
| |
| StubVideoDecoder& Vp8Decoder() { return vp8_decoder_; } |
| StubVideoDecoder& Av1Decoder() { return av1_decoder_; } |
| |
| private: |
| NiceMock<StubVideoDecoder> vp8_decoder_; |
| NiceMock<StubVideoDecoder> av1_decoder_; |
| }; |
| |
| class FakeEncodedFrame : public EncodedFrame { |
| public: |
| int64_t ReceivedTime() const override { return 0; } |
| int64_t RenderTime() const override { return 0; } |
| |
| // Setters for protected variables. |
| void SetPayloadType(int payload_type) { _payloadType = payload_type; } |
| }; |
| |
| class FrameBuilder { |
| public: |
| FrameBuilder() : frame_(std::make_unique<FakeEncodedFrame>()) {} |
| |
| FrameBuilder& WithPayloadType(int payload_type) { |
| frame_->SetPayloadType(payload_type); |
| return *this; |
| } |
| |
| FrameBuilder& WithPictureId(int picture_id) { |
| frame_->SetId(picture_id); |
| return *this; |
| } |
| |
| std::unique_ptr<FakeEncodedFrame> Build() { return std::move(frame_); } |
| |
| private: |
| std::unique_ptr<FakeEncodedFrame> frame_; |
| }; |
| |
| class VideoStreamDecoderImplTest : public ::testing::Test { |
| public: |
| VideoStreamDecoderImplTest() |
| : time_controller_(Timestamp::Seconds(0)), |
| video_stream_decoder_(&callbacks_, |
| &decoder_factory_, |
| time_controller_.GetTaskQueueFactory(), |
| {{1, std::make_pair(SdpVideoFormat("VP8"), 1)}, |
| {2, std::make_pair(SdpVideoFormat("AV1"), 1)}}) { |
| // Set the min playout delay to a value greater than zero to not activate |
| // the low-latency renderer. |
| video_stream_decoder_.SetMinPlayoutDelay(TimeDelta::Millis(10)); |
| } |
| |
| NiceMock<MockVideoStreamDecoderCallbacks> callbacks_; |
| FakeVideoDecoderFactory decoder_factory_; |
| GlobalSimulatedTimeController time_controller_; |
| VideoStreamDecoderImpl video_stream_decoder_; |
| }; |
| |
| TEST_F(VideoStreamDecoderImplTest, InsertAndDecodeFrame) { |
| video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build()); |
| EXPECT_CALL(callbacks_, OnDecodedFrame); |
| time_controller_.AdvanceTime(TimeDelta::Millis(1)); |
| } |
| |
| TEST_F(VideoStreamDecoderImplTest, NonDecodableStateWaitingForKeyframe) { |
| EXPECT_CALL(callbacks_, OnNonDecodableState); |
| time_controller_.AdvanceTime(TimeDelta::Millis(200)); |
| } |
| |
| TEST_F(VideoStreamDecoderImplTest, NonDecodableStateWaitingForDeltaFrame) { |
| video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build()); |
| EXPECT_CALL(callbacks_, OnDecodedFrame); |
| time_controller_.AdvanceTime(TimeDelta::Millis(1)); |
| EXPECT_CALL(callbacks_, OnNonDecodableState); |
| time_controller_.AdvanceTime(TimeDelta::Millis(3000)); |
| } |
| |
| TEST_F(VideoStreamDecoderImplTest, InsertAndDecodeFrameWithKeyframeRequest) { |
| video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build()); |
| EXPECT_CALL(decoder_factory_.Vp8Decoder(), DecodeCall) |
| .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK_REQUEST_KEYFRAME)); |
| EXPECT_CALL(callbacks_, OnDecodedFrame); |
| EXPECT_CALL(callbacks_, OnNonDecodableState); |
| time_controller_.AdvanceTime(TimeDelta::Millis(1)); |
| } |
| |
| TEST_F(VideoStreamDecoderImplTest, FailToInitDecoder) { |
| video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build()); |
| ON_CALL(decoder_factory_.Vp8Decoder(), Configure) |
| .WillByDefault(Return(false)); |
| EXPECT_CALL(callbacks_, OnNonDecodableState); |
| time_controller_.AdvanceTime(TimeDelta::Millis(1)); |
| } |
| |
| TEST_F(VideoStreamDecoderImplTest, FailToDecodeFrame) { |
| video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build()); |
| ON_CALL(decoder_factory_.Vp8Decoder(), DecodeCall) |
| .WillByDefault(Return(WEBRTC_VIDEO_CODEC_ERROR)); |
| EXPECT_CALL(callbacks_, OnNonDecodableState); |
| time_controller_.AdvanceTime(TimeDelta::Millis(1)); |
| } |
| |
| TEST_F(VideoStreamDecoderImplTest, ChangeFramePayloadType) { |
| constexpr TimeDelta kFrameInterval = TimeDelta::Millis(1000 / 60); |
| video_stream_decoder_.OnFrame( |
| FrameBuilder().WithPayloadType(1).WithPictureId(0).Build()); |
| EXPECT_CALL(decoder_factory_.Vp8Decoder(), DecodeCall); |
| EXPECT_CALL(callbacks_, OnDecodedFrame); |
| time_controller_.AdvanceTime(kFrameInterval); |
| |
| video_stream_decoder_.OnFrame( |
| FrameBuilder().WithPayloadType(2).WithPictureId(1).Build()); |
| EXPECT_CALL(decoder_factory_.Av1Decoder(), DecodeCall); |
| EXPECT_CALL(callbacks_, OnDecodedFrame); |
| time_controller_.AdvanceTime(kFrameInterval); |
| } |
| |
| } // namespace |
| } // namespace webrtc |