VideoReceiveStream: Enable encoded frame sink.
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 <handellm@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29988}
diff --git a/api/video/encoded_image.h b/api/video/encoded_image.h
index 1fa2b04..b375d48 100644
--- a/api/video/encoded_image.h
+++ b/api/video/encoded_image.h
@@ -153,6 +153,11 @@
capacity_ = 0;
}
+ rtc::scoped_refptr<EncodedImageBufferInterface> 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 76e1b45..2a89f71 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -292,6 +292,7 @@
"../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 96c60b5..0f5e8e0 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -15,6 +15,7 @@
#include <map>
#include <set>
#include <string>
+#include <utility>
#include <vector>
#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 VideoReceiveStream {
public:
+ // Class for handling moving in/out recording state.
+ struct RecordingState {
+ RecordingState() = default;
+ explicit RecordingState(
+ std::function<void(const RecordableEncodedFrame&)> callback)
+ : callback(std::move(callback)) {}
+
+ // Callback stored from the VideoReceiveStream. The VideoReceiveStream
+ // client should not interpret the attribute.
+ std::function<void(const RecordableEncodedFrame&)> 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<int64_t> 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 @@
virtual void SetFrameDecryptor(
rtc::scoped_refptr<FrameDecryptorInterface> 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 9441e99..5179323 100644
--- a/media/engine/fake_webrtc_call.h
+++ b/media/engine/fake_webrtc_call.h
@@ -229,6 +229,12 @@
void SetFrameDecryptor(rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
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 028c19b..798c005 100644
--- a/modules/video_coding/encoded_frame.h
+++ b/modules/video_coding/encoded_frame.h
@@ -54,7 +54,9 @@
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;
@@ -76,6 +78,12 @@
*/
webrtc::VideoFrameType FrameType() const { return _frameType; }
/**
+ * Set frame type
+ */
+ void SetFrameType(webrtc::VideoFrameType frame_type) {
+ _frameType = frame_type;
+ }
+ /**
* Get frame rotation
*/
VideoRotation rotation() const { return rotation_; }
diff --git a/video/BUILD.gn b/video/BUILD.gn
index bb54fe8..68cee87 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -64,6 +64,7 @@
"../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 @@
"../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 @@
"../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 8213c64..7f68f76 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 @@
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<const EncodedImageBufferInterface> encoded_buffer()
+ const override {
+ return buffer_;
+ }
+
+ absl::optional<webrtc::ColorSpace> 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<EncodedImageBufferInterface> buffer_;
+ int64_t render_time_ms_;
+ VideoCodecType codec_;
+ bool is_key_frame_;
+ EncodedResolution resolution_;
+ absl::optional<webrtc::ColorSpace> color_space_;
+};
+
VideoCodec CreateDecoderVideoCodec(const VideoReceiveStream::Decoder& decoder) {
VideoCodec codec;
memset(&codec, 0, sizeof(codec));
@@ -501,7 +549,7 @@
std::unique_ptr<video_coding::EncodedFrame> 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 @@
}
}
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 @@
// 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<int64_t> last_packet_ms =
rtp_video_stream_receiver_.LastReceivedPacketMs();
- absl::optional<int64_t> 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 @@
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 @@
}
}
+bool VideoReceiveStream::IsReceivingKeyFrame(int64_t timestamp_ms) const {
+ absl::optional<int64_t> 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 @@
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 2a4e0d1..f097710 100644
--- a/video/video_receive_stream.h
+++ b/video/video_receive_stream.h
@@ -15,6 +15,8 @@
#include <vector>
#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 @@
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 @@
std::vector<webrtc::RtpSource> 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<video_coding::EncodedFrame> frame);
- void HandleFrameBufferTimeout();
-
+ void HandleEncodedFrame(std::unique_ptr<video_coding::EncodedFrame> 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 @@
// 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<void(const RecordableEncodedFrame&)>
+ 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 c9d0ad1..2da7f12 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 @@
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 @@
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 @@
}
}
+std::unique_ptr<FrameObjectFake> MakeFrame(VideoFrameType frame_type,
+ int picture_id) {
+ auto frame = std::make_unique<FrameObjectFake>();
+ frame->SetPayloadType(99);
+ frame->id.picture_id = picture_id;
+ frame->SetFrameType(frame_type);
+ return frame;
+}
+
+TEST_F(VideoReceiveStreamTestWithFakeDecoder,
+ PassesFrameWhenEncodedFramesCallbackSet) {
+ testing::MockFunction<void(const RecordableEncodedFrame&)> 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<void(const RecordableEncodedFrame&)> 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<void()> 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<void()> callback_;
+ };
+
+ static VideoReceiveStream::Config GetConfig(
+ Transport* transport,
+ VideoDecoderFactory* decoder_factory,
+ rtc::VideoSinkInterface<webrtc::VideoFrame>* 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<FakeDecoder2>([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<video_coding::EncodedFrame> frame) {
+ time_controller_.InvokeWithControlledYield([this, &frame] {
+ event_ = std::make_unique<rtc::Event>();
+ // 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<ProcessThread> 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<rtc::Event> 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