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}
This commit is contained in:
Markus Handell 2019-12-03 14:31:45 +01:00 committed by Commit Bot
parent 05e4d08e35
commit 269ac81a86
9 changed files with 374 additions and 18 deletions

View File

@ -153,6 +153,11 @@ class RTC_EXPORT EncodedImage {
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_

View File

@ -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",

View File

@ -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 VideoDecoderFactory;
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 @@ class VideoReceiveStream {
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() {}
};

View File

@ -229,6 +229,12 @@ class FakeVideoReceiveStream final : public webrtc::VideoReceiveStream {
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;

View File

@ -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
*/

View File

@ -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",

View File

@ -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<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 @@ void VideoReceiveStream::OnCompleteFrame(
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 @@ 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<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 @@ 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<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 @@ std::vector<webrtc::RtpSource> 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

View File

@ -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 @@ 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<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 @@ 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<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_;
};

View File

@ -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<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