summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/test/testsupport
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/test/testsupport
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/test/testsupport')
-rw-r--r--third_party/libwebrtc/test/testsupport/DEPS4
-rw-r--r--third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.cc45
-rw-r--r--third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.h49
-rw-r--r--third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer_unittest.cc57
-rw-r--r--third_party/libwebrtc/test/testsupport/file_utils.cc249
-rw-r--r--third_party/libwebrtc/test/testsupport/file_utils.h109
-rw-r--r--third_party/libwebrtc/test/testsupport/file_utils_override.cc170
-rw-r--r--third_party/libwebrtc/test/testsupport/file_utils_override.h57
-rw-r--r--third_party/libwebrtc/test/testsupport/file_utils_unittest.cc277
-rw-r--r--third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.cc114
-rw-r--r--third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.h87
-rw-r--r--third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter_test.cc320
-rw-r--r--third_party/libwebrtc/test/testsupport/frame_reader.h149
-rw-r--r--third_party/libwebrtc/test/testsupport/frame_writer.h104
-rw-r--r--third_party/libwebrtc/test/testsupport/ios_file_utils.h29
-rw-r--r--third_party/libwebrtc/test/testsupport/ios_file_utils.mm61
-rw-r--r--third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.cc158
-rw-r--r--third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.h90
-rw-r--r--third_party/libwebrtc/test/testsupport/ivf_video_frame_generator_unittest.cc216
-rw-r--r--third_party/libwebrtc/test/testsupport/jpeg_frame_writer.cc88
-rw-r--r--third_party/libwebrtc/test/testsupport/jpeg_frame_writer_ios.cc30
-rw-r--r--third_party/libwebrtc/test/testsupport/mac_file_utils.h24
-rw-r--r--third_party/libwebrtc/test/testsupport/mac_file_utils.mm43
-rw-r--r--third_party/libwebrtc/test/testsupport/mock/mock_frame_reader.h40
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test.cc355
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test.h124
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.cc201
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.h24
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_no_protobuf.cc24
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_unittest.cc216
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test_result_writer.h58
-rw-r--r--third_party/libwebrtc/test/testsupport/perf_test_unittest.cc205
-rw-r--r--third_party/libwebrtc/test/testsupport/resources_dir_flag.cc21
-rw-r--r--third_party/libwebrtc/test/testsupport/resources_dir_flag.h20
-rw-r--r--third_party/libwebrtc/test/testsupport/rtc_expect_death.h23
-rw-r--r--third_party/libwebrtc/test/testsupport/test_artifacts.cc71
-rw-r--r--third_party/libwebrtc/test/testsupport/test_artifacts.h40
-rw-r--r--third_party/libwebrtc/test/testsupport/test_artifacts_unittest.cc62
-rw-r--r--third_party/libwebrtc/test/testsupport/video_frame_writer.cc111
-rw-r--r--third_party/libwebrtc/test/testsupport/video_frame_writer.h63
-rw-r--r--third_party/libwebrtc/test/testsupport/video_frame_writer_unittest.cc173
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_generator.cc101
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_generator.h68
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_generator_test.cc149
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_reader.cc92
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_reader_unittest.cc158
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_writer.cc59
-rw-r--r--third_party/libwebrtc/test/testsupport/y4m_frame_writer_unittest.cc81
-rw-r--r--third_party/libwebrtc/test/testsupport/yuv_frame_reader.cc162
-rw-r--r--third_party/libwebrtc/test/testsupport/yuv_frame_reader_unittest.cc151
-rw-r--r--third_party/libwebrtc/test/testsupport/yuv_frame_writer.cc80
-rw-r--r--third_party/libwebrtc/test/testsupport/yuv_frame_writer_unittest.cc73
52 files changed, 5535 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/testsupport/DEPS b/third_party/libwebrtc/test/testsupport/DEPS
new file mode 100644
index 0000000000..6f6150ad30
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ # Histogram C++ API, used by perf tests.
+ "+third_party/catapult/tracing/tracing/value"
+]
diff --git a/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.cc b/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.cc
new file mode 100644
index 0000000000..6de8e7fd99
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019 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/copy_to_file_audio_capturer.h"
+
+#include <memory>
+#include <utility>
+
+namespace webrtc {
+namespace test {
+
+CopyToFileAudioCapturer::CopyToFileAudioCapturer(
+ std::unique_ptr<TestAudioDeviceModule::Capturer> delegate,
+ std::string stream_dump_file_name)
+ : delegate_(std::move(delegate)),
+ wav_writer_(std::make_unique<WavWriter>(std::move(stream_dump_file_name),
+ delegate_->SamplingFrequency(),
+ delegate_->NumChannels())) {}
+CopyToFileAudioCapturer::~CopyToFileAudioCapturer() = default;
+
+int CopyToFileAudioCapturer::SamplingFrequency() const {
+ return delegate_->SamplingFrequency();
+}
+
+int CopyToFileAudioCapturer::NumChannels() const {
+ return delegate_->NumChannels();
+}
+
+bool CopyToFileAudioCapturer::Capture(rtc::BufferT<int16_t>* buffer) {
+ bool result = delegate_->Capture(buffer);
+ if (result) {
+ wav_writer_->WriteSamples(buffer->data(), buffer->size());
+ }
+ return result;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.h b/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.h
new file mode 100644
index 0000000000..a410beeea8
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019 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_COPY_TO_FILE_AUDIO_CAPTURER_H_
+#define TEST_TESTSUPPORT_COPY_TO_FILE_AUDIO_CAPTURER_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "common_audio/wav_file.h"
+#include "modules/audio_device/include/test_audio_device.h"
+#include "rtc_base/buffer.h"
+
+namespace webrtc {
+namespace test {
+
+// TestAudioDeviceModule::Capturer that will store audio data, captured by
+// delegate to the specified output file. Can be used to create a copy of
+// generated audio data to be able then to compare it as a reference with
+// audio on the TestAudioDeviceModule::Renderer side.
+class CopyToFileAudioCapturer : public TestAudioDeviceModule::Capturer {
+ public:
+ CopyToFileAudioCapturer(
+ std::unique_ptr<TestAudioDeviceModule::Capturer> delegate,
+ std::string stream_dump_file_name);
+ ~CopyToFileAudioCapturer() override;
+
+ int SamplingFrequency() const override;
+ int NumChannels() const override;
+ bool Capture(rtc::BufferT<int16_t>* buffer) override;
+
+ private:
+ std::unique_ptr<TestAudioDeviceModule::Capturer> delegate_;
+ std::unique_ptr<WavWriter> wav_writer_;
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_COPY_TO_FILE_AUDIO_CAPTURER_H_
diff --git a/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer_unittest.cc b/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer_unittest.cc
new file mode 100644
index 0000000000..3831c28580
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/copy_to_file_audio_capturer_unittest.cc
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 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/copy_to_file_audio_capturer.h"
+
+#include <memory>
+#include <utility>
+
+#include "modules/audio_device/include/test_audio_device.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+
+namespace webrtc {
+namespace test {
+
+class CopyToFileAudioCapturerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ temp_filename_ = webrtc::test::TempFilename(
+ webrtc::test::OutputPath(), "copy_to_file_audio_capturer_unittest");
+ std::unique_ptr<TestAudioDeviceModule::Capturer> delegate =
+ TestAudioDeviceModule::CreatePulsedNoiseCapturer(32000, 48000);
+ capturer_ = std::make_unique<CopyToFileAudioCapturer>(std::move(delegate),
+ temp_filename_);
+ }
+
+ void TearDown() override { ASSERT_EQ(remove(temp_filename_.c_str()), 0); }
+
+ std::unique_ptr<CopyToFileAudioCapturer> capturer_;
+ std::string temp_filename_;
+};
+
+TEST_F(CopyToFileAudioCapturerTest, Capture) {
+ rtc::BufferT<int16_t> expected_buffer;
+ ASSERT_TRUE(capturer_->Capture(&expected_buffer));
+ ASSERT_TRUE(!expected_buffer.empty());
+ // Destruct capturer to close wav file.
+ capturer_.reset(nullptr);
+
+ // Read resulted file content with `wav_file_capture` and compare with
+ // what was captured.
+ std::unique_ptr<TestAudioDeviceModule::Capturer> wav_file_capturer =
+ TestAudioDeviceModule::CreateWavFileReader(temp_filename_, 48000);
+ rtc::BufferT<int16_t> actual_buffer;
+ wav_file_capturer->Capture(&actual_buffer);
+ ASSERT_EQ(actual_buffer, expected_buffer);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/file_utils.cc b/third_party/libwebrtc/test/testsupport/file_utils.cc
new file mode 100644
index 0000000000..47fed9ac05
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/file_utils.cc
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2012 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/file_utils.h"
+
+#if defined(WEBRTC_POSIX)
+#include <unistd.h>
+#endif
+
+#if defined(WEBRTC_WIN)
+#include <direct.h>
+#include <tchar.h>
+#include <windows.h>
+
+#include <algorithm>
+#include <codecvt>
+#include <locale>
+
+#include "Shlwapi.h"
+#include "WinDef.h"
+#include "rtc_base/win32.h"
+
+#define GET_CURRENT_DIR _getcwd
+#else
+#include <dirent.h>
+
+#define GET_CURRENT_DIR getcwd
+#endif
+
+#include <sys/stat.h> // To check for directory existence.
+#ifndef S_ISDIR // Not defined in stat.h on Windows.
+#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR)
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#if defined(WEBRTC_IOS)
+#include "test/testsupport/ios_file_utils.h"
+#elif defined(WEBRTC_MAC)
+#include "test/testsupport/mac_file_utils.h"
+#endif
+
+#include "absl/strings/string_view.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/string_utils.h"
+#include "rtc_base/strings/string_builder.h"
+#include "test/testsupport/file_utils_override.h"
+
+namespace webrtc {
+namespace test {
+
+#if defined(WEBRTC_WIN)
+ABSL_CONST_INIT const absl::string_view kPathDelimiter = "\\";
+#else
+ABSL_CONST_INIT const absl::string_view kPathDelimiter = "/";
+#endif
+
+std::string DirName(absl::string_view path) {
+ if (path.empty())
+ return "";
+ if (path == kPathDelimiter)
+ return std::string(path);
+
+ if (path.back() == kPathDelimiter[0])
+ path.remove_suffix(1); // Remove trailing separator.
+
+ return std::string(path.substr(0, path.find_last_of(kPathDelimiter)));
+}
+
+bool FileExists(absl::string_view file_name) {
+ struct stat file_info = {0};
+ return stat(std::string(file_name).c_str(), &file_info) == 0;
+}
+
+bool DirExists(absl::string_view directory_name) {
+ struct stat directory_info = {0};
+ return stat(std::string(directory_name).c_str(), &directory_info) == 0 &&
+ S_ISDIR(directory_info.st_mode);
+}
+
+std::string OutputPath() {
+ return webrtc::test::internal::OutputPath();
+}
+
+std::string WorkingDir() {
+ return webrtc::test::internal::WorkingDir();
+}
+
+// Generate a temporary filename in a safe way.
+// Largely copied from talk/base/{unixfilesystem,win32filesystem}.cc.
+std::string TempFilename(absl::string_view dir, absl::string_view prefix) {
+#ifdef WIN32
+ wchar_t filename[MAX_PATH];
+ if (::GetTempFileNameW(rtc::ToUtf16(dir).c_str(),
+ rtc::ToUtf16(prefix).c_str(), 0, filename) != 0)
+ return rtc::ToUtf8(filename);
+ RTC_DCHECK_NOTREACHED();
+ return "";
+#else
+ rtc::StringBuilder os;
+ os << dir << "/" << prefix << "XXXXXX";
+ std::string tempname = os.Release();
+
+ int fd = ::mkstemp(tempname.data());
+ if (fd == -1) {
+ RTC_DCHECK_NOTREACHED();
+ return "";
+ } else {
+ ::close(fd);
+ }
+ return tempname;
+#endif
+}
+
+std::string GenerateTempFilename(absl::string_view dir,
+ absl::string_view prefix) {
+ std::string filename = TempFilename(dir, prefix);
+ RemoveFile(filename);
+ return filename;
+}
+
+absl::optional<std::vector<std::string>> ReadDirectory(absl::string_view path) {
+ if (path.length() == 0)
+ return absl::optional<std::vector<std::string>>();
+
+ std::string path_str(path);
+
+#if defined(WEBRTC_WIN)
+ // Append separator character if needed.
+ if (path_str.back() != '\\')
+ path_str += '\\';
+
+ // Init.
+ WIN32_FIND_DATAW data;
+ HANDLE handle = ::FindFirstFileW(rtc::ToUtf16(path_str + '*').c_str(), &data);
+ if (handle == INVALID_HANDLE_VALUE)
+ return absl::optional<std::vector<std::string>>();
+
+ // Populate output.
+ std::vector<std::string> found_entries;
+ do {
+ const std::string name = rtc::ToUtf8(data.cFileName);
+ if (name != "." && name != "..")
+ found_entries.emplace_back(path_str + name);
+ } while (::FindNextFileW(handle, &data) == TRUE);
+
+ // Release resources.
+ if (handle != INVALID_HANDLE_VALUE)
+ ::FindClose(handle);
+#else
+ // Append separator character if needed.
+ if (path_str.back() != '/')
+ path_str += '/';
+
+ // Init.
+ DIR* dir = ::opendir(path_str.c_str());
+ if (dir == nullptr)
+ return absl::optional<std::vector<std::string>>();
+
+ // Populate output.
+ std::vector<std::string> found_entries;
+ while (dirent* dirent = readdir(dir)) {
+ const std::string& name = dirent->d_name;
+ if (name != "." && name != "..")
+ found_entries.emplace_back(path_str + name);
+ }
+
+ // Release resources.
+ closedir(dir);
+#endif
+
+ return absl::optional<std::vector<std::string>>(std::move(found_entries));
+}
+
+bool CreateDir(absl::string_view directory_name) {
+ std::string directory_name_str(directory_name);
+ struct stat path_info = {0};
+ // Check if the path exists already:
+ if (stat(directory_name_str.c_str(), &path_info) == 0) {
+ if (!S_ISDIR(path_info.st_mode)) {
+ fprintf(stderr,
+ "Path %s exists but is not a directory! Remove this "
+ "file and re-run to create the directory.\n",
+ directory_name_str.c_str());
+ return false;
+ }
+ } else {
+#ifdef WIN32
+ return _mkdir(directory_name_str.c_str()) == 0;
+#else
+ return mkdir(directory_name_str.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0;
+#endif
+ }
+ return true;
+}
+
+bool RemoveDir(absl::string_view directory_name) {
+#ifdef WIN32
+ return RemoveDirectoryA(std::string(directory_name).c_str()) != FALSE;
+#else
+ return rmdir(std::string(directory_name).c_str()) == 0;
+#endif
+}
+
+bool RemoveFile(absl::string_view file_name) {
+#ifdef WIN32
+ return DeleteFileA(std::string(file_name).c_str()) != FALSE;
+#else
+ return unlink(std::string(file_name).c_str()) == 0;
+#endif
+}
+
+std::string ResourcePath(absl::string_view name, absl::string_view extension) {
+ return webrtc::test::internal::ResourcePath(name, extension);
+}
+
+std::string JoinFilename(absl::string_view dir, absl::string_view name) {
+ RTC_CHECK(!dir.empty()) << "Special cases not implemented.";
+ rtc::StringBuilder os;
+ os << dir << kPathDelimiter << name;
+ return os.Release();
+}
+
+size_t GetFileSize(absl::string_view filename) {
+ FILE* f = fopen(std::string(filename).c_str(), "rb");
+ size_t size = 0;
+ if (f != NULL) {
+ if (fseek(f, 0, SEEK_END) == 0) {
+ size = ftell(f);
+ }
+ fclose(f);
+ }
+ return size;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/file_utils.h b/third_party/libwebrtc/test/testsupport/file_utils.h
new file mode 100644
index 0000000000..ab80ca4454
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/file_utils.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2011 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 <stdio.h>
+
+#ifndef TEST_TESTSUPPORT_FILE_UTILS_H_
+#define TEST_TESTSUPPORT_FILE_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+namespace webrtc {
+namespace test {
+
+// Slash or backslash, depending on platform.
+ABSL_CONST_INIT extern const absl::string_view kPathDelimiter;
+
+// Returns the absolute path to the output directory where log files and other
+// test artifacts should be put. The output directory is generally a directory
+// named "out" at the project root. This root is assumed to be two levels above
+// where the test binary is located; this is because tests execute in a dir
+// out/Whatever relative to the project root. This convention is also followed
+// in Chromium.
+//
+// The exception is Android where we use /sdcard/ instead.
+//
+// If symbolic links occur in the path they will be resolved and the actual
+// directory will be returned.
+//
+// Returns the path WITH a trailing path delimiter. If the project root is not
+// found, the current working directory ("./") is returned as a fallback.
+std::string OutputPath();
+
+// Generates an empty file with a unique name in the specified directory and
+// returns the file name and path.
+// TODO(titovartem) rename to TempFile and next method to TempFilename
+std::string TempFilename(absl::string_view dir, absl::string_view prefix);
+
+// Generates a unique file name that can be used for file creation. Doesn't
+// create any files.
+std::string GenerateTempFilename(absl::string_view dir,
+ absl::string_view prefix);
+
+// Returns a path to a resource file in [project-root]/resources/ dir.
+// Returns an absolute path
+//
+// Arguments:
+// name - Name of the resource file. If a plain filename (no directory path)
+// is supplied, the file is assumed to be located in resources/
+// If a directory path is prepended to the filename, a subdirectory
+// hierarchy reflecting that path is assumed to be present.
+// extension - File extension, without the dot, i.e. "bmp" or "yuv".
+std::string ResourcePath(absl::string_view name, absl::string_view extension);
+
+// Joins directory name and file name, separated by the path delimiter.
+std::string JoinFilename(absl::string_view dir, absl::string_view name);
+
+// Gets the current working directory for the executing program.
+// Returns "./" if for some reason it is not possible to find the working
+// directory.
+std::string WorkingDir();
+
+// Reads the content of a directory and, in case of success, returns a vector
+// of strings with one element for each found file or directory. Each element is
+// a path created by prepending `dir` to the file/directory name. "." and ".."
+// are never added in the returned vector.
+absl::optional<std::vector<std::string>> ReadDirectory(absl::string_view path);
+
+// Creates a directory if it not already exists.
+// Returns true if successful. Will print an error message to stderr and return
+// false if a file with the same name already exists.
+bool CreateDir(absl::string_view directory_name);
+
+// Removes a directory, which must already be empty.
+bool RemoveDir(absl::string_view directory_name);
+
+// Removes a file.
+bool RemoveFile(absl::string_view file_name);
+
+// Checks if a file exists.
+// TOOD(alito): Merge these once absl::string_view adoption is complete for this
+// file.
+bool FileExists(absl::string_view file_name);
+
+// Checks if a directory exists.
+bool DirExists(absl::string_view directory_name);
+
+// Strips the rightmost path segment from a path.
+std::string DirName(absl::string_view path);
+
+// File size of the supplied file in bytes. Will return 0 if the file is
+// empty or if the file does not exist/is readable.
+size_t GetFileSize(absl::string_view filename);
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_FILE_UTILS_H_
diff --git a/third_party/libwebrtc/test/testsupport/file_utils_override.cc b/third_party/libwebrtc/test/testsupport/file_utils_override.cc
new file mode 100644
index 0000000000..7d0a3e3312
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/file_utils_override.cc
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2018 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/file_utils_override.h"
+
+#include <limits.h>
+#include <stdio.h>
+
+#if defined(WEBRTC_WIN)
+#include <direct.h>
+#include <tchar.h>
+#include <windows.h>
+
+#include <algorithm>
+#include <codecvt>
+#include <locale>
+
+#include "Shlwapi.h"
+#include "WinDef.h"
+#include "rtc_base/win32.h"
+
+#define GET_CURRENT_DIR _getcwd
+#else
+#include <unistd.h>
+
+#define GET_CURRENT_DIR getcwd
+#endif
+
+#if defined(WEBRTC_IOS)
+#include "test/testsupport/ios_file_utils.h"
+#endif
+
+#if defined(WEBRTC_MAC)
+#include "test/testsupport/mac_file_utils.h"
+#endif
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "rtc_base/arraysize.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/string_utils.h"
+#include "rtc_base/strings/string_builder.h"
+
+namespace webrtc {
+namespace test {
+
+std::string DirName(absl::string_view path);
+bool CreateDir(absl::string_view directory_name);
+
+namespace internal {
+
+namespace {
+#if defined(WEBRTC_WIN)
+const absl::string_view kPathDelimiter = "\\";
+#elif !defined(WEBRTC_IOS)
+const absl::string_view kPathDelimiter = "/";
+#endif
+
+#if defined(WEBRTC_ANDROID)
+// This is a special case in Chrome infrastructure. See
+// base/test/test_support_android.cc.
+const absl::string_view kAndroidChromiumTestsRoot =
+ "/sdcard/chromium_tests_root/";
+#endif
+#if defined(WEBRTC_FUCHSIA)
+const absl::string_view kFuchsiaTestRoot = "/pkg/";
+const absl::string_view kFuchsiaTempWritableDir = "/tmp/";
+#endif
+#if !defined(WEBRTC_IOS)
+const absl::string_view kResourcesDirName = "resources";
+#endif
+
+} // namespace
+
+// Finds the WebRTC src dir.
+// The returned path always ends with a path separator.
+absl::optional<std::string> ProjectRootPath() {
+#if defined(WEBRTC_ANDROID)
+ return std::string(kAndroidChromiumTestsRoot);
+#elif defined WEBRTC_IOS
+ return IOSRootPath();
+#elif defined(WEBRTC_MAC)
+ std::string path;
+ GetNSExecutablePath(&path);
+ std::string exe_dir = DirName(path);
+ // On Mac, tests execute in out/Whatever, so src is two levels up except if
+ // the test is bundled (which our tests are not), in which case it's 5 levels.
+ return DirName(DirName(exe_dir)) + std::string(kPathDelimiter);
+#elif defined(WEBRTC_POSIX)
+// Fuchsia uses POSIX defines as well but does not have full POSIX
+// functionality.
+#if defined(WEBRTC_FUCHSIA)
+ return std::string(kFuchsiaTestRoot);
+#else
+ char buf[PATH_MAX];
+ ssize_t count = ::readlink("/proc/self/exe", buf, arraysize(buf));
+ if (count <= 0) {
+ RTC_DCHECK_NOTREACHED() << "Unable to resolve /proc/self/exe.";
+ return absl::nullopt;
+ }
+ // On POSIX, tests execute in out/Whatever, so src is two levels up.
+ std::string exe_dir = DirName(absl::string_view(buf, count));
+ return DirName(DirName(exe_dir)) + std::string(kPathDelimiter);
+#endif
+#elif defined(WEBRTC_WIN)
+ wchar_t buf[MAX_PATH];
+ buf[0] = 0;
+ if (GetModuleFileNameW(NULL, buf, MAX_PATH) == 0)
+ return absl::nullopt;
+
+ std::string exe_path = rtc::ToUtf8(std::wstring(buf));
+ std::string exe_dir = DirName(exe_path);
+ return DirName(DirName(exe_dir)) + std::string(kPathDelimiter);
+#endif
+}
+
+std::string OutputPath() {
+#if defined(WEBRTC_IOS)
+ return IOSOutputPath();
+#elif defined(WEBRTC_ANDROID)
+ return std::string(kAndroidChromiumTestsRoot);
+#elif defined(WEBRTC_FUCHSIA)
+ return std::string(kFuchsiaTempWritableDir);
+#else
+ absl::optional<std::string> path_opt = ProjectRootPath();
+ RTC_DCHECK(path_opt);
+ std::string path = *path_opt + "out";
+ if (!CreateDir(path)) {
+ return "./";
+ }
+ return path + std::string(kPathDelimiter);
+#endif
+}
+
+std::string WorkingDir() {
+#if defined(WEBRTC_ANDROID)
+ return std::string(kAndroidChromiumTestsRoot);
+#else
+ char path_buffer[FILENAME_MAX];
+ if (!GET_CURRENT_DIR(path_buffer, sizeof(path_buffer))) {
+ fprintf(stderr, "Cannot get current directory!\n");
+ return "./";
+ } else {
+ return std::string(path_buffer);
+ }
+#endif
+}
+
+std::string ResourcePath(absl::string_view name, absl::string_view extension) {
+#if defined(WEBRTC_IOS)
+ return IOSResourcePath(name, extension);
+#else
+ absl::optional<std::string> path_opt = ProjectRootPath();
+ RTC_DCHECK(path_opt);
+ rtc::StringBuilder os(*path_opt);
+ os << kResourcesDirName << kPathDelimiter << name << "." << extension;
+ return os.Release();
+#endif
+}
+
+} // namespace internal
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/file_utils_override.h b/third_party/libwebrtc/test/testsupport/file_utils_override.h
new file mode 100644
index 0000000000..9f119e6d4e
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/file_utils_override.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018 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_FILE_UTILS_OVERRIDE_H_
+#define TEST_TESTSUPPORT_FILE_UTILS_OVERRIDE_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+namespace webrtc {
+namespace test {
+namespace internal {
+
+// Returns the absolute path to the output directory where log files and other
+// test artifacts should be put. The output directory is generally a directory
+// named "out" at the project root. This root is assumed to be two levels above
+// where the test binary is located; this is because tests execute in a dir
+// out/Whatever relative to the project root. This convention is also followed
+// in Chromium.
+//
+// The exception is Android where we use /sdcard/ instead.
+//
+// If symbolic links occur in the path they will be resolved and the actual
+// directory will be returned.
+//
+// Returns the path WITH a trailing path delimiter. If the project root is not
+// found, the current working directory ("./") is returned as a fallback.
+std::string OutputPath();
+
+// Gets the current working directory for the executing program.
+// Returns "./" if for some reason it is not possible to find the working
+// directory.
+std::string WorkingDir();
+
+// Returns a full path to a resource file in the resources_dir dir.
+//
+// Arguments:
+// name - Name of the resource file. If a plain filename (no directory path)
+// is supplied, the file is assumed to be located in resources/
+// If a directory path is prepended to the filename, a subdirectory
+// hierarchy reflecting that path is assumed to be present.
+// extension - File extension, without the dot, i.e. "bmp" or "yuv".
+std::string ResourcePath(absl::string_view name, absl::string_view extension);
+
+} // namespace internal
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_FILE_UTILS_OVERRIDE_H_
diff --git a/third_party/libwebrtc/test/testsupport/file_utils_unittest.cc b/third_party/libwebrtc/test/testsupport/file_utils_unittest.cc
new file mode 100644
index 0000000000..b9de01d09d
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/file_utils_unittest.cc
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2012 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/file_utils.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "rtc_base/checks.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+#ifdef WIN32
+#define chdir _chdir
+#endif
+
+using ::testing::EndsWith;
+
+namespace webrtc {
+namespace test {
+
+namespace {
+
+std::string Path(absl::string_view path) {
+ std::string result(path);
+ std::replace(result.begin(), result.end(), '/', kPathDelimiter[0]);
+ return result;
+}
+
+// Remove files and directories in a directory non-recursively and writes the
+// number of deleted items in `num_deleted_entries`.
+void CleanDir(absl::string_view dir, size_t* num_deleted_entries) {
+ RTC_DCHECK(num_deleted_entries);
+ *num_deleted_entries = 0;
+ absl::optional<std::vector<std::string>> dir_content = ReadDirectory(dir);
+ EXPECT_TRUE(dir_content);
+ for (const auto& entry : *dir_content) {
+ if (DirExists(entry)) {
+ EXPECT_TRUE(RemoveDir(entry));
+ (*num_deleted_entries)++;
+ } else if (FileExists(entry)) {
+ EXPECT_TRUE(RemoveFile(entry));
+ (*num_deleted_entries)++;
+ } else {
+ FAIL();
+ }
+ }
+}
+
+void WriteStringInFile(absl::string_view what, absl::string_view file_path) {
+ std::ofstream out(std::string{file_path});
+ out << what;
+ out.close();
+}
+
+} // namespace
+
+// Test fixture to restore the working directory between each test, since some
+// of them change it with chdir during execution (not restored by the
+// gtest framework).
+class FileUtilsTest : public ::testing::Test {
+ protected:
+ FileUtilsTest() {}
+ ~FileUtilsTest() override {}
+ // Runs before the first test
+ static void SetUpTestSuite() {
+ original_working_dir_ = webrtc::test::WorkingDir();
+ }
+ void SetUp() override { ASSERT_EQ(chdir(original_working_dir_.c_str()), 0); }
+ void TearDown() override {
+ ASSERT_EQ(chdir(original_working_dir_.c_str()), 0);
+ }
+
+ private:
+ static std::string original_working_dir_;
+};
+
+std::string FileUtilsTest::original_working_dir_ = "";
+
+// The location will vary depending on where the webrtc checkout is on the
+// system, but it should end as described above and be an absolute path.
+std::string ExpectedRootDirByPlatform() {
+#if defined(WEBRTC_ANDROID)
+ return Path("chromium_tests_root/");
+#elif defined(WEBRTC_IOS)
+ return Path("tmp/");
+#else
+ return Path("out/");
+#endif
+}
+
+TEST_F(FileUtilsTest, OutputPathFromUnchangedWorkingDir) {
+ std::string expected_end = ExpectedRootDirByPlatform();
+ std::string result = webrtc::test::OutputPath();
+
+ ASSERT_THAT(result, EndsWith(expected_end));
+}
+
+// Tests with current working directory set to a directory higher up in the
+// directory tree than the project root dir.
+TEST_F(FileUtilsTest, OutputPathFromRootWorkingDir) {
+ ASSERT_EQ(0, chdir(kPathDelimiter.data()));
+
+ std::string expected_end = ExpectedRootDirByPlatform();
+ std::string result = webrtc::test::OutputPath();
+
+ ASSERT_THAT(result, EndsWith(expected_end));
+}
+
+TEST_F(FileUtilsTest, TempFilename) {
+ std::string temp_filename = webrtc::test::TempFilename(
+ webrtc::test::OutputPath(), "TempFilenameTest");
+ ASSERT_TRUE(webrtc::test::FileExists(temp_filename))
+ << "Couldn't find file: " << temp_filename;
+ remove(temp_filename.c_str());
+}
+
+TEST_F(FileUtilsTest, GenerateTempFilename) {
+ std::string temp_filename = webrtc::test::GenerateTempFilename(
+ webrtc::test::OutputPath(), "TempFilenameTest");
+ ASSERT_FALSE(webrtc::test::FileExists(temp_filename))
+ << "File exists: " << temp_filename;
+ FILE* file = fopen(temp_filename.c_str(), "wb");
+ ASSERT_TRUE(file != NULL) << "Failed to open file: " << temp_filename;
+ ASSERT_GT(fprintf(file, "%s", "Dummy data"), 0)
+ << "Failed to write to file: " << temp_filename;
+ fclose(file);
+ remove(temp_filename.c_str());
+}
+
+// Only tests that the code executes
+#if defined(WEBRTC_IOS)
+#define MAYBE_CreateDir DISABLED_CreateDir
+#else
+#define MAYBE_CreateDir CreateDir
+#endif
+TEST_F(FileUtilsTest, MAYBE_CreateDir) {
+ std::string directory = "fileutils-unittest-empty-dir";
+ // Make sure it's removed if a previous test has failed:
+ remove(directory.c_str());
+ ASSERT_TRUE(webrtc::test::CreateDir(directory));
+ remove(directory.c_str());
+}
+
+TEST_F(FileUtilsTest, WorkingDirReturnsValue) {
+ // This will obviously be different depending on where the webrtc checkout is,
+ // so just check something is returned.
+ std::string working_dir = webrtc::test::WorkingDir();
+ ASSERT_GT(working_dir.length(), 0u);
+}
+
+TEST_F(FileUtilsTest, ResourcePathReturnsCorrectPath) {
+ std::string result = webrtc::test::ResourcePath(
+ Path("video_coding/frame-ethernet-ii"), "pcap");
+#if defined(WEBRTC_IOS)
+ // iOS bundles resources straight into the bundle root.
+ std::string expected_end = Path("/frame-ethernet-ii.pcap");
+#else
+ // Other platforms: it's a separate dir.
+ std::string expected_end =
+ Path("resources/video_coding/frame-ethernet-ii.pcap");
+#endif
+
+ ASSERT_THAT(result, EndsWith(expected_end));
+ ASSERT_TRUE(FileExists(result)) << "Expected " << result
+ << " to exist; did "
+ "ResourcePath return an incorrect path?";
+}
+
+TEST_F(FileUtilsTest, ResourcePathFromRootWorkingDir) {
+ ASSERT_EQ(0, chdir(kPathDelimiter.data()));
+ std::string resource = webrtc::test::ResourcePath("whatever", "ext");
+#if !defined(WEBRTC_IOS)
+ ASSERT_NE(resource.find("resources"), std::string::npos);
+#endif
+ ASSERT_GT(resource.find("whatever"), 0u);
+ ASSERT_GT(resource.find("ext"), 0u);
+}
+
+TEST_F(FileUtilsTest, GetFileSizeExistingFile) {
+ // Create a file with some dummy data in.
+ std::string temp_filename = webrtc::test::TempFilename(
+ webrtc::test::OutputPath(), "fileutils_unittest");
+ FILE* file = fopen(temp_filename.c_str(), "wb");
+ ASSERT_TRUE(file != NULL) << "Failed to open file: " << temp_filename;
+ ASSERT_GT(fprintf(file, "%s", "Dummy data"), 0)
+ << "Failed to write to file: " << temp_filename;
+ fclose(file);
+ ASSERT_GT(webrtc::test::GetFileSize(temp_filename), 0u);
+ remove(temp_filename.c_str());
+}
+
+TEST_F(FileUtilsTest, GetFileSizeNonExistingFile) {
+ ASSERT_EQ(0u, webrtc::test::GetFileSize("non-existing-file.tmp"));
+}
+
+TEST_F(FileUtilsTest, DirExists) {
+ // Check that an existing directory is recognized as such.
+ ASSERT_TRUE(webrtc::test::DirExists(webrtc::test::OutputPath()))
+ << "Existing directory not found";
+
+ // Check that a non-existing directory is recognized as such.
+ std::string directory = "direxists-unittest-non_existing-dir";
+ ASSERT_FALSE(webrtc::test::DirExists(directory))
+ << "Non-existing directory found";
+
+ // Check that an existing file is not recognized as an existing directory.
+ std::string temp_filename = webrtc::test::TempFilename(
+ webrtc::test::OutputPath(), "TempFilenameTest");
+ ASSERT_TRUE(webrtc::test::FileExists(temp_filename))
+ << "Couldn't find file: " << temp_filename;
+ ASSERT_FALSE(webrtc::test::DirExists(temp_filename))
+ << "Existing file recognized as existing directory";
+ remove(temp_filename.c_str());
+}
+
+TEST_F(FileUtilsTest, WriteReadDeleteFilesAndDirs) {
+ size_t num_deleted_entries;
+
+ // Create an empty temporary directory for this test.
+ const std::string temp_directory =
+ OutputPath() + Path("TempFileUtilsTestReadDirectory/");
+ CreateDir(temp_directory);
+ EXPECT_NO_FATAL_FAILURE(CleanDir(temp_directory, &num_deleted_entries));
+ EXPECT_TRUE(DirExists(temp_directory));
+
+ // Add a file.
+ const std::string temp_filename = temp_directory + "TempFilenameTest";
+ WriteStringInFile("test\n", temp_filename);
+ EXPECT_TRUE(FileExists(temp_filename));
+
+ // Add an empty directory.
+ const std::string temp_subdir = temp_directory + Path("subdir/");
+ EXPECT_TRUE(CreateDir(temp_subdir));
+ EXPECT_TRUE(DirExists(temp_subdir));
+
+ // Checks.
+ absl::optional<std::vector<std::string>> dir_content =
+ ReadDirectory(temp_directory);
+ EXPECT_TRUE(dir_content);
+ EXPECT_EQ(2u, dir_content->size());
+ EXPECT_NO_FATAL_FAILURE(CleanDir(temp_directory, &num_deleted_entries));
+ EXPECT_EQ(2u, num_deleted_entries);
+ EXPECT_TRUE(RemoveDir(temp_directory));
+ EXPECT_FALSE(DirExists(temp_directory));
+}
+
+TEST_F(FileUtilsTest, DirNameStripsFilename) {
+ EXPECT_EQ(Path("/some/path"), DirName(Path("/some/path/file.txt")));
+}
+
+TEST_F(FileUtilsTest, DirNameKeepsStrippingRightmostPathComponent) {
+ EXPECT_EQ(Path("/some"), DirName(DirName(Path("/some/path/file.txt"))));
+}
+
+TEST_F(FileUtilsTest, DirNameDoesntCareIfAPathEndsInPathSeparator) {
+ EXPECT_EQ(Path("/some"), DirName(Path("/some/path/")));
+}
+
+TEST_F(FileUtilsTest, DirNameStopsAtRoot) {
+ EXPECT_EQ(Path("/"), DirName(Path("/")));
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.cc b/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.cc
new file mode 100644
index 0000000000..531dade0e8
--- /dev/null
+++ b/third_party/libwebrtc/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/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.h b/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.h
new file mode 100644
index 0000000000..d4d95e9f82
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter.h
@@ -0,0 +1,87 @@
+/*
+ * 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/test/video/video_frame_writer.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/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter_test.cc b/third_party/libwebrtc/test/testsupport/fixed_fps_video_frame_writer_adapter_test.cc
new file mode 100644
index 0000000000..5ee4701cc9
--- /dev/null
+++ b/third_party/libwebrtc/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
diff --git a/third_party/libwebrtc/test/testsupport/frame_reader.h b/third_party/libwebrtc/test/testsupport/frame_reader.h
new file mode 100644
index 0000000000..7856476ca0
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/frame_reader.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2011 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_FRAME_READER_H_
+#define TEST_TESTSUPPORT_FRAME_READER_H_
+
+#include <stdio.h>
+
+#include <string>
+
+#include "absl/types/optional.h"
+#include "api/scoped_refptr.h"
+#include "api/video/resolution.h"
+
+namespace webrtc {
+class I420Buffer;
+namespace test {
+
+// Handles reading of I420 frames from video files.
+class FrameReader {
+ public:
+ struct Ratio {
+ int num = 1;
+ int den = 1;
+ };
+
+ static constexpr Ratio kNoScale = Ratio({.num = 1, .den = 1});
+
+ virtual ~FrameReader() {}
+
+ // Reads and returns next frame. Returns `nullptr` if reading failed or end of
+ // stream is reached.
+ virtual rtc::scoped_refptr<I420Buffer> PullFrame() = 0;
+
+ // Reads and returns next frame. `frame_num` stores unwrapped frame number
+ // which can be passed to `ReadFrame` to re-read this frame later. Returns
+ // `nullptr` if reading failed or end of stream is reached.
+ virtual rtc::scoped_refptr<I420Buffer> PullFrame(int* frame_num) = 0;
+
+ // Reads and returns frame specified by `frame_num`. Returns `nullptr` if
+ // reading failed.
+ virtual rtc::scoped_refptr<I420Buffer> ReadFrame(int frame_num) = 0;
+
+ // Reads next frame, resizes and returns it. `frame_num` stores unwrapped
+ // frame number which can be passed to `ReadFrame` to re-read this frame
+ // later. `resolution` specifies resolution of the returned frame.
+ // `framerate_scale` specifies frame rate scale factor. Frame rate scaling is
+ // done by skipping or repeating frames.
+ virtual rtc::scoped_refptr<I420Buffer> PullFrame(int* frame_num,
+ Resolution resolution,
+ Ratio framerate_scale) = 0;
+
+ // Reads frame specified by `frame_num`, resizes and returns it. Returns
+ // `nullptr` if reading failed.
+ virtual rtc::scoped_refptr<I420Buffer> ReadFrame(int frame_num,
+ Resolution resolution) = 0;
+
+ // Total number of retrievable frames.
+ virtual int num_frames() const = 0;
+};
+
+class YuvFrameReaderImpl : public FrameReader {
+ public:
+ enum class RepeatMode { kSingle, kRepeat, kPingPong };
+
+ // Creates the frame reader for a YUV file specified by `filepath`.
+ // `resolution` specifies width and height of frames in pixels. `repeat_mode`
+ // specifies behaviour of the reader at reaching the end of file (stop, read
+ // it over from the beginning or read in reverse order). The file is assumed
+ // to exist, be readable and to contain at least 1 frame.
+ YuvFrameReaderImpl(std::string filepath,
+ Resolution resolution,
+ RepeatMode repeat_mode);
+
+ ~YuvFrameReaderImpl() override;
+
+ virtual void Init();
+
+ rtc::scoped_refptr<I420Buffer> PullFrame() override;
+
+ rtc::scoped_refptr<I420Buffer> PullFrame(int* frame_num) override;
+
+ rtc::scoped_refptr<I420Buffer> PullFrame(int* frame_num,
+ Resolution resolution,
+ Ratio framerate_scale) override;
+
+ rtc::scoped_refptr<I420Buffer> ReadFrame(int frame_num) override;
+
+ rtc::scoped_refptr<I420Buffer> ReadFrame(int frame_num,
+ Resolution resolution) override;
+
+ int num_frames() const override { return num_frames_; }
+
+ protected:
+ class RateScaler {
+ public:
+ int Skip(Ratio framerate_scale);
+
+ private:
+ absl::optional<int> ticks_;
+ };
+
+ const std::string filepath_;
+ Resolution resolution_;
+ const RepeatMode repeat_mode_;
+ int num_frames_;
+ int frame_num_;
+ int frame_size_bytes_;
+ int header_size_bytes_;
+ FILE* file_;
+ RateScaler framerate_scaler_;
+};
+
+class Y4mFrameReaderImpl : public YuvFrameReaderImpl {
+ public:
+ // Creates the frame reader for a Y4M file specified by `filepath`.
+ // `repeat_mode` specifies behaviour of the reader at reaching the end of file
+ // (stop, read it over from the beginning or read in reverse order). The file
+ // is assumed to exist, be readable and to contain at least 1 frame.
+ Y4mFrameReaderImpl(std::string filepath, RepeatMode repeat_mode);
+
+ void Init() override;
+};
+
+std::unique_ptr<FrameReader> CreateYuvFrameReader(std::string filepath,
+ Resolution resolution);
+
+std::unique_ptr<FrameReader> CreateYuvFrameReader(
+ std::string filepath,
+ Resolution resolution,
+ YuvFrameReaderImpl::RepeatMode repeat_mode);
+
+std::unique_ptr<FrameReader> CreateY4mFrameReader(std::string filepath);
+
+std::unique_ptr<FrameReader> CreateY4mFrameReader(
+ std::string filepath,
+ YuvFrameReaderImpl::RepeatMode repeat_mode);
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_FRAME_READER_H_
diff --git a/third_party/libwebrtc/test/testsupport/frame_writer.h b/third_party/libwebrtc/test/testsupport/frame_writer.h
new file mode 100644
index 0000000000..5f85d8bcd4
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/frame_writer.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2012 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_FRAME_WRITER_H_
+#define TEST_TESTSUPPORT_FRAME_WRITER_H_
+
+#include <stdio.h>
+
+#include <string>
+
+#include "api/video/video_frame.h"
+
+namespace webrtc {
+namespace test {
+
+// Handles writing of video files.
+class FrameWriter {
+ public:
+ virtual ~FrameWriter() {}
+
+ // Initializes the file handler, i.e. opens the input and output files etc.
+ // This must be called before reading or writing frames has started.
+ // Returns false if an error has occurred, in addition to printing to stderr.
+ virtual bool Init() = 0;
+
+ // Writes a frame of the configured frame length to the output file.
+ // Returns true if the write was successful, false otherwise.
+ virtual bool WriteFrame(const uint8_t* frame_buffer) = 0;
+
+ // Closes the output file if open. Essentially makes this class impossible
+ // to use anymore. Will also be invoked by the destructor.
+ virtual void Close() = 0;
+
+ // Frame length in bytes of a single frame image.
+ virtual size_t FrameLength() = 0;
+};
+
+// Writes raw I420 frames in sequence.
+class YuvFrameWriterImpl : public FrameWriter {
+ public:
+ // Creates a file handler. The input file is assumed to exist and be readable
+ // and the output file must be writable.
+ // Parameters:
+ // output_filename The file to write. Will be overwritten if already
+ // existing.
+ // width, height Size of each frame to read.
+ YuvFrameWriterImpl(std::string output_filename, int width, int height);
+ ~YuvFrameWriterImpl() override;
+ bool Init() override;
+ bool WriteFrame(const uint8_t* frame_buffer) override;
+ void Close() override;
+ size_t FrameLength() override;
+
+ protected:
+ const std::string output_filename_;
+ size_t frame_length_in_bytes_;
+ const int width_;
+ const int height_;
+ FILE* output_file_;
+};
+
+// Writes raw I420 frames in sequence, but with Y4M file and frame headers for
+// more convenient playback in external media players.
+class Y4mFrameWriterImpl : public YuvFrameWriterImpl {
+ public:
+ Y4mFrameWriterImpl(std::string output_filename,
+ int width,
+ int height,
+ int frame_rate);
+ ~Y4mFrameWriterImpl() override;
+ bool Init() override;
+ bool WriteFrame(const uint8_t* frame_buffer) override;
+
+ private:
+ const int frame_rate_;
+};
+
+// LibJpeg is not available on iOS. This class will do nothing on iOS.
+class JpegFrameWriter {
+ public:
+ JpegFrameWriter(const std::string& output_filename);
+ // Quality can be from 0 (worst) to 100 (best). Best quality is still lossy.
+ // WriteFrame can be called only once. Subsequent calls will fail.
+ bool WriteFrame(const VideoFrame& input_frame, int quality);
+
+#if !defined(WEBRTC_IOS)
+ private:
+ bool frame_written_;
+ const std::string output_filename_;
+ FILE* output_file_;
+#endif
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_FRAME_WRITER_H_
diff --git a/third_party/libwebrtc/test/testsupport/ios_file_utils.h b/third_party/libwebrtc/test/testsupport/ios_file_utils.h
new file mode 100644
index 0000000000..49df3b9010
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/ios_file_utils.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2018 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_IOS_FILE_UTILS_H_
+#define TEST_TESTSUPPORT_IOS_FILE_UTILS_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+
+namespace webrtc {
+namespace test {
+
+std::string IOSOutputPath();
+std::string IOSRootPath();
+std::string IOSResourcePath(absl::string_view name,
+ absl::string_view extension);
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_IOS_FILE_UTILS_H_
diff --git a/third_party/libwebrtc/test/testsupport/ios_file_utils.mm b/third_party/libwebrtc/test/testsupport/ios_file_utils.mm
new file mode 100644
index 0000000000..ef36937e6a
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/ios_file_utils.mm
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 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.
+ */
+
+#if defined(WEBRTC_IOS)
+
+#import <Foundation/Foundation.h>
+#include <string.h>
+
+#import "sdk/objc/helpers/NSString+StdString.h"
+
+#include "absl/strings/string_view.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace test {
+
+// For iOS, resource files are added to the application bundle in the root
+// and not in separate folders as is the case for other platforms. This method
+// therefore removes any prepended folders and uses only the actual file name.
+std::string IOSResourcePath(absl::string_view name, absl::string_view extension) {
+ @autoreleasepool {
+ NSString* path = [NSString stringForAbslStringView:name];
+ NSString* fileName = path.lastPathComponent;
+ NSString* fileType = [NSString stringForAbslStringView:extension];
+ // Get full pathname for the resource identified by the name and extension.
+ NSString* pathString = [[NSBundle mainBundle] pathForResource:fileName
+ ofType:fileType];
+ return [NSString stdStringForString:pathString];
+ }
+}
+
+std::string IOSRootPath() {
+ @autoreleasepool {
+ NSBundle* mainBundle = [NSBundle mainBundle];
+ return [NSString stdStringForString:mainBundle.bundlePath] + "/";
+ }
+}
+
+// For iOS, we don't have access to the output directory. Return the path to the
+// temporary directory instead. This is mostly used by tests that need to write
+// output files to disk.
+std::string IOSOutputPath() {
+ @autoreleasepool {
+ NSString* tempDir = NSTemporaryDirectory();
+ if (tempDir == nil)
+ tempDir = @"/tmp";
+ return [NSString stdStringForString:tempDir];
+ }
+}
+
+} // namespace test
+} // namespace webrtc
+
+#endif // defined(WEBRTC_IOS)
diff --git a/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.cc b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.cc
new file mode 100644
index 0000000000..0dec1135f0
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2019 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/ivf_video_frame_generator.h"
+
+#include <limits>
+
+#include "api/video/encoded_image.h"
+#include "api/video/i420_buffer.h"
+#include "api/video_codecs/video_codec.h"
+#include "media/base/media_constants.h"
+#include "modules/video_coding/codecs/av1/dav1d_decoder.h"
+#include "modules/video_coding/codecs/h264/include/h264.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "modules/video_coding/codecs/vp9/include/vp9.h"
+#include "modules/video_coding/include/video_error_codes.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/system/file_wrapper.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+constexpr TimeDelta kMaxNextFrameWaitTimeout = TimeDelta::Seconds(1);
+
+} // namespace
+
+IvfVideoFrameGenerator::IvfVideoFrameGenerator(const std::string& file_name)
+ : callback_(this),
+ file_reader_(IvfFileReader::Create(FileWrapper::OpenReadOnly(file_name))),
+ video_decoder_(CreateVideoDecoder(file_reader_->GetVideoCodecType())),
+ width_(file_reader_->GetFrameWidth()),
+ height_(file_reader_->GetFrameHeight()) {
+ RTC_CHECK(video_decoder_) << "No decoder found for file's video codec type";
+ VideoDecoder::Settings decoder_settings;
+ decoder_settings.set_codec_type(file_reader_->GetVideoCodecType());
+ decoder_settings.set_max_render_resolution(
+ {file_reader_->GetFrameWidth(), file_reader_->GetFrameHeight()});
+ // Set buffer pool size to max value to ensure that if users of generator,
+ // ex. test frameworks, will retain frames for quite a long time, decoder
+ // won't crash with buffers pool overflow error.
+ decoder_settings.set_buffer_pool_size(std::numeric_limits<int>::max());
+ RTC_CHECK_EQ(video_decoder_->RegisterDecodeCompleteCallback(&callback_),
+ WEBRTC_VIDEO_CODEC_OK);
+ RTC_CHECK(video_decoder_->Configure(decoder_settings));
+}
+IvfVideoFrameGenerator::~IvfVideoFrameGenerator() {
+ MutexLock lock(&lock_);
+ if (!file_reader_) {
+ return;
+ }
+ file_reader_->Close();
+ file_reader_.reset();
+ // Reset decoder to prevent it from async access to `this`.
+ video_decoder_.reset();
+ {
+ MutexLock frame_lock(&frame_decode_lock_);
+ next_frame_ = absl::nullopt;
+ // Set event in case another thread is waiting on it.
+ next_frame_decoded_.Set();
+ }
+}
+
+FrameGeneratorInterface::VideoFrameData IvfVideoFrameGenerator::NextFrame() {
+ MutexLock lock(&lock_);
+ next_frame_decoded_.Reset();
+ RTC_CHECK(file_reader_);
+ if (!file_reader_->HasMoreFrames()) {
+ file_reader_->Reset();
+ }
+ absl::optional<EncodedImage> image = file_reader_->NextFrame();
+ RTC_CHECK(image);
+ // Last parameter is undocumented and there is no usage of it found.
+ RTC_CHECK_EQ(WEBRTC_VIDEO_CODEC_OK,
+ video_decoder_->Decode(*image, /*render_time_ms=*/0));
+ bool decoded = next_frame_decoded_.Wait(kMaxNextFrameWaitTimeout);
+ RTC_CHECK(decoded) << "Failed to decode next frame in "
+ << kMaxNextFrameWaitTimeout << ". Can't continue";
+
+ MutexLock frame_lock(&frame_decode_lock_);
+ rtc::scoped_refptr<VideoFrameBuffer> buffer =
+ next_frame_->video_frame_buffer();
+ if (width_ != static_cast<size_t>(buffer->width()) ||
+ height_ != static_cast<size_t>(buffer->height())) {
+ // Video adapter has requested a down-scale. Allocate a new buffer and
+ // return scaled version.
+ rtc::scoped_refptr<I420Buffer> scaled_buffer =
+ I420Buffer::Create(width_, height_);
+ scaled_buffer->ScaleFrom(*buffer->ToI420());
+ buffer = scaled_buffer;
+ }
+ return VideoFrameData(buffer, next_frame_->update_rect());
+}
+
+void IvfVideoFrameGenerator::ChangeResolution(size_t width, size_t height) {
+ MutexLock lock(&lock_);
+ width_ = width;
+ height_ = height;
+}
+
+FrameGeneratorInterface::Resolution IvfVideoFrameGenerator::GetResolution()
+ const {
+ return {.width = width_, .height = height_};
+}
+
+int32_t IvfVideoFrameGenerator::DecodedCallback::Decoded(
+ VideoFrame& decoded_image) {
+ Decoded(decoded_image, 0, 0);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+int32_t IvfVideoFrameGenerator::DecodedCallback::Decoded(
+ VideoFrame& decoded_image,
+ int64_t decode_time_ms) {
+ Decoded(decoded_image, decode_time_ms, 0);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+void IvfVideoFrameGenerator::DecodedCallback::Decoded(
+ VideoFrame& decoded_image,
+ absl::optional<int32_t> decode_time_ms,
+ absl::optional<uint8_t> qp) {
+ reader_->OnFrameDecoded(decoded_image);
+}
+
+void IvfVideoFrameGenerator::OnFrameDecoded(const VideoFrame& decoded_frame) {
+ MutexLock lock(&frame_decode_lock_);
+ next_frame_ = decoded_frame;
+ next_frame_decoded_.Set();
+}
+
+std::unique_ptr<VideoDecoder> IvfVideoFrameGenerator::CreateVideoDecoder(
+ VideoCodecType codec_type) {
+ if (codec_type == VideoCodecType::kVideoCodecVP8) {
+ return VP8Decoder::Create();
+ }
+ if (codec_type == VideoCodecType::kVideoCodecVP9) {
+ return VP9Decoder::Create();
+ }
+ if (codec_type == VideoCodecType::kVideoCodecH264) {
+ return H264Decoder::Create();
+ }
+ if (codec_type == VideoCodecType::kVideoCodecAV1) {
+ return CreateDav1dDecoder();
+ }
+ if (codec_type == VideoCodecType::kVideoCodecH265) {
+ // TODO(bugs.webrtc.org/13485): implement H265 decoder
+ }
+ return nullptr;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.h b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.h
new file mode 100644
index 0000000000..6c6fa4951d
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019 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_IVF_VIDEO_FRAME_GENERATOR_H_
+#define TEST_TESTSUPPORT_IVF_VIDEO_FRAME_GENERATOR_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/types/optional.h"
+#include "api/sequence_checker.h"
+#include "api/test/frame_generator_interface.h"
+#include "api/video/video_codec_type.h"
+#include "api/video/video_frame.h"
+#include "api/video_codecs/video_decoder.h"
+#include "modules/video_coding/utility/ivf_file_reader.h"
+#include "rtc_base/event.h"
+#include "rtc_base/synchronization/mutex.h"
+
+namespace webrtc {
+namespace test {
+
+// All methods except constructor must be used from the same thread.
+class IvfVideoFrameGenerator : public FrameGeneratorInterface {
+ public:
+ explicit IvfVideoFrameGenerator(const std::string& file_name);
+ ~IvfVideoFrameGenerator() override;
+
+ VideoFrameData NextFrame() override;
+ void ChangeResolution(size_t width, size_t height) override;
+ Resolution GetResolution() const override;
+
+ absl::optional<int> fps() const override { return absl::nullopt; }
+
+ private:
+ class DecodedCallback : public DecodedImageCallback {
+ public:
+ explicit DecodedCallback(IvfVideoFrameGenerator* reader)
+ : reader_(reader) {}
+
+ int32_t Decoded(VideoFrame& decoded_image) override;
+ int32_t Decoded(VideoFrame& decoded_image, int64_t decode_time_ms) override;
+ void Decoded(VideoFrame& decoded_image,
+ absl::optional<int32_t> decode_time_ms,
+ absl::optional<uint8_t> qp) override;
+
+ private:
+ IvfVideoFrameGenerator* const reader_;
+ };
+
+ void OnFrameDecoded(const VideoFrame& decoded_frame);
+ static std::unique_ptr<VideoDecoder> CreateVideoDecoder(
+ VideoCodecType codec_type);
+
+ DecodedCallback callback_;
+ std::unique_ptr<IvfFileReader> file_reader_;
+ std::unique_ptr<VideoDecoder> video_decoder_;
+
+ size_t width_;
+ size_t height_;
+
+ // This lock is used to ensure that all API method will be called
+ // sequentially. It is required because we need to ensure that generator
+ // won't be destroyed while it is reading the next frame on another thread,
+ // because it will cause SIGSEGV when decoder callback will be invoked.
+ //
+ // FrameGenerator is injected into PeerConnection via some scoped_ref object
+ // and it can happen that the last pointer will be destroyed on the different
+ // thread comparing to the one from which frames were read.
+ Mutex lock_;
+ // This lock is used to sync between sending and receiving frame from decoder.
+ // We can't reuse `lock_` because then generator can be destroyed between
+ // frame was sent to decoder and decoder callback was invoked.
+ Mutex frame_decode_lock_;
+
+ rtc::Event next_frame_decoded_;
+ absl::optional<VideoFrame> next_frame_ RTC_GUARDED_BY(frame_decode_lock_);
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_IVF_VIDEO_FRAME_GENERATOR_H_
diff --git a/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator_unittest.cc b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator_unittest.cc
new file mode 100644
index 0000000000..d6227b9986
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator_unittest.cc
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2019 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/ivf_video_frame_generator.h"
+
+#include <memory>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "api/test/create_frame_generator.h"
+#include "api/units/time_delta.h"
+#include "api/video/encoded_image.h"
+#include "api/video/video_codec_type.h"
+#include "api/video_codecs/video_codec.h"
+#include "api/video_codecs/video_encoder.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "media/base/codec.h"
+#include "media/base/media_constants.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "modules/video_coding/codecs/vp9/include/vp9.h"
+#include "modules/video_coding/include/video_error_codes.h"
+#include "modules/video_coding/utility/ivf_file_writer.h"
+#include "rtc_base/event.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/video_codec_settings.h"
+
+#if defined(WEBRTC_USE_H264)
+#include "modules/video_coding/codecs/h264/include/h264.h"
+#include "rtc_base/synchronization/mutex.h"
+
+#endif
+
+namespace webrtc {
+namespace test {
+namespace {
+
+constexpr int kWidth = 320;
+constexpr int kHeight = 240;
+constexpr int kVideoFramesCount = 30;
+constexpr int kMaxFramerate = 30;
+constexpr TimeDelta kMaxFrameEncodeWaitTimeout = TimeDelta::Seconds(2);
+static const VideoEncoder::Capabilities kCapabilities(false);
+
+#if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) || defined(WEBRTC_ARCH_ARM64)
+constexpr double kExpectedMinPsnr = 35;
+#else
+constexpr double kExpectedMinPsnr = 39;
+#endif
+
+class IvfFileWriterEncodedCallback : public EncodedImageCallback {
+ public:
+ IvfFileWriterEncodedCallback(const std::string& file_name,
+ VideoCodecType video_codec_type,
+ int expected_frames_count)
+ : file_writer_(
+ IvfFileWriter::Wrap(FileWrapper::OpenWriteOnly(file_name), 0)),
+ video_codec_type_(video_codec_type),
+ expected_frames_count_(expected_frames_count) {
+ EXPECT_TRUE(file_writer_.get());
+ }
+ ~IvfFileWriterEncodedCallback() { EXPECT_TRUE(file_writer_->Close()); }
+
+ Result OnEncodedImage(const EncodedImage& encoded_image,
+ const CodecSpecificInfo* codec_specific_info) override {
+ EXPECT_TRUE(file_writer_->WriteFrame(encoded_image, video_codec_type_));
+
+ MutexLock lock(&lock_);
+ received_frames_count_++;
+ RTC_CHECK_LE(received_frames_count_, expected_frames_count_);
+ if (received_frames_count_ == expected_frames_count_) {
+ expected_frames_count_received_.Set();
+ }
+ return Result(Result::Error::OK);
+ }
+
+ bool WaitForExpectedFramesReceived(TimeDelta timeout) {
+ return expected_frames_count_received_.Wait(timeout);
+ }
+
+ private:
+ std::unique_ptr<IvfFileWriter> file_writer_;
+ const VideoCodecType video_codec_type_;
+ const int expected_frames_count_;
+
+ Mutex lock_;
+ int received_frames_count_ RTC_GUARDED_BY(lock_) = 0;
+ rtc::Event expected_frames_count_received_;
+};
+
+class IvfVideoFrameGeneratorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ file_name_ =
+ webrtc::test::TempFilename(webrtc::test::OutputPath(), "test_file.ivf");
+ }
+ void TearDown() override { webrtc::test::RemoveFile(file_name_); }
+
+ VideoFrame BuildFrame(FrameGeneratorInterface::VideoFrameData frame_data) {
+ return VideoFrame::Builder()
+ .set_video_frame_buffer(frame_data.buffer)
+ .set_update_rect(frame_data.update_rect)
+ .build();
+ }
+
+ void CreateTestVideoFile(VideoCodecType video_codec_type,
+ std::unique_ptr<VideoEncoder> video_encoder) {
+ std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
+ test::CreateSquareFrameGenerator(
+ kWidth, kHeight, test::FrameGeneratorInterface::OutputType::kI420,
+ absl::nullopt);
+
+ VideoCodec codec_settings;
+ webrtc::test::CodecSettings(video_codec_type, &codec_settings);
+ codec_settings.width = kWidth;
+ codec_settings.height = kHeight;
+ codec_settings.maxFramerate = kMaxFramerate;
+ const uint32_t kBitrateBps = 500000;
+ VideoBitrateAllocation bitrate_allocation;
+ bitrate_allocation.SetBitrate(0, 0, kBitrateBps);
+
+ IvfFileWriterEncodedCallback ivf_writer_callback(
+ file_name_, video_codec_type, kVideoFramesCount);
+
+ video_encoder->RegisterEncodeCompleteCallback(&ivf_writer_callback);
+ video_encoder->SetRates(VideoEncoder::RateControlParameters(
+ bitrate_allocation, static_cast<double>(codec_settings.maxFramerate)));
+ ASSERT_EQ(WEBRTC_VIDEO_CODEC_OK,
+ video_encoder->InitEncode(
+ &codec_settings,
+ VideoEncoder::Settings(kCapabilities, /*number_of_cores=*/1,
+ /*max_payload_size=*/0)));
+
+ uint32_t last_frame_timestamp = 0;
+
+ for (int i = 0; i < kVideoFramesCount; ++i) {
+ VideoFrame frame = BuildFrame(frame_generator->NextFrame());
+ const uint32_t timestamp =
+ last_frame_timestamp +
+ kVideoPayloadTypeFrequency / codec_settings.maxFramerate;
+ frame.set_timestamp(timestamp);
+
+ last_frame_timestamp = timestamp;
+
+ ASSERT_EQ(WEBRTC_VIDEO_CODEC_OK, video_encoder->Encode(frame, nullptr));
+ video_frames_.push_back(frame);
+ }
+
+ ASSERT_TRUE(ivf_writer_callback.WaitForExpectedFramesReceived(
+ kMaxFrameEncodeWaitTimeout));
+ }
+
+ std::string file_name_;
+ std::vector<VideoFrame> video_frames_;
+};
+
+} // namespace
+
+TEST_F(IvfVideoFrameGeneratorTest, DoesNotKnowFps) {
+ CreateTestVideoFile(VideoCodecType::kVideoCodecVP8, VP8Encoder::Create());
+ IvfVideoFrameGenerator generator(file_name_);
+ EXPECT_EQ(generator.fps(), absl::nullopt);
+}
+
+TEST_F(IvfVideoFrameGeneratorTest, Vp8) {
+ CreateTestVideoFile(VideoCodecType::kVideoCodecVP8, VP8Encoder::Create());
+ IvfVideoFrameGenerator generator(file_name_);
+ for (size_t i = 0; i < video_frames_.size(); ++i) {
+ auto& expected_frame = video_frames_[i];
+ VideoFrame actual_frame = BuildFrame(generator.NextFrame());
+ EXPECT_GT(I420PSNR(&expected_frame, &actual_frame), kExpectedMinPsnr);
+ }
+}
+
+TEST_F(IvfVideoFrameGeneratorTest, Vp8DoubleRead) {
+ CreateTestVideoFile(VideoCodecType::kVideoCodecVP8, VP8Encoder::Create());
+ IvfVideoFrameGenerator generator(file_name_);
+ for (size_t i = 0; i < video_frames_.size() * 2; ++i) {
+ auto& expected_frame = video_frames_[i % video_frames_.size()];
+ VideoFrame actual_frame = BuildFrame(generator.NextFrame());
+ EXPECT_GT(I420PSNR(&expected_frame, &actual_frame), kExpectedMinPsnr);
+ }
+}
+
+TEST_F(IvfVideoFrameGeneratorTest, Vp9) {
+ CreateTestVideoFile(VideoCodecType::kVideoCodecVP9, VP9Encoder::Create());
+ IvfVideoFrameGenerator generator(file_name_);
+ for (size_t i = 0; i < video_frames_.size(); ++i) {
+ auto& expected_frame = video_frames_[i];
+ VideoFrame actual_frame = BuildFrame(generator.NextFrame());
+ EXPECT_GT(I420PSNR(&expected_frame, &actual_frame), kExpectedMinPsnr);
+ }
+}
+
+#if defined(WEBRTC_USE_H264)
+TEST_F(IvfVideoFrameGeneratorTest, H264) {
+ CreateTestVideoFile(VideoCodecType::kVideoCodecH264, H264Encoder::Create());
+ IvfVideoFrameGenerator generator(file_name_);
+ for (size_t i = 0; i < video_frames_.size(); ++i) {
+ auto& expected_frame = video_frames_[i];
+ VideoFrame actual_frame = BuildFrame(generator.NextFrame());
+ EXPECT_GT(I420PSNR(&expected_frame, &actual_frame), kExpectedMinPsnr);
+ }
+}
+#endif
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/jpeg_frame_writer.cc b/third_party/libwebrtc/test/testsupport/jpeg_frame_writer.cc
new file mode 100644
index 0000000000..8bf1ee4630
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/jpeg_frame_writer.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 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 <stdio.h>
+
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "test/testsupport/frame_writer.h"
+
+extern "C" {
+#if defined(USE_SYSTEM_LIBJPEG)
+#include <jpeglib.h>
+#else
+// Include directory supplied by gn
+#include "jpeglib.h" // NOLINT
+#endif
+}
+
+namespace webrtc {
+namespace test {
+
+JpegFrameWriter::JpegFrameWriter(const std::string& output_filename)
+ : frame_written_(false),
+ output_filename_(output_filename),
+ output_file_(nullptr) {}
+
+bool JpegFrameWriter::WriteFrame(const VideoFrame& input_frame, int quality) {
+ if (frame_written_) {
+ RTC_LOG(LS_ERROR) << "Only a single frame can be saved to Jpeg.";
+ return false;
+ }
+ const int kColorPlanes = 3; // R, G and B.
+ size_t rgb_len = input_frame.height() * input_frame.width() * kColorPlanes;
+ std::unique_ptr<uint8_t[]> rgb_buf(new uint8_t[rgb_len]);
+
+ // kRGB24 actually corresponds to FourCC 24BG which is 24-bit BGR.
+ if (ConvertFromI420(input_frame, VideoType::kRGB24, 0, rgb_buf.get()) < 0) {
+ RTC_LOG(LS_ERROR) << "Could not convert input frame to RGB.";
+ return false;
+ }
+ output_file_ = fopen(output_filename_.c_str(), "wb");
+ if (!output_file_) {
+ RTC_LOG(LS_ERROR) << "Couldn't open file to write jpeg frame to:"
+ << output_filename_;
+ return false;
+ }
+
+ // Invoking LIBJPEG
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPROW row_pointer[1];
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+
+ jpeg_stdio_dest(&cinfo, output_file_);
+
+ cinfo.image_width = input_frame.width();
+ cinfo.image_height = input_frame.height();
+ cinfo.input_components = kColorPlanes;
+ cinfo.in_color_space = JCS_EXT_BGR;
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE);
+
+ jpeg_start_compress(&cinfo, TRUE);
+ int row_stride = input_frame.width() * kColorPlanes;
+ while (cinfo.next_scanline < cinfo.image_height) {
+ row_pointer[0] = &rgb_buf.get()[cinfo.next_scanline * row_stride];
+ jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+ fclose(output_file_);
+
+ frame_written_ = true;
+ return true;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/jpeg_frame_writer_ios.cc b/third_party/libwebrtc/test/testsupport/jpeg_frame_writer_ios.cc
new file mode 100644
index 0000000000..e72fea102f
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/jpeg_frame_writer_ios.cc
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 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 "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+JpegFrameWriter::JpegFrameWriter(const std::string& /*output_filename*/) {}
+
+bool JpegFrameWriter::WriteFrame(const VideoFrame& /*input_frame*/,
+ int /*quality*/) {
+ RTC_LOG(LS_WARNING)
+ << "Libjpeg isn't available on IOS. Jpeg frame writer is not "
+ "supported. No frame will be saved.";
+ // Don't fail.
+ return true;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/mac_file_utils.h b/third_party/libwebrtc/test/testsupport/mac_file_utils.h
new file mode 100644
index 0000000000..c6cbdc580d
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/mac_file_utils.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2018 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_MAC_FILE_UTILS_H_
+#define TEST_TESTSUPPORT_MAC_FILE_UTILS_H_
+
+#include <string>
+
+namespace webrtc {
+namespace test {
+
+void GetNSExecutablePath(std::string* path);
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_MAC_FILE_UTILS_H_
diff --git a/third_party/libwebrtc/test/testsupport/mac_file_utils.mm b/third_party/libwebrtc/test/testsupport/mac_file_utils.mm
new file mode 100644
index 0000000000..270ecbc9a5
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/mac_file_utils.mm
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018 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.
+ */
+
+#import <Foundation/Foundation.h>
+#include <dlfcn.h>
+#include <mach-o/dyld.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace test {
+
+void GetNSExecutablePath(std::string* path) {
+ RTC_DCHECK(path);
+ // Executable path can have relative references ("..") depending on
+ // how the app was launched.
+ uint32_t executable_length = 0;
+ _NSGetExecutablePath(NULL, &executable_length);
+ RTC_DCHECK_GT(executable_length, 1u);
+ char executable_path[PATH_MAX + 1];
+ int rv = _NSGetExecutablePath(executable_path, &executable_length);
+ RTC_DCHECK_EQ(rv, 0);
+
+ char full_path[PATH_MAX];
+ if (realpath(executable_path, full_path) == nullptr) {
+ *path = "";
+ return;
+ }
+
+ *path = full_path;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/mock/mock_frame_reader.h b/third_party/libwebrtc/test/testsupport/mock/mock_frame_reader.h
new file mode 100644
index 0000000000..f68bbf8368
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/mock/mock_frame_reader.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2011 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_MOCK_MOCK_FRAME_READER_H_
+#define TEST_TESTSUPPORT_MOCK_MOCK_FRAME_READER_H_
+
+#include "api/video/i420_buffer.h"
+#include "test/gmock.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+
+class MockFrameReader : public FrameReader {
+ public:
+ MOCK_METHOD(rtc::scoped_refptr<I420Buffer>, PullFrame, (), (override));
+ MOCK_METHOD(rtc::scoped_refptr<I420Buffer>, PullFrame, (int*), (override));
+ MOCK_METHOD(rtc::scoped_refptr<I420Buffer>,
+ PullFrame,
+ (int*, Resolution, Ratio),
+ (override));
+ MOCK_METHOD(rtc::scoped_refptr<I420Buffer>, ReadFrame, (int), (override));
+ MOCK_METHOD(rtc::scoped_refptr<I420Buffer>,
+ ReadFrame,
+ (int, Resolution),
+ (override));
+ MOCK_METHOD(int, num_frames, (), (const override));
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_MOCK_MOCK_FRAME_READER_H_
diff --git a/third_party/libwebrtc/test/testsupport/perf_test.cc b/third_party/libwebrtc/test/testsupport/perf_test.cc
new file mode 100644
index 0000000000..bbea5f841a
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test.cc
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2012 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/perf_test.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <fstream>
+#include <set>
+#include <sstream>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "api/numerics/samples_stats_counter.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/perf_test_histogram_writer.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+
+std::string UnitWithDirection(
+ absl::string_view units,
+ webrtc::test::ImproveDirection improve_direction) {
+ switch (improve_direction) {
+ case webrtc::test::ImproveDirection::kNone:
+ return std::string(units);
+ case webrtc::test::ImproveDirection::kSmallerIsBetter:
+ return std::string(units) + "_smallerIsBetter";
+ case webrtc::test::ImproveDirection::kBiggerIsBetter:
+ return std::string(units) + "_biggerIsBetter";
+ }
+}
+
+std::vector<SamplesStatsCounter::StatsSample> GetSortedSamples(
+ const SamplesStatsCounter& counter) {
+ rtc::ArrayView<const SamplesStatsCounter::StatsSample> view =
+ counter.GetTimedSamples();
+ std::vector<SamplesStatsCounter::StatsSample> out(view.begin(), view.end());
+ std::stable_sort(out.begin(), out.end(),
+ [](const SamplesStatsCounter::StatsSample& a,
+ const SamplesStatsCounter::StatsSample& b) {
+ return a.time < b.time;
+ });
+ return out;
+}
+
+template <typename Container>
+void OutputListToStream(std::ostream* ostream, const Container& values) {
+ const char* sep = "";
+ for (const auto& v : values) {
+ (*ostream) << sep << v;
+ sep = ",";
+ }
+}
+
+struct PlottableCounter {
+ std::string graph_name;
+ std::string trace_name;
+ webrtc::SamplesStatsCounter counter;
+ std::string units;
+};
+
+class PlottableCounterPrinter {
+ public:
+ PlottableCounterPrinter() : output_(stdout) {}
+
+ void SetOutput(FILE* output) {
+ MutexLock lock(&mutex_);
+ output_ = output;
+ }
+
+ void AddCounter(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const webrtc::SamplesStatsCounter& counter,
+ absl::string_view units) {
+ MutexLock lock(&mutex_);
+ plottable_counters_.push_back({std::string(graph_name),
+ std::string(trace_name), counter,
+ std::string(units)});
+ }
+
+ void Print(const std::vector<std::string>& desired_graphs_raw) const {
+ std::set<std::string> desired_graphs(desired_graphs_raw.begin(),
+ desired_graphs_raw.end());
+ MutexLock lock(&mutex_);
+ for (auto& counter : plottable_counters_) {
+ if (!desired_graphs.empty()) {
+ auto it = desired_graphs.find(counter.graph_name);
+ if (it == desired_graphs.end()) {
+ continue;
+ }
+ }
+
+ std::ostringstream value_stream;
+ value_stream.precision(8);
+ value_stream << R"({"graph_name":")" << counter.graph_name << R"(",)";
+ value_stream << R"("trace_name":")" << counter.trace_name << R"(",)";
+ value_stream << R"("units":")" << counter.units << R"(",)";
+ if (!counter.counter.IsEmpty()) {
+ value_stream << R"("mean":)" << counter.counter.GetAverage() << ',';
+ value_stream << R"("std":)" << counter.counter.GetStandardDeviation()
+ << ',';
+ }
+ value_stream << R"("samples":[)";
+ const char* sep = "";
+ for (const auto& sample : counter.counter.GetTimedSamples()) {
+ value_stream << sep << R"({"time":)" << sample.time.us() << ','
+ << R"("value":)" << sample.value << '}';
+ sep = ",";
+ }
+ value_stream << "]}";
+
+ fprintf(output_, "PLOTTABLE_DATA: %s\n", value_stream.str().c_str());
+ }
+ }
+
+ private:
+ mutable Mutex mutex_;
+ std::vector<PlottableCounter> plottable_counters_ RTC_GUARDED_BY(&mutex_);
+ FILE* output_ RTC_GUARDED_BY(&mutex_);
+};
+
+PlottableCounterPrinter& GetPlottableCounterPrinter() {
+ static PlottableCounterPrinter* printer_ = new PlottableCounterPrinter();
+ return *printer_;
+}
+
+class ResultsLinePrinter {
+ public:
+ ResultsLinePrinter() : output_(stdout) {}
+
+ void SetOutput(FILE* output) {
+ MutexLock lock(&mutex_);
+ output_ = output;
+ }
+
+ void PrintResult(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const double value,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction) {
+ std::ostringstream value_stream;
+ value_stream.precision(8);
+ value_stream << value;
+
+ PrintResultImpl(graph_name, trace_name, value_stream.str(), std::string(),
+ std::string(), UnitWithDirection(units, improve_direction),
+ important);
+ }
+
+ void PrintResultMeanAndError(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const double mean,
+ const double error,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction) {
+ std::ostringstream value_stream;
+ value_stream.precision(8);
+ value_stream << mean << ',' << error;
+ PrintResultImpl(graph_name, trace_name, value_stream.str(), "{", "}",
+ UnitWithDirection(units, improve_direction), important);
+ }
+
+ void PrintResultList(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const rtc::ArrayView<const double> values,
+ absl::string_view units,
+ const bool important,
+ webrtc::test::ImproveDirection improve_direction) {
+ std::ostringstream value_stream;
+ value_stream.precision(8);
+ OutputListToStream(&value_stream, values);
+ PrintResultImpl(graph_name, trace_name, value_stream.str(), "[", "]", units,
+ important);
+ }
+
+ private:
+ void PrintResultImpl(absl::string_view graph_name,
+ absl::string_view trace_name,
+ absl::string_view values,
+ absl::string_view prefix,
+ absl::string_view suffix,
+ absl::string_view units,
+ bool important) {
+ MutexLock lock(&mutex_);
+ rtc::StringBuilder message;
+ message << (important ? "*" : "") << "RESULT " << graph_name << ": "
+ << trace_name << "= " << prefix << values << suffix << " " << units;
+ // <*>RESULT <graph_name>: <trace_name>= <value> <units>
+ // <*>RESULT <graph_name>: <trace_name>= {<mean>, <std deviation>} <units>
+ // <*>RESULT <graph_name>: <trace_name>= [<value>,value,value,...,] <units>
+ fprintf(output_, "%s\n", message.str().c_str());
+ }
+
+ Mutex mutex_;
+ FILE* output_ RTC_GUARDED_BY(&mutex_);
+};
+
+ResultsLinePrinter& GetResultsLinePrinter() {
+ static ResultsLinePrinter* const printer_ = new ResultsLinePrinter();
+ return *printer_;
+}
+
+PerfTestResultWriter& GetPerfWriter() {
+ static PerfTestResultWriter* writer = CreateHistogramWriter();
+ return *writer;
+}
+
+} // namespace
+
+void ClearPerfResults() {
+ GetPerfWriter().ClearResults();
+}
+
+void SetPerfResultsOutput(FILE* output) {
+ GetPlottableCounterPrinter().SetOutput(output);
+ GetResultsLinePrinter().SetOutput(output);
+}
+
+std::string GetPerfResults() {
+ return GetPerfWriter().Serialize();
+}
+
+void PrintPlottableResults(const std::vector<std::string>& desired_graphs) {
+ GetPlottableCounterPrinter().Print(desired_graphs);
+}
+
+bool WritePerfResults(const std::string& output_path) {
+ std::string results = GetPerfResults();
+ CreateDir(DirName(output_path));
+ FILE* output = fopen(output_path.c_str(), "wb");
+ if (output == NULL) {
+ printf("Failed to write to %s.\n", output_path.c_str());
+ return false;
+ }
+ size_t written =
+ fwrite(results.c_str(), sizeof(char), results.size(), output);
+ fclose(output);
+
+ if (written != results.size()) {
+ long expected = results.size();
+ printf("Wrote %zu, tried to write %lu\n", written, expected);
+ return false;
+ }
+
+ return true;
+}
+
+void PrintResult(absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view trace,
+ const double value,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction) {
+ rtc::StringBuilder graph_name;
+ graph_name << measurement << modifier;
+ RTC_CHECK(std::isfinite(value))
+ << "Expected finite value for graph " << graph_name.str()
+ << ", trace name " << trace << ", units " << units << ", got " << value;
+ GetPerfWriter().LogResult(graph_name.str(), trace, value, units, important,
+ improve_direction);
+ GetResultsLinePrinter().PrintResult(graph_name.str(), trace, value, units,
+ important, improve_direction);
+}
+
+void PrintResult(absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view trace,
+ const SamplesStatsCounter& counter,
+ absl::string_view units,
+ const bool important,
+ ImproveDirection improve_direction) {
+ rtc::StringBuilder graph_name;
+ graph_name << measurement << modifier;
+ GetPlottableCounterPrinter().AddCounter(graph_name.str(), trace, counter,
+ units);
+
+ double mean = counter.IsEmpty() ? 0 : counter.GetAverage();
+ double error = counter.IsEmpty() ? 0 : counter.GetStandardDeviation();
+
+ std::vector<SamplesStatsCounter::StatsSample> timed_samples =
+ GetSortedSamples(counter);
+ std::vector<double> samples(timed_samples.size());
+ for (size_t i = 0; i < timed_samples.size(); ++i) {
+ samples[i] = timed_samples[i].value;
+ }
+ // If we have an empty counter, default it to 0.
+ if (samples.empty()) {
+ samples.push_back(0);
+ }
+
+ GetPerfWriter().LogResultList(graph_name.str(), trace, samples, units,
+ important, improve_direction);
+ GetResultsLinePrinter().PrintResultMeanAndError(graph_name.str(), trace, mean,
+ error, units, important,
+ improve_direction);
+}
+
+void PrintResultMeanAndError(absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view trace,
+ const double mean,
+ const double error,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction) {
+ RTC_CHECK(std::isfinite(mean));
+ RTC_CHECK(std::isfinite(error));
+
+ rtc::StringBuilder graph_name;
+ graph_name << measurement << modifier;
+ GetPerfWriter().LogResultMeanAndError(graph_name.str(), trace, mean, error,
+ units, important, improve_direction);
+ GetResultsLinePrinter().PrintResultMeanAndError(graph_name.str(), trace, mean,
+ error, units, important,
+ improve_direction);
+}
+
+void PrintResultList(absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view trace,
+ const rtc::ArrayView<const double> values,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction) {
+ for (double v : values) {
+ RTC_CHECK(std::isfinite(v));
+ }
+
+ rtc::StringBuilder graph_name;
+ graph_name << measurement << modifier;
+ GetPerfWriter().LogResultList(graph_name.str(), trace, values, units,
+ important, improve_direction);
+ GetResultsLinePrinter().PrintResultList(graph_name.str(), trace, values,
+ units, important, improve_direction);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/perf_test.h b/third_party/libwebrtc/test/testsupport/perf_test.h
new file mode 100644
index 0000000000..732fff7d14
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2012 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_PERF_TEST_H_
+#define TEST_TESTSUPPORT_PERF_TEST_H_
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "api/array_view.h"
+#include "api/numerics/samples_stats_counter.h"
+
+namespace webrtc {
+namespace test {
+
+enum class ImproveDirection {
+ // Direction is undefined.
+ kNone,
+ // Smaller value is better.
+ kSmallerIsBetter,
+ // Bigger value is better.
+ kBiggerIsBetter,
+};
+
+// Prints a performance test result.
+//
+// For example,
+// PrintResult("ramp_up_time_", "turn_over_tcp",
+// "bwe_15s", 1234.2, "ms", false);
+//
+// will show up in the http://chromeperf.appspot.com under
+//
+// (test binary name) > (bot) > ramp_up_time_turn_over_tcp > bwe_15s.
+//
+// The `measurement` + `modifier` is what we're measuring. `user_story` is the
+// scenario we're testing under.
+//
+// The binary this runs in must be hooked up as a perf test in the WebRTC
+// recipes for this to actually be uploaded to chromeperf.appspot.com.
+void PrintResult(absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view user_story,
+ double value,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction = ImproveDirection::kNone);
+
+// Like PrintResult(), but prints a (mean, standard deviation) result pair.
+// The |<values>| should be two comma-separated numbers, the mean and
+// standard deviation (or other error metric) of the measurement.
+// DEPRECATED: soon unsupported.
+void PrintResultMeanAndError(
+ absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view user_story,
+ double mean,
+ double error,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction = ImproveDirection::kNone);
+
+// Like PrintResult(), but prints an entire list of results. The `values`
+// will generally be a list of comma-separated numbers. A typical
+// post-processing step might produce plots of their mean and standard
+// deviation.
+void PrintResultList(
+ absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view user_story,
+ rtc::ArrayView<const double> values,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction = ImproveDirection::kNone);
+
+// Like PrintResult(), but prints a (mean, standard deviation) from stats
+// counter. Also add specified metric to the plotable metrics output.
+void PrintResult(absl::string_view measurement,
+ absl::string_view modifier,
+ absl::string_view user_story,
+ const SamplesStatsCounter& counter,
+ absl::string_view units,
+ bool important,
+ ImproveDirection improve_direction = ImproveDirection::kNone);
+
+// Returns a string-encoded proto as described in
+// tracing/tracing/proto/histogram.proto in
+// https://github.com/catapult-project/catapult/blob/master/.
+// If you want to print the proto in human readable format, use
+// tracing/bin/proto2json from third_party/catapult in your WebRTC checkout.
+std::string GetPerfResults();
+
+// Print into stdout plottable metrics for further post processing.
+// `desired_graphs` - list of metrics, that should be plotted. If empty - all
+// available metrics will be plotted. If some of `desired_graphs` are missing
+// they will be skipped.
+void PrintPlottableResults(const std::vector<std::string>& desired_graphs);
+
+// Call GetPerfResults() and write its output to a file. Returns false if we
+// failed to write to the file. If you want to print the proto in human readable
+// format, use tracing/bin/proto2json from third_party/catapult in your WebRTC
+// checkout.
+bool WritePerfResults(const std::string& output_path);
+
+// By default, human-readable perf results are printed to stdout. Set the FILE*
+// to where they should be printing instead. These results are not used to
+// upload to the dashboard, however - this is only through WritePerfResults.
+void SetPerfResultsOutput(FILE* output);
+
+// Only for use by tests.
+void ClearPerfResults();
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_PERF_TEST_H_
diff --git a/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.cc b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.cc
new file mode 100644
index 0000000000..93924ba16c
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.cc
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2020 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/perf_test_histogram_writer.h"
+
+#include <stdlib.h>
+
+#include <map>
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "api/numerics/samples_stats_counter.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/synchronization/mutex.h"
+#include "third_party/catapult/tracing/tracing/value/diagnostics/reserved_infos.h"
+#include "third_party/catapult/tracing/tracing/value/histogram.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+
+namespace proto = catapult::tracing::tracing::proto;
+
+std::string AsJsonString(const std::string string) {
+ return "\"" + string + "\"";
+}
+
+class PerfTestHistogramWriter : public PerfTestResultWriter {
+ public:
+ PerfTestHistogramWriter() : mutex_() {}
+ void ClearResults() override {
+ MutexLock lock(&mutex_);
+ histograms_.clear();
+ }
+
+ void LogResult(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const double value,
+ absl::string_view units,
+ const bool important,
+ ImproveDirection improve_direction) override {
+ (void)important;
+ AddSample(graph_name, trace_name, value, units, improve_direction);
+ }
+ void LogResultMeanAndError(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const double mean,
+ const double error,
+ absl::string_view units,
+ const bool important,
+ ImproveDirection improve_direction) override {
+ RTC_LOG(LS_WARNING) << "Discarding stddev, not supported by histograms";
+ (void)error;
+ (void)important;
+
+ AddSample(graph_name, trace_name, mean, units, improve_direction);
+ }
+ void LogResultList(absl::string_view graph_name,
+ absl::string_view trace_name,
+ const rtc::ArrayView<const double> values,
+ absl::string_view units,
+ const bool important,
+ ImproveDirection improve_direction) override {
+ (void)important;
+ for (double value : values) {
+ AddSample(graph_name, trace_name, value, units, improve_direction);
+ }
+ }
+ std::string Serialize() const override {
+ proto::HistogramSet histogram_set;
+
+ MutexLock lock(&mutex_);
+ for (const auto& histogram : histograms_) {
+ std::unique_ptr<proto::Histogram> proto = histogram.second->toProto();
+ histogram_set.mutable_histograms()->AddAllocated(proto.release());
+ }
+
+ std::string output;
+ bool ok = histogram_set.SerializeToString(&output);
+ RTC_DCHECK(ok) << "Failed to serialize histogram set to string";
+ return output;
+ }
+
+ private:
+ void AddSample(absl::string_view original_graph_name,
+ absl::string_view trace_name,
+ const double value,
+ absl::string_view units,
+ ImproveDirection improve_direction) {
+ // WebRTC annotates the units into the metric name when they are not
+ // supported by the Histogram API.
+ std::string graph_name(original_graph_name);
+ if (units == "dB") {
+ graph_name += "_dB";
+ } else if (units == "fps") {
+ graph_name += "_fps";
+ } else if (units == "%") {
+ graph_name += "_%";
+ }
+
+ // Lookup on graph name + trace name (or measurement + story in catapult
+ // parlance). There should be several histograms with the same measurement
+ // if they're for different stories.
+ rtc::StringBuilder measurement_and_story;
+ measurement_and_story << graph_name << trace_name;
+ MutexLock lock(&mutex_);
+ if (histograms_.count(measurement_and_story.str()) == 0) {
+ proto::UnitAndDirection unit = ParseUnit(units, improve_direction);
+ std::unique_ptr<catapult::HistogramBuilder> builder =
+ std::make_unique<catapult::HistogramBuilder>(graph_name, unit);
+
+ // Set all summary options as false - we don't want to generate
+ // metric_std, metric_count, and so on for all metrics.
+ builder->SetSummaryOptions(proto::SummaryOptions());
+ histograms_[measurement_and_story.str()] = std::move(builder);
+
+ proto::Diagnostic stories;
+ proto::GenericSet* generic_set = stories.mutable_generic_set();
+ generic_set->add_values(AsJsonString(std::string(trace_name)));
+ histograms_[measurement_and_story.str()]->AddDiagnostic(
+ catapult::kStoriesDiagnostic, stories);
+ }
+
+ if (units == "bps") {
+ // Bps has been interpreted as bits per second in WebRTC tests.
+ histograms_[measurement_and_story.str()]->AddSample(value / 8);
+ } else {
+ histograms_[measurement_and_story.str()]->AddSample(value);
+ }
+ }
+
+ proto::UnitAndDirection ParseUnit(absl::string_view units,
+ ImproveDirection improve_direction) {
+ RTC_DCHECK(units.find('_') == std::string::npos)
+ << "The unit_bigger|smallerIsBetter syntax isn't supported in WebRTC, "
+ "use the enum instead.";
+
+ proto::UnitAndDirection result;
+ result.set_improvement_direction(ParseDirection(improve_direction));
+ if (units == "bps") {
+ result.set_unit(proto::BYTES_PER_SECOND);
+ } else if (units == "dB") {
+ result.set_unit(proto::UNITLESS);
+ } else if (units == "fps") {
+ result.set_unit(proto::HERTZ);
+ } else if (units == "frames") {
+ result.set_unit(proto::COUNT);
+ } else if (units == "ms") {
+ result.set_unit(proto::MS_BEST_FIT_FORMAT);
+ } else if (units == "%") {
+ result.set_unit(proto::UNITLESS);
+ } else {
+ proto::Unit unit = catapult::UnitFromJsonUnit(std::string(units));
+
+ // UnitFromJsonUnit returns UNITLESS if it doesn't recognize the unit.
+ if (unit == proto::UNITLESS && units != "unitless") {
+ RTC_LOG(LS_WARNING) << "Unit " << units << " is unsupported.";
+ }
+
+ result.set_unit(unit);
+ }
+ return result;
+ }
+
+ proto::ImprovementDirection ParseDirection(
+ ImproveDirection improve_direction) {
+ switch (improve_direction) {
+ case ImproveDirection::kNone:
+ return proto::NOT_SPECIFIED;
+ case ImproveDirection::kSmallerIsBetter:
+ return proto::SMALLER_IS_BETTER;
+ case ImproveDirection::kBiggerIsBetter:
+ return proto::BIGGER_IS_BETTER;
+ default:
+ RTC_DCHECK_NOTREACHED() << "Invalid enum value " << improve_direction;
+ }
+ }
+
+ private:
+ mutable Mutex mutex_;
+ std::map<std::string, std::unique_ptr<catapult::HistogramBuilder>> histograms_
+ RTC_GUARDED_BY(&mutex_);
+};
+
+} // namespace
+
+PerfTestResultWriter* CreateHistogramWriter() {
+ return new PerfTestHistogramWriter();
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.h b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.h
new file mode 100644
index 0000000000..244e69fc45
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 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_PERF_TEST_HISTOGRAM_WRITER_H_
+#define TEST_TESTSUPPORT_PERF_TEST_HISTOGRAM_WRITER_H_
+
+#include "test/testsupport/perf_test_result_writer.h"
+
+namespace webrtc {
+namespace test {
+
+PerfTestResultWriter* CreateHistogramWriter();
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_PERF_TEST_HISTOGRAM_WRITER_H_
diff --git a/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_no_protobuf.cc b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_no_protobuf.cc
new file mode 100644
index 0000000000..6bc810b94d
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_no_protobuf.cc
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 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/perf_test_histogram_writer.h"
+
+namespace webrtc {
+namespace test {
+
+PerfTestResultWriter* CreateHistogramWriter() {
+ RTC_DCHECK_NOTREACHED()
+ << "Cannot run perf tests with rtc_enable_protobuf = false. "
+ "Perf write results as protobufs.";
+ return nullptr;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_unittest.cc b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_unittest.cc
new file mode 100644
index 0000000000..83025a7447
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test_histogram_writer_unittest.cc
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2020 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/perf_test_histogram_writer.h"
+
+#include <memory>
+#include <string>
+
+#include "test/gtest.h"
+#include "third_party/catapult/tracing/tracing/value/histogram.h"
+
+namespace webrtc {
+namespace test {
+
+namespace proto = catapult::tracing::tracing::proto;
+
+TEST(PerfHistogramWriterUnittest, TestSimpleHistogram) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("-", "-", 0, "ms", false, ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ EXPECT_TRUE(histogram_set.ParseFromString(writer->Serialize()))
+ << "Expected valid histogram set";
+
+ ASSERT_EQ(histogram_set.histograms_size(), 1);
+}
+
+TEST(PerfHistogramWriterUnittest, TestListOfValuesHistogram) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ std::vector<double> samples{0, 1, 2};
+ writer->LogResultList("-", "-", samples, "ms", false,
+ ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ EXPECT_TRUE(histogram_set.ParseFromString(writer->Serialize()))
+ << "Expected valid histogram set";
+
+ ASSERT_EQ(histogram_set.histograms_size(), 1);
+ ASSERT_EQ(histogram_set.histograms(0).sample_values_size(), 3);
+ EXPECT_EQ(histogram_set.histograms(0).sample_values(0), 0);
+ EXPECT_EQ(histogram_set.histograms(0).sample_values(1), 1);
+ EXPECT_EQ(histogram_set.histograms(0).sample_values(2), 2);
+}
+
+TEST(PerfHistogramWriterUnittest, WritesSamplesAndUserStory) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("measurement", "user_story", 15e7, "Hz", false,
+ ImproveDirection::kBiggerIsBetter);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist1.name(), "measurement");
+
+ EXPECT_EQ(hist1.unit().unit(), proto::HERTZ);
+ EXPECT_EQ(hist1.unit().improvement_direction(), proto::BIGGER_IS_BETTER);
+
+ EXPECT_EQ(hist1.sample_values_size(), 1);
+ EXPECT_EQ(hist1.sample_values(0), 15e7);
+
+ EXPECT_EQ(hist1.diagnostics().diagnostic_map().count("stories"), 1u);
+ const proto::Diagnostic& stories =
+ hist1.diagnostics().diagnostic_map().at("stories");
+ ASSERT_EQ(stories.generic_set().values_size(), 1);
+ EXPECT_EQ(stories.generic_set().values(0), "\"user_story\"");
+}
+
+TEST(PerfHistogramWriterUnittest, WritesOneHistogramPerMeasurementAndStory) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("measurement", "story1", 1, "ms", false,
+ ImproveDirection::kNone);
+ writer->LogResult("measurement", "story1", 2, "ms", false,
+ ImproveDirection::kNone);
+ writer->LogResult("measurement", "story2", 2, "ms", false,
+ ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ ASSERT_EQ(histogram_set.histograms_size(), 2);
+
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+ const proto::Histogram& hist2 = histogram_set.histograms(1);
+
+ EXPECT_EQ(hist1.name(), "measurement");
+ EXPECT_EQ(hist2.name(), "measurement");
+
+ const proto::Diagnostic& stories1 =
+ hist1.diagnostics().diagnostic_map().at("stories");
+ EXPECT_EQ(stories1.generic_set().values(0), "\"story1\"");
+ EXPECT_EQ(hist1.sample_values_size(), 2);
+
+ const proto::Diagnostic& stories2 =
+ hist2.diagnostics().diagnostic_map().at("stories");
+ EXPECT_EQ(stories2.generic_set().values(0), "\"story2\"");
+ EXPECT_EQ(hist2.sample_values_size(), 1);
+}
+
+TEST(PerfHistogramWriterUnittest, IgnoresError) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResultMeanAndError("-", "-", 17, 12345, "ms", false,
+ ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist1.running().mean(), 17);
+ EXPECT_EQ(hist1.running().variance(), 0) << "The error should be ignored.";
+}
+
+TEST(PerfHistogramWriterUnittest, WritesDecibelIntoMeasurementName) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("measurement", "-", 0, "dB", false,
+ ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist1.unit().unit(), proto::UNITLESS)
+ << "dB should map to unitless";
+ EXPECT_EQ(hist1.name(), "measurement_dB") << "measurement should be renamed";
+}
+
+TEST(PerfHistogramWriterUnittest, WritesFpsIntoMeasurementName) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("measurement", "-", 0, "fps", false,
+ ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist1.unit().unit(), proto::HERTZ) << "fps should map to hertz";
+ EXPECT_EQ(hist1.name(), "measurement_fps") << "measurement should be renamed";
+}
+
+TEST(PerfHistogramWriterUnittest, WritesPercentIntoMeasurementName) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("measurement", "-", 0, "%", false, ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist1.unit().unit(), proto::UNITLESS)
+ << "percent should map to hertz";
+ EXPECT_EQ(hist1.name(), "measurement_%") << "measurement should be renamed";
+}
+
+TEST(PerfHistogramWriterUnittest, BitsPerSecondIsConvertedToBytes) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("-", "-", 1024, "bps", false, ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist1.sample_values(0), 128) << "1024 bits = 128 bytes";
+}
+
+TEST(PerfHistogramWriterUnittest, ParsesDirection) {
+ std::unique_ptr<PerfTestResultWriter> writer =
+ std::unique_ptr<PerfTestResultWriter>(CreateHistogramWriter());
+
+ writer->LogResult("measurement1", "-", 0, "bps", false,
+ ImproveDirection::kBiggerIsBetter);
+ writer->LogResult("measurement2", "-", 0, "frames", false,
+ ImproveDirection::kSmallerIsBetter);
+ writer->LogResult("measurement3", "-", 0, "sigma", false,
+ ImproveDirection::kNone);
+
+ proto::HistogramSet histogram_set;
+ histogram_set.ParseFromString(writer->Serialize());
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+ const proto::Histogram& hist2 = histogram_set.histograms(1);
+ const proto::Histogram& hist3 = histogram_set.histograms(2);
+
+ EXPECT_EQ(hist1.unit().unit(), proto::BYTES_PER_SECOND);
+ EXPECT_EQ(hist1.unit().improvement_direction(), proto::BIGGER_IS_BETTER);
+
+ EXPECT_EQ(hist2.unit().unit(), proto::COUNT);
+ EXPECT_EQ(hist2.unit().improvement_direction(), proto::SMALLER_IS_BETTER);
+
+ EXPECT_EQ(hist3.unit().unit(), proto::SIGMA);
+ EXPECT_EQ(hist3.unit().improvement_direction(), proto::NOT_SPECIFIED);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/perf_test_result_writer.h b/third_party/libwebrtc/test/testsupport/perf_test_result_writer.h
new file mode 100644
index 0000000000..1b93bc9583
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test_result_writer.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020 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_PERF_TEST_RESULT_WRITER_H_
+#define TEST_TESTSUPPORT_PERF_TEST_RESULT_WRITER_H_
+
+#include <stdio.h>
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "test/testsupport/perf_test.h"
+
+namespace webrtc {
+namespace test {
+
+// Interface for classes that write perf results to some kind of JSON format.
+class PerfTestResultWriter {
+ public:
+ virtual ~PerfTestResultWriter() = default;
+
+ virtual void ClearResults() = 0;
+ virtual void LogResult(absl::string_view graph_name,
+ absl::string_view trace_name,
+ double value,
+ absl::string_view units,
+ bool important,
+ webrtc::test::ImproveDirection improve_direction) = 0;
+ virtual void LogResultMeanAndError(
+ absl::string_view graph_name,
+ absl::string_view trace_name,
+ double mean,
+ double error,
+ absl::string_view units,
+ bool important,
+ webrtc::test::ImproveDirection improve_direction) = 0;
+ virtual void LogResultList(
+ absl::string_view graph_name,
+ absl::string_view trace_name,
+ rtc::ArrayView<const double> values,
+ absl::string_view units,
+ bool important,
+ webrtc::test::ImproveDirection improve_direction) = 0;
+
+ virtual std::string Serialize() const = 0;
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_PERF_TEST_RESULT_WRITER_H_
diff --git a/third_party/libwebrtc/test/testsupport/perf_test_unittest.cc b/third_party/libwebrtc/test/testsupport/perf_test_unittest.cc
new file mode 100644
index 0000000000..509882db08
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/perf_test_unittest.cc
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2012 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/perf_test.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/testsupport/rtc_expect_death.h"
+
+#if WEBRTC_ENABLE_PROTOBUF
+#include "third_party/catapult/tracing/tracing/value/histogram.h"
+namespace proto = catapult::tracing::tracing::proto;
+#endif
+
+namespace webrtc {
+namespace test {
+
+class PerfTest : public ::testing::Test {
+ protected:
+ void TearDown() override { ClearPerfResults(); }
+};
+
+#if defined(WEBRTC_IOS)
+#define MAYBE_TestPrintResult DISABLED_TestPrintResult
+#else
+#define MAYBE_TestPrintResult TestPrintResult
+#endif
+TEST_F(PerfTest, MAYBE_TestPrintResult) {
+ ::testing::internal::CaptureStdout();
+ std::string expected;
+
+ expected += "RESULT measurementmodifier: trace= 42 units\n";
+ PrintResult("measurement", "modifier", "trace", 42, "units", false);
+
+ expected += "*RESULT foobar: baz_v= 1423730 widgets\n";
+ PrintResult("foo", "bar", "baz_v", 1423730, "widgets", true);
+
+ expected += "RESULT foobar: baz_me= {1,2} lemurs\n";
+ PrintResultMeanAndError("foo", "bar", "baz_me", 1, 2, "lemurs", false);
+
+ const double kListOfScalars[] = {1, 2, 3};
+ expected += "RESULT foobar: baz_vl= [1,2,3] units\n";
+ PrintResultList("foo", "bar", "baz_vl", kListOfScalars, "units", false);
+
+ EXPECT_EQ(expected, ::testing::internal::GetCapturedStdout());
+}
+
+TEST_F(PerfTest, TestClearPerfResults) {
+ PrintResult("measurement", "modifier", "trace", 42, "units", false);
+ ClearPerfResults();
+ EXPECT_EQ("", GetPerfResults());
+}
+
+#if WEBRTC_ENABLE_PROTOBUF
+
+TEST_F(PerfTest, TestGetPerfResultsHistograms) {
+ ClearPerfResults();
+ PrintResult("measurement", "_modifier", "story_1", 42, "ms", false);
+ PrintResult("foo", "bar", "story_1", 7, "sigma", true);
+ // Note: the error will be ignored, not supported by histograms.
+ PrintResultMeanAndError("foo", "bar", "story_1", 1, 2000, "sigma", false);
+ const double kListOfScalars[] = {1, 2, 3};
+ PrintResultList("foo", "bar", "story_1", kListOfScalars, "sigma", false);
+
+ proto::HistogramSet histogram_set;
+ EXPECT_TRUE(histogram_set.ParseFromString(GetPerfResults()))
+ << "Expected valid histogram set";
+
+ ASSERT_EQ(histogram_set.histograms_size(), 2)
+ << "Should be two histograms: foobar and measurement_modifier";
+ const proto::Histogram& hist1 = histogram_set.histograms(0);
+ const proto::Histogram& hist2 = histogram_set.histograms(1);
+
+ EXPECT_EQ(hist1.name(), "foobar");
+
+ // Spot check some things in here (there's a more thorough test on the
+ // histogram writer itself).
+ EXPECT_EQ(hist1.unit().unit(), proto::SIGMA);
+ EXPECT_EQ(hist1.sample_values_size(), 5);
+ EXPECT_EQ(hist1.sample_values(0), 7);
+ EXPECT_EQ(hist1.sample_values(1), 1);
+ EXPECT_EQ(hist1.sample_values(2), 1);
+ EXPECT_EQ(hist1.sample_values(3), 2);
+ EXPECT_EQ(hist1.sample_values(4), 3);
+
+ EXPECT_EQ(hist1.diagnostics().diagnostic_map().count("stories"), 1u);
+ const proto::Diagnostic& stories =
+ hist1.diagnostics().diagnostic_map().at("stories");
+ ASSERT_EQ(stories.generic_set().values_size(), 1);
+ EXPECT_EQ(stories.generic_set().values(0), "\"story_1\"");
+
+ EXPECT_EQ(hist2.name(), "measurement_modifier");
+ EXPECT_EQ(hist2.unit().unit(), proto::MS_BEST_FIT_FORMAT);
+}
+
+TEST_F(PerfTest, TestGetPerfResultsHistogramsWithEmptyCounter) {
+ ClearPerfResults();
+ ::testing::internal::CaptureStdout();
+
+ SamplesStatsCounter empty_counter;
+ PrintResult("measurement", "_modifier", "story", empty_counter, "ms", false);
+
+ proto::HistogramSet histogram_set;
+ EXPECT_TRUE(histogram_set.ParseFromString(GetPerfResults()))
+ << "Expected valid histogram set";
+
+ ASSERT_EQ(histogram_set.histograms_size(), 1)
+ << "Should be one histogram: measurement_modifier";
+ const proto::Histogram& hist = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist.name(), "measurement_modifier");
+
+ // Spot check some things in here (there's a more thorough test on the
+ // histogram writer itself).
+ EXPECT_EQ(hist.unit().unit(), proto::MS_BEST_FIT_FORMAT);
+ EXPECT_EQ(hist.sample_values_size(), 1);
+ EXPECT_EQ(hist.sample_values(0), 0);
+
+ EXPECT_EQ(hist.diagnostics().diagnostic_map().count("stories"), 1u);
+ const proto::Diagnostic& stories =
+ hist.diagnostics().diagnostic_map().at("stories");
+ ASSERT_EQ(stories.generic_set().values_size(), 1);
+ EXPECT_EQ(stories.generic_set().values(0), "\"story\"");
+
+ std::string expected = "RESULT measurement_modifier: story= {0,0} ms\n";
+ EXPECT_EQ(expected, ::testing::internal::GetCapturedStdout());
+}
+
+TEST_F(PerfTest, TestGetPerfResultsHistogramsWithStatsCounter) {
+ ClearPerfResults();
+ ::testing::internal::CaptureStdout();
+
+ SamplesStatsCounter counter;
+ counter.AddSample(1);
+ counter.AddSample(2);
+ counter.AddSample(3);
+ counter.AddSample(4);
+ counter.AddSample(5);
+ PrintResult("measurement", "_modifier", "story", counter, "ms", false);
+
+ proto::HistogramSet histogram_set;
+ EXPECT_TRUE(histogram_set.ParseFromString(GetPerfResults()))
+ << "Expected valid histogram set";
+
+ ASSERT_EQ(histogram_set.histograms_size(), 1)
+ << "Should be one histogram: measurement_modifier";
+ const proto::Histogram& hist = histogram_set.histograms(0);
+
+ EXPECT_EQ(hist.name(), "measurement_modifier");
+
+ // Spot check some things in here (there's a more thorough test on the
+ // histogram writer itself).
+ EXPECT_EQ(hist.unit().unit(), proto::MS_BEST_FIT_FORMAT);
+ EXPECT_EQ(hist.sample_values_size(), 5);
+ EXPECT_THAT(hist.sample_values(), testing::ElementsAre(1, 2, 3, 4, 5));
+
+ EXPECT_EQ(hist.diagnostics().diagnostic_map().count("stories"), 1u);
+ const proto::Diagnostic& stories =
+ hist.diagnostics().diagnostic_map().at("stories");
+ ASSERT_EQ(stories.generic_set().values_size(), 1);
+ EXPECT_EQ(stories.generic_set().values(0), "\"story\"");
+
+ // mean = 3; std = sqrt(2)
+ std::string expected =
+ "RESULT measurement_modifier: story= {3,1.4142136} ms\n";
+ EXPECT_EQ(expected, ::testing::internal::GetCapturedStdout());
+}
+
+#endif // WEBRTC_ENABLE_PROTOBUF
+
+#if GTEST_HAS_DEATH_TEST
+using PerfDeathTest = PerfTest;
+
+TEST_F(PerfDeathTest, TestFiniteResultError) {
+ const double kNan = std::numeric_limits<double>::quiet_NaN();
+ const double kInf = std::numeric_limits<double>::infinity();
+
+ RTC_EXPECT_DEATH(PrintResult("a", "b", "c", kNan, "d", false), "finit");
+ RTC_EXPECT_DEATH(PrintResult("a", "b", "c", kInf, "d", false), "finit");
+
+ RTC_EXPECT_DEATH(PrintResultMeanAndError("a", "b", "c", kNan, 1, "d", false),
+ "");
+ RTC_EXPECT_DEATH(PrintResultMeanAndError("a", "b", "c", 1, kInf, "d", false),
+ "");
+
+ const double kNanList[] = {kNan, kNan};
+ RTC_EXPECT_DEATH(PrintResultList("a", "b", "c", kNanList, "d", false), "");
+ const double kInfList[] = {0, kInf};
+ RTC_EXPECT_DEATH(PrintResultList("a", "b", "c", kInfList, "d", false), "");
+}
+#endif
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/resources_dir_flag.cc b/third_party/libwebrtc/test/testsupport/resources_dir_flag.cc
new file mode 100644
index 0000000000..87a449a401
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/resources_dir_flag.cc
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2019 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/resources_dir_flag.h"
+
+#include "absl/flags/flag.h"
+
+ABSL_FLAG(std::string,
+ resources_dir,
+ "",
+ "Where to look for the runtime dependencies. If not specified, we "
+ "will use a reasonable default depending on where we are running. "
+ "This flag is useful if we copy over test resources to a phone and "
+ "need to tell the tests where their resources are.");
diff --git a/third_party/libwebrtc/test/testsupport/resources_dir_flag.h b/third_party/libwebrtc/test/testsupport/resources_dir_flag.h
new file mode 100644
index 0000000000..7d6f192d9b
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/resources_dir_flag.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2019 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 <string>
+
+#ifndef TEST_TESTSUPPORT_RESOURCES_DIR_FLAG_H__
+#define TEST_TESTSUPPORT_RESOURCES_DIR_FLAG_H__
+
+#include "absl/flags/declare.h"
+
+ABSL_DECLARE_FLAG(std::string, resources_dir);
+
+#endif // TEST_TESTSUPPORT_RESOURCES_DIR_FLAG_H__
diff --git a/third_party/libwebrtc/test/testsupport/rtc_expect_death.h b/third_party/libwebrtc/test/testsupport/rtc_expect_death.h
new file mode 100644
index 0000000000..5941e12bd2
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/rtc_expect_death.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2019 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_RTC_EXPECT_DEATH_H_
+#define TEST_TESTSUPPORT_RTC_EXPECT_DEATH_H_
+
+#include "test/gtest.h"
+
+#if RTC_CHECK_MSG_ENABLED
+#define RTC_EXPECT_DEATH(statement, regex) EXPECT_DEATH(statement, regex)
+#else
+// If RTC_CHECKs messages are disabled we can't validate failure message
+#define RTC_EXPECT_DEATH(statement, regex) EXPECT_DEATH(statement, "")
+#endif
+
+#endif // TEST_TESTSUPPORT_RTC_EXPECT_DEATH_H_
diff --git a/third_party/libwebrtc/test/testsupport/test_artifacts.cc b/third_party/libwebrtc/test/testsupport/test_artifacts.cc
new file mode 100644
index 0000000000..6f062e5fe4
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/test_artifacts.cc
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016 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/test_artifacts.h"
+
+#include <string.h>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/system/file_wrapper.h"
+#include "test/testsupport/file_utils.h"
+
+namespace {
+const std::string& DefaultArtifactPath() {
+ static const std::string path = webrtc::test::OutputPath();
+ return path;
+}
+} // namespace
+
+ABSL_FLAG(std::string,
+ test_artifacts_dir,
+ DefaultArtifactPath().c_str(),
+ "The output folder where test output should be saved.");
+
+namespace webrtc {
+namespace test {
+
+bool GetTestArtifactsDir(std::string* out_dir) {
+ if (absl::GetFlag(FLAGS_test_artifacts_dir).empty()) {
+ RTC_LOG(LS_WARNING) << "No test_out_dir defined.";
+ return false;
+ }
+ *out_dir = absl::GetFlag(FLAGS_test_artifacts_dir);
+ return true;
+}
+
+bool WriteToTestArtifactsDir(const char* filename,
+ const uint8_t* buffer,
+ size_t length) {
+ if (absl::GetFlag(FLAGS_test_artifacts_dir).empty()) {
+ RTC_LOG(LS_WARNING) << "No test_out_dir defined.";
+ return false;
+ }
+
+ if (filename == nullptr || strlen(filename) == 0) {
+ RTC_LOG(LS_WARNING) << "filename must be provided.";
+ return false;
+ }
+
+ FileWrapper output = FileWrapper::OpenWriteOnly(
+ JoinFilename(absl::GetFlag(FLAGS_test_artifacts_dir), filename));
+
+ return output.is_open() && output.Write(buffer, length);
+}
+
+bool WriteToTestArtifactsDir(const char* filename, const std::string& content) {
+ return WriteToTestArtifactsDir(
+ filename, reinterpret_cast<const uint8_t*>(content.c_str()),
+ content.length());
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/test_artifacts.h b/third_party/libwebrtc/test/testsupport/test_artifacts.h
new file mode 100644
index 0000000000..ba0d4d39cb
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/test_artifacts.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016 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_TEST_ARTIFACTS_H_
+#define TEST_TESTSUPPORT_TEST_ARTIFACTS_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <string>
+
+namespace webrtc {
+namespace test {
+
+// If the test_artifacts_dir flag is set, returns true and copies the location
+// of the dir to `out_dir`. Otherwise, return false.
+bool GetTestArtifactsDir(std::string* out_dir);
+
+// Writes a `length` bytes array `buffer` to `filename` in isolated output
+// directory defined by swarming. If the file is existing, content will be
+// appended. Otherwise a new file will be created. This function returns false
+// if isolated output directory has not been defined, or `filename` indicates an
+// invalid or non-writable file, or underlying file system errors.
+bool WriteToTestArtifactsDir(const char* filename,
+ const uint8_t* buffer,
+ size_t length);
+
+bool WriteToTestArtifactsDir(const char* filename, const std::string& content);
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_TEST_ARTIFACTS_H_
diff --git a/third_party/libwebrtc/test/testsupport/test_artifacts_unittest.cc b/third_party/libwebrtc/test/testsupport/test_artifacts_unittest.cc
new file mode 100644
index 0000000000..fb577610fb
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/test_artifacts_unittest.cc
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016 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/test_artifacts.h"
+
+#include <string.h>
+
+#include <string>
+
+#include "absl/flags/declare.h"
+#include "absl/flags/flag.h"
+#include "rtc_base/system/file_wrapper.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+
+ABSL_DECLARE_FLAG(std::string, test_artifacts_dir);
+
+namespace webrtc {
+namespace test {
+
+TEST(IsolatedOutputTest, ShouldRejectInvalidIsolatedOutDir) {
+ const std::string backup = absl::GetFlag(FLAGS_test_artifacts_dir);
+ absl::SetFlag(&FLAGS_test_artifacts_dir, "");
+ ASSERT_FALSE(WriteToTestArtifactsDir("a-file", "some-contents"));
+ absl::SetFlag(&FLAGS_test_artifacts_dir, backup);
+}
+
+TEST(IsolatedOutputTest, ShouldRejectInvalidFileName) {
+ ASSERT_FALSE(WriteToTestArtifactsDir(nullptr, "some-contents"));
+ ASSERT_FALSE(WriteToTestArtifactsDir("", "some-contents"));
+}
+
+// Sets isolated_out_dir=<a-writable-path> to execute this test.
+TEST(IsolatedOutputTest, ShouldBeAbleToWriteContent) {
+ const char* filename = "a-file";
+ const char* content = "some-contents";
+ if (WriteToTestArtifactsDir(filename, content)) {
+ std::string out_file =
+ JoinFilename(absl::GetFlag(FLAGS_test_artifacts_dir), filename);
+ FileWrapper input = FileWrapper::OpenReadOnly(out_file);
+ EXPECT_TRUE(input.is_open());
+ EXPECT_TRUE(input.Rewind());
+ uint8_t buffer[32];
+ EXPECT_EQ(input.Read(buffer, strlen(content)), strlen(content));
+ buffer[strlen(content)] = 0;
+ EXPECT_EQ(std::string(content),
+ std::string(reinterpret_cast<char*>(buffer)));
+ input.Close();
+
+ EXPECT_TRUE(RemoveFile(out_file));
+ }
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/video_frame_writer.cc b/third_party/libwebrtc/test/testsupport/video_frame_writer.cc
new file mode 100644
index 0000000000..c36ebdeed7
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/video_frame_writer.cc
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2019 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/video_frame_writer.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+rtc::Buffer ExtractI420BufferWithSize(const VideoFrame& frame,
+ int width,
+ int height) {
+ if (frame.width() != width || frame.height() != height) {
+ RTC_CHECK_LE(std::abs(static_cast<double>(width) / height -
+ static_cast<double>(frame.width()) / frame.height()),
+ 2 * std::numeric_limits<double>::epsilon());
+ // Same aspect ratio, no cropping needed.
+ rtc::scoped_refptr<I420Buffer> scaled(I420Buffer::Create(width, height));
+ scaled->ScaleFrom(*frame.video_frame_buffer()->ToI420());
+
+ size_t length =
+ CalcBufferSize(VideoType::kI420, scaled->width(), scaled->height());
+ rtc::Buffer buffer(length);
+ RTC_CHECK_NE(ExtractBuffer(scaled, length, buffer.data()), -1);
+ return buffer;
+ }
+
+ // No resize.
+ size_t length =
+ CalcBufferSize(VideoType::kI420, frame.width(), frame.height());
+ rtc::Buffer buffer(length);
+ RTC_CHECK_NE(ExtractBuffer(frame, length, buffer.data()), -1);
+ return buffer;
+}
+
+} // namespace
+
+Y4mVideoFrameWriterImpl::Y4mVideoFrameWriterImpl(std::string output_file_name,
+ int width,
+ int height,
+ int fps)
+ // We will move string here to prevent extra copy. We won't use const ref
+ // to not corrupt caller variable with move and don't assume that caller's
+ // variable won't be destructed before writer.
+ : width_(width),
+ height_(height),
+ frame_writer_(
+ std::make_unique<Y4mFrameWriterImpl>(std::move(output_file_name),
+ width_,
+ height_,
+ fps)) {
+ // Init underlying frame writer and ensure that it is operational.
+ RTC_CHECK(frame_writer_->Init());
+}
+
+bool Y4mVideoFrameWriterImpl::WriteFrame(const webrtc::VideoFrame& frame) {
+ rtc::Buffer frame_buffer = ExtractI420BufferWithSize(frame, width_, height_);
+ RTC_CHECK_EQ(frame_buffer.size(), frame_writer_->FrameLength());
+ return frame_writer_->WriteFrame(frame_buffer.data());
+}
+
+void Y4mVideoFrameWriterImpl::Close() {
+ frame_writer_->Close();
+}
+
+YuvVideoFrameWriterImpl::YuvVideoFrameWriterImpl(std::string output_file_name,
+ int width,
+ int height)
+ // We will move string here to prevent extra copy. We won't use const ref
+ // to not corrupt caller variable with move and don't assume that caller's
+ // variable won't be destructed before writer.
+ : width_(width),
+ height_(height),
+ frame_writer_(
+ std::make_unique<YuvFrameWriterImpl>(std::move(output_file_name),
+ width_,
+ height_)) {
+ // Init underlying frame writer and ensure that it is operational.
+ RTC_CHECK(frame_writer_->Init());
+}
+
+bool YuvVideoFrameWriterImpl::WriteFrame(const webrtc::VideoFrame& frame) {
+ rtc::Buffer frame_buffer = ExtractI420BufferWithSize(frame, width_, height_);
+ RTC_CHECK_EQ(frame_buffer.size(), frame_writer_->FrameLength());
+ return frame_writer_->WriteFrame(frame_buffer.data());
+}
+
+void YuvVideoFrameWriterImpl::Close() {
+ frame_writer_->Close();
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/video_frame_writer.h b/third_party/libwebrtc/test/testsupport/video_frame_writer.h
new file mode 100644
index 0000000000..1cc4e56284
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/video_frame_writer.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019 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_VIDEO_FRAME_WRITER_H_
+#define TEST_TESTSUPPORT_VIDEO_FRAME_WRITER_H_
+
+#include <memory>
+#include <string>
+
+#include "api/test/video/video_frame_writer.h"
+#include "api/video/video_frame.h"
+#include "rtc_base/buffer.h"
+#include "test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+// Writes webrtc::VideoFrame to specified file with y4m frame writer
+class Y4mVideoFrameWriterImpl : public VideoFrameWriter {
+ public:
+ Y4mVideoFrameWriterImpl(std::string output_file_name,
+ int width,
+ int height,
+ int fps);
+ ~Y4mVideoFrameWriterImpl() override = default;
+
+ bool WriteFrame(const webrtc::VideoFrame& frame) override;
+ void Close() override;
+
+ private:
+ const int width_;
+ const int height_;
+
+ std::unique_ptr<FrameWriter> frame_writer_;
+};
+
+// Writes webrtc::VideoFrame to specified file with yuv frame writer
+class YuvVideoFrameWriterImpl : public VideoFrameWriter {
+ public:
+ YuvVideoFrameWriterImpl(std::string output_file_name, int width, int height);
+ ~YuvVideoFrameWriterImpl() override = default;
+
+ bool WriteFrame(const webrtc::VideoFrame& frame) override;
+ void Close() override;
+
+ private:
+ const int width_;
+ const int height_;
+
+ std::unique_ptr<FrameWriter> frame_writer_;
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_VIDEO_FRAME_WRITER_H_
diff --git a/third_party/libwebrtc/test/testsupport/video_frame_writer_unittest.cc b/third_party/libwebrtc/test/testsupport/video_frame_writer_unittest.cc
new file mode 100644
index 0000000000..9d59627c0f
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/video_frame_writer_unittest.cc
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2019 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/video_frame_writer.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "api/test/video/video_frame_writer.h"
+#include "api/video/i420_buffer.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+const size_t kFrameWidth = 50;
+const size_t kFrameHeight = 20;
+const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420.
+const size_t kFrameRate = 30;
+
+// Size of header: "YUV4MPEG2 W50 H20 F30:1 C420\n"
+const size_t kFileHeaderSize = 29;
+// Size of header: "FRAME\n"
+const size_t kFrameHeaderSize = 6;
+
+rtc::scoped_refptr<I420Buffer> CreateI420Buffer(int width, int height) {
+ rtc::scoped_refptr<I420Buffer> buffer(I420Buffer::Create(width, height));
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ buffer->MutableDataY()[x + y * width] = 128;
+ }
+ }
+ int chroma_width = buffer->ChromaWidth();
+ int chroma_height = buffer->ChromaHeight();
+ for (int x = 0; x < chroma_width; x++) {
+ for (int y = 0; y < chroma_height; y++) {
+ buffer->MutableDataU()[x + y * chroma_width] = 1;
+ buffer->MutableDataV()[x + y * chroma_width] = 255;
+ }
+ }
+ return buffer;
+}
+
+void AssertI420BuffersEq(
+ rtc::scoped_refptr<webrtc::I420BufferInterface> actual,
+ rtc::scoped_refptr<webrtc::I420BufferInterface> expected) {
+ ASSERT_TRUE(actual);
+
+ ASSERT_EQ(actual->width(), expected->width());
+ ASSERT_EQ(actual->height(), expected->height());
+ const int width = expected->width();
+ const int height = expected->height();
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ ASSERT_EQ(actual->DataY()[x + y * width],
+ expected->DataY()[x + y * width]);
+ }
+ }
+
+ ASSERT_EQ(actual->ChromaWidth(), expected->ChromaWidth());
+ ASSERT_EQ(actual->ChromaHeight(), expected->ChromaHeight());
+ int chroma_width = expected->ChromaWidth();
+ int chroma_height = expected->ChromaHeight();
+ for (int x = 0; x < chroma_width; x++) {
+ for (int y = 0; y < chroma_height; y++) {
+ ASSERT_EQ(actual->DataU()[x + y * chroma_width],
+ expected->DataU()[x + y * chroma_width]);
+ ASSERT_EQ(actual->DataV()[x + y * chroma_width],
+ expected->DataV()[x + y * chroma_width]);
+ }
+ }
+}
+
+} // namespace
+
+class VideoFrameWriterTest : public ::testing::Test {
+ protected:
+ VideoFrameWriterTest() = default;
+ ~VideoFrameWriterTest() override = default;
+
+ void SetUp() override {
+ temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+ "video_frame_writer_unittest");
+ frame_writer_ = CreateFrameWriter();
+ }
+
+ virtual std::unique_ptr<VideoFrameWriter> CreateFrameWriter() = 0;
+
+ void TearDown() override { remove(temp_filename_.c_str()); }
+
+ std::unique_ptr<VideoFrameWriter> frame_writer_;
+ std::string temp_filename_;
+};
+
+class Y4mVideoFrameWriterTest : public VideoFrameWriterTest {
+ protected:
+ std::unique_ptr<VideoFrameWriter> CreateFrameWriter() override {
+ return std::make_unique<Y4mVideoFrameWriterImpl>(
+ temp_filename_, kFrameWidth, kFrameHeight, kFrameRate);
+ }
+};
+
+class YuvVideoFrameWriterTest : public VideoFrameWriterTest {
+ protected:
+ std::unique_ptr<VideoFrameWriter> CreateFrameWriter() override {
+ return std::make_unique<YuvVideoFrameWriterImpl>(temp_filename_,
+ kFrameWidth, kFrameHeight);
+ }
+};
+
+TEST_F(Y4mVideoFrameWriterTest, InitSuccess) {}
+
+TEST_F(Y4mVideoFrameWriterTest, WriteFrame) {
+ rtc::scoped_refptr<I420Buffer> expected_buffer =
+ CreateI420Buffer(kFrameWidth, kFrameHeight);
+
+ VideoFrame frame =
+ VideoFrame::Builder().set_video_frame_buffer(expected_buffer).build();
+
+ ASSERT_TRUE(frame_writer_->WriteFrame(frame));
+ ASSERT_TRUE(frame_writer_->WriteFrame(frame));
+
+ frame_writer_->Close();
+ EXPECT_EQ(kFileHeaderSize + 2 * kFrameHeaderSize + 2 * kFrameLength,
+ GetFileSize(temp_filename_));
+
+ std::unique_ptr<FrameReader> frame_reader =
+ CreateY4mFrameReader(temp_filename_);
+ AssertI420BuffersEq(frame_reader->PullFrame(), expected_buffer);
+ AssertI420BuffersEq(frame_reader->PullFrame(), expected_buffer);
+ EXPECT_FALSE(frame_reader->PullFrame()); // End of file.
+}
+
+TEST_F(YuvVideoFrameWriterTest, InitSuccess) {}
+
+TEST_F(YuvVideoFrameWriterTest, WriteFrame) {
+ rtc::scoped_refptr<I420Buffer> expected_buffer =
+ CreateI420Buffer(kFrameWidth, kFrameHeight);
+
+ VideoFrame frame =
+ VideoFrame::Builder().set_video_frame_buffer(expected_buffer).build();
+
+ ASSERT_TRUE(frame_writer_->WriteFrame(frame));
+ ASSERT_TRUE(frame_writer_->WriteFrame(frame));
+
+ frame_writer_->Close();
+ EXPECT_EQ(2 * kFrameLength, GetFileSize(temp_filename_));
+
+ std::unique_ptr<FrameReader> frame_reader = CreateYuvFrameReader(
+ temp_filename_,
+ Resolution({.width = kFrameWidth, .height = kFrameHeight}));
+ AssertI420BuffersEq(frame_reader->PullFrame(), expected_buffer);
+ AssertI420BuffersEq(frame_reader->PullFrame(), expected_buffer);
+ EXPECT_FALSE(frame_reader->PullFrame()); // End of file.
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_generator.cc b/third_party/libwebrtc/test/testsupport/y4m_frame_generator.cc
new file mode 100644
index 0000000000..f1ecbf9b41
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_generator.cc
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2023 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/y4m_frame_generator.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "rtc_base/checks.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+// Reading 30 bytes from the Y4M header should be enough to get width
+// and heigth.
+// The header starts with: `YUV4MPEG2 W<WIDTH> H<HEIGTH>`.
+constexpr int kHeaderBytesToRead = 30;
+} // namespace
+
+Y4mFrameGenerator::Y4mFrameGenerator(absl::string_view filename,
+ RepeatMode repeat_mode)
+ : filename_(filename), repeat_mode_(repeat_mode) {
+ // Read resolution from the Y4M header.
+ FILE* file = fopen(filename_.c_str(), "r");
+ RTC_CHECK(file != NULL) << "Cannot open " << filename_;
+ char header[kHeaderBytesToRead];
+ RTC_CHECK(fgets(header, sizeof(header), file) != nullptr)
+ << "File " << filename_ << " is too small";
+ fclose(file);
+ int fps_denominator;
+ RTC_CHECK_EQ(sscanf(header, "YUV4MPEG2 W%zu H%zu F%i:%i", &width_, &height_,
+ &fps_, &fps_denominator),
+ 4);
+ fps_ /= fps_denominator;
+ RTC_CHECK_GT(width_, 0);
+ RTC_CHECK_GT(height_, 0);
+
+ // Delegate the actual reads (from NextFrame) to a Y4mReader.
+ frame_reader_ = webrtc::test::CreateY4mFrameReader(
+ filename_, ToYuvFrameReaderRepeatMode(repeat_mode_));
+}
+
+Y4mFrameGenerator::VideoFrameData Y4mFrameGenerator::NextFrame() {
+ webrtc::VideoFrame::UpdateRect update_rect{0, 0, static_cast<int>(width_),
+ static_cast<int>(height_)};
+ rtc::scoped_refptr<webrtc::I420Buffer> next_frame_buffer =
+ frame_reader_->PullFrame();
+
+ if (!next_frame_buffer ||
+ (static_cast<size_t>(next_frame_buffer->width()) == width_ &&
+ static_cast<size_t>(next_frame_buffer->height()) == height_)) {
+ return VideoFrameData(next_frame_buffer, update_rect);
+ }
+
+ // Allocate a new buffer and return scaled version.
+ rtc::scoped_refptr<webrtc::I420Buffer> scaled_buffer(
+ I420Buffer::Create(width_, height_));
+ webrtc::I420Buffer::SetBlack(scaled_buffer.get());
+ scaled_buffer->ScaleFrom(*next_frame_buffer->ToI420());
+ return VideoFrameData(scaled_buffer, update_rect);
+}
+
+void Y4mFrameGenerator::ChangeResolution(size_t width, size_t height) {
+ width_ = width;
+ height_ = height;
+ RTC_CHECK_GT(width_, 0);
+ RTC_CHECK_GT(height_, 0);
+}
+
+FrameGeneratorInterface::Resolution Y4mFrameGenerator::GetResolution() const {
+ return {.width = width_, .height = height_};
+}
+
+YuvFrameReaderImpl::RepeatMode Y4mFrameGenerator::ToYuvFrameReaderRepeatMode(
+ RepeatMode repeat_mode) const {
+ switch (repeat_mode) {
+ case RepeatMode::kSingle:
+ return YuvFrameReaderImpl::RepeatMode::kSingle;
+ case RepeatMode::kLoop:
+ return YuvFrameReaderImpl::RepeatMode::kRepeat;
+ case RepeatMode::kPingPong:
+ return YuvFrameReaderImpl::RepeatMode::kPingPong;
+ }
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_generator.h b/third_party/libwebrtc/test/testsupport/y4m_frame_generator.h
new file mode 100644
index 0000000000..4ff64be7dc
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_generator.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023 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_Y4M_FRAME_GENERATOR_H_
+#define TEST_TESTSUPPORT_Y4M_FRAME_GENERATOR_H_
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/test/frame_generator_interface.h"
+#include "rtc_base/checks.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+
+// Generates frames from a Y4M file. The behaviour when reaching EOF is
+// configurable via RepeatMode.
+class Y4mFrameGenerator : public FrameGeneratorInterface {
+ public:
+ enum class RepeatMode {
+ // Generate frames from the input file, but it stops generating new frames
+ // once EOF is reached.
+ kSingle,
+ // Generate frames from the input file, when EOF is reached it starts from
+ // the beginning.
+ kLoop,
+ // Generate frames from the input file, when EOF is reached it plays frames
+ // backwards from the end to the beginning of the file (and vice versa,
+ // literally doing Ping/Pong between the beginning and the end of the file).
+ kPingPong,
+ };
+ Y4mFrameGenerator(absl::string_view filename, RepeatMode repeat_mode);
+ ~Y4mFrameGenerator() override = default;
+
+ VideoFrameData NextFrame() override;
+
+ void ChangeResolution(size_t width, size_t height) override;
+
+ Resolution GetResolution() const override;
+
+ absl::optional<int> fps() const override { return fps_; }
+
+ private:
+ YuvFrameReaderImpl::RepeatMode ToYuvFrameReaderRepeatMode(
+ RepeatMode repeat_mode) const;
+ std::unique_ptr<webrtc::test::FrameReader> frame_reader_ = nullptr;
+ std::string filename_;
+ size_t width_;
+ size_t height_;
+ int fps_;
+ const RepeatMode repeat_mode_;
+};
+
+} // namespace test
+} // namespace webrtc
+
+#endif // TEST_TESTSUPPORT_Y4M_FRAME_GENERATOR_H_
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_generator_test.cc b/third_party/libwebrtc/test/testsupport/y4m_frame_generator_test.cc
new file mode 100644
index 0000000000..24d10c8992
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_generator_test.cc
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2023 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/y4m_frame_generator.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+
+namespace webrtc {
+namespace test {
+
+class Y4mFrameGeneratorTest : public testing::Test {
+ protected:
+ Y4mFrameGeneratorTest() = default;
+ ~Y4mFrameGeneratorTest() = default;
+
+ void SetUp() {
+ input_filepath_ = TempFilename(OutputPath(), "2x2.y4m");
+ FILE* y4m_file = fopen(input_filepath_.c_str(), "wb");
+
+ // Input Y4M file: 3 YUV frames of 2x2 resolution.
+ std::string y4m_content =
+ "YUV4MPEG2 W2 H2 F2:1 C420\n"
+ "FRAME\n"
+ "123456FRAME\n"
+ "abcdefFRAME\n"
+ "987654";
+ std::fprintf(y4m_file, "%s", y4m_content.c_str());
+ fclose(y4m_file);
+ }
+
+ void TearDown() { remove(input_filepath_.c_str()); }
+
+ std::string input_filepath_;
+};
+
+TEST_F(Y4mFrameGeneratorTest, CanReadResolutionFromFile) {
+ Y4mFrameGenerator generator(input_filepath_,
+ Y4mFrameGenerator::RepeatMode::kSingle);
+ FrameGeneratorInterface::Resolution res = generator.GetResolution();
+ EXPECT_EQ(res.width, 2u);
+ EXPECT_EQ(res.height, 2u);
+}
+
+TEST_F(Y4mFrameGeneratorTest, CanReadFPSFromFile) {
+ Y4mFrameGenerator generator(input_filepath_,
+ Y4mFrameGenerator::RepeatMode::kSingle);
+ EXPECT_EQ(*generator.fps(), 2);
+}
+
+TEST_F(Y4mFrameGeneratorTest, CanReadFPSFromFileWhenRoundingIsNeeded) {
+ std::string input_filepath = TempFilename(OutputPath(), "2x2_23_FPS.y4m");
+ FILE* y4m_file = fopen(input_filepath.c_str(), "wb");
+
+ // Input Y4M file: 3 YUV frames of 2x2 resolution.
+ std::string y4m_content =
+ "YUV4MPEG2 W2 H2 F24000:1001 C420\n"
+ "FRAME\n"
+ "123456FRAME\n"
+ "abcdefFRAME\n"
+ "987654";
+ std::fprintf(y4m_file, "%s", y4m_content.c_str());
+ fclose(y4m_file);
+
+ Y4mFrameGenerator generator(input_filepath,
+ Y4mFrameGenerator::RepeatMode::kSingle);
+ EXPECT_EQ(generator.fps(), 23);
+ remove(input_filepath.c_str());
+}
+
+TEST_F(Y4mFrameGeneratorTest, CanChangeResolution) {
+ constexpr int kNewWidth = 4;
+ constexpr int kNewHeight = 6;
+ constexpr int kFrameCount = 10;
+
+ Y4mFrameGenerator generator(input_filepath_,
+ Y4mFrameGenerator::RepeatMode::kLoop);
+ FrameGeneratorInterface::Resolution res = generator.GetResolution();
+ EXPECT_EQ(res.width, 2u);
+ EXPECT_EQ(res.height, 2u);
+
+ generator.ChangeResolution(kNewWidth, kNewHeight);
+ res = generator.GetResolution();
+ EXPECT_EQ(static_cast<int>(res.width), kNewWidth);
+ EXPECT_EQ(static_cast<int>(res.height), kNewHeight);
+
+ for (int i = 0; i < kFrameCount; ++i) {
+ FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame();
+ EXPECT_EQ(frame.buffer->width(), kNewWidth);
+ EXPECT_EQ(frame.buffer->height(), kNewHeight);
+ }
+}
+
+TEST_F(Y4mFrameGeneratorTest, SingleRepeatMode) {
+ Y4mFrameGenerator generator(input_filepath_,
+ Y4mFrameGenerator::RepeatMode::kSingle);
+
+ std::vector<std::string> expected_frame_ys = {"123456", "abcdef", "987654"};
+ for (absl::string_view frame_y : expected_frame_ys) {
+ EXPECT_EQ(frame_y.size(), 6u);
+ FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame();
+ EXPECT_EQ(memcmp(frame_y.data(), frame.buffer->GetI420()->DataY(), 6), 0);
+ }
+ FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame();
+ EXPECT_EQ(frame.buffer, nullptr);
+}
+
+TEST_F(Y4mFrameGeneratorTest, LoopRepeatMode) {
+ Y4mFrameGenerator generator(input_filepath_,
+ Y4mFrameGenerator::RepeatMode::kLoop);
+
+ std::vector<std::string> expected_frame_ys = {"123456", "abcdef", "987654",
+ "123456", "abcdef", "987654"};
+ for (absl::string_view frame_y : expected_frame_ys) {
+ EXPECT_EQ(frame_y.size(), 6u);
+ FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame();
+ EXPECT_EQ(memcmp(frame_y.data(), frame.buffer->GetI420()->DataY(), 6), 0);
+ }
+}
+
+TEST_F(Y4mFrameGeneratorTest, PingPongRepeatMode) {
+ Y4mFrameGenerator generator(input_filepath_,
+ Y4mFrameGenerator::RepeatMode::kPingPong);
+
+ std::vector<std::string> expected_frame_ys = {
+ "123456", "abcdef", "987654", "abcdef", "123456", "abcdef", "987654"};
+ for (absl::string_view frame_y : expected_frame_ys) {
+ EXPECT_EQ(frame_y.size(), 6u);
+ FrameGeneratorInterface::VideoFrameData frame = generator.NextFrame();
+ EXPECT_EQ(memcmp(frame_y.data(), frame.buffer->GetI420()->DataY(), 6), 0);
+ }
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_reader.cc b/third_party/libwebrtc/test/testsupport/y4m_frame_reader.cc
new file mode 100644
index 0000000000..72fb9b5188
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_reader.cc
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018 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 <stdio.h>
+
+#include <string>
+
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/strings/string_builder.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+constexpr int kFrameHeaderSize = 6; // "FRAME\n"
+} // namespace
+
+void ParseY4mHeader(std::string filepath,
+ Resolution* resolution,
+ int* header_size) {
+ FILE* file = fopen(filepath.c_str(), "r");
+ RTC_CHECK(file != NULL) << "Cannot open " << filepath;
+
+ // Length of Y4M header is technically unlimited due to the comment tag 'X'.
+ char h[1024];
+ RTC_CHECK(fgets(h, sizeof(h), file) != NULL)
+ << "File " << filepath << " is too small";
+ fclose(file);
+
+ RTC_CHECK(sscanf(h, "YUV4MPEG2 W%d H%d", &resolution->width,
+ &resolution->height) == 2)
+ << filepath << " is not a valid Y4M file";
+
+ RTC_CHECK_GT(resolution->width, 0) << "Width must be positive";
+ RTC_CHECK_GT(resolution->height, 0) << "Height must be positive";
+
+ *header_size = strcspn(h, "\n") + 1;
+ RTC_CHECK(static_cast<unsigned>(*header_size) < sizeof(h))
+ << filepath << " has unexpectedly large header";
+}
+
+Y4mFrameReaderImpl::Y4mFrameReaderImpl(std::string filepath,
+ RepeatMode repeat_mode)
+ : YuvFrameReaderImpl(filepath, Resolution(), repeat_mode) {}
+
+void Y4mFrameReaderImpl::Init() {
+ file_ = fopen(filepath_.c_str(), "rb");
+ RTC_CHECK(file_ != nullptr) << "Cannot open " << filepath_;
+
+ ParseY4mHeader(filepath_, &resolution_, &header_size_bytes_);
+ frame_size_bytes_ =
+ CalcBufferSize(VideoType::kI420, resolution_.width, resolution_.height);
+ frame_size_bytes_ += kFrameHeaderSize;
+
+ size_t file_size_bytes = GetFileSize(filepath_);
+ RTC_CHECK_GT(file_size_bytes, 0u) << "File " << filepath_ << " is empty";
+ RTC_CHECK_GT(file_size_bytes, header_size_bytes_)
+ << "File " << filepath_ << " is too small";
+
+ num_frames_ = static_cast<int>((file_size_bytes - header_size_bytes_) /
+ frame_size_bytes_);
+ RTC_CHECK_GT(num_frames_, 0u) << "File " << filepath_ << " is too small";
+ header_size_bytes_ += kFrameHeaderSize;
+}
+
+std::unique_ptr<FrameReader> CreateY4mFrameReader(std::string filepath) {
+ return CreateY4mFrameReader(filepath,
+ YuvFrameReaderImpl::RepeatMode::kSingle);
+}
+
+std::unique_ptr<FrameReader> CreateY4mFrameReader(
+ std::string filepath,
+ YuvFrameReaderImpl::RepeatMode repeat_mode) {
+ Y4mFrameReaderImpl* frame_reader =
+ new Y4mFrameReaderImpl(filepath, repeat_mode);
+ frame_reader->Init();
+ return std::unique_ptr<FrameReader>(frame_reader);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_reader_unittest.cc b/third_party/libwebrtc/test/testsupport/y4m_frame_reader_unittest.cc
new file mode 100644
index 0000000000..df81a8135b
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_reader_unittest.cc
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2018 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 <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame_buffer.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+using Ratio = FrameReader::Ratio;
+using RepeatMode = YuvFrameReaderImpl::RepeatMode;
+
+constexpr Resolution kResolution({.width = 1, .height = 1});
+constexpr char kFileHeader[] = "YUV4MPEG2 W1 H1 F30:1 C420\n";
+constexpr char kFrameHeader[] = "FRAME\n";
+constexpr char kFrameContent[3][3] = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}};
+constexpr int kNumFrames = sizeof(kFrameContent) / sizeof(kFrameContent[0]);
+} // namespace
+
+class Y4mFrameReaderTest : public ::testing::Test {
+ protected:
+ Y4mFrameReaderTest() = default;
+ ~Y4mFrameReaderTest() override = default;
+
+ void SetUp() override {
+ filepath_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+ "y4m_frame_reader_unittest");
+ FILE* file = fopen(filepath_.c_str(), "wb");
+ fwrite(kFileHeader, 1, sizeof(kFileHeader) - 1, file);
+ for (int n = 0; n < kNumFrames; ++n) {
+ fwrite(kFrameHeader, 1, sizeof(kFrameHeader) - 1, file);
+ fwrite(kFrameContent[n], 1, sizeof(kFrameContent[n]), file);
+ }
+ fclose(file);
+
+ reader_ = CreateY4mFrameReader(filepath_);
+ }
+
+ void TearDown() override { remove(filepath_.c_str()); }
+
+ std::string filepath_;
+ std::unique_ptr<FrameReader> reader_;
+};
+
+TEST_F(Y4mFrameReaderTest, num_frames) {
+ EXPECT_EQ(kNumFrames, reader_->num_frames());
+}
+
+TEST_F(Y4mFrameReaderTest, PullFrame_frameResolution) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame();
+ EXPECT_EQ(kResolution.width, buffer->width());
+ EXPECT_EQ(kResolution.height, buffer->height());
+}
+
+TEST_F(Y4mFrameReaderTest, PullFrame_frameContent) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame();
+ EXPECT_EQ(kFrameContent[0][0], *buffer->DataY());
+ EXPECT_EQ(kFrameContent[0][1], *buffer->DataU());
+ EXPECT_EQ(kFrameContent[0][2], *buffer->DataV());
+}
+
+TEST_F(Y4mFrameReaderTest, ReadFrame_randomOrder) {
+ std::vector<int> expected_frames = {2, 0, 1};
+ std::vector<int> actual_frames;
+ for (int frame_num : expected_frames) {
+ rtc::scoped_refptr<I420BufferInterface> buffer =
+ reader_->ReadFrame(frame_num);
+ actual_frames.push_back(*buffer->DataY());
+ }
+ EXPECT_EQ(expected_frames, actual_frames);
+}
+
+TEST_F(Y4mFrameReaderTest, PullFrame_scale) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame(
+ /*pulled_frame_num=*/nullptr, Resolution({.width = 2, .height = 2}),
+ FrameReader::kNoScale);
+ EXPECT_EQ(2, buffer->width());
+ EXPECT_EQ(2, buffer->height());
+}
+
+class Y4mFrameReaderRepeatModeTest
+ : public Y4mFrameReaderTest,
+ public ::testing::WithParamInterface<
+ std::tuple<RepeatMode, std::vector<int>>> {};
+
+TEST_P(Y4mFrameReaderRepeatModeTest, PullFrame) {
+ RepeatMode mode = std::get<0>(GetParam());
+ std::vector<int> expected_frames = std::get<1>(GetParam());
+
+ reader_ = CreateY4mFrameReader(filepath_, mode);
+ std::vector<int> read_frames;
+ for (size_t i = 0; i < expected_frames.size(); ++i) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame();
+ read_frames.push_back(*buffer->DataY());
+ }
+ EXPECT_EQ(expected_frames, read_frames);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Y4mFrameReaderTest,
+ Y4mFrameReaderRepeatModeTest,
+ ::testing::ValuesIn(
+ {std::make_tuple(RepeatMode::kSingle, std::vector<int>{0, 1, 2}),
+ std::make_tuple(RepeatMode::kRepeat,
+ std::vector<int>{0, 1, 2, 0, 1, 2}),
+ std::make_tuple(RepeatMode::kPingPong,
+ std::vector<int>{0, 1, 2, 1, 0, 1, 2})}));
+
+class Y4mFrameReaderFramerateScaleTest
+ : public Y4mFrameReaderTest,
+ public ::testing::WithParamInterface<
+ std::tuple<Ratio, std::vector<int>>> {};
+
+TEST_P(Y4mFrameReaderFramerateScaleTest, PullFrame) {
+ Ratio framerate_scale = std::get<0>(GetParam());
+ std::vector<int> expected_frames = std::get<1>(GetParam());
+
+ std::vector<int> actual_frames;
+ for (size_t i = 0; i < expected_frames.size(); ++i) {
+ int pulled_frame;
+ rtc::scoped_refptr<I420BufferInterface> buffer =
+ reader_->PullFrame(&pulled_frame, kResolution, framerate_scale);
+ actual_frames.push_back(pulled_frame);
+ }
+ EXPECT_EQ(expected_frames, actual_frames);
+}
+
+INSTANTIATE_TEST_SUITE_P(Y4mFrameReaderTest,
+ Y4mFrameReaderFramerateScaleTest,
+ ::testing::ValuesIn({
+ std::make_tuple(Ratio({.num = 1, .den = 2}),
+ std::vector<int>{0, 2, 4}),
+ std::make_tuple(Ratio({.num = 2, .den = 3}),
+ std::vector<int>{0, 1, 3, 4, 6}),
+ std::make_tuple(Ratio({.num = 2, .den = 1}),
+ std::vector<int>{0, 0, 1, 1}),
+ }));
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_writer.cc b/third_party/libwebrtc/test/testsupport/y4m_frame_writer.cc
new file mode 100644
index 0000000000..1bb4543963
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_writer.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 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 <stdio.h>
+
+#include <string>
+
+#include "rtc_base/logging.h"
+#include "test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+Y4mFrameWriterImpl::Y4mFrameWriterImpl(std::string output_filename,
+ int width,
+ int height,
+ int frame_rate)
+ : YuvFrameWriterImpl(output_filename, width, height),
+ frame_rate_(frame_rate) {}
+
+Y4mFrameWriterImpl::~Y4mFrameWriterImpl() = default;
+
+bool Y4mFrameWriterImpl::Init() {
+ if (!YuvFrameWriterImpl::Init()) {
+ return false;
+ }
+ int bytes_written = fprintf(output_file_, "YUV4MPEG2 W%d H%d F%d:1 C420\n",
+ width_, height_, frame_rate_);
+ if (bytes_written < 0) {
+ RTC_LOG(LS_ERROR) << "Failed to write Y4M file header to file: "
+ << output_filename_.c_str();
+ return false;
+ }
+ return true;
+}
+
+bool Y4mFrameWriterImpl::WriteFrame(const uint8_t* frame_buffer) {
+ if (output_file_ == nullptr) {
+ RTC_LOG(LS_ERROR) << "Y4mFrameWriterImpl is not initialized.";
+ return false;
+ }
+ int bytes_written = fprintf(output_file_, "FRAME\n");
+ if (bytes_written < 0) {
+ RTC_LOG(LS_ERROR) << "Couldn't write Y4M frame header to file: "
+ << output_filename_.c_str();
+ return false;
+ }
+ return YuvFrameWriterImpl::WriteFrame(frame_buffer);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/y4m_frame_writer_unittest.cc b/third_party/libwebrtc/test/testsupport/y4m_frame_writer_unittest.cc
new file mode 100644
index 0000000000..f12a4b8e4f
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/y4m_frame_writer_unittest.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 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 <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <memory>
+#include <string>
+
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+const size_t kFrameWidth = 50;
+const size_t kFrameHeight = 20;
+const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420.
+const size_t kFrameRate = 30;
+
+const std::string kFileHeader = "YUV4MPEG2 W50 H20 F30:1 C420\n";
+const std::string kFrameHeader = "FRAME\n";
+} // namespace
+
+class Y4mFrameWriterTest : public ::testing::Test {
+ protected:
+ Y4mFrameWriterTest() = default;
+ ~Y4mFrameWriterTest() override = default;
+
+ void SetUp() override {
+ temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+ "y4m_frame_writer_unittest");
+ frame_writer_.reset(new Y4mFrameWriterImpl(temp_filename_, kFrameWidth,
+ kFrameHeight, kFrameRate));
+ ASSERT_TRUE(frame_writer_->Init());
+ }
+
+ void TearDown() override { remove(temp_filename_.c_str()); }
+
+ std::unique_ptr<FrameWriter> frame_writer_;
+ std::string temp_filename_;
+};
+
+TEST_F(Y4mFrameWriterTest, InitSuccess) {}
+
+TEST_F(Y4mFrameWriterTest, FrameLength) {
+ EXPECT_EQ(kFrameLength, frame_writer_->FrameLength());
+}
+
+TEST_F(Y4mFrameWriterTest, WriteFrame) {
+ uint8_t buffer[kFrameLength];
+ memset(buffer, 9, kFrameLength); // Write lots of 9s to the buffer.
+ bool result = frame_writer_->WriteFrame(buffer);
+ ASSERT_TRUE(result);
+ result = frame_writer_->WriteFrame(buffer);
+ ASSERT_TRUE(result);
+
+ frame_writer_->Close();
+ EXPECT_EQ(kFileHeader.size() + 2 * kFrameHeader.size() + 2 * kFrameLength,
+ GetFileSize(temp_filename_));
+}
+
+TEST_F(Y4mFrameWriterTest, WriteFrameUninitialized) {
+ uint8_t buffer[kFrameLength];
+ Y4mFrameWriterImpl frame_writer(temp_filename_, kFrameWidth, kFrameHeight,
+ kFrameRate);
+ EXPECT_FALSE(frame_writer.WriteFrame(buffer));
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/yuv_frame_reader.cc b/third_party/libwebrtc/test/testsupport/yuv_frame_reader.cc
new file mode 100644
index 0000000000..db4ba23269
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/yuv_frame_reader.cc
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 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 <stdio.h>
+
+#include <string>
+
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "rtc_base/logging.h"
+#include "test/frame_utils.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+using RepeatMode = YuvFrameReaderImpl::RepeatMode;
+
+int WrapFrameNum(int frame_num, int num_frames, RepeatMode mode) {
+ RTC_CHECK_GE(frame_num, 0) << "frame_num cannot be negative";
+ RTC_CHECK_GT(num_frames, 0) << "num_frames must be greater than 0";
+ if (mode == RepeatMode::kSingle) {
+ return frame_num;
+ }
+ if (mode == RepeatMode::kRepeat) {
+ return frame_num % num_frames;
+ }
+
+ RTC_CHECK_EQ(RepeatMode::kPingPong, mode);
+ int cycle_len = std::max(1, 2 * (num_frames - 1));
+ int wrapped_num = frame_num % cycle_len;
+ if (wrapped_num >= num_frames) {
+ return cycle_len - wrapped_num;
+ }
+ return wrapped_num;
+}
+
+rtc::scoped_refptr<I420Buffer> Scale(rtc::scoped_refptr<I420Buffer> buffer,
+ Resolution resolution) {
+ if (buffer->width() == resolution.width &&
+ buffer->height() == resolution.height) {
+ return buffer;
+ }
+ rtc::scoped_refptr<I420Buffer> scaled(
+ I420Buffer::Create(resolution.width, resolution.height));
+ scaled->ScaleFrom(*buffer.get());
+ return scaled;
+}
+} // namespace
+
+int YuvFrameReaderImpl::RateScaler::Skip(Ratio framerate_scale) {
+ ticks_ = ticks_.value_or(framerate_scale.num);
+ int skip = 0;
+ while (ticks_ <= 0) {
+ *ticks_ += framerate_scale.num;
+ ++skip;
+ }
+ *ticks_ -= framerate_scale.den;
+ return skip;
+}
+
+YuvFrameReaderImpl::YuvFrameReaderImpl(std::string filepath,
+ Resolution resolution,
+ RepeatMode repeat_mode)
+ : filepath_(filepath),
+ resolution_(resolution),
+ repeat_mode_(repeat_mode),
+ num_frames_(0),
+ frame_num_(0),
+ frame_size_bytes_(0),
+ header_size_bytes_(0),
+ file_(nullptr) {}
+
+YuvFrameReaderImpl::~YuvFrameReaderImpl() {
+ if (file_ != nullptr) {
+ fclose(file_);
+ file_ = nullptr;
+ }
+}
+
+void YuvFrameReaderImpl::Init() {
+ RTC_CHECK_GT(resolution_.width, 0) << "Width must be positive";
+ RTC_CHECK_GT(resolution_.height, 0) << "Height must be positive";
+ frame_size_bytes_ =
+ CalcBufferSize(VideoType::kI420, resolution_.width, resolution_.height);
+
+ file_ = fopen(filepath_.c_str(), "rb");
+ RTC_CHECK(file_ != NULL) << "Cannot open " << filepath_;
+
+ size_t file_size_bytes = GetFileSize(filepath_);
+ RTC_CHECK_GT(file_size_bytes, 0u) << "File " << filepath_ << " is empty";
+
+ num_frames_ = static_cast<int>(file_size_bytes / frame_size_bytes_);
+ RTC_CHECK_GT(num_frames_, 0u) << "File " << filepath_ << " is too small";
+}
+
+rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::PullFrame() {
+ return PullFrame(/*frame_num=*/nullptr);
+}
+
+rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::PullFrame(int* frame_num) {
+ return PullFrame(frame_num, resolution_, /*framerate_scale=*/kNoScale);
+}
+
+rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::PullFrame(
+ int* frame_num,
+ Resolution resolution,
+ Ratio framerate_scale) {
+ frame_num_ += framerate_scaler_.Skip(framerate_scale);
+ auto buffer = ReadFrame(frame_num_, resolution);
+ if (frame_num != nullptr) {
+ *frame_num = frame_num_;
+ }
+ return buffer;
+}
+
+rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::ReadFrame(int frame_num) {
+ return ReadFrame(frame_num, resolution_);
+}
+
+rtc::scoped_refptr<I420Buffer> YuvFrameReaderImpl::ReadFrame(
+ int frame_num,
+ Resolution resolution) {
+ int wrapped_num = WrapFrameNum(frame_num, num_frames_, repeat_mode_);
+ if (wrapped_num >= num_frames_) {
+ RTC_CHECK_EQ(RepeatMode::kSingle, repeat_mode_);
+ return nullptr;
+ }
+ fseek(file_, header_size_bytes_ + wrapped_num * frame_size_bytes_, SEEK_SET);
+ auto buffer = ReadI420Buffer(resolution_.width, resolution_.height, file_);
+ RTC_CHECK(buffer != nullptr);
+
+ return Scale(buffer, resolution);
+}
+
+std::unique_ptr<FrameReader> CreateYuvFrameReader(std::string filepath,
+ Resolution resolution) {
+ return CreateYuvFrameReader(filepath, resolution,
+ YuvFrameReaderImpl::RepeatMode::kSingle);
+}
+
+std::unique_ptr<FrameReader> CreateYuvFrameReader(
+ std::string filepath,
+ Resolution resolution,
+ YuvFrameReaderImpl::RepeatMode repeat_mode) {
+ YuvFrameReaderImpl* frame_reader =
+ new YuvFrameReaderImpl(filepath, resolution, repeat_mode);
+ frame_reader->Init();
+ return std::unique_ptr<FrameReader>(frame_reader);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/yuv_frame_reader_unittest.cc b/third_party/libwebrtc/test/testsupport/yuv_frame_reader_unittest.cc
new file mode 100644
index 0000000000..b8f902bb88
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/yuv_frame_reader_unittest.cc
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 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 <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame_buffer.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+using Ratio = FrameReader::Ratio;
+using RepeatMode = YuvFrameReaderImpl::RepeatMode;
+
+constexpr Resolution kResolution({.width = 1, .height = 1});
+constexpr int kDefaultNumFrames = 3;
+} // namespace
+
+class YuvFrameReaderTest : public ::testing::Test {
+ protected:
+ YuvFrameReaderTest() = default;
+ ~YuvFrameReaderTest() override = default;
+
+ void SetUp() override {
+ filepath_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+ "yuv_frame_reader_unittest");
+ CreateYuvFileAndReader(/*num_frames=*/3, RepeatMode::kSingle);
+ }
+
+ void TearDown() override { remove(filepath_.c_str()); }
+
+ void CreateYuvFileAndReader(int num_frames, RepeatMode repeat_mode) {
+ FILE* file = fopen(filepath_.c_str(), "wb");
+ for (int i = 0; i < num_frames; ++i) {
+ uint8_t y = static_cast<uint8_t>(i & 255);
+ uint8_t u = static_cast<uint8_t>((i + 1) & 255);
+ uint8_t v = static_cast<uint8_t>((i + 2) & 255);
+ fwrite(&y, 1, 1, file);
+ fwrite(&u, 1, 1, file);
+ fwrite(&v, 1, 1, file);
+ }
+ fclose(file);
+
+ reader_ = CreateYuvFrameReader(filepath_, kResolution, repeat_mode);
+ }
+
+ std::string filepath_;
+ std::unique_ptr<FrameReader> reader_;
+};
+
+TEST_F(YuvFrameReaderTest, num_frames) {
+ EXPECT_EQ(kDefaultNumFrames, reader_->num_frames());
+}
+
+TEST_F(YuvFrameReaderTest, PullFrame_frameContent) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame();
+ EXPECT_EQ(0u, *buffer->DataY());
+ EXPECT_EQ(1u, *buffer->DataU());
+ EXPECT_EQ(2u, *buffer->DataV());
+}
+
+TEST_F(YuvFrameReaderTest, ReadFrame_randomOrder) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->ReadFrame(2);
+ EXPECT_EQ(2u, *buffer->DataY());
+ buffer = reader_->ReadFrame(0);
+ EXPECT_EQ(0u, *buffer->DataY());
+ buffer = reader_->ReadFrame(1);
+ EXPECT_EQ(1u, *buffer->DataY());
+}
+
+TEST_F(YuvFrameReaderTest, PullFrame_scale) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame(
+ /*pulled_frame_num=*/nullptr, Resolution({.width = 2, .height = 2}),
+ FrameReader::kNoScale);
+ EXPECT_EQ(2, buffer->width());
+ EXPECT_EQ(2, buffer->height());
+}
+
+class YuvFrameReaderRepeatModeTest
+ : public YuvFrameReaderTest,
+ public ::testing::WithParamInterface<
+ std::tuple<int, RepeatMode, std::vector<uint8_t>>> {};
+
+TEST_P(YuvFrameReaderRepeatModeTest, PullFrame) {
+ auto [num_frames, repeat_mode, expected_frames] = GetParam();
+ CreateYuvFileAndReader(num_frames, repeat_mode);
+ for (auto expected_frame : expected_frames) {
+ rtc::scoped_refptr<I420BufferInterface> buffer = reader_->PullFrame();
+ EXPECT_EQ(expected_frame, *buffer->DataY());
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ YuvFrameReaderTest,
+ YuvFrameReaderRepeatModeTest,
+ ::testing::ValuesIn(
+ {std::make_tuple(3, RepeatMode::kSingle, std::vector<uint8_t>{0, 1, 2}),
+ std::make_tuple(3,
+ RepeatMode::kRepeat,
+ std::vector<uint8_t>{0, 1, 2, 0, 1, 2}),
+ std::make_tuple(3,
+ RepeatMode::kPingPong,
+ std::vector<uint8_t>{0, 1, 2, 1, 0, 1, 2}),
+ std::make_tuple(1,
+ RepeatMode::kPingPong,
+ std::vector<uint8_t>{0, 0})}));
+
+class YuvFrameReaderFramerateScaleTest
+ : public YuvFrameReaderTest,
+ public ::testing::WithParamInterface<
+ std::tuple<Ratio, std::vector<int>>> {};
+
+TEST_P(YuvFrameReaderFramerateScaleTest, PullFrame) {
+ auto [framerate_scale, expected_frames] = GetParam();
+ for (auto expected_frame : expected_frames) {
+ int pulled_frame;
+ rtc::scoped_refptr<I420BufferInterface> buffer =
+ reader_->PullFrame(&pulled_frame, kResolution, framerate_scale);
+ EXPECT_EQ(pulled_frame, expected_frame);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(YuvFrameReaderTest,
+ YuvFrameReaderFramerateScaleTest,
+ ::testing::ValuesIn({
+ std::make_tuple(Ratio({.num = 1, .den = 2}),
+ std::vector<int>{0, 2, 4}),
+ std::make_tuple(Ratio({.num = 2, .den = 3}),
+ std::vector<int>{0, 1, 3, 4, 6}),
+ std::make_tuple(Ratio({.num = 2, .den = 1}),
+ std::vector<int>{0, 0, 1, 1}),
+ }));
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/yuv_frame_writer.cc b/third_party/libwebrtc/test/testsupport/yuv_frame_writer.cc
new file mode 100644
index 0000000000..e5e0a6ba7f
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/yuv_frame_writer.cc
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 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 <stdio.h>
+
+#include <string>
+
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+YuvFrameWriterImpl::YuvFrameWriterImpl(std::string output_filename,
+ int width,
+ int height)
+ : output_filename_(output_filename),
+ frame_length_in_bytes_(0),
+ width_(width),
+ height_(height),
+ output_file_(nullptr) {}
+
+YuvFrameWriterImpl::~YuvFrameWriterImpl() {
+ Close();
+}
+
+bool YuvFrameWriterImpl::Init() {
+ if (width_ <= 0 || height_ <= 0) {
+ RTC_LOG(LS_ERROR) << "Frame width and height must be positive.";
+ return false;
+ }
+ frame_length_in_bytes_ =
+ width_ * height_ + 2 * ((width_ + 1) / 2) * ((height_ + 1) / 2);
+
+ output_file_ = fopen(output_filename_.c_str(), "wb");
+ if (output_file_ == nullptr) {
+ RTC_LOG(LS_ERROR) << "Couldn't open output file: "
+ << output_filename_.c_str();
+ return false;
+ }
+ return true;
+}
+
+bool YuvFrameWriterImpl::WriteFrame(const uint8_t* frame_buffer) {
+ RTC_DCHECK(frame_buffer);
+ if (output_file_ == nullptr) {
+ RTC_LOG(LS_ERROR) << "YuvFrameWriterImpl is not initialized.";
+ return false;
+ }
+ size_t bytes_written =
+ fwrite(frame_buffer, 1, frame_length_in_bytes_, output_file_);
+ if (bytes_written != frame_length_in_bytes_) {
+ RTC_LOG(LS_ERROR) << "Cound't write frame to file: "
+ << output_filename_.c_str();
+ return false;
+ }
+ return true;
+}
+
+void YuvFrameWriterImpl::Close() {
+ if (output_file_ != nullptr) {
+ fclose(output_file_);
+ output_file_ = nullptr;
+ }
+}
+
+size_t YuvFrameWriterImpl::FrameLength() {
+ return frame_length_in_bytes_;
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/third_party/libwebrtc/test/testsupport/yuv_frame_writer_unittest.cc b/third_party/libwebrtc/test/testsupport/yuv_frame_writer_unittest.cc
new file mode 100644
index 0000000000..13ed715b9e
--- /dev/null
+++ b/third_party/libwebrtc/test/testsupport/yuv_frame_writer_unittest.cc
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 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 <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <memory>
+#include <string>
+
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_writer.h"
+
+namespace webrtc {
+namespace test {
+
+namespace {
+const size_t kFrameWidth = 50;
+const size_t kFrameHeight = 20;
+const size_t kFrameLength = 3 * kFrameWidth * kFrameHeight / 2; // I420.
+} // namespace
+
+class YuvFrameWriterTest : public ::testing::Test {
+ protected:
+ YuvFrameWriterTest() = default;
+ ~YuvFrameWriterTest() override = default;
+
+ void SetUp() override {
+ temp_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(),
+ "yuv_frame_writer_unittest");
+ frame_writer_.reset(
+ new YuvFrameWriterImpl(temp_filename_, kFrameWidth, kFrameHeight));
+ ASSERT_TRUE(frame_writer_->Init());
+ }
+
+ void TearDown() override { remove(temp_filename_.c_str()); }
+
+ std::unique_ptr<FrameWriter> frame_writer_;
+ std::string temp_filename_;
+};
+
+TEST_F(YuvFrameWriterTest, InitSuccess) {}
+
+TEST_F(YuvFrameWriterTest, FrameLength) {
+ EXPECT_EQ(kFrameLength, frame_writer_->FrameLength());
+}
+
+TEST_F(YuvFrameWriterTest, WriteFrame) {
+ uint8_t buffer[kFrameLength];
+ memset(buffer, 9, kFrameLength); // Write lots of 9s to the buffer.
+ bool result = frame_writer_->WriteFrame(buffer);
+ ASSERT_TRUE(result);
+
+ frame_writer_->Close();
+ EXPECT_EQ(kFrameLength, GetFileSize(temp_filename_));
+}
+
+TEST_F(YuvFrameWriterTest, WriteFrameUninitialized) {
+ uint8_t buffer[kFrameLength];
+ YuvFrameWriterImpl frame_writer(temp_filename_, kFrameWidth, kFrameHeight);
+ EXPECT_FALSE(frame_writer.WriteFrame(buffer));
+}
+
+} // namespace test
+} // namespace webrtc