summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /third_party/libwebrtc/video/video_receive_stream2_unittest.cc
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/video/video_receive_stream2_unittest.cc')
-rw-r--r--third_party/libwebrtc/video/video_receive_stream2_unittest.cc1219
1 files changed, 1219 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/video_receive_stream2_unittest.cc b/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
new file mode 100644
index 0000000000..458944aefa
--- /dev/null
+++ b/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
@@ -0,0 +1,1219 @@
+/*
+ * Copyright 2017 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_receive_stream2.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <deque>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <queue>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "absl/types/optional.h"
+#include "api/metronome/test/fake_metronome.h"
+#include "api/test/mock_video_decoder.h"
+#include "api/test/mock_video_decoder_factory.h"
+#include "api/test/time_controller.h"
+#include "api/units/frequency.h"
+#include "api/units/time_delta.h"
+#include "api/video/encoded_image.h"
+#include "api/video/recordable_encoded_frame.h"
+#include "api/video/test/video_frame_matchers.h"
+#include "api/video/video_frame.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "api/video_codecs/video_decoder.h"
+#include "call/rtp_stream_receiver_controller.h"
+#include "call/video_receive_stream.h"
+#include "common_video/test/utilities.h"
+#include "media/engine/fake_webrtc_call.h"
+#include "modules/pacing/packet_router.h"
+#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
+#include "modules/video_coding/encoded_frame.h"
+#include "rtc_base/logging.h"
+#include "system_wrappers/include/clock.h"
+#include "test/fake_decoder.h"
+#include "test/fake_encoded_frame.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/mock_transport.h"
+#include "test/rtcp_packet_parser.h"
+#include "test/time_controller/simulated_time_controller.h"
+#include "test/video_decoder_proxy_factory.h"
+#include "video/call_stats2.h"
+
+namespace webrtc {
+
+// Printing SdpVideoFormat for gmock argument matchers.
+void PrintTo(const SdpVideoFormat& value, std::ostream* os) {
+ *os << value.ToString();
+}
+
+void PrintTo(const RecordableEncodedFrame::EncodedResolution& value,
+ std::ostream* os) {
+ *os << value.width << "x" << value.height;
+}
+
+void PrintTo(const RecordableEncodedFrame& value, std::ostream* os) {
+ *os << "RecordableEncodedFrame(render_time=" << value.render_time()
+ << " resolution=" << ::testing::PrintToString(value.resolution()) << ")";
+}
+
+} // namespace webrtc
+
+namespace webrtc {
+
+namespace {
+
+using test::video_frame_matchers::NtpTimestamp;
+using test::video_frame_matchers::PacketInfos;
+using test::video_frame_matchers::Rotation;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::IsEmpty;
+using ::testing::Optional;
+using ::testing::Pointee;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::SizeIs;
+using ::testing::WithoutArgs;
+
+auto RenderedFrameWith(::testing::Matcher<VideoFrame> m) {
+ return Optional(m);
+}
+auto RenderedFrame() {
+ return RenderedFrameWith(_);
+}
+testing::Matcher<absl::optional<VideoFrame>> DidNotReceiveFrame() {
+ return Eq(absl::nullopt);
+}
+
+constexpr TimeDelta kDefaultTimeOut = TimeDelta::Millis(50);
+constexpr int kDefaultNumCpuCores = 2;
+
+constexpr Timestamp kStartTime = Timestamp::Millis(1'337'000);
+constexpr Frequency k30Fps = Frequency::Hertz(30);
+constexpr TimeDelta k30FpsDelay = 1 / k30Fps;
+constexpr Frequency kRtpTimestampHz = Frequency::KiloHertz(90);
+constexpr uint32_t k30FpsRtpTimestampDelta = kRtpTimestampHz / k30Fps;
+constexpr uint32_t kFirstRtpTimestamp = 90000;
+
+class FakeVideoRenderer : public rtc::VideoSinkInterface<VideoFrame> {
+ public:
+ explicit FakeVideoRenderer(TimeController* time_controller)
+ : time_controller_(time_controller) {}
+ ~FakeVideoRenderer() override = default;
+
+ void OnFrame(const VideoFrame& frame) override {
+ RTC_LOG(LS_VERBOSE) << "Received frame with timestamp="
+ << frame.timestamp();
+ if (!last_frame_.empty()) {
+ RTC_LOG(LS_INFO) << "Already had frame queue with timestamp="
+ << last_frame_.back().timestamp();
+ }
+ last_frame_.push_back(frame);
+ }
+
+ // If `advance_time`, then the clock will always advance by `timeout`.
+ absl::optional<VideoFrame> WaitForFrame(TimeDelta timeout,
+ bool advance_time = false) {
+ auto start = time_controller_->GetClock()->CurrentTime();
+ if (last_frame_.empty()) {
+ time_controller_->AdvanceTime(TimeDelta::Zero());
+ time_controller_->Wait([this] { return !last_frame_.empty(); }, timeout);
+ }
+ absl::optional<VideoFrame> ret;
+ if (!last_frame_.empty()) {
+ ret = last_frame_.front();
+ last_frame_.pop_front();
+ }
+ if (advance_time) {
+ time_controller_->AdvanceTime(
+ timeout - (time_controller_->GetClock()->CurrentTime() - start));
+ }
+ return ret;
+ }
+
+ private:
+ std::deque<VideoFrame> last_frame_;
+ TimeController* const time_controller_;
+};
+
+MATCHER_P2(MatchResolution, w, h, "") {
+ return arg.resolution().width == w && arg.resolution().height == h;
+}
+
+MATCHER_P(RtpTimestamp, timestamp, "") {
+ if (arg.timestamp() != timestamp) {
+ *result_listener->stream()
+ << "rtp timestamp was " << arg.timestamp() << " != " << timestamp;
+ return false;
+ }
+ return true;
+}
+
+// Rtp timestamp for in order frame at 30fps.
+uint32_t RtpTimestampForFrame(int id) {
+ return kFirstRtpTimestamp + id * k30FpsRtpTimestampDelta;
+}
+
+// Receive time for in order frame at 30fps.
+Timestamp ReceiveTimeForFrame(int id) {
+ return kStartTime + id * k30FpsDelay;
+}
+
+} // namespace
+
+class VideoReceiveStream2Test : public ::testing::TestWithParam<bool> {
+ public:
+ auto DefaultDecodeAction() {
+ return Invoke(&fake_decoder_, &test::FakeDecoder::Decode);
+ }
+
+ bool UseMetronome() const { return GetParam(); }
+
+ VideoReceiveStream2Test()
+ : time_controller_(kStartTime),
+ clock_(time_controller_.GetClock()),
+ config_(&mock_transport_, &mock_h264_decoder_factory_),
+ call_stats_(clock_, time_controller_.GetMainThread()),
+ fake_renderer_(&time_controller_),
+ fake_metronome_(TimeDelta::Millis(16)),
+ decode_sync_(clock_,
+ &fake_metronome_,
+ time_controller_.GetMainThread()),
+ h264_decoder_factory_(&mock_decoder_) {
+ // By default, mock decoder factory is backed by VideoDecoderProxyFactory.
+ ON_CALL(mock_h264_decoder_factory_, CreateVideoDecoder)
+ .WillByDefault(
+ Invoke(&h264_decoder_factory_,
+ &test::VideoDecoderProxyFactory::CreateVideoDecoder));
+
+ // By default, mock decode will wrap the fake decoder.
+ ON_CALL(mock_decoder_, Configure)
+ .WillByDefault(Invoke(&fake_decoder_, &test::FakeDecoder::Configure));
+ ON_CALL(mock_decoder_, Decode).WillByDefault(DefaultDecodeAction());
+ ON_CALL(mock_decoder_, RegisterDecodeCompleteCallback)
+ .WillByDefault(
+ Invoke(&fake_decoder_,
+ &test::FakeDecoder::RegisterDecodeCompleteCallback));
+ ON_CALL(mock_decoder_, Release)
+ .WillByDefault(Invoke(&fake_decoder_, &test::FakeDecoder::Release));
+ ON_CALL(mock_transport_, SendRtcp)
+ .WillByDefault(
+ Invoke(&rtcp_packet_parser_, &test::RtcpPacketParser::Parse));
+ }
+
+ ~VideoReceiveStream2Test() override {
+ if (video_receive_stream_) {
+ video_receive_stream_->Stop();
+ video_receive_stream_->UnregisterFromTransport();
+ }
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+ }
+
+ void SetUp() override {
+ config_.rtp.remote_ssrc = 1111;
+ config_.rtp.local_ssrc = 2222;
+ config_.renderer = &fake_renderer_;
+ VideoReceiveStreamInterface::Decoder h264_decoder;
+ h264_decoder.payload_type = 99;
+ h264_decoder.video_format = SdpVideoFormat("H264");
+ h264_decoder.video_format.parameters.insert(
+ {"sprop-parameter-sets", "Z0IACpZTBYmI,aMljiA=="});
+ VideoReceiveStreamInterface::Decoder h265_decoder;
+ h265_decoder.payload_type = 100;
+ h265_decoder.video_format = SdpVideoFormat("H265");
+
+ config_.decoders = {h265_decoder, h264_decoder};
+
+ RecreateReceiveStream();
+ }
+
+ void RecreateReceiveStream(
+ absl::optional<VideoReceiveStreamInterface::RecordingState> state =
+ absl::nullopt) {
+ if (video_receive_stream_) {
+ video_receive_stream_->UnregisterFromTransport();
+ video_receive_stream_ = nullptr;
+ }
+ timing_ = new VCMTiming(clock_, fake_call_.trials());
+ video_receive_stream_ =
+ std::make_unique<webrtc::internal::VideoReceiveStream2>(
+ time_controller_.GetTaskQueueFactory(), &fake_call_,
+ kDefaultNumCpuCores, &packet_router_, config_.Copy(), &call_stats_,
+ clock_, absl::WrapUnique(timing_), &nack_periodic_processor_,
+ UseMetronome() ? &decode_sync_ : nullptr, nullptr);
+ video_receive_stream_->RegisterWithTransport(
+ &rtp_stream_receiver_controller_);
+ if (state)
+ video_receive_stream_->SetAndGetRecordingState(std::move(*state), false);
+ }
+
+ protected:
+ GlobalSimulatedTimeController time_controller_;
+ Clock* const clock_;
+ NackPeriodicProcessor nack_periodic_processor_;
+ testing::NiceMock<MockVideoDecoderFactory> mock_h264_decoder_factory_;
+ VideoReceiveStreamInterface::Config config_;
+ internal::CallStats call_stats_;
+ testing::NiceMock<MockVideoDecoder> mock_decoder_;
+ FakeVideoRenderer fake_renderer_;
+ cricket::FakeCall fake_call_;
+ MockTransport mock_transport_;
+ test::RtcpPacketParser rtcp_packet_parser_;
+ PacketRouter packet_router_;
+ RtpStreamReceiverController rtp_stream_receiver_controller_;
+ std::unique_ptr<webrtc::internal::VideoReceiveStream2> video_receive_stream_;
+ VCMTiming* timing_;
+ test::FakeMetronome fake_metronome_;
+ DecodeSynchronizer decode_sync_;
+
+ private:
+ test::VideoDecoderProxyFactory h264_decoder_factory_;
+ test::FakeDecoder fake_decoder_;
+};
+
+TEST_P(VideoReceiveStream2Test, CreateFrameFromH264FmtpSpropAndIdr) {
+ constexpr uint8_t idr_nalu[] = {0x05, 0xFF, 0xFF, 0xFF};
+ RtpPacketToSend rtppacket(nullptr);
+ uint8_t* payload = rtppacket.AllocatePayload(sizeof(idr_nalu));
+ memcpy(payload, idr_nalu, sizeof(idr_nalu));
+ rtppacket.SetMarker(true);
+ rtppacket.SetSsrc(1111);
+ rtppacket.SetPayloadType(99);
+ rtppacket.SetSequenceNumber(1);
+ rtppacket.SetTimestamp(0);
+ EXPECT_CALL(mock_decoder_, RegisterDecodeCompleteCallback(_));
+ video_receive_stream_->Start();
+ EXPECT_CALL(mock_decoder_, Decode(_, false, _));
+ RtpPacketReceived parsed_packet;
+ ASSERT_TRUE(parsed_packet.Parse(rtppacket.data(), rtppacket.size()));
+ rtp_stream_receiver_controller_.OnRtpPacket(parsed_packet);
+ EXPECT_CALL(mock_decoder_, Release());
+
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+}
+
+TEST_P(VideoReceiveStream2Test, PlayoutDelay) {
+ const VideoPlayoutDelay kPlayoutDelayMs = {123, 321};
+ std::unique_ptr<test::FakeEncodedFrame> test_frame =
+ test::FakeFrameBuilder().Id(0).AsLast().Build();
+ test_frame->SetPlayoutDelay(kPlayoutDelayMs);
+
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+ auto timings = timing_->GetTimings();
+ EXPECT_EQ(kPlayoutDelayMs.min_ms, timings.min_playout_delay.ms());
+ EXPECT_EQ(kPlayoutDelayMs.max_ms, timings.max_playout_delay.ms());
+
+ // Check that the biggest minimum delay is chosen.
+ video_receive_stream_->SetMinimumPlayoutDelay(400);
+ timings = timing_->GetTimings();
+ EXPECT_EQ(400, timings.min_playout_delay.ms());
+
+ // Check base minimum delay validation.
+ EXPECT_FALSE(video_receive_stream_->SetBaseMinimumPlayoutDelayMs(12345));
+ EXPECT_FALSE(video_receive_stream_->SetBaseMinimumPlayoutDelayMs(-1));
+ EXPECT_TRUE(video_receive_stream_->SetBaseMinimumPlayoutDelayMs(500));
+ timings = timing_->GetTimings();
+ EXPECT_EQ(500, timings.min_playout_delay.ms());
+
+ // Check that intermidiate values are remembered and the biggest remembered
+ // is chosen.
+ video_receive_stream_->SetBaseMinimumPlayoutDelayMs(0);
+ timings = timing_->GetTimings();
+ EXPECT_EQ(400, timings.min_playout_delay.ms());
+
+ video_receive_stream_->SetMinimumPlayoutDelay(0);
+ timings = timing_->GetTimings();
+ EXPECT_EQ(123, timings.min_playout_delay.ms());
+}
+
+TEST_P(VideoReceiveStream2Test, PlayoutDelayPreservesDefaultMaxValue) {
+ const TimeDelta default_max_playout_latency =
+ timing_->GetTimings().max_playout_delay;
+ const VideoPlayoutDelay kPlayoutDelayMs = {123, -1};
+
+ std::unique_ptr<test::FakeEncodedFrame> test_frame =
+ test::FakeFrameBuilder().Id(0).AsLast().Build();
+ test_frame->SetPlayoutDelay(kPlayoutDelayMs);
+
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+
+ // Ensure that -1 preserves default maximum value from `timing_`.
+ auto timings = timing_->GetTimings();
+ EXPECT_EQ(kPlayoutDelayMs.min_ms, timings.min_playout_delay.ms());
+ EXPECT_NE(kPlayoutDelayMs.max_ms, timings.max_playout_delay.ms());
+ EXPECT_EQ(default_max_playout_latency, timings.max_playout_delay);
+}
+
+TEST_P(VideoReceiveStream2Test, PlayoutDelayPreservesDefaultMinValue) {
+ const TimeDelta default_min_playout_latency =
+ timing_->GetTimings().min_playout_delay;
+ const VideoPlayoutDelay kPlayoutDelayMs = {-1, 321};
+
+ std::unique_ptr<test::FakeEncodedFrame> test_frame =
+ test::FakeFrameBuilder().Id(0).AsLast().Build();
+ test_frame->SetPlayoutDelay(kPlayoutDelayMs);
+
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+
+ // Ensure that -1 preserves default minimum value from `timing_`.
+ auto timings = timing_->GetTimings();
+ EXPECT_NE(kPlayoutDelayMs.min_ms, timings.min_playout_delay.ms());
+ EXPECT_EQ(kPlayoutDelayMs.max_ms, timings.max_playout_delay.ms());
+ EXPECT_EQ(default_min_playout_latency, timings.min_playout_delay);
+}
+
+TEST_P(VideoReceiveStream2Test, RenderParametersSetToDefaultValues) {
+ // Default render parameters.
+ const VideoFrame::RenderParameters kDefaultRenderParameters;
+ // Default with no playout delay set.
+ std::unique_ptr<test::FakeEncodedFrame> test_frame0 =
+ test::FakeFrameBuilder().Id(0).AsLast().Build();
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame0));
+ EXPECT_EQ(timing_->RenderParameters(), kDefaultRenderParameters);
+}
+
+TEST_P(VideoReceiveStream2Test, UseLowLatencyRenderingSetFromPlayoutDelay) {
+ // use_low_latency_rendering set if playout delay set to min=0, max<=500 ms.
+ std::unique_ptr<test::FakeEncodedFrame> test_frame0 =
+ test::FakeFrameBuilder().Id(0).AsLast().Build();
+ test_frame0->SetPlayoutDelay({/*min_ms=*/0, /*max_ms=*/0});
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame0));
+ EXPECT_TRUE(timing_->RenderParameters().use_low_latency_rendering);
+
+ std::unique_ptr<test::FakeEncodedFrame> test_frame1 =
+ test::FakeFrameBuilder().Id(1).AsLast().Build();
+ test_frame1->SetPlayoutDelay({/*min_ms=*/0, /*max_ms=*/500});
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame1));
+ EXPECT_TRUE(timing_->RenderParameters().use_low_latency_rendering);
+}
+
+TEST_P(VideoReceiveStream2Test, MaxCompositionDelaySetFromMaxPlayoutDelay) {
+ // The max composition delay is dependent on the number of frames in the
+ // pre-decode queue. It's therefore important to advance the time as the test
+ // runs to get the correct expectations of max_composition_delay_in_frames.
+ video_receive_stream_->Start();
+ // Max composition delay not set if no playout delay is set.
+ std::unique_ptr<test::FakeEncodedFrame> test_frame0 =
+ test::FakeFrameBuilder()
+ .Id(0)
+ .Time(RtpTimestampForFrame(0))
+ .ReceivedTime(ReceiveTimeForFrame(0))
+ .AsLast()
+ .Build();
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame0));
+ EXPECT_THAT(timing_->RenderParameters().max_composition_delay_in_frames,
+ Eq(absl::nullopt));
+ time_controller_.AdvanceTime(k30FpsDelay);
+
+ // Max composition delay not set for playout delay 0,0.
+ std::unique_ptr<test::FakeEncodedFrame> test_frame1 =
+ test::FakeFrameBuilder()
+ .Id(1)
+ .Time(RtpTimestampForFrame(1))
+ .ReceivedTime(ReceiveTimeForFrame(1))
+ .AsLast()
+ .Build();
+ test_frame1->SetPlayoutDelay({0, 0});
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame1));
+ EXPECT_THAT(timing_->RenderParameters().max_composition_delay_in_frames,
+ Eq(absl::nullopt));
+ time_controller_.AdvanceTime(k30FpsDelay);
+
+ // Max composition delay not set for playout delay X,Y, where X,Y>0.
+ std::unique_ptr<test::FakeEncodedFrame> test_frame2 =
+ test::FakeFrameBuilder()
+ .Id(2)
+ .Time(RtpTimestampForFrame(2))
+ .ReceivedTime(ReceiveTimeForFrame(2))
+ .AsLast()
+ .Build();
+ test_frame2->SetPlayoutDelay({10, 30});
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame2));
+ EXPECT_THAT(timing_->RenderParameters().max_composition_delay_in_frames,
+ Eq(absl::nullopt));
+
+ time_controller_.AdvanceTime(k30FpsDelay);
+
+ // Max composition delay set if playout delay X,Y, where X=0,Y>0.
+ const int kExpectedMaxCompositionDelayInFrames = 3; // ~50 ms at 60 fps.
+ std::unique_ptr<test::FakeEncodedFrame> test_frame3 =
+ test::FakeFrameBuilder()
+ .Id(3)
+ .Time(RtpTimestampForFrame(3))
+ .ReceivedTime(ReceiveTimeForFrame(3))
+ .AsLast()
+ .Build();
+ test_frame3->SetPlayoutDelay({0, 50});
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame3));
+ EXPECT_THAT(timing_->RenderParameters().max_composition_delay_in_frames,
+ Optional(kExpectedMaxCompositionDelayInFrames));
+}
+
+TEST_P(VideoReceiveStream2Test, LazyDecoderCreation) {
+ constexpr uint8_t idr_nalu[] = {0x05, 0xFF, 0xFF, 0xFF};
+ RtpPacketToSend rtppacket(nullptr);
+ uint8_t* payload = rtppacket.AllocatePayload(sizeof(idr_nalu));
+ memcpy(payload, idr_nalu, sizeof(idr_nalu));
+ rtppacket.SetMarker(true);
+ rtppacket.SetSsrc(1111);
+ // H265 payload type.
+ rtppacket.SetPayloadType(99);
+ rtppacket.SetSequenceNumber(1);
+ rtppacket.SetTimestamp(0);
+
+ // No decoders are created by default.
+ EXPECT_CALL(mock_h264_decoder_factory_, CreateVideoDecoder(_)).Times(0);
+ video_receive_stream_->Start();
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+
+ EXPECT_TRUE(
+ testing::Mock::VerifyAndClearExpectations(&mock_h264_decoder_factory_));
+ // Verify that the decoder is created when we receive payload data and tries
+ // to decode a frame.
+ EXPECT_CALL(
+ mock_h264_decoder_factory_,
+ CreateVideoDecoder(Field(&SdpVideoFormat::name, testing::Eq("H264"))));
+ EXPECT_CALL(mock_decoder_, Configure);
+ EXPECT_CALL(mock_decoder_, RegisterDecodeCompleteCallback);
+ EXPECT_CALL(mock_decoder_, Decode);
+ RtpPacketReceived parsed_packet;
+ ASSERT_TRUE(parsed_packet.Parse(rtppacket.data(), rtppacket.size()));
+ rtp_stream_receiver_controller_.OnRtpPacket(parsed_packet);
+ EXPECT_CALL(mock_decoder_, Release);
+
+ // Make sure the decoder thread had a chance to run.
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+}
+
+TEST_P(VideoReceiveStream2Test, PassesNtpTime) {
+ const Timestamp kNtpTimestamp = Timestamp::Millis(12345);
+ std::unique_ptr<test::FakeEncodedFrame> test_frame =
+ test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .NtpTime(kNtpTimestamp)
+ .AsLast()
+ .Build();
+
+ video_receive_stream_->Start();
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut),
+ RenderedFrameWith(NtpTimestamp(kNtpTimestamp)));
+}
+
+TEST_P(VideoReceiveStream2Test, PassesRotation) {
+ const webrtc::VideoRotation kRotation = webrtc::kVideoRotation_180;
+ std::unique_ptr<test::FakeEncodedFrame> test_frame = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Rotation(kRotation)
+ .AsLast()
+ .Build();
+
+ video_receive_stream_->Start();
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut),
+ RenderedFrameWith(Rotation(kRotation)));
+}
+
+TEST_P(VideoReceiveStream2Test, PassesPacketInfos) {
+ RtpPacketInfos packet_infos = CreatePacketInfos(3);
+ auto test_frame = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .PacketInfos(packet_infos)
+ .AsLast()
+ .Build();
+
+ video_receive_stream_->Start();
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut),
+ RenderedFrameWith(PacketInfos(ElementsAreArray(packet_infos))));
+}
+
+TEST_P(VideoReceiveStream2Test, RenderedFrameUpdatesGetSources) {
+ constexpr uint32_t kSsrc = 1111;
+ constexpr uint32_t kCsrc = 9001;
+ constexpr uint32_t kRtpTimestamp = 12345;
+
+ // Prepare one video frame with per-packet information.
+ auto test_frame =
+ test::FakeFrameBuilder().Id(0).PayloadType(99).AsLast().Build();
+ RtpPacketInfos packet_infos;
+ {
+ RtpPacketInfos::vector_type infos;
+
+ RtpPacketInfo info;
+ info.set_ssrc(kSsrc);
+ info.set_csrcs({kCsrc});
+ info.set_rtp_timestamp(kRtpTimestamp);
+
+ info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(5000));
+ infos.push_back(info);
+
+ info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(3000));
+ infos.push_back(info);
+
+ info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(2000));
+ infos.push_back(info);
+
+ info.set_receive_time(clock_->CurrentTime() - TimeDelta::Millis(1000));
+ infos.push_back(info);
+
+ packet_infos = RtpPacketInfos(std::move(infos));
+ }
+ test_frame->SetPacketInfos(packet_infos);
+
+ // Start receive stream.
+ video_receive_stream_->Start();
+ EXPECT_THAT(video_receive_stream_->GetSources(), IsEmpty());
+
+ // Render one video frame.
+ int64_t timestamp_ms_min = clock_->TimeInMilliseconds();
+ video_receive_stream_->OnCompleteFrame(std::move(test_frame));
+ // Verify that the per-packet information is passed to the renderer.
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut),
+ RenderedFrameWith(PacketInfos(ElementsAreArray(packet_infos))));
+ int64_t timestamp_ms_max = clock_->TimeInMilliseconds();
+
+ // Verify that the per-packet information also updates `GetSources()`.
+ std::vector<RtpSource> sources = video_receive_stream_->GetSources();
+ ASSERT_THAT(sources, SizeIs(2));
+ {
+ auto it = std::find_if(sources.begin(), sources.end(),
+ [](const RtpSource& source) {
+ return source.source_type() == RtpSourceType::SSRC;
+ });
+ ASSERT_NE(it, sources.end());
+
+ EXPECT_EQ(it->source_id(), kSsrc);
+ EXPECT_EQ(it->source_type(), RtpSourceType::SSRC);
+ EXPECT_EQ(it->rtp_timestamp(), kRtpTimestamp);
+ EXPECT_GE(it->timestamp_ms(), timestamp_ms_min);
+ EXPECT_LE(it->timestamp_ms(), timestamp_ms_max);
+ }
+ {
+ auto it = std::find_if(sources.begin(), sources.end(),
+ [](const RtpSource& source) {
+ return source.source_type() == RtpSourceType::CSRC;
+ });
+ ASSERT_NE(it, sources.end());
+
+ EXPECT_EQ(it->source_id(), kCsrc);
+ EXPECT_EQ(it->source_type(), RtpSourceType::CSRC);
+ EXPECT_EQ(it->rtp_timestamp(), kRtpTimestamp);
+ EXPECT_GE(it->timestamp_ms(), timestamp_ms_min);
+ EXPECT_LE(it->timestamp_ms(), timestamp_ms_max);
+ }
+}
+
+std::unique_ptr<test::FakeEncodedFrame> MakeFrameWithResolution(
+ VideoFrameType frame_type,
+ int picture_id,
+ int width,
+ int height) {
+ auto frame =
+ test::FakeFrameBuilder().Id(picture_id).PayloadType(99).AsLast().Build();
+ frame->SetFrameType(frame_type);
+ frame->_encodedWidth = width;
+ frame->_encodedHeight = height;
+ return frame;
+}
+
+std::unique_ptr<test::FakeEncodedFrame> MakeFrame(VideoFrameType frame_type,
+ int picture_id) {
+ return MakeFrameWithResolution(frame_type, picture_id, 320, 240);
+}
+
+TEST_P(VideoReceiveStream2Test, PassesFrameWhenEncodedFramesCallbackSet) {
+ testing::MockFunction<void(const RecordableEncodedFrame&)> callback;
+ video_receive_stream_->Start();
+ EXPECT_CALL(callback, Call);
+ video_receive_stream_->SetAndGetRecordingState(
+ VideoReceiveStreamInterface::RecordingState(callback.AsStdFunction()),
+ true);
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrame(VideoFrameType::kVideoFrameKey, 0));
+ EXPECT_TRUE(fake_renderer_.WaitForFrame(kDefaultTimeOut));
+
+ EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(1));
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, MovesEncodedFrameDispatchStateWhenReCreating) {
+ testing::MockFunction<void(const RecordableEncodedFrame&)> callback;
+ video_receive_stream_->Start();
+ // Expect a key frame request over RTCP.
+ video_receive_stream_->SetAndGetRecordingState(
+ VideoReceiveStreamInterface::RecordingState(callback.AsStdFunction()),
+ true);
+ video_receive_stream_->Stop();
+ VideoReceiveStreamInterface::RecordingState old_state =
+ video_receive_stream_->SetAndGetRecordingState(
+ VideoReceiveStreamInterface::RecordingState(), false);
+ RecreateReceiveStream(std::move(old_state));
+
+ EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(1));
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, RequestsKeyFramesUntilKeyFrameReceived) {
+ // Recreate receive stream with shorter delay to test rtx.
+ TimeDelta rtx_delay = TimeDelta::Millis(50);
+ config_.rtp.nack.rtp_history_ms = rtx_delay.ms();
+ auto tick = rtx_delay / 2;
+ RecreateReceiveStream();
+ video_receive_stream_->Start();
+
+ video_receive_stream_->GenerateKeyFrame();
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrame(VideoFrameType::kVideoFrameDelta, 0));
+ fake_renderer_.WaitForFrame(kDefaultTimeOut);
+ time_controller_.AdvanceTime(tick);
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrame(VideoFrameType::kVideoFrameDelta, 1));
+ fake_renderer_.WaitForFrame(kDefaultTimeOut);
+ time_controller_.AdvanceTime(TimeDelta::Zero());
+ testing::Mock::VerifyAndClearExpectations(&mock_transport_);
+
+ EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(1));
+
+ // T+keyframetimeout: still no key frame received, expect key frame request
+ // sent again.
+ time_controller_.AdvanceTime(tick);
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrame(VideoFrameType::kVideoFrameDelta, 2));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+ testing::Mock::VerifyAndClearExpectations(&mock_transport_);
+
+ EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(2));
+
+ // T+keyframetimeout: now send a key frame - we should not observe new key
+ // frame requests after this.
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrame(VideoFrameType::kVideoFrameKey, 3));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+ time_controller_.AdvanceTime(2 * tick);
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrame(VideoFrameType::kVideoFrameDelta, 4));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+
+ EXPECT_THAT(rtcp_packet_parser_.pli()->num_packets(), Eq(2));
+}
+
+TEST_P(VideoReceiveStream2Test,
+ DispatchesEncodedFrameSequenceStartingWithKeyframeWithoutResolution) {
+ video_receive_stream_->Start();
+ testing::MockFunction<void(const RecordableEncodedFrame&)> callback;
+ video_receive_stream_->SetAndGetRecordingState(
+ VideoReceiveStreamInterface::RecordingState(callback.AsStdFunction()),
+ /*generate_key_frame=*/false);
+
+ InSequence s;
+ EXPECT_CALL(callback,
+ Call(MatchResolution(test::FakeDecoder::kDefaultWidth,
+ test::FakeDecoder::kDefaultHeight)));
+ EXPECT_CALL(callback, Call);
+
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrameWithResolution(VideoFrameType::kVideoFrameKey, 0, 0, 0));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrameWithResolution(VideoFrameType::kVideoFrameDelta, 1, 0, 0));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test,
+ DispatchesEncodedFrameSequenceStartingWithKeyframeWithResolution) {
+ video_receive_stream_->Start();
+ testing::MockFunction<void(const RecordableEncodedFrame&)> callback;
+ video_receive_stream_->SetAndGetRecordingState(
+ VideoReceiveStreamInterface::RecordingState(callback.AsStdFunction()),
+ /*generate_key_frame=*/false);
+
+ InSequence s;
+ EXPECT_CALL(callback, Call(MatchResolution(1080u, 720u)));
+ EXPECT_CALL(callback, Call);
+
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrameWithResolution(VideoFrameType::kVideoFrameKey, 0, 1080, 720));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+ video_receive_stream_->OnCompleteFrame(
+ MakeFrameWithResolution(VideoFrameType::kVideoFrameDelta, 1, 0, 0));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(kDefaultTimeOut), RenderedFrame());
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, DependantFramesAreScheduled) {
+ video_receive_stream_->Start();
+
+ auto key_frame = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(kFirstRtpTimestamp)
+ .ReceivedTime(kStartTime)
+ .AsLast()
+ .Build();
+ auto delta_frame = test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(1))
+ .ReceivedTime(ReceiveTimeForFrame(1))
+ .Refs({0})
+ .AsLast()
+ .Build();
+
+ // Expect frames are decoded in order.
+ InSequence seq;
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(kFirstRtpTimestamp), _, _));
+ EXPECT_CALL(mock_decoder_, Decode(test::RtpTimestamp(kFirstRtpTimestamp +
+ k30FpsRtpTimestampDelta),
+ _, _))
+ .Times(1);
+ video_receive_stream_->OnCompleteFrame(std::move(key_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
+
+ time_controller_.AdvanceTime(k30FpsDelay);
+ video_receive_stream_->OnCompleteFrame(std::move(delta_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, FramesScheduledInOrder) {
+ video_receive_stream_->Start();
+
+ auto key_frame = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(kFirstRtpTimestamp)
+ .AsLast()
+ .Build();
+ auto delta_frame1 = test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(1))
+ .Refs({0})
+ .AsLast()
+ .Build();
+ auto delta_frame2 = test::FakeFrameBuilder()
+ .Id(2)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(2))
+ .Refs({1})
+ .AsLast()
+ .Build();
+
+ // Expect frames are decoded in order despite delta_frame1 arriving first.
+ InSequence seq;
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(kFirstRtpTimestamp), _, _))
+ .Times(1);
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(RtpTimestampForFrame(1)), _, _))
+ .Times(1);
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(RtpTimestampForFrame(2)), _, _))
+ .Times(1);
+ key_frame->SetReceivedTime(clock_->CurrentTime().ms());
+ video_receive_stream_->OnCompleteFrame(std::move(key_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
+
+ delta_frame2->SetReceivedTime(clock_->CurrentTime().ms());
+ video_receive_stream_->OnCompleteFrame(std::move(delta_frame2));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), DidNotReceiveFrame());
+ // `delta_frame1` arrives late.
+ delta_frame1->SetReceivedTime(clock_->CurrentTime().ms());
+ video_receive_stream_->OnCompleteFrame(std::move(delta_frame1));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay * 2), RenderedFrame());
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, WaitsforAllSpatialLayers) {
+ video_receive_stream_->Start();
+ auto sl0 = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(kFirstRtpTimestamp)
+ .ReceivedTime(kStartTime)
+ .Build();
+ auto sl1 = test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .ReceivedTime(kStartTime)
+ .Time(kFirstRtpTimestamp)
+ .Refs({0})
+ .Build();
+ auto sl2 = test::FakeFrameBuilder()
+ .Id(2)
+ .PayloadType(99)
+ .ReceivedTime(kStartTime)
+ .Time(kFirstRtpTimestamp)
+ .Refs({0, 1})
+ .AsLast()
+ .Build();
+
+ // No decodes should be called until `sl2` is received.
+ EXPECT_CALL(mock_decoder_, Decode).Times(0);
+ sl0->SetReceivedTime(clock_->CurrentTime().ms());
+ video_receive_stream_->OnCompleteFrame(std::move(sl0));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()),
+ DidNotReceiveFrame());
+ video_receive_stream_->OnCompleteFrame(std::move(sl1));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()),
+ DidNotReceiveFrame());
+ // When `sl2` arrives decode should happen.
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(kFirstRtpTimestamp), _, _))
+ .Times(1);
+ video_receive_stream_->OnCompleteFrame(std::move(sl2));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, FramesFastForwardOnSystemHalt) {
+ video_receive_stream_->Start();
+
+ // The frame structure looks like this,
+ // F1
+ // /
+ // F0 --> F2
+ //
+ // In this case we will have a system halt simulated. By the time the system
+ // resumes, F1 will be old and so F2 should be decoded.
+ auto key_frame = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(kFirstRtpTimestamp)
+ .AsLast()
+ .Build();
+ auto ffwd_frame = test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(1))
+ .Refs({0})
+ .AsLast()
+ .Build();
+ auto rendered_frame = test::FakeFrameBuilder()
+ .Id(2)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(2))
+ .Refs({0})
+ .AsLast()
+ .Build();
+ InSequence seq;
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(kFirstRtpTimestamp), _, _))
+ .WillOnce(testing::DoAll(Invoke([&] {
+ // System halt will be simulated in the decode.
+ time_controller_.AdvanceTime(k30FpsDelay * 2);
+ }),
+ DefaultDecodeAction()));
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(RtpTimestampForFrame(2)), _, _));
+ video_receive_stream_->OnCompleteFrame(std::move(key_frame));
+ video_receive_stream_->OnCompleteFrame(std::move(ffwd_frame));
+ video_receive_stream_->OnCompleteFrame(std::move(rendered_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()),
+ RenderedFrameWith(RtpTimestamp(RtpTimestampForFrame(0))));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()),
+ RenderedFrameWith(RtpTimestamp(RtpTimestampForFrame(2))));
+
+ // Check stats show correct dropped frames.
+ auto stats = video_receive_stream_->GetStats();
+ EXPECT_EQ(stats.frames_dropped, 1u);
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, BetterFrameInsertedWhileWaitingToDecodeFrame) {
+ video_receive_stream_->Start();
+
+ auto key_frame = test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(kFirstRtpTimestamp)
+ .ReceivedTime(ReceiveTimeForFrame(0))
+ .AsLast()
+ .Build();
+ auto f1 = test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(1))
+ .ReceivedTime(ReceiveTimeForFrame(1))
+ .Refs({0})
+ .AsLast()
+ .Build();
+ auto f2 = test::FakeFrameBuilder()
+ .Id(2)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(2))
+ .ReceivedTime(ReceiveTimeForFrame(2))
+ .Refs({0})
+ .AsLast()
+ .Build();
+
+ video_receive_stream_->OnCompleteFrame(std::move(key_frame));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
+
+ InSequence seq;
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(RtpTimestampForFrame(1)), _, _))
+ .Times(1);
+ EXPECT_CALL(mock_decoder_,
+ Decode(test::RtpTimestamp(RtpTimestampForFrame(2)), _, _))
+ .Times(1);
+ // Simulate f1 arriving after f2 but before f2 is decoded.
+ video_receive_stream_->OnCompleteFrame(std::move(f2));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), DidNotReceiveFrame());
+ video_receive_stream_->OnCompleteFrame(std::move(f1));
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
+
+ video_receive_stream_->Stop();
+}
+
+// Note: This test takes a long time (~10s) to run if the fake metronome is
+// active. Since the test needs to wait for the timestamp to rollover, it has a
+// fake delay of around 6.5 hours. Even though time is simulated, this will be
+// around 1,500,000 metronome tick invocations.
+TEST_P(VideoReceiveStream2Test, RtpTimestampWrapAround) {
+ EXPECT_CALL(mock_transport_, SendRtcp).Times(AnyNumber());
+ video_receive_stream_->Start();
+
+ constexpr uint32_t kBaseRtp = std::numeric_limits<uint32_t>::max() / 2;
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(kBaseRtp)
+ .ReceivedTime(clock_->CurrentTime())
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
+ time_controller_.AdvanceTime(k30FpsDelay);
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .Time(kBaseRtp + k30FpsRtpTimestampDelta)
+ .ReceivedTime(clock_->CurrentTime())
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay), RenderedFrame());
+
+ // Pause stream so that RTP timestamp wraps around.
+ constexpr uint32_t kLastRtp = kBaseRtp + k30FpsRtpTimestampDelta;
+ constexpr uint32_t kWrapAroundRtp =
+ kLastRtp + std::numeric_limits<uint32_t>::max() / 2 + 1;
+ // Pause for corresponding delay such that RTP timestamp would increase this
+ // much at 30fps.
+ constexpr TimeDelta kWrapAroundDelay =
+ (std::numeric_limits<uint32_t>::max() / 2 + 1) / kRtpTimestampHz;
+
+ time_controller_.AdvanceTime(kWrapAroundDelay);
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(2)
+ .PayloadType(99)
+ .Time(kWrapAroundRtp)
+ .ReceivedTime(clock_->CurrentTime())
+ .AsLast()
+ .Build());
+ EXPECT_CALL(mock_decoder_, Decode(test::RtpTimestamp(kWrapAroundRtp), _, _))
+ .Times(1);
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Zero()), RenderedFrame());
+
+ video_receive_stream_->Stop();
+}
+
+// If a frame was lost causing the stream to become temporarily non-decodable
+// and the sender reduces their framerate during this time, the video stream
+// should start decoding at the new framerate. However, if the connection is
+// poor, a keyframe will take a long time to send. If the timing of the incoming
+// frames was not kept up to date with the new framerate while the stream was
+// decodable, this late frame will have a large delay as the rtp timestamp of
+// this keyframe will look like the frame arrived early if the frame-rate was
+// not updated.
+TEST_P(VideoReceiveStream2Test, PoorConnectionWithFpsChangeDuringLostFrame) {
+ video_receive_stream_->Start();
+
+ constexpr Frequency k15Fps = Frequency::Hertz(15);
+ constexpr TimeDelta k15FpsDelay = 1 / k15Fps;
+ constexpr uint32_t k15FpsRtpTimestampDelta = kRtpTimestampHz / k15Fps;
+
+ // Initial keyframe and frames at 30fps.
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(0))
+ .ReceivedTime(ReceiveTimeForFrame(0))
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay, /*advance_time=*/true),
+ RenderedFrameWith(RtpTimestamp(RtpTimestampForFrame(0))));
+
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(1)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(1))
+ .ReceivedTime(ReceiveTimeForFrame(1))
+ .Refs({0})
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay, /*advance_time=*/true),
+ RenderedFrameWith(RtpTimestamp(RtpTimestampForFrame(1))));
+
+ // Simulate lost frame 2, followed by 2 second of frames at 30fps, followed by
+ // 2 second of frames at 15 fps, and then a keyframe.
+ time_controller_.AdvanceTime(k30FpsDelay);
+
+ Timestamp send_30fps_end_time = clock_->CurrentTime() + TimeDelta::Seconds(2);
+ int id = 3;
+ EXPECT_CALL(mock_transport_, SendRtcp).Times(AnyNumber());
+ while (clock_->CurrentTime() < send_30fps_end_time) {
+ ++id;
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(id)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(id))
+ .ReceivedTime(ReceiveTimeForFrame(id))
+ .Refs({id - 1})
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay, /*advance_time=*/true),
+ Eq(absl::nullopt));
+ }
+ uint32_t current_rtp = RtpTimestampForFrame(id);
+ Timestamp send_15fps_end_time = clock_->CurrentTime() + TimeDelta::Seconds(2);
+ while (clock_->CurrentTime() < send_15fps_end_time) {
+ ++id;
+ current_rtp += k15FpsRtpTimestampDelta;
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(id)
+ .PayloadType(99)
+ .Time(current_rtp)
+ .ReceivedTime(clock_->CurrentTime())
+ .Refs({id - 1})
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k15FpsDelay, /*advance_time=*/true),
+ Eq(absl::nullopt));
+ }
+
+ ++id;
+ current_rtp += k15FpsRtpTimestampDelta;
+ // Insert keyframe which will recover the stream. However, on a poor
+ // connection the keyframe will take significant time to send.
+ constexpr TimeDelta kKeyframeDelay = TimeDelta::Millis(200);
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(id)
+ .PayloadType(99)
+ .Time(current_rtp)
+ .ReceivedTime(clock_->CurrentTime() + kKeyframeDelay)
+ .AsLast()
+ .Build());
+ // If the framerate was not updated to be 15fps from the frames that arrived
+ // previously, this will fail, as the delay will be longer.
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k15FpsDelay, /*advance_time=*/true),
+ RenderedFrameWith(RtpTimestamp(current_rtp)));
+
+ video_receive_stream_->Stop();
+}
+
+TEST_P(VideoReceiveStream2Test, StreamShouldNotTimeoutWhileWaitingForFrame) {
+ // Disable smoothing since this makes it hard to test frame timing.
+ config_.enable_prerenderer_smoothing = false;
+ RecreateReceiveStream();
+
+ video_receive_stream_->Start();
+ EXPECT_CALL(mock_transport_, SendRtcp).Times(AnyNumber());
+
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(0)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(0))
+ .ReceivedTime(ReceiveTimeForFrame(0))
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay, /*advance_time=*/true),
+ RenderedFrameWith(RtpTimestamp(RtpTimestampForFrame(0))));
+
+ for (int id = 1; id < 30; ++id) {
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(id)
+ .PayloadType(99)
+ .Time(RtpTimestampForFrame(id))
+ .ReceivedTime(ReceiveTimeForFrame(id))
+ .Refs({0})
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(k30FpsDelay, /*advance_time=*/true),
+ RenderedFrameWith(RtpTimestamp(RtpTimestampForFrame(id))));
+ }
+
+ // Simulate a pause in the stream, followed by a decodable frame that is ready
+ // long in the future. The stream should not timeout in this case, but rather
+ // decode the frame just before the timeout.
+ time_controller_.AdvanceTime(TimeDelta::Millis(2900));
+ uint32_t late_decode_rtp = kFirstRtpTimestamp + 200 * k30FpsRtpTimestampDelta;
+ video_receive_stream_->OnCompleteFrame(
+ test::FakeFrameBuilder()
+ .Id(121)
+ .PayloadType(99)
+ .Time(late_decode_rtp)
+ .ReceivedTime(clock_->CurrentTime())
+ .AsLast()
+ .Build());
+ EXPECT_THAT(fake_renderer_.WaitForFrame(TimeDelta::Millis(100),
+ /*advance_time=*/true),
+ RenderedFrameWith(RtpTimestamp(late_decode_rtp)));
+
+ video_receive_stream_->Stop();
+}
+
+INSTANTIATE_TEST_SUITE_P(VideoReceiveStream2Test,
+ VideoReceiveStream2Test,
+ testing::Bool(),
+ [](const auto& test_param_info) {
+ return (test_param_info.param
+ ? "ScheduleDecodesWithMetronome"
+ : "ScheduleDecodesWithPostTask");
+ });
+
+} // namespace webrtc