[PCLF] Add video writer which accounts for freezes
Bug: b/237997865
Change-Id: I6d6e3faa48e6bddbe298ead7b1350dd3c70481b2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/268545
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#37543}
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 58f0760..940232f 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -380,6 +380,23 @@
]
}
+rtc_library("fixed_fps_video_frame_writer_adapter") {
+ visibility = [ "*" ]
+ testonly = true
+ sources = [
+ "testsupport/fixed_fps_video_frame_writer_adapter.cc",
+ "testsupport/fixed_fps_video_frame_writer_adapter.h",
+ ]
+ deps = [
+ ":video_test_support",
+ "../api/units:time_delta",
+ "../api/video:video_frame",
+ "../rtc_base:checks",
+ "../system_wrappers",
+ ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+}
+
rtc_library("video_test_support") {
testonly = true
@@ -530,6 +547,22 @@
}
}
+ rtc_library("fixed_fps_video_frame_writer_adapter_test") {
+ testonly = true
+ sources = [ "testsupport/fixed_fps_video_frame_writer_adapter_test.cc" ]
+ deps = [
+ ":fixed_fps_video_frame_writer_adapter",
+ ":test_support",
+ ":video_test_support",
+ "../api/units:time_delta",
+ "../api/units:timestamp",
+ "../api/video:video_frame",
+ "../rtc_base/synchronization:mutex",
+ "time_controller",
+ ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+ }
+
rtc_test("test_support_unittests") {
deps = [
":call_config_utils",
@@ -538,6 +571,7 @@
":fake_video_codecs",
":fileutils",
":fileutils_unittests",
+ ":fixed_fps_video_frame_writer_adapter_test",
":frame_generator_impl",
":perf_test",
":rtc_expect_death",
diff --git a/test/testsupport/fixed_fps_video_frame_writer_adapter.cc b/test/testsupport/fixed_fps_video_frame_writer_adapter.cc
new file mode 100644
index 0000000..531dade
--- /dev/null
+++ b/test/testsupport/fixed_fps_video_frame_writer_adapter.cc
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2022 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 "test/testsupport/fixed_fps_video_frame_writer_adapter.h"
+
+#include <cmath>
+#include <utility>
+
+#include "absl/types/optional.h"
+#include "api/units/time_delta.h"
+#include "api/video/video_sink_interface.h"
+#include "rtc_base/checks.h"
+#include "test/testsupport/video_frame_writer.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+constexpr TimeDelta kOneSecond = TimeDelta::Seconds(1);
+
+} // namespace
+
+FixedFpsVideoFrameWriterAdapter::FixedFpsVideoFrameWriterAdapter(
+ int fps,
+ Clock* clock,
+ std::unique_ptr<VideoFrameWriter> delegate)
+ : inter_frame_interval_(kOneSecond / fps),
+ clock_(clock),
+ delegate_(std::move(delegate)) {}
+
+FixedFpsVideoFrameWriterAdapter::~FixedFpsVideoFrameWriterAdapter() {
+ Close();
+}
+
+void FixedFpsVideoFrameWriterAdapter::Close() {
+ if (is_closed_) {
+ return;
+ }
+ is_closed_ = true;
+ if (!last_frame_.has_value()) {
+ return;
+ }
+ Timestamp now = Now();
+ RTC_CHECK(WriteMissedSlotsExceptLast(now));
+ RTC_CHECK(delegate_->WriteFrame(*last_frame_));
+ delegate_->Close();
+}
+
+bool FixedFpsVideoFrameWriterAdapter::WriteFrame(const VideoFrame& frame) {
+ RTC_CHECK(!is_closed_);
+ Timestamp now = Now();
+ if (!last_frame_.has_value()) {
+ RTC_CHECK(!last_frame_time_.IsFinite());
+ last_frame_ = frame;
+ last_frame_time_ = now;
+ return true;
+ }
+
+ RTC_CHECK(last_frame_time_.IsFinite());
+
+ if (last_frame_time_ > now) {
+ // New frame was recevied before expected time "slot" for current
+ // `last_frame_` came => just replace current `last_frame_` with
+ // received `frame`.
+ RTC_CHECK_LE(last_frame_time_ - now, inter_frame_interval_ / 2);
+ last_frame_ = frame;
+ return true;
+ }
+
+ if (!WriteMissedSlotsExceptLast(now)) {
+ return false;
+ }
+
+ if (now - last_frame_time_ < inter_frame_interval_ / 2) {
+ // New frame was received closer to the expected time "slot" for current
+ // `last_frame_` than to the next "slot" => just replace current
+ // `last_frame_` with received `frame`.
+ last_frame_ = frame;
+ return true;
+ }
+
+ if (!delegate_->WriteFrame(*last_frame_)) {
+ return false;
+ }
+ last_frame_ = frame;
+ last_frame_time_ = last_frame_time_ + inter_frame_interval_;
+ return true;
+}
+
+bool FixedFpsVideoFrameWriterAdapter::WriteMissedSlotsExceptLast(
+ Timestamp now) {
+ RTC_CHECK(last_frame_time_.IsFinite());
+ while (now - last_frame_time_ > inter_frame_interval_) {
+ if (!delegate_->WriteFrame(*last_frame_)) {
+ return false;
+ }
+ last_frame_time_ = last_frame_time_ + inter_frame_interval_;
+ }
+ return true;
+}
+
+Timestamp FixedFpsVideoFrameWriterAdapter::Now() const {
+ return clock_->CurrentTime();
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/test/testsupport/fixed_fps_video_frame_writer_adapter.h b/test/testsupport/fixed_fps_video_frame_writer_adapter.h
new file mode 100644
index 0000000..0bdd6ec
--- /dev/null
+++ b/test/testsupport/fixed_fps_video_frame_writer_adapter.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 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.
+ */
+
+#ifndef TEST_TESTSUPPORT_FIXED_FPS_VIDEO_FRAME_WRITER_ADAPTER_H_
+#define TEST_TESTSUPPORT_FIXED_FPS_VIDEO_FRAME_WRITER_ADAPTER_H_
+
+#include <memory>
+
+#include "absl/types/optional.h"
+#include "api/video/video_sink_interface.h"
+#include "system_wrappers/include/clock.h"
+#include "test/testsupport/video_frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+// Writes video to the specified video writer with specified fixed frame rate.
+// If at the point in time X no new frames are passed to the writer, the
+// previous frame is used to fill the gap and preserve frame rate.
+//
+// This adaptor uses next algorithm:
+// There are output "slots" at a fixed frame rate (starting at the time of the
+// first received frame). Each incoming frame is assigned to the closest output
+// slot. Then empty slots are filled by repeating the closest filled slot before
+// empty one. If there are multiple frames closest to the same slot, the latest
+// received one is used.
+//
+// The frames are outputted for the whole duration of the class life after the
+// first frame was written or until it will be closed.
+//
+// For example if frames from A to F were received, then next output sequence
+// will be generated:
+// Received frames: A B C D EF Destructor called
+// | | | | || |
+// v v v v vv v
+// X----X----X----X----X----X----X----X----X----+----+--
+// | | | | | | | | |
+// Produced frames: A A A B C C F F F
+//
+// This class is not thread safe.
+class FixedFpsVideoFrameWriterAdapter : public VideoFrameWriter {
+ public:
+ FixedFpsVideoFrameWriterAdapter(int fps,
+ Clock* clock,
+ std::unique_ptr<VideoFrameWriter> delegate);
+ ~FixedFpsVideoFrameWriterAdapter() override;
+
+ bool WriteFrame(const webrtc::VideoFrame& frame) override;
+
+ // Closes adapter and underlying delegate. User mustn't call to the WriteFrame
+ // after calling this method.
+ void Close() override;
+
+ private:
+ // Writes `last_frame_` for each "slot" from `last_frame_time_` up to now
+ // excluding the last one.
+ // Updates `last_frame_time_` to the position of the last NOT WRITTEN frame.
+ // Returns true if all writes were successful, otherwise retuns false. In such
+ // case it is not guranteed how many frames were actually written.
+ bool WriteMissedSlotsExceptLast(Timestamp now);
+ Timestamp Now() const;
+
+ // Because `TimeDelta` stores time with microseconds precision
+ // `last_frame_time_` may have a small drift and for very long streams it
+ // must be updated to use double for time.
+ const TimeDelta inter_frame_interval_;
+ Clock* const clock_;
+ std::unique_ptr<VideoFrameWriter> delegate_;
+ bool is_closed_ = false;
+
+ // Expected time slot for the last frame.
+ Timestamp last_frame_time_ = Timestamp::MinusInfinity();
+ absl::optional<VideoFrame> last_frame_ = absl::nullopt;
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_FIXED_FPS_VIDEO_FRAME_WRITER_ADAPTER_H_
diff --git a/test/testsupport/fixed_fps_video_frame_writer_adapter_test.cc b/test/testsupport/fixed_fps_video_frame_writer_adapter_test.cc
new file mode 100644
index 0000000..5ee4701
--- /dev/null
+++ b/test/testsupport/fixed_fps_video_frame_writer_adapter_test.cc
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2022 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 "test/testsupport/fixed_fps_video_frame_writer_adapter.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/testsupport/video_frame_writer.h"
+#include "test/time_controller/simulated_time_controller.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+constexpr TimeDelta kOneSecond = TimeDelta::Seconds(1);
+
+using ::testing::ElementsAre;
+
+class InMemoryVideoWriter : public VideoFrameWriter {
+ public:
+ ~InMemoryVideoWriter() override = default;
+
+ bool WriteFrame(const webrtc::VideoFrame& frame) override {
+ MutexLock lock(&mutex_);
+ received_frames_.push_back(frame);
+ return true;
+ }
+
+ void Close() override {}
+
+ std::vector<VideoFrame> received_frames() const {
+ MutexLock lock(&mutex_);
+ return received_frames_;
+ }
+
+ private:
+ mutable Mutex mutex_;
+ std::vector<VideoFrame> received_frames_ RTC_GUARDED_BY(mutex_);
+};
+
+VideoFrame EmptyFrameWithId(uint16_t frame_id) {
+ return VideoFrame::Builder()
+ .set_video_frame_buffer(I420Buffer::Create(1, 1))
+ .set_id(frame_id)
+ .build();
+}
+
+std::vector<uint16_t> FrameIds(const std::vector<VideoFrame>& frames) {
+ std::vector<uint16_t> out;
+ for (const VideoFrame& frame : frames) {
+ out.push_back(frame.id());
+ }
+ return out;
+}
+
+std::unique_ptr<TimeController> CreateSimulatedTimeController() {
+ // Using an offset of 100000 to get nice fixed width and readable
+ // timestamps in typical test scenarios.
+ const Timestamp kSimulatedStartTime = Timestamp::Seconds(100000);
+ return std::make_unique<GlobalSimulatedTimeController>(kSimulatedStartTime);
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ WhenWrittenWithSameFpsVideoIsCorrect) {
+ auto time_controller = CreateSimulatedTimeController();
+ int fps = 25;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(fps, time_controller->GetClock(),
+ std::move(inmemory_writer));
+
+ for (int i = 1; i <= 30; ++i) {
+ video_writer.WriteFrame(EmptyFrameWithId(i));
+ time_controller->AdvanceTime(kOneSecond / fps);
+ }
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(
+ FrameIds(received_frames),
+ ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest, FrameIsRepeatedWhenThereIsAFreeze) {
+ auto time_controller = CreateSimulatedTimeController();
+ int fps = 25;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(fps, time_controller->GetClock(),
+ std::move(inmemory_writer));
+
+ // Write 10 frames
+ for (int i = 1; i <= 10; ++i) {
+ video_writer.WriteFrame(EmptyFrameWithId(i));
+ time_controller->AdvanceTime(kOneSecond / fps);
+ }
+
+ // Freeze for 4 frames
+ time_controller->AdvanceTime(4 * kOneSecond / fps);
+
+ // Write 10 more frames
+ for (int i = 11; i <= 20; ++i) {
+ video_writer.WriteFrame(EmptyFrameWithId(i));
+ time_controller->AdvanceTime(kOneSecond / fps);
+ }
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames),
+ ElementsAre(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest, NoFramesWritten) {
+ auto time_controller = CreateSimulatedTimeController();
+ int fps = 25;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(fps, time_controller->GetClock(),
+ std::move(inmemory_writer));
+ time_controller->AdvanceTime(TimeDelta::Millis(100));
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ ASSERT_TRUE(received_frames.empty());
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ FreezeInTheMiddleAndNewFrameReceivedBeforeMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(2.3 * kInterval);
+ video_writer.WriteFrame(EmptyFrameWithId(2));
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1, 1, 2));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ FreezeInTheMiddleAndNewFrameReceivedAfterMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(2.5 * kInterval);
+ video_writer.WriteFrame(EmptyFrameWithId(2));
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1, 1, 1, 2));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ NewFrameReceivedBeforeMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(0.3 * kInterval);
+ video_writer.WriteFrame(EmptyFrameWithId(2));
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(2));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ NewFrameReceivedAfterMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(0.5 * kInterval);
+ video_writer.WriteFrame(EmptyFrameWithId(2));
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1, 2));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ FreeezeAtTheEndAndDestroyBeforeMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(2.3 * kInterval);
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1, 1, 1));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ FreeezeAtTheEndAndDestroyAfterMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(2.5 * kInterval);
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1, 1, 1));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ DestroyBeforeMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(0.3 * kInterval);
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1));
+}
+
+TEST(FixedFpsVideoFrameWriterAdapterTest,
+ DestroyAfterMiddleOfExpectedInterval) {
+ auto time_controller = CreateSimulatedTimeController();
+ constexpr int kFps = 10;
+ constexpr TimeDelta kInterval = kOneSecond / kFps;
+
+ auto inmemory_writer = std::make_unique<InMemoryVideoWriter>();
+ InMemoryVideoWriter* inmemory_writer_ref = inmemory_writer.get();
+
+ FixedFpsVideoFrameWriterAdapter video_writer(
+ kFps, time_controller->GetClock(), std::move(inmemory_writer));
+ video_writer.WriteFrame(EmptyFrameWithId(1));
+ time_controller->AdvanceTime(0.5 * kInterval);
+ video_writer.Close();
+
+ std::vector<VideoFrame> received_frames =
+ inmemory_writer_ref->received_frames();
+ EXPECT_THAT(FrameIds(received_frames), ElementsAre(1));
+}
+
+} // namespace
+} // namespace test
+} // namespace webrtc