From 269ac81a860e5b030ccd92ca7478f785fcd86959 Mon Sep 17 00:00:00 2001 From: Markus Handell Date: Tue, 3 Dec 2019 14:31:45 +0100 Subject: [PATCH] VideoReceiveStream: Enable encoded frame sink. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change ultimately enables wiring up VideoRtpReceiver::OnGenerateKeyFrame and OnEncodedSinkEnabled into internal::VideoReceiveStream so that encoded frames can flow to sinks installed in VideoTrackSourceInterface. Bug: chromium:1013590 Change-Id: I0779932c251a2159880a39b2d42d5ce439cc88e6 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161090 Commit-Queue: Markus Handell Reviewed-by: Niels Moller Reviewed-by: Erik Språng Reviewed-by: Philip Eliasson Cr-Commit-Position: refs/heads/master@{#29988} --- api/video/encoded_image.h | 5 + call/BUILD.gn | 1 + call/video_receive_stream.h | 37 ++++++ media/engine/fake_webrtc_call.h | 6 + modules/video_coding/encoded_frame.h | 8 ++ video/BUILD.gn | 3 + video/video_receive_stream.cc | 137 ++++++++++++++++++-- video/video_receive_stream.h | 28 ++++- video/video_receive_stream_unittest.cc | 167 ++++++++++++++++++++++++- 9 files changed, 374 insertions(+), 18 deletions(-) diff --git a/api/video/encoded_image.h b/api/video/encoded_image.h index 1fa2b0455b..b375d4825c 100644 --- a/api/video/encoded_image.h +++ b/api/video/encoded_image.h @@ -153,6 +153,11 @@ class RTC_EXPORT EncodedImage { capacity_ = 0; } + rtc::scoped_refptr GetEncodedData() const { + RTC_DCHECK(buffer_ == nullptr); + return encoded_data_; + } + // TODO(nisse): Delete, provide only read-only access to the buffer. uint8_t* data() { return buffer_ ? buffer_ diff --git a/call/BUILD.gn b/call/BUILD.gn index 76e1b45203..2a89f71ffb 100644 --- a/call/BUILD.gn +++ b/call/BUILD.gn @@ -292,6 +292,7 @@ rtc_library("video_stream_api") { "../api/crypto:frame_encryptor_interface", "../api/crypto:options", "../api/transport/rtp:rtp_source", + "../api/video:recordable_encoded_frame", "../api/video:video_frame", "../api/video:video_rtp_headers", "../api/video:video_stream_encoder", diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h index 96c60b519d..0f5e8e043a 100644 --- a/call/video_receive_stream.h +++ b/call/video_receive_stream.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "api/call/transport.h" @@ -23,6 +24,7 @@ #include "api/rtp_headers.h" #include "api/rtp_parameters.h" #include "api/transport/rtp/rtp_source.h" +#include "api/video/recordable_encoded_frame.h" #include "api/video/video_content_type.h" #include "api/video/video_frame.h" #include "api/video/video_sink_interface.h" @@ -39,6 +41,26 @@ class VideoDecoderFactory; class VideoReceiveStream { public: + // Class for handling moving in/out recording state. + struct RecordingState { + RecordingState() = default; + explicit RecordingState( + std::function callback) + : callback(std::move(callback)) {} + + // Callback stored from the VideoReceiveStream. The VideoReceiveStream + // client should not interpret the attribute. + std::function callback; + // Memento of internal state in VideoReceiveStream, recording wether + // we're currently causing generation of a keyframe from the sender. Needed + // to avoid sending double keyframe requests. The VideoReceiveStream client + // should not interpret the attribute. + bool keyframe_needed = false; + // Memento of when a keyframe request was last sent. The VideoReceiveStream + // client should not interpret the attribute. + absl::optional last_keyframe_request_ms; + }; + // TODO(mflodman) Move all these settings to VideoDecoder and move the // declaration to common_types.h. struct Decoder { @@ -275,6 +297,21 @@ class VideoReceiveStream { virtual void SetFrameDecryptor( rtc::scoped_refptr frame_decryptor) = 0; + // Sets and returns recording state. The old state is moved out + // of the video receive stream and returned to the caller, and |state| + // is moved in. If the state's callback is set, it will be called with + // recordable encoded frames as they arrive. + // If |generate_key_frame| is true, the method will generate a key frame. + // When the function returns, it's guaranteed that all old callouts + // to the returned callback has ceased. + // Note: the client should not interpret the returned state's attributes, but + // instead treat it as opaque data. + virtual RecordingState SetAndGetRecordingState(RecordingState state, + bool generate_key_frame) = 0; + + // Cause eventual generation of a key frame from the sender. + virtual void GenerateKeyFrame() = 0; + protected: virtual ~VideoReceiveStream() {} }; diff --git a/media/engine/fake_webrtc_call.h b/media/engine/fake_webrtc_call.h index 9441e99ece..5179323c80 100644 --- a/media/engine/fake_webrtc_call.h +++ b/media/engine/fake_webrtc_call.h @@ -229,6 +229,12 @@ class FakeVideoReceiveStream final : public webrtc::VideoReceiveStream { void SetFrameDecryptor(rtc::scoped_refptr frame_decryptor) override {} + RecordingState SetAndGetRecordingState(RecordingState state, + bool generate_key_frame) override { + return RecordingState(); + } + void GenerateKeyFrame() override {} + private: // webrtc::VideoReceiveStream implementation. void Start() override; diff --git a/modules/video_coding/encoded_frame.h b/modules/video_coding/encoded_frame.h index 028c19ba1e..798c005e5d 100644 --- a/modules/video_coding/encoded_frame.h +++ b/modules/video_coding/encoded_frame.h @@ -54,7 +54,9 @@ class VCMEncodedFrame : protected EncodedImage { using EncodedImage::ColorSpace; using EncodedImage::data; + using EncodedImage::GetEncodedData; using EncodedImage::PacketInfos; + using EncodedImage::Retain; using EncodedImage::set_size; using EncodedImage::SetColorSpace; using EncodedImage::SetEncodedData; @@ -75,6 +77,12 @@ class VCMEncodedFrame : protected EncodedImage { * Get frame type */ webrtc::VideoFrameType FrameType() const { return _frameType; } + /** + * Set frame type + */ + void SetFrameType(webrtc::VideoFrameType frame_type) { + _frameType = frame_type; + } /** * Get frame rotation */ diff --git a/video/BUILD.gn b/video/BUILD.gn index bb54fe8367..68cee87e6a 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -64,6 +64,7 @@ rtc_library("video") { "../api/task_queue", "../api/transport/media:media_transport_interface", "../api/video:encoded_image", + "../api/video:recordable_encoded_frame", "../api/video:video_bitrate_allocation", "../api/video:video_bitrate_allocator", "../api/video:video_codec_constants", @@ -614,6 +615,7 @@ if (rtc_include_tests) { "../modules/utility", "../modules/video_coding", "../modules/video_coding:codec_globals_headers", + "../modules/video_coding:encoded_frame", "../modules/video_coding:video_codec_interface", "../modules/video_coding:video_coding_utility", "../modules/video_coding:webrtc_h264", @@ -645,6 +647,7 @@ if (rtc_include_tests) { "../test:test_common", "../test:test_support", "../test:video_test_common", + "../test/time_controller:time_controller", "//testing/gtest", "//third_party/abseil-cpp/absl/algorithm:container", "//third_party/abseil-cpp/absl/memory", diff --git a/video/video_receive_stream.cc b/video/video_receive_stream.cc index 8213c64bf3..7f68f76d2e 100644 --- a/video/video_receive_stream.cc +++ b/video/video_receive_stream.cc @@ -54,6 +54,10 @@ namespace webrtc { +namespace internal { +constexpr int VideoReceiveStream::kMaxWaitForKeyFrameMs; +} // namespace internal + namespace { using video_coding::EncodedFrame; @@ -62,9 +66,53 @@ using ReturnReason = video_coding::FrameBuffer::ReturnReason; constexpr int kMinBaseMinimumDelayMs = 0; constexpr int kMaxBaseMinimumDelayMs = 10000; -constexpr int kMaxWaitForKeyFrameMs = 200; constexpr int kMaxWaitForFrameMs = 3000; +// Concrete instance of RecordableEncodedFrame wrapping needed content +// from video_coding::EncodedFrame. +class WebRtcRecordableEncodedFrame : public RecordableEncodedFrame { + public: + explicit WebRtcRecordableEncodedFrame(const EncodedFrame& frame) + : buffer_(frame.GetEncodedData()), + render_time_ms_(frame.RenderTime()), + codec_(frame.CodecSpecific()->codecType), + is_key_frame_(frame.FrameType() == VideoFrameType::kVideoFrameKey), + resolution_{frame.EncodedImage()._encodedWidth, + frame.EncodedImage()._encodedHeight} { + if (frame.ColorSpace()) { + color_space_ = *frame.ColorSpace(); + } + } + + // VideoEncodedSinkInterface::FrameBuffer + rtc::scoped_refptr encoded_buffer() + const override { + return buffer_; + } + + absl::optional color_space() const override { + return color_space_; + } + + VideoCodecType codec() const override { return codec_; } + + bool is_key_frame() const override { return is_key_frame_; } + + EncodedResolution resolution() const override { return resolution_; } + + Timestamp render_time() const override { + return Timestamp::ms(render_time_ms_); + } + + private: + rtc::scoped_refptr buffer_; + int64_t render_time_ms_; + VideoCodecType codec_; + bool is_key_frame_; + EncodedResolution resolution_; + absl::optional color_space_; +}; + VideoCodec CreateDecoderVideoCodec(const VideoReceiveStream::Decoder& decoder) { VideoCodec codec; memset(&codec, 0, sizeof(codec)); @@ -501,7 +549,7 @@ void VideoReceiveStream::OnCompleteFrame( std::unique_ptr frame) { RTC_DCHECK_RUN_ON(&network_sequence_checker_); // TODO(https://bugs.webrtc.org/9974): Consider removing this workaround. - int64_t time_now_ms = rtc::TimeMillis(); + int64_t time_now_ms = clock_->TimeInMilliseconds(); if (last_complete_frame_time_ms_ > 0 && time_now_ms - last_complete_frame_time_ms_ > kInactiveStreamThresholdMs) { frame_buffer_->Clear(); @@ -607,7 +655,8 @@ void VideoReceiveStream::HandleEncodedFrame( } } stats_proxy_.OnPreDecode(frame->CodecSpecific()->codecType, qp); - + HandleKeyFrameGeneration(frame->FrameType() == VideoFrameType::kVideoFrameKey, + now_ms); int decode_result = video_receiver_.Decode(frame.get()); if (decode_result == WEBRTC_VIDEO_CODEC_OK || decode_result == WEBRTC_VIDEO_CODEC_OK_REQUEST_KEYFRAME) { @@ -624,14 +673,35 @@ void VideoReceiveStream::HandleEncodedFrame( // has been fixed. RequestKeyFrame(now_ms); } + + if (encoded_frame_buffer_function_) { + frame->Retain(); + encoded_frame_buffer_function_(WebRtcRecordableEncodedFrame(*frame)); + } +} + +void VideoReceiveStream::HandleKeyFrameGeneration( + bool received_frame_is_keyframe, + int64_t now_ms) { + // Repeat sending keyframe requests if we've requested a keyframe. + if (!keyframe_generation_requested_) { + return; + } + if (received_frame_is_keyframe) { + keyframe_generation_requested_ = false; + } else if (last_keyframe_request_ms_ + max_wait_for_keyframe_ms_ <= now_ms) { + if (!IsReceivingKeyFrame(now_ms)) { + RequestKeyFrame(now_ms); + } + } else { + // It hasn't been long enough since the last keyframe request, do nothing. + } } void VideoReceiveStream::HandleFrameBufferTimeout() { int64_t now_ms = clock_->TimeInMilliseconds(); absl::optional last_packet_ms = rtp_video_stream_receiver_.LastReceivedPacketMs(); - absl::optional last_keyframe_packet_ms = - rtp_video_stream_receiver_.LastReceivedKeyframePacketMs(); // To avoid spamming keyframe requests for a stream that is not active we // check if we have received a packet within the last 5 seconds. @@ -639,13 +709,7 @@ void VideoReceiveStream::HandleFrameBufferTimeout() { if (!stream_is_active) stats_proxy_.OnStreamInactive(); - // If we recently have been receiving packets belonging to a keyframe then - // we assume a keyframe is currently being received. - bool receiving_keyframe = - last_keyframe_packet_ms && - now_ms - *last_keyframe_packet_ms < max_wait_for_keyframe_ms_; - - if (stream_is_active && !receiving_keyframe && + if (stream_is_active && !IsReceivingKeyFrame(now_ms) && (!config_.crypto_options.sframe.require_frame_encryption || rtp_video_stream_receiver_.IsDecryptable())) { RTC_LOG(LS_WARNING) << "No decodable frame in " << GetWaitMs() @@ -654,6 +718,18 @@ void VideoReceiveStream::HandleFrameBufferTimeout() { } } +bool VideoReceiveStream::IsReceivingKeyFrame(int64_t timestamp_ms) const { + absl::optional last_keyframe_packet_ms = + rtp_video_stream_receiver_.LastReceivedKeyframePacketMs(); + + // If we recently have been receiving packets belonging to a keyframe then + // we assume a keyframe is currently being received. + bool receiving_keyframe = + last_keyframe_packet_ms && + timestamp_ms - *last_keyframe_packet_ms < max_wait_for_keyframe_ms_; + return receiving_keyframe; +} + void VideoReceiveStream::UpdatePlayoutDelays() const { const int minimum_delay_ms = std::max({frame_minimum_playout_delay_ms_, base_minimum_playout_delay_ms_, @@ -672,5 +748,42 @@ std::vector VideoReceiveStream::GetSources() const { return source_tracker_.GetSources(); } +VideoReceiveStream::RecordingState VideoReceiveStream::SetAndGetRecordingState( + RecordingState state, + bool generate_key_frame) { + RTC_DCHECK_RUN_ON(&worker_sequence_checker_); + rtc::Event event; + RecordingState old_state; + decode_queue_.PostTask([this, &event, &old_state, generate_key_frame, + state = std::move(state)] { + RTC_DCHECK_RUN_ON(&decode_queue_); + // Save old state. + old_state.callback = std::move(encoded_frame_buffer_function_); + old_state.keyframe_needed = keyframe_generation_requested_; + old_state.last_keyframe_request_ms = last_keyframe_request_ms_; + + // Set new state. + encoded_frame_buffer_function_ = std::move(state.callback); + if (generate_key_frame) { + RequestKeyFrame(clock_->TimeInMilliseconds()); + keyframe_generation_requested_ = true; + } else { + keyframe_generation_requested_ = state.keyframe_needed; + last_keyframe_request_ms_ = state.last_keyframe_request_ms.value_or(0); + } + event.Set(); + }); + event.Wait(rtc::Event::kForever); + return old_state; +} + +void VideoReceiveStream::GenerateKeyFrame() { + decode_queue_.PostTask([this]() { + RTC_DCHECK_RUN_ON(&decode_queue_); + RequestKeyFrame(clock_->TimeInMilliseconds()); + keyframe_generation_requested_ = true; + }); +} + } // namespace internal } // namespace webrtc diff --git a/video/video_receive_stream.h b/video/video_receive_stream.h index 2a4e0d1de5..f097710630 100644 --- a/video/video_receive_stream.h +++ b/video/video_receive_stream.h @@ -15,6 +15,8 @@ #include #include "api/task_queue/task_queue_factory.h" +#include "api/transport/media/media_transport_interface.h" +#include "api/video/recordable_encoded_frame.h" #include "call/rtp_packet_sink_interface.h" #include "call/syncable.h" #include "call/video_receive_stream.h" @@ -50,6 +52,10 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream, public Syncable, public CallStatsObserver { public: + // The default number of milliseconds to pass before re-requesting a key frame + // to be sent. + static constexpr int kMaxWaitForKeyFrameMs = 200; + VideoReceiveStream(TaskQueueFactory* task_queue_factory, RtpStreamReceiverControllerInterface* receiver_controller, int num_cpu_cores, @@ -123,15 +129,23 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream, std::vector GetSources() const override; + RecordingState SetAndGetRecordingState(RecordingState state, + bool generate_key_frame) override; + void GenerateKeyFrame() override; + private: int64_t GetWaitMs() const; void StartNextDecode() RTC_RUN_ON(decode_queue_); - void HandleEncodedFrame(std::unique_ptr frame); - void HandleFrameBufferTimeout(); - + void HandleEncodedFrame(std::unique_ptr frame) + RTC_RUN_ON(decode_queue_); + void HandleFrameBufferTimeout() RTC_RUN_ON(decode_queue_); void UpdatePlayoutDelays() const RTC_EXCLUSIVE_LOCKS_REQUIRED(playout_delay_lock_); - void RequestKeyFrame(int64_t timestamp_ms); + void RequestKeyFrame(int64_t timestamp_ms) RTC_RUN_ON(decode_queue_); + void HandleKeyFrameGeneration(bool received_frame_is_keyframe, int64_t now_ms) + RTC_RUN_ON(decode_queue_); + bool IsReceivingKeyFrame(int64_t timestamp_ms) const + RTC_RUN_ON(decode_queue_); void UpdateHistograms(); @@ -207,6 +221,12 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream, // Maximum delay as decided by the RTP playout delay extension. int frame_maximum_playout_delay_ms_ RTC_GUARDED_BY(playout_delay_lock_) = -1; + // Function that is triggered with encoded frames, if not empty. + std::function + encoded_frame_buffer_function_ RTC_GUARDED_BY(decode_queue_); + // Set to true while we're requesting keyframes but not yet received one. + bool keyframe_generation_requested_ RTC_GUARDED_BY(decode_queue_) = false; + // Defined last so they are destroyed before all other members. rtc::TaskQueue decode_queue_; }; diff --git a/video/video_receive_stream_unittest.cc b/video/video_receive_stream_unittest.cc index c9d0ad13b0..2da7f122d0 100644 --- a/video/video_receive_stream_unittest.cc +++ b/video/video_receive_stream_unittest.cc @@ -24,6 +24,7 @@ #include "modules/pacing/packet_router.h" #include "modules/rtp_rtcp/source/rtp_packet_to_send.h" #include "modules/utility/include/process_thread.h" +#include "modules/video_coding/encoded_frame.h" #include "rtc_base/critical_section.h" #include "rtc_base/event.h" #include "system_wrappers/include/clock.h" @@ -31,6 +32,7 @@ #include "test/field_trial.h" #include "test/gmock.h" #include "test/gtest.h" +#include "test/time_controller/simulated_time_controller.h" #include "test/video_decoder_proxy_factory.h" #include "video/call_stats.h" @@ -239,7 +241,6 @@ class VideoReceiveStreamTestWithFakeDecoder : public ::testing::Test { call_stats_(Clock::GetRealTimeClock(), process_thread_.get()) {} void SetUp() { - constexpr int kDefaultNumCpuCores = 2; config_.rtp.remote_ssrc = 1111; config_.rtp.local_ssrc = 2222; config_.renderer = &fake_renderer_; @@ -249,12 +250,18 @@ class VideoReceiveStreamTestWithFakeDecoder : public ::testing::Test { fake_decoder.decoder_factory = &fake_decoder_factory_; config_.decoders.push_back(fake_decoder); clock_ = Clock::GetRealTimeClock(); - timing_ = new VCMTiming(clock_); + ReCreateReceiveStream(VideoReceiveStream::RecordingState()); + } + void ReCreateReceiveStream(VideoReceiveStream::RecordingState state) { + constexpr int kDefaultNumCpuCores = 2; + video_receive_stream_ = nullptr; + timing_ = new VCMTiming(clock_); video_receive_stream_.reset(new webrtc::internal::VideoReceiveStream( task_queue_factory_.get(), &rtp_stream_receiver_controller_, kDefaultNumCpuCores, &packet_router_, config_.Copy(), process_thread_.get(), &call_stats_, clock_, timing_)); + video_receive_stream_->SetAndGetRecordingState(std::move(state), false); } protected: @@ -391,4 +398,160 @@ TEST_F(VideoReceiveStreamTestWithFakeDecoder, RenderedFrameUpdatesGetSources) { } } +std::unique_ptr MakeFrame(VideoFrameType frame_type, + int picture_id) { + auto frame = std::make_unique(); + frame->SetPayloadType(99); + frame->id.picture_id = picture_id; + frame->SetFrameType(frame_type); + return frame; +} + +TEST_F(VideoReceiveStreamTestWithFakeDecoder, + PassesFrameWhenEncodedFramesCallbackSet) { + testing::MockFunction callback; + video_receive_stream_->Start(); + // Expect a keyframe request to be generated + EXPECT_CALL(mock_transport_, SendRtcp); + EXPECT_CALL(callback, Call); + video_receive_stream_->SetAndGetRecordingState( + VideoReceiveStream::RecordingState(callback.AsStdFunction()), true); + video_receive_stream_->OnCompleteFrame( + MakeFrame(VideoFrameType::kVideoFrameKey, 0)); + EXPECT_TRUE(fake_renderer_.WaitForRenderedFrame(kDefaultTimeOutMs)); + video_receive_stream_->Stop(); +} + +TEST_F(VideoReceiveStreamTestWithFakeDecoder, + MovesEncodedFrameDispatchStateWhenReCreating) { + testing::MockFunction callback; + video_receive_stream_->Start(); + // Expect a key frame request over RTCP. + EXPECT_CALL(mock_transport_, SendRtcp).Times(1); + video_receive_stream_->SetAndGetRecordingState( + VideoReceiveStream::RecordingState(callback.AsStdFunction()), true); + video_receive_stream_->Stop(); + VideoReceiveStream::RecordingState old_state = + video_receive_stream_->SetAndGetRecordingState( + VideoReceiveStream::RecordingState(), false); + ReCreateReceiveStream(std::move(old_state)); + video_receive_stream_->Stop(); +} + +class VideoReceiveStreamTestWithSimulatedClock : public ::testing::Test { + public: + class FakeDecoder2 : public test::FakeDecoder { + public: + explicit FakeDecoder2(std::function decode_callback) + : callback_(decode_callback) {} + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) override { + int32_t result = + FakeDecoder::Decode(input, missing_frames, render_time_ms); + callback_(); + return result; + } + + private: + std::function callback_; + }; + + static VideoReceiveStream::Config GetConfig( + Transport* transport, + VideoDecoderFactory* decoder_factory, + rtc::VideoSinkInterface* renderer) { + VideoReceiveStream::Config config(transport); + config.rtp.remote_ssrc = 1111; + config.rtp.local_ssrc = 2222; + config.renderer = renderer; + VideoReceiveStream::Decoder fake_decoder; + fake_decoder.payload_type = 99; + fake_decoder.video_format = SdpVideoFormat("VP8"); + fake_decoder.decoder_factory = decoder_factory; + config.decoders.push_back(fake_decoder); + return config; + } + + VideoReceiveStreamTestWithSimulatedClock() + : time_controller_(Timestamp::ms(4711)), + fake_decoder_factory_([this] { + return std::make_unique([this] { OnFrameDecoded(); }); + }), + process_thread_(time_controller_.CreateProcessThread("ProcessThread")), + config_(GetConfig(&mock_transport_, + &fake_decoder_factory_, + &fake_renderer_)), + call_stats_(time_controller_.GetClock(), process_thread_.get()), + video_receive_stream_(time_controller_.GetTaskQueueFactory(), + &rtp_stream_receiver_controller_, + /*num_cores=*/2, + &packet_router_, + config_.Copy(), + process_thread_.get(), + &call_stats_, + time_controller_.GetClock(), + new VCMTiming(time_controller_.GetClock())) { + time_controller_.InvokeWithControlledYield( + [this] { video_receive_stream_.Start(); }); + } + + ~VideoReceiveStreamTestWithSimulatedClock() { + time_controller_.InvokeWithControlledYield( + [this] { video_receive_stream_.Stop(); }); + } + + void OnFrameDecoded() { event_->Set(); } + + void PassEncodedFrameAndWait( + std::unique_ptr frame) { + time_controller_.InvokeWithControlledYield([this, &frame] { + event_ = std::make_unique(); + // This call will eventually end up in the Decoded method where the + // event is set. + video_receive_stream_.OnCompleteFrame(std::move(frame)); + event_->Wait(rtc::Event::kForever); + }); + } + + protected: + GlobalSimulatedTimeController time_controller_; + test::FunctionVideoDecoderFactory fake_decoder_factory_; + std::unique_ptr process_thread_; + MockTransport mock_transport_; + cricket::FakeVideoRenderer fake_renderer_; + VideoReceiveStream::Config config_; + CallStats call_stats_; + PacketRouter packet_router_; + RtpStreamReceiverController rtp_stream_receiver_controller_; + webrtc::internal::VideoReceiveStream video_receive_stream_; + std::unique_ptr event_; +}; + +TEST_F(VideoReceiveStreamTestWithSimulatedClock, + RequestsKeyFramesUntilKeyFrameReceived) { + auto tick = + TimeDelta::ms(internal::VideoReceiveStream::kMaxWaitForKeyFrameMs / 2); + EXPECT_CALL(mock_transport_, SendRtcp).Times(1); + video_receive_stream_.GenerateKeyFrame(); + PassEncodedFrameAndWait(MakeFrame(VideoFrameType::kVideoFrameDelta, 0)); + time_controller_.Sleep(tick); + PassEncodedFrameAndWait(MakeFrame(VideoFrameType::kVideoFrameDelta, 1)); + testing::Mock::VerifyAndClearExpectations(&mock_transport_); + + // T+200ms: still no key frame received, expect key frame request sent again. + EXPECT_CALL(mock_transport_, SendRtcp).Times(1); + time_controller_.Sleep(tick); + PassEncodedFrameAndWait(MakeFrame(VideoFrameType::kVideoFrameDelta, 2)); + testing::Mock::VerifyAndClearExpectations(&mock_transport_); + + // T+200ms: now send a key frame - we should not observe new key frame + // requests after this. + EXPECT_CALL(mock_transport_, SendRtcp).Times(0); + PassEncodedFrameAndWait(MakeFrame(VideoFrameType::kVideoFrameKey, 3)); + time_controller_.Sleep(2 * tick); + PassEncodedFrameAndWait(MakeFrame(VideoFrameType::kVideoFrameDelta, 4)); +} + } // namespace webrtc