diff options
Diffstat (limited to 'third_party/libwebrtc/test/testsupport')
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 |