diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /third_party/libwebrtc/test | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/test')
684 files changed, 62289 insertions, 0 deletions
diff --git a/third_party/libwebrtc/test/BUILD.gn b/third_party/libwebrtc/test/BUILD.gn new file mode 100644 index 0000000000..04a718c411 --- /dev/null +++ b/third_party/libwebrtc/test/BUILD.gn @@ -0,0 +1,1263 @@ +# Copyright (c) 2014 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("//build/config/ui.gni") +import("//third_party/google_benchmark/buildconfig.gni") +import("../webrtc.gni") +if (is_android) { + import("//build/config/android/rules.gni") +} + +if (!build_with_chromium) { + group("test") { + testonly = true + + deps = [ + ":copy_to_file_audio_capturer", + ":rtp_test_utils", + ":test_common", + ":test_renderer", + ":test_support", + ":video_test_common", + ] + + if (rtc_include_tests) { + deps += [ + ":test_main", + ":test_support_unittests", + "pc/e2e", + ] + } + } +} + +rtc_library("frame_generator_impl") { + visibility = [ + ":*", + "../api:create_frame_generator", + ] + testonly = true + sources = [ + "frame_generator.cc", + "frame_generator.h", + "testsupport/ivf_video_frame_generator.cc", + "testsupport/ivf_video_frame_generator.h", + ] + deps = [ + ":frame_utils", + "../api:frame_generator_api", + "../api:scoped_refptr", + "../api:sequence_checker", + "../api/video:encoded_image", + "../api/video:video_frame", + "../api/video:video_frame_i010", + "../api/video:video_rtp_headers", + "../api/video_codecs:video_codecs_api", + "../common_video", + "../media:media_constants", + "../media:rtc_media_base", + "../modules/video_coding:video_codec_interface", + "../modules/video_coding:video_coding_utility", + "../modules/video_coding:webrtc_h264", + "../modules/video_coding:webrtc_vp8", + "../modules/video_coding:webrtc_vp9", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:logging", + "../rtc_base:random", + "../rtc_base:rtc_event", + "../rtc_base/synchronization:mutex", + "../rtc_base/system:file_wrapper", + "../system_wrappers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("frame_utils") { + visibility = [ "*" ] + testonly = true + sources = [ + "frame_utils.cc", + "frame_utils.h", + ] + deps = [ + "../api:scoped_refptr", + "../api/video:video_frame", + ] +} + +rtc_library("video_test_common") { + visibility = [ "*" ] + testonly = true + sources = [ + "fake_texture_frame.cc", + "fake_texture_frame.h", + "fake_videorenderer.h", + "frame_forwarder.cc", + "frame_forwarder.h", + "frame_generator_capturer.cc", + "frame_generator_capturer.h", + "mappable_native_buffer.cc", + "mappable_native_buffer.h", + "test_video_capturer.cc", + "test_video_capturer.h", + "video_codec_settings.h", + "video_decoder_proxy_factory.h", + "video_encoder_nullable_proxy_factory.h", + "video_encoder_proxy_factory.h", + ] + + deps = [ + ":fileutils", + ":frame_utils", + ":scoped_key_value_config", + "../api:array_view", + "../api:create_frame_generator", + "../api:frame_generator_api", + "../api:scoped_refptr", + "../api/task_queue", + "../api/video:video_frame", + "../api/video:video_rtp_headers", + "../api/video_codecs:video_codecs_api", + "../common_video", + "../media:rtc_media_base", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:logging", + "../rtc_base:refcount", + "../rtc_base:rtc_task_queue", + "../rtc_base:timeutils", + "../rtc_base/synchronization:mutex", + "../rtc_base/task_utils:repeating_task", + "../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/strings", + ] +} + +rtc_library("audio_test_common") { + visibility = [ "*" ] + testonly = true + sources = [ + "audio_decoder_proxy_factory.h", + "function_audio_decoder_factory.h", + ] + deps = [ + "../api/audio_codecs:audio_codecs_api", + "../rtc_base:checks", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] +} + +if (!build_with_mozilla) { +if (!build_with_chromium) { + if (is_mac || is_ios) { + rtc_library("video_test_mac") { + testonly = true + sources = [ + "mac_capturer.h", + "mac_capturer.mm", + ] + deps = [ + ":video_test_common", + "../api:libjingle_peerconnection_api", + "../api:media_stream_interface", + "../api:scoped_refptr", + "../modules/video_capture:video_capture_module", + "../rtc_base:threading", + "../sdk:base_objc", + "../sdk:native_api", + "../sdk:native_video", + "../sdk:videocapture_objc", + ] + } + } + + rtc_library("platform_video_capturer") { + testonly = true + sources = [ + "platform_video_capturer.cc", + "platform_video_capturer.h", + ] + deps = [ ":video_test_common" ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] + if (is_mac || is_ios) { + deps += [ ":video_test_mac" ] + } else { + sources += [ + "vcm_capturer.cc", + "vcm_capturer.h", + ] + deps += [ + ":scoped_key_value_config", + "../api:scoped_refptr", + "../modules/video_capture:video_capture_module", + "../rtc_base:checks", + "../rtc_base:logging", + ] + } + } +} +} + +rtc_library("rtp_test_utils") { + if (build_with_mozilla) { + sources = [] + } else { + testonly = true + sources = [ + "rtcp_packet_parser.cc", + "rtcp_packet_parser.h", + "rtp_file_reader.cc", + "rtp_file_reader.h", + "rtp_file_writer.cc", + "rtp_file_writer.h", + ] + } + + deps = [ + "../api:array_view", + "../api:rtp_parameters", + "../modules/rtp_rtcp", + "../modules/rtp_rtcp:rtp_rtcp_format", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:logging", + "../rtc_base:macromagic", + "../rtc_base/synchronization:mutex", + "../rtc_base/system:arch", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("field_trial") { + testonly = true + visibility = [ "*" ] + sources = [ + "field_trial.cc", + "field_trial.h", + ] + + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + deps = [ + "../rtc_base:checks", + "../system_wrappers:field_trial", + ] +} + +rtc_library("explicit_key_value_config") { + sources = [ + "explicit_key_value_config.cc", + "explicit_key_value_config.h", + ] + + deps = [ + "../api:field_trials_registry", + "../rtc_base:checks", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ] +} + +rtc_library("scoped_key_value_config") { + testonly = true + visibility = [ "*" ] + sources = [ + "scoped_key_value_config.cc", + "scoped_key_value_config.h", + ] + + deps = [ + ":field_trial", + "../api:field_trials_registry", + "../rtc_base:checks", + "../system_wrappers:field_trial", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ] +} + +rtc_library("perf_test") { + visibility = [ "*" ] + testonly = true + sources = [ + "testsupport/perf_test.cc", + "testsupport/perf_test.h", + "testsupport/perf_test_histogram_writer.h", + "testsupport/perf_test_result_writer.h", + ] + deps = [ + "../api:array_view", + "../api/numerics", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:logging", + "../rtc_base:rtc_numerics", + "../rtc_base:stringutils", + "../rtc_base/synchronization:mutex", + "../test:fileutils", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + if (rtc_enable_protobuf) { + sources += [ "testsupport/perf_test_histogram_writer.cc" ] + deps += [ + "//third_party/catapult/tracing/tracing:histogram", + "//third_party/catapult/tracing/tracing:reserved_infos", + ] + } else { + sources += [ "testsupport/perf_test_histogram_writer_no_protobuf.cc" ] + } +} + +if (is_ios) { + rtc_library("test_support_objc") { + testonly = true + visibility = [ + ":google_test_runner_objc", + ":test_support", + ] + sources = [ + "ios/coverage_util_ios.h", + "ios/coverage_util_ios.mm", + "ios/google_test_runner_delegate.h", + "ios/test_support.h", + "ios/test_support.mm", + ] + deps = [ + ":perf_test", + "../api/test/metrics:chrome_perf_dashboard_metrics_exporter", + "../api/test/metrics:global_metrics_logger_and_exporter", + "../api/test/metrics:metrics_exporter", + "../api/test/metrics:metrics_set_proto_file_exporter", + "../api/test/metrics:print_result_proxy_metrics_exporter", + "../api/test/metrics:stdout_metrics_exporter", + "../sdk:helpers_objc", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + configs += [ ":test_support_objc_config" ] + } + + rtc_library("google_test_runner_objc") { + testonly = true + visibility = [ "*" ] + sources = [ "ios/google_test_runner.mm" ] + deps = [ ":test_support_objc" ] + configs += [ "//build/config/ios:xctest_config" ] + frameworks = [ "UIKit.framework" ] + } + + config("test_support_objc_config") { + defines = [] + + if (use_clang_coverage) { + defines += [ "WEBRTC_IOS_ENABLE_COVERAGE" ] + } + } +} + +config("suppress_warning_4373") { + if (is_win) { + cflags = [ + # MSVC has a bug which generates this warning when using mocks; see the + # section on warning 4373 in he googlemock FAQ. This warning isn't the + # least relevant for us, anyway. + "/wd4373", + ] + } +} + +config("test_main_direct_config") { + visibility = [ ":*" ] + defines = [ "WEBRTC_UNIT_TEST" ] +} +rtc_source_set("test_support") { + visibility = [ "*" ] + testonly = true + + all_dependent_configs = [ + ":suppress_warning_4373", + "//third_party/googletest:gmock_config", + "//third_party/googletest:gtest_config", + ] + + sources = [ + "gmock.h", + "gtest.h", + ] + + public_deps = [] # no-presubmit-check TODO(webrtc:8603) + if (is_ios) { + public_deps += # no-presubmit-check TODO(webrtc:8603) + [ ":test_support_objc" ] + } + + public_configs = [ ":test_main_direct_config" ] + deps = [ + "../rtc_base:ignore_wundef", + "//testing/gmock", + "//testing/gtest", + ] +} + +rtc_library("fixed_fps_video_frame_writer_adapter") { + visibility = [ "*" ] + testonly = true + sources = [ + "testsupport/fixed_fps_video_frame_writer_adapter.cc", + "testsupport/fixed_fps_video_frame_writer_adapter.h", + ] + deps = [ + ":video_test_support", + "../api/test/video:video_frame_writer", + "../api/units:time_delta", + "../api/video:video_frame", + "../rtc_base:checks", + "../system_wrappers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("video_test_support") { + testonly = true + + sources = [ + "testsupport/frame_reader.h", + "testsupport/frame_writer.h", + "testsupport/mock/mock_frame_reader.h", + "testsupport/video_frame_writer.cc", + "testsupport/video_frame_writer.h", + "testsupport/y4m_frame_reader.cc", + "testsupport/y4m_frame_writer.cc", + "testsupport/yuv_frame_reader.cc", + "testsupport/yuv_frame_writer.cc", + ] + + deps = [ + ":fileutils", + ":frame_utils", + ":test_support", + ":video_test_common", + "../api:scoped_refptr", + "../api:sequence_checker", + "../api/test/video:video_frame_writer", + "../api/video:encoded_image", + "../api/video:resolution", + "../api/video:video_frame", + "../api/video_codecs:video_codecs_api", + "../common_video", + "../media:rtc_media_base", + "../modules/video_coding:video_codec_interface", + "../modules/video_coding:video_coding_utility", + "../modules/video_coding:webrtc_h264", + "../modules/video_coding:webrtc_vp8", + "../modules/video_coding:webrtc_vp9", + "../rtc_base:buffer", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:logging", + "../rtc_base:rtc_event", + "../rtc_base:stringutils", + "../rtc_base/system:file_wrapper", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + + if (!is_ios) { + if (!build_with_mozilla) { + deps += [ "//third_party:jpeg" ] + } + sources += [ "testsupport/jpeg_frame_writer.cc" ] + } else { + sources += [ "testsupport/jpeg_frame_writer_ios.cc" ] + } +} + +if (rtc_include_tests && enable_google_benchmarks) { + rtc_library("benchmark_main") { + testonly = true + sources = [ "benchmark_main.cc" ] + + deps = [ "//third_party/google_benchmark" ] + } +} + +if (rtc_include_tests && !build_with_chromium) { + rtc_library("resources_dir_flag") { + testonly = true + visibility = [ "*" ] + sources = [ + "testsupport/resources_dir_flag.cc", + "testsupport/resources_dir_flag.h", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/flags:flag" ] + } + + rtc_library("test_flags") { + visibility = [ "*" ] + testonly = true + sources = [ + "test_flags.cc", + "test_flags.h", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/flags:flag" ] + } + + rtc_library("test_main_lib") { + visibility = [ "*" ] + testonly = true + sources = [ + "test_main_lib.cc", + "test_main_lib.h", + ] + + deps = [ + ":field_trial", + ":perf_test", + ":resources_dir_flag", + ":test_flags", + ":test_support", + "../api/test/metrics:chrome_perf_dashboard_metrics_exporter", + "../api/test/metrics:global_metrics_logger_and_exporter", + "../api/test/metrics:metrics_exporter", + "../api/test/metrics:metrics_set_proto_file_exporter", + "../api/test/metrics:print_result_proxy_metrics_exporter", + "../api/test/metrics:stdout_metrics_exporter", + "../rtc_base:checks", + "../rtc_base:event_tracer", + "../rtc_base:logging", + "../rtc_base:ssl", + "../rtc_base:threading", + "../system_wrappers:field_trial", + "../system_wrappers:metrics", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/flags:flag", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + + if (is_win) { + deps += [ "../rtc_base:win32_socket_init" ] + } + } + + rtc_library("test_main") { + visibility = [ "*" ] + testonly = true + sources = [ "test_main.cc" ] + + deps = [ + ":test_main_lib", + ":test_support", + ] + + absl_deps = [ + "//third_party/abseil-cpp/absl/debugging:failure_signal_handler", + "//third_party/abseil-cpp/absl/debugging:symbolize", + "//third_party/abseil-cpp/absl/flags:parse", + ] + } + + rtc_library("test_support_test_artifacts") { + testonly = true + sources = [ + "testsupport/test_artifacts.cc", + "testsupport/test_artifacts.h", + ] + deps = [ + ":fileutils", + "../rtc_base:logging", + "../rtc_base/system:file_wrapper", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/flags:flag", + "//third_party/abseil-cpp/absl/flags:parse", + ] + } + + test_support_unittests_resources = [ + "../resources/foreman_cif_short.yuv", + "../resources/video_coding/frame-ethernet-ii.pcap", + "../resources/video_coding/frame-loopback.pcap", + "../resources/video_coding/pltype103.rtp", + "../resources/video_coding/pltype103_header_only.rtp", + "../resources/video_coding/ssrcs-2.pcap", + "../resources/video_coding/ssrcs-3.pcap", + ] + + if (is_ios) { + bundle_data("test_support_unittests_bundle_data") { + testonly = true + sources = test_support_unittests_resources + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + } + } + + rtc_library("fixed_fps_video_frame_writer_adapter_test") { + testonly = true + sources = [ "testsupport/fixed_fps_video_frame_writer_adapter_test.cc" ] + deps = [ + ":fixed_fps_video_frame_writer_adapter", + ":test_support", + ":video_test_support", + "../api/units:time_delta", + "../api/units:timestamp", + "../api/video:video_frame", + "../rtc_base/synchronization:mutex", + "time_controller", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + } + + rtc_test("test_support_unittests") { + deps = [ + ":call_config_utils", + ":copy_to_file_audio_capturer_unittest", + ":direct_transport", + ":fake_video_codecs", + ":fileutils", + ":fileutils_unittests", + ":fixed_fps_video_frame_writer_adapter_test", + ":frame_generator_impl", + ":perf_test", + ":rtc_expect_death", + ":rtp_test_utils", + ":run_loop", + ":scoped_key_value_config", + ":test_main", + ":test_support", + ":test_support_test_artifacts", + ":video_test_common", + ":video_test_support", + "../api:array_view", + "../api:create_frame_generator", + "../api:create_simulcast_test_fixture_api", + "../api:frame_generator_api", + "../api:scoped_refptr", + "../api:simulcast_test_fixture_api", + "../api/task_queue:task_queue_test", + "../api/test/video:function_video_factory", + "../api/test/video:video_frame_writer", + "../api/units:time_delta", + "../api/video:encoded_image", + "../api/video:video_frame", + "../api/video_codecs:video_codecs_api", + "../call:video_stream_api", + "../common_video", + "../media:codec", + "../media:media_constants", + "../media:rtc_media_base", + "../modules/rtp_rtcp:rtp_rtcp_format", + "../modules/video_coding:simulcast_test_fixture_impl", + "../modules/video_coding:video_codec_interface", + "../modules/video_coding:video_coding_utility", + "../modules/video_coding:webrtc_h264", + "../modules/video_coding:webrtc_vp8", + "../modules/video_coding:webrtc_vp9", + "../rtc_base:criticalsection", + "../rtc_base:rtc_event", + "../rtc_base:rtc_task_queue", + "../rtc_base/synchronization:mutex", + "../rtc_base/system:file_wrapper", + "pc/e2e:e2e_unittests", + "pc/e2e/analyzer/video:video_analyzer_unittests", + "peer_scenario/tests", + "scenario:scenario_unittests", + "time_controller:time_controller", + "time_controller:time_controller_unittests", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/flags:flag", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + sources = [ + "call_config_utils_unittest.cc", + "direct_transport_unittest.cc", + "fake_vp8_encoder_unittest.cc", + "frame_generator_capturer_unittest.cc", + "frame_generator_unittest.cc", + "rtp_file_reader_unittest.cc", + "rtp_file_writer_unittest.cc", + "run_loop_unittest.cc", + "testsupport/ivf_video_frame_generator_unittest.cc", + "testsupport/perf_test_unittest.cc", + "testsupport/test_artifacts_unittest.cc", + "testsupport/video_frame_writer_unittest.cc", + "testsupport/y4m_frame_reader_unittest.cc", + "testsupport/y4m_frame_writer_unittest.cc", + "testsupport/yuv_frame_reader_unittest.cc", + "testsupport/yuv_frame_writer_unittest.cc", + ] + + if (rtc_enable_protobuf) { + sources += [ "testsupport/perf_test_histogram_writer_unittest.cc" ] + deps += [ "//third_party/catapult/tracing/tracing:histogram" ] + } + + data = test_support_unittests_resources + if (is_android) { + deps += [ "//testing/android/native_test:native_test_support" ] + shard_timeout = 900 + } + + if (is_ios) { + deps += [ ":test_support_unittests_bundle_data" ] + } + + if (!is_android) { + # This is needed in order to avoid: + # undefined symbol: webrtc::videocapturemodule::VideoCaptureImpl::Create + deps += [ "../modules/video_capture:video_capture_internal_impl" ] + } + } +} + +if (is_ios) { + rtc_library("fileutils_ios_objc") { + visibility = [ + ":fileutils", + ":fileutils_override_impl", + ] + sources = [ + "testsupport/ios_file_utils.h", + "testsupport/ios_file_utils.mm", + ] + deps = [ + "../rtc_base:checks", + "../sdk:helpers_objc", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ] + } +} + +if (is_mac) { + rtc_library("fileutils_mac_objc") { + visibility = [ + ":fileutils", + ":fileutils_override_impl", + ] + sources = [ + "testsupport/mac_file_utils.h", + "testsupport/mac_file_utils.mm", + ] + deps = [ "../rtc_base:checks" ] + } +} + +rtc_library("fileutils") { + testonly = true + visibility = [ "*" ] + sources = [ + "testsupport/file_utils.cc", + "testsupport/file_utils.h", + ] + deps = [ + ":fileutils_override_api", + ":fileutils_override_impl", + "../rtc_base:checks", + "../rtc_base:stringutils", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + if (is_ios) { + deps += [ ":fileutils_ios_objc" ] + } + if (is_mac) { + deps += [ ":fileutils_mac_objc" ] + } + if (is_win) { + deps += [ "../rtc_base:win32" ] + } +} + +# We separate header into own target to make it possible for downstream +# projects to override implementation. +rtc_source_set("fileutils_override_api") { + testonly = true + sources = [ "testsupport/file_utils_override.h" ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings:strings" ] +} + +rtc_library("fileutils_override_impl") { + testonly = true + visibility = [ ":fileutils" ] + sources = [ "testsupport/file_utils_override.cc" ] + deps = [ + ":fileutils_override_api", + "../rtc_base:checks", + "../rtc_base:macromagic", + "../rtc_base:stringutils", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + if (is_ios) { + deps += [ ":fileutils_ios_objc" ] + } + if (is_mac) { + deps += [ ":fileutils_mac_objc" ] + } + if (is_win) { + deps += [ "../rtc_base:win32" ] + } +} + +rtc_source_set("run_test") { + testonly = true + if (is_mac) { + public_deps = [ ":run_test_objc" ] # no-presubmit-check TODO(webrtc:8603) + } else { + public_deps = # no-presubmit-check TODO(webrtc:8603) + [ ":run_test_generic" ] + } +} + +rtc_source_set("run_test_interface") { + sources = [ "run_test.h" ] +} + +if (is_mac) { + rtc_library("run_test_objc") { + testonly = true + visibility = [ ":run_test" ] + sources = [ "mac/run_test.mm" ] + deps = [ ":run_test_interface" ] + } +} + +rtc_library("run_test_generic") { + testonly = true + visibility = [ ":run_test" ] + sources = [ "run_test.cc" ] + deps = [ ":run_test_interface" ] +} + +rtc_library("fileutils_unittests") { + testonly = true + visibility = [ ":*" ] # Only targets in this file can depend on this. + sources = [ "testsupport/file_utils_unittest.cc" ] + deps = [ + ":fileutils", + ":test_support", + "../rtc_base:checks", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("direct_transport") { + visibility = [ "*" ] + testonly = true + sources = [ + "direct_transport.cc", + "direct_transport.h", + ] + deps = [ + "../api:rtp_parameters", + "../api:sequence_checker", + "../api:simulated_network_api", + "../api:transport_api", + "../api/task_queue", + "../api/units:time_delta", + "../call:call_interfaces", + "../call:simulated_packet_receiver", + "../modules/rtp_rtcp:rtp_rtcp_format", + "../rtc_base:checks", + "../rtc_base:macromagic", + "../rtc_base:timeutils", + "../rtc_base/synchronization:mutex", + "../rtc_base/task_utils:repeating_task", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] + public_deps = # no-presubmit-check TODO(webrtc:8603) + [ "../call:fake_network" ] +} + +rtc_library("fake_video_codecs") { + allow_poison = [ "software_video_codecs" ] + visibility = [ "*" ] + sources = [ + "configurable_frame_size_encoder.cc", + "configurable_frame_size_encoder.h", + "fake_decoder.cc", + "fake_decoder.h", + "fake_encoder.cc", + "fake_encoder.h", + "fake_vp8_decoder.cc", + "fake_vp8_decoder.h", + "fake_vp8_encoder.cc", + "fake_vp8_encoder.h", + ] + deps = [ + "../api:fec_controller_api", + "../api:scoped_refptr", + "../api:sequence_checker", + "../api/task_queue", + "../api/video:encoded_image", + "../api/video:video_bitrate_allocation", + "../api/video:video_frame", + "../api/video:video_rtp_headers", + "../api/video_codecs:video_codecs_api", + "../api/video_codecs:vp8_temporal_layers_factory", + "../modules/video_coding:codec_globals_headers", + "../modules/video_coding:video_codec_interface", + "../modules/video_coding:video_coding_utility", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:macromagic", + "../rtc_base:rtc_task_queue", + "../rtc_base:timeutils", + "../rtc_base/synchronization:mutex", + "../system_wrappers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("null_transport") { + testonly = true + sources = [ + "null_transport.cc", + "null_transport.h", + ] + deps = [ "../api:transport_api" ] +} + +rtc_library("encoder_settings") { + testonly = true + sources = [ + "encoder_settings.cc", + "encoder_settings.h", + ] + deps = [ + "../api:scoped_refptr", + "../api/video_codecs:video_codecs_api", + "../call:rtp_interfaces", + "../call:video_stream_api", + "../rtc_base:checks", + "../rtc_base:refcount", + "../video/config:encoder_config", + ] +} + +rtc_library("rtc_expect_death") { + testonly = true + sources = [ "testsupport/rtc_expect_death.h" ] + deps = [ ":test_support" ] +} + +rtc_library("run_loop") { + testonly = true + sources = [ + "run_loop.cc", + "run_loop.h", + ] + deps = [ + "../api/task_queue", + "../rtc_base:threading", + "../rtc_base:timeutils", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/functional:any_invocable" ] +} + +rtc_library("test_common") { + testonly = true + sources = [ + "call_test.cc", + "call_test.h", + "drifting_clock.cc", + "drifting_clock.h", + "layer_filtering_transport.cc", + "layer_filtering_transport.h", + "rtp_rtcp_observer.h", + ] + + deps = [ + ":direct_transport", + ":encoder_settings", + ":fake_video_codecs", + ":fileutils", + ":mock_transport", + ":run_loop", + ":scoped_key_value_config", + ":test_support", + ":video_test_common", + "../api:array_view", + "../api:create_frame_generator", + "../api:frame_generator_api", + "../api:rtp_headers", + "../api:rtp_parameters", + "../api:simulated_network_api", + "../api:transport_api", + "../api/audio_codecs:builtin_audio_decoder_factory", + "../api/audio_codecs:builtin_audio_encoder_factory", + "../api/rtc_event_log", + "../api/task_queue", + "../api/task_queue:default_task_queue_factory", + "../api/test/video:function_video_factory", + "../api/transport:field_trial_based_config", + "../api/units:time_delta", + "../api/video:builtin_video_bitrate_allocator_factory", + "../api/video:video_bitrate_allocator_factory", + "../api/video:video_frame", + "../api/video_codecs:video_codecs_api", + "../call", + "../call:call_interfaces", + "../call:fake_network", + "../call:simulated_network", + "../call:simulated_packet_receiver", + "../call:video_stream_api", + "../modules/audio_device:audio_device_impl", + "../modules/audio_mixer:audio_mixer_impl", + "../modules/rtp_rtcp", + "../modules/rtp_rtcp:rtp_rtcp_format", + "../modules/rtp_rtcp:rtp_video_header", + "../modules/video_coding:codec_globals_headers", + "../rtc_base:checks", + "../rtc_base:criticalsection", + "../rtc_base:rtc_event", + "../rtc_base:task_queue_for_test", + "../rtc_base:threading", + "../rtc_base:timeutils", + "../system_wrappers", + "../system_wrappers:field_trial", + "../video/config:encoder_config", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + if (!is_android && !build_with_chromium) { + deps += [ "../modules/video_capture:video_capture_internal_impl" ] + } + # This, or some form of it should be upstreamed. + if (!rtc_include_tests) { + deps -= [ "../rtc_base:task_queue_for_test" ] + } +} + +rtc_library("mock_transport") { + testonly = true + sources = [ + "mock_transport.cc", + "mock_transport.h", + ] + deps = [ + ":test_support", + "../api:transport_api", + ] +} + +rtc_source_set("test_renderer") { + public_deps = # no-presubmit-check TODO(webrtc:8603) + [ ":test_renderer_generic" ] + testonly = true + if (is_mac) { + public_deps += # no-presubmit-check TODO(webrtc:8603) + [ ":test_renderer_objc" ] + } +} + +rtc_library("mock_frame_transformer") { + visibility = [ "*" ] + testonly = true + sources = [ "mock_frame_transformer.h" ] + deps = [ + "../api:frame_transformer_interface", + "../test:test_support", + ] +} + +rtc_library("mock_transformable_frame") { + visibility = [ "*" ] + + testonly = true + sources = [ "mock_transformable_frame.h" ] + + deps = [ + "../api:frame_transformer_interface", + "../test:test_support", + ] +} + +if (is_mac) { + rtc_library("test_renderer_objc") { + testonly = true + visibility = [ ":test_renderer" ] + sources = [ + "mac/video_renderer_mac.h", + "mac/video_renderer_mac.mm", + ] + deps = [ ":test_renderer_generic" ] + frameworks = [ + "Cocoa.framework", + "OpenGL.framework", + "CoreVideo.framework", + ] + defines = [ "GL_SILENCE_DEPRECATION" ] + } +} + +rtc_library("test_renderer_generic") { + testonly = true + visibility = [ + ":test_renderer", + ":test_renderer_objc", + ] + libs = [] + sources = [ + "video_renderer.cc", + "video_renderer.h", + ] + deps = [ + "../api/video:video_frame", + "../common_video", + "../rtc_base:checks", + ] + if (is_win) { + sources += [ + "win/d3d_renderer.cc", + "win/d3d_renderer.h", + ] + deps += [ "../api:scoped_refptr" ] + } + if (!((is_linux || is_chromeos) && rtc_use_x11) && !is_mac && !is_win) { + sources += [ "null_platform_renderer.cc" ] + } + if (((is_linux || is_chromeos) && rtc_use_x11) || is_mac) { + sources += [ + "gl/gl_renderer.cc", + "gl/gl_renderer.h", + ] + } + if (is_mac) { + defines = [ "GL_SILENCE_DEPRECATION" ] + } + + if ((is_linux || is_chromeos) && rtc_use_x11) { + sources += [ + "linux/glx_renderer.cc", + "linux/glx_renderer.h", + "linux/video_renderer_linux.cc", + ] + libs += [ + "Xext", + "X11", + "GL", + ] + } + if (is_android) { + libs += [ + "GLESv2", + "log", + ] + } +} + +rtc_library("audio_codec_mocks") { + testonly = true + sources = [ + "mock_audio_decoder.cc", + "mock_audio_decoder.h", + "mock_audio_decoder_factory.h", + "mock_audio_encoder.cc", + "mock_audio_encoder.h", + "mock_audio_encoder_factory.h", + ] + + deps = [ + ":test_support", + "../api:array_view", + "../api:make_ref_counted", + "../api:scoped_refptr", + "../api/audio_codecs:audio_codecs_api", + "../api/audio_codecs:builtin_audio_decoder_factory", + ] +} + +rtc_library("copy_to_file_audio_capturer") { + testonly = true + sources = [ + "testsupport/copy_to_file_audio_capturer.cc", + "testsupport/copy_to_file_audio_capturer.h", + ] + deps = [ + "../api:array_view", + "../common_audio", + "../modules/audio_device:audio_device_impl", + "../rtc_base:buffer", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("copy_to_file_audio_capturer_unittest") { + testonly = true + sources = [ "testsupport/copy_to_file_audio_capturer_unittest.cc" ] + deps = [ + ":copy_to_file_audio_capturer", + ":fileutils", + ":test_support", + "../modules/audio_device:audio_device_impl", + ] +} + +if (!build_with_mozilla) { +if (!build_with_chromium && is_android) { + rtc_android_library("native_test_java") { + testonly = true + sources = [ + "android/org/webrtc/native_test/RTCNativeUnitTest.java", + "android/org/webrtc/native_test/RTCNativeUnitTestActivity.java", + ] + deps = [ + "../rtc_base:base_java", + "//testing/android/native_test:native_test_java", + ] + } +} +} + +rtc_library("call_config_utils") { + # TODO(bugs.webrtc.org/10814): Remove rtc_json_suppressions as soon as it + # gets removed upstream. + public_configs = [ "../rtc_base:rtc_json_suppressions" ] + sources = [ + "call_config_utils.cc", + "call_config_utils.h", + ] + deps = [ + "../call:video_stream_api", + "../rtc_base:rtc_json", + ] +} + +rtc_library("fake_encoded_frame") { + testonly = true + sources = [ + "fake_encoded_frame.cc", + "fake_encoded_frame.h", + ] + deps = [ + ":test_support", + "../api:rtp_packet_info", + "../api/video:encoded_frame", + "../api/video:encoded_image", + "../api/video:video_frame_type", + "../api/video:video_rtp_headers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} diff --git a/third_party/libwebrtc/test/DEPS b/third_party/libwebrtc/test/DEPS new file mode 100644 index 0000000000..a9e9a7b5f1 --- /dev/null +++ b/third_party/libwebrtc/test/DEPS @@ -0,0 +1,88 @@ +include_rules = [ + "+third_party/libjpeg", + "+third_party/libjpeg_turbo", + "+call", + "+common_audio", + "+common_video", + "+logging/rtc_event_log", + "+media/base", + "+media/sctp", + "+media/engine", + "+modules/audio_coding", + "+modules/congestion_controller", + "+modules/audio_device", + "+modules/audio_mixer", + "+modules/audio_processing", + "+modules/congestion_controller/bbr", + "+modules/rtp_rtcp", + "+modules/utility", + "+modules/video_capture", + "+modules/video_coding", + "+p2p/base/basic_packet_socket_factory.h", + "+sdk", + "+system_wrappers", + "+third_party/libyuv", + "+video/config", +] + +specific_include_rules = { + "gmock\.h": [ + "+testing/gmock/include/gmock", + ], + "gtest\.h": [ + "+testing/gtest/include/gtest", + ], + ".*congestion_controller_feedback_fuzzer\.cc": [ + "+modules/congestion_controller/include/receive_side_congestion_controller.h", + "+modules/pacing/packet_router.h", + "+modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h", + ], + ".*mdns_parser_fuzzer\.cc": [ + "+p2p/base/mdns_message.h", + ], + ".*pseudotcp_parser_fuzzer\.cc": [ + "+p2p/base/pseudo_tcp.h", + ], + ".*stun_parser_fuzzer\.cc": [ + "+p2p/base/stun.h", + ], + ".*stun_validator_fuzzer\.cc": [ + "+p2p/base/stun.h", + ], + ".*test_main\.cc": [ + "+absl/debugging/failure_signal_handler.h", + "+absl/debugging/symbolize.h", + ], + ".*test_peer\.(h|cc)": [ + "+pc", + "+p2p", + ], + ".*test_peer_factory\.(h|cc)": [ + "+pc", + "+p2p", + ], + ".*peer_connection_quality_test_params\.h": [ + "+p2p/base/port_allocator.h", + ], + ".*network_emulation_pc_unittest\.cc": [ + "+pc/peer_connection_wrapper.h", + "+pc/test/mock_peer_connection_observers.h", + "+p2p/client/basic_port_allocator.h", + ], + ".*peer_connection_quality_test\.(h|cc)": [ + "+pc", + ], + ".*sdp_changer\.(h|cc)": [ + "+pc", + "+p2p", + ], + ".*test_video_capturer_video_track_source.h": [ + "+pc", + ], + "benchmark_main\.cc": [ + "+benchmark", + ], + "emulated_turn_server\.h": [ + "+p2p/base/turn_server.h", + ] +} diff --git a/third_party/libwebrtc/test/OWNERS b/third_party/libwebrtc/test/OWNERS new file mode 100644 index 0000000000..a1bd812244 --- /dev/null +++ b/third_party/libwebrtc/test/OWNERS @@ -0,0 +1,7 @@ +sprang@webrtc.org +srte@webrtc.org +stefan@webrtc.org +titovartem@webrtc.org +landrey@webrtc.org +mbonadei@webrtc.org +jleconte@webrtc.org diff --git a/third_party/libwebrtc/test/android/AndroidManifest.xml b/third_party/libwebrtc/test/android/AndroidManifest.xml new file mode 100644 index 0000000000..ad3f434b4f --- /dev/null +++ b/third_party/libwebrtc/test/android/AndroidManifest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.webrtc.native_test" + android:versionCode="1" + android:versionName="1.0"> + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="23" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> + <uses-permission android:name="android.permission.RECORD_AUDIO"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + + <application android:label="NativeTests" + android:name="org.chromium.native_test.NativeTestApplication"> + <uses-library android:name="android.test.runner"/> + <activity android:name=".RTCNativeUnitTestActivity" + android:label="NativeTest" + android:configChanges="orientation|keyboardHidden" + android:process=":test_process"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + + <instrumentation android:name="org.chromium.build.gtest_apk.NativeTestInstrumentationTestRunner" + android:targetPackage="org.webrtc.native_test" + android:label="Instrumentation entry point for org.webrtc.native_test" + chromium-junit3="true"/> + +</manifest> diff --git a/third_party/libwebrtc/test/android/org/webrtc/native_test/RTCNativeUnitTest.java b/third_party/libwebrtc/test/android/org/webrtc/native_test/RTCNativeUnitTest.java new file mode 100644 index 0000000000..dede7edd1f --- /dev/null +++ b/third_party/libwebrtc/test/android/org/webrtc/native_test/RTCNativeUnitTest.java @@ -0,0 +1,26 @@ +/* + * Copyright 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. + */ + +package org.webrtc.native_test; + +import android.app.Activity; +import org.chromium.native_test.NativeUnitTest; +import org.webrtc.ContextUtils; + +/** + * Native unit test that calls ContextUtils.initialize for WebRTC. + */ +public class RTCNativeUnitTest extends NativeUnitTest { + @Override + public void preCreate(Activity activity) { + super.preCreate(activity); + ContextUtils.initialize(activity.getApplicationContext()); + } +} diff --git a/third_party/libwebrtc/test/android/org/webrtc/native_test/RTCNativeUnitTestActivity.java b/third_party/libwebrtc/test/android/org/webrtc/native_test/RTCNativeUnitTestActivity.java new file mode 100644 index 0000000000..2a413682fe --- /dev/null +++ b/third_party/libwebrtc/test/android/org/webrtc/native_test/RTCNativeUnitTestActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright 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. + */ + +package org.webrtc.native_test; + +import android.app.Activity; +import android.os.Bundle; + +/** + * Activity that uses RTCNativeUnitTest to run the tests. + */ +public class RTCNativeUnitTestActivity extends Activity { + private RTCNativeUnitTest mTest = new RTCNativeUnitTest(); + + @Override + public void onCreate(Bundle savedInstanceState) { + mTest.preCreate(this); + super.onCreate(savedInstanceState); + mTest.postCreate(this); + } + + @Override + public void onStart() { + super.onStart(); + mTest.postStart(this, false); + } +} diff --git a/third_party/libwebrtc/test/audio_decoder_proxy_factory.h b/third_party/libwebrtc/test/audio_decoder_proxy_factory.h new file mode 100644 index 0000000000..95606d6ff7 --- /dev/null +++ b/third_party/libwebrtc/test/audio_decoder_proxy_factory.h @@ -0,0 +1,99 @@ +/* + * 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_AUDIO_DECODER_PROXY_FACTORY_H_ +#define TEST_AUDIO_DECODER_PROXY_FACTORY_H_ + +#include <memory> +#include <utility> +#include <vector> + +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_decoder_factory.h" + +namespace webrtc { +namespace test { + +// A decoder factory with a single underlying AudioDecoder object, intended for +// test purposes. Each call to MakeAudioDecoder returns a proxy for the same +// decoder, typically a mock or fake decoder. +class AudioDecoderProxyFactory : public AudioDecoderFactory { + public: + explicit AudioDecoderProxyFactory(AudioDecoder* decoder) + : decoder_(decoder) {} + + // Unused by tests. + std::vector<AudioCodecSpec> GetSupportedDecoders() override { + RTC_DCHECK_NOTREACHED(); + return {}; + } + + bool IsSupportedDecoder(const SdpAudioFormat& format) override { + return true; + } + + std::unique_ptr<AudioDecoder> MakeAudioDecoder( + const SdpAudioFormat& /* format */, + absl::optional<AudioCodecPairId> /* codec_pair_id */) override { + return std::make_unique<DecoderProxy>(decoder_); + } + + private: + // Wrapper class, since CreateAudioDecoder needs to surrender + // ownership to the object it returns. + class DecoderProxy final : public AudioDecoder { + public: + explicit DecoderProxy(AudioDecoder* decoder) : decoder_(decoder) {} + + private: + std::vector<ParseResult> ParsePayload(rtc::Buffer&& payload, + uint32_t timestamp) override { + return decoder_->ParsePayload(std::move(payload), timestamp); + } + + bool HasDecodePlc() const override { return decoder_->HasDecodePlc(); } + + int ErrorCode() override { return decoder_->ErrorCode(); } + + void Reset() override { decoder_->Reset(); } + + int SampleRateHz() const override { return decoder_->SampleRateHz(); } + + size_t Channels() const override { return decoder_->Channels(); } + + int DecodeInternal(const uint8_t* encoded, + size_t encoded_len, + int sample_rate_hz, + int16_t* decoded, + SpeechType* speech_type) override { + // Needed for tests of NetEqImpl::DecodeCng, which calls the deprecated + // Decode method. + size_t max_decoded_bytes = + decoder_->PacketDuration(encoded, encoded_len) * + decoder_->Channels() * sizeof(int16_t); + return decoder_->Decode(encoded, encoded_len, sample_rate_hz, + max_decoded_bytes, decoded, speech_type); + } + + void GeneratePlc(size_t requested_samples_per_channel, + rtc::BufferT<int16_t>* concealment_audio) override { + decoder_->GeneratePlc(requested_samples_per_channel, concealment_audio); + } + + AudioDecoder* const decoder_; + }; + + AudioDecoder* const decoder_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_AUDIO_DECODER_PROXY_FACTORY_H_ diff --git a/third_party/libwebrtc/test/benchmark_main.cc b/third_party/libwebrtc/test/benchmark_main.cc new file mode 100644 index 0000000000..1a79c24913 --- /dev/null +++ b/third_party/libwebrtc/test/benchmark_main.cc @@ -0,0 +1,17 @@ +/* + * 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 "benchmark/benchmark.h" + +int main(int argc, char* argv[]) { + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + return 0; +} diff --git a/third_party/libwebrtc/test/call_config_utils.cc b/third_party/libwebrtc/test/call_config_utils.cc new file mode 100644 index 0000000000..da3d76c689 --- /dev/null +++ b/third_party/libwebrtc/test/call_config_utils.cc @@ -0,0 +1,123 @@ +/* + * 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/call_config_utils.h" + +#include <string> +#include <vector> + +namespace webrtc { +namespace test { + +// Deserializes a JSON representation of the VideoReceiveStreamInterface::Config +// back into a valid object. This will not initialize the decoders or the +// renderer. +VideoReceiveStreamInterface::Config ParseVideoReceiveStreamJsonConfig( + webrtc::Transport* transport, + const Json::Value& json) { + auto receive_config = VideoReceiveStreamInterface::Config(transport); + for (const auto& decoder_json : json["decoders"]) { + VideoReceiveStreamInterface::Decoder decoder; + decoder.video_format = + SdpVideoFormat(decoder_json["payload_name"].asString()); + decoder.payload_type = decoder_json["payload_type"].asInt64(); + for (const auto& params_json : decoder_json["codec_params"]) { + std::vector<std::string> members = params_json.getMemberNames(); + RTC_CHECK_EQ(members.size(), 1); + decoder.video_format.parameters[members[0]] = + params_json[members[0]].asString(); + } + receive_config.decoders.push_back(decoder); + } + receive_config.render_delay_ms = json["render_delay_ms"].asInt64(); + receive_config.rtp.remote_ssrc = json["rtp"]["remote_ssrc"].asInt64(); + receive_config.rtp.local_ssrc = json["rtp"]["local_ssrc"].asInt64(); + receive_config.rtp.rtcp_mode = + json["rtp"]["rtcp_mode"].asString() == "RtcpMode::kCompound" + ? RtcpMode::kCompound + : RtcpMode::kReducedSize; + receive_config.rtp.lntf.enabled = json["rtp"]["lntf"]["enabled"].asInt64(); + receive_config.rtp.nack.rtp_history_ms = + json["rtp"]["nack"]["rtp_history_ms"].asInt64(); + receive_config.rtp.ulpfec_payload_type = + json["rtp"]["ulpfec_payload_type"].asInt64(); + receive_config.rtp.red_payload_type = + json["rtp"]["red_payload_type"].asInt64(); + receive_config.rtp.rtx_ssrc = json["rtp"]["rtx_ssrc"].asInt64(); + + for (const auto& pl_json : json["rtp"]["rtx_payload_types"]) { + std::vector<std::string> members = pl_json.getMemberNames(); + RTC_CHECK_EQ(members.size(), 1); + Json::Value rtx_payload_type = pl_json[members[0]]; + receive_config.rtp.rtx_associated_payload_types[std::stoi(members[0])] = + rtx_payload_type.asInt64(); + } + for (const auto& ext_json : json["rtp"]["extensions"]) { + receive_config.rtp.extensions.emplace_back(ext_json["uri"].asString(), + ext_json["id"].asInt64(), + ext_json["encrypt"].asBool()); + } + return receive_config; +} + +Json::Value GenerateVideoReceiveStreamJsonConfig( + const VideoReceiveStreamInterface::Config& config) { + Json::Value root_json; + + root_json["decoders"] = Json::Value(Json::arrayValue); + for (const auto& decoder : config.decoders) { + Json::Value decoder_json; + decoder_json["payload_type"] = decoder.payload_type; + decoder_json["payload_name"] = decoder.video_format.name; + decoder_json["codec_params"] = Json::Value(Json::arrayValue); + for (const auto& codec_param_entry : decoder.video_format.parameters) { + Json::Value codec_param_json; + codec_param_json[codec_param_entry.first] = codec_param_entry.second; + decoder_json["codec_params"].append(codec_param_json); + } + root_json["decoders"].append(decoder_json); + } + + Json::Value rtp_json; + rtp_json["remote_ssrc"] = config.rtp.remote_ssrc; + rtp_json["local_ssrc"] = config.rtp.local_ssrc; + rtp_json["rtcp_mode"] = config.rtp.rtcp_mode == RtcpMode::kCompound + ? "RtcpMode::kCompound" + : "RtcpMode::kReducedSize"; + rtp_json["lntf"]["enabled"] = config.rtp.lntf.enabled; + rtp_json["nack"]["rtp_history_ms"] = config.rtp.nack.rtp_history_ms; + rtp_json["ulpfec_payload_type"] = config.rtp.ulpfec_payload_type; + rtp_json["red_payload_type"] = config.rtp.red_payload_type; + rtp_json["rtx_ssrc"] = config.rtp.rtx_ssrc; + rtp_json["rtx_payload_types"] = Json::Value(Json::arrayValue); + + for (auto& kv : config.rtp.rtx_associated_payload_types) { + Json::Value val; + val[std::to_string(kv.first)] = kv.second; + rtp_json["rtx_payload_types"].append(val); + } + + rtp_json["extensions"] = Json::Value(Json::arrayValue); + for (auto& ext : config.rtp.extensions) { + Json::Value ext_json; + ext_json["uri"] = ext.uri; + ext_json["id"] = ext.id; + ext_json["encrypt"] = ext.encrypt; + rtp_json["extensions"].append(ext_json); + } + root_json["rtp"] = rtp_json; + + root_json["render_delay_ms"] = config.render_delay_ms; + + return root_json; +} + +} // namespace test. +} // namespace webrtc. diff --git a/third_party/libwebrtc/test/call_config_utils.h b/third_party/libwebrtc/test/call_config_utils.h new file mode 100644 index 0000000000..97cfdc3396 --- /dev/null +++ b/third_party/libwebrtc/test/call_config_utils.h @@ -0,0 +1,34 @@ +/* + * 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_CALL_CONFIG_UTILS_H_ +#define TEST_CALL_CONFIG_UTILS_H_ + +#include "call/video_receive_stream.h" +#include "rtc_base/strings/json.h" + +namespace webrtc { +namespace test { + +// Deserializes a JSON representation of the VideoReceiveStreamInterface::Config +// back into a valid object. This will not initialize the decoders or the +// renderer. +VideoReceiveStreamInterface::Config ParseVideoReceiveStreamJsonConfig( + webrtc::Transport* transport, + const Json::Value& json); + +// Serialize a VideoReceiveStreamInterface::Config into a Json object. +Json::Value GenerateVideoReceiveStreamJsonConfig( + const VideoReceiveStreamInterface::Config& config); + +} // namespace test +} // namespace webrtc + +#endif // TEST_CALL_CONFIG_UTILS_H_ diff --git a/third_party/libwebrtc/test/call_config_utils_unittest.cc b/third_party/libwebrtc/test/call_config_utils_unittest.cc new file mode 100644 index 0000000000..e010ab6707 --- /dev/null +++ b/third_party/libwebrtc/test/call_config_utils_unittest.cc @@ -0,0 +1,64 @@ +/* + * 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/call_config_utils.h" + +#include "call/video_receive_stream.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { + +TEST(CallConfigUtils, MarshalUnmarshalProcessSameObject) { + VideoReceiveStreamInterface::Config recv_config(nullptr); + + VideoReceiveStreamInterface::Decoder decoder; + decoder.payload_type = 10; + decoder.video_format.name = "test"; + decoder.video_format.parameters["99"] = "b"; + recv_config.decoders.push_back(decoder); + recv_config.render_delay_ms = 10; + recv_config.rtp.remote_ssrc = 100; + recv_config.rtp.local_ssrc = 101; + recv_config.rtp.rtcp_mode = RtcpMode::kCompound; + recv_config.rtp.lntf.enabled = false; + recv_config.rtp.nack.rtp_history_ms = 150; + recv_config.rtp.red_payload_type = 50; + recv_config.rtp.rtx_ssrc = 1000; + recv_config.rtp.rtx_associated_payload_types[10] = 10; + recv_config.rtp.extensions.emplace_back("uri", 128, true); + + VideoReceiveStreamInterface::Config unmarshaled_config = + ParseVideoReceiveStreamJsonConfig( + nullptr, GenerateVideoReceiveStreamJsonConfig(recv_config)); + + EXPECT_EQ(recv_config.decoders[0].payload_type, + unmarshaled_config.decoders[0].payload_type); + EXPECT_EQ(recv_config.decoders[0].video_format.name, + unmarshaled_config.decoders[0].video_format.name); + EXPECT_EQ(recv_config.decoders[0].video_format.parameters, + unmarshaled_config.decoders[0].video_format.parameters); + EXPECT_EQ(recv_config.render_delay_ms, unmarshaled_config.render_delay_ms); + EXPECT_EQ(recv_config.rtp.remote_ssrc, unmarshaled_config.rtp.remote_ssrc); + EXPECT_EQ(recv_config.rtp.local_ssrc, unmarshaled_config.rtp.local_ssrc); + EXPECT_EQ(recv_config.rtp.rtcp_mode, unmarshaled_config.rtp.rtcp_mode); + EXPECT_EQ(recv_config.rtp.lntf.enabled, unmarshaled_config.rtp.lntf.enabled); + EXPECT_EQ(recv_config.rtp.nack.rtp_history_ms, + unmarshaled_config.rtp.nack.rtp_history_ms); + EXPECT_EQ(recv_config.rtp.red_payload_type, + unmarshaled_config.rtp.red_payload_type); + EXPECT_EQ(recv_config.rtp.rtx_ssrc, unmarshaled_config.rtp.rtx_ssrc); + EXPECT_EQ(recv_config.rtp.rtx_associated_payload_types, + unmarshaled_config.rtp.rtx_associated_payload_types); + EXPECT_EQ(recv_config.rtp.extensions, recv_config.rtp.extensions); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/call_test.cc b/third_party/libwebrtc/test/call_test.cc new file mode 100644 index 0000000000..62d18394f9 --- /dev/null +++ b/third_party/libwebrtc/test/call_test.cc @@ -0,0 +1,861 @@ +/* + * Copyright (c) 2014 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/call_test.h" + +#include <algorithm> +#include <memory> + +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "api/task_queue/task_queue_base.h" +#include "api/test/create_frame_generator.h" +#include "api/video/builtin_video_bitrate_allocator_factory.h" +#include "call/fake_network_pipe.h" +#include "call/packet_receiver.h" +#include "call/simulated_network.h" +#include "modules/audio_mixer/audio_mixer_impl.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" +#include "rtc_base/task_queue_for_test.h" +#include "test/fake_encoder.h" +#include "test/rtp_rtcp_observer.h" +#include "test/testsupport/file_utils.h" +#include "video/config/video_encoder_config.h" + +namespace webrtc { +namespace test { + +CallTest::CallTest() + : clock_(Clock::GetRealTimeClock()), + task_queue_factory_(CreateDefaultTaskQueueFactory()), + send_event_log_(std::make_unique<RtcEventLogNull>()), + recv_event_log_(std::make_unique<RtcEventLogNull>()), + audio_send_config_(/*send_transport=*/nullptr), + audio_send_stream_(nullptr), + frame_generator_capturer_(nullptr), + fake_encoder_factory_([this]() { + std::unique_ptr<FakeEncoder> fake_encoder; + if (video_encoder_configs_[0].codec_type == kVideoCodecVP8) { + fake_encoder = std::make_unique<FakeVp8Encoder>(clock_); + } else { + fake_encoder = std::make_unique<FakeEncoder>(clock_); + } + fake_encoder->SetMaxBitrate(fake_encoder_max_bitrate_); + return fake_encoder; + }), + fake_decoder_factory_([]() { return std::make_unique<FakeDecoder>(); }), + bitrate_allocator_factory_(CreateBuiltinVideoBitrateAllocatorFactory()), + num_video_streams_(1), + num_audio_streams_(0), + num_flexfec_streams_(0), + audio_decoder_factory_(CreateBuiltinAudioDecoderFactory()), + audio_encoder_factory_(CreateBuiltinAudioEncoderFactory()), + task_queue_(task_queue_factory_->CreateTaskQueue( + "CallTestTaskQueue", + TaskQueueFactory::Priority::NORMAL)) {} + +CallTest::~CallTest() = default; + +void CallTest::RegisterRtpExtension(const RtpExtension& extension) { + for (const RtpExtension& registered_extension : rtp_extensions_) { + if (registered_extension.id == extension.id) { + ASSERT_EQ(registered_extension.uri, extension.uri) + << "Different URIs associated with ID " << extension.id << "."; + ASSERT_EQ(registered_extension.encrypt, extension.encrypt) + << "Encryption mismatch associated with ID " << extension.id << "."; + return; + } else { // Different IDs. + // Different IDs referring to the same extension probably indicate + // a mistake in the test. + ASSERT_FALSE(registered_extension.uri == extension.uri && + registered_extension.encrypt == extension.encrypt) + << "URI " << extension.uri + << (extension.encrypt ? " with " : " without ") + << "encryption already registered with a different " + "ID (" + << extension.id << " vs. " << registered_extension.id << ")."; + } + } + rtp_extensions_.push_back(extension); +} + +void CallTest::RunBaseTest(BaseTest* test) { + SendTask(task_queue(), [this, test]() { + num_video_streams_ = test->GetNumVideoStreams(); + num_audio_streams_ = test->GetNumAudioStreams(); + num_flexfec_streams_ = test->GetNumFlexfecStreams(); + RTC_DCHECK(num_video_streams_ > 0 || num_audio_streams_ > 0); + Call::Config send_config(send_event_log_.get()); + test->ModifySenderBitrateConfig(&send_config.bitrate_config); + if (num_audio_streams_ > 0) { + CreateFakeAudioDevices(test->CreateCapturer(), test->CreateRenderer()); + test->OnFakeAudioDevicesCreated(fake_send_audio_device_.get(), + fake_recv_audio_device_.get()); + apm_send_ = AudioProcessingBuilder().Create(); + apm_recv_ = AudioProcessingBuilder().Create(); + EXPECT_EQ(0, fake_send_audio_device_->Init()); + EXPECT_EQ(0, fake_recv_audio_device_->Init()); + AudioState::Config audio_state_config; + audio_state_config.audio_mixer = AudioMixerImpl::Create(); + audio_state_config.audio_processing = apm_send_; + audio_state_config.audio_device_module = fake_send_audio_device_; + send_config.audio_state = AudioState::Create(audio_state_config); + fake_send_audio_device_->RegisterAudioCallback( + send_config.audio_state->audio_transport()); + } + CreateSenderCall(send_config); + if (test->ShouldCreateReceivers()) { + Call::Config recv_config(recv_event_log_.get()); + test->ModifyReceiverBitrateConfig(&recv_config.bitrate_config); + if (num_audio_streams_ > 0) { + AudioState::Config audio_state_config; + audio_state_config.audio_mixer = AudioMixerImpl::Create(); + audio_state_config.audio_processing = apm_recv_; + audio_state_config.audio_device_module = fake_recv_audio_device_; + recv_config.audio_state = AudioState::Create(audio_state_config); + fake_recv_audio_device_->RegisterAudioCallback( + recv_config.audio_state->audio_transport()); + } + CreateReceiverCall(recv_config); + } + test->OnCallsCreated(sender_call_.get(), receiver_call_.get()); + CreateReceiveTransport(test->GetReceiveTransportConfig(), test); + CreateSendTransport(test->GetSendTransportConfig(), test); + test->OnTransportCreated(send_transport_.get(), send_simulated_network_, + receive_transport_.get(), + receive_simulated_network_); + if (test->ShouldCreateReceivers()) { + if (num_video_streams_ > 0) + receiver_call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + if (num_audio_streams_ > 0) + receiver_call_->SignalChannelNetworkState(MediaType::AUDIO, kNetworkUp); + } else { + // Sender-only call delivers to itself. + send_transport_->SetReceiver(sender_call_->Receiver()); + receive_transport_->SetReceiver(nullptr); + } + + CreateSendConfig(num_video_streams_, num_audio_streams_, + num_flexfec_streams_, send_transport_.get()); + if (test->ShouldCreateReceivers()) { + CreateMatchingReceiveConfigs(); + } + if (num_video_streams_ > 0) { + test->ModifyVideoConfigs(GetVideoSendConfig(), &video_receive_configs_, + GetVideoEncoderConfig()); + } + if (num_audio_streams_ > 0) { + test->ModifyAudioConfigs(&audio_send_config_, &audio_receive_configs_); + } + if (num_flexfec_streams_ > 0) { + test->ModifyFlexfecConfigs(&flexfec_receive_configs_); + } + + if (num_flexfec_streams_ > 0) { + CreateFlexfecStreams(); + test->OnFlexfecStreamsCreated(flexfec_receive_streams_); + } + if (num_video_streams_ > 0) { + CreateVideoStreams(); + test->OnVideoStreamsCreated(GetVideoSendStream(), video_receive_streams_); + } + if (num_audio_streams_ > 0) { + CreateAudioStreams(); + test->OnAudioStreamsCreated(audio_send_stream_, audio_receive_streams_); + } + + if (num_video_streams_ > 0) { + int width = kDefaultWidth; + int height = kDefaultHeight; + int frame_rate = kDefaultFramerate; + test->ModifyVideoCaptureStartResolution(&width, &height, &frame_rate); + test->ModifyVideoDegradationPreference(°radation_preference_); + CreateFrameGeneratorCapturer(frame_rate, width, height); + test->OnFrameGeneratorCapturerCreated(frame_generator_capturer_); + } + + Start(); + }); + + test->PerformTest(); + + SendTask(task_queue(), [this, test]() { + Stop(); + test->OnStreamsStopped(); + DestroyStreams(); + send_transport_.reset(); + receive_transport_.reset(); + + frame_generator_capturer_ = nullptr; + DestroyCalls(); + + fake_send_audio_device_ = nullptr; + fake_recv_audio_device_ = nullptr; + }); +} + +void CallTest::CreateCalls() { + CreateCalls(Call::Config(send_event_log_.get()), + Call::Config(recv_event_log_.get())); +} + +void CallTest::CreateCalls(const Call::Config& sender_config, + const Call::Config& receiver_config) { + CreateSenderCall(sender_config); + CreateReceiverCall(receiver_config); +} + +void CallTest::CreateSenderCall() { + CreateSenderCall(Call::Config(send_event_log_.get())); +} + +void CallTest::CreateSenderCall(const Call::Config& config) { + auto sender_config = config; + sender_config.task_queue_factory = task_queue_factory_.get(); + sender_config.network_state_predictor_factory = + network_state_predictor_factory_.get(); + sender_config.network_controller_factory = network_controller_factory_.get(); + sender_config.trials = &field_trials_; + sender_call_.reset(Call::Create(sender_config)); +} + +void CallTest::CreateReceiverCall(const Call::Config& config) { + auto receiver_config = config; + receiver_config.task_queue_factory = task_queue_factory_.get(); + receiver_config.trials = &field_trials_; + receiver_call_.reset(Call::Create(receiver_config)); +} + +void CallTest::DestroyCalls() { + send_transport_.reset(); + receive_transport_.reset(); + sender_call_.reset(); + receiver_call_.reset(); +} + +void CallTest::CreateVideoSendConfig(VideoSendStream::Config* video_config, + size_t num_video_streams, + size_t num_used_ssrcs, + Transport* send_transport) { + RTC_DCHECK_LE(num_video_streams + num_used_ssrcs, kNumSsrcs); + *video_config = VideoSendStream::Config(send_transport); + video_config->encoder_settings.encoder_factory = &fake_encoder_factory_; + video_config->encoder_settings.bitrate_allocator_factory = + bitrate_allocator_factory_.get(); + video_config->rtp.payload_name = "FAKE"; + video_config->rtp.payload_type = kFakeVideoSendPayloadType; + video_config->rtp.extmap_allow_mixed = true; + AddRtpExtensionByUri(RtpExtension::kTransportSequenceNumberUri, + &video_config->rtp.extensions); + AddRtpExtensionByUri(RtpExtension::kAbsSendTimeUri, + &video_config->rtp.extensions); + AddRtpExtensionByUri(RtpExtension::kTimestampOffsetUri, + &video_config->rtp.extensions); + AddRtpExtensionByUri(RtpExtension::kVideoContentTypeUri, + &video_config->rtp.extensions); + AddRtpExtensionByUri(RtpExtension::kGenericFrameDescriptorUri00, + &video_config->rtp.extensions); + AddRtpExtensionByUri(RtpExtension::kDependencyDescriptorUri, + &video_config->rtp.extensions); + if (video_encoder_configs_.empty()) { + video_encoder_configs_.emplace_back(); + FillEncoderConfiguration(kVideoCodecGeneric, num_video_streams, + &video_encoder_configs_.back()); + } + for (size_t i = 0; i < num_video_streams; ++i) + video_config->rtp.ssrcs.push_back(kVideoSendSsrcs[num_used_ssrcs + i]); + AddRtpExtensionByUri(RtpExtension::kVideoRotationUri, + &video_config->rtp.extensions); + AddRtpExtensionByUri(RtpExtension::kColorSpaceUri, + &video_config->rtp.extensions); +} + +void CallTest::CreateAudioAndFecSendConfigs(size_t num_audio_streams, + size_t num_flexfec_streams, + Transport* send_transport) { + RTC_DCHECK_LE(num_audio_streams, 1); + RTC_DCHECK_LE(num_flexfec_streams, 1); + if (num_audio_streams > 0) { + AudioSendStream::Config audio_send_config(send_transport); + audio_send_config.rtp.ssrc = kAudioSendSsrc; + AddRtpExtensionByUri(RtpExtension::kTransportSequenceNumberUri, + &audio_send_config.rtp.extensions); + + audio_send_config.send_codec_spec = AudioSendStream::Config::SendCodecSpec( + kAudioSendPayloadType, {"opus", 48000, 2, {{"stereo", "1"}}}); + audio_send_config.encoder_factory = audio_encoder_factory_; + SetAudioConfig(audio_send_config); + } + + // TODO(brandtr): Update this when we support multistream protection. + if (num_flexfec_streams > 0) { + SetSendFecConfig({kVideoSendSsrcs[0]}); + } +} + +void CallTest::SetAudioConfig(const AudioSendStream::Config& config) { + audio_send_config_ = config; +} + +void CallTest::SetSendFecConfig(std::vector<uint32_t> video_send_ssrcs) { + GetVideoSendConfig()->rtp.flexfec.payload_type = kFlexfecPayloadType; + GetVideoSendConfig()->rtp.flexfec.ssrc = kFlexfecSendSsrc; + GetVideoSendConfig()->rtp.flexfec.protected_media_ssrcs = video_send_ssrcs; +} + +void CallTest::SetSendUlpFecConfig(VideoSendStream::Config* send_config) { + send_config->rtp.ulpfec.red_payload_type = kRedPayloadType; + send_config->rtp.ulpfec.ulpfec_payload_type = kUlpfecPayloadType; + send_config->rtp.ulpfec.red_rtx_payload_type = kRtxRedPayloadType; +} + +void CallTest::SetReceiveUlpFecConfig( + VideoReceiveStreamInterface::Config* receive_config) { + receive_config->rtp.red_payload_type = kRedPayloadType; + receive_config->rtp.ulpfec_payload_type = kUlpfecPayloadType; + receive_config->rtp.rtx_associated_payload_types[kRtxRedPayloadType] = + kRedPayloadType; +} + +void CallTest::CreateSendConfig(size_t num_video_streams, + size_t num_audio_streams, + size_t num_flexfec_streams, + Transport* send_transport) { + if (num_video_streams > 0) { + video_send_configs_.clear(); + video_send_configs_.emplace_back(nullptr); + CreateVideoSendConfig(&video_send_configs_.back(), num_video_streams, 0, + send_transport); + } + CreateAudioAndFecSendConfigs(num_audio_streams, num_flexfec_streams, + send_transport); +} + +void CallTest::CreateMatchingVideoReceiveConfigs( + const VideoSendStream::Config& video_send_config, + Transport* rtcp_send_transport) { + CreateMatchingVideoReceiveConfigs(video_send_config, rtcp_send_transport, + &fake_decoder_factory_, absl::nullopt, + false, 0); +} + +void CallTest::CreateMatchingVideoReceiveConfigs( + const VideoSendStream::Config& video_send_config, + Transport* rtcp_send_transport, + VideoDecoderFactory* decoder_factory, + absl::optional<size_t> decode_sub_stream, + bool receiver_reference_time_report, + int rtp_history_ms) { + AddMatchingVideoReceiveConfigs( + &video_receive_configs_, video_send_config, rtcp_send_transport, + decoder_factory, decode_sub_stream, receiver_reference_time_report, + rtp_history_ms); +} + +void CallTest::AddMatchingVideoReceiveConfigs( + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + const VideoSendStream::Config& video_send_config, + Transport* rtcp_send_transport, + VideoDecoderFactory* decoder_factory, + absl::optional<size_t> decode_sub_stream, + bool receiver_reference_time_report, + int rtp_history_ms) { + RTC_DCHECK(!video_send_config.rtp.ssrcs.empty()); + VideoReceiveStreamInterface::Config default_config(rtcp_send_transport); + default_config.rtp.local_ssrc = kReceiverLocalVideoSsrc; + for (const RtpExtension& extension : video_send_config.rtp.extensions) + default_config.rtp.extensions.push_back(extension); + default_config.rtp.nack.rtp_history_ms = rtp_history_ms; + // Enable RTT calculation so NTP time estimator will work. + default_config.rtp.rtcp_xr.receiver_reference_time_report = + receiver_reference_time_report; + default_config.renderer = &fake_renderer_; + + for (size_t i = 0; i < video_send_config.rtp.ssrcs.size(); ++i) { + VideoReceiveStreamInterface::Config video_recv_config( + default_config.Copy()); + video_recv_config.decoders.clear(); + if (!video_send_config.rtp.rtx.ssrcs.empty()) { + video_recv_config.rtp.rtx_ssrc = video_send_config.rtp.rtx.ssrcs[i]; + video_recv_config.rtp.rtx_associated_payload_types[kSendRtxPayloadType] = + video_send_config.rtp.payload_type; + } + video_recv_config.rtp.remote_ssrc = video_send_config.rtp.ssrcs[i]; + VideoReceiveStreamInterface::Decoder decoder; + + decoder.payload_type = video_send_config.rtp.payload_type; + decoder.video_format = SdpVideoFormat(video_send_config.rtp.payload_name); + // Force fake decoders on non-selected simulcast streams. + if (!decode_sub_stream || i == *decode_sub_stream) { + video_recv_config.decoder_factory = decoder_factory; + } else { + video_recv_config.decoder_factory = &fake_decoder_factory_; + } + video_recv_config.decoders.push_back(decoder); + receive_configs->emplace_back(std::move(video_recv_config)); + } +} + +void CallTest::CreateMatchingAudioAndFecConfigs( + Transport* rtcp_send_transport) { + RTC_DCHECK_GE(1, num_audio_streams_); + if (num_audio_streams_ == 1) { + CreateMatchingAudioConfigs(rtcp_send_transport, ""); + } + + // TODO(brandtr): Update this when we support multistream protection. + RTC_DCHECK(num_flexfec_streams_ <= 1); + if (num_flexfec_streams_ == 1) { + CreateMatchingFecConfig(rtcp_send_transport, *GetVideoSendConfig()); + for (const RtpExtension& extension : GetVideoSendConfig()->rtp.extensions) + GetFlexFecConfig()->rtp.extensions.push_back(extension); + } +} + +void CallTest::CreateMatchingAudioConfigs(Transport* transport, + std::string sync_group) { + audio_receive_configs_.push_back(CreateMatchingAudioConfig( + audio_send_config_, audio_decoder_factory_, transport, sync_group)); +} + +AudioReceiveStreamInterface::Config CallTest::CreateMatchingAudioConfig( + const AudioSendStream::Config& send_config, + rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory, + Transport* transport, + std::string sync_group) { + AudioReceiveStreamInterface::Config audio_config; + audio_config.rtp.local_ssrc = kReceiverLocalAudioSsrc; + audio_config.rtcp_send_transport = transport; + audio_config.rtp.remote_ssrc = send_config.rtp.ssrc; + audio_config.rtp.extensions = send_config.rtp.extensions; + audio_config.decoder_factory = audio_decoder_factory; + audio_config.decoder_map = {{kAudioSendPayloadType, {"opus", 48000, 2}}}; + audio_config.sync_group = sync_group; + return audio_config; +} + +void CallTest::CreateMatchingFecConfig( + Transport* transport, + const VideoSendStream::Config& send_config) { + FlexfecReceiveStream::Config config(transport); + config.payload_type = send_config.rtp.flexfec.payload_type; + config.rtp.remote_ssrc = send_config.rtp.flexfec.ssrc; + config.protected_media_ssrcs = send_config.rtp.flexfec.protected_media_ssrcs; + config.rtp.local_ssrc = kReceiverLocalVideoSsrc; + if (!video_receive_configs_.empty()) { + video_receive_configs_[0].rtp.protected_by_flexfec = true; + video_receive_configs_[0].rtp.packet_sink_ = this; + } + flexfec_receive_configs_.push_back(config); +} + +void CallTest::CreateMatchingReceiveConfigs(Transport* rtcp_send_transport) { + video_receive_configs_.clear(); + for (VideoSendStream::Config& video_send_config : video_send_configs_) { + CreateMatchingVideoReceiveConfigs(video_send_config, rtcp_send_transport); + } + CreateMatchingAudioAndFecConfigs(rtcp_send_transport); +} + +void CallTest::CreateFrameGeneratorCapturerWithDrift(Clock* clock, + float speed, + int framerate, + int width, + int height) { + video_sources_.clear(); + auto frame_generator_capturer = + std::make_unique<test::FrameGeneratorCapturer>( + clock, + test::CreateSquareFrameGenerator(width, height, absl::nullopt, + absl::nullopt), + framerate * speed, *task_queue_factory_); + frame_generator_capturer_ = frame_generator_capturer.get(); + frame_generator_capturer->Init(); + video_sources_.push_back(std::move(frame_generator_capturer)); + ConnectVideoSourcesToStreams(); +} + +void CallTest::CreateFrameGeneratorCapturer(int framerate, + int width, + int height) { + video_sources_.clear(); + auto frame_generator_capturer = + std::make_unique<test::FrameGeneratorCapturer>( + clock_, + test::CreateSquareFrameGenerator(width, height, absl::nullopt, + absl::nullopt), + framerate, *task_queue_factory_); + frame_generator_capturer_ = frame_generator_capturer.get(); + frame_generator_capturer->Init(); + video_sources_.push_back(std::move(frame_generator_capturer)); + ConnectVideoSourcesToStreams(); +} + +void CallTest::CreateFakeAudioDevices( + std::unique_ptr<TestAudioDeviceModule::Capturer> capturer, + std::unique_ptr<TestAudioDeviceModule::Renderer> renderer) { + fake_send_audio_device_ = TestAudioDeviceModule::Create( + task_queue_factory_.get(), std::move(capturer), nullptr, 1.f); + fake_recv_audio_device_ = TestAudioDeviceModule::Create( + task_queue_factory_.get(), nullptr, std::move(renderer), 1.f); +} + +void CallTest::CreateVideoStreams() { + RTC_DCHECK(video_receive_streams_.empty()); + CreateVideoSendStreams(); + for (size_t i = 0; i < video_receive_configs_.size(); ++i) { + video_receive_streams_.push_back(receiver_call_->CreateVideoReceiveStream( + video_receive_configs_[i].Copy())); + } +} + +void CallTest::CreateVideoSendStreams() { + RTC_DCHECK(video_send_streams_.empty()); + + // We currently only support testing external fec controllers with a single + // VideoSendStream. + if (fec_controller_factory_.get()) { + RTC_DCHECK_LE(video_send_configs_.size(), 1); + } + + // TODO(http://crbug/818127): + // Remove this workaround when ALR is not screenshare-specific. + std::list<size_t> streams_creation_order; + for (size_t i = 0; i < video_send_configs_.size(); ++i) { + // If dual streams are created, add the screenshare stream last. + if (video_encoder_configs_[i].content_type == + VideoEncoderConfig::ContentType::kScreen) { + streams_creation_order.push_back(i); + } else { + streams_creation_order.push_front(i); + } + } + + video_send_streams_.resize(video_send_configs_.size(), nullptr); + + for (size_t i : streams_creation_order) { + if (fec_controller_factory_.get()) { + video_send_streams_[i] = sender_call_->CreateVideoSendStream( + video_send_configs_[i].Copy(), video_encoder_configs_[i].Copy(), + fec_controller_factory_->CreateFecController()); + } else { + video_send_streams_[i] = sender_call_->CreateVideoSendStream( + video_send_configs_[i].Copy(), video_encoder_configs_[i].Copy()); + } + } +} + +void CallTest::CreateVideoSendStream(const VideoEncoderConfig& encoder_config) { + RTC_DCHECK(video_send_streams_.empty()); + video_send_streams_.push_back(sender_call_->CreateVideoSendStream( + GetVideoSendConfig()->Copy(), encoder_config.Copy())); +} + +void CallTest::CreateAudioStreams() { + RTC_DCHECK(audio_send_stream_ == nullptr); + RTC_DCHECK(audio_receive_streams_.empty()); + audio_send_stream_ = sender_call_->CreateAudioSendStream(audio_send_config_); + for (size_t i = 0; i < audio_receive_configs_.size(); ++i) { + audio_receive_streams_.push_back( + receiver_call_->CreateAudioReceiveStream(audio_receive_configs_[i])); + } +} + +void CallTest::CreateFlexfecStreams() { + for (size_t i = 0; i < flexfec_receive_configs_.size(); ++i) { + flexfec_receive_streams_.push_back( + receiver_call_->CreateFlexfecReceiveStream( + flexfec_receive_configs_[i])); + } +} + +void CallTest::CreateSendTransport(const BuiltInNetworkBehaviorConfig& config, + RtpRtcpObserver* observer) { + PacketReceiver* receiver = + receiver_call_ ? receiver_call_->Receiver() : nullptr; + + auto network = std::make_unique<SimulatedNetwork>(config); + send_simulated_network_ = network.get(); + send_transport_ = std::make_unique<PacketTransport>( + task_queue(), sender_call_.get(), observer, + test::PacketTransport::kSender, payload_type_map_, + std::make_unique<FakeNetworkPipe>(Clock::GetRealTimeClock(), + std::move(network), receiver), + rtp_extensions_, rtp_extensions_); +} + +void CallTest::CreateReceiveTransport( + const BuiltInNetworkBehaviorConfig& config, + RtpRtcpObserver* observer) { + auto network = std::make_unique<SimulatedNetwork>(config); + receive_simulated_network_ = network.get(); + receive_transport_ = std::make_unique<PacketTransport>( + task_queue(), nullptr, observer, test::PacketTransport::kReceiver, + payload_type_map_, + std::make_unique<FakeNetworkPipe>(Clock::GetRealTimeClock(), + std::move(network), + sender_call_->Receiver()), + rtp_extensions_, rtp_extensions_); +} + +void CallTest::ConnectVideoSourcesToStreams() { + for (size_t i = 0; i < video_sources_.size(); ++i) + video_send_streams_[i]->SetSource(video_sources_[i].get(), + degradation_preference_); +} + +void CallTest::Start() { + StartVideoStreams(); + if (audio_send_stream_) { + audio_send_stream_->Start(); + } + for (AudioReceiveStreamInterface* audio_recv_stream : audio_receive_streams_) + audio_recv_stream->Start(); +} + +void CallTest::StartVideoStreams() { + for (size_t i = 0; i < video_send_streams_.size(); ++i) { + std::vector<bool> active_rtp_streams( + video_send_configs_[i].rtp.ssrcs.size(), true); + video_send_streams_[i]->StartPerRtpStream(active_rtp_streams); + } + for (VideoReceiveStreamInterface* video_recv_stream : video_receive_streams_) + video_recv_stream->Start(); +} + +void CallTest::Stop() { + for (AudioReceiveStreamInterface* audio_recv_stream : audio_receive_streams_) + audio_recv_stream->Stop(); + if (audio_send_stream_) { + audio_send_stream_->Stop(); + } + StopVideoStreams(); +} + +void CallTest::StopVideoStreams() { + for (VideoSendStream* video_send_stream : video_send_streams_) + video_send_stream->Stop(); + for (VideoReceiveStreamInterface* video_recv_stream : video_receive_streams_) + video_recv_stream->Stop(); +} + +void CallTest::DestroyStreams() { + if (audio_send_stream_) + sender_call_->DestroyAudioSendStream(audio_send_stream_); + audio_send_stream_ = nullptr; + for (AudioReceiveStreamInterface* audio_recv_stream : audio_receive_streams_) + receiver_call_->DestroyAudioReceiveStream(audio_recv_stream); + + DestroyVideoSendStreams(); + + for (VideoReceiveStreamInterface* video_recv_stream : video_receive_streams_) + receiver_call_->DestroyVideoReceiveStream(video_recv_stream); + + for (FlexfecReceiveStream* flexfec_recv_stream : flexfec_receive_streams_) + receiver_call_->DestroyFlexfecReceiveStream(flexfec_recv_stream); + + video_receive_streams_.clear(); + video_sources_.clear(); +} + +void CallTest::DestroyVideoSendStreams() { + for (VideoSendStream* video_send_stream : video_send_streams_) + sender_call_->DestroyVideoSendStream(video_send_stream); + video_send_streams_.clear(); +} + +void CallTest::SetFakeVideoCaptureRotation(VideoRotation rotation) { + frame_generator_capturer_->SetFakeRotation(rotation); +} + +void CallTest::SetVideoDegradation(DegradationPreference preference) { + GetVideoSendStream()->SetSource(frame_generator_capturer_, preference); +} + +VideoSendStream::Config* CallTest::GetVideoSendConfig() { + return &video_send_configs_[0]; +} + +void CallTest::SetVideoSendConfig(const VideoSendStream::Config& config) { + video_send_configs_.clear(); + video_send_configs_.push_back(config.Copy()); +} + +VideoEncoderConfig* CallTest::GetVideoEncoderConfig() { + return &video_encoder_configs_[0]; +} + +void CallTest::SetVideoEncoderConfig(const VideoEncoderConfig& config) { + video_encoder_configs_.clear(); + video_encoder_configs_.push_back(config.Copy()); +} + +VideoSendStream* CallTest::GetVideoSendStream() { + return video_send_streams_[0]; +} +FlexfecReceiveStream::Config* CallTest::GetFlexFecConfig() { + return &flexfec_receive_configs_[0]; +} + +void CallTest::OnRtpPacket(const RtpPacketReceived& packet) { + // All FlexFEC streams protect all of the video streams. + for (FlexfecReceiveStream* flexfec_recv_stream : flexfec_receive_streams_) + flexfec_recv_stream->OnRtpPacket(packet); +} + +absl::optional<RtpExtension> CallTest::GetRtpExtensionByUri( + const std::string& uri) const { + for (const auto& extension : rtp_extensions_) { + if (extension.uri == uri) { + return extension; + } + } + return absl::nullopt; +} + +void CallTest::AddRtpExtensionByUri( + const std::string& uri, + std::vector<RtpExtension>* extensions) const { + const absl::optional<RtpExtension> extension = GetRtpExtensionByUri(uri); + if (extension) { + extensions->push_back(*extension); + } +} + +constexpr size_t CallTest::kNumSsrcs; +const int CallTest::kDefaultWidth; +const int CallTest::kDefaultHeight; +const int CallTest::kDefaultFramerate; +const uint32_t CallTest::kSendRtxSsrcs[kNumSsrcs] = { + 0xBADCAFD, 0xBADCAFE, 0xBADCAFF, 0xBADCB00, 0xBADCB01, 0xBADCB02}; +const uint32_t CallTest::kVideoSendSsrcs[kNumSsrcs] = { + 0xC0FFED, 0xC0FFEE, 0xC0FFEF, 0xC0FFF0, 0xC0FFF1, 0xC0FFF2}; +const uint32_t CallTest::kAudioSendSsrc = 0xDEADBEEF; +const uint32_t CallTest::kFlexfecSendSsrc = 0xBADBEEF; +const uint32_t CallTest::kReceiverLocalVideoSsrc = 0x123456; +const uint32_t CallTest::kReceiverLocalAudioSsrc = 0x1234567; +const int CallTest::kNackRtpHistoryMs = 1000; + +const std::map<uint8_t, MediaType> CallTest::payload_type_map_ = { + {CallTest::kVideoSendPayloadType, MediaType::VIDEO}, + {CallTest::kFakeVideoSendPayloadType, MediaType::VIDEO}, + {CallTest::kSendRtxPayloadType, MediaType::VIDEO}, + {CallTest::kPayloadTypeVP8, MediaType::VIDEO}, + {CallTest::kPayloadTypeVP9, MediaType::VIDEO}, + {CallTest::kPayloadTypeH264, MediaType::VIDEO}, + {CallTest::kPayloadTypeGeneric, MediaType::VIDEO}, + {CallTest::kRedPayloadType, MediaType::VIDEO}, + {CallTest::kRtxRedPayloadType, MediaType::VIDEO}, + {CallTest::kUlpfecPayloadType, MediaType::VIDEO}, + {CallTest::kFlexfecPayloadType, MediaType::VIDEO}, + {CallTest::kAudioSendPayloadType, MediaType::AUDIO}}; + +BaseTest::BaseTest() {} + +BaseTest::BaseTest(TimeDelta timeout) : RtpRtcpObserver(timeout) {} + +BaseTest::~BaseTest() {} + +std::unique_ptr<TestAudioDeviceModule::Capturer> BaseTest::CreateCapturer() { + return TestAudioDeviceModule::CreatePulsedNoiseCapturer(256, 48000); +} + +std::unique_ptr<TestAudioDeviceModule::Renderer> BaseTest::CreateRenderer() { + return TestAudioDeviceModule::CreateDiscardRenderer(48000); +} + +void BaseTest::OnFakeAudioDevicesCreated( + TestAudioDeviceModule* send_audio_device, + TestAudioDeviceModule* recv_audio_device) {} + +void BaseTest::ModifySenderBitrateConfig(BitrateConstraints* bitrate_config) {} + +void BaseTest::ModifyReceiverBitrateConfig(BitrateConstraints* bitrate_config) { +} + +void BaseTest::OnCallsCreated(Call* sender_call, Call* receiver_call) {} + +void BaseTest::OnTransportCreated(PacketTransport* to_receiver, + SimulatedNetworkInterface* sender_network, + PacketTransport* to_sender, + SimulatedNetworkInterface* receiver_network) { +} + +BuiltInNetworkBehaviorConfig BaseTest::GetSendTransportConfig() const { + return BuiltInNetworkBehaviorConfig(); +} +BuiltInNetworkBehaviorConfig BaseTest::GetReceiveTransportConfig() const { + return BuiltInNetworkBehaviorConfig(); +} +size_t BaseTest::GetNumVideoStreams() const { + return 1; +} + +size_t BaseTest::GetNumAudioStreams() const { + return 0; +} + +size_t BaseTest::GetNumFlexfecStreams() const { + return 0; +} + +void BaseTest::ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + VideoEncoderConfig* encoder_config) {} + +void BaseTest::ModifyVideoCaptureStartResolution(int* width, + int* heigt, + int* frame_rate) {} + +void BaseTest::ModifyVideoDegradationPreference( + DegradationPreference* degradation_preference) {} + +void BaseTest::OnVideoStreamsCreated( + VideoSendStream* send_stream, + const std::vector<VideoReceiveStreamInterface*>& receive_streams) {} + +void BaseTest::ModifyAudioConfigs( + AudioSendStream::Config* send_config, + std::vector<AudioReceiveStreamInterface::Config>* receive_configs) {} + +void BaseTest::OnAudioStreamsCreated( + AudioSendStream* send_stream, + const std::vector<AudioReceiveStreamInterface*>& receive_streams) {} + +void BaseTest::ModifyFlexfecConfigs( + std::vector<FlexfecReceiveStream::Config>* receive_configs) {} + +void BaseTest::OnFlexfecStreamsCreated( + const std::vector<FlexfecReceiveStream*>& receive_streams) {} + +void BaseTest::OnFrameGeneratorCapturerCreated( + FrameGeneratorCapturer* frame_generator_capturer) {} + +void BaseTest::OnStreamsStopped() {} + +SendTest::SendTest(TimeDelta timeout) : BaseTest(timeout) {} + +bool SendTest::ShouldCreateReceivers() const { + return false; +} + +EndToEndTest::EndToEndTest() {} + +EndToEndTest::EndToEndTest(TimeDelta timeout) : BaseTest(timeout) {} + +bool EndToEndTest::ShouldCreateReceivers() const { + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/call_test.h b/third_party/libwebrtc/test/call_test.h new file mode 100644 index 0000000000..3324bc4b5e --- /dev/null +++ b/third_party/libwebrtc/test/call_test.h @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2014 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_CALL_TEST_H_ +#define TEST_CALL_TEST_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/test/simulated_network.h" +#include "api/test/video/function_video_decoder_factory.h" +#include "api/test/video/function_video_encoder_factory.h" +#include "api/units/time_delta.h" +#include "api/video/video_bitrate_allocator_factory.h" +#include "call/call.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "test/encoder_settings.h" +#include "test/fake_decoder.h" +#include "test/fake_videorenderer.h" +#include "test/fake_vp8_encoder.h" +#include "test/frame_generator_capturer.h" +#include "test/rtp_rtcp_observer.h" +#include "test/run_loop.h" +#include "test/scoped_key_value_config.h" + +namespace webrtc { +namespace test { + +class BaseTest; + +class CallTest : public ::testing::Test, public RtpPacketSinkInterface { + public: + CallTest(); + virtual ~CallTest(); + + static constexpr size_t kNumSsrcs = 6; + static const int kNumSimulcastStreams = 3; + static const int kDefaultWidth = 320; + static const int kDefaultHeight = 180; + static const int kDefaultFramerate = 30; + static constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(30); + static constexpr TimeDelta kLongTimeout = TimeDelta::Seconds(120); + enum classPayloadTypes : uint8_t { + kSendRtxPayloadType = 98, + kRtxRedPayloadType = 99, + kVideoSendPayloadType = 100, + kAudioSendPayloadType = 103, + kRedPayloadType = 118, + kUlpfecPayloadType = 119, + kFlexfecPayloadType = 120, + kPayloadTypeH264 = 122, + kPayloadTypeVP8 = 123, + kPayloadTypeVP9 = 124, + kPayloadTypeGeneric = 125, + kFakeVideoSendPayloadType = 126, + }; + static const uint32_t kSendRtxSsrcs[kNumSsrcs]; + static const uint32_t kVideoSendSsrcs[kNumSsrcs]; + static const uint32_t kAudioSendSsrc; + static const uint32_t kFlexfecSendSsrc; + static const uint32_t kReceiverLocalVideoSsrc; + static const uint32_t kReceiverLocalAudioSsrc; + static const int kNackRtpHistoryMs; + static const std::map<uint8_t, MediaType> payload_type_map_; + + protected: + void RegisterRtpExtension(const RtpExtension& extension); + // Returns header extensions that can be parsed by the transport. + rtc::ArrayView<const RtpExtension> GetRegisteredExtensions() { + return rtp_extensions_; + } + + // RunBaseTest overwrites the audio_state of the send and receive Call configs + // to simplify test code. + void RunBaseTest(BaseTest* test); + + void CreateCalls(); + void CreateCalls(const Call::Config& sender_config, + const Call::Config& receiver_config); + void CreateSenderCall(); + void CreateSenderCall(const Call::Config& config); + void CreateReceiverCall(const Call::Config& config); + void DestroyCalls(); + + void CreateVideoSendConfig(VideoSendStream::Config* video_config, + size_t num_video_streams, + size_t num_used_ssrcs, + Transport* send_transport); + void CreateAudioAndFecSendConfigs(size_t num_audio_streams, + size_t num_flexfec_streams, + Transport* send_transport); + void SetAudioConfig(const AudioSendStream::Config& config); + + void SetSendFecConfig(std::vector<uint32_t> video_send_ssrcs); + void SetSendUlpFecConfig(VideoSendStream::Config* send_config); + void SetReceiveUlpFecConfig( + VideoReceiveStreamInterface::Config* receive_config); + + void CreateSendConfig(size_t num_video_streams, + size_t num_audio_streams, + size_t num_flexfec_streams) { + CreateSendConfig(num_video_streams, num_audio_streams, num_flexfec_streams, + send_transport_.get()); + } + void CreateSendConfig(size_t num_video_streams, + size_t num_audio_streams, + size_t num_flexfec_streams, + Transport* send_transport); + + void CreateMatchingVideoReceiveConfigs( + const VideoSendStream::Config& video_send_config) { + CreateMatchingVideoReceiveConfigs(video_send_config, + receive_transport_.get()); + } + void CreateMatchingVideoReceiveConfigs( + const VideoSendStream::Config& video_send_config, + Transport* rtcp_send_transport); + void CreateMatchingVideoReceiveConfigs( + const VideoSendStream::Config& video_send_config, + Transport* rtcp_send_transport, + VideoDecoderFactory* decoder_factory, + absl::optional<size_t> decode_sub_stream, + bool receiver_reference_time_report, + int rtp_history_ms); + void AddMatchingVideoReceiveConfigs( + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + const VideoSendStream::Config& video_send_config, + Transport* rtcp_send_transport, + VideoDecoderFactory* decoder_factory, + absl::optional<size_t> decode_sub_stream, + bool receiver_reference_time_report, + int rtp_history_ms); + + void CreateMatchingAudioAndFecConfigs(Transport* rtcp_send_transport); + void CreateMatchingAudioConfigs(Transport* transport, std::string sync_group); + static AudioReceiveStreamInterface::Config CreateMatchingAudioConfig( + const AudioSendStream::Config& send_config, + rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory, + Transport* transport, + std::string sync_group); + void CreateMatchingFecConfig( + Transport* transport, + const VideoSendStream::Config& video_send_config); + void CreateMatchingReceiveConfigs() { + CreateMatchingReceiveConfigs(receive_transport_.get()); + } + void CreateMatchingReceiveConfigs(Transport* rtcp_send_transport); + + void CreateFrameGeneratorCapturerWithDrift(Clock* drift_clock, + float speed, + int framerate, + int width, + int height); + void CreateFrameGeneratorCapturer(int framerate, int width, int height); + void CreateFakeAudioDevices( + std::unique_ptr<TestAudioDeviceModule::Capturer> capturer, + std::unique_ptr<TestAudioDeviceModule::Renderer> renderer); + + void CreateVideoStreams(); + void CreateVideoSendStreams(); + void CreateVideoSendStream(const VideoEncoderConfig& encoder_config); + void CreateAudioStreams(); + void CreateFlexfecStreams(); + + // Receiver call must be created before calling CreateSendTransport in order + // to set a receiver. + // Rtp header extensions must be registered (RegisterRtpExtension(..)) before + // the transport is created in order for the receiving call object receive RTP + // packets with extensions. + void CreateSendTransport(const BuiltInNetworkBehaviorConfig& config, + RtpRtcpObserver* observer); + void CreateReceiveTransport(const BuiltInNetworkBehaviorConfig& config, + RtpRtcpObserver* observer); + + void ConnectVideoSourcesToStreams(); + + void Start(); + void StartVideoStreams(); + void Stop(); + void StopVideoStreams(); + void DestroyStreams(); + void DestroyVideoSendStreams(); + void SetFakeVideoCaptureRotation(VideoRotation rotation); + + void SetVideoDegradation(DegradationPreference preference); + + VideoSendStream::Config* GetVideoSendConfig(); + void SetVideoSendConfig(const VideoSendStream::Config& config); + VideoEncoderConfig* GetVideoEncoderConfig(); + void SetVideoEncoderConfig(const VideoEncoderConfig& config); + VideoSendStream* GetVideoSendStream(); + FlexfecReceiveStream::Config* GetFlexFecConfig(); + TaskQueueBase* task_queue() { return task_queue_.get(); } + + // RtpPacketSinkInterface implementation. + void OnRtpPacket(const RtpPacketReceived& packet) override; + + test::RunLoop loop_; + + Clock* const clock_; + test::ScopedKeyValueConfig field_trials_; + + std::unique_ptr<TaskQueueFactory> task_queue_factory_; + std::unique_ptr<webrtc::RtcEventLog> send_event_log_; + std::unique_ptr<webrtc::RtcEventLog> recv_event_log_; + std::unique_ptr<Call> sender_call_; + std::unique_ptr<PacketTransport> send_transport_; + SimulatedNetworkInterface* send_simulated_network_ = nullptr; + std::vector<VideoSendStream::Config> video_send_configs_; + std::vector<VideoEncoderConfig> video_encoder_configs_; + std::vector<VideoSendStream*> video_send_streams_; + AudioSendStream::Config audio_send_config_; + AudioSendStream* audio_send_stream_; + + std::unique_ptr<Call> receiver_call_; + std::unique_ptr<PacketTransport> receive_transport_; + SimulatedNetworkInterface* receive_simulated_network_ = nullptr; + std::vector<VideoReceiveStreamInterface::Config> video_receive_configs_; + std::vector<VideoReceiveStreamInterface*> video_receive_streams_; + std::vector<AudioReceiveStreamInterface::Config> audio_receive_configs_; + std::vector<AudioReceiveStreamInterface*> audio_receive_streams_; + std::vector<FlexfecReceiveStream::Config> flexfec_receive_configs_; + std::vector<FlexfecReceiveStream*> flexfec_receive_streams_; + + test::FrameGeneratorCapturer* frame_generator_capturer_; + std::vector<std::unique_ptr<rtc::VideoSourceInterface<VideoFrame>>> + video_sources_; + DegradationPreference degradation_preference_ = + DegradationPreference::MAINTAIN_FRAMERATE; + + std::unique_ptr<FecControllerFactoryInterface> fec_controller_factory_; + std::unique_ptr<NetworkStatePredictorFactoryInterface> + network_state_predictor_factory_; + std::unique_ptr<NetworkControllerFactoryInterface> + network_controller_factory_; + + test::FunctionVideoEncoderFactory fake_encoder_factory_; + int fake_encoder_max_bitrate_ = -1; + test::FunctionVideoDecoderFactory fake_decoder_factory_; + std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_; + // Number of simulcast substreams. + size_t num_video_streams_; + size_t num_audio_streams_; + size_t num_flexfec_streams_; + rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory_; + rtc::scoped_refptr<AudioEncoderFactory> audio_encoder_factory_; + test::FakeVideoRenderer fake_renderer_; + + + private: + absl::optional<RtpExtension> GetRtpExtensionByUri( + const std::string& uri) const; + + void AddRtpExtensionByUri(const std::string& uri, + std::vector<RtpExtension>* extensions) const; + + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_; + std::vector<RtpExtension> rtp_extensions_; + rtc::scoped_refptr<AudioProcessing> apm_send_; + rtc::scoped_refptr<AudioProcessing> apm_recv_; + rtc::scoped_refptr<TestAudioDeviceModule> fake_send_audio_device_; + rtc::scoped_refptr<TestAudioDeviceModule> fake_recv_audio_device_; +}; + +class BaseTest : public RtpRtcpObserver { + public: + BaseTest(); + explicit BaseTest(TimeDelta timeout); + virtual ~BaseTest(); + + virtual void PerformTest() = 0; + virtual bool ShouldCreateReceivers() const = 0; + + virtual size_t GetNumVideoStreams() const; + virtual size_t GetNumAudioStreams() const; + virtual size_t GetNumFlexfecStreams() const; + + virtual std::unique_ptr<TestAudioDeviceModule::Capturer> CreateCapturer(); + virtual std::unique_ptr<TestAudioDeviceModule::Renderer> CreateRenderer(); + virtual void OnFakeAudioDevicesCreated( + TestAudioDeviceModule* send_audio_device, + TestAudioDeviceModule* recv_audio_device); + + virtual void ModifySenderBitrateConfig(BitrateConstraints* bitrate_config); + virtual void ModifyReceiverBitrateConfig(BitrateConstraints* bitrate_config); + + virtual void OnCallsCreated(Call* sender_call, Call* receiver_call); + virtual void OnTransportCreated(PacketTransport* to_receiver, + SimulatedNetworkInterface* sender_network, + PacketTransport* to_sender, + SimulatedNetworkInterface* receiver_network); + + virtual BuiltInNetworkBehaviorConfig GetSendTransportConfig() const; + virtual BuiltInNetworkBehaviorConfig GetReceiveTransportConfig() const; + + virtual void ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + VideoEncoderConfig* encoder_config); + virtual void ModifyVideoCaptureStartResolution(int* width, + int* heigt, + int* frame_rate); + virtual void ModifyVideoDegradationPreference( + DegradationPreference* degradation_preference); + + virtual void OnVideoStreamsCreated( + VideoSendStream* send_stream, + const std::vector<VideoReceiveStreamInterface*>& receive_streams); + + virtual void ModifyAudioConfigs( + AudioSendStream::Config* send_config, + std::vector<AudioReceiveStreamInterface::Config>* receive_configs); + virtual void OnAudioStreamsCreated( + AudioSendStream* send_stream, + const std::vector<AudioReceiveStreamInterface*>& receive_streams); + + virtual void ModifyFlexfecConfigs( + std::vector<FlexfecReceiveStream::Config>* receive_configs); + virtual void OnFlexfecStreamsCreated( + const std::vector<FlexfecReceiveStream*>& receive_streams); + + virtual void OnFrameGeneratorCapturerCreated( + FrameGeneratorCapturer* frame_generator_capturer); + + virtual void OnStreamsStopped(); +}; + +class SendTest : public BaseTest { + public: + explicit SendTest(TimeDelta timeout); + + bool ShouldCreateReceivers() const override; +}; + +class EndToEndTest : public BaseTest { + public: + EndToEndTest(); + explicit EndToEndTest(TimeDelta timeout); + + bool ShouldCreateReceivers() const override; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_CALL_TEST_H_ diff --git a/third_party/libwebrtc/test/configurable_frame_size_encoder.cc b/third_party/libwebrtc/test/configurable_frame_size_encoder.cc new file mode 100644 index 0000000000..e3965ef770 --- /dev/null +++ b/third_party/libwebrtc/test/configurable_frame_size_encoder.cc @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2013 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/configurable_frame_size_encoder.h" + +#include <string.h> + +#include <cstdint> +#include <type_traits> +#include <utility> + +#include "api/video/encoded_image.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +ConfigurableFrameSizeEncoder::ConfigurableFrameSizeEncoder( + size_t max_frame_size) + : callback_(NULL), + current_frame_size_(max_frame_size), + codec_type_(kVideoCodecGeneric) {} + +ConfigurableFrameSizeEncoder::~ConfigurableFrameSizeEncoder() {} + +void ConfigurableFrameSizeEncoder::SetFecControllerOverride( + FecControllerOverride* fec_controller_override) { + // Ignored. +} + +int32_t ConfigurableFrameSizeEncoder::InitEncode( + const VideoCodec* codec_settings, + const Settings& settings) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::Encode( + const VideoFrame& inputImage, + const std::vector<VideoFrameType>* frame_types) { + EncodedImage encodedImage; + auto buffer = EncodedImageBuffer::Create(current_frame_size_); + memset(buffer->data(), 0, current_frame_size_); + encodedImage.SetEncodedData(buffer); + encodedImage._encodedHeight = inputImage.height(); + encodedImage._encodedWidth = inputImage.width(); + encodedImage._frameType = VideoFrameType::kVideoFrameKey; + encodedImage.SetTimestamp(inputImage.timestamp()); + encodedImage.capture_time_ms_ = inputImage.render_time_ms(); + CodecSpecificInfo specific{}; + specific.codecType = codec_type_; + callback_->OnEncodedImage(encodedImage, &specific); + if (post_encode_callback_) { + (*post_encode_callback_)(); + } + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t ConfigurableFrameSizeEncoder::Release() { + return WEBRTC_VIDEO_CODEC_OK; +} + +void ConfigurableFrameSizeEncoder::SetRates( + const RateControlParameters& parameters) {} + +int32_t ConfigurableFrameSizeEncoder::SetFrameSize(size_t size) { + current_frame_size_ = size; + return WEBRTC_VIDEO_CODEC_OK; +} + +void ConfigurableFrameSizeEncoder::SetCodecType(VideoCodecType codec_type) { + codec_type_ = codec_type; +} + +void ConfigurableFrameSizeEncoder::RegisterPostEncodeCallback( + std::function<void(void)> post_encode_callback) { + post_encode_callback_ = std::move(post_encode_callback); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/configurable_frame_size_encoder.h b/third_party/libwebrtc/test/configurable_frame_size_encoder.h new file mode 100644 index 0000000000..8dd5157b5b --- /dev/null +++ b/third_party/libwebrtc/test/configurable_frame_size_encoder.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 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_CONFIGURABLE_FRAME_SIZE_ENCODER_H_ +#define TEST_CONFIGURABLE_FRAME_SIZE_ENCODER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <functional> +#include <memory> +#include <vector> + +#include "absl/types/optional.h" +#include "api/video/video_bitrate_allocation.h" +#include "api/video/video_frame.h" +#include "api/video_codecs/video_codec.h" +#include "api/video_codecs/video_encoder.h" +#include "modules/video_coding/include/video_codec_interface.h" + +namespace webrtc { +namespace test { + +class ConfigurableFrameSizeEncoder : public VideoEncoder { + public: + explicit ConfigurableFrameSizeEncoder(size_t max_frame_size); + ~ConfigurableFrameSizeEncoder() override; + + void SetFecControllerOverride( + FecControllerOverride* fec_controller_override) override; + + int32_t InitEncode(const VideoCodec* codec_settings, + const Settings& settings) override; + + int32_t Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) override; + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + + int32_t Release() override; + + void SetRates(const RateControlParameters& parameters) override; + + int32_t SetFrameSize(size_t size); + + void SetCodecType(VideoCodecType codec_type_); + + void RegisterPostEncodeCallback( + std::function<void(void)> post_encode_callback); + + private: + EncodedImageCallback* callback_; + absl::optional<std::function<void(void)>> post_encode_callback_; + + size_t current_frame_size_; + VideoCodecType codec_type_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_CONFIGURABLE_FRAME_SIZE_ENCODER_H_ diff --git a/third_party/libwebrtc/test/direct_transport.cc b/third_party/libwebrtc/test/direct_transport.cc new file mode 100644 index 0000000000..260497947c --- /dev/null +++ b/third_party/libwebrtc/test/direct_transport.cc @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2013 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/direct_transport.h" + +#include "api/media_types.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/time_delta.h" +#include "call/call.h" +#include "call/fake_network_pipe.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "rtc_base/checks.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { +namespace test { + +Demuxer::Demuxer(const std::map<uint8_t, MediaType>& payload_type_map) + : payload_type_map_(payload_type_map) {} + +MediaType Demuxer::GetMediaType(const uint8_t* packet_data, + const size_t packet_length) const { + if (IsRtpPacket(rtc::MakeArrayView(packet_data, packet_length))) { + RTC_CHECK_GE(packet_length, 2); + const uint8_t payload_type = packet_data[1] & 0x7f; + std::map<uint8_t, MediaType>::const_iterator it = + payload_type_map_.find(payload_type); + RTC_CHECK(it != payload_type_map_.end()) + << "payload type " << static_cast<int>(payload_type) << " unknown."; + return it->second; + } + return MediaType::ANY; +} + +DirectTransport::DirectTransport( + TaskQueueBase* task_queue, + std::unique_ptr<SimulatedPacketReceiverInterface> pipe, + Call* send_call, + const std::map<uint8_t, MediaType>& payload_type_map, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions) + : send_call_(send_call), + task_queue_(task_queue), + demuxer_(payload_type_map), + fake_network_(std::move(pipe)), + audio_extensions_(audio_extensions), + video_extensions_(video_extensions) { + Start(); +} + +DirectTransport::~DirectTransport() { + next_process_task_.Stop(); +} + +void DirectTransport::SetReceiver(PacketReceiver* receiver) { + fake_network_->SetReceiver(receiver); +} + +bool DirectTransport::SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& options) { + if (send_call_) { + rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis()); + sent_packet.info.included_in_feedback = options.included_in_feedback; + sent_packet.info.included_in_allocation = options.included_in_allocation; + sent_packet.info.packet_size_bytes = length; + sent_packet.info.packet_type = rtc::PacketType::kData; + send_call_->OnSentPacket(sent_packet); + } + + const RtpHeaderExtensionMap* extensions = nullptr; + MediaType media_type = demuxer_.GetMediaType(data, length); + switch (demuxer_.GetMediaType(data, length)) { + case webrtc::MediaType::AUDIO: + extensions = &audio_extensions_; + break; + case webrtc::MediaType::VIDEO: + extensions = &video_extensions_; + break; + default: + RTC_CHECK_NOTREACHED(); + } + RtpPacketReceived packet(extensions, Timestamp::Micros(rtc::TimeMicros())); + if (media_type == MediaType::VIDEO) { + packet.set_payload_type_frequency(kVideoPayloadTypeFrequency); + } + RTC_CHECK(packet.Parse(rtc::CopyOnWriteBuffer(data, length))); + fake_network_->DeliverRtpPacket( + media_type, std::move(packet), + [](const RtpPacketReceived& packet) { return false; }); + + MutexLock lock(&process_lock_); + if (!next_process_task_.Running()) + ProcessPackets(); + return true; +} + +bool DirectTransport::SendRtcp(const uint8_t* data, size_t length) { + fake_network_->DeliverRtcpPacket(rtc::CopyOnWriteBuffer(data, length)); + MutexLock lock(&process_lock_); + if (!next_process_task_.Running()) + ProcessPackets(); + return true; +} + +int DirectTransport::GetAverageDelayMs() { + return fake_network_->AverageDelay(); +} + +void DirectTransport::Start() { + RTC_DCHECK(task_queue_); + if (send_call_) { + send_call_->SignalChannelNetworkState(MediaType::AUDIO, kNetworkUp); + send_call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + } +} + +void DirectTransport::ProcessPackets() { + absl::optional<int64_t> initial_delay_ms = + fake_network_->TimeUntilNextProcess(); + if (initial_delay_ms == absl::nullopt) + return; + + next_process_task_ = RepeatingTaskHandle::DelayedStart( + task_queue_, TimeDelta::Millis(*initial_delay_ms), [this] { + fake_network_->Process(); + if (auto delay_ms = fake_network_->TimeUntilNextProcess()) + return TimeDelta::Millis(*delay_ms); + // Otherwise stop the task. + MutexLock lock(&process_lock_); + next_process_task_.Stop(); + // Since this task is stopped, return value doesn't matter. + return TimeDelta::Zero(); + }); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/direct_transport.h b/third_party/libwebrtc/test/direct_transport.h new file mode 100644 index 0000000000..468e339c0a --- /dev/null +++ b/third_party/libwebrtc/test/direct_transport.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2013 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_DIRECT_TRANSPORT_H_ +#define TEST_DIRECT_TRANSPORT_H_ + +#include <memory> + +#include "api/call/transport.h" +#include "api/sequence_checker.h" +#include "api/task_queue/task_queue_base.h" +#include "api/test/simulated_network.h" +#include "call/call.h" +#include "call/simulated_packet_receiver.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class PacketReceiver; + +namespace test { +class Demuxer { + public: + explicit Demuxer(const std::map<uint8_t, MediaType>& payload_type_map); + ~Demuxer() = default; + + Demuxer(const Demuxer&) = delete; + Demuxer& operator=(const Demuxer&) = delete; + + MediaType GetMediaType(const uint8_t* packet_data, + size_t packet_length) const; + const std::map<uint8_t, MediaType> payload_type_map_; +}; + +// Objects of this class are expected to be allocated and destroyed on the +// same task-queue - the one that's passed in via the constructor. +class DirectTransport : public Transport { + public: + DirectTransport(TaskQueueBase* task_queue, + std::unique_ptr<SimulatedPacketReceiverInterface> pipe, + Call* send_call, + const std::map<uint8_t, MediaType>& payload_type_map, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions); + + ~DirectTransport() override; + + // TODO(holmer): Look into moving this to the constructor. + virtual void SetReceiver(PacketReceiver* receiver); + + bool SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& options) override; + bool SendRtcp(const uint8_t* data, size_t length) override; + + int GetAverageDelayMs(); + + private: + void ProcessPackets() RTC_EXCLUSIVE_LOCKS_REQUIRED(&process_lock_); + void LegacySendPacket(const uint8_t* data, size_t length); + void Start(); + + Call* const send_call_; + + TaskQueueBase* const task_queue_; + + Mutex process_lock_; + RepeatingTaskHandle next_process_task_ RTC_GUARDED_BY(&process_lock_); + + const Demuxer demuxer_; + const std::unique_ptr<SimulatedPacketReceiverInterface> fake_network_; + const RtpHeaderExtensionMap audio_extensions_; + const RtpHeaderExtensionMap video_extensions_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_DIRECT_TRANSPORT_H_ diff --git a/third_party/libwebrtc/test/direct_transport_unittest.cc b/third_party/libwebrtc/test/direct_transport_unittest.cc new file mode 100644 index 0000000000..ab00971089 --- /dev/null +++ b/third_party/libwebrtc/test/direct_transport_unittest.cc @@ -0,0 +1,34 @@ +/* + * Copyright 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/direct_transport.h" + +#include <string.h> + +#include "test/gtest.h" + +namespace webrtc { +namespace test { +TEST(DemuxerTest, Demuxing) { + constexpr uint8_t kVideoPayloadType = 100; + constexpr uint8_t kAudioPayloadType = 101; + constexpr size_t kPacketSize = 12; + Demuxer demuxer({{kVideoPayloadType, MediaType::VIDEO}, + {kAudioPayloadType, MediaType::AUDIO}}); + + uint8_t data[kPacketSize]; + memset(data, 0, kPacketSize); + data[0] = 0x80; + data[1] = kVideoPayloadType; + EXPECT_EQ(demuxer.GetMediaType(data, kPacketSize), MediaType::VIDEO); + data[1] = kAudioPayloadType; + EXPECT_EQ(demuxer.GetMediaType(data, kPacketSize), MediaType::AUDIO); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/drifting_clock.cc b/third_party/libwebrtc/test/drifting_clock.cc new file mode 100644 index 0000000000..47c8e56916 --- /dev/null +++ b/third_party/libwebrtc/test/drifting_clock.cc @@ -0,0 +1,45 @@ +/* + * 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/drifting_clock.h" + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { +constexpr float DriftingClock::kNoDrift; + +DriftingClock::DriftingClock(Clock* clock, float speed) + : clock_(clock), drift_(speed - 1.0f), start_time_(clock_->CurrentTime()) { + RTC_CHECK(clock); + RTC_CHECK_GT(speed, 0.0f); +} + +TimeDelta DriftingClock::Drift() const { + auto now = clock_->CurrentTime(); + RTC_DCHECK_GE(now, start_time_); + return (now - start_time_) * drift_; +} + +Timestamp DriftingClock::Drift(Timestamp timestamp) const { + return timestamp + Drift() / 1000.; +} + +NtpTime DriftingClock::Drift(NtpTime ntp_time) const { + // NTP precision is 1/2^32 seconds, i.e. 2^32 ntp fractions = 1 second. + const double kNtpFracPerMicroSecond = 4294.967296; // = 2^32 / 10^6 + + uint64_t total_fractions = static_cast<uint64_t>(ntp_time); + total_fractions += Drift().us() * kNtpFracPerMicroSecond; + return NtpTime(total_fractions); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/drifting_clock.h b/third_party/libwebrtc/test/drifting_clock.h new file mode 100644 index 0000000000..4a2500ba30 --- /dev/null +++ b/third_party/libwebrtc/test/drifting_clock.h @@ -0,0 +1,50 @@ +/* + * 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_DRIFTING_CLOCK_H_ +#define TEST_DRIFTING_CLOCK_H_ + +#include <stdint.h> + +#include "system_wrappers/include/clock.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace test { +class DriftingClock : public Clock { + public: + static constexpr float kNoDrift = 1.0f; + + DriftingClock(Clock* clock, float speed); + + static constexpr float PercentsFaster(float percent) { + return 1.0f + percent / 100.0f; + } + static constexpr float PercentsSlower(float percent) { + return 1.0f - percent / 100.0f; + } + + Timestamp CurrentTime() override { return Drift(clock_->CurrentTime()); } + NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override { + return Drift(clock_->ConvertTimestampToNtpTime(timestamp)); + } + + private: + TimeDelta Drift() const; + Timestamp Drift(Timestamp timestamp) const; + NtpTime Drift(NtpTime ntp_time) const; + + Clock* const clock_; + const float drift_; + const Timestamp start_time_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_DRIFTING_CLOCK_H_ diff --git a/third_party/libwebrtc/test/encoder_settings.cc b/third_party/libwebrtc/test/encoder_settings.cc new file mode 100644 index 0000000000..f5b298b107 --- /dev/null +++ b/third_party/libwebrtc/test/encoder_settings.cc @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2014 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/encoder_settings.h" + +#include <algorithm> + +#include "api/scoped_refptr.h" +#include "api/video_codecs/sdp_video_format.h" +#include "call/rtp_config.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +const size_t DefaultVideoStreamFactory::kMaxNumberOfStreams; +const int DefaultVideoStreamFactory::kMaxBitratePerStream[] = {150000, 450000, + 1500000}; +const int DefaultVideoStreamFactory::kDefaultMinBitratePerStream[] = { + 30000, 200000, 700000}; + +// static +std::vector<VideoStream> CreateVideoStreams( + int width, + int height, + const webrtc::VideoEncoderConfig& encoder_config) { + RTC_DCHECK(encoder_config.number_of_streams <= + DefaultVideoStreamFactory::kMaxNumberOfStreams); + + std::vector<VideoStream> stream_settings(encoder_config.number_of_streams); + + int bitrate_left_bps = 0; + if (encoder_config.max_bitrate_bps > 0) { + bitrate_left_bps = encoder_config.max_bitrate_bps; + } else { + for (size_t stream_num = 0; stream_num < encoder_config.number_of_streams; + ++stream_num) { + bitrate_left_bps += + DefaultVideoStreamFactory::kMaxBitratePerStream[stream_num]; + } + } + + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + stream_settings[i].width = + (i + 1) * width / encoder_config.number_of_streams; + stream_settings[i].height = + (i + 1) * height / encoder_config.number_of_streams; + stream_settings[i].max_framerate = 30; + stream_settings[i].max_qp = 56; + stream_settings[i].min_bitrate_bps = + DefaultVideoStreamFactory::kDefaultMinBitratePerStream[i]; + + // Use configured values instead of default values if set. + const VideoStream stream = (i < encoder_config.simulcast_layers.size()) + ? encoder_config.simulcast_layers[i] + : VideoStream(); + + int max_bitrate_bps = + stream.max_bitrate_bps > 0 + ? stream.max_bitrate_bps + : DefaultVideoStreamFactory::kMaxBitratePerStream[i]; + max_bitrate_bps = std::min(bitrate_left_bps, max_bitrate_bps); + + int target_bitrate_bps = stream.target_bitrate_bps > 0 + ? stream.target_bitrate_bps + : max_bitrate_bps; + target_bitrate_bps = std::min(max_bitrate_bps, target_bitrate_bps); + + if (stream.min_bitrate_bps > 0) { + RTC_DCHECK_LE(stream.min_bitrate_bps, target_bitrate_bps); + stream_settings[i].min_bitrate_bps = stream.min_bitrate_bps; + } + if (stream.max_framerate > 0) { + stream_settings[i].max_framerate = stream.max_framerate; + } + if (stream.num_temporal_layers) { + RTC_DCHECK_GE(*stream.num_temporal_layers, 1); + stream_settings[i].num_temporal_layers = stream.num_temporal_layers; + } + if (stream.scale_resolution_down_by >= 1.0) { + stream_settings[i].width = width / stream.scale_resolution_down_by; + stream_settings[i].height = height / stream.scale_resolution_down_by; + } + stream_settings[i].scalability_mode = stream.scalability_mode; + stream_settings[i].target_bitrate_bps = target_bitrate_bps; + stream_settings[i].max_bitrate_bps = max_bitrate_bps; + stream_settings[i].active = + encoder_config.number_of_streams == 1 || stream.active; + + bitrate_left_bps -= stream_settings[i].target_bitrate_bps; + } + + stream_settings[encoder_config.number_of_streams - 1].max_bitrate_bps += + bitrate_left_bps; + stream_settings[0].bitrate_priority = encoder_config.bitrate_priority; + + return stream_settings; +} + +DefaultVideoStreamFactory::DefaultVideoStreamFactory() {} + +std::vector<VideoStream> DefaultVideoStreamFactory::CreateEncoderStreams( + int frame_width, + int frame_height, + const webrtc::VideoEncoderConfig& encoder_config) { + return CreateVideoStreams(frame_width, frame_height, encoder_config); +} + +void FillEncoderConfiguration(VideoCodecType codec_type, + size_t num_streams, + VideoEncoderConfig* configuration) { + RTC_DCHECK_LE(num_streams, DefaultVideoStreamFactory::kMaxNumberOfStreams); + + configuration->codec_type = codec_type; + configuration->number_of_streams = num_streams; + configuration->video_stream_factory = + rtc::make_ref_counted<DefaultVideoStreamFactory>(); + configuration->max_bitrate_bps = 0; + configuration->frame_drop_enabled = true; + configuration->simulcast_layers = std::vector<VideoStream>(num_streams); + for (size_t i = 0; i < num_streams; ++i) { + configuration->max_bitrate_bps += + DefaultVideoStreamFactory::kMaxBitratePerStream[i]; + } +} + +VideoReceiveStreamInterface::Decoder CreateMatchingDecoder( + int payload_type, + const std::string& payload_name) { + VideoReceiveStreamInterface::Decoder decoder; + decoder.payload_type = payload_type; + decoder.video_format = SdpVideoFormat(payload_name); + return decoder; +} + +VideoReceiveStreamInterface::Decoder CreateMatchingDecoder( + const VideoSendStream::Config& config) { + return CreateMatchingDecoder(config.rtp.payload_type, + config.rtp.payload_name); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/encoder_settings.h b/third_party/libwebrtc/test/encoder_settings.h new file mode 100644 index 0000000000..6dbad0fee2 --- /dev/null +++ b/third_party/libwebrtc/test/encoder_settings.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014 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_ENCODER_SETTINGS_H_ +#define TEST_ENCODER_SETTINGS_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "call/video_receive_stream.h" +#include "call/video_send_stream.h" +#include "video/config/video_encoder_config.h" + +namespace webrtc { +namespace test { + +class DefaultVideoStreamFactory + : public VideoEncoderConfig::VideoStreamFactoryInterface { + public: + DefaultVideoStreamFactory(); + + static const size_t kMaxNumberOfStreams = 3; + // Defined as {150000, 450000, 1500000}; + static const int kMaxBitratePerStream[]; + // Defined as {50000, 200000, 700000}; + static const int kDefaultMinBitratePerStream[]; + + private: + std::vector<VideoStream> CreateEncoderStreams( + int frame_width, + int frame_height, + const webrtc::VideoEncoderConfig& encoder_config) override; +}; + +// Creates `encoder_config.number_of_streams` VideoStreams where index +// `encoder_config.number_of_streams -1` have width = `width`, height = +// `height`. The total max bitrate of all VideoStreams is +// `encoder_config.max_bitrate_bps`. +std::vector<VideoStream> CreateVideoStreams( + int width, + int height, + const webrtc::VideoEncoderConfig& encoder_config); + +void FillEncoderConfiguration(VideoCodecType codec_type, + size_t num_streams, + VideoEncoderConfig* configuration); + +VideoReceiveStreamInterface::Decoder CreateMatchingDecoder( + int payload_type, + const std::string& payload_name); + +VideoReceiveStreamInterface::Decoder CreateMatchingDecoder( + const VideoSendStream::Config& config); +} // namespace test +} // namespace webrtc + +#endif // TEST_ENCODER_SETTINGS_H_ diff --git a/third_party/libwebrtc/test/explicit_key_value_config.cc b/third_party/libwebrtc/test/explicit_key_value_config.cc new file mode 100644 index 0000000000..90690c0514 --- /dev/null +++ b/third_party/libwebrtc/test/explicit_key_value_config.cc @@ -0,0 +1,56 @@ +/* + * 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/explicit_key_value_config.h" + +#include "absl/strings/string_view.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +ExplicitKeyValueConfig::ExplicitKeyValueConfig(absl::string_view s) { + std::string::size_type field_start = 0; + while (field_start < s.size()) { + std::string::size_type separator_pos = s.find('/', field_start); + RTC_CHECK_NE(separator_pos, std::string::npos) + << "Missing separator '/' after field trial key."; + RTC_CHECK_GT(separator_pos, field_start) + << "Field trial key cannot be empty."; + std::string key(s.substr(field_start, separator_pos - field_start)); + field_start = separator_pos + 1; + + RTC_CHECK_LT(field_start, s.size()) + << "Missing value after field trial key. String ended."; + separator_pos = s.find('/', field_start); + RTC_CHECK_NE(separator_pos, std::string::npos) + << "Missing terminating '/' in field trial string."; + RTC_CHECK_GT(separator_pos, field_start) + << "Field trial value cannot be empty."; + std::string value(s.substr(field_start, separator_pos - field_start)); + field_start = separator_pos + 1; + + key_value_map_[key] = value; + } + // This check is technically redundant due to earlier checks. + // We nevertheless keep the check to make it clear that the entire + // string has been processed, and without indexing past the end. + RTC_CHECK_EQ(field_start, s.size()); +} + +std::string ExplicitKeyValueConfig::GetValue(absl::string_view key) const { + auto it = key_value_map_.find(key); + if (it != key_value_map_.end()) + return it->second; + return ""; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/explicit_key_value_config.h b/third_party/libwebrtc/test/explicit_key_value_config.h new file mode 100644 index 0000000000..f14a10432c --- /dev/null +++ b/third_party/libwebrtc/test/explicit_key_value_config.h @@ -0,0 +1,39 @@ +/* + * 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_EXPLICIT_KEY_VALUE_CONFIG_H_ +#define TEST_EXPLICIT_KEY_VALUE_CONFIG_H_ + +#include <functional> +#include <map> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/field_trials_registry.h" + +namespace webrtc { +namespace test { + +class ExplicitKeyValueConfig : public FieldTrialsRegistry { + public: + explicit ExplicitKeyValueConfig(absl::string_view s); + + private: + std::string GetValue(absl::string_view key) const override; + + // Unlike std::less<std::string>, std::less<> is transparent and allows + // heterogeneous lookup directly with absl::string_view. + std::map<std::string, std::string, std::less<>> key_value_map_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_EXPLICIT_KEY_VALUE_CONFIG_H_ diff --git a/third_party/libwebrtc/test/fake_decoder.cc b/third_party/libwebrtc/test/fake_decoder.cc new file mode 100644 index 0000000000..53fce37de1 --- /dev/null +++ b/third_party/libwebrtc/test/fake_decoder.cc @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013 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/fake_decoder.h" + +#include <string.h> + +#include <memory> + +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/checks.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { +namespace test { + +FakeDecoder::FakeDecoder() : FakeDecoder(nullptr) {} + +FakeDecoder::FakeDecoder(TaskQueueFactory* task_queue_factory) + : callback_(nullptr), + width_(kDefaultWidth), + height_(kDefaultHeight), + task_queue_factory_(task_queue_factory), + decode_delay_ms_(0) {} + +bool FakeDecoder::Configure(const Settings& settings) { + return true; +} + +int32_t FakeDecoder::Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) { + if (input._encodedWidth > 0 && input._encodedHeight > 0) { + width_ = input._encodedWidth; + height_ = input._encodedHeight; + } + + rtc::scoped_refptr<I420Buffer> buffer = I420Buffer::Create(width_, height_); + I420Buffer::SetBlack(buffer.get()); + VideoFrame frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_rotation(webrtc::kVideoRotation_0) + .set_timestamp_ms(render_time_ms) + .build(); + frame.set_timestamp(input.Timestamp()); + frame.set_ntp_time_ms(input.ntp_time_ms_); + + if (decode_delay_ms_ == 0 || !task_queue_) { + callback_->Decoded(frame); + } else { + task_queue_->PostDelayedHighPrecisionTask( + [frame, this]() { + VideoFrame copy = frame; + callback_->Decoded(copy); + }, + TimeDelta::Millis(decode_delay_ms_)); + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +void FakeDecoder::SetDelayedDecoding(int decode_delay_ms) { + RTC_CHECK(task_queue_factory_); + if (!task_queue_) { + task_queue_ = task_queue_factory_->CreateTaskQueue( + "fake_decoder", TaskQueueFactory::Priority::NORMAL); + } + decode_delay_ms_ = decode_delay_ms; +} + +int32_t FakeDecoder::RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) { + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeDecoder::Release() { + return WEBRTC_VIDEO_CODEC_OK; +} + +const char* FakeDecoder::kImplementationName = "fake_decoder"; +VideoDecoder::DecoderInfo FakeDecoder::GetDecoderInfo() const { + DecoderInfo info; + info.implementation_name = kImplementationName; + info.is_hardware_accelerated = true; + return info; +} +const char* FakeDecoder::ImplementationName() const { + return kImplementationName; +} + +int32_t FakeH264Decoder::Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) { + uint8_t value = 0; + for (size_t i = 0; i < input.size(); ++i) { + uint8_t kStartCode[] = {0, 0, 0, 1}; + if (i < input.size() - sizeof(kStartCode) && + !memcmp(&input.data()[i], kStartCode, sizeof(kStartCode))) { + i += sizeof(kStartCode) + 1; // Skip start code and NAL header. + } + if (input.data()[i] != value) { + RTC_CHECK_EQ(value, input.data()[i]) + << "Bitstream mismatch between sender and receiver."; + return -1; + } + ++value; + } + return FakeDecoder::Decode(input, missing_frames, render_time_ms); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fake_decoder.h b/third_party/libwebrtc/test/fake_decoder.h new file mode 100644 index 0000000000..cea92b49be --- /dev/null +++ b/third_party/libwebrtc/test/fake_decoder.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 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_FAKE_DECODER_H_ +#define TEST_FAKE_DECODER_H_ + +#include <stdint.h> + +#include <memory> + +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/video/encoded_image.h" +#include "api/video_codecs/video_decoder.h" +#include "modules/video_coding/include/video_codec_interface.h" + +namespace webrtc { +namespace test { + +class FakeDecoder : public VideoDecoder { + public: + enum { kDefaultWidth = 320, kDefaultHeight = 180 }; + + FakeDecoder(); + explicit FakeDecoder(TaskQueueFactory* task_queue_factory); + virtual ~FakeDecoder() {} + + bool Configure(const Settings& settings) override; + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) override; + + int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) override; + + int32_t Release() override; + + DecoderInfo GetDecoderInfo() const override; + const char* ImplementationName() const override; + + static const char* kImplementationName; + + void SetDelayedDecoding(int decode_delay_ms); + + private: + DecodedImageCallback* callback_; + int width_; + int height_; + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_; + TaskQueueFactory* task_queue_factory_; + int decode_delay_ms_; +}; + +class FakeH264Decoder : public FakeDecoder { + public: + virtual ~FakeH264Decoder() {} + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) override; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_DECODER_H_ diff --git a/third_party/libwebrtc/test/fake_encoded_frame.cc b/third_party/libwebrtc/test/fake_encoded_frame.cc new file mode 100644 index 0000000000..32fa5d8ccf --- /dev/null +++ b/third_party/libwebrtc/test/fake_encoded_frame.cc @@ -0,0 +1,144 @@ +/* + * 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/fake_encoded_frame.h" + +#include <memory> + +#include "api/video/video_frame_type.h" + +namespace webrtc { + +void PrintTo(const EncodedFrame& frame, + std::ostream* os) /* no-presubmit-check TODO(webrtc:8982) */ { + *os << "EncodedFrame with id=" << frame.Id() << " rtp=" << frame.Timestamp() + << " size=" << frame.size() << " refs=["; + for (size_t ref = 0; ref < frame.num_references; ++ref) { + *os << frame.references[ref] << ","; + } + *os << "]"; +} + +namespace test { + +int64_t FakeEncodedFrame::ReceivedTime() const { + return received_time_; +} + +int64_t FakeEncodedFrame::RenderTime() const { + return _renderTimeMs; +} + +void FakeEncodedFrame::SetReceivedTime(int64_t received_time) { + received_time_ = received_time; +} + +void FakeEncodedFrame::SetPayloadType(int payload_type) { + _payloadType = payload_type; +} + +FakeFrameBuilder& FakeFrameBuilder::Time(uint32_t rtp_timestamp) { + rtp_timestamp_ = rtp_timestamp; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::Id(int64_t frame_id) { + frame_id_ = frame_id; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::AsLast() { + last_spatial_layer_ = true; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::Refs( + const std::vector<int64_t>& references) { + references_ = references; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::PlayoutDelay( + VideoPlayoutDelay playout_delay) { + playout_delay_ = playout_delay; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::SpatialLayer(int spatial_layer) { + spatial_layer_ = spatial_layer; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::ReceivedTime(Timestamp receive_time) { + received_time_ = receive_time; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::Size(size_t size) { + size_ = size; + return *this; +} + +std::unique_ptr<FakeEncodedFrame> FakeFrameBuilder::Build() { + RTC_CHECK_LE(references_.size(), EncodedFrame::kMaxFrameReferences); + + auto frame = std::make_unique<FakeEncodedFrame>(); + frame->is_last_spatial_layer = last_spatial_layer_; + frame->SetEncodedData(EncodedImageBuffer::Create(size_)); + + if (rtp_timestamp_) + frame->SetTimestamp(*rtp_timestamp_); + if (frame_id_) + frame->SetId(*frame_id_); + if (playout_delay_) + frame->SetPlayoutDelay(*playout_delay_); + frame->SetFrameType(references_.empty() ? VideoFrameType::kVideoFrameKey + : VideoFrameType::kVideoFrameDelta); + for (int64_t ref : references_) { + frame->references[frame->num_references] = ref; + frame->num_references++; + } + if (spatial_layer_) + frame->SetSpatialIndex(spatial_layer_); + if (received_time_) + frame->SetReceivedTime(received_time_->ms()); + if (payload_type_) + frame->SetPayloadType(*payload_type_); + if (ntp_time_) + frame->ntp_time_ms_ = ntp_time_->ms(); + if (rotation_) + frame->rotation_ = *rotation_; + if (packet_infos_) + frame->SetPacketInfos(*packet_infos_); + return frame; +} + +FakeFrameBuilder& FakeFrameBuilder::PayloadType(int payload_type) { + payload_type_ = payload_type; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::NtpTime(Timestamp ntp_time) { + ntp_time_ = ntp_time; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::Rotation(VideoRotation rotation) { + rotation_ = rotation; + return *this; +} + +FakeFrameBuilder& FakeFrameBuilder::PacketInfos(RtpPacketInfos packet_infos) { + packet_infos_ = packet_infos; + return *this; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fake_encoded_frame.h b/third_party/libwebrtc/test/fake_encoded_frame.h new file mode 100644 index 0000000000..a5b2aca4a1 --- /dev/null +++ b/third_party/libwebrtc/test/fake_encoded_frame.h @@ -0,0 +1,91 @@ +/* + * 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_FAKE_ENCODED_FRAME_H_ +#define TEST_FAKE_ENCODED_FRAME_H_ + +#include <memory> +#include <ostream> // no-presubmit-check TODO(webrtc:8982) +#include <vector> + +#include "api/rtp_packet_infos.h" +#include "api/video/encoded_frame.h" +#include "api/video/video_rotation.h" +#include "test/gmock.h" + +namespace webrtc { + +// For test printing. +void PrintTo(const EncodedFrame& frame, + std::ostream* os); // no-presubmit-check TODO(webrtc:8982) + +namespace test { + +class FakeEncodedFrame : public EncodedFrame { + public: + // Always 10ms delay and on time. + int64_t ReceivedTime() const override; + int64_t RenderTime() const override; + + // Setters for protected variables. + void SetReceivedTime(int64_t received_time); + void SetPayloadType(int payload_type); + + private: + int64_t received_time_; +}; + +MATCHER_P(WithId, id, "") { + return ::testing::Matches(::testing::Eq(id))(arg.Id()); +} + +MATCHER_P(FrameWithSize, id, "") { + return ::testing::Matches(::testing::Eq(id))(arg.size()); +} + +MATCHER_P(RtpTimestamp, ts, "") { + return ts == arg.Timestamp(); +} + +class FakeFrameBuilder { + public: + FakeFrameBuilder& Time(uint32_t rtp_timestamp); + FakeFrameBuilder& Id(int64_t frame_id); + FakeFrameBuilder& AsLast(); + FakeFrameBuilder& Refs(const std::vector<int64_t>& references); + FakeFrameBuilder& PlayoutDelay(VideoPlayoutDelay playout_delay); + FakeFrameBuilder& SpatialLayer(int spatial_layer); + FakeFrameBuilder& ReceivedTime(Timestamp receive_time); + FakeFrameBuilder& Size(size_t size); + FakeFrameBuilder& PayloadType(int payload_type); + FakeFrameBuilder& NtpTime(Timestamp ntp_time); + FakeFrameBuilder& Rotation(VideoRotation rotation); + FakeFrameBuilder& PacketInfos(RtpPacketInfos packet_infos); + std::unique_ptr<FakeEncodedFrame> Build(); + + private: + absl::optional<uint32_t> rtp_timestamp_; + absl::optional<int64_t> frame_id_; + absl::optional<VideoPlayoutDelay> playout_delay_; + absl::optional<int> spatial_layer_; + absl::optional<Timestamp> received_time_; + absl::optional<int> payload_type_; + absl::optional<Timestamp> ntp_time_; + absl::optional<VideoRotation> rotation_; + absl::optional<RtpPacketInfos> packet_infos_; + std::vector<int64_t> references_; + bool last_spatial_layer_ = false; + size_t size_ = 10; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_ENCODED_FRAME_H_ diff --git a/third_party/libwebrtc/test/fake_encoder.cc b/third_party/libwebrtc/test/fake_encoder.cc new file mode 100644 index 0000000000..bfc72c123d --- /dev/null +++ b/third_party/libwebrtc/test/fake_encoder.cc @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2013 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/fake_encoder.h" + +#include <string.h> + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <string> + +#include "api/video/video_content_type.h" +#include "modules/video_coding/codecs/h264/include/h264_globals.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/sleep.h" + +namespace webrtc { +namespace test { +namespace { +const int kKeyframeSizeFactor = 5; + +// Inverse of proportion of frames assigned to each temporal layer for all +// possible temporal layers numbers. +const int kTemporalLayerRateFactor[4][4] = { + {1, 0, 0, 0}, // 1/1 + {2, 2, 0, 0}, // 1/2 + 1/2 + {4, 4, 2, 0}, // 1/4 + 1/4 + 1/2 + {8, 8, 4, 2}, // 1/8 + 1/8 + 1/4 + 1/2 +}; + +void WriteCounter(unsigned char* payload, uint32_t counter) { + payload[0] = (counter & 0x00FF); + payload[1] = (counter & 0xFF00) >> 8; + payload[2] = (counter & 0xFF0000) >> 16; + payload[3] = (counter & 0xFF000000) >> 24; +} + +} // namespace + +FakeEncoder::FakeEncoder(Clock* clock) + : clock_(clock), + num_initializations_(0), + callback_(nullptr), + max_target_bitrate_kbps_(-1), + pending_keyframe_(true), + counter_(0), + debt_bytes_(0) { + for (bool& used : used_layers_) { + used = false; + } +} + +void FakeEncoder::SetFecControllerOverride( + FecControllerOverride* fec_controller_override) { + // Ignored. +} + +void FakeEncoder::SetMaxBitrate(int max_kbps) { + RTC_DCHECK_GE(max_kbps, -1); // max_kbps == -1 disables it. + MutexLock lock(&mutex_); + max_target_bitrate_kbps_ = max_kbps; + SetRatesLocked(current_rate_settings_); +} + +void FakeEncoder::SetQp(int qp) { + MutexLock lock(&mutex_); + qp_ = qp; +} + +int32_t FakeEncoder::InitEncode(const VideoCodec* config, + const Settings& settings) { + MutexLock lock(&mutex_); + config_ = *config; + ++num_initializations_; + current_rate_settings_.bitrate.SetBitrate(0, 0, config_.startBitrate * 1000); + current_rate_settings_.framerate_fps = config_.maxFramerate; + pending_keyframe_ = true; + last_frame_info_ = FrameInfo(); + return 0; +} + +int32_t FakeEncoder::Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) { + unsigned char max_framerate; + unsigned char num_simulcast_streams; + SimulcastStream simulcast_streams[kMaxSimulcastStreams]; + EncodedImageCallback* callback; + RateControlParameters rates; + bool keyframe; + uint32_t counter; + absl::optional<int> qp; + { + MutexLock lock(&mutex_); + max_framerate = config_.maxFramerate; + num_simulcast_streams = config_.numberOfSimulcastStreams; + for (int i = 0; i < num_simulcast_streams; ++i) { + simulcast_streams[i] = config_.simulcastStream[i]; + } + callback = callback_; + rates = current_rate_settings_; + if (rates.framerate_fps <= 0.0) { + rates.framerate_fps = max_framerate; + } + keyframe = pending_keyframe_; + pending_keyframe_ = false; + counter = counter_++; + qp = qp_; + } + + FrameInfo frame_info = + NextFrame(frame_types, keyframe, num_simulcast_streams, rates.bitrate, + simulcast_streams, static_cast<int>(rates.framerate_fps + 0.5)); + for (uint8_t i = 0; i < frame_info.layers.size(); ++i) { + constexpr int kMinPayLoadLength = 14; + if (frame_info.layers[i].size < kMinPayLoadLength) { + // Drop this temporal layer. + continue; + } + + auto buffer = EncodedImageBuffer::Create(frame_info.layers[i].size); + // Fill the buffer with arbitrary data. Write someting to make Asan happy. + memset(buffer->data(), 9, frame_info.layers[i].size); + // Write a counter to the image to make each frame unique. + WriteCounter(buffer->data() + frame_info.layers[i].size - 4, counter); + + EncodedImage encoded; + encoded.SetEncodedData(buffer); + + encoded.SetTimestamp(input_image.timestamp()); + encoded._frameType = frame_info.keyframe ? VideoFrameType::kVideoFrameKey + : VideoFrameType::kVideoFrameDelta; + encoded._encodedWidth = simulcast_streams[i].width; + encoded._encodedHeight = simulcast_streams[i].height; + if (qp) + encoded.qp_ = *qp; + encoded.SetSpatialIndex(i); + CodecSpecificInfo codec_specific = EncodeHook(encoded, buffer); + + if (callback->OnEncodedImage(encoded, &codec_specific).error != + EncodedImageCallback::Result::OK) { + return -1; + } + } + return 0; +} + +CodecSpecificInfo FakeEncoder::EncodeHook( + EncodedImage& encoded_image, + rtc::scoped_refptr<EncodedImageBuffer> buffer) { + CodecSpecificInfo codec_specific; + codec_specific.codecType = kVideoCodecGeneric; + return codec_specific; +} + +FakeEncoder::FrameInfo FakeEncoder::NextFrame( + const std::vector<VideoFrameType>* frame_types, + bool keyframe, + uint8_t num_simulcast_streams, + const VideoBitrateAllocation& target_bitrate, + SimulcastStream simulcast_streams[kMaxSimulcastStreams], + int framerate) { + FrameInfo frame_info; + frame_info.keyframe = keyframe; + + if (frame_types) { + for (VideoFrameType frame_type : *frame_types) { + if (frame_type == VideoFrameType::kVideoFrameKey) { + frame_info.keyframe = true; + break; + } + } + } + + MutexLock lock(&mutex_); + for (uint8_t i = 0; i < num_simulcast_streams; ++i) { + if (target_bitrate.GetBitrate(i, 0) > 0) { + int temporal_id = last_frame_info_.layers.size() > i + ? ++last_frame_info_.layers[i].temporal_id % + simulcast_streams[i].numberOfTemporalLayers + : 0; + frame_info.layers.emplace_back(0, temporal_id); + } + } + + if (last_frame_info_.layers.size() < frame_info.layers.size()) { + // A new keyframe is needed since a new layer will be added. + frame_info.keyframe = true; + } + + for (uint8_t i = 0; i < frame_info.layers.size(); ++i) { + FrameInfo::SpatialLayer& layer_info = frame_info.layers[i]; + if (frame_info.keyframe) { + layer_info.temporal_id = 0; + size_t avg_frame_size = + (target_bitrate.GetBitrate(i, 0) + 7) * + kTemporalLayerRateFactor[frame_info.layers.size() - 1][i] / + (8 * framerate); + + // The first frame is a key frame and should be larger. + // Store the overshoot bytes and distribute them over the coming frames, + // so that we on average meet the bitrate target. + debt_bytes_ += (kKeyframeSizeFactor - 1) * avg_frame_size; + layer_info.size = kKeyframeSizeFactor * avg_frame_size; + } else { + size_t avg_frame_size = + (target_bitrate.GetBitrate(i, layer_info.temporal_id) + 7) * + kTemporalLayerRateFactor[frame_info.layers.size() - 1][i] / + (8 * framerate); + layer_info.size = avg_frame_size; + if (debt_bytes_ > 0) { + // Pay at most half of the frame size for old debts. + size_t payment_size = std::min(avg_frame_size / 2, debt_bytes_); + debt_bytes_ -= payment_size; + layer_info.size -= payment_size; + } + } + } + last_frame_info_ = frame_info; + return frame_info; +} + +int32_t FakeEncoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + MutexLock lock(&mutex_); + callback_ = callback; + return 0; +} + +int32_t FakeEncoder::Release() { + return 0; +} + +void FakeEncoder::SetRates(const RateControlParameters& parameters) { + MutexLock lock(&mutex_); + SetRatesLocked(parameters); +} + +void FakeEncoder::SetRatesLocked(const RateControlParameters& parameters) { + current_rate_settings_ = parameters; + int allocated_bitrate_kbps = parameters.bitrate.get_sum_kbps(); + + // Scale bitrate allocation to not exceed the given max target bitrate. + if (max_target_bitrate_kbps_ > 0 && + allocated_bitrate_kbps > max_target_bitrate_kbps_) { + for (uint8_t spatial_idx = 0; spatial_idx < kMaxSpatialLayers; + ++spatial_idx) { + for (uint8_t temporal_idx = 0; temporal_idx < kMaxTemporalStreams; + ++temporal_idx) { + if (current_rate_settings_.bitrate.HasBitrate(spatial_idx, + temporal_idx)) { + uint32_t bitrate = current_rate_settings_.bitrate.GetBitrate( + spatial_idx, temporal_idx); + bitrate = static_cast<uint32_t>( + (bitrate * int64_t{max_target_bitrate_kbps_}) / + allocated_bitrate_kbps); + current_rate_settings_.bitrate.SetBitrate(spatial_idx, temporal_idx, + bitrate); + } + } + } + } +} + +const char* FakeEncoder::kImplementationName = "fake_encoder"; +VideoEncoder::EncoderInfo FakeEncoder::GetEncoderInfo() const { + EncoderInfo info; + info.implementation_name = kImplementationName; + info.is_hardware_accelerated = true; + MutexLock lock(&mutex_); + for (int sid = 0; sid < config_.numberOfSimulcastStreams; ++sid) { + int number_of_temporal_layers = + config_.simulcastStream[sid].numberOfTemporalLayers; + info.fps_allocation[sid].clear(); + for (int tid = 0; tid < number_of_temporal_layers; ++tid) { + // {1/4, 1/2, 1} allocation for num layers = 3. + info.fps_allocation[sid].push_back(255 / + (number_of_temporal_layers - tid)); + } + } + return info; +} + +int FakeEncoder::GetConfiguredInputFramerate() const { + MutexLock lock(&mutex_); + return static_cast<int>(current_rate_settings_.framerate_fps + 0.5); +} + +int FakeEncoder::GetNumInitializations() const { + MutexLock lock(&mutex_); + return num_initializations_; +} + +const VideoCodec& FakeEncoder::config() const { + MutexLock lock(&mutex_); + return config_; +} + +FakeH264Encoder::FakeH264Encoder(Clock* clock) + : FakeEncoder(clock), idr_counter_(0) {} + +CodecSpecificInfo FakeH264Encoder::EncodeHook( + EncodedImage& encoded_image, + rtc::scoped_refptr<EncodedImageBuffer> buffer) { + static constexpr std::array<uint8_t, 3> kStartCode = {0, 0, 1}; + const size_t kSpsSize = 8; + const size_t kPpsSize = 11; + const int kIdrFrequency = 10; + int current_idr_counter; + { + MutexLock lock(&local_mutex_); + current_idr_counter = idr_counter_; + ++idr_counter_; + } + for (size_t i = 0; i < encoded_image.size(); ++i) { + buffer->data()[i] = static_cast<uint8_t>(i); + } + + if (current_idr_counter % kIdrFrequency == 0 && + encoded_image.size() > kSpsSize + kPpsSize + 1 + 3 * kStartCode.size()) { + const size_t kSpsNalHeader = 0x67; + const size_t kPpsNalHeader = 0x68; + const size_t kIdrNalHeader = 0x65; + uint8_t* data = buffer->data(); + memcpy(data, kStartCode.data(), kStartCode.size()); + data += kStartCode.size(); + data[0] = kSpsNalHeader; + data += kSpsSize; + + memcpy(data, kStartCode.data(), kStartCode.size()); + data += kStartCode.size(); + data[0] = kPpsNalHeader; + data += kPpsSize; + + memcpy(data, kStartCode.data(), kStartCode.size()); + data += kStartCode.size(); + data[0] = kIdrNalHeader; + } else { + memcpy(buffer->data(), kStartCode.data(), kStartCode.size()); + const size_t kNalHeader = 0x41; + buffer->data()[kStartCode.size()] = kNalHeader; + } + + CodecSpecificInfo codec_specific; + codec_specific.codecType = kVideoCodecH264; + codec_specific.codecSpecific.H264.packetization_mode = + H264PacketizationMode::NonInterleaved; + return codec_specific; +} + +DelayedEncoder::DelayedEncoder(Clock* clock, int delay_ms) + : test::FakeEncoder(clock), delay_ms_(delay_ms) { + // The encoder could be created on a different thread than + // it is being used on. + sequence_checker_.Detach(); +} + +void DelayedEncoder::SetDelay(int delay_ms) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + delay_ms_ = delay_ms; +} + +int32_t DelayedEncoder::Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + SleepMs(delay_ms_); + + return FakeEncoder::Encode(input_image, frame_types); +} + +MultithreadedFakeH264Encoder::MultithreadedFakeH264Encoder( + Clock* clock, + TaskQueueFactory* task_queue_factory) + : test::FakeH264Encoder(clock), + task_queue_factory_(task_queue_factory), + current_queue_(0), + queue1_(nullptr), + queue2_(nullptr) { + // The encoder could be created on a different thread than + // it is being used on. + sequence_checker_.Detach(); +} + +int32_t MultithreadedFakeH264Encoder::InitEncode(const VideoCodec* config, + const Settings& settings) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + queue1_ = task_queue_factory_->CreateTaskQueue( + "Queue 1", TaskQueueFactory::Priority::NORMAL); + queue2_ = task_queue_factory_->CreateTaskQueue( + "Queue 2", TaskQueueFactory::Priority::NORMAL); + + return FakeH264Encoder::InitEncode(config, settings); +} + +int32_t MultithreadedFakeH264Encoder::Encode( + const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + TaskQueueBase* queue = + (current_queue_++ % 2 == 0) ? queue1_.get() : queue2_.get(); + + if (!queue) { + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + queue->PostTask([this, input_image, frame_types = *frame_types] { + EncodeCallback(input_image, &frame_types); + }); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MultithreadedFakeH264Encoder::EncodeCallback( + const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) { + return FakeH264Encoder::Encode(input_image, frame_types); +} + +int32_t MultithreadedFakeH264Encoder::Release() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + queue1_.reset(); + queue2_.reset(); + + return FakeH264Encoder::Release(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fake_encoder.h b/third_party/libwebrtc/test/fake_encoder.h new file mode 100644 index 0000000000..65e03155d7 --- /dev/null +++ b/third_party/libwebrtc/test/fake_encoder.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2013 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_FAKE_ENCODER_H_ +#define TEST_FAKE_ENCODER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "api/fec_controller_override.h" +#include "api/sequence_checker.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/video/encoded_image.h" +#include "api/video/video_bitrate_allocation.h" +#include "api/video/video_frame.h" +#include "api/video_codecs/video_codec.h" +#include "api/video_codecs/video_encoder.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace test { + +class FakeEncoder : public VideoEncoder { + public: + explicit FakeEncoder(Clock* clock); + virtual ~FakeEncoder() = default; + + // Sets max bitrate. Not thread-safe, call before registering the encoder. + void SetMaxBitrate(int max_kbps) RTC_LOCKS_EXCLUDED(mutex_); + void SetQp(int qp) RTC_LOCKS_EXCLUDED(mutex_); + + void SetFecControllerOverride( + FecControllerOverride* fec_controller_override) override; + + int32_t InitEncode(const VideoCodec* config, const Settings& settings) + RTC_LOCKS_EXCLUDED(mutex_) override; + int32_t Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) + RTC_LOCKS_EXCLUDED(mutex_) override; + int32_t RegisterEncodeCompleteCallback(EncodedImageCallback* callback) + RTC_LOCKS_EXCLUDED(mutex_) override; + int32_t Release() override; + void SetRates(const RateControlParameters& parameters) + RTC_LOCKS_EXCLUDED(mutex_) override; + EncoderInfo GetEncoderInfo() const override; + + int GetConfiguredInputFramerate() const RTC_LOCKS_EXCLUDED(mutex_); + int GetNumInitializations() const RTC_LOCKS_EXCLUDED(mutex_); + const VideoCodec& config() const RTC_LOCKS_EXCLUDED(mutex_); + + static const char* kImplementationName; + + protected: + struct FrameInfo { + bool keyframe; + struct SpatialLayer { + SpatialLayer() = default; + SpatialLayer(int size, int temporal_id) + : size(size), temporal_id(temporal_id) {} + // Size of a current frame in the layer. + int size = 0; + // Temporal index of a current frame in the layer. + int temporal_id = 0; + }; + std::vector<SpatialLayer> layers; + }; + + FrameInfo NextFrame(const std::vector<VideoFrameType>* frame_types, + bool keyframe, + uint8_t num_simulcast_streams, + const VideoBitrateAllocation& target_bitrate, + SimulcastStream simulcast_streams[kMaxSimulcastStreams], + int framerate) RTC_LOCKS_EXCLUDED(mutex_); + + // Called before the frame is passed to callback_->OnEncodedImage, to let + // subclasses fill out CodecSpecificInfo, possibly modify `encoded_image` or + // `buffer`. + virtual CodecSpecificInfo EncodeHook( + EncodedImage& encoded_image, + rtc::scoped_refptr<EncodedImageBuffer> buffer); + + void SetRatesLocked(const RateControlParameters& parameters) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + FrameInfo last_frame_info_ RTC_GUARDED_BY(mutex_); + Clock* const clock_; + + VideoCodec config_ RTC_GUARDED_BY(mutex_); + int num_initializations_ RTC_GUARDED_BY(mutex_); + EncodedImageCallback* callback_ RTC_GUARDED_BY(mutex_); + RateControlParameters current_rate_settings_ RTC_GUARDED_BY(mutex_); + int max_target_bitrate_kbps_ RTC_GUARDED_BY(mutex_); + bool pending_keyframe_ RTC_GUARDED_BY(mutex_); + uint32_t counter_ RTC_GUARDED_BY(mutex_); + mutable Mutex mutex_; + bool used_layers_[kMaxSimulcastStreams]; + absl::optional<int> qp_ RTC_GUARDED_BY(mutex_); + + // Current byte debt to be payed over a number of frames. + // The debt is acquired by keyframes overshooting the bitrate target. + size_t debt_bytes_; +}; + +class FakeH264Encoder : public FakeEncoder { + public: + explicit FakeH264Encoder(Clock* clock); + virtual ~FakeH264Encoder() = default; + + private: + CodecSpecificInfo EncodeHook( + EncodedImage& encoded_image, + rtc::scoped_refptr<EncodedImageBuffer> buffer) override; + + int idr_counter_ RTC_GUARDED_BY(local_mutex_); + Mutex local_mutex_; +}; + +class DelayedEncoder : public test::FakeEncoder { + public: + DelayedEncoder(Clock* clock, int delay_ms); + virtual ~DelayedEncoder() = default; + + void SetDelay(int delay_ms); + int32_t Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) override; + + private: + int delay_ms_ RTC_GUARDED_BY(sequence_checker_); + SequenceChecker sequence_checker_; +}; + +// This class implements a multi-threaded fake encoder by posting +// FakeH264Encoder::Encode(.) tasks to `queue1_` and `queue2_`, in an +// alternating fashion. The class itself does not need to be thread safe, +// as it is called from the task queue in VideoStreamEncoder. +class MultithreadedFakeH264Encoder : public test::FakeH264Encoder { + public: + MultithreadedFakeH264Encoder(Clock* clock, + TaskQueueFactory* task_queue_factory); + virtual ~MultithreadedFakeH264Encoder() = default; + + int32_t InitEncode(const VideoCodec* config, + const Settings& settings) override; + + int32_t Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) override; + + int32_t EncodeCallback(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types); + + int32_t Release() override; + + protected: + TaskQueueFactory* const task_queue_factory_; + int current_queue_ RTC_GUARDED_BY(sequence_checker_); + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> queue1_ + RTC_GUARDED_BY(sequence_checker_); + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> queue2_ + RTC_GUARDED_BY(sequence_checker_); + SequenceChecker sequence_checker_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_ENCODER_H_ diff --git a/third_party/libwebrtc/test/fake_texture_frame.cc b/third_party/libwebrtc/test/fake_texture_frame.cc new file mode 100644 index 0000000000..9c17e4c1c0 --- /dev/null +++ b/third_party/libwebrtc/test/fake_texture_frame.cc @@ -0,0 +1,51 @@ +/* + * 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/fake_texture_frame.h" + +#include "api/video/i420_buffer.h" + +namespace webrtc { +namespace test { + +VideoFrame FakeNativeBuffer::CreateFrame(int width, + int height, + uint32_t timestamp, + int64_t render_time_ms, + VideoRotation rotation) { + return VideoFrame::Builder() + .set_video_frame_buffer( + rtc::make_ref_counted<FakeNativeBuffer>(width, height)) + .set_timestamp_rtp(timestamp) + .set_timestamp_ms(render_time_ms) + .set_rotation(rotation) + .build(); +} + +VideoFrameBuffer::Type FakeNativeBuffer::type() const { + return Type::kNative; +} + +int FakeNativeBuffer::width() const { + return width_; +} + +int FakeNativeBuffer::height() const { + return height_; +} + +rtc::scoped_refptr<I420BufferInterface> FakeNativeBuffer::ToI420() { + rtc::scoped_refptr<I420Buffer> buffer = I420Buffer::Create(width_, height_); + I420Buffer::SetBlack(buffer.get()); + return buffer; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fake_texture_frame.h b/third_party/libwebrtc/test/fake_texture_frame.h new file mode 100644 index 0000000000..1b25112e01 --- /dev/null +++ b/third_party/libwebrtc/test/fake_texture_frame.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 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. + */ +#ifndef TEST_FAKE_TEXTURE_FRAME_H_ +#define TEST_FAKE_TEXTURE_FRAME_H_ + +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "common_video/include/video_frame_buffer.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +class FakeNativeBuffer : public VideoFrameBuffer { + public: + static VideoFrame CreateFrame(int width, + int height, + uint32_t timestamp, + int64_t render_time_ms, + VideoRotation rotation); + + FakeNativeBuffer(int width, int height) : width_(width), height_(height) {} + + Type type() const override; + int width() const override; + int height() const override; + + private: + rtc::scoped_refptr<I420BufferInterface> ToI420() override; + + const int width_; + const int height_; +}; + +} // namespace test +} // namespace webrtc +#endif // TEST_FAKE_TEXTURE_FRAME_H_ diff --git a/third_party/libwebrtc/test/fake_videorenderer.h b/third_party/libwebrtc/test/fake_videorenderer.h new file mode 100644 index 0000000000..a2c953ff77 --- /dev/null +++ b/third_party/libwebrtc/test/fake_videorenderer.h @@ -0,0 +1,28 @@ +/* + * 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_FAKE_VIDEORENDERER_H_ +#define TEST_FAKE_VIDEORENDERER_H_ + +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" + +namespace webrtc { +namespace test { + +class FakeVideoRenderer : public rtc::VideoSinkInterface<webrtc::VideoFrame> { + public: + void OnFrame(const webrtc::VideoFrame& frame) override {} +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_VIDEORENDERER_H_ diff --git a/third_party/libwebrtc/test/fake_vp8_decoder.cc b/third_party/libwebrtc/test/fake_vp8_decoder.cc new file mode 100644 index 0000000000..4c2f55a668 --- /dev/null +++ b/third_party/libwebrtc/test/fake_vp8_decoder.cc @@ -0,0 +1,93 @@ +/* + * 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/fake_vp8_decoder.h" + +#include <stddef.h> + +#include "absl/types/optional.h" +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { +namespace test { + +namespace { +// Read width and height from the payload of the frame if it is a key frame the +// same way as the real VP8 decoder. +// FakeEncoder writes width, height and frame type. +void ParseFakeVp8(const unsigned char* data, int* width, int* height) { + bool key_frame = data[0] == 0; + if (key_frame) { + *width = ((data[7] << 8) + data[6]) & 0x3FFF; + *height = ((data[9] << 8) + data[8]) & 0x3FFF; + } +} +} // namespace + +FakeVp8Decoder::FakeVp8Decoder() : callback_(nullptr), width_(0), height_(0) {} + +bool FakeVp8Decoder::Configure(const Settings& settings) { + return true; +} + +int32_t FakeVp8Decoder::Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) { + constexpr size_t kMinPayLoadHeaderLength = 10; + if (input.size() < kMinPayLoadHeaderLength) { + return WEBRTC_VIDEO_CODEC_ERROR; + } + ParseFakeVp8(input.data(), &width_, &height_); + + VideoFrame frame = + VideoFrame::Builder() + .set_video_frame_buffer(I420Buffer::Create(width_, height_)) + .set_rotation(webrtc::kVideoRotation_0) + .set_timestamp_ms(render_time_ms) + .build(); + frame.set_timestamp(input.Timestamp()); + frame.set_ntp_time_ms(input.ntp_time_ms_); + + callback_->Decoded(frame, /*decode_time_ms=*/absl::nullopt, + /*qp=*/absl::nullopt); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeVp8Decoder::RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) { + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeVp8Decoder::Release() { + return WEBRTC_VIDEO_CODEC_OK; +} + +const char* FakeVp8Decoder::kImplementationName = "fake_vp8_decoder"; +VideoDecoder::DecoderInfo FakeVp8Decoder::GetDecoderInfo() const { + DecoderInfo info; + info.implementation_name = kImplementationName; + info.is_hardware_accelerated = false; + return info; +} + +const char* FakeVp8Decoder::ImplementationName() const { + return kImplementationName; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fake_vp8_decoder.h b/third_party/libwebrtc/test/fake_vp8_decoder.h new file mode 100644 index 0000000000..391ee374d4 --- /dev/null +++ b/third_party/libwebrtc/test/fake_vp8_decoder.h @@ -0,0 +1,52 @@ +/* + * 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_FAKE_VP8_DECODER_H_ +#define TEST_FAKE_VP8_DECODER_H_ + +#include <stdint.h> + +#include "api/video/encoded_image.h" +#include "api/video_codecs/video_decoder.h" +#include "modules/video_coding/include/video_codec_interface.h" + +namespace webrtc { +namespace test { + +class FakeVp8Decoder : public VideoDecoder { + public: + FakeVp8Decoder(); + ~FakeVp8Decoder() override {} + + bool Configure(const Settings& settings) override; + + int32_t Decode(const EncodedImage& input, + bool missing_frames, + int64_t render_time_ms) override; + + int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) override; + + int32_t Release() override; + + DecoderInfo GetDecoderInfo() const override; + const char* ImplementationName() const override; + static const char* kImplementationName; + + private: + DecodedImageCallback* callback_; + int width_; + int height_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_VP8_DECODER_H_ diff --git a/third_party/libwebrtc/test/fake_vp8_encoder.cc b/third_party/libwebrtc/test/fake_vp8_encoder.cc new file mode 100644 index 0000000000..625d7a6473 --- /dev/null +++ b/third_party/libwebrtc/test/fake_vp8_encoder.cc @@ -0,0 +1,130 @@ +/* + * 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/fake_vp8_encoder.h" + +#include <algorithm> + +#include "absl/types/optional.h" +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/vp8_temporal_layers.h" +#include "api/video_codecs/vp8_temporal_layers_factory.h" +#include "modules/video_coding/codecs/interface/common_constants.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "modules/video_coding/utility/simulcast_utility.h" + +namespace { + +// Write width and height to the payload the same way as the real encoder does. +// It requires that `payload` has a size of at least kMinPayLoadHeaderLength. +void WriteFakeVp8(unsigned char* payload, + int width, + int height, + bool key_frame) { + payload[0] = key_frame ? 0 : 0x01; + + if (key_frame) { + payload[9] = (height & 0x3F00) >> 8; + payload[8] = (height & 0x00FF); + + payload[7] = (width & 0x3F00) >> 8; + payload[6] = (width & 0x00FF); + } +} +} // namespace + +namespace webrtc { + +namespace test { + +FakeVp8Encoder::FakeVp8Encoder(Clock* clock) : FakeEncoder(clock) { + sequence_checker_.Detach(); +} + +int32_t FakeVp8Encoder::InitEncode(const VideoCodec* config, + const Settings& settings) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + auto result = FakeEncoder::InitEncode(config, settings); + if (result != WEBRTC_VIDEO_CODEC_OK) { + return result; + } + + Vp8TemporalLayersFactory factory; + frame_buffer_controller_ = + factory.Create(*config, settings, &fec_controller_override_); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t FakeVp8Encoder::Release() { + auto result = FakeEncoder::Release(); + sequence_checker_.Detach(); + return result; +} + +CodecSpecificInfo FakeVp8Encoder::PopulateCodecSpecific( + size_t size_bytes, + VideoFrameType frame_type, + int stream_idx, + uint32_t timestamp) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + CodecSpecificInfo codec_specific; + codec_specific.codecType = kVideoCodecVP8; + codec_specific.codecSpecific.VP8.keyIdx = kNoKeyIdx; + codec_specific.codecSpecific.VP8.nonReference = false; + if (size_bytes > 0) { + frame_buffer_controller_->OnEncodeDone( + stream_idx, timestamp, size_bytes, + frame_type == VideoFrameType::kVideoFrameKey, -1, &codec_specific); + } else { + frame_buffer_controller_->OnFrameDropped(stream_idx, timestamp); + } + return codec_specific; +} + +CodecSpecificInfo FakeVp8Encoder::EncodeHook( + EncodedImage& encoded_image, + rtc::scoped_refptr<EncodedImageBuffer> buffer) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + uint8_t stream_idx = encoded_image.SpatialIndex().value_or(0); + frame_buffer_controller_->NextFrameConfig(stream_idx, + encoded_image.Timestamp()); + CodecSpecificInfo codec_specific = + PopulateCodecSpecific(encoded_image.size(), encoded_image._frameType, + stream_idx, encoded_image.Timestamp()); + + // Write width and height to the payload the same way as the real encoder + // does. + WriteFakeVp8(buffer->data(), encoded_image._encodedWidth, + encoded_image._encodedHeight, + encoded_image._frameType == VideoFrameType::kVideoFrameKey); + return codec_specific; +} + +VideoEncoder::EncoderInfo FakeVp8Encoder::GetEncoderInfo() const { + EncoderInfo info; + info.implementation_name = "FakeVp8Encoder"; + MutexLock lock(&mutex_); + for (int sid = 0; sid < config_.numberOfSimulcastStreams; ++sid) { + int number_of_temporal_layers = + config_.simulcastStream[sid].numberOfTemporalLayers; + info.fps_allocation[sid].clear(); + for (int tid = 0; tid < number_of_temporal_layers; ++tid) { + // {1/4, 1/2, 1} allocation for num layers = 3. + info.fps_allocation[sid].push_back(255 / + (number_of_temporal_layers - tid)); + } + } + return info; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fake_vp8_encoder.h b/third_party/libwebrtc/test/fake_vp8_encoder.h new file mode 100644 index 0000000000..6aaf547379 --- /dev/null +++ b/third_party/libwebrtc/test/fake_vp8_encoder.h @@ -0,0 +1,75 @@ +/* + * 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_FAKE_VP8_ENCODER_H_ +#define TEST_FAKE_VP8_ENCODER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "api/fec_controller_override.h" +#include "api/sequence_checker.h" +#include "api/video/encoded_image.h" +#include "api/video_codecs/video_codec.h" +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/vp8_frame_buffer_controller.h" +#include "api/video_codecs/vp8_temporal_layers.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" +#include "test/fake_encoder.h" + +namespace webrtc { +namespace test { + +class FakeVp8Encoder : public FakeEncoder { + public: + explicit FakeVp8Encoder(Clock* clock); + virtual ~FakeVp8Encoder() = default; + + int32_t InitEncode(const VideoCodec* config, + const Settings& settings) override; + + int32_t Release() override; + + EncoderInfo GetEncoderInfo() const override; + + private: + CodecSpecificInfo PopulateCodecSpecific(size_t size_bytes, + VideoFrameType frame_type, + int stream_idx, + uint32_t timestamp); + + CodecSpecificInfo EncodeHook( + EncodedImage& encoded_image, + rtc::scoped_refptr<EncodedImageBuffer> buffer) override; + + SequenceChecker sequence_checker_; + + class FakeFecControllerOverride : public FecControllerOverride { + public: + ~FakeFecControllerOverride() override = default; + + void SetFecAllowed(bool fec_allowed) override {} + }; + + FakeFecControllerOverride fec_controller_override_ + RTC_GUARDED_BY(sequence_checker_); + + std::unique_ptr<Vp8FrameBufferController> frame_buffer_controller_ + RTC_GUARDED_BY(sequence_checker_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FAKE_VP8_ENCODER_H_ diff --git a/third_party/libwebrtc/test/fake_vp8_encoder_unittest.cc b/third_party/libwebrtc/test/fake_vp8_encoder_unittest.cc new file mode 100644 index 0000000000..e79e8e421b --- /dev/null +++ b/third_party/libwebrtc/test/fake_vp8_encoder_unittest.cc @@ -0,0 +1,114 @@ +/* + * 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/fake_vp8_encoder.h" + +#include <memory> +#include <utility> + +#include "api/test/create_simulcast_test_fixture.h" +#include "api/test/simulcast_test_fixture.h" +#include "api/test/video/function_video_decoder_factory.h" +#include "api/test/video/function_video_encoder_factory.h" +#include "modules/video_coding/utility/simulcast_test_fixture_impl.h" +#include "test/fake_vp8_decoder.h" + +namespace webrtc { +namespace test { + +namespace { + +std::unique_ptr<SimulcastTestFixture> CreateSpecificSimulcastTestFixture() { + std::unique_ptr<VideoEncoderFactory> encoder_factory = + std::make_unique<FunctionVideoEncoderFactory>([]() { + return std::make_unique<FakeVp8Encoder>(Clock::GetRealTimeClock()); + }); + std::unique_ptr<VideoDecoderFactory> decoder_factory = + std::make_unique<FunctionVideoDecoderFactory>( + []() { return std::make_unique<FakeVp8Decoder>(); }); + return CreateSimulcastTestFixture(std::move(encoder_factory), + std::move(decoder_factory), + SdpVideoFormat("VP8")); +} +} // namespace + +TEST(TestFakeVp8Codec, TestKeyFrameRequestsOnAllStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestKeyFrameRequestsOnAllStreams(); +} + +TEST(TestFakeVp8Codec, TestPaddingAllStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingAllStreams(); +} + +TEST(TestFakeVp8Codec, TestPaddingTwoStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingTwoStreams(); +} + +TEST(TestFakeVp8Codec, TestPaddingTwoStreamsOneMaxedOut) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingTwoStreamsOneMaxedOut(); +} + +TEST(TestFakeVp8Codec, TestPaddingOneStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingOneStream(); +} + +TEST(TestFakeVp8Codec, TestPaddingOneStreamTwoMaxedOut) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestPaddingOneStreamTwoMaxedOut(); +} + +TEST(TestFakeVp8Codec, TestSendAllStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSendAllStreams(); +} + +TEST(TestFakeVp8Codec, TestDisablingStreams) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestDisablingStreams(); +} + +TEST(TestFakeVp8Codec, TestSwitchingToOneStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSwitchingToOneStream(); +} + +TEST(TestFakeVp8Codec, TestSwitchingToOneOddStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSwitchingToOneOddStream(); +} + +TEST(TestFakeVp8Codec, TestSwitchingToOneSmallStream) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSwitchingToOneSmallStream(); +} + +TEST(TestFakeVp8Codec, TestSpatioTemporalLayers333PatternEncoder) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestSpatioTemporalLayers333PatternEncoder(); +} + +TEST(TestFakeVp8Codec, TestDecodeWidthHeightSet) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestDecodeWidthHeightSet(); +} + +TEST(TestFakeVp8Codec, + TestEncoderInfoForDefaultTemporalLayerProfileHasFpsAllocation) { + auto fixture = CreateSpecificSimulcastTestFixture(); + fixture->TestEncoderInfoForDefaultTemporalLayerProfileHasFpsAllocation(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/field_trial.cc b/third_party/libwebrtc/test/field_trial.cc new file mode 100644 index 0000000000..3d6c6ac617 --- /dev/null +++ b/third_party/libwebrtc/test/field_trial.cc @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014 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/field_trial.h" + +#include <string> + +#include "absl/strings/string_view.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { +namespace test { + +ScopedFieldTrials::ScopedFieldTrials(absl::string_view config) + : current_field_trials_(config), + previous_field_trials_(webrtc::field_trial::GetFieldTrialString()) { + RTC_CHECK(webrtc::field_trial::FieldTrialsStringIsValid( + current_field_trials_.c_str())) + << "Invalid field trials string: " << current_field_trials_; + webrtc::field_trial::InitFieldTrialsFromString(current_field_trials_.c_str()); +} + +ScopedFieldTrials::~ScopedFieldTrials() { + RTC_CHECK( + webrtc::field_trial::FieldTrialsStringIsValid(previous_field_trials_)) + << "Invalid field trials string: " << previous_field_trials_; + webrtc::field_trial::InitFieldTrialsFromString(previous_field_trials_); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/field_trial.h b/third_party/libwebrtc/test/field_trial.h new file mode 100644 index 0000000000..516faa0513 --- /dev/null +++ b/third_party/libwebrtc/test/field_trial.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 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_FIELD_TRIAL_H_ +#define TEST_FIELD_TRIAL_H_ + +#include <string> + +#include "absl/strings/string_view.h" + +namespace webrtc { +namespace test { + +// This class is used to override field-trial configs within specific tests. +// After this class goes out of scope previous field trials will be restored. +class ScopedFieldTrials { + public: + explicit ScopedFieldTrials(absl::string_view config); + ScopedFieldTrials(const ScopedFieldTrials&) = delete; + ScopedFieldTrials& operator=(const ScopedFieldTrials&) = delete; + ~ScopedFieldTrials(); + + private: + std::string current_field_trials_; + const char* previous_field_trials_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FIELD_TRIAL_H_ diff --git a/third_party/libwebrtc/test/frame_forwarder.cc b/third_party/libwebrtc/test/frame_forwarder.cc new file mode 100644 index 0000000000..e89f753bd3 --- /dev/null +++ b/third_party/libwebrtc/test/frame_forwarder.cc @@ -0,0 +1,61 @@ +/* + * 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/frame_forwarder.h" + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +FrameForwarder::FrameForwarder() : sink_(nullptr) {} +FrameForwarder::~FrameForwarder() {} + +void FrameForwarder::IncomingCapturedFrame(const VideoFrame& video_frame) { + MutexLock lock(&mutex_); + if (sink_) + sink_->OnFrame(video_frame); +} + +void FrameForwarder::AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) { + MutexLock lock(&mutex_); + AddOrUpdateSinkLocked(sink, wants); +} + +void FrameForwarder::AddOrUpdateSinkLocked( + rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) { + RTC_DCHECK(!sink_ || sink_ == sink); + sink_ = sink; + sink_wants_ = wants; +} + +void FrameForwarder::RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) { + MutexLock lock(&mutex_); + RTC_DCHECK_EQ(sink, sink_); + sink_ = nullptr; +} + +rtc::VideoSinkWants FrameForwarder::sink_wants() const { + MutexLock lock(&mutex_); + return sink_wants_; +} + +rtc::VideoSinkWants FrameForwarder::sink_wants_locked() const { + return sink_wants_; +} + +bool FrameForwarder::has_sinks() const { + MutexLock lock(&mutex_); + return sink_ != nullptr; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/frame_forwarder.h b/third_party/libwebrtc/test/frame_forwarder.h new file mode 100644 index 0000000000..6dfba9521d --- /dev/null +++ b/third_party/libwebrtc/test/frame_forwarder.h @@ -0,0 +1,54 @@ +/* + * 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_FRAME_FORWARDER_H_ +#define TEST_FRAME_FORWARDER_H_ + +#include "api/video/video_frame.h" +#include "api/video/video_source_interface.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace test { + +// FrameForwarder can be used as an implementation +// of rtc::VideoSourceInterface<VideoFrame> where the caller controls when +// a frame should be forwarded to its sink. +// Currently this implementation only support one sink. +class FrameForwarder : public rtc::VideoSourceInterface<VideoFrame> { + public: + FrameForwarder(); + ~FrameForwarder() override; + // Forwards `video_frame` to the registered `sink_`. + virtual void IncomingCapturedFrame(const VideoFrame& video_frame) + RTC_LOCKS_EXCLUDED(mutex_); + rtc::VideoSinkWants sink_wants() const RTC_LOCKS_EXCLUDED(mutex_); + bool has_sinks() const RTC_LOCKS_EXCLUDED(mutex_); + + protected: + rtc::VideoSinkWants sink_wants_locked() const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) + RTC_LOCKS_EXCLUDED(mutex_) override; + void AddOrUpdateSinkLocked(rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + void RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) + RTC_LOCKS_EXCLUDED(mutex_) override; + + mutable Mutex mutex_; + rtc::VideoSinkInterface<VideoFrame>* sink_ RTC_GUARDED_BY(mutex_); + rtc::VideoSinkWants sink_wants_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FRAME_FORWARDER_H_ diff --git a/third_party/libwebrtc/test/frame_generator.cc b/third_party/libwebrtc/test/frame_generator.cc new file mode 100644 index 0000000000..b6f16a573d --- /dev/null +++ b/third_party/libwebrtc/test/frame_generator.cc @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2013 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/frame_generator.h" + +#include <string.h> + +#include <cstdint> +#include <cstdio> +#include <memory> + +#include "api/video/i010_buffer.h" +#include "api/video/nv12_buffer.h" +#include "api/video/video_rotation.h" +#include "common_video/include/video_frame_buffer.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" +#include "test/frame_utils.h" + +namespace webrtc { +namespace test { + +SquareGenerator::SquareGenerator(int width, + int height, + OutputType type, + int num_squares) + : type_(type) { + ChangeResolution(width, height); + for (int i = 0; i < num_squares; ++i) { + squares_.emplace_back(new Square(width, height, i + 1)); + } +} + +void SquareGenerator::ChangeResolution(size_t width, size_t height) { + MutexLock lock(&mutex_); + width_ = static_cast<int>(width); + height_ = static_cast<int>(height); + RTC_CHECK(width_ > 0); + RTC_CHECK(height_ > 0); +} + +rtc::scoped_refptr<I420Buffer> SquareGenerator::CreateI420Buffer(int width, + int height) { + rtc::scoped_refptr<I420Buffer> buffer(I420Buffer::Create(width, height)); + memset(buffer->MutableDataY(), 127, height * buffer->StrideY()); + memset(buffer->MutableDataU(), 127, + buffer->ChromaHeight() * buffer->StrideU()); + memset(buffer->MutableDataV(), 127, + buffer->ChromaHeight() * buffer->StrideV()); + return buffer; +} + +FrameGeneratorInterface::VideoFrameData SquareGenerator::NextFrame() { + MutexLock lock(&mutex_); + + rtc::scoped_refptr<VideoFrameBuffer> buffer = nullptr; + switch (type_) { + case OutputType::kI420: + case OutputType::kI010: + case OutputType::kNV12: { + buffer = CreateI420Buffer(width_, height_); + break; + } + case OutputType::kI420A: { + rtc::scoped_refptr<I420Buffer> yuv_buffer = + CreateI420Buffer(width_, height_); + rtc::scoped_refptr<I420Buffer> axx_buffer = + CreateI420Buffer(width_, height_); + buffer = WrapI420ABuffer(yuv_buffer->width(), yuv_buffer->height(), + yuv_buffer->DataY(), yuv_buffer->StrideY(), + yuv_buffer->DataU(), yuv_buffer->StrideU(), + yuv_buffer->DataV(), yuv_buffer->StrideV(), + axx_buffer->DataY(), axx_buffer->StrideY(), + // To keep references alive. + [yuv_buffer, axx_buffer] {}); + break; + } + default: + RTC_DCHECK_NOTREACHED() << "The given output format is not supported."; + } + + for (const auto& square : squares_) + square->Draw(buffer); + + if (type_ == OutputType::kI010) { + buffer = I010Buffer::Copy(*buffer->ToI420()); + } else if (type_ == OutputType::kNV12) { + buffer = NV12Buffer::Copy(*buffer->ToI420()); + } + + return VideoFrameData(buffer, absl::nullopt); +} + +SquareGenerator::Square::Square(int width, int height, int seed) + : random_generator_(seed), + x_(random_generator_.Rand(0, width)), + y_(random_generator_.Rand(0, height)), + length_(random_generator_.Rand(1, width > 4 ? width / 4 : 1)), + yuv_y_(random_generator_.Rand(0, 255)), + yuv_u_(random_generator_.Rand(0, 255)), + yuv_v_(random_generator_.Rand(0, 255)), + yuv_a_(random_generator_.Rand(0, 255)) {} + +void SquareGenerator::Square::Draw( + const rtc::scoped_refptr<VideoFrameBuffer>& frame_buffer) { + RTC_DCHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420 || + frame_buffer->type() == VideoFrameBuffer::Type::kI420A); + rtc::scoped_refptr<I420BufferInterface> buffer = frame_buffer->ToI420(); + int length_cap = std::min(buffer->height(), buffer->width()) / 4; + int length = std::min(length_, length_cap); + x_ = (x_ + random_generator_.Rand(0, 4)) % (buffer->width() - length); + y_ = (y_ + random_generator_.Rand(0, 4)) % (buffer->height() - length); + for (int y = y_; y < y_ + length; ++y) { + uint8_t* pos_y = + (const_cast<uint8_t*>(buffer->DataY()) + x_ + y * buffer->StrideY()); + memset(pos_y, yuv_y_, length); + } + + for (int y = y_; y < y_ + length; y = y + 2) { + uint8_t* pos_u = (const_cast<uint8_t*>(buffer->DataU()) + x_ / 2 + + y / 2 * buffer->StrideU()); + memset(pos_u, yuv_u_, length / 2); + uint8_t* pos_v = (const_cast<uint8_t*>(buffer->DataV()) + x_ / 2 + + y / 2 * buffer->StrideV()); + memset(pos_v, yuv_v_, length / 2); + } + + if (frame_buffer->type() == VideoFrameBuffer::Type::kI420) + return; + + // Optionally draw on alpha plane if given. + const webrtc::I420ABufferInterface* yuva_buffer = frame_buffer->GetI420A(); + for (int y = y_; y < y_ + length; ++y) { + uint8_t* pos_y = (const_cast<uint8_t*>(yuva_buffer->DataA()) + x_ + + y * yuva_buffer->StrideA()); + memset(pos_y, yuv_a_, length); + } +} + +YuvFileGenerator::YuvFileGenerator(std::vector<FILE*> files, + size_t width, + size_t height, + int frame_repeat_count) + : file_index_(0), + frame_index_(std::numeric_limits<size_t>::max()), + files_(files), + width_(width), + height_(height), + frame_size_(CalcBufferSize(VideoType::kI420, + static_cast<int>(width_), + static_cast<int>(height_))), + frame_buffer_(new uint8_t[frame_size_]), + frame_display_count_(frame_repeat_count), + current_display_count_(0) { + RTC_DCHECK_GT(width, 0); + RTC_DCHECK_GT(height, 0); + RTC_DCHECK_GT(frame_repeat_count, 0); +} + +YuvFileGenerator::~YuvFileGenerator() { + for (FILE* file : files_) + fclose(file); +} + +FrameGeneratorInterface::VideoFrameData YuvFileGenerator::NextFrame() { + // Empty update by default. + VideoFrame::UpdateRect update_rect{0, 0, 0, 0}; + if (current_display_count_ == 0) { + const bool got_new_frame = ReadNextFrame(); + // Full update on a new frame from file. + if (got_new_frame) { + update_rect = VideoFrame::UpdateRect{0, 0, static_cast<int>(width_), + static_cast<int>(height_)}; + } + } + if (++current_display_count_ >= frame_display_count_) + current_display_count_ = 0; + + return VideoFrameData(last_read_buffer_, update_rect); +} + +bool YuvFileGenerator::ReadNextFrame() { + size_t prev_frame_index = frame_index_; + size_t prev_file_index = file_index_; + last_read_buffer_ = test::ReadI420Buffer( + static_cast<int>(width_), static_cast<int>(height_), files_[file_index_]); + ++frame_index_; + if (!last_read_buffer_) { + // No more frames to read in this file, rewind and move to next file. + rewind(files_[file_index_]); + + frame_index_ = 0; + file_index_ = (file_index_ + 1) % files_.size(); + last_read_buffer_ = + test::ReadI420Buffer(static_cast<int>(width_), + static_cast<int>(height_), files_[file_index_]); + RTC_CHECK(last_read_buffer_); + } + return frame_index_ != prev_frame_index || file_index_ != prev_file_index; +} + +NV12FileGenerator::NV12FileGenerator(std::vector<FILE*> files, + size_t width, + size_t height, + int frame_repeat_count) + : file_index_(0), + frame_index_(std::numeric_limits<size_t>::max()), + files_(files), + width_(width), + height_(height), + frame_size_(CalcBufferSize(VideoType::kNV12, + static_cast<int>(width_), + static_cast<int>(height_))), + frame_buffer_(new uint8_t[frame_size_]), + frame_display_count_(frame_repeat_count), + current_display_count_(0) { + RTC_DCHECK_GT(width, 0); + RTC_DCHECK_GT(height, 0); + RTC_DCHECK_GT(frame_repeat_count, 0); +} + +NV12FileGenerator::~NV12FileGenerator() { + for (FILE* file : files_) + fclose(file); +} + +FrameGeneratorInterface::VideoFrameData NV12FileGenerator::NextFrame() { + // Empty update by default. + VideoFrame::UpdateRect update_rect{0, 0, 0, 0}; + if (current_display_count_ == 0) { + const bool got_new_frame = ReadNextFrame(); + // Full update on a new frame from file. + if (got_new_frame) { + update_rect = VideoFrame::UpdateRect{0, 0, static_cast<int>(width_), + static_cast<int>(height_)}; + } + } + if (++current_display_count_ >= frame_display_count_) + current_display_count_ = 0; + + return VideoFrameData(last_read_buffer_, update_rect); +} + +bool NV12FileGenerator::ReadNextFrame() { + size_t prev_frame_index = frame_index_; + size_t prev_file_index = file_index_; + last_read_buffer_ = test::ReadNV12Buffer( + static_cast<int>(width_), static_cast<int>(height_), files_[file_index_]); + ++frame_index_; + if (!last_read_buffer_) { + // No more frames to read in this file, rewind and move to next file. + rewind(files_[file_index_]); + + frame_index_ = 0; + file_index_ = (file_index_ + 1) % files_.size(); + last_read_buffer_ = + test::ReadNV12Buffer(static_cast<int>(width_), + static_cast<int>(height_), files_[file_index_]); + RTC_CHECK(last_read_buffer_); + } + return frame_index_ != prev_frame_index || file_index_ != prev_file_index; +} + +SlideGenerator::SlideGenerator(int width, int height, int frame_repeat_count) + : width_(width), + height_(height), + frame_display_count_(frame_repeat_count), + current_display_count_(0), + random_generator_(1234) { + RTC_DCHECK_GT(width, 0); + RTC_DCHECK_GT(height, 0); + RTC_DCHECK_GT(frame_repeat_count, 0); +} + +FrameGeneratorInterface::VideoFrameData SlideGenerator::NextFrame() { + if (current_display_count_ == 0) + GenerateNewFrame(); + if (++current_display_count_ >= frame_display_count_) + current_display_count_ = 0; + + return VideoFrameData(buffer_, absl::nullopt); +} + +void SlideGenerator::GenerateNewFrame() { + // The squares should have a varying order of magnitude in order + // to simulate variation in the slides' complexity. + const int kSquareNum = 1 << (4 + (random_generator_.Rand(0, 3) * 2)); + + buffer_ = I420Buffer::Create(width_, height_); + memset(buffer_->MutableDataY(), 127, height_ * buffer_->StrideY()); + memset(buffer_->MutableDataU(), 127, + buffer_->ChromaHeight() * buffer_->StrideU()); + memset(buffer_->MutableDataV(), 127, + buffer_->ChromaHeight() * buffer_->StrideV()); + + for (int i = 0; i < kSquareNum; ++i) { + int length = random_generator_.Rand(1, width_ > 4 ? width_ / 4 : 1); + // Limit the length of later squares so that they don't overwrite the + // previous ones too much. + length = (length * (kSquareNum - i)) / kSquareNum; + + int x = random_generator_.Rand(0, width_ - length); + int y = random_generator_.Rand(0, height_ - length); + uint8_t yuv_y = random_generator_.Rand(0, 255); + uint8_t yuv_u = random_generator_.Rand(0, 255); + uint8_t yuv_v = random_generator_.Rand(0, 255); + + for (int yy = y; yy < y + length; ++yy) { + uint8_t* pos_y = (buffer_->MutableDataY() + x + yy * buffer_->StrideY()); + memset(pos_y, yuv_y, length); + } + for (int yy = y; yy < y + length; yy += 2) { + uint8_t* pos_u = + (buffer_->MutableDataU() + x / 2 + yy / 2 * buffer_->StrideU()); + memset(pos_u, yuv_u, length / 2); + uint8_t* pos_v = + (buffer_->MutableDataV() + x / 2 + yy / 2 * buffer_->StrideV()); + memset(pos_v, yuv_v, length / 2); + } + } +} + +ScrollingImageFrameGenerator::ScrollingImageFrameGenerator( + Clock* clock, + const std::vector<FILE*>& files, + size_t source_width, + size_t source_height, + size_t target_width, + size_t target_height, + int64_t scroll_time_ms, + int64_t pause_time_ms) + : clock_(clock), + start_time_(clock->TimeInMilliseconds()), + scroll_time_(scroll_time_ms), + pause_time_(pause_time_ms), + num_frames_(files.size()), + target_width_(static_cast<int>(target_width)), + target_height_(static_cast<int>(target_height)), + current_frame_num_(num_frames_ - 1), + prev_frame_not_scrolled_(false), + current_source_frame_(nullptr, absl::nullopt), + current_frame_(nullptr, absl::nullopt), + file_generator_(files, source_width, source_height, 1) { + RTC_DCHECK(clock_ != nullptr); + RTC_DCHECK_GT(num_frames_, 0); + RTC_DCHECK_GE(source_height, target_height); + RTC_DCHECK_GE(source_width, target_width); + RTC_DCHECK_GE(scroll_time_ms, 0); + RTC_DCHECK_GE(pause_time_ms, 0); + RTC_DCHECK_GT(scroll_time_ms + pause_time_ms, 0); +} + +FrameGeneratorInterface::VideoFrameData +ScrollingImageFrameGenerator::NextFrame() { + const int64_t kFrameDisplayTime = scroll_time_ + pause_time_; + const int64_t now = clock_->TimeInMilliseconds(); + int64_t ms_since_start = now - start_time_; + + size_t frame_num = (ms_since_start / kFrameDisplayTime) % num_frames_; + UpdateSourceFrame(frame_num); + + bool cur_frame_not_scrolled; + + double scroll_factor; + int64_t time_into_frame = ms_since_start % kFrameDisplayTime; + if (time_into_frame < scroll_time_) { + scroll_factor = static_cast<double>(time_into_frame) / scroll_time_; + cur_frame_not_scrolled = false; + } else { + scroll_factor = 1.0; + cur_frame_not_scrolled = true; + } + CropSourceToScrolledImage(scroll_factor); + + bool same_scroll_position = + prev_frame_not_scrolled_ && cur_frame_not_scrolled; + if (!same_scroll_position) { + // If scrolling is not finished yet, force full frame update. + current_frame_.update_rect = + VideoFrame::UpdateRect{0, 0, target_width_, target_height_}; + } + prev_frame_not_scrolled_ = cur_frame_not_scrolled; + + return current_frame_; +} + +void ScrollingImageFrameGenerator::UpdateSourceFrame(size_t frame_num) { + VideoFrame::UpdateRect acc_update{0, 0, 0, 0}; + while (current_frame_num_ != frame_num) { + current_source_frame_ = file_generator_.NextFrame(); + if (current_source_frame_.update_rect) { + acc_update.Union(*current_source_frame_.update_rect); + } + current_frame_num_ = (current_frame_num_ + 1) % num_frames_; + } + current_source_frame_.update_rect = acc_update; +} + +void ScrollingImageFrameGenerator::CropSourceToScrolledImage( + double scroll_factor) { + int scroll_margin_x = current_source_frame_.buffer->width() - target_width_; + int pixels_scrolled_x = + static_cast<int>(scroll_margin_x * scroll_factor + 0.5); + int scroll_margin_y = current_source_frame_.buffer->height() - target_height_; + int pixels_scrolled_y = + static_cast<int>(scroll_margin_y * scroll_factor + 0.5); + + rtc::scoped_refptr<I420BufferInterface> i420_buffer = + current_source_frame_.buffer->ToI420(); + int offset_y = + (i420_buffer->StrideY() * pixels_scrolled_y) + pixels_scrolled_x; + int offset_u = (i420_buffer->StrideU() * (pixels_scrolled_y / 2)) + + (pixels_scrolled_x / 2); + int offset_v = (i420_buffer->StrideV() * (pixels_scrolled_y / 2)) + + (pixels_scrolled_x / 2); + + VideoFrame::UpdateRect update_rect = + current_source_frame_.update_rect->IsEmpty() + ? VideoFrame::UpdateRect{0, 0, 0, 0} + : VideoFrame::UpdateRect{0, 0, target_width_, target_height_}; + current_frame_ = VideoFrameData( + WrapI420Buffer(target_width_, target_height_, + &i420_buffer->DataY()[offset_y], i420_buffer->StrideY(), + &i420_buffer->DataU()[offset_u], i420_buffer->StrideU(), + &i420_buffer->DataV()[offset_v], i420_buffer->StrideV(), + // To keep reference alive. + [i420_buffer] {}), + update_rect); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/frame_generator.h b/third_party/libwebrtc/test/frame_generator.h new file mode 100644 index 0000000000..9a8f08cea6 --- /dev/null +++ b/third_party/libwebrtc/test/frame_generator.h @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2013 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_FRAME_GENERATOR_H_ +#define TEST_FRAME_GENERATOR_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "api/scoped_refptr.h" +#include "api/test/frame_generator_interface.h" +#include "api/video/i420_buffer.h" +#include "api/video/nv12_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_source_interface.h" +#include "rtc_base/logging.h" +#include "rtc_base/random.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace test { + +// SquareGenerator is a FrameGenerator that draws a given amount of randomly +// sized and colored squares. Between each new generated frame, the squares +// are moved slightly towards the lower right corner. +class SquareGenerator : public FrameGeneratorInterface { + public: + SquareGenerator(int width, int height, OutputType type, int num_squares); + + void ChangeResolution(size_t width, size_t height) override; + VideoFrameData NextFrame() override; + + private: + rtc::scoped_refptr<I420Buffer> CreateI420Buffer(int width, int height); + + class Square { + public: + Square(int width, int height, int seed); + + void Draw(const rtc::scoped_refptr<VideoFrameBuffer>& frame_buffer); + + private: + Random random_generator_; + int x_; + int y_; + const int length_; + const uint8_t yuv_y_; + const uint8_t yuv_u_; + const uint8_t yuv_v_; + const uint8_t yuv_a_; + }; + + Mutex mutex_; + const OutputType type_; + int width_ RTC_GUARDED_BY(&mutex_); + int height_ RTC_GUARDED_BY(&mutex_); + std::vector<std::unique_ptr<Square>> squares_ RTC_GUARDED_BY(&mutex_); +}; + +class YuvFileGenerator : public FrameGeneratorInterface { + public: + YuvFileGenerator(std::vector<FILE*> files, + size_t width, + size_t height, + int frame_repeat_count); + + ~YuvFileGenerator(); + + VideoFrameData NextFrame() override; + void ChangeResolution(size_t width, size_t height) override { + RTC_LOG(LS_WARNING) << "YuvFileGenerator::ChangeResolution not implemented"; + } + + private: + // Returns true if the new frame was loaded. + // False only in case of a single file with a single frame in it. + bool ReadNextFrame(); + + size_t file_index_; + size_t frame_index_; + const std::vector<FILE*> files_; + const size_t width_; + const size_t height_; + const size_t frame_size_; + const std::unique_ptr<uint8_t[]> frame_buffer_; + const int frame_display_count_; + int current_display_count_; + rtc::scoped_refptr<I420Buffer> last_read_buffer_; +}; + +class NV12FileGenerator : public FrameGeneratorInterface { + public: + NV12FileGenerator(std::vector<FILE*> files, + size_t width, + size_t height, + int frame_repeat_count); + + ~NV12FileGenerator(); + + VideoFrameData NextFrame() override; + void ChangeResolution(size_t width, size_t height) override { + RTC_LOG(LS_WARNING) + << "NV12FileGenerator::ChangeResolution not implemented"; + } + + private: + // Returns true if the new frame was loaded. + // False only in case of a single file with a single frame in it. + bool ReadNextFrame(); + + size_t file_index_; + size_t frame_index_; + const std::vector<FILE*> files_; + const size_t width_; + const size_t height_; + const size_t frame_size_; + const std::unique_ptr<uint8_t[]> frame_buffer_; + const int frame_display_count_; + int current_display_count_; + rtc::scoped_refptr<NV12Buffer> last_read_buffer_; +}; + +// SlideGenerator works similarly to YuvFileGenerator but it fills the frames +// with randomly sized and colored squares instead of reading their content +// from files. +class SlideGenerator : public FrameGeneratorInterface { + public: + SlideGenerator(int width, int height, int frame_repeat_count); + + VideoFrameData NextFrame() override; + void ChangeResolution(size_t width, size_t height) override { + RTC_LOG(LS_WARNING) << "SlideGenerator::ChangeResolution not implemented"; + } + + private: + // Generates some randomly sized and colored squares scattered + // over the frame. + void GenerateNewFrame(); + + const int width_; + const int height_; + const int frame_display_count_; + int current_display_count_; + Random random_generator_; + rtc::scoped_refptr<I420Buffer> buffer_; +}; + +class ScrollingImageFrameGenerator : public FrameGeneratorInterface { + public: + ScrollingImageFrameGenerator(Clock* clock, + const std::vector<FILE*>& files, + size_t source_width, + size_t source_height, + size_t target_width, + size_t target_height, + int64_t scroll_time_ms, + int64_t pause_time_ms); + ~ScrollingImageFrameGenerator() override = default; + + VideoFrameData NextFrame() override; + void ChangeResolution(size_t width, size_t height) override { + RTC_LOG(LS_WARNING) + << "ScrollingImageFrameGenerator::ChangeResolution not implemented"; + } + + private: + void UpdateSourceFrame(size_t frame_num); + void CropSourceToScrolledImage(double scroll_factor); + + Clock* const clock_; + const int64_t start_time_; + const int64_t scroll_time_; + const int64_t pause_time_; + const size_t num_frames_; + const int target_width_; + const int target_height_; + + size_t current_frame_num_; + bool prev_frame_not_scrolled_; + VideoFrameData current_source_frame_; + VideoFrameData current_frame_; + YuvFileGenerator file_generator_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FRAME_GENERATOR_H_ diff --git a/third_party/libwebrtc/test/frame_generator_capturer.cc b/third_party/libwebrtc/test/frame_generator_capturer.cc new file mode 100644 index 0000000000..c69fca0965 --- /dev/null +++ b/third_party/libwebrtc/test/frame_generator_capturer.cc @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2013 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/frame_generator_capturer.h" + +#include <algorithm> +#include <cmath> +#include <limits> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/strings/match.h" +#include "api/test/create_frame_generator.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace test { +namespace { +std::string TransformFilePath(std::string path) { + static const std::string resource_prefix = "res://"; + int ext_pos = path.rfind('.'); + if (ext_pos < 0) { + return test::ResourcePath(path, "yuv"); + } else if (absl::StartsWith(path, resource_prefix)) { + std::string name = path.substr(resource_prefix.length(), ext_pos); + std::string ext = path.substr(ext_pos, path.size()); + return test::ResourcePath(name, ext); + } + return path; +} +} // namespace + +FrameGeneratorCapturer::FrameGeneratorCapturer( + Clock* clock, + std::unique_ptr<FrameGeneratorInterface> frame_generator, + int target_fps, + TaskQueueFactory& task_queue_factory) + : clock_(clock), + sending_(true), + sink_wants_observer_(nullptr), + frame_generator_(std::move(frame_generator)), + source_fps_(target_fps), + target_capture_fps_(target_fps), + first_frame_capture_time_(-1), + task_queue_(task_queue_factory.CreateTaskQueue( + "FrameGenCapQ", + TaskQueueFactory::Priority::HIGH)) { + RTC_DCHECK(frame_generator_); + RTC_DCHECK_GT(target_fps, 0); +} + +FrameGeneratorCapturer::~FrameGeneratorCapturer() { + Stop(); +} + +std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::SquaresVideo config) { + return std::make_unique<FrameGeneratorCapturer>( + clock, + CreateSquareFrameGenerator(config.width, config.height, + config.pixel_format, config.num_squares), + config.framerate, task_queue_factory); +} +std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::SquareSlides config) { + return std::make_unique<FrameGeneratorCapturer>( + clock, + CreateSlideFrameGenerator( + config.width, config.height, + /*frame_repeat_count*/ config.change_interval.seconds<double>() * + config.framerate), + config.framerate, task_queue_factory); +} +std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::VideoFile config) { + RTC_CHECK(config.width && config.height); + return std::make_unique<FrameGeneratorCapturer>( + clock, + CreateFromYuvFileFrameGenerator({TransformFilePath(config.name)}, + config.width, config.height, + /*frame_repeat_count*/ 1), + config.framerate, task_queue_factory); +} + +std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::ImageSlides config) { + std::unique_ptr<FrameGeneratorInterface> slides_generator; + std::vector<std::string> paths = config.paths; + for (std::string& path : paths) + path = TransformFilePath(path); + + if (config.crop.width || config.crop.height) { + TimeDelta pause_duration = + config.change_interval - config.crop.scroll_duration; + RTC_CHECK_GE(pause_duration, TimeDelta::Zero()); + int crop_width = config.crop.width.value_or(config.width); + int crop_height = config.crop.height.value_or(config.height); + RTC_CHECK_LE(crop_width, config.width); + RTC_CHECK_LE(crop_height, config.height); + slides_generator = CreateScrollingInputFromYuvFilesFrameGenerator( + clock, paths, config.width, config.height, crop_width, crop_height, + config.crop.scroll_duration.ms(), pause_duration.ms()); + } else { + slides_generator = CreateFromYuvFileFrameGenerator( + paths, config.width, config.height, + /*frame_repeat_count*/ config.change_interval.seconds<double>() * + config.framerate); + } + return std::make_unique<FrameGeneratorCapturer>( + clock, std::move(slides_generator), config.framerate, task_queue_factory); +} + +std::unique_ptr<FrameGeneratorCapturer> FrameGeneratorCapturer::Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + const FrameGeneratorCapturerConfig& config) { + if (config.video_file) { + return Create(clock, task_queue_factory, *config.video_file); + } else if (config.image_slides) { + return Create(clock, task_queue_factory, *config.image_slides); + } else if (config.squares_slides) { + return Create(clock, task_queue_factory, *config.squares_slides); + } else { + return Create(clock, task_queue_factory, + config.squares_video.value_or( + FrameGeneratorCapturerConfig::SquaresVideo())); + } +} + +void FrameGeneratorCapturer::SetFakeRotation(VideoRotation rotation) { + MutexLock lock(&lock_); + fake_rotation_ = rotation; +} + +void FrameGeneratorCapturer::SetFakeColorSpace( + absl::optional<ColorSpace> color_space) { + MutexLock lock(&lock_); + fake_color_space_ = color_space; +} + +bool FrameGeneratorCapturer::Init() { + // This check is added because frame_generator_ might be file based and should + // not crash because a file moved. + if (frame_generator_.get() == nullptr) + return false; + + frame_task_ = RepeatingTaskHandle::DelayedStart( + task_queue_.Get(), + TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(), + [this] { + InsertFrame(); + return TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(); + }, + TaskQueueBase::DelayPrecision::kHigh); + return true; +} + +void FrameGeneratorCapturer::InsertFrame() { + absl::optional<Resolution> resolution; + + { + MutexLock lock(&lock_); + if (sending_) { + FrameGeneratorInterface::VideoFrameData frame_data = + frame_generator_->NextFrame(); + // TODO(srte): Use more advanced frame rate control to allow arbritrary + // fractions. + int decimation = + std::round(static_cast<double>(source_fps_) / target_capture_fps_); + for (int i = 1; i < decimation; ++i) + frame_data = frame_generator_->NextFrame(); + + VideoFrame frame = + VideoFrame::Builder() + .set_video_frame_buffer(frame_data.buffer) + .set_rotation(fake_rotation_) + .set_timestamp_us(clock_->TimeInMicroseconds()) + .set_ntp_time_ms(clock_->CurrentNtpInMilliseconds()) + .set_update_rect(frame_data.update_rect) + .set_color_space(fake_color_space_) + .build(); + if (first_frame_capture_time_ == -1) { + first_frame_capture_time_ = frame.ntp_time_ms(); + } + + resolution = Resolution{frame.width(), frame.height()}; + + TestVideoCapturer::OnFrame(frame); + } + } + + if (resolution) { + MutexLock lock(&stats_lock_); + source_resolution_ = resolution; + } +} + +absl::optional<FrameGeneratorCapturer::Resolution> +FrameGeneratorCapturer::GetResolution() { + MutexLock lock(&stats_lock_); + return source_resolution_; +} + +void FrameGeneratorCapturer::Start() { + { + MutexLock lock(&lock_); + sending_ = true; + } + if (!frame_task_.Running()) { + frame_task_ = RepeatingTaskHandle::Start( + task_queue_.Get(), + [this] { + InsertFrame(); + return TimeDelta::Seconds(1) / GetCurrentConfiguredFramerate(); + }, + TaskQueueBase::DelayPrecision::kHigh); + } +} + +void FrameGeneratorCapturer::Stop() { + MutexLock lock(&lock_); + sending_ = false; +} + +void FrameGeneratorCapturer::ChangeResolution(size_t width, size_t height) { + MutexLock lock(&lock_); + frame_generator_->ChangeResolution(width, height); +} + +void FrameGeneratorCapturer::ChangeFramerate(int target_framerate) { + MutexLock lock(&lock_); + RTC_CHECK(target_capture_fps_ > 0); + if (target_framerate > source_fps_) + RTC_LOG(LS_WARNING) << "Target framerate clamped from " << target_framerate + << " to " << source_fps_; + if (source_fps_ % target_capture_fps_ != 0) { + int decimation = + std::round(static_cast<double>(source_fps_) / target_capture_fps_); + int effective_rate = target_capture_fps_ / decimation; + RTC_LOG(LS_WARNING) << "Target framerate, " << target_framerate + << ", is an uneven fraction of the source rate, " + << source_fps_ + << ". The framerate will be :" << effective_rate; + } + target_capture_fps_ = std::min(source_fps_, target_framerate); +} + +void FrameGeneratorCapturer::OnOutputFormatRequest( + int width, + int height, + const absl::optional<int>& max_fps) { + TestVideoCapturer::OnOutputFormatRequest(width, height, max_fps); +} + +void FrameGeneratorCapturer::SetSinkWantsObserver(SinkWantsObserver* observer) { + MutexLock lock(&lock_); + RTC_DCHECK(!sink_wants_observer_); + sink_wants_observer_ = observer; +} + +void FrameGeneratorCapturer::AddOrUpdateSink( + rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) { + TestVideoCapturer::AddOrUpdateSink(sink, wants); + MutexLock lock(&lock_); + if (sink_wants_observer_) { + // Tests need to observe unmodified sink wants. + sink_wants_observer_->OnSinkWantsChanged(sink, wants); + } + UpdateFps(GetSinkWants().max_framerate_fps); +} + +void FrameGeneratorCapturer::RemoveSink( + rtc::VideoSinkInterface<VideoFrame>* sink) { + TestVideoCapturer::RemoveSink(sink); + + MutexLock lock(&lock_); + UpdateFps(GetSinkWants().max_framerate_fps); +} + +void FrameGeneratorCapturer::UpdateFps(int max_fps) { + if (max_fps < target_capture_fps_) { + wanted_fps_.emplace(max_fps); + } else { + wanted_fps_.reset(); + } +} + +void FrameGeneratorCapturer::ForceFrame() { + // One-time non-repeating task, + task_queue_.PostTask([this] { InsertFrame(); }); +} + +int FrameGeneratorCapturer::GetCurrentConfiguredFramerate() { + MutexLock lock(&lock_); + if (wanted_fps_ && *wanted_fps_ < target_capture_fps_) + return *wanted_fps_; + return target_capture_fps_; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/frame_generator_capturer.h b/third_party/libwebrtc/test/frame_generator_capturer.h new file mode 100644 index 0000000000..e310e40129 --- /dev/null +++ b/third_party/libwebrtc/test/frame_generator_capturer.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2013 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_FRAME_GENERATOR_CAPTURER_H_ +#define TEST_FRAME_GENERATOR_CAPTURER_H_ + +#include <memory> +#include <string> + +#include "api/task_queue/task_queue_factory.h" +#include "api/test/frame_generator_interface.h" +#include "api/video/video_frame.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "system_wrappers/include/clock.h" +#include "test/test_video_capturer.h" + +namespace webrtc { + +namespace test { +namespace frame_gen_cap_impl { +template <typename T> +class AutoOpt : public absl::optional<T> { + public: + using absl::optional<T>::optional; + T* operator->() { + if (!absl::optional<T>::has_value()) + this->emplace(T()); + return absl::optional<T>::operator->(); + } +}; +} // namespace frame_gen_cap_impl +struct FrameGeneratorCapturerConfig { + struct SquaresVideo { + int framerate = 30; + FrameGeneratorInterface::OutputType pixel_format = + FrameGeneratorInterface::OutputType::kI420; + int width = 320; + int height = 180; + int num_squares = 10; + }; + + struct SquareSlides { + int framerate = 30; + TimeDelta change_interval = TimeDelta::Seconds(10); + int width = 1600; + int height = 1200; + }; + + struct VideoFile { + int framerate = 30; + std::string name; + // Must be set to width and height of the source video file. + int width = 0; + int height = 0; + }; + + struct ImageSlides { + int framerate = 30; + TimeDelta change_interval = TimeDelta::Seconds(10); + struct Crop { + TimeDelta scroll_duration = TimeDelta::Seconds(0); + absl::optional<int> width; + absl::optional<int> height; + } crop; + int width = 1850; + int height = 1110; + std::vector<std::string> paths = { + "web_screenshot_1850_1110", + "presentation_1850_1110", + "photo_1850_1110", + "difficult_photo_1850_1110", + }; + }; + + frame_gen_cap_impl::AutoOpt<SquaresVideo> squares_video; + frame_gen_cap_impl::AutoOpt<SquareSlides> squares_slides; + frame_gen_cap_impl::AutoOpt<VideoFile> video_file; + frame_gen_cap_impl::AutoOpt<ImageSlides> image_slides; +}; + +class FrameGeneratorCapturer : public TestVideoCapturer { + public: + class SinkWantsObserver { + public: + // OnSinkWantsChanged is called when FrameGeneratorCapturer::AddOrUpdateSink + // is called. + virtual void OnSinkWantsChanged(rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) = 0; + + protected: + virtual ~SinkWantsObserver() {} + }; + + FrameGeneratorCapturer( + Clock* clock, + std::unique_ptr<FrameGeneratorInterface> frame_generator, + int target_fps, + TaskQueueFactory& task_queue_factory); + virtual ~FrameGeneratorCapturer(); + + static std::unique_ptr<FrameGeneratorCapturer> Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::SquaresVideo config); + static std::unique_ptr<FrameGeneratorCapturer> Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::SquareSlides config); + static std::unique_ptr<FrameGeneratorCapturer> Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::VideoFile config); + static std::unique_ptr<FrameGeneratorCapturer> Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + FrameGeneratorCapturerConfig::ImageSlides config); + static std::unique_ptr<FrameGeneratorCapturer> Create( + Clock* clock, + TaskQueueFactory& task_queue_factory, + const FrameGeneratorCapturerConfig& config); + + void Start(); + void Stop(); + void ChangeResolution(size_t width, size_t height); + void ChangeFramerate(int target_framerate); + + struct Resolution { + int width; + int height; + }; + absl::optional<Resolution> GetResolution(); + + void OnOutputFormatRequest(int width, + int height, + const absl::optional<int>& max_fps); + + void SetSinkWantsObserver(SinkWantsObserver* observer); + + void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) override; + void RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) override; + + void ForceFrame(); + void SetFakeRotation(VideoRotation rotation); + void SetFakeColorSpace(absl::optional<ColorSpace> color_space); + + int64_t first_frame_capture_time() const { return first_frame_capture_time_; } + + bool Init(); + + private: + void InsertFrame(); + static bool Run(void* obj); + int GetCurrentConfiguredFramerate(); + void UpdateFps(int max_fps) RTC_EXCLUSIVE_LOCKS_REQUIRED(&lock_); + + Clock* const clock_; + RepeatingTaskHandle frame_task_; + bool sending_; + SinkWantsObserver* sink_wants_observer_ RTC_GUARDED_BY(&lock_); + + Mutex lock_; + std::unique_ptr<FrameGeneratorInterface> frame_generator_; + + int source_fps_ RTC_GUARDED_BY(&lock_); + int target_capture_fps_ RTC_GUARDED_BY(&lock_); + absl::optional<int> wanted_fps_ RTC_GUARDED_BY(&lock_); + VideoRotation fake_rotation_ = kVideoRotation_0; + absl::optional<ColorSpace> fake_color_space_ RTC_GUARDED_BY(&lock_); + + int64_t first_frame_capture_time_; + + Mutex stats_lock_; + absl::optional<Resolution> source_resolution_ RTC_GUARDED_BY(&stats_lock_); + + // Must be the last field, so it will be deconstructed first as tasks + // in the TaskQueue access other fields of the instance of this class. + rtc::TaskQueue task_queue_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_FRAME_GENERATOR_CAPTURER_H_ diff --git a/third_party/libwebrtc/test/frame_generator_capturer_unittest.cc b/third_party/libwebrtc/test/frame_generator_capturer_unittest.cc new file mode 100644 index 0000000000..d8371f4efd --- /dev/null +++ b/third_party/libwebrtc/test/frame_generator_capturer_unittest.cc @@ -0,0 +1,89 @@ +/* + * 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/frame_generator_capturer.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace test { +namespace { +using ::testing::Eq; +using ::testing::Property; + +constexpr int kWidth = 640; +constexpr int kHeight = 360; + +class MockVideoSinkInterfaceVideoFrame + : public rtc::VideoSinkInterface<VideoFrame> { + public: + MOCK_METHOD(void, OnFrame, (const VideoFrame& frame), (override)); + MOCK_METHOD(void, OnDiscardedFrame, (), (override)); +}; +} // namespace + +TEST(FrameGeneratorCapturerTest, CreateFromConfig) { + GlobalSimulatedTimeController time(Timestamp::Seconds(1000)); + FrameGeneratorCapturerConfig config; + config.squares_video->width = 300; + config.squares_video->height = 200; + config.squares_video->framerate = 20; + auto capturer = FrameGeneratorCapturer::Create( + time.GetClock(), *time.GetTaskQueueFactory(), config); + testing::StrictMock<MockVideoSinkInterfaceVideoFrame> mock_sink; + capturer->AddOrUpdateSink(&mock_sink, rtc::VideoSinkWants()); + capturer->Start(); + EXPECT_CALL(mock_sink, OnFrame(Property(&VideoFrame::width, Eq(300)))) + .Times(21); + time.AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(FrameGeneratorCapturerTest, OnOutputFormatRequest) { + GlobalSimulatedTimeController time(Timestamp::Seconds(1000)); + FrameGeneratorCapturerConfig config; + config.squares_video->width = kWidth; + config.squares_video->height = kHeight; + config.squares_video->framerate = 20; + auto capturer = FrameGeneratorCapturer::Create( + time.GetClock(), *time.GetTaskQueueFactory(), config); + testing::StrictMock<MockVideoSinkInterfaceVideoFrame> mock_sink; + capturer->AddOrUpdateSink(&mock_sink, rtc::VideoSinkWants()); + capturer->OnOutputFormatRequest(kWidth / 2, kHeight / 2, /*max_fps=*/10); + capturer->Start(); + EXPECT_CALL(mock_sink, OnFrame(Property(&VideoFrame::width, Eq(kWidth / 2)))) + .Times(11); + time.AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(FrameGeneratorCapturerTest, ChangeResolution) { + GlobalSimulatedTimeController time(Timestamp::Seconds(1000)); + FrameGeneratorCapturerConfig config; + config.squares_video->width = kWidth; + config.squares_video->height = kHeight; + config.squares_video->framerate = 20; + auto capturer = FrameGeneratorCapturer::Create( + time.GetClock(), *time.GetTaskQueueFactory(), config); + EXPECT_FALSE(capturer->GetResolution()); + capturer->Start(); + time.AdvanceTime(TimeDelta::Seconds(1)); + ASSERT_TRUE(capturer->GetResolution()); + EXPECT_EQ(kWidth, capturer->GetResolution()->width); + EXPECT_EQ(kHeight, capturer->GetResolution()->height); + + capturer->ChangeResolution(kWidth / 2, kHeight / 2); + time.AdvanceTime(TimeDelta::Seconds(1)); + ASSERT_TRUE(capturer->GetResolution()); + EXPECT_EQ(kWidth / 2, capturer->GetResolution()->width); + EXPECT_EQ(kHeight / 2, capturer->GetResolution()->height); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/frame_generator_unittest.cc b/third_party/libwebrtc/test/frame_generator_unittest.cc new file mode 100644 index 0000000000..ece37a547f --- /dev/null +++ b/third_party/libwebrtc/test/frame_generator_unittest.cc @@ -0,0 +1,284 @@ +/* + * Copyright (c) 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. + */ + +#include "test/frame_generator.h" + +#include <stdio.h> +#include <string.h> + +#include <cstdint> +#include <memory> +#include <string> + +#include "api/scoped_refptr.h" +#include "api/test/create_frame_generator.h" +#include "api/test/frame_generator_interface.h" +#include "api/video/video_frame_buffer.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace test { + +constexpr int kFrameWidth = 4; +constexpr int kFrameHeight = 4; +constexpr int y_size = kFrameWidth * kFrameHeight; +constexpr int uv_size = ((kFrameHeight + 1) / 2) * ((kFrameWidth + 1) / 2); + +class FrameGeneratorTest : public ::testing::Test { + public: + void SetUp() override { + two_frame_yuv_filename_ = + test::TempFilename(test::OutputPath(), "2_frame_yuv_file"); + one_frame_yuv_filename_ = + test::TempFilename(test::OutputPath(), "1_frame_yuv_file"); + two_frame_nv12_filename_ = + test::TempFilename(test::OutputPath(), "2_frame_nv12_file"); + one_frame_nv12_filename_ = + test::TempFilename(test::OutputPath(), "1_frame_nv12_file"); + + FILE* file = fopen(two_frame_yuv_filename_.c_str(), "wb"); + WriteYuvFile(file, 0, 0, 0); + WriteYuvFile(file, 127, 128, 129); + fclose(file); + file = fopen(one_frame_yuv_filename_.c_str(), "wb"); + WriteYuvFile(file, 255, 255, 255); + fclose(file); + file = fopen(two_frame_nv12_filename_.c_str(), "wb"); + WriteNV12File(file, 0, 0, 0); + WriteNV12File(file, 127, 128, 129); + fclose(file); + file = fopen(one_frame_nv12_filename_.c_str(), "wb"); + WriteNV12File(file, 255, 255, 255); + fclose(file); + } + + void TearDown() override { + remove(one_frame_yuv_filename_.c_str()); + remove(two_frame_yuv_filename_.c_str()); + remove(one_frame_nv12_filename_.c_str()); + remove(two_frame_nv12_filename_.c_str()); + } + + protected: + void WriteYuvFile(FILE* file, uint8_t y, uint8_t u, uint8_t v) { + RTC_DCHECK(file); + std::unique_ptr<uint8_t[]> plane_buffer(new uint8_t[y_size]); + memset(plane_buffer.get(), y, y_size); + fwrite(plane_buffer.get(), 1, y_size, file); + memset(plane_buffer.get(), u, uv_size); + fwrite(plane_buffer.get(), 1, uv_size, file); + memset(plane_buffer.get(), v, uv_size); + fwrite(plane_buffer.get(), 1, uv_size, file); + } + + void WriteNV12File(FILE* file, uint8_t y, uint8_t u, uint8_t v) { + RTC_DCHECK(file); + uint8_t plane_buffer[y_size]; + + memset(&plane_buffer, y, y_size); + fwrite(&plane_buffer, 1, y_size, file); + for (size_t i = 0; i < uv_size; ++i) { + plane_buffer[2 * i] = u; + plane_buffer[2 * i + 1] = v; + } + fwrite(&plane_buffer, 1, 2 * uv_size, file); + } + + void CheckFrameAndMutate(const FrameGeneratorInterface::VideoFrameData& frame, + uint8_t y, + uint8_t u, + uint8_t v) { + // Check that frame is valid, has the correct color and timestamp are clean. + rtc::scoped_refptr<I420BufferInterface> i420_buffer = + frame.buffer->ToI420(); + const uint8_t* buffer; + buffer = i420_buffer->DataY(); + for (int i = 0; i < y_size; ++i) + ASSERT_EQ(y, buffer[i]); + buffer = i420_buffer->DataU(); + for (int i = 0; i < uv_size; ++i) + ASSERT_EQ(u, buffer[i]); + buffer = i420_buffer->DataV(); + for (int i = 0; i < uv_size; ++i) + ASSERT_EQ(v, buffer[i]); + } + + uint64_t Hash(const FrameGeneratorInterface::VideoFrameData& frame) { + // Generate a 64-bit hash from the frame's buffer. + uint64_t hash = 19; + rtc::scoped_refptr<I420BufferInterface> i420_buffer = + frame.buffer->ToI420(); + const uint8_t* buffer = i420_buffer->DataY(); + for (int i = 0; i < y_size; ++i) { + hash = (37 * hash) + buffer[i]; + } + buffer = i420_buffer->DataU(); + for (int i = 0; i < uv_size; ++i) { + hash = (37 * hash) + buffer[i]; + } + buffer = i420_buffer->DataV(); + for (int i = 0; i < uv_size; ++i) { + hash = (37 * hash) + buffer[i]; + } + return hash; + } + + std::string two_frame_yuv_filename_; + std::string one_frame_yuv_filename_; + std::string two_frame_nv12_filename_; + std::string one_frame_nv12_filename_; +}; + +TEST_F(FrameGeneratorTest, SingleFrameYuvFile) { + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromYuvFileFrameGenerator( + std::vector<std::string>(1, one_frame_yuv_filename_), kFrameWidth, + kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); +} + +TEST_F(FrameGeneratorTest, TwoFrameYuvFile) { + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromYuvFileFrameGenerator( + std::vector<std::string>(1, two_frame_yuv_filename_), kFrameWidth, + kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, MultipleFrameYuvFiles) { + std::vector<std::string> files; + files.push_back(two_frame_yuv_filename_); + files.push_back(one_frame_yuv_filename_); + + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromYuvFileFrameGenerator(files, kFrameWidth, kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, TwoFrameYuvFileWithRepeat) { + const int kRepeatCount = 3; + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromYuvFileFrameGenerator( + std::vector<std::string>(1, two_frame_yuv_filename_), kFrameWidth, + kFrameHeight, kRepeatCount)); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, MultipleFrameYuvFilesWithRepeat) { + const int kRepeatCount = 3; + std::vector<std::string> files; + files.push_back(two_frame_yuv_filename_); + files.push_back(one_frame_yuv_filename_); + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromYuvFileFrameGenerator(files, kFrameWidth, kFrameHeight, + kRepeatCount)); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, SingleFrameNV12File) { + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromNV12FileFrameGenerator( + std::vector<std::string>(1, one_frame_nv12_filename_), kFrameWidth, + kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); +} + +TEST_F(FrameGeneratorTest, TwoFrameNV12File) { + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromNV12FileFrameGenerator( + std::vector<std::string>(1, two_frame_nv12_filename_), kFrameWidth, + kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, MultipleFrameNV12Files) { + std::vector<std::string> files; + files.push_back(two_frame_nv12_filename_); + files.push_back(one_frame_nv12_filename_); + + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromNV12FileFrameGenerator(files, kFrameWidth, kFrameHeight, 1)); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, TwoFrameNV12FileWithRepeat) { + const int kRepeatCount = 3; + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromNV12FileFrameGenerator( + std::vector<std::string>(1, two_frame_nv12_filename_), kFrameWidth, + kFrameHeight, kRepeatCount)); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, MultipleFrameNV12FilesWithRepeat) { + const int kRepeatCount = 3; + std::vector<std::string> files; + files.push_back(two_frame_nv12_filename_); + files.push_back(one_frame_nv12_filename_); + std::unique_ptr<FrameGeneratorInterface> generator( + CreateFromNV12FileFrameGenerator(files, kFrameWidth, kFrameHeight, + kRepeatCount)); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 127, 128, 129); + for (int i = 0; i < kRepeatCount; ++i) + CheckFrameAndMutate(generator->NextFrame(), 255, 255, 255); + CheckFrameAndMutate(generator->NextFrame(), 0, 0, 0); +} + +TEST_F(FrameGeneratorTest, SlideGenerator) { + const int kGenCount = 9; + const int kRepeatCount = 3; + std::unique_ptr<FrameGeneratorInterface> generator( + CreateSlideFrameGenerator(kFrameWidth, kFrameHeight, kRepeatCount)); + uint64_t hashes[kGenCount]; + for (int i = 0; i < kGenCount; ++i) { + hashes[i] = Hash(generator->NextFrame()); + } + // Check that the buffer changes only every `kRepeatCount` frames. + for (int i = 1; i < kGenCount; ++i) { + if (i % kRepeatCount == 0) { + EXPECT_NE(hashes[i - 1], hashes[i]); + } else { + EXPECT_EQ(hashes[i - 1], hashes[i]); + } + } +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/frame_utils.cc b/third_party/libwebrtc/test/frame_utils.cc new file mode 100644 index 0000000000..b280de1ad1 --- /dev/null +++ b/third_party/libwebrtc/test/frame_utils.cc @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2013 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/frame_utils.h" + +#include <stdio.h> +#include <string.h> + +#include "api/video/i420_buffer.h" +#include "api/video/nv12_buffer.h" +#include "api/video/video_frame.h" + +namespace webrtc { +namespace test { + +bool EqualPlane(const uint8_t* data1, + const uint8_t* data2, + int stride1, + int stride2, + int width, + int height) { + for (int y = 0; y < height; ++y) { + if (memcmp(data1, data2, width) != 0) + return false; + data1 += stride1; + data2 += stride2; + } + return true; +} + +bool FramesEqual(const webrtc::VideoFrame& f1, const webrtc::VideoFrame& f2) { + if (f1.timestamp() != f2.timestamp() || + f1.ntp_time_ms() != f2.ntp_time_ms() || + f1.render_time_ms() != f2.render_time_ms()) { + return false; + } + return FrameBufsEqual(f1.video_frame_buffer(), f2.video_frame_buffer()); +} + +bool FrameBufsEqual(const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& f1, + const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& f2) { + if (f1 == f2) { + return true; + } + // Exlude nullptr (except if both are nullptr, as above) + if (!f1 || !f2) { + return false; + } + + if (f1->width() != f2->width() || f1->height() != f2->height() || + f1->type() != f2->type()) { + return false; + } + + rtc::scoped_refptr<webrtc::I420BufferInterface> f1_i420 = f1->ToI420(); + rtc::scoped_refptr<webrtc::I420BufferInterface> f2_i420 = f2->ToI420(); + return EqualPlane(f1_i420->DataY(), f2_i420->DataY(), f1_i420->StrideY(), + f2_i420->StrideY(), f1_i420->width(), f1_i420->height()) && + EqualPlane(f1_i420->DataU(), f2_i420->DataU(), f1_i420->StrideU(), + f2_i420->StrideU(), f1_i420->ChromaWidth(), + f1_i420->ChromaHeight()) && + EqualPlane(f1_i420->DataV(), f2_i420->DataV(), f1_i420->StrideV(), + f2_i420->StrideV(), f1_i420->ChromaWidth(), + f1_i420->ChromaHeight()); +} + +rtc::scoped_refptr<I420Buffer> ReadI420Buffer(int width, int height, FILE* f) { + int half_width = (width + 1) / 2; + rtc::scoped_refptr<I420Buffer> buffer( + // Explicit stride, no padding between rows. + I420Buffer::Create(width, height, width, half_width, half_width)); + size_t size_y = static_cast<size_t>(width) * height; + size_t size_uv = static_cast<size_t>(half_width) * ((height + 1) / 2); + + if (fread(buffer->MutableDataY(), 1, size_y, f) < size_y) + return nullptr; + if (fread(buffer->MutableDataU(), 1, size_uv, f) < size_uv) + return nullptr; + if (fread(buffer->MutableDataV(), 1, size_uv, f) < size_uv) + return nullptr; + return buffer; +} + +rtc::scoped_refptr<NV12Buffer> ReadNV12Buffer(int width, int height, FILE* f) { + rtc::scoped_refptr<NV12Buffer> buffer(NV12Buffer::Create(width, height)); + size_t size_y = static_cast<size_t>(width) * height; + size_t size_uv = static_cast<size_t>(width + width % 2) * ((height + 1) / 2); + + if (fread(buffer->MutableDataY(), 1, size_y, f) < size_y) + return nullptr; + if (fread(buffer->MutableDataUV(), 1, size_uv, f) < size_uv) + return nullptr; + return buffer; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/frame_utils.h b/third_party/libwebrtc/test/frame_utils.h new file mode 100644 index 0000000000..1f2b381afb --- /dev/null +++ b/third_party/libwebrtc/test/frame_utils.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 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_FRAME_UTILS_H_ +#define TEST_FRAME_UTILS_H_ + +#include <stdint.h> + +#include "api/scoped_refptr.h" +#include "api/video/nv12_buffer.h" + +namespace webrtc { +class I420Buffer; +class VideoFrame; +class VideoFrameBuffer; +namespace test { + +bool EqualPlane(const uint8_t* data1, + const uint8_t* data2, + int stride1, + int stride2, + int width, + int height); + +static inline bool EqualPlane(const uint8_t* data1, + const uint8_t* data2, + int stride, + int width, + int height) { + return EqualPlane(data1, data2, stride, stride, width, height); +} + +bool FramesEqual(const webrtc::VideoFrame& f1, const webrtc::VideoFrame& f2); + +bool FrameBufsEqual(const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& f1, + const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& f2); + +rtc::scoped_refptr<I420Buffer> ReadI420Buffer(int width, int height, FILE*); + +rtc::scoped_refptr<NV12Buffer> ReadNV12Buffer(int width, int height, FILE*); + +} // namespace test +} // namespace webrtc + +#endif // TEST_FRAME_UTILS_H_ diff --git a/third_party/libwebrtc/test/function_audio_decoder_factory.h b/third_party/libwebrtc/test/function_audio_decoder_factory.h new file mode 100644 index 0000000000..8464f3d9aa --- /dev/null +++ b/third_party/libwebrtc/test/function_audio_decoder_factory.h @@ -0,0 +1,68 @@ +/* + * 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_FUNCTION_AUDIO_DECODER_FACTORY_H_ +#define TEST_FUNCTION_AUDIO_DECODER_FACTORY_H_ + +#include <functional> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_format.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +// A decoder factory producing decoders by calling a supplied create function. +class FunctionAudioDecoderFactory : public AudioDecoderFactory { + public: + explicit FunctionAudioDecoderFactory( + std::function<std::unique_ptr<AudioDecoder>()> create) + : create_([create](const SdpAudioFormat&, + absl::optional<AudioCodecPairId> codec_pair_id) { + return create(); + }) {} + explicit FunctionAudioDecoderFactory( + std::function<std::unique_ptr<AudioDecoder>( + const SdpAudioFormat&, + absl::optional<AudioCodecPairId> codec_pair_id)> create) + : create_(std::move(create)) {} + + // Unused by tests. + std::vector<AudioCodecSpec> GetSupportedDecoders() override { + RTC_DCHECK_NOTREACHED(); + return {}; + } + + bool IsSupportedDecoder(const SdpAudioFormat& format) override { + return true; + } + + std::unique_ptr<AudioDecoder> MakeAudioDecoder( + const SdpAudioFormat& format, + absl::optional<AudioCodecPairId> codec_pair_id) override { + return create_(format, codec_pair_id); + } + + private: + const std::function<std::unique_ptr<AudioDecoder>( + const SdpAudioFormat&, + absl::optional<AudioCodecPairId> codec_pair_id)> + create_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FUNCTION_AUDIO_DECODER_FACTORY_H_ diff --git a/third_party/libwebrtc/test/fuzzers/BUILD.gn b/third_party/libwebrtc/test/fuzzers/BUILD.gn new file mode 100644 index 0000000000..c3576713ab --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/BUILD.gn @@ -0,0 +1,689 @@ +# Copyright (c) 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. + +import("//build/config/features.gni") +import("//testing/libfuzzer/fuzzer_test.gni") +import("../../webrtc.gni") + +rtc_library("webrtc_fuzzer_main") { + sources = [ "webrtc_fuzzer_main.cc" ] + deps = [ + "../../rtc_base:logging", + "//testing/libfuzzer:libfuzzer_main", + ] + + # When WebRTC fuzzer tests are built on Chromium bots they need to link + # with Chromium's implementation of metrics, field trial, and system time. + if (build_with_chromium) { + deps += [ + "../../../webrtc_overrides:field_trial", + "../../../webrtc_overrides:metrics", + "../../../webrtc_overrides:system_time", + ] + } +} + +rtc_library("fuzz_data_helper") { + testonly = true + sources = [ + "fuzz_data_helper.cc", + "fuzz_data_helper.h", + ] + deps = [ + "../../api:array_view", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] + visibility = [ ":*" ] # Only targets in this file can depend on this. +} + +set_defaults("webrtc_fuzzer_test") { + configs = rtc_add_configs + absl_deps = [] +} + +template("webrtc_fuzzer_test") { + fuzzer_test(target_name) { + forward_variables_from(invoker, "*") + deps += [ + ":fuzz_data_helper", + ":webrtc_fuzzer_main", + ] + additional_configs = configs + + # If absl_deps is [], no action is needed. If not [], then it needs to be + # converted to //third_party/abseil-cpp:absl when build_with_chromium=true + # otherwise it just needs to be added to deps. + if (absl_deps != []) { + if (!defined(deps)) { + deps = [] + } + if (build_with_chromium) { + deps += [ "//third_party/abseil-cpp:absl" ] + } else { + deps += absl_deps + } + } + + if (!build_with_chromium && is_clang) { + suppressed_configs = [ "//build/config/clang:find_bad_constructs" ] + } + } +} + +webrtc_fuzzer_test("h264_depacketizer_fuzzer") { + sources = [ "h264_depacketizer_fuzzer.cc" ] + deps = [ "../../modules/rtp_rtcp" ] + seed_corpus = "corpora/h264-depacketizer-fuzzer-corpus" +} + +webrtc_fuzzer_test("vp8_depacketizer_fuzzer") { + sources = [ "vp8_depacketizer_fuzzer.cc" ] + deps = [ + "../../api:array_view", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_video_header", + ] +} + +webrtc_fuzzer_test("vp9_depacketizer_fuzzer") { + sources = [ "vp9_depacketizer_fuzzer.cc" ] + deps = [ + "../../api:array_view", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_video_header", + ] +} + +webrtc_fuzzer_test("vp8_qp_parser_fuzzer") { + sources = [ "vp8_qp_parser_fuzzer.cc" ] + deps = [ + "../../modules/video_coding:video_coding_utility", + "../../modules/video_coding/", + ] +} + +webrtc_fuzzer_test("vp9_qp_parser_fuzzer") { + sources = [ "vp9_qp_parser_fuzzer.cc" ] + deps = [ + "../../modules/video_coding:video_coding_utility", + "../../modules/video_coding/", + ] +} + +webrtc_fuzzer_test("h264_bitstream_parser_fuzzer") { + sources = [ "h264_bitstream_parser_fuzzer.cc" ] + deps = [ + "../../common_video", + "../../modules/video_coding/", + ] +} + +webrtc_fuzzer_test("forward_error_correction_fuzzer") { + sources = [ "forward_error_correction_fuzzer.cc" ] + deps = [ + "../../api:scoped_refptr", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:byte_buffer", + ] +} + +webrtc_fuzzer_test("flexfec_header_reader_fuzzer") { + sources = [ "flexfec_header_reader_fuzzer.cc" ] + deps = [ + "../../api:scoped_refptr", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] +} + +webrtc_fuzzer_test("flexfec_sender_fuzzer") { + sources = [ "flexfec_sender_fuzzer.cc" ] + deps = [ + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../system_wrappers", + ] +} + +webrtc_fuzzer_test("ulpfec_header_reader_fuzzer") { + sources = [ "ulpfec_header_reader_fuzzer.cc" ] + deps = [ + "../../api:scoped_refptr", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:fec_test_helper", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] +} + +webrtc_fuzzer_test("ulpfec_generator_fuzzer") { + sources = [ "ulpfec_generator_fuzzer.cc" ] + deps = [ + "../../modules:module_api_public", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:fec_test_helper", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:checks", + "../../rtc_base:copy_on_write_buffer", + "../../system_wrappers", + ] +} + +webrtc_fuzzer_test("ulpfec_receiver_fuzzer") { + sources = [ "ulpfec_receiver_fuzzer.cc" ] + deps = [ + ":fuzz_data_helper", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] +} + +webrtc_fuzzer_test("flexfec_receiver_fuzzer") { + sources = [ "flexfec_receiver_fuzzer.cc" ] + deps = [ + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] +} + +webrtc_fuzzer_test("rtp_video_frame_assembler_fuzzer") { + sources = [ "rtp_video_frame_assembler_fuzzer.cc" ] + deps = [ + "../../api/video:rtp_video_frame_assembler", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] +} + +webrtc_fuzzer_test("rtcp_receiver_fuzzer") { + sources = [ "rtcp_receiver_fuzzer.cc" ] + deps = [ + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:checks", + "../../system_wrappers", + ] + seed_corpus = "corpora/rtcp-corpus" +} + +webrtc_fuzzer_test("rtp_packet_fuzzer") { + sources = [ "rtp_packet_fuzzer.cc" ] + deps = [ "../../modules/rtp_rtcp:rtp_rtcp_format" ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + seed_corpus = "corpora/rtp-corpus" +} + +webrtc_fuzzer_test("rtp_packetizer_av1_fuzzer") { + sources = [ "rtp_packetizer_av1_fuzzer.cc" ] + deps = [ + "../../api/video:video_frame_type", + "../../modules/rtp_rtcp:rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:checks", + ] +} + +webrtc_fuzzer_test("congestion_controller_feedback_fuzzer") { + sources = [ "congestion_controller_feedback_fuzzer.cc" ] + deps = [ + "../../modules/congestion_controller", + "../../modules/pacing", + "../../modules/remote_bitrate_estimator", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/functional:bind_front" ] +} + +rtc_library("audio_decoder_fuzzer") { + testonly = true + sources = [ + "audio_decoder_fuzzer.cc", + "audio_decoder_fuzzer.h", + ] + deps = [ + "../../api/audio_codecs:audio_codecs_api", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:checks", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +webrtc_fuzzer_test("audio_decoder_g722_fuzzer") { + sources = [ "audio_decoder_g722_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../modules/audio_coding:g722", + ] +} + +webrtc_fuzzer_test("audio_decoder_ilbc_fuzzer") { + sources = [ "audio_decoder_ilbc_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../modules/audio_coding:ilbc", + ] +} + +webrtc_fuzzer_test("audio_decoder_opus_fuzzer") { + sources = [ "audio_decoder_opus_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../modules/audio_coding:webrtc_opus", + ] +} + +webrtc_fuzzer_test("audio_decoder_opus_redundant_fuzzer") { + sources = [ "audio_decoder_opus_redundant_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../modules/audio_coding:webrtc_opus", + ] +} + +webrtc_fuzzer_test("audio_decoder_multiopus_fuzzer") { + sources = [ "audio_decoder_multistream_opus_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../api/audio_codecs/opus:audio_decoder_multiopus", + "../../api/audio_codecs/opus:audio_decoder_opus_config", + ] +} + +webrtc_fuzzer_test("audio_decoder_pcm_fuzzer") { + sources = [ "audio_decoder_pcm_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../modules/audio_coding:g711", + ] +} + +webrtc_fuzzer_test("audio_decoder_pcm16b_fuzzer") { + sources = [ "audio_decoder_pcm16b_fuzzer.cc" ] + deps = [ + ":audio_decoder_fuzzer", + "../../modules/audio_coding:pcm16b", + ] +} + +rtc_library("audio_encoder_fuzzer") { + testonly = true + sources = [ + "audio_encoder_fuzzer.cc", + "audio_encoder_fuzzer.h", + ] + deps = [ + ":fuzz_data_helper", + "../../api:array_view", + "../../api/audio_codecs:audio_codecs_api", + "../../rtc_base:buffer", + "../../rtc_base:checks", + ] +} + +webrtc_fuzzer_test("audio_encoder_opus_fuzzer") { + sources = [ "audio_encoder_opus_fuzzer.cc" ] + deps = [ + ":audio_encoder_fuzzer", + "../../api/audio_codecs/opus:audio_encoder_opus", + "../../rtc_base:checks", + ] +} + +webrtc_fuzzer_test("turn_unwrap_fuzzer") { + sources = [ "turn_unwrap_fuzzer.cc" ] + deps = [ + "../../media", + "../../media:rtc_media_base", + "../../media:turn_utils", + ] +} + +webrtc_fuzzer_test("neteq_rtp_fuzzer") { + sources = [ "neteq_rtp_fuzzer.cc" ] + deps = [ + "../../api:array_view", + "../../api/audio_codecs:builtin_audio_decoder_factory", + "../../modules/audio_coding:neteq", + "../../modules/audio_coding:neteq_test_tools", + "../../modules/audio_coding:neteq_tools_minimal", + "../../modules/audio_coding:pcm16b", + "../../modules/rtp_rtcp:rtp_rtcp_format", + ] +} + +webrtc_fuzzer_test("neteq_signal_fuzzer") { + sources = [ "neteq_signal_fuzzer.cc" ] + deps = [ + "../../api:array_view", + "../../api/audio_codecs:builtin_audio_decoder_factory", + "../../modules/audio_coding:neteq", + "../../modules/audio_coding:neteq_test_tools", + "../../modules/audio_coding:neteq_tools_minimal", + "../../modules/audio_coding:pcm16b", + "../../rtc_base:random", + "../../rtc_base:safe_conversions", + ] +} + +webrtc_fuzzer_test("residual_echo_detector_fuzzer") { + sources = [ "residual_echo_detector_fuzzer.cc" ] + deps = [ + "../../api/audio:echo_detector_creator", + "../../rtc_base:checks", + "../../rtc_base:refcount", + ] +} + +webrtc_fuzzer_test("sdp_parser_fuzzer") { + sources = [ "sdp_parser_fuzzer.cc" ] + deps = [ + "../../api:libjingle_peerconnection_api", + "../../pc:libjingle_peerconnection", + ] + seed_corpus = "corpora/sdp-corpus" +} + +if (!build_with_chromium) { + # This target depends on test infrastructure that can't be built + # with Chromium at the moment. + # TODO(bugs.chromium.org/12534): Make this fuzzer build in Chromium. + + webrtc_fuzzer_test("sdp_integration_fuzzer") { + sources = [ "sdp_integration_fuzzer.cc" ] + deps = [ + "../../api:libjingle_peerconnection_api", + "../../pc:integration_test_helpers", + "../../pc:libjingle_peerconnection", + "../../test:test_support", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + seed_corpus = "corpora/sdp-corpus" + } +} + +webrtc_fuzzer_test("stun_parser_fuzzer") { + sources = [ "stun_parser_fuzzer.cc" ] + deps = [ + "../../api/transport:stun_types", + "../../p2p:rtc_p2p", + ] + seed_corpus = "corpora/stun-corpus" + dict = "corpora/stun.tokens" +} + +webrtc_fuzzer_test("stun_validator_fuzzer") { + sources = [ "stun_validator_fuzzer.cc" ] + deps = [ + "../../api/transport:stun_types", + "../../p2p:rtc_p2p", + ] + seed_corpus = "corpora/stun-corpus" + dict = "corpora/stun.tokens" +} + +webrtc_fuzzer_test("pseudotcp_parser_fuzzer") { + sources = [ "pseudotcp_parser_fuzzer.cc" ] + deps = [ + "../../p2p:rtc_p2p", + "../../rtc_base:threading", + ] +} + +rtc_library("audio_processing_fuzzer_helper") { + testonly = true + sources = [ + "audio_processing_fuzzer_helper.cc", + "audio_processing_fuzzer_helper.h", + ] + deps = [ + ":fuzz_data_helper", + "../../api/audio:audio_frame_api", + "../../modules/audio_processing", + "../../modules/audio_processing:api", + "../../modules/audio_processing:audio_frame_proxies", + "../../rtc_base:checks", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +webrtc_fuzzer_test("audio_processing_fuzzer") { + sources = [ "audio_processing_configs_fuzzer.cc" ] + deps = [ + ":audio_processing_fuzzer_helper", + "../../api:scoped_refptr", + "../../api/audio:aec3_factory", + "../../api/audio:echo_detector_creator", + "../../api/task_queue:default_task_queue_factory", + "../../modules/audio_processing", + "../../modules/audio_processing:api", + "../../modules/audio_processing:audio_buffer", + "../../modules/audio_processing:audioproc_test_utils", + "../../modules/audio_processing/aec3", + "../../modules/audio_processing/aec_dump", + "../../modules/audio_processing/aec_dump:aec_dump_impl", + "../../rtc_base:macromagic", + "../../rtc_base:rtc_task_queue", + "../../rtc_base:safe_minmax", + "../../system_wrappers:field_trial", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] + seed_corpus = "corpora/audio_processing-corpus" +} + +webrtc_fuzzer_test("audio_processing_sample_rate_fuzzer") { + sources = [ "audio_processing_sample_rate_fuzzer.cc" ] + deps = [ + "../../api:scoped_refptr", + "../../api/audio:audio_frame_api", + "../../modules/audio_processing", + "../../modules/audio_processing:api", + "../../modules/audio_processing:audio_frame_proxies", + "../../modules/audio_processing:audioproc_test_utils", + "../../rtc_base:checks", + "../../rtc_base:macromagic", + "../../rtc_base:safe_minmax", + ] +} + +webrtc_fuzzer_test("agc_fuzzer") { + sources = [ "agc_fuzzer.cc" ] + deps = [ + ":fuzz_data_helper", + "../../modules/audio_processing", + "../../modules/audio_processing:api", + "../../modules/audio_processing:audio_buffer", + "../../rtc_base:macromagic", + "../../rtc_base:safe_minmax", + ] + + seed_corpus = "corpora/agc-corpus" +} + +webrtc_fuzzer_test("aec3_config_json_fuzzer") { + sources = [ "aec3_config_json_fuzzer.cc" ] + deps = [ + ":fuzz_data_helper", + "../../api/audio:aec3_config", + "../../api/audio:aec3_config_json", + ] + dict = "//testing/libfuzzer/fuzzers/dicts/json.dict" + seed_corpus = "corpora/aec3-config-json-corpus" +} + +webrtc_fuzzer_test("aec3_fuzzer") { + defines = [] + if (apm_debug_dump) { + defines += [ "WEBRTC_APM_DEBUG_DUMP=1" ] + } else { + defines += [ "WEBRTC_APM_DEBUG_DUMP=0" ] + } + sources = [ "aec3_fuzzer.cc" ] + deps = [ + ":fuzz_data_helper", + "../../modules/audio_processing:api", + "../../modules/audio_processing:audio_buffer", + "../../modules/audio_processing/aec3", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +webrtc_fuzzer_test("comfort_noise_decoder_fuzzer") { + sources = [ "comfort_noise_decoder_fuzzer.cc" ] + deps = [ + "../../api:array_view", + "../../modules/audio_coding:webrtc_cng", + "../../rtc_base:buffer", + ] +} + +webrtc_fuzzer_test("rtp_depacketizer_av1_assemble_frame_fuzzer") { + sources = [ "rtp_depacketizer_av1_assemble_frame_fuzzer.cc" ] + seed_corpus = "corpora/rtp-depacketizer-av1-assemble-frame-corpus" + deps = [ + ":fuzz_data_helper", + "../../api:array_view", + "../../modules/rtp_rtcp", + ] +} + +webrtc_fuzzer_test("rtp_dependency_descriptor_fuzzer") { + sources = [ "rtp_dependency_descriptor_fuzzer.cc" ] + seed_corpus = "corpora/dependency_descriptor-corpus" + deps = [ + "../../api:array_view", + "../../common_video/generic_frame_descriptor", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:checks", + ] +} + +webrtc_fuzzer_test("rtp_video_layers_allocation_fuzzer") { + sources = [ "rtp_video_layers_allocation_fuzzer.cc" ] + seed_corpus = "corpora/video_layers_allocation-corpus" + deps = [ + "../../api:array_view", + "../../api/video:video_layers_allocation", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../rtc_base:checks", + ] + + # video_layers_allocation is an rtp header extension and thus can't be longer + # than 255 bytes on the wire. + libfuzzer_options = [ "max_len=255" ] +} + +webrtc_fuzzer_test("rtp_frame_reference_finder_fuzzer") { + sources = [ "rtp_frame_reference_finder_fuzzer.cc" ] + deps = [ + "../../api:rtp_packet_info", + "../../api:scoped_refptr", + "../../modules/video_coding/", + "../../system_wrappers", + ] +} + +webrtc_fuzzer_test("frame_buffer2_fuzzer") { + sources = [ "frame_buffer2_fuzzer.cc" ] + deps = [ + "../../api/task_queue", + "../../modules/video_coding:frame_buffer2", + "../../modules/video_coding/timing:timing_module", + "../../test:scoped_key_value_config", + "../time_controller:time_controller", + ] +} + +webrtc_fuzzer_test("frame_buffer_fuzzer") { + sources = [ "frame_buffer_fuzzer.cc" ] + deps = [ + ":fuzz_data_helper", + "../../api:array_view", + "../../api/video:encoded_frame", + "../../api/video:frame_buffer", + "../../rtc_base:rtc_numerics", + "../../test:scoped_key_value_config", + ] +} + +webrtc_fuzzer_test("field_trial_fuzzer") { + sources = [ "field_trial_fuzzer.cc" ] + deps = [ "../../system_wrappers:field_trial" ] + seed_corpus = "corpora/field_trial-corpus" +} + +webrtc_fuzzer_test("string_to_number_fuzzer") { + sources = [ "string_to_number_fuzzer.cc" ] + deps = [ "../../rtc_base:stringutils" ] + seed_corpus = "corpora/string_to_number-corpus" +} + +webrtc_fuzzer_test("sctp_utils_fuzzer") { + sources = [ "sctp_utils_fuzzer.cc" ] + deps = [ + "../../api:libjingle_peerconnection_api", + "../../pc:libjingle_peerconnection", + "../../pc:sctp_utils", + "../../rtc_base:copy_on_write_buffer", + ] +} + +webrtc_fuzzer_test("dcsctp_socket_fuzzer") { + sources = [ "dcsctp_socket_fuzzer.cc" ] + deps = [ + "../../net/dcsctp/fuzzers:dcsctp_fuzzers", + "../../net/dcsctp/public:socket", + "../../net/dcsctp/public:types", + "../../net/dcsctp/socket:dcsctp_socket", + "../../rtc_base:logging", + ] +} + +webrtc_fuzzer_test("ssl_certificate_fuzzer") { + sources = [ "ssl_certificate_fuzzer.cc" ] + deps = [ + "../:rtp_test_utils", + "../../rtc_base:ssl", + "../../rtc_base:stringutils", + ] +} + +webrtc_fuzzer_test("vp8_replay_fuzzer") { + sources = [ "vp8_replay_fuzzer.cc" ] + deps = [ "utils:rtp_replayer" ] + seed_corpus = "corpora/rtpdump-corpus/vp8" +} + +if (rtc_build_libvpx) { + webrtc_fuzzer_test("vp9_encoder_references_fuzzer") { + sources = [ "vp9_encoder_references_fuzzer.cc" ] + deps = [ + "..:test_support", + "../../api:array_view", + "../../api:field_trials_view", + "../../api/video:video_frame", + "../../api/video_codecs:video_codecs_api", + "../../modules/video_coding:frame_dependencies_calculator", + "../../modules/video_coding:webrtc_libvpx_interface", + "../../modules/video_coding:webrtc_vp9", + "../../rtc_base:safe_compare", + rtc_libvpx_dir, + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/container:inlined_vector", + ] + seed_corpus = "corpora/vp9-encoder-references-corpus" + defines = [ "RTC_ENABLE_VP9" ] + } +} + +webrtc_fuzzer_test("vp9_replay_fuzzer") { + sources = [ "vp9_replay_fuzzer.cc" ] + deps = [ "utils:rtp_replayer" ] + seed_corpus = "corpora/rtpdump-corpus/vp9" +} diff --git a/third_party/libwebrtc/test/fuzzers/DEPS b/third_party/libwebrtc/test/fuzzers/DEPS new file mode 100644 index 0000000000..50b1c8adce --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+audio", + "+pc", + "+net/dcsctp", +] diff --git a/third_party/libwebrtc/test/fuzzers/OWNERS b/third_party/libwebrtc/test/fuzzers/OWNERS new file mode 100644 index 0000000000..3d97816b3b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/OWNERS @@ -0,0 +1,3 @@ +danilchap@webrtc.org +henrik.lundin@webrtc.org +saza@webrtc.org diff --git a/third_party/libwebrtc/test/fuzzers/aec3_config_json_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/aec3_config_json_fuzzer.cc new file mode 100644 index 0000000000..626350c52c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/aec3_config_json_fuzzer.cc @@ -0,0 +1,30 @@ +/* + * 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> + +#include "api/audio/echo_canceller3_config.h" +#include "api/audio/echo_canceller3_config_json.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000) { + return; + } + std::string config_json(reinterpret_cast<const char*>(data), size); + + EchoCanceller3Config config; + bool success; + Aec3ConfigFromJsonString(config_json, &config, &success); + EchoCanceller3Config::Validate(&config); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/aec3_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/aec3_fuzzer.cc new file mode 100644 index 0000000000..a12ca30f63 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/aec3_fuzzer.cc @@ -0,0 +1,79 @@ +/* + * 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 "absl/types/optional.h" +#include "modules/audio_processing/aec3/echo_canceller3.h" +#include "modules/audio_processing/audio_buffer.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace { +using SampleRate = ::webrtc::AudioProcessing::NativeRate; + +void PrepareAudioBuffer(int sample_rate_hz, + test::FuzzDataHelper* fuzz_data, + AudioBuffer* buffer) { + float* const* channels = buffer->channels_f(); + for (size_t i = 0; i < buffer->num_channels(); ++i) { + for (size_t j = 0; j < buffer->num_frames(); ++j) { + channels[i][j] = + static_cast<float>(fuzz_data->ReadOrDefaultValue<int16_t>(0)); + } + } + if (sample_rate_hz == 32000 || sample_rate_hz == 48000) { + buffer->SplitIntoFrequencyBands(); + } +} + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 200000) { + return; + } + + test::FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size)); + + constexpr int kSampleRates[] = {16000, 32000, 48000}; + const int sample_rate_hz = + static_cast<size_t>(fuzz_data.SelectOneOf(kSampleRates)); + + constexpr int kMaxNumChannels = 9; + const size_t num_render_channels = + 1 + fuzz_data.ReadOrDefaultValue<uint8_t>(0) % (kMaxNumChannels - 1); + const size_t num_capture_channels = + 1 + fuzz_data.ReadOrDefaultValue<uint8_t>(0) % (kMaxNumChannels - 1); + + EchoCanceller3 aec3(EchoCanceller3Config(), + /*multichannel_config=*/absl::nullopt, sample_rate_hz, + num_render_channels, num_capture_channels); + + AudioBuffer capture_audio(sample_rate_hz, num_capture_channels, + sample_rate_hz, num_capture_channels, + sample_rate_hz, num_capture_channels); + AudioBuffer render_audio(sample_rate_hz, num_render_channels, sample_rate_hz, + num_render_channels, sample_rate_hz, + num_render_channels); + + // Fuzz frames while there is still fuzzer data. + while (fuzz_data.BytesLeft() > 0) { + bool is_capture = fuzz_data.ReadOrDefaultValue(true); + bool level_changed = fuzz_data.ReadOrDefaultValue(true); + if (is_capture) { + PrepareAudioBuffer(sample_rate_hz, &fuzz_data, &capture_audio); + aec3.ProcessCapture(&capture_audio, level_changed); + } else { + PrepareAudioBuffer(sample_rate_hz, &fuzz_data, &render_audio); + aec3.AnalyzeRender(&render_audio); + } + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/agc_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/agc_fuzzer.cc new file mode 100644 index 0000000000..597103993e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/agc_fuzzer.cc @@ -0,0 +1,124 @@ +/* + * 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 <memory> + +#include "modules/audio_processing/audio_buffer.h" +#include "modules/audio_processing/gain_control_impl.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "rtc_base/numerics/safe_minmax.h" +#include "rtc_base/thread_annotations.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace { + +void FillAudioBuffer(size_t sample_rate_hz, + test::FuzzDataHelper* fuzz_data, + AudioBuffer* buffer) { + float* const* channels = buffer->channels_f(); + for (size_t i = 0; i < buffer->num_channels(); ++i) { + for (size_t j = 0; j < buffer->num_frames(); ++j) { + channels[i][j] = + static_cast<float>(fuzz_data->ReadOrDefaultValue<int16_t>(0)); + } + } + + if (sample_rate_hz != 16000) { + buffer->SplitIntoFrequencyBands(); + } +} + +// This function calls the GainControl functions that are overriden as private +// in GainControlInterface. +void FuzzGainControllerConfig(test::FuzzDataHelper* fuzz_data, + GainControl* gc) { + GainControl::Mode modes[] = {GainControl::Mode::kAdaptiveAnalog, + GainControl::Mode::kAdaptiveDigital, + GainControl::Mode::kFixedDigital}; + GainControl::Mode mode = fuzz_data->SelectOneOf(modes); + const bool enable_limiter = fuzz_data->ReadOrDefaultValue(true); + // The values are capped to comply with the API of webrtc::GainControl. + const int analog_level_min = + rtc::SafeClamp<int>(fuzz_data->ReadOrDefaultValue<uint16_t>(0), 0, 65534); + const int analog_level_max = + rtc::SafeClamp<int>(fuzz_data->ReadOrDefaultValue<uint16_t>(65535), + analog_level_min + 1, 65535); + const int stream_analog_level = + rtc::SafeClamp<int>(fuzz_data->ReadOrDefaultValue<uint16_t>(30000), + analog_level_min, analog_level_max); + const int gain = + rtc::SafeClamp<int>(fuzz_data->ReadOrDefaultValue<int8_t>(30), -1, 100); + const int target_level_dbfs = + rtc::SafeClamp<int>(fuzz_data->ReadOrDefaultValue<int8_t>(15), -1, 35); + + gc->set_mode(mode); + gc->enable_limiter(enable_limiter); + if (mode == GainControl::Mode::kAdaptiveAnalog) { + gc->set_analog_level_limits(analog_level_min, analog_level_max); + gc->set_stream_analog_level(stream_analog_level); + } + gc->set_compression_gain_db(gain); + gc->set_target_level_dbfs(target_level_dbfs); + + static_cast<void>(gc->mode()); + static_cast<void>(gc->analog_level_minimum()); + static_cast<void>(gc->analog_level_maximum()); + static_cast<void>(gc->stream_analog_level()); + static_cast<void>(gc->compression_gain_db()); + static_cast<void>(gc->stream_is_saturated()); + static_cast<void>(gc->target_level_dbfs()); + static_cast<void>(gc->is_limiter_enabled()); +} + +void FuzzGainController(test::FuzzDataHelper* fuzz_data, GainControlImpl* gci) { + using Rate = ::webrtc::AudioProcessing::NativeRate; + const Rate rate_kinds[] = {Rate::kSampleRate16kHz, Rate::kSampleRate32kHz, + Rate::kSampleRate48kHz}; + + const auto sample_rate_hz = + static_cast<size_t>(fuzz_data->SelectOneOf(rate_kinds)); + const size_t samples_per_frame = sample_rate_hz / 100; + const size_t num_channels = fuzz_data->ReadOrDefaultValue(true) ? 2 : 1; + + gci->Initialize(num_channels, sample_rate_hz); + FuzzGainControllerConfig(fuzz_data, gci); + + // The audio buffer is used for both capture and render. + AudioBuffer audio(sample_rate_hz, num_channels, sample_rate_hz, + num_channels, sample_rate_hz, num_channels); + + std::vector<int16_t> packed_render_audio(samples_per_frame); + + while (fuzz_data->CanReadBytes(1)) { + FillAudioBuffer(sample_rate_hz, fuzz_data, &audio); + + const bool stream_has_echo = fuzz_data->ReadOrDefaultValue(true); + gci->AnalyzeCaptureAudio(audio); + gci->ProcessCaptureAudio(&audio, stream_has_echo); + + FillAudioBuffer(sample_rate_hz, fuzz_data, &audio); + + gci->PackRenderAudioBuffer(audio, &packed_render_audio); + gci->ProcessRenderAudio(packed_render_audio); + } +} + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 200000) { + return; + } + test::FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size)); + auto gci = std::make_unique<GainControlImpl>(); + FuzzGainController(&fuzz_data, gci.get()); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_fuzzer.cc new file mode 100644 index 0000000000..1db332eeb5 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_fuzzer.cc @@ -0,0 +1,77 @@ +/* + * Copyright (c) 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. + */ + +#include "test/fuzzers/audio_decoder_fuzzer.h" + +#include <limits> + +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_decoder.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { +template <typename T, unsigned int B = sizeof(T)> +bool ParseInt(const uint8_t** data, size_t* remaining_size, T* value) { + static_assert(std::numeric_limits<T>::is_integer, "Type must be an integer."); + static_assert(sizeof(T) <= sizeof(uint64_t), + "Cannot read wider than uint64_t."); + static_assert(B <= sizeof(T), "T must be at least B bytes wide."); + if (B > *remaining_size) + return false; + uint64_t val = ByteReader<uint64_t, B>::ReadBigEndian(*data); + *data += B; + *remaining_size -= B; + *value = static_cast<T>(val); + return true; +} +} // namespace + +// This function reads two bytes from the beginning of `data`, interprets them +// as the first packet length, and reads this many bytes if available. The +// payload is inserted into the decoder, and the process continues until no more +// data is available. Either AudioDecoder::Decode or +// AudioDecoder::DecodeRedundant is used, depending on the value of +// `decode_type`. +void FuzzAudioDecoder(DecoderFunctionType decode_type, + const uint8_t* data, + size_t size, + AudioDecoder* decoder, + int sample_rate_hz, + size_t max_decoded_bytes, + int16_t* decoded) { + const uint8_t* data_ptr = data; + size_t remaining_size = size; + size_t packet_len; + constexpr size_t kMaxNumFuzzedPackets = 200; + for (size_t num_packets = 0; num_packets < kMaxNumFuzzedPackets; + ++num_packets) { + if (!(ParseInt<size_t, 2>(&data_ptr, &remaining_size, &packet_len) && + packet_len <= remaining_size)) { + break; + } + AudioDecoder::SpeechType speech_type; + switch (decode_type) { + case DecoderFunctionType::kNormalDecode: + decoder->Decode(data_ptr, packet_len, sample_rate_hz, max_decoded_bytes, + decoded, &speech_type); + break; + case DecoderFunctionType::kRedundantDecode: + decoder->DecodeRedundant(data_ptr, packet_len, sample_rate_hz, + max_decoded_bytes, decoded, &speech_type); + break; + } + data_ptr += packet_len; + remaining_size -= packet_len; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_fuzzer.h b/third_party/libwebrtc/test/fuzzers/audio_decoder_fuzzer.h new file mode 100644 index 0000000000..8c5246bb5e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_fuzzer.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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. + */ + +#ifndef TEST_FUZZERS_AUDIO_DECODER_FUZZER_H_ +#define TEST_FUZZERS_AUDIO_DECODER_FUZZER_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { + +class AudioDecoder; + +enum class DecoderFunctionType { + kNormalDecode, + kRedundantDecode, +}; + +void FuzzAudioDecoder(DecoderFunctionType decode_type, + const uint8_t* data, + size_t size, + AudioDecoder* decoder, + int sample_rate_hz, + size_t max_decoded_bytes, + int16_t* decoded); + +} // namespace webrtc + +#endif // TEST_FUZZERS_AUDIO_DECODER_FUZZER_H_ diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_g722_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_g722_fuzzer.cc new file mode 100644 index 0000000000..08599aa333 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_g722_fuzzer.cc @@ -0,0 +1,39 @@ +/* + * 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 "modules/audio_coding/codecs/g722/audio_decoder_g722.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000 || size < 1) { + return; + } + + std::unique_ptr<AudioDecoder> dec; + size_t num_channels; + if (data[0] % 2) { + dec = std::make_unique<AudioDecoderG722Impl>(); + num_channels = 1; + } else { + dec = std::make_unique<AudioDecoderG722StereoImpl>(); + num_channels = 2; + } + // Allocate a maximum output size of 100 ms. + const int sample_rate_hz = dec->SampleRateHz(); + const size_t allocated_ouput_size_samples = + sample_rate_hz / 10 * num_channels; + std::unique_ptr<int16_t[]> output = + std::make_unique<int16_t[]>(allocated_ouput_size_samples); + FuzzAudioDecoder( + DecoderFunctionType::kNormalDecode, data, size, dec.get(), sample_rate_hz, + allocated_ouput_size_samples * sizeof(int16_t), output.get()); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_ilbc_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_ilbc_fuzzer.cc new file mode 100644 index 0000000000..8548645c63 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_ilbc_fuzzer.cc @@ -0,0 +1,26 @@ +/* + * Copyright (c) 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. + */ + +#include "modules/audio_coding/codecs/ilbc/audio_decoder_ilbc.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000) { + return; + } + AudioDecoderIlbcImpl dec; + static const int kSampleRateHz = 8000; + static const size_t kAllocatedOuputSizeSamples = kSampleRateHz / 10; + int16_t output[kAllocatedOuputSizeSamples]; + FuzzAudioDecoder(DecoderFunctionType::kNormalDecode, data, size, &dec, + kSampleRateHz, sizeof(output), output); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_multistream_opus_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_multistream_opus_fuzzer.cc new file mode 100644 index 0000000000..474a1cdc43 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_multistream_opus_fuzzer.cc @@ -0,0 +1,61 @@ +/* + * 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 "api/audio_codecs/opus/audio_decoder_multi_channel_opus.h" +#include "api/audio_codecs/opus/audio_decoder_multi_channel_opus_config.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { + +AudioDecoderMultiChannelOpusConfig MakeDecoderConfig( + int num_channels, + int num_streams, + int coupled_streams, + std::vector<unsigned char> channel_mapping) { + AudioDecoderMultiChannelOpusConfig config; + config.num_channels = num_channels; + config.num_streams = num_streams; + config.coupled_streams = coupled_streams; + config.channel_mapping = channel_mapping; + return config; +} + +void FuzzOneInput(const uint8_t* data, size_t size) { + const std::vector<AudioDecoderMultiChannelOpusConfig> surround_configs = { + MakeDecoderConfig(1, 1, 0, {0}), // Mono + + MakeDecoderConfig(2, 2, 0, {0, 0}), // Copy the first (of + // 2) decoded streams + // into both output + // channel 0 and output + // channel 1. Ignore + // the 2nd decoded + // stream. + + MakeDecoderConfig(4, 2, 2, {0, 1, 2, 3}), // Quad. + MakeDecoderConfig(6, 4, 2, {0, 4, 1, 2, 3, 5}), // 5.1 + MakeDecoderConfig(8, 5, 3, {0, 6, 1, 2, 3, 4, 5, 7}) // 7.1 + }; + + const auto config = surround_configs[data[0] % surround_configs.size()]; + RTC_CHECK(config.IsOk()); + std::unique_ptr<AudioDecoder> dec = + AudioDecoderMultiChannelOpus::MakeAudioDecoder(config); + RTC_CHECK(dec); + const int kSampleRateHz = 48000; + const size_t kAllocatedOuputSizeSamples = + 4 * kSampleRateHz / 10; // 4x100 ms, 4 times the size of the output array + // for the stereo Opus codec. It should be enough + // for 8 channels. + int16_t output[kAllocatedOuputSizeSamples]; + FuzzAudioDecoder(DecoderFunctionType::kNormalDecode, data, size, dec.get(), + kSampleRateHz, sizeof(output), output); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_opus_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_opus_fuzzer.cc new file mode 100644 index 0000000000..a015f98b5b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_opus_fuzzer.cc @@ -0,0 +1,24 @@ +/* + * Copyright (c) 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. + */ + +#include "modules/audio_coding/codecs/opus/audio_decoder_opus.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + const size_t channels = (size % 2) + 1; // 1 or 2 channels. + AudioDecoderOpusImpl dec(channels); + const int kSampleRateHz = 48000; + const size_t kAllocatedOuputSizeSamples = kSampleRateHz / 10; // 100 ms. + int16_t output[kAllocatedOuputSizeSamples]; + FuzzAudioDecoder(DecoderFunctionType::kNormalDecode, data, size, &dec, + kSampleRateHz, sizeof(output), output); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_opus_redundant_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_opus_redundant_fuzzer.cc new file mode 100644 index 0000000000..efcba0f35d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_opus_redundant_fuzzer.cc @@ -0,0 +1,24 @@ +/* + * 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 "modules/audio_coding/codecs/opus/audio_decoder_opus.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + const size_t channels = (size % 2) + 1; // 1 or 2 channels. + AudioDecoderOpusImpl dec(channels); + const int kSampleRateHz = 48000; + const size_t kAllocatedOuputSizeSamples = kSampleRateHz / 10; // 100 ms. + int16_t output[kAllocatedOuputSizeSamples]; + FuzzAudioDecoder(DecoderFunctionType::kRedundantDecode, data, size, &dec, + kSampleRateHz, sizeof(output), output); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_pcm16b_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_pcm16b_fuzzer.cc new file mode 100644 index 0000000000..6e5d6e2190 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_pcm16b_fuzzer.cc @@ -0,0 +1,56 @@ +/* + * 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 <memory> + +#include "modules/audio_coding/codecs/pcm16b/audio_decoder_pcm16b.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000 || size < 2) { + return; + } + + int sample_rate_hz; + switch (data[0] % 4) { + case 0: + sample_rate_hz = 8000; + break; + case 1: + sample_rate_hz = 16000; + break; + case 2: + sample_rate_hz = 32000; + break; + case 3: + sample_rate_hz = 48000; + break; + default: + RTC_DCHECK_NOTREACHED(); + return; + } + const size_t num_channels = data[1] % 16 + 1; + + // Two first bytes of the data are used. Move forward. + data += 2; + size -= 2; + + AudioDecoderPcm16B dec(sample_rate_hz, num_channels); + // Allocate a maximum output size of 100 ms. + const size_t allocated_ouput_size_samples = + sample_rate_hz * num_channels / 10; + std::unique_ptr<int16_t[]> output = + std::make_unique<int16_t[]>(allocated_ouput_size_samples); + FuzzAudioDecoder( + DecoderFunctionType::kNormalDecode, data, size, &dec, sample_rate_hz, + allocated_ouput_size_samples * sizeof(int16_t), output.get()); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_decoder_pcm_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_decoder_pcm_fuzzer.cc new file mode 100644 index 0000000000..dbfcaf9976 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_decoder_pcm_fuzzer.cc @@ -0,0 +1,45 @@ +/* + * 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 <memory> + +#include "modules/audio_coding/codecs/g711/audio_decoder_pcm.h" +#include "test/fuzzers/audio_decoder_fuzzer.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000 || size < 2) { + return; + } + + const size_t num_channels = data[0] % 16 + 1; + + std::unique_ptr<AudioDecoder> dec; + if (data[1] % 2) { + dec = std::make_unique<AudioDecoderPcmU>(num_channels); + } else { + dec = std::make_unique<AudioDecoderPcmA>(num_channels); + } + + // Two first bytes of the data are used. Move forward. + data += 2; + size -= 2; + + // Allocate a maximum output size of 100 ms. + const size_t allocated_ouput_size_samples = + dec->SampleRateHz() * num_channels / 10; + std::unique_ptr<int16_t[]> output = + std::make_unique<int16_t[]>(allocated_ouput_size_samples); + FuzzAudioDecoder(DecoderFunctionType::kNormalDecode, data, size, dec.get(), + dec->SampleRateHz(), + allocated_ouput_size_samples * sizeof(int16_t), + output.get()); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_encoder_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_encoder_fuzzer.cc new file mode 100644 index 0000000000..837c26df56 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_encoder_fuzzer.cc @@ -0,0 +1,53 @@ +/* + * 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/fuzzers/audio_encoder_fuzzer.h" + +#include <cstring> + +#include "rtc_base/buffer.h" +#include "rtc_base/checks.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { + +// This function reads bytes from `data_view`, interprets them as RTP timestamp +// and input samples, and sends them for encoding. The process continues until +// no more data is available. +void FuzzAudioEncoder(rtc::ArrayView<const uint8_t> data_view, + std::unique_ptr<AudioEncoder> encoder) { + test::FuzzDataHelper data(data_view); + const size_t block_size_samples = + encoder->SampleRateHz() / 100 * encoder->NumChannels(); + const size_t block_size_bytes = block_size_samples * sizeof(int16_t); + if (data_view.size() / block_size_bytes > 1000) { + // If the size of the fuzzer data is more than 1000 input blocks (i.e., more + // than 10 seconds), then don't fuzz at all for the fear of timing out. + return; + } + + rtc::BufferT<int16_t> input_aligned(block_size_samples); + rtc::Buffer encoded; + + // Each round in the loop below will need one block of samples + a 32-bit + // timestamp from the fuzzer input. + const size_t bytes_to_read = block_size_bytes + sizeof(uint32_t); + while (data.CanReadBytes(bytes_to_read)) { + const uint32_t timestamp = data.Read<uint32_t>(); + auto byte_array = data.ReadByteArray(block_size_bytes); + // Align the data by copying to another array. + RTC_DCHECK_EQ(input_aligned.size() * sizeof(int16_t), + byte_array.size() * sizeof(uint8_t)); + memcpy(input_aligned.data(), byte_array.data(), byte_array.size()); + auto info = encoder->Encode(timestamp, input_aligned, &encoded); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_encoder_fuzzer.h b/third_party/libwebrtc/test/fuzzers/audio_encoder_fuzzer.h new file mode 100644 index 0000000000..0c879df4d3 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_encoder_fuzzer.h @@ -0,0 +1,26 @@ +/* + * 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_FUZZERS_AUDIO_ENCODER_FUZZER_H_ +#define TEST_FUZZERS_AUDIO_ENCODER_FUZZER_H_ + +#include <memory> + +#include "api/array_view.h" +#include "api/audio_codecs/audio_encoder.h" + +namespace webrtc { + +void FuzzAudioEncoder(rtc::ArrayView<const uint8_t> data_view, + std::unique_ptr<AudioEncoder> encoder); + +} // namespace webrtc + +#endif // TEST_FUZZERS_AUDIO_ENCODER_FUZZER_H_ diff --git a/third_party/libwebrtc/test/fuzzers/audio_encoder_opus_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_encoder_opus_fuzzer.cc new file mode 100644 index 0000000000..d67e6d6067 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_encoder_opus_fuzzer.cc @@ -0,0 +1,27 @@ +/* + * 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 "api/audio_codecs/opus/audio_encoder_opus.h" +#include "rtc_base/checks.h" +#include "test/fuzzers/audio_encoder_fuzzer.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + AudioEncoderOpus::Config config; + config.frame_size_ms = 20; + RTC_CHECK(config.IsOk()); + constexpr int kPayloadType = 100; + FuzzAudioEncoder( + /*data_view=*/{data, size}, + /*encoder=*/AudioEncoderOpus::MakeAudioEncoder(config, kPayloadType)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc new file mode 100644 index 0000000000..afcb4318f9 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_processing_configs_fuzzer.cc @@ -0,0 +1,147 @@ +/* + * 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 <bitset> +#include <string> + +#include "absl/memory/memory.h" +#include "api/audio/echo_canceller3_factory.h" +#include "api/audio/echo_detector_creator.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "modules/audio_processing/aec_dump/aec_dump_factory.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "modules/audio_processing/test/audio_processing_builder_for_testing.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/numerics/safe_minmax.h" +#include "rtc_base/task_queue.h" +#include "system_wrappers/include/field_trial.h" +#include "test/fuzzers/audio_processing_fuzzer_helper.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace { + +const std::string kFieldTrialNames[] = { + "WebRTC-Audio-Agc2ForceExtraSaturationMargin", + "WebRTC-Audio-Agc2ForceInitialSaturationMargin", + "WebRTC-Aec3MinErleDuringOnsetsKillSwitch", + "WebRTC-Aec3ShortHeadroomKillSwitch", +}; + +rtc::scoped_refptr<AudioProcessing> CreateApm(test::FuzzDataHelper* fuzz_data, + std::string* field_trial_string, + rtc::TaskQueue* worker_queue) { + // Parse boolean values for optionally enabling different + // configurable public components of APM. + bool use_ts = fuzz_data->ReadOrDefaultValue(true); + bool use_red = fuzz_data->ReadOrDefaultValue(true); + bool use_hpf = fuzz_data->ReadOrDefaultValue(true); + bool use_aec3 = fuzz_data->ReadOrDefaultValue(true); + bool use_aec = fuzz_data->ReadOrDefaultValue(true); + bool use_aecm = fuzz_data->ReadOrDefaultValue(true); + bool use_agc = fuzz_data->ReadOrDefaultValue(true); + bool use_ns = fuzz_data->ReadOrDefaultValue(true); + bool use_agc_limiter = fuzz_data->ReadOrDefaultValue(true); + bool use_agc2 = fuzz_data->ReadOrDefaultValue(true); + bool use_agc2_adaptive_digital = fuzz_data->ReadOrDefaultValue(true); + + // Read a gain value supported by GainController2::Validate(). + const float gain_controller2_gain_db = + fuzz_data->ReadOrDefaultValue<uint8_t>(0) % 50; + + constexpr size_t kNumFieldTrials = arraysize(kFieldTrialNames); + // Verify that the read data type has enough bits to fuzz the field trials. + using FieldTrialBitmaskType = uint64_t; + static_assert(kNumFieldTrials <= sizeof(FieldTrialBitmaskType) * 8, + "FieldTrialBitmaskType is not large enough."); + std::bitset<kNumFieldTrials> field_trial_bitmask( + fuzz_data->ReadOrDefaultValue<FieldTrialBitmaskType>(0)); + for (size_t i = 0; i < kNumFieldTrials; ++i) { + if (field_trial_bitmask[i]) { + *field_trial_string += kFieldTrialNames[i] + "/Enabled/"; + } + } + field_trial::InitFieldTrialsFromString(field_trial_string->c_str()); + + // Ignore a few bytes. Bytes from this segment will be used for + // future config flag changes. We assume 40 bytes is enough for + // configuring the APM. + constexpr size_t kSizeOfConfigSegment = 40; + RTC_DCHECK(kSizeOfConfigSegment >= fuzz_data->BytesRead()); + static_cast<void>( + fuzz_data->ReadByteArray(kSizeOfConfigSegment - fuzz_data->BytesRead())); + + // Filter out incompatible settings that lead to CHECK failures. + if ((use_aecm && use_aec) || // These settings cause CHECK failure. + (use_aecm && use_aec3 && use_ns) // These settings trigger webrtc:9489. + ) { + return nullptr; + } + + std::unique_ptr<EchoControlFactory> echo_control_factory; + if (use_aec3) { + echo_control_factory.reset(new EchoCanceller3Factory()); + } + + webrtc::AudioProcessing::Config apm_config; + apm_config.pipeline.multi_channel_render = true; + apm_config.pipeline.multi_channel_capture = true; + apm_config.echo_canceller.enabled = use_aec || use_aecm; + apm_config.echo_canceller.mobile_mode = use_aecm; + apm_config.high_pass_filter.enabled = use_hpf; + apm_config.gain_controller1.enabled = use_agc; + apm_config.gain_controller1.enable_limiter = use_agc_limiter; + apm_config.gain_controller2.enabled = use_agc2; + apm_config.gain_controller2.fixed_digital.gain_db = gain_controller2_gain_db; + apm_config.gain_controller2.adaptive_digital.enabled = + use_agc2_adaptive_digital; + apm_config.noise_suppression.enabled = use_ns; + apm_config.transient_suppression.enabled = use_ts; + + rtc::scoped_refptr<AudioProcessing> apm = + AudioProcessingBuilderForTesting() + .SetEchoControlFactory(std::move(echo_control_factory)) + .SetEchoDetector(use_red ? CreateEchoDetector() : nullptr) + .SetConfig(apm_config) + .Create(); + +#ifdef WEBRTC_LINUX + apm->AttachAecDump(AecDumpFactory::Create("/dev/null", -1, worker_queue)); +#endif + + return apm; +} + +TaskQueueFactory* GetTaskQueueFactory() { + static TaskQueueFactory* const factory = + CreateDefaultTaskQueueFactory().release(); + return factory; +} + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 400000) { + return; + } + test::FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size)); + // This string must be in scope during execution, according to documentation + // for field_trial.h. Hence it's created here and not in CreateApm. + std::string field_trial_string = ""; + + rtc::TaskQueue worker_queue(GetTaskQueueFactory()->CreateTaskQueue( + "rtc-low-prio", rtc::TaskQueue::Priority::LOW)); + auto apm = CreateApm(&fuzz_data, &field_trial_string, &worker_queue); + + if (apm) { + FuzzAudioProcessing(&fuzz_data, std::move(apm)); + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_processing_fuzzer_helper.cc b/third_party/libwebrtc/test/fuzzers/audio_processing_fuzzer_helper.cc new file mode 100644 index 0000000000..5252918d77 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_processing_fuzzer_helper.cc @@ -0,0 +1,143 @@ +/* + * 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 "test/fuzzers/audio_processing_fuzzer_helper.h" + +#include <algorithm> +#include <array> +#include <cmath> +#include <limits> + +#include "api/audio/audio_frame.h" +#include "modules/audio_processing/include/audio_frame_proxies.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { +bool ValidForApm(float x) { + return std::isfinite(x) && -1.0f <= x && x <= 1.0f; +} + +void GenerateFloatFrame(test::FuzzDataHelper* fuzz_data, + int input_rate, + int num_channels, + float* const* float_frames) { + const int samples_per_input_channel = + AudioProcessing::GetFrameSize(input_rate); + RTC_DCHECK_LE(samples_per_input_channel, 480); + for (int i = 0; i < num_channels; ++i) { + std::fill(float_frames[i], float_frames[i] + samples_per_input_channel, 0); + const size_t read_bytes = sizeof(float) * samples_per_input_channel; + if (fuzz_data->CanReadBytes(read_bytes)) { + rtc::ArrayView<const uint8_t> byte_array = + fuzz_data->ReadByteArray(read_bytes); + memmove(float_frames[i], byte_array.begin(), read_bytes); + } + + // Sanitize input. + for (int j = 0; j < samples_per_input_channel; ++j) { + if (!ValidForApm(float_frames[i][j])) { + float_frames[i][j] = 0.f; + } + } + } +} + +void GenerateFixedFrame(test::FuzzDataHelper* fuzz_data, + int input_rate, + int num_channels, + AudioFrame* fixed_frame) { + const int samples_per_input_channel = + AudioProcessing::GetFrameSize(input_rate); + + fixed_frame->samples_per_channel_ = samples_per_input_channel; + fixed_frame->sample_rate_hz_ = input_rate; + fixed_frame->num_channels_ = num_channels; + + RTC_DCHECK_LE(samples_per_input_channel * num_channels, + AudioFrame::kMaxDataSizeSamples); + for (int i = 0; i < samples_per_input_channel * num_channels; ++i) { + fixed_frame->mutable_data()[i] = fuzz_data->ReadOrDefaultValue<int16_t>(0); + } +} +} // namespace + +void FuzzAudioProcessing(test::FuzzDataHelper* fuzz_data, + rtc::scoped_refptr<AudioProcessing> apm) { + AudioFrame fixed_frame; + // Normal usage is up to 8 channels. Allowing to fuzz one beyond this allows + // us to catch implicit assumptions about normal usage. + constexpr int kMaxNumChannels = 9; + std::array<std::array<float, 480>, kMaxNumChannels> float_frames; + std::array<float*, kMaxNumChannels> float_frame_ptrs; + for (int i = 0; i < kMaxNumChannels; ++i) { + float_frame_ptrs[i] = float_frames[i].data(); + } + float* const* ptr_to_float_frames = &float_frame_ptrs[0]; + + constexpr int kSampleRatesHz[] = {8000, 11025, 16000, 22050, + 32000, 44100, 48000}; + + // We may run out of fuzz data in the middle of a loop iteration. In + // that case, default values will be used for the rest of that + // iteration. + while (fuzz_data->CanReadBytes(1)) { + const bool is_float = fuzz_data->ReadOrDefaultValue(true); + // Decide input/output rate for this iteration. + const int input_rate = fuzz_data->SelectOneOf(kSampleRatesHz); + const int output_rate = fuzz_data->SelectOneOf(kSampleRatesHz); + + const uint8_t stream_delay = fuzz_data->ReadOrDefaultValue<uint8_t>(0); + // API call needed for AECM to run. + apm->set_stream_delay_ms(stream_delay); + + const bool key_pressed = fuzz_data->ReadOrDefaultValue(true); + apm->set_stream_key_pressed(key_pressed); + + // Make the APM call depending on capture/render mode and float / + // fix interface. + const bool is_capture = fuzz_data->ReadOrDefaultValue(true); + + // Fill the arrays with audio samples from the data. + int apm_return_code = AudioProcessing::Error::kNoError; + if (is_float) { + const int num_channels = + fuzz_data->ReadOrDefaultValue<uint8_t>(1) % kMaxNumChannels; + + GenerateFloatFrame(fuzz_data, input_rate, num_channels, + ptr_to_float_frames); + if (is_capture) { + apm_return_code = apm->ProcessStream( + ptr_to_float_frames, StreamConfig(input_rate, num_channels), + StreamConfig(output_rate, num_channels), ptr_to_float_frames); + } else { + apm_return_code = apm->ProcessReverseStream( + ptr_to_float_frames, StreamConfig(input_rate, num_channels), + StreamConfig(output_rate, num_channels), ptr_to_float_frames); + } + } else { + const int num_channels = fuzz_data->ReadOrDefaultValue(true) ? 2 : 1; + GenerateFixedFrame(fuzz_data, input_rate, num_channels, &fixed_frame); + + if (is_capture) { + apm_return_code = ProcessAudioFrame(apm.get(), &fixed_frame); + } else { + apm_return_code = ProcessReverseAudioFrame(apm.get(), &fixed_frame); + } + } + + // Cover stats gathering code paths. + static_cast<void>(apm->GetStatistics(true /*has_remote_tracks*/)); + + RTC_DCHECK_NE(apm_return_code, AudioProcessing::kBadDataLengthError); + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/audio_processing_fuzzer_helper.h b/third_party/libwebrtc/test/fuzzers/audio_processing_fuzzer_helper.h new file mode 100644 index 0000000000..a604db8cef --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_processing_fuzzer_helper.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef TEST_FUZZERS_AUDIO_PROCESSING_FUZZER_HELPER_H_ +#define TEST_FUZZERS_AUDIO_PROCESSING_FUZZER_HELPER_H_ + +#include <memory> + +#include "modules/audio_processing/include/audio_processing.h" +#include "test/fuzzers/fuzz_data_helper.h" +namespace webrtc { + +void FuzzAudioProcessing(test::FuzzDataHelper* fuzz_data, + rtc::scoped_refptr<AudioProcessing> apm); + +} // namespace webrtc + +#endif // TEST_FUZZERS_AUDIO_PROCESSING_FUZZER_HELPER_H_ diff --git a/third_party/libwebrtc/test/fuzzers/audio_processing_sample_rate_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/audio_processing_sample_rate_fuzzer.cc new file mode 100644 index 0000000000..ca3946988c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/audio_processing_sample_rate_fuzzer.cc @@ -0,0 +1,162 @@ +/* + * 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 <algorithm> +#include <array> +#include <cmath> +#include <limits> + +#include "modules/audio_processing/include/audio_processing.h" +#include "modules/audio_processing/test/audio_processing_builder_for_testing.h" +#include "rtc_base/checks.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace { +constexpr int kMaxNumChannels = 2; +// APM supported max rate is 384000 Hz, using a limit slightly above lets the +// fuzzer exercise the handling of too high rates. +constexpr int kMaxSampleRateHz = 400000; +constexpr int kMaxSamplesPerChannel = kMaxSampleRateHz / 100; + +void GenerateFloatFrame(test::FuzzDataHelper& fuzz_data, + int input_rate, + int num_channels, + float* const* float_frames) { + const int samples_per_input_channel = + AudioProcessing::GetFrameSize(input_rate); + RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel); + for (int i = 0; i < num_channels; ++i) { + float channel_value; + fuzz_data.CopyTo<float>(&channel_value); + std::fill(float_frames[i], float_frames[i] + samples_per_input_channel, + channel_value); + } +} + +void GenerateFixedFrame(test::FuzzDataHelper& fuzz_data, + int input_rate, + int num_channels, + int16_t* fixed_frames) { + const int samples_per_input_channel = + AudioProcessing::GetFrameSize(input_rate); + RTC_DCHECK_LE(samples_per_input_channel, kMaxSamplesPerChannel); + // Write interleaved samples. + for (int ch = 0; ch < num_channels; ++ch) { + const int16_t channel_value = fuzz_data.ReadOrDefaultValue<int16_t>(0); + for (int i = ch; i < samples_per_input_channel * num_channels; + i += num_channels) { + fixed_frames[i] = channel_value; + } + } +} + +// No-op processor used to influence APM input/output pipeline decisions based +// on what submodules are present. +class NoopCustomProcessing : public CustomProcessing { + public: + NoopCustomProcessing() {} + ~NoopCustomProcessing() override {} + void Initialize(int sample_rate_hz, int num_channels) override {} + void Process(AudioBuffer* audio) override {} + std::string ToString() const override { return ""; } + void SetRuntimeSetting(AudioProcessing::RuntimeSetting setting) override {} +}; +} // namespace + +// This fuzzer is directed at fuzzing unexpected input and output sample rates +// of APM. For example, the sample rate 22050 Hz is processed by APM in frames +// of floor(22050/100) = 220 samples. This is not exactly 10 ms of audio +// content, and may break assumptions commonly made on the APM frame size. +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 100) { + return; + } + test::FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size)); + + std::unique_ptr<CustomProcessing> capture_processor = + fuzz_data.ReadOrDefaultValue(true) + ? std::make_unique<NoopCustomProcessing>() + : nullptr; + std::unique_ptr<CustomProcessing> render_processor = + fuzz_data.ReadOrDefaultValue(true) + ? std::make_unique<NoopCustomProcessing>() + : nullptr; + rtc::scoped_refptr<AudioProcessing> apm = + AudioProcessingBuilderForTesting() + .SetConfig({.pipeline = {.multi_channel_render = true, + .multi_channel_capture = true}}) + .SetCapturePostProcessing(std::move(capture_processor)) + .SetRenderPreProcessing(std::move(render_processor)) + .Create(); + RTC_DCHECK(apm); + + std::array<int16_t, kMaxSamplesPerChannel * kMaxNumChannels> fixed_frame; + std::array<std::array<float, kMaxSamplesPerChannel>, kMaxNumChannels> + float_frames; + std::array<float*, kMaxNumChannels> float_frame_ptrs; + for (int i = 0; i < kMaxNumChannels; ++i) { + float_frame_ptrs[i] = float_frames[i].data(); + } + float* const* ptr_to_float_frames = &float_frame_ptrs[0]; + + // Choose whether to fuzz the float or int16_t interfaces of APM. + const bool is_float = fuzz_data.ReadOrDefaultValue(true); + + // We may run out of fuzz data in the middle of a loop iteration. In + // that case, default values will be used for the rest of that + // iteration. + while (fuzz_data.CanReadBytes(1)) { + // Decide input/output rate for this iteration. + const int input_rate = static_cast<int>( + fuzz_data.ReadOrDefaultValue<size_t>(8000) % kMaxSampleRateHz); + const int output_rate = static_cast<int>( + fuzz_data.ReadOrDefaultValue<size_t>(8000) % kMaxSampleRateHz); + const int num_channels = fuzz_data.ReadOrDefaultValue(true) ? 2 : 1; + + // Since render and capture calls have slightly different reinitialization + // procedures, we let the fuzzer choose the order. + const bool is_capture = fuzz_data.ReadOrDefaultValue(true); + + int apm_return_code = AudioProcessing::Error::kNoError; + if (is_float) { + GenerateFloatFrame(fuzz_data, input_rate, num_channels, + ptr_to_float_frames); + + if (is_capture) { + apm_return_code = apm->ProcessStream( + ptr_to_float_frames, StreamConfig(input_rate, num_channels), + StreamConfig(output_rate, num_channels), ptr_to_float_frames); + } else { + apm_return_code = apm->ProcessReverseStream( + ptr_to_float_frames, StreamConfig(input_rate, num_channels), + StreamConfig(output_rate, num_channels), ptr_to_float_frames); + } + } else { + GenerateFixedFrame(fuzz_data, input_rate, num_channels, + fixed_frame.data()); + + if (is_capture) { + apm_return_code = apm->ProcessStream( + fixed_frame.data(), StreamConfig(input_rate, num_channels), + StreamConfig(output_rate, num_channels), fixed_frame.data()); + } else { + apm_return_code = apm->ProcessReverseStream( + fixed_frame.data(), StreamConfig(input_rate, num_channels), + StreamConfig(output_rate, num_channels), fixed_frame.data()); + } + } + // APM may flag an error on unsupported audio formats, but should not crash. + RTC_DCHECK(apm_return_code == AudioProcessing::kNoError || + apm_return_code == AudioProcessing::kBadSampleRateError); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/comfort_noise_decoder_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/comfort_noise_decoder_fuzzer.cc new file mode 100644 index 0000000000..7f44af99fb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/comfort_noise_decoder_fuzzer.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 <algorithm> + +#include "api/array_view.h" +#include "modules/audio_coding/codecs/cng/webrtc_cng.h" +#include "rtc_base/buffer.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace test { +namespace { + +void FuzzOneInputTest(rtc::ArrayView<const uint8_t> data) { + FuzzDataHelper fuzz_data(data); + ComfortNoiseDecoder cng_decoder; + + while (1) { + if (!fuzz_data.CanReadBytes(1)) + break; + const uint8_t sid_frame_len = fuzz_data.Read<uint8_t>(); + auto sid_frame = fuzz_data.ReadByteArray(sid_frame_len); + if (sid_frame.empty()) + break; + cng_decoder.UpdateSid(sid_frame); + if (!fuzz_data.CanReadBytes(3)) + break; + constexpr bool kTrueOrFalse[] = {true, false}; + const bool new_period = fuzz_data.SelectOneOf(kTrueOrFalse); + constexpr size_t kOutputSizes[] = {80, 160, 320, 480}; + const size_t output_size = fuzz_data.SelectOneOf(kOutputSizes); + const size_t num_generate_calls = + std::min(fuzz_data.Read<uint8_t>(), static_cast<uint8_t>(17)); + rtc::BufferT<int16_t> output(output_size); + for (size_t i = 0; i < num_generate_calls; ++i) { + cng_decoder.Generate(output, new_period); + } + } +} + +} // namespace +} // namespace test + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 5000) { + return; + } + test::FuzzOneInputTest(rtc::ArrayView<const uint8_t>(data, size)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json new file mode 100644 index 0000000000..59b5db9446 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_fec_config.json @@ -0,0 +1,151 @@ +[ + { + "decoders" : [ + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "1" + }, + { + "profile-level-id" : "42001f" + } + ], + "payload_name" : "H264", + "payload_type" : 100 + }, + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "0" + }, + { + "profile-level-id" : "42001f" + } + ], + "payload_name" : "H264", + "payload_type" : 102 + }, + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "1" + }, + { + "profile-level-id" : "42e01f" + } + ], + "payload_name" : "H264", + "payload_type" : 127 + }, + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "0" + }, + { + "profile-level-id" : "42e01f" + } + ], + "payload_name" : "H264", + "payload_type" : 125 + }, + { + "codec_params" : [], + "payload_name" : "VP8", + "payload_type" : 96 + }, + { + "codec_params" : [], + "payload_name" : "VP9", + "payload_type" : 98 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [ + { + "encrypt" : false, + "id" : 5, + "uri" : "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + }, + { + "encrypt" : false, + "id" : 3, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" + }, + { + "encrypt" : false, + "id" : 6, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" + }, + { + "encrypt" : false, + "id" : 7, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type" + }, + { + "encrypt" : false, + "id" : 8, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/video-timing" + }, + { + "encrypt" : false, + "id" : 4, + "uri" : "urn:3gpp:video-orientation" + }, + { + "encrypt" : false, + "id" : 2, + "uri" : "urn:ietf:params:rtp-hdrext:toffset" + } + ], + "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : -1, + "remb" : true, + "remote_ssrc" : 2736493666, + "rtcp_mode" : "RtcpMode::kReducedSize", + "rtx_payload_types" : [ + { + "97" : 96 + }, + { + "99" : 98 + }, + { + "101" : 100 + }, + { + "107" : 125 + }, + { + "122" : 127 + }, + { + "123" : 102 + } + ], + "rtx_ssrc" : 885796452, + "transport_cc" : true, + "ulpfec_payload_type" : -1 + }, + "target_delay_ms" : 0 + } +] diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json new file mode 100644 index 0000000000..9cb5bd767b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_non_interleaved_config.json @@ -0,0 +1,66 @@ +[ + { + "decoders" : [ + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "0" + }, + { + "packetization-mode" : "0" + }, + { + "profile-level-id" : "42001f" + } + ], + "payload_name" : "H264", + "payload_type" : 100 + }, + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "1" + }, + { + "profile-level-id" : "42e01f" + } + ], + "payload_name" : "H264", + "payload_type" : 102 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [], + "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : 125, + "remb" : true, + "remote_ssrc" : 1989790381, + "rtcp_mode" : "RtcpMode::kReducedSize", + "rtx_payload_types" : [ + { + "101" : 100 + }, + { + "122" : 125 + }, + { + "123" : 127 + } + ], + "rtx_ssrc" : 1406083315, + "transport_cc" : true, + "ulpfec_payload_type" : -1 + }, + "target_delay_ms" : 0 + } +] diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json new file mode 100644 index 0000000000..f3ae6bbafc --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/h264_single_nal_config.json @@ -0,0 +1,66 @@ +[ + { + "decoders" : [ + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "1" + }, + { + "profile-level-id" : "42001f" + } + ], + "payload_name" : "H264", + "payload_type" : 100 + }, + { + "codec_params" : [ + { + "level-asymmetry-allowed" : "1" + }, + { + "packetization-mode" : "1" + }, + { + "profile-level-id" : "42e01f" + } + ], + "payload_name" : "H264", + "payload_type" : 102 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [], + "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : 125, + "remb" : true, + "remote_ssrc" : 1989790381, + "rtcp_mode" : "RtcpMode::kReducedSize", + "rtx_payload_types" : [ + { + "101" : 100 + }, + { + "122" : 125 + }, + { + "123" : 127 + } + ], + "rtx_ssrc" : 1406083315, + "transport_cc" : true, + "ulpfec_payload_type" : -1 + }, + "target_delay_ms" : 0 + } +] diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json new file mode 100644 index 0000000000..0a5eef8b09 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp8_config.json @@ -0,0 +1,29 @@ +[{ + "decoders" : [ + { + "codec_params" : [], + "payload_name" : "VP8", + "payload_type" : 125 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [], + "local_ssrc" : 7331, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : -1, + "remb" : true, + "remote_ssrc" : 1337, + "rtcp_mode" : "RtcpMode::kCompound", + "rtx_payload_types" : [], + "rtx_ssrc" : 100, + "transport_cc" : true, + "ulpfec_payload_type" : -1 + }, + "target_delay_ms" : 0 +}] diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json new file mode 100644 index 0000000000..3d2b66301d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp8_fec_config.json @@ -0,0 +1,73 @@ +[ + { + "decoders" : [ + { + "codec_params" : [], + "payload_name" : "VP8", + "payload_type" : 96 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [ + { + "encrypt" : false, + "id" : 5, + "uri" : "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + }, + { + "encrypt" : false, + "id" : 3, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" + }, + { + "encrypt" : false, + "id" : 6, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" + }, + { + "encrypt" : false, + "id" : 7, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type" + }, + { + "encrypt" : false, + "id" : 8, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/video-timing" + }, + { + "encrypt" : false, + "id" : 4, + "uri" : "urn:3gpp:video-orientation" + }, + { + "encrypt" : false, + "id" : 2, + "uri" : "urn:ietf:params:rtp-hdrext:toffset" + } + ], + "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : -1, + "remb" : true, + "remote_ssrc" : 2672243158, + "rtcp_mode" : "RtcpMode::kReducedSize", + "rtx_payload_types" : [ + { + "97" : 96 + }, + { + "99" : 98 + } + ], + "rtx_ssrc" : 1807563126, + "transport_cc" : true + }, + "target_delay_ms" : 0 + } +] diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json new file mode 100644 index 0000000000..a9c88a2402 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp9_config.json @@ -0,0 +1,29 @@ +[{ + "decoders" : [ + { + "codec_params" : [], + "payload_name" : "VP9", + "payload_type" : 124 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [], + "local_ssrc" : 7331, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : -1, + "remb" : true, + "remote_ssrc" : 1337, + "rtcp_mode" : "RtcpMode::kCompound", + "rtx_payload_types" : [], + "rtx_ssrc" : 100, + "transport_cc" : true, + "ulpfec_payload_type" : -1 + }, + "target_delay_ms" : 0 +}] diff --git a/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json new file mode 100644 index 0000000000..d089aa8fec --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/configs/replay_packet_fuzzer/vp9_fec_config.json @@ -0,0 +1,79 @@ +[ + { + "decoders" : [ + { + "codec_params" : [], + "payload_name" : "VP9", + "payload_type" : 98 + }, + { + "codec_params" : [], + "payload_name" : "VP8", + "payload_type" : 96 + } + ], + "render_delay_ms" : 10, + "rtp" : { + "extensions" : [ + { + "encrypt" : false, + "id" : 5, + "uri" : "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + }, + { + "encrypt" : false, + "id" : 3, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" + }, + { + "encrypt" : false, + "id" : 6, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" + }, + { + "encrypt" : false, + "id" : 7, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type" + }, + { + "encrypt" : false, + "id" : 8, + "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/video-timing" + }, + { + "encrypt" : false, + "id" : 4, + "uri" : "urn:3gpp:video-orientation" + }, + { + "encrypt" : false, + "id" : 2, + "uri" : "urn:ietf:params:rtp-hdrext:toffset" + } + ], + "local_ssrc" : 1, + "lntf" : { + "enabled": false, + }, + "nack" : { + "rtp_history_ms" : 1000 + }, + "red_payload_type" : -1, + "remb" : true, + "remote_ssrc" : 2678204013, + "rtcp_mode" : "RtcpMode::kReducedSize", + "rtx_payload_types" : [ + { + "97" : 96 + }, + { + "99" : 98 + } + ], + "rtx_ssrc" : 1110725867, + "transport_cc" : true, + "ulpfec_payload_type" : -1 + }, + "target_delay_ms" : 0 + } +] diff --git a/third_party/libwebrtc/test/fuzzers/congestion_controller_feedback_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/congestion_controller_feedback_fuzzer.cc new file mode 100644 index 0000000000..e266b85fa4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/congestion_controller_feedback_fuzzer.cc @@ -0,0 +1,52 @@ +/* + * Copyright (c) 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. + */ + +#include "absl/functional/bind_front.h" +#include "modules/congestion_controller/include/receive_side_congestion_controller.h" +#include "modules/pacing/packet_router.h" +#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + size_t i = 0; + if (size < sizeof(int64_t) + sizeof(uint8_t) + sizeof(uint32_t)) + return; + SimulatedClock clock(data[i++]); + PacketRouter packet_router; + ReceiveSideCongestionController cc( + &clock, + absl::bind_front(&PacketRouter::SendCombinedRtcpPacket, &packet_router), + absl::bind_front(&PacketRouter::SendRemb, &packet_router), nullptr); + RTPHeader header; + header.ssrc = ByteReader<uint32_t>::ReadBigEndian(&data[i]); + i += sizeof(uint32_t); + header.extension.hasTransportSequenceNumber = true; + int64_t arrival_time_ms = std::min<int64_t>( + std::max<int64_t>(ByteReader<int64_t>::ReadBigEndian(&data[i]), 0), + std::numeric_limits<int64_t>::max() / 2); + i += sizeof(int64_t); + const size_t kMinPacketSize = + sizeof(size_t) + sizeof(uint16_t) + sizeof(uint8_t); + while (i + kMinPacketSize < size) { + size_t payload_size = ByteReader<size_t>::ReadBigEndian(&data[i]) % 1500; + i += sizeof(size_t); + header.extension.transportSequenceNumber = + ByteReader<uint16_t>::ReadBigEndian(&data[i]); + i += sizeof(uint16_t); + cc.OnReceivedPacket(arrival_time_ms, payload_size, header); + clock.AdvanceTimeMilliseconds(5); + arrival_time_ms += ByteReader<uint8_t>::ReadBigEndian(&data[i]); + i += sizeof(uint8_t); + cc.MaybeProcess(); + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/corpora/README b/third_party/libwebrtc/test/fuzzers/corpora/README new file mode 100644 index 0000000000..cc87025ff6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/README @@ -0,0 +1,37 @@ +This is a collection of corpora for various WebRTC fuzzers. To use +them, the gn targets define seed_corpus=$corpus_dir, which causes the +ClusterFuzz upload bot to bundle $corpus_dir and upload it. + +The format is simple: one file per test case. Specific notes are +included below. + +### SDP ### +This corpus was initially assembled manually from the following +sources: + + - curl --silent https://www.ietf.org/rfc/rfc4317.txt | grep '^[ a-z]*=[^=]*$' | sed 's/^[[:space:]]*//' | awk -v RS='(^|\n)v=' '/./ {print "v="$0 > NR".sdp"}' + - all the SDPs used in the parser unit tests + - some manually gathered SDPs from Firefox and Opera + +The SDP tokens come from: + + - grep "^static const " webrtc/api/webrtcsdp.cc | cut -d'=' -f2 | cut -d ';' -f1 | tr -d '"' | tr -d "'" | tr -d ' ' | sort -u | grep -v '^(\n|\r|\r\n)$|^$' | sed -e 's/^/"/' -e 's/$/"/' | tail -n +2 + +### STUN ### +This corpus was initially assembled from the STUN unit tests, together +with a crash that it found relatively quickly. + +### RT(C)P ### +This corpus was initially assembled from the unittests. RTCP was +minimised first. + +There is also rt(c?)p-corpus-with-extra-byte, in which each sample is +prefixed by the byte 0xff. Some of the rtp fuzzers need to decide +which header extensions to enable, and the first byte of the fuzz data +is used for this. + +### PseudoTCP ### +Very small corpus minimised from the unit tests. + +### SCTP ### +This corpus was extracted from a few manually recorder wireshark dumps. diff --git a/third_party/libwebrtc/test/fuzzers/corpora/aec3-config-json-corpus/default.json b/third_party/libwebrtc/test/fuzzers/corpora/aec3-config-json-corpus/default.json new file mode 100644 index 0000000000..4dcbcee53a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/aec3-config-json-corpus/default.json @@ -0,0 +1 @@ +{"aec3": {"buffering": {"excess_render_detection_interval_blocks": 250,"max_allowed_excess_render_blocks": 8},"delay": {"default_delay": 5,"down_sampling_factor": 4,"num_filters": 5,"delay_headroom_samples": 32,"hysteresis_limit_blocks": 1,"fixed_capture_delay_samples": 0,"delay_estimate_smoothing": 0.7,"delay_candidate_detection_threshold": 0.2,"delay_selection_thresholds": {"initial": 5,"converged": 20},"use_external_delay_estimator": false,"downmix_before_delay_estimation": true,"log_warning_on_delay_changes": false},"filter": {"main": [13,5e-05,0.05,0.001,2,2.00753e+07],"shadow": [13,0.7,2.00753e+07],"main_initial": [12,0.005,0.5,0.001,2,2.00753e+07],"shadow_initial": [12,0.9,2.00753e+07],"config_change_duration_blocks": 250,"initial_state_seconds": 2.5,"conservative_initial_phase": false,"enable_shadow_filter_output_usage": true},"erle": {"min": 1,"max_l": 4,"max_h": 1.5,"onset_detection": true,"num_sections": 1,"clamp_quality_estimate_to_zero": true,"clamp_quality_estimate_to_one": true},"ep_strength": {"default_gain": 1,"default_len": 0.83,"echo_can_saturate": true,"bounded_erl": false},"echo_audibility": {"low_render_limit": 256,"normal_render_limit": 64,"floor_power": 128,"audibility_threshold_lf": 10,"audibility_threshold_mf": 10,"audibility_threshold_hf": 10,"use_stationarity_properties": false,"use_stationarity_properties_at_init": false},"render_levels": {"active_render_limit": 100,"poor_excitation_render_limit": 150,"poor_excitation_render_limit_ds8": 20},"echo_removal_control": {"has_clock_drift": false,"linear_and_stable_echo_path": false},"echo_model": {"noise_floor_hold": 50,"min_noise_floor_power": 1.6384e+06,"stationary_gate_slope": 10,"noise_gate_power": 27509.4,"noise_gate_slope": 0.3,"render_pre_window_size": 1,"render_post_window_size": 1},"suppressor": {"nearend_average_blocks": 4,"normal_tuning": {"mask_lf": [0.3,0.4,0.3],"mask_hf": [0.07,0.1,0.3],"max_inc_factor": 2,"max_dec_factor_lf": 0.25},"nearend_tuning": {"mask_lf": [1.09,1.1,0.3],"mask_hf": [0.1,0.3,0.3],"max_inc_factor": 2,"max_dec_factor_lf": 0.25},"dominant_nearend_detection": {"enr_threshold": 0.25,"enr_exit_threshold": 10,"snr_threshold": 30,"hold_duration": 50,"trigger_threshold": 12,"use_during_initial_phase": 1},"high_bands_suppression": {"enr_threshold": 1,"max_gain_during_echo": 1},"floor_first_increase": 1e-05,"enforce_transparent": false,"enforce_empty_higher_bands": false}}} diff --git a/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-1 b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-1 Binary files differnew file mode 100644 index 0000000000..cda107c534 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-2 b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-2 Binary files differnew file mode 100644 index 0000000000..bf1a98dccd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-2 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-3 b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-3 Binary files differnew file mode 100644 index 0000000000..f28283502a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-3 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-4 b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-4 Binary files differnew file mode 100644 index 0000000000..7b5d1cd790 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/agc-corpus/agc-4 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-0 b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-0 Binary files differnew file mode 100644 index 0000000000..da32f0c29c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-1 b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-1 Binary files differnew file mode 100644 index 0000000000..097af39c3f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-2 b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-2 Binary files differnew file mode 100644 index 0000000000..a9f4011d3f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-2 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-3 b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-3 Binary files differnew file mode 100644 index 0000000000..b4ba1578cd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/audio_processing-corpus/audio-processing-3 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-0 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-0 Binary files differnew file mode 100644 index 0000000000..fc56e0e591 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-1 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-1 Binary files differnew file mode 100644 index 0000000000..b5c147f9f3 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-10 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-10 Binary files differnew file mode 100644 index 0000000000..ced48741aa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-10 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-11 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-11 new file mode 100644 index 0000000000..53afe97468 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-11 @@ -0,0 +1 @@ +ÿÿÿÿÿ°Ë±±o
\ No newline at end of file diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-12 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-12 Binary files differnew file mode 100644 index 0000000000..4d126eacc3 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-12 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-13 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-13 Binary files differnew file mode 100644 index 0000000000..847e67b79a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-13 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-14 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-14 Binary files differnew file mode 100644 index 0000000000..f5efdd1317 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-14 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-15 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-15 Binary files differnew file mode 100644 index 0000000000..830dc8a679 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-15 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-16 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-16 Binary files differnew file mode 100644 index 0000000000..06017917dc --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-16 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-2 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-2 Binary files differnew file mode 100644 index 0000000000..43cf72619c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-2 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-3 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-3 Binary files differnew file mode 100644 index 0000000000..a2e8e0c9ae --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-3 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-4 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-4 Binary files differnew file mode 100644 index 0000000000..ff5785f0c3 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-4 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-5 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-5 Binary files differnew file mode 100644 index 0000000000..f1196c98aa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-5 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-6 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-6 Binary files differnew file mode 100644 index 0000000000..88ce0a1600 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-6 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-7 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-7 Binary files differnew file mode 100644 index 0000000000..9adb5c2125 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-7 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-8 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-8 new file mode 100644 index 0000000000..5a8f929a61 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-8 @@ -0,0 +1,2 @@ +ÿÿÿÞ +‚ÿ diff --git a/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-9 b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-9 Binary files differnew file mode 100644 index 0000000000..bde1b127b3 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/dependency_descriptor-corpus/dependency-descriptor-9 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-0 b/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-0 new file mode 100644 index 0000000000..73e1d22f10 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-0 @@ -0,0 +1 @@ +WebRTC-DecoderDataDumpDirectory/Enabled/ diff --git a/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-1 b/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-1 new file mode 100644 index 0000000000..997888a6fd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-1 @@ -0,0 +1 @@ +WebRTC-DecoderDataDumpDirectory/Disabled/ diff --git a/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-2 b/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-2 new file mode 100644 index 0000000000..7e897a418d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/field_trial-corpus/field-trial-2 @@ -0,0 +1 @@ +WebRTC-DecoderDataDumpDirectory/Disabled/WebRTC-IPv6Default/Enabled/ diff --git a/third_party/libwebrtc/test/fuzzers/corpora/h264-depacketizer-fuzzer-corpus/h264-0 b/third_party/libwebrtc/test/fuzzers/corpora/h264-depacketizer-fuzzer-corpus/h264-0 Binary files differnew file mode 100644 index 0000000000..dbe089f278 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/h264-depacketizer-fuzzer-corpus/h264-0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/h264-depacketizer-fuzzer-corpus/h264-1 b/third_party/libwebrtc/test/fuzzers/corpora/h264-depacketizer-fuzzer-corpus/h264-1 Binary files differnew file mode 100644 index 0000000000..dcb9c476f0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/h264-depacketizer-fuzzer-corpus/h264-1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/1.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/1.mdns Binary files differnew file mode 100644 index 0000000000..6e1789a83b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/1.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/10.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/10.mdns Binary files differnew file mode 100644 index 0000000000..f5fa6f8d17 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/10.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/11.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/11.mdns Binary files differnew file mode 100644 index 0000000000..f72f28c13c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/11.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/12.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/12.mdns Binary files differnew file mode 100644 index 0000000000..9efa76d64a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/12.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/13.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/13.mdns Binary files differnew file mode 100644 index 0000000000..538a5a62dc --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/13.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/14.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/14.mdns Binary files differnew file mode 100644 index 0000000000..d4ce4efea8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/14.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/15.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/15.mdns Binary files differnew file mode 100644 index 0000000000..ff2810565b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/15.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/16.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/16.mdns Binary files differnew file mode 100644 index 0000000000..a1a02d7f8e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/16.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/17.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/17.mdns Binary files differnew file mode 100644 index 0000000000..ba30f7bc2f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/17.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/18.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/18.mdns Binary files differnew file mode 100644 index 0000000000..7cbdd3e7a0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/18.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/19.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/19.mdns Binary files differnew file mode 100644 index 0000000000..f70eaa3ab2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/19.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/2.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/2.mdns Binary files differnew file mode 100644 index 0000000000..7d259c2ea9 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/2.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/20.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/20.mdns Binary files differnew file mode 100644 index 0000000000..6681f943e4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/20.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/3.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/3.mdns Binary files differnew file mode 100644 index 0000000000..3ac4fd6c98 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/3.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/4.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/4.mdns Binary files differnew file mode 100644 index 0000000000..3207842f48 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/4.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/5.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/5.mdns Binary files differnew file mode 100644 index 0000000000..871ea41405 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/5.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/6.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/6.mdns Binary files differnew file mode 100644 index 0000000000..ee2f8eca1c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/6.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/7.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/7.mdns Binary files differnew file mode 100644 index 0000000000..d37b935654 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/7.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/8.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/8.mdns Binary files differnew file mode 100644 index 0000000000..dd2f976afd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/8.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/9.mdns b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/9.mdns Binary files differnew file mode 100644 index 0000000000..a01e729342 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/mdns-corpus/9.mdns diff --git a/third_party/libwebrtc/test/fuzzers/corpora/pseudotcp-corpus/785b96587d0eb44dd5d75b7a886f37e2ac504511 b/third_party/libwebrtc/test/fuzzers/corpora/pseudotcp-corpus/785b96587d0eb44dd5d75b7a886f37e2ac504511 Binary files differnew file mode 100644 index 0000000000..21f5cffa12 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/pseudotcp-corpus/785b96587d0eb44dd5d75b7a886f37e2ac504511 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/0.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/0.rtcp Binary files differnew file mode 100644 index 0000000000..802eecf052 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/0.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/1.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/1.rtcp Binary files differnew file mode 100644 index 0000000000..c062793b3c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/1.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/10.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/10.rtcp Binary files differnew file mode 100644 index 0000000000..fdc7780218 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/10.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/11.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/11.rtcp Binary files differnew file mode 100644 index 0000000000..994f721dca --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/11.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/12.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/12.rtcp Binary files differnew file mode 100644 index 0000000000..557fe9c23a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/12.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/13.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/13.rtcp Binary files differnew file mode 100644 index 0000000000..0f1b1809a7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/13.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/14.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/14.rtcp Binary files differnew file mode 100644 index 0000000000..df6bfed86c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/14.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/15.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/15.rtcp Binary files differnew file mode 100644 index 0000000000..3b31b3079d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/15.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/16.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/16.rtcp Binary files differnew file mode 100644 index 0000000000..0496ddf910 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/16.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/17.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/17.rtcp Binary files differnew file mode 100644 index 0000000000..755b7198ba --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/17.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/18.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/18.rtcp Binary files differnew file mode 100644 index 0000000000..04fd1e3417 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/18.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/19.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/19.rtcp Binary files differnew file mode 100644 index 0000000000..8ae9812f10 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/19.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/2.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/2.rtcp Binary files differnew file mode 100644 index 0000000000..ba38a0a599 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/2.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/20.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/20.rtcp Binary files differnew file mode 100644 index 0000000000..debb7de940 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/20.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/21.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/21.rtcp Binary files differnew file mode 100644 index 0000000000..3fcbd405e2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/21.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/22.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/22.rtcp Binary files differnew file mode 100644 index 0000000000..3defd8c253 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/22.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/23.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/23.rtcp Binary files differnew file mode 100644 index 0000000000..211ccbd5be --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/23.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/24.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/24.rtcp Binary files differnew file mode 100644 index 0000000000..8ded9be7fd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/24.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/25.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/25.rtcp Binary files differnew file mode 100644 index 0000000000..b5c8146cef --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/25.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/26.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/26.rtcp Binary files differnew file mode 100644 index 0000000000..0fd4f25511 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/26.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/27.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/27.rtcp Binary files differnew file mode 100644 index 0000000000..2c8bb63c9c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/27.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/28.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/28.rtcp Binary files differnew file mode 100644 index 0000000000..6a20bc27d8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/28.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/29.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/29.rtcp Binary files differnew file mode 100644 index 0000000000..76fd214197 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/29.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/3.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/3.rtcp Binary files differnew file mode 100644 index 0000000000..72307e08bd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/3.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/30.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/30.rtcp Binary files differnew file mode 100644 index 0000000000..cfa38faa67 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/30.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/31.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/31.rtcp Binary files differnew file mode 100644 index 0000000000..8abf725057 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/31.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/32.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/32.rtcp Binary files differnew file mode 100644 index 0000000000..76518c88dd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/32.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/33.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/33.rtcp Binary files differnew file mode 100644 index 0000000000..92964965a6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/33.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/34.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/34.rtcp Binary files differnew file mode 100644 index 0000000000..9045c158b5 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/34.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/35.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/35.rtcp Binary files differnew file mode 100644 index 0000000000..f86df02448 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/35.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/36.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/36.rtcp Binary files differnew file mode 100644 index 0000000000..d274d416a2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/36.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/37.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/37.rtcp Binary files differnew file mode 100644 index 0000000000..b4f04f4069 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/37.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/38.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/38.rtcp Binary files differnew file mode 100644 index 0000000000..8d65e0ff35 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/38.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/39.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/39.rtcp Binary files differnew file mode 100644 index 0000000000..fb969f85f0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/39.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/4.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/4.rtcp Binary files differnew file mode 100644 index 0000000000..5170185122 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/4.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/40.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/40.rtcp Binary files differnew file mode 100644 index 0000000000..279047c6b2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/40.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/41.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/41.rtcp Binary files differnew file mode 100644 index 0000000000..09a8a589bd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/41.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/42.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/42.rtcp Binary files differnew file mode 100644 index 0000000000..f727b190a7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/42.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/43.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/43.rtcp Binary files differnew file mode 100644 index 0000000000..9aab33a48a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/43.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/44.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/44.rtcp Binary files differnew file mode 100644 index 0000000000..2eba529c3a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/44.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/45.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/45.rtcp Binary files differnew file mode 100644 index 0000000000..50c0beabed --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/45.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/46.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/46.rtcp Binary files differnew file mode 100644 index 0000000000..2de424a5d1 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/46.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/47.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/47.rtcp new file mode 100644 index 0000000000..71d530b41f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/47.rtcp @@ -0,0 +1 @@ +€
\ No newline at end of file diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/48.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/48.rtcp Binary files differnew file mode 100644 index 0000000000..665695029a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/48.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/49.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/49.rtcp Binary files differnew file mode 100644 index 0000000000..33b0f3d873 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/49.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/5.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/5.rtcp Binary files differnew file mode 100644 index 0000000000..46bef3242a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/5.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/50.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/50.rtcp Binary files differnew file mode 100644 index 0000000000..5bec125397 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/50.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/51.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/51.rtcp Binary files differnew file mode 100644 index 0000000000..ff94fb84b7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/51.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/52.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/52.rtcp Binary files differnew file mode 100644 index 0000000000..55cc76cd81 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/52.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/53.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/53.rtcp Binary files differnew file mode 100644 index 0000000000..ac11523c94 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/53.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/54.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/54.rtcp Binary files differnew file mode 100644 index 0000000000..f3f0bb8fa1 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/54.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/55.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/55.rtcp new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/55.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/56.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/56.rtcp Binary files differnew file mode 100644 index 0000000000..f0012f1efa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/56.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/57.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/57.rtcp Binary files differnew file mode 100644 index 0000000000..61517f01d8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/57.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/58.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/58.rtcp Binary files differnew file mode 100644 index 0000000000..3688310a05 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/58.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/59.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/59.rtcp Binary files differnew file mode 100644 index 0000000000..e914f83b16 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/59.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/6.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/6.rtcp Binary files differnew file mode 100644 index 0000000000..dcad8f6b40 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/6.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/60.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/60.rtcp Binary files differnew file mode 100644 index 0000000000..8e999e7832 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/60.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/61.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/61.rtcp Binary files differnew file mode 100644 index 0000000000..eb38d237aa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/61.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/62.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/62.rtcp Binary files differnew file mode 100644 index 0000000000..6df94b715f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/62.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/63.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/63.rtcp Binary files differnew file mode 100644 index 0000000000..a3b2acb3a8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/63.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/64.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/64.rtcp Binary files differnew file mode 100644 index 0000000000..4d50c0f4ae --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/64.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/65.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/65.rtcp Binary files differnew file mode 100644 index 0000000000..537d10c6ce --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/65.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/66.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/66.rtcp Binary files differnew file mode 100644 index 0000000000..f280f3ca09 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/66.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/7.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/7.rtcp Binary files differnew file mode 100644 index 0000000000..f8f74e12c6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/7.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/8.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/8.rtcp Binary files differnew file mode 100644 index 0000000000..f0ceba83f4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/8.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/9.rtcp b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/9.rtcp Binary files differnew file mode 100644 index 0000000000..7cc07893c3 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtcp-corpus/9.rtcp diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-0 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-0 Binary files differnew file mode 100644 index 0000000000..c93ce82ec0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-1 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-1 Binary files differnew file mode 100644 index 0000000000..84834aefea --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-2 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-2 Binary files differnew file mode 100644 index 0000000000..3a5aedba9b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-2 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-3 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-3 Binary files differnew file mode 100644 index 0000000000..300309ff37 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-3 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-4 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-4 Binary files differnew file mode 100644 index 0000000000..84834aefea --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-4 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-5 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-5 Binary files differnew file mode 100644 index 0000000000..7dd6632007 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-5 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-6 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-6 Binary files differnew file mode 100644 index 0000000000..3a1145cd0f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-6 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-7 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-7 Binary files differnew file mode 100644 index 0000000000..6c4a9feb52 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-7 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-8 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-8 Binary files differnew file mode 100644 index 0000000000..4b069389b1 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-corpus/rtp-8 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtp-depacketizer-av1-assemble-frame-corpus/av1-assemble-frame-0 b/third_party/libwebrtc/test/fuzzers/corpora/rtp-depacketizer-av1-assemble-frame-corpus/av1-assemble-frame-0 new file mode 100644 index 0000000000..540a770e29 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtp-depacketizer-av1-assemble-frame-corpus/av1-assemble-frame-0 @@ -0,0 +1 @@ +ô0ÿÿÿ¸¸¸
\ No newline at end of file diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtpdump-corpus/vp8/vp8.rtpdump b/third_party/libwebrtc/test/fuzzers/corpora/rtpdump-corpus/vp8/vp8.rtpdump Binary files differnew file mode 100644 index 0000000000..328559820d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtpdump-corpus/vp8/vp8.rtpdump diff --git a/third_party/libwebrtc/test/fuzzers/corpora/rtpdump-corpus/vp9/vp9.rtpdump b/third_party/libwebrtc/test/fuzzers/corpora/rtpdump-corpus/vp9/vp9.rtpdump Binary files differnew file mode 100644 index 0000000000..4dc780cd16 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/rtpdump-corpus/vp9/vp9.rtpdump diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-ack-sack.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-ack-sack.bin Binary files differnew file mode 100644 index 0000000000..4374f5aad5 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-ack-sack.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data-data-data.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data-data-data.bin Binary files differnew file mode 100644 index 0000000000..1f1d0be301 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data-data-data.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data-data.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data-data.bin Binary files differnew file mode 100644 index 0000000000..21a0c22837 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data-data.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data.bin Binary files differnew file mode 100644 index 0000000000..fc8600106e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/cookie-echo-data.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/data-fragment1.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/data-fragment1.bin Binary files differnew file mode 100644 index 0000000000..bec7b289e7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/data-fragment1.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/forward-tsn.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/forward-tsn.bin Binary files differnew file mode 100644 index 0000000000..ab98a0a4a7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/forward-tsn.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/heartbeat-ack.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/heartbeat-ack.bin Binary files differnew file mode 100644 index 0000000000..59200abe5e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/heartbeat-ack.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/heartbeat.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/heartbeat.bin Binary files differnew file mode 100644 index 0000000000..cef8cfe929 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/heartbeat.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/init-ack.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/init-ack.bin Binary files differnew file mode 100644 index 0000000000..80438434d0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/init-ack.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/init.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/init.bin Binary files differnew file mode 100644 index 0000000000..3fb4977d58 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/init.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/re-config.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/re-config.bin Binary files differnew file mode 100644 index 0000000000..74c74f3377 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/re-config.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/sack-data.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/sack-data.bin Binary files differnew file mode 100644 index 0000000000..fe4de63863 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/sack-data.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/sack-gap-ack-1.bin b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/sack-gap-ack-1.bin Binary files differnew file mode 100644 index 0000000000..08494c1515 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sctp-packet-corpus/sack-gap-ack-1.bin diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/10.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/10.sdp new file mode 100644 index 0000000000..36319c4476 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/10.sdp @@ -0,0 +1,11 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:97 iLBC/8000 +m=audio 49172 RTP/AVP 98 +a=rtpmap:98 telephone-event/8000 +a=sendonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/11.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/11.sdp new file mode 100644 index 0000000000..c3dbce401f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/11.sdp @@ -0,0 +1,10 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=audio 49174 RTP/AVP 98 +a=rtpmap:98 telephone-event/8000 +a=recvonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/12.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/12.sdp new file mode 100644 index 0000000000..26d4ff6266 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/12.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/13.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/13.sdp new file mode 100644 index 0000000000..f625e3890c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/13.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49174 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 49170 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/14.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/14.sdp new file mode 100644 index 0000000000..c36b9fe6cb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/14.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 newhost.biloxi.example.com +t=0 0 +m=audio 49178 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 49188 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/15.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/15.sdp new file mode 100644 index 0000000000..26d4ff6266 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/15.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/16.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/16.sdp new file mode 100644 index 0000000000..1d187a0dc5 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/16.sdp @@ -0,0 +1,10 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=audio 51372 RTP/AVP 97 101 +a=rtpmap:97 iLBC/8000 +a=rtpmap:101 telephone-event/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/17.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/17.sdp new file mode 100644 index 0000000000..a328d16a16 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/17.sdp @@ -0,0 +1,10 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 0 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=audio 49170 RTP/AVP 97 101 +a=rtpmap:97 iLBC/8000 +a=rtpmap:101 telephone-event/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/18.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/18.sdp new file mode 100644 index 0000000000..1e0d3f5705 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/18.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 99 +a=rtpmap:99 iLBC/8000 +m=video 51372 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/19.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/19.sdp new file mode 100644 index 0000000000..a9a5e12138 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/19.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 99 +a=rtpmap:99 iLBC/8000 +m=video 51374 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/2.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/2.sdp new file mode 100644 index 0000000000..96c4975ec0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/2.sdp @@ -0,0 +1,12 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 8 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 32 +a=rtpmap:31 H261/90000 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/20.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/20.sdp new file mode 100644 index 0000000000..b2c072af1d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/20.sdp @@ -0,0 +1,10 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 99 +a=rtpmap:99 iLBC/8000 +m=video 51372 RTP/AVP 31 32 +a=rtpmap:31 H261/90000 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/21.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/21.sdp new file mode 100644 index 0000000000..28e949133e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/21.sdp @@ -0,0 +1,10 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 99 +a=rtpmap:99 iLBC/8000 +m=video 51374 RTP/AVP 31 32 +a=rtpmap:31 H261/90000 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/22.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/22.sdp new file mode 100644 index 0000000000..96c4975ec0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/22.sdp @@ -0,0 +1,12 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 8 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 32 +a=rtpmap:31 H261/90000 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/23.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/23.sdp new file mode 100644 index 0000000000..36c7d70148 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/23.sdp @@ -0,0 +1,10 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49174 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=video 49172 RTP/AVP 32 +c=IN IP4 otherhost.biloxi.example.com +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/24.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/24.sdp new file mode 100644 index 0000000000..6cf5dc5894 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/24.sdp @@ -0,0 +1,8 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/25.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/25.sdp new file mode 100644 index 0000000000..ac1ec85b29 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/25.sdp @@ -0,0 +1,8 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 placeholder.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +a=sendonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/26.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/26.sdp new file mode 100644 index 0000000000..f0eb0d62a7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/26.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/27.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/27.sdp new file mode 100644 index 0000000000..1e4f3b3e19 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/27.sdp @@ -0,0 +1,7 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49178 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/28.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/28.sdp new file mode 100644 index 0000000000..36319c4476 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/28.sdp @@ -0,0 +1,11 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:97 iLBC/8000 +m=audio 49172 RTP/AVP 98 +a=rtpmap:98 telephone-event/8000 +a=sendonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/29.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/29.sdp new file mode 100644 index 0000000000..c3dbce401f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/29.sdp @@ -0,0 +1,10 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=audio 49174 RTP/AVP 98 +a=rtpmap:98 telephone-event/8000 +a=recvonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/3.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/3.sdp new file mode 100644 index 0000000000..b0142f5dcf --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/3.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49174 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=video 49170 RTP/AVP 32 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/30.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/30.sdp new file mode 100644 index 0000000000..0798631d2e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/30.sdp @@ -0,0 +1,11 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +a=sendonly +m=audio 49174 RTP/AVP 98 +a=rtpmap:98 telephone-event/8000 +a=recvonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/31.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/31.sdp new file mode 100644 index 0000000000..78efdd8fcb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/31.sdp @@ -0,0 +1,11 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:97 iLBC/8000 +m=audio 49172 RTP/AVP 98 +a=rtpmap:98 telephone-event/8000 +a=sendonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/32.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/32.sdp new file mode 100644 index 0000000000..6cf5dc5894 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/32.sdp @@ -0,0 +1,8 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/33.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/33.sdp new file mode 100644 index 0000000000..23f462f5a6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/33.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/34.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/34.sdp new file mode 100644 index 0000000000..b52b947486 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/34.sdp @@ -0,0 +1,11 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=audio 48282 RTP/AVP 98 +c=IN IP4 mediaserver.biloxi.example.com +a=rtpmap:98 telephone-event/8000 +a=recvonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/35.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/35.sdp new file mode 100644 index 0000000000..07ad4f8758 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/35.sdp @@ -0,0 +1,11 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=audio 49172 RTP/AVP 98 +c=IN IP4 host.atlanta.example.com +a=rtpmap:98 telephone-event/8000 +a=sendonly diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/36.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/36.sdp new file mode 100644 index 0000000000..c75c6977bb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/36.sdp @@ -0,0 +1,7 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/37.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/37.sdp new file mode 100644 index 0000000000..d05dbd61fd --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/37.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/38.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/38.sdp new file mode 100644 index 0000000000..253ac0705a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/38.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=video 49172 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/39.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/39.sdp new file mode 100644 index 0000000000..57d3203c74 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/39.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=video 49168 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/4.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/4.sdp new file mode 100644 index 0000000000..96c4975ec0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/4.sdp @@ -0,0 +1,12 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 8 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 32 +a=rtpmap:31 H261/90000 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/40.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/40.sdp new file mode 100644 index 0000000000..26d4ff6266 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/40.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/41.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/41.sdp new file mode 100644 index 0000000000..f625e3890c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/41.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49174 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 49170 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/42.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/42.sdp new file mode 100644 index 0000000000..2dda9bb252 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/42.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49174 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 0 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/43.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/43.sdp new file mode 100644 index 0000000000..83a2bf9e66 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/43.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 +m=video 0 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/44.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/44.sdp new file mode 100644 index 0000000000..e187d7df7d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/44.sdp @@ -0,0 +1,5 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/45.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/45.sdp new file mode 100644 index 0000000000..9e344f035a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/45.sdp @@ -0,0 +1,5 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/46.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/46.sdp new file mode 100644 index 0000000000..8a63ca0992 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/46.sdp @@ -0,0 +1,7 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/47.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/47.sdp new file mode 100644 index 0000000000..66dadbb93f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/47.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/48.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/48.sdp new file mode 100644 index 0000000000..6df76ab942 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/48.sdp @@ -0,0 +1,7 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 0.0.0.0 +t=0 0 +m=audio 23442 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/49.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/49.sdp new file mode 100644 index 0000000000..23f462f5a6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/49.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/5.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/5.sdp new file mode 100644 index 0000000000..264ab8024f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/5.sdp @@ -0,0 +1,10 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 0 8 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +m=video 0 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/50.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/50.sdp new file mode 100644 index 0000000000..8a63ca0992 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/50.sdp @@ -0,0 +1,7 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/51.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/51.sdp new file mode 100644 index 0000000000..23f462f5a6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/51.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/52.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/52.sdp new file mode 100644 index 0000000000..7c1730eb1e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/52.sdp @@ -0,0 +1,7 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/53.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/53.sdp new file mode 100644 index 0000000000..599157691a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/53.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 0.0.0.0 +t=0 0 +m=audio 9322 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/54.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/54.sdp new file mode 100644 index 0000000000..66dadbb93f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/54.sdp @@ -0,0 +1,7 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/55.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/55.sdp new file mode 100644 index 0000000000..bd724c471b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/55.sdp @@ -0,0 +1,8 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 97 +a=rtpmap:97 iLBC/8000 + diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/6.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/6.sdp new file mode 100644 index 0000000000..3f80345e30 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/6.sdp @@ -0,0 +1,9 @@ +v=0 +o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 51372 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=video 0 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/7.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/7.sdp new file mode 100644 index 0000000000..920ad8229e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/7.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 0 +a=rtpmap:0 PCMU/8000 +m=video 0 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/8.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/8.sdp new file mode 100644 index 0000000000..96c4975ec0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/8.sdp @@ -0,0 +1,12 @@ +v=0 +o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com +s= +c=IN IP4 host.atlanta.example.com +t=0 0 +m=audio 49170 RTP/AVP 0 8 97 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:97 iLBC/8000 +m=video 51372 RTP/AVP 31 32 +a=rtpmap:31 H261/90000 +a=rtpmap:32 MPV/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/9.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/9.sdp new file mode 100644 index 0000000000..a9a5e12138 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/9.sdp @@ -0,0 +1,9 @@ +v=0 +o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com +s= +c=IN IP4 host.biloxi.example.com +t=0 0 +m=audio 49172 RTP/AVP 99 +a=rtpmap:99 iLBC/8000 +m=video 51374 RTP/AVP 31 +a=rtpmap:31 H261/90000 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/firefox-1.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/firefox-1.sdp new file mode 100644 index 0000000000..6d664bc051 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/firefox-1.sdp @@ -0,0 +1,58 @@ +v=0 +o=mozilla...THIS_IS_SDPARTA-46.0.1 5115930144083302970 0 IN IP4 0.0.0.0 +s=- +t=0 0 +a=fingerprint:sha-256 24:67:5E:1B:9A:B9:CF:36:C5:30:8F:35:F7:B1:50:66:88:81:92:CB:29:BA:53:A5:02:C8:0A:A5:4E:9C:AE:D9 +a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2 +a=ice-options:trickle +a=msid-semantic:WMS * +m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 +c=IN IP4 0.0.0.0 +a=sendrecv +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=fmtp:109 maxplaybackrate=48000;stereo=1 +a=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3 +a=ice-ufrag:3edc9012 +a=mid:sdparta_0 +a=msid:{258e92fb-547c-40ca-92e9-efe0cedb4cba} {bd1fafff-bfd0-40d4-b0a3-2a87cff307ee} +a=rtcp-mux +a=rtpmap:109 opus/48000/2 +a=rtpmap:9 G722/8000/1 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=setup:actpass +a=ssrc:2121669360 cname:{387b0735-bde2-43a4-8484-7f5663b60f24} +m=video 9 UDP/TLS/RTP/SAVPF 120 126 97 +c=IN IP4 0.0.0.0 +a=sendrecv +a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1 +a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1 +a=fmtp:120 max-fs=12288;max-fr=60 +a=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3 +a=ice-ufrag:3edc9012 +a=mid:sdparta_1 +a=msid:{258e92fb-547c-40ca-92e9-efe0cedb4cba} {9e8f5867-a9aa-4489-8bd4-3a8a57a5e592} +a=rtcp-fb:120 nack +a=rtcp-fb:120 nack pli +a=rtcp-fb:120 ccm fir +a=rtcp-fb:126 nack +a=rtcp-fb:126 nack pli +a=rtcp-fb:126 ccm fir +a=rtcp-fb:97 nack +a=rtcp-fb:97 nack pli +a=rtcp-fb:97 ccm fir +a=rtcp-mux +a=rtpmap:120 VP8/90000 +a=rtpmap:126 H264/90000 +a=rtpmap:97 H264/90000 +a=setup:actpass +a=ssrc:2158832026 cname:{387b0735-bde2-43a4-8484-7f5663b60f24} +m=application 9 DTLS/SCTP 5000 +c=IN IP4 0.0.0.0 +a=sendrecv +a=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3 +a=ice-ufrag:3edc9012 +a=mid:sdparta_2 +a=sctpmap:5000 webrtc-datachannel 256 +a=setup:actpass +a=ssrc:2670959794 cname:{387b0735-bde2-43a4-8484-7f5663b60f24} diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/firefox-2.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/firefox-2.sdp new file mode 100644 index 0000000000..07806eb225 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/firefox-2.sdp @@ -0,0 +1,43 @@ +v=0 +o=mozilla...THIS_IS_SDPARTA-46.0.1 3068771576687940834 0 IN IP4 0.0.0.0 +s=- +t=0 0 +a=fingerprint:sha-256 AD:87:B3:11:E4:E2:BA:EF:D2:3F:2E:AC:24:57:8E:DC:1F:67:41:29:44:C4:96:E3:62:90:CC:90:59:CA:2C:84 +a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2 +a=ice-options:trickle +a=msid-semantic:WMS * +m=audio 9 UDP/TLS/RTP/SAVPF 109 +c=IN IP4 0.0.0.0 +a=recvonly +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=fmtp:109 maxplaybackrate=48000;stereo=1 +a=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5 +a=ice-ufrag:a539544b +a=mid:sdparta_0 +a=rtcp-mux +a=rtpmap:109 opus/48000/2 +a=setup:active +a=ssrc:600995474 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a} +m=video 9 UDP/TLS/RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=recvonly +a=fmtp:120 max-fs=12288;max-fr=60 +a=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5 +a=ice-ufrag:a539544b +a=mid:sdparta_1 +a=rtcp-fb:120 nack +a=rtcp-fb:120 nack pli +a=rtcp-fb:120 ccm fir +a=rtcp-mux +a=rtpmap:120 VP8/90000 +a=setup:active +a=ssrc:3480150809 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a} +m=application 9 DTLS/SCTP 5000 +c=IN IP4 0.0.0.0 +a=sendrecv +a=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5 +a=ice-ufrag:a539544b +a=mid:sdparta_2 +a=sctpmap:5000 webrtc-datachannel 256 +a=setup:active +a=ssrc:3021788991 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a} diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/opera-1.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/opera-1.sdp new file mode 100644 index 0000000000..42d15e81f4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/opera-1.sdp @@ -0,0 +1,85 @@ +v=0 +o=- 1656229333038673902 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE audio video data +a=msid-semantic: WMS Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 +m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:1Jyk4q3nLIL5NiMx +a=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH +a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA +a=setup:actpass +a=mid:audio +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=sendrecv +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10; useinbandfec=1 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=rtpmap:9 G722/8000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:106 CN/32000 +a=rtpmap:105 CN/16000 +a=rtpmap:13 CN/8000 +a=rtpmap:126 telephone-event/8000 +a=maxptime:60 +a=ssrc:2233075910 cname:VhHMGYCjn4alR9zP +a=ssrc:2233075910 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 689d3496-0896-4d52-bce6-8e90512a368b +a=ssrc:2233075910 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 +a=ssrc:2233075910 label:689d3496-0896-4d52-bce6-8e90512a368b +m=video 9 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:1Jyk4q3nLIL5NiMx +a=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH +a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA +a=setup:actpass +a=mid:video +a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:4 urn:3gpp:video-orientation +a=sendrecv +a=rtcp-mux +a=rtpmap:100 VP8/90000 +a=rtcp-fb:100 ccm fir +a=rtcp-fb:100 nack +a=rtcp-fb:100 nack pli +a=rtcp-fb:100 goog-remb +a=rtcp-fb:100 transport-cc +a=rtpmap:101 VP9/90000 +a=rtcp-fb:101 ccm fir +a=rtcp-fb:101 nack +a=rtcp-fb:101 nack pli +a=rtcp-fb:101 goog-remb +a=rtcp-fb:101 transport-cc +a=rtpmap:116 red/90000 +a=rtpmap:117 ulpfec/90000 +a=rtpmap:96 rtx/90000 +a=fmtp:96 apt=100 +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=101 +a=rtpmap:98 rtx/90000 +a=fmtp:98 apt=116 +a=ssrc-group:FID 50498894 2399294607 +a=ssrc:50498894 cname:VhHMGYCjn4alR9zP +a=ssrc:50498894 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 +a=ssrc:50498894 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 +a=ssrc:50498894 label:1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 +a=ssrc:2399294607 cname:VhHMGYCjn4alR9zP +a=ssrc:2399294607 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 +a=ssrc:2399294607 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 +a=ssrc:2399294607 label:1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 +m=application 9 DTLS/SCTP 5000 +c=IN IP4 0.0.0.0 +a=ice-ufrag:1Jyk4q3nLIL5NiMx +a=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH +a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA +a=setup:actpass +a=mid:data +a=sctpmap:5000 webrtc-datachannel 1024 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/opera-2.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/opera-2.sdp new file mode 100644 index 0000000000..6ed4e3b4a8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/opera-2.sdp @@ -0,0 +1,73 @@ +v=0 +o=- 2013283641453412290 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE audio video data +a=msid-semantic: WMS +m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:YVa3KTlFDCwsfPOQ +a=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa +a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA +a=setup:active +a=mid:audio +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=recvonly +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10; useinbandfec=1 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=rtpmap:9 G722/8000 +a=rtpmap:0 PCMU/8000 +a=rtpmap:8 PCMA/8000 +a=rtpmap:106 CN/32000 +a=rtpmap:105 CN/16000 +a=rtpmap:13 CN/8000 +a=rtpmap:126 telephone-event/8000 +a=maxptime:60 +m=video 9 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:YVa3KTlFDCwsfPOQ +a=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa +a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA +a=setup:active +a=mid:video +a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:4 urn:3gpp:video-orientation +a=recvonly +a=rtcp-mux +a=rtpmap:100 VP8/90000 +a=rtcp-fb:100 ccm fir +a=rtcp-fb:100 nack +a=rtcp-fb:100 nack pli +a=rtcp-fb:100 goog-remb +a=rtcp-fb:100 transport-cc +a=rtpmap:101 VP9/90000 +a=rtcp-fb:101 ccm fir +a=rtcp-fb:101 nack +a=rtcp-fb:101 nack pli +a=rtcp-fb:101 goog-remb +a=rtcp-fb:101 transport-cc +a=rtpmap:116 red/90000 +a=rtpmap:117 ulpfec/90000 +a=rtpmap:96 rtx/90000 +a=fmtp:96 apt=100 +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=101 +a=rtpmap:98 rtx/90000 +a=fmtp:98 apt=116 +m=application 9 DTLS/SCTP 5000 +c=IN IP4 0.0.0.0 +b=AS:30 +a=ice-ufrag:YVa3KTlFDCwsfPOQ +a=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa +a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA +a=setup:active +a=mid:data +a=sctpmap:5000 webrtc-datachannel 1024 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/simulcast.1.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/simulcast.1.sdp new file mode 100644 index 0000000000..e7a219812b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/simulcast.1.sdp @@ -0,0 +1,41 @@ +v=0 +o=- 367669362084170381 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 +a=msid-semantic: WMS VfhSdt9LWGwoduWpoASvxGyAGEQFAkQe1hT1 +m=video 9 UDP/TLS/RTP/SAVPF 96 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:OLwt +a=ice-pwd:kjGBqIFYs8UqCyfmJ7nEJw/Q +a=ice-options:trickle +a=fingerprint:sha-256 B4:AB:3E:01:90:D1:FC:22:8F:05:6A:01:76:B6:C5:0D:45:88:1A:E1:99:AF:18:43:E6:18:BB:D0:92:51:9B:0E +a=setup:actpass +a=mid:0 +a=extmap:14 urn:ietf:params:rtp-hdrext:toffset +a=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:12 urn:3gpp:video-orientation +a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type +a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing +a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 +a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space +a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id +a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id +a=sendrecv +a=msid:VfhSdt9LWGwoduWpoASvxGyAGEQFAkQe1hT1 a8c06601-e9ed-4312-a7d4-283e078c5966 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:96 VP8/90000 +a=rtcp-fb:96 goog-remb +a=rtcp-fb:96 transport-cc +a=rtcp-fb:96 ccm fir +a=rtcp-fb:96 nack +a=rtcp-fb:96 nack pli +a=rid:f send +a=rid:h send +a=rid:q send +a=simulcast:send f;h;q diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/simulcast.2.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/simulcast.2.sdp new file mode 100644 index 0000000000..52879f9e66 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/simulcast.2.sdp @@ -0,0 +1,41 @@ +v=0 +o=- 367669362084170381 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 +a=msid-semantic: WMS VfhSdt9LWGwoduWpoASvxGyAGEQFAkQe1hT1 +m=video 9 UDP/TLS/RTP/SAVPF 96 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:OLwt +a=ice-pwd:kjGBqIFYs8UqCyfmJ7nEJw/Q +a=ice-options:trickle +a=fingerprint:sha-256 B4:AB:3E:01:90:D1:FC:22:8F:05:6A:01:76:B6:C5:0D:45:88:1A:E1:99:AF:18:43:E6:18:BB:D0:92:51:9B:0E +a=setup:actpass +a=mid:0 +a=extmap:14 urn:ietf:params:rtp-hdrext:toffset +a=extmap:13 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:12 urn:3gpp:video-orientation +a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type +a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing +a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 +a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space +a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid +a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id +a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id +a=sendrecv +a=msid:VfhSdt9LWGwoduWpoASvxGyAGEQFAkQe1hT1 a8c06601-e9ed-4312-a7d4-283e078c5966 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:96 VP8/90000 +a=rtcp-fb:96 goog-remb +a=rtcp-fb:96 transport-cc +a=rtcp-fb:96 ccm fir +a=rtcp-fb:96 nack +a=rtcp-fb:96 nack pli +a=rid:f recv +a=rid:h recv +a=rid:q recv +a=simulcast:recv f;h;q diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-1.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-1.sdp new file mode 100644 index 0000000000..ce349ac30f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-1.sdp @@ -0,0 +1,52 @@ +v=0 +o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 +s=- +t=0 0 +a=msid-semantic: WMS local_stream_1 +m=audio 2345 RTP/SAVPF 111 103 104 +c=IN IP4 74.125.127.126 +a=rtcp:2347 IN IP4 74.125.127.126 +a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2 +a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2 +a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2 +a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2 +a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2 +a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2 +a=ice-ufrag:ufrag_voice +a=ice-pwd:pwd_voice +a=mid:audio_content_name +a=sendrecv +a=rtcp-mux +a=rtcp-rsize +a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params +a=rtpmap:111 opus/48000/2 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=ssrc:1 cname:stream_1_cname +a=ssrc:1 msid:local_stream_1 audio_track_id_1 +a=ssrc:1 mslabel:local_stream_1 +a=ssrc:1 label:audio_track_id_1 +m=video 3457 RTP/SAVPF 120 +c=IN IP4 74.125.224.39 +a=rtcp:3456 IN IP4 74.125.224.39 +a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2 +a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2 +a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2 +a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2 +a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2 +a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2 +a=ice-ufrag:ufrag_video +a=ice-pwd:pwd_video +a=mid:video_content_name +a=sendrecv +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 +a=rtpmap:120 VP8/90000 +a=ssrc-group:FEC 2 3 +a=ssrc:2 cname:stream_1_cname +a=ssrc:2 msid:local_stream_1 video_track_id_1 +a=ssrc:2 mslabel:local_stream_1 +a=ssrc:2 label:video_track_id_1 +a=ssrc:3 cname:stream_1_cname +a=ssrc:3 msid:local_stream_1 video_track_id_1 +a=ssrc:3 mslabel:local_stream_1 +a=ssrc:3 label:video_track_id_1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-2.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-2.sdp new file mode 100644 index 0000000000..a35392513f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-2.sdp @@ -0,0 +1,40 @@ +v=0 +o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 +s=- +t=0 0 +a=msid-semantic: WMS local_stream_1 +m=audio 9 RTP/SAVPF 111 103 104 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:ufrag_voice +a=ice-pwd:pwd_voice +a=mid:audio_content_name +a=sendrecv +a=rtcp-mux +a=rtcp-rsize +a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params +a=rtpmap:111 opus/48000/2 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=ssrc:1 cname:stream_1_cname +a=ssrc:1 msid:local_stream_1 audio_track_id_1 +a=ssrc:1 mslabel:local_stream_1 +a=ssrc:1 label:audio_track_id_1 +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:ufrag_video +a=ice-pwd:pwd_video +a=mid:video_content_name +a=sendrecv +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 +a=rtpmap:120 VP8/90000 +a=ssrc-group:FEC 2 3 +a=ssrc:2 cname:stream_1_cname +a=ssrc:2 msid:local_stream_1 video_track_id_1 +a=ssrc:2 mslabel:local_stream_1 +a=ssrc:2 label:video_track_id_1 +a=ssrc:3 cname:stream_1_cname +a=ssrc:3 msid:local_stream_1 video_track_id_1 +a=ssrc:3 mslabel:local_stream_1 +a=ssrc:3 label:video_track_id_1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-3.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-3.sdp new file mode 100644 index 0000000000..50f8e55ef7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-3.sdp @@ -0,0 +1,13 @@ +m=application 9 RTP/SAVPF 101 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:ufrag_data +a=ice-pwd:pwd_data +a=mid:data_content_name +a=sendrecv +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:FvLcvU2P3ZWmQxgPAgcDu7Zl9vftYElFOjEzhWs5 +a=rtpmap:101 google-data/90000 +a=ssrc:10 cname:data_channel_cname +a=ssrc:10 msid:data_channel data_channeld0 +a=ssrc:10 mslabel:data_channel +a=ssrc:10 label:data_channeld0 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-4.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-4.sdp new file mode 100644 index 0000000000..709db42c00 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-4.sdp @@ -0,0 +1,11 @@ +v=0 +o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 +s=- +t=0 0 +a=msid-semantic: WMS +m=audio 9 RTP/SAVPF 111 103 104 +c=IN IP4 0.0.0.0 +a=x-google-flag:conference +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=x-google-flag:conference diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-5.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-5.sdp new file mode 100644 index 0000000000..c440386062 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-5.sdp @@ -0,0 +1,5 @@ +v=0 +o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 +s=- +t=0 0 +a=msid-semantic: WMS local_stream diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-6.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-6.sdp new file mode 100644 index 0000000000..e07b5647bf --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-6.sdp @@ -0,0 +1,12 @@ +m=audio 9 RTP/SAVPF 111 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:ufrag_voice +a=ice-pwd:pwd_voice +a=mid:audio_content_name +a=sendrecv +a=rtpmap:111 opus/48000/2 +a=ssrc:1 cname:stream_1_cname +a=ssrc:1 msid:local_stream audio_track_id_1 +a=ssrc:1 mslabel:local_stream +a=ssrc:1 label:audio_track_id_1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-7.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-7.sdp new file mode 100644 index 0000000000..8bdacc2baa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-7.sdp @@ -0,0 +1,12 @@ +m=video 9 RTP/SAVPF 120 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:ufrag_video +a=ice-pwd:pwd_video +a=mid:video_content_name +a=sendrecv +a=rtpmap:120 VP8/90000 +a=ssrc:2 cname:stream_1_cname +a=ssrc:2 msid:local_stream video_track_id_1 +a=ssrc:2 mslabel:local_stream +a=ssrc:2 label:video_track_id_1 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-8.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-8.sdp new file mode 100644 index 0000000000..9688fc46a2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-8.sdp @@ -0,0 +1,64 @@ +v=0 +o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 +s=- +t=0 0 +a=msid-semantic: WMS local_stream_1 local_stream_2 +m=audio 2345 RTP/SAVPF 111 103 104 +c=IN IP4 74.125.127.126 +a=rtcp:2347 IN IP4 74.125.127.126 +a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2 +a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2 +a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2 +a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2 +a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2 +a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2 +a=ice-ufrag:ufrag_voice +a=ice-pwd:pwd_voice +a=mid:audio_content_name +a=sendrecv +a=rtcp-mux +a=rtcp-rsize +a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params +a=rtpmap:111 opus/48000/2 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=ssrc:1 cname:stream_1_cname +a=ssrc:1 msid:local_stream_1 audio_track_id_1 +a=ssrc:1 mslabel:local_stream_1 +a=ssrc:1 label:audio_track_id_1 +a=ssrc:4 cname:stream_2_cname +a=ssrc:4 msid:local_stream_2 audio_track_id_2 +a=ssrc:4 mslabel:local_stream_2 +a=ssrc:4 label:audio_track_id_2 +m=video 3457 RTP/SAVPF 120 +c=IN IP4 74.125.224.39 +a=rtcp:3456 IN IP4 74.125.224.39 +a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2 +a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2 +a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2 +a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2 +a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2 +a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2 +a=ice-ufrag:ufrag_video +a=ice-pwd:pwd_video +a=mid:video_content_name +a=sendrecv +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 +a=rtpmap:120 VP8/90000 +a=ssrc-group:FEC 2 3 +a=ssrc:2 cname:stream_1_cname +a=ssrc:2 msid:local_stream_1 video_track_id_1 +a=ssrc:2 mslabel:local_stream_1 +a=ssrc:2 label:video_track_id_1 +a=ssrc:3 cname:stream_1_cname +a=ssrc:3 msid:local_stream_1 video_track_id_1 +a=ssrc:3 mslabel:local_stream_1 +a=ssrc:3 label:video_track_id_1 +a=ssrc:5 cname:stream_2_cname +a=ssrc:5 msid:local_stream_2 video_track_id_2 +a=ssrc:5 mslabel:local_stream_2 +a=ssrc:5 label:video_track_id_2 +a=ssrc:6 cname:stream_2_cname +a=ssrc:6 msid:local_stream_2 video_track_id_3 +a=ssrc:6 mslabel:local_stream_2 +a=ssrc:6 label:video_track_id_3 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-9.sdp b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-9.sdp new file mode 100644 index 0000000000..8c7c4c7c3d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp-corpus/unittest-9.sdp @@ -0,0 +1,66 @@ +v=0 +o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 +s=- +t=0 0 +a=msid-semantic: WMS local_stream_1 local_stream_2 +m=audio 2345 RTP/SAVPF 111 103 104 +c=IN IP4 74.125.127.126 +a=rtcp:2347 IN IP4 74.125.127.126 +a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2 +a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2 +a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2 +a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2 +a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2 +a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2 +a=ice-ufrag:ufrag_voice +a=ice-pwd:pwd_voice +a=mid:audio_content_name +a=msid:local_stream_1 audio_track_id_1 +a=sendrecv +a=rtcp-mux +a=rtcp-rsize +a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params +a=rtpmap:111 opus/48000/2 +a=rtpmap:103 ISAC/16000 +a=rtpmap:104 ISAC/32000 +a=ssrc:1 cname:stream_1_cname +a=ssrc:1 msid:local_stream_1 audio_track_id_1 +a=ssrc:1 mslabel:local_stream_1 +a=ssrc:1 label:audio_track_id_1 +a=ssrc:4 cname:stream_2_cname +a=ssrc:4 msid:local_stream_2 audio_track_id_2 +a=ssrc:4 mslabel:local_stream_2 +a=ssrc:4 label:audio_track_id_2 +m=video 3457 RTP/SAVPF 120 +c=IN IP4 74.125.224.39 +a=rtcp:3456 IN IP4 74.125.224.39 +a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2 +a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2 +a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2 +a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2 +a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2 +a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2 +a=ice-ufrag:ufrag_video +a=ice-pwd:pwd_video +a=mid:video_content_name +a=msid:local_stream_1 video_track_id_1 +a=sendrecv +a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 +a=rtpmap:120 VP8/90000 +a=ssrc-group:FEC 2 3 +a=ssrc:2 cname:stream_1_cname +a=ssrc:2 msid:local_stream_1 video_track_id_1 +a=ssrc:2 mslabel:local_stream_1 +a=ssrc:2 label:video_track_id_1 +a=ssrc:3 cname:stream_1_cname +a=ssrc:3 msid:local_stream_1 video_track_id_1 +a=ssrc:3 mslabel:local_stream_1 +a=ssrc:3 label:video_track_id_1 +a=ssrc:5 cname:stream_2_cname +a=ssrc:5 msid:local_stream_2 video_track_id_2 +a=ssrc:5 mslabel:local_stream_2 +a=ssrc:5 label:video_track_id_2 +a=ssrc:6 cname:stream_2_cname +a=ssrc:6 msid:local_stream_2 video_track_id_3 +a=ssrc:6 mslabel:local_stream_2 +a=ssrc:6 label:video_track_id_3 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/sdp.tokens b/third_party/libwebrtc/test/fuzzers/corpora/sdp.tokens new file mode 100644 index 0000000000..ddfdad5811 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/sdp.tokens @@ -0,0 +1,56 @@ +"0.0.0.0" +"127.0.0.1" +"application" +"audio" +"candidate" +"cname" +"conference" +"crypto" +"default" +"extmap" +"fingerprint" +"fmtp" +"generation" +"group" +"host" +"ice-lite" +"ice-options" +"ice-pwd" +"ice-ufrag" +"inactive" +"IP4" +"IP6" +"ISAC" +"label" +"mid" +"msid" +"msid-semantic" +"mslabel" +"network-cost" +"network-id" +"prflx" +"pwd" +"raddr" +"recvonly" +"relay" +"rport" +"rtcp" +"rtcp-fb" +"rtcp-mux" +"rtcp-rsize" +"rtpmap" +"sctpmap" +"sctp-port" +"sendonly" +"sendrecv" +"setup" +"srflx" +"ssrc" +"ssrc-group" +"tcptype" +"typ" +"ufrag" +"video" +"webrtc-datachannel" +"WMS" +"x-google-flag"
\ No newline at end of file diff --git a/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/0 b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/0 new file mode 100644 index 0000000000..fbfcbf9105 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/0 @@ -0,0 +1 @@ +90742757 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/1 b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/1 new file mode 100644 index 0000000000..587611a1d6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/1 @@ -0,0 +1 @@ +27.70727677 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/2 b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/2 new file mode 100644 index 0000000000..45a4fb75db --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/2 @@ -0,0 +1 @@ +8 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/3 b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/3 new file mode 100644 index 0000000000..a0a162047c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/3 @@ -0,0 +1 @@ +0.0001 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/4 b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/4 new file mode 100644 index 0000000000..da24ab1137 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/4 @@ -0,0 +1 @@ +27277272727272727272 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/5 b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/5 new file mode 100644 index 0000000000..575c0a830c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/string_to_number-corpus/5 @@ -0,0 +1 @@ +999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/0.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/0.stun Binary files differnew file mode 100644 index 0000000000..205997706f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/0.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/1.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/1.stun Binary files differnew file mode 100644 index 0000000000..1f82c03ffa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/1.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/10.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/10.stun Binary files differnew file mode 100644 index 0000000000..a6b06b2be8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/10.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/11.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/11.stun Binary files differnew file mode 100644 index 0000000000..1f82c03ffa --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/11.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/12.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/12.stun Binary files differnew file mode 100644 index 0000000000..cb91baa250 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/12.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/13.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/13.stun Binary files differnew file mode 100644 index 0000000000..63298fce7d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/13.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/14.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/14.stun Binary files differnew file mode 100644 index 0000000000..31f9f732d4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/14.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/15.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/15.stun Binary files differnew file mode 100644 index 0000000000..3d15a67193 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/15.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/16.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/16.stun Binary files differnew file mode 100644 index 0000000000..8ccf0af26d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/16.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/17.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/17.stun Binary files differnew file mode 100644 index 0000000000..9c3efa0b80 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/17.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/2.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/2.stun Binary files differnew file mode 100644 index 0000000000..50fe614adb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/2.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/3.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/3.stun Binary files differnew file mode 100644 index 0000000000..50fe614adb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/3.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/4.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/4.stun Binary files differnew file mode 100644 index 0000000000..a6b06b2be8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/4.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/5.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/5.stun Binary files differnew file mode 100644 index 0000000000..c0a79fa7ab --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/5.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/6.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/6.stun Binary files differnew file mode 100644 index 0000000000..1f43a4787d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/6.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/7.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/7.stun Binary files differnew file mode 100644 index 0000000000..f5e824a68d --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/7.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/8.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/8.stun Binary files differnew file mode 100644 index 0000000000..99f4e3bcf8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/8.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/9.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/9.stun Binary files differnew file mode 100644 index 0000000000..cbbcab4e74 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/9.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/validator-crash-1.stun b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/validator-crash-1.stun Binary files differnew file mode 100644 index 0000000000..5428c38d75 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun-corpus/validator-crash-1.stun diff --git a/third_party/libwebrtc/test/fuzzers/corpora/stun.tokens b/third_party/libwebrtc/test/fuzzers/corpora/stun.tokens new file mode 100644 index 0000000000..e01130f12e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/stun.tokens @@ -0,0 +1,2 @@ +"2112A442" +"12214A24" diff --git a/third_party/libwebrtc/test/fuzzers/corpora/video_layers_allocation-corpus/vla-0 b/third_party/libwebrtc/test/fuzzers/corpora/video_layers_allocation-corpus/vla-0 new file mode 100644 index 0000000000..1b6fdf78f6 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/video_layers_allocation-corpus/vla-0 @@ -0,0 +1 @@ +@2?ÿÿ
\ No newline at end of file diff --git a/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/0cee4d5fd2905dc1fb2979f10a9724265b7075e2 b/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/0cee4d5fd2905dc1fb2979f10a9724265b7075e2 Binary files differnew file mode 100644 index 0000000000..febe4ad130 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/0cee4d5fd2905dc1fb2979f10a9724265b7075e2 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/a1c75436e1872a23391d58316d88c45da0fb7682 b/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/a1c75436e1872a23391d58316d88c45da0fb7682 Binary files differnew file mode 100644 index 0000000000..fc538ac83f --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/a1c75436e1872a23391d58316d88c45da0fb7682 diff --git a/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/a8b3fb7be82395c9462684c766841d668dc0029f b/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/a8b3fb7be82395c9462684c766841d668dc0029f Binary files differnew file mode 100644 index 0000000000..1bd09373c8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/corpora/vp9-encoder-references-corpus/a8b3fb7be82395c9462684c766841d668dc0029f diff --git a/third_party/libwebrtc/test/fuzzers/dcsctp_packet_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/dcsctp_packet_fuzzer.cc new file mode 100644 index 0000000000..2fc3fe10f1 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/dcsctp_packet_fuzzer.cc @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 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 "net/dcsctp/packet/chunk/chunk.h" +#include "net/dcsctp/packet/sctp_packet.h" + +namespace webrtc { +using dcsctp::SctpPacket; + +void FuzzOneInput(const uint8_t* data, size_t size) { + absl::optional<SctpPacket> c = + SctpPacket::Parse(rtc::ArrayView<const uint8_t>(data, size), + /*disable_checksum_verification=*/true); + + if (!c.has_value()) { + return; + } + + for (const SctpPacket::ChunkDescriptor& desc : c->descriptors()) { + dcsctp::DebugConvertChunkToString(desc.data); + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/dcsctp_socket_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/dcsctp_socket_fuzzer.cc new file mode 100644 index 0000000000..390cbb7f6c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/dcsctp_socket_fuzzer.cc @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 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 "net/dcsctp/fuzzers/dcsctp_fuzzers.h" +#include "net/dcsctp/public/dcsctp_message.h" +#include "net/dcsctp/public/dcsctp_options.h" +#include "net/dcsctp/public/dcsctp_socket.h" +#include "net/dcsctp/socket/dcsctp_socket.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + dcsctp::dcsctp_fuzzers::FuzzerCallbacks cb; + dcsctp::DcSctpOptions options; + options.disable_checksum_verification = true; + dcsctp::DcSctpSocket socket("A", cb, nullptr, options); + + dcsctp::dcsctp_fuzzers::FuzzSocket(socket, cb, + rtc::ArrayView<const uint8_t>(data, size)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/field_trial_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/field_trial_fuzzer.cc new file mode 100644 index 0000000000..74fe65ce56 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/field_trial_fuzzer.cc @@ -0,0 +1,26 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include <string> + +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + std::string field_trial(reinterpret_cast<const char*>(data), size); + field_trial::InitFieldTrialsFromString(field_trial.c_str()); + field_trial::FindFullName(field_trial); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/flexfec_header_reader_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/flexfec_header_reader_fuzzer.cc new file mode 100644 index 0000000000..854cc8b811 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/flexfec_header_reader_fuzzer.cc @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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. + */ + +#include <algorithm> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/flexfec_header_reader_writer.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" + +namespace webrtc { + +using Packet = ForwardErrorCorrection::Packet; +using ReceivedFecPacket = ForwardErrorCorrection::ReceivedFecPacket; + +void FuzzOneInput(const uint8_t* data, size_t size) { + ReceivedFecPacket packet; + packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + const size_t packet_size = + std::min(size, static_cast<size_t>(IP_PACKET_SIZE)); + packet.pkt->data.SetSize(packet_size); + packet.pkt->data.EnsureCapacity(IP_PACKET_SIZE); + memcpy(packet.pkt->data.MutableData(), data, packet_size); + + FlexfecHeaderReader flexfec_reader; + flexfec_reader.ReadFecHeader(&packet); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/flexfec_receiver_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/flexfec_receiver_fuzzer.cc new file mode 100644 index 0000000000..67d603d3fc --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/flexfec_receiver_fuzzer.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 <algorithm> + +#include "modules/rtp_rtcp/include/flexfec_receiver.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" + +namespace webrtc { + +namespace { +class DummyCallback : public RecoveredPacketReceiver { + void OnRecoveredPacket(const RtpPacketReceived& packet) override {} +}; +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + constexpr size_t kMinDataNeeded = 12; + if (size < kMinDataNeeded || size > 2000) { + return; + } + + uint32_t flexfec_ssrc; + memcpy(&flexfec_ssrc, data + 0, 4); + uint16_t flexfec_seq_num; + memcpy(&flexfec_seq_num, data + 4, 2); + uint32_t media_ssrc; + memcpy(&media_ssrc, data + 6, 4); + uint16_t media_seq_num; + memcpy(&media_seq_num, data + 10, 2); + + DummyCallback callback; + FlexfecReceiver receiver(flexfec_ssrc, media_ssrc, &callback); + + std::unique_ptr<uint8_t[]> packet; + size_t packet_length; + size_t i = kMinDataNeeded; + while (i < size) { + packet_length = kRtpHeaderSize + data[i++]; + packet = std::unique_ptr<uint8_t[]>(new uint8_t[packet_length]); + if (i + packet_length >= size) { + break; + } + memcpy(packet.get(), data + i, packet_length); + i += packet_length; + if (i < size && data[i++] % 2 == 0) { + // Simulate FlexFEC packet. + ByteWriter<uint16_t>::WriteBigEndian(packet.get() + 2, flexfec_seq_num++); + ByteWriter<uint32_t>::WriteBigEndian(packet.get() + 8, flexfec_ssrc); + } else { + // Simulate media packet. + ByteWriter<uint16_t>::WriteBigEndian(packet.get() + 2, media_seq_num++); + ByteWriter<uint32_t>::WriteBigEndian(packet.get() + 8, media_ssrc); + } + RtpPacketReceived parsed_packet; + if (parsed_packet.Parse(packet.get(), packet_length)) { + receiver.OnRtpPacket(parsed_packet); + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/flexfec_sender_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/flexfec_sender_fuzzer.cc new file mode 100644 index 0000000000..8ddd1c0fe0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/flexfec_sender_fuzzer.cc @@ -0,0 +1,67 @@ +/* + * 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 <memory> + +#include "modules/rtp_rtcp/include/flexfec_sender.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +namespace { + +constexpr int kFlexfecPayloadType = 123; +constexpr uint32_t kMediaSsrc = 1234; +constexpr uint32_t kFlexfecSsrc = 5678; +const char kNoMid[] = ""; +const std::vector<RtpExtension> kNoRtpHeaderExtensions; +const std::vector<RtpExtensionSize> kNoRtpHeaderExtensionSizes; + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + size_t i = 0; + if (size < 5 || size > 200) { + return; + } + SimulatedClock clock(1 + data[i++]); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + FecProtectionParams params = { + data[i++], static_cast<int>(data[i++] % 100), + data[i++] <= 127 ? kFecMaskRandom : kFecMaskBursty}; + sender.SetProtectionParameters(params, params); + uint16_t seq_num = data[i++]; + + while (i + 1 < size) { + // Everything past the base RTP header (12 bytes) is payload, + // from the perspective of FlexFEC. + size_t payload_size = data[i++]; + if (i + kRtpHeaderSize + payload_size >= size) + break; + std::unique_ptr<uint8_t[]> packet( + new uint8_t[kRtpHeaderSize + payload_size]); + memcpy(packet.get(), &data[i], kRtpHeaderSize + payload_size); + i += kRtpHeaderSize + payload_size; + ByteWriter<uint16_t>::WriteBigEndian(&packet[2], seq_num++); + ByteWriter<uint32_t>::WriteBigEndian(&packet[8], kMediaSsrc); + RtpPacketToSend rtp_packet(nullptr); + if (!rtp_packet.Parse(packet.get(), kRtpHeaderSize + payload_size)) + break; + sender.AddPacketAndGenerateFec(rtp_packet); + sender.GetFecPackets(); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/forward_error_correction_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/forward_error_correction_fuzzer.cc new file mode 100644 index 0000000000..04a459bc71 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/forward_error_correction_fuzzer.cc @@ -0,0 +1,119 @@ +/* + * 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 <memory> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "rtc_base/byte_buffer.h" + +namespace webrtc { + +namespace { +constexpr uint32_t kMediaSsrc = 100200300; +constexpr uint32_t kFecSsrc = 111222333; + +constexpr size_t kPacketSize = 50; +constexpr size_t kMaxPacketsInBuffer = 48; +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 5000) { + return; + } + // Object under test. + std::unique_ptr<ForwardErrorCorrection> fec = + ForwardErrorCorrection::CreateFlexfec(kFecSsrc, kMediaSsrc); + + // Entropy from fuzzer. + rtc::ByteBufferReader fuzz_buffer(reinterpret_cast<const char*>(data), size); + + // Initial stream state. + uint16_t media_seqnum; + if (!fuzz_buffer.ReadUInt16(&media_seqnum)) + return; + const uint16_t original_media_seqnum = media_seqnum; + uint16_t fec_seqnum; + if (!fuzz_buffer.ReadUInt16(&fec_seqnum)) + return; + + // Existing packets in the packet buffer. + ForwardErrorCorrection::RecoveredPacketList recovered_packets; + uint8_t num_existing_recovered_packets; + if (!fuzz_buffer.ReadUInt8(&num_existing_recovered_packets)) + return; + for (size_t i = 0; i < num_existing_recovered_packets % kMaxPacketsInBuffer; + ++i) { + ForwardErrorCorrection::RecoveredPacket* recovered_packet = + new ForwardErrorCorrection::RecoveredPacket(); + recovered_packet->pkt = rtc::scoped_refptr<ForwardErrorCorrection::Packet>( + new ForwardErrorCorrection::Packet()); + recovered_packet->pkt->data.SetSize(kPacketSize); + memset(recovered_packet->pkt->data.MutableData(), 0, kPacketSize); + recovered_packet->ssrc = kMediaSsrc; + recovered_packet->seq_num = media_seqnum++; + recovered_packets.emplace_back(recovered_packet); + } + + // New packets received from the network. + ForwardErrorCorrection::ReceivedPacket received_packet; + received_packet.pkt = rtc::scoped_refptr<ForwardErrorCorrection::Packet>( + new ForwardErrorCorrection::Packet()); + received_packet.pkt->data.SetSize(kPacketSize); + received_packet.pkt->data.EnsureCapacity(IP_PACKET_SIZE); + uint8_t* packet_buffer = received_packet.pkt->data.MutableData(); + uint8_t reordering; + uint16_t seq_num_diff; + uint8_t packet_type; + uint8_t packet_loss; + while (true) { + if (!fuzz_buffer.ReadBytes(reinterpret_cast<char*>(packet_buffer), + kPacketSize)) { + return; + } + if (!fuzz_buffer.ReadUInt8(&reordering)) + return; + if (!fuzz_buffer.ReadUInt16(&seq_num_diff)) + return; + if (!fuzz_buffer.ReadUInt8(&packet_type)) + return; + if (!fuzz_buffer.ReadUInt8(&packet_loss)) + return; + + if (reordering % 10 != 0) + seq_num_diff = 0; + + if (packet_type % 2 == 0) { + received_packet.is_fec = true; + received_packet.ssrc = kFecSsrc; + received_packet.seq_num = seq_num_diff + fec_seqnum++; + + // Overwrite parts of the FlexFEC header for fuzzing efficiency. + packet_buffer[0] = 0; // R, F bits. + ByteWriter<uint8_t>::WriteBigEndian(&packet_buffer[8], 1); // SSRCCount. + ByteWriter<uint32_t>::WriteBigEndian(&packet_buffer[12], + kMediaSsrc); // SSRC_i. + ByteWriter<uint16_t>::WriteBigEndian( + &packet_buffer[16], original_media_seqnum); // SN base_i. + } else { + received_packet.is_fec = false; + received_packet.ssrc = kMediaSsrc; + received_packet.seq_num = seq_num_diff + media_seqnum++; + } + + if (packet_loss % 10 == 0) + continue; + + fec->DecodeFec(received_packet, &recovered_packets); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/frame_buffer2_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/frame_buffer2_fuzzer.cc new file mode 100644 index 0000000000..ec1bbbb4c1 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/frame_buffer2_fuzzer.cc @@ -0,0 +1,116 @@ +/* + * 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 <memory> + +#include "api/task_queue/task_queue_base.h" +#include "modules/video_coding/frame_buffer2.h" +#include "modules/video_coding/timing/timing.h" +#include "test/scoped_key_value_config.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +namespace { + +// When DataReader runs out of data provided in the constructor it will +// just set/return 0 instead. +struct DataReader { + DataReader(const uint8_t* data, size_t size) : data_(data), size_(size) {} + + void CopyTo(void* destination, size_t dest_size) { + memset(destination, 0, dest_size); + + size_t bytes_to_copy = std::min(size_ - offset_, dest_size); + memcpy(destination, data_ + offset_, bytes_to_copy); + offset_ += bytes_to_copy; + } + + template <typename T> + T GetNum() { + T res; + if (offset_ + sizeof(res) < size_) { + memcpy(&res, data_ + offset_, sizeof(res)); + offset_ += sizeof(res); + return res; + } + + offset_ = size_; + return T(0); + } + + bool MoreToRead() { return offset_ < size_; } + + const uint8_t* const data_; + size_t size_; + size_t offset_ = 0; +}; + +class FuzzyFrameObject : public EncodedFrame { + public: + FuzzyFrameObject() {} + ~FuzzyFrameObject() {} + + int64_t ReceivedTime() const override { return 0; } + int64_t RenderTime() const override { return _renderTimeMs; } +}; +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000) { + return; + } + DataReader reader(data, size); + GlobalSimulatedTimeController time_controller(Timestamp::Seconds(0)); + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue = + time_controller.GetTaskQueueFactory()->CreateTaskQueue( + "time_tq", TaskQueueFactory::Priority::NORMAL); + test::ScopedKeyValueConfig field_trials; + VCMTiming timing(time_controller.GetClock(), field_trials); + video_coding::FrameBuffer frame_buffer(time_controller.GetClock(), &timing, + field_trials); + + bool next_frame_task_running = false; + + while (reader.MoreToRead()) { + if (reader.GetNum<uint8_t>() % 2) { + std::unique_ptr<FuzzyFrameObject> frame(new FuzzyFrameObject()); + frame->SetId(reader.GetNum<int64_t>()); + frame->SetSpatialIndex(reader.GetNum<uint8_t>() % 5); + frame->SetTimestamp(reader.GetNum<uint32_t>()); + frame->num_references = + reader.GetNum<uint8_t>() % EncodedFrame::kMaxFrameReferences; + + for (size_t r = 0; r < frame->num_references; ++r) + frame->references[r] = reader.GetNum<int64_t>(); + + frame_buffer.InsertFrame(std::move(frame)); + } else { + if (!next_frame_task_running) { + next_frame_task_running = true; + bool keyframe_required = reader.GetNum<uint8_t>() % 2; + int max_wait_time_ms = reader.GetNum<uint8_t>(); + task_queue->PostTask([&task_queue, &frame_buffer, + &next_frame_task_running, keyframe_required, + max_wait_time_ms] { + frame_buffer.NextFrame( + max_wait_time_ms, keyframe_required, task_queue.get(), + [&next_frame_task_running](std::unique_ptr<EncodedFrame> frame) { + next_frame_task_running = false; + }); + }); + } + } + + time_controller.AdvanceTime(TimeDelta::Millis(reader.GetNum<uint8_t>())); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/frame_buffer_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/frame_buffer_fuzzer.cc new file mode 100644 index 0000000000..e58d5e9f98 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/frame_buffer_fuzzer.cc @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 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 "api/array_view.h" +#include "api/video/encoded_frame.h" +#include "api/video/frame_buffer.h" +#include "rtc_base/numerics/sequence_number_unwrapper.h" +#include "test/fuzzers/fuzz_data_helper.h" +#include "test/scoped_key_value_config.h" + +namespace webrtc { +namespace { +class FuzzyFrameObject : public EncodedFrame { + public: + int64_t ReceivedTime() const override { return 0; } + int64_t RenderTime() const override { return 0; } +}; + +constexpr int kFrameIdLength = 1 << 15; + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 10000) { + return; + } + + test::ScopedKeyValueConfig field_trials; + FrameBuffer buffer(/*max_frame_slots=*/100, /*max_decode_history=*/1000, + field_trials); + test::FuzzDataHelper helper(rtc::MakeArrayView(data, size)); + SeqNumUnwrapper<uint16_t, kFrameIdLength> unwrapper; + + while (helper.BytesLeft() > 0) { + int action = helper.ReadOrDefaultValue<uint8_t>(0) % 6; + + switch (action) { + case 0: { + buffer.LastContinuousFrameId(); + break; + } + case 1: { + buffer.LastContinuousTemporalUnitFrameId(); + break; + } + case 2: { + buffer.DecodableTemporalUnitsInfo(); + break; + } + case 3: { + buffer.ExtractNextDecodableTemporalUnit(); + break; + } + case 4: { + buffer.DropNextDecodableTemporalUnit(); + break; + } + case 5: { + auto frame = std::make_unique<FuzzyFrameObject>(); + frame->SetTimestamp(helper.ReadOrDefaultValue<uint32_t>(0)); + int64_t wire_id = + helper.ReadOrDefaultValue<uint16_t>(0) & (kFrameIdLength - 1); + frame->SetId(unwrapper.Unwrap(wire_id)); + frame->is_last_spatial_layer = helper.ReadOrDefaultValue<bool>(false); + + frame->num_references = helper.ReadOrDefaultValue<uint8_t>(0) % + EncodedFrame::kMaxFrameReferences; + + for (uint8_t i = 0; i < frame->num_references; ++i) { + frame->references[i] = helper.ReadOrDefaultValue<int64_t>(0); + } + + buffer.InsertFrame(std::move(frame)); + break; + } + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/fuzz_data_helper.cc b/third_party/libwebrtc/test/fuzzers/fuzz_data_helper.cc new file mode 100644 index 0000000000..866f7bc4b8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/fuzz_data_helper.cc @@ -0,0 +1,20 @@ +/* + * 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 "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace test { + +FuzzDataHelper::FuzzDataHelper(rtc::ArrayView<const uint8_t> data) + : data_(data) {} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/fuzz_data_helper.h b/third_party/libwebrtc/test/fuzzers/fuzz_data_helper.h new file mode 100644 index 0000000000..cedc31d0a4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/fuzz_data_helper.h @@ -0,0 +1,105 @@ +/* + * 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. + */ + +#ifndef TEST_FUZZERS_FUZZ_DATA_HELPER_H_ +#define TEST_FUZZERS_FUZZ_DATA_HELPER_H_ + +#include <limits> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace test { + +// Helper class to take care of the fuzzer input, read from it, and keep track +// of when the end of the data has been reached. +class FuzzDataHelper { + public: + explicit FuzzDataHelper(rtc::ArrayView<const uint8_t> data); + + // Returns true if n bytes can be read. + bool CanReadBytes(size_t n) const { return data_ix_ + n <= data_.size(); } + + // Reads and returns data of type T. + template <typename T> + T Read() { + RTC_CHECK(CanReadBytes(sizeof(T))); + T x = ByteReader<T>::ReadLittleEndian(&data_[data_ix_]); + data_ix_ += sizeof(T); + return x; + } + + // Reads and returns data of type T. Returns default_value if not enough + // fuzzer input remains to read a T. + template <typename T> + T ReadOrDefaultValue(T default_value) { + if (!CanReadBytes(sizeof(T))) { + return default_value; + } + return Read<T>(); + } + + // Like ReadOrDefaultValue, but replaces the value 0 with default_value. + template <typename T> + T ReadOrDefaultValueNotZero(T default_value) { + static_assert(std::is_integral<T>::value, ""); + T x = ReadOrDefaultValue(default_value); + return x == 0 ? default_value : x; + } + + // Returns one of the elements from the provided input array. The selection + // is based on the fuzzer input data. If not enough fuzzer data is available, + // the method will return the first element in the input array. The reason for + // not flagging this as an error is to allow the method to be called from + // class constructors, and in constructors we typically do not handle + // errors. The code will work anyway, and the fuzzer will likely see that + // providing more data will actually make this method return something else. + template <typename T, size_t N> + T SelectOneOf(const T (&select_from)[N]) { + static_assert(N <= std::numeric_limits<uint8_t>::max(), ""); + // Read an index between 0 and select_from.size() - 1 from the fuzzer data. + uint8_t index = ReadOrDefaultValue<uint8_t>(0) % N; + return select_from[index]; + } + + rtc::ArrayView<const uint8_t> ReadByteArray(size_t bytes) { + if (!CanReadBytes(bytes)) { + return rtc::ArrayView<const uint8_t>(nullptr, 0); + } + const size_t index_to_return = data_ix_; + data_ix_ += bytes; + return data_.subview(index_to_return, bytes); + } + + // If sizeof(T) > BytesLeft then the remaining bytes will be used and the rest + // of the object will be zero initialized. + template <typename T> + void CopyTo(T* object) { + memset(object, 0, sizeof(T)); + + size_t bytes_to_copy = std::min(BytesLeft(), sizeof(T)); + memcpy(object, data_.data() + data_ix_, bytes_to_copy); + data_ix_ += bytes_to_copy; + } + + size_t BytesRead() const { return data_ix_; } + + size_t BytesLeft() const { return data_.size() - data_ix_; } + + private: + rtc::ArrayView<const uint8_t> data_; + size_t data_ix_ = 0; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_FUZZERS_FUZZ_DATA_HELPER_H_ diff --git a/third_party/libwebrtc/test/fuzzers/h264_bitstream_parser_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/h264_bitstream_parser_fuzzer.cc new file mode 100644 index 0000000000..cd1128c0b4 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/h264_bitstream_parser_fuzzer.cc @@ -0,0 +1,21 @@ +/* + * 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 <stdint.h> + +#include "common_video/h264/h264_bitstream_parser.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + H264BitstreamParser h264_bitstream_parser; + h264_bitstream_parser.ParseBitstream( + rtc::ArrayView<const uint8_t>(data, size)); + h264_bitstream_parser.GetLastSliceQp(); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/h264_depacketizer_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/h264_depacketizer_fuzzer.cc new file mode 100644 index 0000000000..97127228ed --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/h264_depacketizer_fuzzer.cc @@ -0,0 +1,19 @@ +/* + * Copyright (c) 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. + */ +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 200000) + return; + VideoRtpDepacketizerH264 depacketizer; + depacketizer.Parse(rtc::CopyOnWriteBuffer(data, size)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/neteq_rtp_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/neteq_rtp_fuzzer.cc new file mode 100644 index 0000000000..348c84f040 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/neteq_rtp_fuzzer.cc @@ -0,0 +1,184 @@ +/* + * 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 <algorithm> +#include <cmath> +#include <cstring> +#include <memory> +#include <vector> + +#include "api/array_view.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "modules/audio_coding/codecs/pcm16b/audio_encoder_pcm16b.h" +#include "modules/audio_coding/neteq/tools/audio_checksum.h" +#include "modules/audio_coding/neteq/tools/encode_neteq_input.h" +#include "modules/audio_coding/neteq/tools/neteq_test.h" +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace test { +namespace { +constexpr int kPayloadType = 95; + +class SineGenerator : public EncodeNetEqInput::Generator { + public: + explicit SineGenerator(int sample_rate_hz) + : sample_rate_hz_(sample_rate_hz) {} + + rtc::ArrayView<const int16_t> Generate(size_t num_samples) override { + if (samples_.size() < num_samples) { + samples_.resize(num_samples); + } + + rtc::ArrayView<int16_t> output(samples_.data(), num_samples); + for (auto& x : output) { + x = static_cast<int16_t>(2000.0 * std::sin(phase_)); + phase_ += 2 * kPi * kFreqHz / sample_rate_hz_; + } + return output; + } + + private: + static constexpr int kFreqHz = 300; // The sinewave frequency. + const int sample_rate_hz_; + const double kPi = std::acos(-1); + std::vector<int16_t> samples_; + double phase_ = 0.0; +}; + +class FuzzRtpInput : public NetEqInput { + public: + explicit FuzzRtpInput(rtc::ArrayView<const uint8_t> data) : data_(data) { + AudioEncoderPcm16B::Config config; + config.payload_type = kPayloadType; + config.sample_rate_hz = 32000; + std::unique_ptr<AudioEncoder> encoder(new AudioEncoderPcm16B(config)); + std::unique_ptr<EncodeNetEqInput::Generator> generator( + new SineGenerator(config.sample_rate_hz)); + input_.reset(new EncodeNetEqInput(std::move(generator), std::move(encoder), + std::numeric_limits<int64_t>::max())); + packet_ = input_->PopPacket(); + FuzzHeader(); + MaybeFuzzPayload(); + } + + absl::optional<int64_t> NextPacketTime() const override { + return packet_->time_ms; + } + + absl::optional<int64_t> NextOutputEventTime() const override { + return input_->NextOutputEventTime(); + } + + std::unique_ptr<PacketData> PopPacket() override { + RTC_DCHECK(packet_); + std::unique_ptr<PacketData> packet_to_return = std::move(packet_); + packet_ = input_->PopPacket(); + FuzzHeader(); + MaybeFuzzPayload(); + return packet_to_return; + } + + void AdvanceOutputEvent() override { return input_->AdvanceOutputEvent(); } + + bool ended() const override { return ended_; } + + absl::optional<RTPHeader> NextHeader() const override { + RTC_DCHECK(packet_); + return packet_->header; + } + + private: + void FuzzHeader() { + constexpr size_t kNumBytesToFuzz = 11; + if (data_ix_ + kNumBytesToFuzz > data_.size()) { + ended_ = true; + return; + } + RTC_DCHECK(packet_); + const size_t start_ix = data_ix_; + packet_->header.payloadType = + ByteReader<uint8_t>::ReadLittleEndian(&data_[data_ix_]); + packet_->header.payloadType &= 0x7F; + data_ix_ += sizeof(uint8_t); + packet_->header.sequenceNumber = + ByteReader<uint16_t>::ReadLittleEndian(&data_[data_ix_]); + data_ix_ += sizeof(uint16_t); + packet_->header.timestamp = + ByteReader<uint32_t>::ReadLittleEndian(&data_[data_ix_]); + data_ix_ += sizeof(uint32_t); + packet_->header.ssrc = + ByteReader<uint32_t>::ReadLittleEndian(&data_[data_ix_]); + data_ix_ += sizeof(uint32_t); + RTC_CHECK_EQ(data_ix_ - start_ix, kNumBytesToFuzz); + } + + void MaybeFuzzPayload() { + // Read one byte of fuzz data to determine how many payload bytes to fuzz. + if (data_ix_ + 1 > data_.size()) { + ended_ = true; + return; + } + size_t bytes_to_fuzz = data_[data_ix_++]; + + // Restrict number of bytes to fuzz to 16; a reasonably low number enough to + // cover a few RED headers. Also don't write outside the payload length. + bytes_to_fuzz = std::min(bytes_to_fuzz % 16, packet_->payload.size()); + + if (bytes_to_fuzz == 0) + return; + + if (data_ix_ + bytes_to_fuzz > data_.size()) { + ended_ = true; + return; + } + + std::memcpy(packet_->payload.data(), &data_[data_ix_], bytes_to_fuzz); + data_ix_ += bytes_to_fuzz; + } + + bool ended_ = false; + rtc::ArrayView<const uint8_t> data_; + size_t data_ix_ = 0; + std::unique_ptr<EncodeNetEqInput> input_; + std::unique_ptr<PacketData> packet_; +}; +} // namespace + +void FuzzOneInputTest(const uint8_t* data, size_t size) { + std::unique_ptr<FuzzRtpInput> input( + new FuzzRtpInput(rtc::ArrayView<const uint8_t>(data, size))); + std::unique_ptr<AudioChecksum> output(new AudioChecksum); + NetEqTest::Callbacks callbacks; + NetEq::Config config; + auto codecs = NetEqTest::StandardDecoderMap(); + // kPayloadType is the payload type that will be used for encoding. Verify + // that it is included in the standard decoder map, and that it points to the + // expected decoder type. + const auto it = codecs.find(kPayloadType); + RTC_CHECK(it != codecs.end()); + RTC_CHECK(it->second == SdpAudioFormat("L16", 32000, 1)); + + NetEqTest test(config, CreateBuiltinAudioDecoderFactory(), codecs, + /*text_log=*/nullptr, /*neteq_factory=*/nullptr, + std::move(input), std::move(output), callbacks); + test.Run(); +} + +} // namespace test + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 70000) { + return; + } + test::FuzzOneInputTest(data, size); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/neteq_signal_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/neteq_signal_fuzzer.cc new file mode 100644 index 0000000000..8653f137a2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/neteq_signal_fuzzer.cc @@ -0,0 +1,201 @@ +/* + * 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 <cmath> +#include <limits> +#include <memory> +#include <vector> + +#include "api/array_view.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "modules/audio_coding/codecs/pcm16b/audio_encoder_pcm16b.h" +#include "modules/audio_coding/neteq/tools/audio_checksum.h" +#include "modules/audio_coding/neteq/tools/encode_neteq_input.h" +#include "modules/audio_coding/neteq/tools/neteq_test.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/random.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +namespace test { +namespace { +// Generate a mixture of sine wave and gaussian noise. +class SineAndNoiseGenerator : public EncodeNetEqInput::Generator { + public: + // The noise generator is seeded with a value from the fuzzer data, but 0 is + // avoided (since it is not allowed by the Random class). + SineAndNoiseGenerator(int sample_rate_hz, FuzzDataHelper* fuzz_data) + : sample_rate_hz_(sample_rate_hz), + fuzz_data_(*fuzz_data), + noise_generator_(fuzz_data_.ReadOrDefaultValueNotZero<uint64_t>(1)) {} + + // Generates num_samples of the sine-gaussian mixture. + rtc::ArrayView<const int16_t> Generate(size_t num_samples) override { + if (samples_.size() < num_samples) { + samples_.resize(num_samples); + } + + rtc::ArrayView<int16_t> output(samples_.data(), num_samples); + // Randomize an amplitude between 0 and 32768; use 65000/2 if we are out of + // fuzzer data. + const float amplitude = fuzz_data_.ReadOrDefaultValue<uint16_t>(65000) / 2; + // Randomize a noise standard deviation between 0 and 1999. + const float noise_std = fuzz_data_.ReadOrDefaultValue<uint16_t>(0) % 2000; + for (auto& x : output) { + x = rtc::saturated_cast<int16_t>(amplitude * std::sin(phase_) + + noise_generator_.Gaussian(0, noise_std)); + phase_ += 2 * kPi * kFreqHz / sample_rate_hz_; + } + return output; + } + + private: + static constexpr int kFreqHz = 300; // The sinewave frequency. + const int sample_rate_hz_; + const double kPi = std::acos(-1); + std::vector<int16_t> samples_; + double phase_ = 0.0; + FuzzDataHelper& fuzz_data_; + Random noise_generator_; +}; + +class FuzzSignalInput : public NetEqInput { + public: + explicit FuzzSignalInput(FuzzDataHelper* fuzz_data, + int sample_rate, + uint8_t payload_type) + : fuzz_data_(*fuzz_data) { + AudioEncoderPcm16B::Config config; + config.payload_type = payload_type; + config.sample_rate_hz = sample_rate; + std::unique_ptr<AudioEncoder> encoder(new AudioEncoderPcm16B(config)); + std::unique_ptr<EncodeNetEqInput::Generator> generator( + new SineAndNoiseGenerator(config.sample_rate_hz, fuzz_data)); + input_.reset(new EncodeNetEqInput(std::move(generator), std::move(encoder), + std::numeric_limits<int64_t>::max())); + packet_ = input_->PopPacket(); + + // Select an output event period. This is how long time we wait between each + // call to NetEq::GetAudio. 10 ms is nominal, 9 and 11 ms will both lead to + // clock drift (in different directions). + constexpr int output_event_periods[] = {9, 10, 11}; + output_event_period_ms_ = fuzz_data_.SelectOneOf(output_event_periods); + } + + absl::optional<int64_t> NextPacketTime() const override { + return packet_->time_ms; + } + + absl::optional<int64_t> NextOutputEventTime() const override { + return next_output_event_ms_; + } + + std::unique_ptr<PacketData> PopPacket() override { + RTC_DCHECK(packet_); + std::unique_ptr<PacketData> packet_to_return = std::move(packet_); + do { + packet_ = input_->PopPacket(); + // If the next value from the fuzzer input is 0, the packet is discarded + // and the next one is pulled from the source. + } while (fuzz_data_.CanReadBytes(1) && fuzz_data_.Read<uint8_t>() == 0); + if (fuzz_data_.CanReadBytes(1)) { + // Generate jitter by setting an offset for the arrival time. + const int8_t arrival_time_offset_ms = fuzz_data_.Read<int8_t>(); + // The arrival time can not be before the previous packets. + packet_->time_ms = std::max(packet_to_return->time_ms, + packet_->time_ms + arrival_time_offset_ms); + } else { + // Mark that we are at the end of the test. However, the current packet is + // still valid (but it may not have been fuzzed as expected). + ended_ = true; + } + return packet_to_return; + } + + void AdvanceOutputEvent() override { + next_output_event_ms_ += output_event_period_ms_; + } + + bool ended() const override { return ended_; } + + absl::optional<RTPHeader> NextHeader() const override { + RTC_DCHECK(packet_); + return packet_->header; + } + + private: + bool ended_ = false; + FuzzDataHelper& fuzz_data_; + std::unique_ptr<EncodeNetEqInput> input_; + std::unique_ptr<PacketData> packet_; + int64_t next_output_event_ms_ = 0; + int64_t output_event_period_ms_ = 10; +}; + +template <class T> +bool MapHas(const std::map<int, T>& m, int key, const T& value) { + const auto it = m.find(key); + return (it != m.end() && it->second == value); +} + +} // namespace + +void FuzzOneInputTest(const uint8_t* data, size_t size) { + if (size < 1 || size > 65000) { + return; + } + + FuzzDataHelper fuzz_data(rtc::ArrayView<const uint8_t>(data, size)); + + // Allowed sample rates and payload types used in the test. + std::pair<int, uint8_t> rate_types[] = { + {8000, 93}, {16000, 94}, {32000, 95}, {48000, 96}}; + const auto rate_type = fuzz_data.SelectOneOf(rate_types); + const int sample_rate = rate_type.first; + const uint8_t payload_type = rate_type.second; + + // Set up the input signal generator. + std::unique_ptr<FuzzSignalInput> input( + new FuzzSignalInput(&fuzz_data, sample_rate, payload_type)); + + // Output sink for the test. + std::unique_ptr<AudioChecksum> output(new AudioChecksum); + + // Configure NetEq and the NetEqTest object. + NetEqTest::Callbacks callbacks; + NetEq::Config config; + config.enable_post_decode_vad = true; + config.enable_fast_accelerate = true; + auto codecs = NetEqTest::StandardDecoderMap(); + // rate_types contains the payload types that will be used for encoding. + // Verify that they all are included in the standard decoder map, and that + // they point to the expected decoder types. + RTC_CHECK( + MapHas(codecs, rate_types[0].second, SdpAudioFormat("l16", 8000, 1))); + RTC_CHECK( + MapHas(codecs, rate_types[1].second, SdpAudioFormat("l16", 16000, 1))); + RTC_CHECK( + MapHas(codecs, rate_types[2].second, SdpAudioFormat("l16", 32000, 1))); + RTC_CHECK( + MapHas(codecs, rate_types[3].second, SdpAudioFormat("l16", 48000, 1))); + + NetEqTest test(config, CreateBuiltinAudioDecoderFactory(), codecs, + /*text_log=*/nullptr, /*neteq_factory=*/nullptr, + std::move(input), std::move(output), callbacks); + test.Run(); +} + +} // namespace test + +void FuzzOneInput(const uint8_t* data, size_t size) { + test::FuzzOneInputTest(data, size); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/pseudotcp_parser_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/pseudotcp_parser_fuzzer.cc new file mode 100644 index 0000000000..78ddf0e455 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/pseudotcp_parser_fuzzer.cc @@ -0,0 +1,47 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include "p2p/base/pseudo_tcp.h" +#include "rtc_base/thread.h" + +namespace webrtc { +class FakeIPseudoTcpNotify : public cricket::IPseudoTcpNotify { + public: + void OnTcpOpen(cricket::PseudoTcp* tcp) {} + void OnTcpReadable(cricket::PseudoTcp* tcp) {} + void OnTcpWriteable(cricket::PseudoTcp* tcp) {} + void OnTcpClosed(cricket::PseudoTcp* tcp, uint32_t error) {} + + cricket::IPseudoTcpNotify::WriteResult TcpWritePacket(cricket::PseudoTcp* tcp, + const char* buffer, + size_t len) { + return cricket::IPseudoTcpNotify::WriteResult::WR_SUCCESS; + } +}; + +struct Environment { + explicit Environment(cricket::IPseudoTcpNotify* notifier) + : ptcp(notifier, 0) {} + + // We need the thread to avoid some uninteresting crashes, since the + // production code expects there to be a thread object available. + rtc::AutoThread thread; + cricket::PseudoTcp ptcp; +}; + +Environment* env = new Environment(new FakeIPseudoTcpNotify()); + +void FuzzOneInput(const uint8_t* data, size_t size) { + env->ptcp.NotifyPacket(reinterpret_cast<const char*>(data), size); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/residual_echo_detector_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/residual_echo_detector_fuzzer.cc new file mode 100644 index 0000000000..0efe81f220 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/residual_echo_detector_fuzzer.cc @@ -0,0 +1,66 @@ +/* + * 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 <math.h> +#include <string.h> + +#include <algorithm> +#include <bitset> +#include <vector> + +#include "api/audio/echo_detector_creator.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + // Number of times to update the echo detector. + constexpr size_t kNrOfUpdates = 7; + // Each round of updates requires a call to both AnalyzeRender and + // AnalyzeCapture, so the amount of needed input bytes doubles. Also, two + // bytes are used to set the call order. + constexpr size_t kNrOfNeededInputBytes = 2 * kNrOfUpdates * sizeof(float) + 2; + // The maximum audio energy that an audio frame can have is equal to the + // number of samples in the frame multiplied by 2^30. We use a single sample + // to represent an audio frame in this test, so it should have a maximum value + // equal to the square root of that value. + const float maxFuzzedValue = sqrtf(20 * 48) * 32768; + if (size < kNrOfNeededInputBytes) { + return; + } + size_t read_idx = 0; + // Use the first two bytes to choose the call order. + uint16_t call_order_int; + memcpy(&call_order_int, &data[read_idx], 2); + read_idx += 2; + std::bitset<16> call_order(call_order_int); + + rtc::scoped_refptr<EchoDetector> echo_detector = CreateEchoDetector(); + std::vector<float> input(1); + // Call AnalyzeCaptureAudio once to prevent the flushing of the buffer. + echo_detector->AnalyzeCaptureAudio(input); + for (size_t i = 0; i < 2 * kNrOfUpdates; ++i) { + // Convert 4 input bytes to a float. + RTC_DCHECK_LE(read_idx + sizeof(float), size); + memcpy(input.data(), &data[read_idx], sizeof(float)); + read_idx += sizeof(float); + if (!isfinite(input[0]) || fabs(input[0]) > maxFuzzedValue) { + // Ignore infinity, nan values and values that are unrealistically large. + continue; + } + if (call_order[i]) { + echo_detector->AnalyzeRenderAudio(input); + } else { + echo_detector->AnalyzeCaptureAudio(input); + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtcp_receiver_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtcp_receiver_fuzzer.cc new file mode 100644 index 0000000000..8bad9e456a --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtcp_receiver_fuzzer.cc @@ -0,0 +1,52 @@ +/* + * Copyright (c) 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. + */ +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" +#include "modules/rtp_rtcp/source/rtcp_receiver.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace { + +constexpr int kRtcpIntervalMs = 1000; + +// RTCP is typically sent over UDP, which has a maximum payload length +// of 65535 bytes. We err on the side of caution and check a bit above that. +constexpr size_t kMaxInputLenBytes = 66000; + +class NullModuleRtpRtcp : public RTCPReceiver::ModuleRtpRtcp { + public: + void SetTmmbn(std::vector<rtcp::TmmbItem>) override {} + void OnRequestSendReport() override {} + void OnReceivedNack(const std::vector<uint16_t>&) override {} + void OnReceivedRtcpReportBlocks(const ReportBlockList&) override {} +}; + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > kMaxInputLenBytes) { + return; + } + + NullModuleRtpRtcp rtp_rtcp_module; + SimulatedClock clock(1234); + + RtpRtcpInterface::Configuration config; + config.clock = &clock; + config.rtcp_report_interval_ms = kRtcpIntervalMs; + config.local_media_ssrc = 1; + + RTCPReceiver receiver(config, &rtp_rtcp_module); + + receiver.IncomingPacket(data, size); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_depacketizer_av1_assemble_frame_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_depacketizer_av1_assemble_frame_fuzzer.cc new file mode 100644 index 0000000000..168e7b606b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_depacketizer_av1_assemble_frame_fuzzer.cc @@ -0,0 +1,39 @@ +/* + * 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 "modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h" + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "api/array_view.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + std::vector<rtc::ArrayView<const uint8_t>> rtp_payloads; + + // Convert plain array of bytes into array of array bytes. + test::FuzzDataHelper fuzz_input(rtc::MakeArrayView(data, size)); + while (fuzz_input.CanReadBytes(sizeof(uint16_t))) { + // In practice one rtp payload can be up to ~1200 - 1500 bytes. Majority + // of the payload is just copied. To make fuzzing more efficient limit the + // size of rtp payload to realistic value. + uint16_t next_size = fuzz_input.Read<uint16_t>() % 1200; + if (next_size > fuzz_input.BytesLeft()) { + next_size = fuzz_input.BytesLeft(); + } + rtp_payloads.push_back(fuzz_input.ReadByteArray(next_size)); + } + // Run code under test. + VideoRtpDepacketizerAv1().AssembleFrame(rtp_payloads); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_dependency_descriptor_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_dependency_descriptor_fuzzer.cc new file mode 100644 index 0000000000..82404f7264 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_dependency_descriptor_fuzzer.cc @@ -0,0 +1,94 @@ +/* + * 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 <algorithm> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <utility> + +#include "api/array_view.h" +#include "common_video/generic_frame_descriptor/generic_frame_info.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" +#include "rtc_base/checks.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + FrameDependencyStructure structure1; + // nullptr during 1st while loop, after that should point to structure1. + const FrameDependencyStructure* structure1_ptr = nullptr; + std::unique_ptr<const FrameDependencyStructure> structure2; + + test::FuzzDataHelper fuzz_data(rtc::MakeArrayView(data, size)); + while (fuzz_data.CanReadBytes(1)) { + // Treat next byte as size of the next extension. That aligns how + // two-byte rtp header extension sizes are written. + size_t next_size = fuzz_data.Read<uint8_t>(); + auto raw = + fuzz_data.ReadByteArray(std::min(next_size, fuzz_data.BytesLeft())); + + // Read the random input. + DependencyDescriptor descriptor1; + if (!RtpDependencyDescriptorExtension::Parse(raw, structure1_ptr, + &descriptor1)) { + // Ignore invalid buffer and move on. + continue; + } + if (descriptor1.attached_structure) { + structure1 = *descriptor1.attached_structure; + structure1_ptr = &structure1; + } + RTC_CHECK(structure1_ptr); + + // Write parsed descriptor back into raw buffer. + size_t value_size = + RtpDependencyDescriptorExtension::ValueSize(structure1, descriptor1); + // Check `writer` use minimal number of bytes to pack the descriptor by + // checking it doesn't use more than reader consumed. + RTC_CHECK_LE(value_size, raw.size()); + uint8_t some_memory[256]; + // That should be true because value_size <= next_size < 256 + RTC_CHECK_LT(value_size, 256); + rtc::ArrayView<uint8_t> write_buffer(some_memory, value_size); + RTC_CHECK(RtpDependencyDescriptorExtension::Write(write_buffer, structure1, + descriptor1)); + + // Parse what Write assembled. + // Unlike random input that should always succeed. + DependencyDescriptor descriptor2; + RTC_CHECK(RtpDependencyDescriptorExtension::Parse( + write_buffer, structure2.get(), &descriptor2)); + // Check descriptor1 and descriptor2 have same values. + RTC_CHECK_EQ(descriptor1.first_packet_in_frame, + descriptor2.first_packet_in_frame); + RTC_CHECK_EQ(descriptor1.last_packet_in_frame, + descriptor2.last_packet_in_frame); + RTC_CHECK_EQ(descriptor1.attached_structure != nullptr, + descriptor2.attached_structure != nullptr); + // Using value_or would miss invalid corner case when one value is nullopt + // while another one is 0, but for other errors would produce much nicer + // error message than using RTC_CHECK(optional1 == optional2); + // If logger would support pretty printing optional values, value_or can be + // removed. + RTC_CHECK_EQ(descriptor1.active_decode_targets_bitmask.value_or(0), + descriptor2.active_decode_targets_bitmask.value_or(0)); + RTC_CHECK_EQ(descriptor1.frame_number, descriptor2.frame_number); + RTC_CHECK(descriptor1.resolution == descriptor2.resolution); + RTC_CHECK(descriptor1.frame_dependencies == descriptor2.frame_dependencies); + + if (descriptor2.attached_structure) { + structure2 = std::move(descriptor2.attached_structure); + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc new file mode 100644 index 0000000000..fdb4aa5f3c --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_frame_reference_finder_fuzzer.cc @@ -0,0 +1,154 @@ +/* + * 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 <memory> + +#include "api/rtp_packet_infos.h" +#include "modules/video_coding/frame_object.h" +#include "modules/video_coding/rtp_frame_reference_finder.h" + +namespace webrtc { + +namespace { +class DataReader { + public: + DataReader(const uint8_t* data, size_t size) : data_(data), size_(size) {} + + template <typename T> + void CopyTo(T* object) { + static_assert(std::is_pod<T>(), ""); + uint8_t* destination = reinterpret_cast<uint8_t*>(object); + size_t object_size = sizeof(T); + size_t num_bytes = std::min(size_ - offset_, object_size); + memcpy(destination, data_ + offset_, num_bytes); + offset_ += num_bytes; + + // If we did not have enough data, fill the rest with 0. + object_size -= num_bytes; + memset(destination + num_bytes, 0, object_size); + } + + template <typename T> + T GetNum() { + T res; + if (offset_ + sizeof(res) < size_) { + memcpy(&res, data_ + offset_, sizeof(res)); + offset_ += sizeof(res); + return res; + } + + offset_ = size_; + return T(0); + } + + bool MoreToRead() { return offset_ < size_; } + + private: + const uint8_t* data_; + size_t size_; + size_t offset_ = 0; +}; + +absl::optional<RTPVideoHeader::GenericDescriptorInfo> +GenerateGenericFrameDependencies(DataReader* reader) { + absl::optional<RTPVideoHeader::GenericDescriptorInfo> result; + uint8_t flags = reader->GetNum<uint8_t>(); + if (flags & 0b1000'0000) { + // i.e. with 50% chance there are no generic dependencies. + // in such case codec-specfic code path of the RtpFrameReferenceFinder will + // be validated. + return result; + } + + result.emplace(); + result->frame_id = reader->GetNum<int32_t>(); + result->spatial_index = (flags & 0b0111'0000) >> 4; + result->temporal_index = (flags & 0b0000'1110) >> 1; + + // Larger than supported by the RtpFrameReferenceFinder. + int num_diffs = (reader->GetNum<uint8_t>() % 16); + for (int i = 0; i < num_diffs; ++i) { + result->dependencies.push_back(result->frame_id - + (reader->GetNum<uint16_t>() % (1 << 14))); + } + + return result; +} +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + DataReader reader(data, size); + RtpFrameReferenceFinder reference_finder; + + auto codec = static_cast<VideoCodecType>(reader.GetNum<uint8_t>() % 5); + + while (reader.MoreToRead()) { + uint16_t first_seq_num = reader.GetNum<uint16_t>(); + uint16_t last_seq_num = reader.GetNum<uint16_t>(); + bool marker_bit = reader.GetNum<uint8_t>(); + + RTPVideoHeader video_header; + switch (reader.GetNum<uint8_t>() % 3) { + case 0: + video_header.frame_type = VideoFrameType::kEmptyFrame; + break; + case 1: + video_header.frame_type = VideoFrameType::kVideoFrameKey; + break; + case 2: + video_header.frame_type = VideoFrameType::kVideoFrameDelta; + break; + } + + switch (codec) { + case kVideoCodecVP8: + reader.CopyTo( + &video_header.video_type_header.emplace<RTPVideoHeaderVP8>()); + break; + case kVideoCodecVP9: + reader.CopyTo( + &video_header.video_type_header.emplace<RTPVideoHeaderVP9>()); + break; + case kVideoCodecH264: + reader.CopyTo( + &video_header.video_type_header.emplace<RTPVideoHeaderH264>()); + break; + default: + break; + } + + video_header.generic = GenerateGenericFrameDependencies(&reader); + + // clang-format off + auto frame = std::make_unique<RtpFrameObject>( + first_seq_num, + last_seq_num, + marker_bit, + /*times_nacked=*/0, + /*first_packet_received_time=*/0, + /*last_packet_received_time=*/0, + /*rtp_timestamp=*/0, + /*ntp_time_ms=*/0, + VideoSendTiming(), + /*payload_type=*/0, + codec, + kVideoRotation_0, + VideoContentType::UNSPECIFIED, + video_header, + /*color_space=*/absl::nullopt, + RtpPacketInfos(), + EncodedImageBuffer::Create(/*size=*/0)); + // clang-format on + + reference_finder.ManageFrame(std::move(frame)); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_packet_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_packet_fuzzer.cc new file mode 100644 index 0000000000..5d117529bb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_packet_fuzzer.cc @@ -0,0 +1,182 @@ +/* + * 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 <bitset> +#include <vector> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" + +namespace webrtc { +// We decide which header extensions to register by reading four bytes +// from the beginning of `data` and interpreting it as a bitmask over +// the RTPExtensionType enum. This assert ensures four bytes are enough. +static_assert(kRtpExtensionNumberOfExtensions <= 32, + "Insufficient bits read to configure all header extensions. Add " + "an extra byte and update the switches."); + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size <= 4) + return; + + // Don't use the configuration byte as part of the packet. + std::bitset<32> extensionMask(*reinterpret_cast<const uint32_t*>(data)); + data += 4; + size -= 4; + + RtpPacketReceived::ExtensionManager extensions(/*extmap_allow_mixed=*/true); + // Start at local_id = 1 since 0 is an invalid extension id. + int local_id = 1; + // Skip i = 0 since it maps to kRtpExtensionNone. + for (int i = 1; i < kRtpExtensionNumberOfExtensions; i++) { + RTPExtensionType extension_type = static_cast<RTPExtensionType>(i); + if (extensionMask[i]) { + // Extensions are registered with an ID, which you signal to the + // peer so they know what to expect. This code only cares about + // parsing so the value of the ID isn't relevant. + extensions.RegisterByType(local_id++, extension_type); + } + } + + RtpPacketReceived packet(&extensions); + packet.Parse(data, size); + + // Call packet accessors because they have extra checks. + packet.Marker(); + packet.PayloadType(); + packet.SequenceNumber(); + packet.Timestamp(); + packet.Ssrc(); + packet.Csrcs(); + + // Each extension has its own getter. It is supported behaviour to + // call GetExtension on an extension which was not registered, so we + // don't check the bitmask here. + for (int i = 0; i < kRtpExtensionNumberOfExtensions; i++) { + switch (static_cast<RTPExtensionType>(i)) { + case kRtpExtensionNone: + case kRtpExtensionNumberOfExtensions: + break; + case kRtpExtensionTransmissionTimeOffset: + int32_t offset; + packet.GetExtension<TransmissionOffset>(&offset); + break; + case kRtpExtensionAudioLevel: + bool voice_activity; + uint8_t audio_level; + packet.GetExtension<AudioLevel>(&voice_activity, &audio_level); + break; +#if !defined(WEBRTC_MOZILLA_BUILD) + case kRtpExtensionCsrcAudioLevel: { + std::vector<uint8_t> audio_levels; + packet.GetExtension<CsrcAudioLevel>(&audio_levels); + break; + } +#endif + case kRtpExtensionAbsoluteSendTime: + uint32_t sendtime; + packet.GetExtension<AbsoluteSendTime>(&sendtime); + break; + case kRtpExtensionAbsoluteCaptureTime: { + AbsoluteCaptureTime extension; + packet.GetExtension<AbsoluteCaptureTimeExtension>(&extension); + break; + } + case kRtpExtensionVideoRotation: + uint8_t rotation; + packet.GetExtension<VideoOrientation>(&rotation); + break; + case kRtpExtensionTransportSequenceNumber: + uint16_t seqnum; + packet.GetExtension<TransportSequenceNumber>(&seqnum); + break; + case kRtpExtensionTransportSequenceNumber02: { + uint16_t seqnum; + absl::optional<FeedbackRequest> feedback_request; + packet.GetExtension<TransportSequenceNumberV2>(&seqnum, + &feedback_request); + break; + } + case kRtpExtensionPlayoutDelay: { + VideoPlayoutDelay playout; + packet.GetExtension<PlayoutDelayLimits>(&playout); + break; + } + case kRtpExtensionVideoContentType: + VideoContentType content_type; + packet.GetExtension<VideoContentTypeExtension>(&content_type); + break; + case kRtpExtensionVideoTiming: { + VideoSendTiming timing; + packet.GetExtension<VideoTimingExtension>(&timing); + break; + } + case kRtpExtensionRtpStreamId: { + std::string rsid; + packet.GetExtension<RtpStreamId>(&rsid); + break; + } + case kRtpExtensionRepairedRtpStreamId: { + std::string rsid; + packet.GetExtension<RepairedRtpStreamId>(&rsid); + break; + } + case kRtpExtensionMid: { + std::string mid; + packet.GetExtension<RtpMid>(&mid); + break; + } + case kRtpExtensionGenericFrameDescriptor: { + RtpGenericFrameDescriptor descriptor; + packet.GetExtension<RtpGenericFrameDescriptorExtension00>(&descriptor); + break; + } + case kRtpExtensionColorSpace: { + ColorSpace color_space; + packet.GetExtension<ColorSpaceExtension>(&color_space); + break; + } + case kRtpExtensionInbandComfortNoise: { + absl::optional<uint8_t> noise_level; + packet.GetExtension<InbandComfortNoiseExtension>(&noise_level); + break; + } + case kRtpExtensionVideoLayersAllocation: { + VideoLayersAllocation allocation; + packet.GetExtension<RtpVideoLayersAllocationExtension>(&allocation); + break; + } + case kRtpExtensionVideoFrameTrackingId: { + uint16_t tracking_id; + packet.GetExtension<VideoFrameTrackingIdExtension>(&tracking_id); + break; + } + case kRtpExtensionDependencyDescriptor: + // This extension requires state to read and so complicated that + // deserves own fuzzer. + break; +#if defined(WEBRTC_MOZILLA_BUILD) + case kRtpExtensionCsrcAudioLevel: { + CsrcAudioLevelList levels; + packet.GetExtension<CsrcAudioLevel>(&levels); + break; + } +#endif + } + } + + // Check that zero-ing mutable extensions wouldn't cause any problems. + packet.ZeroMutableExtensions(); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_packetizer_av1_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_packetizer_av1_fuzzer.cc new file mode 100644 index 0000000000..e5550c1279 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_packetizer_av1_fuzzer.cc @@ -0,0 +1,71 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include "api/video/video_frame_type.h" +#include "modules/rtp_rtcp/source/rtp_format.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_packetizer_av1.h" +#include "rtc_base/checks.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + test::FuzzDataHelper fuzz_input(rtc::MakeArrayView(data, size)); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1200; + // Read uint8_t to be sure reduction_lens are much smaller than + // max_payload_len and thus limits structure is valid. + limits.first_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0); + limits.last_packet_reduction_len = fuzz_input.ReadOrDefaultValue<uint8_t>(0); + limits.single_packet_reduction_len = + fuzz_input.ReadOrDefaultValue<uint8_t>(0); + const VideoFrameType kFrameTypes[] = {VideoFrameType::kVideoFrameKey, + VideoFrameType::kVideoFrameDelta}; + VideoFrameType frame_type = fuzz_input.SelectOneOf(kFrameTypes); + + // Main function under test: RtpPacketizerAv1's constructor. + RtpPacketizerAv1 packetizer(fuzz_input.ReadByteArray(fuzz_input.BytesLeft()), + limits, frame_type, + /*is_last_frame_in_picture=*/true); + + size_t num_packets = packetizer.NumPackets(); + if (num_packets == 0) { + return; + } + // When packetization was successful, validate NextPacket function too. + // While at it, check that packets respect the payload size limits. + RtpPacketToSend rtp_packet(nullptr); + // Single packet. + if (num_packets == 1) { + RTC_CHECK(packetizer.NextPacket(&rtp_packet)); + RTC_CHECK_LE(rtp_packet.payload_size(), + limits.max_payload_len - limits.single_packet_reduction_len); + return; + } + // First packet. + RTC_CHECK(packetizer.NextPacket(&rtp_packet)); + RTC_CHECK_LE(rtp_packet.payload_size(), + limits.max_payload_len - limits.first_packet_reduction_len); + // Middle packets. + for (size_t i = 1; i < num_packets - 1; ++i) { + RTC_CHECK(packetizer.NextPacket(&rtp_packet)) + << "Failed to get packet#" << i; + RTC_CHECK_LE(rtp_packet.payload_size(), limits.max_payload_len) + << "Packet #" << i << " exceeds it's limit"; + } + // Last packet. + RTC_CHECK(packetizer.NextPacket(&rtp_packet)); + RTC_CHECK_LE(rtp_packet.payload_size(), + limits.max_payload_len - limits.last_packet_reduction_len); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_video_frame_assembler_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_video_frame_assembler_fuzzer.cc new file mode 100644 index 0000000000..6ab6b9a905 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_video_frame_assembler_fuzzer.cc @@ -0,0 +1,44 @@ +/* + * 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 <algorithm> +#include <cstddef> +#include <cstdint> + +#include "api/video/rtp_video_frame_assembler.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size == 0) { + return; + } + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>(1); + extensions.Register<RtpGenericFrameDescriptorExtension00>(2); + RtpPacketReceived rtp_packet(&extensions); + + RtpVideoFrameAssembler assembler( + static_cast<RtpVideoFrameAssembler::PayloadFormat>(data[0] % 6)); + + for (size_t i = 1; i < size;) { + size_t packet_size = std::min<size_t>(size - i, 300); + if (rtp_packet.Parse(data + i, packet_size)) { + assembler.InsertPacket(rtp_packet); + } + i += packet_size; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/rtp_video_layers_allocation_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/rtp_video_layers_allocation_fuzzer.cc new file mode 100644 index 0000000000..ae8b8728fb --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/rtp_video_layers_allocation_fuzzer.cc @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 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 <cstddef> +#include <cstdint> +#include <limits> + +#include "api/array_view.h" +#include "api/video/video_layers_allocation.h" +#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + // Video layers allocation is an rtp header extension. + // Per https://datatracker.ietf.org/doc/html/rfc8285#section-4.3 + // rtp header extension uses up to one byte to store the size, i.e. + // maximum size of any rtp header extension is 255 bytes. + constexpr int kMaxSize = std::numeric_limits<uint8_t>::max(); + if (size > kMaxSize) { + return; + } + auto raw = rtc::MakeArrayView(data, size); + + VideoLayersAllocation allocation1; + if (!RtpVideoLayersAllocationExtension::Parse(raw, &allocation1)) { + // Ignore invalid buffer and move on. + return; + } + + // Write parsed allocation back into raw buffer. + size_t value_size = RtpVideoLayersAllocationExtension::ValueSize(allocation1); + // Check `writer` use minimal number of bytes to pack the extension by + // checking it doesn't use more than reader consumed. + RTC_CHECK_LE(value_size, raw.size()); + uint8_t some_memory[kMaxSize]; + RTC_CHECK_LE(value_size, kMaxSize); + rtc::ArrayView<uint8_t> write_buffer(some_memory, value_size); + RTC_CHECK( + RtpVideoLayersAllocationExtension::Write(write_buffer, allocation1)); + + // Parse what Write assembled. + // Unlike random input that should always succeed. + VideoLayersAllocation allocation2; + RTC_CHECK( + RtpVideoLayersAllocationExtension::Parse(write_buffer, &allocation2)); + + RTC_CHECK_EQ(allocation1.rtp_stream_index, allocation2.rtp_stream_index); + RTC_CHECK_EQ(allocation1.resolution_and_frame_rate_is_valid, + allocation2.resolution_and_frame_rate_is_valid); + RTC_CHECK_EQ(allocation1.active_spatial_layers.size(), + allocation2.active_spatial_layers.size()); + RTC_CHECK(allocation1 == allocation2); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/sctp_utils_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/sctp_utils_fuzzer.cc new file mode 100644 index 0000000000..249707514e --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/sctp_utils_fuzzer.cc @@ -0,0 +1,31 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include <string> + +#include "api/data_channel_interface.h" +#include "pc/sctp_utils.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + rtc::CopyOnWriteBuffer payload(data, size); + std::string label; + DataChannelInit config; + IsOpenMessage(payload); + ParseDataChannelOpenMessage(payload, &label, &config); + ParseDataChannelOpenAckMessage(payload); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/sdp_integration_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/sdp_integration_fuzzer.cc new file mode 100644 index 0000000000..ece4b50505 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/sdp_integration_fuzzer.cc @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 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 <stddef.h> +#include <stdint.h> + +#include "absl/strings/string_view.h" +#include "pc/test/integration_test_helpers.h" + +namespace webrtc { + +class FuzzerTest : public PeerConnectionIntegrationBaseTest { + public: + FuzzerTest() + : PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {} + + void RunNegotiateCycle(absl::string_view message) { + CreatePeerConnectionWrappers(); + // Note - we do not do test.ConnectFakeSignaling(); all signals + // generated are discarded. + + auto srd_observer = + rtc::make_ref_counted<FakeSetRemoteDescriptionObserver>(); + + SdpParseError error; + std::unique_ptr<SessionDescriptionInterface> sdp( + CreateSessionDescription("offer", std::string(message), &error)); + caller()->pc()->SetRemoteDescription(std::move(sdp), srd_observer); + // Wait a short time for observer to be called. Timeout is short + // because the fuzzer should be trying many branches. + EXPECT_TRUE_WAIT(srd_observer->called(), 100); + + // If set-remote-description was successful, try to answer. + auto sld_observer = + rtc::make_ref_counted<FakeSetLocalDescriptionObserver>(); + if (srd_observer->error().ok()) { + caller()->pc()->SetLocalDescription(sld_observer); + EXPECT_TRUE_WAIT(sld_observer->called(), 100); + } + // If there is an EXPECT failure, die here. + RTC_CHECK(!HasFailure()); + } + + // This test isn't using the test definition macros, so we have to + // define the TestBody() function, even though we don't need it. + void TestBody() override {} +}; + +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 16384) { + return; + } + + FuzzerTest test; + test.RunNegotiateCycle( + absl::string_view(reinterpret_cast<const char*>(data), size)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/sdp_parser_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/sdp_parser_fuzzer.cc new file mode 100644 index 0000000000..c85eab4047 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/sdp_parser_fuzzer.cc @@ -0,0 +1,28 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include "api/jsep_session_description.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + if (size > 16384) { + return; + } + std::string message(reinterpret_cast<const char*>(data), size); + webrtc::SdpParseError error; + + std::unique_ptr<webrtc::SessionDescriptionInterface> sdp( + CreateSessionDescription("offer", message, &error)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/ssl_certificate_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/ssl_certificate_fuzzer.cc new file mode 100644 index 0000000000..4bab5c8f02 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/ssl_certificate_fuzzer.cc @@ -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. + */ + +#include <stddef.h> +#include <stdint.h> + +#include <string> + +#include "rtc_base/message_digest.h" +#include "rtc_base/ssl_certificate.h" +#include "rtc_base/string_encode.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + std::string pem_certificate(reinterpret_cast<const char*>(data), size); + + std::unique_ptr<rtc::SSLCertificate> cert = + rtc::SSLCertificate::FromPEMString(pem_certificate); + + if (cert == nullptr) { + return; + } + + cert->Clone(); + cert->GetStats(); + cert->ToPEMString(); + cert->CertificateExpirationTime(); + + std::string algorithm; + cert->GetSignatureDigestAlgorithm(&algorithm); + + unsigned char digest[rtc::MessageDigest::kMaxSize]; + size_t digest_len; + cert->ComputeDigest(algorithm, digest, rtc::MessageDigest::kMaxSize, + &digest_len); + + rtc::Buffer der_buffer; + cert->ToDER(&der_buffer); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/string_to_number_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/string_to_number_fuzzer.cc new file mode 100644 index 0000000000..28b36a73ce --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/string_to_number_fuzzer.cc @@ -0,0 +1,35 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include <string> + +#include "rtc_base/string_to_number.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + std::string number_to_parse(reinterpret_cast<const char*>(data), size); + rtc::StringToNumber<int8_t>(number_to_parse); + rtc::StringToNumber<int16_t>(number_to_parse); + rtc::StringToNumber<int32_t>(number_to_parse); + rtc::StringToNumber<int64_t>(number_to_parse); + rtc::StringToNumber<uint8_t>(number_to_parse); + rtc::StringToNumber<uint16_t>(number_to_parse); + rtc::StringToNumber<uint32_t>(number_to_parse); + rtc::StringToNumber<uint64_t>(number_to_parse); + rtc::StringToNumber<float>(number_to_parse); + rtc::StringToNumber<double>(number_to_parse); + rtc::StringToNumber<long double>(number_to_parse); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/stun_parser_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/stun_parser_fuzzer.cc new file mode 100644 index 0000000000..6ca9eac8b2 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/stun_parser_fuzzer.cc @@ -0,0 +1,29 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include "api/transport/stun.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + const char* message = reinterpret_cast<const char*>(data); + + // Normally we'd check the integrity first, but those checks are + // fuzzed separately in stun_validator_fuzzer.cc. We still want to + // fuzz this target since the integrity checks could be forged by a + // malicious adversary who receives a call. + std::unique_ptr<cricket::IceMessage> stun_msg(new cricket::IceMessage()); + rtc::ByteBufferReader buf(message, size); + stun_msg->Read(&buf); + stun_msg->ValidateMessageIntegrity(""); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/stun_validator_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/stun_validator_fuzzer.cc new file mode 100644 index 0000000000..421638db1b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/stun_validator_fuzzer.cc @@ -0,0 +1,23 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include "api/transport/stun.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + const char* message = reinterpret_cast<const char*>(data); + + cricket::StunMessage::ValidateFingerprint(message, size); + cricket::StunMessage::ValidateMessageIntegrityForTesting(message, size, ""); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/turn_unwrap_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/turn_unwrap_fuzzer.cc new file mode 100644 index 0000000000..47ee7fd205 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/turn_unwrap_fuzzer.cc @@ -0,0 +1,22 @@ +/* + * Copyright (c) 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. + */ + +#include <stddef.h> +#include <stdint.h> + +#include "media/base/turn_utils.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + size_t content_position; + size_t content_size; + cricket::UnwrapTurnPacket(data, size, &content_position, &content_size); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/ulpfec_generator_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/ulpfec_generator_fuzzer.cc new file mode 100644 index 0000000000..43d9450918 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/ulpfec_generator_fuzzer.cc @@ -0,0 +1,70 @@ +/* + * Copyright (c) 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. + */ + +#include <memory> + +#include "modules/include/module_common_types_public.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/fec_test_helper.h" +#include "modules/rtp_rtcp/source/ulpfec_generator.h" +#include "rtc_base/checks.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +namespace { +constexpr uint8_t kFecPayloadType = 96; +constexpr uint8_t kRedPayloadType = 97; +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + SimulatedClock clock(1); + UlpfecGenerator generator(kRedPayloadType, kFecPayloadType, &clock); + size_t i = 0; + if (size < 4) + return; + FecProtectionParams params = { + data[i++] % 128, static_cast<int>(data[i++] % 10), kFecMaskBursty}; + generator.SetProtectionParameters(params, params); + uint16_t seq_num = data[i++]; + uint16_t prev_seq_num = 0; + while (i + 3 < size) { + size_t rtp_header_length = data[i++] % 10 + 12; + size_t payload_size = data[i++] % 10; + if (i + payload_size + rtp_header_length + 2 > size) + break; + rtc::CopyOnWriteBuffer packet(&data[i], payload_size + rtp_header_length); + packet.EnsureCapacity(IP_PACKET_SIZE); + // Write a valid parsable header (version = 2, no padding, no extensions, + // no CSRCs). + ByteWriter<uint8_t>::WriteBigEndian(packet.MutableData(), 2 << 6); + // Make sure sequence numbers are increasing. + ByteWriter<uint16_t>::WriteBigEndian(packet.MutableData() + 2, seq_num++); + i += payload_size + rtp_header_length; + const bool protect = data[i++] % 2 == 1; + + // Check the sequence numbers are monotonic. In rare case the packets number + // may loop around and in the same FEC-protected group the packet sequence + // number became out of order. + if (protect && IsNewerSequenceNumber(seq_num, prev_seq_num) && + seq_num < prev_seq_num + kUlpfecMaxMediaPackets) { + RtpPacketToSend rtp_packet(nullptr); + // Check that we actually have a parsable packet, we want to fuzz FEC + // logic, not RTP header parsing. + RTC_CHECK(rtp_packet.Parse(packet)); + generator.AddPacketAndGenerateFec(rtp_packet); + prev_seq_num = seq_num; + } + + generator.GetFecPackets(); + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/ulpfec_header_reader_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/ulpfec_header_reader_fuzzer.cc new file mode 100644 index 0000000000..243cb4ed70 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/ulpfec_header_reader_fuzzer.cc @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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. + */ + +#include <algorithm> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/ulpfec_header_reader_writer.h" + +namespace webrtc { + +using Packet = ForwardErrorCorrection::Packet; +using ReceivedFecPacket = ForwardErrorCorrection::ReceivedFecPacket; + +void FuzzOneInput(const uint8_t* data, size_t size) { + ReceivedFecPacket packet; + packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + const size_t packet_size = + std::min(size, static_cast<size_t>(IP_PACKET_SIZE)); + packet.pkt->data.SetSize(packet_size); + packet.pkt->data.EnsureCapacity(IP_PACKET_SIZE); + memcpy(packet.pkt->data.MutableData(), data, packet_size); + + UlpfecHeaderReader ulpfec_reader; + ulpfec_reader.ReadFecHeader(&packet); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/ulpfec_receiver_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/ulpfec_receiver_fuzzer.cc new file mode 100644 index 0000000000..0a29ba3259 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/ulpfec_receiver_fuzzer.cc @@ -0,0 +1,71 @@ +/* + * 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 <algorithm> + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/ulpfec_receiver.h" +#include "test/fuzzers/fuzz_data_helper.h" + +namespace webrtc { + +namespace { +class DummyCallback : public RecoveredPacketReceiver { + void OnRecoveredPacket(const RtpPacketReceived& packet) override {} +}; +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + constexpr size_t kMinDataNeeded = 12; + if (size < kMinDataNeeded || size > 2000) { + return; + } + + uint32_t ulpfec_ssrc = ByteReader<uint32_t>::ReadLittleEndian(data + 0); + uint16_t ulpfec_seq_num = ByteReader<uint16_t>::ReadLittleEndian(data + 4); + uint32_t media_ssrc = ByteReader<uint32_t>::ReadLittleEndian(data + 6); + uint16_t media_seq_num = ByteReader<uint16_t>::ReadLittleEndian(data + 10); + + DummyCallback callback; + UlpfecReceiver receiver(ulpfec_ssrc, 0, &callback, Clock::GetRealTimeClock()); + + test::FuzzDataHelper fuzz_data(rtc::MakeArrayView(data, size)); + while (fuzz_data.CanReadBytes(kMinDataNeeded)) { + size_t packet_length = kRtpHeaderSize + fuzz_data.Read<uint8_t>(); + auto raw_packet = fuzz_data.ReadByteArray(packet_length); + + RtpPacketReceived parsed_packet; + if (!parsed_packet.Parse(raw_packet)) + continue; + + // Overwrite the fields for the sequence number and SSRC with + // consistent values for either a received UlpFEC packet or received media + // packet. (We're still relying on libfuzzer to manage to generate packet + // headers that interact together; this just ensures that we have two + // consistent streams). + if (fuzz_data.ReadOrDefaultValue<uint8_t>(0) % 2 == 0) { + // Simulate UlpFEC packet. + parsed_packet.SetSequenceNumber(ulpfec_seq_num++); + parsed_packet.SetSsrc(ulpfec_ssrc); + } else { + // Simulate media packet. + parsed_packet.SetSequenceNumber(media_seq_num++); + parsed_packet.SetSsrc(media_ssrc); + } + + receiver.AddReceivedRedPacket(parsed_packet); + } + + receiver.ProcessReceivedFec(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/utils/BUILD.gn b/third_party/libwebrtc/test/fuzzers/utils/BUILD.gn new file mode 100644 index 0000000000..c5744fc33b --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/utils/BUILD.gn @@ -0,0 +1,47 @@ +# 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. + +import("../../../webrtc.gni") + +rtc_library("rtp_replayer") { + testonly = true + sources = [ + "rtp_replayer.cc", + "rtp_replayer.h", + ] + deps = [ + "../../../api/rtc_event_log", + "../../../api/task_queue:default_task_queue_factory", + "../../../api/test/video:function_video_factory", + "../../../api/transport:field_trial_based_config", + "../../../api/units:timestamp", + "../../../api/video_codecs:video_codecs_api", + "../../../call", + "../../../call:call_interfaces", + "../../../common_video", + "../../../media:rtc_internal_video_codecs", + "../../../modules/rtp_rtcp:rtp_rtcp_format", + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_tests_utils", + "../../../rtc_base:rtc_json", + "../../../rtc_base:timeutils", + "../../../system_wrappers", + "../../../test:call_config_utils", + "../../../test:encoder_settings", + "../../../test:fake_video_codecs", + "../../../test:null_transport", + "../../../test:rtp_test_utils", + "../../../test:run_loop", + "../../../test:run_test", + "../../../test:run_test_interface", + "../../../test:test_renderer", + "../../../test:test_support", + "../../../test:video_test_common", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory:memory" ] +} diff --git a/third_party/libwebrtc/test/fuzzers/utils/rtp_replayer.cc b/third_party/libwebrtc/test/fuzzers/utils/rtp_replayer.cc new file mode 100644 index 0000000000..12743d89d9 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/utils/rtp_replayer.cc @@ -0,0 +1,200 @@ +/* + * 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/fuzzers/utils/rtp_replayer.h" + +#include <algorithm> +#include <memory> +#include <string> +#include <utility> + +#include "absl/memory/memory.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "api/transport/field_trial_based_config.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/strings/json.h" +#include "system_wrappers/include/clock.h" +#include "test/call_config_utils.h" +#include "test/encoder_settings.h" +#include "test/fake_decoder.h" +#include "test/rtp_file_reader.h" +#include "test/run_loop.h" + +namespace webrtc { +namespace test { + +void RtpReplayer::Replay(const std::string& replay_config_filepath, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size) { + auto stream_state = std::make_unique<StreamState>(); + std::vector<VideoReceiveStreamInterface::Config> receive_stream_configs = + ReadConfigFromFile(replay_config_filepath, &(stream_state->transport)); + return Replay(std::move(stream_state), std::move(receive_stream_configs), + rtp_dump_data, rtp_dump_size); +} + +void RtpReplayer::Replay( + std::unique_ptr<StreamState> stream_state, + std::vector<VideoReceiveStreamInterface::Config> receive_stream_configs, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size) { + RunLoop loop; + rtc::ScopedBaseFakeClock fake_clock; + + // Work around: webrtc calls webrtc::Random(clock.TimeInMicroseconds()) + // everywhere and Random expects non-zero seed. Let's set the clock non-zero + // to make them happy. + fake_clock.SetTime(webrtc::Timestamp::Millis(1)); + + // Attempt to create an RtpReader from the input file. + auto rtp_reader = CreateRtpReader(rtp_dump_data, rtp_dump_size); + if (rtp_reader == nullptr) { + RTC_LOG(LS_ERROR) << "Failed to create the rtp_reader"; + return; + } + + RtpHeaderExtensionMap extensions(/*extmap_allow_mixed=*/true); + // Skip i = 0 since it maps to kRtpExtensionNone. + for (int i = 1; i < kRtpExtensionNumberOfExtensions; i++) { + RTPExtensionType extension_type = static_cast<RTPExtensionType>(i); + // Extensions are registered with an ID, which you signal to the + // peer so they know what to expect. This code only cares about + // parsing so the value of the ID isn't relevant. + extensions.RegisterByType(i, extension_type); + } + + // Setup the video streams based on the configuration. + webrtc::RtcEventLogNull event_log; + std::unique_ptr<TaskQueueFactory> task_queue_factory = + CreateDefaultTaskQueueFactory(); + Call::Config call_config(&event_log); + call_config.task_queue_factory = task_queue_factory.get(); + FieldTrialBasedConfig field_trials; + call_config.trials = &field_trials; + std::unique_ptr<Call> call(Call::Create(call_config)); + SetupVideoStreams(&receive_stream_configs, stream_state.get(), call.get()); + + // Start replaying the provided stream now that it has been configured. + for (const auto& receive_stream : stream_state->receive_streams) { + receive_stream->Start(); + } + + ReplayPackets(&fake_clock, call.get(), rtp_reader.get(), extensions); + + for (const auto& receive_stream : stream_state->receive_streams) { + call->DestroyVideoReceiveStream(receive_stream); + } +} + +std::vector<VideoReceiveStreamInterface::Config> +RtpReplayer::ReadConfigFromFile(const std::string& replay_config, + Transport* transport) { + Json::CharReaderBuilder factory; + std::unique_ptr<Json::CharReader> json_reader = + absl::WrapUnique(factory.newCharReader()); + Json::Value json_configs; + Json::String errors; + if (!json_reader->parse(replay_config.data(), + replay_config.data() + replay_config.length(), + &json_configs, &errors)) { + RTC_LOG(LS_ERROR) + << "Error parsing JSON replay configuration for the fuzzer: " << errors; + return {}; + } + + std::vector<VideoReceiveStreamInterface::Config> receive_stream_configs; + receive_stream_configs.reserve(json_configs.size()); + for (const auto& json : json_configs) { + receive_stream_configs.push_back( + ParseVideoReceiveStreamJsonConfig(transport, json)); + } + return receive_stream_configs; +} + +void RtpReplayer::SetupVideoStreams( + std::vector<VideoReceiveStreamInterface::Config>* receive_stream_configs, + StreamState* stream_state, + Call* call) { + stream_state->decoder_factory = std::make_unique<InternalDecoderFactory>(); + for (auto& receive_config : *receive_stream_configs) { + // Attach the decoder for the corresponding payload type in the config. + for (auto& decoder : receive_config.decoders) { + decoder = test::CreateMatchingDecoder(decoder.payload_type, + decoder.video_format.name); + } + + // Create the window to display the rendered video. + stream_state->sinks.emplace_back( + test::VideoRenderer::Create("Fuzzing WebRTC Video Config", 640, 480)); + // Create a receive stream for this config. + receive_config.renderer = stream_state->sinks.back().get(); + receive_config.decoder_factory = stream_state->decoder_factory.get(); + stream_state->receive_streams.emplace_back( + call->CreateVideoReceiveStream(std::move(receive_config))); + } +} + +std::unique_ptr<test::RtpFileReader> RtpReplayer::CreateRtpReader( + const uint8_t* rtp_dump_data, + size_t rtp_dump_size) { + std::unique_ptr<test::RtpFileReader> rtp_reader(test::RtpFileReader::Create( + test::RtpFileReader::kRtpDump, rtp_dump_data, rtp_dump_size, {})); + if (!rtp_reader) { + RTC_LOG(LS_ERROR) << "Unable to open input file with any supported format"; + return nullptr; + } + return rtp_reader; +} + +void RtpReplayer::ReplayPackets( + rtc::FakeClock* clock, + Call* call, + test::RtpFileReader* rtp_reader, + const RtpPacketReceived::ExtensionManager& extensions) { + int64_t replay_start_ms = -1; + + while (true) { + int64_t now_ms = rtc::TimeMillis(); + if (replay_start_ms == -1) { + replay_start_ms = now_ms; + } + + test::RtpPacket packet; + if (!rtp_reader->NextPacket(&packet)) { + break; + } + + int64_t deliver_in_ms = replay_start_ms + packet.time_ms - now_ms; + if (deliver_in_ms > 0) { + // StatsCounter::ReportMetricToAggregatedCounter is O(elapsed time). + // Set an upper limit to prevent waste time. + clock->AdvanceTime(webrtc::TimeDelta::Millis( + std::min(deliver_in_ms, static_cast<int64_t>(100)))); + } + + RtpPacketReceived received_packet( + &extensions, Timestamp::Micros(clock->TimeNanos() / 1000)); + if (!received_packet.Parse(packet.data, packet.length)) { + RTC_LOG(LS_ERROR) << "Packet error, corrupt packets or incorrect setup?"; + break; + } + + call->Receiver()->DeliverRtpPacket( + MediaType::VIDEO, std::move(received_packet), + [&](const RtpPacketReceived& parsed_packet) { + RTC_LOG(LS_ERROR) << "Unknown SSRC: " << parsed_packet.Ssrc(); + return false; + }); + } +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/utils/rtp_replayer.h b/third_party/libwebrtc/test/fuzzers/utils/rtp_replayer.h new file mode 100644 index 0000000000..ae94a640a5 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/utils/rtp_replayer.h @@ -0,0 +1,92 @@ +/* + * 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_FUZZERS_UTILS_RTP_REPLAYER_H_ +#define TEST_FUZZERS_UTILS_RTP_REPLAYER_H_ + +#include <stdio.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/test/video/function_video_decoder_factory.h" +#include "api/video_codecs/video_decoder.h" +#include "call/call.h" +#include "media/engine/internal_decoder_factory.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/time_utils.h" +#include "test/null_transport.h" +#include "test/rtp_file_reader.h" +#include "test/test_video_capturer.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace test { + +// The RtpReplayer is a utility for fuzzing the RTP/RTCP receiver stack in +// WebRTC. It achieves this by accepting a set of Receiver configurations and +// an RtpDump (consisting of both RTP and RTCP packets). The `rtp_dump` is +// passed in as a buffer to allow simple mutation fuzzing directly on the dump. +class RtpReplayer final { + public: + // Holds all the important stream information required to emulate the WebRTC + // rtp receival code path. + struct StreamState { + test::NullTransport transport; + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks; + std::vector<VideoReceiveStreamInterface*> receive_streams; + std::unique_ptr<VideoDecoderFactory> decoder_factory; + }; + + // Construct an RtpReplayer from a JSON replay configuration file. + static void Replay(const std::string& replay_config_filepath, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size); + + // Construct an RtpReplayer from a set of + // VideoReceiveStreamInterface::Configs. Note the stream_state.transport must + // be set for each receiver stream. + static void Replay( + std::unique_ptr<StreamState> stream_state, + std::vector<VideoReceiveStreamInterface::Config> receive_stream_config, + const uint8_t* rtp_dump_data, + size_t rtp_dump_size); + + private: + // Reads the replay configuration from Json. + static std::vector<VideoReceiveStreamInterface::Config> ReadConfigFromFile( + const std::string& replay_config, + Transport* transport); + + // Configures the stream state based on the receiver configurations. + static void SetupVideoStreams( + std::vector<VideoReceiveStreamInterface::Config>* receive_stream_configs, + StreamState* stream_state, + Call* call); + + // Creates a new RtpReader which can read the RtpDump + static std::unique_ptr<test::RtpFileReader> CreateRtpReader( + const uint8_t* rtp_dump_data, + size_t rtp_dump_size); + + // Replays each packet to from the RtpDump. + static void ReplayPackets(rtc::FakeClock* clock, + Call* call, + test::RtpFileReader* rtp_reader, + const RtpHeaderExtensionMap& extensions); +}; // class RtpReplayer + +} // namespace test +} // namespace webrtc + +#endif // TEST_FUZZERS_UTILS_RTP_REPLAYER_H_ diff --git a/third_party/libwebrtc/test/fuzzers/vp8_depacketizer_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp8_depacketizer_fuzzer.cc new file mode 100644 index 0000000000..1691b55cc0 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp8_depacketizer_fuzzer.cc @@ -0,0 +1,20 @@ +/* + * Copyright (c) 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. + */ +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + RTPVideoHeader video_header; + VideoRtpDepacketizerVp8::ParseRtpPayload(rtc::MakeArrayView(data, size), + &video_header); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/vp8_qp_parser_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp8_qp_parser_fuzzer.cc new file mode 100644 index 0000000000..2ecfd820c8 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp8_qp_parser_fuzzer.cc @@ -0,0 +1,17 @@ +/* + * Copyright (c) 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. + */ +#include "modules/video_coding/utility/vp8_header_parser.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + int qp; + vp8::GetQp(data, size, &qp); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/vp8_replay_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp8_replay_fuzzer.cc new file mode 100644 index 0000000000..819b9626f9 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp8_replay_fuzzer.cc @@ -0,0 +1,42 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "test/fuzzers/utils/rtp_replayer.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + auto stream_state = std::make_unique<test::RtpReplayer::StreamState>(); + VideoReceiveStreamInterface::Config vp8_config(&(stream_state->transport)); + + VideoReceiveStreamInterface::Decoder vp8_decoder; + vp8_decoder.video_format = SdpVideoFormat("VP8"); + vp8_decoder.payload_type = 125; + vp8_config.decoders.push_back(std::move(vp8_decoder)); + + vp8_config.rtp.local_ssrc = 7731; + vp8_config.rtp.remote_ssrc = 1337; + vp8_config.rtp.rtx_ssrc = 100; + vp8_config.rtp.nack.rtp_history_ms = 1000; + vp8_config.rtp.lntf.enabled = true; + + std::vector<VideoReceiveStreamInterface::Config> replay_configs; + replay_configs.push_back(std::move(vp8_config)); + + test::RtpReplayer::Replay(std::move(stream_state), std::move(replay_configs), + data, size); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/vp9_depacketizer_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp9_depacketizer_fuzzer.cc new file mode 100644 index 0000000000..ae36a94931 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp9_depacketizer_fuzzer.cc @@ -0,0 +1,20 @@ +/* + * Copyright (c) 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. + */ +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + RTPVideoHeader video_header; + VideoRtpDepacketizerVp9::ParseRtpPayload(rtc::MakeArrayView(data, size), + &video_header); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/vp9_encoder_references_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp9_encoder_references_fuzzer.cc new file mode 100644 index 0000000000..09848ae4de --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp9_encoder_references_fuzzer.cc @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2021 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 "absl/algorithm/container.h" +#include "absl/base/macros.h" +#include "absl/container/inlined_vector.h" +#include "api/array_view.h" +#include "api/field_trials_view.h" +#include "api/video/video_frame.h" +#include "api/video_codecs/video_codec.h" +#include "api/video_codecs/video_encoder.h" +#include "modules/video_coding/codecs/interface/libvpx_interface.h" +#include "modules/video_coding/codecs/vp9/libvpx_vp9_encoder.h" +#include "modules/video_coding/frame_dependencies_calculator.h" +#include "rtc_base/numerics/safe_compare.h" +#include "test/fuzzers/fuzz_data_helper.h" + +// Fuzzer simulates various svc configurations and libvpx encoder dropping +// layer frames. +// Validates vp9 encoder wrapper produces consistent frame references. +namespace webrtc { +namespace { + +using test::FuzzDataHelper; + +constexpr int kBitrateEnabledBps = 100'000; + +class FrameValidator : public EncodedImageCallback { + public: + ~FrameValidator() override = default; + + Result OnEncodedImage(const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info) override { + RTC_CHECK(codec_specific_info); + RTC_CHECK_EQ(codec_specific_info->codecType, kVideoCodecVP9); + if (codec_specific_info->codecSpecific.VP9.first_frame_in_picture) { + ++picture_id_; + } + int64_t frame_id = frame_id_++; + LayerFrame& layer_frame = frames_[frame_id % kMaxFrameHistorySize]; + layer_frame.picture_id = picture_id_; + layer_frame.spatial_id = encoded_image.SpatialIndex().value_or(0); + layer_frame.frame_id = frame_id; + layer_frame.temporal_id = + codec_specific_info->codecSpecific.VP9.temporal_idx; + if (layer_frame.temporal_id == kNoTemporalIdx) { + layer_frame.temporal_id = 0; + } + layer_frame.vp9_non_ref_for_inter_layer_pred = + codec_specific_info->codecSpecific.VP9.non_ref_for_inter_layer_pred; + CheckVp9References(layer_frame, codec_specific_info->codecSpecific.VP9); + + if (codec_specific_info->generic_frame_info.has_value()) { + absl::InlinedVector<int64_t, 5> frame_dependencies = + dependencies_calculator_.FromBuffersUsage( + frame_id, + codec_specific_info->generic_frame_info->encoder_buffers); + + CheckGenericReferences(frame_dependencies, + *codec_specific_info->generic_frame_info); + CheckGenericAndCodecSpecificReferencesAreConsistent( + frame_dependencies, *codec_specific_info, layer_frame); + } + + return Result(Result::OK); + } + + private: + // With 4 spatial layers and patterns up to 8 pictures, it should be enough to + // keep the last 32 frames to validate dependencies. + static constexpr size_t kMaxFrameHistorySize = 32; + struct LayerFrame { + int64_t frame_id; + int64_t picture_id; + int spatial_id; + int temporal_id; + bool vp9_non_ref_for_inter_layer_pred; + }; + + void CheckVp9References(const LayerFrame& layer_frame, + const CodecSpecificInfoVP9& vp9_info) { + if (layer_frame.frame_id == 0) { + RTC_CHECK(!vp9_info.inter_layer_predicted); + } else { + const LayerFrame& previous_frame = Frame(layer_frame.frame_id - 1); + if (vp9_info.inter_layer_predicted) { + RTC_CHECK(!previous_frame.vp9_non_ref_for_inter_layer_pred); + RTC_CHECK_EQ(layer_frame.picture_id, previous_frame.picture_id); + } + if (previous_frame.picture_id == layer_frame.picture_id) { + RTC_CHECK_GT(layer_frame.spatial_id, previous_frame.spatial_id); + // The check below would fail for temporal shift structures. Remove it + // or move it to !flexible_mode section when vp9 encoder starts + // supporting such structures. + RTC_CHECK_EQ(layer_frame.temporal_id, previous_frame.temporal_id); + } + } + if (!vp9_info.flexible_mode) { + if (vp9_info.gof.num_frames_in_gof > 0) { + gof_.CopyGofInfoVP9(vp9_info.gof); + } + RTC_CHECK_EQ(gof_.temporal_idx[vp9_info.gof_idx], + layer_frame.temporal_id); + } + } + + void CheckGenericReferences(rtc::ArrayView<const int64_t> frame_dependencies, + const GenericFrameInfo& generic_info) const { + for (int64_t dependency_frame_id : frame_dependencies) { + RTC_CHECK_GE(dependency_frame_id, 0); + const LayerFrame& dependency = Frame(dependency_frame_id); + RTC_CHECK_GE(generic_info.spatial_id, dependency.spatial_id); + RTC_CHECK_GE(generic_info.temporal_id, dependency.temporal_id); + } + } + + void CheckGenericAndCodecSpecificReferencesAreConsistent( + rtc::ArrayView<const int64_t> frame_dependencies, + const CodecSpecificInfo& info, + const LayerFrame& layer_frame) const { + const CodecSpecificInfoVP9& vp9_info = info.codecSpecific.VP9; + const GenericFrameInfo& generic_info = *info.generic_frame_info; + + RTC_CHECK_EQ(generic_info.spatial_id, layer_frame.spatial_id); + RTC_CHECK_EQ(generic_info.temporal_id, layer_frame.temporal_id); + auto picture_id_diffs = + rtc::MakeArrayView(vp9_info.p_diff, vp9_info.num_ref_pics); + RTC_CHECK_EQ( + frame_dependencies.size(), + picture_id_diffs.size() + (vp9_info.inter_layer_predicted ? 1 : 0)); + for (int64_t dependency_frame_id : frame_dependencies) { + RTC_CHECK_GE(dependency_frame_id, 0); + const LayerFrame& dependency = Frame(dependency_frame_id); + if (dependency.spatial_id != layer_frame.spatial_id) { + RTC_CHECK(vp9_info.inter_layer_predicted); + RTC_CHECK_EQ(layer_frame.picture_id, dependency.picture_id); + RTC_CHECK_GT(layer_frame.spatial_id, dependency.spatial_id); + } else { + RTC_CHECK(vp9_info.inter_pic_predicted); + RTC_CHECK_EQ(layer_frame.spatial_id, dependency.spatial_id); + RTC_CHECK(absl::c_linear_search( + picture_id_diffs, layer_frame.picture_id - dependency.picture_id)); + } + } + } + + const LayerFrame& Frame(int64_t frame_id) const { + auto& frame = frames_[frame_id % kMaxFrameHistorySize]; + RTC_CHECK_EQ(frame.frame_id, frame_id); + return frame; + } + + GofInfoVP9 gof_; + int64_t frame_id_ = 0; + int64_t picture_id_ = 1; + FrameDependenciesCalculator dependencies_calculator_; + LayerFrame frames_[kMaxFrameHistorySize]; +}; + +class FieldTrials : public FieldTrialsView { + public: + explicit FieldTrials(FuzzDataHelper& config) + : flags_(config.ReadOrDefaultValue<uint8_t>(0)) {} + + ~FieldTrials() override = default; + std::string Lookup(absl::string_view key) const override { + static constexpr absl::string_view kBinaryFieldTrials[] = { + "WebRTC-Vp9ExternalRefCtrl", + "WebRTC-Vp9IssueKeyFrameOnLayerDeactivation", + }; + for (size_t i = 0; i < ABSL_ARRAYSIZE(kBinaryFieldTrials); ++i) { + if (key == kBinaryFieldTrials[i]) { + return (flags_ & (1u << i)) ? "Enabled" : "Disabled"; + } + } + + // Ignore following field trials. + if (key == "WebRTC-CongestionWindow" || + key == "WebRTC-UseBaseHeavyVP8TL3RateAllocation" || + key == "WebRTC-SimulcastUpswitchHysteresisPercent" || + key == "WebRTC-SimulcastScreenshareUpswitchHysteresisPercent" || + key == "WebRTC-VideoRateControl" || + key == "WebRTC-VP9-PerformanceFlags" || + key == "WebRTC-VP9VariableFramerateScreenshare" || + key == "WebRTC-VP9QualityScaler") { + return ""; + } + // Crash when using unexpected field trial to decide if it should be fuzzed + // or have a constant value. + RTC_CHECK(false) << "Unfuzzed field trial " << key << "\n"; + } + + private: + const uint8_t flags_; +}; + +VideoCodec CodecSettings(FuzzDataHelper& rng) { + uint16_t config = rng.ReadOrDefaultValue<uint16_t>(0); + // Test up to to 4 spatial and 4 temporal layers. + int num_spatial_layers = 1 + (config & 0b11); + int num_temporal_layers = 1 + ((config >> 2) & 0b11); + + VideoCodec codec_settings = {}; + codec_settings.codecType = kVideoCodecVP9; + codec_settings.maxFramerate = 30; + codec_settings.width = 320 << (num_spatial_layers - 1); + codec_settings.height = 180 << (num_spatial_layers - 1); + if (num_spatial_layers > 1) { + for (int sid = 0; sid < num_spatial_layers; ++sid) { + SpatialLayer& spatial_layer = codec_settings.spatialLayers[sid]; + codec_settings.width = 320 << sid; + codec_settings.height = 180 << sid; + spatial_layer.width = codec_settings.width; + spatial_layer.height = codec_settings.height; + spatial_layer.targetBitrate = kBitrateEnabledBps * num_temporal_layers; + spatial_layer.maxFramerate = codec_settings.maxFramerate; + spatial_layer.numberOfTemporalLayers = num_temporal_layers; + } + } + codec_settings.VP9()->numberOfSpatialLayers = num_spatial_layers; + codec_settings.VP9()->numberOfTemporalLayers = num_temporal_layers; + int inter_layer_pred = (config >> 4) & 0b11; + // There are only 3 valid values. + codec_settings.VP9()->interLayerPred = static_cast<InterLayerPredMode>( + inter_layer_pred < 3 ? inter_layer_pred : 0); + codec_settings.VP9()->flexibleMode = (config & (1u << 6)) != 0; + codec_settings.SetFrameDropEnabled((config & (1u << 7)) != 0); + codec_settings.mode = VideoCodecMode::kRealtimeVideo; + return codec_settings; +} + +VideoEncoder::Settings EncoderSettings() { + return VideoEncoder::Settings(VideoEncoder::Capabilities(false), + /*number_of_cores=*/1, + /*max_payload_size=*/0); +} + +bool IsSupported(int num_spatial_layers, + int num_temporal_layers, + const VideoBitrateAllocation& allocation) { + // VP9 encoder doesn't support certain configurations. + // BitrateAllocator shouldn't produce them. + if (allocation.get_sum_bps() == 0) { + // Ignore allocation that turns off all the layers. + // In such a case it is up to upper layer code not to call Encode. + return false; + } + + for (int tid = 0; tid < num_temporal_layers; ++tid) { + int min_enabled_spatial_id = -1; + int max_enabled_spatial_id = -1; + int num_enabled_spatial_layers = 0; + for (int sid = 0; sid < num_spatial_layers; ++sid) { + if (allocation.GetBitrate(sid, tid) > 0) { + if (min_enabled_spatial_id == -1) { + min_enabled_spatial_id = sid; + } + max_enabled_spatial_id = sid; + ++num_enabled_spatial_layers; + } + } + if (num_enabled_spatial_layers == 0) { + // Each temporal layer should be enabled because skipping a full frame is + // not supported in non-flexible mode. + return false; + } + if (max_enabled_spatial_id - min_enabled_spatial_id + 1 != + num_enabled_spatial_layers) { + // To avoid odd spatial dependencies, there should be no gaps in active + // spatial layers. + return false; + } + } + + return true; +} + +struct LibvpxState { + LibvpxState() { + pkt.kind = VPX_CODEC_CX_FRAME_PKT; + pkt.data.frame.buf = pkt_buffer; + pkt.data.frame.sz = ABSL_ARRAYSIZE(pkt_buffer); + layer_id.spatial_layer_id = -1; + } + + uint8_t pkt_buffer[1000] = {}; + vpx_codec_enc_cfg_t config = {}; + vpx_codec_priv_output_cx_pkt_cb_pair_t callback = {}; + vpx_image_t img = {}; + vpx_svc_ref_frame_config_t ref_config = {}; + vpx_svc_layer_id_t layer_id = {}; + vpx_svc_frame_drop_t frame_drop = {}; + vpx_codec_cx_pkt pkt = {}; +}; + +class StubLibvpx : public LibvpxInterface { + public: + explicit StubLibvpx(LibvpxState* state) : state_(state) { RTC_CHECK(state_); } + + vpx_codec_err_t codec_enc_config_default(vpx_codec_iface_t* iface, + vpx_codec_enc_cfg_t* cfg, + unsigned int usage) const override { + state_->config = *cfg; + return VPX_CODEC_OK; + } + + vpx_codec_err_t codec_enc_init(vpx_codec_ctx_t* ctx, + vpx_codec_iface_t* iface, + const vpx_codec_enc_cfg_t* cfg, + vpx_codec_flags_t flags) const override { + RTC_CHECK(ctx); + ctx->err = VPX_CODEC_OK; + return VPX_CODEC_OK; + } + + vpx_image_t* img_wrap(vpx_image_t* img, + vpx_img_fmt_t fmt, + unsigned int d_w, + unsigned int d_h, + unsigned int stride_align, + unsigned char* img_data) const override { + state_->img.fmt = fmt; + state_->img.d_w = d_w; + state_->img.d_h = d_h; + return &state_->img; + } + + vpx_codec_err_t codec_encode(vpx_codec_ctx_t* ctx, + const vpx_image_t* img, + vpx_codec_pts_t pts, + uint64_t duration, + vpx_enc_frame_flags_t flags, + uint64_t deadline) const override { + if (flags & VPX_EFLAG_FORCE_KF) { + state_->pkt.data.frame.flags = VPX_FRAME_IS_KEY; + } else { + state_->pkt.data.frame.flags = 0; + } + state_->pkt.data.frame.duration = duration; + return VPX_CODEC_OK; + } + + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + void* param) const override { + if (ctrl_id == VP9E_REGISTER_CX_CALLBACK) { + state_->callback = + *reinterpret_cast<vpx_codec_priv_output_cx_pkt_cb_pair_t*>(param); + } + return VPX_CODEC_OK; + } + + vpx_codec_err_t codec_control( + vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_svc_ref_frame_config_t* param) const override { + switch (ctrl_id) { + case VP9E_SET_SVC_REF_FRAME_CONFIG: + state_->ref_config = *param; + break; + case VP9E_GET_SVC_REF_FRAME_CONFIG: + *param = state_->ref_config; + break; + default: + break; + } + return VPX_CODEC_OK; + } + + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_svc_layer_id_t* param) const override { + switch (ctrl_id) { + case VP9E_SET_SVC_LAYER_ID: + state_->layer_id = *param; + break; + case VP9E_GET_SVC_LAYER_ID: + *param = state_->layer_id; + break; + default: + break; + } + return VPX_CODEC_OK; + } + + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_svc_frame_drop_t* param) const override { + if (ctrl_id == VP9E_SET_SVC_FRAME_DROP_LAYER) { + state_->frame_drop = *param; + } + return VPX_CODEC_OK; + } + + vpx_codec_err_t codec_enc_config_set( + vpx_codec_ctx_t* ctx, + const vpx_codec_enc_cfg_t* cfg) const override { + state_->config = *cfg; + return VPX_CODEC_OK; + } + + vpx_image_t* img_alloc(vpx_image_t* img, + vpx_img_fmt_t fmt, + unsigned int d_w, + unsigned int d_h, + unsigned int align) const override { + return nullptr; + } + void img_free(vpx_image_t* img) const override {} + vpx_codec_err_t codec_enc_init_multi(vpx_codec_ctx_t* ctx, + vpx_codec_iface_t* iface, + vpx_codec_enc_cfg_t* cfg, + int num_enc, + vpx_codec_flags_t flags, + vpx_rational_t* dsf) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_destroy(vpx_codec_ctx_t* ctx) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + uint32_t param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + int param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + int* param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_roi_map* param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_active_map* param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_scaling_mode* param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_svc_extra_cfg_t* param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control( + vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_svc_spatial_layer_sync_t* param) const override { + return VPX_CODEC_OK; + } + vpx_codec_err_t codec_control(vpx_codec_ctx_t* ctx, + vp8e_enc_control_id ctrl_id, + vpx_rc_funcs_t* param) const override { + return VPX_CODEC_OK; + } + const vpx_codec_cx_pkt_t* codec_get_cx_data( + vpx_codec_ctx_t* ctx, + vpx_codec_iter_t* iter) const override { + return nullptr; + } + const char* codec_error_detail(vpx_codec_ctx_t* ctx) const override { + return nullptr; + } + const char* codec_error(vpx_codec_ctx_t* ctx) const override { + return nullptr; + } + const char* codec_err_to_string(vpx_codec_err_t err) const override { + return nullptr; + } + + private: + LibvpxState* const state_; +}; + +enum Actions { + kEncode, + kSetRates, +}; + +// When a layer frame is marked for drop, drops all layer frames from that +// pictures with larger spatial ids. +constexpr bool DropAbove(uint8_t layers_mask, int sid) { + uint8_t full_mask = (uint8_t{1} << (sid + 1)) - 1; + return (layers_mask & full_mask) != full_mask; +} +// inline unittests +static_assert(DropAbove(0b1011, /*sid=*/0) == false, ""); +static_assert(DropAbove(0b1011, /*sid=*/1) == false, ""); +static_assert(DropAbove(0b1011, /*sid=*/2) == true, ""); +static_assert(DropAbove(0b1011, /*sid=*/3) == true, ""); + +// When a layer frame is marked for drop, drops all layer frames from that +// pictures with smaller spatial ids. +constexpr bool DropBelow(uint8_t layers_mask, int sid, int num_layers) { + return (layers_mask >> sid) != (1 << (num_layers - sid)) - 1; +} +// inline unittests +static_assert(DropBelow(0b1101, /*sid=*/0, 4) == true, ""); +static_assert(DropBelow(0b1101, /*sid=*/1, 4) == true, ""); +static_assert(DropBelow(0b1101, /*sid=*/2, 4) == false, ""); +static_assert(DropBelow(0b1101, /*sid=*/3, 4) == false, ""); + +} // namespace + +void FuzzOneInput(const uint8_t* data, size_t size) { + FuzzDataHelper helper(rtc::MakeArrayView(data, size)); + + FrameValidator validator; + FieldTrials field_trials(helper); + // Setup call callbacks for the fake + LibvpxState state; + + // Initialize encoder + LibvpxVp9Encoder encoder(cricket::VideoCodec(), + std::make_unique<StubLibvpx>(&state), field_trials); + VideoCodec codec = CodecSettings(helper); + if (encoder.InitEncode(&codec, EncoderSettings()) != WEBRTC_VIDEO_CODEC_OK) { + return; + } + RTC_CHECK_EQ(encoder.RegisterEncodeCompleteCallback(&validator), + WEBRTC_VIDEO_CODEC_OK); + { + // Enable all the layers initially. Encoder doesn't support producing + // frames when no layers are enabled. + LibvpxVp9Encoder::RateControlParameters parameters; + parameters.framerate_fps = 30.0; + for (int sid = 0; sid < codec.VP9()->numberOfSpatialLayers; ++sid) { + for (int tid = 0; tid < codec.VP9()->numberOfTemporalLayers; ++tid) { + parameters.bitrate.SetBitrate(sid, tid, kBitrateEnabledBps); + } + } + encoder.SetRates(parameters); + } + + std::vector<VideoFrameType> frame_types(1); + VideoFrame fake_image = VideoFrame::Builder() + .set_video_frame_buffer(I420Buffer::Create( + int{codec.width}, int{codec.height})) + .build(); + + // Start producing frames at random. + while (helper.CanReadBytes(1)) { + uint8_t action = helper.Read<uint8_t>(); + switch (action & 0b11) { + case kEncode: { + // bitmask of the action: SSSS-K00, where + // four S bit indicate which spatial layers should be produced, + // K bit indicates if frame should be a key frame. + frame_types[0] = (action & 0b100) ? VideoFrameType::kVideoFrameKey + : VideoFrameType::kVideoFrameDelta; + encoder.Encode(fake_image, &frame_types); + uint8_t encode_spatial_layers = (action >> 4); + for (size_t sid = 0; sid < state.config.ss_number_layers; ++sid) { + if (state.config.ss_target_bitrate[sid] == 0) { + // Don't encode disabled spatial layers. + continue; + } + bool drop = true; + switch (state.frame_drop.framedrop_mode) { + case FULL_SUPERFRAME_DROP: + drop = encode_spatial_layers == 0; + break; + case LAYER_DROP: + drop = (encode_spatial_layers & (1 << sid)) == 0; + break; + case CONSTRAINED_LAYER_DROP: + drop = DropBelow(encode_spatial_layers, sid, + state.config.ss_number_layers); + break; + case CONSTRAINED_FROM_ABOVE_DROP: + drop = DropAbove(encode_spatial_layers, sid); + break; + } + if (!drop) { + state.layer_id.spatial_layer_id = sid; + state.callback.output_cx_pkt(&state.pkt, state.callback.user_priv); + } + } + } break; + case kSetRates: { + // bitmask of the action: (S2)(S1)(S0)01, + // where Sx is number of temporal layers to enable for spatial layer x + // In pariculat Sx = 0 indicates spatial layer x should be disabled. + LibvpxVp9Encoder::RateControlParameters parameters; + parameters.framerate_fps = 30.0; + for (int sid = 0; sid < codec.VP9()->numberOfSpatialLayers; ++sid) { + int temporal_layers = (action >> ((1 + sid) * 2)) & 0b11; + for (int tid = 0; tid < temporal_layers; ++tid) { + parameters.bitrate.SetBitrate(sid, tid, kBitrateEnabledBps); + } + } + if (IsSupported(codec.VP9()->numberOfSpatialLayers, + codec.VP9()->numberOfTemporalLayers, + parameters.bitrate)) { + encoder.SetRates(parameters); + } + } break; + default: + // Unspecificed values are noop. + break; + } + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/vp9_qp_parser_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp9_qp_parser_fuzzer.cc new file mode 100644 index 0000000000..80dfe15b16 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp9_qp_parser_fuzzer.cc @@ -0,0 +1,17 @@ +/* + * 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 "modules/video_coding/utility/vp9_uncompressed_header_parser.h" + +namespace webrtc { +void FuzzOneInput(const uint8_t* data, size_t size) { + ParseUncompressedVp9Header(rtc::MakeArrayView(data, size)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/vp9_replay_fuzzer.cc b/third_party/libwebrtc/test/fuzzers/vp9_replay_fuzzer.cc new file mode 100644 index 0000000000..fc10d9ffc7 --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/vp9_replay_fuzzer.cc @@ -0,0 +1,41 @@ +/* + * 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 <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "test/fuzzers/utils/rtp_replayer.h" + +namespace webrtc { + +void FuzzOneInput(const uint8_t* data, size_t size) { + auto stream_state = std::make_unique<test::RtpReplayer::StreamState>(); + VideoReceiveStreamInterface::Config vp9_config(&(stream_state->transport)); + + VideoReceiveStreamInterface::Decoder vp9_decoder; + vp9_decoder.video_format = SdpVideoFormat("VP9"); + vp9_decoder.payload_type = 124; + vp9_config.decoders.push_back(std::move(vp9_decoder)); + + vp9_config.rtp.local_ssrc = 7731; + vp9_config.rtp.remote_ssrc = 1337; + vp9_config.rtp.rtx_ssrc = 100; + vp9_config.rtp.nack.rtp_history_ms = 1000; + + std::vector<VideoReceiveStreamInterface::Config> replay_configs; + replay_configs.push_back(std::move(vp9_config)); + + test::RtpReplayer::Replay(std::move(stream_state), std::move(replay_configs), + data, size); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/fuzzers/webrtc_fuzzer_main.cc b/third_party/libwebrtc/test/fuzzers/webrtc_fuzzer_main.cc new file mode 100644 index 0000000000..a52dd231be --- /dev/null +++ b/third_party/libwebrtc/test/fuzzers/webrtc_fuzzer_main.cc @@ -0,0 +1,41 @@ +/* + * Copyright (c) 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. + */ + +// This file is intended to provide a common interface for fuzzing functions. +// It's intended to set sane defaults, such as removing logging for further +// fuzzing efficiency. + +#include "rtc_base/logging.h" + +namespace { +bool g_initialized = false; +void InitializeWebRtcFuzzDefaults() { + if (g_initialized) + return; + +// Remove default logging to prevent huge slowdowns. +// TODO(pbos): Disable in Chromium: http://crbug.com/561667 +#if !defined(WEBRTC_CHROMIUM_BUILD) + rtc::LogMessage::LogToDebug(rtc::LS_NONE); +#endif // !defined(WEBRTC_CHROMIUM_BUILD) + + g_initialized = true; +} +} // namespace + +namespace webrtc { +extern void FuzzOneInput(const uint8_t* data, size_t size); +} // namespace webrtc + +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) { + InitializeWebRtcFuzzDefaults(); + webrtc::FuzzOneInput(data, size); + return 0; +} diff --git a/third_party/libwebrtc/test/gl/gl_renderer.cc b/third_party/libwebrtc/test/gl/gl_renderer.cc new file mode 100644 index 0000000000..10162ee056 --- /dev/null +++ b/third_party/libwebrtc/test/gl/gl_renderer.cc @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013 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/gl/gl_renderer.h" + +#include <string.h> + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +GlRenderer::GlRenderer() + : is_init_(false), buffer_(NULL), width_(0), height_(0) {} + +void GlRenderer::Init() { + RTC_DCHECK(!is_init_); + is_init_ = true; + + glGenTextures(1, &texture_); +} + +void GlRenderer::Destroy() { + if (!is_init_) { + return; + } + + is_init_ = false; + + delete[] buffer_; + buffer_ = NULL; + + glDeleteTextures(1, &texture_); +} + +void GlRenderer::ResizeViewport(size_t width, size_t height) { + // TODO(pbos): Aspect ratio, letterbox the video. + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glOrtho(0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); +} + +void GlRenderer::ResizeVideo(size_t width, size_t height) { + RTC_DCHECK(is_init_); + width_ = width; + height_ = height; + + buffer_size_ = width * height * 4; // BGRA + + delete[] buffer_; + buffer_ = new uint8_t[buffer_size_]; + RTC_DCHECK(buffer_); + memset(buffer_, 0, buffer_size_); + glBindTexture(GL_TEXTURE_2D, texture_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8, static_cast<GLvoid*>(buffer_)); +} + +void GlRenderer::OnFrame(const webrtc::VideoFrame& frame) { + RTC_DCHECK(is_init_); + + if (static_cast<size_t>(frame.width()) != width_ || + static_cast<size_t>(frame.height()) != height_) { + ResizeVideo(frame.width(), frame.height()); + } + + webrtc::ConvertFromI420(frame, VideoType::kBGRA, 0, buffer_); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, texture_); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8, buffer_); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glLoadIdentity(); + + glBegin(GL_QUADS); + { + glTexCoord2f(0.0f, 0.0f); + glVertex3f(0.0f, 0.0f, 0.0f); + + glTexCoord2f(0.0f, 1.0f); + glVertex3f(0.0f, 1.0f, 0.0f); + + glTexCoord2f(1.0f, 1.0f); + glVertex3f(1.0f, 1.0f, 0.0f); + + glTexCoord2f(1.0f, 0.0f); + glVertex3f(1.0f, 0.0f, 0.0f); + } + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glFlush(); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/gl/gl_renderer.h b/third_party/libwebrtc/test/gl/gl_renderer.h new file mode 100644 index 0000000000..8338591244 --- /dev/null +++ b/third_party/libwebrtc/test/gl/gl_renderer.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 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_GL_GL_RENDERER_H_ +#define TEST_GL_GL_RENDERER_H_ + +#ifdef WEBRTC_MAC +#include <OpenGL/gl.h> +#else +#include <GL/gl.h> +#endif + +#include <stddef.h> +#include <stdint.h> + +#include "api/video/video_frame.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace test { + +class GlRenderer : public VideoRenderer { + public: + void OnFrame(const webrtc::VideoFrame& frame) override; + + protected: + GlRenderer(); + + void Init(); + void Destroy(); + + void ResizeViewport(size_t width, size_t height); + + private: + bool is_init_; + uint8_t* buffer_; + GLuint texture_; + size_t width_, height_, buffer_size_; + + void ResizeVideo(size_t width, size_t height); +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_GL_GL_RENDERER_H_ diff --git a/third_party/libwebrtc/test/gmock.h b/third_party/libwebrtc/test/gmock.h new file mode 100644 index 0000000000..f137d080a4 --- /dev/null +++ b/third_party/libwebrtc/test/gmock.h @@ -0,0 +1,20 @@ +/* + * 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_GMOCK_H_ +#define TEST_GMOCK_H_ + +#include "rtc_base/ignore_wundef.h" + +RTC_PUSH_IGNORING_WUNDEF() +#include "testing/gmock/include/gmock/gmock.h" +RTC_POP_IGNORING_WUNDEF() + +#endif // TEST_GMOCK_H_ diff --git a/third_party/libwebrtc/test/gtest.h b/third_party/libwebrtc/test/gtest.h new file mode 100644 index 0000000000..fa4396420e --- /dev/null +++ b/third_party/libwebrtc/test/gtest.h @@ -0,0 +1,28 @@ +/* + * 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_GTEST_H_ +#define TEST_GTEST_H_ + +#include "rtc_base/ignore_wundef.h" + +RTC_PUSH_IGNORING_WUNDEF() +#include "testing/gtest/include/gtest/gtest-spi.h" +#include "testing/gtest/include/gtest/gtest.h" +RTC_POP_IGNORING_WUNDEF() + +// GTEST_HAS_DEATH_TEST is set to 1 when death tests are supported, but appears +// to be left unset if they're not supported. Rather than depend on this, we +// set it to 0 ourselves here. +#ifndef GTEST_HAS_DEATH_TEST +#define GTEST_HAS_DEATH_TEST 0 +#endif + +#endif // TEST_GTEST_H_ diff --git a/third_party/libwebrtc/test/ios/Info.plist b/third_party/libwebrtc/test/ios/Info.plist new file mode 100644 index 0000000000..fe06a5e005 --- /dev/null +++ b/third_party/libwebrtc/test/ios/Info.plist @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>${BUNDLE_IDENTIFIER}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>NSAppTransportSecurity</key> + <dict> + <key>NSAllowsArbitraryLoads</key> + <true/> + </dict> + <key>UIFileSharingEnabled</key> + <true/> + <key>UISupportedInterfaceOrientation</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UIBackgroundModes</key> + <array> + <string>fetch</string> + </array> + <key>NSMicrophoneUsageDescription</key> + <string>For testing purposes</string> +</dict> +</plist> diff --git a/third_party/libwebrtc/test/ios/coverage_util_ios.h b/third_party/libwebrtc/test/ios/coverage_util_ios.h new file mode 100644 index 0000000000..a17b69dca8 --- /dev/null +++ b/third_party/libwebrtc/test/ios/coverage_util_ios.h @@ -0,0 +1,24 @@ +/* + * Copyright 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_IOS_COVERAGE_UTIL_IOS_H_ +#define TEST_IOS_COVERAGE_UTIL_IOS_H_ + +namespace rtc { +namespace test { + +// In debug builds, if IOS_ENABLE_COVERAGE is defined, sets the filename of the +// coverage file. Otherwise, it does nothing. +void ConfigureCoverageReportPath(); + +} // namespace test +} // namespace rtc + +#endif // TEST_IOS_COVERAGE_UTIL_IOS_H_ diff --git a/third_party/libwebrtc/test/ios/coverage_util_ios.mm b/third_party/libwebrtc/test/ios/coverage_util_ios.mm new file mode 100644 index 0000000000..c21a16def2 --- /dev/null +++ b/third_party/libwebrtc/test/ios/coverage_util_ios.mm @@ -0,0 +1,42 @@ +/* + * Copyright 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> + +#ifdef WEBRTC_IOS_ENABLE_COVERAGE +extern "C" void __llvm_profile_set_filename(const char* name); +#endif + +namespace rtc { +namespace test { + +void ConfigureCoverageReportPath() { +#ifdef WEBRTC_IOS_ENABLE_COVERAGE + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + // Writes the profraw file to the Documents directory, where the app has + // write rights. + NSArray* paths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString* documents_directory = [paths firstObject]; + NSString* file_name = [documents_directory stringByAppendingPathComponent:@"coverage.profraw"]; + + // For documentation, see: + // http://clang.llvm.org/docs/SourceBasedCodeCoverage.html + __llvm_profile_set_filename([file_name cStringUsingEncoding:NSUTF8StringEncoding]); + + // Print the path for easier retrieval. + NSLog(@"Coverage data at %@.", file_name); + }); +#endif // ifdef WEBRTC_IOS_ENABLE_COVERAGE +} + +} // namespace test +} // namespace rtc diff --git a/third_party/libwebrtc/test/ios/google_test_runner.mm b/third_party/libwebrtc/test/ios/google_test_runner.mm new file mode 100644 index 0000000000..87b7f7dfd7 --- /dev/null +++ b/third_party/libwebrtc/test/ios/google_test_runner.mm @@ -0,0 +1,40 @@ +/* + * 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. + */ + +// Copied from Chromium base/test/ios/google_test_runner.mm to avoid +// the //base dependency. The protocol is required to run iOS Unittest. + +#import <UIKit/UIKit.h> +#import <XCTest/XCTest.h> + +#import "test/ios/google_test_runner_delegate.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@interface GoogleTestRunner : XCTestCase +@end + +@implementation GoogleTestRunner + +- (void)testRunGoogleTests { + self.continueAfterFailure = false; + + id appDelegate = UIApplication.sharedApplication.delegate; + XCTAssertTrue([appDelegate conformsToProtocol:@protocol(GoogleTestRunnerDelegate)]); + + id<GoogleTestRunnerDelegate> runnerDelegate = + static_cast<id<GoogleTestRunnerDelegate>>(appDelegate); + XCTAssertTrue(runnerDelegate.supportsRunningGoogleTests); + XCTAssertTrue([runnerDelegate runGoogleTests] == 0); +} + +@end diff --git a/third_party/libwebrtc/test/ios/google_test_runner_delegate.h b/third_party/libwebrtc/test/ios/google_test_runner_delegate.h new file mode 100644 index 0000000000..78ee59e028 --- /dev/null +++ b/third_party/libwebrtc/test/ios/google_test_runner_delegate.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 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_IOS_GOOGLE_TEST_RUNNER_DELEGATE_H_ +#define TEST_IOS_GOOGLE_TEST_RUNNER_DELEGATE_H_ + +// Copied from Chromium base/test/ios/google_test_runner_delegate.h +// to avoid the //base dependency. This protocol is required +// to run iOS Unittest. +@protocol GoogleTestRunnerDelegate + +// Returns YES if this delegate supports running GoogleTests via a call to +// `runGoogleTests`. +@property(nonatomic, readonly, assign) BOOL supportsRunningGoogleTests; + +// Runs GoogleTests and returns the final exit code. +- (int)runGoogleTests; + +@end + +#endif // TEST_IOS_GOOGLE_TEST_RUNNER_DELEGATE_H_ diff --git a/third_party/libwebrtc/test/ios/test_support.h b/third_party/libwebrtc/test/ios/test_support.h new file mode 100644 index 0000000000..5ac731393f --- /dev/null +++ b/third_party/libwebrtc/test/ios/test_support.h @@ -0,0 +1,39 @@ +/* + * Copyright 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. + */ + +#ifndef TEST_IOS_TEST_SUPPORT_H_ +#define TEST_IOS_TEST_SUPPORT_H_ + +#include <string> +#include <vector> + +#include "absl/types/optional.h" + +namespace rtc { +namespace test { +// Launches an iOS app that serves as a host for a test suite. +// This is necessary as iOS doesn't like processes without a gui +// running for longer than a few seconds. +void RunTestsFromIOSApp(); +void InitTestSuite(int (*test_suite)(void), + int argc, + char* argv[], + bool save_chartjson_result, + bool export_perf_results_new_api, + std::string webrtc_test_metrics_output_path, + absl::optional<std::vector<std::string>> metrics_to_plot); + +// Returns true if unittests should be run by the XCTest runnner. +bool ShouldRunIOSUnittestsWithXCTest(); + +} // namespace test +} // namespace rtc + +#endif // TEST_IOS_TEST_SUPPORT_H_ diff --git a/third_party/libwebrtc/test/ios/test_support.mm b/third_party/libwebrtc/test/ios/test_support.mm new file mode 100644 index 0000000000..d3c9ee0c74 --- /dev/null +++ b/third_party/libwebrtc/test/ios/test_support.mm @@ -0,0 +1,217 @@ +/* + * Copyright 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. + */ + +#import <UIKit/UIKit.h> + +#include "api/test/metrics/chrome_perf_dashboard_metrics_exporter.h" +#include "api/test/metrics/global_metrics_logger_and_exporter.h" +#include "api/test/metrics/metrics_exporter.h" +#include "api/test/metrics/metrics_set_proto_file_exporter.h" +#include "api/test/metrics/print_result_proxy_metrics_exporter.h" +#include "api/test/metrics/stdout_metrics_exporter.h" +#include "test/ios/coverage_util_ios.h" +#include "test/ios/google_test_runner_delegate.h" +#include "test/ios/test_support.h" +#include "test/testsupport/perf_test.h" + +#import "sdk/objc/helpers/NSString+StdString.h" + +// Springboard will kill any iOS app that fails to check in after launch within +// a given time. Starting a UIApplication before invoking TestSuite::Run +// prevents this from happening. + +// InitIOSRunHook saves the TestSuite and argc/argv, then invoking +// RunTestsFromIOSApp calls UIApplicationMain(), providing an application +// delegate class: WebRtcUnitTestDelegate. The delegate implements +// application:didFinishLaunchingWithOptions: to invoke the TestSuite's Run +// method. + +// Since the executable isn't likely to be a real iOS UI, the delegate puts up a +// window displaying the app name. If a bunch of apps using MainHook are being +// run in a row, this provides an indication of which one is currently running. + +// If enabled, runs unittests using the XCTest test runner. +const char kEnableRunIOSUnittestsWithXCTest[] = "enable-run-ios-unittests-with-xctest"; + +static int (*g_test_suite)(void) = NULL; +static int g_argc; +static char **g_argv; +static bool g_write_perf_output; +static bool g_export_perf_results_new_api; +static std::string g_webrtc_test_metrics_output_path; +static absl::optional<bool> g_is_xctest; +static absl::optional<std::vector<std::string>> g_metrics_to_plot; + +@interface UIApplication (Testing) +- (void)_terminateWithStatus:(int)status; +@end + +@interface WebRtcUnitTestDelegate : NSObject <GoogleTestRunnerDelegate> { + UIWindow *_window; +} +- (void)runTests; +@end + +@implementation WebRtcUnitTestDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + CGRect bounds = [[UIScreen mainScreen] bounds]; + + _window = [[UIWindow alloc] initWithFrame:bounds]; + [_window setBackgroundColor:[UIColor whiteColor]]; + [_window makeKeyAndVisible]; + + // Add a label with the app name. + UILabel *label = [[UILabel alloc] initWithFrame:bounds]; + label.text = [[NSProcessInfo processInfo] processName]; + label.textAlignment = NSTextAlignmentCenter; + [_window addSubview:label]; + + // An NSInternalInconsistencyException is thrown if the app doesn't have a + // root view controller. Set an empty one here. + [_window setRootViewController:[[UIViewController alloc] init]]; + + if (!rtc::test::ShouldRunIOSUnittestsWithXCTest()) { + // When running in XCTest mode, XCTest will invoke `runGoogleTest` directly. + // Otherwise, schedule a call to `runTests`. + [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; + } + + return YES; +} + +- (BOOL)supportsRunningGoogleTests { + return rtc::test::ShouldRunIOSUnittestsWithXCTest(); +} + +- (int)runGoogleTests { + rtc::test::ConfigureCoverageReportPath(); + + int exitStatus = g_test_suite(); + + NSArray<NSString *> *outputDirectories = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + std::vector<std::unique_ptr<webrtc::test::MetricsExporter>> exporters; + if (g_export_perf_results_new_api) { + exporters.push_back(std::make_unique<webrtc::test::StdoutMetricsExporter>()); + if (g_write_perf_output) { + // Stores data into a proto file under the app's document directory. + NSString *fileName = @"perftest-output.pb"; + if ([outputDirectories count] != 0) { + NSString *outputPath = [outputDirectories[0] stringByAppendingPathComponent:fileName]; + + exporters.push_back(std::make_unique<webrtc::test::ChromePerfDashboardMetricsExporter>( + [NSString stdStringForString:outputPath])); + } + } + if (!g_webrtc_test_metrics_output_path.empty()) { + RTC_CHECK_EQ(g_webrtc_test_metrics_output_path.find('/'), std::string::npos) + << "On iOS, --webrtc_test_metrics_output_path must only be a file name."; + if ([outputDirectories count] != 0) { + NSString *fileName = [NSString stringWithCString:g_webrtc_test_metrics_output_path.c_str() + encoding:[NSString defaultCStringEncoding]]; + NSString *outputPath = [outputDirectories[0] stringByAppendingPathComponent:fileName]; + exporters.push_back(std::make_unique<webrtc::test::MetricsSetProtoFileExporter>( + webrtc::test::MetricsSetProtoFileExporter::Options( + [NSString stdStringForString:outputPath]))); + } + } + } else { + exporters.push_back(std::make_unique<webrtc::test::PrintResultProxyMetricsExporter>()); + } + webrtc::test::ExportPerfMetric(*webrtc::test::GetGlobalMetricsLogger(), std::move(exporters)); + if (!g_export_perf_results_new_api) { + if (g_write_perf_output) { + // Stores data into a proto file under the app's document directory. + NSString *fileName = @"perftest-output.pb"; + if ([outputDirectories count] != 0) { + NSString *outputPath = [outputDirectories[0] stringByAppendingPathComponent:fileName]; + + if (!webrtc::test::WritePerfResults([NSString stdStringForString:outputPath])) { + return 1; + } + } + } + if (g_metrics_to_plot) { + webrtc::test::PrintPlottableResults(*g_metrics_to_plot); + } + } + + return exitStatus; +} + +- (void)runTests { + RTC_DCHECK(!rtc::test::ShouldRunIOSUnittestsWithXCTest()); + rtc::test::ConfigureCoverageReportPath(); + + int exitStatus = [self runGoogleTests]; + + // If a test app is too fast, it will exit before Instruments has has a + // a chance to initialize and no test results will be seen. + // TODO(crbug.com/137010): Figure out how much time is actually needed, and + // sleep only to make sure that much time has elapsed since launch. + [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; + + // Use the hidden selector to try and cleanly take down the app (otherwise + // things can think the app crashed even on a zero exit status). + UIApplication *application = [UIApplication sharedApplication]; + [application _terminateWithStatus:exitStatus]; + + exit(exitStatus); +} + +@end +namespace rtc { +namespace test { + +// Note: This is not thread safe, and must be called from the same thread as +// runTests above. +void InitTestSuite(int (*test_suite)(void), + int argc, + char *argv[], + bool write_perf_output, + bool export_perf_results_new_api, + std::string webrtc_test_metrics_output_path, + absl::optional<std::vector<std::string>> metrics_to_plot) { + g_test_suite = test_suite; + g_argc = argc; + g_argv = argv; + g_write_perf_output = write_perf_output; + g_export_perf_results_new_api = export_perf_results_new_api; + g_webrtc_test_metrics_output_path = webrtc_test_metrics_output_path; + g_metrics_to_plot = std::move(metrics_to_plot); +} + +void RunTestsFromIOSApp() { + @autoreleasepool { + exit(UIApplicationMain(g_argc, g_argv, nil, @"WebRtcUnitTestDelegate")); + } +} + +bool ShouldRunIOSUnittestsWithXCTest() { + if (g_is_xctest.has_value()) { + return g_is_xctest.value(); + } + + char **argv = g_argv; + while (*argv != nullptr) { + if (strstr(*argv, kEnableRunIOSUnittestsWithXCTest) != nullptr) { + g_is_xctest = absl::optional<bool>(true); + return true; + } + argv++; + } + g_is_xctest = absl::optional<bool>(false); + return false; +} + +} // namespace test +} // namespace rtc diff --git a/third_party/libwebrtc/test/layer_filtering_transport.cc b/third_party/libwebrtc/test/layer_filtering_transport.cc new file mode 100644 index 0000000000..f5e5ee3002 --- /dev/null +++ b/third_party/libwebrtc/test/layer_filtering_transport.cc @@ -0,0 +1,186 @@ +/* + * Copyright (c) 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. + */ + +#include "test/layer_filtering_transport.h" + +#include <string.h> + +#include <algorithm> +#include <memory> +#include <utility> + +#include "api/rtp_headers.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/create_video_rtp_depacketizer.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "modules/video_coding/codecs/interface/common_constants.h" +#include "modules/video_coding/codecs/vp8/include/vp8_globals.h" +#include "modules/video_coding/codecs/vp9/include/vp9_globals.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +LayerFilteringTransport::LayerFilteringTransport( + TaskQueueBase* task_queue, + std::unique_ptr<SimulatedPacketReceiverInterface> pipe, + Call* send_call, + uint8_t vp8_video_payload_type, + uint8_t vp9_video_payload_type, + int selected_tl, + int selected_sl, + const std::map<uint8_t, MediaType>& payload_type_map, + uint32_t ssrc_to_filter_min, + uint32_t ssrc_to_filter_max, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions) + : DirectTransport(task_queue, + std::move(pipe), + send_call, + payload_type_map, + audio_extensions, + video_extensions), + vp8_video_payload_type_(vp8_video_payload_type), + vp9_video_payload_type_(vp9_video_payload_type), + vp8_depacketizer_(CreateVideoRtpDepacketizer(kVideoCodecVP8)), + vp9_depacketizer_(CreateVideoRtpDepacketizer(kVideoCodecVP9)), + selected_tl_(selected_tl), + selected_sl_(selected_sl), + discarded_last_packet_(false), + ssrc_to_filter_min_(ssrc_to_filter_min), + ssrc_to_filter_max_(ssrc_to_filter_max) {} + +LayerFilteringTransport::LayerFilteringTransport( + TaskQueueBase* task_queue, + std::unique_ptr<SimulatedPacketReceiverInterface> pipe, + Call* send_call, + uint8_t vp8_video_payload_type, + uint8_t vp9_video_payload_type, + int selected_tl, + int selected_sl, + const std::map<uint8_t, MediaType>& payload_type_map, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions) + : LayerFilteringTransport(task_queue, + std::move(pipe), + send_call, + vp8_video_payload_type, + vp9_video_payload_type, + selected_tl, + selected_sl, + payload_type_map, + /*ssrc_to_filter_min=*/0, + /*ssrc_to_filter_max=*/0xFFFFFFFF, + audio_extensions, + video_extensions) {} + +bool LayerFilteringTransport::DiscardedLastPacket() const { + return discarded_last_packet_; +} + +bool LayerFilteringTransport::SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) { + if (selected_tl_ == -1 && selected_sl_ == -1) { + // Nothing to change, forward the packet immediately. + return test::DirectTransport::SendRtp(packet, length, options); + } + + RtpPacket rtp_packet; + rtp_packet.Parse(packet, length); + + if (rtp_packet.Ssrc() < ssrc_to_filter_min_ || + rtp_packet.Ssrc() > ssrc_to_filter_max_) { + // Nothing to change, forward the packet immediately. + return test::DirectTransport::SendRtp(packet, length, options); + } + + if (rtp_packet.PayloadType() == vp8_video_payload_type_ || + rtp_packet.PayloadType() == vp9_video_payload_type_) { + const bool is_vp8 = rtp_packet.PayloadType() == vp8_video_payload_type_; + VideoRtpDepacketizer& depacketizer = + is_vp8 ? *vp8_depacketizer_ : *vp9_depacketizer_; + if (auto parsed_payload = depacketizer.Parse(rtp_packet.PayloadBuffer())) { + int temporal_idx; + int spatial_idx; + bool non_ref_for_inter_layer_pred; + bool end_of_frame; + + if (is_vp8) { + temporal_idx = absl::get<RTPVideoHeaderVP8>( + parsed_payload->video_header.video_type_header) + .temporalIdx; + spatial_idx = kNoSpatialIdx; + num_active_spatial_layers_ = 1; + non_ref_for_inter_layer_pred = false; + end_of_frame = true; + } else { + const auto& vp9_header = absl::get<RTPVideoHeaderVP9>( + parsed_payload->video_header.video_type_header); + temporal_idx = vp9_header.temporal_idx; + spatial_idx = vp9_header.spatial_idx; + non_ref_for_inter_layer_pred = vp9_header.non_ref_for_inter_layer_pred; + end_of_frame = vp9_header.end_of_frame; + if (vp9_header.ss_data_available) { + RTC_DCHECK(vp9_header.temporal_idx == kNoTemporalIdx || + vp9_header.temporal_idx == 0); + num_active_spatial_layers_ = vp9_header.num_spatial_layers; + } + } + + if (spatial_idx == kNoSpatialIdx) + num_active_spatial_layers_ = 1; + + RTC_CHECK_GT(num_active_spatial_layers_, 0); + + if (selected_sl_ >= 0 && + spatial_idx == + std::min(num_active_spatial_layers_ - 1, selected_sl_) && + end_of_frame) { + // This layer is now the last in the superframe. + rtp_packet.SetMarker(true); + } else { + const bool higher_temporal_layer = + (selected_tl_ >= 0 && temporal_idx != kNoTemporalIdx && + temporal_idx > selected_tl_); + + const bool higher_spatial_layer = + (selected_sl_ >= 0 && spatial_idx != kNoSpatialIdx && + spatial_idx > selected_sl_); + + // Filter out non-reference lower spatial layers since they are not + // needed for decoding of target spatial layer. + const bool lower_non_ref_spatial_layer = + (selected_sl_ >= 0 && spatial_idx != kNoSpatialIdx && + spatial_idx < + std::min(num_active_spatial_layers_ - 1, selected_sl_) && + non_ref_for_inter_layer_pred); + + if (higher_temporal_layer || higher_spatial_layer || + lower_non_ref_spatial_layer) { + // Truncate packet to a padding packet. + rtp_packet.SetPayloadSize(0); + rtp_packet.SetPadding(1); + rtp_packet.SetMarker(false); + discarded_last_packet_ = true; + } + } + } else { + RTC_DCHECK_NOTREACHED() << "Parse error"; + } + } + + return test::DirectTransport::SendRtp(rtp_packet.data(), rtp_packet.size(), + options); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/layer_filtering_transport.h b/third_party/libwebrtc/test/layer_filtering_transport.h new file mode 100644 index 0000000000..3aefd7159b --- /dev/null +++ b/third_party/libwebrtc/test/layer_filtering_transport.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 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. + */ +#ifndef TEST_LAYER_FILTERING_TRANSPORT_H_ +#define TEST_LAYER_FILTERING_TRANSPORT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <memory> + +#include "api/call/transport.h" +#include "api/media_types.h" +#include "call/call.h" +#include "call/simulated_packet_receiver.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "test/direct_transport.h" + +namespace webrtc { + +namespace test { + +class LayerFilteringTransport : public test::DirectTransport { + public: + LayerFilteringTransport( + TaskQueueBase* task_queue, + std::unique_ptr<SimulatedPacketReceiverInterface> pipe, + Call* send_call, + uint8_t vp8_video_payload_type, + uint8_t vp9_video_payload_type, + int selected_tl, + int selected_sl, + const std::map<uint8_t, MediaType>& payload_type_map, + uint32_t ssrc_to_filter_min, + uint32_t ssrc_to_filter_max, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions); + LayerFilteringTransport( + TaskQueueBase* task_queue, + std::unique_ptr<SimulatedPacketReceiverInterface> pipe, + Call* send_call, + uint8_t vp8_video_payload_type, + uint8_t vp9_video_payload_type, + int selected_tl, + int selected_sl, + const std::map<uint8_t, MediaType>& payload_type_map, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions); + bool DiscardedLastPacket() const; + bool SendRtp(const uint8_t* data, + size_t length, + const PacketOptions& options) override; + + private: + // Used to distinguish between VP8 and VP9. + const uint8_t vp8_video_payload_type_; + const uint8_t vp9_video_payload_type_; + const std::unique_ptr<VideoRtpDepacketizer> vp8_depacketizer_; + const std::unique_ptr<VideoRtpDepacketizer> vp9_depacketizer_; + // Discard or invalidate all temporal/spatial layers with id greater than the + // selected one. -1 to disable filtering. + const int selected_tl_; + const int selected_sl_; + bool discarded_last_packet_; + int num_active_spatial_layers_; + const uint32_t ssrc_to_filter_min_; + const uint32_t ssrc_to_filter_max_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_LAYER_FILTERING_TRANSPORT_H_ diff --git a/third_party/libwebrtc/test/linux/glx_renderer.cc b/third_party/libwebrtc/test/linux/glx_renderer.cc new file mode 100644 index 0000000000..509a6c286e --- /dev/null +++ b/third_party/libwebrtc/test/linux/glx_renderer.cc @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2013 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/linux/glx_renderer.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <stdlib.h> + +namespace webrtc { +namespace test { + +GlxRenderer::GlxRenderer(size_t width, size_t height) + : width_(width), height_(height), display_(NULL), context_(NULL) { + RTC_DCHECK_GT(width, 0); + RTC_DCHECK_GT(height, 0); +} + +GlxRenderer::~GlxRenderer() { + Destroy(); +} + +bool GlxRenderer::Init(const char* window_title) { + if ((display_ = XOpenDisplay(NULL)) == NULL) { + Destroy(); + return false; + } + + int screen = DefaultScreen(display_); + + XVisualInfo* vi; + int attr_list[] = { + GLX_DOUBLEBUFFER, GLX_RGBA, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, + GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, None, + }; + + if ((vi = glXChooseVisual(display_, screen, attr_list)) == NULL) { + Destroy(); + return false; + } + + context_ = glXCreateContext(display_, vi, 0, true); + if (context_ == NULL) { + Destroy(); + return false; + } + + XSetWindowAttributes window_attributes; + window_attributes.colormap = XCreateColormap( + display_, RootWindow(display_, vi->screen), vi->visual, AllocNone); + window_attributes.border_pixel = 0; + window_attributes.event_mask = StructureNotifyMask | ExposureMask; + window_ = XCreateWindow(display_, RootWindow(display_, vi->screen), 0, 0, + width_, height_, 0, vi->depth, InputOutput, + vi->visual, CWBorderPixel | CWColormap | CWEventMask, + &window_attributes); + XFree(vi); + + XSetStandardProperties(display_, window_, window_title, window_title, None, + NULL, 0, NULL); + + Atom wm_delete = XInternAtom(display_, "WM_DELETE_WINDOW", True); + if (wm_delete != None) { + XSetWMProtocols(display_, window_, &wm_delete, 1); + } + + XMapRaised(display_, window_); + + if (!glXMakeCurrent(display_, window_, context_)) { + Destroy(); + return false; + } + GlRenderer::Init(); + if (!glXMakeCurrent(display_, None, NULL)) { + Destroy(); + return false; + } + + Resize(width_, height_); + return true; +} + +void GlxRenderer::Destroy() { + if (context_ != NULL) { + glXMakeCurrent(display_, window_, context_); + GlRenderer::Destroy(); + glXMakeCurrent(display_, None, NULL); + glXDestroyContext(display_, context_); + context_ = NULL; + } + + if (display_ != NULL) { + XCloseDisplay(display_); + display_ = NULL; + } +} + +GlxRenderer* GlxRenderer::Create(const char* window_title, + size_t width, + size_t height) { + GlxRenderer* glx_renderer = new GlxRenderer(width, height); + if (!glx_renderer->Init(window_title)) { + // TODO(pbos): Add GLX-failed warning here? + delete glx_renderer; + return NULL; + } + return glx_renderer; +} + +void GlxRenderer::Resize(size_t width, size_t height) { + width_ = width; + height_ = height; + if (!glXMakeCurrent(display_, window_, context_)) { + abort(); + } + GlRenderer::ResizeViewport(width_, height_); + if (!glXMakeCurrent(display_, None, NULL)) { + abort(); + } + + XSizeHints* size_hints = XAllocSizeHints(); + if (size_hints == NULL) { + abort(); + } + size_hints->flags = PAspect; + size_hints->min_aspect.x = size_hints->max_aspect.x = width_; + size_hints->min_aspect.y = size_hints->max_aspect.y = height_; + XSetWMNormalHints(display_, window_, size_hints); + XFree(size_hints); + + XWindowChanges wc; + wc.width = static_cast<int>(width); + wc.height = static_cast<int>(height); + XConfigureWindow(display_, window_, CWWidth | CWHeight, &wc); +} + +void GlxRenderer::OnFrame(const webrtc::VideoFrame& frame) { + if (static_cast<size_t>(frame.width()) != width_ || + static_cast<size_t>(frame.height()) != height_) { + Resize(static_cast<size_t>(frame.width()), + static_cast<size_t>(frame.height())); + } + + XEvent event; + if (!glXMakeCurrent(display_, window_, context_)) { + abort(); + } + while (XPending(display_)) { + XNextEvent(display_, &event); + switch (event.type) { + case ConfigureNotify: + GlRenderer::ResizeViewport(event.xconfigure.width, + event.xconfigure.height); + break; + default: + break; + } + } + + GlRenderer::OnFrame(frame); + glXSwapBuffers(display_, window_); + + if (!glXMakeCurrent(display_, None, NULL)) { + abort(); + } +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/linux/glx_renderer.h b/third_party/libwebrtc/test/linux/glx_renderer.h new file mode 100644 index 0000000000..8add60d964 --- /dev/null +++ b/third_party/libwebrtc/test/linux/glx_renderer.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013 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_LINUX_GLX_RENDERER_H_ +#define TEST_LINUX_GLX_RENDERER_H_ + +#include <GL/glx.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <stddef.h> + +#include "api/video/video_frame.h" +#include "test/gl/gl_renderer.h" + +namespace webrtc { +namespace test { + +class GlxRenderer : public GlRenderer { + public: + static GlxRenderer* Create(const char* window_title, + size_t width, + size_t height); + virtual ~GlxRenderer(); + + void OnFrame(const webrtc::VideoFrame& frame) override; + + private: + GlxRenderer(size_t width, size_t height); + + bool Init(const char* window_title); + void Resize(size_t width, size_t height); + void Destroy(); + + size_t width_, height_; + + Display* display_; + Window window_; + GLXContext context_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_LINUX_GLX_RENDERER_H_ diff --git a/third_party/libwebrtc/test/linux/video_renderer_linux.cc b/third_party/libwebrtc/test/linux/video_renderer_linux.cc new file mode 100644 index 0000000000..74c95235df --- /dev/null +++ b/third_party/libwebrtc/test/linux/video_renderer_linux.cc @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 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 <stddef.h> + +#include "test/linux/glx_renderer.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace test { + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + GlxRenderer* glx_renderer = GlxRenderer::Create(window_title, width, height); + if (glx_renderer != NULL) { + return glx_renderer; + } + return NULL; +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/logging/BUILD.gn b/third_party/libwebrtc/test/logging/BUILD.gn new file mode 100644 index 0000000000..301c0e59c0 --- /dev/null +++ b/third_party/libwebrtc/test/logging/BUILD.gn @@ -0,0 +1,35 @@ +# 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. + +import("../../webrtc.gni") + +rtc_library("log_writer") { + testonly = true + visibility = [ "*" ] + sources = [ + "file_log_writer.cc", + "file_log_writer.h", + "log_writer.cc", + "log_writer.h", + "memory_log_writer.cc", + "memory_log_writer.h", + ] + + deps = [ + "../../api:libjingle_logging_api", + "../../rtc_base:checks", + "../../rtc_base:logging", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:stringutils", + "../../test:fileutils", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} diff --git a/third_party/libwebrtc/test/logging/file_log_writer.cc b/third_party/libwebrtc/test/logging/file_log_writer.cc new file mode 100644 index 0000000000..9189e1630d --- /dev/null +++ b/third_party/libwebrtc/test/logging/file_log_writer.cc @@ -0,0 +1,65 @@ +/* + * Copyright 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/logging/file_log_writer.h" + +#include <memory> + +#include "absl/strings/string_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace webrtc_impl { + +FileLogWriter::FileLogWriter(absl::string_view file_path) + : out_(std::fopen(std::string(file_path).c_str(), "wb")) { + RTC_CHECK(out_ != nullptr) + << "Failed to open file: '" << file_path << "' for writing."; +} + +FileLogWriter::~FileLogWriter() { + std::fclose(out_); +} + +bool FileLogWriter::IsActive() const { + return true; +} + +bool FileLogWriter::Write(absl::string_view value) { + // We don't expect the write to fail. If it does, we don't want to risk + // silently ignoring it. + RTC_CHECK_EQ(std::fwrite(value.data(), 1, value.size(), out_), value.size()) + << "fwrite failed unexpectedly: " << errno; + return true; +} + +void FileLogWriter::Flush() { + RTC_CHECK_EQ(fflush(out_), 0) << "fflush failed unexpectedly: " << errno; +} + +} // namespace webrtc_impl + +FileLogWriterFactory::FileLogWriterFactory(absl::string_view base_path) + : base_path_(base_path) { + for (size_t i = 0; i < base_path.size(); ++i) { + if (base_path[i] == '/') + test::CreateDir(base_path.substr(0, i)); + } +} + +FileLogWriterFactory::~FileLogWriterFactory() {} + +std::unique_ptr<RtcEventLogOutput> FileLogWriterFactory::Create( + absl::string_view filename) { + return std::make_unique<webrtc_impl::FileLogWriter>(base_path_ + + std::string(filename)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/logging/file_log_writer.h b/third_party/libwebrtc/test/logging/file_log_writer.h new file mode 100644 index 0000000000..c49b96ceff --- /dev/null +++ b/third_party/libwebrtc/test/logging/file_log_writer.h @@ -0,0 +1,50 @@ +/* + * Copyright 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_LOGGING_FILE_LOG_WRITER_H_ +#define TEST_LOGGING_FILE_LOG_WRITER_H_ + +#include <cstdio> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "test/logging/log_writer.h" + +namespace webrtc { +namespace webrtc_impl { +class FileLogWriter final : public RtcEventLogOutput { + public: + explicit FileLogWriter(absl::string_view file_path); + ~FileLogWriter() final; + bool IsActive() const override; + bool Write(absl::string_view value) override; + void Flush() override; + + private: + std::FILE* const out_; +}; +} // namespace webrtc_impl +class FileLogWriterFactory final : public LogWriterFactoryInterface { + public: + explicit FileLogWriterFactory(absl::string_view base_path); + ~FileLogWriterFactory() final; + + std::unique_ptr<RtcEventLogOutput> Create( + absl::string_view filename) override; + + private: + const std::string base_path_; + std::vector<std::unique_ptr<webrtc_impl::FileLogWriter>> writers_; +}; + +} // namespace webrtc + +#endif // TEST_LOGGING_FILE_LOG_WRITER_H_ diff --git a/third_party/libwebrtc/test/logging/log_writer.cc b/third_party/libwebrtc/test/logging/log_writer.cc new file mode 100644 index 0000000000..d9b8c1e68f --- /dev/null +++ b/third_party/libwebrtc/test/logging/log_writer.cc @@ -0,0 +1,26 @@ +/* + * Copyright 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/logging/log_writer.h" + +#include "absl/strings/string_view.h" + +namespace webrtc { + +LogWriterFactoryAddPrefix::LogWriterFactoryAddPrefix( + LogWriterFactoryInterface* base, + absl::string_view prefix) + : base_factory_(base), prefix_(prefix) {} + +std::unique_ptr<RtcEventLogOutput> LogWriterFactoryAddPrefix::Create( + absl::string_view filename) { + return base_factory_->Create(prefix_ + std::string(filename)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/logging/log_writer.h b/third_party/libwebrtc/test/logging/log_writer.h new file mode 100644 index 0000000000..335dab353f --- /dev/null +++ b/third_party/libwebrtc/test/logging/log_writer.h @@ -0,0 +1,65 @@ +/* + * Copyright 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_LOGGING_LOG_WRITER_H_ +#define TEST_LOGGING_LOG_WRITER_H_ + +#include <stdarg.h> + +#include <memory> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "api/rtc_event_log_output.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +template <class... Args> +inline void LogWriteFormat(RtcEventLogOutput* out_, const char* fmt, ...) { + va_list args, copy; + va_start(args, fmt); + va_copy(copy, args); + const int predicted_length = std::vsnprintf(nullptr, 0, fmt, copy); + va_end(copy); + + RTC_DCHECK_GE(predicted_length, 0); + std::string out_str(predicted_length, '\0'); + if (predicted_length > 0) { + // Pass "+ 1" to vsnprintf to include space for the '\0'. + const int actual_length = + std::vsnprintf(&out_str.front(), predicted_length + 1, fmt, args); + RTC_DCHECK_GE(actual_length, 0); + } + va_end(args); + out_->Write(out_str); +} + +class LogWriterFactoryInterface { + public: + virtual std::unique_ptr<RtcEventLogOutput> Create( + absl::string_view filename) = 0; + virtual ~LogWriterFactoryInterface() = default; +}; + +class LogWriterFactoryAddPrefix : public LogWriterFactoryInterface { + public: + LogWriterFactoryAddPrefix(LogWriterFactoryInterface* base, + absl::string_view prefix); + std::unique_ptr<RtcEventLogOutput> Create( + absl::string_view filename) override; + + private: + LogWriterFactoryInterface* const base_factory_; + const std::string prefix_; +}; + +} // namespace webrtc + +#endif // TEST_LOGGING_LOG_WRITER_H_ diff --git a/third_party/libwebrtc/test/logging/memory_log_writer.cc b/third_party/libwebrtc/test/logging/memory_log_writer.cc new file mode 100644 index 0000000000..eae9223c77 --- /dev/null +++ b/third_party/libwebrtc/test/logging/memory_log_writer.cc @@ -0,0 +1,64 @@ +/* + * Copyright 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/logging/memory_log_writer.h" + +#include <memory> + +#include "absl/strings/string_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { +class MemoryLogWriter final : public RtcEventLogOutput { + public: + explicit MemoryLogWriter(std::map<std::string, std::string>* target, + absl::string_view filename) + : target_(target), filename_(filename) {} + ~MemoryLogWriter() final { target_->insert({filename_, std::move(buffer_)}); } + bool IsActive() const override { return true; } + bool Write(absl::string_view value) override { + buffer_.append(value.data(), value.size()); + return true; + } + void Flush() override {} + + private: + std::map<std::string, std::string>* const target_; + const std::string filename_; + std::string buffer_; +}; + +class MemoryLogWriterFactory final : public LogWriterFactoryInterface { + public: + explicit MemoryLogWriterFactory(std::map<std::string, std::string>* target) + : target_(target) {} + ~MemoryLogWriterFactory() override {} + std::unique_ptr<RtcEventLogOutput> Create( + absl::string_view filename) override { + return std::make_unique<MemoryLogWriter>(target_, filename); + } + + private: + std::map<std::string, std::string>* const target_; +}; + +} // namespace + +MemoryLogStorage::MemoryLogStorage() {} + +MemoryLogStorage::~MemoryLogStorage() {} + +std::unique_ptr<LogWriterFactoryInterface> MemoryLogStorage::CreateFactory() { + return std::make_unique<MemoryLogWriterFactory>(&logs_); +} + +// namespace webrtc_impl +} // namespace webrtc diff --git a/third_party/libwebrtc/test/logging/memory_log_writer.h b/third_party/libwebrtc/test/logging/memory_log_writer.h new file mode 100644 index 0000000000..e795b2fd10 --- /dev/null +++ b/third_party/libwebrtc/test/logging/memory_log_writer.h @@ -0,0 +1,40 @@ +/* + * Copyright 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_LOGGING_MEMORY_LOG_WRITER_H_ +#define TEST_LOGGING_MEMORY_LOG_WRITER_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "test/logging/log_writer.h" + +namespace webrtc { + +// Allows creating log writer factories that creates log writers that saves +// their content to memory. When the log writers are destroyed, their content is +// saved to the logs_ member of this class. The intended usage is to keep this +// class alive after the created factories and writers have been destroyed and +// then use logs() to access all the saved logs. +class MemoryLogStorage { + public: + MemoryLogStorage(); + ~MemoryLogStorage(); + std::unique_ptr<LogWriterFactoryInterface> CreateFactory(); + const std::map<std::string, std::string>& logs() { return logs_; } + + private: + std::map<std::string, std::string> logs_; +}; + +} // namespace webrtc + +#endif // TEST_LOGGING_MEMORY_LOG_WRITER_H_ diff --git a/third_party/libwebrtc/test/mac/Info.plist b/third_party/libwebrtc/test/mac/Info.plist new file mode 100644 index 0000000000..8a2b5cf0a0 --- /dev/null +++ b/third_party/libwebrtc/test/mac/Info.plist @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleIdentifier</key> + <string>org.webrtc.video_loopback</string> + <key>CFBundleName</key> + <string>video_loopback</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>NSCameraUsageDescription</key> + <string>Camera access needed for video calling</string> + <key>NSMicrophoneUsageDescription</key> + <string>Microphone access needed for video calling</string> +</dict> +</plist> diff --git a/third_party/libwebrtc/test/mac/run_test.mm b/third_party/libwebrtc/test/mac/run_test.mm new file mode 100644 index 0000000000..38c6c8f8c1 --- /dev/null +++ b/third_party/libwebrtc/test/mac/run_test.mm @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2013 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 <Cocoa/Cocoa.h> + +#include "test/run_test.h" + +// Converting a C++ function pointer to an Objective-C block. +typedef void(^TestBlock)(); +TestBlock functionToBlock(void(*function)()) { + return [^(void) { function(); } copy]; +} + +// Class calling the test function on the platform specific thread. +@interface TestRunner : NSObject { + BOOL running_; +} +- (void)runAllTests:(TestBlock)ignored; +- (BOOL)running; +@end + +@implementation TestRunner +- (id)init { + self = [super init]; + if (self) { + running_ = YES; + } + return self; +} + +- (void)runAllTests:(TestBlock)testBlock { + @autoreleasepool { + testBlock(); + running_ = NO; + } +} + +- (BOOL)running { + return running_; +} +@end + +namespace webrtc { +namespace test { + +void RunTest(void(*test)()) { + @autoreleasepool { + [NSApplication sharedApplication]; + + // Convert the function pointer to an Objective-C block and call on a + // separate thread, to avoid blocking the main thread. + TestRunner *testRunner = [[TestRunner alloc] init]; + TestBlock testBlock = functionToBlock(test); + [NSThread detachNewThreadSelector:@selector(runAllTests:) + toTarget:testRunner + withObject:testBlock]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + while ([testRunner running] && [runLoop runMode:NSDefaultRunLoopMode + beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]) + ; + } +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/mac/video_renderer_mac.h b/third_party/libwebrtc/test/mac/video_renderer_mac.h new file mode 100644 index 0000000000..8e629b0a49 --- /dev/null +++ b/third_party/libwebrtc/test/mac/video_renderer_mac.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 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_MAC_VIDEO_RENDERER_MAC_H_ +#define TEST_MAC_VIDEO_RENDERER_MAC_H_ + +#include "test/gl/gl_renderer.h" + +@class CocoaWindow; + +namespace webrtc { +namespace test { + +class MacRenderer : public GlRenderer { + public: + MacRenderer(); + virtual ~MacRenderer(); + + MacRenderer(const MacRenderer&) = delete; + MacRenderer& operator=(const MacRenderer&) = delete; + + bool Init(const char* window_title, int width, int height); + + // Implements GlRenderer. + void OnFrame(const VideoFrame& frame) override; + + private: + CocoaWindow* window_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_MAC_VIDEO_RENDERER_MAC_H_ diff --git a/third_party/libwebrtc/test/mac/video_renderer_mac.mm b/third_party/libwebrtc/test/mac/video_renderer_mac.mm new file mode 100644 index 0000000000..7103375383 --- /dev/null +++ b/third_party/libwebrtc/test/mac/video_renderer_mac.mm @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2013 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/mac/video_renderer_mac.h" + +#import <Cocoa/Cocoa.h> + +// Creates a Cocoa Window with an OpenGL context, used together with an OpenGL +// renderer. +@interface CocoaWindow : NSObject { + @private + NSWindow *window_; + NSOpenGLContext *context_; + NSString *title_; + int width_; + int height_; +} + +- (id)initWithTitle:(NSString *)title width:(int)width height:(int)height; +// 'createWindow' must be called on the main thread. +- (void)createWindow:(NSObject *)ignored; +- (void)makeCurrentContext; + +@end + +@implementation CocoaWindow + static NSInteger nextXOrigin_; + static NSInteger nextYOrigin_; + +- (id)initWithTitle:(NSString *)title width:(int)width height:(int)height { + if (self = [super init]) { + title_ = title; + width_ = width; + height_ = height; + } + return self; +} + +- (void)createWindow:(NSObject *)ignored { + NSInteger xOrigin = nextXOrigin_; + NSRect screenFrame = [[NSScreen mainScreen] frame]; + if (nextXOrigin_ + width_ < screenFrame.size.width) { + nextXOrigin_ += width_; + } else { + xOrigin = 0; + nextXOrigin_ = 0; + nextYOrigin_ += height_; + } + if (nextYOrigin_ + height_ > screenFrame.size.height) { + xOrigin = 0; + nextXOrigin_ = 0; + nextYOrigin_ = 0; + } + NSInteger yOrigin = nextYOrigin_; + NSRect windowFrame = NSMakeRect(xOrigin, yOrigin, width_, height_); + window_ = [[NSWindow alloc] initWithContentRect:windowFrame + styleMask:NSWindowStyleMaskTitled + backing:NSBackingStoreBuffered + defer:NO]; + + NSRect viewFrame = NSMakeRect(0, 0, width_, height_); + NSOpenGLView *view = [[NSOpenGLView alloc] initWithFrame:viewFrame pixelFormat:nil]; + context_ = [view openGLContext]; + + [[window_ contentView] addSubview:view]; + [window_ setTitle:title_]; + [window_ makeKeyAndOrderFront:NSApp]; +} + +- (void)makeCurrentContext { + [context_ makeCurrentContext]; +} + +@end + +namespace webrtc { +namespace test { + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + MacRenderer* renderer = new MacRenderer(); + if (!renderer->Init(window_title, width, height)) { + delete renderer; + return NULL; + } + return renderer; +} + +MacRenderer::MacRenderer() + : window_(NULL) {} + +MacRenderer::~MacRenderer() { + GlRenderer::Destroy(); +} + +bool MacRenderer::Init(const char* window_title, int width, int height) { + window_ = [[CocoaWindow alloc] + initWithTitle:[NSString stringWithUTF8String:window_title] + width:width + height:height]; + if (!window_) + return false; + [window_ performSelectorOnMainThread:@selector(createWindow:) + withObject:nil + waitUntilDone:YES]; + + [window_ makeCurrentContext]; + GlRenderer::Init(); + GlRenderer::ResizeViewport(width, height); + return true; +} + +void MacRenderer::OnFrame(const VideoFrame& frame) { + [window_ makeCurrentContext]; + GlRenderer::OnFrame(frame); +} + +} // test +} // webrtc diff --git a/third_party/libwebrtc/test/mac_capturer.h b/third_party/libwebrtc/test/mac_capturer.h new file mode 100644 index 0000000000..3d7ee77b45 --- /dev/null +++ b/third_party/libwebrtc/test/mac_capturer.h @@ -0,0 +1,50 @@ +/* + * 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_MAC_CAPTURER_H_ +#define TEST_MAC_CAPTURER_H_ + +#include <memory> +#include <vector> + +#include "api/media_stream_interface.h" +#include "api/scoped_refptr.h" +#include "modules/video_capture/video_capture.h" +#include "rtc_base/thread.h" +#include "test/test_video_capturer.h" + +namespace webrtc { +namespace test { + +class MacCapturer : public TestVideoCapturer, + public rtc::VideoSinkInterface<VideoFrame> { + public: + static MacCapturer* Create(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index); + ~MacCapturer() override; + + void OnFrame(const VideoFrame& frame) override; + + private: + MacCapturer(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index); + void Destroy(); + + void* capturer_; + void* adapter_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_MAC_CAPTURER_H_ diff --git a/third_party/libwebrtc/test/mac_capturer.mm b/third_party/libwebrtc/test/mac_capturer.mm new file mode 100644 index 0000000000..da8e9b76b6 --- /dev/null +++ b/third_party/libwebrtc/test/mac_capturer.mm @@ -0,0 +1,107 @@ +/* + * 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/mac_capturer.h" + +#import "sdk/objc/base/RTCVideoCapturer.h" +#import "sdk/objc/components/capturer/RTCCameraVideoCapturer.h" +#import "sdk/objc/native/api/video_capturer.h" +#import "sdk/objc/native/src/objc_frame_buffer.h" + +@interface RTCTestVideoSourceAdapter : NSObject <RTC_OBJC_TYPE (RTCVideoCapturerDelegate)> +@property(nonatomic) webrtc::test::MacCapturer *capturer; +@end + +@implementation RTCTestVideoSourceAdapter +@synthesize capturer = _capturer; + +- (void)capturer:(RTC_OBJC_TYPE(RTCVideoCapturer) *)capturer + didCaptureVideoFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame { + const int64_t timestamp_us = frame.timeStampNs / rtc::kNumNanosecsPerMicrosec; + rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer = + rtc::make_ref_counted<webrtc::ObjCFrameBuffer>(frame.buffer); + _capturer->OnFrame(webrtc::VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_rotation(webrtc::kVideoRotation_0) + .set_timestamp_us(timestamp_us) + .build()); +} + +@end + +namespace { + +AVCaptureDeviceFormat *SelectClosestFormat(AVCaptureDevice *device, size_t width, size_t height) { + NSArray<AVCaptureDeviceFormat *> *formats = + [RTC_OBJC_TYPE(RTCCameraVideoCapturer) supportedFormatsForDevice:device]; + AVCaptureDeviceFormat *selectedFormat = nil; + int currentDiff = INT_MAX; + for (AVCaptureDeviceFormat *format in formats) { + CMVideoDimensions dimension = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + int diff = + std::abs((int64_t)width - dimension.width) + std::abs((int64_t)height - dimension.height); + if (diff < currentDiff) { + selectedFormat = format; + currentDiff = diff; + } + } + return selectedFormat; +} + +} // namespace + +namespace webrtc { +namespace test { + +MacCapturer::MacCapturer(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index) { + RTCTestVideoSourceAdapter *adapter = [[RTCTestVideoSourceAdapter alloc] init]; + adapter_ = (__bridge_retained void *)adapter; + adapter.capturer = this; + + RTC_OBJC_TYPE(RTCCameraVideoCapturer) *capturer = + [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) alloc] initWithDelegate:adapter]; + capturer_ = (__bridge_retained void *)capturer; + + AVCaptureDevice *device = + [[RTC_OBJC_TYPE(RTCCameraVideoCapturer) captureDevices] objectAtIndex:capture_device_index]; + AVCaptureDeviceFormat *format = SelectClosestFormat(device, width, height); + [capturer startCaptureWithDevice:device format:format fps:target_fps]; +} + +MacCapturer *MacCapturer::Create(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index) { + return new MacCapturer(width, height, target_fps, capture_device_index); +} + +void MacCapturer::Destroy() { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" + RTCTestVideoSourceAdapter *adapter = (__bridge_transfer RTCTestVideoSourceAdapter *)adapter_; + RTC_OBJC_TYPE(RTCCameraVideoCapturer) *capturer = + (__bridge_transfer RTC_OBJC_TYPE(RTCCameraVideoCapturer) *)capturer_; + [capturer stopCapture]; +#pragma clang diagnostic pop +} + +MacCapturer::~MacCapturer() { + Destroy(); +} + +void MacCapturer::OnFrame(const VideoFrame &frame) { + TestVideoCapturer::OnFrame(frame); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/mappable_native_buffer.cc b/third_party/libwebrtc/test/mappable_native_buffer.cc new file mode 100644 index 0000000000..1b171e604b --- /dev/null +++ b/third_party/libwebrtc/test/mappable_native_buffer.cc @@ -0,0 +1,185 @@ +/* + * 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/mappable_native_buffer.h" + +#include "absl/algorithm/container.h" +#include "api/video/i420_buffer.h" +#include "api/video/nv12_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_rotation.h" +#include "common_video/include/video_frame_buffer.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +namespace { + +class NV12BufferWithDidConvertToI420 : public NV12Buffer { + public: + NV12BufferWithDidConvertToI420(int width, int height) + : NV12Buffer(width, height), did_convert_to_i420_(false) {} + + bool did_convert_to_i420() const { return did_convert_to_i420_; } + + rtc::scoped_refptr<I420BufferInterface> ToI420() override { + did_convert_to_i420_ = true; + return NV12Buffer::ToI420(); + } + + private: + bool did_convert_to_i420_; +}; + +} // namespace + +VideoFrame CreateMappableNativeFrame(int64_t ntp_time_ms, + VideoFrameBuffer::Type mappable_type, + int width, + int height) { + VideoFrame frame = + VideoFrame::Builder() + .set_video_frame_buffer(rtc::make_ref_counted<MappableNativeBuffer>( + mappable_type, width, height)) + .set_timestamp_rtp(99) + .set_timestamp_ms(99) + .set_rotation(kVideoRotation_0) + .build(); + frame.set_ntp_time_ms(ntp_time_ms); + return frame; +} + +rtc::scoped_refptr<MappableNativeBuffer> GetMappableNativeBufferFromVideoFrame( + const VideoFrame& frame) { + return rtc::scoped_refptr<MappableNativeBuffer>( + static_cast<MappableNativeBuffer*>(frame.video_frame_buffer().get())); +} + +MappableNativeBuffer::ScaledBuffer::ScaledBuffer( + rtc::scoped_refptr<MappableNativeBuffer> parent, + int width, + int height) + : parent_(std::move(parent)), width_(width), height_(height) {} + +MappableNativeBuffer::ScaledBuffer::~ScaledBuffer() {} + +rtc::scoped_refptr<VideoFrameBuffer> +MappableNativeBuffer::ScaledBuffer::CropAndScale(int offset_x, + int offset_y, + int crop_width, + int crop_height, + int scaled_width, + int scaled_height) { + return rtc::make_ref_counted<ScaledBuffer>(parent_, scaled_width, + scaled_height); +} + +rtc::scoped_refptr<I420BufferInterface> +MappableNativeBuffer::ScaledBuffer::ToI420() { + return parent_->GetOrCreateMappedBuffer(width_, height_)->ToI420(); +} + +rtc::scoped_refptr<VideoFrameBuffer> +MappableNativeBuffer::ScaledBuffer::GetMappedFrameBuffer( + rtc::ArrayView<VideoFrameBuffer::Type> types) { + if (absl::c_find(types, parent_->mappable_type_) == types.end()) + return nullptr; + return parent_->GetOrCreateMappedBuffer(width_, height_); +} + +MappableNativeBuffer::MappableNativeBuffer(VideoFrameBuffer::Type mappable_type, + int width, + int height) + : mappable_type_(mappable_type), width_(width), height_(height) { + RTC_DCHECK(mappable_type_ == VideoFrameBuffer::Type::kI420 || + mappable_type_ == VideoFrameBuffer::Type::kNV12); +} + +MappableNativeBuffer::~MappableNativeBuffer() {} + +rtc::scoped_refptr<VideoFrameBuffer> MappableNativeBuffer::CropAndScale( + int offset_x, + int offset_y, + int crop_width, + int crop_height, + int scaled_width, + int scaled_height) { + return FullSizeBuffer()->CropAndScale( + offset_x, offset_y, crop_width, crop_height, scaled_width, scaled_height); +} + +rtc::scoped_refptr<I420BufferInterface> MappableNativeBuffer::ToI420() { + return FullSizeBuffer()->ToI420(); +} + +rtc::scoped_refptr<VideoFrameBuffer> MappableNativeBuffer::GetMappedFrameBuffer( + rtc::ArrayView<VideoFrameBuffer::Type> types) { + return FullSizeBuffer()->GetMappedFrameBuffer(types); +} + +std::vector<rtc::scoped_refptr<VideoFrameBuffer>> +MappableNativeBuffer::GetMappedFramedBuffers() const { + MutexLock lock(&lock_); + return mapped_buffers_; +} + +bool MappableNativeBuffer::DidConvertToI420() const { + if (mappable_type_ != VideoFrameBuffer::Type::kNV12) + return false; + MutexLock lock(&lock_); + for (auto& mapped_buffer : mapped_buffers_) { + if (static_cast<NV12BufferWithDidConvertToI420*>(mapped_buffer.get()) + ->did_convert_to_i420()) { + return true; + } + } + return false; +} + +rtc::scoped_refptr<MappableNativeBuffer::ScaledBuffer> +MappableNativeBuffer::FullSizeBuffer() { + return rtc::make_ref_counted<ScaledBuffer>( + rtc::scoped_refptr<MappableNativeBuffer>(this), width_, height_); +} + +rtc::scoped_refptr<VideoFrameBuffer> +MappableNativeBuffer::GetOrCreateMappedBuffer(int width, int height) { + MutexLock lock(&lock_); + for (auto& mapped_buffer : mapped_buffers_) { + if (mapped_buffer->width() == width && mapped_buffer->height() == height) { + return mapped_buffer; + } + } + rtc::scoped_refptr<VideoFrameBuffer> mapped_buffer; + switch (mappable_type_) { + case VideoFrameBuffer::Type::kI420: { + rtc::scoped_refptr<I420Buffer> i420_buffer = + I420Buffer::Create(width, height); + I420Buffer::SetBlack(i420_buffer.get()); + mapped_buffer = i420_buffer; + break; + } + case VideoFrameBuffer::Type::kNV12: { + auto nv12_buffer = + rtc::make_ref_counted<NV12BufferWithDidConvertToI420>(width, height); + nv12_buffer->InitializeData(); + mapped_buffer = std::move(nv12_buffer); + break; + } + default: + RTC_DCHECK_NOTREACHED(); + } + mapped_buffers_.push_back(mapped_buffer); + return mapped_buffer; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/mappable_native_buffer.h b/third_party/libwebrtc/test/mappable_native_buffer.h new file mode 100644 index 0000000000..08f155e07f --- /dev/null +++ b/third_party/libwebrtc/test/mappable_native_buffer.h @@ -0,0 +1,122 @@ +/* + * 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_MAPPABLE_NATIVE_BUFFER_H_ +#define TEST_MAPPABLE_NATIVE_BUFFER_H_ + +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/video/video_frame.h" +#include "common_video/include/video_frame_buffer.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace test { + +class MappableNativeBuffer; + +VideoFrame CreateMappableNativeFrame(int64_t ntp_time_ms, + VideoFrameBuffer::Type mappable_type, + int width, + int height); + +rtc::scoped_refptr<MappableNativeBuffer> GetMappableNativeBufferFromVideoFrame( + const VideoFrame& frame); + +// A for-testing native buffer that is scalable and mappable. The contents of +// the buffer is black and the pixels are created upon mapping. Mapped buffers +// are stored inside MappableNativeBuffer, allowing tests to verify which +// resolutions were mapped, e.g. when passing them in to an encoder or other +// modules. +class MappableNativeBuffer : public VideoFrameBuffer { + public: + // If `allow_i420_conversion` is false, calling ToI420() on a non-I420 buffer + // will DCHECK-crash. Used to ensure zero-copy in tests. + MappableNativeBuffer(VideoFrameBuffer::Type mappable_type, + int width, + int height); + ~MappableNativeBuffer() override; + + VideoFrameBuffer::Type mappable_type() const { return mappable_type_; } + + VideoFrameBuffer::Type type() const override { return Type::kNative; } + int width() const override { return width_; } + int height() const override { return height_; } + + rtc::scoped_refptr<VideoFrameBuffer> CropAndScale(int offset_x, + int offset_y, + int crop_width, + int crop_height, + int scaled_width, + int scaled_height) override; + + rtc::scoped_refptr<I420BufferInterface> ToI420() override; + rtc::scoped_refptr<VideoFrameBuffer> GetMappedFrameBuffer( + rtc::ArrayView<VideoFrameBuffer::Type> types) override; + + // Gets all the buffers that have been mapped so far, including mappings of + // cropped and scaled buffers. + std::vector<rtc::scoped_refptr<VideoFrameBuffer>> GetMappedFramedBuffers() + const; + bool DidConvertToI420() const; + + private: + friend class rtc::RefCountedObject<MappableNativeBuffer>; + + class ScaledBuffer : public VideoFrameBuffer { + public: + ScaledBuffer(rtc::scoped_refptr<MappableNativeBuffer> parent, + int width, + int height); + ~ScaledBuffer() override; + + VideoFrameBuffer::Type type() const override { return Type::kNative; } + int width() const override { return width_; } + int height() const override { return height_; } + + rtc::scoped_refptr<VideoFrameBuffer> CropAndScale( + int offset_x, + int offset_y, + int crop_width, + int crop_height, + int scaled_width, + int scaled_height) override; + + rtc::scoped_refptr<I420BufferInterface> ToI420() override; + rtc::scoped_refptr<VideoFrameBuffer> GetMappedFrameBuffer( + rtc::ArrayView<VideoFrameBuffer::Type> types) override; + + private: + friend class rtc::RefCountedObject<ScaledBuffer>; + + const rtc::scoped_refptr<MappableNativeBuffer> parent_; + const int width_; + const int height_; + }; + + rtc::scoped_refptr<ScaledBuffer> FullSizeBuffer(); + rtc::scoped_refptr<VideoFrameBuffer> GetOrCreateMappedBuffer(int width, + int height); + + const VideoFrameBuffer::Type mappable_type_; + const int width_; + const int height_; + mutable Mutex lock_; + std::vector<rtc::scoped_refptr<VideoFrameBuffer>> mapped_buffers_ + RTC_GUARDED_BY(&lock_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_MAPPABLE_NATIVE_BUFFER_H_ diff --git a/third_party/libwebrtc/test/mock_audio_decoder.cc b/third_party/libwebrtc/test/mock_audio_decoder.cc new file mode 100644 index 0000000000..5af9f370cd --- /dev/null +++ b/third_party/libwebrtc/test/mock_audio_decoder.cc @@ -0,0 +1,20 @@ +/* + * 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 "test/mock_audio_decoder.h" + +namespace webrtc { + +MockAudioDecoder::MockAudioDecoder() = default; +MockAudioDecoder::~MockAudioDecoder() { + Die(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/mock_audio_decoder.h b/third_party/libwebrtc/test/mock_audio_decoder.h new file mode 100644 index 0000000000..8f44bf891d --- /dev/null +++ b/third_party/libwebrtc/test/mock_audio_decoder.h @@ -0,0 +1,38 @@ +/* + * 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_MOCK_AUDIO_DECODER_H_ +#define TEST_MOCK_AUDIO_DECODER_H_ + +#include "api/audio_codecs/audio_decoder.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockAudioDecoder : public AudioDecoder { + public: + MockAudioDecoder(); + ~MockAudioDecoder(); + MOCK_METHOD(void, Die, ()); + MOCK_METHOD(int, + DecodeInternal, + (const uint8_t*, size_t, int, int16_t*, SpeechType*), + (override)); + MOCK_METHOD(bool, HasDecodePlc, (), (const, override)); + MOCK_METHOD(size_t, DecodePlc, (size_t, int16_t*), (override)); + MOCK_METHOD(void, Reset, (), (override)); + MOCK_METHOD(int, ErrorCode, (), (override)); + MOCK_METHOD(int, PacketDuration, (const uint8_t*, size_t), (const, override)); + MOCK_METHOD(size_t, Channels, (), (const, override)); + MOCK_METHOD(int, SampleRateHz, (), (const, override)); +}; + +} // namespace webrtc +#endif // TEST_MOCK_AUDIO_DECODER_H_ diff --git a/third_party/libwebrtc/test/mock_audio_decoder_factory.h b/third_party/libwebrtc/test/mock_audio_decoder_factory.h new file mode 100644 index 0000000000..425ea38f9c --- /dev/null +++ b/third_party/libwebrtc/test/mock_audio_decoder_factory.h @@ -0,0 +1,92 @@ +/* + * 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_MOCK_AUDIO_DECODER_FACTORY_H_ +#define TEST_MOCK_AUDIO_DECODER_FACTORY_H_ + +#include <memory> +#include <vector> + +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/make_ref_counted.h" +#include "api/scoped_refptr.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockAudioDecoderFactory : public AudioDecoderFactory { + public: + MOCK_METHOD(std::vector<AudioCodecSpec>, + GetSupportedDecoders, + (), + (override)); + MOCK_METHOD(bool, IsSupportedDecoder, (const SdpAudioFormat&), (override)); + std::unique_ptr<AudioDecoder> MakeAudioDecoder( + const SdpAudioFormat& format, + absl::optional<AudioCodecPairId> codec_pair_id) override { + std::unique_ptr<AudioDecoder> return_value; + MakeAudioDecoderMock(format, codec_pair_id, &return_value); + return return_value; + } + MOCK_METHOD(void, + MakeAudioDecoderMock, + (const SdpAudioFormat& format, + absl::optional<AudioCodecPairId> codec_pair_id, + std::unique_ptr<AudioDecoder>*)); + + // Creates a MockAudioDecoderFactory with no formats and that may not be + // invoked to create a codec - useful for initializing a voice engine, for + // example. + static rtc::scoped_refptr<webrtc::MockAudioDecoderFactory> + CreateUnusedFactory() { + using ::testing::_; + using ::testing::AnyNumber; + using ::testing::Return; + + rtc::scoped_refptr<webrtc::MockAudioDecoderFactory> factory = + rtc::make_ref_counted<webrtc::MockAudioDecoderFactory>(); + ON_CALL(*factory.get(), GetSupportedDecoders()) + .WillByDefault(Return(std::vector<webrtc::AudioCodecSpec>())); + EXPECT_CALL(*factory.get(), GetSupportedDecoders()).Times(AnyNumber()); + ON_CALL(*factory, IsSupportedDecoder(_)).WillByDefault(Return(false)); + EXPECT_CALL(*factory, IsSupportedDecoder(_)).Times(AnyNumber()); + EXPECT_CALL(*factory.get(), MakeAudioDecoderMock(_, _, _)).Times(0); + return factory; + } + + // Creates a MockAudioDecoderFactory with no formats that may be invoked to + // create a codec any number of times. It will, though, return nullptr on each + // call, since it supports no codecs. + static rtc::scoped_refptr<webrtc::MockAudioDecoderFactory> + CreateEmptyFactory() { + using ::testing::_; + using ::testing::AnyNumber; + using ::testing::Return; + using ::testing::SetArgPointee; + + rtc::scoped_refptr<webrtc::MockAudioDecoderFactory> factory = + rtc::make_ref_counted<webrtc::MockAudioDecoderFactory>(); + ON_CALL(*factory.get(), GetSupportedDecoders()) + .WillByDefault(Return(std::vector<webrtc::AudioCodecSpec>())); + EXPECT_CALL(*factory.get(), GetSupportedDecoders()).Times(AnyNumber()); + ON_CALL(*factory, IsSupportedDecoder(_)).WillByDefault(Return(false)); + EXPECT_CALL(*factory, IsSupportedDecoder(_)).Times(AnyNumber()); + ON_CALL(*factory.get(), MakeAudioDecoderMock(_, _, _)) + .WillByDefault(SetArgPointee<2>(nullptr)); + EXPECT_CALL(*factory.get(), MakeAudioDecoderMock(_, _, _)) + .Times(AnyNumber()); + return factory; + } +}; + +} // namespace webrtc + +#endif // TEST_MOCK_AUDIO_DECODER_FACTORY_H_ diff --git a/third_party/libwebrtc/test/mock_audio_encoder.cc b/third_party/libwebrtc/test/mock_audio_encoder.cc new file mode 100644 index 0000000000..36615111a5 --- /dev/null +++ b/third_party/libwebrtc/test/mock_audio_encoder.cc @@ -0,0 +1,57 @@ +/* + * 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/mock_audio_encoder.h" + +namespace webrtc { + +MockAudioEncoder::MockAudioEncoder() = default; +MockAudioEncoder::~MockAudioEncoder() = default; + +MockAudioEncoder::FakeEncoding::FakeEncoding( + const AudioEncoder::EncodedInfo& info) + : info_(info) {} + +MockAudioEncoder::FakeEncoding::FakeEncoding(size_t encoded_bytes) { + info_.encoded_bytes = encoded_bytes; +} + +AudioEncoder::EncodedInfo MockAudioEncoder::FakeEncoding::operator()( + uint32_t timestamp, + rtc::ArrayView<const int16_t> audio, + rtc::Buffer* encoded) { + encoded->SetSize(encoded->size() + info_.encoded_bytes); + return info_; +} + +MockAudioEncoder::CopyEncoding::~CopyEncoding() = default; + +MockAudioEncoder::CopyEncoding::CopyEncoding( + AudioEncoder::EncodedInfo info, + rtc::ArrayView<const uint8_t> payload) + : info_(info), payload_(payload) {} + +MockAudioEncoder::CopyEncoding::CopyEncoding( + rtc::ArrayView<const uint8_t> payload) + : payload_(payload) { + info_.encoded_bytes = payload_.size(); +} + +AudioEncoder::EncodedInfo MockAudioEncoder::CopyEncoding::operator()( + uint32_t timestamp, + rtc::ArrayView<const int16_t> audio, + rtc::Buffer* encoded) { + RTC_CHECK(encoded); + RTC_CHECK_LE(info_.encoded_bytes, payload_.size()); + encoded->AppendData(payload_.data(), info_.encoded_bytes); + return info_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/mock_audio_encoder.h b/third_party/libwebrtc/test/mock_audio_encoder.h new file mode 100644 index 0000000000..1f4510e885 --- /dev/null +++ b/third_party/libwebrtc/test/mock_audio_encoder.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2014 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_MOCK_AUDIO_ENCODER_H_ +#define TEST_MOCK_AUDIO_ENCODER_H_ + +#include <string> + +#include "api/array_view.h" +#include "api/audio_codecs/audio_encoder.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockAudioEncoder : public AudioEncoder { + public: + MockAudioEncoder(); + ~MockAudioEncoder(); + MOCK_METHOD(int, SampleRateHz, (), (const, override)); + MOCK_METHOD(size_t, NumChannels, (), (const, override)); + MOCK_METHOD(int, RtpTimestampRateHz, (), (const, override)); + MOCK_METHOD(size_t, Num10MsFramesInNextPacket, (), (const, override)); + MOCK_METHOD(size_t, Max10MsFramesInAPacket, (), (const, override)); + MOCK_METHOD(int, GetTargetBitrate, (), (const, override)); + MOCK_METHOD((absl::optional<std::pair<TimeDelta, TimeDelta>>), + GetFrameLengthRange, + (), + (const, override)); + + MOCK_METHOD(void, Reset, (), (override)); + MOCK_METHOD(bool, SetFec, (bool enable), (override)); + MOCK_METHOD(bool, SetDtx, (bool enable), (override)); + MOCK_METHOD(bool, SetApplication, (Application application), (override)); + MOCK_METHOD(void, SetMaxPlaybackRate, (int frequency_hz), (override)); + MOCK_METHOD(void, + OnReceivedUplinkBandwidth, + (int target_audio_bitrate_bps, + absl::optional<int64_t> probing_interval_ms), + (override)); + MOCK_METHOD(void, + OnReceivedUplinkPacketLossFraction, + (float uplink_packet_loss_fraction), + (override)); + MOCK_METHOD(void, + OnReceivedOverhead, + (size_t overhead_bytes_per_packet), + (override)); + + MOCK_METHOD(bool, + EnableAudioNetworkAdaptor, + (const std::string& config_string, RtcEventLog*), + (override)); + + // Note, we explicitly chose not to create a mock for the Encode method. + MOCK_METHOD(EncodedInfo, + EncodeImpl, + (uint32_t timestamp, + rtc::ArrayView<const int16_t> audio, + rtc::Buffer*), + (override)); + + class FakeEncoding { + public: + // Creates a functor that will return `info` and adjust the rtc::Buffer + // given as input to it, so it is info.encoded_bytes larger. + explicit FakeEncoding(const AudioEncoder::EncodedInfo& info); + + // Shorthand version of the constructor above, for when only setting + // encoded_bytes in the EncodedInfo object matters. + explicit FakeEncoding(size_t encoded_bytes); + + AudioEncoder::EncodedInfo operator()(uint32_t timestamp, + rtc::ArrayView<const int16_t> audio, + rtc::Buffer* encoded); + + private: + AudioEncoder::EncodedInfo info_; + }; + + class CopyEncoding { + public: + ~CopyEncoding(); + + // Creates a functor that will return `info` and append the data in the + // payload to the buffer given as input to it. Up to info.encoded_bytes are + // appended - make sure the payload is big enough! Since it uses an + // ArrayView, it _does not_ copy the payload. Make sure it doesn't fall out + // of scope! + CopyEncoding(AudioEncoder::EncodedInfo info, + rtc::ArrayView<const uint8_t> payload); + + // Shorthand version of the constructor above, for when you wish to append + // the whole payload and do not care about any EncodedInfo attribute other + // than encoded_bytes. + explicit CopyEncoding(rtc::ArrayView<const uint8_t> payload); + + AudioEncoder::EncodedInfo operator()(uint32_t timestamp, + rtc::ArrayView<const int16_t> audio, + rtc::Buffer* encoded); + + private: + AudioEncoder::EncodedInfo info_; + rtc::ArrayView<const uint8_t> payload_; + }; +}; + +} // namespace webrtc + +#endif // TEST_MOCK_AUDIO_ENCODER_H_ diff --git a/third_party/libwebrtc/test/mock_audio_encoder_factory.h b/third_party/libwebrtc/test/mock_audio_encoder_factory.h new file mode 100644 index 0000000000..eaa5b8f17d --- /dev/null +++ b/third_party/libwebrtc/test/mock_audio_encoder_factory.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef TEST_MOCK_AUDIO_ENCODER_FACTORY_H_ +#define TEST_MOCK_AUDIO_ENCODER_FACTORY_H_ + +#include <memory> +#include <vector> + +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/make_ref_counted.h" +#include "api/scoped_refptr.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockAudioEncoderFactory + : public ::testing::NiceMock<AudioEncoderFactory> { + public: + MOCK_METHOD(std::vector<AudioCodecSpec>, + GetSupportedEncoders, + (), + (override)); + MOCK_METHOD(absl::optional<AudioCodecInfo>, + QueryAudioEncoder, + (const SdpAudioFormat& format), + (override)); + + std::unique_ptr<AudioEncoder> MakeAudioEncoder( + int payload_type, + const SdpAudioFormat& format, + absl::optional<AudioCodecPairId> codec_pair_id) override { + std::unique_ptr<AudioEncoder> return_value; + MakeAudioEncoderMock(payload_type, format, codec_pair_id, &return_value); + return return_value; + } + MOCK_METHOD(void, + MakeAudioEncoderMock, + (int payload_type, + const SdpAudioFormat& format, + absl::optional<AudioCodecPairId> codec_pair_id, + std::unique_ptr<AudioEncoder>*)); + + // Creates a MockAudioEncoderFactory with no formats and that may not be + // invoked to create a codec - useful for initializing a voice engine, for + // example. + static rtc::scoped_refptr<webrtc::MockAudioEncoderFactory> + CreateUnusedFactory() { + using ::testing::_; + using ::testing::AnyNumber; + using ::testing::Return; + + auto factory = rtc::make_ref_counted<webrtc::MockAudioEncoderFactory>(); + ON_CALL(*factory.get(), GetSupportedEncoders()) + .WillByDefault(Return(std::vector<webrtc::AudioCodecSpec>())); + ON_CALL(*factory.get(), QueryAudioEncoder(_)) + .WillByDefault(Return(absl::nullopt)); + + EXPECT_CALL(*factory.get(), GetSupportedEncoders()).Times(AnyNumber()); + EXPECT_CALL(*factory.get(), QueryAudioEncoder(_)).Times(AnyNumber()); + EXPECT_CALL(*factory.get(), MakeAudioEncoderMock(_, _, _, _)).Times(0); + return factory; + } + + // Creates a MockAudioEncoderFactory with no formats that may be invoked to + // create a codec any number of times. It will, though, return nullptr on each + // call, since it supports no codecs. + static rtc::scoped_refptr<webrtc::MockAudioEncoderFactory> + CreateEmptyFactory() { + using ::testing::_; + using ::testing::AnyNumber; + using ::testing::Return; + using ::testing::SetArgPointee; + + auto factory = rtc::make_ref_counted<webrtc::MockAudioEncoderFactory>(); + ON_CALL(*factory.get(), GetSupportedEncoders()) + .WillByDefault(Return(std::vector<webrtc::AudioCodecSpec>())); + ON_CALL(*factory.get(), QueryAudioEncoder(_)) + .WillByDefault(Return(absl::nullopt)); + ON_CALL(*factory.get(), MakeAudioEncoderMock(_, _, _, _)) + .WillByDefault(SetArgPointee<3>(nullptr)); + + EXPECT_CALL(*factory.get(), GetSupportedEncoders()).Times(AnyNumber()); + EXPECT_CALL(*factory.get(), QueryAudioEncoder(_)).Times(AnyNumber()); + EXPECT_CALL(*factory.get(), MakeAudioEncoderMock(_, _, _, _)) + .Times(AnyNumber()); + return factory; + } +}; + +} // namespace webrtc + +#endif // TEST_MOCK_AUDIO_ENCODER_FACTORY_H_ diff --git a/third_party/libwebrtc/test/mock_frame_transformer.h b/third_party/libwebrtc/test/mock_frame_transformer.h new file mode 100644 index 0000000000..617cda8a43 --- /dev/null +++ b/third_party/libwebrtc/test/mock_frame_transformer.h @@ -0,0 +1,45 @@ +/* + * 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_MOCK_FRAME_TRANSFORMER_H_ +#define TEST_MOCK_FRAME_TRANSFORMER_H_ + +#include <memory> +#include <vector> + +#include "api/frame_transformer_interface.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockFrameTransformer : public FrameTransformerInterface { + public: + MOCK_METHOD(void, + Transform, + (std::unique_ptr<TransformableFrameInterface>), + (override)); + MOCK_METHOD(void, + RegisterTransformedFrameCallback, + (rtc::scoped_refptr<TransformedFrameCallback>), + (override)); + MOCK_METHOD(void, + RegisterTransformedFrameSinkCallback, + (rtc::scoped_refptr<TransformedFrameCallback>, uint32_t), + (override)); + MOCK_METHOD(void, UnregisterTransformedFrameCallback, (), (override)); + MOCK_METHOD(void, + UnregisterTransformedFrameSinkCallback, + (uint32_t), + (override)); +}; + +} // namespace webrtc + +#endif // TEST_MOCK_FRAME_TRANSFORMER_H_ diff --git a/third_party/libwebrtc/test/mock_transformable_frame.h b/third_party/libwebrtc/test/mock_transformable_frame.h new file mode 100644 index 0000000000..039013f218 --- /dev/null +++ b/third_party/libwebrtc/test/mock_transformable_frame.h @@ -0,0 +1,30 @@ +/* + * 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_MOCK_TRANSFORMABLE_FRAME_H_ +#define TEST_MOCK_TRANSFORMABLE_FRAME_H_ + +#include "api/frame_transformer_interface.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockTransformableFrame : public TransformableFrameInterface { + public: + MOCK_METHOD(rtc::ArrayView<const uint8_t>, GetData, (), (const, override)); + MOCK_METHOD(void, SetData, (rtc::ArrayView<const uint8_t>), (override)); + MOCK_METHOD(uint8_t, GetPayloadType, (), (const, override)); + MOCK_METHOD(uint32_t, GetSsrc, (), (const, override)); + MOCK_METHOD(uint32_t, GetTimestamp, (), (const, override)); +}; + +} // namespace webrtc + +#endif // TEST_MOCK_TRANSFORMABLE_FRAME_H_ diff --git a/third_party/libwebrtc/test/mock_transport.cc b/third_party/libwebrtc/test/mock_transport.cc new file mode 100644 index 0000000000..3878b3bdbe --- /dev/null +++ b/third_party/libwebrtc/test/mock_transport.cc @@ -0,0 +1,18 @@ +/* + * 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/mock_transport.h" + +namespace webrtc { + +MockTransport::MockTransport() = default; +MockTransport::~MockTransport() = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/mock_transport.h b/third_party/libwebrtc/test/mock_transport.h new file mode 100644 index 0000000000..9c4dc4bf8d --- /dev/null +++ b/third_party/libwebrtc/test/mock_transport.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 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_MOCK_TRANSPORT_H_ +#define TEST_MOCK_TRANSPORT_H_ + +#include "api/call/transport.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockTransport : public Transport { + public: + MockTransport(); + ~MockTransport(); + + MOCK_METHOD(bool, + SendRtp, + (const uint8_t*, size_t, const PacketOptions&), + (override)); + MOCK_METHOD(bool, SendRtcp, (const uint8_t*, size_t len), (override)); +}; + +} // namespace webrtc + +#endif // TEST_MOCK_TRANSPORT_H_ diff --git a/third_party/libwebrtc/test/network/BUILD.gn b/third_party/libwebrtc/test/network/BUILD.gn new file mode 100644 index 0000000000..20b17bc804 --- /dev/null +++ b/third_party/libwebrtc/test/network/BUILD.gn @@ -0,0 +1,199 @@ +# 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("../../webrtc.gni") + +rtc_library("emulated_network") { + visibility = [ + ":*", + "../../api:create_network_emulation_manager", + "../../api/test/network_emulation:create_cross_traffic", + ] + if (rtc_include_tests) { + visibility += [ + "../peer_scenario:*", + "../scenario:*", + ] + } + testonly = true + sources = [ + "cross_traffic.cc", + "cross_traffic.h", + "emulated_network_manager.cc", + "emulated_network_manager.h", + "emulated_turn_server.cc", + "emulated_turn_server.h", + "fake_network_socket_server.cc", + "fake_network_socket_server.h", + "network_emulation.cc", + "network_emulation.h", + "network_emulation_manager.cc", + "network_emulation_manager.h", + "traffic_route.cc", + "traffic_route.h", + ] + deps = [ + "../../api:array_view", + "../../api:field_trials_view", + "../../api:network_emulation_manager_api", + "../../api:packet_socket_factory", + "../../api:scoped_refptr", + "../../api:sequence_checker", + "../../api:simulated_network_api", + "../../api:time_controller", + "../../api/numerics", + "../../api/task_queue:pending_task_safety_flag", + "../../api/test/network_emulation", + "../../api/transport:stun_types", + "../../api/units:data_rate", + "../../api/units:data_size", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../call:simulated_network", + "../../p2p:p2p_server_utils", + "../../p2p:rtc_p2p", + "../../rtc_base:async_packet_socket", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:ip_address", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:network", + "../../rtc_base:network_constants", + "../../rtc_base:random", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_task_queue", + "../../rtc_base:safe_minmax", + "../../rtc_base:socket", + "../../rtc_base:socket_address", + "../../rtc_base:socket_server", + "../../rtc_base:stringutils", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../rtc_base/memory:always_valid_pointer", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/system:no_unique_address", + "../../rtc_base/task_utils:repeating_task", + "../../system_wrappers", + "../../test:scoped_key_value_config", + "../scenario:column_printer", + "../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("network_emulation_unittest") { + testonly = true + sources = [ "network_emulation_unittest.cc" ] + deps = [ + ":emulated_network", + "../:test_support", + "../../api:simulated_network_api", + "../../api/units:time_delta", + "../../call:simulated_network", + "../../rtc_base:gunit_helpers", + "../../rtc_base:logging", + "../../rtc_base:rtc_event", + "../../rtc_base:task_queue_for_test", + "../../rtc_base/synchronization:mutex", + ] +} + +if (rtc_include_tests && !build_with_chromium) { + rtc_library("network_emulation_pc_unittest") { + testonly = true + sources = [ "network_emulation_pc_unittest.cc" ] + deps = [ + ":emulated_network", + "../:test_support", + "../../api:callfactory_api", + "../../api:libjingle_peerconnection_api", + "../../api:scoped_refptr", + "../../api:simulated_network_api", + "../../api/rtc_event_log:rtc_event_log_factory", + "../../api/task_queue:default_task_queue_factory", + "../../api/transport:field_trial_based_config", + "../../call:simulated_network", + "../../media:rtc_audio_video", + "../../media:rtc_media_engine_defaults", + "../../modules/audio_device:audio_device_impl", + "../../p2p:rtc_p2p", + "../../pc:pc_test_utils", + "../../pc:peerconnection_wrapper", + "../../rtc_base:gunit_helpers", + "../../rtc_base:logging", + "../../rtc_base:rtc_event", + "../../rtc_base:task_queue_for_test", + ] + } +} + +rtc_library("cross_traffic_unittest") { + testonly = true + sources = [ "cross_traffic_unittest.cc" ] + deps = [ + ":emulated_network", + "../:test_support", + "../../api:network_emulation_manager_api", + "../../api:simulated_network_api", + "../../call:simulated_network", + "../../rtc_base:logging", + "../../rtc_base:network_constants", + "../../rtc_base:rtc_event", + "../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +if (rtc_include_tests) { + rtc_library("feedback_generator") { + testonly = true + sources = [ + "feedback_generator.cc", + "feedback_generator.h", + ] + deps = [ + ":emulated_network", + "../../api/transport:network_control", + "../../api/transport:test_feedback_generator_interface", + "../../call:simulated_network", + "../../rtc_base:checks", + "../time_controller", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] + } + + rtc_library("feedback_generator_unittest") { + testonly = true + sources = [ "feedback_generator_unittest.cc" ] + deps = [ + "../:test_support", + "../../api/transport:test_feedback_generator", + ] + } + + if (!build_with_chromium) { + rtc_library("network_emulation_unittests") { + testonly = true + deps = [ + ":cross_traffic_unittest", + ":feedback_generator_unittest", + ":network_emulation_pc_unittest", + ":network_emulation_unittest", + ] + } + } +} diff --git a/third_party/libwebrtc/test/network/OWNERS b/third_party/libwebrtc/test/network/OWNERS new file mode 100644 index 0000000000..b177c4eec5 --- /dev/null +++ b/third_party/libwebrtc/test/network/OWNERS @@ -0,0 +1 @@ +titovartem@webrtc.org diff --git a/third_party/libwebrtc/test/network/cross_traffic.cc b/third_party/libwebrtc/test/network/cross_traffic.cc new file mode 100644 index 0000000000..0a817a2d39 --- /dev/null +++ b/third_party/libwebrtc/test/network/cross_traffic.cc @@ -0,0 +1,322 @@ +/* + * 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/network/cross_traffic.h" + +#include <math.h> + +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "cross_traffic.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { +namespace test { + +RandomWalkCrossTraffic::RandomWalkCrossTraffic(RandomWalkConfig config, + CrossTrafficRoute* traffic_route) + : config_(config), + traffic_route_(traffic_route), + random_(config_.random_seed) { + sequence_checker_.Detach(); +} +RandomWalkCrossTraffic::~RandomWalkCrossTraffic() = default; + +void RandomWalkCrossTraffic::Process(Timestamp at_time) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (last_process_time_.IsMinusInfinity()) { + last_process_time_ = at_time; + } + TimeDelta delta = at_time - last_process_time_; + last_process_time_ = at_time; + + if (at_time - last_update_time_ >= config_.update_interval) { + intensity_ += random_.Gaussian(config_.bias, config_.variance) * + sqrt((at_time - last_update_time_).seconds<double>()); + intensity_ = rtc::SafeClamp(intensity_, 0.0, 1.0); + last_update_time_ = at_time; + } + pending_size_ += TrafficRate() * delta; + + if (pending_size_ >= config_.min_packet_size && + at_time >= last_send_time_ + config_.min_packet_interval) { + traffic_route_->SendPacket(pending_size_.bytes()); + pending_size_ = DataSize::Zero(); + last_send_time_ = at_time; + } +} + +TimeDelta RandomWalkCrossTraffic::GetProcessInterval() const { + return config_.min_packet_interval; +} + +DataRate RandomWalkCrossTraffic::TrafficRate() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return config_.peak_rate * intensity_; +} + +ColumnPrinter RandomWalkCrossTraffic::StatsPrinter() { + return ColumnPrinter::Lambda( + "random_walk_cross_traffic_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.0lf", TrafficRate().bps() / 8.0); + }, + 32); +} + +PulsedPeaksCrossTraffic::PulsedPeaksCrossTraffic( + PulsedPeaksConfig config, + CrossTrafficRoute* traffic_route) + : config_(config), traffic_route_(traffic_route) { + sequence_checker_.Detach(); +} +PulsedPeaksCrossTraffic::~PulsedPeaksCrossTraffic() = default; + +void PulsedPeaksCrossTraffic::Process(Timestamp at_time) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + TimeDelta time_since_toggle = at_time - last_update_time_; + if (time_since_toggle.IsInfinite() || + (sending_ && time_since_toggle >= config_.send_duration)) { + sending_ = false; + last_update_time_ = at_time; + } else if (!sending_ && time_since_toggle >= config_.hold_duration) { + sending_ = true; + last_update_time_ = at_time; + // Start sending period. + last_send_time_ = at_time; + } + + if (sending_) { + DataSize pending_size = config_.peak_rate * (at_time - last_send_time_); + + if (pending_size >= config_.min_packet_size && + at_time >= last_send_time_ + config_.min_packet_interval) { + traffic_route_->SendPacket(pending_size.bytes()); + last_send_time_ = at_time; + } + } +} + +TimeDelta PulsedPeaksCrossTraffic::GetProcessInterval() const { + return config_.min_packet_interval; +} + +DataRate PulsedPeaksCrossTraffic::TrafficRate() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return sending_ ? config_.peak_rate : DataRate::Zero(); +} + +ColumnPrinter PulsedPeaksCrossTraffic::StatsPrinter() { + return ColumnPrinter::Lambda( + "pulsed_peaks_cross_traffic_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.0lf", TrafficRate().bps() / 8.0); + }, + 32); +} + +TcpMessageRouteImpl::TcpMessageRouteImpl(Clock* clock, + TaskQueueBase* task_queue, + EmulatedRoute* send_route, + EmulatedRoute* ret_route) + : clock_(clock), + task_queue_(task_queue), + request_route_(send_route, + [this](TcpPacket packet, Timestamp) { + OnRequest(std::move(packet)); + }), + response_route_(ret_route, + [this](TcpPacket packet, Timestamp arrival_time) { + OnResponse(std::move(packet), arrival_time); + }) {} + +void TcpMessageRouteImpl::SendMessage(size_t size, + std::function<void()> on_received) { + task_queue_->PostTask( + [this, size, handler = std::move(on_received)] { + // If we are currently sending a message we won't reset the connection, + // we'll act as if the messages are sent in the same TCP stream. This is + // intended to simulate recreation of a TCP session for each message + // in the typical case while avoiding the complexity overhead of + // maintaining multiple virtual TCP sessions in parallel. + if (pending_.empty() && in_flight_.empty()) { + cwnd_ = 10; + ssthresh_ = INFINITY; + } + int64_t data_left = static_cast<int64_t>(size); + int64_t kMaxPacketSize = 1200; + int64_t kMinPacketSize = 4; + Message message{std::move(handler)}; + while (data_left > 0) { + int64_t packet_size = std::min(data_left, kMaxPacketSize); + int fragment_id = next_fragment_id_++; + pending_.push_back(MessageFragment{ + fragment_id, + static_cast<size_t>(std::max(kMinPacketSize, packet_size))}); + message.pending_fragment_ids.insert(fragment_id); + data_left -= packet_size; + } + messages_.emplace_back(message); + SendPackets(clock_->CurrentTime()); + }); +} + +void TcpMessageRouteImpl::OnRequest(TcpPacket packet_info) { + for (auto it = messages_.begin(); it != messages_.end(); ++it) { + if (it->pending_fragment_ids.count(packet_info.fragment.fragment_id) != 0) { + it->pending_fragment_ids.erase(packet_info.fragment.fragment_id); + if (it->pending_fragment_ids.empty()) { + it->handler(); + messages_.erase(it); + } + break; + } + } + const size_t kAckPacketSize = 20; + response_route_.SendPacket(kAckPacketSize, packet_info); +} + +void TcpMessageRouteImpl::OnResponse(TcpPacket packet_info, Timestamp at_time) { + auto it = in_flight_.find(packet_info.sequence_number); + if (it != in_flight_.end()) { + last_rtt_ = at_time - packet_info.send_time; + in_flight_.erase(it); + } + auto lost_end = in_flight_.lower_bound(packet_info.sequence_number); + for (auto lost_it = in_flight_.begin(); lost_it != lost_end; + lost_it = in_flight_.erase(lost_it)) { + pending_.push_front(lost_it->second.fragment); + } + + if (packet_info.sequence_number - last_acked_seq_num_ > 1) { + HandleLoss(at_time); + } else if (cwnd_ <= ssthresh_) { + cwnd_ += 1; + } else { + cwnd_ += 1.0f / cwnd_; + } + last_acked_seq_num_ = + std::max(packet_info.sequence_number, last_acked_seq_num_); + SendPackets(at_time); +} + +void TcpMessageRouteImpl::HandleLoss(Timestamp at_time) { + if (at_time - last_reduction_time_ < last_rtt_) + return; + last_reduction_time_ = at_time; + ssthresh_ = std::max(static_cast<int>(in_flight_.size() / 2), 2); + cwnd_ = ssthresh_; +} + +void TcpMessageRouteImpl::SendPackets(Timestamp at_time) { + const TimeDelta kPacketTimeout = TimeDelta::Seconds(1); + int cwnd = std::ceil(cwnd_); + int packets_to_send = std::max(cwnd - static_cast<int>(in_flight_.size()), 0); + while (packets_to_send-- > 0 && !pending_.empty()) { + auto seq_num = next_sequence_number_++; + TcpPacket send; + send.sequence_number = seq_num; + send.send_time = at_time; + send.fragment = pending_.front(); + pending_.pop_front(); + request_route_.SendPacket(send.fragment.size, send); + in_flight_.insert({seq_num, send}); + task_queue_->PostDelayedTask( + [this, seq_num] { + HandlePacketTimeout(seq_num, clock_->CurrentTime()); + }, + kPacketTimeout); + } +} + +void TcpMessageRouteImpl::HandlePacketTimeout(int seq_num, Timestamp at_time) { + auto lost = in_flight_.find(seq_num); + if (lost != in_flight_.end()) { + pending_.push_front(lost->second.fragment); + in_flight_.erase(lost); + HandleLoss(at_time); + SendPackets(at_time); + } +} + +FakeTcpCrossTraffic::FakeTcpCrossTraffic(FakeTcpConfig config, + EmulatedRoute* send_route, + EmulatedRoute* ret_route) + : conf_(config), route_(this, send_route, ret_route) {} + +TimeDelta FakeTcpCrossTraffic::GetProcessInterval() const { + return conf_.process_interval; +} + +void FakeTcpCrossTraffic::Process(Timestamp at_time) { + SendPackets(at_time); +} + +void FakeTcpCrossTraffic::OnRequest(int sequence_number, Timestamp at_time) { + const size_t kAckPacketSize = 20; + route_.SendResponse(kAckPacketSize, sequence_number); +} + +void FakeTcpCrossTraffic::OnResponse(int sequence_number, Timestamp at_time) { + ack_received_ = true; + auto it = in_flight_.find(sequence_number); + if (it != in_flight_.end()) { + last_rtt_ = at_time - in_flight_.at(sequence_number); + in_flight_.erase(sequence_number); + } + if (sequence_number - last_acked_seq_num_ > 1) { + HandleLoss(at_time); + } else if (cwnd_ <= ssthresh_) { + cwnd_ += 1; + } else { + cwnd_ += 1.0f / cwnd_; + } + last_acked_seq_num_ = std::max(sequence_number, last_acked_seq_num_); + SendPackets(at_time); +} + +void FakeTcpCrossTraffic::HandleLoss(Timestamp at_time) { + if (at_time - last_reduction_time_ < last_rtt_) + return; + last_reduction_time_ = at_time; + ssthresh_ = std::max(static_cast<int>(in_flight_.size() / 2), 2); + cwnd_ = ssthresh_; +} + +void FakeTcpCrossTraffic::SendPackets(Timestamp at_time) { + int cwnd = std::ceil(cwnd_); + int packets_to_send = std::max(cwnd - static_cast<int>(in_flight_.size()), 0); + bool timeouts = false; + for (auto it = in_flight_.begin(); it != in_flight_.end();) { + if (it->second < at_time - conf_.packet_timeout) { + it = in_flight_.erase(it); + timeouts = true; + } else { + ++it; + } + } + if (timeouts) + HandleLoss(at_time); + for (int i = 0; i < packets_to_send; ++i) { + if ((total_sent_ + conf_.packet_size) > conf_.send_limit) { + break; + } + in_flight_.insert({next_sequence_number_, at_time}); + route_.SendRequest(conf_.packet_size.bytes<size_t>(), + next_sequence_number_++); + total_sent_ += conf_.packet_size; + } +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/cross_traffic.h b/third_party/libwebrtc/test/network/cross_traffic.h new file mode 100644 index 0000000000..d21e942475 --- /dev/null +++ b/third_party/libwebrtc/test/network/cross_traffic.h @@ -0,0 +1,174 @@ +/* + * 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_NETWORK_CROSS_TRAFFIC_H_ +#define TEST_NETWORK_CROSS_TRAFFIC_H_ + +#include <algorithm> +#include <map> +#include <memory> + +#include "api/sequence_checker.h" +#include "api/test/network_emulation_manager.h" +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/random.h" +#include "test/network/network_emulation.h" +#include "test/scenario/column_printer.h" + +namespace webrtc { +namespace test { + +class RandomWalkCrossTraffic final : public CrossTrafficGenerator { + public: + RandomWalkCrossTraffic(RandomWalkConfig config, + CrossTrafficRoute* traffic_route); + ~RandomWalkCrossTraffic(); + + void Process(Timestamp at_time) override; + TimeDelta GetProcessInterval() const override; + DataRate TrafficRate() const; + ColumnPrinter StatsPrinter(); + + private: + SequenceChecker sequence_checker_; + const RandomWalkConfig config_; + CrossTrafficRoute* const traffic_route_ RTC_PT_GUARDED_BY(sequence_checker_); + webrtc::Random random_ RTC_GUARDED_BY(sequence_checker_); + + Timestamp last_process_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + Timestamp last_update_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + Timestamp last_send_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + double intensity_ RTC_GUARDED_BY(sequence_checker_) = 0; + DataSize pending_size_ RTC_GUARDED_BY(sequence_checker_) = DataSize::Zero(); +}; + +class PulsedPeaksCrossTraffic final : public CrossTrafficGenerator { + public: + PulsedPeaksCrossTraffic(PulsedPeaksConfig config, + CrossTrafficRoute* traffic_route); + ~PulsedPeaksCrossTraffic(); + + void Process(Timestamp at_time) override; + TimeDelta GetProcessInterval() const override; + DataRate TrafficRate() const; + ColumnPrinter StatsPrinter(); + + private: + SequenceChecker sequence_checker_; + const PulsedPeaksConfig config_; + CrossTrafficRoute* const traffic_route_ RTC_PT_GUARDED_BY(sequence_checker_); + + Timestamp last_update_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + Timestamp last_send_time_ RTC_GUARDED_BY(sequence_checker_) = + Timestamp::MinusInfinity(); + bool sending_ RTC_GUARDED_BY(sequence_checker_) = false; +}; + +class TcpMessageRouteImpl final : public TcpMessageRoute { + public: + TcpMessageRouteImpl(Clock* clock, + TaskQueueBase* task_queue, + EmulatedRoute* send_route, + EmulatedRoute* ret_route); + + // Sends a TCP message of the given `size` over the route, `on_received` is + // called when the message has been delivered. Note that the connection + // parameters are reset iff there's no currently pending message on the route. + void SendMessage(size_t size, std::function<void()> on_received) override; + + private: + // Represents a message sent over the route. When all fragments has been + // delivered, the message is considered delivered and the handler is + // triggered. This only happen once. + struct Message { + std::function<void()> handler; + std::set<int> pending_fragment_ids; + }; + // Represents a piece of a message that fit into a TCP packet. + struct MessageFragment { + int fragment_id; + size_t size; + }; + // Represents a packet sent on the wire. + struct TcpPacket { + int sequence_number; + Timestamp send_time = Timestamp::MinusInfinity(); + MessageFragment fragment; + }; + + void OnRequest(TcpPacket packet_info); + void OnResponse(TcpPacket packet_info, Timestamp at_time); + void HandleLoss(Timestamp at_time); + void SendPackets(Timestamp at_time); + void HandlePacketTimeout(int seq_num, Timestamp at_time); + + Clock* const clock_; + TaskQueueBase* const task_queue_; + FakePacketRoute<TcpPacket> request_route_; + FakePacketRoute<TcpPacket> response_route_; + + std::deque<MessageFragment> pending_; + std::map<int, TcpPacket> in_flight_; + std::list<Message> messages_; + + double cwnd_; + double ssthresh_; + + int last_acked_seq_num_ = 0; + int next_sequence_number_ = 0; + int next_fragment_id_ = 0; + Timestamp last_reduction_time_ = Timestamp::MinusInfinity(); + TimeDelta last_rtt_ = TimeDelta::Zero(); +}; + +class FakeTcpCrossTraffic + : public TwoWayFakeTrafficRoute<int, int>::TrafficHandlerInterface, + public CrossTrafficGenerator { + public: + FakeTcpCrossTraffic(FakeTcpConfig config, + EmulatedRoute* send_route, + EmulatedRoute* ret_route); + + TimeDelta GetProcessInterval() const override; + void Process(Timestamp at_time) override; + + void OnRequest(int sequence_number, Timestamp at_time) override; + void OnResponse(int sequence_number, Timestamp at_time) override; + + void HandleLoss(Timestamp at_time); + + void SendPackets(Timestamp at_time); + + private: + const FakeTcpConfig conf_; + TwoWayFakeTrafficRoute<int, int> route_; + + std::map<int, Timestamp> in_flight_; + double cwnd_ = 10; + double ssthresh_ = INFINITY; + bool ack_received_ = false; + int last_acked_seq_num_ = 0; + int next_sequence_number_ = 0; + Timestamp last_reduction_time_ = Timestamp::MinusInfinity(); + TimeDelta last_rtt_ = TimeDelta::Zero(); + DataSize total_sent_ = DataSize::Zero(); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_CROSS_TRAFFIC_H_ diff --git a/third_party/libwebrtc/test/network/cross_traffic_unittest.cc b/third_party/libwebrtc/test/network/cross_traffic_unittest.cc new file mode 100644 index 0000000000..36aff67bb2 --- /dev/null +++ b/third_party/libwebrtc/test/network/cross_traffic_unittest.cc @@ -0,0 +1,163 @@ +/* + * Copyright 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/network/cross_traffic.h" + +#include <atomic> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/simulated_network.h" +#include "call/simulated_network.h" +#include "rtc_base/event.h" +#include "rtc_base/logging.h" +#include "rtc_base/network_constants.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/network/network_emulation_manager.h" +#include "test/network/traffic_route.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace test { +namespace { + +constexpr uint32_t kTestIpAddress = 0xC0A80011; // 192.168.0.17 + +class CountingReceiver : public EmulatedNetworkReceiverInterface { + public: + void OnPacketReceived(EmulatedIpPacket packet) override { + packets_count_++; + total_packets_size_ += packet.size(); + } + + std::atomic<int> packets_count_{0}; + std::atomic<uint64_t> total_packets_size_{0}; +}; +struct TrafficCounterFixture { + SimulatedClock clock{0}; + CountingReceiver counter; + TaskQueueForTest task_queue_; + EmulatedEndpointImpl endpoint{EmulatedEndpointImpl::Options{ + /*id=*/1, + rtc::IPAddress(kTestIpAddress), + EmulatedEndpointConfig(), + EmulatedNetworkStatsGatheringMode::kDefault, + }, + /*is_enabled=*/true, &task_queue_, &clock}; +}; + +} // namespace + +TEST(CrossTrafficTest, TriggerPacketBurst) { + TrafficCounterFixture fixture; + CrossTrafficRouteImpl traffic(&fixture.clock, &fixture.counter, + &fixture.endpoint); + traffic.TriggerPacketBurst(100, 1000); + + EXPECT_EQ(fixture.counter.packets_count_, 100); + EXPECT_EQ(fixture.counter.total_packets_size_, 100 * 1000ul); +} + +TEST(CrossTrafficTest, PulsedPeaksCrossTraffic) { + TrafficCounterFixture fixture; + CrossTrafficRouteImpl traffic(&fixture.clock, &fixture.counter, + &fixture.endpoint); + + PulsedPeaksConfig config; + config.peak_rate = DataRate::KilobitsPerSec(1000); + config.min_packet_size = DataSize::Bytes(1); + config.min_packet_interval = TimeDelta::Millis(25); + config.send_duration = TimeDelta::Millis(500); + config.hold_duration = TimeDelta::Millis(250); + PulsedPeaksCrossTraffic pulsed_peaks(config, &traffic); + const auto kRunTime = TimeDelta::Seconds(1); + while (fixture.clock.TimeInMilliseconds() < kRunTime.ms()) { + pulsed_peaks.Process(Timestamp::Millis(fixture.clock.TimeInMilliseconds())); + fixture.clock.AdvanceTimeMilliseconds(1); + } + + RTC_LOG(LS_INFO) << fixture.counter.packets_count_ << " packets; " + << fixture.counter.total_packets_size_ << " bytes"; + // Using 50% duty cycle. + const auto kExpectedDataSent = kRunTime * config.peak_rate * 0.5; + EXPECT_NEAR(fixture.counter.total_packets_size_, kExpectedDataSent.bytes(), + kExpectedDataSent.bytes() * 0.1); +} + +TEST(CrossTrafficTest, RandomWalkCrossTraffic) { + TrafficCounterFixture fixture; + CrossTrafficRouteImpl traffic(&fixture.clock, &fixture.counter, + &fixture.endpoint); + + RandomWalkConfig config; + config.peak_rate = DataRate::KilobitsPerSec(1000); + config.min_packet_size = DataSize::Bytes(1); + config.min_packet_interval = TimeDelta::Millis(25); + config.update_interval = TimeDelta::Millis(500); + config.variance = 0.0; + config.bias = 1.0; + + RandomWalkCrossTraffic random_walk(config, &traffic); + const auto kRunTime = TimeDelta::Seconds(1); + while (fixture.clock.TimeInMilliseconds() < kRunTime.ms()) { + random_walk.Process(Timestamp::Millis(fixture.clock.TimeInMilliseconds())); + fixture.clock.AdvanceTimeMilliseconds(1); + } + + RTC_LOG(LS_INFO) << fixture.counter.packets_count_ << " packets; " + << fixture.counter.total_packets_size_ << " bytes"; + // Sending at peak rate since bias = 1. + const auto kExpectedDataSent = kRunTime * config.peak_rate; + EXPECT_NEAR(fixture.counter.total_packets_size_, kExpectedDataSent.bytes(), + kExpectedDataSent.bytes() * 0.1); +} + +TEST(TcpMessageRouteTest, DeliveredOnLossyNetwork) { + NetworkEmulationManagerImpl net(TimeMode::kSimulated, + EmulatedNetworkStatsGatheringMode::kDefault); + BuiltInNetworkBehaviorConfig send; + // 800 kbps means that the 100 kB message would be delivered in ca 1 second + // under ideal conditions and no overhead. + send.link_capacity_kbps = 100 * 8; + send.loss_percent = 50; + send.queue_delay_ms = 100; + send.delay_standard_deviation_ms = 20; + send.allow_reordering = true; + auto ret = send; + ret.loss_percent = 10; + + auto* tcp_route = + net.CreateTcpRoute(net.CreateRoute({net.CreateEmulatedNode(send)}), + net.CreateRoute({net.CreateEmulatedNode(ret)})); + int deliver_count = 0; + // 100 kB is more than what fits into a single packet. + constexpr size_t kMessageSize = 100000; + + tcp_route->SendMessage(kMessageSize, [&] { + RTC_LOG(LS_INFO) << "Received at " << ToString(net.Now()); + deliver_count++; + }); + + // If there was no loss, we would have delivered the message in ca 1 second, + // with 50% it should take much longer. + net.time_controller()->AdvanceTime(TimeDelta::Seconds(5)); + ASSERT_EQ(deliver_count, 0); + // But given enough time the messsage will be delivered, but only once. + net.time_controller()->AdvanceTime(TimeDelta::Seconds(60)); + EXPECT_EQ(deliver_count, 1); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/emulated_network_manager.cc b/third_party/libwebrtc/test/network/emulated_network_manager.cc new file mode 100644 index 0000000000..fa4037e5db --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_network_manager.cc @@ -0,0 +1,122 @@ +/* + * 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/network/emulated_network_manager.h" + +#include <memory> +#include <utility> + +#include "absl/memory/memory.h" +#include "p2p/base/basic_packet_socket_factory.h" +#include "test/network/fake_network_socket_server.h" + +namespace webrtc { +namespace test { + +EmulatedNetworkManager::EmulatedNetworkManager( + TimeController* time_controller, + TaskQueueForTest* task_queue, + EndpointsContainer* endpoints_container) + : task_queue_(task_queue), + endpoints_container_(endpoints_container), + sent_first_update_(false), + start_count_(0) { + auto socket_server = + std::make_unique<FakeNetworkSocketServer>(endpoints_container); + packet_socket_factory_ = + std::make_unique<rtc::BasicPacketSocketFactory>(socket_server.get()); + // Since we pass ownership of the socket server to `network_thread_`, we must + // arrange that it outlives `packet_socket_factory_` which refers to it. + network_thread_ = + time_controller->CreateThread("net_thread", std::move(socket_server)); +} + +void EmulatedNetworkManager::EnableEndpoint(EmulatedEndpointImpl* endpoint) { + RTC_CHECK(endpoints_container_->HasEndpoint(endpoint)) + << "No such interface: " << endpoint->GetPeerLocalAddress().ToString(); + network_thread_->PostTask([this, endpoint]() { + endpoint->Enable(); + UpdateNetworksOnce(); + }); +} + +void EmulatedNetworkManager::DisableEndpoint(EmulatedEndpointImpl* endpoint) { + RTC_CHECK(endpoints_container_->HasEndpoint(endpoint)) + << "No such interface: " << endpoint->GetPeerLocalAddress().ToString(); + network_thread_->PostTask([this, endpoint]() { + endpoint->Disable(); + UpdateNetworksOnce(); + }); +} + +// Network manager interface. All these methods are supposed to be called from +// the same thread. +void EmulatedNetworkManager::StartUpdating() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + + if (start_count_) { + // If network interfaces are already discovered and signal is sent, + // we should trigger network signal immediately for the new clients + // to start allocating ports. + if (sent_first_update_) + network_thread_->PostTask([this]() { MaybeSignalNetworksChanged(); }); + } else { + network_thread_->PostTask([this]() { UpdateNetworksOnce(); }); + } + ++start_count_; +} + +void EmulatedNetworkManager::StopUpdating() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + if (!start_count_) + return; + + --start_count_; + if (!start_count_) { + sent_first_update_ = false; + } +} + +void EmulatedNetworkManager::GetStats( + std::function<void(EmulatedNetworkStats)> stats_callback) const { + task_queue_->PostTask([stats_callback, this]() { + stats_callback(endpoints_container_->GetStats()); + }); +} + +void EmulatedNetworkManager::UpdateNetworksOnce() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + + std::vector<std::unique_ptr<rtc::Network>> networks; + for (std::unique_ptr<rtc::Network>& net : + endpoints_container_->GetEnabledNetworks()) { + net->set_default_local_address_provider(this); + networks.push_back(std::move(net)); + } + + bool changed; + MergeNetworkList(std::move(networks), &changed); + if (changed || !sent_first_update_) { + MaybeSignalNetworksChanged(); + sent_first_update_ = true; + } +} + +void EmulatedNetworkManager::MaybeSignalNetworksChanged() { + RTC_DCHECK_RUN_ON(network_thread_.get()); + // If manager is stopped we don't need to signal anything. + if (start_count_ == 0) { + return; + } + SignalNetworksChanged(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/emulated_network_manager.h b/third_party/libwebrtc/test/network/emulated_network_manager.h new file mode 100644 index 0000000000..fb4ee1ee85 --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_network_manager.h @@ -0,0 +1,83 @@ +/* + * 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_NETWORK_EMULATED_NETWORK_MANAGER_H_ +#define TEST_NETWORK_EMULATED_NETWORK_MANAGER_H_ + +#include <functional> +#include <memory> +#include <vector> + +#include "api/sequence_checker.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/time_controller.h" +#include "rtc_base/ip_address.h" +#include "rtc_base/network.h" +#include "rtc_base/socket_server.h" +#include "rtc_base/thread.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { + +// Framework assumes that rtc::NetworkManager is called from network thread. +class EmulatedNetworkManager : public rtc::NetworkManagerBase, + public sigslot::has_slots<>, + public EmulatedNetworkManagerInterface { + public: + EmulatedNetworkManager(TimeController* time_controller, + TaskQueueForTest* task_queue, + EndpointsContainer* endpoints_container); + + void EnableEndpoint(EmulatedEndpointImpl* endpoint); + void DisableEndpoint(EmulatedEndpointImpl* endpoint); + + // NetworkManager interface. All these methods are supposed to be called from + // the same thread. + void StartUpdating() override; + void StopUpdating() override; + + // We don't support any address interfaces in the network emulation framework. + std::vector<const rtc::Network*> GetAnyAddressNetworks() override { + return {}; + } + + // EmulatedNetworkManagerInterface API + rtc::Thread* network_thread() override { return network_thread_.get(); } + rtc::NetworkManager* network_manager() override { return this; } + rtc::PacketSocketFactory* packet_socket_factory() override { + return packet_socket_factory_.get(); + } + std::vector<EmulatedEndpoint*> endpoints() const override { + return endpoints_container_->GetEndpoints(); + } + void GetStats( + std::function<void(EmulatedNetworkStats)> stats_callback) const override; + + private: + void UpdateNetworksOnce(); + void MaybeSignalNetworksChanged(); + + TaskQueueForTest* const task_queue_; + const EndpointsContainer* const endpoints_container_; + // The `network_thread_` must outlive `packet_socket_factory_`, because they + // both refer to a socket server that is owned by `network_thread_`. Both + // pointers are assigned only in the constructor, but the way they are + // initialized unfortunately doesn't work with const std::unique_ptr<...>. + std::unique_ptr<rtc::Thread> network_thread_; + std::unique_ptr<rtc::PacketSocketFactory> packet_socket_factory_; + bool sent_first_update_ RTC_GUARDED_BY(network_thread_); + int start_count_ RTC_GUARDED_BY(network_thread_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_EMULATED_NETWORK_MANAGER_H_ diff --git a/third_party/libwebrtc/test/network/emulated_turn_server.cc b/third_party/libwebrtc/test/network/emulated_turn_server.cc new file mode 100644 index 0000000000..0bc7ec6e2a --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_turn_server.cc @@ -0,0 +1,191 @@ +/* + * 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/network/emulated_turn_server.h" + +#include <string> +#include <utility> + +#include "api/packet_socket_factory.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/task_queue_for_test.h" + +namespace { + +static const char kTestRealm[] = "example.org"; +static const char kTestSoftware[] = "TestTurnServer"; + +// A wrapper class for copying data between an AsyncPacketSocket and a +// EmulatedEndpoint. This is used by the cricket::TurnServer when +// sending data back into the emulated network. +class AsyncPacketSocketWrapper : public rtc::AsyncPacketSocket { + public: + AsyncPacketSocketWrapper(webrtc::test::EmulatedTURNServer* turn_server, + webrtc::EmulatedEndpoint* endpoint, + uint16_t port) + : turn_server_(turn_server), + endpoint_(endpoint), + local_address_( + rtc::SocketAddress(endpoint_->GetPeerLocalAddress(), port)) {} + ~AsyncPacketSocketWrapper() { turn_server_->Unbind(local_address_); } + + rtc::SocketAddress GetLocalAddress() const override { return local_address_; } + rtc::SocketAddress GetRemoteAddress() const override { + return rtc::SocketAddress(); + } + int Send(const void* pv, + size_t cb, + const rtc::PacketOptions& options) override { + RTC_CHECK(false) << "TCP not implemented"; + return -1; + } + int SendTo(const void* pv, + size_t cb, + const rtc::SocketAddress& addr, + const rtc::PacketOptions& options) override { + // Copy from rtc::AsyncPacketSocket to EmulatedEndpoint. + rtc::CopyOnWriteBuffer buf(reinterpret_cast<const char*>(pv), cb); + endpoint_->SendPacket(local_address_, addr, buf); + return cb; + } + int Close() override { return 0; } + + rtc::AsyncPacketSocket::State GetState() const override { + return rtc::AsyncPacketSocket::STATE_BOUND; + } + int GetOption(rtc::Socket::Option opt, int* value) override { return 0; } + int SetOption(rtc::Socket::Option opt, int value) override { return 0; } + int GetError() const override { return 0; } + void SetError(int error) override {} + + private: + webrtc::test::EmulatedTURNServer* const turn_server_; + webrtc::EmulatedEndpoint* const endpoint_; + const rtc::SocketAddress local_address_; +}; + +// A wrapper class for cricket::TurnServer to allocate sockets. +class PacketSocketFactoryWrapper : public rtc::PacketSocketFactory { + public: + explicit PacketSocketFactoryWrapper( + webrtc::test::EmulatedTURNServer* turn_server) + : turn_server_(turn_server) {} + ~PacketSocketFactoryWrapper() override {} + + // This method is called from TurnServer when making a TURN ALLOCATION. + // It will create a socket on the `peer_` endpoint. + rtc::AsyncPacketSocket* CreateUdpSocket(const rtc::SocketAddress& address, + uint16_t min_port, + uint16_t max_port) override { + return turn_server_->CreatePeerSocket(); + } + + rtc::AsyncListenSocket* CreateServerTcpSocket( + const rtc::SocketAddress& local_address, + uint16_t min_port, + uint16_t max_port, + int opts) override { + return nullptr; + } + rtc::AsyncPacketSocket* CreateClientTcpSocket( + const rtc::SocketAddress& local_address, + const rtc::SocketAddress& remote_address, + const rtc::ProxyInfo& proxy_info, + const std::string& user_agent, + const rtc::PacketSocketTcpOptions& tcp_options) override { + return nullptr; + } + std::unique_ptr<webrtc::AsyncDnsResolverInterface> CreateAsyncDnsResolver() + override { + return nullptr; + } + + private: + webrtc::test::EmulatedTURNServer* turn_server_; +}; + +} // namespace + +namespace webrtc { +namespace test { + +EmulatedTURNServer::EmulatedTURNServer(std::unique_ptr<rtc::Thread> thread, + EmulatedEndpoint* client, + EmulatedEndpoint* peer) + : thread_(std::move(thread)), client_(client), peer_(peer) { + ice_config_.username = "keso"; + ice_config_.password = "keso"; + SendTask(thread_.get(), [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + turn_server_ = std::make_unique<cricket::TurnServer>(thread_.get()); + turn_server_->set_realm(kTestRealm); + turn_server_->set_realm(kTestSoftware); + turn_server_->set_auth_hook(this); + + auto client_socket = Wrap(client_); + turn_server_->AddInternalSocket(client_socket, cricket::PROTO_UDP); + turn_server_->SetExternalSocketFactory(new PacketSocketFactoryWrapper(this), + rtc::SocketAddress()); + client_address_ = client_socket->GetLocalAddress(); + char buf[256]; + rtc::SimpleStringBuilder str(buf); + str.AppendFormat("turn:%s?transport=udp", + client_address_.ToString().c_str()); + ice_config_.url = str.str(); + }); +} + +void EmulatedTURNServer::Stop() { + SendTask(thread_.get(), [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + sockets_.clear(); + }); +} + +EmulatedTURNServer::~EmulatedTURNServer() { + SendTask(thread_.get(), [=]() { + RTC_DCHECK_RUN_ON(thread_.get()); + turn_server_.reset(nullptr); + }); +} + +rtc::AsyncPacketSocket* EmulatedTURNServer::Wrap(EmulatedEndpoint* endpoint) { + RTC_DCHECK_RUN_ON(thread_.get()); + auto port = endpoint->BindReceiver(0, this).value(); + auto socket = new AsyncPacketSocketWrapper(this, endpoint, port); + sockets_[rtc::SocketAddress(endpoint->GetPeerLocalAddress(), port)] = socket; + return socket; +} + +void EmulatedTURNServer::OnPacketReceived(webrtc::EmulatedIpPacket packet) { + // Copy from EmulatedEndpoint to rtc::AsyncPacketSocket. + thread_->PostTask([this, packet(std::move(packet))]() { + RTC_DCHECK_RUN_ON(thread_.get()); + auto it = sockets_.find(packet.to); + if (it != sockets_.end()) { + it->second->SignalReadPacket( + it->second, reinterpret_cast<const char*>(packet.cdata()), + packet.size(), packet.from, packet.arrival_time.ms()); + } + }); +} + +void EmulatedTURNServer::Unbind(rtc::SocketAddress address) { + RTC_DCHECK_RUN_ON(thread_.get()); + if (GetClientEndpoint()->GetPeerLocalAddress() == address.ipaddr()) { + GetClientEndpoint()->UnbindReceiver(address.port()); + } else { + GetPeerEndpoint()->UnbindReceiver(address.port()); + } + sockets_.erase(address); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/emulated_turn_server.h b/third_party/libwebrtc/test/network/emulated_turn_server.h new file mode 100644 index 0000000000..9cb0ceabf6 --- /dev/null +++ b/third_party/libwebrtc/test/network/emulated_turn_server.h @@ -0,0 +1,98 @@ +/* + * 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_NETWORK_EMULATED_TURN_SERVER_H_ +#define TEST_NETWORK_EMULATED_TURN_SERVER_H_ + +#include <map> +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/test/network_emulation_manager.h" +#include "api/transport/stun.h" +#include "p2p/base/turn_server.h" +#include "rtc_base/async_packet_socket.h" + +namespace webrtc { +namespace test { + +// EmulatedTURNServer wraps cricket::TurnServer to be used inside +// a emulated network. +// +// Packets from EmulatedEndpoint (client or peer) are received in +// EmulatedTURNServer::OnPacketReceived which performs a map lookup +// and delivers them into cricket::TurnServer using +// AsyncPacketSocket::SignalReadPacket +// +// Packets from cricket::TurnServer to EmulatedEndpoint are sent into +// using a wrapper around AsyncPacketSocket (no lookup required as the +// wrapper around AsyncPacketSocket keep a pointer to the EmulatedEndpoint). +class EmulatedTURNServer : public EmulatedTURNServerInterface, + public cricket::TurnAuthInterface, + public webrtc::EmulatedNetworkReceiverInterface { + public: + // Create an EmulatedTURNServer. + // `thread` is a thread that will be used to run cricket::TurnServer + // that expects all calls to be made from a single thread. + EmulatedTURNServer(std::unique_ptr<rtc::Thread> thread, + EmulatedEndpoint* client, + EmulatedEndpoint* peer); + ~EmulatedTURNServer() override; + + IceServerConfig GetIceServerConfig() const override { return ice_config_; } + + EmulatedEndpoint* GetClientEndpoint() const override { return client_; } + + rtc::SocketAddress GetClientEndpointAddress() const override { + return client_address_; + } + + EmulatedEndpoint* GetPeerEndpoint() const override { return peer_; } + + // cricket::TurnAuthInterface + bool GetKey(absl::string_view username, + absl::string_view realm, + std::string* key) override { + return cricket::ComputeStunCredentialHash( + std::string(username), std::string(realm), std::string(username), key); + } + + rtc::AsyncPacketSocket* CreatePeerSocket() { return Wrap(peer_); } + + // This method is called by network emulation when a packet + // comes from an emulated link. + void OnPacketReceived(webrtc::EmulatedIpPacket packet) override; + + // This is called when the TURN server deletes a socket. + void Unbind(rtc::SocketAddress address); + + // Unbind all sockets. + void Stop(); + + private: + std::unique_ptr<rtc::Thread> thread_; + rtc::SocketAddress client_address_; + IceServerConfig ice_config_; + EmulatedEndpoint* const client_; + EmulatedEndpoint* const peer_; + std::unique_ptr<cricket::TurnServer> turn_server_ RTC_GUARDED_BY(&thread_); + std::map<rtc::SocketAddress, rtc::AsyncPacketSocket*> sockets_ + RTC_GUARDED_BY(&thread_); + + // Wraps a EmulatedEndpoint in a AsyncPacketSocket to bridge interaction + // with TurnServer. cricket::TurnServer gets ownership of the socket. + rtc::AsyncPacketSocket* Wrap(EmulatedEndpoint* endpoint); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_EMULATED_TURN_SERVER_H_ diff --git a/third_party/libwebrtc/test/network/fake_network_socket_server.cc b/third_party/libwebrtc/test/network/fake_network_socket_server.cc new file mode 100644 index 0000000000..c94c4e372a --- /dev/null +++ b/third_party/libwebrtc/test/network/fake_network_socket_server.cc @@ -0,0 +1,322 @@ +/* + * 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/network/fake_network_socket_server.h" + +#include <algorithm> +#include <string> +#include <utility> +#include <vector> + +#include "absl/algorithm/container.h" +#include "api/scoped_refptr.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "rtc_base/event.h" +#include "rtc_base/logging.h" +#include "rtc_base/thread.h" + +namespace webrtc { +namespace test { +namespace { +std::string ToString(const rtc::SocketAddress& addr) { + return addr.HostAsURIString() + ":" + std::to_string(addr.port()); +} + +} // namespace + +// Represents a socket, which will operate with emulated network. +class FakeNetworkSocket : public rtc::Socket, + public EmulatedNetworkReceiverInterface { + public: + explicit FakeNetworkSocket(FakeNetworkSocketServer* scoket_manager, + rtc::Thread* thread); + ~FakeNetworkSocket() override; + + // Will be invoked by EmulatedEndpoint to deliver packets into this socket. + void OnPacketReceived(EmulatedIpPacket packet) override; + + // rtc::Socket methods: + rtc::SocketAddress GetLocalAddress() const override; + rtc::SocketAddress GetRemoteAddress() const override; + int Bind(const rtc::SocketAddress& addr) override; + int Connect(const rtc::SocketAddress& addr) override; + int Close() override; + int Send(const void* pv, size_t cb) override; + int SendTo(const void* pv, + size_t cb, + const rtc::SocketAddress& addr) override; + int Recv(void* pv, size_t cb, int64_t* timestamp) override; + int RecvFrom(void* pv, + size_t cb, + rtc::SocketAddress* paddr, + int64_t* timestamp) override; + int Listen(int backlog) override; + rtc::Socket* Accept(rtc::SocketAddress* paddr) override; + int GetError() const override; + void SetError(int error) override; + ConnState GetState() const override; + int GetOption(Option opt, int* value) override; + int SetOption(Option opt, int value) override; + + private: + FakeNetworkSocketServer* const socket_server_; + rtc::Thread* const thread_; + EmulatedEndpointImpl* endpoint_ RTC_GUARDED_BY(&thread_); + rtc::SocketAddress local_addr_ RTC_GUARDED_BY(&thread_); + rtc::SocketAddress remote_addr_ RTC_GUARDED_BY(&thread_); + ConnState state_ RTC_GUARDED_BY(&thread_); + int error_ RTC_GUARDED_BY(&thread_); + std::map<Option, int> options_map_ RTC_GUARDED_BY(&thread_); + + absl::optional<EmulatedIpPacket> pending_ RTC_GUARDED_BY(thread_); + rtc::scoped_refptr<PendingTaskSafetyFlag> alive_; +}; + +FakeNetworkSocket::FakeNetworkSocket(FakeNetworkSocketServer* socket_server, + rtc::Thread* thread) + : socket_server_(socket_server), + thread_(thread), + state_(CS_CLOSED), + error_(0), + alive_(PendingTaskSafetyFlag::Create()) {} + +FakeNetworkSocket::~FakeNetworkSocket() { + // Abandon all pending packets. + alive_->SetNotAlive(); + + Close(); + socket_server_->Unregister(this); +} + +void FakeNetworkSocket::OnPacketReceived(EmulatedIpPacket packet) { + auto task = [this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(thread_); + if (!endpoint_->Enabled()) + return; + RTC_DCHECK(!pending_); + pending_ = std::move(packet); + // Note that we expect that this will trigger exactly one call to RecvFrom() + // where pending_packet will be read and reset. This call is done without + // any thread switch (see AsyncUDPSocket::OnReadEvent) so it's safe to + // assume that SignalReadEvent() will block until the packet has been read. + SignalReadEvent(this); + RTC_DCHECK(!pending_); + }; + thread_->PostTask(SafeTask(alive_, std::move(task))); + socket_server_->WakeUp(); +} + + +rtc::SocketAddress FakeNetworkSocket::GetLocalAddress() const { + RTC_DCHECK_RUN_ON(thread_); + return local_addr_; +} + +rtc::SocketAddress FakeNetworkSocket::GetRemoteAddress() const { + RTC_DCHECK_RUN_ON(thread_); + return remote_addr_; +} + +int FakeNetworkSocket::Bind(const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(local_addr_.IsNil()) + << "Socket already bound to address: " << ToString(local_addr_); + local_addr_ = addr; + endpoint_ = socket_server_->GetEndpointNode(local_addr_.ipaddr()); + if (!endpoint_) { + local_addr_.Clear(); + RTC_LOG(LS_INFO) << "No endpoint for address: " << ToString(addr); + error_ = EADDRNOTAVAIL; + return 2; + } + absl::optional<uint16_t> port = + endpoint_->BindReceiver(local_addr_.port(), this); + if (!port) { + local_addr_.Clear(); + RTC_LOG(LS_INFO) << "Cannot bind to in-use address: " << ToString(addr); + error_ = EADDRINUSE; + return 1; + } + local_addr_.SetPort(port.value()); + return 0; +} + +int FakeNetworkSocket::Connect(const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(remote_addr_.IsNil()) + << "Socket already connected to address: " << ToString(remote_addr_); + RTC_CHECK(!local_addr_.IsNil()) + << "Socket have to be bind to some local address"; + remote_addr_ = addr; + state_ = CS_CONNECTED; + return 0; +} + +int FakeNetworkSocket::Send(const void* pv, size_t cb) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(state_ == CS_CONNECTED) << "Socket cannot send: not connected"; + return SendTo(pv, cb, remote_addr_); +} + +int FakeNetworkSocket::SendTo(const void* pv, + size_t cb, + const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(!local_addr_.IsNil()) + << "Socket have to be bind to some local address"; + if (!endpoint_->Enabled()) { + error_ = ENETDOWN; + return -1; + } + rtc::CopyOnWriteBuffer packet(static_cast<const uint8_t*>(pv), cb); + endpoint_->SendPacket(local_addr_, addr, packet); + return cb; +} + +int FakeNetworkSocket::Recv(void* pv, size_t cb, int64_t* timestamp) { + rtc::SocketAddress paddr; + return RecvFrom(pv, cb, &paddr, timestamp); +} + +// Reads 1 packet from internal queue. Reads up to `cb` bytes into `pv` +// and returns the length of received packet. +int FakeNetworkSocket::RecvFrom(void* pv, + size_t cb, + rtc::SocketAddress* paddr, + int64_t* timestamp) { + RTC_DCHECK_RUN_ON(thread_); + + if (timestamp) { + *timestamp = -1; + } + RTC_CHECK(pending_); + + *paddr = pending_->from; + size_t data_read = std::min(cb, pending_->size()); + memcpy(pv, pending_->cdata(), data_read); + *timestamp = pending_->arrival_time.us(); + + // According to RECV(2) Linux Man page + // real socket will discard data, that won't fit into provided buffer, + // but we won't to skip such error, so we will assert here. + RTC_CHECK(data_read == pending_->size()) + << "Too small buffer is provided for socket read. " + "Received data size: " + << pending_->size() << "; Provided buffer size: " << cb; + + pending_.reset(); + + // According to RECV(2) Linux Man page + // real socket will return message length, not data read. In our case it is + // actually the same value. + return static_cast<int>(data_read); +} + +int FakeNetworkSocket::Listen(int backlog) { + RTC_CHECK(false) << "Listen() isn't valid for SOCK_DGRAM"; +} + +rtc::Socket* FakeNetworkSocket::Accept(rtc::SocketAddress* /*paddr*/) { + RTC_CHECK(false) << "Accept() isn't valid for SOCK_DGRAM"; +} + +int FakeNetworkSocket::Close() { + RTC_DCHECK_RUN_ON(thread_); + state_ = CS_CLOSED; + if (!local_addr_.IsNil()) { + endpoint_->UnbindReceiver(local_addr_.port()); + } + local_addr_.Clear(); + remote_addr_.Clear(); + return 0; +} + +int FakeNetworkSocket::GetError() const { + RTC_DCHECK_RUN_ON(thread_); + return error_; +} + +void FakeNetworkSocket::SetError(int error) { + RTC_DCHECK_RUN_ON(thread_); + RTC_CHECK(error == 0); + error_ = error; +} + +rtc::Socket::ConnState FakeNetworkSocket::GetState() const { + RTC_DCHECK_RUN_ON(thread_); + return state_; +} + +int FakeNetworkSocket::GetOption(Option opt, int* value) { + RTC_DCHECK_RUN_ON(thread_); + auto it = options_map_.find(opt); + if (it == options_map_.end()) { + return -1; + } + *value = it->second; + return 0; +} + +int FakeNetworkSocket::SetOption(Option opt, int value) { + RTC_DCHECK_RUN_ON(thread_); + options_map_[opt] = value; + return 0; +} + +FakeNetworkSocketServer::FakeNetworkSocketServer( + EndpointsContainer* endpoints_container) + : endpoints_container_(endpoints_container), + wakeup_(/*manual_reset=*/false, /*initially_signaled=*/false) {} +FakeNetworkSocketServer::~FakeNetworkSocketServer() = default; + +EmulatedEndpointImpl* FakeNetworkSocketServer::GetEndpointNode( + const rtc::IPAddress& ip) { + return endpoints_container_->LookupByLocalAddress(ip); +} + +void FakeNetworkSocketServer::Unregister(FakeNetworkSocket* socket) { + MutexLock lock(&lock_); + sockets_.erase(absl::c_find(sockets_, socket)); +} + +rtc::Socket* FakeNetworkSocketServer::CreateSocket(int family, int type) { + RTC_DCHECK(family == AF_INET || family == AF_INET6); + // We support only UDP sockets for now. + RTC_DCHECK(type == SOCK_DGRAM) << "Only UDP sockets are supported"; + RTC_DCHECK(thread_) << "must be attached to thread before creating sockets"; + FakeNetworkSocket* out = new FakeNetworkSocket(this, thread_); + { + MutexLock lock(&lock_); + sockets_.push_back(out); + } + return out; +} + +void FakeNetworkSocketServer::SetMessageQueue(rtc::Thread* thread) { + thread_ = thread; +} + +// Always returns true (if return false, it won't be invoked again...) +bool FakeNetworkSocketServer::Wait(webrtc::TimeDelta max_wait_duration, + bool process_io) { + RTC_DCHECK(thread_ == rtc::Thread::Current()); + if (!max_wait_duration.IsZero()) + wakeup_.Wait(max_wait_duration); + + return true; +} + +void FakeNetworkSocketServer::WakeUp() { + wakeup_.Set(); +} + + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/fake_network_socket_server.h b/third_party/libwebrtc/test/network/fake_network_socket_server.h new file mode 100644 index 0000000000..25c85d048a --- /dev/null +++ b/third_party/libwebrtc/test/network/fake_network_socket_server.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_NETWORK_FAKE_NETWORK_SOCKET_SERVER_H_ +#define TEST_NETWORK_FAKE_NETWORK_SOCKET_SERVER_H_ + +#include <set> +#include <vector> + +#include "api/units/timestamp.h" +#include "rtc_base/event.h" +#include "rtc_base/socket.h" +#include "rtc_base/socket_server.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { +class FakeNetworkSocket; + +// FakeNetworkSocketServer must outlive any sockets it creates. +class FakeNetworkSocketServer : public rtc::SocketServer { + public: + explicit FakeNetworkSocketServer(EndpointsContainer* endpoints_controller); + ~FakeNetworkSocketServer() override; + + + // rtc::SocketFactory methods: + rtc::Socket* CreateSocket(int family, int type) override; + + // rtc::SocketServer methods: + // Called by the network thread when this server is installed, kicking off the + // message handler loop. + void SetMessageQueue(rtc::Thread* thread) override; + bool Wait(webrtc::TimeDelta max_wait_duration, bool process_io) override; + void WakeUp() override; + + protected: + friend class FakeNetworkSocket; + EmulatedEndpointImpl* GetEndpointNode(const rtc::IPAddress& ip); + void Unregister(FakeNetworkSocket* socket); + + private: + const EndpointsContainer* endpoints_container_; + rtc::Event wakeup_; + rtc::Thread* thread_ = nullptr; + + Mutex lock_; + std::vector<FakeNetworkSocket*> sockets_ RTC_GUARDED_BY(lock_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_FAKE_NETWORK_SOCKET_SERVER_H_ diff --git a/third_party/libwebrtc/test/network/feedback_generator.cc b/third_party/libwebrtc/test/network/feedback_generator.cc new file mode 100644 index 0000000000..e339fd87b0 --- /dev/null +++ b/third_party/libwebrtc/test/network/feedback_generator.cc @@ -0,0 +1,111 @@ +/* + * Copyright 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/network/feedback_generator.h" + +#include "absl/memory/memory.h" +#include "api/transport/network_types.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +FeedbackGeneratorImpl::FeedbackGeneratorImpl( + FeedbackGeneratorImpl::Config config) + : conf_(config), + net_(TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault), + send_link_{new SimulatedNetwork(conf_.send_link)}, + ret_link_{new SimulatedNetwork(conf_.return_link)}, + route_(this, + net_.CreateRoute( + {net_.CreateEmulatedNode(absl::WrapUnique(send_link_))}), + net_.CreateRoute( + {net_.CreateEmulatedNode(absl::WrapUnique(ret_link_))})) {} + +Timestamp FeedbackGeneratorImpl::Now() { + return net_.Now(); +} + +void FeedbackGeneratorImpl::Sleep(TimeDelta duration) { + net_.time_controller()->AdvanceTime(duration); +} + +void FeedbackGeneratorImpl::SendPacket(size_t size) { + SentPacket sent; + sent.send_time = Now(); + sent.size = DataSize::Bytes(size); + sent.sequence_number = sequence_number_++; + sent_packets_.push(sent); + route_.SendRequest(size, sent); +} + +std::vector<TransportPacketsFeedback> FeedbackGeneratorImpl::PopFeedback() { + std::vector<TransportPacketsFeedback> ret; + ret.swap(feedback_); + return ret; +} + +void FeedbackGeneratorImpl::SetSendConfig(BuiltInNetworkBehaviorConfig config) { + conf_.send_link = config; + send_link_->SetConfig(conf_.send_link); +} + +void FeedbackGeneratorImpl::SetReturnConfig( + BuiltInNetworkBehaviorConfig config) { + conf_.return_link = config; + ret_link_->SetConfig(conf_.return_link); +} + +void FeedbackGeneratorImpl::SetSendLinkCapacity(DataRate capacity) { + conf_.send_link.link_capacity_kbps = capacity.kbps<int>(); + send_link_->SetConfig(conf_.send_link); +} + +void FeedbackGeneratorImpl::OnRequest(SentPacket packet, + Timestamp arrival_time) { + PacketResult result; + result.sent_packet = packet; + result.receive_time = arrival_time; + received_packets_.push_back(result); + Timestamp first_recv = received_packets_.front().receive_time; + if (Now() - first_recv > conf_.feedback_interval) { + route_.SendResponse(conf_.feedback_packet_size.bytes<size_t>(), + std::move(received_packets_)); + received_packets_ = {}; + } +} + +void FeedbackGeneratorImpl::OnResponse(std::vector<PacketResult> packet_results, + Timestamp arrival_time) { + TransportPacketsFeedback feedback; + feedback.feedback_time = arrival_time; + std::vector<PacketResult>::const_iterator received_packet_iterator = + packet_results.begin(); + while (received_packet_iterator != packet_results.end()) { + RTC_DCHECK(!sent_packets_.empty() && + sent_packets_.front().sequence_number <= + received_packet_iterator->sent_packet.sequence_number) + << "reordering not implemented"; + if (sent_packets_.front().sequence_number < + received_packet_iterator->sent_packet.sequence_number) { + // Packet lost. + PacketResult lost; + lost.sent_packet = sent_packets_.front(); + feedback.packet_feedbacks.push_back(lost); + } + if (sent_packets_.front().sequence_number == + received_packet_iterator->sent_packet.sequence_number) { + feedback.packet_feedbacks.push_back(*received_packet_iterator); + ++received_packet_iterator; + } + sent_packets_.pop(); + } + feedback_.push_back(feedback); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/feedback_generator.h b/third_party/libwebrtc/test/network/feedback_generator.h new file mode 100644 index 0000000000..ecd4597d3f --- /dev/null +++ b/third_party/libwebrtc/test/network/feedback_generator.h @@ -0,0 +1,60 @@ +/* + * Copyright 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_NETWORK_FEEDBACK_GENERATOR_H_ +#define TEST_NETWORK_FEEDBACK_GENERATOR_H_ + +#include <cstdint> +#include <queue> +#include <utility> +#include <vector> + +#include "api/transport/network_types.h" +#include "api/transport/test/feedback_generator_interface.h" +#include "call/simulated_network.h" +#include "test/network/network_emulation.h" +#include "test/network/network_emulation_manager.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +class FeedbackGeneratorImpl + : public FeedbackGenerator, + public TwoWayFakeTrafficRoute<SentPacket, std::vector<PacketResult>>:: + TrafficHandlerInterface { + public: + explicit FeedbackGeneratorImpl(Config config); + Timestamp Now() override; + void Sleep(TimeDelta duration) override; + void SendPacket(size_t size) override; + std::vector<TransportPacketsFeedback> PopFeedback() override; + + void SetSendConfig(BuiltInNetworkBehaviorConfig config) override; + void SetReturnConfig(BuiltInNetworkBehaviorConfig config) override; + + void SetSendLinkCapacity(DataRate capacity) override; + + void OnRequest(SentPacket packet, Timestamp arrival_time) override; + void OnResponse(std::vector<PacketResult> packet_results, + Timestamp arrival_time) override; + + private: + Config conf_; + ::webrtc::test::NetworkEmulationManagerImpl net_; + SimulatedNetwork* const send_link_; + SimulatedNetwork* const ret_link_; + TwoWayFakeTrafficRoute<SentPacket, std::vector<PacketResult>> route_; + + std::queue<SentPacket> sent_packets_; + std::vector<PacketResult> received_packets_; + std::vector<TransportPacketsFeedback> feedback_; + int64_t sequence_number_ = 1; +}; +} // namespace webrtc +#endif // TEST_NETWORK_FEEDBACK_GENERATOR_H_ diff --git a/third_party/libwebrtc/test/network/feedback_generator_unittest.cc b/third_party/libwebrtc/test/network/feedback_generator_unittest.cc new file mode 100644 index 0000000000..9a577bea00 --- /dev/null +++ b/third_party/libwebrtc/test/network/feedback_generator_unittest.cc @@ -0,0 +1,47 @@ +/* + * Copyright 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 "api/transport/test/create_feedback_generator.h" +#include "test/gtest.h" + +namespace webrtc { +TEST(FeedbackGeneratorTest, ReportsFeedbackForSentPackets) { + size_t kPacketSize = 1000; + auto gen = CreateFeedbackGenerator(FeedbackGenerator::Config()); + for (int i = 0; i < 10; ++i) { + gen->SendPacket(kPacketSize); + gen->Sleep(TimeDelta::Millis(50)); + } + auto feedback_list = gen->PopFeedback(); + EXPECT_GT(feedback_list.size(), 0u); + for (const auto& feedback : feedback_list) { + EXPECT_GT(feedback.packet_feedbacks.size(), 0u); + for (const auto& packet : feedback.packet_feedbacks) { + EXPECT_EQ(packet.sent_packet.size.bytes<size_t>(), kPacketSize); + } + } +} + +TEST(FeedbackGeneratorTest, FeedbackIncludesLostPackets) { + size_t kPacketSize = 1000; + auto gen = CreateFeedbackGenerator(FeedbackGenerator::Config()); + BuiltInNetworkBehaviorConfig send_config_with_loss; + send_config_with_loss.loss_percent = 50; + gen->SetSendConfig(send_config_with_loss); + for (int i = 0; i < 20; ++i) { + gen->SendPacket(kPacketSize); + gen->Sleep(TimeDelta::Millis(5)); + } + auto feedback_list = gen->PopFeedback(); + ASSERT_GT(feedback_list.size(), 0u); + EXPECT_NEAR(feedback_list[0].LostWithSendInfo().size(), + feedback_list[0].ReceivedWithSendInfo().size(), 2); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/g3doc/index.md b/third_party/libwebrtc/test/network/g3doc/index.md new file mode 100644 index 0000000000..c82b56445e --- /dev/null +++ b/third_party/libwebrtc/test/network/g3doc/index.md @@ -0,0 +1,137 @@ +<!-- go/cmark --> +<!--* freshness: {owner: 'titovartem' reviewed: '2021-03-01'} *--> + +# Network Emulation Framework + +[TOC] + +## Disclamer + +This documentation explain the implementation details of Network Emulation +Framework. Framework's public APIs are located in: + +* [`/api/test/network_emulation_manager.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fnetwork_emulation_manager.h) +* [`/api/test/create_network_emulation_manager.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fcreate_network_emulation_manager.h) +* [`/api/test/network_emulation/network_emulation_interfaces.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fnetwork_emulation%2Fnetwork_emulation_interfaces.h) +* [`/api/test/simulated_network.h`](https://source.chromium.org/search?q=%2Fapi%2Ftest%2Fsimulated_network.h) + +## Overview + +Network Emulation Framework provides an ability to emulate network behavior +between different clients, including a WebRTC PeerConnection client. To +configure network behavior, the user can choose different options: + +* Use predefined implementation that can be configured with parameters such as + packet loss, bandwidth, delay, etc. +* Custom implementation + +Conceptually the framework provides the ability to define multiple endpoints and +routes used to connect them. All network related entities are created and +managed by single factory class `webrtc::NetworkEmulationManager` which is +implemented by `webrtc::test::NetworkEmulationManagerImpl` and can work in two +modes: + +* Real time +* Simulated time + +The manager has a dedicated task queue which pipes all packets through all +network routes from senders to receivers. This task queue behaviour is +determined by `webrtc::TimeController`, which is based on either in real time or +simulated time mode. + +The network operates on IP level and supports only UDP for now. + +## Abstractions + +The framework contains the following public abstractions: + +* `webrtc::NetworkBehaviorInterface` - defines how emulated network should + behave. It operates on packets metadata level and is responsible for telling + which packet at which time have to be delivered to the next receiver. + +* `webrtc::EmulatedIpPacket` - represents a single packet that can be sent or + received via emulated network. It has source and destination address and + payload to transfer. + +* `webrtc::EmulatedNetworkReceiverInterface` - generic packet receiver + interface. + +* `webrtc::EmulatedEndpoint` - primary user facing abstraction of the + framework. It represents a network interface on client's machine. It has its + own unique IP address and can be used to send and receive packets. + + `EmulatedEndpoint` implements `EmulatedNetworkReceiverInterface` to receive + packets from the network and provides an API to send packets to the network + and API to bind other `EmulatedNetworkReceiverInterface` which will be able + to receive packets from the endpoint. `EmulatedEndpoint` interface has the + only implementation: `webrtc::test::EmulatedEndpointImpl`. + +* `webrtc::EmulatedNetworkNode` - represents single network in the real world, + like a 3G network between peers, or Wi-Fi for one peer and LTE for another. + Each `EmulatedNetworkNode` is a single direction connetion and to form + bidirectional connection between endpoints two nodes should be used. + Multiple nodes can be joined into chain emulating a network path from one + peer to another. + + In public API this class is forward declared and fully accessible only by + the framework implementation. + + Internally consist of two parts: `LinkEmulation`, which is responsible for + behavior of current `EmulatedNetworkNode` and `NetworkRouterNode` which is + responsible for routing packets to the next node or to the endpoint. + +* `webrtc::EmulatedRoute` - represents single route from one network interface + on one device to another network interface on another device. + + In public API this class is forward declared and fully accessible only by + the framework implementation. + + It contains start and end endpoint and ordered list of `EmulatedNetworkNode` + which forms the single directional route between those endpoints. + +The framework has also the following private abstractions: + +* `webrtc::test::NetworkRouterNode` - an `EmulatedNetworkReceiverInterface` + that can route incoming packets to the next receiver based on internal IP + routing table. + +* `webrtc::test::LinkEmulation` - an `EmulatedNetworkReceiverInterface` that + can emulate network leg behavior via `webrtc::NetworkBehaviorInterface` + interface. + +For integrating with `webrtc::PeerConnection` there are helper abstractions: + +* `webrtc::EmulatedNetworkManagerInterface` which is implemented by + `webrtc::test::EmulatedNetworkManager` and provides `rtc::Thread` and + `rtc::NetworkManager` for WebRTC to use as network thread for + `PeerConnection` and for `cricket::BasicPortAllocator`. + + Implementation represent framework endpoints as `rtc::Network` to WebRTC. + +## Architecture + +Let's take a look on how framework's abstractions are connected to each other. + +When the user wants to setup emulated network, first of all, they should create +an instance of `NetworkEmulationManager` using +`webrtc::CreateNetworkEmulationManager(...)` API. Then user should use a manager +to create at least one `EmulatedEndpoint` for each client. After endpoints, the +user should create required `EmulatedNetworkNode`s and with help of manager +chain them into `EmulatedRoute`s conecting desired endpoints. + +Here is a visual overview of the emulated network architecture: + +![Architecture](network_emulation_framework.png "Architecture") + +When network is hooked into `PeerConnection` it is done through network thread +and `NetworkManager`. In the network thread the custom `rtc::SocketServer` is +provided: `webrtc::test::FakeNetworkSocketServer`. This custom socket server +will construct custom sockets (`webrtc::test::FakeNetworkSocket`), which +internally bind themselves to the required endpoint. All packets processing +inside socket have to be done on the `PeerConnection`'s network thread. When +packet is going from `PeerConnection` to the network it's already comming from +the network thread and when it's comming from the emulated network switch from +the Network Emulation Framework internal task queue and `PeerConnection`'s +network thread is done inside socket's `OnPacketReceived(...)` method. + +![Network Injection](network_injection_into_peer_connection.png "Network Injection") diff --git a/third_party/libwebrtc/test/network/g3doc/network_emulation_framework.png b/third_party/libwebrtc/test/network/g3doc/network_emulation_framework.png Binary files differnew file mode 100644 index 0000000000..afec47773f --- /dev/null +++ b/third_party/libwebrtc/test/network/g3doc/network_emulation_framework.png diff --git a/third_party/libwebrtc/test/network/g3doc/network_injection_into_peer_connection.png b/third_party/libwebrtc/test/network/g3doc/network_injection_into_peer_connection.png Binary files differnew file mode 100644 index 0000000000..c9e3bf8baf --- /dev/null +++ b/third_party/libwebrtc/test/network/g3doc/network_injection_into_peer_connection.png diff --git a/third_party/libwebrtc/test/network/network_emulation.cc b/third_party/libwebrtc/test/network/network_emulation.cc new file mode 100644 index 0000000000..f1c9ca80dd --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation.cc @@ -0,0 +1,767 @@ +/* + * 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/network/network_emulation.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <utility> + +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/sequence_checker.h" +#include "api/test/network_emulation/network_emulation_interfaces.h" +#include "api/test/network_emulation_manager.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +EmulatedNetworkOutgoingStats GetOverallOutgoingStats( + const std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats>& + outgoing_stats, + EmulatedNetworkStatsGatheringMode mode) { + EmulatedNetworkOutgoingStatsBuilder builder(mode); + for (const auto& entry : outgoing_stats) { + builder.AddOutgoingStats(entry.second); + } + return builder.Build(); +} + +EmulatedNetworkIncomingStats GetOverallIncomingStats( + const std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>& + incoming_stats, + EmulatedNetworkStatsGatheringMode mode) { + EmulatedNetworkIncomingStatsBuilder builder(mode); + for (const auto& entry : incoming_stats) { + builder.AddIncomingStats(entry.second); + } + return builder.Build(); +} + +} // namespace + +EmulatedNetworkOutgoingStatsBuilder::EmulatedNetworkOutgoingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +void EmulatedNetworkOutgoingStatsBuilder::OnPacketSent(Timestamp sent_time, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_CHECK_GE(packet_size, DataSize::Zero()); + if (stats_.first_packet_sent_time.IsInfinite()) { + stats_.first_packet_sent_time = sent_time; + stats_.first_sent_packet_size = packet_size; + } + stats_.last_packet_sent_time = sent_time; + stats_.packets_sent++; + stats_.bytes_sent += packet_size; + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.sent_packets_size.AddSample(packet_size.bytes()); + } +} + +void EmulatedNetworkOutgoingStatsBuilder::AddOutgoingStats( + const EmulatedNetworkOutgoingStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packets_sent += stats.packets_sent; + stats_.bytes_sent += stats.bytes_sent; + stats_.sent_packets_size.AddSamples(stats.sent_packets_size); + if (stats_.first_packet_sent_time > stats.first_packet_sent_time) { + stats_.first_packet_sent_time = stats.first_packet_sent_time; + stats_.first_sent_packet_size = stats.first_sent_packet_size; + } + if (stats_.last_packet_sent_time < stats.last_packet_sent_time) { + stats_.last_packet_sent_time = stats.last_packet_sent_time; + } +} + +EmulatedNetworkOutgoingStats EmulatedNetworkOutgoingStatsBuilder::Build() + const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return stats_; +} + +EmulatedNetworkIncomingStatsBuilder::EmulatedNetworkIncomingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +void EmulatedNetworkIncomingStatsBuilder::OnPacketDropped( + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packets_discarded_no_receiver++; + stats_.bytes_discarded_no_receiver += packet_size; + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.packets_discarded_no_receiver_size.AddSample(packet_size.bytes()); + } +} + +void EmulatedNetworkIncomingStatsBuilder::OnPacketReceived( + Timestamp received_time, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_CHECK_GE(packet_size, DataSize::Zero()); + if (stats_.first_packet_received_time.IsInfinite()) { + stats_.first_packet_received_time = received_time; + stats_.first_received_packet_size = packet_size; + } + stats_.last_packet_received_time = received_time; + stats_.packets_received++; + stats_.bytes_received += packet_size; + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.received_packets_size.AddSample(packet_size.bytes()); + } +} + +void EmulatedNetworkIncomingStatsBuilder::AddIncomingStats( + const EmulatedNetworkIncomingStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packets_received += stats.packets_received; + stats_.bytes_received += stats.bytes_received; + stats_.received_packets_size.AddSamples(stats.received_packets_size); + stats_.packets_discarded_no_receiver += stats.packets_discarded_no_receiver; + stats_.bytes_discarded_no_receiver += stats.bytes_discarded_no_receiver; + stats_.packets_discarded_no_receiver_size.AddSamples( + stats.packets_discarded_no_receiver_size); + if (stats_.first_packet_received_time > stats.first_packet_received_time) { + stats_.first_packet_received_time = stats.first_packet_received_time; + stats_.first_received_packet_size = stats.first_received_packet_size; + } + if (stats_.last_packet_received_time < stats.last_packet_received_time) { + stats_.last_packet_received_time = stats.last_packet_received_time; + } +} + +EmulatedNetworkIncomingStats EmulatedNetworkIncomingStatsBuilder::Build() + const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return stats_; +} + +EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder( + rtc::IPAddress local_ip, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + local_addresses_.push_back(local_ip); + sequence_checker_.Detach(); +} + +void EmulatedNetworkStatsBuilder::OnPacketSent(Timestamp queued_time, + Timestamp sent_time, + rtc::IPAddress destination_ip, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + sent_packets_queue_wait_time_us_.AddSample((sent_time - queued_time).us()); + } + auto it = outgoing_stats_per_destination_.find(destination_ip); + if (it == outgoing_stats_per_destination_.end()) { + outgoing_stats_per_destination_ + .emplace(destination_ip, + std::make_unique<EmulatedNetworkOutgoingStatsBuilder>( + stats_gathering_mode_)) + .first->second->OnPacketSent(sent_time, packet_size); + } else { + it->second->OnPacketSent(sent_time, packet_size); + } +} + +void EmulatedNetworkStatsBuilder::OnPacketDropped(rtc::IPAddress source_ip, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + auto it = incoming_stats_per_source_.find(source_ip); + if (it == incoming_stats_per_source_.end()) { + incoming_stats_per_source_ + .emplace(source_ip, + std::make_unique<EmulatedNetworkIncomingStatsBuilder>( + stats_gathering_mode_)) + .first->second->OnPacketDropped(packet_size); + } else { + it->second->OnPacketDropped(packet_size); + } +} + +void EmulatedNetworkStatsBuilder::OnPacketReceived(Timestamp received_time, + rtc::IPAddress source_ip, + DataSize packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + auto it = incoming_stats_per_source_.find(source_ip); + if (it == incoming_stats_per_source_.end()) { + incoming_stats_per_source_ + .emplace(source_ip, + std::make_unique<EmulatedNetworkIncomingStatsBuilder>( + stats_gathering_mode_)) + .first->second->OnPacketReceived(received_time, packet_size); + } else { + it->second->OnPacketReceived(received_time, packet_size); + } +} + +void EmulatedNetworkStatsBuilder::AddEmulatedNetworkStats( + const EmulatedNetworkStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // Append IPs from other endpoints stats to the builder. + for (const rtc::IPAddress& addr : stats.local_addresses) { + local_addresses_.push_back(addr); + } + + sent_packets_queue_wait_time_us_.AddSamples( + stats.sent_packets_queue_wait_time_us); + + // Add outgoing stats from other endpoints to the builder. + for (const auto& entry : stats.outgoing_stats_per_destination) { + auto it = outgoing_stats_per_destination_.find(entry.first); + if (it == outgoing_stats_per_destination_.end()) { + outgoing_stats_per_destination_ + .emplace(entry.first, + std::make_unique<EmulatedNetworkOutgoingStatsBuilder>( + stats_gathering_mode_)) + .first->second->AddOutgoingStats(entry.second); + } else { + it->second->AddOutgoingStats(entry.second); + } + } + + // Add incoming stats from other endpoints to the builder. + for (const auto& entry : stats.incoming_stats_per_source) { + auto it = incoming_stats_per_source_.find(entry.first); + if (it == incoming_stats_per_source_.end()) { + incoming_stats_per_source_ + .emplace(entry.first, + std::make_unique<EmulatedNetworkIncomingStatsBuilder>( + stats_gathering_mode_)) + .first->second->AddIncomingStats(entry.second); + } else { + it->second->AddIncomingStats(entry.second); + } + } +} + +EmulatedNetworkStats EmulatedNetworkStatsBuilder::Build() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> outgoing_stats; + for (const auto& entry : outgoing_stats_per_destination_) { + outgoing_stats.emplace(entry.first, entry.second->Build()); + } + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> incoming_stats; + for (const auto& entry : incoming_stats_per_source_) { + incoming_stats.emplace(entry.first, entry.second->Build()); + } + return EmulatedNetworkStats{ + .local_addresses = local_addresses_, + .overall_outgoing_stats = + GetOverallOutgoingStats(outgoing_stats, stats_gathering_mode_), + .overall_incoming_stats = + GetOverallIncomingStats(incoming_stats, stats_gathering_mode_), + .outgoing_stats_per_destination = std::move(outgoing_stats), + .incoming_stats_per_source = std::move(incoming_stats), + .sent_packets_queue_wait_time_us = sent_packets_queue_wait_time_us_}; +} + +EmulatedNetworkNodeStatsBuilder::EmulatedNetworkNodeStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : stats_gathering_mode_(stats_gathering_mode) { + sequence_checker_.Detach(); +} + +void EmulatedNetworkNodeStatsBuilder::AddPacketTransportTime( + TimeDelta time, + size_t packet_size) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + if (stats_gathering_mode_ == EmulatedNetworkStatsGatheringMode::kDebug) { + stats_.packet_transport_time.AddSample(time.ms<double>()); + stats_.size_to_packet_transport_time.AddSample(packet_size / + time.ms<double>()); + } +} + +void EmulatedNetworkNodeStatsBuilder::AddEmulatedNetworkNodeStats( + const EmulatedNetworkNodeStats& stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + stats_.packet_transport_time.AddSamples(stats.packet_transport_time); + stats_.size_to_packet_transport_time.AddSamples( + stats.size_to_packet_transport_time); +} + +EmulatedNetworkNodeStats EmulatedNetworkNodeStatsBuilder::Build() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return stats_; +} + +void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) { + task_queue_->PostTask([this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(task_queue_); + + uint64_t packet_id = next_packet_id_++; + bool sent = network_behavior_->EnqueuePacket(PacketInFlightInfo( + packet.ip_packet_size(), packet.arrival_time.us(), packet_id)); + if (sent) { + packets_.emplace_back(StoredPacket{.id = packet_id, + .sent_time = clock_->CurrentTime(), + .packet = std::move(packet), + .removed = false}); + } + if (process_task_.Running()) + return; + absl::optional<int64_t> next_time_us = + network_behavior_->NextDeliveryTimeUs(); + if (!next_time_us) + return; + Timestamp current_time = clock_->CurrentTime(); + process_task_ = RepeatingTaskHandle::DelayedStart( + task_queue_->Get(), + std::max(TimeDelta::Zero(), + Timestamp::Micros(*next_time_us) - current_time), + [this]() { + RTC_DCHECK_RUN_ON(task_queue_); + Timestamp current_time = clock_->CurrentTime(); + Process(current_time); + absl::optional<int64_t> next_time_us = + network_behavior_->NextDeliveryTimeUs(); + if (!next_time_us) { + process_task_.Stop(); + return TimeDelta::Zero(); // This is ignored. + } + RTC_DCHECK_GE(*next_time_us, current_time.us()); + return Timestamp::Micros(*next_time_us) - current_time; + }); + }); +} + +EmulatedNetworkNodeStats LinkEmulation::stats() const { + RTC_DCHECK_RUN_ON(task_queue_); + return stats_builder_.Build(); +} + +void LinkEmulation::Process(Timestamp at_time) { + std::vector<PacketDeliveryInfo> delivery_infos = + network_behavior_->DequeueDeliverablePackets(at_time.us()); + for (PacketDeliveryInfo& delivery_info : delivery_infos) { + StoredPacket* packet = nullptr; + for (auto& stored_packet : packets_) { + if (stored_packet.id == delivery_info.packet_id) { + packet = &stored_packet; + break; + } + } + RTC_CHECK(packet); + RTC_DCHECK(!packet->removed); + packet->removed = true; + stats_builder_.AddPacketTransportTime( + clock_->CurrentTime() - packet->sent_time, + packet->packet.ip_packet_size()); + + if (delivery_info.receive_time_us != PacketDeliveryInfo::kNotReceived) { + packet->packet.arrival_time = + Timestamp::Micros(delivery_info.receive_time_us); + receiver_->OnPacketReceived(std::move(packet->packet)); + } + while (!packets_.empty() && packets_.front().removed) { + packets_.pop_front(); + } + } +} + +NetworkRouterNode::NetworkRouterNode(rtc::TaskQueue* task_queue) + : task_queue_(task_queue) {} + +void NetworkRouterNode::OnPacketReceived(EmulatedIpPacket packet) { + RTC_DCHECK_RUN_ON(task_queue_); + if (watcher_) { + watcher_(packet); + } + if (filter_) { + if (!filter_(packet)) + return; + } + auto receiver_it = routing_.find(packet.to.ipaddr()); + if (receiver_it == routing_.end()) { + if (default_receiver_.has_value()) { + (*default_receiver_)->OnPacketReceived(std::move(packet)); + } + return; + } + RTC_CHECK(receiver_it != routing_.end()); + + receiver_it->second->OnPacketReceived(std::move(packet)); +} + +void NetworkRouterNode::SetReceiver( + const rtc::IPAddress& dest_ip, + EmulatedNetworkReceiverInterface* receiver) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + EmulatedNetworkReceiverInterface* cur_receiver = routing_[dest_ip]; + RTC_CHECK(cur_receiver == nullptr || cur_receiver == receiver) + << "Routing for dest_ip=" << dest_ip.ToString() << " already exists"; + routing_[dest_ip] = receiver; + }); +} + +void NetworkRouterNode::RemoveReceiver(const rtc::IPAddress& dest_ip) { + RTC_DCHECK_RUN_ON(task_queue_); + routing_.erase(dest_ip); +} + +void NetworkRouterNode::SetDefaultReceiver( + EmulatedNetworkReceiverInterface* receiver) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + if (default_receiver_.has_value()) { + RTC_CHECK_EQ(*default_receiver_, receiver) + << "Router already default receiver"; + } + default_receiver_ = receiver; + }); +} + +void NetworkRouterNode::RemoveDefaultReceiver() { + RTC_DCHECK_RUN_ON(task_queue_); + default_receiver_ = absl::nullopt; +} + +void NetworkRouterNode::SetWatcher( + std::function<void(const EmulatedIpPacket&)> watcher) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + watcher_ = watcher; + }); +} + +void NetworkRouterNode::SetFilter( + std::function<bool(const EmulatedIpPacket&)> filter) { + task_queue_->PostTask([=] { + RTC_DCHECK_RUN_ON(task_queue_); + filter_ = filter; + }); +} + +EmulatedNetworkNode::EmulatedNetworkNode( + Clock* clock, + rtc::TaskQueue* task_queue, + std::unique_ptr<NetworkBehaviorInterface> network_behavior, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : router_(task_queue), + link_(clock, + task_queue, + std::move(network_behavior), + &router_, + stats_gathering_mode) {} + +void EmulatedNetworkNode::OnPacketReceived(EmulatedIpPacket packet) { + link_.OnPacketReceived(std::move(packet)); +} + +EmulatedNetworkNodeStats EmulatedNetworkNode::stats() const { + return link_.stats(); +} + +void EmulatedNetworkNode::CreateRoute( + const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes, + EmulatedNetworkReceiverInterface* receiver) { + RTC_CHECK(!nodes.empty()); + for (size_t i = 0; i + 1 < nodes.size(); ++i) + nodes[i]->router()->SetReceiver(receiver_ip, nodes[i + 1]); + nodes.back()->router()->SetReceiver(receiver_ip, receiver); +} + +void EmulatedNetworkNode::ClearRoute(const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes) { + for (EmulatedNetworkNode* node : nodes) + node->router()->RemoveReceiver(receiver_ip); +} + +EmulatedNetworkNode::~EmulatedNetworkNode() = default; + +EmulatedEndpointImpl::Options::Options( + uint64_t id, + const rtc::IPAddress& ip, + const EmulatedEndpointConfig& config, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : id(id), + ip(ip), + stats_gathering_mode(stats_gathering_mode), + type(config.type), + allow_send_packet_with_different_source_ip( + config.allow_send_packet_with_different_source_ip), + allow_receive_packets_with_different_dest_ip( + config.allow_receive_packets_with_different_dest_ip), + log_name(ip.ToString() + " (" + config.name.value_or("") + ")") {} + +EmulatedEndpointImpl::EmulatedEndpointImpl(const Options& options, + bool is_enabled, + rtc::TaskQueue* task_queue, + Clock* clock) + : options_(options), + is_enabled_(is_enabled), + clock_(clock), + task_queue_(task_queue), + router_(task_queue_), + next_port_(kFirstEphemeralPort), + stats_builder_(options_.ip, options_.stats_gathering_mode) { + constexpr int kIPv4NetworkPrefixLength = 24; + constexpr int kIPv6NetworkPrefixLength = 64; + + int prefix_length = 0; + if (options_.ip.family() == AF_INET) { + prefix_length = kIPv4NetworkPrefixLength; + } else if (options_.ip.family() == AF_INET6) { + prefix_length = kIPv6NetworkPrefixLength; + } + rtc::IPAddress prefix = TruncateIP(options_.ip, prefix_length); + network_ = std::make_unique<rtc::Network>( + options_.ip.ToString(), "Endpoint id=" + std::to_string(options_.id), + prefix, prefix_length, options_.type); + network_->AddIP(options_.ip); + + enabled_state_checker_.Detach(); + RTC_LOG(LS_INFO) << "Created emulated endpoint " << options_.log_name + << "; id=" << options_.id; +} +EmulatedEndpointImpl::~EmulatedEndpointImpl() = default; + +uint64_t EmulatedEndpointImpl::GetId() const { + return options_.id; +} + +void EmulatedEndpointImpl::SendPacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + rtc::CopyOnWriteBuffer packet_data, + uint16_t application_overhead) { + if (!options_.allow_send_packet_with_different_source_ip) { + RTC_CHECK(from.ipaddr() == options_.ip); + } + EmulatedIpPacket packet(from, to, std::move(packet_data), + clock_->CurrentTime(), application_overhead); + task_queue_->PostTask([this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(task_queue_); + stats_builder_.OnPacketSent(packet.arrival_time, clock_->CurrentTime(), + packet.to.ipaddr(), + DataSize::Bytes(packet.ip_packet_size())); + + if (packet.to.ipaddr() == options_.ip) { + OnPacketReceived(std::move(packet)); + } else { + router_.OnPacketReceived(std::move(packet)); + } + }); +} + +absl::optional<uint16_t> EmulatedEndpointImpl::BindReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver) { + return BindReceiverInternal(desired_port, receiver, /*is_one_shot=*/false); +} + +absl::optional<uint16_t> EmulatedEndpointImpl::BindOneShotReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver) { + return BindReceiverInternal(desired_port, receiver, /*is_one_shot=*/true); +} + +absl::optional<uint16_t> EmulatedEndpointImpl::BindReceiverInternal( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver, + bool is_one_shot) { + MutexLock lock(&receiver_lock_); + uint16_t port = desired_port; + if (port == 0) { + // Because client can specify its own port, next_port_ can be already in + // use, so we need to find next available port. + int ports_pool_size = + std::numeric_limits<uint16_t>::max() - kFirstEphemeralPort + 1; + for (int i = 0; i < ports_pool_size; ++i) { + uint16_t next_port = NextPort(); + if (port_to_receiver_.find(next_port) == port_to_receiver_.end()) { + port = next_port; + break; + } + } + } + RTC_CHECK(port != 0) << "Can't find free port for receiver in endpoint " + << options_.log_name << "; id=" << options_.id; + bool result = + port_to_receiver_.insert({port, {receiver, is_one_shot}}).second; + if (!result) { + RTC_LOG(LS_INFO) << "Can't bind receiver to used port " << desired_port + << " in endpoint " << options_.log_name + << "; id=" << options_.id; + return absl::nullopt; + } + RTC_LOG(LS_INFO) << "New receiver is binded to endpoint " << options_.log_name + << "; id=" << options_.id << " on port " << port; + return port; +} + +uint16_t EmulatedEndpointImpl::NextPort() { + uint16_t out = next_port_; + if (next_port_ == std::numeric_limits<uint16_t>::max()) { + next_port_ = kFirstEphemeralPort; + } else { + next_port_++; + } + return out; +} + +void EmulatedEndpointImpl::UnbindReceiver(uint16_t port) { + MutexLock lock(&receiver_lock_); + RTC_LOG(LS_INFO) << "Receiver is removed on port " << port + << " from endpoint " << options_.log_name + << "; id=" << options_.id; + port_to_receiver_.erase(port); +} + +void EmulatedEndpointImpl::BindDefaultReceiver( + EmulatedNetworkReceiverInterface* receiver) { + MutexLock lock(&receiver_lock_); + RTC_CHECK(!default_receiver_.has_value()) + << "Endpoint " << options_.log_name << "; id=" << options_.id + << " already has default receiver"; + RTC_LOG(LS_INFO) << "Default receiver is binded to endpoint " + << options_.log_name << "; id=" << options_.id; + default_receiver_ = receiver; +} + +void EmulatedEndpointImpl::UnbindDefaultReceiver() { + MutexLock lock(&receiver_lock_); + RTC_LOG(LS_INFO) << "Default receiver is removed from endpoint " + << options_.log_name << "; id=" << options_.id; + default_receiver_ = absl::nullopt; +} + +rtc::IPAddress EmulatedEndpointImpl::GetPeerLocalAddress() const { + return options_.ip; +} + +void EmulatedEndpointImpl::OnPacketReceived(EmulatedIpPacket packet) { + RTC_DCHECK_RUN_ON(task_queue_); + if (!options_.allow_receive_packets_with_different_dest_ip) { + RTC_CHECK(packet.to.ipaddr() == options_.ip) + << "Routing error: wrong destination endpoint. Packet.to.ipaddr()=: " + << packet.to.ipaddr().ToString() + << "; Receiver options_.ip=" << options_.ip.ToString(); + } + MutexLock lock(&receiver_lock_); + stats_builder_.OnPacketReceived(clock_->CurrentTime(), packet.from.ipaddr(), + DataSize::Bytes(packet.ip_packet_size())); + auto it = port_to_receiver_.find(packet.to.port()); + if (it == port_to_receiver_.end()) { + if (default_receiver_.has_value()) { + (*default_receiver_)->OnPacketReceived(std::move(packet)); + return; + } + // It can happen, that remote peer closed connection, but there still some + // packets, that are going to it. It can happen during peer connection close + // process: one peer closed connection, second still sending data. + RTC_LOG(LS_INFO) << "Drop packet: no receiver registered in " + << options_.log_name << "; id=" << options_.id + << " on port " << packet.to.port() + << ". Packet source: " << packet.from.ToString(); + stats_builder_.OnPacketDropped(packet.from.ipaddr(), + DataSize::Bytes(packet.ip_packet_size())); + return; + } + // Endpoint holds lock during packet processing to ensure that a call to + // UnbindReceiver followed by a delete of the receiver cannot race with this + // call to OnPacketReceived. + it->second.receiver->OnPacketReceived(std::move(packet)); + + if (it->second.is_one_shot) { + port_to_receiver_.erase(it); + } +} + +void EmulatedEndpointImpl::Enable() { + RTC_DCHECK_RUN_ON(&enabled_state_checker_); + RTC_CHECK(!is_enabled_); + is_enabled_ = true; +} + +void EmulatedEndpointImpl::Disable() { + RTC_DCHECK_RUN_ON(&enabled_state_checker_); + RTC_CHECK(is_enabled_); + is_enabled_ = false; +} + +bool EmulatedEndpointImpl::Enabled() const { + RTC_DCHECK_RUN_ON(&enabled_state_checker_); + return is_enabled_; +} + +EmulatedNetworkStats EmulatedEndpointImpl::stats() const { + RTC_DCHECK_RUN_ON(task_queue_); + return stats_builder_.Build(); +} + +EmulatedEndpointImpl* EndpointsContainer::LookupByLocalAddress( + const rtc::IPAddress& local_ip) const { + for (auto* endpoint : endpoints_) { + rtc::IPAddress peer_local_address = endpoint->GetPeerLocalAddress(); + if (peer_local_address == local_ip) { + return endpoint; + } + } + RTC_CHECK(false) << "No network found for address" << local_ip.ToString(); +} + +EndpointsContainer::EndpointsContainer( + const std::vector<EmulatedEndpointImpl*>& endpoints, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : endpoints_(endpoints), stats_gathering_mode_(stats_gathering_mode) {} + +bool EndpointsContainer::HasEndpoint(EmulatedEndpointImpl* endpoint) const { + for (auto* e : endpoints_) { + if (e->GetId() == endpoint->GetId()) { + return true; + } + } + return false; +} + +std::vector<std::unique_ptr<rtc::Network>> +EndpointsContainer::GetEnabledNetworks() const { + std::vector<std::unique_ptr<rtc::Network>> networks; + for (auto* endpoint : endpoints_) { + if (endpoint->Enabled()) { + networks.emplace_back( + std::make_unique<rtc::Network>(endpoint->network())); + } + } + return networks; +} + +std::vector<EmulatedEndpoint*> EndpointsContainer::GetEndpoints() const { + return std::vector<EmulatedEndpoint*>(endpoints_.begin(), endpoints_.end()); +} + +EmulatedNetworkStats EndpointsContainer::GetStats() const { + EmulatedNetworkStatsBuilder stats_builder(stats_gathering_mode_); + for (auto* endpoint : endpoints_) { + stats_builder.AddEmulatedNetworkStats(endpoint->stats()); + } + return stats_builder.Build(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/network_emulation.h b/third_party/libwebrtc/test/network/network_emulation.h new file mode 100644 index 0000000000..dffabafa7c --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation.h @@ -0,0 +1,467 @@ +/* + * 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_NETWORK_NETWORK_EMULATION_H_ +#define TEST_NETWORK_NETWORK_EMULATION_H_ + +#include <cstdint> +#include <deque> +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/sequence_checker.h" +#include "api/test/network_emulation/network_emulation_interfaces.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/simulated_network.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/network.h" +#include "rtc_base/network_constants.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +// All methods of EmulatedNetworkOutgoingStatsBuilder have to be used on a +// single thread. It may be created on another thread. +class EmulatedNetworkOutgoingStatsBuilder { + public: + explicit EmulatedNetworkOutgoingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void OnPacketSent(Timestamp sent_time, DataSize packet_size); + + void AddOutgoingStats(const EmulatedNetworkOutgoingStats& stats); + + EmulatedNetworkOutgoingStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + EmulatedNetworkOutgoingStats stats_ RTC_GUARDED_BY(sequence_checker_); +}; + +// All methods of EmulatedNetworkIncomingStatsBuilder have to be used on a +// single thread. It may be created on another thread. +class EmulatedNetworkIncomingStatsBuilder { + public: + explicit EmulatedNetworkIncomingStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void OnPacketDropped(DataSize packet_size); + + void OnPacketReceived(Timestamp received_time, DataSize packet_size); + + // Adds stats collected from another endpoints to the builder. + void AddIncomingStats(const EmulatedNetworkIncomingStats& stats); + + EmulatedNetworkIncomingStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + EmulatedNetworkIncomingStats stats_ RTC_GUARDED_BY(sequence_checker_); +}; + +// All methods of EmulatedNetworkStatsBuilder have to be used on a single +// thread. It may be created on another thread. +class EmulatedNetworkStatsBuilder { + public: + explicit EmulatedNetworkStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + explicit EmulatedNetworkStatsBuilder( + rtc::IPAddress local_ip, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void OnPacketSent(Timestamp queued_time, + Timestamp sent_time, + rtc::IPAddress destination_ip, + DataSize packet_size); + + void OnPacketDropped(rtc::IPAddress source_ip, DataSize packet_size); + + void OnPacketReceived(Timestamp received_time, + rtc::IPAddress source_ip, + DataSize packet_size); + + void AddEmulatedNetworkStats(const EmulatedNetworkStats& stats); + + EmulatedNetworkStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + std::vector<rtc::IPAddress> local_addresses_ + RTC_GUARDED_BY(sequence_checker_); + SamplesStatsCounter sent_packets_queue_wait_time_us_; + std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStatsBuilder>> + outgoing_stats_per_destination_ RTC_GUARDED_BY(sequence_checker_); + std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStatsBuilder>> + incoming_stats_per_source_ RTC_GUARDED_BY(sequence_checker_); +}; + +// All methods of EmulatedNetworkNodeStatsBuilder have to be used on a +// single thread. It may be created on another thread. +class EmulatedNetworkNodeStatsBuilder { + public: + explicit EmulatedNetworkNodeStatsBuilder( + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + void AddPacketTransportTime(TimeDelta time, size_t packet_size); + + void AddEmulatedNetworkNodeStats(const EmulatedNetworkNodeStats& stats); + + EmulatedNetworkNodeStats Build() const; + + private: + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + EmulatedNetworkNodeStats stats_ RTC_GUARDED_BY(sequence_checker_); +}; + +class LinkEmulation : public EmulatedNetworkReceiverInterface { + public: + LinkEmulation(Clock* clock, + rtc::TaskQueue* task_queue, + std::unique_ptr<NetworkBehaviorInterface> network_behavior, + EmulatedNetworkReceiverInterface* receiver, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : clock_(clock), + task_queue_(task_queue), + network_behavior_(std::move(network_behavior)), + receiver_(receiver), + stats_builder_(stats_gathering_mode) {} + void OnPacketReceived(EmulatedIpPacket packet) override; + + EmulatedNetworkNodeStats stats() const; + + private: + struct StoredPacket { + uint64_t id; + Timestamp sent_time; + EmulatedIpPacket packet; + bool removed; + }; + void Process(Timestamp at_time) RTC_RUN_ON(task_queue_); + + Clock* const clock_; + rtc::TaskQueue* const task_queue_; + const std::unique_ptr<NetworkBehaviorInterface> network_behavior_ + RTC_GUARDED_BY(task_queue_); + EmulatedNetworkReceiverInterface* const receiver_; + + RepeatingTaskHandle process_task_ RTC_GUARDED_BY(task_queue_); + std::deque<StoredPacket> packets_ RTC_GUARDED_BY(task_queue_); + uint64_t next_packet_id_ RTC_GUARDED_BY(task_queue_) = 1; + + EmulatedNetworkNodeStatsBuilder stats_builder_ RTC_GUARDED_BY(task_queue_); +}; + +// Represents a component responsible for routing packets based on their IP +// address. All possible routes have to be set explicitly before packet for +// desired destination will be seen for the first time. If route is unknown +// the packet will be silently dropped. +class NetworkRouterNode : public EmulatedNetworkReceiverInterface { + public: + explicit NetworkRouterNode(rtc::TaskQueue* task_queue); + + void OnPacketReceived(EmulatedIpPacket packet) override; + void SetReceiver(const rtc::IPAddress& dest_ip, + EmulatedNetworkReceiverInterface* receiver); + void RemoveReceiver(const rtc::IPAddress& dest_ip); + // Sets a default receive that will be used for all incoming packets for which + // there is no specific receiver binded to their destination port. + void SetDefaultReceiver(EmulatedNetworkReceiverInterface* receiver); + void RemoveDefaultReceiver(); + void SetWatcher(std::function<void(const EmulatedIpPacket&)> watcher); + void SetFilter(std::function<bool(const EmulatedIpPacket&)> filter); + + private: + rtc::TaskQueue* const task_queue_; + absl::optional<EmulatedNetworkReceiverInterface*> default_receiver_ + RTC_GUARDED_BY(task_queue_); + std::map<rtc::IPAddress, EmulatedNetworkReceiverInterface*> routing_ + RTC_GUARDED_BY(task_queue_); + std::function<void(const EmulatedIpPacket&)> watcher_ + RTC_GUARDED_BY(task_queue_); + std::function<bool(const EmulatedIpPacket&)> filter_ + RTC_GUARDED_BY(task_queue_); +}; + +// Represents node in the emulated network. Nodes can be connected with each +// other to form different networks with different behavior. The behavior of +// the node itself is determined by a concrete implementation of +// NetworkBehaviorInterface that is provided on construction. +class EmulatedNetworkNode : public EmulatedNetworkReceiverInterface { + public: + // Creates node based on `network_behavior`. The specified `packet_overhead` + // is added to the size of each packet in the information provided to + // `network_behavior`. + // `task_queue` is used to process packets and to forward the packets when + // they are ready. + EmulatedNetworkNode( + Clock* clock, + rtc::TaskQueue* task_queue, + std::unique_ptr<NetworkBehaviorInterface> network_behavior, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + ~EmulatedNetworkNode() override; + + EmulatedNetworkNode(const EmulatedNetworkNode&) = delete; + EmulatedNetworkNode& operator=(const EmulatedNetworkNode&) = delete; + + void OnPacketReceived(EmulatedIpPacket packet) override; + + LinkEmulation* link() { return &link_; } + NetworkRouterNode* router() { return &router_; } + EmulatedNetworkNodeStats stats() const; + + // Creates a route for the given receiver_ip over all the given nodes to the + // given receiver. + static void CreateRoute(const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes, + EmulatedNetworkReceiverInterface* receiver); + static void ClearRoute(const rtc::IPAddress& receiver_ip, + std::vector<EmulatedNetworkNode*> nodes); + + private: + NetworkRouterNode router_; + LinkEmulation link_; +}; + +// Represents single network interface on the device. +// It will be used as sender from socket side to send data to the network and +// will act as packet receiver from emulated network side to receive packets +// from other EmulatedNetworkNodes. +class EmulatedEndpointImpl : public EmulatedEndpoint { + public: + struct Options { + Options(uint64_t id, + const rtc::IPAddress& ip, + const EmulatedEndpointConfig& config, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + // TODO(titovartem) check if we can remove id. + uint64_t id; + // Endpoint local IP address. + rtc::IPAddress ip; + EmulatedNetworkStatsGatheringMode stats_gathering_mode; + rtc::AdapterType type; + // Allow endpoint to send packets specifying source IP address different to + // the current endpoint IP address. If false endpoint will crash if attempt + // to send such packet will be done. + bool allow_send_packet_with_different_source_ip; + // Allow endpoint to receive packet with destination IP address different to + // the current endpoint IP address. If false endpoint will crash if such + // packet will arrive. + bool allow_receive_packets_with_different_dest_ip; + // Name of the endpoint used for logging purposes. + std::string log_name; + }; + + EmulatedEndpointImpl(const Options& options, + bool is_enabled, + rtc::TaskQueue* task_queue, + Clock* clock); + ~EmulatedEndpointImpl() override; + + uint64_t GetId() const; + + NetworkRouterNode* router() { return &router_; } + + void SendPacket(const rtc::SocketAddress& from, + const rtc::SocketAddress& to, + rtc::CopyOnWriteBuffer packet_data, + uint16_t application_overhead = 0) override; + + absl::optional<uint16_t> BindReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver) override; + // Binds a receiver, and automatically removes the binding after first call to + // OnPacketReceived. + absl::optional<uint16_t> BindOneShotReceiver( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver); + void UnbindReceiver(uint16_t port) override; + void BindDefaultReceiver(EmulatedNetworkReceiverInterface* receiver) override; + void UnbindDefaultReceiver() override; + + rtc::IPAddress GetPeerLocalAddress() const override; + + // Will be called to deliver packet into endpoint from network node. + void OnPacketReceived(EmulatedIpPacket packet) override; + + void Enable(); + void Disable(); + bool Enabled() const; + + const rtc::Network& network() const { return *network_.get(); } + + EmulatedNetworkStats stats() const; + + private: + struct ReceiverBinding { + EmulatedNetworkReceiverInterface* receiver; + bool is_one_shot; + }; + + absl::optional<uint16_t> BindReceiverInternal( + uint16_t desired_port, + EmulatedNetworkReceiverInterface* receiver, + bool is_one_shot); + + static constexpr uint16_t kFirstEphemeralPort = 49152; + uint16_t NextPort() RTC_EXCLUSIVE_LOCKS_REQUIRED(receiver_lock_); + + Mutex receiver_lock_; + RTC_NO_UNIQUE_ADDRESS SequenceChecker enabled_state_checker_; + + const Options options_; + bool is_enabled_ RTC_GUARDED_BY(enabled_state_checker_); + Clock* const clock_; + rtc::TaskQueue* const task_queue_; + std::unique_ptr<rtc::Network> network_; + NetworkRouterNode router_; + + uint16_t next_port_ RTC_GUARDED_BY(receiver_lock_); + absl::optional<EmulatedNetworkReceiverInterface*> default_receiver_ + RTC_GUARDED_BY(receiver_lock_); + std::map<uint16_t, ReceiverBinding> port_to_receiver_ + RTC_GUARDED_BY(receiver_lock_); + + EmulatedNetworkStatsBuilder stats_builder_ RTC_GUARDED_BY(task_queue_); +}; + +class EmulatedRoute { + public: + EmulatedRoute(EmulatedEndpointImpl* from, + std::vector<EmulatedNetworkNode*> via_nodes, + EmulatedEndpointImpl* to, + bool is_default) + : from(from), + via_nodes(std::move(via_nodes)), + to(to), + active(true), + is_default(is_default) {} + + EmulatedEndpointImpl* from; + std::vector<EmulatedNetworkNode*> via_nodes; + EmulatedEndpointImpl* to; + bool active; + bool is_default; +}; + +// This object is immutable and so thread safe. +class EndpointsContainer { + public: + EndpointsContainer(const std::vector<EmulatedEndpointImpl*>& endpoints, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + + EmulatedEndpointImpl* LookupByLocalAddress( + const rtc::IPAddress& local_ip) const; + bool HasEndpoint(EmulatedEndpointImpl* endpoint) const; + // Returns list of networks for enabled endpoints. Caller takes ownership of + // returned rtc::Network objects. + std::vector<std::unique_ptr<rtc::Network>> GetEnabledNetworks() const; + std::vector<EmulatedEndpoint*> GetEndpoints() const; + EmulatedNetworkStats GetStats() const; + + private: + const std::vector<EmulatedEndpointImpl*> endpoints_; + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; +}; + +template <typename FakePacketType> +class FakePacketRoute : public EmulatedNetworkReceiverInterface { + public: + FakePacketRoute(EmulatedRoute* route, + std::function<void(FakePacketType, Timestamp)> action) + : route_(route), + action_(std::move(action)), + send_addr_(route_->from->GetPeerLocalAddress(), 0), + recv_addr_(route_->to->GetPeerLocalAddress(), + *route_->to->BindReceiver(0, this)) {} + + ~FakePacketRoute() { route_->to->UnbindReceiver(recv_addr_.port()); } + + void SendPacket(size_t size, FakePacketType packet) { + RTC_CHECK_GE(size, sizeof(int)); + sent_.emplace(next_packet_id_, packet); + rtc::CopyOnWriteBuffer buf(size); + reinterpret_cast<int*>(buf.MutableData())[0] = next_packet_id_++; + route_->from->SendPacket(send_addr_, recv_addr_, buf); + } + + void OnPacketReceived(EmulatedIpPacket packet) override { + int packet_id = reinterpret_cast<const int*>(packet.data.data())[0]; + action_(std::move(sent_[packet_id]), packet.arrival_time); + sent_.erase(packet_id); + } + + private: + EmulatedRoute* const route_; + const std::function<void(FakePacketType, Timestamp)> action_; + const rtc::SocketAddress send_addr_; + const rtc::SocketAddress recv_addr_; + int next_packet_id_ = 0; + std::map<int, FakePacketType> sent_; +}; + +template <typename RequestPacketType, typename ResponsePacketType> +class TwoWayFakeTrafficRoute { + public: + class TrafficHandlerInterface { + public: + virtual void OnRequest(RequestPacketType, Timestamp) = 0; + virtual void OnResponse(ResponsePacketType, Timestamp) = 0; + virtual ~TrafficHandlerInterface() = default; + }; + TwoWayFakeTrafficRoute(TrafficHandlerInterface* handler, + EmulatedRoute* send_route, + EmulatedRoute* ret_route) + : handler_(handler), + request_handler_{send_route, + [&](RequestPacketType packet, Timestamp arrival_time) { + handler_->OnRequest(std::move(packet), arrival_time); + }}, + response_handler_{ + ret_route, [&](ResponsePacketType packet, Timestamp arrival_time) { + handler_->OnResponse(std::move(packet), arrival_time); + }} {} + void SendRequest(size_t size, RequestPacketType packet) { + request_handler_.SendPacket(size, std::move(packet)); + } + void SendResponse(size_t size, ResponsePacketType packet) { + response_handler_.SendPacket(size, std::move(packet)); + } + + private: + TrafficHandlerInterface* handler_; + FakePacketRoute<RequestPacketType> request_handler_; + FakePacketRoute<ResponsePacketType> response_handler_; +}; +} // namespace webrtc + +#endif // TEST_NETWORK_NETWORK_EMULATION_H_ diff --git a/third_party/libwebrtc/test/network/network_emulation_manager.cc b/third_party/libwebrtc/test/network/network_emulation_manager.cc new file mode 100644 index 0000000000..97c0bc1ba8 --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_manager.cc @@ -0,0 +1,373 @@ +/* + * 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/network/network_emulation_manager.h" + +#include <algorithm> +#include <memory> + +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "call/simulated_network.h" +#include "test/network/emulated_turn_server.h" +#include "test/network/traffic_route.h" +#include "test/time_controller/real_time_controller.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace test { +namespace { + +// uint32_t representation of 192.168.0.0 address +constexpr uint32_t kMinIPv4Address = 0xC0A80000; +// uint32_t representation of 192.168.255.255 address +constexpr uint32_t kMaxIPv4Address = 0xC0A8FFFF; + +std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) { + switch (mode) { + case TimeMode::kRealTime: + return std::make_unique<RealTimeController>(); + case TimeMode::kSimulated: + // 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); + } +} +} // namespace + +NetworkEmulationManagerImpl::NetworkEmulationManagerImpl( + TimeMode mode, + EmulatedNetworkStatsGatheringMode stats_gathering_mode) + : time_mode_(mode), + stats_gathering_mode_(stats_gathering_mode), + time_controller_(CreateTimeController(mode)), + clock_(time_controller_->GetClock()), + next_node_id_(1), + next_ip4_address_(kMinIPv4Address), + task_queue_(time_controller_->GetTaskQueueFactory()->CreateTaskQueue( + "NetworkEmulation", + TaskQueueFactory::Priority::NORMAL)) {} + +// TODO(srte): Ensure that any pending task that must be run for consistency +// (such as stats collection tasks) are not cancelled when the task queue is +// destroyed. +NetworkEmulationManagerImpl::~NetworkEmulationManagerImpl() { + for (auto& turn_server : turn_servers_) { + turn_server->Stop(); + } +} + +EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode( + BuiltInNetworkBehaviorConfig config, + uint64_t random_seed) { + return CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(config, random_seed)); +} + +EmulatedNetworkNode* NetworkEmulationManagerImpl::CreateEmulatedNode( + std::unique_ptr<NetworkBehaviorInterface> network_behavior) { + auto node = std::make_unique<EmulatedNetworkNode>( + clock_, &task_queue_, std::move(network_behavior), stats_gathering_mode_); + EmulatedNetworkNode* out = node.get(); + task_queue_.PostTask([this, node = std::move(node)]() mutable { + network_nodes_.push_back(std::move(node)); + }); + return out; +} + +NetworkEmulationManager::SimulatedNetworkNode::Builder +NetworkEmulationManagerImpl::NodeBuilder() { + return SimulatedNetworkNode::Builder(this); +} + +EmulatedEndpointImpl* NetworkEmulationManagerImpl::CreateEndpoint( + EmulatedEndpointConfig config) { + absl::optional<rtc::IPAddress> ip = config.ip; + if (!ip) { + switch (config.generated_ip_family) { + case EmulatedEndpointConfig::IpAddressFamily::kIpv4: + ip = GetNextIPv4Address(); + RTC_CHECK(ip) << "All auto generated IPv4 addresses exhausted"; + break; + case EmulatedEndpointConfig::IpAddressFamily::kIpv6: + ip = GetNextIPv4Address(); + RTC_CHECK(ip) << "All auto generated IPv6 addresses exhausted"; + ip = ip->AsIPv6Address(); + break; + } + } + + bool res = used_ip_addresses_.insert(*ip).second; + RTC_CHECK(res) << "IP=" << ip->ToString() << " already in use"; + auto node = std::make_unique<EmulatedEndpointImpl>( + EmulatedEndpointImpl::Options(next_node_id_++, *ip, config, + stats_gathering_mode_), + config.start_as_enabled, &task_queue_, clock_); + EmulatedEndpointImpl* out = node.get(); + endpoints_.push_back(std::move(node)); + return out; +} + +void NetworkEmulationManagerImpl::EnableEndpoint(EmulatedEndpoint* endpoint) { + EmulatedNetworkManager* network_manager = + endpoint_to_network_manager_[endpoint]; + RTC_CHECK(network_manager); + network_manager->EnableEndpoint(static_cast<EmulatedEndpointImpl*>(endpoint)); +} + +void NetworkEmulationManagerImpl::DisableEndpoint(EmulatedEndpoint* endpoint) { + EmulatedNetworkManager* network_manager = + endpoint_to_network_manager_[endpoint]; + RTC_CHECK(network_manager); + network_manager->DisableEndpoint( + static_cast<EmulatedEndpointImpl*>(endpoint)); +} + +EmulatedRoute* NetworkEmulationManagerImpl::CreateRoute( + EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) { + // Because endpoint has no send node by default at least one should be + // provided here. + RTC_CHECK(!via_nodes.empty()); + + static_cast<EmulatedEndpointImpl*>(from)->router()->SetReceiver( + to->GetPeerLocalAddress(), via_nodes[0]); + EmulatedNetworkNode* cur_node = via_nodes[0]; + for (size_t i = 1; i < via_nodes.size(); ++i) { + cur_node->router()->SetReceiver(to->GetPeerLocalAddress(), via_nodes[i]); + cur_node = via_nodes[i]; + } + cur_node->router()->SetReceiver(to->GetPeerLocalAddress(), to); + + std::unique_ptr<EmulatedRoute> route = std::make_unique<EmulatedRoute>( + static_cast<EmulatedEndpointImpl*>(from), std::move(via_nodes), + static_cast<EmulatedEndpointImpl*>(to), /*is_default=*/false); + EmulatedRoute* out = route.get(); + routes_.push_back(std::move(route)); + return out; +} + +EmulatedRoute* NetworkEmulationManagerImpl::CreateRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) { + EmulatedEndpoint* from = CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* to = CreateEndpoint(EmulatedEndpointConfig()); + return CreateRoute(from, via_nodes, to); +} + +EmulatedRoute* NetworkEmulationManagerImpl::CreateDefaultRoute( + EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) { + // Because endpoint has no send node by default at least one should be + // provided here. + RTC_CHECK(!via_nodes.empty()); + + static_cast<EmulatedEndpointImpl*>(from)->router()->SetDefaultReceiver( + via_nodes[0]); + EmulatedNetworkNode* cur_node = via_nodes[0]; + for (size_t i = 1; i < via_nodes.size(); ++i) { + cur_node->router()->SetDefaultReceiver(via_nodes[i]); + cur_node = via_nodes[i]; + } + cur_node->router()->SetDefaultReceiver(to); + + std::unique_ptr<EmulatedRoute> route = std::make_unique<EmulatedRoute>( + static_cast<EmulatedEndpointImpl*>(from), std::move(via_nodes), + static_cast<EmulatedEndpointImpl*>(to), /*is_default=*/true); + EmulatedRoute* out = route.get(); + routes_.push_back(std::move(route)); + return out; +} + +void NetworkEmulationManagerImpl::ClearRoute(EmulatedRoute* route) { + RTC_CHECK(route->active) << "Route already cleared"; + task_queue_.SendTask([route]() { + // Remove receiver from intermediate nodes. + for (auto* node : route->via_nodes) { + if (route->is_default) { + node->router()->RemoveDefaultReceiver(); + } else { + node->router()->RemoveReceiver(route->to->GetPeerLocalAddress()); + } + } + // Remove destination endpoint from source endpoint's router. + if (route->is_default) { + route->from->router()->RemoveDefaultReceiver(); + } else { + route->from->router()->RemoveReceiver(route->to->GetPeerLocalAddress()); + } + + route->active = false; + }); +} + +TcpMessageRoute* NetworkEmulationManagerImpl::CreateTcpRoute( + EmulatedRoute* send_route, + EmulatedRoute* ret_route) { + auto tcp_route = std::make_unique<TcpMessageRouteImpl>( + clock_, task_queue_.Get(), send_route, ret_route); + auto* route_ptr = tcp_route.get(); + task_queue_.PostTask([this, tcp_route = std::move(tcp_route)]() mutable { + tcp_message_routes_.push_back(std::move(tcp_route)); + }); + return route_ptr; +} + +CrossTrafficRoute* NetworkEmulationManagerImpl::CreateCrossTrafficRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) { + RTC_CHECK(!via_nodes.empty()); + EmulatedEndpointImpl* endpoint = CreateEndpoint(EmulatedEndpointConfig()); + + // Setup a route via specified nodes. + EmulatedNetworkNode* cur_node = via_nodes[0]; + for (size_t i = 1; i < via_nodes.size(); ++i) { + cur_node->router()->SetReceiver(endpoint->GetPeerLocalAddress(), + via_nodes[i]); + cur_node = via_nodes[i]; + } + cur_node->router()->SetReceiver(endpoint->GetPeerLocalAddress(), endpoint); + + std::unique_ptr<CrossTrafficRoute> traffic_route = + std::make_unique<CrossTrafficRouteImpl>(clock_, via_nodes[0], endpoint); + CrossTrafficRoute* out = traffic_route.get(); + traffic_routes_.push_back(std::move(traffic_route)); + return out; +} + +CrossTrafficGenerator* NetworkEmulationManagerImpl::StartCrossTraffic( + std::unique_ptr<CrossTrafficGenerator> generator) { + CrossTrafficGenerator* out = generator.get(); + task_queue_.PostTask([this, generator = std::move(generator)]() mutable { + auto* generator_ptr = generator.get(); + + auto repeating_task_handle = + RepeatingTaskHandle::Start(task_queue_.Get(), [this, generator_ptr] { + generator_ptr->Process(Now()); + return generator_ptr->GetProcessInterval(); + }); + + cross_traffics_.push_back(CrossTrafficSource( + std::move(generator), std::move(repeating_task_handle))); + }); + return out; +} + +void NetworkEmulationManagerImpl::StopCrossTraffic( + CrossTrafficGenerator* generator) { + task_queue_.PostTask([=]() { + auto it = std::find_if(cross_traffics_.begin(), cross_traffics_.end(), + [=](const CrossTrafficSource& el) { + return el.first.get() == generator; + }); + it->second.Stop(); + cross_traffics_.erase(it); + }); +} + +EmulatedNetworkManagerInterface* +NetworkEmulationManagerImpl::CreateEmulatedNetworkManagerInterface( + const std::vector<EmulatedEndpoint*>& endpoints) { + std::vector<EmulatedEndpointImpl*> endpoint_impls; + endpoint_impls.reserve(endpoints.size()); + for (EmulatedEndpoint* endpoint : endpoints) { + endpoint_impls.push_back(static_cast<EmulatedEndpointImpl*>(endpoint)); + } + auto endpoints_container = std::make_unique<EndpointsContainer>( + endpoint_impls, stats_gathering_mode_); + auto network_manager = std::make_unique<EmulatedNetworkManager>( + time_controller_.get(), &task_queue_, endpoints_container.get()); + for (auto* endpoint : endpoints) { + // Associate endpoint with network manager. + bool insertion_result = + endpoint_to_network_manager_.insert({endpoint, network_manager.get()}) + .second; + RTC_CHECK(insertion_result) + << "Endpoint ip=" << endpoint->GetPeerLocalAddress().ToString() + << " is already used for another network"; + } + + EmulatedNetworkManagerInterface* out = network_manager.get(); + + endpoints_containers_.push_back(std::move(endpoints_container)); + network_managers_.push_back(std::move(network_manager)); + return out; +} + +void NetworkEmulationManagerImpl::GetStats( + rtc::ArrayView<EmulatedEndpoint* const> endpoints, + std::function<void(EmulatedNetworkStats)> stats_callback) { + task_queue_.PostTask([endpoints, stats_callback, + stats_gathering_mode = stats_gathering_mode_]() { + EmulatedNetworkStatsBuilder stats_builder(stats_gathering_mode); + for (auto* endpoint : endpoints) { + // It's safe to cast here because EmulatedEndpointImpl can be the only + // implementation of EmulatedEndpoint, because only it has access to + // EmulatedEndpoint constructor. + auto endpoint_impl = static_cast<EmulatedEndpointImpl*>(endpoint); + stats_builder.AddEmulatedNetworkStats(endpoint_impl->stats()); + } + stats_callback(stats_builder.Build()); + }); +} + +void NetworkEmulationManagerImpl::GetStats( + rtc::ArrayView<EmulatedNetworkNode* const> nodes, + std::function<void(EmulatedNetworkNodeStats)> stats_callback) { + task_queue_.PostTask( + [nodes, stats_callback, stats_gathering_mode = stats_gathering_mode_]() { + EmulatedNetworkNodeStatsBuilder stats_builder(stats_gathering_mode); + for (auto* node : nodes) { + stats_builder.AddEmulatedNetworkNodeStats(node->stats()); + } + stats_callback(stats_builder.Build()); + }); +} + +absl::optional<rtc::IPAddress> +NetworkEmulationManagerImpl::GetNextIPv4Address() { + uint32_t addresses_count = kMaxIPv4Address - kMinIPv4Address; + for (uint32_t i = 0; i < addresses_count; i++) { + rtc::IPAddress ip(next_ip4_address_); + if (next_ip4_address_ == kMaxIPv4Address) { + next_ip4_address_ = kMinIPv4Address; + } else { + next_ip4_address_++; + } + if (used_ip_addresses_.find(ip) == used_ip_addresses_.end()) { + return ip; + } + } + return absl::nullopt; +} + +Timestamp NetworkEmulationManagerImpl::Now() const { + return clock_->CurrentTime(); +} + +EmulatedTURNServerInterface* NetworkEmulationManagerImpl::CreateTURNServer( + EmulatedTURNServerConfig config) { + auto* client = CreateEndpoint(config.client_config); + auto* peer = CreateEndpoint(config.client_config); + char buf[128]; + rtc::SimpleStringBuilder str(buf); + str.AppendFormat("turn_server_%u", + static_cast<unsigned>(turn_servers_.size())); + auto turn = std::make_unique<EmulatedTURNServer>( + time_controller_->CreateThread(str.str()), client, peer); + auto out = turn.get(); + turn_servers_.push_back(std::move(turn)); + return out; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/network_emulation_manager.h b/third_party/libwebrtc/test/network/network_emulation_manager.h new file mode 100644 index 0000000000..29debca693 --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_manager.h @@ -0,0 +1,138 @@ +/* + * 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_NETWORK_NETWORK_EMULATION_MANAGER_H_ +#define TEST_NETWORK_NETWORK_EMULATION_MANAGER_H_ + +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/simulated_network.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "system_wrappers/include/clock.h" +#include "test/network/cross_traffic.h" +#include "test/network/emulated_network_manager.h" +#include "test/network/emulated_turn_server.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { + +class NetworkEmulationManagerImpl : public NetworkEmulationManager { + public: + NetworkEmulationManagerImpl( + TimeMode mode, + EmulatedNetworkStatsGatheringMode stats_gathering_mode); + ~NetworkEmulationManagerImpl(); + + EmulatedNetworkNode* CreateEmulatedNode(BuiltInNetworkBehaviorConfig config, + uint64_t random_seed = 1) override; + EmulatedNetworkNode* CreateEmulatedNode( + std::unique_ptr<NetworkBehaviorInterface> network_behavior) override; + + SimulatedNetworkNode::Builder NodeBuilder() override; + + EmulatedEndpointImpl* CreateEndpoint(EmulatedEndpointConfig config) override; + void EnableEndpoint(EmulatedEndpoint* endpoint) override; + void DisableEndpoint(EmulatedEndpoint* endpoint) override; + + EmulatedRoute* CreateRoute(EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) override; + + EmulatedRoute* CreateRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) override; + + EmulatedRoute* CreateDefaultRoute( + EmulatedEndpoint* from, + const std::vector<EmulatedNetworkNode*>& via_nodes, + EmulatedEndpoint* to) override; + + void ClearRoute(EmulatedRoute* route) override; + + TcpMessageRoute* CreateTcpRoute(EmulatedRoute* send_route, + EmulatedRoute* ret_route) override; + + CrossTrafficRoute* CreateCrossTrafficRoute( + const std::vector<EmulatedNetworkNode*>& via_nodes) override; + + CrossTrafficGenerator* StartCrossTraffic( + std::unique_ptr<CrossTrafficGenerator> generator) override; + void StopCrossTraffic(CrossTrafficGenerator* generator) override; + + EmulatedNetworkManagerInterface* CreateEmulatedNetworkManagerInterface( + const std::vector<EmulatedEndpoint*>& endpoints) override; + + void GetStats( + rtc::ArrayView<EmulatedEndpoint* const> endpoints, + std::function<void(EmulatedNetworkStats)> stats_callback) override; + + void GetStats( + rtc::ArrayView<EmulatedNetworkNode* const> nodes, + std::function<void(EmulatedNetworkNodeStats)> stats_callback) override; + + TimeController* time_controller() override { return time_controller_.get(); } + + TimeMode time_mode() const override { return time_mode_; } + + Timestamp Now() const; + + EmulatedTURNServerInterface* CreateTURNServer( + EmulatedTURNServerConfig config) override; + + private: + using CrossTrafficSource = + std::pair<std::unique_ptr<CrossTrafficGenerator>, RepeatingTaskHandle>; + + absl::optional<rtc::IPAddress> GetNextIPv4Address(); + + const TimeMode time_mode_; + const EmulatedNetworkStatsGatheringMode stats_gathering_mode_; + const std::unique_ptr<TimeController> time_controller_; + Clock* const clock_; + int next_node_id_; + + RepeatingTaskHandle process_task_handle_; + + uint32_t next_ip4_address_; + std::set<rtc::IPAddress> used_ip_addresses_; + + // All objects can be added to the manager only when it is idle. + std::vector<std::unique_ptr<EmulatedEndpoint>> endpoints_; + std::vector<std::unique_ptr<EmulatedNetworkNode>> network_nodes_; + std::vector<std::unique_ptr<EmulatedRoute>> routes_; + std::vector<std::unique_ptr<CrossTrafficRoute>> traffic_routes_; + std::vector<CrossTrafficSource> cross_traffics_; + std::list<std::unique_ptr<TcpMessageRouteImpl>> tcp_message_routes_; + std::vector<std::unique_ptr<EndpointsContainer>> endpoints_containers_; + std::vector<std::unique_ptr<EmulatedNetworkManager>> network_managers_; + std::vector<std::unique_ptr<EmulatedTURNServer>> turn_servers_; + + std::map<EmulatedEndpoint*, EmulatedNetworkManager*> + endpoint_to_network_manager_; + + // Must be the last field, so it will be deleted first, because tasks + // in the TaskQueue can access other fields of the instance of this class. + TaskQueueForTest task_queue_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_NETWORK_EMULATION_MANAGER_H_ diff --git a/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc b/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc new file mode 100644 index 0000000000..51a45a8234 --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_pc_unittest.cc @@ -0,0 +1,319 @@ +/* + * Copyright 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 <cstdint> +#include <memory> + +#include "api/call/call_factory_interface.h" +#include "api/peer_connection_interface.h" +#include "api/rtc_event_log/rtc_event_log_factory.h" +#include "api/scoped_refptr.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "api/transport/field_trial_based_config.h" +#include "call/simulated_network.h" +#include "media/engine/webrtc_media_engine.h" +#include "media/engine/webrtc_media_engine_defaults.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "p2p/base/basic_packet_socket_factory.h" +#include "p2p/client/basic_port_allocator.h" +#include "pc/peer_connection_wrapper.h" +#include "pc/test/mock_peer_connection_observers.h" +#include "rtc_base/gunit.h" +#include "rtc_base/task_queue_for_test.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/network/network_emulation.h" +#include "test/network/network_emulation_manager.h" + +namespace webrtc { +namespace test { +namespace { + +constexpr int kDefaultTimeoutMs = 1000; +constexpr int kMaxAptitude = 32000; +constexpr int kSamplingFrequency = 48000; +constexpr char kSignalThreadName[] = "signaling_thread"; + +bool AddIceCandidates(PeerConnectionWrapper* peer, + std::vector<const IceCandidateInterface*> candidates) { + bool success = true; + for (const auto candidate : candidates) { + if (!peer->pc()->AddIceCandidate(candidate)) { + success = false; + } + } + return success; +} + +rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory( + rtc::Thread* signaling_thread, + rtc::Thread* network_thread) { + PeerConnectionFactoryDependencies pcf_deps; + pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory(); + pcf_deps.call_factory = CreateCallFactory(); + pcf_deps.event_log_factory = + std::make_unique<RtcEventLogFactory>(pcf_deps.task_queue_factory.get()); + pcf_deps.network_thread = network_thread; + pcf_deps.signaling_thread = signaling_thread; + pcf_deps.trials = std::make_unique<FieldTrialBasedConfig>(); + cricket::MediaEngineDependencies media_deps; + media_deps.task_queue_factory = pcf_deps.task_queue_factory.get(); + media_deps.adm = TestAudioDeviceModule::Create( + media_deps.task_queue_factory, + TestAudioDeviceModule::CreatePulsedNoiseCapturer(kMaxAptitude, + kSamplingFrequency), + TestAudioDeviceModule::CreateDiscardRenderer(kSamplingFrequency), + /*speed=*/1.f); + media_deps.trials = pcf_deps.trials.get(); + SetMediaEngineDefaults(&media_deps); + pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps)); + return CreateModularPeerConnectionFactory(std::move(pcf_deps)); +} + +rtc::scoped_refptr<PeerConnectionInterface> CreatePeerConnection( + const rtc::scoped_refptr<PeerConnectionFactoryInterface>& pcf, + PeerConnectionObserver* observer, + rtc::PacketSocketFactory* packet_socket_factory, + rtc::NetworkManager* network_manager, + EmulatedTURNServerInterface* turn_server = nullptr) { + PeerConnectionDependencies pc_deps(observer); + auto port_allocator = std::make_unique<cricket::BasicPortAllocator>( + network_manager, packet_socket_factory); + + // This test does not support TCP + int flags = cricket::PORTALLOCATOR_DISABLE_TCP; + port_allocator->set_flags(port_allocator->flags() | flags); + + pc_deps.allocator = std::move(port_allocator); + PeerConnectionInterface::RTCConfiguration rtc_configuration; + rtc_configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; + if (turn_server != nullptr) { + webrtc::PeerConnectionInterface::IceServer server; + server.username = turn_server->GetIceServerConfig().username; + server.password = turn_server->GetIceServerConfig().username; + server.urls.push_back(turn_server->GetIceServerConfig().url); + rtc_configuration.servers.push_back(server); + } + + auto result = + pcf->CreatePeerConnectionOrError(rtc_configuration, std::move(pc_deps)); + if (!result.ok()) { + return nullptr; + } + return result.MoveValue(); +} + +} // namespace + +TEST(NetworkEmulationManagerPCTest, Run) { + std::unique_ptr<rtc::Thread> signaling_thread = rtc::Thread::Create(); + signaling_thread->SetName(kSignalThreadName, nullptr); + signaling_thread->Start(); + + // Setup emulated network + NetworkEmulationManagerImpl emulation( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + emulation.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + emulation.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + emulation.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + emulation.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + // Setup peer connections. + rtc::scoped_refptr<PeerConnectionFactoryInterface> alice_pcf; + rtc::scoped_refptr<PeerConnectionInterface> alice_pc; + std::unique_ptr<MockPeerConnectionObserver> alice_observer = + std::make_unique<MockPeerConnectionObserver>(); + + rtc::scoped_refptr<PeerConnectionFactoryInterface> bob_pcf; + rtc::scoped_refptr<PeerConnectionInterface> bob_pc; + std::unique_ptr<MockPeerConnectionObserver> bob_observer = + std::make_unique<MockPeerConnectionObserver>(); + + SendTask(signaling_thread.get(), [&]() { + alice_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + alice_network->network_thread()); + alice_pc = CreatePeerConnection(alice_pcf, alice_observer.get(), + alice_network->packet_socket_factory(), + alice_network->network_manager()); + + bob_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + bob_network->network_thread()); + bob_pc = CreatePeerConnection(bob_pcf, bob_observer.get(), + bob_network->packet_socket_factory(), + bob_network->network_manager()); + }); + + std::unique_ptr<PeerConnectionWrapper> alice = + std::make_unique<PeerConnectionWrapper>(alice_pcf, alice_pc, + std::move(alice_observer)); + std::unique_ptr<PeerConnectionWrapper> bob = + std::make_unique<PeerConnectionWrapper>(bob_pcf, bob_pc, + std::move(bob_observer)); + + SendTask(signaling_thread.get(), [&]() { + rtc::scoped_refptr<webrtc::AudioSourceInterface> source = + alice_pcf->CreateAudioSource(cricket::AudioOptions()); + rtc::scoped_refptr<AudioTrackInterface> track = + alice_pcf->CreateAudioTrack("audio", source.get()); + alice->AddTransceiver(track); + + // Connect peers. + ASSERT_TRUE(alice->ExchangeOfferAnswerWith(bob.get())); + // Do the SDP negotiation, and also exchange ice candidates. + ASSERT_TRUE_WAIT( + alice->signaling_state() == PeerConnectionInterface::kStable, + kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(alice->IsIceGatheringDone(), kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(bob->IsIceGatheringDone(), kDefaultTimeoutMs); + + // Connect an ICE candidate pairs. + ASSERT_TRUE( + AddIceCandidates(bob.get(), alice->observer()->GetAllCandidates())); + ASSERT_TRUE( + AddIceCandidates(alice.get(), bob->observer()->GetAllCandidates())); + // This means that ICE and DTLS are connected. + ASSERT_TRUE_WAIT(bob->IsIceConnected(), kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(alice->IsIceConnected(), kDefaultTimeoutMs); + + // Close peer connections + alice->pc()->Close(); + bob->pc()->Close(); + + // Delete peers. + alice.reset(); + bob.reset(); + }); +} + +TEST(NetworkEmulationManagerPCTest, RunTURN) { + std::unique_ptr<rtc::Thread> signaling_thread = rtc::Thread::Create(); + signaling_thread->SetName(kSignalThreadName, nullptr); + signaling_thread->Start(); + + // Setup emulated network + NetworkEmulationManagerImpl emulation( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* turn_node = emulation.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + emulation.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedTURNServerInterface* alice_turn = + emulation.CreateTURNServer(EmulatedTURNServerConfig()); + EmulatedTURNServerInterface* bob_turn = + emulation.CreateTURNServer(EmulatedTURNServerConfig()); + + emulation.CreateRoute(alice_endpoint, {alice_node}, + alice_turn->GetClientEndpoint()); + emulation.CreateRoute(alice_turn->GetClientEndpoint(), {alice_node}, + alice_endpoint); + + emulation.CreateRoute(bob_endpoint, {bob_node}, + bob_turn->GetClientEndpoint()); + emulation.CreateRoute(bob_turn->GetClientEndpoint(), {bob_node}, + bob_endpoint); + + emulation.CreateRoute(alice_turn->GetPeerEndpoint(), {turn_node}, + bob_turn->GetPeerEndpoint()); + emulation.CreateRoute(bob_turn->GetPeerEndpoint(), {turn_node}, + alice_turn->GetPeerEndpoint()); + + EmulatedNetworkManagerInterface* alice_network = + emulation.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + emulation.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + // Setup peer connections. + rtc::scoped_refptr<PeerConnectionFactoryInterface> alice_pcf; + rtc::scoped_refptr<PeerConnectionInterface> alice_pc; + std::unique_ptr<MockPeerConnectionObserver> alice_observer = + std::make_unique<MockPeerConnectionObserver>(); + + rtc::scoped_refptr<PeerConnectionFactoryInterface> bob_pcf; + rtc::scoped_refptr<PeerConnectionInterface> bob_pc; + std::unique_ptr<MockPeerConnectionObserver> bob_observer = + std::make_unique<MockPeerConnectionObserver>(); + + SendTask(signaling_thread.get(), [&]() { + alice_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + alice_network->network_thread()); + alice_pc = CreatePeerConnection( + alice_pcf, alice_observer.get(), alice_network->packet_socket_factory(), + alice_network->network_manager(), alice_turn); + + bob_pcf = CreatePeerConnectionFactory(signaling_thread.get(), + bob_network->network_thread()); + bob_pc = CreatePeerConnection(bob_pcf, bob_observer.get(), + bob_network->packet_socket_factory(), + bob_network->network_manager(), bob_turn); + }); + + std::unique_ptr<PeerConnectionWrapper> alice = + std::make_unique<PeerConnectionWrapper>(alice_pcf, alice_pc, + std::move(alice_observer)); + std::unique_ptr<PeerConnectionWrapper> bob = + std::make_unique<PeerConnectionWrapper>(bob_pcf, bob_pc, + std::move(bob_observer)); + + SendTask(signaling_thread.get(), [&]() { + rtc::scoped_refptr<webrtc::AudioSourceInterface> source = + alice_pcf->CreateAudioSource(cricket::AudioOptions()); + rtc::scoped_refptr<AudioTrackInterface> track = + alice_pcf->CreateAudioTrack("audio", source.get()); + alice->AddTransceiver(track); + + // Connect peers. + ASSERT_TRUE(alice->ExchangeOfferAnswerWith(bob.get())); + // Do the SDP negotiation, and also exchange ice candidates. + ASSERT_TRUE_WAIT( + alice->signaling_state() == PeerConnectionInterface::kStable, + kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(alice->IsIceGatheringDone(), kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(bob->IsIceGatheringDone(), kDefaultTimeoutMs); + + // Connect an ICE candidate pairs. + ASSERT_TRUE( + AddIceCandidates(bob.get(), alice->observer()->GetAllCandidates())); + ASSERT_TRUE( + AddIceCandidates(alice.get(), bob->observer()->GetAllCandidates())); + // This means that ICE and DTLS are connected. + ASSERT_TRUE_WAIT(bob->IsIceConnected(), kDefaultTimeoutMs); + ASSERT_TRUE_WAIT(alice->IsIceConnected(), kDefaultTimeoutMs); + + // Close peer connections + alice->pc()->Close(); + bob->pc()->Close(); + + // Delete peers. + alice.reset(); + bob.reset(); + }); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/network_emulation_unittest.cc b/third_party/libwebrtc/test/network/network_emulation_unittest.cc new file mode 100644 index 0000000000..2e67a5a00a --- /dev/null +++ b/third_party/libwebrtc/test/network/network_emulation_unittest.cc @@ -0,0 +1,676 @@ +/* + * Copyright 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/network/network_emulation.h" + +#include <atomic> +#include <memory> +#include <set> + +#include "api/test/simulated_network.h" +#include "api/units/time_delta.h" +#include "call/simulated_network.h" +#include "rtc_base/event.h" +#include "rtc_base/gunit.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue_for_test.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/network/network_emulation_manager.h" + +namespace webrtc { +namespace test { +namespace { + +using ::testing::ElementsAreArray; + +constexpr TimeDelta kNetworkPacketWaitTimeout = TimeDelta::Millis(100); +constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1); +constexpr int kOverheadIpv4Udp = 20 + 8; + +class SocketReader : public sigslot::has_slots<> { + public: + explicit SocketReader(rtc::Socket* socket, rtc::Thread* network_thread) + : socket_(socket), network_thread_(network_thread) { + socket_->SignalReadEvent.connect(this, &SocketReader::OnReadEvent); + size_ = 128 * 1024; + buf_ = new char[size_]; + } + ~SocketReader() override { delete[] buf_; } + + void OnReadEvent(rtc::Socket* socket) { + RTC_DCHECK(socket_ == socket); + RTC_DCHECK(network_thread_->IsCurrent()); + int64_t timestamp; + len_ = socket_->Recv(buf_, size_, ×tamp); + + MutexLock lock(&lock_); + received_count_++; + } + + int ReceivedCount() { + MutexLock lock(&lock_); + return received_count_; + } + + private: + rtc::Socket* const socket_; + rtc::Thread* const network_thread_; + char* buf_; + size_t size_; + int len_; + + Mutex lock_; + int received_count_ RTC_GUARDED_BY(lock_) = 0; +}; + +class MockReceiver : public EmulatedNetworkReceiverInterface { + public: + MOCK_METHOD(void, OnPacketReceived, (EmulatedIpPacket packet), (override)); +}; + +class NetworkEmulationManagerThreeNodesRoutingTest : public ::testing::Test { + public: + NetworkEmulationManagerThreeNodesRoutingTest() { + e1_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + e2_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + e3_ = emulation_.CreateEndpoint(EmulatedEndpointConfig()); + } + + void SetupRouting( + std::function<void(EmulatedEndpoint*, + EmulatedEndpoint*, + EmulatedEndpoint*, + NetworkEmulationManager*)> create_routing_func) { + create_routing_func(e1_, e2_, e3_, &emulation_); + } + + void SendPacketsAndValidateDelivery() { + EXPECT_CALL(r_e1_e2_, OnPacketReceived(::testing::_)).Times(1); + EXPECT_CALL(r_e2_e1_, OnPacketReceived(::testing::_)).Times(1); + EXPECT_CALL(r_e1_e3_, OnPacketReceived(::testing::_)).Times(1); + EXPECT_CALL(r_e3_e1_, OnPacketReceived(::testing::_)).Times(1); + + uint16_t common_send_port = 80; + uint16_t r_e1_e2_port = e2_->BindReceiver(0, &r_e1_e2_).value(); + uint16_t r_e2_e1_port = e1_->BindReceiver(0, &r_e2_e1_).value(); + uint16_t r_e1_e3_port = e3_->BindReceiver(0, &r_e1_e3_).value(); + uint16_t r_e3_e1_port = e1_->BindReceiver(0, &r_e3_e1_).value(); + + // Next code is using API of EmulatedEndpoint, that is visible only for + // internals of network emulation layer. Don't use this API in other tests. + // Send packet from e1 to e2. + e1_->SendPacket( + rtc::SocketAddress(e1_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e2_->GetPeerLocalAddress(), r_e1_e2_port), + rtc::CopyOnWriteBuffer(10)); + + // Send packet from e2 to e1. + e2_->SendPacket( + rtc::SocketAddress(e2_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e1_->GetPeerLocalAddress(), r_e2_e1_port), + rtc::CopyOnWriteBuffer(10)); + + // Send packet from e1 to e3. + e1_->SendPacket( + rtc::SocketAddress(e1_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e3_->GetPeerLocalAddress(), r_e1_e3_port), + rtc::CopyOnWriteBuffer(10)); + + // Send packet from e3 to e1. + e3_->SendPacket( + rtc::SocketAddress(e3_->GetPeerLocalAddress(), common_send_port), + rtc::SocketAddress(e1_->GetPeerLocalAddress(), r_e3_e1_port), + rtc::CopyOnWriteBuffer(10)); + + // Sleep at the end to wait for async packets delivery. + emulation_.time_controller()->AdvanceTime(kNetworkPacketWaitTimeout); + } + + private: + // Receivers: r_<source endpoint>_<destination endpoint> + // They must be destroyed after emulation, so they should be declared before. + MockReceiver r_e1_e2_; + MockReceiver r_e2_e1_; + MockReceiver r_e1_e3_; + MockReceiver r_e3_e1_; + + NetworkEmulationManagerImpl emulation_{ + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault}; + EmulatedEndpoint* e1_; + EmulatedEndpoint* e2_; + EmulatedEndpoint* e3_; +}; + +EmulatedNetworkNode* CreateEmulatedNodeWithDefaultBuiltInConfig( + NetworkEmulationManager* emulation) { + return emulation->CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); +} + +} // namespace + +using ::testing::_; + +TEST(NetworkEmulationManagerTest, GeneratedIpv4AddressDoesNotCollide) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + std::set<rtc::IPAddress> ips; + EmulatedEndpointConfig config; + config.generated_ip_family = EmulatedEndpointConfig::IpAddressFamily::kIpv4; + for (int i = 0; i < 1000; i++) { + EmulatedEndpoint* endpoint = network_manager.CreateEndpoint(config); + ASSERT_EQ(endpoint->GetPeerLocalAddress().family(), AF_INET); + bool result = ips.insert(endpoint->GetPeerLocalAddress()).second; + ASSERT_TRUE(result); + } +} + +TEST(NetworkEmulationManagerTest, GeneratedIpv6AddressDoesNotCollide) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + std::set<rtc::IPAddress> ips; + EmulatedEndpointConfig config; + config.generated_ip_family = EmulatedEndpointConfig::IpAddressFamily::kIpv6; + for (int i = 0; i < 1000; i++) { + EmulatedEndpoint* endpoint = network_manager.CreateEndpoint(config); + ASSERT_EQ(endpoint->GetPeerLocalAddress().family(), AF_INET6); + bool result = ips.insert(endpoint->GetPeerLocalAddress()).second; + ASSERT_TRUE(result); + } +} + +TEST(NetworkEmulationManagerTest, Run) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + network_manager.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_manager.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* nt1 = + network_manager.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* nt2 = + network_manager.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + rtc::Thread* t1 = nt1->network_thread(); + rtc::Thread* t2 = nt2->network_thread(); + + rtc::CopyOnWriteBuffer data("Hello"); + for (uint64_t j = 0; j < 2; j++) { + rtc::Socket* s1 = nullptr; + rtc::Socket* s2 = nullptr; + SendTask(t1, [&] { + s1 = t1->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + SendTask(t2, [&] { + s2 = t2->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + + SocketReader r1(s1, t1); + SocketReader r2(s2, t2); + + rtc::SocketAddress a1(alice_endpoint->GetPeerLocalAddress(), 0); + rtc::SocketAddress a2(bob_endpoint->GetPeerLocalAddress(), 0); + + SendTask(t1, [&] { + s1->Bind(a1); + a1 = s1->GetLocalAddress(); + }); + SendTask(t2, [&] { + s2->Bind(a2); + a2 = s2->GetLocalAddress(); + }); + + SendTask(t1, [&] { s1->Connect(a2); }); + SendTask(t2, [&] { s2->Connect(a1); }); + + for (uint64_t i = 0; i < 1000; i++) { + t1->PostTask([&]() { s1->Send(data.data(), data.size()); }); + t2->PostTask([&]() { s2->Send(data.data(), data.size()); }); + } + + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); + + EXPECT_EQ(r1.ReceivedCount(), 1000); + EXPECT_EQ(r2.ReceivedCount(), 1000); + + SendTask(t1, [&] { delete s1; }); + SendTask(t2, [&] { delete s2; }); + } + + const int64_t single_packet_size = data.size() + kOverheadIpv4Udp; + std::atomic<int> received_stats_count{0}; + nt1->GetStats([&](EmulatedNetworkStats st) { + EXPECT_EQ(st.PacketsSent(), 2000l); + EXPECT_EQ(st.BytesSent().bytes(), single_packet_size * 2000l); + EXPECT_THAT(st.local_addresses, + ElementsAreArray({alice_endpoint->GetPeerLocalAddress()})); + EXPECT_EQ(st.PacketsReceived(), 2000l); + EXPECT_EQ(st.BytesReceived().bytes(), single_packet_size * 2000l); + EXPECT_EQ(st.PacketsDiscardedNoReceiver(), 0l); + EXPECT_EQ(st.BytesDiscardedNoReceiver().bytes(), 0l); + + rtc::IPAddress bob_ip = bob_endpoint->GetPeerLocalAddress(); + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st = + st.incoming_stats_per_source; + ASSERT_EQ(source_st.size(), 1lu); + EXPECT_EQ(source_st.at(bob_ip).packets_received, 2000l); + EXPECT_EQ(source_st.at(bob_ip).bytes_received.bytes(), + single_packet_size * 2000l); + EXPECT_EQ(source_st.at(bob_ip).packets_discarded_no_receiver, 0l); + EXPECT_EQ(source_st.at(bob_ip).bytes_discarded_no_receiver.bytes(), 0l); + + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> dest_st = + st.outgoing_stats_per_destination; + ASSERT_EQ(dest_st.size(), 1lu); + EXPECT_EQ(dest_st.at(bob_ip).packets_sent, 2000l); + EXPECT_EQ(dest_st.at(bob_ip).bytes_sent.bytes(), + single_packet_size * 2000l); + + // No debug stats are collected by default. + EXPECT_TRUE(st.SentPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.sent_packets_queue_wait_time_us.IsEmpty()); + EXPECT_TRUE(st.ReceivedPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.PacketsDiscardedNoReceiverSizeCounter().IsEmpty()); + EXPECT_TRUE(dest_st.at(bob_ip).sent_packets_size.IsEmpty()); + EXPECT_TRUE(source_st.at(bob_ip).received_packets_size.IsEmpty()); + EXPECT_TRUE( + source_st.at(bob_ip).packets_discarded_no_receiver_size.IsEmpty()); + + received_stats_count++; + }); + nt2->GetStats([&](EmulatedNetworkStats st) { + EXPECT_EQ(st.PacketsSent(), 2000l); + EXPECT_EQ(st.BytesSent().bytes(), single_packet_size * 2000l); + EXPECT_THAT(st.local_addresses, + ElementsAreArray({bob_endpoint->GetPeerLocalAddress()})); + EXPECT_EQ(st.PacketsReceived(), 2000l); + EXPECT_EQ(st.BytesReceived().bytes(), single_packet_size * 2000l); + EXPECT_EQ(st.PacketsDiscardedNoReceiver(), 0l); + EXPECT_EQ(st.BytesDiscardedNoReceiver().bytes(), 0l); + EXPECT_GT(st.FirstReceivedPacketSize(), DataSize::Zero()); + EXPECT_TRUE(st.FirstPacketReceivedTime().IsFinite()); + EXPECT_TRUE(st.LastPacketReceivedTime().IsFinite()); + + rtc::IPAddress alice_ip = alice_endpoint->GetPeerLocalAddress(); + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st = + st.incoming_stats_per_source; + ASSERT_EQ(source_st.size(), 1lu); + EXPECT_EQ(source_st.at(alice_ip).packets_received, 2000l); + EXPECT_EQ(source_st.at(alice_ip).bytes_received.bytes(), + single_packet_size * 2000l); + EXPECT_EQ(source_st.at(alice_ip).packets_discarded_no_receiver, 0l); + EXPECT_EQ(source_st.at(alice_ip).bytes_discarded_no_receiver.bytes(), 0l); + + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> dest_st = + st.outgoing_stats_per_destination; + ASSERT_EQ(dest_st.size(), 1lu); + EXPECT_EQ(dest_st.at(alice_ip).packets_sent, 2000l); + EXPECT_EQ(dest_st.at(alice_ip).bytes_sent.bytes(), + single_packet_size * 2000l); + + // No debug stats are collected by default. + EXPECT_TRUE(st.SentPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.sent_packets_queue_wait_time_us.IsEmpty()); + EXPECT_TRUE(st.ReceivedPacketsSizeCounter().IsEmpty()); + EXPECT_TRUE(st.PacketsDiscardedNoReceiverSizeCounter().IsEmpty()); + EXPECT_TRUE(dest_st.at(alice_ip).sent_packets_size.IsEmpty()); + EXPECT_TRUE(source_st.at(alice_ip).received_packets_size.IsEmpty()); + EXPECT_TRUE( + source_st.at(alice_ip).packets_discarded_no_receiver_size.IsEmpty()); + + received_stats_count++; + }); + ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 2, + kStatsWaitTimeout.ms(), + *network_manager.time_controller()); +} + +TEST(NetworkEmulationManagerTest, DebugStatsCollectedInDebugMode) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDebug); + + EmulatedNetworkNode* alice_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + network_manager.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_manager.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* nt1 = + network_manager.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* nt2 = + network_manager.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + rtc::Thread* t1 = nt1->network_thread(); + rtc::Thread* t2 = nt2->network_thread(); + + rtc::CopyOnWriteBuffer data("Hello"); + for (uint64_t j = 0; j < 2; j++) { + rtc::Socket* s1 = nullptr; + rtc::Socket* s2 = nullptr; + SendTask(t1, [&] { + s1 = t1->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + SendTask(t2, [&] { + s2 = t2->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); + }); + + SocketReader r1(s1, t1); + SocketReader r2(s2, t2); + + rtc::SocketAddress a1(alice_endpoint->GetPeerLocalAddress(), 0); + rtc::SocketAddress a2(bob_endpoint->GetPeerLocalAddress(), 0); + + SendTask(t1, [&] { + s1->Bind(a1); + a1 = s1->GetLocalAddress(); + }); + SendTask(t2, [&] { + s2->Bind(a2); + a2 = s2->GetLocalAddress(); + }); + + SendTask(t1, [&] { s1->Connect(a2); }); + SendTask(t2, [&] { s2->Connect(a1); }); + + for (uint64_t i = 0; i < 1000; i++) { + t1->PostTask([&]() { s1->Send(data.data(), data.size()); }); + t2->PostTask([&]() { s2->Send(data.data(), data.size()); }); + } + + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); + + EXPECT_EQ(r1.ReceivedCount(), 1000); + EXPECT_EQ(r2.ReceivedCount(), 1000); + + SendTask(t1, [&] { delete s1; }); + SendTask(t2, [&] { delete s2; }); + } + + const int64_t single_packet_size = data.size() + kOverheadIpv4Udp; + std::atomic<int> received_stats_count{0}; + nt1->GetStats([&](EmulatedNetworkStats st) { + rtc::IPAddress bob_ip = bob_endpoint->GetPeerLocalAddress(); + std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st = + st.incoming_stats_per_source; + ASSERT_EQ(source_st.size(), 1lu); + + std::map<rtc::IPAddress, EmulatedNetworkOutgoingStats> dest_st = + st.outgoing_stats_per_destination; + ASSERT_EQ(dest_st.size(), 1lu); + + // No debug stats are collected by default. + EXPECT_EQ(st.SentPacketsSizeCounter().NumSamples(), 2000l); + EXPECT_EQ(st.ReceivedPacketsSizeCounter().GetAverage(), single_packet_size); + EXPECT_EQ(st.sent_packets_queue_wait_time_us.NumSamples(), 2000l); + EXPECT_LT(st.sent_packets_queue_wait_time_us.GetMax(), 1); + EXPECT_TRUE(st.PacketsDiscardedNoReceiverSizeCounter().IsEmpty()); + EXPECT_EQ(dest_st.at(bob_ip).sent_packets_size.NumSamples(), 2000l); + EXPECT_EQ(dest_st.at(bob_ip).sent_packets_size.GetAverage(), + single_packet_size); + EXPECT_EQ(source_st.at(bob_ip).received_packets_size.NumSamples(), 2000l); + EXPECT_EQ(source_st.at(bob_ip).received_packets_size.GetAverage(), + single_packet_size); + EXPECT_TRUE( + source_st.at(bob_ip).packets_discarded_no_receiver_size.IsEmpty()); + + received_stats_count++; + }); + ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 1, + kStatsWaitTimeout.ms(), + *network_manager.time_controller()); +} + +TEST(NetworkEmulationManagerTest, ThroughputStats) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + + EmulatedNetworkNode* alice_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_manager.CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedEndpoint* alice_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + network_manager.CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_manager.CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* nt1 = + network_manager.CreateEmulatedNetworkManagerInterface({alice_endpoint}); + EmulatedNetworkManagerInterface* nt2 = + network_manager.CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + rtc::Thread* t1 = nt1->network_thread(); + rtc::Thread* t2 = nt2->network_thread(); + + constexpr int64_t kUdpPayloadSize = 100; + constexpr int64_t kSinglePacketSize = kUdpPayloadSize + kOverheadIpv4Udp; + rtc::CopyOnWriteBuffer data(kUdpPayloadSize); + + rtc::Socket* s1 = nullptr; + rtc::Socket* s2 = nullptr; + SendTask(t1, + [&] { s1 = t1->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); }); + SendTask(t2, + [&] { s2 = t2->socketserver()->CreateSocket(AF_INET, SOCK_DGRAM); }); + + SocketReader r1(s1, t1); + SocketReader r2(s2, t2); + + rtc::SocketAddress a1(alice_endpoint->GetPeerLocalAddress(), 0); + rtc::SocketAddress a2(bob_endpoint->GetPeerLocalAddress(), 0); + + SendTask(t1, [&] { + s1->Bind(a1); + a1 = s1->GetLocalAddress(); + }); + SendTask(t2, [&] { + s2->Bind(a2); + a2 = s2->GetLocalAddress(); + }); + + SendTask(t1, [&] { s1->Connect(a2); }); + SendTask(t2, [&] { s2->Connect(a1); }); + + // Send 11 packets, totalizing 1 second between the first and the last-> + const int kNumPacketsSent = 11; + const TimeDelta kDelay = TimeDelta::Millis(100); + for (int i = 0; i < kNumPacketsSent; i++) { + t1->PostTask([&]() { s1->Send(data.data(), data.size()); }); + t2->PostTask([&]() { s2->Send(data.data(), data.size()); }); + network_manager.time_controller()->AdvanceTime(kDelay); + } + + std::atomic<int> received_stats_count{0}; + nt1->GetStats([&](EmulatedNetworkStats st) { + EXPECT_EQ(st.PacketsSent(), kNumPacketsSent); + EXPECT_EQ(st.BytesSent().bytes(), kSinglePacketSize * kNumPacketsSent); + + const double tolerance = 0.95; // Accept 5% tolerance for timing. + EXPECT_GE(st.LastPacketSentTime() - st.FirstPacketSentTime(), + (kNumPacketsSent - 1) * kDelay * tolerance); + EXPECT_GT(st.AverageSendRate().bps(), 0); + received_stats_count++; + }); + + ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 1, + kStatsWaitTimeout.ms(), + *network_manager.time_controller()); + + EXPECT_EQ(r1.ReceivedCount(), 11); + EXPECT_EQ(r2.ReceivedCount(), 11); + + SendTask(t1, [&] { delete s1; }); + SendTask(t2, [&] { delete s2; }); +} + +// Testing that packets are delivered via all routes using a routing scheme as +// follows: +// * e1 -> n1 -> e2 +// * e2 -> n2 -> e1 +// * e1 -> n3 -> e3 +// * e3 -> n4 -> e1 +TEST_F(NetworkEmulationManagerThreeNodesRoutingTest, + PacketsAreDeliveredInBothWaysWhenConnectedToTwoPeers) { + SetupRouting([](EmulatedEndpoint* e1, EmulatedEndpoint* e2, + EmulatedEndpoint* e3, NetworkEmulationManager* emulation) { + auto* node1 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node2 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node3 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node4 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + + emulation->CreateRoute(e1, {node1}, e2); + emulation->CreateRoute(e2, {node2}, e1); + + emulation->CreateRoute(e1, {node3}, e3); + emulation->CreateRoute(e3, {node4}, e1); + }); + SendPacketsAndValidateDelivery(); +} + +// Testing that packets are delivered via all routes using a routing scheme as +// follows: +// * e1 -> n1 -> e2 +// * e2 -> n2 -> e1 +// * e1 -> n1 -> e3 +// * e3 -> n4 -> e1 +TEST_F(NetworkEmulationManagerThreeNodesRoutingTest, + PacketsAreDeliveredInBothWaysWhenConnectedToTwoPeersOverSameSendLink) { + SetupRouting([](EmulatedEndpoint* e1, EmulatedEndpoint* e2, + EmulatedEndpoint* e3, NetworkEmulationManager* emulation) { + auto* node1 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node2 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + auto* node3 = CreateEmulatedNodeWithDefaultBuiltInConfig(emulation); + + emulation->CreateRoute(e1, {node1}, e2); + emulation->CreateRoute(e2, {node2}, e1); + + emulation->CreateRoute(e1, {node1}, e3); + emulation->CreateRoute(e3, {node3}, e1); + }); + SendPacketsAndValidateDelivery(); +} + +TEST(NetworkEmulationManagerTest, EndpointLoopback) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + auto endpoint = network_manager.CreateEndpoint(EmulatedEndpointConfig()); + + MockReceiver receiver; + EXPECT_CALL(receiver, OnPacketReceived(::testing::_)).Times(1); + ASSERT_EQ(endpoint->BindReceiver(80, &receiver), 80); + + endpoint->SendPacket(rtc::SocketAddress(endpoint->GetPeerLocalAddress(), 80), + rtc::SocketAddress(endpoint->GetPeerLocalAddress(), 80), + "Hello"); + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(NetworkEmulationManagerTest, EndpointCanSendWithDifferentSourceIp) { + constexpr uint32_t kEndpointIp = 0xC0A80011; // 192.168.0.17 + constexpr uint32_t kSourceIp = 0xC0A80012; // 192.168.0.18 + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + EmulatedEndpointConfig endpoint_config; + endpoint_config.ip = rtc::IPAddress(kEndpointIp); + endpoint_config.allow_send_packet_with_different_source_ip = true; + auto endpoint = network_manager.CreateEndpoint(endpoint_config); + + MockReceiver receiver; + EXPECT_CALL(receiver, OnPacketReceived(::testing::_)).Times(1); + ASSERT_EQ(endpoint->BindReceiver(80, &receiver), 80); + + endpoint->SendPacket(rtc::SocketAddress(kSourceIp, 80), + rtc::SocketAddress(endpoint->GetPeerLocalAddress(), 80), + "Hello"); + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(NetworkEmulationManagerTest, + EndpointCanReceiveWithDifferentDestIpThroughDefaultRoute) { + constexpr uint32_t kDestEndpointIp = 0xC0A80011; // 192.168.0.17 + constexpr uint32_t kDestIp = 0xC0A80012; // 192.168.0.18 + NetworkEmulationManagerImpl network_manager( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + auto sender_endpoint = + network_manager.CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpointConfig endpoint_config; + endpoint_config.ip = rtc::IPAddress(kDestEndpointIp); + endpoint_config.allow_receive_packets_with_different_dest_ip = true; + auto receiver_endpoint = network_manager.CreateEndpoint(endpoint_config); + + MockReceiver receiver; + EXPECT_CALL(receiver, OnPacketReceived(::testing::_)).Times(1); + ASSERT_EQ(receiver_endpoint->BindReceiver(80, &receiver), 80); + + network_manager.CreateDefaultRoute( + sender_endpoint, {network_manager.NodeBuilder().Build().node}, + receiver_endpoint); + + sender_endpoint->SendPacket( + rtc::SocketAddress(sender_endpoint->GetPeerLocalAddress(), 80), + rtc::SocketAddress(kDestIp, 80), "Hello"); + network_manager.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +TEST(NetworkEmulationManagerTURNTest, GetIceServerConfig) { + NetworkEmulationManagerImpl network_manager( + TimeMode::kRealTime, EmulatedNetworkStatsGatheringMode::kDefault); + auto turn = network_manager.CreateTURNServer(EmulatedTURNServerConfig()); + + EXPECT_GT(turn->GetIceServerConfig().username.size(), 0u); + EXPECT_GT(turn->GetIceServerConfig().password.size(), 0u); + EXPECT_NE(turn->GetIceServerConfig().url.find( + turn->GetClientEndpoint()->GetPeerLocalAddress().ToString()), + std::string::npos); +} + +TEST(NetworkEmulationManagerTURNTest, ClientTraffic) { + NetworkEmulationManagerImpl emulation( + TimeMode::kSimulated, EmulatedNetworkStatsGatheringMode::kDefault); + auto* ep = emulation.CreateEndpoint(EmulatedEndpointConfig()); + auto* turn = emulation.CreateTURNServer(EmulatedTURNServerConfig()); + auto* node = CreateEmulatedNodeWithDefaultBuiltInConfig(&emulation); + emulation.CreateRoute(ep, {node}, turn->GetClientEndpoint()); + emulation.CreateRoute(turn->GetClientEndpoint(), {node}, ep); + + MockReceiver recv; + int port = ep->BindReceiver(0, &recv).value(); + + // Construct a STUN BINDING. + cricket::StunMessage ping(cricket::STUN_BINDING_REQUEST); + rtc::ByteBufferWriter buf; + ping.Write(&buf); + rtc::CopyOnWriteBuffer packet(buf.Data(), buf.Length()); + + // We expect to get a ping reply. + EXPECT_CALL(recv, OnPacketReceived(::testing::_)).Times(1); + + ep->SendPacket(rtc::SocketAddress(ep->GetPeerLocalAddress(), port), + turn->GetClientEndpointAddress(), packet); + emulation.time_controller()->AdvanceTime(TimeDelta::Seconds(1)); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/traffic_route.cc b/third_party/libwebrtc/test/network/traffic_route.cc new file mode 100644 index 0000000000..81bb8ca514 --- /dev/null +++ b/third_party/libwebrtc/test/network/traffic_route.cc @@ -0,0 +1,91 @@ +/* + * 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/network/traffic_route.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "absl/types/optional.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { +namespace test { +namespace { + +class NullReceiver : public EmulatedNetworkReceiverInterface { + public: + void OnPacketReceived(EmulatedIpPacket packet) override {} +}; + +class ActionReceiver : public EmulatedNetworkReceiverInterface { + public: + explicit ActionReceiver(std::function<void()> action) : action_(action) {} + ~ActionReceiver() override = default; + + void OnPacketReceived(EmulatedIpPacket packet) override { + action_(); + } + + private: + std::function<void()> action_; +}; + +} // namespace + +CrossTrafficRouteImpl::CrossTrafficRouteImpl( + Clock* clock, + EmulatedNetworkReceiverInterface* receiver, + EmulatedEndpointImpl* endpoint) + : clock_(clock), receiver_(receiver), endpoint_(endpoint) { + null_receiver_ = std::make_unique<NullReceiver>(); + absl::optional<uint16_t> port = + endpoint_->BindReceiver(0, null_receiver_.get()); + RTC_DCHECK(port); + null_receiver_port_ = port.value(); +} +CrossTrafficRouteImpl::~CrossTrafficRouteImpl() = default; + +void CrossTrafficRouteImpl::TriggerPacketBurst(size_t num_packets, + size_t packet_size) { + for (size_t i = 0; i < num_packets; ++i) { + SendPacket(packet_size); + } +} + +void CrossTrafficRouteImpl::NetworkDelayedAction(size_t packet_size, + std::function<void()> action) { + auto action_receiver = std::make_unique<ActionReceiver>(action); + // BindOneShotReceiver arranges to free the port in the endpoint after the + // action is done. + absl::optional<uint16_t> port = + endpoint_->BindOneShotReceiver(0, action_receiver.get()); + RTC_DCHECK(port); + actions_.push_back(std::move(action_receiver)); + SendPacket(packet_size, port.value()); +} + +void CrossTrafficRouteImpl::SendPacket(size_t packet_size) { + SendPacket(packet_size, null_receiver_port_); +} + +void CrossTrafficRouteImpl::SendPacket(size_t packet_size, uint16_t dest_port) { + rtc::CopyOnWriteBuffer data(packet_size); + std::fill_n(data.MutableData(), data.size(), 0); + receiver_->OnPacketReceived(EmulatedIpPacket( + /*from=*/rtc::SocketAddress(), + rtc::SocketAddress(endpoint_->GetPeerLocalAddress(), dest_port), data, + clock_->CurrentTime())); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/network/traffic_route.h b/third_party/libwebrtc/test/network/traffic_route.h new file mode 100644 index 0000000000..dbc41a694f --- /dev/null +++ b/third_party/libwebrtc/test/network/traffic_route.h @@ -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. + */ + +#ifndef TEST_NETWORK_TRAFFIC_ROUTE_H_ +#define TEST_NETWORK_TRAFFIC_ROUTE_H_ + +#include <memory> +#include <vector> + +#include "api/test/network_emulation_manager.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "system_wrappers/include/clock.h" +#include "test/network/network_emulation.h" + +namespace webrtc { +namespace test { + +// Represents the endpoint for cross traffic that is going through the network. +// It can be used to emulate unexpected network load. +class CrossTrafficRouteImpl final : public CrossTrafficRoute { + public: + CrossTrafficRouteImpl(Clock* clock, + EmulatedNetworkReceiverInterface* receiver, + EmulatedEndpointImpl* endpoint); + ~CrossTrafficRouteImpl(); + + // Triggers sending of dummy packets with size `packet_size` bytes. + void TriggerPacketBurst(size_t num_packets, size_t packet_size) override; + // Sends a packet over the nodes and runs `action` when it has been delivered. + void NetworkDelayedAction(size_t packet_size, + std::function<void()> action) override; + + void SendPacket(size_t packet_size) override; + + private: + void SendPacket(size_t packet_size, uint16_t dest_port); + + Clock* const clock_; + EmulatedNetworkReceiverInterface* const receiver_; + EmulatedEndpointImpl* const endpoint_; + + uint16_t null_receiver_port_; + std::unique_ptr<EmulatedNetworkReceiverInterface> null_receiver_; + std::vector<std::unique_ptr<EmulatedNetworkReceiverInterface>> actions_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_NETWORK_TRAFFIC_ROUTE_H_ diff --git a/third_party/libwebrtc/test/null_platform_renderer.cc b/third_party/libwebrtc/test/null_platform_renderer.cc new file mode 100644 index 0000000000..7ea604ead8 --- /dev/null +++ b/third_party/libwebrtc/test/null_platform_renderer.cc @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 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/video_renderer.h" + +namespace webrtc { +namespace test { + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + return NULL; +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/null_transport.cc b/third_party/libwebrtc/test/null_transport.cc new file mode 100644 index 0000000000..efbd9499d8 --- /dev/null +++ b/third_party/libwebrtc/test/null_transport.cc @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 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/null_transport.h" + +namespace webrtc { +namespace test { + +bool NullTransport::SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) { + return true; +} + +bool NullTransport::SendRtcp(const uint8_t* packet, size_t length) { + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/null_transport.h b/third_party/libwebrtc/test/null_transport.h new file mode 100644 index 0000000000..f264e7b45a --- /dev/null +++ b/third_party/libwebrtc/test/null_transport.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 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_NULL_TRANSPORT_H_ +#define TEST_NULL_TRANSPORT_H_ + +#include "api/call/transport.h" + +namespace webrtc { + +class PacketReceiver; + +namespace test { +class NullTransport : public Transport { + public: + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override; + bool SendRtcp(const uint8_t* packet, size_t length) override; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_NULL_TRANSPORT_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/BUILD.gn b/third_party/libwebrtc/test/pc/e2e/BUILD.gn new file mode 100644 index 0000000000..7354aa8ba4 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/BUILD.gn @@ -0,0 +1,573 @@ +# 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. + +import("../../../webrtc.gni") + +rtc_library("metric_metadata_keys") { + testonly = true + sources = [ "metric_metadata_keys.h" ] +} + +if (!build_with_chromium) { + group("e2e") { + testonly = true + + deps = [ ":metric_metadata_keys" ] + if (rtc_include_tests) { + deps += [ + ":peerconnection_quality_test", + ":test_peer", + ] + } + } + + if (rtc_include_tests) { + group("e2e_unittests") { + testonly = true + + deps = [ + ":peer_connection_e2e_smoke_test", + ":peer_connection_quality_test_metric_names_test", + ":peer_connection_quality_test_test", + ":stats_based_network_quality_metrics_reporter_test", + ":stats_poller_test", + ] + } + } + + if (rtc_include_tests) { + rtc_library("echo_emulation") { + testonly = true + sources = [ + "echo/echo_emulation.cc", + "echo/echo_emulation.h", + ] + deps = [ + "../../../api/test/pclf:media_configuration", + "../../../modules/audio_device:audio_device_impl", + "../../../rtc_base:swap_queue", + ] + } + + rtc_library("test_peer") { + testonly = true + sources = [ + "test_peer.cc", + "test_peer.h", + ] + deps = [ + ":stats_provider", + "../../../api:frame_generator_api", + "../../../api:function_view", + "../../../api:libjingle_peerconnection_api", + "../../../api:scoped_refptr", + "../../../api:sequence_checker", + "../../../api/task_queue:pending_task_safety_flag", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../modules/audio_processing:api", + "../../../pc:peerconnection_wrapper", + "../../../rtc_base:logging", + "../../../rtc_base:refcount", + "../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:variant", + ] + } + + rtc_library("test_peer_factory") { + testonly = true + sources = [ + "test_peer_factory.cc", + "test_peer_factory.h", + ] + deps = [ + ":echo_emulation", + ":test_peer", + "../..:copy_to_file_audio_capturer", + "../../../api:create_time_controller", + "../../../api:time_controller", + "../../../api/rtc_event_log:rtc_event_log_factory", + "../../../api/task_queue:default_task_queue_factory", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/transport:field_trial_based_config", + "../../../api/video_codecs:builtin_video_decoder_factory", + "../../../api/video_codecs:builtin_video_encoder_factory", + "../../../media:rtc_audio_video", + "../../../media:rtc_media_engine_defaults", + "../../../modules/audio_device:audio_device_impl", + "../../../modules/audio_processing/aec_dump", + "../../../p2p:rtc_p2p", + "../../../rtc_base:rtc_task_queue", + "../../../rtc_base:threading", + "analyzer/video:quality_analyzing_video_encoder", + "analyzer/video:video_quality_analyzer_injection_helper", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + ] + } + + rtc_library("media_helper") { + testonly = true + sources = [ + "media/media_helper.cc", + "media/media_helper.h", + "media/test_video_capturer_video_track_source.h", + ] + deps = [ + ":test_peer", + "../..:fileutils", + "../..:platform_video_capturer", + "../..:video_test_common", + "../../../api:create_frame_generator", + "../../../api:frame_generator_api", + "../../../api:media_stream_interface", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:peer_configurer", + "../../../api/video:video_frame", + "../../../pc:session_description", + "../../../pc:video_track_source", + "analyzer/video:video_quality_analyzer_injection_helper", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:variant" ] + } + + rtc_library("peer_params_preprocessor") { + visibility = [ "*" ] + testonly = true + sources = [ + "peer_params_preprocessor.cc", + "peer_params_preprocessor.h", + ] + deps = [ + "../..:fileutils", + "../../../api:peer_network_dependencies", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../modules/video_coding/svc:scalability_mode_util", + "../../../modules/video_coding/svc:scalability_structures", + "../../../rtc_base:macromagic", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + } + + rtc_library("test_activities_executor") { + testonly = true + sources = [ + "test_activities_executor.cc", + "test_activities_executor.h", + ] + deps = [ + "../../../api/task_queue", + "../../../api/units:time_delta", + "../../../api/units:timestamp", + "../../../rtc_base:checks", + "../../../rtc_base:criticalsection", + "../../../rtc_base:logging", + "../../../rtc_base:task_queue_for_test", + "../../../rtc_base/synchronization:mutex", + "../../../rtc_base/task_utils:repeating_task", + "../../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_library("peerconnection_quality_test") { + testonly = true + + sources = [ + "peer_connection_quality_test.cc", + "peer_connection_quality_test.h", + ] + deps = [ + ":analyzer_helper", + ":cross_media_metrics_reporter", + ":default_audio_quality_analyzer", + ":media_helper", + ":metric_metadata_keys", + ":peer_params_preprocessor", + ":sdp_changer", + ":stats_poller", + ":test_activities_executor", + ":test_peer", + ":test_peer_factory", + "../..:field_trial", + "../..:fileutils", + "../..:perf_test", + "../../../api:audio_quality_analyzer_api", + "../../../api:libjingle_peerconnection_api", + "../../../api:media_stream_interface", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api:rtc_event_log_output_file", + "../../../api:scoped_refptr", + "../../../api:time_controller", + "../../../api:video_quality_analyzer_api", + "../../../api/rtc_event_log", + "../../../api/task_queue", + "../../../api/test/metrics:metric", + "../../../api/test/metrics:metrics_logger", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/units:time_delta", + "../../../api/units:timestamp", + "../../../pc:pc_test_utils", + "../../../pc:sdp_utils", + "../../../rtc_base:gunit_helpers", + "../../../rtc_base:macromagic", + "../../../rtc_base:safe_conversions", + "../../../rtc_base:stringutils", + "../../../rtc_base:task_queue_for_test", + "../../../rtc_base:threading", + "../../../rtc_base/synchronization:mutex", + "../../../system_wrappers", + "../../../system_wrappers:field_trial", + "analyzer/video:default_video_quality_analyzer", + "analyzer/video:single_process_encoded_image_data_injector", + "analyzer/video:video_frame_tracking_id_injector", + "analyzer/video:video_quality_analyzer_injection_helper", + "analyzer/video:video_quality_metrics_reporter", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + } + + peer_connection_e2e_smoke_test_resources = [ + "../../../resources/pc_quality_smoke_test_alice_source.wav", + "../../../resources/pc_quality_smoke_test_bob_source.wav", + ] + if (is_ios) { + bundle_data("peer_connection_e2e_smoke_test_resources_bundle_data") { + testonly = true + sources = peer_connection_e2e_smoke_test_resources + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + } + } + + rtc_library("peer_connection_e2e_smoke_test") { + testonly = true + + sources = [ "peer_connection_e2e_smoke_test.cc" ] + deps = [ + ":default_audio_quality_analyzer", + ":network_quality_metrics_reporter", + ":stats_based_network_quality_metrics_reporter", + "../../../api:callfactory_api", + "../../../api:create_network_emulation_manager", + "../../../api:create_peer_connection_quality_test_frame_generator", + "../../../api:create_peerconnection_quality_test_fixture", + "../../../api:libjingle_peerconnection_api", + "../../../api:media_stream_interface", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api:scoped_refptr", + "../../../api:simulated_network_api", + "../../../api/audio_codecs:builtin_audio_decoder_factory", + "../../../api/audio_codecs:builtin_audio_encoder_factory", + "../../../api/test/metrics:global_metrics_logger_and_exporter", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/video_codecs:builtin_video_decoder_factory", + "../../../api/video_codecs:builtin_video_encoder_factory", + "../../../call:simulated_network", + "../../../media:rtc_audio_video", + "../../../modules/audio_device:audio_device_impl", + "../../../p2p:rtc_p2p", + "../../../pc:pc_test_utils", + "../../../pc:peerconnection_wrapper", + "../../../rtc_base:gunit_helpers", + "../../../rtc_base:logging", + "../../../rtc_base:rtc_event", + "../../../system_wrappers:field_trial", + "../../../test:field_trial", + "../../../test:fileutils", + "../../../test:test_support", + "analyzer/video:default_video_quality_analyzer", + "analyzer/video:default_video_quality_analyzer_shared", + ] + data = peer_connection_e2e_smoke_test_resources + if (is_mac || is_ios) { + deps += [ "../../../modules/video_coding:objc_codec_factory_helper" ] + } + if (is_ios) { + deps += [ ":peer_connection_e2e_smoke_test_resources_bundle_data" ] + } + } + + rtc_library("peer_connection_quality_test_metric_names_test") { + testonly = true + sources = [ "peer_connection_quality_test_metric_names_test.cc" ] + deps = [ + ":metric_metadata_keys", + ":peerconnection_quality_test", + ":stats_based_network_quality_metrics_reporter", + "../..:test_support", + "../../../api:create_network_emulation_manager", + "../../../api:create_peer_connection_quality_test_frame_generator", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api/test/metrics:metrics_logger", + "../../../api/test/metrics:stdout_metrics_exporter", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/units:time_delta", + ] + } + + rtc_library("stats_based_network_quality_metrics_reporter_test") { + testonly = true + sources = [ "stats_based_network_quality_metrics_reporter_test.cc" ] + deps = [ + ":metric_metadata_keys", + ":peerconnection_quality_test", + ":stats_based_network_quality_metrics_reporter", + "../..:test_support", + "../../../api:array_view", + "../../../api:create_network_emulation_manager", + "../../../api:create_peer_connection_quality_test_frame_generator", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api/test/metrics:metrics_logger", + "../../../api/test/metrics:stdout_metrics_exporter", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/units:time_delta", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_library("peer_connection_quality_test_test") { + testonly = true + sources = [ "peer_connection_quality_test_test.cc" ] + deps = [ + ":peerconnection_quality_test", + "../..:fileutils", + "../..:test_support", + "../..:video_test_support", + "../../../api:create_network_emulation_manager", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api/test/metrics:global_metrics_logger_and_exporter", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/units:time_delta", + "../../../rtc_base:timeutils", + ] + } + + rtc_library("stats_provider") { + testonly = true + sources = [ "stats_provider.h" ] + deps = [ "../../../api:rtc_stats_api" ] + } + + rtc_library("stats_poller") { + testonly = true + sources = [ + "stats_poller.cc", + "stats_poller.h", + ] + deps = [ + ":stats_provider", + ":test_peer", + "../../../api:libjingle_peerconnection_api", + "../../../api:rtc_stats_api", + "../../../api:stats_observer_interface", + "../../../rtc_base:logging", + "../../../rtc_base:macromagic", + "../../../rtc_base/synchronization:mutex", + ] + } + + rtc_library("stats_poller_test") { + testonly = true + sources = [ "stats_poller_test.cc" ] + deps = [ + ":stats_poller", + "../..:test_support", + "../../../api:rtc_stats_api", + ] + } + } + + rtc_library("analyzer_helper") { + sources = [ + "analyzer_helper.cc", + "analyzer_helper.h", + ] + deps = [ + "../../../api:sequence_checker", + "../../../api:track_id_stream_info_map", + "../../../rtc_base:macromagic", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_library("default_audio_quality_analyzer") { + testonly = true + sources = [ + "analyzer/audio/default_audio_quality_analyzer.cc", + "analyzer/audio/default_audio_quality_analyzer.h", + ] + + deps = [ + ":metric_metadata_keys", + "../..:perf_test", + "../../../api:audio_quality_analyzer_api", + "../../../api:rtc_stats_api", + "../../../api:stats_observer_interface", + "../../../api:track_id_stream_info_map", + "../../../api/numerics", + "../../../api/test/metrics:metric", + "../../../api/test/metrics:metrics_logger", + "../../../api/units:time_delta", + "../../../api/units:timestamp", + "../../../rtc_base:checks", + "../../../rtc_base:criticalsection", + "../../../rtc_base:logging", + "../../../rtc_base:rtc_numerics", + "../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + } + + rtc_library("network_quality_metrics_reporter") { + testonly = true + sources = [ + "network_quality_metrics_reporter.cc", + "network_quality_metrics_reporter.h", + ] + deps = [ + "../..:perf_test", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api:rtc_stats_api", + "../../../api:track_id_stream_info_map", + "../../../api/test/metrics:metric", + "../../../api/test/metrics:metrics_logger", + "../../../api/units:data_size", + "../../../rtc_base:checks", + "../../../rtc_base:criticalsection", + "../../../rtc_base:rtc_event", + "../../../rtc_base/synchronization:mutex", + "../../../system_wrappers:field_trial", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + } + + rtc_library("stats_based_network_quality_metrics_reporter") { + testonly = true + sources = [ + "stats_based_network_quality_metrics_reporter.cc", + "stats_based_network_quality_metrics_reporter.h", + ] + deps = [ + ":metric_metadata_keys", + "../..:perf_test", + "../../../api:array_view", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api:rtc_stats_api", + "../../../api:scoped_refptr", + "../../../api:sequence_checker", + "../../../api/numerics", + "../../../api/test/metrics:metric", + "../../../api/test/metrics:metrics_logger", + "../../../api/test/network_emulation", + "../../../api/units:data_rate", + "../../../api/units:data_size", + "../../../api/units:timestamp", + "../../../rtc_base:checks", + "../../../rtc_base:ip_address", + "../../../rtc_base:rtc_event", + "../../../rtc_base:stringutils", + "../../../rtc_base/synchronization:mutex", + "../../../rtc_base/system:no_unique_address", + "../../../system_wrappers:field_trial", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + } + + rtc_library("cross_media_metrics_reporter") { + testonly = true + sources = [ + "cross_media_metrics_reporter.cc", + "cross_media_metrics_reporter.h", + ] + deps = [ + ":metric_metadata_keys", + "../..:perf_test", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api:rtc_stats_api", + "../../../api:track_id_stream_info_map", + "../../../api/numerics", + "../../../api/test/metrics:metric", + "../../../api/test/metrics:metrics_logger", + "../../../api/units:timestamp", + "../../../rtc_base:checks", + "../../../rtc_base:criticalsection", + "../../../rtc_base:rtc_event", + "../../../rtc_base:rtc_numerics", + "../../../rtc_base/synchronization:mutex", + "../../../system_wrappers:field_trial", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_library("sdp_changer") { + testonly = true + sources = [ + "sdp/sdp_changer.cc", + "sdp/sdp_changer.h", + ] + deps = [ + "../../../api:array_view", + "../../../api:libjingle_peerconnection_api", + "../../../api:rtp_parameters", + "../../../api/test/pclf:media_configuration", + "../../../media:media_constants", + "../../../media:rid_description", + "../../../media:rtc_media_base", + "../../../p2p:rtc_p2p", + "../../../pc:sdp_utils", + "../../../pc:session_description", + "../../../pc:simulcast_description", + "../../../rtc_base:stringutils", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } +} diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc new file mode 100644 index 0000000000..98d0c533c2 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.cc @@ -0,0 +1,175 @@ +/* + * 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/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h" + +#include "api/stats/rtc_stats.h" +#include "api/stats/rtcstats_objects.h" +#include "api/test/metrics/metric.h" +#include "api/test/track_id_stream_info_map.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "test/pc/e2e/metric_metadata_keys.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; + +DefaultAudioQualityAnalyzer::DefaultAudioQualityAnalyzer( + test::MetricsLogger* const metrics_logger) + : metrics_logger_(metrics_logger) { + RTC_CHECK(metrics_logger_); +} + +void DefaultAudioQualityAnalyzer::Start(std::string test_case_name, + TrackIdStreamInfoMap* analyzer_helper) { + test_case_name_ = std::move(test_case_name); + analyzer_helper_ = analyzer_helper; +} + +void DefaultAudioQualityAnalyzer::OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) { + auto stats = report->GetStatsOfType<RTCInboundRTPStreamStats>(); + + for (auto& stat : stats) { + if (!stat->kind.is_defined() || + !(*stat->kind == RTCMediaStreamTrackKind::kAudio)) { + continue; + } + + StatsSample sample; + sample.total_samples_received = + stat->total_samples_received.ValueOrDefault(0ul); + sample.concealed_samples = stat->concealed_samples.ValueOrDefault(0ul); + sample.removed_samples_for_acceleration = + stat->removed_samples_for_acceleration.ValueOrDefault(0ul); + sample.inserted_samples_for_deceleration = + stat->inserted_samples_for_deceleration.ValueOrDefault(0ul); + sample.silent_concealed_samples = + stat->silent_concealed_samples.ValueOrDefault(0ul); + sample.jitter_buffer_delay = + TimeDelta::Seconds(stat->jitter_buffer_delay.ValueOrDefault(0.)); + sample.jitter_buffer_target_delay = + TimeDelta::Seconds(stat->jitter_buffer_target_delay.ValueOrDefault(0.)); + sample.jitter_buffer_emitted_count = + stat->jitter_buffer_emitted_count.ValueOrDefault(0ul); + + TrackIdStreamInfoMap::StreamInfo stream_info = + analyzer_helper_->GetStreamInfoFromTrackId(*stat->track_identifier); + + MutexLock lock(&lock_); + stream_info_.emplace(stream_info.stream_label, stream_info); + StatsSample prev_sample = last_stats_sample_[stream_info.stream_label]; + RTC_CHECK_GE(sample.total_samples_received, + prev_sample.total_samples_received); + double total_samples_diff = static_cast<double>( + sample.total_samples_received - prev_sample.total_samples_received); + if (total_samples_diff == 0) { + return; + } + + AudioStreamStats& audio_stream_stats = + streams_stats_[stream_info.stream_label]; + audio_stream_stats.expand_rate.AddSample( + (sample.concealed_samples - prev_sample.concealed_samples) / + total_samples_diff); + audio_stream_stats.accelerate_rate.AddSample( + (sample.removed_samples_for_acceleration - + prev_sample.removed_samples_for_acceleration) / + total_samples_diff); + audio_stream_stats.preemptive_rate.AddSample( + (sample.inserted_samples_for_deceleration - + prev_sample.inserted_samples_for_deceleration) / + total_samples_diff); + + int64_t speech_concealed_samples = + sample.concealed_samples - sample.silent_concealed_samples; + int64_t prev_speech_concealed_samples = + prev_sample.concealed_samples - prev_sample.silent_concealed_samples; + audio_stream_stats.speech_expand_rate.AddSample( + (speech_concealed_samples - prev_speech_concealed_samples) / + total_samples_diff); + + int64_t jitter_buffer_emitted_count_diff = + sample.jitter_buffer_emitted_count - + prev_sample.jitter_buffer_emitted_count; + if (jitter_buffer_emitted_count_diff > 0) { + TimeDelta jitter_buffer_delay_diff = + sample.jitter_buffer_delay - prev_sample.jitter_buffer_delay; + TimeDelta jitter_buffer_target_delay_diff = + sample.jitter_buffer_target_delay - + prev_sample.jitter_buffer_target_delay; + audio_stream_stats.average_jitter_buffer_delay_ms.AddSample( + jitter_buffer_delay_diff.ms<double>() / + jitter_buffer_emitted_count_diff); + audio_stream_stats.preferred_buffer_size_ms.AddSample( + jitter_buffer_target_delay_diff.ms<double>() / + jitter_buffer_emitted_count_diff); + } + + last_stats_sample_[stream_info.stream_label] = sample; + } +} + +std::string DefaultAudioQualityAnalyzer::GetTestCaseName( + const std::string& stream_label) const { + return test_case_name_ + "/" + stream_label; +} + +void DefaultAudioQualityAnalyzer::Stop() { + MutexLock lock(&lock_); + for (auto& item : streams_stats_) { + const TrackIdStreamInfoMap::StreamInfo& stream_info = + stream_info_[item.first]; + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> metric_metadata{ + {MetricMetadataKey::kAudioStreamMetadataKey, item.first}, + {MetricMetadataKey::kPeerMetadataKey, stream_info.receiver_peer}, + {MetricMetadataKey::kReceiverMetadataKey, stream_info.receiver_peer}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; + + metrics_logger_->LogMetric("expand_rate", GetTestCaseName(item.first), + item.second.expand_rate, Unit::kUnitless, + ImprovementDirection::kSmallerIsBetter, + metric_metadata); + metrics_logger_->LogMetric("accelerate_rate", GetTestCaseName(item.first), + item.second.accelerate_rate, Unit::kUnitless, + ImprovementDirection::kSmallerIsBetter, + metric_metadata); + metrics_logger_->LogMetric("preemptive_rate", GetTestCaseName(item.first), + item.second.preemptive_rate, Unit::kUnitless, + ImprovementDirection::kSmallerIsBetter, + metric_metadata); + metrics_logger_->LogMetric( + "speech_expand_rate", GetTestCaseName(item.first), + item.second.speech_expand_rate, Unit::kUnitless, + ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "average_jitter_buffer_delay_ms", GetTestCaseName(item.first), + item.second.average_jitter_buffer_delay_ms, Unit::kMilliseconds, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "preferred_buffer_size_ms", GetTestCaseName(item.first), + item.second.preferred_buffer_size_ms, Unit::kMilliseconds, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } +} + +std::map<std::string, AudioStreamStats> +DefaultAudioQualityAnalyzer::GetAudioStreamsStats() const { + MutexLock lock(&lock_); + return streams_stats_; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h b/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h new file mode 100644 index 0000000000..9e427afed8 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h @@ -0,0 +1,81 @@ +/* + * 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_PC_E2E_ANALYZER_AUDIO_DEFAULT_AUDIO_QUALITY_ANALYZER_H_ +#define TEST_PC_E2E_ANALYZER_AUDIO_DEFAULT_AUDIO_QUALITY_ANALYZER_H_ + +#include <map> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/audio_quality_analyzer_interface.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/track_id_stream_info_map.h" +#include "api/units/time_delta.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +struct AudioStreamStats { + SamplesStatsCounter expand_rate; + SamplesStatsCounter accelerate_rate; + SamplesStatsCounter preemptive_rate; + SamplesStatsCounter speech_expand_rate; + SamplesStatsCounter average_jitter_buffer_delay_ms; + SamplesStatsCounter preferred_buffer_size_ms; +}; + +class DefaultAudioQualityAnalyzer : public AudioQualityAnalyzerInterface { + public: + explicit DefaultAudioQualityAnalyzer( + test::MetricsLogger* const metrics_logger); + + void Start(std::string test_case_name, + TrackIdStreamInfoMap* analyzer_helper) override; + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + void Stop() override; + + // Returns audio quality stats per stream label. + std::map<std::string, AudioStreamStats> GetAudioStreamsStats() const; + + private: + struct StatsSample { + uint64_t total_samples_received = 0; + uint64_t concealed_samples = 0; + uint64_t removed_samples_for_acceleration = 0; + uint64_t inserted_samples_for_deceleration = 0; + uint64_t silent_concealed_samples = 0; + TimeDelta jitter_buffer_delay = TimeDelta::Zero(); + TimeDelta jitter_buffer_target_delay = TimeDelta::Zero(); + uint64_t jitter_buffer_emitted_count = 0; + }; + + std::string GetTestCaseName(const std::string& stream_label) const; + + test::MetricsLogger* const metrics_logger_; + + std::string test_case_name_; + TrackIdStreamInfoMap* analyzer_helper_; + + mutable Mutex lock_; + std::map<std::string, AudioStreamStats> streams_stats_ RTC_GUARDED_BY(lock_); + std::map<std::string, TrackIdStreamInfoMap::StreamInfo> stream_info_ + RTC_GUARDED_BY(lock_); + std::map<std::string, StatsSample> last_stats_sample_ RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_AUDIO_DEFAULT_AUDIO_QUALITY_ANALYZER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/BUILD.gn b/third_party/libwebrtc/test/pc/e2e/analyzer/video/BUILD.gn new file mode 100644 index 0000000000..cbb4c078f3 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/BUILD.gn @@ -0,0 +1,573 @@ +# 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. + +import("../../../../../webrtc.gni") + +if (!build_with_chromium) { + group("video_analyzer") { + testonly = true + + deps = [ + ":analyzing_video_sinks_helper", + ":default_video_quality_analyzer_internal", + ":encoded_image_data_injector_api", + ":example_video_quality_analyzer", + ":multi_reader_queue", + ":quality_analyzing_video_decoder", + ":quality_analyzing_video_encoder", + ":simulcast_dummy_buffer_helper", + ":single_process_encoded_image_data_injector", + ":video_dumping", + ":video_frame_tracking_id_injector", + ":video_quality_metrics_reporter", + ] + if (rtc_include_tests) { + deps += [ + ":analyzing_video_sink", + ":video_quality_analyzer_injection_helper", + ] + } + } + + if (rtc_include_tests) { + group("video_analyzer_unittests") { + testonly = true + + deps = [ + ":analyzing_video_sink_test", + ":analyzing_video_sinks_helper_test", + ":default_video_quality_analyzer_frames_comparator_test", + ":default_video_quality_analyzer_metric_names_test", + ":default_video_quality_analyzer_stream_state_test", + ":default_video_quality_analyzer_test", + ":multi_reader_queue_test", + ":names_collection_test", + ":simulcast_dummy_buffer_helper_test", + ":single_process_encoded_image_data_injector_unittest", + ":video_dumping_test", + ":video_frame_tracking_id_injector_unittest", + ] + } + } +} + +rtc_library("video_dumping") { + testonly = true + sources = [ + "video_dumping.cc", + "video_dumping.h", + ] + deps = [ + "../../../..:video_test_support", + "../../../../../api/test/video:video_frame_writer", + "../../../../../api/video:video_frame", + "../../../../../rtc_base:logging", + "../../../../../system_wrappers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] +} + +rtc_library("encoded_image_data_injector_api") { + testonly = true + sources = [ "encoded_image_data_injector.h" ] + + deps = [ "../../../../../api/video:encoded_image" ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("single_process_encoded_image_data_injector") { + testonly = true + sources = [ + "single_process_encoded_image_data_injector.cc", + "single_process_encoded_image_data_injector.h", + ] + + deps = [ + ":encoded_image_data_injector_api", + "../../../../../api/video:encoded_image", + "../../../../../rtc_base:checks", + "../../../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] +} + +rtc_library("video_frame_tracking_id_injector") { + testonly = true + sources = [ + "video_frame_tracking_id_injector.cc", + "video_frame_tracking_id_injector.h", + ] + + deps = [ + ":encoded_image_data_injector_api", + "../../../../../api/video:encoded_image", + "../../../../../rtc_base:checks", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] +} + +rtc_library("simulcast_dummy_buffer_helper") { + testonly = true + sources = [ + "simulcast_dummy_buffer_helper.cc", + "simulcast_dummy_buffer_helper.h", + ] + deps = [ "../../../../../api/video:video_frame" ] +} + +rtc_library("quality_analyzing_video_decoder") { + testonly = true + sources = [ + "quality_analyzing_video_decoder.cc", + "quality_analyzing_video_decoder.h", + ] + deps = [ + ":encoded_image_data_injector_api", + ":simulcast_dummy_buffer_helper", + "../../../../../api:video_quality_analyzer_api", + "../../../../../api/video:encoded_image", + "../../../../../api/video:video_frame", + "../../../../../api/video_codecs:video_codecs_api", + "../../../../../modules/video_coding:video_codec_interface", + "../../../../../rtc_base:logging", + "../../../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("quality_analyzing_video_encoder") { + testonly = true + sources = [ + "quality_analyzing_video_encoder.cc", + "quality_analyzing_video_encoder.h", + ] + deps = [ + ":encoded_image_data_injector_api", + "../../../../../api:video_quality_analyzer_api", + "../../../../../api/test/pclf:media_configuration", + "../../../../../api/video:video_frame", + "../../../../../api/video_codecs:video_codecs_api", + "../../../../../modules/video_coding:video_codec_interface", + "../../../../../modules/video_coding/svc:scalability_mode_util", + "../../../../../rtc_base:logging", + "../../../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] +} + +rtc_library("analyzing_video_sinks_helper") { + testonly = true + sources = [ + "analyzing_video_sinks_helper.cc", + "analyzing_video_sinks_helper.h", + ] + deps = [ + "../../../../../api/test/pclf:media_configuration", + "../../../../../api/test/video:video_frame_writer", + "../../../../../rtc_base:macromagic", + "../../../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("example_video_quality_analyzer") { + testonly = true + sources = [ + "example_video_quality_analyzer.cc", + "example_video_quality_analyzer.h", + ] + + deps = [ + "../../../../../api:array_view", + "../../../../../api:video_quality_analyzer_api", + "../../../../../api/video:encoded_image", + "../../../../../api/video:video_frame", + "../../../../../rtc_base:logging", + "../../../../../rtc_base/synchronization:mutex", + ] +} + +# This target contains implementation details of DefaultVideoQualityAnalyzer, +# so headers exported by it shouldn't be used in other places. +rtc_library("default_video_quality_analyzer_internal") { + visibility = [ + ":default_video_quality_analyzer", + ":default_video_quality_analyzer_frames_comparator_test", + ":default_video_quality_analyzer_stream_state_test", + ":names_collection_test", + ":video_analyzer", + ] + + testonly = true + sources = [ + "default_video_quality_analyzer_cpu_measurer.cc", + "default_video_quality_analyzer_cpu_measurer.h", + "default_video_quality_analyzer_frame_in_flight.cc", + "default_video_quality_analyzer_frame_in_flight.h", + "default_video_quality_analyzer_frames_comparator.cc", + "default_video_quality_analyzer_frames_comparator.h", + "default_video_quality_analyzer_internal_shared_objects.cc", + "default_video_quality_analyzer_internal_shared_objects.h", + "default_video_quality_analyzer_stream_state.cc", + "default_video_quality_analyzer_stream_state.h", + "names_collection.cc", + "names_collection.h", + ] + + deps = [ + ":default_video_quality_analyzer_shared", + ":multi_reader_queue", + "../..:metric_metadata_keys", + "../../../../../api:array_view", + "../../../../../api:scoped_refptr", + "../../../../../api/numerics", + "../../../../../api/units:data_size", + "../../../../../api/units:timestamp", + "../../../../../api/video:video_frame", + "../../../../../api/video:video_frame_type", + "../../../../../common_video", + "../../../../../rtc_base:checks", + "../../../../../rtc_base:platform_thread", + "../../../../../rtc_base:rtc_base_tests_utils", + "../../../../../rtc_base:rtc_event", + "../../../../../rtc_base:stringutils", + "../../../../../rtc_base:timeutils", + "../../../../../rtc_base/synchronization:mutex", + "../../../../../rtc_tools:video_quality_analysis", + "../../../../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("multi_reader_queue") { + testonly = true + sources = [ "multi_reader_queue.h" ] + deps = [ "../../../../../rtc_base:checks" ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("video_quality_metrics_reporter") { + testonly = true + sources = [ + "video_quality_metrics_reporter.cc", + "video_quality_metrics_reporter.h", + ] + deps = [ + "../..:metric_metadata_keys", + "../../../../../api:peer_connection_quality_test_fixture_api", + "../../../../../api:rtc_stats_api", + "../../../../../api:track_id_stream_info_map", + "../../../../../api/numerics", + "../../../../../api/test/metrics:metric", + "../../../../../api/test/metrics:metrics_logger", + "../../../../../api/units:data_rate", + "../../../../../api/units:data_size", + "../../../../../api/units:time_delta", + "../../../../../api/units:timestamp", + "../../../../../rtc_base:checks", + "../../../../../rtc_base/synchronization:mutex", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] +} + +rtc_library("default_video_quality_analyzer") { + testonly = true + sources = [ + "default_video_quality_analyzer.cc", + "default_video_quality_analyzer.h", + ] + + deps = [ + ":default_video_quality_analyzer_internal", + ":default_video_quality_analyzer_shared", + "../..:metric_metadata_keys", + "../../../../../api:array_view", + "../../../../../api:video_quality_analyzer_api", + "../../../../../api/numerics", + "../../../../../api/test/metrics:metric", + "../../../../../api/test/metrics:metrics_logger", + "../../../../../api/units:data_size", + "../../../../../api/units:time_delta", + "../../../../../api/units:timestamp", + "../../../../../api/video:encoded_image", + "../../../../../api/video:video_frame", + "../../../../../rtc_base:checks", + "../../../../../rtc_base:logging", + "../../../../../rtc_base:macromagic", + "../../../../../rtc_base:stringutils", + "../../../../../rtc_base/synchronization:mutex", + "../../../../../system_wrappers", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("default_video_quality_analyzer_shared") { + testonly = true + sources = [ + "default_video_quality_analyzer_shared_objects.cc", + "default_video_quality_analyzer_shared_objects.h", + ] + + deps = [ + "../../../../../api/numerics", + "../../../../../api/units:timestamp", + "../../../../../rtc_base:checks", + "../../../../../rtc_base:stringutils", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] +} + +rtc_library("analyzing_video_sink") { + testonly = true + sources = [ + "analyzing_video_sink.cc", + "analyzing_video_sink.h", + ] + deps = [ + ":analyzing_video_sinks_helper", + ":simulcast_dummy_buffer_helper", + ":video_dumping", + "../../../..:fixed_fps_video_frame_writer_adapter", + "../../../..:test_renderer", + "../../../../../api:video_quality_analyzer_api", + "../../../../../api/numerics", + "../../../../../api/test/pclf:media_configuration", + "../../../../../api/test/video:video_frame_writer", + "../../../../../api/units:timestamp", + "../../../../../api/video:video_frame", + "../../../../../rtc_base:checks", + "../../../../../rtc_base:logging", + "../../../../../rtc_base:macromagic", + "../../../../../rtc_base/synchronization:mutex", + "../../../../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory:memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("video_quality_analyzer_injection_helper") { + testonly = true + sources = [ + "video_quality_analyzer_injection_helper.cc", + "video_quality_analyzer_injection_helper.h", + ] + deps = [ + ":analyzing_video_sink", + ":analyzing_video_sinks_helper", + ":encoded_image_data_injector_api", + ":quality_analyzing_video_decoder", + ":quality_analyzing_video_encoder", + ":simulcast_dummy_buffer_helper", + ":video_dumping", + "../../../..:fixed_fps_video_frame_writer_adapter", + "../../../..:test_renderer", + "../../../..:video_test_common", + "../../../..:video_test_support", + "../../../../../api:array_view", + "../../../../../api:stats_observer_interface", + "../../../../../api:video_quality_analyzer_api", + "../../../../../api/test/pclf:media_configuration", + "../../../../../api/video:video_frame", + "../../../../../api/video_codecs:video_codecs_api", + "../../../../../rtc_base:checks", + "../../../../../rtc_base:logging", + "../../../../../rtc_base:stringutils", + "../../../../../rtc_base/synchronization:mutex", + "../../../../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + ] +} + +if (rtc_include_tests) { + rtc_library("simulcast_dummy_buffer_helper_test") { + testonly = true + sources = [ "simulcast_dummy_buffer_helper_test.cc" ] + deps = [ + ":simulcast_dummy_buffer_helper", + "../../../..:test_support", + "../../../../../api/video:video_frame", + "../../../../../rtc_base:random", + ] + } + + rtc_library("analyzing_video_sink_test") { + testonly = true + sources = [ "analyzing_video_sink_test.cc" ] + deps = [ + ":analyzing_video_sink", + ":example_video_quality_analyzer", + "../../../..:fileutils", + "../../../..:test_support", + "../../../..:video_test_support", + "../../../../../api:create_frame_generator", + "../../../../../api:frame_generator_api", + "../../../../../api:scoped_refptr", + "../../../../../api/test/pclf:media_configuration", + "../../../../../api/units:time_delta", + "../../../../../api/units:timestamp", + "../../../../../api/video:video_frame", + "../../../../../common_video", + "../../../../../rtc_base:timeutils", + "../../../../../system_wrappers", + "../../../../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_library("analyzing_video_sinks_helper_test") { + testonly = true + sources = [ "analyzing_video_sinks_helper_test.cc" ] + deps = [ + ":analyzing_video_sinks_helper", + "../../../..:test_support", + "../../../../../api/test/pclf:media_configuration", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + } + + rtc_library("default_video_quality_analyzer_frames_comparator_test") { + testonly = true + sources = [ "default_video_quality_analyzer_frames_comparator_test.cc" ] + deps = [ + ":default_video_quality_analyzer_internal", + ":default_video_quality_analyzer_shared", + "../../../..:test_support", + "../../../../../api:create_frame_generator", + "../../../../../api/units:timestamp", + "../../../../../rtc_base:stringutils", + "../../../../../system_wrappers", + ] + } + + rtc_library("names_collection_test") { + testonly = true + sources = [ "names_collection_test.cc" ] + deps = [ + ":default_video_quality_analyzer_internal", + "../../../..:test_support", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_library("multi_reader_queue_test") { + testonly = true + sources = [ "multi_reader_queue_test.cc" ] + deps = [ + ":multi_reader_queue", + "../../../..:test_support", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + } + + rtc_library("default_video_quality_analyzer_stream_state_test") { + testonly = true + sources = [ "default_video_quality_analyzer_stream_state_test.cc" ] + deps = [ + ":default_video_quality_analyzer_internal", + "../../../..:test_support", + "../../../../../api/units:timestamp", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + } + + rtc_library("default_video_quality_analyzer_test") { + testonly = true + sources = [ "default_video_quality_analyzer_test.cc" ] + deps = [ + ":default_video_quality_analyzer", + ":default_video_quality_analyzer_shared", + "../../../..:test_support", + "../../../../../api:create_frame_generator", + "../../../../../api:rtp_packet_info", + "../../../../../api/test/metrics:global_metrics_logger_and_exporter", + "../../../../../api/video:encoded_image", + "../../../../../api/video:video_frame", + "../../../../../common_video", + "../../../../../rtc_base:stringutils", + "../../../../../rtc_tools:video_quality_analysis", + "../../../../../system_wrappers", + ] + } + + rtc_library("default_video_quality_analyzer_metric_names_test") { + testonly = true + sources = [ "default_video_quality_analyzer_metric_names_test.cc" ] + deps = [ + ":default_video_quality_analyzer", + "../../../..:test_support", + "../../../../../api:create_frame_generator", + "../../../../../api:rtp_packet_info", + "../../../../../api/test/metrics:metric", + "../../../../../api/test/metrics:metrics_logger", + "../../../../../api/test/metrics:stdout_metrics_exporter", + "../../../../../api/video:encoded_image", + "../../../../../api/video:video_frame", + "../../../../../common_video", + "../../../../../rtc_tools:video_quality_analysis", + "../../../../../system_wrappers", + ] + } + + rtc_library("video_dumping_test") { + testonly = true + sources = [ "video_dumping_test.cc" ] + deps = [ + ":video_dumping", + "../../../..:fileutils", + "../../../..:test_support", + "../../../..:video_test_support", + "../../../../../api:scoped_refptr", + "../../../../../api/video:video_frame", + "../../../../../rtc_base:random", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + } + + rtc_library("single_process_encoded_image_data_injector_unittest") { + testonly = true + sources = [ "single_process_encoded_image_data_injector_unittest.cc" ] + deps = [ + ":single_process_encoded_image_data_injector", + "../../../..:test_support", + "../../../../../api/video:encoded_image", + "../../../../../rtc_base:buffer", + ] + } + + rtc_library("video_frame_tracking_id_injector_unittest") { + testonly = true + sources = [ "video_frame_tracking_id_injector_unittest.cc" ] + deps = [ + ":video_frame_tracking_id_injector", + "../../../..:test_support", + "../../../../../api/video:encoded_image", + "../../../../../rtc_base:buffer", + ] + } +} diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink.cc new file mode 100644 index 0000000000..fb221e6797 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink.cc @@ -0,0 +1,220 @@ +/* + * 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/pc/e2e/analyzer/video/analyzing_video_sink.h" + +#include <memory> +#include <set> +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/video/video_frame_writer.h" +#include "api/units/timestamp.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h" +#include "test/pc/e2e/analyzer/video/video_dumping.h" +#include "test/testsupport/fixed_fps_video_frame_writer_adapter.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +AnalyzingVideoSink::AnalyzingVideoSink(absl::string_view peer_name, + Clock* clock, + VideoQualityAnalyzerInterface& analyzer, + AnalyzingVideoSinksHelper& sinks_helper, + const VideoSubscription& subscription, + bool report_infra_stats) + : peer_name_(peer_name), + report_infra_stats_(report_infra_stats), + clock_(clock), + analyzer_(&analyzer), + sinks_helper_(&sinks_helper), + subscription_(subscription) {} + +void AnalyzingVideoSink::UpdateSubscription( + const VideoSubscription& subscription) { + // For peers with changed resolutions we need to close current writers and + // open new ones. This is done by removing existing sinks, which will force + // creation of the new sinks when next frame will be received. + std::set<test::VideoFrameWriter*> writers_to_close; + { + MutexLock lock(&mutex_); + subscription_ = subscription; + for (auto it = stream_sinks_.cbegin(); it != stream_sinks_.cend();) { + absl::optional<VideoResolution> new_requested_resolution = + subscription_.GetResolutionForPeer(it->second.sender_peer_name); + if (!new_requested_resolution.has_value() || + (*new_requested_resolution != it->second.resolution)) { + RTC_LOG(LS_INFO) << peer_name_ << ": Subscribed resolution for stream " + << it->first << " from " << it->second.sender_peer_name + << " was updated from " + << it->second.resolution.ToString() << " to " + << new_requested_resolution->ToString() + << ". Repopulating all video sinks and recreating " + << "requested video writers"; + writers_to_close.insert(it->second.video_frame_writer); + it = stream_sinks_.erase(it); + } else { + ++it; + } + } + } + sinks_helper_->CloseAndRemoveVideoWriters(writers_to_close); +} + +void AnalyzingVideoSink::OnFrame(const VideoFrame& frame) { + if (IsDummyFrame(frame)) { + // This is dummy frame, so we don't need to process it further. + return; + } + + if (frame.id() == VideoFrame::kNotSetId) { + // If frame ID is unknown we can't get required render resolution, so pass + // to the analyzer in the actual resolution of the frame. + AnalyzeFrame(frame); + } else { + std::string stream_label = analyzer_->GetStreamLabel(frame.id()); + MutexLock lock(&mutex_); + Timestamp processing_started = clock_->CurrentTime(); + SinksDescriptor* sinks_descriptor = PopulateSinks(stream_label); + RTC_CHECK(sinks_descriptor != nullptr); + + VideoFrame scaled_frame = + ScaleVideoFrame(frame, sinks_descriptor->resolution); + AnalyzeFrame(scaled_frame); + for (auto& sink : sinks_descriptor->sinks) { + sink->OnFrame(scaled_frame); + } + Timestamp processing_finished = clock_->CurrentTime(); + + if (report_infra_stats_) { + stats_.analyzing_sink_processing_time_ms.AddSample( + (processing_finished - processing_started).ms<double>()); + } + } +} + +AnalyzingVideoSink::Stats AnalyzingVideoSink::stats() const { + MutexLock lock(&mutex_); + return stats_; +} + +VideoFrame AnalyzingVideoSink::ScaleVideoFrame( + const VideoFrame& frame, + const VideoResolution& required_resolution) { + Timestamp processing_started = clock_->CurrentTime(); + if (required_resolution.width() == static_cast<size_t>(frame.width()) && + required_resolution.height() == static_cast<size_t>(frame.height())) { + if (report_infra_stats_) { + stats_.scaling_tims_ms.AddSample( + (clock_->CurrentTime() - processing_started).ms<double>()); + } + return frame; + } + + // We allow some difference in the aspect ration because when decoder + // downscales video stream it may round up some dimensions to make them even, + // ex: 960x540 -> 480x270 -> 240x136 instead of 240x135. + RTC_CHECK_LE(std::abs(static_cast<double>(required_resolution.width()) / + required_resolution.height() - + static_cast<double>(frame.width()) / frame.height()), + 0.1) + << peer_name_ + << ": Received frame has too different aspect ratio compared to " + << "requested video resolution: required resolution=" + << required_resolution.ToString() + << "; actual resolution=" << frame.width() << "x" << frame.height(); + + rtc::scoped_refptr<I420Buffer> scaled_buffer(I420Buffer::Create( + required_resolution.width(), required_resolution.height())); + scaled_buffer->ScaleFrom(*frame.video_frame_buffer()->ToI420()); + + VideoFrame scaled_frame = frame; + scaled_frame.set_video_frame_buffer(scaled_buffer); + if (report_infra_stats_) { + stats_.scaling_tims_ms.AddSample( + (clock_->CurrentTime() - processing_started).ms<double>()); + } + return scaled_frame; +} + +void AnalyzingVideoSink::AnalyzeFrame(const VideoFrame& frame) { + VideoFrame frame_copy = frame; + frame_copy.set_video_frame_buffer( + I420Buffer::Copy(*frame.video_frame_buffer()->ToI420())); + analyzer_->OnFrameRendered(peer_name_, frame_copy); +} + +AnalyzingVideoSink::SinksDescriptor* AnalyzingVideoSink::PopulateSinks( + absl::string_view stream_label) { + // Fast pass: sinks already exists. + auto sinks_it = stream_sinks_.find(std::string(stream_label)); + if (sinks_it != stream_sinks_.end()) { + return &sinks_it->second; + } + + // Slow pass: we need to create and save sinks + absl::optional<std::pair<std::string, VideoConfig>> peer_and_config = + sinks_helper_->GetPeerAndConfig(stream_label); + RTC_CHECK(peer_and_config.has_value()) + << "No video config for stream " << stream_label; + const std::string& sender_peer_name = peer_and_config->first; + const VideoConfig& config = peer_and_config->second; + + absl::optional<VideoResolution> resolution = + subscription_.GetResolutionForPeer(sender_peer_name); + if (!resolution.has_value()) { + RTC_LOG(LS_ERROR) << peer_name_ << " received stream " << stream_label + << " from " << sender_peer_name + << " for which they were not subscribed"; + resolution = config.GetResolution(); + } + if (!resolution->IsRegular()) { + RTC_LOG(LS_ERROR) << peer_name_ << " received stream " << stream_label + << " from " << sender_peer_name + << " for which resolution wasn't resolved"; + resolution = config.GetResolution(); + } + + RTC_CHECK(resolution.has_value()); + + SinksDescriptor sinks_descriptor(sender_peer_name, *resolution); + if (config.output_dump_options.has_value()) { + std::unique_ptr<test::VideoFrameWriter> writer = + config.output_dump_options->CreateOutputDumpVideoFrameWriter( + stream_label, peer_name_, *resolution); + if (config.output_dump_use_fixed_framerate) { + writer = std::make_unique<test::FixedFpsVideoFrameWriterAdapter>( + resolution->fps(), clock_, std::move(writer)); + } + sinks_descriptor.sinks.push_back(std::make_unique<VideoWriter>( + writer.get(), config.output_dump_options->sampling_modulo())); + sinks_descriptor.video_frame_writer = + sinks_helper_->AddVideoWriter(std::move(writer)); + } + if (config.show_on_screen) { + sinks_descriptor.sinks.push_back( + absl::WrapUnique(test::VideoRenderer::Create( + (*config.stream_label + "-render").c_str(), resolution->width(), + resolution->height()))); + } + return &stream_sinks_.emplace(stream_label, std::move(sinks_descriptor)) + .first->second; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink.h new file mode 100644 index 0000000000..1834bbe469 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink.h @@ -0,0 +1,106 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINK_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINK_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/video/video_frame_writer.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" +#include "test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// A sink to inject video quality analyzer as a sink into WebRTC. +class AnalyzingVideoSink : public rtc::VideoSinkInterface<VideoFrame> { + public: + struct Stats { + // Time required to scale video frame to the requested rendered resolution. + // Collected only for frames with ID set and iff `report_infra_stats` is + // true. + SamplesStatsCounter scaling_tims_ms; + // Time required to process single video frame. Collected only for frames + // with ID set and iff `report_infra_stats` is true. + SamplesStatsCounter analyzing_sink_processing_time_ms; + }; + + AnalyzingVideoSink(absl::string_view peer_name, + Clock* clock, + VideoQualityAnalyzerInterface& analyzer, + AnalyzingVideoSinksHelper& sinks_helper, + const VideoSubscription& subscription, + bool report_infra_stats); + + // Updates subscription used by this peer to render received video. + void UpdateSubscription(const VideoSubscription& subscription); + + void OnFrame(const VideoFrame& frame) override; + + Stats stats() const; + + private: + struct SinksDescriptor { + SinksDescriptor(absl::string_view sender_peer_name, + const VideoResolution& resolution) + : sender_peer_name(sender_peer_name), resolution(resolution) {} + + // Required to be able to resolve resolutions on new subscription and + // understand if we need to recreate `video_frame_writer` and `sinks`. + std::string sender_peer_name; + // Resolution which was used to create `video_frame_writer` and `sinks`. + VideoResolution resolution; + + // Is set if dumping of output video was requested; + test::VideoFrameWriter* video_frame_writer = nullptr; + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks; + }; + + // Scales video frame to `required_resolution` if necessary. Crashes if video + // frame and `required_resolution` have different aspect ratio. + VideoFrame ScaleVideoFrame(const VideoFrame& frame, + const VideoResolution& required_resolution) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Creates full copy of the frame to free any frame owned internal buffers + // and passes created copy to analyzer. Uses `I420Buffer` to represent + // frame content. + void AnalyzeFrame(const VideoFrame& frame); + // Populates sink for specified stream and caches them in `stream_sinks_`. + SinksDescriptor* PopulateSinks(absl::string_view stream_label) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + const std::string peer_name_; + const bool report_infra_stats_; + Clock* const clock_; + VideoQualityAnalyzerInterface* const analyzer_; + AnalyzingVideoSinksHelper* const sinks_helper_; + + mutable Mutex mutex_; + VideoSubscription subscription_ RTC_GUARDED_BY(mutex_); + std::map<std::string, SinksDescriptor> stream_sinks_ RTC_GUARDED_BY(mutex_); + Stats stats_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINK_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink_test.cc new file mode 100644 index 0000000000..6cd89551ea --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sink_test.cc @@ -0,0 +1,598 @@ +/* + * 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/pc/e2e/analyzer/video/analyzing_video_sink.h" + +#include <stdio.h> + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/scoped_refptr.h" +#include "api/test/create_frame_generator.h" +#include "api/test/frame_generator_interface.h" +#include "api/test/pclf/media_configuration.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/pc/e2e/analyzer/video/example_video_quality_analyzer.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::Test; + +// Remove files and directories in a directory non-recursively. +void CleanDir(absl::string_view dir, size_t expected_output_files_count) { + absl::optional<std::vector<std::string>> dir_content = + test::ReadDirectory(dir); + if (expected_output_files_count == 0) { + ASSERT_TRUE(!dir_content.has_value() || dir_content->empty()) + << "Empty directory is expected"; + } else { + ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!"; + EXPECT_EQ(dir_content->size(), expected_output_files_count); + for (const auto& entry : *dir_content) { + if (test::DirExists(entry)) { + EXPECT_TRUE(test::RemoveDir(entry)) + << "Failed to remove sub directory: " << entry; + } else if (test::FileExists(entry)) { + EXPECT_TRUE(test::RemoveFile(entry)) + << "Failed to remove file: " << entry; + } else { + FAIL() << "Can't remove unknown file type: " << entry; + } + } + } + EXPECT_TRUE(test::RemoveDir(dir)) << "Failed to remove directory: " << dir; +} + +VideoFrame CreateFrame(test::FrameGeneratorInterface& frame_generator) { + test::FrameGeneratorInterface::VideoFrameData frame_data = + frame_generator.NextFrame(); + return VideoFrame::Builder() + .set_video_frame_buffer(frame_data.buffer) + .set_update_rect(frame_data.update_rect) + .build(); +} + +std::unique_ptr<test::FrameGeneratorInterface> CreateFrameGenerator( + size_t width, + size_t height) { + return test::CreateSquareFrameGenerator(width, height, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); +} + +void AssertFrameIdsAre(const std::string& filename, + std::vector<std::string> expected_ids) { + FILE* file = fopen(filename.c_str(), "r"); + ASSERT_TRUE(file != nullptr) << "Failed to open frame ids file: " << filename; + std::vector<std::string> actual_ids; + char buffer[8]; + while (fgets(buffer, sizeof buffer, file) != nullptr) { + std::string current_id(buffer); + EXPECT_GE(current_id.size(), 2lu) + << "Found invalid frame id: [" << current_id << "]"; + if (current_id.size() < 2) { + continue; + } + // Trim "\n" at the end. + actual_ids.push_back(current_id.substr(0, current_id.size() - 1)); + } + fclose(file); + EXPECT_THAT(actual_ids, ElementsAreArray(expected_ids)); +} + +class AnalyzingVideoSinkTest : public Test { + protected: + ~AnalyzingVideoSinkTest() override = default; + + void SetUp() override { + // Create an empty temporary directory for this test. + test_directory_ = test::JoinFilename( + test::OutputPath(), + "TestDir_AnalyzingVideoSinkTest_" + + std::string( + testing::UnitTest::GetInstance()->current_test_info()->name())); + test::CreateDir(test_directory_); + } + + void TearDown() override { + CleanDir(test_directory_, expected_output_files_count_); + } + + void ExpectOutputFilesCount(size_t count) { + expected_output_files_count_ = count; + } + + std::string test_directory_; + size_t expected_output_files_count_ = 0; +}; + +TEST_F(AnalyzingVideoSinkTest, VideoFramesAreDumpedCorrectly) { + VideoSubscription subscription; + subscription.SubscribeToPeer( + "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720, + /*fps=*/30); + video_config.output_dump_options = VideoDumpOptions(test_directory_); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/1280, /*height=*/720); + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + sink.OnFrame(frame); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1))); + + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(1)); + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Actual should be downscaled version of expected. + EXPECT_GT(ssim, 0.98); + EXPECT_GT(psnr, 38); + + ExpectOutputFilesCount(1); +} + +TEST_F(AnalyzingVideoSinkTest, + FallbackOnConfigResolutionIfNoSubscriptionProvided) { + VideoSubscription subscription; + VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240, + /*fps=*/30); + video_config.output_dump_options = VideoDumpOptions(test_directory_); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/320, /*height=*/240); + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + sink.OnFrame(frame); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1))); + + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(1)); + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Frames should be equal. + EXPECT_DOUBLE_EQ(ssim, 1.00); + EXPECT_DOUBLE_EQ(psnr, 48); + + ExpectOutputFilesCount(1); +} + +TEST_F(AnalyzingVideoSinkTest, + FallbackOnConfigResolutionIfNoSubscriptionIsNotResolved) { + VideoSubscription subscription; + subscription.SubscribeToAllPeers( + VideoResolution(VideoResolution::Spec::kMaxFromSender)); + VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240, + /*fps=*/30); + video_config.output_dump_options = VideoDumpOptions(test_directory_); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/320, /*height=*/240); + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + sink.OnFrame(frame); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1))); + + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(1)); + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Frames should be equal. + EXPECT_DOUBLE_EQ(ssim, 1.00); + EXPECT_DOUBLE_EQ(psnr, 48); + + ExpectOutputFilesCount(1); +} + +TEST_F(AnalyzingVideoSinkTest, + VideoFramesAreDumpedCorrectlyWhenSubscriptionChanged) { + VideoSubscription subscription_before; + subscription_before.SubscribeToPeer( + "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30)); + VideoSubscription subscription_after; + subscription_after.SubscribeToPeer( + "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720, + /*fps=*/30); + video_config.output_dump_options = VideoDumpOptions(test_directory_); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/1280, /*height=*/720); + VideoFrame frame_before = CreateFrame(*frame_generator); + frame_before.set_id( + analyzer.OnFrameCaptured("alice", "alice_video", frame_before)); + VideoFrame frame_after = CreateFrame(*frame_generator); + frame_after.set_id( + analyzer.OnFrameCaptured("alice", "alice_video", frame_after)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription_before, /*report_infra_stats=*/false); + sink.OnFrame(frame_before); + + sink.UpdateSubscription(subscription_after); + sink.OnFrame(frame_after); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2))); + + { + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_1280x720_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(1)); + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame_before.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Frames should be equal. + EXPECT_DOUBLE_EQ(ssim, 1.00); + EXPECT_DOUBLE_EQ(psnr, 48); + } + { + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(1)); + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame_after.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Actual should be downscaled version of expected. + EXPECT_GT(ssim, 0.98); + EXPECT_GT(psnr, 38); + } + + ExpectOutputFilesCount(2); +} + +TEST_F(AnalyzingVideoSinkTest, + VideoFramesAreDumpedCorrectlyWhenSubscriptionChangedOnTheSameOne) { + VideoSubscription subscription_before; + subscription_before.SubscribeToPeer( + "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30)); + VideoSubscription subscription_after; + subscription_after.SubscribeToPeer( + "alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360, + /*fps=*/30); + video_config.output_dump_options = VideoDumpOptions(test_directory_); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/640, /*height=*/360); + VideoFrame frame_before = CreateFrame(*frame_generator); + frame_before.set_id( + analyzer.OnFrameCaptured("alice", "alice_video", frame_before)); + VideoFrame frame_after = CreateFrame(*frame_generator); + frame_after.set_id( + analyzer.OnFrameCaptured("alice", "alice_video", frame_after)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription_before, /*report_infra_stats=*/false); + sink.OnFrame(frame_before); + + sink.UpdateSubscription(subscription_after); + sink.OnFrame(frame_after); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2))); + + { + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(2)); + // Read the first frame. + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame_before.video_frame_buffer()->ToI420(); + // Frames should be equal. + EXPECT_DOUBLE_EQ(I420SSIM(*expected_frame, *actual_frame), 1.00); + EXPECT_DOUBLE_EQ(I420PSNR(*expected_frame, *actual_frame), 48); + // Read the second frame. + actual_frame = frame_reader->PullFrame(); + expected_frame = frame_after.video_frame_buffer()->ToI420(); + // Frames should be equal. + EXPECT_DOUBLE_EQ(I420SSIM(*expected_frame, *actual_frame), 1.00); + EXPECT_DOUBLE_EQ(I420PSNR(*expected_frame, *actual_frame), 48); + } + + ExpectOutputFilesCount(1); +} + +TEST_F(AnalyzingVideoSinkTest, SmallDiviationsInAspectRationAreAllowed) { + VideoSubscription subscription; + subscription.SubscribeToPeer( + "alice", VideoResolution(/*width=*/480, /*height=*/270, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/480, /*height=*/270, + /*fps=*/30); + video_config.output_dump_options = VideoDumpOptions(test_directory_); + + ExampleVideoQualityAnalyzer analyzer; + // Generator produces downscaled frames with a bit different aspect ration. + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/240, /*height=*/136); + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + sink.OnFrame(frame); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1))); + + { + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_480x270_30.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(1)); + // Read the first frame. + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame.video_frame_buffer()->ToI420(); + // Actual frame is upscaled version of the expected. But because rendered + // resolution is equal to the actual frame size we need to upscale expected + // during comparison and then they have to be the same. + EXPECT_DOUBLE_EQ(I420SSIM(*actual_frame, *expected_frame), 1); + EXPECT_DOUBLE_EQ(I420PSNR(*actual_frame, *expected_frame), 48); + } + + ExpectOutputFilesCount(1); +} + +TEST_F(AnalyzingVideoSinkTest, VideoFramesIdsAreDumpedWhenRequested) { + VideoSubscription subscription; + subscription.SubscribeToPeer( + "alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240, + /*fps=*/30); + video_config.output_dump_options = + VideoDumpOptions(test_directory_, /*export_frame_ids=*/true); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/320, /*height=*/240); + + std::vector<std::string> expected_frame_ids; + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + for (int i = 0; i < 10; ++i) { + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + expected_frame_ids.push_back(std::to_string(frame.id())); + sink.OnFrame(frame); + } + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(10))); + + AssertFrameIdsAre( + test::JoinFilename(test_directory_, + "alice_video_bob_320x240_30.frame_ids.txt"), + expected_frame_ids); + + ExpectOutputFilesCount(2); +} + +TEST_F(AnalyzingVideoSinkTest, + VideoFramesAndIdsAreDumpedWithFixedFpsWhenRequested) { + GlobalSimulatedTimeController simulated_time(Timestamp::Seconds(100000)); + + VideoSubscription subscription; + subscription.SubscribeToPeer( + "alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/10)); + VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240, + /*fps=*/10); + video_config.output_dump_options = + VideoDumpOptions(test_directory_, /*export_frame_ids=*/true); + video_config.output_dump_use_fixed_framerate = true; + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/320, /*height=*/240); + + VideoFrame frame1 = CreateFrame(*frame_generator); + frame1.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame1)); + VideoFrame frame2 = CreateFrame(*frame_generator); + frame2.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame2)); + + { + // `helper` and `sink` has to be destroyed so all frames will be written + // to the disk. + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", simulated_time.GetClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + sink.OnFrame(frame1); + // Advance almost 1 second, so the first frame has to be repeated 9 time + // more. + simulated_time.AdvanceTime(TimeDelta::Millis(990)); + sink.OnFrame(frame2); + simulated_time.AdvanceTime(TimeDelta::Millis(100)); + } + + EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2))); + + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_320x240_10.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(11)); + for (int i = 0; i < 10; ++i) { + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame1.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Frames should be equal. + EXPECT_DOUBLE_EQ(ssim, 1.00); + EXPECT_DOUBLE_EQ(psnr, 48); + } + rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader->PullFrame(); + rtc::scoped_refptr<I420BufferInterface> expected_frame = + frame2.video_frame_buffer()->ToI420(); + double psnr = I420PSNR(*expected_frame, *actual_frame); + double ssim = I420SSIM(*expected_frame, *actual_frame); + // Frames should be equal. + EXPECT_DOUBLE_EQ(ssim, 1.00); + EXPECT_DOUBLE_EQ(psnr, 48); + + AssertFrameIdsAre( + test::JoinFilename(test_directory_, + "alice_video_bob_320x240_10.frame_ids.txt"), + {std::to_string(frame1.id()), std::to_string(frame1.id()), + std::to_string(frame1.id()), std::to_string(frame1.id()), + std::to_string(frame1.id()), std::to_string(frame1.id()), + std::to_string(frame1.id()), std::to_string(frame1.id()), + std::to_string(frame1.id()), std::to_string(frame1.id()), + std::to_string(frame2.id())}); + + ExpectOutputFilesCount(2); +} + +TEST_F(AnalyzingVideoSinkTest, InfraMetricsCollectedWhenRequested) { + VideoSubscription subscription; + subscription.SubscribeToPeer( + "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360, + /*fps=*/30); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/640, /*height=*/360); + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/true); + sink.OnFrame(frame); + + AnalyzingVideoSink::Stats stats = sink.stats(); + EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(1)); + EXPECT_THAT(stats.scaling_tims_ms.GetAverage(), Ge(0)); + EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(1)); + EXPECT_THAT(stats.analyzing_sink_processing_time_ms.GetAverage(), + Ge(stats.scaling_tims_ms.GetAverage())); + + ExpectOutputFilesCount(0); +} + +TEST_F(AnalyzingVideoSinkTest, InfraMetricsNotCollectedWhenNotRequested) { + VideoSubscription subscription; + subscription.SubscribeToPeer( + "alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30)); + VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360, + /*fps=*/30); + + ExampleVideoQualityAnalyzer analyzer; + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + CreateFrameGenerator(/*width=*/640, /*height=*/360); + VideoFrame frame = CreateFrame(*frame_generator); + frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame)); + + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", video_config); + AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper, + subscription, /*report_infra_stats=*/false); + sink.OnFrame(frame); + + AnalyzingVideoSink::Stats stats = sink.stats(); + EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(0)); + EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(0)); + + ExpectOutputFilesCount(0); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.cc new file mode 100644 index 0000000000..70dc4b00b5 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.cc @@ -0,0 +1,85 @@ +/* + * 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/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h" + +#include <memory> +#include <set> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/video/video_frame_writer.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +void AnalyzingVideoSinksHelper::AddConfig(absl::string_view sender_peer_name, + VideoConfig config) { + MutexLock lock(&mutex_); + auto it = video_configs_.find(*config.stream_label); + if (it == video_configs_.end()) { + std::string stream_label = *config.stream_label; + video_configs_.emplace( + std::move(stream_label), + std::pair{std::string(sender_peer_name), std::move(config)}); + } else { + it->second = std::pair{std::string(sender_peer_name), std::move(config)}; + } +} + +absl::optional<std::pair<std::string, VideoConfig>> +AnalyzingVideoSinksHelper::GetPeerAndConfig(absl::string_view stream_label) { + MutexLock lock(&mutex_); + auto it = video_configs_.find(std::string(stream_label)); + if (it == video_configs_.end()) { + return absl::nullopt; + } + return it->second; +} + +void AnalyzingVideoSinksHelper::RemoveConfig(absl::string_view stream_label) { + MutexLock lock(&mutex_); + video_configs_.erase(std::string(stream_label)); +} + +test::VideoFrameWriter* AnalyzingVideoSinksHelper::AddVideoWriter( + std::unique_ptr<test::VideoFrameWriter> video_writer) { + MutexLock lock(&mutex_); + test::VideoFrameWriter* out = video_writer.get(); + video_writers_.push_back(std::move(video_writer)); + return out; +} + +void AnalyzingVideoSinksHelper::CloseAndRemoveVideoWriters( + std::set<test::VideoFrameWriter*> writers_to_close) { + MutexLock lock(&mutex_); + for (auto it = video_writers_.cbegin(); it != video_writers_.cend();) { + if (writers_to_close.find(it->get()) != writers_to_close.end()) { + (*it)->Close(); + it = video_writers_.erase(it); + } else { + ++it; + } + } +} + +void AnalyzingVideoSinksHelper::Clear() { + MutexLock lock(&mutex_); + video_configs_.clear(); + for (const auto& video_writer : video_writers_) { + video_writer->Close(); + } + video_writers_.clear(); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h new file mode 100644 index 0000000000..5f38c5a40e --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h @@ -0,0 +1,73 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINKS_HELPER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINKS_HELPER_H_ + +#include <list> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/video/video_frame_writer.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Registry of known video configs and video writers. +// This class is thread safe. +class AnalyzingVideoSinksHelper { + public: + // Adds config in the registry. If config with such stream label was + // registered before, the new value will override the old one. + void AddConfig(absl::string_view sender_peer_name, VideoConfig config); + absl::optional<std::pair<std::string, VideoConfig>> GetPeerAndConfig( + absl::string_view stream_label); + // Removes video config for specified stream label. If there are no know video + // config for such stream label - does nothing. + void RemoveConfig(absl::string_view stream_label); + + // Takes ownership of the provided video writer. All video writers owned by + // this class will be closed during `AnalyzingVideoSinksHelper` destruction + // and guaranteed to be alive either until explicitly removed by + // `CloseAndRemoveVideoWriters` or until `AnalyzingVideoSinksHelper` is + // destroyed. + // + // Returns pointer to the added writer. Ownership is maintained by + // `AnalyzingVideoSinksHelper`. + test::VideoFrameWriter* AddVideoWriter( + std::unique_ptr<test::VideoFrameWriter> video_writer); + // For each provided `writers_to_close`, if it is known, will close and + // destroy it, otherwise does nothing with it. + void CloseAndRemoveVideoWriters( + std::set<test::VideoFrameWriter*> writers_to_close); + + // Removes all added configs and close and removes all added writers. + void Clear(); + + private: + Mutex mutex_; + std::map<std::string, std::pair<std::string, VideoConfig>> video_configs_ + RTC_GUARDED_BY(mutex_); + std::list<std::unique_ptr<test::VideoFrameWriter>> video_writers_ + RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINKS_HELPER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper_test.cc new file mode 100644 index 0000000000..1a820a5229 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/analyzing_video_sinks_helper_test.cc @@ -0,0 +1,160 @@ +/* + * 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/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h" + +#include <memory> +#include <string> +#include <utility> + +#include "absl/types/optional.h" +#include "api/test/pclf/media_configuration.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::Eq; + +// Asserts equality of the main fields of the video config. We don't compare +// the full config due to the lack of equality definition for a lot of subtypes. +void AssertConfigsAreEquals(const VideoConfig& actual, + const VideoConfig& expected) { + EXPECT_THAT(actual.stream_label, Eq(expected.stream_label)); + EXPECT_THAT(actual.width, Eq(expected.width)); + EXPECT_THAT(actual.height, Eq(expected.height)); + EXPECT_THAT(actual.fps, Eq(expected.fps)); +} + +TEST(AnalyzingVideoSinksHelperTest, ConfigsCanBeAdded) { + VideoConfig config("alice_video", /*width=*/1280, /*height=*/720, /*fps=*/30); + + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", config); + + absl::optional<std::pair<std::string, VideoConfig>> registred_config = + helper.GetPeerAndConfig("alice_video"); + ASSERT_TRUE(registred_config.has_value()); + EXPECT_THAT(registred_config->first, Eq("alice")); + AssertConfigsAreEquals(registred_config->second, config); +} + +TEST(AnalyzingVideoSinksHelperTest, AddingForExistingLabelWillOverwriteValue) { + VideoConfig config_before("alice_video", /*width=*/1280, /*height=*/720, + /*fps=*/30); + VideoConfig config_after("alice_video", /*width=*/640, /*height=*/360, + /*fps=*/15); + + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", config_before); + + absl::optional<std::pair<std::string, VideoConfig>> registred_config = + helper.GetPeerAndConfig("alice_video"); + ASSERT_TRUE(registred_config.has_value()); + EXPECT_THAT(registred_config->first, Eq("alice")); + AssertConfigsAreEquals(registred_config->second, config_before); + + helper.AddConfig("alice", config_after); + + registred_config = helper.GetPeerAndConfig("alice_video"); + ASSERT_TRUE(registred_config.has_value()); + EXPECT_THAT(registred_config->first, Eq("alice")); + AssertConfigsAreEquals(registred_config->second, config_after); +} + +TEST(AnalyzingVideoSinksHelperTest, ConfigsCanBeRemoved) { + VideoConfig config("alice_video", /*width=*/1280, /*height=*/720, /*fps=*/30); + + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", config); + + ASSERT_TRUE(helper.GetPeerAndConfig("alice_video").has_value()); + + helper.RemoveConfig("alice_video"); + ASSERT_FALSE(helper.GetPeerAndConfig("alice_video").has_value()); +} + +TEST(AnalyzingVideoSinksHelperTest, RemoveOfNonExistingConfigDontCrash) { + AnalyzingVideoSinksHelper helper; + helper.RemoveConfig("alice_video"); +} + +TEST(AnalyzingVideoSinksHelperTest, ClearRemovesAllConfigs) { + VideoConfig config1("alice_video", /*width=*/640, /*height=*/360, /*fps=*/30); + VideoConfig config2("bob_video", /*width=*/640, /*height=*/360, /*fps=*/30); + + AnalyzingVideoSinksHelper helper; + helper.AddConfig("alice", config1); + helper.AddConfig("bob", config2); + + ASSERT_TRUE(helper.GetPeerAndConfig("alice_video").has_value()); + ASSERT_TRUE(helper.GetPeerAndConfig("bob_video").has_value()); + + helper.Clear(); + ASSERT_FALSE(helper.GetPeerAndConfig("alice_video").has_value()); + ASSERT_FALSE(helper.GetPeerAndConfig("bob_video").has_value()); +} + +struct TestVideoFrameWriterFactory { + int closed_writers_count = 0; + int deleted_writers_count = 0; + + std::unique_ptr<test::VideoFrameWriter> CreateWriter() { + return std::make_unique<TestVideoFrameWriter>(this); + } + + private: + class TestVideoFrameWriter : public test::VideoFrameWriter { + public: + explicit TestVideoFrameWriter(TestVideoFrameWriterFactory* factory) + : factory_(factory) {} + ~TestVideoFrameWriter() override { factory_->deleted_writers_count++; } + + bool WriteFrame(const VideoFrame& frame) override { return true; } + + void Close() override { factory_->closed_writers_count++; } + + private: + TestVideoFrameWriterFactory* factory_; + }; +}; + +TEST(AnalyzingVideoSinksHelperTest, RemovingWritersCloseAndDestroyAllOfThem) { + TestVideoFrameWriterFactory factory; + + AnalyzingVideoSinksHelper helper; + test::VideoFrameWriter* writer1 = + helper.AddVideoWriter(factory.CreateWriter()); + test::VideoFrameWriter* writer2 = + helper.AddVideoWriter(factory.CreateWriter()); + + helper.CloseAndRemoveVideoWriters({writer1, writer2}); + + EXPECT_THAT(factory.closed_writers_count, Eq(2)); + EXPECT_THAT(factory.deleted_writers_count, Eq(2)); +} + +TEST(AnalyzingVideoSinksHelperTest, ClearCloseAndDestroyAllWriters) { + TestVideoFrameWriterFactory factory; + + AnalyzingVideoSinksHelper helper; + helper.AddVideoWriter(factory.CreateWriter()); + helper.AddVideoWriter(factory.CreateWriter()); + + helper.Clear(); + + EXPECT_THAT(factory.closed_writers_count, Eq(2)); + EXPECT_THAT(factory.deleted_writers_count, Eq(2)); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc new file mode 100644 index 0000000000..59144589fc --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc @@ -0,0 +1,1228 @@ +/* + * 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/pc/e2e/analyzer/video/default_video_quality_analyzer.h" + +#include <algorithm> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metric.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/strings/string_builder.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h" +#include "test/pc/e2e/metric_metadata_keys.h" + +namespace webrtc { +namespace { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; +using ::webrtc::webrtc_pc_e2e::MetricMetadataKey; + +constexpr int kBitsInByte = 8; +constexpr absl::string_view kSkipRenderedFrameReasonProcessed = "processed"; +constexpr absl::string_view kSkipRenderedFrameReasonRendered = "rendered"; +constexpr absl::string_view kSkipRenderedFrameReasonDropped = + "considered dropped"; + +void LogFrameCounters(const std::string& name, const FrameCounters& counters) { + RTC_LOG(LS_INFO) << "[" << name + << "] Captured : " << counters.captured; + RTC_LOG(LS_INFO) << "[" << name + << "] Pre encoded : " << counters.pre_encoded; + RTC_LOG(LS_INFO) << "[" << name + << "] Encoded : " << counters.encoded; + RTC_LOG(LS_INFO) << "[" << name + << "] Received : " << counters.received; + RTC_LOG(LS_INFO) << "[" << name + << "] Decoded : " << counters.decoded; + RTC_LOG(LS_INFO) << "[" << name + << "] Rendered : " << counters.rendered; + RTC_LOG(LS_INFO) << "[" << name + << "] Dropped : " << counters.dropped; + RTC_LOG(LS_INFO) << "[" << name + << "] Failed to decode : " << counters.failed_to_decode; +} + +void LogStreamInternalStats(const std::string& name, + const StreamStats& stats, + Timestamp start_time) { + for (const auto& entry : stats.dropped_by_phase) { + RTC_LOG(LS_INFO) << "[" << name << "] Dropped at " << ToString(entry.first) + << ": " << entry.second; + } + Timestamp first_encoded_frame_time = Timestamp::PlusInfinity(); + for (const StreamCodecInfo& encoder : stats.encoders) { + RTC_DCHECK(encoder.switched_on_at.IsFinite()); + RTC_DCHECK(encoder.switched_from_at.IsFinite()); + if (first_encoded_frame_time.IsInfinite()) { + first_encoded_frame_time = encoder.switched_on_at; + } + RTC_LOG(LS_INFO) + << "[" << name << "] Used encoder: \"" << encoder.codec_name + << "\" used from (frame_id=" << encoder.first_frame_id + << "; from_stream_start=" + << (encoder.switched_on_at - stats.stream_started_time).ms() + << "ms, from_call_start=" << (encoder.switched_on_at - start_time).ms() + << "ms) until (frame_id=" << encoder.last_frame_id + << "; from_stream_start=" + << (encoder.switched_from_at - stats.stream_started_time).ms() + << "ms, from_call_start=" + << (encoder.switched_from_at - start_time).ms() << "ms)"; + } + for (const StreamCodecInfo& decoder : stats.decoders) { + RTC_DCHECK(decoder.switched_on_at.IsFinite()); + RTC_DCHECK(decoder.switched_from_at.IsFinite()); + RTC_LOG(LS_INFO) + << "[" << name << "] Used decoder: \"" << decoder.codec_name + << "\" used from (frame_id=" << decoder.first_frame_id + << "; from_stream_start=" + << (decoder.switched_on_at - stats.stream_started_time).ms() + << "ms, from_call_start=" << (decoder.switched_on_at - start_time).ms() + << "ms) until (frame_id=" << decoder.last_frame_id + << "; from_stream_start=" + << (decoder.switched_from_at - stats.stream_started_time).ms() + << "ms, from_call_start=" + << (decoder.switched_from_at - start_time).ms() << "ms)"; + } +} + +template <typename T> +absl::optional<T> MaybeGetValue(const std::map<size_t, T>& map, size_t key) { + auto it = map.find(key); + if (it == map.end()) { + return absl::nullopt; + } + return it->second; +} + +SamplesStatsCounter::StatsSample StatsSample(double value, + Timestamp sampling_time) { + return SamplesStatsCounter::StatsSample{value, sampling_time}; +} + +} // namespace + +DefaultVideoQualityAnalyzer::DefaultVideoQualityAnalyzer( + webrtc::Clock* clock, + test::MetricsLogger* metrics_logger, + DefaultVideoQualityAnalyzerOptions options) + : options_(options), + clock_(clock), + metrics_logger_(metrics_logger), + frames_comparator_(clock, cpu_measurer_, options) { + RTC_CHECK(metrics_logger_); +} + +DefaultVideoQualityAnalyzer::~DefaultVideoQualityAnalyzer() { + Stop(); +} + +void DefaultVideoQualityAnalyzer::Start( + std::string test_case_name, + rtc::ArrayView<const std::string> peer_names, + int max_threads_count) { + test_label_ = std::move(test_case_name); + frames_comparator_.Start(max_threads_count); + { + MutexLock lock(&mutex_); + peers_ = std::make_unique<NamesCollection>(peer_names); + RTC_CHECK(start_time_.IsMinusInfinity()); + + RTC_CHECK_EQ(state_, State::kNew) + << "DefaultVideoQualityAnalyzer is already started"; + state_ = State::kActive; + start_time_ = Now(); + } +} + +uint16_t DefaultVideoQualityAnalyzer::OnFrameCaptured( + absl::string_view peer_name, + const std::string& stream_label, + const webrtc::VideoFrame& frame) { + // `next_frame_id` is atomic, so we needn't lock here. + Timestamp captured_time = Now(); + Timestamp start_time = Timestamp::MinusInfinity(); + size_t peer_index = -1; + size_t peers_count = -1; + size_t stream_index; + uint16_t frame_id = VideoFrame::kNotSetId; + { + MutexLock lock(&mutex_); + frame_id = GetNextFrameId(); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + // Create a local copy of `start_time_`, peer's index and total peers count + // to access it without holding a `mutex_` during access to + // `frames_comparator_`. + start_time = start_time_; + peer_index = peers_->index(peer_name); + peers_count = peers_->size(); + stream_index = streams_.AddIfAbsent(stream_label); + } + // Ensure stats for this stream exists. + frames_comparator_.EnsureStatsForStream(stream_index, peer_index, peers_count, + captured_time, start_time); + { + MutexLock lock(&mutex_); + stream_to_sender_[stream_index] = peer_index; + frame_counters_.captured++; + for (size_t i : peers_->GetAllIndexes()) { + if (i != peer_index || options_.enable_receive_own_stream) { + InternalStatsKey key(stream_index, peer_index, i); + stream_frame_counters_[key].captured++; + } + } + + std::set<size_t> frame_receivers_indexes = peers_->GetPresentIndexes(); + if (!options_.enable_receive_own_stream) { + frame_receivers_indexes.erase(peer_index); + } + + auto state_it = stream_states_.find(stream_index); + if (state_it == stream_states_.end()) { + stream_states_.emplace( + stream_index, + StreamState(peer_index, frame_receivers_indexes, captured_time)); + } + StreamState* state = &stream_states_.at(stream_index); + state->PushBack(frame_id); + // Update frames in flight info. + auto it = captured_frames_in_flight_.find(frame_id); + if (it != captured_frames_in_flight_.end()) { + // If we overflow uint16_t and hit previous frame id and this frame is + // still in flight, it means that this stream wasn't rendered for long + // time and we need to process existing frame as dropped. + for (size_t i : peers_->GetPresentIndexes()) { + if (i == peer_index && !options_.enable_receive_own_stream) { + continue; + } + + uint16_t oldest_frame_id = state->PopFront(i); + RTC_DCHECK_EQ(frame_id, oldest_frame_id); + frame_counters_.dropped++; + InternalStatsKey key(stream_index, peer_index, i); + stream_frame_counters_.at(key).dropped++; + + analyzer_stats_.frames_in_flight_left_count.AddSample( + StatsSample(captured_frames_in_flight_.size(), Now())); + frames_comparator_.AddComparison( + InternalStatsKey(stream_index, peer_index, i), + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, FrameComparisonType::kDroppedFrame, + it->second.GetStatsForPeer(i)); + } + + captured_frames_in_flight_.erase(it); + } + captured_frames_in_flight_.emplace( + frame_id, FrameInFlight(stream_index, frame, captured_time, + std::move(frame_receivers_indexes))); + // Set frame id on local copy of the frame + captured_frames_in_flight_.at(frame_id).SetFrameId(frame_id); + + // Update history stream<->frame mapping + for (auto it = stream_to_frame_id_history_.begin(); + it != stream_to_frame_id_history_.end(); ++it) { + it->second.erase(frame_id); + } + stream_to_frame_id_history_[stream_index].insert(frame_id); + stream_to_frame_id_full_history_[stream_index].push_back(frame_id); + + // If state has too many frames that are in flight => remove the oldest + // queued frame in order to avoid to use too much memory. + if (state->GetAliveFramesCount() > + options_.max_frames_in_flight_per_stream_count) { + uint16_t frame_id_to_remove = state->MarkNextAliveFrameAsDead(); + auto it = captured_frames_in_flight_.find(frame_id_to_remove); + RTC_CHECK(it != captured_frames_in_flight_.end()) + << "Frame with ID " << frame_id_to_remove + << " is expected to be in flight, but hasn't been found in " + << "|captured_frames_in_flight_|"; + bool is_removed = it->second.RemoveFrame(); + RTC_DCHECK(is_removed) + << "Invalid stream state: alive frame is removed already"; + } + if (options_.report_infra_metrics) { + analyzer_stats_.on_frame_captured_processing_time_ms.AddSample( + (Now() - captured_time).ms<double>()); + } + } + return frame_id; +} + +void DefaultVideoQualityAnalyzer::OnFramePreEncode( + absl::string_view peer_name, + const webrtc::VideoFrame& frame) { + Timestamp processing_started = Now(); + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + auto it = captured_frames_in_flight_.find(frame.id()); + RTC_CHECK(it != captured_frames_in_flight_.end()) + << "Frame id=" << frame.id() << " not found"; + FrameInFlight& frame_in_flight = it->second; + frame_counters_.pre_encoded++; + size_t peer_index = peers_->index(peer_name); + for (size_t i : peers_->GetAllIndexes()) { + if (i != peer_index || options_.enable_receive_own_stream) { + InternalStatsKey key(frame_in_flight.stream(), peer_index, i); + stream_frame_counters_.at(key).pre_encoded++; + } + } + frame_in_flight.SetPreEncodeTime(Now()); + + if (options_.report_infra_metrics) { + analyzer_stats_.on_frame_pre_encode_processing_time_ms.AddSample( + (Now() - processing_started).ms<double>()); + } +} + +void DefaultVideoQualityAnalyzer::OnFrameEncoded( + absl::string_view peer_name, + uint16_t frame_id, + const webrtc::EncodedImage& encoded_image, + const EncoderStats& stats, + bool discarded) { + if (discarded) + return; + + Timestamp processing_started = Now(); + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + auto it = captured_frames_in_flight_.find(frame_id); + if (it == captured_frames_in_flight_.end()) { + RTC_LOG(LS_WARNING) + << "The encoding of video frame with id [" << frame_id << "] for peer [" + << peer_name << "] finished after all receivers rendered this frame or " + << "were removed. It can be OK for simulcast/SVC if higher quality " + << "stream is not required or the last receiver was unregistered " + << "between encoding of different layers, but it may indicate an ERROR " + << "for singlecast or if it happens often."; + return; + } + FrameInFlight& frame_in_flight = it->second; + // For SVC we can receive multiple encoded images for one frame, so to cover + // all cases we have to pick the last encode time. + if (!frame_in_flight.HasEncodedTime()) { + // Increase counters only when we meet this frame first time. + frame_counters_.encoded++; + size_t peer_index = peers_->index(peer_name); + for (size_t i : peers_->GetAllIndexes()) { + if (i != peer_index || options_.enable_receive_own_stream) { + InternalStatsKey key(frame_in_flight.stream(), peer_index, i); + stream_frame_counters_.at(key).encoded++; + } + } + } + Timestamp now = Now(); + StreamCodecInfo used_encoder; + used_encoder.codec_name = stats.encoder_name; + used_encoder.first_frame_id = frame_id; + used_encoder.last_frame_id = frame_id; + used_encoder.switched_on_at = now; + used_encoder.switched_from_at = now; + frame_in_flight.OnFrameEncoded( + now, encoded_image._frameType, DataSize::Bytes(encoded_image.size()), + stats.target_encode_bitrate, encoded_image.SpatialIndex().value_or(0), + stats.qp, used_encoder); + + if (options_.report_infra_metrics) { + analyzer_stats_.on_frame_encoded_processing_time_ms.AddSample( + (Now() - processing_started).ms<double>()); + } +} + +void DefaultVideoQualityAnalyzer::OnFrameDropped( + absl::string_view peer_name, + webrtc::EncodedImageCallback::DropReason reason) { + // Here we do nothing, because we will see this drop on renderer side. +} + +void DefaultVideoQualityAnalyzer::OnFramePreDecode( + absl::string_view peer_name, + uint16_t frame_id, + const webrtc::EncodedImage& input_image) { + Timestamp processing_started = Now(); + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + size_t peer_index = peers_->index(peer_name); + + if (frame_id == VideoFrame::kNotSetId) { + frame_counters_.received++; + unknown_sender_frame_counters_[std::string(peer_name)].received++; + return; + } + + auto it = captured_frames_in_flight_.find(frame_id); + if (it == captured_frames_in_flight_.end() || + it->second.HasReceivedTime(peer_index)) { + // It means this frame was predecoded before, so we can skip it. It may + // happen when we have multiple simulcast streams in one track and received + // the same picture from two different streams because SFU can't reliably + // correlate two simulcast streams and started relaying the second stream + // from the same frame it has relayed right before for the first stream. + return; + } + + frame_counters_.received++; + InternalStatsKey key(it->second.stream(), + stream_to_sender_.at(it->second.stream()), peer_index); + stream_frame_counters_.at(key).received++; + // Determine the time of the last received packet of this video frame. + RTC_DCHECK(!input_image.PacketInfos().empty()); + Timestamp last_receive_time = + std::max_element(input_image.PacketInfos().cbegin(), + input_image.PacketInfos().cend(), + [](const RtpPacketInfo& a, const RtpPacketInfo& b) { + return a.receive_time() < b.receive_time(); + }) + ->receive_time(); + it->second.OnFramePreDecode(peer_index, + /*received_time=*/last_receive_time, + /*decode_start_time=*/Now(), + input_image._frameType, + DataSize::Bytes(input_image.size())); + + if (options_.report_infra_metrics) { + analyzer_stats_.on_frame_pre_decode_processing_time_ms.AddSample( + (Now() - processing_started).ms<double>()); + } +} + +void DefaultVideoQualityAnalyzer::OnFrameDecoded( + absl::string_view peer_name, + const webrtc::VideoFrame& frame, + const DecoderStats& stats) { + Timestamp processing_started = Now(); + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + size_t peer_index = peers_->index(peer_name); + + if (frame.id() == VideoFrame::kNotSetId) { + frame_counters_.decoded++; + unknown_sender_frame_counters_[std::string(peer_name)].decoded++; + return; + } + + auto it = captured_frames_in_flight_.find(frame.id()); + if (it == captured_frames_in_flight_.end() || + it->second.HasDecodeEndTime(peer_index)) { + // It means this frame was decoded before, so we can skip it. It may happen + // when we have multiple simulcast streams in one track and received + // the same frame from two different streams because SFU can't reliably + // correlate two simulcast streams and started relaying the second stream + // from the same frame it has relayed right before for the first stream. + return; + } + frame_counters_.decoded++; + InternalStatsKey key(it->second.stream(), + stream_to_sender_.at(it->second.stream()), peer_index); + stream_frame_counters_.at(key).decoded++; + Timestamp now = Now(); + StreamCodecInfo used_decoder; + used_decoder.codec_name = stats.decoder_name; + used_decoder.first_frame_id = frame.id(); + used_decoder.last_frame_id = frame.id(); + used_decoder.switched_on_at = now; + used_decoder.switched_from_at = now; + it->second.OnFrameDecoded(peer_index, now, frame.width(), frame.height(), + used_decoder); + + if (options_.report_infra_metrics) { + analyzer_stats_.on_frame_decoded_processing_time_ms.AddSample( + (Now() - processing_started).ms<double>()); + } +} + +void DefaultVideoQualityAnalyzer::OnFrameRendered( + absl::string_view peer_name, + const webrtc::VideoFrame& frame) { + Timestamp processing_started = Now(); + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + size_t peer_index = peers_->index(peer_name); + + if (frame.id() == VideoFrame::kNotSetId) { + frame_counters_.rendered++; + unknown_sender_frame_counters_[std::string(peer_name)].rendered++; + return; + } + + auto frame_it = captured_frames_in_flight_.find(frame.id()); + if (frame_it == captured_frames_in_flight_.end() || + frame_it->second.HasRenderedTime(peer_index) || + frame_it->second.IsDropped(peer_index)) { + // It means this frame was rendered or dropped before, so we can skip it. + // It may happen when we have multiple simulcast streams in one track and + // received the same frame from two different streams because SFU can't + // reliably correlate two simulcast streams and started relaying the second + // stream from the same frame it has relayed right before for the first + // stream. + absl::string_view reason = kSkipRenderedFrameReasonProcessed; + if (frame_it != captured_frames_in_flight_.end()) { + if (frame_it->second.HasRenderedTime(peer_index)) { + reason = kSkipRenderedFrameReasonRendered; + } else if (frame_it->second.IsDropped(peer_index)) { + reason = kSkipRenderedFrameReasonDropped; + } + } + RTC_LOG(LS_WARNING) + << "Peer " << peer_name + << "; Received frame out of order: received frame with id " + << frame.id() << " which was " << reason << " before"; + return; + } + + // Find corresponding captured frame. + FrameInFlight* frame_in_flight = &frame_it->second; + absl::optional<VideoFrame> captured_frame = frame_in_flight->frame(); + + const size_t stream_index = frame_in_flight->stream(); + StreamState* state = &stream_states_.at(stream_index); + const InternalStatsKey stats_key(stream_index, state->sender(), peer_index); + + // Update frames counters. + frame_counters_.rendered++; + stream_frame_counters_.at(stats_key).rendered++; + + // Update current frame stats. + frame_in_flight->OnFrameRendered(peer_index, Now()); + + // After we received frame here we need to check if there are any dropped + // frames between this one and last one, that was rendered for this video + // stream. + int dropped_count = 0; + while (!state->IsEmpty(peer_index) && + state->Front(peer_index) != frame.id()) { + dropped_count++; + uint16_t dropped_frame_id = state->PopFront(peer_index); + // Frame with id `dropped_frame_id` was dropped. We need: + // 1. Update global and stream frame counters + // 2. Extract corresponding frame from `captured_frames_in_flight_` + // 3. Send extracted frame to comparison with dropped=true + // 4. Cleanup dropped frame + frame_counters_.dropped++; + stream_frame_counters_.at(stats_key).dropped++; + + auto dropped_frame_it = captured_frames_in_flight_.find(dropped_frame_id); + RTC_DCHECK(dropped_frame_it != captured_frames_in_flight_.end()); + dropped_frame_it->second.MarkDropped(peer_index); + + analyzer_stats_.frames_in_flight_left_count.AddSample( + StatsSample(captured_frames_in_flight_.size(), Now())); + frames_comparator_.AddComparison( + stats_key, /*captured=*/absl::nullopt, /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, + dropped_frame_it->second.GetStatsForPeer(peer_index)); + + if (dropped_frame_it->second.HaveAllPeersReceived()) { + captured_frames_in_flight_.erase(dropped_frame_it); + } + } + RTC_DCHECK(!state->IsEmpty(peer_index)); + state->PopFront(peer_index); + + if (state->last_rendered_frame_time(peer_index)) { + frame_in_flight->SetPrevFrameRenderedTime( + peer_index, state->last_rendered_frame_time(peer_index).value()); + } + state->SetLastRenderedFrameTime(peer_index, + frame_in_flight->rendered_time(peer_index)); + analyzer_stats_.frames_in_flight_left_count.AddSample( + StatsSample(captured_frames_in_flight_.size(), Now())); + frames_comparator_.AddComparison( + stats_key, dropped_count, captured_frame, /*rendered=*/frame, + FrameComparisonType::kRegular, + frame_in_flight->GetStatsForPeer(peer_index)); + + if (frame_it->second.HaveAllPeersReceived()) { + captured_frames_in_flight_.erase(frame_it); + } + + if (options_.report_infra_metrics) { + analyzer_stats_.on_frame_rendered_processing_time_ms.AddSample( + (Now() - processing_started).ms<double>()); + } +} + +void DefaultVideoQualityAnalyzer::OnEncoderError( + absl::string_view peer_name, + const webrtc::VideoFrame& frame, + int32_t error_code) { + RTC_LOG(LS_ERROR) << "Encoder error for frame.id=" << frame.id() + << ", code=" << error_code; +} + +void DefaultVideoQualityAnalyzer::OnDecoderError(absl::string_view peer_name, + uint16_t frame_id, + int32_t error_code, + const DecoderStats& stats) { + RTC_LOG(LS_ERROR) << "Decoder error for frame_id=" << frame_id + << ", code=" << error_code; + + Timestamp processing_started = Now(); + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + size_t peer_index = peers_->index(peer_name); + + if (frame_id == VideoFrame::kNotSetId) { + frame_counters_.failed_to_decode++; + unknown_sender_frame_counters_[std::string(peer_name)].failed_to_decode++; + return; + } + + auto it = captured_frames_in_flight_.find(frame_id); + if (it == captured_frames_in_flight_.end() || + it->second.HasDecodeEndTime(peer_index)) { + // It means this frame was decoded before, so we can skip it. It may happen + // when we have multiple simulcast streams in one track and received + // the same frame from two different streams because SFU can't reliably + // correlate two simulcast streams and started relaying the second stream + // from the same frame it has relayed right before for the first stream. + return; + } + frame_counters_.failed_to_decode++; + InternalStatsKey key(it->second.stream(), + stream_to_sender_.at(it->second.stream()), peer_index); + stream_frame_counters_.at(key).failed_to_decode++; + Timestamp now = Now(); + StreamCodecInfo used_decoder; + used_decoder.codec_name = stats.decoder_name; + used_decoder.first_frame_id = frame_id; + used_decoder.last_frame_id = frame_id; + used_decoder.switched_on_at = now; + used_decoder.switched_from_at = now; + it->second.OnDecoderError(peer_index, used_decoder); + + if (options_.report_infra_metrics) { + analyzer_stats_.on_decoder_error_processing_time_ms.AddSample( + (Now() - processing_started).ms<double>()); + } +} + +void DefaultVideoQualityAnalyzer::RegisterParticipantInCall( + absl::string_view peer_name) { + MutexLock lock(&mutex_); + RTC_CHECK(!peers_->HasName(peer_name)); + size_t new_peer_index = peers_->AddIfAbsent(peer_name); + + // Ensure stats for receiving (for frames from other peers to this one) + // streams exists. Since in flight frames will be sent to the new peer + // as well. Sending stats (from this peer to others) will be added by + // DefaultVideoQualityAnalyzer::OnFrameCaptured. + std::vector<std::pair<InternalStatsKey, Timestamp>> stream_started_time; + for (auto [stream_index, sender_peer_index] : stream_to_sender_) { + InternalStatsKey key(stream_index, sender_peer_index, new_peer_index); + + // To initiate `FrameCounters` for the stream we should pick frame + // counters with the same stream index and the same sender's peer index + // and any receiver's peer index and copy from its sender side + // counters. + FrameCounters counters; + for (size_t i : peers_->GetPresentIndexes()) { + InternalStatsKey prototype_key(stream_index, sender_peer_index, i); + auto it = stream_frame_counters_.find(prototype_key); + if (it != stream_frame_counters_.end()) { + counters.captured = it->second.captured; + counters.pre_encoded = it->second.pre_encoded; + counters.encoded = it->second.encoded; + break; + } + } + // It may happen if we had only one peer before this method was invoked, + // then `counters` will be empty. In such case empty `counters` are ok. + stream_frame_counters_.insert({key, std::move(counters)}); + + stream_started_time.push_back( + {key, stream_states_.at(stream_index).stream_started_time()}); + } + frames_comparator_.RegisterParticipantInCall(stream_started_time, + start_time_); + // Ensure, that frames states are handled correctly + // (e.g. dropped frames tracking). + for (auto& [stream_index, stream_state] : stream_states_) { + stream_state.AddPeer(new_peer_index); + } + // Register new peer for every frame in flight. + // It is guaranteed, that no garbage FrameInFlight objects will stay in + // memory because of adding new peer. Even if the new peer won't receive the + // frame, the frame will be removed by OnFrameRendered after next frame comes + // for the new peer. It is important because FrameInFlight is a large object. + for (auto& [frame_id, frame_in_flight] : captured_frames_in_flight_) { + frame_in_flight.AddExpectedReceiver(new_peer_index); + } +} + +void DefaultVideoQualityAnalyzer::UnregisterParticipantInCall( + absl::string_view peer_name) { + MutexLock lock(&mutex_); + RTC_CHECK(peers_->HasName(peer_name)); + absl::optional<size_t> peer_index = peers_->RemoveIfPresent(peer_name); + RTC_CHECK(peer_index.has_value()); + + for (auto& [stream_index, stream_state] : stream_states_) { + if (!options_.enable_receive_own_stream && + peer_index == stream_state.sender()) { + continue; + } + + AddExistingFramesInFlightForStreamToComparator(stream_index, stream_state, + *peer_index); + + stream_state.RemovePeer(*peer_index); + } + + // Remove peer from every frame in flight. If we removed that last expected + // receiver for the frame, then we should removed this frame if it was + // already encoded. If frame wasn't encoded, it still will be used by sender + // side pipeline, so we can't delete it yet. + for (auto it = captured_frames_in_flight_.begin(); + it != captured_frames_in_flight_.end();) { + FrameInFlight& frame_in_flight = it->second; + frame_in_flight.RemoveExpectedReceiver(*peer_index); + // If frame was fully sent and all receivers received it, then erase it. + // It may happen that when we remove FrameInFlight only some Simulcast/SVC + // layers were encoded and frame has encoded time, but more layers might be + // encoded after removal. In such case it's safe to still remove a frame, + // because OnFrameEncoded method will correctly handle the case when there + // is no FrameInFlight for the received encoded image. + if (frame_in_flight.HasEncodedTime() && + frame_in_flight.HaveAllPeersReceived()) { + it = captured_frames_in_flight_.erase(it); + } else { + it++; + } + } +} + +void DefaultVideoQualityAnalyzer::Stop() { + std::map<InternalStatsKey, Timestamp> last_rendered_frame_times; + { + MutexLock lock(&mutex_); + if (state_ == State::kStopped) { + return; + } + RTC_CHECK_EQ(state_, State::kActive) + << "DefaultVideoQualityAnalyzer has to be started before use"; + + state_ = State::kStopped; + + // Add the amount of frames in flight to the analyzer stats before all left + // frames in flight will be sent to the `frames_compartor_`. + analyzer_stats_.frames_in_flight_left_count.AddSample( + StatsSample(captured_frames_in_flight_.size(), Now())); + + for (auto& state_entry : stream_states_) { + const size_t stream_index = state_entry.first; + StreamState& stream_state = state_entry.second; + + // Populate `last_rendered_frame_times` map for all peers that were met in + // call, not only for the currently presented ones. + for (size_t peer_index : peers_->GetAllIndexes()) { + if (peer_index == stream_state.sender() && + !options_.enable_receive_own_stream) { + continue; + } + + InternalStatsKey stats_key(stream_index, stream_state.sender(), + peer_index); + + // If there are no freezes in the call we have to report + // time_between_freezes_ms as call duration and in such case + // `stream_last_freeze_end_time` for this stream will be `start_time_`. + // If there is freeze, then we need add time from last rendered frame + // to last freeze end as time between freezes. + if (stream_state.last_rendered_frame_time(peer_index)) { + last_rendered_frame_times.emplace( + stats_key, + stream_state.last_rendered_frame_time(peer_index).value()); + } + } + + // Push left frame in flight for analysis for the peers that are still in + // the call. + for (size_t peer_index : peers_->GetPresentIndexes()) { + if (peer_index == stream_state.sender() && + !options_.enable_receive_own_stream) { + continue; + } + + AddExistingFramesInFlightForStreamToComparator( + stream_index, stream_state, peer_index); + } + } + } + frames_comparator_.Stop(last_rendered_frame_times); + + // Perform final Metrics update. On this place analyzer is stopped and no one + // holds any locks. + { + MutexLock lock(&mutex_); + FramesComparatorStats frames_comparator_stats = + frames_comparator_.frames_comparator_stats(); + analyzer_stats_.comparisons_queue_size = + std::move(frames_comparator_stats.comparisons_queue_size); + analyzer_stats_.comparisons_done = frames_comparator_stats.comparisons_done; + analyzer_stats_.cpu_overloaded_comparisons_done = + frames_comparator_stats.cpu_overloaded_comparisons_done; + analyzer_stats_.memory_overloaded_comparisons_done = + frames_comparator_stats.memory_overloaded_comparisons_done; + } + ReportResults(); +} + +std::string DefaultVideoQualityAnalyzer::GetStreamLabel(uint16_t frame_id) { + MutexLock lock1(&mutex_); + auto it = captured_frames_in_flight_.find(frame_id); + if (it != captured_frames_in_flight_.end()) { + return streams_.name(it->second.stream()); + } + for (auto hist_it = stream_to_frame_id_history_.begin(); + hist_it != stream_to_frame_id_history_.end(); ++hist_it) { + auto hist_set_it = hist_it->second.find(frame_id); + if (hist_set_it != hist_it->second.end()) { + return streams_.name(hist_it->first); + } + } + RTC_CHECK(false) << "Unknown frame_id=" << frame_id; +} + +std::set<StatsKey> DefaultVideoQualityAnalyzer::GetKnownVideoStreams() const { + MutexLock lock(&mutex_); + std::set<StatsKey> out; + for (auto& item : frames_comparator_.stream_stats()) { + RTC_LOG(LS_INFO) << item.first.ToString() << " ==> " + << ToStatsKey(item.first).ToString(); + out.insert(ToStatsKey(item.first)); + } + return out; +} + +VideoStreamsInfo DefaultVideoQualityAnalyzer::GetKnownStreams() const { + MutexLock lock(&mutex_); + std::map<std::string, std::string> stream_to_sender; + std::map<std::string, std::set<std::string>> sender_to_streams; + std::map<std::string, std::set<std::string>> stream_to_receivers; + + for (auto& item : frames_comparator_.stream_stats()) { + const std::string& stream_label = streams_.name(item.first.stream); + const std::string& sender = peers_->name(item.first.sender); + const std::string& receiver = peers_->name(item.first.receiver); + RTC_LOG(LS_INFO) << item.first.ToString() << " ==> " + << "stream=" << stream_label << "; sender=" << sender + << "; receiver=" << receiver; + stream_to_sender.emplace(stream_label, sender); + auto streams_it = sender_to_streams.find(sender); + if (streams_it != sender_to_streams.end()) { + streams_it->second.emplace(stream_label); + } else { + sender_to_streams.emplace(sender, std::set<std::string>{stream_label}); + } + auto receivers_it = stream_to_receivers.find(stream_label); + if (receivers_it != stream_to_receivers.end()) { + receivers_it->second.emplace(receiver); + } else { + stream_to_receivers.emplace(stream_label, + std::set<std::string>{receiver}); + } + } + + return VideoStreamsInfo(std::move(stream_to_sender), + std::move(sender_to_streams), + std::move(stream_to_receivers)); +} + +FrameCounters DefaultVideoQualityAnalyzer::GetGlobalCounters() const { + MutexLock lock(&mutex_); + return frame_counters_; +} + +std::map<std::string, FrameCounters> +DefaultVideoQualityAnalyzer::GetUnknownSenderFrameCounters() const { + MutexLock lock(&mutex_); + return unknown_sender_frame_counters_; +} + +std::map<StatsKey, FrameCounters> +DefaultVideoQualityAnalyzer::GetPerStreamCounters() const { + MutexLock lock(&mutex_); + std::map<StatsKey, FrameCounters> out; + for (auto& item : stream_frame_counters_) { + out.emplace(ToStatsKey(item.first), item.second); + } + return out; +} + +std::map<StatsKey, StreamStats> DefaultVideoQualityAnalyzer::GetStats() const { + MutexLock lock1(&mutex_); + std::map<StatsKey, StreamStats> out; + for (auto& item : frames_comparator_.stream_stats()) { + out.emplace(ToStatsKey(item.first), item.second); + } + return out; +} + +AnalyzerStats DefaultVideoQualityAnalyzer::GetAnalyzerStats() const { + MutexLock lock(&mutex_); + return analyzer_stats_; +} + +uint16_t DefaultVideoQualityAnalyzer::GetNextFrameId() { + uint16_t frame_id = next_frame_id_++; + if (next_frame_id_ == VideoFrame::kNotSetId) { + next_frame_id_ = 1; + } + return frame_id; +} + +void DefaultVideoQualityAnalyzer:: + AddExistingFramesInFlightForStreamToComparator(size_t stream_index, + StreamState& stream_state, + size_t peer_index) { + InternalStatsKey stats_key(stream_index, stream_state.sender(), peer_index); + + // Add frames in flight for this stream into frames comparator. + // Frames in flight were not rendered, so they won't affect stream's + // last rendered frame time. + while (!stream_state.IsEmpty(peer_index)) { + uint16_t frame_id = stream_state.PopFront(peer_index); + auto it = captured_frames_in_flight_.find(frame_id); + RTC_DCHECK(it != captured_frames_in_flight_.end()); + FrameInFlight& frame = it->second; + + frames_comparator_.AddComparison(stats_key, /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, + frame.GetStatsForPeer(peer_index)); + } +} + +void DefaultVideoQualityAnalyzer::ReportResults() { + MutexLock lock(&mutex_); + for (auto& item : frames_comparator_.stream_stats()) { + ReportResults(item.first, item.second, + stream_frame_counters_.at(item.first)); + } + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + metrics_logger_->LogSingleValueMetric( + "cpu_usage_%", test_label_, GetCpuUsagePercent(), Unit::kUnitless, + ImprovementDirection::kSmallerIsBetter, + {{MetricMetadataKey::kExperimentalTestNameMetadataKey, test_label_}}); + LogFrameCounters("Global", frame_counters_); + if (!unknown_sender_frame_counters_.empty()) { + RTC_LOG(LS_INFO) << "Received frame counters with unknown frame id:"; + for (const auto& [peer_name, frame_counters] : + unknown_sender_frame_counters_) { + LogFrameCounters(peer_name, frame_counters); + } + } + RTC_LOG(LS_INFO) << "Received frame counters per stream:"; + for (const auto& [stats_key, stream_stats] : + frames_comparator_.stream_stats()) { + LogFrameCounters(ToStatsKey(stats_key).ToString(), + stream_frame_counters_.at(stats_key)); + LogStreamInternalStats(ToStatsKey(stats_key).ToString(), stream_stats, + start_time_); + } + if (!analyzer_stats_.comparisons_queue_size.IsEmpty()) { + RTC_LOG(LS_INFO) << "comparisons_queue_size min=" + << analyzer_stats_.comparisons_queue_size.GetMin() + << "; max=" + << analyzer_stats_.comparisons_queue_size.GetMax() + << "; 99%=" + << analyzer_stats_.comparisons_queue_size.GetPercentile( + 0.99); + } + RTC_LOG(LS_INFO) << "comparisons_done=" << analyzer_stats_.comparisons_done; + RTC_LOG(LS_INFO) << "cpu_overloaded_comparisons_done=" + << analyzer_stats_.cpu_overloaded_comparisons_done; + RTC_LOG(LS_INFO) << "memory_overloaded_comparisons_done=" + << analyzer_stats_.memory_overloaded_comparisons_done; + if (options_.report_infra_metrics) { + metrics_logger_->LogMetric("comparisons_queue_size", test_label_, + analyzer_stats_.comparisons_queue_size, + Unit::kCount, + ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric("frames_in_flight_left_count", test_label_, + analyzer_stats_.frames_in_flight_left_count, + Unit::kCount, + ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogSingleValueMetric( + "comparisons_done", test_label_, analyzer_stats_.comparisons_done, + Unit::kCount, ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "cpu_overloaded_comparisons_done", test_label_, + analyzer_stats_.cpu_overloaded_comparisons_done, Unit::kCount, + ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "memory_overloaded_comparisons_done", test_label_, + analyzer_stats_.memory_overloaded_comparisons_done, Unit::kCount, + ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "test_duration", test_label_, (Now() - start_time_).ms(), + Unit::kMilliseconds, ImprovementDirection::kNeitherIsBetter); + + metrics_logger_->LogMetric( + "on_frame_captured_processing_time_ms", test_label_, + analyzer_stats_.on_frame_captured_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric( + "on_frame_pre_encode_processing_time_ms", test_label_, + analyzer_stats_.on_frame_pre_encode_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric( + "on_frame_encoded_processing_time_ms", test_label_, + analyzer_stats_.on_frame_encoded_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric( + "on_frame_pre_decode_processing_time_ms", test_label_, + analyzer_stats_.on_frame_pre_decode_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric( + "on_frame_decoded_processing_time_ms", test_label_, + analyzer_stats_.on_frame_decoded_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric( + "on_frame_rendered_processing_time_ms", test_label_, + analyzer_stats_.on_frame_rendered_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + metrics_logger_->LogMetric( + "on_decoder_error_processing_time_ms", test_label_, + analyzer_stats_.on_decoder_error_processing_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); + } +} + +void DefaultVideoQualityAnalyzer::ReportResults( + const InternalStatsKey& key, + const StreamStats& stats, + const FrameCounters& frame_counters) { + TimeDelta test_duration = Now() - start_time_; + std::string test_case_name = GetTestCaseName(ToMetricName(key)); + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> metric_metadata{ + {MetricMetadataKey::kPeerMetadataKey, peers_->name(key.sender)}, + {MetricMetadataKey::kVideoStreamMetadataKey, streams_.name(key.stream)}, + {MetricMetadataKey::kSenderMetadataKey, peers_->name(key.sender)}, + {MetricMetadataKey::kReceiverMetadataKey, peers_->name(key.receiver)}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_label_}}; + + double sum_squared_interframe_delays_secs = 0; + Timestamp video_start_time = Timestamp::PlusInfinity(); + Timestamp video_end_time = Timestamp::MinusInfinity(); + for (const SamplesStatsCounter::StatsSample& sample : + stats.time_between_rendered_frames_ms.GetTimedSamples()) { + double interframe_delay_ms = sample.value; + const double interframe_delays_secs = interframe_delay_ms / 1000.0; + // Sum of squared inter frame intervals is used to calculate the harmonic + // frame rate metric. The metric aims to reflect overall experience related + // to smoothness of video playback and includes both freezes and pauses. + sum_squared_interframe_delays_secs += + interframe_delays_secs * interframe_delays_secs; + if (sample.time < video_start_time) { + video_start_time = sample.time; + } + if (sample.time > video_end_time) { + video_end_time = sample.time; + } + } + double harmonic_framerate_fps = 0; + TimeDelta video_duration = video_end_time - video_start_time; + if (sum_squared_interframe_delays_secs > 0.0 && video_duration.IsFinite()) { + harmonic_framerate_fps = + video_duration.seconds<double>() / sum_squared_interframe_delays_secs; + } + + metrics_logger_->LogMetric( + "psnr_dB", test_case_name, stats.psnr, Unit::kUnitless, + ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "ssim", test_case_name, stats.ssim, Unit::kUnitless, + ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogMetric("transport_time", test_case_name, + stats.transport_time_ms, Unit::kMilliseconds, + ImprovementDirection::kSmallerIsBetter, + metric_metadata); + metrics_logger_->LogMetric( + "total_delay_incl_transport", test_case_name, + stats.total_delay_incl_transport_ms, Unit::kMilliseconds, + ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "time_between_rendered_frames", test_case_name, + stats.time_between_rendered_frames_ms, Unit::kMilliseconds, + ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "harmonic_framerate", test_case_name, harmonic_framerate_fps, + Unit::kHertz, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "encode_frame_rate", test_case_name, + stats.encode_frame_rate.IsEmpty() + ? 0 + : stats.encode_frame_rate.GetEventsPerSecond(), + Unit::kHertz, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "encode_time", test_case_name, stats.encode_time_ms, Unit::kMilliseconds, + ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogMetric("time_between_freezes", test_case_name, + stats.time_between_freezes_ms, Unit::kMilliseconds, + ImprovementDirection::kBiggerIsBetter, + metric_metadata); + metrics_logger_->LogMetric("freeze_time_ms", test_case_name, + stats.freeze_time_ms, Unit::kMilliseconds, + ImprovementDirection::kSmallerIsBetter, + metric_metadata); + metrics_logger_->LogMetric( + "pixels_per_frame", test_case_name, stats.resolution_of_decoded_frame, + Unit::kCount, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "min_psnr_dB", test_case_name, + stats.psnr.IsEmpty() ? 0 : stats.psnr.GetMin(), Unit::kUnitless, + ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "decode_time", test_case_name, stats.decode_time_ms, Unit::kMilliseconds, + ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "receive_to_render_time", test_case_name, stats.receive_to_render_time_ms, + Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter, + metric_metadata); + metrics_logger_->LogSingleValueMetric( + "dropped_frames", test_case_name, frame_counters.dropped, Unit::kCount, + ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "frames_in_flight", test_case_name, + frame_counters.captured - frame_counters.rendered - + frame_counters.dropped, + Unit::kCount, ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "rendered_frames", test_case_name, frame_counters.rendered, Unit::kCount, + ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "max_skipped", test_case_name, stats.skipped_between_rendered, + Unit::kCount, ImprovementDirection::kSmallerIsBetter, metric_metadata); + metrics_logger_->LogMetric( + "target_encode_bitrate", test_case_name, + stats.target_encode_bitrate / 1000, Unit::kKilobitsPerSecond, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + for (const auto& [spatial_layer, qp] : stats.spatial_layers_qp) { + std::map<std::string, std::string> qp_metadata = metric_metadata; + qp_metadata[MetricMetadataKey::kSpatialLayerMetadataKey] = + std::to_string(spatial_layer); + metrics_logger_->LogMetric("qp_sl" + std::to_string(spatial_layer), + test_case_name, qp, Unit::kUnitless, + ImprovementDirection::kSmallerIsBetter, + std::move(qp_metadata)); + } + metrics_logger_->LogSingleValueMetric( + "actual_encode_bitrate", test_case_name, + static_cast<double>(stats.total_encoded_images_payload) / + test_duration.seconds<double>() * kBitsInByte / 1000, + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, + metric_metadata); + + if (options_.report_detailed_frame_stats) { + metrics_logger_->LogSingleValueMetric( + "capture_frame_rate", test_case_name, + stats.capture_frame_rate.IsEmpty() + ? 0 + : stats.capture_frame_rate.GetEventsPerSecond(), + Unit::kHertz, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "num_encoded_frames", test_case_name, frame_counters.encoded, + Unit::kCount, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "num_decoded_frames", test_case_name, frame_counters.decoded, + Unit::kCount, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "num_send_key_frames", test_case_name, stats.num_send_key_frames, + Unit::kCount, ImprovementDirection::kBiggerIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "num_recv_key_frames", test_case_name, stats.num_recv_key_frames, + Unit::kCount, ImprovementDirection::kBiggerIsBetter, metric_metadata); + + metrics_logger_->LogMetric("recv_key_frame_size_bytes", test_case_name, + stats.recv_key_frame_size_bytes, Unit::kCount, + ImprovementDirection::kBiggerIsBetter, + metric_metadata); + metrics_logger_->LogMetric("recv_delta_frame_size_bytes", test_case_name, + stats.recv_delta_frame_size_bytes, Unit::kCount, + ImprovementDirection::kBiggerIsBetter, + metric_metadata); + } +} + +std::string DefaultVideoQualityAnalyzer::GetTestCaseName( + const std::string& stream_label) const { + return test_label_ + "/" + stream_label; +} + +Timestamp DefaultVideoQualityAnalyzer::Now() { + return clock_->CurrentTime(); +} + +StatsKey DefaultVideoQualityAnalyzer::ToStatsKey( + const InternalStatsKey& key) const { + return StatsKey(streams_.name(key.stream), peers_->name(key.receiver)); +} + +std::string DefaultVideoQualityAnalyzer::ToMetricName( + const InternalStatsKey& key) const { + const std::string& stream_label = streams_.name(key.stream); + if (peers_->GetKnownSize() <= 2 && key.sender != key.receiver) { + // TODO(titovartem): remove this special case. + return stream_label; + } + rtc::StringBuilder out; + out << stream_label << "_" << peers_->name(key.sender) << "_" + << peers_->name(key.receiver); + return out.str(); +} + +double DefaultVideoQualityAnalyzer::GetCpuUsagePercent() { + return cpu_measurer_.GetCpuUsagePercent(); +} + +std::map<std::string, std::vector<uint16_t>> +DefaultVideoQualityAnalyzer::GetStreamFrames() const { + MutexLock lock(&mutex_); + std::map<std::string, std::vector<uint16_t>> out; + for (auto entry_it : stream_to_frame_id_full_history_) { + out.insert({streams_.name(entry_it.first), entry_it.second}); + } + return out; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h new file mode 100644 index 0000000000..b67e5a0147 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h @@ -0,0 +1,197 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_H_ + +#include <atomic> +#include <cstdint> +#include <deque> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "api/array_view.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "api/video/encoded_image.h" +#include "api/video/video_frame.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h" +#include "test/pc/e2e/analyzer/video/names_collection.h" + +namespace webrtc { + +class DefaultVideoQualityAnalyzer : public VideoQualityAnalyzerInterface { + public: + DefaultVideoQualityAnalyzer(webrtc::Clock* clock, + test::MetricsLogger* metrics_logger, + DefaultVideoQualityAnalyzerOptions options = {}); + ~DefaultVideoQualityAnalyzer() override; + + void Start(std::string test_case_name, + rtc::ArrayView<const std::string> peer_names, + int max_threads_count) override; + uint16_t OnFrameCaptured(absl::string_view peer_name, + const std::string& stream_label, + const VideoFrame& frame) override; + void OnFramePreEncode(absl::string_view peer_name, + const VideoFrame& frame) override; + void OnFrameEncoded(absl::string_view peer_name, + uint16_t frame_id, + const EncodedImage& encoded_image, + const EncoderStats& stats, + bool discarded) override; + void OnFrameDropped(absl::string_view peer_name, + EncodedImageCallback::DropReason reason) override; + void OnFramePreDecode(absl::string_view peer_name, + uint16_t frame_id, + const EncodedImage& input_image) override; + void OnFrameDecoded(absl::string_view peer_name, + const VideoFrame& frame, + const DecoderStats& stats) override; + void OnFrameRendered(absl::string_view peer_name, + const VideoFrame& frame) override; + void OnEncoderError(absl::string_view peer_name, + const VideoFrame& frame, + int32_t error_code) override; + void OnDecoderError(absl::string_view peer_name, + uint16_t frame_id, + int32_t error_code, + const DecoderStats& stats) override; + + void RegisterParticipantInCall(absl::string_view peer_name) override; + void UnregisterParticipantInCall(absl::string_view peer_name) override; + + void Stop() override; + std::string GetStreamLabel(uint16_t frame_id) override; + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override {} + + // Returns set of stream labels, that were met during test call. + std::set<StatsKey> GetKnownVideoStreams() const; + VideoStreamsInfo GetKnownStreams() const; + FrameCounters GetGlobalCounters() const; + // Returns frame counter for frames received without frame id set. + std::map<std::string, FrameCounters> GetUnknownSenderFrameCounters() const; + // Returns frame counter per stream label. Valid stream labels can be obtained + // by calling GetKnownVideoStreams() + std::map<StatsKey, FrameCounters> GetPerStreamCounters() const; + // Returns video quality stats per stream label. Valid stream labels can be + // obtained by calling GetKnownVideoStreams() + std::map<StatsKey, StreamStats> GetStats() const; + AnalyzerStats GetAnalyzerStats() const; + double GetCpuUsagePercent(); + + // Returns mapping from the stream label to the history of frames that were + // met in this stream in the order as they were captured. + std::map<std::string, std::vector<uint16_t>> GetStreamFrames() const; + + private: + enum State { kNew, kActive, kStopped }; + + // Returns next frame id to use. Frame ID can't be `VideoFrame::kNotSetId`, + // because this value is reserved by `VideoFrame` as "ID not set". + uint16_t GetNextFrameId() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + void AddExistingFramesInFlightForStreamToComparator(size_t stream_index, + StreamState& stream_state, + size_t peer_index) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Report results for all metrics for all streams. + void ReportResults(); + void ReportResults(const InternalStatsKey& key, + const StreamStats& stats, + const FrameCounters& frame_counters) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Returns name of current test case for reporting. + std::string GetTestCaseName(const std::string& stream_label) const; + Timestamp Now(); + StatsKey ToStatsKey(const InternalStatsKey& key) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Returns string representation of stats key for metrics naming. Used for + // backward compatibility by metrics naming for 2 peers cases. + std::string ToMetricName(const InternalStatsKey& key) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + static const uint16_t kStartingFrameId = 1; + + const DefaultVideoQualityAnalyzerOptions options_; + webrtc::Clock* const clock_; + test::MetricsLogger* const metrics_logger_; + + std::string test_label_; + + mutable Mutex mutex_; + uint16_t next_frame_id_ RTC_GUARDED_BY(mutex_) = kStartingFrameId; + std::unique_ptr<NamesCollection> peers_ RTC_GUARDED_BY(mutex_); + State state_ RTC_GUARDED_BY(mutex_) = State::kNew; + Timestamp start_time_ RTC_GUARDED_BY(mutex_) = Timestamp::MinusInfinity(); + // Mapping from stream label to unique size_t value to use in stats and avoid + // extra string copying. + NamesCollection streams_ RTC_GUARDED_BY(mutex_); + // Frames that were captured by all streams and still aren't rendered on + // receivers or deemed dropped. Frame with id X can be removed from this map + // if: + // 1. The frame with id X was received in OnFrameRendered by all expected + // receivers. + // 2. The frame with id Y > X was received in OnFrameRendered by all expected + // receivers. + // 3. Next available frame id for newly captured frame is X + // 4. There too many frames in flight for current video stream and X is the + // oldest frame id in this stream. In such case only the frame content + // will be removed, but the map entry will be preserved. + std::map<uint16_t, FrameInFlight> captured_frames_in_flight_ + RTC_GUARDED_BY(mutex_); + // Global frames count for all video streams. + FrameCounters frame_counters_ RTC_GUARDED_BY(mutex_); + // Frame counters for received frames without video frame id set. + // Map from peer name to the frame counters. + std::map<std::string, FrameCounters> unknown_sender_frame_counters_ + RTC_GUARDED_BY(mutex_); + // Frame counters per each stream per each receiver. + std::map<InternalStatsKey, FrameCounters> stream_frame_counters_ + RTC_GUARDED_BY(mutex_); + // Map from stream index in `streams_` to its StreamState. + std::map<size_t, StreamState> stream_states_ RTC_GUARDED_BY(mutex_); + // Map from stream index in `streams_` to sender peer index in `peers_`. + std::map<size_t, size_t> stream_to_sender_ RTC_GUARDED_BY(mutex_); + + // Stores history mapping between stream index in `streams_` and frame ids. + // Updated when frame id overlap. It required to properly return stream label + // after 1st frame from simulcast streams was already rendered and last is + // still encoding. + std::map<size_t, std::set<uint16_t>> stream_to_frame_id_history_ + RTC_GUARDED_BY(mutex_); + // Map from stream index to the list of frames as they were met in the stream. + std::map<size_t, std::vector<uint16_t>> stream_to_frame_id_full_history_ + RTC_GUARDED_BY(mutex_); + AnalyzerStats analyzer_stats_ RTC_GUARDED_BY(mutex_); + + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer_; + DefaultVideoQualityAnalyzerFramesComparator frames_comparator_; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.cc new file mode 100644 index 0000000000..847c9f09a6 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.cc @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h" + +#include "rtc_base/cpu_time.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system_time.h" + +namespace webrtc { + +void DefaultVideoQualityAnalyzerCpuMeasurer::StartMeasuringCpuProcessTime() { + MutexLock lock(&mutex_); + cpu_time_ -= rtc::GetProcessCpuTimeNanos(); + wallclock_time_ -= rtc::SystemTimeNanos(); +} + +void DefaultVideoQualityAnalyzerCpuMeasurer::StopMeasuringCpuProcessTime() { + MutexLock lock(&mutex_); + cpu_time_ += rtc::GetProcessCpuTimeNanos(); + wallclock_time_ += rtc::SystemTimeNanos(); +} + +void DefaultVideoQualityAnalyzerCpuMeasurer::StartExcludingCpuThreadTime() { + MutexLock lock(&mutex_); + cpu_time_ += rtc::GetThreadCpuTimeNanos(); +} + +void DefaultVideoQualityAnalyzerCpuMeasurer::StopExcludingCpuThreadTime() { + MutexLock lock(&mutex_); + cpu_time_ -= rtc::GetThreadCpuTimeNanos(); +} + +double DefaultVideoQualityAnalyzerCpuMeasurer::GetCpuUsagePercent() { + MutexLock lock(&mutex_); + return static_cast<double>(cpu_time_) / wallclock_time_ * 100.0; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h new file mode 100644 index 0000000000..dd9fa07af2 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_CPU_MEASURER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_CPU_MEASURER_H_ + +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +// This class is thread safe. +class DefaultVideoQualityAnalyzerCpuMeasurer { + public: + double GetCpuUsagePercent(); + + void StartMeasuringCpuProcessTime(); + void StopMeasuringCpuProcessTime(); + void StartExcludingCpuThreadTime(); + void StopExcludingCpuThreadTime(); + + private: + Mutex mutex_; + int64_t cpu_time_ RTC_GUARDED_BY(mutex_) = 0; + int64_t wallclock_time_ RTC_GUARDED_BY(mutex_) = 0; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_CPU_MEASURER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.cc new file mode 100644 index 0000000000..df34dadaf0 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.cc @@ -0,0 +1,209 @@ +/* + * 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/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h" + +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_type.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" + +namespace webrtc { +namespace { + +template <typename T> +absl::optional<T> MaybeGetValue(const std::map<size_t, T>& map, size_t key) { + auto it = map.find(key); + if (it == map.end()) { + return absl::nullopt; + } + return it->second; +} + +} // namespace + +FrameInFlight::FrameInFlight(size_t stream, + VideoFrame frame, + Timestamp captured_time, + std::set<size_t> expected_receivers) + : stream_(stream), + expected_receivers_(std::move(expected_receivers)), + frame_(std::move(frame)), + captured_time_(captured_time) {} + +bool FrameInFlight::RemoveFrame() { + if (!frame_) { + return false; + } + frame_ = absl::nullopt; + return true; +} + +void FrameInFlight::SetFrameId(uint16_t id) { + if (frame_) { + frame_->set_id(id); + } + frame_id_ = id; +} + +std::vector<size_t> FrameInFlight::GetPeersWhichDidntReceive() const { + std::vector<size_t> out; + for (size_t peer : expected_receivers_) { + auto it = receiver_stats_.find(peer); + if (it == receiver_stats_.end() || + (!it->second.dropped && it->second.rendered_time.IsInfinite())) { + out.push_back(peer); + } + } + return out; +} + +bool FrameInFlight::HaveAllPeersReceived() const { + for (size_t peer : expected_receivers_) { + auto it = receiver_stats_.find(peer); + if (it == receiver_stats_.end()) { + return false; + } + + if (!it->second.dropped && it->second.rendered_time.IsInfinite()) { + return false; + } + } + return true; +} + +void FrameInFlight::OnFrameEncoded(webrtc::Timestamp time, + VideoFrameType frame_type, + DataSize encoded_image_size, + uint32_t target_encode_bitrate, + int spatial_layer, + int qp, + StreamCodecInfo used_encoder) { + encoded_time_ = time; + frame_type_ = frame_type; + encoded_image_size_ = encoded_image_size; + target_encode_bitrate_ += target_encode_bitrate; + spatial_layers_qp_[spatial_layer].AddSample(SamplesStatsCounter::StatsSample{ + .value = static_cast<double>(qp), .time = time}); + // Update used encoder info. If simulcast/SVC is used, this method can + // be called multiple times, in such case we should preserve the value + // of `used_encoder_.switched_on_at` from the first invocation as the + // smallest one. + Timestamp encoder_switched_on_at = used_encoder_.has_value() + ? used_encoder_->switched_on_at + : Timestamp::PlusInfinity(); + RTC_DCHECK(used_encoder.switched_on_at.IsFinite()); + RTC_DCHECK(used_encoder.switched_from_at.IsFinite()); + used_encoder_ = used_encoder; + if (encoder_switched_on_at < used_encoder_->switched_on_at) { + used_encoder_->switched_on_at = encoder_switched_on_at; + } +} + +void FrameInFlight::OnFramePreDecode(size_t peer, + webrtc::Timestamp received_time, + webrtc::Timestamp decode_start_time, + VideoFrameType frame_type, + DataSize encoded_image_size) { + receiver_stats_[peer].received_time = received_time; + receiver_stats_[peer].decode_start_time = decode_start_time; + receiver_stats_[peer].frame_type = frame_type; + receiver_stats_[peer].encoded_image_size = encoded_image_size; +} + +bool FrameInFlight::HasReceivedTime(size_t peer) const { + auto it = receiver_stats_.find(peer); + if (it == receiver_stats_.end()) { + return false; + } + return it->second.received_time.IsFinite(); +} + +void FrameInFlight::OnFrameDecoded(size_t peer, + webrtc::Timestamp time, + int width, + int height, + const StreamCodecInfo& used_decoder) { + receiver_stats_[peer].decode_end_time = time; + receiver_stats_[peer].used_decoder = used_decoder; + receiver_stats_[peer].decoded_frame_width = width; + receiver_stats_[peer].decoded_frame_height = height; +} + +void FrameInFlight::OnDecoderError(size_t peer, + const StreamCodecInfo& used_decoder) { + receiver_stats_[peer].decoder_failed = true; + receiver_stats_[peer].used_decoder = used_decoder; +} + +bool FrameInFlight::HasDecodeEndTime(size_t peer) const { + auto it = receiver_stats_.find(peer); + if (it == receiver_stats_.end()) { + return false; + } + return it->second.decode_end_time.IsFinite(); +} + +void FrameInFlight::OnFrameRendered(size_t peer, webrtc::Timestamp time) { + receiver_stats_[peer].rendered_time = time; +} + +bool FrameInFlight::HasRenderedTime(size_t peer) const { + auto it = receiver_stats_.find(peer); + if (it == receiver_stats_.end()) { + return false; + } + return it->second.rendered_time.IsFinite(); +} + +bool FrameInFlight::IsDropped(size_t peer) const { + auto it = receiver_stats_.find(peer); + if (it == receiver_stats_.end()) { + return false; + } + return it->second.dropped; +} + +FrameStats FrameInFlight::GetStatsForPeer(size_t peer) const { + RTC_DCHECK_NE(frame_id_, VideoFrame::kNotSetId) + << "Frame id isn't initialized"; + FrameStats stats(frame_id_, captured_time_); + stats.pre_encode_time = pre_encode_time_; + stats.encoded_time = encoded_time_; + stats.target_encode_bitrate = target_encode_bitrate_; + stats.encoded_frame_type = frame_type_; + stats.encoded_image_size = encoded_image_size_; + stats.used_encoder = used_encoder_; + stats.spatial_layers_qp = spatial_layers_qp_; + + absl::optional<ReceiverFrameStats> receiver_stats = + MaybeGetValue<ReceiverFrameStats>(receiver_stats_, peer); + if (receiver_stats.has_value()) { + stats.received_time = receiver_stats->received_time; + stats.decode_start_time = receiver_stats->decode_start_time; + stats.decode_end_time = receiver_stats->decode_end_time; + stats.rendered_time = receiver_stats->rendered_time; + stats.prev_frame_rendered_time = receiver_stats->prev_frame_rendered_time; + stats.decoded_frame_width = receiver_stats->decoded_frame_width; + stats.decoded_frame_height = receiver_stats->decoded_frame_height; + stats.used_decoder = receiver_stats->used_decoder; + stats.pre_decoded_frame_type = receiver_stats->frame_type; + stats.pre_decoded_image_size = receiver_stats->encoded_image_size; + stats.decoder_failed = receiver_stats->decoder_failed; + } + return stats; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h new file mode 100644 index 0000000000..52a526d09b --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frame_in_flight.h @@ -0,0 +1,169 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_FRAME_IN_FLIGHT_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_FRAME_IN_FLIGHT_H_ + +#include <map> +#include <set> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_type.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" + +namespace webrtc { + +struct ReceiverFrameStats { + // Time when last packet of a frame was received. + Timestamp received_time = Timestamp::MinusInfinity(); + Timestamp decode_start_time = Timestamp::MinusInfinity(); + Timestamp decode_end_time = Timestamp::MinusInfinity(); + Timestamp rendered_time = Timestamp::MinusInfinity(); + Timestamp prev_frame_rendered_time = Timestamp::MinusInfinity(); + + // Type and encoded size of received frame. + VideoFrameType frame_type = VideoFrameType::kEmptyFrame; + DataSize encoded_image_size = DataSize::Bytes(0); + + absl::optional<int> decoded_frame_width = absl::nullopt; + absl::optional<int> decoded_frame_height = absl::nullopt; + + // Can be not set if frame was dropped in the network. + absl::optional<StreamCodecInfo> used_decoder = absl::nullopt; + + bool dropped = false; + bool decoder_failed = false; +}; + +// Represents a frame which was sent by sender and is currently on the way to +// multiple receivers. Some receivers may receive this frame and some don't. +// +// Contains all statistic associated with the frame and gathered in multiple +// points of the video pipeline. +// +// Internally may store the copy of the source frame which was sent. In such +// case this frame is "alive". +class FrameInFlight { + public: + FrameInFlight(size_t stream, + VideoFrame frame, + Timestamp captured_time, + std::set<size_t> expected_receivers); + + size_t stream() const { return stream_; } + // Returns internal copy of source `VideoFrame` or `absl::nullopt` if it was + // removed before. + const absl::optional<VideoFrame>& frame() const { return frame_; } + // Removes internal copy of the source `VideoFrame` to free up extra memory. + // Returns was frame removed or not. + bool RemoveFrame(); + void SetFrameId(uint16_t id); + + void AddExpectedReceiver(size_t peer) { expected_receivers_.insert(peer); } + + void RemoveExpectedReceiver(size_t peer) { expected_receivers_.erase(peer); } + + std::vector<size_t> GetPeersWhichDidntReceive() const; + + // Returns if all peers which were expected to receive this frame actually + // received it or not. + bool HaveAllPeersReceived() const; + + void SetPreEncodeTime(webrtc::Timestamp time) { pre_encode_time_ = time; } + + void OnFrameEncoded(webrtc::Timestamp time, + VideoFrameType frame_type, + DataSize encoded_image_size, + uint32_t target_encode_bitrate, + int spatial_layer, + int qp, + StreamCodecInfo used_encoder); + + bool HasEncodedTime() const { return encoded_time_.IsFinite(); } + + void OnFramePreDecode(size_t peer, + webrtc::Timestamp received_time, + webrtc::Timestamp decode_start_time, + VideoFrameType frame_type, + DataSize encoded_image_size); + + bool HasReceivedTime(size_t peer) const; + + void OnFrameDecoded(size_t peer, + webrtc::Timestamp time, + int width, + int height, + const StreamCodecInfo& used_decoder); + void OnDecoderError(size_t peer, const StreamCodecInfo& used_decoder); + + bool HasDecodeEndTime(size_t peer) const; + + void OnFrameRendered(size_t peer, webrtc::Timestamp time); + + bool HasRenderedTime(size_t peer) const; + + // Crash if rendered time is not set for specified `peer`. + webrtc::Timestamp rendered_time(size_t peer) const { + return receiver_stats_.at(peer).rendered_time; + } + + // Marks that frame was dropped and wasn't seen by particular `peer`. + void MarkDropped(size_t peer) { receiver_stats_[peer].dropped = true; } + bool IsDropped(size_t peer) const; + + void SetPrevFrameRenderedTime(size_t peer, webrtc::Timestamp time) { + receiver_stats_[peer].prev_frame_rendered_time = time; + } + + FrameStats GetStatsForPeer(size_t peer) const; + + private: + const size_t stream_; + // Set of peer's indexes who are expected to receive this frame. This is not + // the set of peer's indexes that received the frame. For example, if peer A + // was among expected receivers, it received frame and then left the call, A + // will be removed from this set, but the Stats for peer A still will be + // preserved in the FrameInFlight. + // + // This set is used to determine if this frame is expected to be received by + // any peer or can be safely deleted. It is responsibility of the user of this + // object to decide when it should be deleted. + std::set<size_t> expected_receivers_; + absl::optional<VideoFrame> frame_; + // Store frame id separately because `frame_` can be removed when we have too + // much memory consuption. + uint16_t frame_id_ = VideoFrame::kNotSetId; + + // Frame events timestamp. + Timestamp captured_time_; + Timestamp pre_encode_time_ = Timestamp::MinusInfinity(); + Timestamp encoded_time_ = Timestamp::MinusInfinity(); + // Type and encoded size of sent frame. + VideoFrameType frame_type_ = VideoFrameType::kEmptyFrame; + DataSize encoded_image_size_ = DataSize::Bytes(0); + uint32_t target_encode_bitrate_ = 0; + // Sender side qp values per spatial layer. In case when spatial layer is not + // set for `webrtc::EncodedImage`, 0 is used as default. + std::map<int, SamplesStatsCounter> spatial_layers_qp_; + // Can be not set if frame was dropped by encoder. + absl::optional<StreamCodecInfo> used_encoder_ = absl::nullopt; + // Map from the receiver peer's index to frame stats for that peer. + std::map<size_t, ReceiverFrameStats> receiver_stats_; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_FRAME_IN_FLIGHT_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.cc new file mode 100644 index 0000000000..cbc0b7e8f3 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.cc @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h" + +#include <algorithm> +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame_type.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" +#include "rtc_base/platform_thread.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_tools/frame_analyzer/video_geometry_aligner.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" +#include "test/pc/e2e/metric_metadata_keys.h" + +namespace webrtc { +namespace { + +using ::webrtc::webrtc_pc_e2e::SampleMetadataKey; + +constexpr TimeDelta kFreezeThreshold = TimeDelta::Millis(150); +constexpr int kMaxActiveComparisons = 10; + +SamplesStatsCounter::StatsSample StatsSample( + double value, + Timestamp sampling_time, + std::map<std::string, std::string> metadata) { + return SamplesStatsCounter::StatsSample{value, sampling_time, + std::move(metadata)}; +} + +SamplesStatsCounter::StatsSample StatsSample( + TimeDelta duration, + Timestamp sampling_time, + std::map<std::string, std::string> metadata) { + return SamplesStatsCounter::StatsSample{duration.ms<double>(), sampling_time, + std::move(metadata)}; +} + +FrameComparison ValidateFrameComparison(FrameComparison comparison) { + RTC_DCHECK(comparison.frame_stats.captured_time.IsFinite()) + << "Any comparison has to have finite captured_time"; + switch (comparison.type) { + case FrameComparisonType::kRegular: + // Regular comparison has to have all FrameStats filled in. + RTC_DCHECK(comparison.captured.has_value() || + comparison.overload_reason != OverloadReason::kNone) + << "Regular comparison has to have captured frame if it's not " + << "overloaded comparison"; + RTC_DCHECK(comparison.rendered.has_value() || + comparison.overload_reason != OverloadReason::kNone) + << "rendered frame has to be presented if it's not overloaded " + << "comparison"; + RTC_DCHECK(comparison.frame_stats.pre_encode_time.IsFinite()) + << "Regular comparison has to have finite pre_encode_time"; + RTC_DCHECK(comparison.frame_stats.encoded_time.IsFinite()) + << "Regular comparison has to have finite encoded_time"; + RTC_DCHECK(comparison.frame_stats.received_time.IsFinite()) + << "Regular comparison has to have finite received_time"; + RTC_DCHECK(comparison.frame_stats.decode_start_time.IsFinite()) + << "Regular comparison has to have finite decode_start_time"; + RTC_DCHECK(comparison.frame_stats.decode_end_time.IsFinite()) + << "Regular comparison has to have finite decode_end_time"; + RTC_DCHECK(comparison.frame_stats.rendered_time.IsFinite()) + << "Regular comparison has to have finite rendered_time"; + RTC_DCHECK(comparison.frame_stats.decoded_frame_width.has_value()) + << "Regular comparison has to have decoded_frame_width"; + RTC_DCHECK(comparison.frame_stats.decoded_frame_height.has_value()) + << "Regular comparison has to have decoded_frame_height"; + RTC_DCHECK(comparison.frame_stats.used_encoder.has_value()) + << "Regular comparison has to have used_encoder"; + RTC_DCHECK(comparison.frame_stats.used_decoder.has_value()) + << "Regular comparison has to have used_decoder"; + RTC_DCHECK(!comparison.frame_stats.decoder_failed) + << "Regular comparison can't have decoder failure"; + break; + case FrameComparisonType::kDroppedFrame: + // Frame can be dropped before encoder, by encoder, inside network or + // after decoder. + RTC_DCHECK(!comparison.captured.has_value()) + << "Dropped frame comparison can't have captured frame"; + RTC_DCHECK(!comparison.rendered.has_value()) + << "Dropped frame comparison can't have rendered frame"; + + if (comparison.frame_stats.encoded_time.IsFinite()) { + RTC_DCHECK(comparison.frame_stats.used_encoder.has_value()) + << "Dropped frame comparison has to have used_encoder when " + << "encoded_time is set"; + RTC_DCHECK(comparison.frame_stats.pre_encode_time.IsFinite()) + << "Dropped frame comparison has to have finite pre_encode_time " + << "when encoded_time is finite."; + } + + if (comparison.frame_stats.decode_end_time.IsFinite() || + comparison.frame_stats.decoder_failed) { + RTC_DCHECK(comparison.frame_stats.received_time.IsFinite()) + << "Dropped frame comparison has to have received_time when " + << "decode_end_time is set or decoder_failed is true"; + RTC_DCHECK(comparison.frame_stats.decode_start_time.IsFinite()) + << "Dropped frame comparison has to have decode_start_time when " + << "decode_end_time is set or decoder_failed is true"; + RTC_DCHECK(comparison.frame_stats.used_decoder.has_value()) + << "Dropped frame comparison has to have used_decoder when " + << "decode_end_time is set or decoder_failed is true"; + } else if (comparison.frame_stats.decode_end_time.IsFinite()) { + RTC_DCHECK(comparison.frame_stats.decoded_frame_width.has_value()) + << "Dropped frame comparison has to have decoded_frame_width when " + << "decode_end_time is set"; + RTC_DCHECK(comparison.frame_stats.decoded_frame_height.has_value()) + << "Dropped frame comparison has to have decoded_frame_height when " + << "decode_end_time is set"; + } + RTC_DCHECK(!comparison.frame_stats.rendered_time.IsFinite()) + << "Dropped frame comparison can't have rendered_time"; + break; + case FrameComparisonType::kFrameInFlight: + // Frame in flight comparison may miss almost any FrameStats, but if + // stats for stage X are set, then stats for stage X - 1 also has to be + // set. Also these frames were never rendered. + RTC_DCHECK(!comparison.captured.has_value()) + << "Frame in flight comparison can't have captured frame"; + RTC_DCHECK(!comparison.rendered.has_value()) + << "Frame in flight comparison can't have rendered frame"; + RTC_DCHECK(!comparison.frame_stats.rendered_time.IsFinite()) + << "Frame in flight comparison can't have rendered_time"; + + if (comparison.frame_stats.decode_end_time.IsFinite() || + comparison.frame_stats.decoder_failed) { + RTC_DCHECK(comparison.frame_stats.used_decoder.has_value()) + << "Frame in flight comparison has to have used_decoder when " + << "decode_end_time is set or decoder_failed is true."; + RTC_DCHECK(comparison.frame_stats.decode_start_time.IsFinite()) + << "Frame in flight comparison has to have finite " + << "decode_start_time when decode_end_time is finite or " + << "decoder_failed is true."; + } + if (comparison.frame_stats.decode_end_time.IsFinite()) { + RTC_DCHECK(comparison.frame_stats.decoded_frame_width.has_value()) + << "Frame in flight comparison has to have decoded_frame_width " + << "when decode_end_time is set."; + RTC_DCHECK(comparison.frame_stats.decoded_frame_height.has_value()) + << "Frame in flight comparison has to have decoded_frame_height " + << "when decode_end_time is set."; + } + if (comparison.frame_stats.decode_start_time.IsFinite()) { + RTC_DCHECK(comparison.frame_stats.received_time.IsFinite()) + << "Frame in flight comparison has to have finite received_time " + << "when decode_start_time is finite."; + } + if (comparison.frame_stats.received_time.IsFinite()) { + RTC_DCHECK(comparison.frame_stats.encoded_time.IsFinite()) + << "Frame in flight comparison has to have finite encoded_time " + << "when received_time is finite."; + } + if (comparison.frame_stats.encoded_time.IsFinite()) { + RTC_DCHECK(comparison.frame_stats.used_encoder.has_value()) + << "Frame in flight comparison has to have used_encoder when " + << "encoded_time is set"; + RTC_DCHECK(comparison.frame_stats.pre_encode_time.IsFinite()) + << "Frame in flight comparison has to have finite pre_encode_time " + << "when encoded_time is finite."; + } + break; + } + return comparison; +} + +} // namespace + +void DefaultVideoQualityAnalyzerFramesComparator::Start(int max_threads_count) { + for (int i = 0; i < max_threads_count; i++) { + thread_pool_.push_back(rtc::PlatformThread::SpawnJoinable( + [this] { ProcessComparisons(); }, + "DefaultVideoQualityAnalyzerFramesComparator-" + std::to_string(i))); + } + { + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kNew) << "Frames comparator is already started"; + state_ = State::kActive; + } + cpu_measurer_.StartMeasuringCpuProcessTime(); +} + +void DefaultVideoQualityAnalyzerFramesComparator::Stop( + const std::map<InternalStatsKey, Timestamp>& last_rendered_frame_times) { + { + MutexLock lock(&mutex_); + if (state_ == State::kStopped) { + return; + } + RTC_CHECK_EQ(state_, State::kActive) + << "Frames comparator has to be started before it will be used"; + state_ = State::kStopped; + } + cpu_measurer_.StopMeasuringCpuProcessTime(); + comparison_available_event_.Set(); + thread_pool_.clear(); + + { + MutexLock lock(&mutex_); + // Perform final Metrics update. On this place analyzer is stopped and no + // one holds any locks. + + // Time between freezes. + // Count time since the last freeze to the end of the call as time + // between freezes. + for (auto& entry : last_rendered_frame_times) { + const InternalStatsKey& stats_key = entry.first; + const Timestamp& last_rendered_frame_time = entry.second; + + // If there are no freezes in the call we have to report + // time_between_freezes_ms as call duration and in such case + // `last_rendered_frame_time` for this stream will be stream start time. + // If there is freeze, then we need add time from last rendered frame + // to last freeze end as time between freezes. + stream_stats_.at(stats_key).time_between_freezes_ms.AddSample(StatsSample( + last_rendered_frame_time - stream_last_freeze_end_time_.at(stats_key), + Now(), /*metadata=*/{})); + } + + // Freeze Time: + // If there were no freezes on a video stream, add only one sample with + // value 0 (0ms freezes time). + for (auto& [key, stream_stats] : stream_stats_) { + if (stream_stats.freeze_time_ms.IsEmpty()) { + stream_stats.freeze_time_ms.AddSample(0); + } + } + } +} + +void DefaultVideoQualityAnalyzerFramesComparator::EnsureStatsForStream( + size_t stream_index, + size_t sender_peer_index, + size_t peers_count, + Timestamp captured_time, + Timestamp start_time) { + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "Frames comparator has to be started before it will be used"; + + for (size_t i = 0; i < peers_count; ++i) { + if (i == sender_peer_index && !options_.enable_receive_own_stream) { + continue; + } + InternalStatsKey stats_key(stream_index, sender_peer_index, i); + if (stream_stats_.find(stats_key) == stream_stats_.end()) { + stream_stats_.insert({stats_key, StreamStats(captured_time)}); + // Assume that the first freeze was before first stream frame captured. + // This way time before the first freeze would be counted as time + // between freezes. + stream_last_freeze_end_time_.insert({stats_key, start_time}); + } else { + // When we see some `stream_label` for the first time we need to create + // stream stats object for it and set up some states, but we need to do + // it only once and for all receivers, so on the next frame on the same + // `stream_label` we can be sure, that it's already done and we needn't + // to scan though all peers again. + break; + } + } +} + +void DefaultVideoQualityAnalyzerFramesComparator::RegisterParticipantInCall( + rtc::ArrayView<std::pair<InternalStatsKey, Timestamp>> stream_started_time, + Timestamp start_time) { + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "Frames comparator has to be started before it will be used"; + + for (const std::pair<InternalStatsKey, Timestamp>& pair : + stream_started_time) { + stream_stats_.insert({pair.first, StreamStats(pair.second)}); + stream_last_freeze_end_time_.insert({pair.first, start_time}); + } +} + +void DefaultVideoQualityAnalyzerFramesComparator::AddComparison( + InternalStatsKey stats_key, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats) { + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "Frames comparator has to be started before it will be used"; + AddComparisonInternal(std::move(stats_key), std::move(captured), + std::move(rendered), type, std::move(frame_stats)); +} + +void DefaultVideoQualityAnalyzerFramesComparator::AddComparison( + InternalStatsKey stats_key, + int skipped_between_rendered, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats) { + MutexLock lock(&mutex_); + RTC_CHECK_EQ(state_, State::kActive) + << "Frames comparator has to be started before it will be used"; + stream_stats_.at(stats_key).skipped_between_rendered.AddSample( + StatsSample(skipped_between_rendered, Now(), + /*metadata=*/ + {{SampleMetadataKey::kFrameIdMetadataKey, + std::to_string(frame_stats.frame_id)}})); + AddComparisonInternal(std::move(stats_key), std::move(captured), + std::move(rendered), type, std::move(frame_stats)); +} + +void DefaultVideoQualityAnalyzerFramesComparator::AddComparisonInternal( + InternalStatsKey stats_key, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats) { + cpu_measurer_.StartExcludingCpuThreadTime(); + frames_comparator_stats_.comparisons_queue_size.AddSample( + StatsSample(comparisons_.size(), Now(), /*metadata=*/{})); + // If there too many computations waiting in the queue, we won't provide + // frames itself to make future computations lighter. + if (comparisons_.size() >= kMaxActiveComparisons) { + comparisons_.emplace_back(ValidateFrameComparison( + FrameComparison(std::move(stats_key), /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, type, + std::move(frame_stats), OverloadReason::kCpu))); + } else { + OverloadReason overload_reason = OverloadReason::kNone; + if (!captured && type == FrameComparisonType::kRegular) { + overload_reason = OverloadReason::kMemory; + } + comparisons_.emplace_back(ValidateFrameComparison(FrameComparison( + std::move(stats_key), std::move(captured), std::move(rendered), type, + std::move(frame_stats), overload_reason))); + } + comparison_available_event_.Set(); + cpu_measurer_.StopExcludingCpuThreadTime(); +} + +void DefaultVideoQualityAnalyzerFramesComparator::ProcessComparisons() { + while (true) { + // Try to pick next comparison to perform from the queue. + absl::optional<FrameComparison> comparison = absl::nullopt; + bool more_new_comparisons_expected; + { + MutexLock lock(&mutex_); + if (!comparisons_.empty()) { + comparison = comparisons_.front(); + comparisons_.pop_front(); + if (!comparisons_.empty()) { + comparison_available_event_.Set(); + } + } + // If state is stopped => no new frame comparisons are expected. + more_new_comparisons_expected = state_ != State::kStopped; + } + if (!comparison) { + if (!more_new_comparisons_expected) { + comparison_available_event_.Set(); + return; + } + comparison_available_event_.Wait(TimeDelta::Seconds(1)); + continue; + } + + cpu_measurer_.StartExcludingCpuThreadTime(); + ProcessComparison(comparison.value()); + cpu_measurer_.StopExcludingCpuThreadTime(); + } +} + +void DefaultVideoQualityAnalyzerFramesComparator::ProcessComparison( + const FrameComparison& comparison) { + // Comparison is checked to be valid before adding, so we can use this + // assumptions during computations. + + // Perform expensive psnr and ssim calculations while not holding lock. + double psnr = -1.0; + double ssim = -1.0; + if ((options_.compute_psnr || options_.compute_ssim) && + comparison.captured.has_value() && comparison.rendered.has_value()) { + rtc::scoped_refptr<I420BufferInterface> reference_buffer = + comparison.captured->video_frame_buffer()->ToI420(); + rtc::scoped_refptr<I420BufferInterface> test_buffer = + comparison.rendered->video_frame_buffer()->ToI420(); + if (options_.adjust_cropping_before_comparing_frames) { + test_buffer = ScaleVideoFrameBuffer( + *test_buffer, reference_buffer->width(), reference_buffer->height()); + reference_buffer = test::AdjustCropping(reference_buffer, test_buffer); + } + if (options_.compute_psnr) { + psnr = options_.use_weighted_psnr + ? I420WeightedPSNR(*reference_buffer, *test_buffer) + : I420PSNR(*reference_buffer, *test_buffer); + } + if (options_.compute_ssim) { + ssim = I420SSIM(*reference_buffer, *test_buffer); + } + } + + const FrameStats& frame_stats = comparison.frame_stats; + + MutexLock lock(&mutex_); + auto stats_it = stream_stats_.find(comparison.stats_key); + RTC_CHECK(stats_it != stream_stats_.end()) << comparison.stats_key.ToString(); + StreamStats* stats = &stats_it->second; + + frames_comparator_stats_.comparisons_done++; + if (comparison.overload_reason == OverloadReason::kCpu) { + frames_comparator_stats_.cpu_overloaded_comparisons_done++; + } else if (comparison.overload_reason == OverloadReason::kMemory) { + frames_comparator_stats_.memory_overloaded_comparisons_done++; + } + + std::map<std::string, std::string> metadata; + metadata.emplace(SampleMetadataKey::kFrameIdMetadataKey, + std::to_string(frame_stats.frame_id)); + + if (psnr > 0) { + stats->psnr.AddSample( + StatsSample(psnr, frame_stats.rendered_time, metadata)); + } + if (ssim > 0) { + stats->ssim.AddSample( + StatsSample(ssim, frame_stats.received_time, metadata)); + } + stats->capture_frame_rate.AddEvent(frame_stats.captured_time); + + // Compute dropped phase for dropped frame + if (comparison.type == FrameComparisonType::kDroppedFrame) { + FrameDropPhase dropped_phase; + if (frame_stats.decode_end_time.IsFinite()) { + dropped_phase = FrameDropPhase::kAfterDecoder; + } else if (frame_stats.decode_start_time.IsFinite()) { + dropped_phase = FrameDropPhase::kByDecoder; + } else if (frame_stats.encoded_time.IsFinite()) { + dropped_phase = FrameDropPhase::kTransport; + } else if (frame_stats.pre_encode_time.IsFinite()) { + dropped_phase = FrameDropPhase::kByEncoder; + } else { + dropped_phase = FrameDropPhase::kBeforeEncoder; + } + stats->dropped_by_phase[dropped_phase]++; + } + + if (frame_stats.encoded_time.IsFinite()) { + stats->encode_time_ms.AddSample( + StatsSample(frame_stats.encoded_time - frame_stats.pre_encode_time, + frame_stats.encoded_time, metadata)); + stats->encode_frame_rate.AddEvent(frame_stats.encoded_time); + stats->total_encoded_images_payload += + frame_stats.encoded_image_size.bytes(); + stats->target_encode_bitrate.AddSample(StatsSample( + frame_stats.target_encode_bitrate, frame_stats.encoded_time, metadata)); + for (const auto& [spatial_layer, qp_values] : + frame_stats.spatial_layers_qp) { + for (SamplesStatsCounter::StatsSample qp : qp_values.GetTimedSamples()) { + qp.metadata = metadata; + stats->spatial_layers_qp[spatial_layer].AddSample(std::move(qp)); + } + } + + // Stats sliced on encoded frame type. + if (frame_stats.encoded_frame_type == VideoFrameType::kVideoFrameKey) { + ++stats->num_send_key_frames; + } + } + // Next stats can be calculated only if frame was received on remote side. + if (comparison.type != FrameComparisonType::kDroppedFrame || + comparison.frame_stats.decoder_failed) { + if (frame_stats.rendered_time.IsFinite()) { + stats->total_delay_incl_transport_ms.AddSample( + StatsSample(frame_stats.rendered_time - frame_stats.captured_time, + frame_stats.received_time, metadata)); + stats->receive_to_render_time_ms.AddSample( + StatsSample(frame_stats.rendered_time - frame_stats.received_time, + frame_stats.rendered_time, metadata)); + } + if (frame_stats.decode_start_time.IsFinite()) { + stats->transport_time_ms.AddSample( + StatsSample(frame_stats.decode_start_time - frame_stats.encoded_time, + frame_stats.decode_start_time, metadata)); + + // Stats sliced on decoded frame type. + if (frame_stats.pre_decoded_frame_type == + VideoFrameType::kVideoFrameKey) { + ++stats->num_recv_key_frames; + stats->recv_key_frame_size_bytes.AddSample( + StatsSample(frame_stats.pre_decoded_image_size.bytes(), + frame_stats.decode_start_time, metadata)); + } else if (frame_stats.pre_decoded_frame_type == + VideoFrameType::kVideoFrameDelta) { + stats->recv_delta_frame_size_bytes.AddSample( + StatsSample(frame_stats.pre_decoded_image_size.bytes(), + frame_stats.decode_start_time, metadata)); + } + } + if (frame_stats.decode_end_time.IsFinite()) { + stats->decode_time_ms.AddSample(StatsSample( + frame_stats.decode_end_time - frame_stats.decode_start_time, + frame_stats.decode_end_time, metadata)); + stats->resolution_of_decoded_frame.AddSample( + StatsSample(*comparison.frame_stats.decoded_frame_width * + *comparison.frame_stats.decoded_frame_height, + frame_stats.decode_end_time, metadata)); + } + + if (frame_stats.prev_frame_rendered_time.IsFinite() && + frame_stats.rendered_time.IsFinite()) { + TimeDelta time_between_rendered_frames = + frame_stats.rendered_time - frame_stats.prev_frame_rendered_time; + stats->time_between_rendered_frames_ms.AddSample(StatsSample( + time_between_rendered_frames, frame_stats.rendered_time, metadata)); + TimeDelta average_time_between_rendered_frames = TimeDelta::Millis( + stats->time_between_rendered_frames_ms.GetAverage()); + if (time_between_rendered_frames > + std::max(kFreezeThreshold + average_time_between_rendered_frames, + 3 * average_time_between_rendered_frames)) { + stats->freeze_time_ms.AddSample(StatsSample( + time_between_rendered_frames, frame_stats.rendered_time, metadata)); + auto freeze_end_it = + stream_last_freeze_end_time_.find(comparison.stats_key); + RTC_DCHECK(freeze_end_it != stream_last_freeze_end_time_.end()); + stats->time_between_freezes_ms.AddSample(StatsSample( + frame_stats.prev_frame_rendered_time - freeze_end_it->second, + frame_stats.rendered_time, metadata)); + freeze_end_it->second = frame_stats.rendered_time; + } + } + } + // Compute stream codec info. + if (frame_stats.used_encoder.has_value()) { + if (stats->encoders.empty() || stats->encoders.back().codec_name != + frame_stats.used_encoder->codec_name) { + stats->encoders.push_back(*frame_stats.used_encoder); + } + stats->encoders.back().last_frame_id = + frame_stats.used_encoder->last_frame_id; + stats->encoders.back().switched_from_at = + frame_stats.used_encoder->switched_from_at; + } + + if (frame_stats.used_decoder.has_value()) { + if (stats->decoders.empty() || stats->decoders.back().codec_name != + frame_stats.used_decoder->codec_name) { + stats->decoders.push_back(*frame_stats.used_decoder); + } + stats->decoders.back().last_frame_id = + frame_stats.used_decoder->last_frame_id; + stats->decoders.back().switched_from_at = + frame_stats.used_decoder->switched_from_at; + } +} + +Timestamp DefaultVideoQualityAnalyzerFramesComparator::Now() { + return clock_->CurrentTime(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h new file mode 100644 index 0000000000..006c3eb9bf --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2021 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_FRAMES_COMPARATOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_FRAMES_COMPARATOR_H_ + +#include <deque> +#include <map> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "rtc_base/event.h" +#include "rtc_base/platform_thread.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" + +namespace webrtc { + +struct FramesComparatorStats { + // Size of analyzer internal comparisons queue, measured when new element + // id added to the queue. + SamplesStatsCounter comparisons_queue_size; + // Number of performed comparisons of 2 video frames from captured and + // rendered streams. + int64_t comparisons_done = 0; + // Number of cpu overloaded comparisons. Comparison is cpu overloaded if it is + // queued when there are too many not processed comparisons in the queue. + // Overloaded comparison doesn't include metrics like SSIM and PSNR that + // require heavy computations. + int64_t cpu_overloaded_comparisons_done = 0; + // Number of memory overloaded comparisons. Comparison is memory overloaded if + // it is queued when its captured frame was already removed due to high memory + // usage for that video stream. + int64_t memory_overloaded_comparisons_done = 0; +}; + +// Performs comparisons of added frames and tracks frames related statistics. +// This class is thread safe. +class DefaultVideoQualityAnalyzerFramesComparator { + public: + // Creates frames comparator. + // Frames comparator doesn't use `options.enable_receive_own_stream` for any + // purposes, because it's unrelated to its functionality. + DefaultVideoQualityAnalyzerFramesComparator( + webrtc::Clock* clock, + DefaultVideoQualityAnalyzerCpuMeasurer& cpu_measurer, + DefaultVideoQualityAnalyzerOptions options = {}) + : options_(options), clock_(clock), cpu_measurer_(cpu_measurer) {} + ~DefaultVideoQualityAnalyzerFramesComparator() { Stop({}); } + + // Starts frames comparator. This method must be invoked before calling + // any other method on this object. + void Start(int max_threads_count); + // Stops frames comparator. This method will block until all added frame + // comparisons will be processed. After `Stop()` is invoked no more new + // comparisons can be added to this frames comparator. + // + // `last_rendered_frame_time` contains timestamps of last rendered frame for + // each (stream, sender, receiver) tuple to properly update time between + // freezes: it has include time from the last freeze until and of call. + void Stop( + const std::map<InternalStatsKey, Timestamp>& last_rendered_frame_times); + + // Ensures that stream `stream_index` has stats objects created for all + // potential receivers. This method must be called before adding any + // frames comparison for that stream. + void EnsureStatsForStream(size_t stream_index, + size_t sender_peer_index, + size_t peers_count, + Timestamp captured_time, + Timestamp start_time); + // Ensures that newly added participant will have stream stats objects created + // for all streams which they can receive. This method must be called before + // any frames comparison will be added for the newly added participant. + // + // `stream_started_time` - start time of each stream for which stats object + // has to be created. + // `start_time` - call start time. + void RegisterParticipantInCall( + rtc::ArrayView<std::pair<InternalStatsKey, Timestamp>> + stream_started_time, + Timestamp start_time); + + // `captured` - video frame captured by sender to use for PSNR/SSIM + // computation. If `type` is `FrameComparisonType::kRegular` and + // `captured` is `absl::nullopt` comparison is assumed to be overloaded + // due to memory constraints. + // `rendered` - video frame rendered by receiver to use for PSNR/SSIM + // computation. Required only if `type` is + // `FrameComparisonType::kRegular`, but can still be omitted if + // `captured` is `absl::nullopt`. + void AddComparison(InternalStatsKey stats_key, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats); + // `skipped_between_rendered` - amount of frames dropped on this stream before + // last received frame and current frame. + void AddComparison(InternalStatsKey stats_key, + int skipped_between_rendered, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats); + + std::map<InternalStatsKey, StreamStats> stream_stats() const { + MutexLock lock(&mutex_); + return stream_stats_; + } + FramesComparatorStats frames_comparator_stats() const { + MutexLock lock(&mutex_); + return frames_comparator_stats_; + } + + private: + enum State { kNew, kActive, kStopped }; + + void AddComparisonInternal(InternalStatsKey stats_key, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + void ProcessComparisons(); + void ProcessComparison(const FrameComparison& comparison); + Timestamp Now(); + + const DefaultVideoQualityAnalyzerOptions options_; + webrtc::Clock* const clock_; + DefaultVideoQualityAnalyzerCpuMeasurer& cpu_measurer_; + + mutable Mutex mutex_; + State state_ RTC_GUARDED_BY(mutex_) = State::kNew; + std::map<InternalStatsKey, StreamStats> stream_stats_ RTC_GUARDED_BY(mutex_); + std::map<InternalStatsKey, Timestamp> stream_last_freeze_end_time_ + RTC_GUARDED_BY(mutex_); + std::deque<FrameComparison> comparisons_ RTC_GUARDED_BY(mutex_); + FramesComparatorStats frames_comparator_stats_ RTC_GUARDED_BY(mutex_); + + std::vector<rtc::PlatformThread> thread_pool_; + rtc::Event comparison_available_event_; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_FRAMES_COMPARATOR_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator_test.cc new file mode 100644 index 0000000000..8d3cd47ed6 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator_test.cc @@ -0,0 +1,1648 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/default_video_quality_analyzer_frames_comparator.h" + +#include <map> +#include <string> +#include <vector> + +#include "api/test/create_frame_generator.h" +#include "api/units/timestamp.h" +#include "rtc_base/strings/string_builder.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" + +namespace webrtc { +namespace { + +using ::testing::Contains; +using ::testing::DoubleEq; +using ::testing::Each; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::SizeIs; + +using StatsSample = ::webrtc::SamplesStatsCounter::StatsSample; + +constexpr int kMaxFramesInFlightPerStream = 10; + +DefaultVideoQualityAnalyzerOptions AnalyzerOptionsForTest() { + DefaultVideoQualityAnalyzerOptions options; + options.compute_psnr = false; + options.compute_ssim = false; + options.adjust_cropping_before_comparing_frames = false; + options.max_frames_in_flight_per_stream_count = kMaxFramesInFlightPerStream; + return options; +} + +VideoFrame CreateFrame(uint16_t frame_id, + int width, + int height, + Timestamp timestamp) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(width, height, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + test::FrameGeneratorInterface::VideoFrameData frame_data = + frame_generator->NextFrame(); + return VideoFrame::Builder() + .set_id(frame_id) + .set_video_frame_buffer(frame_data.buffer) + .set_update_rect(frame_data.update_rect) + .set_timestamp_us(timestamp.us()) + .build(); +} + +StreamCodecInfo Vp8CodecForOneFrame(uint16_t frame_id, Timestamp time) { + StreamCodecInfo info; + info.codec_name = "VP8"; + info.first_frame_id = frame_id; + info.last_frame_id = frame_id; + info.switched_on_at = time; + info.switched_from_at = time; + return info; +} + +FrameStats FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame( + uint16_t frame_id, + Timestamp captured_time) { + FrameStats frame_stats(frame_id, captured_time); + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Decode time is in microseconds. + frame_stats.decode_end_time = captured_time + TimeDelta::Micros(40010); + frame_stats.rendered_time = captured_time + TimeDelta::Millis(60); + frame_stats.used_encoder = Vp8CodecForOneFrame(1, frame_stats.encoded_time); + frame_stats.used_decoder = + Vp8CodecForOneFrame(1, frame_stats.decode_end_time); + frame_stats.decoded_frame_width = 10; + frame_stats.decoded_frame_height = 10; + return frame_stats; +} + +FrameStats ShiftStatsOn(const FrameStats& stats, TimeDelta delta) { + FrameStats frame_stats(stats.frame_id, stats.captured_time + delta); + frame_stats.pre_encode_time = stats.pre_encode_time + delta; + frame_stats.encoded_time = stats.encoded_time + delta; + frame_stats.received_time = stats.received_time + delta; + frame_stats.decode_start_time = stats.decode_start_time + delta; + frame_stats.decode_end_time = stats.decode_end_time + delta; + frame_stats.rendered_time = stats.rendered_time + delta; + + frame_stats.used_encoder = stats.used_encoder; + frame_stats.used_decoder = stats.used_decoder; + frame_stats.decoded_frame_width = stats.decoded_frame_width; + frame_stats.decoded_frame_height = stats.decoded_frame_height; + + return frame_stats; +} + +SamplesStatsCounter StatsCounter( + const std::vector<std::pair<double, Timestamp>>& samples) { + SamplesStatsCounter counter; + for (const std::pair<double, Timestamp>& sample : samples) { + counter.AddSample(SamplesStatsCounter::StatsSample{.value = sample.first, + .time = sample.second}); + } + return counter; +} + +double GetFirstOrDie(const SamplesStatsCounter& counter) { + EXPECT_FALSE(counter.IsEmpty()) << "Counter has to be not empty"; + return counter.GetSamples()[0]; +} + +void AssertFirstMetadataHasField(const SamplesStatsCounter& counter, + const std::string& field_name, + const std::string& field_value) { + EXPECT_FALSE(counter.IsEmpty()) << "Coutner has to be not empty"; + EXPECT_THAT(counter.GetTimedSamples()[0].metadata, + Contains(Pair(field_name, field_value))); +} + +std::string ToString(const SamplesStatsCounter& counter) { + rtc::StringBuilder out; + for (const StatsSample& s : counter.GetTimedSamples()) { + out << "{ time_ms=" << s.time.ms() << "; value=" << s.value << "}, "; + } + return out.str(); +} + +void ExpectEmpty(const SamplesStatsCounter& counter) { + EXPECT_TRUE(counter.IsEmpty()) + << "Expected empty SamplesStatsCounter, but got " << ToString(counter); +} + +void ExpectEmpty(const SamplesRateCounter& counter) { + EXPECT_TRUE(counter.IsEmpty()) + << "Expected empty SamplesRateCounter, but got " + << counter.GetEventsPerSecond(); +} + +void ExpectSizeAndAllElementsAre(const SamplesStatsCounter& counter, + int size, + double value) { + EXPECT_EQ(counter.NumSamples(), size); + EXPECT_THAT(counter.GetSamples(), Each(DoubleEq(value))); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + StatsPresentedAfterAddingOneComparison) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest()); + + Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + size_t peers_count = 2; + InternalStatsKey stats_key(stream, sender, receiver); + + FrameStats frame_stats = FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame( + /*frame_id=*/1, stream_start_time); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, peers_count, + stream_start_time, stream_start_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kRegular, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + std::map<InternalStatsKey, StreamStats> stats = comparator.stream_stats(); + ExpectSizeAndAllElementsAre(stats.at(stats_key).transport_time_ms, /*size=*/1, + /*value=*/20.0); + ExpectSizeAndAllElementsAre(stats.at(stats_key).total_delay_incl_transport_ms, + /*size=*/1, /*value=*/60.0); + ExpectSizeAndAllElementsAre(stats.at(stats_key).encode_time_ms, /*size=*/1, + /*value=*/10.0); + ExpectSizeAndAllElementsAre(stats.at(stats_key).decode_time_ms, /*size=*/1, + /*value=*/0.01); + ExpectSizeAndAllElementsAre(stats.at(stats_key).receive_to_render_time_ms, + /*size=*/1, /*value=*/30.0); + ExpectSizeAndAllElementsAre(stats.at(stats_key).resolution_of_decoded_frame, + /*size=*/1, /*value=*/100.0); +} + +TEST( + DefaultVideoQualityAnalyzerFramesComparatorTest, + MultiFrameStatsPresentedWithMetadataAfterAddingTwoComparisonWith10msDelay) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest()); + + Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + size_t peers_count = 2; + InternalStatsKey stats_key(stream, sender, receiver); + + FrameStats frame_stats1 = FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame( + /*frame_id=*/1, stream_start_time); + FrameStats frame_stats2 = FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame( + /*frame_id=*/2, stream_start_time + TimeDelta::Millis(15)); + frame_stats2.prev_frame_rendered_time = frame_stats1.rendered_time; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, peers_count, + stream_start_time, stream_start_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kRegular, frame_stats1); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kRegular, frame_stats2); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + std::map<InternalStatsKey, StreamStats> stats = comparator.stream_stats(); + ExpectSizeAndAllElementsAre( + stats.at(stats_key).time_between_rendered_frames_ms, /*size=*/1, + /*value=*/15.0); + AssertFirstMetadataHasField( + stats.at(stats_key).time_between_rendered_frames_ms, "frame_id", "2"); + EXPECT_DOUBLE_EQ(stats.at(stats_key).encode_frame_rate.GetEventsPerSecond(), + 2.0 / 15 * 1000) + << "There should be 2 events with interval of 15 ms"; +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + FrameInFlightStatsAreHandledCorrectly) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest()); + + Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + size_t peers_count = 2; + InternalStatsKey stats_key(stream, sender, receiver); + + // There are 7 different timings inside frame stats: captured, pre_encode, + // encoded, received, decode_start, decode_end, rendered. captured is always + // set and received is set together with decode_start. So we create 6 + // different frame stats with interval of 15 ms, where for each stat next + // timings will be set + // * 1st - captured + // * 2nd - captured, pre_encode + // * 3rd - captured, pre_encode, encoded + // * 4th - captured, pre_encode, encoded, received, decode_start + // * 5th - captured, pre_encode, encoded, received, decode_start, decode_end + // * 6th - all of them set + std::vector<FrameStats> stats; + // 1st stat + FrameStats frame_stats(/*frame_id=*/1, stream_start_time); + stats.push_back(frame_stats); + // 2nd stat + frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15)); + frame_stats.frame_id = 2; + frame_stats.pre_encode_time = + frame_stats.captured_time + TimeDelta::Millis(10); + stats.push_back(frame_stats); + // 3rd stat + frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15)); + frame_stats.frame_id = 3; + frame_stats.encoded_time = frame_stats.captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = Vp8CodecForOneFrame(1, frame_stats.encoded_time); + stats.push_back(frame_stats); + // 4th stat + frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15)); + frame_stats.frame_id = 4; + frame_stats.received_time = frame_stats.captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = + frame_stats.captured_time + TimeDelta::Millis(40); + stats.push_back(frame_stats); + // 5th stat + frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15)); + frame_stats.frame_id = 5; + frame_stats.decode_end_time = + frame_stats.captured_time + TimeDelta::Millis(50); + frame_stats.used_decoder = + Vp8CodecForOneFrame(1, frame_stats.decode_end_time); + frame_stats.decoded_frame_width = 10; + frame_stats.decoded_frame_height = 10; + stats.push_back(frame_stats); + // 6th stat + frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15)); + frame_stats.frame_id = 6; + frame_stats.rendered_time = frame_stats.captured_time + TimeDelta::Millis(60); + stats.push_back(frame_stats); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, peers_count, + stream_start_time, stream_start_time); + for (size_t i = 0; i < stats.size() - 1; ++i) { + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, stats[i]); + } + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kRegular, + stats[stats.size() - 1]); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats result_stats = comparator.stream_stats().at(stats_key); + + EXPECT_DOUBLE_EQ(result_stats.transport_time_ms.GetAverage(), 20.0) + << ToString(result_stats.transport_time_ms); + EXPECT_EQ(result_stats.transport_time_ms.NumSamples(), 3); + + EXPECT_DOUBLE_EQ(result_stats.total_delay_incl_transport_ms.GetAverage(), + 60.0) + << ToString(result_stats.total_delay_incl_transport_ms); + EXPECT_EQ(result_stats.total_delay_incl_transport_ms.NumSamples(), 1); + + EXPECT_DOUBLE_EQ(result_stats.encode_time_ms.GetAverage(), 10) + << ToString(result_stats.encode_time_ms); + EXPECT_EQ(result_stats.encode_time_ms.NumSamples(), 4); + + EXPECT_DOUBLE_EQ(result_stats.decode_time_ms.GetAverage(), 10) + << ToString(result_stats.decode_time_ms); + EXPECT_EQ(result_stats.decode_time_ms.NumSamples(), 2); + + EXPECT_DOUBLE_EQ(result_stats.receive_to_render_time_ms.GetAverage(), 30) + << ToString(result_stats.receive_to_render_time_ms); + EXPECT_EQ(result_stats.receive_to_render_time_ms.NumSamples(), 1); + + EXPECT_DOUBLE_EQ(result_stats.resolution_of_decoded_frame.GetAverage(), 100) + << ToString(result_stats.resolution_of_decoded_frame); + EXPECT_EQ(result_stats.resolution_of_decoded_frame.NumSamples(), 2); + + EXPECT_DOUBLE_EQ(result_stats.encode_frame_rate.GetEventsPerSecond(), + 4.0 / 45 * 1000) + << "There should be 4 events with interval of 15 ms"; +} + +// Tests to validate that stats for each possible input frame are computed +// correctly. +// Frame in flight start +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + CapturedOnlyInFlightFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectEmpty(stats.encode_time_ms); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectEmpty(stats.target_encode_bitrate); + EXPECT_THAT(stats.spatial_layers_qp, IsEmpty()); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 0); + EXPECT_EQ(stats.num_send_key_frames, 0); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_THAT(stats.encoders, IsEmpty()); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + PreEncodedInFlightFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectEmpty(stats.encode_time_ms); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectEmpty(stats.target_encode_bitrate); + EXPECT_THAT(stats.spatial_layers_qp, IsEmpty()); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 0); + EXPECT_EQ(stats.num_send_key_frames, 0); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_THAT(stats.encoders, IsEmpty()); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + EncodedInFlightKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + EncodedInFlightDeltaFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameDelta; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 0); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + PreDecodedInFlightKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectSizeAndAllElementsAre(stats.transport_time_ms, /*size=*/1, + /*value=*/20.0); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectSizeAndAllElementsAre(stats.recv_key_frame_size_bytes, /*size=*/1, + /*value=*/500.0); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 1); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + DecodedInFlightKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Frame decoded + frame_stats.decode_end_time = captured_time + TimeDelta::Millis(50); + frame_stats.decoded_frame_width = 200; + frame_stats.decoded_frame_height = 100; + + frame_stats.used_decoder = + Vp8CodecForOneFrame(frame_id, frame_stats.decode_end_time); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectSizeAndAllElementsAre(stats.transport_time_ms, /*size=*/1, + /*value=*/20.0); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectSizeAndAllElementsAre(stats.decode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + EXPECT_GE(GetFirstOrDie(stats.resolution_of_decoded_frame), 200 * 100.0); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectSizeAndAllElementsAre(stats.recv_key_frame_size_bytes, /*size=*/1, + /*value=*/500.0); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 1); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_EQ(stats.decoders, + std::vector<StreamCodecInfo>{*frame_stats.used_decoder}); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + DecoderFailureOnInFlightKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Frame decoded + frame_stats.decoder_failed = true; + frame_stats.used_decoder = + Vp8CodecForOneFrame(frame_id, frame_stats.decode_end_time); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kFrameInFlight, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectSizeAndAllElementsAre(stats.transport_time_ms, /*size=*/1, + /*value=*/20.0); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectSizeAndAllElementsAre(stats.recv_key_frame_size_bytes, /*size=*/1, + /*value=*/500.0); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 1); + // All frame in flight are not considered as dropped. + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_EQ(stats.decoders, + std::vector<StreamCodecInfo>{*frame_stats.used_decoder}); +} +// Frame in flight end + +// Dropped frame start +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + CapturedOnlyDroppedFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectEmpty(stats.encode_time_ms); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectEmpty(stats.target_encode_bitrate); + EXPECT_THAT(stats.spatial_layers_qp, IsEmpty()); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 0); + EXPECT_EQ(stats.num_send_key_frames, 0); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 1}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_THAT(stats.encoders, IsEmpty()); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + PreEncodedDroppedFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectEmpty(stats.encode_time_ms); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectEmpty(stats.target_encode_bitrate); + EXPECT_THAT(stats.spatial_layers_qp, IsEmpty()); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 0); + EXPECT_EQ(stats.num_send_key_frames, 0); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 1}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_THAT(stats.encoders, IsEmpty()); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + EncodedDroppedKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 1}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + EncodedDroppedDeltaFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameDelta; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 0); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 1}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + PreDecodedDroppedKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 1}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_THAT(stats.decoders, IsEmpty()); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + DecodedDroppedKeyFrameAccountedInStats) { + // We don't really drop frames after decoder, so it's a bit unclear what is + // correct way to account such frames in stats, so this test just fixes some + // current way. + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Frame decoded + frame_stats.decode_end_time = captured_time + TimeDelta::Millis(50); + frame_stats.used_decoder = + Vp8CodecForOneFrame(frame_id, frame_stats.decode_end_time); + frame_stats.decoded_frame_width = 200; + frame_stats.decoded_frame_height = 100; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectEmpty(stats.transport_time_ms); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectEmpty(stats.recv_key_frame_size_bytes); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 0); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 1}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_EQ(stats.decoders, + std::vector<StreamCodecInfo>{*frame_stats.used_decoder}); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + DecoderFailedDroppedKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Frame decoded + frame_stats.decoder_failed = true; + frame_stats.used_decoder = + Vp8CodecForOneFrame(frame_id, frame_stats.decode_end_time); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kDroppedFrame, frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + ExpectEmpty(stats.psnr); + ExpectEmpty(stats.ssim); + ExpectSizeAndAllElementsAre(stats.transport_time_ms, /*size=*/1, + /*value=*/20.0); + ExpectEmpty(stats.total_delay_incl_transport_ms); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + ExpectEmpty(stats.decode_time_ms); + ExpectEmpty(stats.receive_to_render_time_ms); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + ExpectEmpty(stats.resolution_of_decoded_frame); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectSizeAndAllElementsAre(stats.recv_key_frame_size_bytes, /*size=*/1, + /*value=*/500.0); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 1); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 1}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_EQ(stats.decoders, + std::vector<StreamCodecInfo>{*frame_stats.used_decoder}); +} +// Dropped frame end + +// Regular frame start +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + RenderedKeyFrameAccountedInStats) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + VideoFrame frame = + CreateFrame(frame_id, /*width=*/320, /*height=*/180, captured_time); + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Frame decoded + frame_stats.decode_end_time = captured_time + TimeDelta::Millis(50); + frame_stats.used_decoder = + Vp8CodecForOneFrame(frame_id, frame_stats.decode_end_time); + frame_stats.decoded_frame_width = 200; + frame_stats.decoded_frame_height = 100; + // Frame rendered + frame_stats.rendered_time = captured_time + TimeDelta::Millis(60); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/frame, + /*rendered=*/frame, FrameComparisonType::kRegular, + frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + EXPECT_EQ(stats.stream_started_time, captured_time); + EXPECT_GE(GetFirstOrDie(stats.psnr), 20); + EXPECT_GE(GetFirstOrDie(stats.ssim), 0.5); + ExpectSizeAndAllElementsAre(stats.transport_time_ms, /*size=*/1, + /*value=*/20.0); + EXPECT_GE(GetFirstOrDie(stats.total_delay_incl_transport_ms), 60.0); + ExpectEmpty(stats.time_between_rendered_frames_ms); + ExpectEmpty(stats.encode_frame_rate); + ExpectSizeAndAllElementsAre(stats.encode_time_ms, /*size=*/1, /*value=*/10.0); + EXPECT_GE(GetFirstOrDie(stats.decode_time_ms), 10.0); + EXPECT_GE(GetFirstOrDie(stats.receive_to_render_time_ms), 30.0); + ExpectEmpty(stats.skipped_between_rendered); + ExpectSizeAndAllElementsAre(stats.freeze_time_ms, /*size=*/1, /*value=*/0); + ExpectEmpty(stats.time_between_freezes_ms); + EXPECT_GE(GetFirstOrDie(stats.resolution_of_decoded_frame), 200 * 100.0); + ExpectSizeAndAllElementsAre(stats.target_encode_bitrate, /*size=*/1, + /*value=*/2000.0); + EXPECT_THAT(stats.spatial_layers_qp, SizeIs(1)); + ExpectSizeAndAllElementsAre(stats.spatial_layers_qp[0], /*size=*/2, + /*value=*/5.0); + ExpectSizeAndAllElementsAre(stats.recv_key_frame_size_bytes, /*size=*/1, + /*value=*/500.0); + ExpectEmpty(stats.recv_delta_frame_size_bytes); + EXPECT_EQ(stats.total_encoded_images_payload, 1000); + EXPECT_EQ(stats.num_send_key_frames, 1); + EXPECT_EQ(stats.num_recv_key_frames, 1); + EXPECT_THAT(stats.dropped_by_phase, Eq(std::map<FrameDropPhase, int64_t>{ + {FrameDropPhase::kBeforeEncoder, 0}, + {FrameDropPhase::kByEncoder, 0}, + {FrameDropPhase::kTransport, 0}, + {FrameDropPhase::kByDecoder, 0}, + {FrameDropPhase::kAfterDecoder, 0}})); + EXPECT_EQ(stats.encoders, + std::vector<StreamCodecInfo>{*frame_stats.used_encoder}); + EXPECT_EQ(stats.decoders, + std::vector<StreamCodecInfo>{*frame_stats.used_decoder}); +} + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, AllStatsHaveMetadataSet) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, + DefaultVideoQualityAnalyzerOptions()); + + Timestamp captured_time = Clock::GetRealTimeClock()->CurrentTime(); + uint16_t frame_id = 1; + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + InternalStatsKey stats_key(stream, sender, receiver); + + // Frame captured + VideoFrame frame = + CreateFrame(frame_id, /*width=*/320, /*height=*/180, captured_time); + FrameStats frame_stats(/*frame_id=*/1, captured_time); + // Frame pre encoded + frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10); + // Frame encoded + frame_stats.encoded_time = captured_time + TimeDelta::Millis(20); + frame_stats.used_encoder = + Vp8CodecForOneFrame(frame_id, frame_stats.encoded_time); + frame_stats.encoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.encoded_image_size = DataSize::Bytes(1000); + frame_stats.target_encode_bitrate = 2000; + frame_stats.spatial_layers_qp = { + {0, StatsCounter( + /*samples=*/{{5, Timestamp::Seconds(1)}, + {5, Timestamp::Seconds(2)}})}}; + // Frame pre decoded + frame_stats.pre_decoded_frame_type = VideoFrameType::kVideoFrameKey; + frame_stats.pre_decoded_image_size = DataSize::Bytes(500); + frame_stats.received_time = captured_time + TimeDelta::Millis(30); + frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40); + // Frame decoded + frame_stats.decode_end_time = captured_time + TimeDelta::Millis(50); + frame_stats.used_decoder = + Vp8CodecForOneFrame(frame_id, frame_stats.decode_end_time); + // Frame rendered + frame_stats.rendered_time = captured_time + TimeDelta::Millis(60); + frame_stats.decoded_frame_width = 200; + frame_stats.decoded_frame_height = 100; + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, /*peers_count=*/2, + captured_time, captured_time); + comparator.AddComparison(stats_key, + /*captured=*/frame, + /*rendered=*/frame, FrameComparisonType::kRegular, + frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + EXPECT_EQ(comparator.stream_stats().size(), 1lu); + StreamStats stats = comparator.stream_stats().at(stats_key); + AssertFirstMetadataHasField(stats.psnr, "frame_id", "1"); + AssertFirstMetadataHasField(stats.ssim, "frame_id", "1"); + AssertFirstMetadataHasField(stats.transport_time_ms, "frame_id", "1"); + AssertFirstMetadataHasField(stats.total_delay_incl_transport_ms, "frame_id", + "1"); + AssertFirstMetadataHasField(stats.encode_time_ms, "frame_id", "1"); + AssertFirstMetadataHasField(stats.decode_time_ms, "frame_id", "1"); + AssertFirstMetadataHasField(stats.receive_to_render_time_ms, "frame_id", "1"); + AssertFirstMetadataHasField(stats.resolution_of_decoded_frame, "frame_id", + "1"); + AssertFirstMetadataHasField(stats.target_encode_bitrate, "frame_id", "1"); + AssertFirstMetadataHasField(stats.spatial_layers_qp[0], "frame_id", "1"); + AssertFirstMetadataHasField(stats.recv_key_frame_size_bytes, "frame_id", "1"); + + ExpectEmpty(stats.recv_delta_frame_size_bytes); +} +// Regular frame end + +TEST(DefaultVideoQualityAnalyzerFramesComparatorTest, + FreezeStatsPresentedWithMetadataAfterAddFrameWithSkippedAndDelay) { + DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer; + DefaultVideoQualityAnalyzerFramesComparator comparator( + Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest()); + + Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime(); + size_t stream = 0; + size_t sender = 0; + size_t receiver = 1; + size_t peers_count = 2; + InternalStatsKey stats_key(stream, sender, receiver); + + comparator.Start(/*max_threads_count=*/1); + comparator.EnsureStatsForStream(stream, sender, peers_count, + stream_start_time, stream_start_time); + + // Add 5 frames which were rendered with 30 fps (~30ms between frames) + // Frame ids are in [1..5] and last frame is with 120ms offset from first. + Timestamp prev_frame_rendered_time = Timestamp::MinusInfinity(); + for (int i = 0; i < 5; ++i) { + FrameStats frame_stats = FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame( + /*frame_id=*/i + 1, stream_start_time + TimeDelta::Millis(30 * i)); + frame_stats.prev_frame_rendered_time = prev_frame_rendered_time; + prev_frame_rendered_time = frame_stats.rendered_time; + + comparator.AddComparison(stats_key, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kRegular, frame_stats); + } + + // Next frame was rendered with 4 frames skipped and delay 300ms after last + // frame. + FrameStats freeze_frame_stats = + FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame( + /*frame_id=*/10, stream_start_time + TimeDelta::Millis(120 + 300)); + freeze_frame_stats.prev_frame_rendered_time = prev_frame_rendered_time; + + comparator.AddComparison(stats_key, + /*skipped_between_rendered=*/4, + /*captured=*/absl::nullopt, + /*rendered=*/absl::nullopt, + FrameComparisonType::kRegular, freeze_frame_stats); + comparator.Stop(/*last_rendered_frame_times=*/{}); + + StreamStats stats = comparator.stream_stats().at(stats_key); + ASSERT_THAT(GetFirstOrDie(stats.skipped_between_rendered), Eq(4)); + AssertFirstMetadataHasField(stats.skipped_between_rendered, "frame_id", "10"); + ASSERT_THAT(GetFirstOrDie(stats.freeze_time_ms), Eq(300)); + AssertFirstMetadataHasField(stats.freeze_time_ms, "frame_id", "10"); + // 180ms is time from the stream start to the rendered time of the last frame + // among first 5 frames which were received before freeze. + ASSERT_THAT(GetFirstOrDie(stats.time_between_freezes_ms), Eq(180)); + AssertFirstMetadataHasField(stats.time_between_freezes_ms, "frame_id", "10"); +} +// Stats validation tests end. + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.cc new file mode 100644 index 0000000000..16f49ef154 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.cc @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h" + +#include "api/video/video_frame.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +std::string InternalStatsKey::ToString() const { + rtc::StringBuilder out; + out << "stream=" << stream << "_sender=" << sender + << "_receiver=" << receiver; + return out.str(); +} + +bool operator<(const InternalStatsKey& a, const InternalStatsKey& b) { + if (a.stream != b.stream) { + return a.stream < b.stream; + } + if (a.sender != b.sender) { + return a.sender < b.sender; + } + return a.receiver < b.receiver; +} + +bool operator==(const InternalStatsKey& a, const InternalStatsKey& b) { + return a.stream == b.stream && a.sender == b.sender && + a.receiver == b.receiver; +} + +FrameComparison::FrameComparison(InternalStatsKey stats_key, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats, + OverloadReason overload_reason) + : stats_key(std::move(stats_key)), + captured(std::move(captured)), + rendered(std::move(rendered)), + type(type), + frame_stats(std::move(frame_stats)), + overload_reason(overload_reason) {} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h new file mode 100644 index 0000000000..10f1314f46 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_internal_shared_objects.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_INTERNAL_SHARED_OBJECTS_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_INTERNAL_SHARED_OBJECTS_H_ + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_type.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" + +namespace webrtc { + +struct InternalStatsKey { + InternalStatsKey(size_t stream, size_t sender, size_t receiver) + : stream(stream), sender(sender), receiver(receiver) {} + + std::string ToString() const; + + size_t stream; + size_t sender; + size_t receiver; +}; + +// Required to use InternalStatsKey as std::map key. +bool operator<(const InternalStatsKey& a, const InternalStatsKey& b); +bool operator==(const InternalStatsKey& a, const InternalStatsKey& b); + +// Final stats computed for frame after it went through the whole video +// pipeline from capturing to rendering or dropping. +struct FrameStats { + FrameStats(uint16_t frame_id, Timestamp captured_time) + : frame_id(frame_id), captured_time(captured_time) {} + + uint16_t frame_id; + // Frame events timestamp. + Timestamp captured_time; + Timestamp pre_encode_time = Timestamp::MinusInfinity(); + Timestamp encoded_time = Timestamp::MinusInfinity(); + // Time when last packet of a frame was received. + Timestamp received_time = Timestamp::MinusInfinity(); + Timestamp decode_start_time = Timestamp::MinusInfinity(); + Timestamp decode_end_time = Timestamp::MinusInfinity(); + Timestamp rendered_time = Timestamp::MinusInfinity(); + Timestamp prev_frame_rendered_time = Timestamp::MinusInfinity(); + + VideoFrameType encoded_frame_type = VideoFrameType::kEmptyFrame; + DataSize encoded_image_size = DataSize::Bytes(0); + VideoFrameType pre_decoded_frame_type = VideoFrameType::kEmptyFrame; + DataSize pre_decoded_image_size = DataSize::Bytes(0); + uint32_t target_encode_bitrate = 0; + // Sender side qp values per spatial layer. In case when spatial layer is not + // set for `webrtc::EncodedImage`, 0 is used as default. + std::map<int, SamplesStatsCounter> spatial_layers_qp; + + absl::optional<int> decoded_frame_width = absl::nullopt; + absl::optional<int> decoded_frame_height = absl::nullopt; + + // Can be not set if frame was dropped by encoder. + absl::optional<StreamCodecInfo> used_encoder = absl::nullopt; + // Can be not set if frame was dropped in the network. + absl::optional<StreamCodecInfo> used_decoder = absl::nullopt; + + bool decoder_failed = false; +}; + +// Describes why comparison was done in overloaded mode (without calculating +// PSNR and SSIM). +enum class OverloadReason { + kNone, + // Not enough CPU to process all incoming comparisons. + kCpu, + // Not enough memory to store captured frames for all comparisons. + kMemory +}; + +enum class FrameComparisonType { + // Comparison for captured and rendered frame. + kRegular, + // Comparison for captured frame that is known to be dropped somewhere in + // video pipeline. + kDroppedFrame, + // Comparison for captured frame that was still in the video pipeline when + // test was stopped. It's unknown is this frame dropped or would it be + // delivered if test continue. + kFrameInFlight +}; + +// Represents comparison between two VideoFrames. Contains video frames itself +// and stats. Can be one of two types: +// 1. Normal - in this case `captured` is presented and either `rendered` is +// presented and `dropped` is false, either `rendered` is omitted and +// `dropped` is true. +// 2. Overloaded - in this case both `captured` and `rendered` are omitted +// because there were too many comparisons in the queue. `dropped` can be +// true or false showing was frame dropped or not. +struct FrameComparison { + FrameComparison(InternalStatsKey stats_key, + absl::optional<VideoFrame> captured, + absl::optional<VideoFrame> rendered, + FrameComparisonType type, + FrameStats frame_stats, + OverloadReason overload_reason); + + InternalStatsKey stats_key; + // Frames can be omitted if there too many computations waiting in the + // queue. + absl::optional<VideoFrame> captured; + absl::optional<VideoFrame> rendered; + FrameComparisonType type; + FrameStats frame_stats; + OverloadReason overload_reason; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_INTERNAL_SHARED_OBJECTS_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_metric_names_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_metric_names_test.cc new file mode 100644 index 0000000000..f5029ac956 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_metric_names_test.cc @@ -0,0 +1,682 @@ +/* + * 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 <memory> +#include <string> +#include <vector> + +#include "api/rtp_packet_info.h" +#include "api/rtp_packet_infos.h" +#include "api/test/create_frame_generator.h" +#include "api/test/metrics/metric.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/metrics/stdout_metrics_exporter.h" +#include "api/video/encoded_image.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_tools/frame_analyzer/video_geometry_aligner.h" +#include "system_wrappers/include/sleep.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h" + +namespace webrtc { +namespace { + +using ::testing::Contains; +using ::testing::SizeIs; +using ::testing::UnorderedElementsAre; + +using ::webrtc::test::DefaultMetricsLogger; +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Metric; +using ::webrtc::test::MetricsExporter; +using ::webrtc::test::StdoutMetricsExporter; +using ::webrtc::test::Unit; + +constexpr int kAnalyzerMaxThreadsCount = 1; +constexpr int kMaxFramesInFlightPerStream = 10; +constexpr int kFrameWidth = 320; +constexpr int kFrameHeight = 240; + +DefaultVideoQualityAnalyzerOptions AnalyzerOptionsForTest() { + DefaultVideoQualityAnalyzerOptions options; + options.compute_psnr = true; + options.compute_ssim = true; + options.adjust_cropping_before_comparing_frames = false; + options.max_frames_in_flight_per_stream_count = kMaxFramesInFlightPerStream; + options.report_detailed_frame_stats = true; + return options; +} + +VideoFrame NextFrame(test::FrameGeneratorInterface* frame_generator, + int64_t timestamp_us) { + test::FrameGeneratorInterface::VideoFrameData frame_data = + frame_generator->NextFrame(); + return VideoFrame::Builder() + .set_video_frame_buffer(frame_data.buffer) + .set_update_rect(frame_data.update_rect) + .set_timestamp_us(timestamp_us) + .build(); +} + +EncodedImage FakeEncode(const VideoFrame& frame) { + EncodedImage image; + std::vector<RtpPacketInfo> packet_infos; + packet_infos.push_back(RtpPacketInfo( + /*ssrc=*/1, + /*csrcs=*/{}, + /*rtp_timestamp=*/frame.timestamp(), + /*receive_time=*/Timestamp::Micros(frame.timestamp_us() + 10000))); + image.SetPacketInfos(RtpPacketInfos(packet_infos)); + return image; +} + +VideoFrame DeepCopy(const VideoFrame& frame) { + VideoFrame copy = frame; + copy.set_video_frame_buffer( + I420Buffer::Copy(*frame.video_frame_buffer()->ToI420())); + return copy; +} + +void PassFramesThroughAnalyzer(DefaultVideoQualityAnalyzer& analyzer, + absl::string_view sender, + absl::string_view stream_label, + std::vector<absl::string_view> receivers, + int frames_count, + test::FrameGeneratorInterface& frame_generator, + int interframe_delay_ms = 0) { + for (int i = 0; i < frames_count; ++i) { + VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1); + uint16_t frame_id = + analyzer.OnFrameCaptured(sender, std::string(stream_label), frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode(sender, frame); + analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + for (absl::string_view receiver : receivers) { + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(receiver, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(receiver, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(receiver, received_frame); + } + if (i < frames_count - 1 && interframe_delay_ms > 0) { + SleepMs(interframe_delay_ms); + } + } +} + +// Metric fields to assert on +struct MetricValidationInfo { + std::string test_case; + std::string name; + Unit unit; + ImprovementDirection improvement_direction; +}; + +bool operator==(const MetricValidationInfo& a, const MetricValidationInfo& b) { + return a.name == b.name && a.test_case == b.test_case && a.unit == b.unit && + a.improvement_direction == b.improvement_direction; +} + +std::ostream& operator<<(std::ostream& os, const MetricValidationInfo& m) { + os << "{ test_case=" << m.test_case << "; name=" << m.name + << "; unit=" << test::ToString(m.unit) + << "; improvement_direction=" << test::ToString(m.improvement_direction) + << " }"; + return os; +} + +std::vector<MetricValidationInfo> ToValidationInfo( + const std::vector<Metric>& metrics) { + std::vector<MetricValidationInfo> out; + for (const Metric& m : metrics) { + out.push_back( + MetricValidationInfo{.test_case = m.test_case, + .name = m.name, + .unit = m.unit, + .improvement_direction = m.improvement_direction}); + } + return out; +} + +std::vector<std::string> ToTestCases(const std::vector<Metric>& metrics) { + std::vector<std::string> out; + for (const Metric& m : metrics) { + out.push_back(m.test_case); + } + return out; +} + +TEST(DefaultVideoQualityAnalyzerMetricNamesTest, MetricNamesForP2PAreCorrect) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultMetricsLogger metrics_logger(Clock::GetRealTimeClock()); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + &metrics_logger, options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"}, + /*frames_count=*/5, *frame_generator, + /*interframe_delay_ms=*/50); + analyzer.Stop(); + + std::vector<MetricValidationInfo> metrics = + ToValidationInfo(metrics_logger.GetCollectedMetrics()); + EXPECT_THAT( + metrics, + UnorderedElementsAre( + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "ssim", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "transport_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "total_delay_incl_transport", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "time_between_rendered_frames", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "harmonic_framerate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "encode_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "encode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "time_between_freezes", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "freeze_time_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "pixels_per_frame", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "min_psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "decode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "receive_to_render_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "dropped_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "frames_in_flight", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "rendered_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "max_skipped", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "target_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "qp_sl0", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "actual_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "capture_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "num_encoded_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "num_decoded_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "num_send_key_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "num_recv_key_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "recv_key_frame_size_bytes", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "recv_delta_frame_size_bytes", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{.test_case = "test_case", + .name = "cpu_usage_%", + .unit = Unit::kUnitless, + .improvement_direction = + ImprovementDirection::kSmallerIsBetter})); +} + +TEST(DefaultVideoQualityAnalyzerMetricNamesTest, + MetricNamesFor3PeersAreCorrect) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultMetricsLogger metrics_logger(Clock::GetRealTimeClock()); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + &metrics_logger, options); + analyzer.Start("test_case", + std::vector<std::string>{"alice", "bob", "charlie"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", + {"bob", "charlie"}, + /*frames_count=*/5, *frame_generator, + /*interframe_delay_ms=*/50); + analyzer.Stop(); + + std::vector<MetricValidationInfo> metrics = + ToValidationInfo(metrics_logger.GetCollectedMetrics()); + EXPECT_THAT( + metrics, + UnorderedElementsAre( + // Bob + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "ssim", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "transport_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "total_delay_incl_transport", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "time_between_rendered_frames", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "harmonic_framerate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "encode_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "encode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "time_between_freezes", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "freeze_time_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "pixels_per_frame", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "min_psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "decode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "receive_to_render_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "dropped_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "frames_in_flight", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "rendered_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "max_skipped", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "target_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "qp_sl0", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "actual_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "capture_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "num_encoded_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "num_decoded_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "num_send_key_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "num_recv_key_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "recv_key_frame_size_bytes", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_bob", + .name = "recv_delta_frame_size_bytes", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + + // Charlie + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "ssim", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "transport_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "total_delay_incl_transport", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "time_between_rendered_frames", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "harmonic_framerate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "encode_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "encode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "time_between_freezes", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "freeze_time_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "pixels_per_frame", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "min_psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "decode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "receive_to_render_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "dropped_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "frames_in_flight", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "rendered_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "max_skipped", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "target_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "qp_sl0", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "actual_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "capture_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "num_encoded_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "num_decoded_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "num_send_key_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "num_recv_key_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "recv_key_frame_size_bytes", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{ + .test_case = "test_case/alice_video_alice_charlie", + .name = "recv_delta_frame_size_bytes", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter}, + MetricValidationInfo{.test_case = "test_case", + .name = "cpu_usage_%", + .unit = Unit::kUnitless, + .improvement_direction = + ImprovementDirection::kSmallerIsBetter})); +} + +TEST(DefaultVideoQualityAnalyzerMetricNamesTest, + TestCaseFor3PeerIsTheSameAfterAllPeersLeft) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultMetricsLogger metrics_logger(Clock::GetRealTimeClock()); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + &metrics_logger, options); + analyzer.Start("test_case", + std::vector<std::string>{"alice", "bob", "charlie"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", + {"bob", "charlie"}, + /*frames_count=*/5, *frame_generator, + /*interframe_delay_ms=*/50); + analyzer.UnregisterParticipantInCall("alice"); + analyzer.UnregisterParticipantInCall("bob"); + analyzer.UnregisterParticipantInCall("charlie"); + analyzer.Stop(); + + std::vector<std::string> metrics = + ToTestCases(metrics_logger.GetCollectedMetrics()); + EXPECT_THAT(metrics, SizeIs(57)); + EXPECT_THAT(metrics, Contains("test_case/alice_video_alice_bob").Times(28)); + EXPECT_THAT(metrics, + Contains("test_case/alice_video_alice_charlie").Times(28)); + EXPECT_THAT(metrics, Contains("test_case").Times(1)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.cc new file mode 100644 index 0000000000..79b9286e2d --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.cc @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" + +#include <algorithm> +#include <iterator> +#include <ostream> +#include <string> + +#include "api/units/timestamp.h" +#include "rtc_base/checks.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +namespace { + +constexpr int kMicrosPerSecond = 1000000; + +} // namespace + +std::string StreamCodecInfo::ToString() const { + rtc::StringBuilder out; + out << "{codec_name=" << codec_name << "; first_frame_id=" << first_frame_id + << "; last_frame_id=" << last_frame_id + << "; switched_on_at=" << webrtc::ToString(switched_on_at) + << "; switched_from_at=" << webrtc::ToString(switched_from_at) << " }"; + return out.str(); +} + +std::ostream& operator<<(std::ostream& os, const StreamCodecInfo& state) { + return os << state.ToString(); +} + +rtc::StringBuilder& operator<<(rtc::StringBuilder& sb, + const StreamCodecInfo& state) { + return sb << state.ToString(); +} + +bool operator==(const StreamCodecInfo& a, const StreamCodecInfo& b) { + return a.codec_name == b.codec_name && a.first_frame_id == b.first_frame_id && + a.last_frame_id == b.last_frame_id && + a.switched_on_at == b.switched_on_at && + a.switched_from_at == b.switched_from_at; +} + +std::string ToString(FrameDropPhase phase) { + switch (phase) { + case FrameDropPhase::kBeforeEncoder: + return "kBeforeEncoder"; + case FrameDropPhase::kByEncoder: + return "kByEncoder"; + case FrameDropPhase::kTransport: + return "kTransport"; + case FrameDropPhase::kByDecoder: + return "kByDecoder"; + case FrameDropPhase::kAfterDecoder: + return "kAfterDecoder"; + case FrameDropPhase::kLastValue: + return "kLastValue"; + } +} + +std::ostream& operator<<(std::ostream& os, FrameDropPhase phase) { + return os << ToString(phase); +} +rtc::StringBuilder& operator<<(rtc::StringBuilder& sb, FrameDropPhase phase) { + return sb << ToString(phase); +} + +void SamplesRateCounter::AddEvent(Timestamp event_time) { + if (event_first_time_.IsMinusInfinity()) { + event_first_time_ = event_time; + } + event_last_time_ = event_time; + events_count_++; +} + +double SamplesRateCounter::GetEventsPerSecond() const { + RTC_DCHECK(!IsEmpty()); + // Divide on us and multiply on kMicrosPerSecond to correctly process cases + // where there were too small amount of events, so difference is less then 1 + // sec. We can use us here, because Timestamp has us resolution. + return static_cast<double>(events_count_) / + (event_last_time_ - event_first_time_).us() * kMicrosPerSecond; +} + +StreamStats::StreamStats(Timestamp stream_started_time) + : stream_started_time(stream_started_time) { + for (int i = static_cast<int>(FrameDropPhase::kBeforeEncoder); + i < static_cast<int>(FrameDropPhase::kLastValue); ++i) { + dropped_by_phase.emplace(static_cast<FrameDropPhase>(i), 0); + } +} + +std::string StatsKey::ToString() const { + rtc::StringBuilder out; + out << stream_label << "_" << receiver; + return out.str(); +} + +bool operator<(const StatsKey& a, const StatsKey& b) { + if (a.stream_label != b.stream_label) { + return a.stream_label < b.stream_label; + } + return a.receiver < b.receiver; +} + +bool operator==(const StatsKey& a, const StatsKey& b) { + return a.stream_label == b.stream_label && a.receiver == b.receiver; +} + +VideoStreamsInfo::VideoStreamsInfo( + std::map<std::string, std::string> stream_to_sender, + std::map<std::string, std::set<std::string>> sender_to_streams, + std::map<std::string, std::set<std::string>> stream_to_receivers) + : stream_to_sender_(std::move(stream_to_sender)), + sender_to_streams_(std::move(sender_to_streams)), + stream_to_receivers_(std::move(stream_to_receivers)) {} + +std::set<StatsKey> VideoStreamsInfo::GetStatsKeys() const { + std::set<StatsKey> out; + for (const std::string& stream_label : GetStreams()) { + for (const std::string& receiver : GetReceivers(stream_label)) { + out.insert(StatsKey(stream_label, receiver)); + } + } + return out; +} + +std::set<std::string> VideoStreamsInfo::GetStreams() const { + std::set<std::string> out; + std::transform(stream_to_sender_.begin(), stream_to_sender_.end(), + std::inserter(out, out.end()), + [](auto map_entry) { return map_entry.first; }); + return out; +} + +std::set<std::string> VideoStreamsInfo::GetStreams( + absl::string_view sender_name) const { + auto it = sender_to_streams_.find(std::string(sender_name)); + if (it == sender_to_streams_.end()) { + return {}; + } + return it->second; +} + +absl::optional<std::string> VideoStreamsInfo::GetSender( + absl::string_view stream_label) const { + auto it = stream_to_sender_.find(std::string(stream_label)); + if (it == stream_to_sender_.end()) { + return absl::nullopt; + } + return it->second; +} + +std::set<std::string> VideoStreamsInfo::GetReceivers( + absl::string_view stream_label) const { + auto it = stream_to_receivers_.find(std::string(stream_label)); + if (it == stream_to_receivers_.end()) { + return {}; + } + return it->second; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h new file mode 100644 index 0000000000..175f777b68 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2021 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_SHARED_OBJECTS_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_SHARED_OBJECTS_H_ + +#include <cstdint> +#include <map> +#include <memory> +#include <ostream> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/units/timestamp.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +// WebRTC will request a key frame after 3 seconds if no frames were received. +// We assume max frame rate ~60 fps, so 270 frames will cover max freeze without +// key frame request. +constexpr size_t kDefaultMaxFramesInFlightPerStream = 270; + +class SamplesRateCounter { + public: + void AddEvent(Timestamp event_time); + + bool IsEmpty() const { return event_first_time_ == event_last_time_; } + + double GetEventsPerSecond() const; + + private: + Timestamp event_first_time_ = Timestamp::MinusInfinity(); + Timestamp event_last_time_ = Timestamp::MinusInfinity(); + int64_t events_count_ = 0; +}; + +struct FrameCounters { + // Count of frames, that were passed into WebRTC pipeline by video stream + // source. + int64_t captured = 0; + // Count of frames that reached video encoder. + int64_t pre_encoded = 0; + // Count of encoded images that were produced by encoder for all requested + // spatial layers and simulcast streams. + int64_t encoded = 0; + // Count of encoded images received in decoder for all requested spatial + // layers and simulcast streams. + int64_t received = 0; + // Count of frames that were produced by decoder. + int64_t decoded = 0; + // Count of frames that went out from WebRTC pipeline to video sink. + int64_t rendered = 0; + // Count of frames that were dropped in any point between capturing and + // rendering. + int64_t dropped = 0; + // Count of frames for which decoder returned error when they were sent for + // decoding. + int64_t failed_to_decode = 0; +}; + +// Contains information about the codec that was used for encoding or decoding +// the stream. +struct StreamCodecInfo { + // Codec implementation name. + std::string codec_name; + // Id of the first frame for which this codec was used. + uint16_t first_frame_id; + // Id of the last frame for which this codec was used. + uint16_t last_frame_id; + // Timestamp when the first frame was handled by the encode/decoder. + Timestamp switched_on_at = Timestamp::PlusInfinity(); + // Timestamp when this codec was used last time. + Timestamp switched_from_at = Timestamp::PlusInfinity(); + + std::string ToString() const; +}; + +std::ostream& operator<<(std::ostream& os, const StreamCodecInfo& state); +rtc::StringBuilder& operator<<(rtc::StringBuilder& sb, + const StreamCodecInfo& state); +bool operator==(const StreamCodecInfo& a, const StreamCodecInfo& b); + +// Represents phases where video frame can be dropped and such drop will be +// detected by analyzer. +enum class FrameDropPhase : int { + kBeforeEncoder, + kByEncoder, + kTransport, + kByDecoder, + kAfterDecoder, + // kLastValue must be the last value in this enumeration. + kLastValue +}; + +std::string ToString(FrameDropPhase phase); +std::ostream& operator<<(std::ostream& os, FrameDropPhase phase); +rtc::StringBuilder& operator<<(rtc::StringBuilder& sb, FrameDropPhase phase); + +struct StreamStats { + explicit StreamStats(Timestamp stream_started_time); + + // The time when the first frame of this stream was captured. + Timestamp stream_started_time; + + // Spatial quality metrics. + SamplesStatsCounter psnr; + SamplesStatsCounter ssim; + + // Time from frame encoded (time point on exit from encoder) to the + // encoded image received in decoder (time point on entrance to decoder). + SamplesStatsCounter transport_time_ms; + // Time from frame was captured on device to time frame was displayed on + // device. + SamplesStatsCounter total_delay_incl_transport_ms; + // Time between frames out from renderer. + SamplesStatsCounter time_between_rendered_frames_ms; + SamplesRateCounter capture_frame_rate; + SamplesRateCounter encode_frame_rate; + SamplesStatsCounter encode_time_ms; + SamplesStatsCounter decode_time_ms; + // Time from last packet of frame is received until it's sent to the renderer. + SamplesStatsCounter receive_to_render_time_ms; + // Max frames skipped between two nearest. + SamplesStatsCounter skipped_between_rendered; + // In the next 2 metrics freeze is a pause that is longer, than maximum: + // 1. 150ms + // 2. 3 * average time between two sequential frames. + // Item 1 will cover high fps video and is a duration, that is noticeable by + // human eye. Item 2 will cover low fps video like screen sharing. + // Freeze duration. + SamplesStatsCounter freeze_time_ms; + // Mean time between one freeze end and next freeze start. + SamplesStatsCounter time_between_freezes_ms; + SamplesStatsCounter resolution_of_decoded_frame; + SamplesStatsCounter target_encode_bitrate; + // Sender side qp values per spatial layer. In case when spatial layer is not + // set for `webrtc::EncodedImage`, 0 is used as default. + std::map<int, SamplesStatsCounter> spatial_layers_qp; + + int64_t total_encoded_images_payload = 0; + // Counters on which phase how many frames were dropped. + std::map<FrameDropPhase, int64_t> dropped_by_phase; + + // Frame count metrics. + int64_t num_send_key_frames = 0; + int64_t num_recv_key_frames = 0; + + // Encoded frame size (in bytes) metrics. + SamplesStatsCounter recv_key_frame_size_bytes; + SamplesStatsCounter recv_delta_frame_size_bytes; + + // Vector of encoders used for this stream by sending client. + std::vector<StreamCodecInfo> encoders; + // Vectors of decoders used for this stream by receiving client. + std::vector<StreamCodecInfo> decoders; +}; + +struct AnalyzerStats { + // Size of analyzer internal comparisons queue, measured when new element + // id added to the queue. + SamplesStatsCounter comparisons_queue_size; + // Number of performed comparisons of 2 video frames from captured and + // rendered streams. + int64_t comparisons_done = 0; + // Number of cpu overloaded comparisons. Comparison is cpu overloaded if it is + // queued when there are too many not processed comparisons in the queue. + // Overloaded comparison doesn't include metrics like SSIM and PSNR that + // require heavy computations. + int64_t cpu_overloaded_comparisons_done = 0; + // Number of memory overloaded comparisons. Comparison is memory overloaded if + // it is queued when its captured frame was already removed due to high memory + // usage for that video stream. + int64_t memory_overloaded_comparisons_done = 0; + // Count of frames in flight in analyzer measured when new comparison is added + // and after analyzer was stopped. + SamplesStatsCounter frames_in_flight_left_count; + + // Next metrics are collected and reported iff + // `DefaultVideoQualityAnalyzerOptions::report_infra_metrics` is true. + SamplesStatsCounter on_frame_captured_processing_time_ms; + SamplesStatsCounter on_frame_pre_encode_processing_time_ms; + SamplesStatsCounter on_frame_encoded_processing_time_ms; + SamplesStatsCounter on_frame_pre_decode_processing_time_ms; + SamplesStatsCounter on_frame_decoded_processing_time_ms; + SamplesStatsCounter on_frame_rendered_processing_time_ms; + SamplesStatsCounter on_decoder_error_processing_time_ms; +}; + +struct StatsKey { + StatsKey(std::string stream_label, std::string receiver) + : stream_label(std::move(stream_label)), receiver(std::move(receiver)) {} + + std::string ToString() const; + + // Label of video stream to which stats belongs to. + std::string stream_label; + // Name of the peer on which stream was received. + std::string receiver; +}; + +// Required to use StatsKey as std::map key. +bool operator<(const StatsKey& a, const StatsKey& b); +bool operator==(const StatsKey& a, const StatsKey& b); + +// Contains all metadata related to the video streams that were seen by the +// video analyzer. +class VideoStreamsInfo { + public: + std::set<StatsKey> GetStatsKeys() const; + + // Returns all stream labels that are known to the video analyzer. + std::set<std::string> GetStreams() const; + + // Returns set of the stream for specified `sender_name`. If sender didn't + // send any streams or `sender_name` isn't known to the video analyzer + // empty set will be returned. + std::set<std::string> GetStreams(absl::string_view sender_name) const; + + // Returns sender name for specified `stream_label`. Returns `absl::nullopt` + // if provided `stream_label` isn't known to the video analyzer. + absl::optional<std::string> GetSender(absl::string_view stream_label) const; + + // Returns set of the receivers for specified `stream_label`. If stream wasn't + // received by any peer or `stream_label` isn't known to the video analyzer + // empty set will be returned. + std::set<std::string> GetReceivers(absl::string_view stream_label) const; + + protected: + friend class DefaultVideoQualityAnalyzer; + VideoStreamsInfo( + std::map<std::string, std::string> stream_to_sender, + std::map<std::string, std::set<std::string>> sender_to_streams, + std::map<std::string, std::set<std::string>> stream_to_receivers); + + private: + std::map<std::string, std::string> stream_to_sender_; + std::map<std::string, std::set<std::string>> sender_to_streams_; + std::map<std::string, std::set<std::string>> stream_to_receivers_; +}; + +struct DefaultVideoQualityAnalyzerOptions { + // Tells DefaultVideoQualityAnalyzer if heavy metrics have to be computed. + bool compute_psnr = true; + bool compute_ssim = true; + // If true, weights the luma plane more than the chroma planes in the PSNR. + bool use_weighted_psnr = false; + // Tells DefaultVideoQualityAnalyzer if detailed frame stats should be + // reported. + bool report_detailed_frame_stats = false; + // Tells DefaultVideoQualityAnalyzer if infra metrics related to the + // performance and stability of the analyzer itself should be reported. + bool report_infra_metrics = false; + // If true DefaultVideoQualityAnalyzer will try to adjust frames before + // computing PSNR and SSIM for them. In some cases picture may be shifted by + // a few pixels after the encode/decode step. Those difference is invisible + // for a human eye, but it affects the metrics. So the adjustment is used to + // get metrics that are closer to how human perceive the video. This feature + // significantly slows down the comparison, so turn it on only when it is + // needed. + bool adjust_cropping_before_comparing_frames = false; + // Amount of frames that are queued in the DefaultVideoQualityAnalyzer from + // the point they were captured to the point they were rendered on all + // receivers per stream. + size_t max_frames_in_flight_per_stream_count = + kDefaultMaxFramesInFlightPerStream; + // If true, the analyzer will expect peers to receive their own video streams. + bool enable_receive_own_stream = false; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_SHARED_OBJECTS_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.cc new file mode 100644 index 0000000000..d59ef12c63 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.cc @@ -0,0 +1,121 @@ +/* + * 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/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h" + +#include <map> +#include <set> + +#include "absl/types/optional.h" +#include "api/units/timestamp.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +template <typename T> +absl::optional<T> MaybeGetValue(const std::map<size_t, T>& map, size_t key) { + auto it = map.find(key); + if (it == map.end()) { + return absl::nullopt; + } + return it->second; +} + +} // namespace + +StreamState::StreamState(size_t sender, + std::set<size_t> receivers, + Timestamp stream_started_time) + : sender_(sender), + stream_started_time_(stream_started_time), + receivers_(receivers), + frame_ids_(std::move(receivers)) { + frame_ids_.AddReader(kAliveFramesQueueIndex); + RTC_CHECK_NE(sender_, kAliveFramesQueueIndex); + for (size_t receiver : receivers_) { + RTC_CHECK_NE(receiver, kAliveFramesQueueIndex); + } +} + +uint16_t StreamState::PopFront(size_t peer) { + RTC_CHECK_NE(peer, kAliveFramesQueueIndex); + absl::optional<uint16_t> frame_id = frame_ids_.PopFront(peer); + RTC_DCHECK(frame_id.has_value()); + + // If alive's frame queue is longer than all others, than also pop frame from + // it, because that frame is received by all receivers. + size_t alive_size = frame_ids_.size(kAliveFramesQueueIndex); + size_t other_size = GetLongestReceiverQueue(); + // Pops frame from alive queue if alive's queue is the longest one. + if (alive_size > other_size) { + absl::optional<uint16_t> alive_frame_id = + frame_ids_.PopFront(kAliveFramesQueueIndex); + RTC_DCHECK(alive_frame_id.has_value()); + RTC_DCHECK_EQ(frame_id.value(), alive_frame_id.value()); + } + + return frame_id.value(); +} + +void StreamState::AddPeer(size_t peer) { + RTC_CHECK_NE(peer, kAliveFramesQueueIndex); + frame_ids_.AddReader(peer, kAliveFramesQueueIndex); + receivers_.insert(peer); +} + +void StreamState::RemovePeer(size_t peer) { + RTC_CHECK_NE(peer, kAliveFramesQueueIndex); + frame_ids_.RemoveReader(peer); + receivers_.erase(peer); + + // If we removed the last receiver for the alive frames, we need to pop them + // from the queue, because now they received by all receivers. + size_t alive_size = frame_ids_.size(kAliveFramesQueueIndex); + size_t other_size = GetLongestReceiverQueue(); + while (alive_size > other_size) { + frame_ids_.PopFront(kAliveFramesQueueIndex); + alive_size--; + } +} + +uint16_t StreamState::MarkNextAliveFrameAsDead() { + absl::optional<uint16_t> frame_id = + frame_ids_.PopFront(kAliveFramesQueueIndex); + RTC_DCHECK(frame_id.has_value()); + return frame_id.value(); +} + +void StreamState::SetLastRenderedFrameTime(size_t peer, Timestamp time) { + auto it = last_rendered_frame_time_.find(peer); + if (it == last_rendered_frame_time_.end()) { + last_rendered_frame_time_.insert({peer, time}); + } else { + it->second = time; + } +} + +absl::optional<Timestamp> StreamState::last_rendered_frame_time( + size_t peer) const { + return MaybeGetValue(last_rendered_frame_time_, peer); +} + +size_t StreamState::GetLongestReceiverQueue() const { + size_t max = 0; + for (size_t receiver : receivers_) { + size_t cur_size = frame_ids_.size(receiver); + if (cur_size > max) { + max = cur_size; + } + } + return max; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h new file mode 100644 index 0000000000..829a79c7bf --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h @@ -0,0 +1,100 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_STREAM_STATE_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_STREAM_STATE_H_ + +#include <limits> +#include <map> +#include <set> + +#include "absl/types/optional.h" +#include "api/units/timestamp.h" +#include "test/pc/e2e/analyzer/video/multi_reader_queue.h" + +namespace webrtc { + +// Represents a current state of video stream inside +// DefaultVideoQualityAnalyzer. +// +// Maintains the sequence of frames for each video stream and keeps track about +// which frames were seen by each of the possible stream receiver. +// +// Keeps information about which frames are alive and which are dead. Frame is +// alive if it contains VideoFrame payload for corresponding FrameInFlight +// object inside DefaultVideoQualityAnalyzer, otherwise frame is considered +// dead. +// +// Supports peer indexes from 0 to max(size_t) - 1. +class StreamState { + public: + StreamState(size_t sender, + std::set<size_t> receivers, + Timestamp stream_started_time); + + size_t sender() const { return sender_; } + Timestamp stream_started_time() const { return stream_started_time_; } + + void PushBack(uint16_t frame_id) { frame_ids_.PushBack(frame_id); } + // Crash if state is empty. + uint16_t PopFront(size_t peer); + bool IsEmpty(size_t peer) const { return frame_ids_.IsEmpty(peer); } + // Crash if state is empty. + uint16_t Front(size_t peer) const { return frame_ids_.Front(peer).value(); } + + // Adds a new peer to the state. All currently alive frames will be expected + // to be received by the newly added peer. + void AddPeer(size_t peer); + + // Removes peer from the state. Frames that were expected to be received by + // this peer will be removed from it. On the other hand last rendered frame + // time for the removed peer will be preserved, because + // DefaultVideoQualityAnalyzer still may request it for stats processing. + void RemovePeer(size_t peer); + + size_t GetAliveFramesCount() const { + return frame_ids_.size(kAliveFramesQueueIndex); + } + uint16_t MarkNextAliveFrameAsDead(); + + void SetLastRenderedFrameTime(size_t peer, Timestamp time); + absl::optional<Timestamp> last_rendered_frame_time(size_t peer) const; + + private: + // Index of the `frame_ids_` queue which is used to track alive frames for + // this stream. + static constexpr size_t kAliveFramesQueueIndex = + std::numeric_limits<size_t>::max(); + + size_t GetLongestReceiverQueue() const; + + // Index of the owner. Owner's queue in `frame_ids_` will keep alive frames. + const size_t sender_; + const Timestamp stream_started_time_; + std::set<size_t> receivers_; + // To correctly determine dropped frames we have to know sequence of frames + // in each stream so we will keep a list of frame ids inside the stream. + // This list is represented by multi head queue of frame ids with separate + // head for each receiver. When the frame is rendered, we will pop ids from + // the corresponding head until id will match with rendered one. All ids + // before matched one can be considered as dropped: + // + // | frame_id1 |->| frame_id2 |->| frame_id3 |->| frame_id4 | + // + // If we received frame with id frame_id3, then we will pop frame_id1 and + // frame_id2 and consider those frames as dropped and then compare received + // frame with the one from `FrameInFlight` with id frame_id3. + MultiReaderQueue<uint16_t> frame_ids_; + std::map<size_t, Timestamp> last_rendered_frame_time_; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_STREAM_STATE_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state_test.cc new file mode 100644 index 0000000000..01a6aab28a --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state_test.cc @@ -0,0 +1,126 @@ +/* + * 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/pc/e2e/analyzer/video/default_video_quality_analyzer_stream_state.h" + +#include <set> + +#include "api/units/timestamp.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +TEST(StreamStateTest, PopFrontAndFrontIndependentForEachPeer) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + state.PushBack(/*frame_id=*/2); + + EXPECT_EQ(state.Front(/*peer=*/1), 1); + EXPECT_EQ(state.PopFront(/*peer=*/1), 1); + EXPECT_EQ(state.Front(/*peer=*/1), 2); + EXPECT_EQ(state.PopFront(/*peer=*/1), 2); + EXPECT_EQ(state.Front(/*peer=*/2), 1); + EXPECT_EQ(state.PopFront(/*peer=*/2), 1); + EXPECT_EQ(state.Front(/*peer=*/2), 2); + EXPECT_EQ(state.PopFront(/*peer=*/2), 2); +} + +TEST(StreamStateTest, IsEmpty) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + + EXPECT_FALSE(state.IsEmpty(/*peer=*/1)); + + state.PopFront(/*peer=*/1); + + EXPECT_TRUE(state.IsEmpty(/*peer=*/1)); +} + +TEST(StreamStateTest, PopFrontForOnlyOnePeerDontChangeAliveFramesCount) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + state.PushBack(/*frame_id=*/2); + + EXPECT_EQ(state.GetAliveFramesCount(), 2lu); + + state.PopFront(/*peer=*/1); + state.PopFront(/*peer=*/1); + + EXPECT_EQ(state.GetAliveFramesCount(), 2lu); +} + +TEST(StreamStateTest, PopFrontForAllPeersReducesAliveFramesCount) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + state.PushBack(/*frame_id=*/2); + + EXPECT_EQ(state.GetAliveFramesCount(), 2lu); + + state.PopFront(/*peer=*/1); + state.PopFront(/*peer=*/2); + + EXPECT_EQ(state.GetAliveFramesCount(), 1lu); +} + +TEST(StreamStateTest, RemovePeerForLastExpectedReceiverUpdatesAliveFrames) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + state.PushBack(/*frame_id=*/2); + + state.PopFront(/*peer=*/1); + + EXPECT_EQ(state.GetAliveFramesCount(), 2lu); + + state.RemovePeer(/*peer=*/2); + + EXPECT_EQ(state.GetAliveFramesCount(), 1lu); +} + +TEST(StreamStateTest, MarkNextAliveFrameAsDeadDecreseAliveFramesCount) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + state.PushBack(/*frame_id=*/2); + + EXPECT_EQ(state.GetAliveFramesCount(), 2lu); + + state.MarkNextAliveFrameAsDead(); + + EXPECT_EQ(state.GetAliveFramesCount(), 1lu); +} + +TEST(StreamStateTest, MarkNextAliveFrameAsDeadDoesntAffectFrontFrameForPeer) { + StreamState state(/*sender=*/0, + /*receivers=*/std::set<size_t>{1, 2}, + Timestamp::Seconds(1)); + state.PushBack(/*frame_id=*/1); + state.PushBack(/*frame_id=*/2); + + EXPECT_EQ(state.Front(/*peer=*/1), 1); + + state.MarkNextAliveFrameAsDead(); + + EXPECT_EQ(state.Front(/*peer=*/1), 1); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc new file mode 100644 index 0000000000..fc970e1ea2 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer_test.cc @@ -0,0 +1,2204 @@ +/* + * 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/pc/e2e/analyzer/video/default_video_quality_analyzer.h" + +#include <algorithm> +#include <map> +#include <memory> +#include <vector> + +#include "api/rtp_packet_info.h" +#include "api/rtp_packet_infos.h" +#include "api/test/create_frame_generator.h" +#include "api/test/metrics/global_metrics_logger_and_exporter.h" +#include "api/video/encoded_image.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_tools/frame_analyzer/video_geometry_aligner.h" +#include "system_wrappers/include/sleep.h" +#include "test/gtest.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" + +namespace webrtc { +namespace { + +using ::testing::TestWithParam; +using ::testing::ValuesIn; + +using StatsSample = ::webrtc::SamplesStatsCounter::StatsSample; + +constexpr int kAnalyzerMaxThreadsCount = 1; +constexpr int kMaxFramesInFlightPerStream = 10; +constexpr int kFrameWidth = 320; +constexpr int kFrameHeight = 240; +constexpr double kMaxSsim = 1; +constexpr char kStreamLabel[] = "video-stream"; +constexpr char kSenderPeerName[] = "alice"; +constexpr char kReceiverPeerName[] = "bob"; + +DefaultVideoQualityAnalyzerOptions AnalyzerOptionsForTest() { + DefaultVideoQualityAnalyzerOptions options; + options.compute_psnr = false; + options.compute_ssim = false; + options.adjust_cropping_before_comparing_frames = false; + options.max_frames_in_flight_per_stream_count = kMaxFramesInFlightPerStream; + return options; +} + +VideoFrame NextFrame(test::FrameGeneratorInterface* frame_generator, + int64_t timestamp_us) { + test::FrameGeneratorInterface::VideoFrameData frame_data = + frame_generator->NextFrame(); + return VideoFrame::Builder() + .set_video_frame_buffer(frame_data.buffer) + .set_update_rect(frame_data.update_rect) + .set_timestamp_us(timestamp_us) + .build(); +} + +EncodedImage FakeEncode(const VideoFrame& frame) { + EncodedImage image; + std::vector<RtpPacketInfo> packet_infos; + packet_infos.push_back(RtpPacketInfo( + /*ssrc=*/1, + /*csrcs=*/{}, + /*rtp_timestamp=*/frame.timestamp(), + /*receive_time=*/Timestamp::Micros(frame.timestamp_us() + 10000))); + image.SetPacketInfos(RtpPacketInfos(packet_infos)); + return image; +} + +VideoFrame DeepCopy(const VideoFrame& frame) { + VideoFrame copy = frame; + copy.set_video_frame_buffer( + I420Buffer::Copy(*frame.video_frame_buffer()->ToI420())); + return copy; +} + +std::vector<StatsSample> GetSortedSamples(const SamplesStatsCounter& counter) { + rtc::ArrayView<const StatsSample> view = counter.GetTimedSamples(); + std::vector<StatsSample> out(view.begin(), view.end()); + std::sort(out.begin(), out.end(), + [](const StatsSample& a, const StatsSample& b) { + return a.time < b.time; + }); + return out; +} + +std::string ToString(const std::vector<StatsSample>& values) { + rtc::StringBuilder out; + for (const auto& v : values) { + out << "{ time_ms=" << v.time.ms() << "; value=" << v.value << "}, "; + } + return out.str(); +} + +void FakeCPULoad() { + std::vector<int> temp(1000000); + for (size_t i = 0; i < temp.size(); ++i) { + temp[i] = rand(); + } + std::sort(temp.begin(), temp.end()); + ASSERT_TRUE(std::is_sorted(temp.begin(), temp.end())); +} + +void PassFramesThroughAnalyzer(DefaultVideoQualityAnalyzer& analyzer, + absl::string_view sender, + absl::string_view stream_label, + std::vector<absl::string_view> receivers, + int frames_count, + test::FrameGeneratorInterface& frame_generator, + int interframe_delay_ms = 0) { + for (int i = 0; i < frames_count; ++i) { + VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1); + uint16_t frame_id = + analyzer.OnFrameCaptured(sender, std::string(stream_label), frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode(sender, frame); + analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + for (absl::string_view receiver : receivers) { + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(receiver, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(receiver, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(receiver, received_frame); + } + if (i < frames_count - 1 && interframe_delay_ms > 0) { + SleepMs(interframe_delay_ms); + } + } +} + +TEST(DefaultVideoQualityAnalyzerTest, + MemoryOverloadedAndThenAllFramesReceived) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + for (int i = 0; i < kMaxFramesInFlightPerStream * 2; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + for (const uint16_t& frame_id : frames_order) { + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, + kMaxFramesInFlightPerStream); + EXPECT_EQ(stats.comparisons_done, kMaxFramesInFlightPerStream * 2); + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, kMaxFramesInFlightPerStream * 2); + EXPECT_EQ(frame_counters.rendered, kMaxFramesInFlightPerStream * 2); + EXPECT_EQ(frame_counters.dropped, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, + FillMaxMemoryReceiveAllMemoryOverloadedAndThenAllFramesReceived) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + // Feel analyzer's memory up to limit + for (int i = 0; i < kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + // Receive all frames. + for (const uint16_t& frame_id : frames_order) { + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + frames_order.clear(); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + + // Overload analyzer's memory up to limit + for (int i = 0; i < 2 * kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + // Receive all frames. + for (const uint16_t& frame_id : frames_order) { + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, + kMaxFramesInFlightPerStream); + EXPECT_EQ(stats.comparisons_done, kMaxFramesInFlightPerStream * 3); + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, kMaxFramesInFlightPerStream * 3); + EXPECT_EQ(frame_counters.rendered, kMaxFramesInFlightPerStream * 3); + EXPECT_EQ(frame_counters.dropped, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, + MemoryOverloadedHalfDroppedAndThenHalfFramesReceived) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + for (int i = 0; i < kMaxFramesInFlightPerStream * 2; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + for (size_t i = kMaxFramesInFlightPerStream; i < frames_order.size(); ++i) { + uint16_t frame_id = frames_order.at(i); + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(stats.comparisons_done, kMaxFramesInFlightPerStream * 2); + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, kMaxFramesInFlightPerStream * 2); + EXPECT_EQ(frame_counters.rendered, kMaxFramesInFlightPerStream); + EXPECT_EQ(frame_counters.dropped, kMaxFramesInFlightPerStream); +} + +TEST(DefaultVideoQualityAnalyzerTest, NormalScenario) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + for (int i = 0; i < kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + for (size_t i = 1; i < frames_order.size(); i += 2) { + uint16_t frame_id = frames_order.at(i); + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(stats.comparisons_done, kMaxFramesInFlightPerStream); + + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, kMaxFramesInFlightPerStream); + EXPECT_EQ(frame_counters.received, kMaxFramesInFlightPerStream / 2); + EXPECT_EQ(frame_counters.decoded, kMaxFramesInFlightPerStream / 2); + EXPECT_EQ(frame_counters.rendered, kMaxFramesInFlightPerStream / 2); + EXPECT_EQ(frame_counters.dropped, kMaxFramesInFlightPerStream / 2); +} + +TEST(DefaultVideoQualityAnalyzerTest, OneFrameReceivedTwice) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + VideoFrame captured_frame = NextFrame(frame_generator.get(), 0); + captured_frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, captured_frame)); + analyzer.OnFramePreEncode(kSenderPeerName, captured_frame); + analyzer.OnFrameEncoded(kSenderPeerName, captured_frame.id(), + FakeEncode(captured_frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + VideoFrame received_frame = DeepCopy(captured_frame); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + + received_frame = DeepCopy(captured_frame); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(stats.comparisons_done, 1); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 1); + EXPECT_EQ(frame_counters.received, 1); + EXPECT_EQ(frame_counters.decoded, 1); + EXPECT_EQ(frame_counters.rendered, 1); + EXPECT_EQ(frame_counters.dropped, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, NormalScenario2Receivers) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + constexpr char kAlice[] = "alice"; + constexpr char kBob[] = "bob"; + constexpr char kCharlie[] = "charlie"; + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob, kCharlie}, + kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + for (int i = 0; i < kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id(analyzer.OnFrameCaptured(kAlice, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kAlice, frame); + SleepMs(20); + analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + SleepMs(50); + + for (size_t i = 1; i < frames_order.size(); i += 2) { + uint16_t frame_id = frames_order.at(i); + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kBob, received_frame.id(), + FakeEncode(received_frame)); + SleepMs(30); + analyzer.OnFrameDecoded(kBob, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + SleepMs(10); + analyzer.OnFrameRendered(kBob, received_frame); + } + + for (size_t i = 1; i < frames_order.size(); i += 2) { + uint16_t frame_id = frames_order.at(i); + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kCharlie, received_frame.id(), + FakeEncode(received_frame)); + SleepMs(40); + analyzer.OnFrameDecoded(kCharlie, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + SleepMs(5); + analyzer.OnFrameRendered(kCharlie, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(analyzer_stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(analyzer_stats.comparisons_done, kMaxFramesInFlightPerStream * 2); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, kMaxFramesInFlightPerStream); + EXPECT_EQ(frame_counters.received, kMaxFramesInFlightPerStream); + EXPECT_EQ(frame_counters.decoded, kMaxFramesInFlightPerStream); + EXPECT_EQ(frame_counters.rendered, kMaxFramesInFlightPerStream); + EXPECT_EQ(frame_counters.dropped, kMaxFramesInFlightPerStream); + + VideoStreamsInfo streams_info = analyzer.GetKnownStreams(); + EXPECT_EQ(streams_info.GetStreams(), std::set<std::string>{kStreamLabel}); + EXPECT_EQ(streams_info.GetStreams(kAlice), + std::set<std::string>{kStreamLabel}); + EXPECT_EQ(streams_info.GetSender(kStreamLabel), kAlice); + EXPECT_EQ(streams_info.GetReceivers(kStreamLabel), + (std::set<std::string>{kBob, kCharlie})); + + EXPECT_EQ(streams_info.GetStatsKeys().size(), 2lu); + for (auto stream_key : streams_info.GetStatsKeys()) { + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(stream_key); + // On some devices the pipeline can be too slow, so we actually can't + // force real constraints here. Lets just check, that at least 1 + // frame passed whole pipeline. + EXPECT_GE(stream_conters.captured, 10); + EXPECT_GE(stream_conters.pre_encoded, 10); + EXPECT_GE(stream_conters.encoded, 10); + EXPECT_GE(stream_conters.received, 5); + EXPECT_GE(stream_conters.decoded, 5); + EXPECT_GE(stream_conters.rendered, 5); + EXPECT_GE(stream_conters.dropped, 5); + } + + std::map<StatsKey, StreamStats> stats = analyzer.GetStats(); + const StatsKey kAliceBobStats(kStreamLabel, kBob); + const StatsKey kAliceCharlieStats(kStreamLabel, kCharlie); + EXPECT_EQ(stats.size(), 2lu); + { + auto it = stats.find(kAliceBobStats); + EXPECT_FALSE(it == stats.end()); + ASSERT_FALSE(it->second.encode_time_ms.IsEmpty()); + EXPECT_GE(it->second.encode_time_ms.GetMin(), 20); + ASSERT_FALSE(it->second.decode_time_ms.IsEmpty()); + EXPECT_GE(it->second.decode_time_ms.GetMin(), 30); + ASSERT_FALSE(it->second.resolution_of_decoded_frame.IsEmpty()); + EXPECT_GE(it->second.resolution_of_decoded_frame.GetMin(), + kFrameWidth * kFrameHeight - 1); + EXPECT_LE(it->second.resolution_of_decoded_frame.GetMax(), + kFrameWidth * kFrameHeight + 1); + } + { + auto it = stats.find(kAliceCharlieStats); + EXPECT_FALSE(it == stats.end()); + ASSERT_FALSE(it->second.encode_time_ms.IsEmpty()); + EXPECT_GE(it->second.encode_time_ms.GetMin(), 20); + ASSERT_FALSE(it->second.decode_time_ms.IsEmpty()); + EXPECT_GE(it->second.decode_time_ms.GetMin(), 30); + ASSERT_FALSE(it->second.resolution_of_decoded_frame.IsEmpty()); + EXPECT_GE(it->second.resolution_of_decoded_frame.GetMin(), + kFrameWidth * kFrameHeight - 1); + EXPECT_LE(it->second.resolution_of_decoded_frame.GetMax(), + kFrameWidth * kFrameHeight + 1); + } +} + +// Test the case which can happen when SFU is switching from one layer to +// another, so the same frame can be received twice by the same peer. +TEST(DefaultVideoQualityAnalyzerTest, + OneFrameReceivedTwiceBySamePeerWith2Receivers) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + constexpr char kAlice[] = "alice"; + constexpr char kBob[] = "bob"; + constexpr char kCharlie[] = "charlie"; + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob, kCharlie}, + kAnalyzerMaxThreadsCount); + + VideoFrame captured_frame = NextFrame(frame_generator.get(), 0); + captured_frame.set_id( + analyzer.OnFrameCaptured(kAlice, kStreamLabel, captured_frame)); + analyzer.OnFramePreEncode(kAlice, captured_frame); + analyzer.OnFrameEncoded(kAlice, captured_frame.id(), + FakeEncode(captured_frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + VideoFrame received_frame = DeepCopy(captured_frame); + analyzer.OnFramePreDecode(kBob, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kBob, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kBob, received_frame); + + received_frame = DeepCopy(captured_frame); + analyzer.OnFramePreDecode(kBob, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kBob, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kBob, received_frame); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + // We have 2 comparisons here because 1 for the frame received by Bob and + // 1 for the frame in flight from Alice to Charlie. + EXPECT_EQ(stats.comparisons_done, 2); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 1); + EXPECT_EQ(frame_counters.received, 1); + EXPECT_EQ(frame_counters.decoded, 1); + EXPECT_EQ(frame_counters.rendered, 1); + EXPECT_EQ(frame_counters.dropped, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, HeavyQualityMetricsFromEqualFrames) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions analyzer_options; + analyzer_options.compute_psnr = true; + analyzer_options.compute_ssim = true; + analyzer_options.adjust_cropping_before_comparing_frames = false; + analyzer_options.max_frames_in_flight_per_stream_count = + kMaxFramesInFlightPerStream; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + analyzer_options); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + for (int i = 0; i < kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. Heavy metrics + // computation is turned on, so giving some extra time to be sure that + // computatio have ended. + SleepMs(500); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(stats.comparisons_done, kMaxFramesInFlightPerStream); + + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + std::map<StatsKey, StreamStats> stream_stats = analyzer.GetStats(); + const StatsKey kAliceBobStats(kStreamLabel, kReceiverPeerName); + EXPECT_EQ(stream_stats.size(), 1lu); + + auto it = stream_stats.find(kAliceBobStats); + EXPECT_GE(it->second.psnr.GetMin(), kPerfectPSNR); + EXPECT_GE(it->second.ssim.GetMin(), kMaxSsim); +} + +TEST(DefaultVideoQualityAnalyzerTest, + HeavyQualityMetricsFromShiftedFramesWithAdjustment) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions analyzer_options; + analyzer_options.compute_psnr = true; + analyzer_options.compute_ssim = true; + analyzer_options.adjust_cropping_before_comparing_frames = true; + analyzer_options.max_frames_in_flight_per_stream_count = + kMaxFramesInFlightPerStream; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + analyzer_options); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + for (int i = 0; i < kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + + VideoFrame received_frame = frame; + // Shift frame by a few pixels. + test::CropRegion crop_region{0, 1, 3, 0}; + rtc::scoped_refptr<VideoFrameBuffer> cropped_buffer = + CropAndZoom(crop_region, received_frame.video_frame_buffer()->ToI420()); + received_frame.set_video_frame_buffer(cropped_buffer); + + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. Heavy metrics + // computation is turned on, so giving some extra time to be sure that + // computatio have ended. + SleepMs(500); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(stats.comparisons_done, kMaxFramesInFlightPerStream); + + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + std::map<StatsKey, StreamStats> stream_stats = analyzer.GetStats(); + const StatsKey kAliceBobStats(kStreamLabel, kReceiverPeerName); + EXPECT_EQ(stream_stats.size(), 1lu); + + auto it = stream_stats.find(kAliceBobStats); + EXPECT_GE(it->second.psnr.GetMin(), kPerfectPSNR); + EXPECT_GE(it->second.ssim.GetMin(), kMaxSsim); +} + +TEST(DefaultVideoQualityAnalyzerTest, CpuUsage) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + for (int i = 0; i < kMaxFramesInFlightPerStream; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + // Windows CPU clock has low accuracy. We need to fake some additional load to + // be sure that the clock ticks (https://crbug.com/webrtc/12249). + FakeCPULoad(); + + for (size_t i = 1; i < frames_order.size(); i += 2) { + uint16_t frame_id = frames_order.at(i); + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + double cpu_usage = analyzer.GetCpuUsagePercent(); + ASSERT_GT(cpu_usage, 0); + + SleepMs(100); + analyzer.Stop(); + + EXPECT_EQ(analyzer.GetCpuUsagePercent(), cpu_usage); +} + +TEST(DefaultVideoQualityAnalyzerTest, RuntimeParticipantsAdding) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + constexpr char kAlice[] = "alice"; + constexpr char kBob[] = "bob"; + constexpr char kCharlie[] = "charlie"; + constexpr char kKatie[] = "katie"; + + constexpr int kFramesCount = 9; + constexpr int kOneThirdFrames = kFramesCount / 3; + constexpr int kTwoThirdFrames = 2 * kOneThirdFrames; + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", {}, kAnalyzerMaxThreadsCount); + + std::map<uint16_t, VideoFrame> captured_frames; + std::vector<uint16_t> frames_order; + analyzer.RegisterParticipantInCall(kAlice); + analyzer.RegisterParticipantInCall(kBob); + + // Alice is sending frames. + for (int i = 0; i < kFramesCount; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), i); + frame.set_id(analyzer.OnFrameCaptured(kAlice, kStreamLabel, frame)); + frames_order.push_back(frame.id()); + captured_frames.insert({frame.id(), frame}); + analyzer.OnFramePreEncode(kAlice, frame); + analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + // Bob receives one third of the sent frames. + for (int i = 0; i < kOneThirdFrames; ++i) { + uint16_t frame_id = frames_order.at(i); + VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kBob, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kBob, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kBob, received_frame); + } + + analyzer.RegisterParticipantInCall(kCharlie); + analyzer.RegisterParticipantInCall(kKatie); + + // New participants were dynamically added. Bob and Charlie receive second + // third of the sent frames. Katie drops the frames. + for (int i = kOneThirdFrames; i < kTwoThirdFrames; ++i) { + uint16_t frame_id = frames_order.at(i); + VideoFrame bob_received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kBob, bob_received_frame.id(), + FakeEncode(bob_received_frame)); + analyzer.OnFrameDecoded(kBob, bob_received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kBob, bob_received_frame); + + VideoFrame charlie_received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kCharlie, charlie_received_frame.id(), + FakeEncode(charlie_received_frame)); + analyzer.OnFrameDecoded(kCharlie, charlie_received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kCharlie, charlie_received_frame); + } + + // Bob, Charlie and Katie receive the rest of the sent frames. + for (int i = kTwoThirdFrames; i < kFramesCount; ++i) { + uint16_t frame_id = frames_order.at(i); + VideoFrame bob_received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kBob, bob_received_frame.id(), + FakeEncode(bob_received_frame)); + analyzer.OnFrameDecoded(kBob, bob_received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kBob, bob_received_frame); + + VideoFrame charlie_received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kCharlie, charlie_received_frame.id(), + FakeEncode(charlie_received_frame)); + analyzer.OnFrameDecoded(kCharlie, charlie_received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kCharlie, charlie_received_frame); + + VideoFrame katie_received_frame = DeepCopy(captured_frames.at(frame_id)); + analyzer.OnFramePreDecode(kKatie, katie_received_frame.id(), + FakeEncode(katie_received_frame)); + analyzer.OnFrameDecoded(kKatie, katie_received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kKatie, katie_received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0); + EXPECT_EQ(stats.comparisons_done, kFramesCount + 2 * kTwoThirdFrames); + + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, kFramesCount); + EXPECT_EQ(frame_counters.received, 2 * kFramesCount); + EXPECT_EQ(frame_counters.decoded, 2 * kFramesCount); + EXPECT_EQ(frame_counters.rendered, 2 * kFramesCount); + EXPECT_EQ(frame_counters.dropped, kOneThirdFrames); + + const StatsKey kAliceBobStats(kStreamLabel, kBob); + const StatsKey kAliceCharlieStats(kStreamLabel, kCharlie); + const StatsKey kAliceKatieStats(kStreamLabel, kKatie); + EXPECT_EQ(analyzer.GetKnownStreams().GetStatsKeys(), + (std::set<StatsKey>{kAliceBobStats, kAliceCharlieStats, + kAliceKatieStats})); + { + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(kAliceBobStats); + EXPECT_EQ(stream_conters.captured, kFramesCount); + EXPECT_EQ(stream_conters.pre_encoded, kFramesCount); + EXPECT_EQ(stream_conters.encoded, kFramesCount); + EXPECT_EQ(stream_conters.received, kFramesCount); + EXPECT_EQ(stream_conters.decoded, kFramesCount); + EXPECT_EQ(stream_conters.rendered, kFramesCount); + } + { + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(kAliceCharlieStats); + EXPECT_EQ(stream_conters.captured, kFramesCount); + EXPECT_EQ(stream_conters.pre_encoded, kFramesCount); + EXPECT_EQ(stream_conters.encoded, kFramesCount); + EXPECT_EQ(stream_conters.received, kTwoThirdFrames); + EXPECT_EQ(stream_conters.decoded, kTwoThirdFrames); + EXPECT_EQ(stream_conters.rendered, kTwoThirdFrames); + } + { + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(kAliceKatieStats); + EXPECT_EQ(stream_conters.captured, kFramesCount); + EXPECT_EQ(stream_conters.pre_encoded, kFramesCount); + EXPECT_EQ(stream_conters.encoded, kFramesCount); + EXPECT_EQ(stream_conters.received, kOneThirdFrames); + EXPECT_EQ(stream_conters.decoded, kOneThirdFrames); + EXPECT_EQ(stream_conters.rendered, kOneThirdFrames); + } +} + +TEST(DefaultVideoQualityAnalyzerTest, + SimulcastFrameWasFullyReceivedByAllPeersBeforeEncodeFinish) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + constexpr char kAlice[] = "alice"; + constexpr char kBob[] = "bob"; + constexpr char kCharlie[] = "charlie"; + analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob, kCharlie}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), 1); + + frame.set_id(analyzer.OnFrameCaptured(kAlice, kStreamLabel, frame)); + analyzer.OnFramePreEncode(kAlice, frame); + // Encode 1st simulcast layer + analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + // Receive by Bob + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kBob, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kBob, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kBob, received_frame); + // Receive by Charlie + received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kCharlie, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kCharlie, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kCharlie, received_frame); + + // Encode 2nd simulcast layer + analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.comparisons_done, 2); + + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 1); + EXPECT_EQ(frame_counters.rendered, 2); +} + +TEST(DefaultVideoQualityAnalyzerTest, + FrameCanBeReceivedBySenderAfterItWasReceivedByReceiver) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.enable_receive_own_stream = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::vector<VideoFrame> frames; + for (int i = 0; i < 3; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), 1); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames.push_back(frame); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + // Receive by 2nd peer. + for (VideoFrame& frame : frames) { + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Check that we still have that frame in flight. + AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats(); + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(analyzer_stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 3) + << "Expected that frame is still in flight, " + << "because it wasn't received by sender" + << ToString(frames_in_flight_sizes); + + // Receive by sender + for (VideoFrame& frame : frames) { + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kSenderPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kSenderPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kSenderPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + analyzer_stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(analyzer_stats.comparisons_done, 6); + + frames_in_flight_sizes = + GetSortedSamples(analyzer_stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 3); + EXPECT_EQ(frame_counters.rendered, 6); + + EXPECT_EQ(analyzer.GetStats().size(), 2lu); + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kReceiverPeerName)); + EXPECT_EQ(stream_conters.captured, 3); + EXPECT_EQ(stream_conters.pre_encoded, 3); + EXPECT_EQ(stream_conters.encoded, 3); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 3); + EXPECT_EQ(stream_conters.rendered, 3); + } + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kSenderPeerName)); + EXPECT_EQ(stream_conters.captured, 3); + EXPECT_EQ(stream_conters.pre_encoded, 3); + EXPECT_EQ(stream_conters.encoded, 3); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 3); + EXPECT_EQ(stream_conters.rendered, 3); + } +} + +TEST(DefaultVideoQualityAnalyzerTest, + FrameCanBeReceivedByReceiverAfterItWasReceivedBySender) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.enable_receive_own_stream = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + std::vector<VideoFrame> frames; + for (int i = 0; i < 3; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), 1); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames.push_back(frame); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + + // Receive by sender + for (VideoFrame& frame : frames) { + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kSenderPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kSenderPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kSenderPeerName, received_frame); + } + + // Check that we still have that frame in flight. + AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats(); + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(analyzer_stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 3) + << "Expected that frame is still in flight, " + << "because it wasn't received by sender" + << ToString(frames_in_flight_sizes); + + // Receive by 2nd peer. + for (VideoFrame& frame : frames) { + VideoFrame received_frame = DeepCopy(frame); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + analyzer_stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(analyzer_stats.comparisons_done, 6); + + frames_in_flight_sizes = + GetSortedSamples(analyzer_stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 0) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 3); + EXPECT_EQ(frame_counters.rendered, 6); + + EXPECT_EQ(analyzer.GetStats().size(), 2lu); + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kReceiverPeerName)); + EXPECT_EQ(stream_conters.captured, 3); + EXPECT_EQ(stream_conters.pre_encoded, 3); + EXPECT_EQ(stream_conters.encoded, 3); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 3); + EXPECT_EQ(stream_conters.rendered, 3); + } + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kSenderPeerName)); + EXPECT_EQ(stream_conters.captured, 3); + EXPECT_EQ(stream_conters.pre_encoded, 3); + EXPECT_EQ(stream_conters.encoded, 3); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 3); + EXPECT_EQ(stream_conters.rendered, 3); + } +} + +TEST(DefaultVideoQualityAnalyzerTest, CodecTrackedCorrectly) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), + AnalyzerOptionsForTest()); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + VideoQualityAnalyzerInterface::EncoderStats encoder_stats; + std::vector<std::string> codec_names = {"codec_1", "codec_2"}; + std::vector<VideoFrame> frames; + // Send 3 frame for each codec. + for (size_t i = 0; i < codec_names.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + VideoFrame frame = NextFrame(frame_generator.get(), 3 * i + j); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + analyzer.OnFramePreEncode(kSenderPeerName, frame); + encoder_stats.encoder_name = codec_names[i]; + analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame), + encoder_stats, false); + frames.push_back(std::move(frame)); + } + } + + // Receive 3 frame for each codec. + VideoQualityAnalyzerInterface::DecoderStats decoder_stats; + for (size_t i = 0; i < codec_names.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + VideoFrame received_frame = DeepCopy(frames[3 * i + j]); + analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(), + FakeEncode(received_frame)); + decoder_stats.decoder_name = codec_names[i]; + analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, decoder_stats); + analyzer.OnFrameRendered(kReceiverPeerName, received_frame); + } + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + std::map<StatsKey, StreamStats> stats = analyzer.GetStats(); + ASSERT_EQ(stats.size(), 1lu); + const StreamStats& stream_stats = + stats.at(StatsKey(kStreamLabel, kReceiverPeerName)); + ASSERT_EQ(stream_stats.encoders.size(), 2lu); + EXPECT_EQ(stream_stats.encoders[0].codec_name, codec_names[0]); + EXPECT_EQ(stream_stats.encoders[0].first_frame_id, frames[0].id()); + EXPECT_EQ(stream_stats.encoders[0].last_frame_id, frames[2].id()); + EXPECT_EQ(stream_stats.encoders[1].codec_name, codec_names[1]); + EXPECT_EQ(stream_stats.encoders[1].first_frame_id, frames[3].id()); + EXPECT_EQ(stream_stats.encoders[1].last_frame_id, frames[5].id()); + + ASSERT_EQ(stream_stats.decoders.size(), 2lu); + EXPECT_EQ(stream_stats.decoders[0].codec_name, codec_names[0]); + EXPECT_EQ(stream_stats.decoders[0].first_frame_id, frames[0].id()); + EXPECT_EQ(stream_stats.decoders[0].last_frame_id, frames[2].id()); + EXPECT_EQ(stream_stats.decoders[1].codec_name, codec_names[1]); + EXPECT_EQ(stream_stats.decoders[1].first_frame_id, frames[3].id()); + EXPECT_EQ(stream_stats.decoders[1].last_frame_id, frames[5].id()); +} + +TEST(DefaultVideoQualityAnalyzerTest, + FramesInFlightAreCorrectlySentToTheComparatorAfterStop) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + // There are 7 different timings inside frame stats: captured, pre_encode, + // encoded, received, decode_start, decode_end, rendered. captured is always + // set and received is set together with decode_start. So we create 6 + // different frames, where for each frame next timings will be set + // * 1st - all of them set + // * 2nd - captured, pre_encode, encoded, received, decode_start, decode_end + // * 3rd - captured, pre_encode, encoded, received, decode_start + // * 4th - captured, pre_encode, encoded + // * 5th - captured, pre_encode + // * 6th - captured + std::vector<VideoFrame> frames; + // Sender side actions + for (int i = 0; i < 6; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), 1); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames.push_back(frame); + } + for (int i = 0; i < 5; ++i) { + analyzer.OnFramePreEncode(kSenderPeerName, frames[i]); + } + for (int i = 0; i < 4; ++i) { + analyzer.OnFrameEncoded( + kSenderPeerName, frames[i].id(), FakeEncode(frames[i]), + VideoQualityAnalyzerInterface::EncoderStats(), false); + } + + // Receiver side actions + for (int i = 0; i < 3; ++i) { + analyzer.OnFramePreDecode(kReceiverPeerName, frames[i].id(), + FakeEncode(frames[i])); + } + for (int i = 0; i < 2; ++i) { + analyzer.OnFrameDecoded(kReceiverPeerName, DeepCopy(frames[i]), + VideoQualityAnalyzerInterface::DecoderStats()); + } + for (int i = 0; i < 1; ++i) { + analyzer.OnFrameRendered(kReceiverPeerName, DeepCopy(frames[i])); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(analyzer_stats.comparisons_done, 6); + + // The last frames in flight size has to reflect the amount of frame in flight + // before all of them were sent to the comparison when Stop() was invoked. + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(analyzer_stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 5) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 6); + EXPECT_EQ(frame_counters.pre_encoded, 5); + EXPECT_EQ(frame_counters.encoded, 4); + EXPECT_EQ(frame_counters.received, 3); + EXPECT_EQ(frame_counters.decoded, 2); + EXPECT_EQ(frame_counters.rendered, 1); + + EXPECT_EQ(analyzer.GetStats().size(), 1lu); + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kReceiverPeerName)); + EXPECT_EQ(stream_conters.captured, 6); + EXPECT_EQ(stream_conters.pre_encoded, 5); + EXPECT_EQ(stream_conters.encoded, 4); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 2); + EXPECT_EQ(stream_conters.rendered, 1); + } +} + +TEST( + DefaultVideoQualityAnalyzerTest, + FramesInFlightAreCorrectlySentToTheComparatorAfterStopForSenderAndReceiver) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.enable_receive_own_stream = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", + std::vector<std::string>{kSenderPeerName, kReceiverPeerName}, + kAnalyzerMaxThreadsCount); + + // There are 7 different timings inside frame stats: captured, pre_encode, + // encoded, received, decode_start, decode_end, rendered. captured is always + // set and received is set together with decode_start. So we create 6 + // different frames, where for each frame next timings will be set + // * 1st - all of them set + // * 2nd - captured, pre_encode, encoded, received, decode_start, decode_end + // * 3rd - captured, pre_encode, encoded, received, decode_start + // * 4th - captured, pre_encode, encoded + // * 5th - captured, pre_encode + // * 6th - captured + std::vector<VideoFrame> frames; + // Sender side actions + for (int i = 0; i < 6; ++i) { + VideoFrame frame = NextFrame(frame_generator.get(), 1); + frame.set_id( + analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame)); + frames.push_back(frame); + } + for (int i = 0; i < 5; ++i) { + analyzer.OnFramePreEncode(kSenderPeerName, frames[i]); + } + for (int i = 0; i < 4; ++i) { + analyzer.OnFrameEncoded( + kSenderPeerName, frames[i].id(), FakeEncode(frames[i]), + VideoQualityAnalyzerInterface::EncoderStats(), false); + } + + // Receiver side actions + for (int i = 0; i < 3; ++i) { + analyzer.OnFramePreDecode(kSenderPeerName, frames[i].id(), + FakeEncode(frames[i])); + analyzer.OnFramePreDecode(kReceiverPeerName, frames[i].id(), + FakeEncode(frames[i])); + } + for (int i = 0; i < 2; ++i) { + analyzer.OnFrameDecoded(kSenderPeerName, DeepCopy(frames[i]), + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameDecoded(kReceiverPeerName, DeepCopy(frames[i]), + VideoQualityAnalyzerInterface::DecoderStats()); + } + for (int i = 0; i < 1; ++i) { + analyzer.OnFrameRendered(kSenderPeerName, DeepCopy(frames[i])); + analyzer.OnFrameRendered(kReceiverPeerName, DeepCopy(frames[i])); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(analyzer_stats.comparisons_done, 12); + + // The last frames in flight size has to reflect the amount of frame in flight + // before all of them were sent to the comparison when Stop() was invoked. + std::vector<StatsSample> frames_in_flight_sizes = + GetSortedSamples(analyzer_stats.frames_in_flight_left_count); + EXPECT_EQ(frames_in_flight_sizes.back().value, 5) + << ToString(frames_in_flight_sizes); + + FrameCounters frame_counters = analyzer.GetGlobalCounters(); + EXPECT_EQ(frame_counters.captured, 6); + EXPECT_EQ(frame_counters.pre_encoded, 5); + EXPECT_EQ(frame_counters.encoded, 4); + EXPECT_EQ(frame_counters.received, 6); + EXPECT_EQ(frame_counters.decoded, 4); + EXPECT_EQ(frame_counters.rendered, 2); + + EXPECT_EQ(analyzer.GetStats().size(), 2lu); + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kReceiverPeerName)); + EXPECT_EQ(stream_conters.captured, 6); + EXPECT_EQ(stream_conters.pre_encoded, 5); + EXPECT_EQ(stream_conters.encoded, 4); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 2); + EXPECT_EQ(stream_conters.rendered, 1); + } + { + FrameCounters stream_conters = analyzer.GetPerStreamCounters().at( + StatsKey(kStreamLabel, kSenderPeerName)); + EXPECT_EQ(stream_conters.captured, 6); + EXPECT_EQ(stream_conters.pre_encoded, 5); + EXPECT_EQ(stream_conters.encoded, 4); + EXPECT_EQ(stream_conters.received, 3); + EXPECT_EQ(stream_conters.decoded, 2); + EXPECT_EQ(stream_conters.rendered, 1); + } +} + +TEST(DefaultVideoQualityAnalyzerTest, GetStreamFrames) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + // The order in which peers captured frames and passed them to analyzer. + std::vector<std::string> frame_capturers_sequence{ + "alice", "alice", "bob", "bob", "bob", + "bob", "bob", "alice", "alice", "alice", + }; + + std::map<std::string, std::vector<uint16_t>> stream_to_frame_ids; + stream_to_frame_ids.emplace("alice_video", std::vector<uint16_t>{}); + stream_to_frame_ids.emplace("bob_video", std::vector<uint16_t>{}); + + std::vector<VideoFrame> frames; + for (const std::string& sender : frame_capturers_sequence) { + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = + analyzer.OnFrameCaptured(sender, sender + "_video", frame); + frame.set_id(frame_id); + stream_to_frame_ids.find(sender + "_video")->second.push_back(frame_id); + frames.push_back(frame); + analyzer.OnFramePreEncode(sender, frame); + analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), + false); + } + // We don't need to receive frames for stats to be gathered correctly. + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + EXPECT_EQ(analyzer.GetStreamFrames(), stream_to_frame_ids); +} + +TEST(DefaultVideoQualityAnalyzerTest, ReceiverReceivedFramesWhenSenderRemoved) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + analyzer.UnregisterParticipantInCall("alice"); + + analyzer.OnFramePreDecode("bob", frame.id(), FakeEncode(frame)); + analyzer.OnFrameDecoded("bob", DeepCopy(frame), + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered("bob", DeepCopy(frame)); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(stream_conters.captured, 1); + EXPECT_EQ(stream_conters.pre_encoded, 1); + EXPECT_EQ(stream_conters.encoded, 1); + EXPECT_EQ(stream_conters.received, 1); + EXPECT_EQ(stream_conters.decoded, 1); + EXPECT_EQ(stream_conters.rendered, 1); +} + +TEST(DefaultVideoQualityAnalyzerTest, + ReceiverReceivedFramesWhenSenderRemovedWithSelfview) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.enable_receive_own_stream = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + analyzer.UnregisterParticipantInCall("alice"); + + analyzer.OnFramePreDecode("bob", frame.id(), FakeEncode(frame)); + analyzer.OnFrameDecoded("bob", DeepCopy(frame), + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered("bob", DeepCopy(frame)); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(stream_conters.captured, 1); + EXPECT_EQ(stream_conters.pre_encoded, 1); + EXPECT_EQ(stream_conters.encoded, 1); + EXPECT_EQ(stream_conters.received, 1); + EXPECT_EQ(stream_conters.decoded, 1); + EXPECT_EQ(stream_conters.rendered, 1); +} + +TEST(DefaultVideoQualityAnalyzerTest, + SenderReceivedFramesWhenReceiverRemovedWithSelfview) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.enable_receive_own_stream = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + analyzer.UnregisterParticipantInCall("bob"); + + analyzer.OnFramePreDecode("alice", frame.id(), FakeEncode(frame)); + analyzer.OnFrameDecoded("alice", DeepCopy(frame), + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered("alice", DeepCopy(frame)); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "alice")); + EXPECT_EQ(stream_conters.captured, 1); + EXPECT_EQ(stream_conters.pre_encoded, 1); + EXPECT_EQ(stream_conters.encoded, 1); + EXPECT_EQ(stream_conters.received, 1); + EXPECT_EQ(stream_conters.decoded, 1); + EXPECT_EQ(stream_conters.rendered, 1); +} + +TEST(DefaultVideoQualityAnalyzerTest, + SenderAndReceiverReceivedFramesWhenReceiverRemovedWithSelfview) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.enable_receive_own_stream = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + analyzer.OnFramePreDecode("bob", frame.id(), FakeEncode(frame)); + analyzer.OnFrameDecoded("bob", DeepCopy(frame), + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered("bob", DeepCopy(frame)); + + analyzer.UnregisterParticipantInCall("bob"); + + analyzer.OnFramePreDecode("alice", frame.id(), FakeEncode(frame)); + analyzer.OnFrameDecoded("alice", DeepCopy(frame), + VideoQualityAnalyzerInterface::DecoderStats()); + analyzer.OnFrameRendered("alice", DeepCopy(frame)); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters alice_alice_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "alice")); + EXPECT_EQ(alice_alice_stream_conters.captured, 1); + EXPECT_EQ(alice_alice_stream_conters.pre_encoded, 1); + EXPECT_EQ(alice_alice_stream_conters.encoded, 1); + EXPECT_EQ(alice_alice_stream_conters.received, 1); + EXPECT_EQ(alice_alice_stream_conters.decoded, 1); + EXPECT_EQ(alice_alice_stream_conters.rendered, 1); + + FrameCounters alice_bob_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(alice_bob_stream_conters.captured, 1); + EXPECT_EQ(alice_bob_stream_conters.pre_encoded, 1); + EXPECT_EQ(alice_bob_stream_conters.encoded, 1); + EXPECT_EQ(alice_bob_stream_conters.received, 1); + EXPECT_EQ(alice_bob_stream_conters.decoded, 1); + EXPECT_EQ(alice_bob_stream_conters.rendered, 1); +} + +TEST(DefaultVideoQualityAnalyzerTest, ReceiverRemovedBeforeCapturing2ndFrame) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"}, + /*frames_count=*/1, *frame_generator); + analyzer.UnregisterParticipantInCall("bob"); + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {}, + /*frames_count=*/1, *frame_generator); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters global_stream_conters = analyzer.GetGlobalCounters(); + EXPECT_EQ(global_stream_conters.captured, 2); + EXPECT_EQ(global_stream_conters.pre_encoded, 2); + EXPECT_EQ(global_stream_conters.encoded, 2); + EXPECT_EQ(global_stream_conters.received, 1); + EXPECT_EQ(global_stream_conters.decoded, 1); + EXPECT_EQ(global_stream_conters.rendered, 1); + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(stream_conters.captured, 2); + EXPECT_EQ(stream_conters.pre_encoded, 2); + EXPECT_EQ(stream_conters.encoded, 2); + EXPECT_EQ(stream_conters.received, 1); + EXPECT_EQ(stream_conters.decoded, 1); + EXPECT_EQ(stream_conters.rendered, 1); +} + +TEST(DefaultVideoQualityAnalyzerTest, ReceiverRemovedBeforePreEncoded) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.UnregisterParticipantInCall("bob"); + analyzer.OnFramePreEncode("alice", frame); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters global_stream_conters = analyzer.GetGlobalCounters(); + EXPECT_EQ(global_stream_conters.captured, 1); + EXPECT_EQ(global_stream_conters.pre_encoded, 1); + EXPECT_EQ(global_stream_conters.encoded, 1); + EXPECT_EQ(global_stream_conters.received, 0); + EXPECT_EQ(global_stream_conters.decoded, 0); + EXPECT_EQ(global_stream_conters.rendered, 0); + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(stream_conters.captured, 1); + EXPECT_EQ(stream_conters.pre_encoded, 1); + EXPECT_EQ(stream_conters.encoded, 1); + EXPECT_EQ(stream_conters.received, 0); + EXPECT_EQ(stream_conters.decoded, 0); + EXPECT_EQ(stream_conters.rendered, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, ReceiverRemovedBeforeEncoded) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + analyzer.UnregisterParticipantInCall("bob"); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters global_stream_conters = analyzer.GetGlobalCounters(); + EXPECT_EQ(global_stream_conters.captured, 1); + EXPECT_EQ(global_stream_conters.pre_encoded, 1); + EXPECT_EQ(global_stream_conters.encoded, 1); + EXPECT_EQ(global_stream_conters.received, 0); + EXPECT_EQ(global_stream_conters.decoded, 0); + EXPECT_EQ(global_stream_conters.rendered, 0); + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(stream_conters.captured, 1); + EXPECT_EQ(stream_conters.pre_encoded, 1); + EXPECT_EQ(stream_conters.encoded, 1); + EXPECT_EQ(stream_conters.received, 0); + EXPECT_EQ(stream_conters.decoded, 0); + EXPECT_EQ(stream_conters.rendered, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, + ReceiverRemovedBetweenSimulcastLayersEncoded) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + // 1st simulcast layer encoded + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + analyzer.UnregisterParticipantInCall("bob"); + // 2nd simulcast layer encoded + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters global_stream_conters = analyzer.GetGlobalCounters(); + EXPECT_EQ(global_stream_conters.captured, 1); + EXPECT_EQ(global_stream_conters.pre_encoded, 1); + EXPECT_EQ(global_stream_conters.encoded, 1); + EXPECT_EQ(global_stream_conters.received, 0); + EXPECT_EQ(global_stream_conters.decoded, 0); + EXPECT_EQ(global_stream_conters.rendered, 0); + FrameCounters stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(stream_conters.captured, 1); + EXPECT_EQ(stream_conters.pre_encoded, 1); + EXPECT_EQ(stream_conters.encoded, 1); + EXPECT_EQ(stream_conters.received, 0); + EXPECT_EQ(stream_conters.decoded, 0); + EXPECT_EQ(stream_conters.rendered, 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, UnregisterOneAndRegisterAnother) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", + std::vector<std::string>{"alice", "bob", "charlie"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", + {"bob", "charlie"}, + /*frames_count=*/2, *frame_generator); + analyzer.UnregisterParticipantInCall("bob"); + analyzer.RegisterParticipantInCall("david"); + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", + {"charlie", "david"}, + /*frames_count=*/4, *frame_generator); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters global_stream_conters = analyzer.GetGlobalCounters(); + EXPECT_EQ(global_stream_conters.captured, 6); + EXPECT_EQ(global_stream_conters.pre_encoded, 6); + EXPECT_EQ(global_stream_conters.encoded, 6); + EXPECT_EQ(global_stream_conters.received, 12); + EXPECT_EQ(global_stream_conters.decoded, 12); + EXPECT_EQ(global_stream_conters.rendered, 12); + FrameCounters alice_bob_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(alice_bob_stream_conters.captured, 6); + EXPECT_EQ(alice_bob_stream_conters.pre_encoded, 6); + EXPECT_EQ(alice_bob_stream_conters.encoded, 6); + EXPECT_EQ(alice_bob_stream_conters.received, 2); + EXPECT_EQ(alice_bob_stream_conters.decoded, 2); + EXPECT_EQ(alice_bob_stream_conters.rendered, 2); + FrameCounters alice_charlie_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "charlie")); + EXPECT_EQ(alice_charlie_stream_conters.captured, 6); + EXPECT_EQ(alice_charlie_stream_conters.pre_encoded, 6); + EXPECT_EQ(alice_charlie_stream_conters.encoded, 6); + EXPECT_EQ(alice_charlie_stream_conters.received, 6); + EXPECT_EQ(alice_charlie_stream_conters.decoded, 6); + EXPECT_EQ(alice_charlie_stream_conters.rendered, 6); + FrameCounters alice_david_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "david")); + EXPECT_EQ(alice_david_stream_conters.captured, 6); + EXPECT_EQ(alice_david_stream_conters.pre_encoded, 6); + EXPECT_EQ(alice_david_stream_conters.encoded, 6); + EXPECT_EQ(alice_david_stream_conters.received, 4); + EXPECT_EQ(alice_david_stream_conters.decoded, 4); + EXPECT_EQ(alice_david_stream_conters.rendered, 4); +} + +TEST(DefaultVideoQualityAnalyzerTest, + UnregisterOneAndRegisterAnotherRegisterBack) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", + std::vector<std::string>{"alice", "bob", "charlie"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", + {"bob", "charlie"}, + /*frames_count=*/2, *frame_generator); + analyzer.UnregisterParticipantInCall("bob"); + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"charlie"}, + /*frames_count=*/4, *frame_generator); + analyzer.RegisterParticipantInCall("bob"); + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", + {"bob", "charlie"}, + /*frames_count=*/6, *frame_generator); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + FrameCounters global_stream_conters = analyzer.GetGlobalCounters(); + EXPECT_EQ(global_stream_conters.captured, 12); + EXPECT_EQ(global_stream_conters.pre_encoded, 12); + EXPECT_EQ(global_stream_conters.encoded, 12); + EXPECT_EQ(global_stream_conters.received, 20); + EXPECT_EQ(global_stream_conters.decoded, 20); + EXPECT_EQ(global_stream_conters.rendered, 20); + FrameCounters alice_bob_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob")); + EXPECT_EQ(alice_bob_stream_conters.captured, 12); + EXPECT_EQ(alice_bob_stream_conters.pre_encoded, 12); + EXPECT_EQ(alice_bob_stream_conters.encoded, 12); + EXPECT_EQ(alice_bob_stream_conters.received, 8); + EXPECT_EQ(alice_bob_stream_conters.decoded, 8); + EXPECT_EQ(alice_bob_stream_conters.rendered, 8); + FrameCounters alice_charlie_stream_conters = + analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "charlie")); + EXPECT_EQ(alice_charlie_stream_conters.captured, 12); + EXPECT_EQ(alice_charlie_stream_conters.pre_encoded, 12); + EXPECT_EQ(alice_charlie_stream_conters.encoded, 12); + EXPECT_EQ(alice_charlie_stream_conters.received, 12); + EXPECT_EQ(alice_charlie_stream_conters.decoded, 12); + EXPECT_EQ(alice_charlie_stream_conters.rendered, 12); +} + +TEST(DefaultVideoQualityAnalyzerTest, + FramesInFlightAreAccountedForUnregisterPeers) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + // Add one frame in flight which has encode time >= 10ms. + VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame); + frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", frame); + SleepMs(10); + analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + + analyzer.UnregisterParticipantInCall("bob"); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob")); + ASSERT_EQ(stats.encode_time_ms.NumSamples(), 1); + EXPECT_GE(stats.encode_time_ms.GetAverage(), 10); +} + +TEST(DefaultVideoQualityAnalyzerTest, InfraMetricsAreReportedWhenRequested) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.report_infra_metrics = true; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"}, + /*frames_count=*/1, *frame_generator); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.on_frame_captured_processing_time_ms.NumSamples(), 1); + EXPECT_EQ(stats.on_frame_pre_encode_processing_time_ms.NumSamples(), 1); + EXPECT_EQ(stats.on_frame_encoded_processing_time_ms.NumSamples(), 1); + EXPECT_EQ(stats.on_frame_pre_decode_processing_time_ms.NumSamples(), 1); + EXPECT_EQ(stats.on_frame_decoded_processing_time_ms.NumSamples(), 1); + EXPECT_EQ(stats.on_frame_rendered_processing_time_ms.NumSamples(), 1); + EXPECT_EQ(stats.on_decoder_error_processing_time_ms.NumSamples(), 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, InfraMetricsNotCollectedByDefault) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.report_infra_metrics = false; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"}, + /*frames_count=*/1, *frame_generator); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + AnalyzerStats stats = analyzer.GetAnalyzerStats(); + EXPECT_EQ(stats.on_frame_captured_processing_time_ms.NumSamples(), 0); + EXPECT_EQ(stats.on_frame_pre_encode_processing_time_ms.NumSamples(), 0); + EXPECT_EQ(stats.on_frame_encoded_processing_time_ms.NumSamples(), 0); + EXPECT_EQ(stats.on_frame_pre_decode_processing_time_ms.NumSamples(), 0); + EXPECT_EQ(stats.on_frame_decoded_processing_time_ms.NumSamples(), 0); + EXPECT_EQ(stats.on_frame_rendered_processing_time_ms.NumSamples(), 0); + EXPECT_EQ(stats.on_decoder_error_processing_time_ms.NumSamples(), 0); +} + +TEST(DefaultVideoQualityAnalyzerTest, + FrameDroppedByDecoderIsAccountedCorrectly) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + options.report_infra_metrics = false; + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + VideoFrame to_be_dropped_frame = + NextFrame(frame_generator.get(), /*timestamp_us=*/1); + uint16_t frame_id = + analyzer.OnFrameCaptured("alice", "alice_video", to_be_dropped_frame); + to_be_dropped_frame.set_id(frame_id); + analyzer.OnFramePreEncode("alice", to_be_dropped_frame); + analyzer.OnFrameEncoded("alice", to_be_dropped_frame.id(), + FakeEncode(to_be_dropped_frame), + VideoQualityAnalyzerInterface::EncoderStats(), false); + VideoFrame received_to_be_dropped_frame = DeepCopy(to_be_dropped_frame); + analyzer.OnFramePreDecode("bob", received_to_be_dropped_frame.id(), + FakeEncode(received_to_be_dropped_frame)); + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"}, + /*frames_count=*/1, *frame_generator); + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(100); + analyzer.Stop(); + + StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob")); + ASSERT_EQ(stats.dropped_by_phase[FrameDropPhase::kByDecoder], 1); +} + +class DefaultVideoQualityAnalyzerTimeBetweenFreezesTest + : public TestWithParam<bool> {}; + +TEST_P(DefaultVideoQualityAnalyzerTimeBetweenFreezesTest, + TimeBetweenFreezesIsEqualToStreamDurationWhenThereAreNoFeeezes) { + std::unique_ptr<test::FrameGeneratorInterface> frame_generator = + test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight, + /*type=*/absl::nullopt, + /*num_squares=*/absl::nullopt); + + DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest(); + DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(), + test::GetGlobalMetricsLogger(), options); + analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"}, + kAnalyzerMaxThreadsCount); + + PassFramesThroughAnalyzer(analyzer, "alice", "alice_video", {"bob"}, + /*frames_count=*/5, *frame_generator, + /*interframe_delay_ms=*/50); + if (GetParam()) { + analyzer.UnregisterParticipantInCall("bob"); + } + + // Give analyzer some time to process frames on async thread. The computations + // have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it + // means we have an issue! + SleepMs(50); + analyzer.Stop(); + + StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob")); + ASSERT_EQ(stats.time_between_freezes_ms.NumSamples(), 1); + EXPECT_GE(stats.time_between_freezes_ms.GetAverage(), 200); +} + +INSTANTIATE_TEST_SUITE_P(WithRegisteredAndUnregisteredPeerAtTheEndOfTheCall, + DefaultVideoQualityAnalyzerTimeBetweenFreezesTest, + ValuesIn({true, false})); + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/encoded_image_data_injector.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/encoded_image_data_injector.h new file mode 100644 index 0000000000..384e901462 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/encoded_image_data_injector.h @@ -0,0 +1,79 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_ENCODED_IMAGE_DATA_INJECTOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_ENCODED_IMAGE_DATA_INJECTOR_H_ + +#include <cstdint> +#include <utility> + +#include "absl/types/optional.h" +#include "api/video/encoded_image.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Injects frame id into EncodedImage on encoder side +class EncodedImageDataInjector { + public: + virtual ~EncodedImageDataInjector() = default; + + // Return encoded image with specified `id` and `discard` flag injected into + // its payload. `discard` flag mean does analyzing decoder should discard this + // encoded image because it belongs to unnecessary simulcast stream or spatial + // layer. + virtual EncodedImage InjectData(uint16_t id, + bool discard, + const EncodedImage& source) = 0; +}; + +struct EncodedImageExtractionResult { + absl::optional<uint16_t> id; + EncodedImage image; + // Is true if encoded image should be discarded. It is used to filter out + // unnecessary spatial layers and simulcast streams. + bool discard; +}; + +// Extracts frame id from EncodedImage on decoder side. +class EncodedImageDataExtractor { + public: + virtual ~EncodedImageDataExtractor() = default; + + // Invoked by framework before any image will come to the extractor. + // `expected_receivers_count` is the expected amount of receivers for each + // encoded image. + virtual void Start(int expected_receivers_count) = 0; + + // Invoked by framework when it is required to add one more receiver for + // frames. Will be invoked before that receiver will start receive data. + virtual void AddParticipantInCall() = 0; + + // Invoked by framework when it is required to remove receiver for frames. + // Will be invoked after that receiver will stop receiving data. + virtual void RemoveParticipantInCall() = 0; + + // Returns encoded image id, extracted from payload and also encoded image + // with its original payload. For concatenated spatial layers it should be the + // same id. + virtual EncodedImageExtractionResult ExtractData( + const EncodedImage& source) = 0; +}; + +class EncodedImageDataPropagator : public EncodedImageDataInjector, + public EncodedImageDataExtractor { + public: + ~EncodedImageDataPropagator() override = default; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_ENCODED_IMAGE_DATA_INJECTOR_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/example_video_quality_analyzer.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/example_video_quality_analyzer.cc new file mode 100644 index 0000000000..da9c53beb9 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/example_video_quality_analyzer.cc @@ -0,0 +1,168 @@ +/* + * 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/pc/e2e/analyzer/video/example_video_quality_analyzer.h" + +#include "api/array_view.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +ExampleVideoQualityAnalyzer::ExampleVideoQualityAnalyzer() = default; +ExampleVideoQualityAnalyzer::~ExampleVideoQualityAnalyzer() = default; + +void ExampleVideoQualityAnalyzer::Start( + std::string test_case_name, + rtc::ArrayView<const std::string> peer_names, + int max_threads_count) {} + +uint16_t ExampleVideoQualityAnalyzer::OnFrameCaptured( + absl::string_view peer_name, + const std::string& stream_label, + const webrtc::VideoFrame& frame) { + MutexLock lock(&lock_); + uint16_t frame_id = next_frame_id_++; + if (frame_id == VideoFrame::kNotSetId) { + frame_id = next_frame_id_++; + } + auto it = frames_in_flight_.find(frame_id); + if (it == frames_in_flight_.end()) { + frames_in_flight_.insert(frame_id); + frames_to_stream_label_.insert({frame_id, stream_label}); + } else { + RTC_LOG(LS_WARNING) << "Meet new frame with the same id: " << frame_id + << ". Assumes old one as dropped"; + // We needn't insert frame to frames_in_flight_, because it is already + // there. + ++frames_dropped_; + auto stream_it = frames_to_stream_label_.find(frame_id); + RTC_CHECK(stream_it != frames_to_stream_label_.end()); + stream_it->second = stream_label; + } + ++frames_captured_; + return frame_id; +} + +void ExampleVideoQualityAnalyzer::OnFramePreEncode( + absl::string_view peer_name, + const webrtc::VideoFrame& frame) { + MutexLock lock(&lock_); + ++frames_pre_encoded_; +} + +void ExampleVideoQualityAnalyzer::OnFrameEncoded( + absl::string_view peer_name, + uint16_t frame_id, + const webrtc::EncodedImage& encoded_image, + const EncoderStats& stats, + bool discarded) { + MutexLock lock(&lock_); + ++frames_encoded_; +} + +void ExampleVideoQualityAnalyzer::OnFrameDropped( + absl::string_view peer_name, + webrtc::EncodedImageCallback::DropReason reason) { + RTC_LOG(LS_INFO) << "Frame dropped by encoder"; + MutexLock lock(&lock_); + ++frames_dropped_; +} + +void ExampleVideoQualityAnalyzer::OnFramePreDecode( + absl::string_view peer_name, + uint16_t frame_id, + const webrtc::EncodedImage& encoded_image) { + MutexLock lock(&lock_); + ++frames_received_; +} + +void ExampleVideoQualityAnalyzer::OnFrameDecoded( + absl::string_view peer_name, + const webrtc::VideoFrame& frame, + const DecoderStats& stats) { + MutexLock lock(&lock_); + ++frames_decoded_; +} + +void ExampleVideoQualityAnalyzer::OnFrameRendered( + absl::string_view peer_name, + const webrtc::VideoFrame& frame) { + MutexLock lock(&lock_); + frames_in_flight_.erase(frame.id()); + ++frames_rendered_; +} + +void ExampleVideoQualityAnalyzer::OnEncoderError( + absl::string_view peer_name, + const webrtc::VideoFrame& frame, + int32_t error_code) { + RTC_LOG(LS_ERROR) << "Failed to encode frame " << frame.id() + << ". Code: " << error_code; +} + +void ExampleVideoQualityAnalyzer::OnDecoderError(absl::string_view peer_name, + uint16_t frame_id, + int32_t error_code, + const DecoderStats& stats) { + RTC_LOG(LS_ERROR) << "Failed to decode frame " << frame_id + << ". Code: " << error_code; +} + +void ExampleVideoQualityAnalyzer::Stop() { + MutexLock lock(&lock_); + RTC_LOG(LS_INFO) << "There are " << frames_in_flight_.size() + << " frames in flight, assuming all of them are dropped"; + frames_dropped_ += frames_in_flight_.size(); +} + +std::string ExampleVideoQualityAnalyzer::GetStreamLabel(uint16_t frame_id) { + MutexLock lock(&lock_); + auto it = frames_to_stream_label_.find(frame_id); + RTC_DCHECK(it != frames_to_stream_label_.end()) + << "Unknown frame_id=" << frame_id; + return it->second; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_captured() const { + MutexLock lock(&lock_); + return frames_captured_; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_pre_encoded() const { + MutexLock lock(&lock_); + return frames_pre_encoded_; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_encoded() const { + MutexLock lock(&lock_); + return frames_encoded_; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_received() const { + MutexLock lock(&lock_); + return frames_received_; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_decoded() const { + MutexLock lock(&lock_); + return frames_decoded_; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_rendered() const { + MutexLock lock(&lock_); + return frames_rendered_; +} + +uint64_t ExampleVideoQualityAnalyzer::frames_dropped() const { + MutexLock lock(&lock_); + return frames_dropped_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/example_video_quality_analyzer.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/example_video_quality_analyzer.h new file mode 100644 index 0000000000..af4868a961 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/example_video_quality_analyzer.h @@ -0,0 +1,101 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_EXAMPLE_VIDEO_QUALITY_ANALYZER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_EXAMPLE_VIDEO_QUALITY_ANALYZER_H_ + +#include <atomic> +#include <map> +#include <set> +#include <string> + +#include "api/array_view.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "api/video/encoded_image.h" +#include "api/video/video_frame.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +// This class is an example implementation of +// webrtc::VideoQualityAnalyzerInterface and calculates simple metrics +// just to demonstration purposes. Assumed to be used in the single process +// test cases, where both peers are in the same process. +class ExampleVideoQualityAnalyzer : public VideoQualityAnalyzerInterface { + public: + ExampleVideoQualityAnalyzer(); + ~ExampleVideoQualityAnalyzer() override; + + void Start(std::string test_case_name, + rtc::ArrayView<const std::string> peer_names, + int max_threads_count) override; + uint16_t OnFrameCaptured(absl::string_view peer_name, + const std::string& stream_label, + const VideoFrame& frame) override; + void OnFramePreEncode(absl::string_view peer_name, + const VideoFrame& frame) override; + void OnFrameEncoded(absl::string_view peer_name, + uint16_t frame_id, + const EncodedImage& encoded_image, + const EncoderStats& stats, + bool discarded) override; + void OnFrameDropped(absl::string_view peer_name, + EncodedImageCallback::DropReason reason) override; + void OnFramePreDecode(absl::string_view peer_name, + uint16_t frame_id, + const EncodedImage& encoded_image) override; + void OnFrameDecoded(absl::string_view peer_name, + const VideoFrame& frame, + const DecoderStats& stats) override; + void OnFrameRendered(absl::string_view peer_name, + const VideoFrame& frame) override; + void OnEncoderError(absl::string_view peer_name, + const VideoFrame& frame, + int32_t error_code) override; + void OnDecoderError(absl::string_view peer_name, + uint16_t frame_id, + int32_t error_code, + const DecoderStats& stats) override; + void Stop() override; + std::string GetStreamLabel(uint16_t frame_id) override; + + uint64_t frames_captured() const; + uint64_t frames_pre_encoded() const; + uint64_t frames_encoded() const; + uint64_t frames_received() const; + uint64_t frames_decoded() const; + uint64_t frames_rendered() const; + uint64_t frames_dropped() const; + + private: + // When peer A captured the frame it will come into analyzer's OnFrameCaptured + // and will be stored in frames_in_flight_. It will be removed from there + // when it will be received in peer B, so we need to guard it with lock. + // Also because analyzer will serve for all video streams it can be called + // from different threads inside one peer. + mutable Mutex lock_; + // Stores frame ids, that are currently going from one peer to another. We + // need to keep them to correctly determine dropped frames and also correctly + // process frame id overlap. + std::set<uint16_t> frames_in_flight_ RTC_GUARDED_BY(lock_); + std::map<uint16_t, std::string> frames_to_stream_label_ RTC_GUARDED_BY(lock_); + uint16_t next_frame_id_ RTC_GUARDED_BY(lock_) = 1; + uint64_t frames_captured_ RTC_GUARDED_BY(lock_) = 0; + uint64_t frames_pre_encoded_ RTC_GUARDED_BY(lock_) = 0; + uint64_t frames_encoded_ RTC_GUARDED_BY(lock_) = 0; + uint64_t frames_received_ RTC_GUARDED_BY(lock_) = 0; + uint64_t frames_decoded_ RTC_GUARDED_BY(lock_) = 0; + uint64_t frames_rendered_ RTC_GUARDED_BY(lock_) = 0; + uint64_t frames_dropped_ RTC_GUARDED_BY(lock_) = 0; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_EXAMPLE_VIDEO_QUALITY_ANALYZER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/multi_reader_queue.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/multi_reader_queue.h new file mode 100644 index 0000000000..39d26b42bc --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/multi_reader_queue.h @@ -0,0 +1,168 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_MULTI_READER_QUEUE_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_MULTI_READER_QUEUE_H_ + +#include <deque> +#include <memory> +#include <set> +#include <unordered_map> + +#include "absl/types/optional.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +// Represents the queue which can be read by multiple readers. Each reader reads +// from its own queue head. When an element is added it will become visible for +// all readers. When an element will be removed by all the readers, the element +// will be removed from the queue. +template <typename T> +class MultiReaderQueue { + public: + // Creates queue with exactly `readers_count` readers named from 0 to + // `readers_count - 1`. + explicit MultiReaderQueue(size_t readers_count) { + for (size_t i = 0; i < readers_count; ++i) { + heads_[i] = 0; + } + } + // Creates queue with specified readers. + explicit MultiReaderQueue(std::set<size_t> readers) { + for (size_t reader : readers) { + heads_[reader] = 0; + } + } + + // Adds a new `reader`, initializing its reading position (the reader's head) + // equal to the one of `reader_to_copy`. + // Complexity O(MultiReaderQueue::size(reader_to_copy)). + void AddReader(size_t reader, size_t reader_to_copy) { + size_t pos = GetHeadPositionOrDie(reader_to_copy); + + auto it = heads_.find(reader); + RTC_CHECK(it == heads_.end()) + << "Reader " << reader << " is already in the queue"; + heads_[reader] = heads_[reader_to_copy]; + for (size_t i = pos; i < queue_.size(); ++i) { + in_queues_[i]++; + } + } + + // Adds a new `reader`, initializing its reading position equal to first + // element in the queue. + // Complexity O(MultiReaderQueue::size()). + void AddReader(size_t reader) { + auto it = heads_.find(reader); + RTC_CHECK(it == heads_.end()) + << "Reader " << reader << " is already in the queue"; + heads_[reader] = removed_elements_count_; + for (size_t i = 0; i < queue_.size(); ++i) { + in_queues_[i]++; + } + } + + // Removes specified `reader` from the queue. + // Complexity O(MultiReaderQueue::size(reader)). + void RemoveReader(size_t reader) { + size_t pos = GetHeadPositionOrDie(reader); + for (size_t i = pos; i < queue_.size(); ++i) { + in_queues_[i]--; + } + while (!in_queues_.empty() && in_queues_[0] == 0) { + PopFront(); + } + heads_.erase(reader); + } + + // Add value to the end of the queue. Complexity O(1). + void PushBack(T value) { + queue_.push_back(value); + in_queues_.push_back(heads_.size()); + } + + // Extract element from specified head. Complexity O(1). + absl::optional<T> PopFront(size_t reader) { + size_t pos = GetHeadPositionOrDie(reader); + if (pos >= queue_.size()) { + return absl::nullopt; + } + + T out = queue_[pos]; + + in_queues_[pos]--; + heads_[reader]++; + + if (in_queues_[pos] == 0) { + RTC_DCHECK_EQ(pos, 0); + PopFront(); + } + return out; + } + + // Returns element at specified head. Complexity O(1). + absl::optional<T> Front(size_t reader) const { + size_t pos = GetHeadPositionOrDie(reader); + if (pos >= queue_.size()) { + return absl::nullopt; + } + return queue_[pos]; + } + + // Returns true if for specified head there are no more elements in the queue + // or false otherwise. Complexity O(1). + bool IsEmpty(size_t reader) const { + size_t pos = GetHeadPositionOrDie(reader); + return pos >= queue_.size(); + } + + // Returns size of the longest queue between all readers. + // Complexity O(1). + size_t size() const { return queue_.size(); } + + // Returns size of the specified queue. Complexity O(1). + size_t size(size_t reader) const { + size_t pos = GetHeadPositionOrDie(reader); + return queue_.size() - pos; + } + + // Complexity O(1). + size_t readers_count() const { return heads_.size(); } + + private: + size_t GetHeadPositionOrDie(size_t reader) const { + auto it = heads_.find(reader); + RTC_CHECK(it != heads_.end()) << "No queue for reader " << reader; + return it->second - removed_elements_count_; + } + + void PopFront() { + RTC_DCHECK(!queue_.empty()); + RTC_DCHECK_EQ(in_queues_[0], 0); + queue_.pop_front(); + in_queues_.pop_front(); + removed_elements_count_++; + } + + // Number of the elements that were removed from the queue. It is used to + // subtract from each head to compute the right index inside `queue_`; + size_t removed_elements_count_ = 0; + std::deque<T> queue_; + // In how may queues the element at index `i` is. An element can be removed + // from the front if and only if it is in 0 queues. + std::deque<size_t> in_queues_; + // Map from the reader to the head position in the queue. + std::unordered_map<size_t, size_t> heads_; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_MULTI_READER_QUEUE_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/multi_reader_queue_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/multi_reader_queue_test.cc new file mode 100644 index 0000000000..ea6aa0a416 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/multi_reader_queue_test.cc @@ -0,0 +1,206 @@ +/* + * 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/pc/e2e/analyzer/video/multi_reader_queue.h" + +#include "absl/types/optional.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +TEST(MultiReaderQueueTest, EmptyQueueEmptyForAllHeads) { + MultiReaderQueue<int> queue = MultiReaderQueue<int>(/*readers_count=*/10); + EXPECT_EQ(queue.size(), 0lu); + for (int i = 0; i < 10; ++i) { + EXPECT_TRUE(queue.IsEmpty(/*reader=*/i)); + EXPECT_EQ(queue.size(/*reader=*/i), 0lu); + EXPECT_FALSE(queue.PopFront(/*reader=*/i).has_value()); + EXPECT_FALSE(queue.Front(/*reader=*/i).has_value()); + } +} + +TEST(MultiReaderQueueTest, SizeIsEqualForAllHeadsAfterAddOnly) { + MultiReaderQueue<int> queue = MultiReaderQueue<int>(/*readers_count=*/10); + queue.PushBack(1); + queue.PushBack(2); + queue.PushBack(3); + EXPECT_EQ(queue.size(), 3lu); + for (int i = 0; i < 10; ++i) { + EXPECT_FALSE(queue.IsEmpty(/*reader=*/i)); + EXPECT_EQ(queue.size(/*reader=*/i), 3lu); + } +} + +TEST(MultiReaderQueueTest, SizeIsCorrectAfterRemoveFromOnlyOneHead) { + MultiReaderQueue<int> queue = MultiReaderQueue<int>(/*readers_count=*/10); + for (int i = 0; i < 5; ++i) { + queue.PushBack(i); + } + EXPECT_EQ(queue.size(), 5lu); + // Removing elements from queue #0 + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(queue.size(/*reader=*/0), static_cast<size_t>(5 - i)); + EXPECT_EQ(queue.PopFront(/*reader=*/0), absl::optional<int>(i)); + for (int j = 1; j < 10; ++j) { + EXPECT_EQ(queue.size(/*reader=*/j), 5lu); + } + } + EXPECT_EQ(queue.size(/*reader=*/0), 0lu); + EXPECT_TRUE(queue.IsEmpty(/*reader=*/0)); +} + +TEST(MultiReaderQueueTest, SingleHeadOneAddOneRemove) { + MultiReaderQueue<int> queue = MultiReaderQueue<int>(/*readers_count=*/1); + queue.PushBack(1); + EXPECT_EQ(queue.size(), 1lu); + EXPECT_TRUE(queue.Front(/*reader=*/0).has_value()); + EXPECT_EQ(queue.Front(/*reader=*/0).value(), 1); + absl::optional<int> value = queue.PopFront(/*reader=*/0); + EXPECT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 1); + EXPECT_EQ(queue.size(), 0lu); + EXPECT_TRUE(queue.IsEmpty(/*reader=*/0)); +} + +TEST(MultiReaderQueueTest, SingleHead) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/1); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + EXPECT_EQ(queue.size(), i + 1); + } + for (size_t i = 0; i < 10; ++i) { + EXPECT_EQ(queue.Front(/*reader=*/0), absl::optional<size_t>(i)); + EXPECT_EQ(queue.PopFront(/*reader=*/0), absl::optional<size_t>(i)); + EXPECT_EQ(queue.size(), 10 - i - 1); + } +} + +TEST(MultiReaderQueueTest, ThreeHeadsAddAllRemoveAllPerHead) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/3); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + EXPECT_EQ(queue.size(), i + 1); + } + for (size_t i = 0; i < 10; ++i) { + absl::optional<size_t> value = queue.PopFront(/*reader=*/0); + EXPECT_EQ(queue.size(), 10lu); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), i); + } + for (size_t i = 0; i < 10; ++i) { + absl::optional<size_t> value = queue.PopFront(/*reader=*/1); + EXPECT_EQ(queue.size(), 10lu); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), i); + } + for (size_t i = 0; i < 10; ++i) { + absl::optional<size_t> value = queue.PopFront(/*reader=*/2); + EXPECT_EQ(queue.size(), 10 - i - 1); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), i); + } +} + +TEST(MultiReaderQueueTest, ThreeHeadsAddAllRemoveAll) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/3); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + EXPECT_EQ(queue.size(), i + 1); + } + for (size_t i = 0; i < 10; ++i) { + absl::optional<size_t> value1 = queue.PopFront(/*reader=*/0); + absl::optional<size_t> value2 = queue.PopFront(/*reader=*/1); + absl::optional<size_t> value3 = queue.PopFront(/*reader=*/2); + EXPECT_EQ(queue.size(), 10 - i - 1); + ASSERT_TRUE(value1.has_value()); + ASSERT_TRUE(value2.has_value()); + ASSERT_TRUE(value3.has_value()); + EXPECT_EQ(value1.value(), i); + EXPECT_EQ(value2.value(), i); + EXPECT_EQ(value3.value(), i); + } +} + +TEST(MultiReaderQueueTest, AddReaderSeeElementsOnlyFromReaderToCopy) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/2); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + } + for (size_t i = 0; i < 5; ++i) { + queue.PopFront(0); + } + + queue.AddReader(/*reader=*/2, /*reader_to_copy=*/0); + + EXPECT_EQ(queue.readers_count(), 3lu); + for (size_t i = 5; i < 10; ++i) { + absl::optional<size_t> value = queue.PopFront(/*reader=*/2); + EXPECT_EQ(queue.size(/*reader=*/2), 10 - i - 1); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), i); + } +} + +TEST(MultiReaderQueueTest, AddReaderWithoutReaderToCopySeeFullQueue) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/2); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + } + for (size_t i = 0; i < 5; ++i) { + queue.PopFront(/*reader=*/0); + } + + queue.AddReader(/*reader=*/2); + + EXPECT_EQ(queue.readers_count(), 3lu); + for (size_t i = 0; i < 10; ++i) { + absl::optional<size_t> value = queue.PopFront(/*reader=*/2); + EXPECT_EQ(queue.size(/*reader=*/2), 10 - i - 1); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), i); + } +} + +TEST(MultiReaderQueueTest, RemoveReaderWontChangeOthers) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/2); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + } + EXPECT_EQ(queue.size(/*reader=*/1), 10lu); + + queue.RemoveReader(0); + + EXPECT_EQ(queue.readers_count(), 1lu); + EXPECT_EQ(queue.size(/*reader=*/1), 10lu); +} + +TEST(MultiReaderQueueTest, RemoveLastReaderMakesQueueEmpty) { + MultiReaderQueue<size_t> queue = + MultiReaderQueue<size_t>(/*readers_count=*/1); + for (size_t i = 0; i < 10; ++i) { + queue.PushBack(i); + } + EXPECT_EQ(queue.size(), 10lu); + + queue.RemoveReader(0); + + EXPECT_EQ(queue.size(), 0lu); + EXPECT_EQ(queue.readers_count(), 0lu); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection.cc new file mode 100644 index 0000000000..3ccab620f8 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection.cc @@ -0,0 +1,101 @@ +/* + * 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/pc/e2e/analyzer/video/names_collection.h" + +#include <set> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace webrtc { + +NamesCollection::NamesCollection(rtc::ArrayView<const std::string> names) { + names_ = std::vector<std::string>(names.begin(), names.end()); + for (size_t i = 0; i < names_.size(); ++i) { + index_.emplace(names_[i], i); + removed_.emplace_back(false); + } + size_ = names_.size(); +} + +bool NamesCollection::HasName(absl::string_view name) const { + auto it = index_.find(name); + if (it == index_.end()) { + return false; + } + return !removed_[it->second]; +} + +size_t NamesCollection::AddIfAbsent(absl::string_view name) { + auto it = index_.find(name); + if (it != index_.end()) { + // Name was registered in the collection before: we need to restore it. + size_t index = it->second; + if (removed_[index]) { + removed_[index] = false; + size_++; + } + return index; + } + size_t out = names_.size(); + size_t old_capacity = names_.capacity(); + names_.emplace_back(name); + removed_.emplace_back(false); + size_++; + size_t new_capacity = names_.capacity(); + + if (old_capacity == new_capacity) { + index_.emplace(names_[out], out); + } else { + // Reallocation happened in the vector, so we need to rebuild `index_` to + // fix absl::string_view internal references. + index_.clear(); + for (size_t i = 0; i < names_.size(); ++i) { + index_.emplace(names_[i], i); + } + } + return out; +} + +absl::optional<size_t> NamesCollection::RemoveIfPresent( + absl::string_view name) { + auto it = index_.find(name); + if (it == index_.end()) { + return absl::nullopt; + } + size_t index = it->second; + if (removed_[index]) { + return absl::nullopt; + } + removed_[index] = true; + size_--; + return index; +} + +std::set<size_t> NamesCollection::GetPresentIndexes() const { + std::set<size_t> out; + for (size_t i = 0; i < removed_.size(); ++i) { + if (!removed_[i]) { + out.insert(i); + } + } + return out; +} + +std::set<size_t> NamesCollection::GetAllIndexes() const { + std::set<size_t> out; + for (size_t i = 0; i < names_.size(); ++i) { + out.insert(i); + } + return out; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection.h new file mode 100644 index 0000000000..f9a13a2a11 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection.h @@ -0,0 +1,94 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_NAMES_COLLECTION_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_NAMES_COLLECTION_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" + +namespace webrtc { + +// Contains mapping between string names and unique size_t values (indexes). +// Once the name is added to the collection it is guaranteed: +// 1. Name will have the same index until collection will be destructed +// 2. Adding, removing and re-adding name won't change its index +// +// The name is considered in the collection if it was added and wasn't removed. +// Adding the name when it is in the collection won't change the collection, the +// same as removing the name when it is removed. +// +// Collection will return name's index and name for the index independently from +// was name removed or not. Once the name was added to the collection the index +// will be allocated for it. To check if name is in collection right now user +// has to explicitly call to `HasName` function. +class NamesCollection { + public: + NamesCollection() = default; + + explicit NamesCollection(rtc::ArrayView<const std::string> names); + + // Returns amount of currently presented names in the collection. + size_t size() const { return size_; } + + // Returns amount of all names known to this collection. + size_t GetKnownSize() const { return names_.size(); } + + // Returns index of the `name` which was known to the collection. Crashes + // if `name` was never registered in the collection. + size_t index(absl::string_view name) const { return index_.at(name); } + + // Returns name which was known to the collection for the specified `index`. + // Crashes if there was no any name registered in the collection for such + // `index`. + const std::string& name(size_t index) const { return names_.at(index); } + + // Returns if `name` is currently presented in this collection. + bool HasName(absl::string_view name) const; + + // Adds specified `name` to the collection if it isn't presented. + // Returns index which corresponds to specified `name`. + size_t AddIfAbsent(absl::string_view name); + + // Removes specified `name` from the collection if it is presented. + // + // After name was removed, collection size will be decreased, but `name` index + // will be preserved. Collection will return false for `HasName(name)`, but + // will continue to return previously known index for `index(name)` and return + // `name` for `name(index(name))`. + // + // Returns the index of the removed value or absl::nullopt if no such `name` + // registered in the collection. + absl::optional<size_t> RemoveIfPresent(absl::string_view name); + + // Returns a set of indexes for all currently present names in the + // collection. + std::set<size_t> GetPresentIndexes() const; + + // Returns a set of all indexes known to the collection including indexes for + // names that were removed. + std::set<size_t> GetAllIndexes() const; + + private: + std::vector<std::string> names_; + std::vector<bool> removed_; + std::map<absl::string_view, size_t> index_; + size_t size_ = 0; +}; + +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_NAMES_COLLECTION_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection_test.cc new file mode 100644 index 0000000000..6c52f96975 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/names_collection_test.cc @@ -0,0 +1,152 @@ +/* + * 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/pc/e2e/analyzer/video/names_collection.h" + +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Eq; +using ::testing::Ne; + +TEST(NamesCollectionTest, NamesFromCtorHasUniqueIndexes) { + NamesCollection collection(std::vector<std::string>{"alice", "bob"}); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(2))); + EXPECT_TRUE(collection.HasName("alice")); + EXPECT_THAT(collection.name(collection.index("alice")), Eq("alice")); + + EXPECT_TRUE(collection.HasName("bob")); + EXPECT_THAT(collection.name(collection.index("bob")), Eq("bob")); + + EXPECT_THAT(collection.index("bob"), Ne(collection.index("alice"))); +} + +TEST(NamesCollectionTest, AddedNamesHasIndexes) { + NamesCollection collection(std::vector<std::string>{}); + collection.AddIfAbsent("alice"); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + EXPECT_TRUE(collection.HasName("alice")); + EXPECT_THAT(collection.name(collection.index("alice")), Eq("alice")); +} + +TEST(NamesCollectionTest, AddBobDoesNotChangeAliceIndex) { + NamesCollection collection(std::vector<std::string>{"alice"}); + + size_t alice_index = collection.index("alice"); + + collection.AddIfAbsent("bob"); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(2))); + EXPECT_THAT(collection.index("alice"), Eq(alice_index)); + EXPECT_THAT(collection.index("bob"), Ne(alice_index)); +} + +TEST(NamesCollectionTest, AddAliceSecondTimeDoesNotChangeIndex) { + NamesCollection collection(std::vector<std::string>{"alice"}); + + size_t alice_index = collection.index("alice"); + + EXPECT_THAT(collection.AddIfAbsent("alice"), Eq(alice_index)); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + EXPECT_THAT(collection.index("alice"), Eq(alice_index)); +} + +TEST(NamesCollectionTest, RemoveRemovesFromCollectionButNotIndex) { + NamesCollection collection(std::vector<std::string>{"alice", "bob"}); + + size_t bob_index = collection.index("bob"); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(2))); + + EXPECT_THAT(collection.RemoveIfPresent("bob"), + Eq(absl::optional<size_t>(bob_index))); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + EXPECT_FALSE(collection.HasName("bob")); + + EXPECT_THAT(collection.index("bob"), Eq(bob_index)); + EXPECT_THAT(collection.name(bob_index), Eq("bob")); +} + +TEST(NamesCollectionTest, RemoveOfAliceDoesNotChangeBobIndex) { + NamesCollection collection(std::vector<std::string>{"alice", "bob"}); + + size_t alice_index = collection.index("alice"); + size_t bob_index = collection.index("bob"); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(2))); + + EXPECT_THAT(collection.RemoveIfPresent("alice"), + Eq(absl::optional<size_t>(alice_index))); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + EXPECT_THAT(collection.index("bob"), Eq(bob_index)); + EXPECT_THAT(collection.name(bob_index), Eq("bob")); +} + +TEST(NamesCollectionTest, RemoveSecondTimeHasNoEffect) { + NamesCollection collection(std::vector<std::string>{"bob"}); + + size_t bob_index = collection.index("bob"); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + EXPECT_THAT(collection.RemoveIfPresent("bob"), + Eq(absl::optional<size_t>(bob_index))); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(0))); + EXPECT_THAT(collection.RemoveIfPresent("bob"), Eq(absl::nullopt)); +} + +TEST(NamesCollectionTest, RemoveOfNotExistingHasNoEffect) { + NamesCollection collection(std::vector<std::string>{"bob"}); + + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + EXPECT_THAT(collection.RemoveIfPresent("alice"), Eq(absl::nullopt)); + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); +} + +TEST(NamesCollectionTest, AddRemoveAddPreserveTheIndex) { + NamesCollection collection(std::vector<std::string>{}); + + size_t alice_index = collection.AddIfAbsent("alice"); + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); + + EXPECT_THAT(collection.RemoveIfPresent("alice"), + Eq(absl::optional<size_t>(alice_index))); + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(0))); + + EXPECT_THAT(collection.AddIfAbsent("alice"), Eq(alice_index)); + EXPECT_THAT(collection.index("alice"), Eq(alice_index)); + EXPECT_THAT(collection.size(), Eq(static_cast<size_t>(1))); +} + +TEST(NamesCollectionTest, GetKnownSizeReturnsForRemovedNames) { + NamesCollection collection(std::vector<std::string>{}); + + size_t alice_index = collection.AddIfAbsent("alice"); + EXPECT_THAT(collection.GetKnownSize(), Eq(static_cast<size_t>(1))); + + EXPECT_THAT(collection.RemoveIfPresent("alice"), + Eq(absl::optional<size_t>(alice_index))); + EXPECT_THAT(collection.GetKnownSize(), Eq(static_cast<size_t>(1))); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.cc new file mode 100644 index 0000000000..b958f4d027 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.cc @@ -0,0 +1,272 @@ +/* + * 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/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h" + +#include <cstdint> +#include <cstring> +#include <memory> +#include <utility> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/logging.h" +#include "test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +QualityAnalyzingVideoDecoder::QualityAnalyzingVideoDecoder( + absl::string_view peer_name, + std::unique_ptr<VideoDecoder> delegate, + EncodedImageDataExtractor* extractor, + VideoQualityAnalyzerInterface* analyzer) + : peer_name_(peer_name), + implementation_name_("AnalyzingDecoder-" + + std::string(delegate->ImplementationName())), + delegate_(std::move(delegate)), + extractor_(extractor), + analyzer_(analyzer) { + analyzing_callback_ = std::make_unique<DecoderCallback>(this); +} +QualityAnalyzingVideoDecoder::~QualityAnalyzingVideoDecoder() = default; + +bool QualityAnalyzingVideoDecoder::Configure(const Settings& settings) { + { + MutexLock lock(&mutex_); + codec_name_ = std::string(CodecTypeToPayloadString(settings.codec_type())) + + "_" + delegate_->GetDecoderInfo().implementation_name; + } + return delegate_->Configure(settings); +} + +int32_t QualityAnalyzingVideoDecoder::Decode(const EncodedImage& input_image, + bool missing_frames, + int64_t render_time_ms) { + // Image extractor extracts id from provided EncodedImage and also returns + // the image with the original buffer. Buffer can be modified in place, so + // owner of original buffer will be responsible for deleting it, or extractor + // can create a new buffer. In such case extractor will be responsible for + // deleting it. + EncodedImageExtractionResult out = extractor_->ExtractData(input_image); + + if (out.discard) { + // To partly emulate behavior of Selective Forwarding Unit (SFU) in the + // test, on receiver side we will "discard" frames from irrelevant streams. + // When all encoded images were marked to discarded, black frame have to be + // returned. Because simulcast streams will be received by receiver as 3 + // different independent streams we don't want that irrelevant streams + // affect video quality metrics and also we don't want to use CPU time to + // decode them to prevent regressions on relevant streams. Also we can't + // just drop frame, because in such case, receiving part will be confused + // with all frames missing and will request a key frame, which will result + // into extra load on network and sender side. Because of it, discarded + // image will be always decoded as black frame and will be passed to + // callback directly without reaching decoder and video quality analyzer. + // + // For more details see QualityAnalyzingVideoEncoder. + return analyzing_callback_->IrrelevantSimulcastStreamDecoded( + out.id.value_or(VideoFrame::kNotSetId), input_image.Timestamp()); + } + + EncodedImage* origin_image; + { + MutexLock lock(&mutex_); + // Store id to be able to retrieve it in analyzing callback. + timestamp_to_frame_id_.insert({input_image.Timestamp(), out.id}); + // Store encoded image to prevent its destruction while it is used in + // decoder. + origin_image = &( + decoding_images_.insert({input_image.Timestamp(), std::move(out.image)}) + .first->second); + } + // We can safely dereference `origin_image`, because it can be removed from + // the map only after `delegate_` Decode method will be invoked. Image will + // be removed inside DecodedImageCallback, which can be done on separate + // thread. + analyzer_->OnFramePreDecode( + peer_name_, out.id.value_or(VideoFrame::kNotSetId), *origin_image); + int32_t result = + delegate_->Decode(*origin_image, missing_frames, render_time_ms); + if (result != WEBRTC_VIDEO_CODEC_OK) { + // If delegate decoder failed, then cleanup data for this image. + VideoQualityAnalyzerInterface::DecoderStats stats; + { + MutexLock lock(&mutex_); + timestamp_to_frame_id_.erase(input_image.Timestamp()); + decoding_images_.erase(input_image.Timestamp()); + stats.decoder_name = codec_name_; + } + analyzer_->OnDecoderError( + peer_name_, out.id.value_or(VideoFrame::kNotSetId), result, stats); + } + return result; +} + +int32_t QualityAnalyzingVideoDecoder::RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) { + analyzing_callback_->SetDelegateCallback(callback); + return delegate_->RegisterDecodeCompleteCallback(analyzing_callback_.get()); +} + +int32_t QualityAnalyzingVideoDecoder::Release() { + // Release decoder first. During release process it can still decode some + // frames, so we don't take a lock to prevent deadlock. + int32_t result = delegate_->Release(); + + MutexLock lock(&mutex_); + analyzing_callback_->SetDelegateCallback(nullptr); + timestamp_to_frame_id_.clear(); + decoding_images_.clear(); + return result; +} + +VideoDecoder::DecoderInfo QualityAnalyzingVideoDecoder::GetDecoderInfo() const { + DecoderInfo info = delegate_->GetDecoderInfo(); + info.implementation_name = implementation_name_; + return info; +} + +const char* QualityAnalyzingVideoDecoder::ImplementationName() const { + return implementation_name_.c_str(); +} + +QualityAnalyzingVideoDecoder::DecoderCallback::DecoderCallback( + QualityAnalyzingVideoDecoder* decoder) + : decoder_(decoder), delegate_callback_(nullptr) {} +QualityAnalyzingVideoDecoder::DecoderCallback::~DecoderCallback() = default; + +void QualityAnalyzingVideoDecoder::DecoderCallback::SetDelegateCallback( + DecodedImageCallback* delegate) { + MutexLock lock(&callback_mutex_); + delegate_callback_ = delegate; +} + +// We have to implement all next 3 methods because we don't know which one +// exactly is implemented in `delegate_callback_`, so we need to call the same +// method on `delegate_callback_`, as was called on `this` callback. +int32_t QualityAnalyzingVideoDecoder::DecoderCallback::Decoded( + VideoFrame& decodedImage) { + decoder_->OnFrameDecoded(&decodedImage, /*decode_time_ms=*/absl::nullopt, + /*qp=*/absl::nullopt); + + MutexLock lock(&callback_mutex_); + RTC_DCHECK(delegate_callback_); + return delegate_callback_->Decoded(decodedImage); +} + +int32_t QualityAnalyzingVideoDecoder::DecoderCallback::Decoded( + VideoFrame& decodedImage, + int64_t decode_time_ms) { + decoder_->OnFrameDecoded(&decodedImage, decode_time_ms, /*qp=*/absl::nullopt); + + MutexLock lock(&callback_mutex_); + RTC_DCHECK(delegate_callback_); + return delegate_callback_->Decoded(decodedImage, decode_time_ms); +} + +void QualityAnalyzingVideoDecoder::DecoderCallback::Decoded( + VideoFrame& decodedImage, + absl::optional<int32_t> decode_time_ms, + absl::optional<uint8_t> qp) { + decoder_->OnFrameDecoded(&decodedImage, decode_time_ms, qp); + + MutexLock lock(&callback_mutex_); + RTC_DCHECK(delegate_callback_); + delegate_callback_->Decoded(decodedImage, decode_time_ms, qp); +} + +int32_t +QualityAnalyzingVideoDecoder::DecoderCallback::IrrelevantSimulcastStreamDecoded( + uint16_t frame_id, + uint32_t timestamp_ms) { + webrtc::VideoFrame dummy_frame = + webrtc::VideoFrame::Builder() + .set_video_frame_buffer(GetDummyFrameBuffer()) + .set_timestamp_rtp(timestamp_ms) + .set_id(frame_id) + .build(); + MutexLock lock(&callback_mutex_); + RTC_DCHECK(delegate_callback_); + delegate_callback_->Decoded(dummy_frame, absl::nullopt, absl::nullopt); + return WEBRTC_VIDEO_CODEC_OK; +} + +rtc::scoped_refptr<webrtc::VideoFrameBuffer> +QualityAnalyzingVideoDecoder::DecoderCallback::GetDummyFrameBuffer() { + if (!dummy_frame_buffer_) { + dummy_frame_buffer_ = CreateDummyFrameBuffer(); + } + + return dummy_frame_buffer_; +} + +void QualityAnalyzingVideoDecoder::OnFrameDecoded( + VideoFrame* frame, + absl::optional<int32_t> decode_time_ms, + absl::optional<uint8_t> qp) { + absl::optional<uint16_t> frame_id; + std::string codec_name; + { + MutexLock lock(&mutex_); + auto it = timestamp_to_frame_id_.find(frame->timestamp()); + if (it == timestamp_to_frame_id_.end()) { + // Ensure, that we have info about this frame. It can happen that for some + // reasons decoder response, that it failed to decode, when we were + // posting frame to it, but then call the callback for this frame. + RTC_LOG(LS_ERROR) << "QualityAnalyzingVideoDecoder::OnFrameDecoded: No " + "frame id for frame for frame->timestamp()=" + << frame->timestamp(); + return; + } + frame_id = it->second; + timestamp_to_frame_id_.erase(it); + decoding_images_.erase(frame->timestamp()); + codec_name = codec_name_; + } + // Set frame id to the value, that was extracted from corresponding encoded + // image. + frame->set_id(frame_id.value_or(VideoFrame::kNotSetId)); + VideoQualityAnalyzerInterface::DecoderStats stats; + stats.decoder_name = codec_name; + stats.decode_time_ms = decode_time_ms; + analyzer_->OnFrameDecoded(peer_name_, *frame, stats); +} + +QualityAnalyzingVideoDecoderFactory::QualityAnalyzingVideoDecoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoDecoderFactory> delegate, + EncodedImageDataExtractor* extractor, + VideoQualityAnalyzerInterface* analyzer) + : peer_name_(peer_name), + delegate_(std::move(delegate)), + extractor_(extractor), + analyzer_(analyzer) {} +QualityAnalyzingVideoDecoderFactory::~QualityAnalyzingVideoDecoderFactory() = + default; + +std::vector<SdpVideoFormat> +QualityAnalyzingVideoDecoderFactory::GetSupportedFormats() const { + return delegate_->GetSupportedFormats(); +} + +std::unique_ptr<VideoDecoder> +QualityAnalyzingVideoDecoderFactory::CreateVideoDecoder( + const SdpVideoFormat& format) { + std::unique_ptr<VideoDecoder> decoder = delegate_->CreateVideoDecoder(format); + return std::make_unique<QualityAnalyzingVideoDecoder>( + peer_name_, std::move(decoder), extractor_, analyzer_); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h new file mode 100644 index 0000000000..a86f4196b0 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h @@ -0,0 +1,153 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_QUALITY_ANALYZING_VIDEO_DECODER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_QUALITY_ANALYZING_VIDEO_DECODER_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "api/video/encoded_image.h" +#include "api/video/video_frame.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_decoder.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/pc/e2e/analyzer/video/encoded_image_data_injector.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// QualityAnalyzingVideoDecoder is used to wrap origin video decoder and inject +// VideoQualityAnalyzerInterface before and after decoder. +// +// QualityAnalyzingVideoDecoder propagates all calls to the origin decoder. +// It registers its own DecodedImageCallback in the origin decoder and will +// store user specified callback inside itself. +// +// When Decode(...) will be invoked, quality decoder first will extract frame id +// from passed EncodedImage with EncodedImageIdExtracor that was specified in +// constructor, then will call video quality analyzer, with correct +// EncodedImage and only then will pass image to origin decoder. +// +// When origin decoder decodes the image it will call quality decoder's special +// callback, where video analyzer will be called again and then decoded frame +// will be passed to origin callback, provided by user. +// +// Quality decoder registers its own callback in origin decoder, at the same +// time the user registers their callback in quality decoder. +class QualityAnalyzingVideoDecoder : public VideoDecoder { + public: + QualityAnalyzingVideoDecoder(absl::string_view peer_name, + std::unique_ptr<VideoDecoder> delegate, + EncodedImageDataExtractor* extractor, + VideoQualityAnalyzerInterface* analyzer); + ~QualityAnalyzingVideoDecoder() override; + + // Methods of VideoDecoder interface. + bool Configure(const Settings& settings) override; + int32_t Decode(const EncodedImage& input_image, + bool missing_frames, + int64_t render_time_ms) override; + int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) override; + int32_t Release() override; + DecoderInfo GetDecoderInfo() const override; + const char* ImplementationName() const override; + + private: + class DecoderCallback : public DecodedImageCallback { + public: + explicit DecoderCallback(QualityAnalyzingVideoDecoder* decoder); + ~DecoderCallback() override; + + void SetDelegateCallback(DecodedImageCallback* delegate); + + // Methods of DecodedImageCallback interface. + int32_t Decoded(VideoFrame& decodedImage) override; + int32_t Decoded(VideoFrame& decodedImage, int64_t decode_time_ms) override; + void Decoded(VideoFrame& decodedImage, + absl::optional<int32_t> decode_time_ms, + absl::optional<uint8_t> qp) override; + + int32_t IrrelevantSimulcastStreamDecoded(uint16_t frame_id, + uint32_t timestamp_ms); + + private: + rtc::scoped_refptr<webrtc::VideoFrameBuffer> GetDummyFrameBuffer(); + + QualityAnalyzingVideoDecoder* const decoder_; + + rtc::scoped_refptr<webrtc::VideoFrameBuffer> dummy_frame_buffer_; + + Mutex callback_mutex_; + DecodedImageCallback* delegate_callback_ RTC_GUARDED_BY(callback_mutex_); + }; + + void OnFrameDecoded(VideoFrame* frame, + absl::optional<int32_t> decode_time_ms, + absl::optional<uint8_t> qp); + + const std::string peer_name_; + const std::string implementation_name_; + std::unique_ptr<VideoDecoder> delegate_; + EncodedImageDataExtractor* const extractor_; + VideoQualityAnalyzerInterface* const analyzer_; + std::unique_ptr<DecoderCallback> analyzing_callback_; + + // VideoDecoder interface assumes async delivery of decoded video frames. + // This lock is used to protect shared state, that have to be propagated + // from received EncodedImage to resulted VideoFrame. + Mutex mutex_; + + // Name of the video codec type used. Ex: VP8, VP9, H264 etc. + std::string codec_name_ RTC_GUARDED_BY(mutex_); + std::map<uint32_t, absl::optional<uint16_t>> timestamp_to_frame_id_ + RTC_GUARDED_BY(mutex_); + // Stores currently being decoded images by timestamp. Because + // EncodedImageDataExtractor can create new copy on EncodedImage we need to + // ensure, that this image won't be deleted during async decoding. To do it + // all images are putted into this map and removed from here inside callback. + std::map<uint32_t, EncodedImage> decoding_images_ RTC_GUARDED_BY(mutex_); +}; + +// Produces QualityAnalyzingVideoDecoder, which hold decoders, produced by +// specified factory as delegates. Forwards all other calls to specified +// factory. +class QualityAnalyzingVideoDecoderFactory : public VideoDecoderFactory { + public: + QualityAnalyzingVideoDecoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoDecoderFactory> delegate, + EncodedImageDataExtractor* extractor, + VideoQualityAnalyzerInterface* analyzer); + ~QualityAnalyzingVideoDecoderFactory() override; + + // Methods of VideoDecoderFactory interface. + std::vector<SdpVideoFormat> GetSupportedFormats() const override; + std::unique_ptr<VideoDecoder> CreateVideoDecoder( + const SdpVideoFormat& format) override; + + private: + const std::string peer_name_; + std::unique_ptr<VideoDecoderFactory> delegate_; + EncodedImageDataExtractor* const extractor_; + VideoQualityAnalyzerInterface* const analyzer_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_QUALITY_ANALYZING_VIDEO_DECODER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.cc new file mode 100644 index 0000000000..e814ba88b7 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.cc @@ -0,0 +1,403 @@ +/* + * 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/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h" + +#include <cmath> +#include <memory> +#include <utility> + +#include "absl/strings/string_view.h" +#include "api/video/video_codec_type.h" +#include "api/video_codecs/video_encoder.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "modules/video_coding/svc/scalability_mode_util.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using EmulatedSFUConfigMap = + ::webrtc::webrtc_pc_e2e::QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap; + +constexpr size_t kMaxFrameInPipelineCount = 1000; +constexpr double kNoMultiplier = 1.0; +constexpr double kEps = 1e-6; + +std::pair<uint32_t, uint32_t> GetMinMaxBitratesBps(const VideoCodec& codec, + size_t spatial_idx) { + uint32_t min_bitrate = codec.minBitrate; + uint32_t max_bitrate = codec.maxBitrate; + if (spatial_idx < codec.numberOfSimulcastStreams && + codec.codecType != VideoCodecType::kVideoCodecVP9) { + min_bitrate = + std::max(min_bitrate, codec.simulcastStream[spatial_idx].minBitrate); + max_bitrate = + std::min(max_bitrate, codec.simulcastStream[spatial_idx].maxBitrate); + } + if (codec.codecType == VideoCodecType::kVideoCodecVP9 && + spatial_idx < codec.VP9().numberOfSpatialLayers) { + min_bitrate = + std::max(min_bitrate, codec.spatialLayers[spatial_idx].minBitrate); + max_bitrate = + std::min(max_bitrate, codec.spatialLayers[spatial_idx].maxBitrate); + } + RTC_DCHECK_GT(max_bitrate, min_bitrate); + return {min_bitrate * 1000, max_bitrate * 1000}; +} + +} // namespace + +QualityAnalyzingVideoEncoder::QualityAnalyzingVideoEncoder( + absl::string_view peer_name, + std::unique_ptr<VideoEncoder> delegate, + double bitrate_multiplier, + EmulatedSFUConfigMap stream_to_sfu_config, + EncodedImageDataInjector* injector, + VideoQualityAnalyzerInterface* analyzer) + : peer_name_(peer_name), + delegate_(std::move(delegate)), + bitrate_multiplier_(bitrate_multiplier), + stream_to_sfu_config_(std::move(stream_to_sfu_config)), + injector_(injector), + analyzer_(analyzer), + mode_(SimulcastMode::kNormal), + delegate_callback_(nullptr) {} +QualityAnalyzingVideoEncoder::~QualityAnalyzingVideoEncoder() = default; + +void QualityAnalyzingVideoEncoder::SetFecControllerOverride( + FecControllerOverride* fec_controller_override) { + // Ignored. +} + +int32_t QualityAnalyzingVideoEncoder::InitEncode( + const VideoCodec* codec_settings, + const Settings& settings) { + MutexLock lock(&mutex_); + codec_settings_ = *codec_settings; + mode_ = SimulcastMode::kNormal; + absl::optional<InterLayerPredMode> inter_layer_pred_mode; + if (codec_settings->GetScalabilityMode().has_value()) { + inter_layer_pred_mode = ScalabilityModeToInterLayerPredMode( + *codec_settings->GetScalabilityMode()); + } else if (codec_settings->codecType == kVideoCodecVP9) { + if (codec_settings->VP9().numberOfSpatialLayers > 1) { + inter_layer_pred_mode = codec_settings->VP9().interLayerPred; + } + } + if (inter_layer_pred_mode.has_value()) { + switch (*inter_layer_pred_mode) { + case InterLayerPredMode::kOn: + mode_ = SimulcastMode::kSVC; + break; + case InterLayerPredMode::kOnKeyPic: + mode_ = SimulcastMode::kKSVC; + break; + case InterLayerPredMode::kOff: + mode_ = SimulcastMode::kSimulcast; + break; + default: + RTC_DCHECK_NOTREACHED() + << "Unknown InterLayerPredMode value " << *inter_layer_pred_mode; + break; + } + } + if (codec_settings->numberOfSimulcastStreams > 1) { + mode_ = SimulcastMode::kSimulcast; + } + return delegate_->InitEncode(codec_settings, settings); +} + +int32_t QualityAnalyzingVideoEncoder::RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) { + // We need to get a lock here because delegate_callback can be hypothetically + // accessed from different thread (encoder one) concurrently. + MutexLock lock(&mutex_); + delegate_callback_ = callback; + return delegate_->RegisterEncodeCompleteCallback(this); +} + +int32_t QualityAnalyzingVideoEncoder::Release() { + // Release encoder first. During release process it can still encode some + // frames, so we don't take a lock to prevent deadlock. + int32_t result = delegate_->Release(); + + MutexLock lock(&mutex_); + delegate_callback_ = nullptr; + return result; +} + +int32_t QualityAnalyzingVideoEncoder::Encode( + const VideoFrame& frame, + const std::vector<VideoFrameType>* frame_types) { + { + MutexLock lock(&mutex_); + // Store id to be able to retrieve it in analyzing callback. + timestamp_to_frame_id_list_.push_back({frame.timestamp(), frame.id()}); + // If this list is growing, it means that we are not receiving new encoded + // images from encoder. So it should be a bug in setup on in the encoder. + RTC_DCHECK_LT(timestamp_to_frame_id_list_.size(), kMaxFrameInPipelineCount); + } + analyzer_->OnFramePreEncode(peer_name_, frame); + int32_t result = delegate_->Encode(frame, frame_types); + if (result != WEBRTC_VIDEO_CODEC_OK) { + // If origin encoder failed, then cleanup data for this frame. + { + MutexLock lock(&mutex_); + // The timestamp-frame_id pair can be not the last one, so we need to + // find it first and then remove. We will search from the end, because + // usually it will be the last or close to the last one. + auto it = timestamp_to_frame_id_list_.end(); + while (it != timestamp_to_frame_id_list_.begin()) { + --it; + if (it->first == frame.timestamp()) { + timestamp_to_frame_id_list_.erase(it); + break; + } + } + } + analyzer_->OnEncoderError(peer_name_, frame, result); + } + return result; +} + +void QualityAnalyzingVideoEncoder::SetRates( + const VideoEncoder::RateControlParameters& parameters) { + RTC_DCHECK_GT(bitrate_multiplier_, 0.0); + if (fabs(bitrate_multiplier_ - kNoMultiplier) < kEps) { + { + MutexLock lock(&mutex_); + bitrate_allocation_ = parameters.bitrate; + } + return delegate_->SetRates(parameters); + } + + RateControlParameters adjusted_params = parameters; + { + MutexLock lock(&mutex_); + // Simulating encoder overshooting target bitrate, by configuring actual + // encoder too high. Take care not to adjust past limits of config, + // otherwise encoders may crash on DCHECK. + VideoBitrateAllocation multiplied_allocation; + for (size_t si = 0; si < kMaxSpatialLayers; ++si) { + const uint32_t spatial_layer_bitrate_bps = + parameters.bitrate.GetSpatialLayerSum(si); + if (spatial_layer_bitrate_bps == 0) { + continue; + } + + uint32_t min_bitrate_bps; + uint32_t max_bitrate_bps; + std::tie(min_bitrate_bps, max_bitrate_bps) = + GetMinMaxBitratesBps(codec_settings_, si); + double bitrate_multiplier = bitrate_multiplier_; + const uint32_t corrected_bitrate = rtc::checked_cast<uint32_t>( + bitrate_multiplier * spatial_layer_bitrate_bps); + if (corrected_bitrate < min_bitrate_bps) { + bitrate_multiplier = min_bitrate_bps / spatial_layer_bitrate_bps; + } else if (corrected_bitrate > max_bitrate_bps) { + bitrate_multiplier = max_bitrate_bps / spatial_layer_bitrate_bps; + } + + for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) { + if (parameters.bitrate.HasBitrate(si, ti)) { + multiplied_allocation.SetBitrate( + si, ti, + rtc::checked_cast<uint32_t>( + bitrate_multiplier * parameters.bitrate.GetBitrate(si, ti))); + } + } + } + + adjusted_params.bitrate = multiplied_allocation; + bitrate_allocation_ = adjusted_params.bitrate; + } + return delegate_->SetRates(adjusted_params); +} + +VideoEncoder::EncoderInfo QualityAnalyzingVideoEncoder::GetEncoderInfo() const { + return delegate_->GetEncoderInfo(); +} + +// It is assumed, that encoded callback will be always invoked with encoded +// images that correspond to the frames in the same sequence, that frames +// arrived. In other words, assume we have frames F1, F2 and F3 and they have +// corresponding encoded images I1, I2 and I3. In such case if we will call +// encode first with F1, then with F2 and then with F3, then encoder callback +// will be called first with all spatial layers for F1 (I1), then F2 (I2) and +// then F3 (I3). +// +// Basing on it we will use a list of timestamp-frame_id pairs like this: +// 1. If current encoded image timestamp is equals to timestamp in the front +// pair - pick frame id from that pair +// 2. If current encoded image timestamp isn't equals to timestamp in the front +// pair - remove the front pair and got to the step 1. +EncodedImageCallback::Result QualityAnalyzingVideoEncoder::OnEncodedImage( + const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info) { + uint16_t frame_id; + bool discard = false; + uint32_t target_encode_bitrate = 0; + std::string codec_name; + { + MutexLock lock(&mutex_); + std::pair<uint32_t, uint16_t> timestamp_frame_id; + while (!timestamp_to_frame_id_list_.empty()) { + timestamp_frame_id = timestamp_to_frame_id_list_.front(); + if (timestamp_frame_id.first == encoded_image.Timestamp()) { + break; + } + timestamp_to_frame_id_list_.pop_front(); + } + + // After the loop the first element should point to current `encoded_image` + // frame id. We don't remove it from the list, because there may be + // multiple spatial layers for this frame, so encoder can produce more + // encoded images with this timestamp. The first element will be removed + // when the next frame would be encoded and EncodedImageCallback would be + // called with the next timestamp. + + if (timestamp_to_frame_id_list_.empty()) { + // Ensure, that we have info about this frame. It can happen that for some + // reasons encoder response, that he failed to decode, when we were + // posting frame to it, but then call the callback for this frame. + RTC_LOG(LS_ERROR) << "QualityAnalyzingVideoEncoder::OnEncodedImage: No " + "frame id for encoded_image.Timestamp()=" + << encoded_image.Timestamp(); + return EncodedImageCallback::Result( + EncodedImageCallback::Result::Error::OK); + } + frame_id = timestamp_frame_id.second; + + discard = ShouldDiscard(frame_id, encoded_image); + if (!discard) { + target_encode_bitrate = bitrate_allocation_.GetSpatialLayerSum( + encoded_image.SpatialIndex().value_or(0)); + } + codec_name = + std::string(CodecTypeToPayloadString(codec_settings_.codecType)) + "_" + + delegate_->GetEncoderInfo().implementation_name; + } + + VideoQualityAnalyzerInterface::EncoderStats stats; + stats.encoder_name = codec_name; + stats.target_encode_bitrate = target_encode_bitrate; + stats.qp = encoded_image.qp_; + analyzer_->OnFrameEncoded(peer_name_, frame_id, encoded_image, stats, + discard); + + // Image data injector injects frame id and discard flag into provided + // EncodedImage and returns the image with a) modified original buffer (in + // such case the current owner of the buffer will be responsible for deleting + // it) or b) a new buffer (in such case injector will be responsible for + // deleting it). + const EncodedImage& image = + injector_->InjectData(frame_id, discard, encoded_image); + { + MutexLock lock(&mutex_); + RTC_DCHECK(delegate_callback_); + return delegate_callback_->OnEncodedImage(image, codec_specific_info); + } +} + +void QualityAnalyzingVideoEncoder::OnDroppedFrame( + EncodedImageCallback::DropReason reason) { + MutexLock lock(&mutex_); + analyzer_->OnFrameDropped(peer_name_, reason); + RTC_DCHECK(delegate_callback_); + delegate_callback_->OnDroppedFrame(reason); +} + +bool QualityAnalyzingVideoEncoder::ShouldDiscard( + uint16_t frame_id, + const EncodedImage& encoded_image) { + std::string stream_label = analyzer_->GetStreamLabel(frame_id); + EmulatedSFUConfigMap::mapped_type emulated_sfu_config = + stream_to_sfu_config_[stream_label]; + + if (!emulated_sfu_config) + return false; + + int cur_spatial_index = encoded_image.SpatialIndex().value_or(0); + int cur_temporal_index = encoded_image.TemporalIndex().value_or(0); + + if (emulated_sfu_config->target_temporal_index && + cur_temporal_index > *emulated_sfu_config->target_temporal_index) + return true; + + if (emulated_sfu_config->target_layer_index) { + switch (mode_) { + case SimulcastMode::kSimulcast: + // In simulcast mode only encoded images with required spatial index are + // interested, so all others have to be discarded. + return cur_spatial_index != *emulated_sfu_config->target_layer_index; + case SimulcastMode::kSVC: + // In SVC mode encoded images with spatial indexes that are equal or + // less than required one are interesting, so all above have to be + // discarded. + return cur_spatial_index > *emulated_sfu_config->target_layer_index; + case SimulcastMode::kKSVC: + // In KSVC mode for key frame encoded images with spatial indexes that + // are equal or less than required one are interesting, so all above + // have to be discarded. For other frames only required spatial index + // is interesting, so all others except the ones depending on the + // keyframes can be discarded. There's no good test for that, so we keep + // all of temporal layer 0 for now. + if (encoded_image._frameType == VideoFrameType::kVideoFrameKey || + cur_temporal_index == 0) + return cur_spatial_index > *emulated_sfu_config->target_layer_index; + return cur_spatial_index != *emulated_sfu_config->target_layer_index; + case SimulcastMode::kNormal: + RTC_DCHECK_NOTREACHED() << "Analyzing encoder is in kNormal mode, but " + "target_layer_index is set"; + } + } + return false; +} + +QualityAnalyzingVideoEncoderFactory::QualityAnalyzingVideoEncoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoEncoderFactory> delegate, + double bitrate_multiplier, + EmulatedSFUConfigMap stream_to_sfu_config, + EncodedImageDataInjector* injector, + VideoQualityAnalyzerInterface* analyzer) + : peer_name_(peer_name), + delegate_(std::move(delegate)), + bitrate_multiplier_(bitrate_multiplier), + stream_to_sfu_config_(std::move(stream_to_sfu_config)), + injector_(injector), + analyzer_(analyzer) {} +QualityAnalyzingVideoEncoderFactory::~QualityAnalyzingVideoEncoderFactory() = + default; + +std::vector<SdpVideoFormat> +QualityAnalyzingVideoEncoderFactory::GetSupportedFormats() const { + return delegate_->GetSupportedFormats(); +} + +VideoEncoderFactory::CodecSupport +QualityAnalyzingVideoEncoderFactory::QueryCodecSupport( + const SdpVideoFormat& format, + absl::optional<std::string> scalability_mode) const { + return delegate_->QueryCodecSupport(format, scalability_mode); +} + +std::unique_ptr<VideoEncoder> +QualityAnalyzingVideoEncoderFactory::CreateVideoEncoder( + const SdpVideoFormat& format) { + return std::make_unique<QualityAnalyzingVideoEncoder>( + peer_name_, delegate_->CreateVideoEncoder(format), bitrate_multiplier_, + stream_to_sfu_config_, injector_, analyzer_); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h new file mode 100644 index 0000000000..4adeacc0cd --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h @@ -0,0 +1,194 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_QUALITY_ANALYZING_VIDEO_ENCODER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_QUALITY_ANALYZING_VIDEO_ENCODER_H_ + +#include <list> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "api/video/video_frame.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_codec.h" +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/pc/e2e/analyzer/video/encoded_image_data_injector.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// QualityAnalyzingVideoEncoder is used to wrap origin video encoder and inject +// VideoQualityAnalyzerInterface before and after encoder. +// +// QualityAnalyzingVideoEncoder propagates all calls to the origin encoder. +// It registers its own EncodedImageCallback in the origin encoder and will +// store user specified callback inside itself. +// +// When Encode(...) will be invoked, quality encoder first calls video quality +// analyzer with original frame, then encodes frame with original encoder. +// +// When origin encoder encodes the image it will call quality encoder's special +// callback, where video analyzer will be called again and then frame id will be +// injected into EncodedImage with passed EncodedImageDataInjector. Then new +// EncodedImage will be passed to origin callback, provided by user. +// +// Quality encoder registers its own callback in origin encoder, at the same +// time the user registers their callback in quality encoder. +class QualityAnalyzingVideoEncoder : public VideoEncoder, + public EncodedImageCallback { + public: + using EmulatedSFUConfigMap = + std::map<std::string, absl::optional<EmulatedSFUConfig>>; + + QualityAnalyzingVideoEncoder(absl::string_view peer_name, + std::unique_ptr<VideoEncoder> delegate, + double bitrate_multiplier, + EmulatedSFUConfigMap stream_to_sfu_config, + EncodedImageDataInjector* injector, + VideoQualityAnalyzerInterface* analyzer); + ~QualityAnalyzingVideoEncoder() override; + + // Methods of VideoEncoder interface. + void SetFecControllerOverride( + FecControllerOverride* fec_controller_override) override; + int32_t InitEncode(const VideoCodec* codec_settings, + const Settings& settings) override; + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override; + int32_t Release() override; + int32_t Encode(const VideoFrame& frame, + const std::vector<VideoFrameType>* frame_types) override; + void SetRates(const VideoEncoder::RateControlParameters& parameters) override; + EncoderInfo GetEncoderInfo() const override; + + // Methods of EncodedImageCallback interface. + EncodedImageCallback::Result OnEncodedImage( + const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info) override; + void OnDroppedFrame(DropReason reason) override; + + private: + enum SimulcastMode { + // In this mode encoder assumes not more than 1 encoded image per video + // frame + kNormal, + + // Next modes are to test video conference behavior. For conference sender + // will send multiple spatial layers/simulcast streams for single video + // track and there is some Selective Forwarding Unit (SFU), that forwards + // only best one, that will pass through downlink to the receiver. + // + // Here this behavior will be partly emulated. Sender will send all spatial + // layers/simulcast streams and then some of them will be filtered out on + // the receiver side. During test setup user can specify which spatial + // layer/simulcast stream is required, what will simulated which spatial + // layer/simulcast stream will be chosen by SFU in the real world. Then + // sender will mark encoded images for all spatial layers above required or + // all simulcast streams except required as to be discarded and on receiver + // side they will be discarded in quality analyzing decoder and won't be + // passed into delegate decoder. + // + // If the sender for some reasons won't send specified spatial layer, then + // receiver still will fall back on lower spatial layers. But for simulcast + // streams if required one won't be sent, receiver will assume all frames + // in that period as dropped and will experience video freeze. + // + // Test based on this simulation will be used to evaluate video quality + // of concrete spatial layers/simulcast streams and also check distribution + // of bandwidth between spatial layers/simulcast streams by BWE. + + // In this mode encoder assumes that for each frame simulcast encoded + // images will be produced. So all simulcast streams except required will + // be marked as to be discarded in decoder and won't reach video quality + // analyzer. + kSimulcast, + // In this mode encoder assumes that for each frame encoded images for + // different spatial layers will be produced. So all spatial layers above + // required will be marked to be discarded in decoder and won't reach + // video quality analyzer. + kSVC, + // In this mode encoder assumes that for each frame encoded images for + // different spatial layers will be produced. Compared to kSVC mode + // spatial layers that are above required will be marked to be discarded + // only for key frames and for regular frames all except required spatial + // layer will be marked as to be discarded in decoder and won't reach video + // quality analyzer. + kKSVC + }; + + bool ShouldDiscard(uint16_t frame_id, const EncodedImage& encoded_image) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + const std::string peer_name_; + std::unique_ptr<VideoEncoder> delegate_; + const double bitrate_multiplier_; + // Contains mapping from stream label to optional spatial index. + // If we have stream label "Foo" and mapping contains + // 1. `absl::nullopt` means all streams are required + // 2. Concrete value means that particular simulcast/SVC stream have to be + // analyzed. + EmulatedSFUConfigMap stream_to_sfu_config_; + EncodedImageDataInjector* const injector_; + VideoQualityAnalyzerInterface* const analyzer_; + + // VideoEncoder interface assumes async delivery of encoded images. + // This lock is used to protect shared state, that have to be propagated + // from received VideoFrame to resulted EncodedImage. + Mutex mutex_; + + VideoCodec codec_settings_ RTC_GUARDED_BY(mutex_); + SimulcastMode mode_ RTC_GUARDED_BY(mutex_); + EncodedImageCallback* delegate_callback_ RTC_GUARDED_BY(mutex_); + std::list<std::pair<uint32_t, uint16_t>> timestamp_to_frame_id_list_ + RTC_GUARDED_BY(mutex_); + VideoBitrateAllocation bitrate_allocation_ RTC_GUARDED_BY(mutex_); +}; + +// Produces QualityAnalyzingVideoEncoder, which hold decoders, produced by +// specified factory as delegates. Forwards all other calls to specified +// factory. +class QualityAnalyzingVideoEncoderFactory : public VideoEncoderFactory { + public: + QualityAnalyzingVideoEncoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoEncoderFactory> delegate, + double bitrate_multiplier, + QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap stream_to_sfu_config, + EncodedImageDataInjector* injector, + VideoQualityAnalyzerInterface* analyzer); + ~QualityAnalyzingVideoEncoderFactory() override; + + // Methods of VideoEncoderFactory interface. + std::vector<SdpVideoFormat> GetSupportedFormats() const override; + VideoEncoderFactory::CodecSupport QueryCodecSupport( + const SdpVideoFormat& format, + absl::optional<std::string> scalability_mode) const override; + std::unique_ptr<VideoEncoder> CreateVideoEncoder( + const SdpVideoFormat& format) override; + + private: + const std::string peer_name_; + std::unique_ptr<VideoEncoderFactory> delegate_; + const double bitrate_multiplier_; + QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap stream_to_sfu_config_; + EncodedImageDataInjector* const injector_; + VideoQualityAnalyzerInterface* const analyzer_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_QUALITY_ANALYZING_VIDEO_ENCODER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.cc new file mode 100644 index 0000000000..7a73b9f4f1 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.cc @@ -0,0 +1,60 @@ +/* + * 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/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h" + +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +constexpr char kIrrelatedSimulcastStreamFrameData[] = "Dummy!"; + +} // namespace + +rtc::scoped_refptr<webrtc::VideoFrameBuffer> CreateDummyFrameBuffer() { + // Use i420 buffer here as default one and supported by all codecs. + rtc::scoped_refptr<webrtc::I420Buffer> buffer = + webrtc::I420Buffer::Create(2, 2); + memcpy(buffer->MutableDataY(), kIrrelatedSimulcastStreamFrameData, 2); + memcpy(buffer->MutableDataY() + buffer->StrideY(), + kIrrelatedSimulcastStreamFrameData + 2, 2); + memcpy(buffer->MutableDataU(), kIrrelatedSimulcastStreamFrameData + 4, 1); + memcpy(buffer->MutableDataV(), kIrrelatedSimulcastStreamFrameData + 5, 1); + return buffer; +} + +bool IsDummyFrame(const webrtc::VideoFrame& video_frame) { + if (video_frame.width() != 2 || video_frame.height() != 2) { + return false; + } + rtc::scoped_refptr<webrtc::I420BufferInterface> buffer = + video_frame.video_frame_buffer()->ToI420(); + if (memcmp(buffer->DataY(), kIrrelatedSimulcastStreamFrameData, 2) != 0) { + return false; + } + if (memcmp(buffer->DataY() + buffer->StrideY(), + kIrrelatedSimulcastStreamFrameData + 2, 2) != 0) { + return false; + } + if (memcmp(buffer->DataU(), kIrrelatedSimulcastStreamFrameData + 4, 1) != 0) { + return false; + } + if (memcmp(buffer->DataV(), kIrrelatedSimulcastStreamFrameData + 5, 1) != 0) { + return false; + } + return true; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h new file mode 100644 index 0000000000..8ecfae7385 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h @@ -0,0 +1,34 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_SIMULCAST_DUMMY_BUFFER_HELPER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_SIMULCAST_DUMMY_BUFFER_HELPER_H_ + +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Creates a special video frame buffer that should be used to create frames +// during Selective Forwarding Unit (SFU) emulation. Such frames are used when +// original was discarded and some frame is required to be passed upstream +// to make WebRTC pipeline happy and not request key frame on the received +// stream due to lack of incoming frames. +rtc::scoped_refptr<webrtc::VideoFrameBuffer> CreateDummyFrameBuffer(); + +// Tests if provided frame contains a buffer created by +// `CreateDummyFrameBuffer`. +bool IsDummyFrame(const webrtc::VideoFrame& video_frame); + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_SIMULCAST_DUMMY_BUFFER_HELPER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper_test.cc new file mode 100644 index 0000000000..db1030232d --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper_test.cc @@ -0,0 +1,61 @@ +/* + * 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/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h" + +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" +#include "rtc_base/random.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +uint8_t RandByte(Random& random) { + return random.Rand(255); +} + +VideoFrame CreateRandom2x2VideoFrame(uint16_t id, Random& random) { + rtc::scoped_refptr<I420Buffer> buffer = I420Buffer::Create(2, 2); + + uint8_t data[6] = {RandByte(random), RandByte(random), RandByte(random), + RandByte(random), RandByte(random), RandByte(random)}; + + memcpy(buffer->MutableDataY(), data, 2); + memcpy(buffer->MutableDataY() + buffer->StrideY(), data + 2, 2); + memcpy(buffer->MutableDataU(), data + 4, 1); + memcpy(buffer->MutableDataV(), data + 5, 1); + + return VideoFrame::Builder() + .set_id(id) + .set_video_frame_buffer(buffer) + .set_timestamp_us(1) + .build(); +} + +TEST(CreateDummyFrameBufferTest, CreatedBufferIsDummy) { + VideoFrame dummy_frame = VideoFrame::Builder() + .set_video_frame_buffer(CreateDummyFrameBuffer()) + .build(); + + EXPECT_TRUE(IsDummyFrame(dummy_frame)); +} + +TEST(IsDummyFrameTest, NotEveryFrameIsDummy) { + Random random(/*seed=*/100); + VideoFrame frame = CreateRandom2x2VideoFrame(1, random); + EXPECT_FALSE(IsDummyFrame(frame)); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc new file mode 100644 index 0000000000..ccd2f03537 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.cc @@ -0,0 +1,187 @@ +/* + * 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/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h" + +#include <algorithm> +#include <cstddef> + +#include "absl/memory/memory.h" +#include "api/video/encoded_image.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +SingleProcessEncodedImageDataInjector::SingleProcessEncodedImageDataInjector() = + default; +SingleProcessEncodedImageDataInjector:: + ~SingleProcessEncodedImageDataInjector() = default; + +EncodedImage SingleProcessEncodedImageDataInjector::InjectData( + uint16_t id, + bool discard, + const EncodedImage& source) { + RTC_CHECK(source.size() >= ExtractionInfo::kUsedBufferSize); + + ExtractionInfo info; + info.discard = discard; + size_t insertion_pos = source.size() - ExtractionInfo::kUsedBufferSize; + memcpy(info.origin_data, &source.data()[insertion_pos], + ExtractionInfo::kUsedBufferSize); + { + MutexLock lock(&lock_); + // Will create new one if missed. + ExtractionInfoVector& ev = extraction_cache_[id]; + info.sub_id = ev.next_sub_id++; + ev.infos[info.sub_id] = info; + } + + auto buffer = EncodedImageBuffer::Create(source.data(), source.size()); + buffer->data()[insertion_pos] = id & 0x00ff; + buffer->data()[insertion_pos + 1] = (id & 0xff00) >> 8; + buffer->data()[insertion_pos + 2] = info.sub_id; + + EncodedImage out = source; + out.SetEncodedData(buffer); + return out; +} + +void SingleProcessEncodedImageDataInjector::AddParticipantInCall() { + MutexLock crit(&lock_); + expected_receivers_count_++; +} + +void SingleProcessEncodedImageDataInjector::RemoveParticipantInCall() { + MutexLock crit(&lock_); + expected_receivers_count_--; + // Now we need go over `extraction_cache_` and removed frames which have been + // received by `expected_receivers_count_`. + for (auto& [frame_id, extraction_infos] : extraction_cache_) { + for (auto it = extraction_infos.infos.begin(); + it != extraction_infos.infos.end();) { + // Frame is received if `received_count` equals to + // `expected_receivers_count_`. + if (it->second.received_count == expected_receivers_count_) { + it = extraction_infos.infos.erase(it); + } else { + ++it; + } + } + } +} + +EncodedImageExtractionResult SingleProcessEncodedImageDataInjector::ExtractData( + const EncodedImage& source) { + size_t size = source.size(); + auto buffer = EncodedImageBuffer::Create(source.data(), source.size()); + EncodedImage out = source; + out.SetEncodedData(buffer); + + std::vector<size_t> frame_sizes; + std::vector<size_t> frame_sl_index; + size_t max_spatial_index = out.SpatialIndex().value_or(0); + for (size_t i = 0; i <= max_spatial_index; ++i) { + auto frame_size = source.SpatialLayerFrameSize(i); + if (frame_size.value_or(0)) { + frame_sl_index.push_back(i); + frame_sizes.push_back(frame_size.value()); + } + } + if (frame_sizes.empty()) { + frame_sizes.push_back(size); + } + + size_t prev_frames_size = 0; + absl::optional<uint16_t> id = absl::nullopt; + bool discard = true; + std::vector<ExtractionInfo> extraction_infos; + for (size_t frame_size : frame_sizes) { + size_t insertion_pos = + prev_frames_size + frame_size - ExtractionInfo::kUsedBufferSize; + // Extract frame id from first 2 bytes starting from insertion pos. + uint16_t next_id = buffer->data()[insertion_pos] + + (buffer->data()[insertion_pos + 1] << 8); + // Extract frame sub id from second 3 byte starting from insertion pos. + uint8_t sub_id = buffer->data()[insertion_pos + 2]; + RTC_CHECK(!id || *id == next_id) + << "Different frames encoded into single encoded image: " << *id + << " vs " << next_id; + id = next_id; + ExtractionInfo info; + { + MutexLock lock(&lock_); + auto ext_vector_it = extraction_cache_.find(next_id); + RTC_CHECK(ext_vector_it != extraction_cache_.end()) + << "Unknown frame_id=" << next_id; + + auto info_it = ext_vector_it->second.infos.find(sub_id); + RTC_CHECK(info_it != ext_vector_it->second.infos.end()) + << "Unknown sub_id=" << sub_id << " for frame_id=" << next_id; + info_it->second.received_count++; + info = info_it->second; + if (info.received_count == expected_receivers_count_) { + ext_vector_it->second.infos.erase(info_it); + } + } + // We need to discard encoded image only if all concatenated encoded images + // have to be discarded. + discard = discard && info.discard; + + extraction_infos.push_back(info); + prev_frames_size += frame_size; + } + RTC_CHECK(id); + + if (discard) { + out.set_size(0); + for (size_t i = 0; i <= max_spatial_index; ++i) { + out.SetSpatialLayerFrameSize(i, 0); + } + return EncodedImageExtractionResult{*id, out, true}; + } + + // Make a pass from begin to end to restore origin payload and erase discarded + // encoded images. + size_t pos = 0; + for (size_t frame_index = 0; frame_index < frame_sizes.size(); + ++frame_index) { + RTC_CHECK(pos < size); + const size_t frame_size = frame_sizes[frame_index]; + const ExtractionInfo& info = extraction_infos[frame_index]; + if (info.discard) { + // If this encoded image is marked to be discarded - erase it's payload + // from the buffer. + memmove(&buffer->data()[pos], &buffer->data()[pos + frame_size], + size - pos - frame_size); + RTC_CHECK_LT(frame_index, frame_sl_index.size()) + << "codec doesn't support discard option or the image, that was " + "supposed to be discarded, is lost"; + out.SetSpatialLayerFrameSize(frame_sl_index[frame_index], 0); + size -= frame_size; + } else { + memcpy( + &buffer->data()[pos + frame_size - ExtractionInfo::kUsedBufferSize], + info.origin_data, ExtractionInfo::kUsedBufferSize); + pos += frame_size; + } + } + out.set_size(pos); + + return EncodedImageExtractionResult{*id, out, discard}; +} + +SingleProcessEncodedImageDataInjector::ExtractionInfoVector:: + ExtractionInfoVector() = default; +SingleProcessEncodedImageDataInjector::ExtractionInfoVector:: + ~ExtractionInfoVector() = default; + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h new file mode 100644 index 0000000000..1082440e2f --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h @@ -0,0 +1,104 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_SINGLE_PROCESS_ENCODED_IMAGE_DATA_INJECTOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_SINGLE_PROCESS_ENCODED_IMAGE_DATA_INJECTOR_H_ + +#include <cstdint> +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "api/video/encoded_image.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/pc/e2e/analyzer/video/encoded_image_data_injector.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Based on assumption that all call participants are in the same OS process +// and uses same QualityAnalyzingVideoContext to obtain +// EncodedImageDataInjector. +// +// To inject frame id and discard flag into EncodedImage injector uses last 3rd +// and 2nd bytes of EncodedImage payload. Then it uses last byte for frame +// sub id, that is required to distinguish different spatial layers. The origin +// data from these 3 bytes will be stored inside injector's internal storage and +// then will be restored during extraction phase. +// +// This injector won't add any extra overhead into EncodedImage payload and +// support frames with any size of payload. Also assumes that every EncodedImage +// payload size is greater or equals to 3 bytes +// +// This injector doesn't support video frames/encoded images without frame ID. +class SingleProcessEncodedImageDataInjector + : public EncodedImageDataPropagator { + public: + SingleProcessEncodedImageDataInjector(); + ~SingleProcessEncodedImageDataInjector() override; + + // Id and discard flag will be injected into EncodedImage buffer directly. + // This buffer won't be fully copied, so `source` image buffer will be also + // changed. + EncodedImage InjectData(uint16_t id, + bool discard, + const EncodedImage& source) override; + + void Start(int expected_receivers_count) override { + MutexLock crit(&lock_); + expected_receivers_count_ = expected_receivers_count; + } + void AddParticipantInCall() override; + void RemoveParticipantInCall() override; + EncodedImageExtractionResult ExtractData(const EncodedImage& source) override; + + private: + // Contains data required to extract frame id from EncodedImage and restore + // original buffer. + struct ExtractionInfo { + // Number of bytes from the beginning of the EncodedImage buffer that will + // be used to store frame id and sub id. + const static size_t kUsedBufferSize = 3; + // Frame sub id to distinguish encoded images for different spatial layers. + uint8_t sub_id; + // Flag to show is this encoded images should be discarded by analyzing + // decoder because of not required spatial layer/simulcast stream. + bool discard; + // Data from first 3 bytes of origin encoded image's payload. + uint8_t origin_data[ExtractionInfo::kUsedBufferSize]; + // Count of how many times this frame was received. + int received_count = 0; + }; + + struct ExtractionInfoVector { + ExtractionInfoVector(); + ~ExtractionInfoVector(); + + // Next sub id, that have to be used for this frame id. + uint8_t next_sub_id = 0; + std::map<uint8_t, ExtractionInfo> infos; + }; + + Mutex lock_; + int expected_receivers_count_ RTC_GUARDED_BY(lock_); + // Stores a mapping from frame id to extraction info for spatial layers + // for this frame id. There can be a lot of them, because if frame was + // dropped we can't clean it up, because we won't receive a signal on + // decoder side about that frame. In such case it will be replaced + // when sub id will overlap. + std::map<uint16_t, ExtractionInfoVector> extraction_cache_ + RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_SINGLE_PROCESS_ENCODED_IMAGE_DATA_INJECTOR_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc new file mode 100644 index 0000000000..f6fa40455a --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector_unittest.cc @@ -0,0 +1,445 @@ +/* + * 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/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h" + +#include <utility> + +#include "api/video/encoded_image.h" +#include "rtc_base/buffer.h" +#include "test/gtest.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +rtc::scoped_refptr<EncodedImageBuffer> +CreateEncodedImageBufferOfSizeNFilledWithValuesFromX(size_t n, uint8_t x) { + auto buffer = EncodedImageBuffer::Create(n); + for (size_t i = 0; i < n; ++i) { + buffer->data()[i] = static_cast<uint8_t>(x + i); + } + return buffer; +} + +EncodedImage CreateEncodedImageOfSizeNFilledWithValuesFromX(size_t n, + uint8_t x) { + EncodedImage image; + image.SetEncodedData( + CreateEncodedImageBufferOfSizeNFilledWithValuesFromX(n, x)); + return image; +} + +EncodedImage DeepCopyEncodedImage(const EncodedImage& source) { + EncodedImage copy = source; + copy.SetEncodedData(EncodedImageBuffer::Create(source.data(), source.size())); + return copy; +} + +TEST(SingleProcessEncodedImageDataInjectorTest, InjectExtractDiscardFalse) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + + EncodedImageExtractionResult out = + injector.ExtractData(injector.InjectData(512, false, source)); + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, InjectExtractDiscardTrue) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + + EncodedImageExtractionResult out = + injector.ExtractData(injector.InjectData(512, true, source)); + EXPECT_EQ(out.id, 512); + EXPECT_TRUE(out.discard); + EXPECT_EQ(out.image.size(), 0ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); +} + +TEST(SingleProcessEncodedImageDataInjectorTest, + InjectWithUnsetSpatialLayerSizes) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + + EncodedImage intermediate = injector.InjectData(512, false, source); + intermediate.SetSpatialIndex(2); + + EncodedImageExtractionResult out = injector.ExtractData(intermediate); + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } + EXPECT_EQ(out.image.SpatialIndex().value_or(0), 2); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(out.image.SpatialLayerFrameSize(i).value_or(0), 0ul); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, + InjectWithZeroSpatialLayerSizes) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + + EncodedImage intermediate = injector.InjectData(512, false, source); + intermediate.SetSpatialIndex(2); + intermediate.SetSpatialLayerFrameSize(0, 0); + intermediate.SetSpatialLayerFrameSize(1, 0); + intermediate.SetSpatialLayerFrameSize(2, 0); + + EncodedImageExtractionResult out = injector.ExtractData(intermediate); + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } + EXPECT_EQ(out.image.SpatialIndex().value_or(0), 2); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(out.image.SpatialLayerFrameSize(i).value_or(0), 0ul); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, Inject3Extract3) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + // 1st frame + EncodedImage source1 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source1.SetTimestamp(123456710); + // 2nd frame 1st spatial layer + EncodedImage source2 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/11); + source2.SetTimestamp(123456720); + // 2nd frame 2nd spatial layer + EncodedImage source3 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/21); + source3.SetTimestamp(123456720); + + EncodedImage intermediate1 = injector.InjectData(510, false, source1); + EncodedImage intermediate2 = injector.InjectData(520, true, source2); + EncodedImage intermediate3 = injector.InjectData(520, false, source3); + + // Extract ids in different order. + EncodedImageExtractionResult out3 = injector.ExtractData(intermediate3); + EncodedImageExtractionResult out1 = injector.ExtractData(intermediate1); + EncodedImageExtractionResult out2 = injector.ExtractData(intermediate2); + + EXPECT_EQ(out1.id, 510); + EXPECT_FALSE(out1.discard); + EXPECT_EQ(out1.image.size(), 10ul); + EXPECT_EQ(out1.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out1.image.data()[i], i + 1); + } + EXPECT_EQ(out2.id, 520); + EXPECT_TRUE(out2.discard); + EXPECT_EQ(out2.image.size(), 0ul); + EXPECT_EQ(out2.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + EXPECT_EQ(out3.id, 520); + EXPECT_FALSE(out3.discard); + EXPECT_EQ(out3.image.size(), 10ul); + EXPECT_EQ(out3.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out3.image.data()[i], i + 21); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, InjectExtractFromConcatenated) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + EncodedImage source1 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source1.SetTimestamp(123456710); + EncodedImage source2 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/11); + source2.SetTimestamp(123456710); + EncodedImage source3 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/21); + source3.SetTimestamp(123456710); + + // Inject id into 3 images with same frame id. + EncodedImage intermediate1 = injector.InjectData(512, false, source1); + EncodedImage intermediate2 = injector.InjectData(512, true, source2); + EncodedImage intermediate3 = injector.InjectData(512, false, source3); + + // Concatenate them into single encoded image, like it can be done in jitter + // buffer. + size_t concatenated_length = + intermediate1.size() + intermediate2.size() + intermediate3.size(); + rtc::Buffer concatenated_buffer; + concatenated_buffer.AppendData(intermediate1.data(), intermediate1.size()); + concatenated_buffer.AppendData(intermediate2.data(), intermediate2.size()); + concatenated_buffer.AppendData(intermediate3.data(), intermediate3.size()); + EncodedImage concatenated; + concatenated.SetEncodedData(EncodedImageBuffer::Create( + concatenated_buffer.data(), concatenated_length)); + concatenated.SetSpatialIndex(2); + concatenated.SetSpatialLayerFrameSize(0, intermediate1.size()); + concatenated.SetSpatialLayerFrameSize(1, intermediate2.size()); + concatenated.SetSpatialLayerFrameSize(2, intermediate3.size()); + + // Extract frame id from concatenated image + EncodedImageExtractionResult out = injector.ExtractData(concatenated); + + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 2 * 10ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + EXPECT_EQ(out.image.data()[i + 10], i + 21); + } + EXPECT_EQ(out.image.SpatialIndex().value_or(0), 2); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(1).value_or(0), 0ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(2).value_or(0), 10ul); +} + +TEST(SingleProcessEncodedImageDataInjector, + InjectExtractFromConcatenatedAllDiscarded) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/1); + + EncodedImage source1 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source1.SetTimestamp(123456710); + EncodedImage source2 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/11); + source2.SetTimestamp(123456710); + EncodedImage source3 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/21); + source3.SetTimestamp(123456710); + + // Inject id into 3 images with same frame id. + EncodedImage intermediate1 = injector.InjectData(512, true, source1); + EncodedImage intermediate2 = injector.InjectData(512, true, source2); + EncodedImage intermediate3 = injector.InjectData(512, true, source3); + + // Concatenate them into single encoded image, like it can be done in jitter + // buffer. + size_t concatenated_length = + intermediate1.size() + intermediate2.size() + intermediate3.size(); + rtc::Buffer concatenated_buffer; + concatenated_buffer.AppendData(intermediate1.data(), intermediate1.size()); + concatenated_buffer.AppendData(intermediate2.data(), intermediate2.size()); + concatenated_buffer.AppendData(intermediate3.data(), intermediate3.size()); + EncodedImage concatenated; + concatenated.SetEncodedData(EncodedImageBuffer::Create( + concatenated_buffer.data(), concatenated_length)); + concatenated.SetSpatialIndex(2); + concatenated.SetSpatialLayerFrameSize(0, intermediate1.size()); + concatenated.SetSpatialLayerFrameSize(1, intermediate2.size()); + concatenated.SetSpatialLayerFrameSize(2, intermediate3.size()); + + // Extract frame id from concatenated image + EncodedImageExtractionResult out = injector.ExtractData(concatenated); + + EXPECT_EQ(out.id, 512); + EXPECT_TRUE(out.discard); + EXPECT_EQ(out.image.size(), 0ul); + EXPECT_EQ(out.image.SpatialIndex().value_or(0), 2); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ(out.image.SpatialLayerFrameSize(i).value_or(0), 0ul); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, InjectOnceExtractTwice) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/2); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + + EncodedImageExtractionResult out = injector.ExtractData( + injector.InjectData(/*id=*/512, /*discard=*/false, source)); + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } + out = injector.ExtractData( + injector.InjectData(/*id=*/512, /*discard=*/false, source)); + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, Add1stReceiverAfterStart) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/0); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + EncodedImage modified_image = injector.InjectData( + /*id=*/512, /*discard=*/false, source); + + injector.AddParticipantInCall(); + EncodedImageExtractionResult out = injector.ExtractData(modified_image); + + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, Add3rdReceiverAfterStart) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/2); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + EncodedImage modified_image = injector.InjectData( + /*id=*/512, /*discard=*/false, source); + injector.ExtractData(modified_image); + + injector.AddParticipantInCall(); + injector.ExtractData(modified_image); + EncodedImageExtractionResult out = injector.ExtractData(modified_image); + + EXPECT_EQ(out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } +} + +TEST(SingleProcessEncodedImageDataInjectorTest, + RemoveReceiverRemovesOnlyFullyReceivedFrames) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/3); + + EncodedImage source1 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source1.SetTimestamp(10); + EncodedImage source2 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source2.SetTimestamp(20); + + EncodedImage modified_image1 = injector.InjectData( + /*id=*/512, /*discard=*/false, source1); + EncodedImage modified_image2 = injector.InjectData( + /*id=*/513, /*discard=*/false, source2); + + // Out of 3 receivers 1st image received by 2 and 2nd image by 1 + injector.ExtractData(DeepCopyEncodedImage(modified_image1)); + injector.ExtractData(DeepCopyEncodedImage(modified_image1)); + injector.ExtractData(DeepCopyEncodedImage(modified_image2)); + + // When we removed one receiver, 2nd image should still be available for + // extraction. + injector.RemoveParticipantInCall(); + + EncodedImageExtractionResult out = + injector.ExtractData(DeepCopyEncodedImage(modified_image2)); + + EXPECT_EQ(out.id, 513); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + EXPECT_EQ(out.image.SpatialLayerFrameSize(0).value_or(0), 0ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(out.image.data()[i], i + 1); + } +} + +// Death tests. +// Disabled on Android because death tests misbehave on Android, see +// base/test/gtest_util.h. +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(SingleProcessEncodedImageDataInjectorTestDeathTest, + InjectOnceExtractMoreThenExpected) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/2); + + EncodedImage source = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source.SetTimestamp(123456789); + + EncodedImage modified = + injector.InjectData(/*id=*/512, /*discard=*/false, source); + + injector.ExtractData(DeepCopyEncodedImage(modified)); + injector.ExtractData(DeepCopyEncodedImage(modified)); + EXPECT_DEATH(injector.ExtractData(DeepCopyEncodedImage(modified)), + "Unknown sub_id=0 for frame_id=512"); +} + +TEST(SingleProcessEncodedImageDataInjectorTestDeathTest, + RemoveReceiverRemovesOnlyFullyReceivedFramesVerifyFrameIsRemoved) { + SingleProcessEncodedImageDataInjector injector; + injector.Start(/*expected_receivers_count=*/3); + + EncodedImage source1 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source1.SetTimestamp(10); + EncodedImage source2 = + CreateEncodedImageOfSizeNFilledWithValuesFromX(/*n=*/10, /*x=*/1); + source2.SetTimestamp(20); + + EncodedImage modified_image1 = injector.InjectData( + /*id=*/512, /*discard=*/false, source1); + EncodedImage modified_image2 = injector.InjectData( + /*id=*/513, /*discard=*/false, source2); + + // Out of 3 receivers 1st image received by 2 and 2nd image by 1 + injector.ExtractData(DeepCopyEncodedImage(modified_image1)); + injector.ExtractData(DeepCopyEncodedImage(modified_image1)); + injector.ExtractData(DeepCopyEncodedImage(modified_image2)); + + // When we removed one receiver 1st image should be removed. + injector.RemoveParticipantInCall(); + + EXPECT_DEATH(injector.ExtractData(DeepCopyEncodedImage(modified_image1)), + "Unknown sub_id=0 for frame_id=512"); +} +#endif // RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping.cc new file mode 100644 index 0000000000..4fec0a8f9e --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping.cc @@ -0,0 +1,118 @@ +/* + * 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/pc/e2e/analyzer/video/video_dumping.h" + +#include <stdio.h> + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/test/video/video_frame_writer.h" +#include "api/video/video_frame.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/clock.h" +#include "test/testsupport/video_frame_writer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +class VideoFrameIdsWriter final : public test::VideoFrameWriter { + public: + explicit VideoFrameIdsWriter(absl::string_view file_name) + : file_name_(file_name) { + output_file_ = fopen(file_name_.c_str(), "wb"); + RTC_LOG(LS_INFO) << "Writing VideoFrame IDs into " << file_name_; + RTC_CHECK(output_file_ != nullptr) + << "Failed to open file to dump frame ids for writing: " << file_name_; + } + ~VideoFrameIdsWriter() override { Close(); } + + bool WriteFrame(const VideoFrame& frame) override { + RTC_CHECK(output_file_ != nullptr) << "Writer is already closed"; + int chars_written = fprintf(output_file_, "%d\n", frame.id()); + if (chars_written < 2) { + RTC_LOG(LS_ERROR) << "Failed to write frame id to the output file: " + << file_name_; + return false; + } + return true; + } + + void Close() override { + if (output_file_ != nullptr) { + RTC_LOG(LS_INFO) << "Closing file for VideoFrame IDs: " << file_name_; + fclose(output_file_); + output_file_ = nullptr; + } + } + + private: + const std::string file_name_; + FILE* output_file_; +}; + +// Broadcast received frame to multiple underlying frame writers. +class BroadcastingFrameWriter final : public test::VideoFrameWriter { + public: + explicit BroadcastingFrameWriter( + std::vector<std::unique_ptr<test::VideoFrameWriter>> delegates) + : delegates_(std::move(delegates)) {} + ~BroadcastingFrameWriter() override { Close(); } + + bool WriteFrame(const webrtc::VideoFrame& frame) override { + for (auto& delegate : delegates_) { + if (!delegate->WriteFrame(frame)) { + return false; + } + } + return true; + } + + void Close() override { + for (auto& delegate : delegates_) { + delegate->Close(); + } + } + + private: + std::vector<std::unique_ptr<test::VideoFrameWriter>> delegates_; +}; + +} // namespace + +VideoWriter::VideoWriter(test::VideoFrameWriter* video_writer, + int sampling_modulo) + : video_writer_(video_writer), sampling_modulo_(sampling_modulo) {} + +void VideoWriter::OnFrame(const VideoFrame& frame) { + if (frames_counter_++ % sampling_modulo_ != 0) { + return; + } + bool result = video_writer_->WriteFrame(frame); + RTC_CHECK(result) << "Failed to write frame"; +} + +std::unique_ptr<test::VideoFrameWriter> CreateVideoFrameWithIdsWriter( + std::unique_ptr<test::VideoFrameWriter> video_writer_delegate, + absl::string_view frame_ids_dump_file_name) { + std::vector<std::unique_ptr<test::VideoFrameWriter>> requested_writers; + requested_writers.push_back(std::move(video_writer_delegate)); + requested_writers.push_back( + std::make_unique<VideoFrameIdsWriter>(frame_ids_dump_file_name)); + return std::make_unique<BroadcastingFrameWriter>( + std::move(requested_writers)); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping.h new file mode 100644 index 0000000000..cad4e1bdbf --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping.h @@ -0,0 +1,56 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_VIDEO_DUMPING_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_DUMPING_H_ + +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/test/video/video_frame_writer.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "test/testsupport/video_frame_writer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// `VideoSinkInterface` to dump incoming video frames into specified video +// writer. +class VideoWriter final : public rtc::VideoSinkInterface<VideoFrame> { + public: + // Creates video writer. Caller keeps ownership of `video_writer` and is + // responsible for closing it after VideoWriter will be destroyed. + VideoWriter(test::VideoFrameWriter* video_writer, int sampling_modulo); + VideoWriter(const VideoWriter&) = delete; + VideoWriter& operator=(const VideoWriter&) = delete; + ~VideoWriter() override = default; + + void OnFrame(const VideoFrame& frame) override; + + private: + test::VideoFrameWriter* const video_writer_; + const int sampling_modulo_; + + int64_t frames_counter_ = 0; +}; + +// Creates a `VideoFrameWriter` to dump video frames together with their ids. +// It uses provided `video_writer_delegate` to write video itself. Frame ids +// will be logged into the specified file. +std::unique_ptr<test::VideoFrameWriter> CreateVideoFrameWithIdsWriter( + std::unique_ptr<test::VideoFrameWriter> video_writer_delegate, + absl::string_view frame_ids_dump_file_name); + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_DUMPING_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping_test.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping_test.cc new file mode 100644 index 0000000000..5dd4021516 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_dumping_test.cc @@ -0,0 +1,196 @@ +/* + * 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/pc/e2e/analyzer/video/video_dumping.h" + +#include <stdio.h> + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" +#include "rtc_base/random.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" +#include "test/testsupport/video_frame_writer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Test; + +uint8_t RandByte(Random& random) { + return random.Rand(255); +} + +VideoFrame CreateRandom2x2VideoFrame(uint16_t id, Random& random) { + rtc::scoped_refptr<I420Buffer> buffer = I420Buffer::Create(2, 2); + + uint8_t data[6] = {RandByte(random), RandByte(random), RandByte(random), + RandByte(random), RandByte(random), RandByte(random)}; + + memcpy(buffer->MutableDataY(), data, 2); + memcpy(buffer->MutableDataY() + buffer->StrideY(), data + 2, 2); + memcpy(buffer->MutableDataU(), data + 4, 1); + memcpy(buffer->MutableDataV(), data + 5, 1); + + return VideoFrame::Builder() + .set_id(id) + .set_video_frame_buffer(buffer) + .set_timestamp_us(1) + .build(); +} + +std::vector<uint8_t> AsVector(const uint8_t* data, size_t size) { + std::vector<uint8_t> out; + out.assign(data, data + size); + return out; +} + +void AssertFramesEqual(rtc::scoped_refptr<webrtc::I420BufferInterface> actual, + rtc::scoped_refptr<VideoFrameBuffer> expected) { + ASSERT_THAT(actual->width(), Eq(expected->width())); + ASSERT_THAT(actual->height(), Eq(expected->height())); + rtc::scoped_refptr<webrtc::I420BufferInterface> expected_i420 = + expected->ToI420(); + + int height = actual->height(); + + EXPECT_THAT(AsVector(actual->DataY(), actual->StrideY() * height), + ElementsAreArray(expected_i420->DataY(), + expected_i420->StrideY() * height)); + EXPECT_THAT(AsVector(actual->DataU(), actual->StrideU() * (height + 1) / 2), + ElementsAreArray(expected_i420->DataU(), + expected_i420->StrideU() * (height + 1) / 2)); + EXPECT_THAT(AsVector(actual->DataV(), actual->StrideV() * (height + 1) / 2), + ElementsAreArray(expected_i420->DataV(), + expected_i420->StrideV() * (height + 1) / 2)); +} + +void AssertFrameIdsAre(const std::string& filename, + std::vector<std::string> expected_ids) { + FILE* file = fopen(filename.c_str(), "r"); + ASSERT_TRUE(file != nullptr); + std::vector<std::string> actual_ids; + char buffer[8]; + while (fgets(buffer, sizeof buffer, file) != nullptr) { + std::string current_id(buffer); + ASSERT_GE(current_id.size(), 2lu); + // Trim "\n" at the end. + actual_ids.push_back(current_id.substr(0, current_id.size() - 1)); + } + EXPECT_THAT(actual_ids, ElementsAreArray(expected_ids)); +} + +class VideoDumpingTest : public Test { + protected: + ~VideoDumpingTest() override = default; + + void SetUp() override { + video_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "video_dumping_test"); + ids_filename_ = webrtc::test::TempFilename(webrtc::test::OutputPath(), + "video_dumping_test"); + } + + void TearDown() override { + remove(video_filename_.c_str()); + remove(ids_filename_.c_str()); + } + + std::string video_filename_; + std::string ids_filename_; +}; + +using CreateVideoFrameWithIdsWriterTest = VideoDumpingTest; + +TEST_F(CreateVideoFrameWithIdsWriterTest, VideoIsWritenWithFrameIds) { + Random random(/*seed=*/100); + VideoFrame frame1 = CreateRandom2x2VideoFrame(1, random); + VideoFrame frame2 = CreateRandom2x2VideoFrame(2, random); + + std::unique_ptr<test::VideoFrameWriter> writer = + CreateVideoFrameWithIdsWriter( + std::make_unique<test::Y4mVideoFrameWriterImpl>( + std::string(video_filename_), + /*width=*/2, /*height=*/2, /*fps=*/2), + ids_filename_); + + ASSERT_TRUE(writer->WriteFrame(frame1)); + ASSERT_TRUE(writer->WriteFrame(frame2)); + writer->Close(); + + auto frame_reader = test::CreateY4mFrameReader(video_filename_); + EXPECT_THAT(frame_reader->num_frames(), Eq(2)); + AssertFramesEqual(frame_reader->PullFrame(), frame1.video_frame_buffer()); + AssertFramesEqual(frame_reader->PullFrame(), frame2.video_frame_buffer()); + AssertFrameIdsAre(ids_filename_, {"1", "2"}); +} + +using VideoWriterTest = VideoDumpingTest; + +TEST_F(VideoWriterTest, AllFramesAreWrittenWithSamplingModulo1) { + Random random(/*seed=*/100); + VideoFrame frame1 = CreateRandom2x2VideoFrame(1, random); + VideoFrame frame2 = CreateRandom2x2VideoFrame(2, random); + + { + test::Y4mVideoFrameWriterImpl frame_writer(std::string(video_filename_), + /*width=*/2, /*height=*/2, + /*fps=*/2); + VideoWriter writer(&frame_writer, /*sampling_modulo=*/1); + + writer.OnFrame(frame1); + writer.OnFrame(frame2); + frame_writer.Close(); + } + + auto frame_reader = test::CreateY4mFrameReader(video_filename_); + EXPECT_THAT(frame_reader->num_frames(), Eq(2)); + AssertFramesEqual(frame_reader->PullFrame(), frame1.video_frame_buffer()); + AssertFramesEqual(frame_reader->PullFrame(), frame2.video_frame_buffer()); +} + +TEST_F(VideoWriterTest, OnlyEvery2ndFramesIsWrittenWithSamplingModulo2) { + Random random(/*seed=*/100); + VideoFrame frame1 = CreateRandom2x2VideoFrame(1, random); + VideoFrame frame2 = CreateRandom2x2VideoFrame(2, random); + VideoFrame frame3 = CreateRandom2x2VideoFrame(3, random); + + { + test::Y4mVideoFrameWriterImpl frame_writer(std::string(video_filename_), + /*width=*/2, /*height=*/2, + /*fps=*/2); + VideoWriter writer(&frame_writer, /*sampling_modulo=*/2); + + writer.OnFrame(frame1); + writer.OnFrame(frame2); + writer.OnFrame(frame3); + frame_writer.Close(); + } + + auto frame_reader = test::CreateY4mFrameReader(video_filename_); + EXPECT_THAT(frame_reader->num_frames(), Eq(2)); + AssertFramesEqual(frame_reader->PullFrame(), frame1.video_frame_buffer()); + AssertFramesEqual(frame_reader->PullFrame(), frame3.video_frame_buffer()); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.cc new file mode 100644 index 0000000000..5a74d60250 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.cc @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/video_frame_tracking_id_injector.h" + +#include "absl/memory/memory.h" +#include "api/video/encoded_image.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +EncodedImage VideoFrameTrackingIdInjector::InjectData( + uint16_t id, + bool unused_discard, + const EncodedImage& source) { + RTC_CHECK(!unused_discard); + EncodedImage out = source; + out.SetVideoFrameTrackingId(id); + return out; +} + +EncodedImageExtractionResult VideoFrameTrackingIdInjector::ExtractData( + const EncodedImage& source) { + return EncodedImageExtractionResult{source.VideoFrameTrackingId(), source, + /*discard=*/false}; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.h new file mode 100644 index 0000000000..ecc3cd3f51 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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_PC_E2E_ANALYZER_VIDEO_VIDEO_FRAME_TRACKING_ID_INJECTOR_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_FRAME_TRACKING_ID_INJECTOR_H_ + +#include <cstdint> + +#include "api/video/encoded_image.h" +#include "test/pc/e2e/analyzer/video/encoded_image_data_injector.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// This injector sets and retrieves the provided id in the EncodedImage +// video_frame_tracking_id field. This is only possible with the RTP header +// extension VideoFrameTrackingIdExtension that will propagate the input +// tracking id to the received EncodedImage. This RTP header extension is +// enabled with the field trial WebRTC-VideoFrameTrackingIdAdvertised +// (http://www.webrtc.org/experiments/rtp-hdrext/video-frame-tracking-id). +// +// Note that this injector doesn't allow to discard frames. +class VideoFrameTrackingIdInjector : public EncodedImageDataPropagator { + public: + EncodedImage InjectData(uint16_t id, + bool unused_discard, + const EncodedImage& source) override; + + EncodedImageExtractionResult ExtractData(const EncodedImage& source) override; + + void Start(int) override {} + void AddParticipantInCall() override {} + void RemoveParticipantInCall() override {} +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_FRAME_TRACKING_ID_INJECTOR_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector_unittest.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector_unittest.cc new file mode 100644 index 0000000000..c7d453c4bb --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_frame_tracking_id_injector_unittest.cc @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 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/pc/e2e/analyzer/video/video_frame_tracking_id_injector.h" + +#include "api/video/encoded_image.h" +#include "rtc_base/buffer.h" +#include "test/gtest.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +EncodedImage CreateEncodedImageOfSizeN(size_t n) { + EncodedImage image; + rtc::scoped_refptr<EncodedImageBuffer> buffer = EncodedImageBuffer::Create(n); + for (size_t i = 0; i < n; ++i) { + buffer->data()[i] = static_cast<uint8_t>(i); + } + image.SetEncodedData(buffer); + return image; +} + +TEST(VideoFrameTrackingIdInjectorTest, InjectExtractDiscardFalse) { + VideoFrameTrackingIdInjector injector; + EncodedImage source = CreateEncodedImageOfSizeN(10); + EncodedImageExtractionResult out = + injector.ExtractData(injector.InjectData(512, false, source)); + + ASSERT_TRUE(out.id.has_value()); + EXPECT_EQ(*out.id, 512); + EXPECT_FALSE(out.discard); + EXPECT_EQ(out.image.size(), 10ul); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(source.data()[i], out.image.data()[i]); + } +} + +#if GTEST_HAS_DEATH_TEST +TEST(VideoFrameTrackingIdInjectorTest, InjectExtractDiscardTrue) { + VideoFrameTrackingIdInjector injector; + EncodedImage source = CreateEncodedImageOfSizeN(10); + + EXPECT_DEATH(injector.InjectData(512, true, source), ""); +} +#endif // GTEST_HAS_DEATH_TEST + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc new file mode 100644 index 0000000000..87c11886cc --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc @@ -0,0 +1,264 @@ +/* + * 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/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h" + +#include <stdio.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/test/pclf/media_configuration.h" +#include "api/video/i420_buffer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/strings/string_builder.h" +#include "system_wrappers/include/clock.h" +#include "test/pc/e2e/analyzer/video/analyzing_video_sink.h" +#include "test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h" +#include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h" +#include "test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h" +#include "test/pc/e2e/analyzer/video/video_dumping.h" +#include "test/testsupport/fixed_fps_video_frame_writer_adapter.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using webrtc::webrtc_pc_e2e::VideoConfig; +using EmulatedSFUConfigMap = + ::webrtc::webrtc_pc_e2e::QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap; + +class AnalyzingFramePreprocessor + : public test::TestVideoCapturer::FramePreprocessor { + public: + AnalyzingFramePreprocessor( + absl::string_view peer_name, + absl::string_view stream_label, + VideoQualityAnalyzerInterface* analyzer, + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks) + : peer_name_(peer_name), + stream_label_(stream_label), + analyzer_(analyzer), + sinks_(std::move(sinks)) {} + ~AnalyzingFramePreprocessor() override = default; + + VideoFrame Preprocess(const VideoFrame& source_frame) override { + // Copy VideoFrame to be able to set id on it. + VideoFrame frame = source_frame; + uint16_t frame_id = + analyzer_->OnFrameCaptured(peer_name_, stream_label_, frame); + frame.set_id(frame_id); + + for (auto& sink : sinks_) { + sink->OnFrame(frame); + } + return frame; + } + + private: + const std::string peer_name_; + const std::string stream_label_; + VideoQualityAnalyzerInterface* const analyzer_; + const std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> + sinks_; +}; + +} // namespace + +VideoQualityAnalyzerInjectionHelper::VideoQualityAnalyzerInjectionHelper( + Clock* clock, + std::unique_ptr<VideoQualityAnalyzerInterface> analyzer, + EncodedImageDataInjector* injector, + EncodedImageDataExtractor* extractor) + : clock_(clock), + analyzer_(std::move(analyzer)), + injector_(injector), + extractor_(extractor) { + RTC_DCHECK(clock_); + RTC_DCHECK(injector_); + RTC_DCHECK(extractor_); +} +VideoQualityAnalyzerInjectionHelper::~VideoQualityAnalyzerInjectionHelper() = + default; + +std::unique_ptr<VideoEncoderFactory> +VideoQualityAnalyzerInjectionHelper::WrapVideoEncoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoEncoderFactory> delegate, + double bitrate_multiplier, + EmulatedSFUConfigMap stream_to_sfu_config) const { + return std::make_unique<QualityAnalyzingVideoEncoderFactory>( + peer_name, std::move(delegate), bitrate_multiplier, + std::move(stream_to_sfu_config), injector_, analyzer_.get()); +} + +std::unique_ptr<VideoDecoderFactory> +VideoQualityAnalyzerInjectionHelper::WrapVideoDecoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoDecoderFactory> delegate) const { + return std::make_unique<QualityAnalyzingVideoDecoderFactory>( + peer_name, std::move(delegate), extractor_, analyzer_.get()); +} + +std::unique_ptr<test::TestVideoCapturer::FramePreprocessor> +VideoQualityAnalyzerInjectionHelper::CreateFramePreprocessor( + absl::string_view peer_name, + const VideoConfig& config) { + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks; + if (config.input_dump_options.has_value()) { + std::unique_ptr<test::VideoFrameWriter> writer = + config.input_dump_options->CreateInputDumpVideoFrameWriter( + *config.stream_label, config.GetResolution()); + sinks.push_back(std::make_unique<VideoWriter>( + writer.get(), config.input_dump_options->sampling_modulo())); + video_writers_.push_back(std::move(writer)); + } + if (config.show_on_screen) { + sinks.push_back(absl::WrapUnique( + test::VideoRenderer::Create((*config.stream_label + "-capture").c_str(), + config.width, config.height))); + } + sinks_helper_.AddConfig(peer_name, config); + { + MutexLock lock(&mutex_); + known_video_configs_.insert({*config.stream_label, config}); + } + return std::make_unique<AnalyzingFramePreprocessor>( + peer_name, std::move(*config.stream_label), analyzer_.get(), + std::move(sinks)); +} + +std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> +VideoQualityAnalyzerInjectionHelper::CreateVideoSink( + absl::string_view peer_name) { + return std::make_unique<AnalyzingVideoSink2>(peer_name, this); +} + +std::unique_ptr<AnalyzingVideoSink> +VideoQualityAnalyzerInjectionHelper::CreateVideoSink( + absl::string_view peer_name, + const VideoSubscription& subscription, + bool report_infra_metrics) { + return std::make_unique<AnalyzingVideoSink>(peer_name, clock_, *analyzer_, + sinks_helper_, subscription, + report_infra_metrics); +} + +void VideoQualityAnalyzerInjectionHelper::Start( + std::string test_case_name, + rtc::ArrayView<const std::string> peer_names, + int max_threads_count) { + analyzer_->Start(std::move(test_case_name), peer_names, max_threads_count); + extractor_->Start(peer_names.size()); + MutexLock lock(&mutex_); + peers_count_ = peer_names.size(); +} + +void VideoQualityAnalyzerInjectionHelper::RegisterParticipantInCall( + absl::string_view peer_name) { + analyzer_->RegisterParticipantInCall(peer_name); + extractor_->AddParticipantInCall(); + MutexLock lock(&mutex_); + peers_count_++; +} + +void VideoQualityAnalyzerInjectionHelper::UnregisterParticipantInCall( + absl::string_view peer_name) { + analyzer_->UnregisterParticipantInCall(peer_name); + extractor_->RemoveParticipantInCall(); + MutexLock lock(&mutex_); + peers_count_--; +} + +void VideoQualityAnalyzerInjectionHelper::OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) { + analyzer_->OnStatsReports(pc_label, report); +} + +void VideoQualityAnalyzerInjectionHelper::Stop() { + analyzer_->Stop(); + for (const auto& video_writer : video_writers_) { + video_writer->Close(); + } + video_writers_.clear(); + sinks_helper_.Clear(); +} + +void VideoQualityAnalyzerInjectionHelper::OnFrame(absl::string_view peer_name, + const VideoFrame& frame) { + if (IsDummyFrame(frame)) { + // This is dummy frame, so we don't need to process it further. + return; + } + // Copy entire video frame including video buffer to ensure that analyzer + // won't hold any WebRTC internal buffers. + VideoFrame frame_copy = frame; + frame_copy.set_video_frame_buffer( + I420Buffer::Copy(*frame.video_frame_buffer()->ToI420())); + analyzer_->OnFrameRendered(peer_name, frame_copy); + + if (frame.id() != VideoFrame::kNotSetId) { + std::string stream_label = analyzer_->GetStreamLabel(frame.id()); + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>* sinks = + PopulateSinks(ReceiverStream(peer_name, stream_label)); + if (sinks == nullptr) { + return; + } + for (auto& sink : *sinks) { + sink->OnFrame(frame); + } + } +} + +std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>* +VideoQualityAnalyzerInjectionHelper::PopulateSinks( + const ReceiverStream& receiver_stream) { + MutexLock lock(&mutex_); + auto sinks_it = sinks_.find(receiver_stream); + if (sinks_it != sinks_.end()) { + return &sinks_it->second; + } + auto it = known_video_configs_.find(receiver_stream.stream_label); + RTC_DCHECK(it != known_video_configs_.end()) + << "No video config for stream " << receiver_stream.stream_label; + const VideoConfig& config = it->second; + + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks; + if (config.output_dump_options.has_value()) { + std::unique_ptr<test::VideoFrameWriter> writer = + config.output_dump_options->CreateOutputDumpVideoFrameWriter( + receiver_stream.stream_label, receiver_stream.peer_name, + config.GetResolution()); + if (config.output_dump_use_fixed_framerate) { + writer = std::make_unique<test::FixedFpsVideoFrameWriterAdapter>( + config.fps, clock_, std::move(writer)); + } + sinks.push_back(std::make_unique<VideoWriter>( + writer.get(), config.output_dump_options->sampling_modulo())); + video_writers_.push_back(std::move(writer)); + } + if (config.show_on_screen) { + sinks.push_back(absl::WrapUnique( + test::VideoRenderer::Create((*config.stream_label + "-render").c_str(), + config.width, config.height))); + } + sinks_.insert({receiver_stream, std::move(sinks)}); + return &(sinks_.find(receiver_stream)->second); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h new file mode 100644 index 0000000000..7421c8e4a7 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h @@ -0,0 +1,170 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_VIDEO_QUALITY_ANALYZER_INJECTION_HELPER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_QUALITY_ANALYZER_INJECTION_HELPER_H_ + +#include <stdio.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/stats_observer_interface.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" +#include "test/pc/e2e/analyzer/video/analyzing_video_sink.h" +#include "test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h" +#include "test/pc/e2e/analyzer/video/encoded_image_data_injector.h" +#include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h" +#include "test/test_video_capturer.h" +#include "test/testsupport/video_frame_writer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Provides factory methods for components, that will be used to inject +// VideoQualityAnalyzerInterface into PeerConnection pipeline. +class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface { + public: + VideoQualityAnalyzerInjectionHelper( + Clock* clock, + std::unique_ptr<VideoQualityAnalyzerInterface> analyzer, + EncodedImageDataInjector* injector, + EncodedImageDataExtractor* extractor); + ~VideoQualityAnalyzerInjectionHelper() override; + + // Wraps video encoder factory to give video quality analyzer access to frames + // before encoding and encoded images after. + std::unique_ptr<VideoEncoderFactory> WrapVideoEncoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoEncoderFactory> delegate, + double bitrate_multiplier, + QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap stream_to_sfu_config) + const; + // Wraps video decoder factory to give video quality analyzer access to + // received encoded images and frames, that were decoded from them. + std::unique_ptr<VideoDecoderFactory> WrapVideoDecoderFactory( + absl::string_view peer_name, + std::unique_ptr<VideoDecoderFactory> delegate) const; + + // Creates VideoFrame preprocessor, that will allow video quality analyzer to + // get access to the captured frames. If provided config also specifies + // `input_dump_file_name`, video will be written into that file. + std::unique_ptr<test::TestVideoCapturer::FramePreprocessor> + CreateFramePreprocessor(absl::string_view peer_name, + const webrtc::webrtc_pc_e2e::VideoConfig& config); + // Creates sink, that will allow video quality analyzer to get access to + // the rendered frames. If corresponding video track has + // `output_dump_file_name` in its VideoConfig, which was used for + // CreateFramePreprocessor(...), then video also will be written + // into that file. + // TODO(titovartem): Remove method with `peer_name` only parameter. + std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> CreateVideoSink( + absl::string_view peer_name); + std::unique_ptr<AnalyzingVideoSink> CreateVideoSink( + absl::string_view peer_name, + const VideoSubscription& subscription, + bool report_infra_metrics); + + void Start(std::string test_case_name, + rtc::ArrayView<const std::string> peer_names, + int max_threads_count = 1); + + // Registers new call participant to the underlying video quality analyzer. + // The method should be called before the participant is actually added. + void RegisterParticipantInCall(absl::string_view peer_name); + + // Will be called after test removed existing participant in the middle of the + // call. + void UnregisterParticipantInCall(absl::string_view peer_name); + + // Forwards `stats_reports` for Peer Connection `pc_label` to + // `analyzer_`. + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + + // Stops VideoQualityAnalyzerInterface to populate final data and metrics. + // Should be invoked after analyzed video tracks are disposed. + void Stop(); + + private: + // Deprecated, to be removed when old API isn't used anymore. + class AnalyzingVideoSink2 final : public rtc::VideoSinkInterface<VideoFrame> { + public: + explicit AnalyzingVideoSink2(absl::string_view peer_name, + VideoQualityAnalyzerInjectionHelper* helper) + : peer_name_(peer_name), helper_(helper) {} + ~AnalyzingVideoSink2() override = default; + + void OnFrame(const VideoFrame& frame) override { + helper_->OnFrame(peer_name_, frame); + } + + private: + const std::string peer_name_; + VideoQualityAnalyzerInjectionHelper* const helper_; + }; + + struct ReceiverStream { + ReceiverStream(absl::string_view peer_name, absl::string_view stream_label) + : peer_name(peer_name), stream_label(stream_label) {} + + std::string peer_name; + std::string stream_label; + + // Define operators required to use ReceiverStream as std::map key. + bool operator==(const ReceiverStream& o) const { + return peer_name == o.peer_name && stream_label == o.stream_label; + } + bool operator<(const ReceiverStream& o) const { + return (peer_name == o.peer_name) ? stream_label < o.stream_label + : peer_name < o.peer_name; + } + }; + + // Creates a deep copy of the frame and passes it to the video analyzer, while + // passing real frame to the sinks + void OnFrame(absl::string_view peer_name, const VideoFrame& frame); + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>* + PopulateSinks(const ReceiverStream& receiver_stream); + + Clock* const clock_; + std::unique_ptr<VideoQualityAnalyzerInterface> analyzer_; + EncodedImageDataInjector* injector_; + EncodedImageDataExtractor* extractor_; + + std::vector<std::unique_ptr<test::VideoFrameWriter>> video_writers_; + + AnalyzingVideoSinksHelper sinks_helper_; + Mutex mutex_; + int peers_count_ RTC_GUARDED_BY(mutex_); + // Map from stream label to the video config. + std::map<std::string, webrtc::webrtc_pc_e2e::VideoConfig> known_video_configs_ + RTC_GUARDED_BY(mutex_); + std::map<ReceiverStream, + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>> + sinks_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_QUALITY_ANALYZER_INJECTION_HELPER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc new file mode 100644 index 0000000000..8049af308e --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.cc @@ -0,0 +1,162 @@ +/* + * 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/pc/e2e/analyzer/video/video_quality_metrics_reporter.h" + +#include <map> +#include <string> + +#include "api/stats/rtc_stats.h" +#include "api/stats/rtcstats_objects.h" +#include "api/test/metrics/metric.h" +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/checks.h" +#include "test/pc/e2e/metric_metadata_keys.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; +using ::webrtc::webrtc_pc_e2e::MetricMetadataKey; + +SamplesStatsCounter BytesPerSecondToKbps(const SamplesStatsCounter& counter) { + return counter * 0.008; +} + +} // namespace + +VideoQualityMetricsReporter::VideoQualityMetricsReporter( + Clock* const clock, + test::MetricsLogger* const metrics_logger) + : clock_(clock), metrics_logger_(metrics_logger) { + RTC_CHECK(metrics_logger_); +} + +void VideoQualityMetricsReporter::Start( + absl::string_view test_case_name, + const TrackIdStreamInfoMap* /*reporter_helper*/) { + test_case_name_ = std::string(test_case_name); + start_time_ = Now(); +} + +void VideoQualityMetricsReporter::OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) { + RTC_CHECK(start_time_) + << "Please invoke Start(...) method before calling OnStatsReports(...)"; + + auto transport_stats = report->GetStatsOfType<RTCTransportStats>(); + if (transport_stats.size() == 0u || + !transport_stats[0]->selected_candidate_pair_id.is_defined()) { + return; + } + RTC_DCHECK_EQ(transport_stats.size(), 1); + std::string selected_ice_id = + transport_stats[0]->selected_candidate_pair_id.ValueToString(); + // Use the selected ICE candidate pair ID to get the appropriate ICE stats. + const RTCIceCandidatePairStats ice_candidate_pair_stats = + report->Get(selected_ice_id)->cast_to<const RTCIceCandidatePairStats>(); + + auto outbound_rtp_stats = report->GetStatsOfType<RTCOutboundRTPStreamStats>(); + StatsSample sample; + for (auto& s : outbound_rtp_stats) { + if (!s->kind.is_defined()) { + continue; + } + if (!(*s->kind == RTCMediaStreamTrackKind::kVideo)) { + continue; + } + if (s->timestamp() > sample.sample_time) { + sample.sample_time = s->timestamp(); + } + sample.retransmitted_bytes_sent += + DataSize::Bytes(s->retransmitted_bytes_sent.ValueOrDefault(0ul)); + sample.bytes_sent += DataSize::Bytes(s->bytes_sent.ValueOrDefault(0ul)); + sample.header_bytes_sent += + DataSize::Bytes(s->header_bytes_sent.ValueOrDefault(0ul)); + } + + MutexLock lock(&video_bwe_stats_lock_); + VideoBweStats& video_bwe_stats = video_bwe_stats_[std::string(pc_label)]; + if (ice_candidate_pair_stats.available_outgoing_bitrate.is_defined()) { + video_bwe_stats.available_send_bandwidth.AddSample( + DataRate::BitsPerSec( + *ice_candidate_pair_stats.available_outgoing_bitrate) + .bytes_per_sec()); + } + + StatsSample prev_sample = last_stats_sample_[std::string(pc_label)]; + if (prev_sample.sample_time.IsZero()) { + prev_sample.sample_time = start_time_.value(); + } + last_stats_sample_[std::string(pc_label)] = sample; + + TimeDelta time_between_samples = sample.sample_time - prev_sample.sample_time; + if (time_between_samples.IsZero()) { + return; + } + + DataRate retransmission_bitrate = + (sample.retransmitted_bytes_sent - prev_sample.retransmitted_bytes_sent) / + time_between_samples; + video_bwe_stats.retransmission_bitrate.AddSample( + retransmission_bitrate.bytes_per_sec()); + DataRate transmission_bitrate = + (sample.bytes_sent + sample.header_bytes_sent - prev_sample.bytes_sent - + prev_sample.header_bytes_sent) / + time_between_samples; + video_bwe_stats.transmission_bitrate.AddSample( + transmission_bitrate.bytes_per_sec()); +} + +void VideoQualityMetricsReporter::StopAndReportResults() { + MutexLock video_bwemutex_(&video_bwe_stats_lock_); + for (const auto& item : video_bwe_stats_) { + ReportVideoBweResults(item.first, item.second); + } +} + +std::string VideoQualityMetricsReporter::GetTestCaseName( + const std::string& peer_name) const { + return test_case_name_ + "/" + peer_name; +} + +void VideoQualityMetricsReporter::ReportVideoBweResults( + const std::string& peer_name, + const VideoBweStats& video_bwe_stats) { + std::string test_case_name = GetTestCaseName(peer_name); + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> metric_metadata{ + {MetricMetadataKey::kPeerMetadataKey, peer_name}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; + + metrics_logger_->LogMetric( + "available_send_bandwidth", test_case_name, + BytesPerSecondToKbps(video_bwe_stats.available_send_bandwidth), + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, + metric_metadata); + metrics_logger_->LogMetric( + "transmission_bitrate", test_case_name, + BytesPerSecondToKbps(video_bwe_stats.transmission_bitrate), + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, + metric_metadata); + metrics_logger_->LogMetric( + "retransmission_bitrate", test_case_name, + BytesPerSecondToKbps(video_bwe_stats.retransmission_bitrate), + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, + metric_metadata); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.h b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.h new file mode 100644 index 0000000000..d3d976343b --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer/video/video_quality_metrics_reporter.h @@ -0,0 +1,81 @@ +/* + * 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_PC_E2E_ANALYZER_VIDEO_VIDEO_QUALITY_METRICS_REPORTER_H_ +#define TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_QUALITY_METRICS_REPORTER_H_ + +#include <map> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/test/track_id_stream_info_map.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +struct VideoBweStats { + SamplesStatsCounter available_send_bandwidth; + SamplesStatsCounter transmission_bitrate; + SamplesStatsCounter retransmission_bitrate; +}; + +class VideoQualityMetricsReporter + : public PeerConnectionE2EQualityTestFixture::QualityMetricsReporter { + public: + VideoQualityMetricsReporter(Clock* const clock, + test::MetricsLogger* const metrics_logger); + ~VideoQualityMetricsReporter() override = default; + + void Start(absl::string_view test_case_name, + const TrackIdStreamInfoMap* reporter_helper) override; + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + void StopAndReportResults() override; + + private: + struct StatsSample { + DataSize bytes_sent = DataSize::Zero(); + DataSize header_bytes_sent = DataSize::Zero(); + DataSize retransmitted_bytes_sent = DataSize::Zero(); + + Timestamp sample_time = Timestamp::Zero(); + }; + + std::string GetTestCaseName(const std::string& peer_name) const; + void ReportVideoBweResults(const std::string& peer_name, + const VideoBweStats& video_bwe_stats); + Timestamp Now() const { return clock_->CurrentTime(); } + + Clock* const clock_; + test::MetricsLogger* const metrics_logger_; + + std::string test_case_name_; + absl::optional<Timestamp> start_time_; + + Mutex video_bwe_stats_lock_; + // Map between a peer connection label (provided by the framework) and + // its video BWE stats. + std::map<std::string, VideoBweStats> video_bwe_stats_ + RTC_GUARDED_BY(video_bwe_stats_lock_); + std::map<std::string, StatsSample> last_stats_sample_ + RTC_GUARDED_BY(video_bwe_stats_lock_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_VIDEO_VIDEO_QUALITY_METRICS_REPORTER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer_helper.cc b/third_party/libwebrtc/test/pc/e2e/analyzer_helper.cc new file mode 100644 index 0000000000..76cd9a7c78 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer_helper.cc @@ -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. + */ + +#include "test/pc/e2e/analyzer_helper.h" + +#include <string> +#include <utility> + +namespace webrtc { +namespace webrtc_pc_e2e { + +AnalyzerHelper::AnalyzerHelper() { + signaling_sequence_checker_.Detach(); +} + +void AnalyzerHelper::AddTrackToStreamMapping( + absl::string_view track_id, + absl::string_view receiver_peer, + absl::string_view stream_label, + absl::optional<std::string> sync_group) { + RTC_DCHECK_RUN_ON(&signaling_sequence_checker_); + track_to_stream_map_.insert( + {std::string(track_id), + StreamInfo{.receiver_peer = std::string(receiver_peer), + .stream_label = std::string(stream_label), + .sync_group = sync_group.has_value() + ? *sync_group + : std::string(stream_label)}}); +} + +void AnalyzerHelper::AddTrackToStreamMapping(std::string track_id, + std::string stream_label) { + RTC_DCHECK_RUN_ON(&signaling_sequence_checker_); + track_to_stream_map_.insert( + {std::move(track_id), StreamInfo{stream_label, stream_label}}); +} + +void AnalyzerHelper::AddTrackToStreamMapping(std::string track_id, + std::string stream_label, + std::string sync_group) { + RTC_DCHECK_RUN_ON(&signaling_sequence_checker_); + track_to_stream_map_.insert( + {std::move(track_id), + StreamInfo{std::move(stream_label), std::move(sync_group)}}); +} + +AnalyzerHelper::StreamInfo AnalyzerHelper::GetStreamInfoFromTrackId( + absl::string_view track_id) const { + RTC_DCHECK_RUN_ON(&signaling_sequence_checker_); + auto track_to_stream_pair = track_to_stream_map_.find(std::string(track_id)); + RTC_CHECK(track_to_stream_pair != track_to_stream_map_.end()); + return track_to_stream_pair->second; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/analyzer_helper.h b/third_party/libwebrtc/test/pc/e2e/analyzer_helper.h new file mode 100644 index 0000000000..d0b47c4fb9 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/analyzer_helper.h @@ -0,0 +1,61 @@ +/* + * 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_PC_E2E_ANALYZER_HELPER_H_ +#define TEST_PC_E2E_ANALYZER_HELPER_H_ + +#include <map> +#include <string> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/sequence_checker.h" +#include "api/test/track_id_stream_info_map.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// This class is a utility that provides bookkeeping capabilities that +// are useful to associate stats reports track_ids to the remote stream info. +// The framework will populate an instance of this class and it will pass +// it to the Start method of Media Quality Analyzers. +// An instance of AnalyzerHelper must only be accessed from a single +// thread and since stats collection happens on the signaling thread, +// AddTrackToStreamMapping, GetStreamLabelFromTrackId and +// GetSyncGroupLabelFromTrackId must be invoked from the signaling thread. Get +// methods should be invoked only after all data is added. Mixing Get methods +// with adding new data may lead to undefined behavior. +class AnalyzerHelper : public TrackIdStreamInfoMap { + public: + AnalyzerHelper(); + + void AddTrackToStreamMapping(absl::string_view track_id, + absl::string_view receiver_peer, + absl::string_view stream_label, + absl::optional<std::string> sync_group); + void AddTrackToStreamMapping(std::string track_id, std::string stream_label); + void AddTrackToStreamMapping(std::string track_id, + std::string stream_label, + std::string sync_group); + + StreamInfo GetStreamInfoFromTrackId( + absl::string_view track_id) const override; + + private: + SequenceChecker signaling_sequence_checker_; + std::map<std::string, StreamInfo> track_to_stream_map_ + RTC_GUARDED_BY(signaling_sequence_checker_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ANALYZER_HELPER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc new file mode 100644 index 0000000000..0d4fe7478d --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.cc @@ -0,0 +1,151 @@ +/* + * 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/pc/e2e/cross_media_metrics_reporter.h" + +#include <utility> +#include <vector> + +#include "api/stats/rtc_stats.h" +#include "api/stats/rtcstats_objects.h" +#include "api/test/metrics/metric.h" +#include "api/units/timestamp.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" +#include "system_wrappers/include/field_trial.h" +#include "test/pc/e2e/metric_metadata_keys.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; + +CrossMediaMetricsReporter::CrossMediaMetricsReporter( + test::MetricsLogger* metrics_logger) + : metrics_logger_(metrics_logger) { + RTC_CHECK(metrics_logger_); +} + +void CrossMediaMetricsReporter::Start( + absl::string_view test_case_name, + const TrackIdStreamInfoMap* reporter_helper) { + test_case_name_ = std::string(test_case_name); + reporter_helper_ = reporter_helper; +} + +void CrossMediaMetricsReporter::OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) { + auto inbound_stats = report->GetStatsOfType<RTCInboundRTPStreamStats>(); + std::map<std::string, std::vector<const RTCInboundRTPStreamStats*>> + sync_group_stats; + for (const auto& stat : inbound_stats) { + if (stat->estimated_playout_timestamp.ValueOrDefault(0.) > 0 && + stat->track_identifier.is_defined()) { + sync_group_stats[reporter_helper_ + ->GetStreamInfoFromTrackId(*stat->track_identifier) + .sync_group] + .push_back(stat); + } + } + + MutexLock lock(&mutex_); + for (const auto& pair : sync_group_stats) { + // If there is less than two streams, it is not a sync group. + if (pair.second.size() < 2) { + continue; + } + auto sync_group = std::string(pair.first); + const RTCInboundRTPStreamStats* audio_stat = pair.second[0]; + const RTCInboundRTPStreamStats* video_stat = pair.second[1]; + + RTC_CHECK(pair.second.size() == 2 && audio_stat->kind.is_defined() && + video_stat->kind.is_defined() && + *audio_stat->kind != *video_stat->kind) + << "Sync group should consist of one audio and one video stream."; + + if (*audio_stat->kind == RTCMediaStreamTrackKind::kVideo) { + std::swap(audio_stat, video_stat); + } + // Stream labels of a sync group are same for all polls, so we need it add + // it only once. + if (stats_info_.find(sync_group) == stats_info_.end()) { + RTC_CHECK(audio_stat->track_identifier.is_defined()); + RTC_CHECK(video_stat->track_identifier.is_defined()); + stats_info_[sync_group].audio_stream_info = + reporter_helper_->GetStreamInfoFromTrackId( + *audio_stat->track_identifier); + stats_info_[sync_group].video_stream_info = + reporter_helper_->GetStreamInfoFromTrackId( + *video_stat->track_identifier); + } + + double audio_video_playout_diff = *audio_stat->estimated_playout_timestamp - + *video_stat->estimated_playout_timestamp; + if (audio_video_playout_diff > 0) { + stats_info_[sync_group].audio_ahead_ms.AddSample( + audio_video_playout_diff); + stats_info_[sync_group].video_ahead_ms.AddSample(0); + } else { + stats_info_[sync_group].audio_ahead_ms.AddSample(0); + stats_info_[sync_group].video_ahead_ms.AddSample( + std::abs(audio_video_playout_diff)); + } + } +} + +void CrossMediaMetricsReporter::StopAndReportResults() { + MutexLock lock(&mutex_); + for (const auto& pair : stats_info_) { + const std::string& sync_group = pair.first; + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> audio_metric_metadata{ + {MetricMetadataKey::kPeerSyncGroupMetadataKey, sync_group}, + {MetricMetadataKey::kAudioStreamMetadataKey, + pair.second.audio_stream_info.stream_label}, + {MetricMetadataKey::kPeerMetadataKey, + pair.second.audio_stream_info.receiver_peer}, + {MetricMetadataKey::kReceiverMetadataKey, + pair.second.audio_stream_info.receiver_peer}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; + metrics_logger_->LogMetric( + "audio_ahead_ms", + GetTestCaseName(pair.second.audio_stream_info.stream_label, sync_group), + pair.second.audio_ahead_ms, Unit::kMilliseconds, + webrtc::test::ImprovementDirection::kSmallerIsBetter, + std::move(audio_metric_metadata)); + + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> video_metric_metadata{ + {MetricMetadataKey::kPeerSyncGroupMetadataKey, sync_group}, + {MetricMetadataKey::kAudioStreamMetadataKey, + pair.second.video_stream_info.stream_label}, + {MetricMetadataKey::kPeerMetadataKey, + pair.second.video_stream_info.receiver_peer}, + {MetricMetadataKey::kReceiverMetadataKey, + pair.second.video_stream_info.receiver_peer}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; + metrics_logger_->LogMetric( + "video_ahead_ms", + GetTestCaseName(pair.second.video_stream_info.stream_label, sync_group), + pair.second.video_ahead_ms, Unit::kMilliseconds, + webrtc::test::ImprovementDirection::kSmallerIsBetter, + std::move(video_metric_metadata)); + } +} + +std::string CrossMediaMetricsReporter::GetTestCaseName( + const std::string& stream_label, + const std::string& sync_group) const { + return test_case_name_ + "/" + sync_group + "_" + stream_label; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.h b/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.h new file mode 100644 index 0000000000..2d51ebb20f --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/cross_media_metrics_reporter.h @@ -0,0 +1,68 @@ +/* + * 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_PC_E2E_CROSS_MEDIA_METRICS_REPORTER_H_ +#define TEST_PC_E2E_CROSS_MEDIA_METRICS_REPORTER_H_ + +#include <map> +#include <string> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/test/track_id_stream_info_map.h" +#include "api/units/timestamp.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class CrossMediaMetricsReporter + : public PeerConnectionE2EQualityTestFixture::QualityMetricsReporter { + public: + explicit CrossMediaMetricsReporter(test::MetricsLogger* metrics_logger); + ~CrossMediaMetricsReporter() override = default; + + void Start(absl::string_view test_case_name, + const TrackIdStreamInfoMap* reporter_helper) override; + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + void StopAndReportResults() override; + + private: + struct StatsInfo { + SamplesStatsCounter audio_ahead_ms; + SamplesStatsCounter video_ahead_ms; + + TrackIdStreamInfoMap::StreamInfo audio_stream_info; + TrackIdStreamInfoMap::StreamInfo video_stream_info; + std::string audio_stream_label; + std::string video_stream_label; + }; + + std::string GetTestCaseName(const std::string& stream_label, + const std::string& sync_group) const; + + test::MetricsLogger* const metrics_logger_; + + std::string test_case_name_; + const TrackIdStreamInfoMap* reporter_helper_; + + Mutex mutex_; + std::map<std::string, StatsInfo> stats_info_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_CROSS_MEDIA_METRICS_REPORTER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/echo/echo_emulation.cc b/third_party/libwebrtc/test/pc/e2e/echo/echo_emulation.cc new file mode 100644 index 0000000000..8fdabeb16f --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/echo/echo_emulation.cc @@ -0,0 +1,117 @@ +/* + * 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/pc/e2e/echo/echo_emulation.h" + +#include <limits> +#include <utility> + +#include "api/test/pclf/media_configuration.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +constexpr int kSingleBufferDurationMs = 10; + +} // namespace + +EchoEmulatingCapturer::EchoEmulatingCapturer( + std::unique_ptr<TestAudioDeviceModule::Capturer> capturer, + EchoEmulationConfig config) + : delegate_(std::move(capturer)), + config_(config), + renderer_queue_(2 * config_.echo_delay.ms() / kSingleBufferDurationMs), + queue_input_(TestAudioDeviceModule::SamplesPerFrame( + delegate_->SamplingFrequency()) * + delegate_->NumChannels()), + queue_output_(TestAudioDeviceModule::SamplesPerFrame( + delegate_->SamplingFrequency()) * + delegate_->NumChannels()) { + renderer_thread_.Detach(); + capturer_thread_.Detach(); +} + +void EchoEmulatingCapturer::OnAudioRendered( + rtc::ArrayView<const int16_t> data) { + RTC_DCHECK_RUN_ON(&renderer_thread_); + if (!recording_started_) { + // Because rendering can start before capturing in the beginning we can have + // a set of empty audio data frames. So we will skip them and will start + // fill the queue only after 1st non-empty audio data frame will arrive. + bool is_empty = true; + for (auto d : data) { + if (d != 0) { + is_empty = false; + break; + } + } + if (is_empty) { + return; + } + recording_started_ = true; + } + queue_input_.assign(data.begin(), data.end()); + if (!renderer_queue_.Insert(&queue_input_)) { + RTC_LOG(LS_WARNING) << "Echo queue is full"; + } +} + +bool EchoEmulatingCapturer::Capture(rtc::BufferT<int16_t>* buffer) { + RTC_DCHECK_RUN_ON(&capturer_thread_); + bool result = delegate_->Capture(buffer); + // Now we have to reduce input signal to avoid saturation when mixing in the + // fake echo. + for (size_t i = 0; i < buffer->size(); ++i) { + (*buffer)[i] /= 2; + } + + // When we accumulated enough delay in the echo buffer we will pop from + // that buffer on each ::Capture(...) call. If the buffer become empty it + // will mean some bug, so we will crash during removing item from the queue. + if (!delay_accumulated_) { + delay_accumulated_ = + renderer_queue_.SizeAtLeast() >= + static_cast<size_t>(config_.echo_delay.ms() / kSingleBufferDurationMs); + } + + if (delay_accumulated_) { + RTC_CHECK(renderer_queue_.Remove(&queue_output_)); + for (size_t i = 0; i < buffer->size() && i < queue_output_.size(); ++i) { + int32_t res = (*buffer)[i] + queue_output_[i]; + if (res < std::numeric_limits<int16_t>::min()) { + res = std::numeric_limits<int16_t>::min(); + } + if (res > std::numeric_limits<int16_t>::max()) { + res = std::numeric_limits<int16_t>::max(); + } + (*buffer)[i] = static_cast<int16_t>(res); + } + } + + return result; +} + +EchoEmulatingRenderer::EchoEmulatingRenderer( + std::unique_ptr<TestAudioDeviceModule::Renderer> renderer, + EchoEmulatingCapturer* echo_emulating_capturer) + : delegate_(std::move(renderer)), + echo_emulating_capturer_(echo_emulating_capturer) { + RTC_DCHECK(echo_emulating_capturer_); +} + +bool EchoEmulatingRenderer::Render(rtc::ArrayView<const int16_t> data) { + if (data.size() > 0) { + echo_emulating_capturer_->OnAudioRendered(data); + } + return delegate_->Render(data); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/echo/echo_emulation.h b/third_party/libwebrtc/test/pc/e2e/echo/echo_emulation.h new file mode 100644 index 0000000000..359a481e46 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/echo/echo_emulation.h @@ -0,0 +1,79 @@ +/* + * 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_PC_E2E_ECHO_ECHO_EMULATION_H_ +#define TEST_PC_E2E_ECHO_ECHO_EMULATION_H_ + +#include <atomic> +#include <deque> +#include <memory> +#include <vector> + +#include "api/test/pclf/media_configuration.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "rtc_base/swap_queue.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Reduces audio input strength from provided capturer twice and adds input +// provided into EchoEmulatingCapturer::OnAudioRendered(...). +class EchoEmulatingCapturer : public TestAudioDeviceModule::Capturer { + public: + EchoEmulatingCapturer( + std::unique_ptr<TestAudioDeviceModule::Capturer> capturer, + EchoEmulationConfig config); + + void OnAudioRendered(rtc::ArrayView<const int16_t> data); + + int SamplingFrequency() const override { + return delegate_->SamplingFrequency(); + } + int NumChannels() const override { return delegate_->NumChannels(); } + bool Capture(rtc::BufferT<int16_t>* buffer) override; + + private: + std::unique_ptr<TestAudioDeviceModule::Capturer> delegate_; + const EchoEmulationConfig config_; + + SwapQueue<std::vector<int16_t>> renderer_queue_; + + SequenceChecker renderer_thread_; + std::vector<int16_t> queue_input_ RTC_GUARDED_BY(renderer_thread_); + bool recording_started_ RTC_GUARDED_BY(renderer_thread_) = false; + + SequenceChecker capturer_thread_; + std::vector<int16_t> queue_output_ RTC_GUARDED_BY(capturer_thread_); + bool delay_accumulated_ RTC_GUARDED_BY(capturer_thread_) = false; +}; + +// Renders output into provided renderer and also copy output into provided +// EchoEmulationCapturer. +class EchoEmulatingRenderer : public TestAudioDeviceModule::Renderer { + public: + EchoEmulatingRenderer( + std::unique_ptr<TestAudioDeviceModule::Renderer> renderer, + EchoEmulatingCapturer* echo_emulating_capturer); + + int SamplingFrequency() const override { + return delegate_->SamplingFrequency(); + } + int NumChannels() const override { return delegate_->NumChannels(); } + bool Render(rtc::ArrayView<const int16_t> data) override; + + private: + std::unique_ptr<TestAudioDeviceModule::Renderer> delegate_; + EchoEmulatingCapturer* echo_emulating_capturer_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_ECHO_ECHO_EMULATION_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/architecture.md b/third_party/libwebrtc/test/pc/e2e/g3doc/architecture.md new file mode 100644 index 0000000000..1b68c6db2c --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/architecture.md @@ -0,0 +1,209 @@ +<!-- go/cmark --> +<!--* freshness: {owner: 'titovartem' reviewed: '2021-04-12'} *--> + +# PeerConnection level framework fixture architecture + +## Overview + +The main implementation of +[`webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture`][1] is +[`webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTest`][2]. Internally it owns +the next main pieces: + +* [`MediaHelper`][3] - responsible for adding audio and video tracks to the + peers. +* [`VideoQualityAnalyzerInjectionHelper`][4] and + [`SingleProcessEncodedImageDataInjector`][5] - used to inject video quality + analysis and properly match captured and rendered video frames. You can read + more about it in + [DefaultVideoQualityAnalyzer](default_video_quality_analyzer.md) section. +* [`AudioQualityAnalyzerInterface`][6] - used to measure audio quality metrics +* [`TestActivitiesExecutor`][7] - used to support [`ExecuteAt(...)`][8] and + [`ExecuteEvery(...)`][9] API of `PeerConnectionE2EQualityTestFixture` to run + any arbitrary action during test execution timely synchronized with a test + call. +* A vector of [`QualityMetricsReporter`][10] added by the + `PeerConnectionE2EQualityTestFixture` user. +* Two peers: Alice and Bob represented by instances of [`TestPeer`][11] + object. + +Also it keeps a reference to [`webrtc::TimeController`][12], which is used to +create all required threads, task queues, task queue factories and time related +objects. + +## TestPeer + +Call participants are represented by instances of `TestPeer` object. +[`TestPeerFactory`][13] is used to create them. `TestPeer` owns all instances +related to the `webrtc::PeerConnection`, including required listeners and +callbacks. Also it provides an API to do offer/answer exchange and ICE candidate +exchange. For this purposes internally it uses an instance of +[`webrtc::PeerConnectionWrapper`][14]. + +The `TestPeer` also owns the `PeerConnection` worker thread. The signaling +thread for all `PeerConnection`'s is owned by +`PeerConnectionE2EQualityTestFixture` and shared between all participants in the +call. The network thread is owned by the network layer (it maybe either emulated +network provided by [Network Emulation Framework][24] or network thread and +`rtc::NetworkManager` provided by user) and provided when peer is added to the +fixture via [`AddPeer(...)`][15] API. + +## GetStats API based metrics reporters + +`PeerConnectionE2EQualityTestFixture` gives the user ability to provide +different `QualityMetricsReporter`s which will listen for `PeerConnection` +[`GetStats`][16] API. Then such reporters will be able to report various metrics +that user wants to measure. + +`PeerConnectionE2EQualityTestFixture` itself also uses this mechanism to +measure: + +* Audio quality metrics +* Audio/Video sync metrics (with help of [`CrossMediaMetricsReporter`][17]) + +Also framework provides a [`StatsBasedNetworkQualityMetricsReporter`][18] to +measure network related WebRTC metrics and print debug raw emulated network +statistic. This reporter should be added by user via +[`AddQualityMetricsReporter(...)`][19] API if requried. + +Internally stats gathering is done by [`StatsPoller`][20]. Stats are requested +once per second for each `PeerConnection` and then resulted object is provided +into each stats listener. + +## Offer/Answer exchange + +`PeerConnectionE2EQualityTest` provides ability to test Simulcast and SVC for +video. These features aren't supported by P2P call and in general requires a +Selective Forwarding Unit (SFU). So special logic is applied to mimic SFU +behavior in P2P call. This logic is located inside [`SignalingInterceptor`][21], +[`QualityAnalyzingVideoEncoder`][22] and [`QualityAnalyzingVideoDecoder`][23] +and consist of SDP modification during offer/answer exchange and special +handling of video frames from unrelated Simulcast/SVC streams during decoding. + +### Simulcast + +In case of Simulcast we have a video track, which internally contains multiple +video streams, for example low resolution, medium resolution and high +resolution. WebRTC client doesn't support receiving an offer with multiple +streams in it, because usually SFU will keep only single stream for the client. +To bypass it framework will modify offer by converting a single track with three +video streams into three independent video tracks. Then sender will think that +it send simulcast, but receiver will think that it receives 3 independent +tracks. + +To achieve such behavior some extra tweaks are required: + +* MID RTP header extension from original offer have to be removed +* RID RTP header extension from original offer is replaced with MID RTP header + extension, so the ID that sender uses for RID on receiver will be parsed as + MID. +* Answer have to be modified in the opposite way. + +Described modifications are illustrated on the picture below. + +![VP8 Simulcast offer modification](vp8_simulcast_offer_modification.png "VP8 Simulcast offer modification") + +The exchange will look like this: + +1. Alice creates an offer +2. Alice sets offer as local description +3. Do described offer modification +4. Alice sends modified offer to Bob +5. Bob sets modified offer as remote description +6. Bob creates answer +7. Bob sets answer as local description +8. Do reverse modifications on answer +9. Bob sends modified answer to Alice +10. Alice sets modified answer as remote description + +Such mechanism put a constraint that RTX streams are not supported, because they +don't have RID RTP header extension in their packets. + +### SVC + +In case of SVC the framework will update the sender's offer before even setting +it as local description on the sender side. Then no changes to answer will be +required. + +`ssrc` is a 32 bit random value that is generated in RTP to denote a specific +source used to send media in an RTP connection. In original offer video track +section will look like this: + +``` +m=video 9 UDP/TLS/RTP/SAVPF 98 100 99 101 +... +a=ssrc-group:FID <primary ssrc> <retransmission ssrc> +a=ssrc:<primary ssrc> cname:... +.... +a=ssrc:<retransmission ssrc> cname:... +.... +``` + +To enable SVC for such video track framework will add extra `ssrc`s for each SVC +stream that is required like this: + +``` +a=ssrc-group:FID <Low resolution primary ssrc> <Low resolution retransmission ssrc> +a=ssrc:<Low resolution primary ssrc> cname:... +.... +a=ssrc:<Low resolution retransmission ssrc> cname:.... +... +a=ssrc-group:FID <Medium resolution primary ssrc> <Medium resolution retransmission ssrc> +a=ssrc:<Medium resolution primary ssrc> cname:... +.... +a=ssrc:<Medium resolution retransmission ssrc> cname:.... +... +a=ssrc-group:FID <High resolution primary ssrc> <High resolution retransmission ssrc> +a=ssrc:<High resolution primary ssrc> cname:... +.... +a=ssrc:<High resolution retransmission ssrc> cname:.... +... +``` + +The next line will also be added to the video track section of the offer: + +``` +a=ssrc-group:SIM <Low resolution primary ssrc> <Medium resolution primary ssrc> <High resolution primary ssrc> +``` + +It will tell PeerConnection that this track should be configured as SVC. It +utilize WebRTC Plan B offer structure to achieve SVC behavior, also it modifies +offer before setting it as local description which violates WebRTC standard. +Also it adds limitations that on lossy networks only top resolution streams can +be analyzed, because WebRTC won't try to restore low resolution streams in case +of loss, because it still receives higher stream. + +### Handling in encoder/decoder + +In the encoder, the framework for each encoded video frame will propagate +information requried for the fake SFU to know if it belongs to an interesting +simulcast stream/spatial layer of if it should be "discarded". + +On the decoder side frames that should be "discarded" by fake SFU will be auto +decoded into single pixel images and only the interesting simulcast +stream/spatial layer will go into real decoder and then will be analyzed. + +[1]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=55;drc=484acf27231d931dbc99aedce85bc27e06486b96 +[2]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/peer_connection_quality_test.h;l=44;drc=6cc893ad778a0965e2b7a8e614f3c98aa81bee5b +[3]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/media/media_helper.h;l=27;drc=d46db9f1523ae45909b4a6fdc90a140443068bc6 +[4]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h;l=38;drc=79020414fd5c71f9ec1f25445ea5f1c8001e1a49 +[5]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h;l=40;drc=79020414fd5c71f9ec1f25445ea5f1c8001e1a49 +[6]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/audio_quality_analyzer_interface.h;l=23;drc=20f45823e37fd7272aa841831c029c21f29742c2 +[7]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/test_activities_executor.h;l=28;drc=6cc893ad778a0965e2b7a8e614f3c98aa81bee5b +[8]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=439;drc=484acf27231d931dbc99aedce85bc27e06486b96 +[9]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=445;drc=484acf27231d931dbc99aedce85bc27e06486b96 +[10]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=413;drc=9438fb3fff97c803d1ead34c0e4f223db168526f +[11]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/test_activities_executor.h;l=28;drc=6cc893ad778a0965e2b7a8e614f3c98aa81bee5b +[12]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/test_activities_executor.h;l=28;drc=6cc893ad778a0965e2b7a8e614f3c98aa81bee5b +[13]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/test_peer_factory.h;l=46;drc=0ef4a2488a466a24ab97b31fdddde55440d451f9 +[14]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/pc/peer_connection_wrapper.h;l=47;drc=5ab79e62f691875a237ea28ca3975ea1f0ed62ec +[15]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=459;drc=484acf27231d931dbc99aedce85bc27e06486b96 +[16]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/peer_connection_interface.h;l=886;drc=9438fb3fff97c803d1ead34c0e4f223db168526f +[17]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/cross_media_metrics_reporter.h;l=29;drc=9d777620236ec76754cfce19f6e82dd18e52d22c +[18]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/cross_media_metrics_reporter.h;l=29;drc=9d777620236ec76754cfce19f6e82dd18e52d22c +[19]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=450;drc=484acf27231d931dbc99aedce85bc27e06486b96 +[20]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/stats_poller.h;l=52;drc=9b526180c9e9722d3fc7f8689da6ec094fc7fc0a +[21]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/sdp/sdp_changer.h;l=79;drc=ee558dcca89fd8b105114ededf9e74d948da85e8 +[22]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h;l=54;drc=79020414fd5c71f9ec1f25445ea5f1c8001e1a49 +[23]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h;l=50;drc=79020414fd5c71f9ec1f25445ea5f1c8001e1a49 +[24]: /test/network/g3doc/index.md diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/default_video_quality_analyzer.md b/third_party/libwebrtc/test/pc/e2e/g3doc/default_video_quality_analyzer.md new file mode 100644 index 0000000000..67596777f2 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/default_video_quality_analyzer.md @@ -0,0 +1,197 @@ +<!-- go/cmark --> +<!--* freshness: {owner: 'titovartem' reviewed: '2021-02-21'} *--> + +# DefaultVideoQualityAnalyzer + +## Audience + +This document is for users of +[`webrtc::webrtc_pc_e2e::DefaultVideoQualityAnalyzer`][1]. + +## Overview + +`DefaultVideoQualityAnalyzer` implements +[`webrtc::VideoQualityAnalyzerInterface`][2] and is a main +implementation of video quality analyzer for WebRTC. To operate correctly it +requires to receive video frame on each step: + +1. On frame captured - analyzer will generate a unique ID for the frame, that + caller should attach to the it. +2. Immediately before frame enter the encoder. +3. Immediately after the frame was encoded. +4. After the frame was received and immediately before it entered the decoder. +5. Immediately after the frame was decoded. +6. When the frame was rendered. + +![VideoQualityAnalyzerInterface pipeline](video_quality_analyzer_pipeline.png "VideoQualityAnalyzerInterface pipeline") + +The analyzer updates its internal metrics per frame when it was rendered and +reports all of them after it was stopped through +[WebRTC perf results reporting system][10]. + +To properly inject `DefaultVideoQualityAnalyzer` into pipeline the following helpers can be used: + +### VideoQualityAnalyzerInjectionHelper + +[`webrtc::webrtc_pc_e2e::VideoQualityAnalyzerInjectionHelper`][3] provides +factory methods for components, that will be used to inject +`VideoQualityAnalyzerInterface` into the `PeerConnection` pipeline: + +* Wrappers for [`webrtc::VideoEncoderFactory`][4] and + [`webrtc::VideoDecodeFactory`][5] which will properly pass + [`webrtc::VideoFrame`][6] and [`webrtc::EncodedImage`][7] into analyzer + before and after real video encode and decoder. +* [`webrtc::test::TestVideoCapturer::FramePreprocessor`][8] which is used to + pass generated frames into analyzer on capturing and then set the returned + frame ID. It also configures dumping of captured frames if requried. +* [`rtc::VideoSinkInterface<VideoFrame>`][9] which is used to pass frames to + the analyzer before they will be rendered to compute per frame metrics. It + also configures dumping of rendered video if requried. + +Besides factories `VideoQualityAnalyzerInjectionHelper` has method to +orchestrate `VideoQualityAnalyzerInterface` workflow: + +* `Start` - to start video analyzer, so it will be able to receive and analyze + video frames. +* `RegisterParticipantInCall` - to add new participants after analyzer was + started. +* `Stop` - to stop analyzer, compute all metrics for frames that were recevied + before and report them. + +Also `VideoQualityAnalyzerInjectionHelper` implements +[`webrtc::webrtc_pc_e2e::StatsObserverInterface`][11] to propagate WebRTC stats +to `VideoQualityAnalyzerInterface`. + +### EncodedImageDataInjector and EncodedImageDataExtractor + +[`webrtc::webrtc_pc_e2e::EncodedImageDataInjector`][14] and +[`webrtc::webrtc_pc_e2e::EncodedImageDataInjector`][15] are used to inject and +extract data into `webrtc::EncodedImage` to propagate frame ID and other +required information through the network. + +By default [`webrtc::webrtc_pc_e2e::SingleProcessEncodedImageDataInjector`][16] +is used. It assumes `webrtc::EncodedImage` payload as black box which is +remaining unchanged from encoder to decoder and stores the information required +for its work in the last 3 bytes of the payload, replacing the original data +during injection and restoring it back during extraction. Also +`SingleProcessEncodedImageDataInjector` requires that sender and receiver were +inside single process. + +![SingleProcessEncodedImageDataInjector](single_process_encoded_image_data_injector.png "SingleProcessEncodedImageDataInjector") + +## Exported metrics + +Exported metrics are reported to WebRTC perf results reporting system. + +### General + +* *`cpu_usage`* - CPU usage excluding video analyzer + +### Video + +* *`psnr`* - peak signal-to-noise ratio: + [wikipedia](https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio) +* *`ssim`* - structural similarity: + [wikipedia](https://en.wikipedia.org/wiki/Structural_similarity). +* *`min_psnr`* - minimum value of psnr across all frames of video stream. +* *`encode_time`* - time to encode a single frame. +* *`decode_time`* - time to decode a single frame. +* *`transport_time`* - time from frame encoded to frame received for decoding. +* *`receive_to_render_time`* - time from frame received for decoding to frame + rendered. +* *`total_delay_incl_transport`* - time from frame was captured on device to + time when frame was displayed on device. +* *`encode_frame_rate`* - frame rate after encoder. +* *`harmonic_framerate`* - video duration divided on squared sum of interframe + delays. Reflects render frame rate penalized by freezes. +* *`time_between_rendered_frames`* - time between frames out to renderer. +* *`dropped_frames`* - amount of frames that were sent, but weren't rendered + and are known not to be “on the way†from sender to receiver. + +Freeze is a pause when no new frames from decoder arrived for 150ms + avg time +between frames or 3 * avg time between frames. + +* *`time_between_freezes`* - mean time from previous freeze end to new freeze + start. +* *`freeze_time_ms`* - total freeze time in ms. +* *`max_skipped`* - frames skipped between two nearest rendered. +* *`pixels_per_frame`* - amount of pixels on frame (width * height). +* *`target_encode_bitrate`* - target encode bitrate provided by BWE to + encoder. +* *`actual_encode_bitrate -`* - actual encode bitrate produced by encoder. +* *`available_send_bandwidth -`* - available send bandwidth estimated by BWE. +* *`transmission_bitrate`* - bitrate of media in the emulated network, not + counting retransmissions FEC, and RTCP messages +* *`retransmission_bitrate`* - bitrate of retransmission streams only. + +### Framework stability + +* *`frames_in_flight`* - amount of frames that were captured but wasn't seen + on receiver. + +## Debug metrics + +Debug metrics are not reported to WebRTC perf results reporting system, but are +available through `DefaultVideoQualityAnalyzer` API. + +### [FrameCounters][12] + +Frame counters consist of next counters: + +* *`captured`* - count of frames, that were passed into WebRTC pipeline by + video stream source +* *`pre_encoded`* - count of frames that reached video encoder. +* *`encoded`* - count of encoded images that were produced by encoder for all + requested spatial layers and simulcast streams. +* *`received`* - count of encoded images received in decoder for all requested + spatial layers and simulcast streams. +* *`decoded`* - count of frames that were produced by decoder. +* *`rendered`* - count of frames that went out from WebRTC pipeline to video + sink. +* *`dropped`* - count of frames that were dropped in any point between + capturing and rendering. + +`DefaultVideoQualityAnalyzer` exports these frame counters: + +* *`GlobalCounters`* - frame counters for frames met on each stage of analysis + for all media streams. +* *`PerStreamCounters`* - frame counters for frames met on each stage of + analysis separated per individual video track (single media section in the + SDP offer). + +### [AnalyzerStats][13] + +Contains metrics about internal state of video analyzer during its work + +* *`comparisons_queue_size`* - size of analyzer internal queue used to perform + captured and rendered frames comparisons measured when new element is added + to the queue. +* *`comparisons_done`* - number of performed comparisons of 2 video frames + from captured and rendered streams. +* *`cpu_overloaded_comparisons_done`* - number of cpu overloaded comparisons. + Comparison is cpu overloaded if it is queued when there are too many not + processed comparisons in the queue. Overloaded comparison doesn't include + metrics like SSIM and PSNR that require heavy computations. +* *`memory_overloaded_comparisons_done`* - number of memory overloaded + comparisons. Comparison is memory overloaded if it is queued when its + captured frame was already removed due to high memory usage for that video + stream. +* *`frames_in_flight_left_count`* - count of frames in flight in analyzer + measured when new comparison is added and after analyzer was stopped. + +[1]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h;l=188;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[2]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/test/video_quality_analyzer_interface.h;l=56;drc=d7808f1c464a07c8f1e2f97ec7ee92fda998d590 +[3]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h;l=39;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[4]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/video_codecs/video_encoder_factory.h;l=27;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[5]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/video_codecs/video_decoder_factory.h;l=27;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[6]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/video/video_frame.h;l=30;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[7]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/video/encoded_image.h;l=71;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[8]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/test_video_capturer.h;l=28;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[9]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/video/video_sink_interface.h;l=19;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[10]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/testsupport/perf_test.h;drc=0710b401b1e5b500b8e84946fb657656ba1b58b7 +[11]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/api/test/stats_observer_interface.h;l=21;drc=9b526180c9e9722d3fc7f8689da6ec094fc7fc0a +[12]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h;l=57;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[13]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h;l=113;drc=08f46909a8735cf181b99ef2f7e1791c5a7531d2 +[14]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/encoded_image_data_injector.h;l=23;drc=c57089a97a3df454f4356d882cc8df173e8b3ead +[15]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/encoded_image_data_injector.h;l=46;drc=c57089a97a3df454f4356d882cc8df173e8b3ead +[16]: https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h;l=40;drc=c57089a97a3df454f4356d882cc8df173e8b3ead diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/in_test_psnr_plot.png b/third_party/libwebrtc/test/pc/e2e/g3doc/in_test_psnr_plot.png Binary files differnew file mode 100644 index 0000000000..3f36725727 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/in_test_psnr_plot.png diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/index.md b/third_party/libwebrtc/test/pc/e2e/g3doc/index.md new file mode 100644 index 0000000000..678262bb2b --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/index.md @@ -0,0 +1,224 @@ +<!-- go/cmark --> +<!--* freshness: {owner: 'titovartem' reviewed: '2021-04-12'} *--> + +# PeerConnection Level Framework + +## API + +* [Fixture][1] +* [Fixture factory function][2] + +## Documentation + +The PeerConnection level framework is designed for end-to-end media quality +testing through the PeerConnection level public API. The framework uses the +*Unified plan* API to generate offers/answers during the signaling phase. The +framework also wraps the video encoder/decoder and inject it into +*`webrtc::PeerConnection`* to measure video quality, performing 1:1 frames +matching between captured and rendered frames without any extra requirements to +input video. For audio quality evaluation the standard `GetStats()` API from +PeerConnection is used. + +The framework API is located in the namespace *`webrtc::webrtc_pc_e2e`*. + +### Supported features + +* Single or bidirectional media in the call +* RTC Event log dump per peer +* AEC dump per peer +* Compatible with *`webrtc::TimeController`* for both real and simulated time +* Media + * AV sync +* Video + * Any amount of video tracks both from caller and callee sides + * Input video from + * Video generator + * Specified file + * Any instance of *`webrtc::test::FrameGeneratorInterface`* + * Dumping of captured/rendered video into file + * Screen sharing + * Vp8 simulcast from caller side + * Vp9 SVC from caller side + * Choosing of video codec (name and parameters), having multiple codecs + negotiated to support codec-switching testing. + * FEC (ULP or Flex) + * Forced codec overshooting (for encoder overshoot emulation on some + mobile devices, when hardware encoder can overshoot target bitrate) +* Audio + * Up to 1 audio track both from caller and callee sides + * Generated audio + * Audio from specified file + * Dumping of captured/rendered audio into file + * Parameterizing of `cricket::AudioOptions` + * Echo emulation +* Injection of various WebRTC components into underlying + *`webrtc::PeerConnection`* or *`webrtc::PeerConnectionFactory`*. You can see + the full list [here][11] +* Scheduling of events, that can happen during the test, for example: + * Changes in network configuration + * User statistics measurements + * Custom defined actions +* User defined statistics reporting via + *`webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::QualityMetricsReporter`* + interface + +## Exported metrics + +### General + +* *`<peer_name>_connected`* - peer successfully established connection to + remote side +* *`cpu_usage`* - CPU usage excluding video analyzer +* *`audio_ahead_ms`* - Used to estimate how much audio and video is out of + sync when the two tracks were from the same source. Stats are polled + periodically during a call. The metric represents how much earlier was audio + played out on average over the call. If, during a stats poll, video is + ahead, then audio_ahead_ms will be equal to 0 for this poll. +* *`video_ahead_ms`* - Used to estimate how much audio and video is out of + sync when the two tracks were from the same source. Stats are polled + periodically during a call. The metric represents how much earlier was video + played out on average over the call. If, during a stats poll, audio is + ahead, then video_ahead_ms will be equal to 0 for this poll. + +### Video + +See documentation for +[*`DefaultVideoQualityAnalyzer`*](default_video_quality_analyzer.md#exported-metrics) + +### Audio + +* *`accelerate_rate`* - when playout is sped up, this counter is increased by + the difference between the number of samples received and the number of + samples played out. If speedup is achieved by removing samples, this will be + the count of samples removed. Rate is calculated as difference between + nearby samples divided on sample interval. +* *`expand_rate`* - the total number of samples that are concealed samples + over time. A concealed sample is a sample that was replaced with synthesized + samples generated locally before being played out. Examples of samples that + have to be concealed are samples from lost packets or samples from packets + that arrive too late to be played out +* *`speech_expand_rate`* - the total number of samples that are concealed + samples minus the total number of concealed samples inserted that are + "silent" over time. Playing out silent samples results in silence or comfort + noise. +* *`preemptive_rate`* - when playout is slowed down, this counter is increased + by the difference between the number of samples received and the number of + samples played out. If playout is slowed down by inserting samples, this + will be the number of inserted samples. Rate is calculated as difference + between nearby samples divided on sample interval. +* *`average_jitter_buffer_delay_ms`* - average size of NetEQ jitter buffer. +* *`preferred_buffer_size_ms`* - preferred size of NetEQ jitter buffer. +* *`visqol_mos`* - proxy for audio quality itself. +* *`asdm_samples`* - measure of how much acceleration/deceleration was in the + signal. +* *`word_error_rate`* - measure of how intelligible the audio was (percent of + words that could not be recognized in output audio). + +### Network + +* *`bytes_sent`* - represents the total number of payload bytes sent on this + PeerConnection, i.e., not including headers or padding +* *`packets_sent`* - represents the total number of packets sent over this + PeerConnection’s transports. +* *`average_send_rate`* - average send rate calculated on bytes_sent divided + by test duration. +* *`payload_bytes_sent`* - total number of bytes sent for all SSRC plus total + number of RTP header and padding bytes sent for all SSRC. This does not + include the size of transport layer headers such as IP or UDP. +* *`sent_packets_loss`* - packets_sent minus corresponding packets_received. +* *`bytes_received`* - represents the total number of bytes received on this + PeerConnection, i.e., not including headers or padding. +* *`packets_received`* - represents the total number of packets received on + this PeerConnection’s transports. +* *`average_receive_rate`* - average receive rate calculated on bytes_received + divided by test duration. +* *`payload_bytes_received`* - total number of bytes received for all SSRC + plus total number of RTP header and padding bytes received for all SSRC. + This does not include the size of transport layer headers such as IP or UDP. + +### Framework stability + +* *`frames_in_flight`* - amount of frames that were captured but wasn't seen + on receiver in the way that also all frames after also weren't seen on + receiver. +* *`bytes_discarded_no_receiver`* - total number of bytes that were received + on network interfaces related to the peer, but destination port was closed. +* *`packets_discarded_no_receiver`* - total number of packets that were + received on network interfaces related to the peer, but destination port was + closed. + +## Examples + +Examples can be found in + +* [peer_connection_e2e_smoke_test.cc][3] +* [pc_full_stack_tests.cc][4] + +## Stats plotting + +### Description + +Stats plotting provides ability to plot statistic collected during the test. +Right now it is used in PeerConnection level framework and give ability to see +how video quality metrics changed during test execution. + +### Usage + +To make any metrics plottable you need: + +1. Collect metric data with [SamplesStatsCounter][5] which internally will + store all intermediate points and timestamps when these points were added. +2. Then you need to report collected data with + [`webrtc::test::PrintResult(...)`][6]. By using these method you will also + specify name of the plottable metric. + +After these steps it will be possible to export your metric for plotting. There +are several options how you can do this: + +1. Use [`webrtc::TestMain::Create()`][7] as `main` function implementation, for + example use [`test/test_main.cc`][8] as `main` function for your test. + + In such case your binary will have flag `--plot`, where you can provide a + list of metrics, that you want to plot or specify `all` to plot all + available metrics. + + If `--plot` is specified, the binary will output metrics data into `stdout`. + Then you need to pipe this `stdout` into python plotter script + [`rtc_tools/metrics_plotter.py`][9], which will plot data. + + Examples: + + ```shell + $ ./out/Default/test_support_unittests \ + --gtest_filter=PeerConnectionE2EQualityTestSmokeTest.Svc \ + --nologs \ + --plot=all \ + | python rtc_tools/metrics_plotter.py + ``` + + ```shell + $ ./out/Default/test_support_unittests \ + --gtest_filter=PeerConnectionE2EQualityTestSmokeTest.Svc \ + --nologs \ + --plot=psnr,ssim \ + | python rtc_tools/metrics_plotter.py + ``` + + Example chart: ![PSNR changes during the test](in_test_psnr_plot.png) + +2. Use API from [`test/testsupport/perf_test.h`][10] directly by invoking + `webrtc::test::PrintPlottableResults(const std::vector<std::string>& + desired_graphs)` to print plottable metrics to stdout. Then as in previous + option you need to pipe result into plotter script. + +[1]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;drc=cbe6e8a2589a925d4c91a2ac2c69201f03de9c39 +[2]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/create_peerconnection_quality_test_fixture.h;drc=cbe6e8a2589a925d4c91a2ac2c69201f03de9c39 +[3]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/pc/e2e/peer_connection_e2e_smoke_test.cc;drc=cbe6e8a2589a925d4c91a2ac2c69201f03de9c39 +[4]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/video/pc_full_stack_tests.cc;drc=cbe6e8a2589a925d4c91a2ac2c69201f03de9c39 +[5]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/numerics/samples_stats_counter.h;drc=cbe6e8a2589a925d4c91a2ac2c69201f03de9c39 +[6]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/testsupport/perf_test.h;l=86;drc=0710b401b1e5b500b8e84946fb657656ba1b58b7 +[7]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/test_main_lib.h;l=23;drc=bcb42f1e4be136c390986a40d9d5cb3ad0de260b +[8]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/test_main.cc;drc=bcb42f1e4be136c390986a40d9d5cb3ad0de260b +[9]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/rtc_tools/metrics_plotter.py;drc=8cc6695652307929edfc877cd64b75cd9ec2d615 +[10]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/test/testsupport/perf_test.h;l=105;drc=0710b401b1e5b500b8e84946fb657656ba1b58b7 +[11]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/test/peerconnection_quality_test_fixture.h;l=272;drc=484acf27231d931dbc99aedce85bc27e06486b96 diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/single_process_encoded_image_data_injector.png b/third_party/libwebrtc/test/pc/e2e/g3doc/single_process_encoded_image_data_injector.png Binary files differnew file mode 100644 index 0000000000..73480bafbe --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/single_process_encoded_image_data_injector.png diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/video_quality_analyzer_pipeline.png b/third_party/libwebrtc/test/pc/e2e/g3doc/video_quality_analyzer_pipeline.png Binary files differnew file mode 100644 index 0000000000..6cddb91110 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/video_quality_analyzer_pipeline.png diff --git a/third_party/libwebrtc/test/pc/e2e/g3doc/vp8_simulcast_offer_modification.png b/third_party/libwebrtc/test/pc/e2e/g3doc/vp8_simulcast_offer_modification.png Binary files differnew file mode 100644 index 0000000000..c7eaa04c0e --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/g3doc/vp8_simulcast_offer_modification.png diff --git a/third_party/libwebrtc/test/pc/e2e/media/media_helper.cc b/third_party/libwebrtc/test/pc/e2e/media/media_helper.cc new file mode 100644 index 0000000000..e945bd4dae --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/media/media_helper.cc @@ -0,0 +1,128 @@ +/* + * 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/pc/e2e/media/media_helper.h" + +#include <string> +#include <utility> + +#include "absl/types/variant.h" +#include "api/media_stream_interface.h" +#include "api/test/create_frame_generator.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/peer_configurer.h" +#include "test/frame_generator_capturer.h" +#include "test/platform_video_capturer.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +void MediaHelper::MaybeAddAudio(TestPeer* peer) { + if (!peer->params().audio_config) { + return; + } + const AudioConfig& audio_config = peer->params().audio_config.value(); + rtc::scoped_refptr<webrtc::AudioSourceInterface> source = + peer->pc_factory()->CreateAudioSource(audio_config.audio_options); + rtc::scoped_refptr<AudioTrackInterface> track = + peer->pc_factory()->CreateAudioTrack(*audio_config.stream_label, + source.get()); + std::string sync_group = audio_config.sync_group + ? audio_config.sync_group.value() + : audio_config.stream_label.value() + "-sync"; + peer->AddTrack(track, {sync_group, *audio_config.stream_label}); +} + +std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>> +MediaHelper::MaybeAddVideo(TestPeer* peer) { + // Params here valid because of pre-run validation. + const Params& params = peer->params(); + const ConfigurableParams& configurable_params = peer->configurable_params(); + std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>> out; + for (size_t i = 0; i < configurable_params.video_configs.size(); ++i) { + const VideoConfig& video_config = configurable_params.video_configs[i]; + // Setup input video source into peer connection. + std::unique_ptr<test::TestVideoCapturer> capturer = CreateVideoCapturer( + video_config, peer->ReleaseVideoSource(i), + video_quality_analyzer_injection_helper_->CreateFramePreprocessor( + params.name.value(), video_config)); + bool is_screencast = + video_config.content_hint == VideoTrackInterface::ContentHint::kText || + video_config.content_hint == + VideoTrackInterface::ContentHint::kDetailed; + rtc::scoped_refptr<TestVideoCapturerVideoTrackSource> source = + rtc::make_ref_counted<TestVideoCapturerVideoTrackSource>( + std::move(capturer), is_screencast); + out.push_back(source); + RTC_LOG(LS_INFO) << "Adding video with video_config.stream_label=" + << video_config.stream_label.value(); + rtc::scoped_refptr<VideoTrackInterface> track = + peer->pc_factory()->CreateVideoTrack(video_config.stream_label.value(), + source.get()); + if (video_config.content_hint.has_value()) { + track->set_content_hint(video_config.content_hint.value()); + } + std::string sync_group = video_config.sync_group + ? video_config.sync_group.value() + : video_config.stream_label.value() + "-sync"; + RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> sender = + peer->AddTrack(track, {sync_group, *video_config.stream_label}); + RTC_CHECK(sender.ok()); + if (video_config.temporal_layers_count || + video_config.degradation_preference) { + RtpParameters rtp_parameters = sender.value()->GetParameters(); + if (video_config.temporal_layers_count) { + for (auto& encoding_parameters : rtp_parameters.encodings) { + encoding_parameters.num_temporal_layers = + video_config.temporal_layers_count; + } + } + if (video_config.degradation_preference) { + rtp_parameters.degradation_preference = + video_config.degradation_preference; + } + RTCError res = sender.value()->SetParameters(rtp_parameters); + RTC_CHECK(res.ok()) << "Failed to set RTP parameters"; + } + } + return out; +} + +std::unique_ptr<test::TestVideoCapturer> MediaHelper::CreateVideoCapturer( + const VideoConfig& video_config, + PeerConfigurer::VideoSource source, + std::unique_ptr<test::TestVideoCapturer::FramePreprocessor> + frame_preprocessor) { + CapturingDeviceIndex* capturing_device_index = + absl::get_if<CapturingDeviceIndex>(&source); + if (capturing_device_index != nullptr) { + std::unique_ptr<test::TestVideoCapturer> capturer = + test::CreateVideoCapturer(video_config.width, video_config.height, + video_config.fps, + static_cast<size_t>(*capturing_device_index)); + RTC_CHECK(capturer) + << "Failed to obtain input stream from capturing device #" + << *capturing_device_index; + capturer->SetFramePreprocessor(std::move(frame_preprocessor)); + return capturer; + } + + auto capturer = std::make_unique<test::FrameGeneratorCapturer>( + clock_, + absl::get<std::unique_ptr<test::FrameGeneratorInterface>>( + std::move(source)), + video_config.fps, *task_queue_factory_); + capturer->SetFramePreprocessor(std::move(frame_preprocessor)); + capturer->Init(); + return capturer; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/media/media_helper.h b/third_party/libwebrtc/test/pc/e2e/media/media_helper.h new file mode 100644 index 0000000000..2d163d009e --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/media/media_helper.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_PC_E2E_MEDIA_MEDIA_HELPER_H_ +#define TEST_PC_E2E_MEDIA_MEDIA_HELPER_H_ + +#include <memory> +#include <vector> + +#include "api/test/frame_generator_interface.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/peer_configurer.h" +#include "test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h" +#include "test/pc/e2e/media/test_video_capturer_video_track_source.h" +#include "test/pc/e2e/test_peer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class MediaHelper { + public: + MediaHelper(VideoQualityAnalyzerInjectionHelper* + video_quality_analyzer_injection_helper, + TaskQueueFactory* task_queue_factory, + Clock* clock) + : clock_(clock), + task_queue_factory_(task_queue_factory), + video_quality_analyzer_injection_helper_( + video_quality_analyzer_injection_helper) {} + + void MaybeAddAudio(TestPeer* peer); + + std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>> + MaybeAddVideo(TestPeer* peer); + + private: + std::unique_ptr<test::TestVideoCapturer> CreateVideoCapturer( + const VideoConfig& video_config, + PeerConfigurer::VideoSource source, + std::unique_ptr<test::TestVideoCapturer::FramePreprocessor> + frame_preprocessor); + + Clock* const clock_; + TaskQueueFactory* const task_queue_factory_; + VideoQualityAnalyzerInjectionHelper* video_quality_analyzer_injection_helper_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_MEDIA_MEDIA_HELPER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/media/test_video_capturer_video_track_source.h b/third_party/libwebrtc/test/pc/e2e/media/test_video_capturer_video_track_source.h new file mode 100644 index 0000000000..c883a2e8e9 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/media/test_video_capturer_video_track_source.h @@ -0,0 +1,55 @@ +/* + * 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_PC_E2E_MEDIA_TEST_VIDEO_CAPTURER_VIDEO_TRACK_SOURCE_H_ +#define TEST_PC_E2E_MEDIA_TEST_VIDEO_CAPTURER_VIDEO_TRACK_SOURCE_H_ + +#include <memory> +#include <utility> + +#include "api/video/video_frame.h" +#include "api/video/video_source_interface.h" +#include "pc/video_track_source.h" +#include "test/test_video_capturer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class TestVideoCapturerVideoTrackSource : public VideoTrackSource { + public: + TestVideoCapturerVideoTrackSource( + std::unique_ptr<test::TestVideoCapturer> video_capturer, + bool is_screencast) + : VideoTrackSource(/*remote=*/false), + video_capturer_(std::move(video_capturer)), + is_screencast_(is_screencast) {} + + ~TestVideoCapturerVideoTrackSource() = default; + + void Start() { SetState(kLive); } + + void Stop() { SetState(kMuted); } + + bool is_screencast() const override { return is_screencast_; } + + protected: + rtc::VideoSourceInterface<VideoFrame>* source() override { + return video_capturer_.get(); + } + + private: + std::unique_ptr<test::TestVideoCapturer> video_capturer_; + const bool is_screencast_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_MEDIA_TEST_VIDEO_CAPTURER_VIDEO_TRACK_SOURCE_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/metric_metadata_keys.h b/third_party/libwebrtc/test/pc/e2e/metric_metadata_keys.h new file mode 100644 index 0000000000..fbcd3b90fe --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/metric_metadata_keys.h @@ -0,0 +1,60 @@ +/* + * 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_PC_E2E_METRIC_METADATA_KEYS_H_ +#define TEST_PC_E2E_METRIC_METADATA_KEYS_H_ + +#include <string> + +namespace webrtc { +namespace webrtc_pc_e2e { + +// All metadata fields are present only if applicable for particular metric. +class MetricMetadataKey { + public: + // Represents on peer with whom the metric is associated. + static constexpr char kPeerMetadataKey[] = "peer"; + // Represents sender of the media stream. + static constexpr char kSenderMetadataKey[] = "sender"; + // Represents receiver of the media stream. + static constexpr char kReceiverMetadataKey[] = "receiver"; + // Represents name of the audio stream. + static constexpr char kAudioStreamMetadataKey[] = "audio_stream"; + // Represents name of the video stream. + static constexpr char kVideoStreamMetadataKey[] = "video_stream"; + // Represents name of the sync group to which stream belongs. + static constexpr char kPeerSyncGroupMetadataKey[] = "peer_sync_group"; + // Represents the test name (without any peer and stream data appended to it + // as it currently happens with the webrtc.test_metrics.Metric.test_case + // field). This metadata is temporary and it will be removed once this + // information is moved to webrtc.test_metrics.Metric.test_case. + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + static constexpr char kExperimentalTestNameMetadataKey[] = + "experimental_test_name"; + // Represents index of a video spatial layer to which metric belongs. + static constexpr char kSpatialLayerMetadataKey[] = "spatial_layer"; + + private: + MetricMetadataKey() = default; +}; + +// All metadata fields are presented only if applicable for particular metric. +class SampleMetadataKey { + public: + // Represents a frame ID with which data point is associated. + static constexpr char kFrameIdMetadataKey[] = "frame_id"; + + private: + SampleMetadataKey() = default; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_METRIC_METADATA_KEYS_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc new file mode 100644 index 0000000000..0bb28f0847 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.cc @@ -0,0 +1,183 @@ +/* + * 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/pc/e2e/network_quality_metrics_reporter.h" + +#include <utility> + +#include "api/stats/rtc_stats.h" +#include "api/stats/rtcstats_objects.h" +#include "api/test/metrics/metric.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; + +constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1); + +// Field trial which controls whether to report standard-compliant bytes +// sent/received per stream. If enabled, padding and headers are not included +// in bytes sent or received. +constexpr char kUseStandardBytesStats[] = "WebRTC-UseStandardBytesStats"; + +} // namespace + +NetworkQualityMetricsReporter::NetworkQualityMetricsReporter( + EmulatedNetworkManagerInterface* alice_network, + EmulatedNetworkManagerInterface* bob_network, + test::MetricsLogger* metrics_logger) + : alice_network_(alice_network), + bob_network_(bob_network), + metrics_logger_(metrics_logger) { + RTC_CHECK(metrics_logger_); +} + +void NetworkQualityMetricsReporter::Start( + absl::string_view test_case_name, + const TrackIdStreamInfoMap* /*reporter_helper*/) { + test_case_name_ = std::string(test_case_name); + // Check that network stats are clean before test execution. + EmulatedNetworkStats alice_stats = PopulateStats(alice_network_); + RTC_CHECK_EQ(alice_stats.overall_outgoing_stats.packets_sent, 0); + RTC_CHECK_EQ(alice_stats.overall_incoming_stats.packets_received, 0); + EmulatedNetworkStats bob_stats = PopulateStats(bob_network_); + RTC_CHECK_EQ(bob_stats.overall_outgoing_stats.packets_sent, 0); + RTC_CHECK_EQ(bob_stats.overall_incoming_stats.packets_received, 0); +} + +void NetworkQualityMetricsReporter::OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) { + DataSize payload_received = DataSize::Zero(); + DataSize payload_sent = DataSize::Zero(); + + auto inbound_stats = report->GetStatsOfType<RTCInboundRTPStreamStats>(); + for (const auto& stat : inbound_stats) { + payload_received += + DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul) + + stat->header_bytes_received.ValueOrDefault(0ul)); + } + + auto outbound_stats = report->GetStatsOfType<RTCOutboundRTPStreamStats>(); + for (const auto& stat : outbound_stats) { + payload_sent += + DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul) + + stat->header_bytes_sent.ValueOrDefault(0ul)); + } + + MutexLock lock(&lock_); + PCStats& stats = pc_stats_[std::string(pc_label)]; + stats.payload_received = payload_received; + stats.payload_sent = payload_sent; +} + +void NetworkQualityMetricsReporter::StopAndReportResults() { + EmulatedNetworkStats alice_stats = PopulateStats(alice_network_); + EmulatedNetworkStats bob_stats = PopulateStats(bob_network_); + int64_t alice_packets_loss = + alice_stats.overall_outgoing_stats.packets_sent - + bob_stats.overall_incoming_stats.packets_received; + int64_t bob_packets_loss = + bob_stats.overall_outgoing_stats.packets_sent - + alice_stats.overall_incoming_stats.packets_received; + ReportStats("alice", alice_stats, alice_packets_loss); + ReportStats("bob", bob_stats, bob_packets_loss); + + if (!webrtc::field_trial::IsEnabled(kUseStandardBytesStats)) { + RTC_LOG(LS_ERROR) + << "Non-standard GetStats; \"payload\" counts include RTP headers"; + } + + MutexLock lock(&lock_); + for (const auto& pair : pc_stats_) { + ReportPCStats(pair.first, pair.second); + } +} + +EmulatedNetworkStats NetworkQualityMetricsReporter::PopulateStats( + EmulatedNetworkManagerInterface* network) { + rtc::Event wait; + EmulatedNetworkStats stats; + network->GetStats([&](EmulatedNetworkStats s) { + stats = std::move(s); + wait.Set(); + }); + bool stats_received = wait.Wait(kStatsWaitTimeout); + RTC_CHECK(stats_received); + return stats; +} + +void NetworkQualityMetricsReporter::ReportStats( + const std::string& network_label, + const EmulatedNetworkStats& stats, + int64_t packet_loss) { + metrics_logger_->LogSingleValueMetric( + "bytes_sent", GetTestCaseName(network_label), + stats.overall_outgoing_stats.bytes_sent.bytes(), Unit::kBytes, + ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "packets_sent", GetTestCaseName(network_label), + stats.overall_outgoing_stats.packets_sent, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "average_send_rate", GetTestCaseName(network_label), + stats.overall_outgoing_stats.packets_sent >= 2 + ? stats.overall_outgoing_stats.AverageSendRate().kbps<double>() + : 0, + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "bytes_discarded_no_receiver", GetTestCaseName(network_label), + stats.overall_incoming_stats.bytes_discarded_no_receiver.bytes(), + Unit::kBytes, ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "packets_discarded_no_receiver", GetTestCaseName(network_label), + stats.overall_incoming_stats.packets_discarded_no_receiver, + Unit::kUnitless, ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "bytes_received", GetTestCaseName(network_label), + stats.overall_incoming_stats.bytes_received.bytes(), Unit::kBytes, + ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "packets_received", GetTestCaseName(network_label), + stats.overall_incoming_stats.packets_received, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "average_receive_rate", GetTestCaseName(network_label), + stats.overall_incoming_stats.packets_received >= 2 + ? stats.overall_incoming_stats.AverageReceiveRate().kbps<double>() + : 0, + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "sent_packets_loss", GetTestCaseName(network_label), packet_loss, + Unit::kUnitless, ImprovementDirection::kNeitherIsBetter); +} + +void NetworkQualityMetricsReporter::ReportPCStats(const std::string& pc_label, + const PCStats& stats) { + metrics_logger_->LogSingleValueMetric( + "payload_bytes_received", pc_label, stats.payload_received.bytes(), + Unit::kBytes, ImprovementDirection::kNeitherIsBetter); + metrics_logger_->LogSingleValueMetric( + "payload_bytes_sent", pc_label, stats.payload_sent.bytes(), Unit::kBytes, + ImprovementDirection::kNeitherIsBetter); +} + +std::string NetworkQualityMetricsReporter::GetTestCaseName( + const std::string& network_label) const { + return test_case_name_ + "/" + network_label; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.h b/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.h new file mode 100644 index 0000000000..ed894bcf54 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/network_quality_metrics_reporter.h @@ -0,0 +1,72 @@ +/* + * 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_PC_E2E_NETWORK_QUALITY_METRICS_REPORTER_H_ +#define TEST_PC_E2E_NETWORK_QUALITY_METRICS_REPORTER_H_ + +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/test/track_id_stream_info_map.h" +#include "api/units/data_size.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class NetworkQualityMetricsReporter + : public PeerConnectionE2EQualityTestFixture::QualityMetricsReporter { + public: + NetworkQualityMetricsReporter(EmulatedNetworkManagerInterface* alice_network, + EmulatedNetworkManagerInterface* bob_network, + test::MetricsLogger* metrics_logger); + ~NetworkQualityMetricsReporter() override = default; + + // Network stats must be empty when this method will be invoked. + void Start(absl::string_view test_case_name, + const TrackIdStreamInfoMap* reporter_helper) override; + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + void StopAndReportResults() override; + + private: + struct PCStats { + // TODO(nisse): Separate audio and video counters. Depends on standard stat + // counters, enabled by field trial "WebRTC-UseStandardBytesStats". + DataSize payload_received = DataSize::Zero(); + DataSize payload_sent = DataSize::Zero(); + }; + + static EmulatedNetworkStats PopulateStats( + EmulatedNetworkManagerInterface* network); + void ReportStats(const std::string& network_label, + const EmulatedNetworkStats& stats, + int64_t packet_loss); + void ReportPCStats(const std::string& pc_label, const PCStats& stats); + std::string GetTestCaseName(const std::string& network_label) const; + + std::string test_case_name_; + + EmulatedNetworkManagerInterface* const alice_network_; + EmulatedNetworkManagerInterface* const bob_network_; + test::MetricsLogger* const metrics_logger_; + Mutex lock_; + std::map<std::string, PCStats> pc_stats_ RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_NETWORK_QUALITY_METRICS_REPORTER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/peer_connection_e2e_smoke_test.cc b/third_party/libwebrtc/test/pc/e2e/peer_connection_e2e_smoke_test.cc new file mode 100644 index 0000000000..0e7993e5be --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_connection_e2e_smoke_test.cc @@ -0,0 +1,536 @@ +/* + * 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 <cstdint> +#include <memory> +#include <string> + +#include "api/media_stream_interface.h" +#include "api/test/create_network_emulation_manager.h" +#include "api/test/create_peer_connection_quality_test_frame_generator.h" +#include "api/test/create_peerconnection_quality_test_fixture.h" +#include "api/test/metrics/global_metrics_logger_and_exporter.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "call/simulated_network.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h" +#include "test/pc/e2e/stats_based_network_quality_metrics_reporter.h" +#include "test/testsupport/file_utils.h" + +#if defined(WEBRTC_MAC) || defined(WEBRTC_IOS) +#include "modules/video_coding/codecs/test/objc_codec_factory_helper.h" // nogncheck +#endif + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +class PeerConnectionE2EQualityTestSmokeTest : public ::testing::Test { + public: + void SetUp() override { + network_emulation_ = CreateNetworkEmulationManager(); + auto video_quality_analyzer = std::make_unique<DefaultVideoQualityAnalyzer>( + network_emulation_->time_controller()->GetClock(), + test::GetGlobalMetricsLogger()); + video_quality_analyzer_ = video_quality_analyzer.get(); + fixture_ = CreatePeerConnectionE2EQualityTestFixture( + testing::UnitTest::GetInstance()->current_test_info()->name(), + *network_emulation_->time_controller(), + /*audio_quality_analyzer=*/nullptr, std::move(video_quality_analyzer)); + } + + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + CreateNetwork() { + EmulatedNetworkNode* alice_node = network_emulation_->CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + EmulatedNetworkNode* bob_node = network_emulation_->CreateEmulatedNode( + std::make_unique<SimulatedNetwork>(BuiltInNetworkBehaviorConfig())); + + EmulatedEndpoint* alice_endpoint = + network_emulation_->CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_emulation_->CreateEndpoint(EmulatedEndpointConfig()); + + network_emulation_->CreateRoute(alice_endpoint, {alice_node}, bob_endpoint); + network_emulation_->CreateRoute(bob_endpoint, {bob_node}, alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + network_emulation_->CreateEmulatedNetworkManagerInterface( + {alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + network_emulation_->CreateEmulatedNetworkManagerInterface( + {bob_endpoint}); + + return std::make_pair(alice_network, bob_network); + } + + void AddPeer(EmulatedNetworkManagerInterface* network, + rtc::FunctionView<void(PeerConfigurer*)> update_configurer) { + auto configurer = + std::make_unique<PeerConfigurer>(network->network_dependencies()); + update_configurer(configurer.get()); + fixture_->AddPeer(std::move(configurer)); + } + + void RunAndCheckEachVideoStreamReceivedFrames(const RunParams& run_params) { + fixture_->Run(run_params); + + EXPECT_GE(fixture_->GetRealTestDuration(), run_params.run_duration); + VideoStreamsInfo known_streams = video_quality_analyzer_->GetKnownStreams(); + for (const StatsKey& stream_key : known_streams.GetStatsKeys()) { + FrameCounters stream_conters = + video_quality_analyzer_->GetPerStreamCounters().at(stream_key); + // On some devices the pipeline can be too slow, so we actually can't + // force real constraints here. Lets just check, that at least 1 + // frame passed whole pipeline. + int64_t expected_min_fps = run_params.run_duration.seconds() * 15; + EXPECT_GE(stream_conters.captured, expected_min_fps) + << stream_key.ToString(); + EXPECT_GE(stream_conters.pre_encoded, 1) << stream_key.ToString(); + EXPECT_GE(stream_conters.encoded, 1) << stream_key.ToString(); + EXPECT_GE(stream_conters.received, 1) << stream_key.ToString(); + EXPECT_GE(stream_conters.decoded, 1) << stream_key.ToString(); + EXPECT_GE(stream_conters.rendered, 1) << stream_key.ToString(); + } + } + + NetworkEmulationManager* network_emulation() { + return network_emulation_.get(); + } + + PeerConnectionE2EQualityTestFixture* fixture() { return fixture_.get(); } + + private: + std::unique_ptr<NetworkEmulationManager> network_emulation_; + DefaultVideoQualityAnalyzer* video_quality_analyzer_; + std::unique_ptr<PeerConnectionE2EQualityTestFixture> fixture_; +}; + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_Smoke DISABLED_Smoke +#else +#define MAYBE_Smoke Smoke +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_Smoke) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + VideoConfig video(160, 120, 15); + video.stream_label = "alice-video"; + video.sync_group = "alice-media"; + alice->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "alice-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + audio.sampling_frequency_in_hz = 48000; + audio.sync_group = "alice-media"; + alice->SetAudioConfig(std::move(audio)); + alice->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + + alice->SetUseFlexFEC(true); + alice->SetUseUlpFEC(true); + alice->SetVideoEncoderBitrateMultiplier(1.1); + }); + AddPeer(network_links.second, [](PeerConfigurer* charlie) { + charlie->SetName("charlie"); + VideoConfig video(160, 120, 15); + video.stream_label = "charlie-video"; + video.temporal_layers_count = 2; + charlie->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "charlie-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_bob_source", "wav"); + charlie->SetAudioConfig(std::move(audio)); + charlie->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + + charlie->SetUseFlexFEC(true); + charlie->SetUseUlpFEC(true); + charlie->SetVideoEncoderBitrateMultiplier(1.1); + }); + fixture()->AddQualityMetricsReporter( + std::make_unique<StatsBasedNetworkQualityMetricsReporter>( + std::map<std::string, std::vector<EmulatedEndpoint*>>( + {{"alice", network_links.first->endpoints()}, + {"charlie", network_links.second->endpoints()}}), + network_emulation(), test::GetGlobalMetricsLogger())); + RunParams run_params(TimeDelta::Seconds(2)); + run_params.enable_flex_fec_support = true; + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_Smoke DISABLED_Smoke +#else +#define MAYBE_SendAndReceivePacketsOnOneThread \ + SmokeSendAndReceivePacketsOnOneThread +#endif +// Only use the network thread for sending and receiving packets. +// The one and only network thread is used as a worker thread in all +// PeerConnections. Pacing when sending packets is done on the worker thread. +// See bugs.webrtc.org/14502. +TEST_F(PeerConnectionE2EQualityTestSmokeTest, + MAYBE_SendAndReceivePacketsOnOneThread) { + test::ScopedFieldTrials trials( + std::string(field_trial::GetFieldTrialString()) + + "WebRTC-SendPacketsOnWorkerThread/Enabled/"); + + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + // Peerconnection use the network thread as the worker thread. + alice->SetUseNetworkThreadAsWorkerThread(); + VideoConfig video(160, 120, 15); + video.stream_label = "alice-video"; + video.sync_group = "alice-media"; + alice->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "alice-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + audio.sampling_frequency_in_hz = 48000; + audio.sync_group = "alice-media"; + alice->SetAudioConfig(std::move(audio)); + alice->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + }); + AddPeer(network_links.second, [](PeerConfigurer* charlie) { + // Peerconnection use the network thread as the worker thread. + charlie->SetUseNetworkThreadAsWorkerThread(); + charlie->SetName("charlie"); + VideoConfig video(160, 120, 15); + video.stream_label = "charlie-video"; + video.temporal_layers_count = 2; + charlie->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "charlie-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_bob_source", "wav"); + charlie->SetAudioConfig(std::move(audio)); + charlie->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + charlie->SetVideoEncoderBitrateMultiplier(1.1); + }); + fixture()->AddQualityMetricsReporter( + std::make_unique<StatsBasedNetworkQualityMetricsReporter>( + std::map<std::string, std::vector<EmulatedEndpoint*>>( + {{"alice", network_links.first->endpoints()}, + {"charlie", network_links.second->endpoints()}}), + network_emulation(), test::GetGlobalMetricsLogger())); + RunParams run_params(TimeDelta::Seconds(2)); + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +#if defined(WEBRTC_MAC) || defined(WEBRTC_IOS) +TEST_F(PeerConnectionE2EQualityTestSmokeTest, SmokeH264) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + + AddPeer(network_links.first, [](PeerConfigurer* alice) { + VideoConfig video(160, 120, 15); + video.stream_label = "alice-video"; + video.sync_group = "alice-media"; + alice->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "alice-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + audio.sampling_frequency_in_hz = 48000; + audio.sync_group = "alice-media"; + alice->SetAudioConfig(std::move(audio)); + alice->SetVideoCodecs({VideoCodecConfig(cricket::kH264CodecName)}); + alice->SetVideoEncoderFactory(webrtc::test::CreateObjCEncoderFactory()); + alice->SetVideoDecoderFactory(webrtc::test::CreateObjCDecoderFactory()); + }); + AddPeer(network_links.second, [](PeerConfigurer* charlie) { + charlie->SetName("charlie"); + VideoConfig video(160, 120, 15); + video.stream_label = "charlie-video"; + video.temporal_layers_count = 2; + charlie->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "charlie-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_bob_source", "wav"); + charlie->SetAudioConfig(std::move(audio)); + charlie->SetVideoCodecs({VideoCodecConfig(cricket::kH264CodecName)}); + charlie->SetVideoEncoderFactory(webrtc::test::CreateObjCEncoderFactory()); + charlie->SetVideoDecoderFactory(webrtc::test::CreateObjCDecoderFactory()); + }); + + fixture()->AddQualityMetricsReporter( + std::make_unique<StatsBasedNetworkQualityMetricsReporter>( + std::map<std::string, std::vector<EmulatedEndpoint*>>( + {{"alice", network_links.first->endpoints()}, + {"charlie", network_links.second->endpoints()}}), + network_emulation(), test::GetGlobalMetricsLogger())); + RunParams run_params(TimeDelta::Seconds(2)); + run_params.enable_flex_fec_support = true; + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} +#endif + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_ChangeNetworkConditions DISABLED_ChangeNetworkConditions +#else +#define MAYBE_ChangeNetworkConditions ChangeNetworkConditions +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_ChangeNetworkConditions) { + NetworkEmulationManager::SimulatedNetworkNode alice_node = + network_emulation() + ->NodeBuilder() + .config(BuiltInNetworkBehaviorConfig()) + .Build(); + NetworkEmulationManager::SimulatedNetworkNode bob_node = + network_emulation() + ->NodeBuilder() + .config(BuiltInNetworkBehaviorConfig()) + .Build(); + + EmulatedEndpoint* alice_endpoint = + network_emulation()->CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_emulation()->CreateEndpoint(EmulatedEndpointConfig()); + + network_emulation()->CreateRoute(alice_endpoint, {alice_node.node}, + bob_endpoint); + network_emulation()->CreateRoute(bob_endpoint, {bob_node.node}, + alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + network_emulation()->CreateEmulatedNetworkManagerInterface( + {alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + network_emulation()->CreateEmulatedNetworkManagerInterface( + {bob_endpoint}); + + AddPeer(alice_network, [](PeerConfigurer* alice) { + VideoConfig video(160, 120, 15); + video.stream_label = "alice-video"; + video.sync_group = "alice-media"; + alice->AddVideoConfig(std::move(video)); + alice->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + + alice->SetUseFlexFEC(true); + alice->SetUseUlpFEC(true); + alice->SetVideoEncoderBitrateMultiplier(1.1); + }); + AddPeer(bob_network, [](PeerConfigurer* bob) { + bob->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + + bob->SetUseFlexFEC(true); + bob->SetUseUlpFEC(true); + bob->SetVideoEncoderBitrateMultiplier(1.1); + }); + fixture()->AddQualityMetricsReporter( + std::make_unique<StatsBasedNetworkQualityMetricsReporter>( + std::map<std::string, std::vector<EmulatedEndpoint*>>( + {{"alice", alice_network->endpoints()}, + {"bob", bob_network->endpoints()}}), + network_emulation(), test::GetGlobalMetricsLogger())); + + fixture()->ExecuteAt(TimeDelta::Seconds(1), [alice_node](TimeDelta) { + BuiltInNetworkBehaviorConfig config; + config.loss_percent = 5; + alice_node.simulation->SetConfig(config); + }); + + RunParams run_params(TimeDelta::Seconds(2)); + run_params.enable_flex_fec_support = true; + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_Screenshare DISABLED_Screenshare +#else +#define MAYBE_Screenshare Screenshare +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_Screenshare) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + VideoConfig screenshare(320, 180, 30); + screenshare.stream_label = "alice-screenshare"; + screenshare.content_hint = VideoTrackInterface::ContentHint::kText; + ScreenShareConfig screen_share_config = + ScreenShareConfig(TimeDelta::Seconds(2)); + screen_share_config.scrolling_params = + ScrollingParams{.duration = TimeDelta::Millis(1800)}; + auto screen_share_frame_generator = + CreateScreenShareFrameGenerator(screenshare, screen_share_config); + alice->AddVideoConfig(std::move(screenshare), + std::move(screen_share_frame_generator)); + }); + AddPeer(network_links.second, [](PeerConfigurer* bob) {}); + RunAndCheckEachVideoStreamReceivedFrames(RunParams(TimeDelta::Seconds(2))); +} + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_Echo DISABLED_Echo +#else +#define MAYBE_Echo Echo +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_Echo) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + AudioConfig audio; + audio.stream_label = "alice-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + audio.sampling_frequency_in_hz = 48000; + alice->SetAudioConfig(std::move(audio)); + }); + AddPeer(network_links.second, [](PeerConfigurer* bob) { + AudioConfig audio; + audio.stream_label = "bob-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_bob_source", "wav"); + bob->SetAudioConfig(std::move(audio)); + }); + RunParams run_params(TimeDelta::Seconds(2)); + run_params.echo_emulation_config = EchoEmulationConfig(); + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_Simulcast DISABLED_Simulcast +#else +#define MAYBE_Simulcast Simulcast +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_Simulcast) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + VideoConfig simulcast(1280, 720, 15); + simulcast.stream_label = "alice-simulcast"; + simulcast.simulcast_config = VideoSimulcastConfig(2); + simulcast.emulated_sfu_config = EmulatedSFUConfig(0); + alice->AddVideoConfig(std::move(simulcast)); + + AudioConfig audio; + audio.stream_label = "alice-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + alice->SetAudioConfig(std::move(audio)); + }); + AddPeer(network_links.second, [](PeerConfigurer* bob) {}); + RunParams run_params(TimeDelta::Seconds(2)); + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_Svc DISABLED_Svc +#else +#define MAYBE_Svc Svc +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_Svc) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + VideoConfig simulcast("alice-svc", 1280, 720, 15); + // Because we have network with packets loss we can analyze only the + // highest spatial layer in SVC mode. + simulcast.simulcast_config = VideoSimulcastConfig(2); + simulcast.emulated_sfu_config = EmulatedSFUConfig(1); + alice->AddVideoConfig(std::move(simulcast)); + + AudioConfig audio("alice-audio"); + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + alice->SetAudioConfig(std::move(audio)); + alice->SetVideoCodecs({VideoCodecConfig(cricket::kVp9CodecName)}); + }); + AddPeer(network_links.second, [](PeerConfigurer* bob) { + bob->SetVideoCodecs({VideoCodecConfig(cricket::kVp9CodecName)}); + }); + RunParams run_params(TimeDelta::Seconds(2)); + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +// IOS debug builds can be quite slow, disabling to avoid issues with timeouts. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_HighBitrate DISABLED_HighBitrate +#else +#define MAYBE_HighBitrate HighBitrate +#endif +TEST_F(PeerConnectionE2EQualityTestSmokeTest, MAYBE_HighBitrate) { + std::pair<EmulatedNetworkManagerInterface*, EmulatedNetworkManagerInterface*> + network_links = CreateNetwork(); + AddPeer(network_links.first, [](PeerConfigurer* alice) { + BitrateSettings bitrate_settings; + bitrate_settings.start_bitrate_bps = 3'000'000; + bitrate_settings.max_bitrate_bps = 3'000'000; + alice->SetBitrateSettings(bitrate_settings); + VideoConfig video(800, 600, 15); + video.stream_label = "alice-video"; + RtpEncodingParameters encoding_parameters; + encoding_parameters.min_bitrate_bps = 500'000; + encoding_parameters.max_bitrate_bps = 3'000'000; + video.encoding_params.push_back(std::move(encoding_parameters)); + alice->AddVideoConfig(std::move(video)); + + AudioConfig audio; + audio.stream_label = "alice-audio"; + audio.mode = AudioConfig::Mode::kFile; + audio.input_file_name = + test::ResourcePath("pc_quality_smoke_test_alice_source", "wav"); + audio.sampling_frequency_in_hz = 48000; + alice->SetAudioConfig(std::move(audio)); + alice->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + }); + AddPeer(network_links.second, [](PeerConfigurer* bob) { + bob->SetVideoCodecs( + {VideoCodecConfig(cricket::kVp9CodecName, {{"profile-id", "0"}})}); + }); + RunParams run_params(TimeDelta::Seconds(2)); + RunAndCheckEachVideoStreamReceivedFrames(run_params); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc new file mode 100644 index 0000000000..83613118f9 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.cc @@ -0,0 +1,763 @@ +/* + * 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/pc/e2e/peer_connection_quality_test.h" + +#include <algorithm> +#include <memory> +#include <set> +#include <utility> + +#include "absl/strings/string_view.h" +#include "api/jsep.h" +#include "api/media_stream_interface.h" +#include "api/peer_connection_interface.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/rtc_event_log_output_file.h" +#include "api/scoped_refptr.h" +#include "api/test/metrics/metric.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/time_controller.h" +#include "api/test/video_quality_analyzer_interface.h" +#include "pc/sdp_utils.h" +#include "pc/test/mock_peer_connection_observers.h" +#include "rtc_base/gunit.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/task_queue_for_test.h" +#include "system_wrappers/include/cpu_info.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" +#include "test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h" +#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h" +#include "test/pc/e2e/analyzer/video/video_frame_tracking_id_injector.h" +#include "test/pc/e2e/analyzer/video/video_quality_metrics_reporter.h" +#include "test/pc/e2e/cross_media_metrics_reporter.h" +#include "test/pc/e2e/metric_metadata_keys.h" +#include "test/pc/e2e/peer_params_preprocessor.h" +#include "test/pc/e2e/stats_poller.h" +#include "test/pc/e2e/test_peer_factory.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; + +constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(10); +constexpr char kSignalThreadName[] = "signaling_thread"; +// 1 signaling, 2 network, 2 worker and 2 extra for codecs etc. +constexpr int kPeerConnectionUsedThreads = 7; +// Framework has extra thread for network layer and extra thread for peer +// connection stats polling. +constexpr int kFrameworkUsedThreads = 2; +constexpr int kMaxVideoAnalyzerThreads = 8; + +constexpr TimeDelta kStatsUpdateInterval = TimeDelta::Seconds(1); + +constexpr TimeDelta kAliveMessageLogInterval = TimeDelta::Seconds(30); + +constexpr TimeDelta kQuickTestModeRunDuration = TimeDelta::Millis(100); + +// Field trials to enable Flex FEC advertising and receiving. +constexpr char kFlexFecEnabledFieldTrials[] = + "WebRTC-FlexFEC-03-Advertised/Enabled/WebRTC-FlexFEC-03/Enabled/"; +constexpr char kUseStandardsBytesStats[] = + "WebRTC-UseStandardBytesStats/Enabled/"; + +class FixturePeerConnectionObserver : public MockPeerConnectionObserver { + public: + // `on_track_callback` will be called when any new track will be added to peer + // connection. + // `on_connected_callback` will be called when peer connection will come to + // either connected or completed state. Client should notice that in the case + // of reconnect this callback can be called again, so it should be tolerant + // to such behavior. + FixturePeerConnectionObserver( + std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)> + on_track_callback, + std::function<void()> on_connected_callback) + : on_track_callback_(std::move(on_track_callback)), + on_connected_callback_(std::move(on_connected_callback)) {} + + void OnTrack( + rtc::scoped_refptr<RtpTransceiverInterface> transceiver) override { + MockPeerConnectionObserver::OnTrack(transceiver); + on_track_callback_(transceiver); + } + + void OnIceConnectionChange( + PeerConnectionInterface::IceConnectionState new_state) override { + MockPeerConnectionObserver::OnIceConnectionChange(new_state); + if (ice_connected_) { + on_connected_callback_(); + } + } + + private: + std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)> + on_track_callback_; + std::function<void()> on_connected_callback_; +}; + +void ValidateP2PSimulcastParams( + const std::vector<std::unique_ptr<PeerConfigurer>>& peers) { + for (size_t i = 0; i < peers.size(); ++i) { + Params* params = peers[i]->params(); + ConfigurableParams* configurable_params = peers[i]->configurable_params(); + for (const VideoConfig& video_config : configurable_params->video_configs) { + if (video_config.simulcast_config) { + // When we simulate SFU we support only one video codec. + RTC_CHECK_EQ(params->video_codecs.size(), 1) + << "Only 1 video codec is supported when simulcast is enabled in " + << "at least 1 video config"; + } + } + } +} + +} // namespace + +PeerConnectionE2EQualityTest::PeerConnectionE2EQualityTest( + std::string test_case_name, + TimeController& time_controller, + std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer, + std::unique_ptr<VideoQualityAnalyzerInterface> video_quality_analyzer) + : PeerConnectionE2EQualityTest(std::move(test_case_name), + time_controller, + std::move(audio_quality_analyzer), + std::move(video_quality_analyzer), + /*metrics_logger_=*/nullptr) {} + +PeerConnectionE2EQualityTest::PeerConnectionE2EQualityTest( + std::string test_case_name, + TimeController& time_controller, + std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer, + std::unique_ptr<VideoQualityAnalyzerInterface> video_quality_analyzer, + test::MetricsLogger* metrics_logger) + : time_controller_(time_controller), + task_queue_factory_(time_controller_.CreateTaskQueueFactory()), + test_case_name_(std::move(test_case_name)), + executor_(std::make_unique<TestActivitiesExecutor>( + time_controller_.GetClock())), + metrics_logger_(metrics_logger) { + // Create default video quality analyzer. We will always create an analyzer, + // even if there are no video streams, because it will be installed into video + // encoder/decoder factories. + if (video_quality_analyzer == nullptr) { + video_quality_analyzer = std::make_unique<DefaultVideoQualityAnalyzer>( + time_controller_.GetClock(), metrics_logger_); + } + if (field_trial::IsEnabled("WebRTC-VideoFrameTrackingIdAdvertised")) { + encoded_image_data_propagator_ = + std::make_unique<VideoFrameTrackingIdInjector>(); + } else { + encoded_image_data_propagator_ = + std::make_unique<SingleProcessEncodedImageDataInjector>(); + } + video_quality_analyzer_injection_helper_ = + std::make_unique<VideoQualityAnalyzerInjectionHelper>( + time_controller_.GetClock(), std::move(video_quality_analyzer), + encoded_image_data_propagator_.get(), + encoded_image_data_propagator_.get()); + + if (audio_quality_analyzer == nullptr) { + audio_quality_analyzer = + std::make_unique<DefaultAudioQualityAnalyzer>(metrics_logger_); + } + audio_quality_analyzer_.swap(audio_quality_analyzer); +} + +void PeerConnectionE2EQualityTest::ExecuteAt( + TimeDelta target_time_since_start, + std::function<void(TimeDelta)> func) { + executor_->ScheduleActivity(target_time_since_start, absl::nullopt, func); +} + +void PeerConnectionE2EQualityTest::ExecuteEvery( + TimeDelta initial_delay_since_start, + TimeDelta interval, + std::function<void(TimeDelta)> func) { + executor_->ScheduleActivity(initial_delay_since_start, interval, func); +} + +void PeerConnectionE2EQualityTest::AddQualityMetricsReporter( + std::unique_ptr<QualityMetricsReporter> quality_metrics_reporter) { + quality_metrics_reporters_.push_back(std::move(quality_metrics_reporter)); +} + +PeerConnectionE2EQualityTest::PeerHandle* PeerConnectionE2EQualityTest::AddPeer( + std::unique_ptr<PeerConfigurer> configurer) { + peer_configurations_.push_back(std::move(configurer)); + peer_handles_.push_back(PeerHandleImpl()); + return &peer_handles_.back(); +} + +void PeerConnectionE2EQualityTest::Run(RunParams run_params) { + webrtc::webrtc_pc_e2e::PeerParamsPreprocessor params_preprocessor; + for (auto& peer_configuration : peer_configurations_) { + params_preprocessor.SetDefaultValuesForMissingParams(*peer_configuration); + params_preprocessor.ValidateParams(*peer_configuration); + } + ValidateP2PSimulcastParams(peer_configurations_); + RTC_CHECK_EQ(peer_configurations_.size(), 2) + << "Only peer to peer calls are allowed, please add 2 peers"; + + std::unique_ptr<PeerConfigurer> alice_configurer = + std::move(peer_configurations_[0]); + std::unique_ptr<PeerConfigurer> bob_configurer = + std::move(peer_configurations_[1]); + peer_configurations_.clear(); + + for (size_t i = 0; + i < bob_configurer->configurable_params()->video_configs.size(); ++i) { + // We support simulcast only from caller. + RTC_CHECK(!bob_configurer->configurable_params() + ->video_configs[i] + .simulcast_config) + << "Only simulcast stream from first peer is supported"; + } + + test::ScopedFieldTrials field_trials(GetFieldTrials(run_params)); + + // Print test summary + RTC_LOG(LS_INFO) + << "Media quality test: " << *alice_configurer->params()->name + << " will make a call to " << *bob_configurer->params()->name + << " with media video=" + << !alice_configurer->configurable_params()->video_configs.empty() + << "; audio=" << alice_configurer->params()->audio_config.has_value() + << ". " << *bob_configurer->params()->name + << " will respond with media video=" + << !bob_configurer->configurable_params()->video_configs.empty() + << "; audio=" << bob_configurer->params()->audio_config.has_value(); + + const std::unique_ptr<rtc::Thread> signaling_thread = + time_controller_.CreateThread(kSignalThreadName); + media_helper_ = std::make_unique<MediaHelper>( + video_quality_analyzer_injection_helper_.get(), task_queue_factory_.get(), + time_controller_.GetClock()); + + // Create a `task_queue_`. + task_queue_ = std::make_unique<webrtc::TaskQueueForTest>( + time_controller_.GetTaskQueueFactory()->CreateTaskQueue( + "pc_e2e_quality_test", webrtc::TaskQueueFactory::Priority::NORMAL)); + + // Create call participants: Alice and Bob. + // Audio streams are intercepted in AudioDeviceModule, so if it is required to + // catch output of Alice's stream, Alice's output_dump_file_name should be + // passed to Bob's TestPeer setup as audio output file name. + absl::optional<RemotePeerAudioConfig> alice_remote_audio_config = + RemotePeerAudioConfig::Create(bob_configurer->params()->audio_config); + absl::optional<RemotePeerAudioConfig> bob_remote_audio_config = + RemotePeerAudioConfig::Create(alice_configurer->params()->audio_config); + // Copy Alice and Bob video configs, subscriptions and names to correctly pass + // them into lambdas. + VideoSubscription alice_subscription = + alice_configurer->configurable_params()->video_subscription; + std::vector<VideoConfig> alice_video_configs = + alice_configurer->configurable_params()->video_configs; + std::string alice_name = alice_configurer->params()->name.value(); + VideoSubscription bob_subscription = + alice_configurer->configurable_params()->video_subscription; + std::vector<VideoConfig> bob_video_configs = + bob_configurer->configurable_params()->video_configs; + std::string bob_name = bob_configurer->params()->name.value(); + + TestPeerFactory test_peer_factory( + signaling_thread.get(), time_controller_, + video_quality_analyzer_injection_helper_.get(), task_queue_.get()); + alice_ = test_peer_factory.CreateTestPeer( + std::move(alice_configurer), + std::make_unique<FixturePeerConnectionObserver>( + [this, alice_name, alice_subscription, bob_video_configs]( + rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { + OnTrackCallback(alice_name, alice_subscription, transceiver, + bob_video_configs); + }, + [this]() { StartVideo(alice_video_sources_); }), + alice_remote_audio_config, run_params.echo_emulation_config); + bob_ = test_peer_factory.CreateTestPeer( + std::move(bob_configurer), + std::make_unique<FixturePeerConnectionObserver>( + [this, bob_name, bob_subscription, alice_video_configs]( + rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { + OnTrackCallback(bob_name, bob_subscription, transceiver, + alice_video_configs); + }, + [this]() { StartVideo(bob_video_sources_); }), + bob_remote_audio_config, run_params.echo_emulation_config); + + int num_cores = CpuInfo::DetectNumberOfCores(); + RTC_DCHECK_GE(num_cores, 1); + + int video_analyzer_threads = + num_cores - kPeerConnectionUsedThreads - kFrameworkUsedThreads; + if (video_analyzer_threads <= 0) { + video_analyzer_threads = 1; + } + video_analyzer_threads = + std::min(video_analyzer_threads, kMaxVideoAnalyzerThreads); + RTC_LOG(LS_INFO) << "video_analyzer_threads=" << video_analyzer_threads; + quality_metrics_reporters_.push_back( + std::make_unique<VideoQualityMetricsReporter>(time_controller_.GetClock(), + metrics_logger_)); + quality_metrics_reporters_.push_back( + std::make_unique<CrossMediaMetricsReporter>(metrics_logger_)); + + video_quality_analyzer_injection_helper_->Start( + test_case_name_, + std::vector<std::string>{alice_->params().name.value(), + bob_->params().name.value()}, + video_analyzer_threads); + audio_quality_analyzer_->Start(test_case_name_, &analyzer_helper_); + for (auto& reporter : quality_metrics_reporters_) { + reporter->Start(test_case_name_, &analyzer_helper_); + } + + // Start RTCEventLog recording if requested. + if (alice_->params().rtc_event_log_path) { + auto alice_rtc_event_log = std::make_unique<webrtc::RtcEventLogOutputFile>( + alice_->params().rtc_event_log_path.value()); + alice_->pc()->StartRtcEventLog(std::move(alice_rtc_event_log), + webrtc::RtcEventLog::kImmediateOutput); + } + if (bob_->params().rtc_event_log_path) { + auto bob_rtc_event_log = std::make_unique<webrtc::RtcEventLogOutputFile>( + bob_->params().rtc_event_log_path.value()); + bob_->pc()->StartRtcEventLog(std::move(bob_rtc_event_log), + webrtc::RtcEventLog::kImmediateOutput); + } + + // Setup alive logging. It is done to prevent test infra to think that test is + // dead. + RepeatingTaskHandle::DelayedStart(task_queue_->Get(), + kAliveMessageLogInterval, []() { + std::printf("Test is still running...\n"); + return kAliveMessageLogInterval; + }); + + RTC_LOG(LS_INFO) << "Configuration is done. Now " << *alice_->params().name + << " is calling to " << *bob_->params().name << "..."; + + // Setup stats poller. + std::vector<StatsObserverInterface*> observers = { + audio_quality_analyzer_.get(), + video_quality_analyzer_injection_helper_.get()}; + for (auto& reporter : quality_metrics_reporters_) { + observers.push_back(reporter.get()); + } + StatsPoller stats_poller(observers, + std::map<std::string, StatsProvider*>{ + {*alice_->params().name, alice_.get()}, + {*bob_->params().name, bob_.get()}}); + executor_->ScheduleActivity(TimeDelta::Zero(), kStatsUpdateInterval, + [&stats_poller](TimeDelta) { + stats_poller.PollStatsAndNotifyObservers(); + }); + + // Setup call. + SendTask(signaling_thread.get(), + [this, &run_params] { SetupCallOnSignalingThread(run_params); }); + std::unique_ptr<SignalingInterceptor> signaling_interceptor = + CreateSignalingInterceptor(run_params); + // Connect peers. + SendTask(signaling_thread.get(), [this, &signaling_interceptor] { + ExchangeOfferAnswer(signaling_interceptor.get()); + }); + WaitUntilIceCandidatesGathered(signaling_thread.get()); + + SendTask(signaling_thread.get(), [this, &signaling_interceptor] { + ExchangeIceCandidates(signaling_interceptor.get()); + }); + WaitUntilPeersAreConnected(signaling_thread.get()); + + executor_->Start(task_queue_.get()); + Timestamp start_time = Now(); + + bool is_quick_test_enabled = field_trial::IsEnabled("WebRTC-QuickPerfTest"); + if (is_quick_test_enabled) { + time_controller_.AdvanceTime(kQuickTestModeRunDuration); + } else { + time_controller_.AdvanceTime(run_params.run_duration); + } + + RTC_LOG(LS_INFO) << "Test is done, initiating disconnect sequence."; + + // Stop all client started tasks to prevent their access to any call related + // objects after these objects will be destroyed during call tear down. + executor_->Stop(); + // There is no guarantee, that last stats collection will happen at the end + // of the call, so we force it after executor, which is among others is doing + // stats collection, was stopped. + task_queue_->SendTask([&stats_poller]() { + // Get final end-of-call stats. + stats_poller.PollStatsAndNotifyObservers(); + }); + // We need to detach AEC dumping from peers, because dump uses `task_queue_` + // inside. + alice_->DetachAecDump(); + bob_->DetachAecDump(); + // Tear down the call. + SendTask(signaling_thread.get(), [this] { TearDownCallOnSignalingThread(); }); + + Timestamp end_time = Now(); + RTC_LOG(LS_INFO) << "All peers are disconnected."; + { + MutexLock lock(&lock_); + real_test_duration_ = end_time - start_time; + } + + ReportGeneralTestResults(); + audio_quality_analyzer_->Stop(); + video_quality_analyzer_injection_helper_->Stop(); + for (auto& reporter : quality_metrics_reporters_) { + reporter->StopAndReportResults(); + } + + // Reset `task_queue_` after test to cleanup. + task_queue_.reset(); + + alice_ = nullptr; + bob_ = nullptr; + // Ensuring that TestVideoCapturerVideoTrackSource are destroyed on the right + // thread. + RTC_CHECK(alice_video_sources_.empty()); + RTC_CHECK(bob_video_sources_.empty()); +} + +std::string PeerConnectionE2EQualityTest::GetFieldTrials( + const RunParams& run_params) { + std::vector<absl::string_view> default_field_trials = { + kUseStandardsBytesStats}; + if (run_params.enable_flex_fec_support) { + default_field_trials.push_back(kFlexFecEnabledFieldTrials); + } + rtc::StringBuilder sb; + sb << field_trial::GetFieldTrialString(); + for (const absl::string_view& field_trial : default_field_trials) { + sb << field_trial; + } + return sb.Release(); +} + +void PeerConnectionE2EQualityTest::OnTrackCallback( + absl::string_view peer_name, + VideoSubscription peer_subscription, + rtc::scoped_refptr<RtpTransceiverInterface> transceiver, + std::vector<VideoConfig> remote_video_configs) { + const rtc::scoped_refptr<MediaStreamTrackInterface>& track = + transceiver->receiver()->track(); + RTC_CHECK_EQ(transceiver->receiver()->stream_ids().size(), 2) + << "Expected 2 stream ids: 1st - sync group, 2nd - unique stream label"; + std::string sync_group = transceiver->receiver()->stream_ids()[0]; + std::string stream_label = transceiver->receiver()->stream_ids()[1]; + analyzer_helper_.AddTrackToStreamMapping(track->id(), peer_name, stream_label, + sync_group); + if (track->kind() != MediaStreamTrackInterface::kVideoKind) { + return; + } + + // It is safe to cast here, because it is checked above that + // track->kind() is kVideoKind. + auto* video_track = static_cast<VideoTrackInterface*>(track.get()); + std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> video_sink = + video_quality_analyzer_injection_helper_->CreateVideoSink( + peer_name, peer_subscription, /*report_infra_stats=*/false); + video_track->AddOrUpdateSink(video_sink.get(), rtc::VideoSinkWants()); + output_video_sinks_.push_back(std::move(video_sink)); +} + +void PeerConnectionE2EQualityTest::SetupCallOnSignalingThread( + const RunParams& run_params) { + // We need receive-only transceivers for Bob's media stream, so there will + // be media section in SDP for that streams in Alice's offer, because it is + // forbidden to add new media sections in answer in Unified Plan. + RtpTransceiverInit receive_only_transceiver_init; + receive_only_transceiver_init.direction = RtpTransceiverDirection::kRecvOnly; + int alice_transceivers_counter = 0; + if (bob_->params().audio_config) { + // Setup receive audio transceiver if Bob has audio to send. If we'll need + // multiple audio streams, then we need transceiver for each Bob's audio + // stream. + RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result = + alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_AUDIO, + receive_only_transceiver_init); + RTC_CHECK(result.ok()); + alice_transceivers_counter++; + } + + size_t alice_video_transceivers_non_simulcast_counter = 0; + for (auto& video_config : alice_->configurable_params().video_configs) { + RtpTransceiverInit transceiver_params; + if (video_config.simulcast_config) { + transceiver_params.direction = RtpTransceiverDirection::kSendOnly; + // Because simulcast enabled `alice_->params().video_codecs` has only 1 + // element. + if (alice_->params().video_codecs[0].name == cricket::kVp8CodecName) { + // For Vp8 simulcast we need to add as many RtpEncodingParameters to the + // track as many simulcast streams requested. If they specified in + // `video_config.simulcast_config` it should be copied from there. + for (int i = 0; + i < video_config.simulcast_config->simulcast_streams_count; ++i) { + RtpEncodingParameters enc_params; + if (!video_config.encoding_params.empty()) { + enc_params = video_config.encoding_params[i]; + } + // We need to be sure, that all rids will be unique with all mids. + enc_params.rid = std::to_string(alice_transceivers_counter) + "000" + + std::to_string(i); + transceiver_params.send_encodings.push_back(enc_params); + } + } + } else { + transceiver_params.direction = RtpTransceiverDirection::kSendRecv; + RtpEncodingParameters enc_params; + if (video_config.encoding_params.size() == 1) { + enc_params = video_config.encoding_params[0]; + } + transceiver_params.send_encodings.push_back(enc_params); + + alice_video_transceivers_non_simulcast_counter++; + } + RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result = + alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_VIDEO, + transceiver_params); + RTC_CHECK(result.ok()); + + alice_transceivers_counter++; + } + + // Add receive only transceivers in case Bob has more video_configs than + // Alice. + for (size_t i = alice_video_transceivers_non_simulcast_counter; + i < bob_->configurable_params().video_configs.size(); ++i) { + RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result = + alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_VIDEO, + receive_only_transceiver_init); + RTC_CHECK(result.ok()); + alice_transceivers_counter++; + } + + // Then add media for Alice and Bob + media_helper_->MaybeAddAudio(alice_.get()); + alice_video_sources_ = media_helper_->MaybeAddVideo(alice_.get()); + media_helper_->MaybeAddAudio(bob_.get()); + bob_video_sources_ = media_helper_->MaybeAddVideo(bob_.get()); + + SetPeerCodecPreferences(alice_.get()); + SetPeerCodecPreferences(bob_.get()); +} + +void PeerConnectionE2EQualityTest::TearDownCallOnSignalingThread() { + TearDownCall(); +} + +void PeerConnectionE2EQualityTest::SetPeerCodecPreferences(TestPeer* peer) { + std::vector<RtpCodecCapability> with_rtx_video_capabilities = + FilterVideoCodecCapabilities( + peer->params().video_codecs, true, peer->params().use_ulp_fec, + peer->params().use_flex_fec, + peer->pc_factory() + ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO) + .codecs); + std::vector<RtpCodecCapability> without_rtx_video_capabilities = + FilterVideoCodecCapabilities( + peer->params().video_codecs, false, peer->params().use_ulp_fec, + peer->params().use_flex_fec, + peer->pc_factory() + ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO) + .codecs); + + // Set codecs for transceivers + for (auto transceiver : peer->pc()->GetTransceivers()) { + if (transceiver->media_type() == cricket::MediaType::MEDIA_TYPE_VIDEO) { + if (transceiver->sender()->init_send_encodings().size() > 1) { + // If transceiver's sender has more then 1 send encodings, it means it + // has multiple simulcast streams, so we need disable RTX on it. + RTCError result = + transceiver->SetCodecPreferences(without_rtx_video_capabilities); + RTC_CHECK(result.ok()); + } else { + RTCError result = + transceiver->SetCodecPreferences(with_rtx_video_capabilities); + RTC_CHECK(result.ok()); + } + } + } +} + +std::unique_ptr<SignalingInterceptor> +PeerConnectionE2EQualityTest::CreateSignalingInterceptor( + const RunParams& run_params) { + std::map<std::string, int> stream_label_to_simulcast_streams_count; + // We add only Alice here, because simulcast/svc is supported only from the + // first peer. + for (auto& video_config : alice_->configurable_params().video_configs) { + if (video_config.simulcast_config) { + stream_label_to_simulcast_streams_count.insert( + {*video_config.stream_label, + video_config.simulcast_config->simulcast_streams_count}); + } + } + PatchingParams patching_params(run_params.use_conference_mode, + stream_label_to_simulcast_streams_count); + return std::make_unique<SignalingInterceptor>(patching_params); +} + +void PeerConnectionE2EQualityTest::WaitUntilIceCandidatesGathered( + rtc::Thread* signaling_thread) { + ASSERT_TRUE(time_controller_.Wait( + [&]() { + bool result; + SendTask(signaling_thread, [&]() { + result = alice_->IsIceGatheringDone() && bob_->IsIceGatheringDone(); + }); + return result; + }, + 2 * kDefaultTimeout)); +} + +void PeerConnectionE2EQualityTest::WaitUntilPeersAreConnected( + rtc::Thread* signaling_thread) { + // This means that ICE and DTLS are connected. + alice_connected_ = time_controller_.Wait( + [&]() { + bool result; + SendTask(signaling_thread, [&] { result = alice_->IsIceConnected(); }); + return result; + }, + kDefaultTimeout); + bob_connected_ = time_controller_.Wait( + [&]() { + bool result; + SendTask(signaling_thread, [&] { result = bob_->IsIceConnected(); }); + return result; + }, + kDefaultTimeout); +} + +void PeerConnectionE2EQualityTest::ExchangeOfferAnswer( + SignalingInterceptor* signaling_interceptor) { + std::string log_output; + + auto offer = alice_->CreateOffer(); + RTC_CHECK(offer); + offer->ToString(&log_output); + RTC_LOG(LS_INFO) << "Original offer: " << log_output; + LocalAndRemoteSdp patch_result = signaling_interceptor->PatchOffer( + std::move(offer), alice_->params().video_codecs[0]); + patch_result.local_sdp->ToString(&log_output); + RTC_LOG(LS_INFO) << "Offer to set as local description: " << log_output; + patch_result.remote_sdp->ToString(&log_output); + RTC_LOG(LS_INFO) << "Offer to set as remote description: " << log_output; + + bool set_local_offer = + alice_->SetLocalDescription(std::move(patch_result.local_sdp)); + RTC_CHECK(set_local_offer); + bool set_remote_offer = + bob_->SetRemoteDescription(std::move(patch_result.remote_sdp)); + RTC_CHECK(set_remote_offer); + auto answer = bob_->CreateAnswer(); + RTC_CHECK(answer); + answer->ToString(&log_output); + RTC_LOG(LS_INFO) << "Original answer: " << log_output; + patch_result = signaling_interceptor->PatchAnswer( + std::move(answer), bob_->params().video_codecs[0]); + patch_result.local_sdp->ToString(&log_output); + RTC_LOG(LS_INFO) << "Answer to set as local description: " << log_output; + patch_result.remote_sdp->ToString(&log_output); + RTC_LOG(LS_INFO) << "Answer to set as remote description: " << log_output; + + bool set_local_answer = + bob_->SetLocalDescription(std::move(patch_result.local_sdp)); + RTC_CHECK(set_local_answer); + bool set_remote_answer = + alice_->SetRemoteDescription(std::move(patch_result.remote_sdp)); + RTC_CHECK(set_remote_answer); +} + +void PeerConnectionE2EQualityTest::ExchangeIceCandidates( + SignalingInterceptor* signaling_interceptor) { + // Connect an ICE candidate pairs. + std::vector<std::unique_ptr<IceCandidateInterface>> alice_candidates = + signaling_interceptor->PatchOffererIceCandidates( + alice_->observer()->GetAllCandidates()); + for (auto& candidate : alice_candidates) { + std::string candidate_str; + RTC_CHECK(candidate->ToString(&candidate_str)); + RTC_LOG(LS_INFO) << *alice_->params().name + << " ICE candidate(mid= " << candidate->sdp_mid() + << "): " << candidate_str; + } + ASSERT_TRUE(bob_->AddIceCandidates(std::move(alice_candidates))); + std::vector<std::unique_ptr<IceCandidateInterface>> bob_candidates = + signaling_interceptor->PatchAnswererIceCandidates( + bob_->observer()->GetAllCandidates()); + for (auto& candidate : bob_candidates) { + std::string candidate_str; + RTC_CHECK(candidate->ToString(&candidate_str)); + RTC_LOG(LS_INFO) << *bob_->params().name + << " ICE candidate(mid= " << candidate->sdp_mid() + << "): " << candidate_str; + } + ASSERT_TRUE(alice_->AddIceCandidates(std::move(bob_candidates))); +} + +void PeerConnectionE2EQualityTest::StartVideo( + const std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>>& + sources) { + for (auto& source : sources) { + if (source->state() != MediaSourceInterface::SourceState::kLive) { + source->Start(); + } + } +} + +void PeerConnectionE2EQualityTest::TearDownCall() { + for (const auto& video_source : alice_video_sources_) { + video_source->Stop(); + } + for (const auto& video_source : bob_video_sources_) { + video_source->Stop(); + } + + alice_video_sources_.clear(); + bob_video_sources_.clear(); + + alice_->Close(); + bob_->Close(); + + media_helper_ = nullptr; +} + +void PeerConnectionE2EQualityTest::ReportGeneralTestResults() { + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + metrics_logger_->LogSingleValueMetric( + *alice_->params().name + "_connected", test_case_name_, alice_connected_, + Unit::kUnitless, ImprovementDirection::kBiggerIsBetter, + {{MetricMetadataKey::kPeerMetadataKey, *alice_->params().name}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}); + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + metrics_logger_->LogSingleValueMetric( + *bob_->params().name + "_connected", test_case_name_, bob_connected_, + Unit::kUnitless, ImprovementDirection::kBiggerIsBetter, + {{MetricMetadataKey::kPeerMetadataKey, *bob_->params().name}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}); +} + +Timestamp PeerConnectionE2EQualityTest::Now() const { + return time_controller_.GetClock()->CurrentTime(); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.h b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.h new file mode 100644 index 0000000000..6cbf232874 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test.h @@ -0,0 +1,155 @@ +/* + * 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_PC_E2E_PEER_CONNECTION_QUALITY_TEST_H_ +#define TEST_PC_E2E_PEER_CONNECTION_QUALITY_TEST_H_ + +#include <memory> +#include <queue> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/test/audio_quality_analyzer_interface.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/thread.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" +#include "test/pc/e2e/analyzer/video/single_process_encoded_image_data_injector.h" +#include "test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h" +#include "test/pc/e2e/analyzer_helper.h" +#include "test/pc/e2e/media/media_helper.h" +#include "test/pc/e2e/sdp/sdp_changer.h" +#include "test/pc/e2e/test_activities_executor.h" +#include "test/pc/e2e/test_peer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class PeerConnectionE2EQualityTest + : public PeerConnectionE2EQualityTestFixture { + public: + using QualityMetricsReporter = + PeerConnectionE2EQualityTestFixture::QualityMetricsReporter; + + PeerConnectionE2EQualityTest( + std::string test_case_name, + TimeController& time_controller, + std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer, + std::unique_ptr<VideoQualityAnalyzerInterface> video_quality_analyzer); + PeerConnectionE2EQualityTest( + std::string test_case_name, + TimeController& time_controller, + std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer, + std::unique_ptr<VideoQualityAnalyzerInterface> video_quality_analyzer, + test::MetricsLogger* metrics_logger); + + ~PeerConnectionE2EQualityTest() override = default; + + void ExecuteAt(TimeDelta target_time_since_start, + std::function<void(TimeDelta)> func) override; + void ExecuteEvery(TimeDelta initial_delay_since_start, + TimeDelta interval, + std::function<void(TimeDelta)> func) override; + + void AddQualityMetricsReporter(std::unique_ptr<QualityMetricsReporter> + quality_metrics_reporter) override; + + PeerHandle* AddPeer(std::unique_ptr<PeerConfigurer> configurer) override; + void Run(RunParams run_params) override; + + TimeDelta GetRealTestDuration() const override { + MutexLock lock(&lock_); + RTC_CHECK_NE(real_test_duration_, TimeDelta::Zero()); + return real_test_duration_; + } + + private: + class PeerHandleImpl : public PeerHandle { + public: + ~PeerHandleImpl() override = default; + }; + + // For some functionality some field trials have to be enabled, they will be + // enabled in Run(). + std::string GetFieldTrials(const RunParams& run_params); + void OnTrackCallback(absl::string_view peer_name, + VideoSubscription peer_subscription, + rtc::scoped_refptr<RtpTransceiverInterface> transceiver, + std::vector<VideoConfig> remote_video_configs); + // Have to be run on the signaling thread. + void SetupCallOnSignalingThread(const RunParams& run_params); + void TearDownCallOnSignalingThread(); + void SetPeerCodecPreferences(TestPeer* peer); + std::unique_ptr<SignalingInterceptor> CreateSignalingInterceptor( + const RunParams& run_params); + void WaitUntilIceCandidatesGathered(rtc::Thread* signaling_thread); + void WaitUntilPeersAreConnected(rtc::Thread* signaling_thread); + void ExchangeOfferAnswer(SignalingInterceptor* signaling_interceptor); + void ExchangeIceCandidates(SignalingInterceptor* signaling_interceptor); + void StartVideo( + const std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>>& + sources); + void TearDownCall(); + void ReportGeneralTestResults(); + Timestamp Now() const; + + TimeController& time_controller_; + const std::unique_ptr<TaskQueueFactory> task_queue_factory_; + std::string test_case_name_; + std::unique_ptr<VideoQualityAnalyzerInjectionHelper> + video_quality_analyzer_injection_helper_; + std::unique_ptr<MediaHelper> media_helper_; + std::unique_ptr<EncodedImageDataPropagator> encoded_image_data_propagator_; + std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer_; + std::unique_ptr<TestActivitiesExecutor> executor_; + test::MetricsLogger* const metrics_logger_; + + std::vector<std::unique_ptr<PeerConfigurer>> peer_configurations_; + std::vector<PeerHandleImpl> peer_handles_; + + std::unique_ptr<TestPeer> alice_; + std::unique_ptr<TestPeer> bob_; + std::vector<std::unique_ptr<QualityMetricsReporter>> + quality_metrics_reporters_; + + std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>> + alice_video_sources_; + std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>> + bob_video_sources_; + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> + output_video_sinks_; + AnalyzerHelper analyzer_helper_; + + mutable Mutex lock_; + TimeDelta real_test_duration_ RTC_GUARDED_BY(lock_) = TimeDelta::Zero(); + + // Task queue, that is used for running activities during test call. + // This task queue will be created before call set up and will be destroyed + // immediately before call tear down. + std::unique_ptr<TaskQueueForTest> task_queue_; + + bool alice_connected_ = false; + bool bob_connected_ = false; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_PEER_CONNECTION_QUALITY_TEST_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test_metric_names_test.cc b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test_metric_names_test.cc new file mode 100644 index 0000000000..8a47e108e0 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test_metric_names_test.cc @@ -0,0 +1,1102 @@ +/* + * 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 <map> +#include <memory> +#include <string> + +#include "api/test/create_network_emulation_manager.h" +#include "api/test/create_peer_connection_quality_test_frame_generator.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/metrics/stdout_metrics_exporter.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/units/time_delta.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/pc/e2e/metric_metadata_keys.h" +#include "test/pc/e2e/peer_connection_quality_test.h" +#include "test/pc/e2e/stats_based_network_quality_metrics_reporter.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::UnorderedElementsAre; + +using ::webrtc::test::DefaultMetricsLogger; +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Metric; +using ::webrtc::test::MetricsExporter; +using ::webrtc::test::StdoutMetricsExporter; +using ::webrtc::test::Unit; +using ::webrtc::webrtc_pc_e2e::PeerConfigurer; + +// Adds a peer with some audio and video (the client should not care about +// details about audio and video configs). +void AddDefaultAudioVideoPeer( + absl::string_view peer_name, + absl::string_view audio_stream_label, + absl::string_view video_stream_label, + const PeerNetworkDependencies& network_dependencies, + PeerConnectionE2EQualityTestFixture& fixture) { + AudioConfig audio{std::string(audio_stream_label)}; + audio.sync_group = std::string(peer_name); + VideoConfig video(std::string(video_stream_label), 320, 180, 15); + video.sync_group = std::string(peer_name); + auto peer = std::make_unique<PeerConfigurer>(network_dependencies); + peer->SetName(peer_name); + peer->SetAudioConfig(std::move(audio)); + peer->AddVideoConfig(std::move(video)); + peer->SetVideoCodecs({VideoCodecConfig(cricket::kVp8CodecName)}); + fixture.AddPeer(std::move(peer)); +} + +// Metric fields to assert on +struct MetricValidationInfo { + std::string test_case; + std::string name; + Unit unit; + ImprovementDirection improvement_direction; + std::map<std::string, std::string> metadata; +}; + +bool operator==(const MetricValidationInfo& a, const MetricValidationInfo& b) { + return a.name == b.name && a.test_case == b.test_case && a.unit == b.unit && + a.improvement_direction == b.improvement_direction && + a.metadata == b.metadata; +} + +std::ostream& operator<<(std::ostream& os, const MetricValidationInfo& m) { + os << "{ test_case=" << m.test_case << "; name=" << m.name + << "; unit=" << test::ToString(m.unit) + << "; improvement_direction=" << test::ToString(m.improvement_direction) + << "; metadata={ "; + for (const auto& [key, value] : m.metadata) { + os << "{ key=" << key << "; value=" << value << " }"; + } + os << " }}"; + return os; +} + +std::vector<MetricValidationInfo> ToValidationInfo( + const std::vector<Metric>& metrics) { + std::vector<MetricValidationInfo> out; + for (const Metric& m : metrics) { + out.push_back( + MetricValidationInfo{.test_case = m.test_case, + .name = m.name, + .unit = m.unit, + .improvement_direction = m.improvement_direction, + .metadata = m.metric_metadata}); + } + return out; +} + +TEST(PeerConnectionE2EQualityTestMetricNamesTest, + ExportedMetricsHasCorrectNamesAndAnnotation) { + std::unique_ptr<NetworkEmulationManager> network_emulation = + CreateNetworkEmulationManager(TimeMode::kSimulated); + DefaultMetricsLogger metrics_logger( + network_emulation->time_controller()->GetClock()); + PeerConnectionE2EQualityTest fixture( + "test_case", *network_emulation->time_controller(), + /*audio_quality_analyzer=*/nullptr, /*video_quality_analyzer=*/nullptr, + &metrics_logger); + + EmulatedEndpoint* alice_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + + network_emulation->CreateRoute( + alice_endpoint, {network_emulation->CreateUnconstrainedEmulatedNode()}, + bob_endpoint); + network_emulation->CreateRoute( + bob_endpoint, {network_emulation->CreateUnconstrainedEmulatedNode()}, + alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + network_emulation->CreateEmulatedNetworkManagerInterface( + {alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + network_emulation->CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + AddDefaultAudioVideoPeer("alice", "alice_audio", "alice_video", + alice_network->network_dependencies(), fixture); + AddDefaultAudioVideoPeer("bob", "bob_audio", "bob_video", + bob_network->network_dependencies(), fixture); + fixture.AddQualityMetricsReporter( + std::make_unique<StatsBasedNetworkQualityMetricsReporter>( + std::map<std::string, std::vector<EmulatedEndpoint*>>( + {{"alice", alice_network->endpoints()}, + {"bob", bob_network->endpoints()}}), + network_emulation.get(), &metrics_logger)); + + // Run for at least 7 seconds, so AV-sync metrics will be collected. + fixture.Run(RunParams(TimeDelta::Seconds(7))); + + std::vector<MetricValidationInfo> metrics = + ToValidationInfo(metrics_logger.GetCollectedMetrics()); + EXPECT_THAT( + metrics, + UnorderedElementsAre( + // Metrics from PeerConnectionE2EQualityTest + MetricValidationInfo{ + .test_case = "test_case", + .name = "alice_connected", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case", + .name = "bob_connected", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + + // Metrics from DefaultAudioQualityAnalyzer + MetricValidationInfo{ + .test_case = "test_case/alice_audio", + .name = "expand_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_audio", + .name = "accelerate_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_audio", + .name = "preemptive_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_audio", + .name = "speech_expand_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_audio", + .name = "average_jitter_buffer_delay_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_audio", + .name = "preferred_buffer_size_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_audio", + .name = "expand_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_audio", + .name = "accelerate_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_audio", + .name = "preemptive_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_audio", + .name = "speech_expand_rate", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_audio", + .name = "average_jitter_buffer_delay_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_audio", + .name = "preferred_buffer_size_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kAudioStreamMetadataKey, + "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + + // Metrics from DefaultVideoQualityAnalyzer + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "ssim", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "transport_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "total_delay_incl_transport", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "time_between_rendered_frames", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "harmonic_framerate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "encode_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "encode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "time_between_freezes", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "freeze_time_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "pixels_per_frame", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "min_psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "decode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "receive_to_render_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "dropped_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "frames_in_flight", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "rendered_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "max_skipped", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "target_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "qp_sl0", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kSpatialLayerMetadataKey, "0"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_video", + .name = "actual_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "alice_video"}, + {MetricMetadataKey::kSenderMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "ssim", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "transport_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "total_delay_incl_transport", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "time_between_rendered_frames", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "harmonic_framerate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "encode_frame_rate", + .unit = Unit::kHertz, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "encode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "time_between_freezes", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "freeze_time_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "pixels_per_frame", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "min_psnr_dB", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "decode_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "receive_to_render_time", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "dropped_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "frames_in_flight", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "rendered_frames", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kBiggerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "max_skipped", + .unit = Unit::kCount, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "target_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "actual_encode_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_video", + .name = "qp_sl0", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kVideoStreamMetadataKey, + "bob_video"}, + {MetricMetadataKey::kSenderMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kSpatialLayerMetadataKey, "0"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case", + .name = "cpu_usage_%", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = {{MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + + // Metrics from StatsBasedNetworkQualityMetricsReporter + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "bytes_discarded_no_receiver", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "packets_discarded_no_receiver", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "payload_bytes_received", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "payload_bytes_sent", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "bytes_sent", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "packets_sent", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "average_send_rate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "bytes_received", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "packets_received", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "average_receive_rate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "sent_packets_loss", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "bytes_discarded_no_receiver", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "packets_discarded_no_receiver", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "payload_bytes_received", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "payload_bytes_sent", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "bytes_sent", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "packets_sent", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "average_send_rate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "bytes_received", + .unit = Unit::kBytes, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "packets_received", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "average_receive_rate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "sent_packets_loss", + .unit = Unit::kUnitless, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + + // Metrics from VideoQualityMetricsReporter + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "available_send_bandwidth", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "transmission_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice", + .name = "retransmission_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "available_send_bandwidth", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "transmission_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob", + .name = "retransmission_bitrate", + .unit = Unit::kKilobitsPerSecond, + .improvement_direction = ImprovementDirection::kNeitherIsBetter, + .metadata = {{MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + + // Metrics from CrossMediaMetricsReporter + MetricValidationInfo{ + .test_case = "test_case/alice_alice_audio", + .name = "audio_ahead_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = + {{MetricMetadataKey::kAudioStreamMetadataKey, "alice_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kPeerSyncGroupMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/alice_alice_video", + .name = "video_ahead_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = + {{MetricMetadataKey::kAudioStreamMetadataKey, "alice_video"}, + {MetricMetadataKey::kPeerMetadataKey, "bob"}, + {MetricMetadataKey::kPeerSyncGroupMetadataKey, "alice"}, + {MetricMetadataKey::kReceiverMetadataKey, "bob"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_bob_audio", + .name = "audio_ahead_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = + {{MetricMetadataKey::kAudioStreamMetadataKey, "bob_audio"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kPeerSyncGroupMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}}, + MetricValidationInfo{ + .test_case = "test_case/bob_bob_video", + .name = "video_ahead_ms", + .unit = Unit::kMilliseconds, + .improvement_direction = ImprovementDirection::kSmallerIsBetter, + .metadata = { + {MetricMetadataKey::kAudioStreamMetadataKey, "bob_video"}, + {MetricMetadataKey::kPeerMetadataKey, "alice"}, + {MetricMetadataKey::kPeerSyncGroupMetadataKey, "bob"}, + {MetricMetadataKey::kReceiverMetadataKey, "alice"}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, + "test_case"}}})); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test_test.cc b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test_test.cc new file mode 100644 index 0000000000..066fe7d8ee --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_connection_quality_test_test.cc @@ -0,0 +1,139 @@ +/* + * 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/pc/e2e/peer_connection_quality_test.h" + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "api/test/create_network_emulation_manager.h" +#include "api/test/metrics/global_metrics_logger_and_exporter.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/units/time_delta.h" +#include "rtc_base/time_utils.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" +#include "test/testsupport/frame_reader.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::Eq; +using ::testing::Test; + +using ::webrtc::webrtc_pc_e2e::PeerConfigurer; + +// Remove files and directories in a directory non-recursively. +void CleanDir(absl::string_view dir, size_t expected_output_files_count) { + absl::optional<std::vector<std::string>> dir_content = + test::ReadDirectory(dir); + if (expected_output_files_count == 0) { + ASSERT_FALSE(dir_content.has_value()) << "Empty directory is expected"; + } else { + ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!"; + EXPECT_EQ(dir_content->size(), expected_output_files_count); + for (const auto& entry : *dir_content) { + if (test::DirExists(entry)) { + EXPECT_TRUE(test::RemoveDir(entry)) + << "Failed to remove sub directory: " << entry; + } else if (test::FileExists(entry)) { + EXPECT_TRUE(test::RemoveFile(entry)) + << "Failed to remove file: " << entry; + } else { + FAIL() << "Can't remove unknown file type: " << entry; + } + } + } + EXPECT_TRUE(test::RemoveDir(dir)) << "Failed to remove directory: " << dir; +} + +class PeerConnectionE2EQualityTestTest : public Test { + protected: + ~PeerConnectionE2EQualityTestTest() override = default; + + void SetUp() override { + // Create an empty temporary directory for this test. + test_directory_ = test::JoinFilename( + test::OutputPath(), + "TestDir_PeerConnectionE2EQualityTestTest_" + + std::string( + testing::UnitTest::GetInstance()->current_test_info()->name())); + test::CreateDir(test_directory_); + } + + void TearDown() override { + CleanDir(test_directory_, expected_output_files_count_); + } + + void ExpectOutputFilesCount(size_t count) { + expected_output_files_count_ = count; + } + + std::string test_directory_; + size_t expected_output_files_count_ = 0; +}; + +TEST_F(PeerConnectionE2EQualityTestTest, OutputVideoIsDumpedWhenRequested) { + std::unique_ptr<NetworkEmulationManager> network_emulation = + CreateNetworkEmulationManager(TimeMode::kSimulated); + PeerConnectionE2EQualityTest fixture( + "test_case", *network_emulation->time_controller(), + /*audio_quality_analyzer=*/nullptr, /*video_quality_analyzer=*/nullptr, + test::GetGlobalMetricsLogger()); + + EmulatedEndpoint* alice_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + + network_emulation->CreateRoute( + alice_endpoint, {network_emulation->CreateUnconstrainedEmulatedNode()}, + bob_endpoint); + network_emulation->CreateRoute( + bob_endpoint, {network_emulation->CreateUnconstrainedEmulatedNode()}, + alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + network_emulation->CreateEmulatedNetworkManagerInterface( + {alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + network_emulation->CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + VideoConfig alice_video("alice_video", 320, 180, 15); + alice_video.output_dump_options = VideoDumpOptions(test_directory_); + PeerConfigurer alice(alice_network->network_dependencies()); + alice.SetName("alice"); + alice.AddVideoConfig(std::move(alice_video)); + fixture.AddPeer(std::make_unique<PeerConfigurer>(std::move(alice))); + + PeerConfigurer bob(bob_network->network_dependencies()); + bob.SetName("bob"); + fixture.AddPeer(std::make_unique<PeerConfigurer>(std::move(bob))); + + fixture.Run(RunParams(TimeDelta::Seconds(2))); + + auto frame_reader = test::CreateY4mFrameReader( + test::JoinFilename(test_directory_, "alice_video_bob_320x180_15.y4m")); + EXPECT_THAT(frame_reader->num_frames(), Eq(31)); // 2 seconds 15 fps + 1 + + ExpectOutputFilesCount(1); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/peer_params_preprocessor.cc b/third_party/libwebrtc/test/pc/e2e/peer_params_preprocessor.cc new file mode 100644 index 0000000000..05372125d2 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_params_preprocessor.cc @@ -0,0 +1,217 @@ +/* + * 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/pc/e2e/peer_params_preprocessor.h" + +#include <set> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peer_network_dependencies.h" +#include "modules/video_coding/svc/create_scalability_structure.h" +#include "modules/video_coding/svc/scalability_mode_util.h" +#include "rtc_base/arraysize.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +// List of default names of generic participants according to +// https://en.wikipedia.org/wiki/Alice_and_Bob +constexpr absl::string_view kDefaultNames[] = {"alice", "bob", "charlie", + "david", "erin", "frank"}; + +} // namespace + +class PeerParamsPreprocessor::DefaultNamesProvider { + public: + // Caller have to ensure that default names array will outlive names provider + // instance. + explicit DefaultNamesProvider( + absl::string_view prefix, + rtc::ArrayView<const absl::string_view> default_names = {}) + : prefix_(prefix), default_names_(default_names) {} + + void MaybeSetName(absl::optional<std::string>& name) { + if (name.has_value()) { + known_names_.insert(name.value()); + } else { + name = GenerateName(); + } + } + + private: + std::string GenerateName() { + std::string name; + do { + name = GenerateNameInternal(); + } while (!known_names_.insert(name).second); + return name; + } + + std::string GenerateNameInternal() { + if (counter_ < default_names_.size()) { + return std::string(default_names_[counter_++]); + } + return prefix_ + std::to_string(counter_++); + } + + const std::string prefix_; + const rtc::ArrayView<const absl::string_view> default_names_; + + std::set<std::string> known_names_; + size_t counter_ = 0; +}; + +PeerParamsPreprocessor::PeerParamsPreprocessor() + : peer_names_provider_( + std::make_unique<DefaultNamesProvider>("peer_", kDefaultNames)) {} +PeerParamsPreprocessor::~PeerParamsPreprocessor() = default; + +void PeerParamsPreprocessor::SetDefaultValuesForMissingParams( + PeerConfigurer& peer) { + Params* params = peer.params(); + ConfigurableParams* configurable_params = peer.configurable_params(); + peer_names_provider_->MaybeSetName(params->name); + DefaultNamesProvider video_stream_names_provider(*params->name + + "_auto_video_stream_label_"); + for (VideoConfig& config : configurable_params->video_configs) { + video_stream_names_provider.MaybeSetName(config.stream_label); + } + if (params->audio_config) { + DefaultNamesProvider audio_stream_names_provider( + *params->name + "_auto_audio_stream_label_"); + audio_stream_names_provider.MaybeSetName( + params->audio_config->stream_label); + } + + if (params->video_codecs.empty()) { + params->video_codecs.push_back(VideoCodecConfig(cricket::kVp8CodecName)); + } +} + +void PeerParamsPreprocessor::ValidateParams(const PeerConfigurer& peer) { + const Params& p = peer.params(); + RTC_CHECK_GT(p.video_encoder_bitrate_multiplier, 0.0); + // Each peer should at least support 1 video codec. + RTC_CHECK_GE(p.video_codecs.size(), 1); + + { + RTC_CHECK(p.name); + bool inserted = peer_names_.insert(p.name.value()).second; + RTC_CHECK(inserted) << "Duplicate name=" << p.name.value(); + } + + // Validate that all video stream labels are unique and sync groups are + // valid. + for (const VideoConfig& video_config : + peer.configurable_params().video_configs) { + RTC_CHECK(video_config.stream_label); + bool inserted = + video_labels_.insert(video_config.stream_label.value()).second; + RTC_CHECK(inserted) << "Duplicate video_config.stream_label=" + << video_config.stream_label.value(); + + // TODO(bugs.webrtc.org/4762): remove this check after synchronization of + // more than two streams is supported. + if (video_config.sync_group.has_value()) { + bool sync_group_inserted = + video_sync_groups_.insert(video_config.sync_group.value()).second; + RTC_CHECK(sync_group_inserted) + << "Sync group shouldn't consist of more than two streams (one " + "video and one audio). Duplicate video_config.sync_group=" + << video_config.sync_group.value(); + } + + if (video_config.simulcast_config) { + if (!video_config.encoding_params.empty()) { + RTC_CHECK_EQ(video_config.simulcast_config->simulcast_streams_count, + video_config.encoding_params.size()) + << "|encoding_params| have to be specified for each simulcast " + << "stream in |video_config|."; + } + } else { + RTC_CHECK_LE(video_config.encoding_params.size(), 1) + << "|encoding_params| has multiple values but simulcast is not " + "enabled."; + } + + if (video_config.emulated_sfu_config) { + if (video_config.simulcast_config && + video_config.emulated_sfu_config->target_layer_index) { + RTC_CHECK_LT(*video_config.emulated_sfu_config->target_layer_index, + video_config.simulcast_config->simulcast_streams_count); + } + if (!video_config.encoding_params.empty()) { + bool is_svc = false; + for (const auto& encoding_param : video_config.encoding_params) { + if (!encoding_param.scalability_mode) + continue; + + absl::optional<ScalabilityMode> scalability_mode = + ScalabilityModeFromString(*encoding_param.scalability_mode); + RTC_CHECK(scalability_mode) << "Unknown scalability_mode requested"; + + absl::optional<ScalableVideoController::StreamLayersConfig> + stream_layers_config = + ScalabilityStructureConfig(*scalability_mode); + is_svc |= stream_layers_config->num_spatial_layers > 1; + RTC_CHECK(stream_layers_config->num_spatial_layers == 1 || + video_config.encoding_params.size() == 1) + << "Can't enable SVC modes with multiple spatial layers (" + << stream_layers_config->num_spatial_layers + << " layers) or simulcast (" + << video_config.encoding_params.size() << " layers)"; + if (video_config.emulated_sfu_config->target_layer_index) { + RTC_CHECK_LT(*video_config.emulated_sfu_config->target_layer_index, + stream_layers_config->num_spatial_layers); + } + } + if (!is_svc && video_config.emulated_sfu_config->target_layer_index) { + RTC_CHECK_LT(*video_config.emulated_sfu_config->target_layer_index, + video_config.encoding_params.size()); + } + } + } + } + if (p.audio_config) { + bool inserted = + audio_labels_.insert(p.audio_config->stream_label.value()).second; + RTC_CHECK(inserted) << "Duplicate audio_config.stream_label=" + << p.audio_config->stream_label.value(); + // TODO(bugs.webrtc.org/4762): remove this check after synchronization of + // more than two streams is supported. + if (p.audio_config->sync_group.has_value()) { + bool sync_group_inserted = + audio_sync_groups_.insert(p.audio_config->sync_group.value()).second; + RTC_CHECK(sync_group_inserted) + << "Sync group shouldn't consist of more than two streams (one " + "video and one audio). Duplicate audio_config.sync_group=" + << p.audio_config->sync_group.value(); + } + // Check that if mode input file name specified only if mode is kFile. + if (p.audio_config.value().mode == AudioConfig::Mode::kGenerated) { + RTC_CHECK(!p.audio_config.value().input_file_name); + } + if (p.audio_config.value().mode == AudioConfig::Mode::kFile) { + RTC_CHECK(p.audio_config.value().input_file_name); + RTC_CHECK( + test::FileExists(p.audio_config.value().input_file_name.value())) + << p.audio_config.value().input_file_name.value() << " doesn't exist"; + } + } +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/peer_params_preprocessor.h b/third_party/libwebrtc/test/pc/e2e/peer_params_preprocessor.h new file mode 100644 index 0000000000..c222811546 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/peer_params_preprocessor.h @@ -0,0 +1,52 @@ +/* + * 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_PC_E2E_PEER_PARAMS_PREPROCESSOR_H_ +#define TEST_PC_E2E_PEER_PARAMS_PREPROCESSOR_H_ + +#include <memory> +#include <set> +#include <string> + +#include "api/test/pclf/peer_configurer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class PeerParamsPreprocessor { + public: + PeerParamsPreprocessor(); + ~PeerParamsPreprocessor(); + + // Set missing params to default values if it is required: + // * Generate video stream labels if some of them are missing + // * Generate audio stream labels if some of them are missing + // * Set video source generation mode if it is not specified + // * Video codecs under test + void SetDefaultValuesForMissingParams(PeerConfigurer& peer); + + // Validate peer's parameters, also ensure uniqueness of all video stream + // labels. + void ValidateParams(const PeerConfigurer& peer); + + private: + class DefaultNamesProvider; + std::unique_ptr<DefaultNamesProvider> peer_names_provider_; + + std::set<std::string> peer_names_; + std::set<std::string> video_labels_; + std::set<std::string> audio_labels_; + std::set<std::string> video_sync_groups_; + std::set<std::string> audio_sync_groups_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_PEER_PARAMS_PREPROCESSOR_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/sdp/sdp_changer.cc b/third_party/libwebrtc/test/pc/e2e/sdp/sdp_changer.cc new file mode 100644 index 0000000000..af55f29175 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/sdp/sdp_changer.cc @@ -0,0 +1,601 @@ +/* + * 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/pc/e2e/sdp/sdp_changer.h" + +#include <utility> + +#include "absl/memory/memory.h" +#include "api/jsep_session_description.h" +#include "api/test/pclf/media_configuration.h" +#include "media/base/media_constants.h" +#include "p2p/base/p2p_constants.h" +#include "pc/sdp_utils.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +std::string CodecRequiredParamsToString( + const std::map<std::string, std::string>& codec_required_params) { + rtc::StringBuilder out; + for (const auto& entry : codec_required_params) { + out << entry.first << "=" << entry.second << ";"; + } + return out.str(); +} + +std::string SupportedCodecsToString( + rtc::ArrayView<const RtpCodecCapability> supported_codecs) { + rtc::StringBuilder out; + for (const auto& codec : supported_codecs) { + out << codec.name; + if (!codec.parameters.empty()) { + out << "("; + for (const auto& param : codec.parameters) { + out << param.first << "=" << param.second << ";"; + } + out << ")"; + } + out << "; "; + } + return out.str(); +} + +} // namespace + +std::vector<RtpCodecCapability> FilterVideoCodecCapabilities( + rtc::ArrayView<const VideoCodecConfig> video_codecs, + bool use_rtx, + bool use_ulpfec, + bool use_flexfec, + rtc::ArrayView<const RtpCodecCapability> supported_codecs) { + std::vector<RtpCodecCapability> output_codecs; + // Find requested codecs among supported and add them to output in the order + // they were requested. + for (auto& codec_request : video_codecs) { + size_t size_before = output_codecs.size(); + for (auto& codec : supported_codecs) { + if (codec.name != codec_request.name) { + continue; + } + bool parameters_matched = true; + for (const auto& item : codec_request.required_params) { + auto it = codec.parameters.find(item.first); + if (it == codec.parameters.end()) { + parameters_matched = false; + break; + } + if (item.second != it->second) { + parameters_matched = false; + break; + } + } + if (parameters_matched) { + output_codecs.push_back(codec); + } + } + RTC_CHECK_GT(output_codecs.size(), size_before) + << "Codec with name=" << codec_request.name << " and params {" + << CodecRequiredParamsToString(codec_request.required_params) + << "} is unsupported for this peer connection. Supported codecs are: " + << SupportedCodecsToString(supported_codecs); + } + + // Add required FEC and RTX codecs to output. + for (auto& codec : supported_codecs) { + if (codec.name == cricket::kRtxCodecName && use_rtx) { + output_codecs.push_back(codec); + } else if (codec.name == cricket::kFlexfecCodecName && use_flexfec) { + output_codecs.push_back(codec); + } else if ((codec.name == cricket::kRedCodecName || + codec.name == cricket::kUlpfecCodecName) && + use_ulpfec) { + // Red and ulpfec should be enabled or disabled together. + output_codecs.push_back(codec); + } + } + return output_codecs; +} + +// If offer has no simulcast video sections - do nothing. +// +// If offer has simulcast video sections - for each section creates +// SimulcastSectionInfo and put it into `context_`. +void SignalingInterceptor::FillSimulcastContext( + SessionDescriptionInterface* offer) { + for (auto& content : offer->description()->contents()) { + cricket::MediaContentDescription* media_desc = content.media_description(); + if (media_desc->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) { + continue; + } + if (media_desc->HasSimulcast()) { + // We support only single stream simulcast sections with rids. + RTC_CHECK_EQ(media_desc->mutable_streams().size(), 1); + RTC_CHECK(media_desc->mutable_streams()[0].has_rids()); + + // Create SimulcastSectionInfo for this video section. + SimulcastSectionInfo info(content.mid(), content.type, + media_desc->mutable_streams()[0].rids()); + + // Set new rids basing on created SimulcastSectionInfo. + std::vector<cricket::RidDescription> rids; + cricket::SimulcastDescription simulcast_description; + for (std::string& rid : info.rids) { + rids.emplace_back(rid, cricket::RidDirection::kSend); + simulcast_description.send_layers().AddLayer( + cricket::SimulcastLayer(rid, false)); + } + media_desc->mutable_streams()[0].set_rids(rids); + media_desc->set_simulcast_description(simulcast_description); + + info.simulcast_description = media_desc->simulcast_description(); + for (const auto& extension : media_desc->rtp_header_extensions()) { + if (extension.uri == RtpExtension::kMidUri) { + info.mid_extension = extension; + } else if (extension.uri == RtpExtension::kRidUri) { + info.rid_extension = extension; + } else if (extension.uri == RtpExtension::kRepairedRidUri) { + info.rrid_extension = extension; + } + } + RTC_CHECK_NE(info.rid_extension.id, 0); + RTC_CHECK_NE(info.mid_extension.id, 0); + bool transport_description_found = false; + for (auto& transport_info : offer->description()->transport_infos()) { + if (transport_info.content_name == info.mid) { + info.transport_description = transport_info.description; + transport_description_found = true; + break; + } + } + RTC_CHECK(transport_description_found); + + context_.AddSimulcastInfo(info); + } + } +} + +LocalAndRemoteSdp SignalingInterceptor::PatchOffer( + std::unique_ptr<SessionDescriptionInterface> offer, + const VideoCodecConfig& first_codec) { + for (auto& content : offer->description()->contents()) { + context_.mids_order.push_back(content.mid()); + cricket::MediaContentDescription* media_desc = content.media_description(); + if (media_desc->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) { + continue; + } + if (content.media_description()->streams().empty()) { + // It means that this media section describes receive only media section + // in SDP. + RTC_CHECK_EQ(content.media_description()->direction(), + RtpTransceiverDirection::kRecvOnly); + continue; + } + media_desc->set_conference_mode(params_.use_conference_mode); + } + + if (!params_.stream_label_to_simulcast_streams_count.empty()) { + // Because simulcast enabled `params_.video_codecs` has only 1 element. + if (first_codec.name == cricket::kVp8CodecName) { + return PatchVp8Offer(std::move(offer)); + } + + if (first_codec.name == cricket::kVp9CodecName) { + return PatchVp9Offer(std::move(offer)); + } + } + + auto offer_for_remote = CloneSessionDescription(offer.get()); + return LocalAndRemoteSdp(std::move(offer), std::move(offer_for_remote)); +} + +LocalAndRemoteSdp SignalingInterceptor::PatchVp8Offer( + std::unique_ptr<SessionDescriptionInterface> offer) { + FillSimulcastContext(offer.get()); + if (!context_.HasSimulcast()) { + auto offer_for_remote = CloneSessionDescription(offer.get()); + return LocalAndRemoteSdp(std::move(offer), std::move(offer_for_remote)); + } + + // Clone original offer description. We mustn't access original offer after + // this point. + std::unique_ptr<cricket::SessionDescription> desc = + offer->description()->Clone(); + + for (auto& info : context_.simulcast_infos) { + // For each simulcast section we have to perform: + // 1. Swap MID and RID header extensions + // 2. Remove RIDs from streams and remove SimulcastDescription + // 3. For each RID duplicate media section + cricket::ContentInfo* simulcast_content = desc->GetContentByName(info.mid); + + // Now we need to prepare common prototype for "m=video" sections, in which + // single simulcast section will be converted. Do it before removing content + // because otherwise description will be deleted. + std::unique_ptr<cricket::MediaContentDescription> prototype_media_desc = + simulcast_content->media_description()->Clone(); + + // Remove simulcast video section from offer. + RTC_CHECK(desc->RemoveContentByName(simulcast_content->mid())); + // Clear `simulcast_content`, because now it is pointing to removed object. + simulcast_content = nullptr; + + // Swap mid and rid extensions, so remote peer will understand rid as mid. + // Also remove rid extension. + std::vector<webrtc::RtpExtension> extensions = + prototype_media_desc->rtp_header_extensions(); + for (auto ext_it = extensions.begin(); ext_it != extensions.end();) { + if (ext_it->uri == RtpExtension::kRidUri) { + // We don't need rid extension for remote peer. + ext_it = extensions.erase(ext_it); + continue; + } + if (ext_it->uri == RtpExtension::kRepairedRidUri) { + // We don't support RTX in simulcast. + ext_it = extensions.erase(ext_it); + continue; + } + if (ext_it->uri == RtpExtension::kMidUri) { + ext_it->id = info.rid_extension.id; + } + ++ext_it; + } + + prototype_media_desc->ClearRtpHeaderExtensions(); + prototype_media_desc->set_rtp_header_extensions(extensions); + + // We support only single stream inside video section with simulcast + RTC_CHECK_EQ(prototype_media_desc->mutable_streams().size(), 1); + // This stream must have rids. + RTC_CHECK(prototype_media_desc->mutable_streams()[0].has_rids()); + + // Remove rids and simulcast description from media description. + prototype_media_desc->mutable_streams()[0].set_rids({}); + prototype_media_desc->set_simulcast_description( + cricket::SimulcastDescription()); + + // For each rid add separate video section. + for (std::string& rid : info.rids) { + desc->AddContent(rid, info.media_protocol_type, + prototype_media_desc->Clone()); + } + } + + // Now we need to add bundle line to have all media bundled together. + cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE); + for (auto& content : desc->contents()) { + bundle_group.AddContentName(content.mid()); + } + if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) { + desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); + } + desc->AddGroup(bundle_group); + + // Update transport_infos to add TransportInfo for each new media section. + std::vector<cricket::TransportInfo> transport_infos = desc->transport_infos(); + transport_infos.erase(std::remove_if( + transport_infos.begin(), transport_infos.end(), + [this](const cricket::TransportInfo& ti) { + // Remove transport infos that correspond to simulcast video sections. + return context_.simulcast_infos_by_mid.find(ti.content_name) != + context_.simulcast_infos_by_mid.end(); + })); + for (auto& info : context_.simulcast_infos) { + for (auto& rid : info.rids) { + transport_infos.emplace_back(rid, info.transport_description); + } + } + desc->set_transport_infos(transport_infos); + + // Create patched offer. + auto patched_offer = + std::make_unique<JsepSessionDescription>(SdpType::kOffer); + patched_offer->Initialize(std::move(desc), offer->session_id(), + offer->session_version()); + return LocalAndRemoteSdp(std::move(offer), std::move(patched_offer)); +} + +LocalAndRemoteSdp SignalingInterceptor::PatchVp9Offer( + std::unique_ptr<SessionDescriptionInterface> offer) { + rtc::UniqueRandomIdGenerator ssrcs_generator; + for (auto& content : offer->description()->contents()) { + for (auto& stream : content.media_description()->streams()) { + for (auto& ssrc : stream.ssrcs) { + ssrcs_generator.AddKnownId(ssrc); + } + } + } + + for (auto& content : offer->description()->contents()) { + if (content.media_description()->type() != + cricket::MediaType::MEDIA_TYPE_VIDEO) { + // We are interested in only video tracks + continue; + } + if (content.media_description()->direction() == + RtpTransceiverDirection::kRecvOnly) { + // If direction is receive only, then there is no media in this track from + // sender side, so we needn't to do anything with this track. + continue; + } + RTC_CHECK_EQ(content.media_description()->streams().size(), 1); + cricket::StreamParams& stream = + content.media_description()->mutable_streams()[0]; + RTC_CHECK_EQ(stream.stream_ids().size(), 2) + << "Expected 2 stream ids in video stream: 1st - sync_group, 2nd - " + "unique label"; + std::string stream_label = stream.stream_ids()[1]; + + auto it = + params_.stream_label_to_simulcast_streams_count.find(stream_label); + if (it == params_.stream_label_to_simulcast_streams_count.end()) { + continue; + } + int svc_layers_count = it->second; + + RTC_CHECK(stream.has_ssrc_groups()) << "Only SVC with RTX is supported"; + RTC_CHECK_EQ(stream.ssrc_groups.size(), 1) + << "Too many ssrc groups in the track"; + std::vector<uint32_t> primary_ssrcs; + stream.GetPrimarySsrcs(&primary_ssrcs); + RTC_CHECK(primary_ssrcs.size() == 1); + for (int i = 1; i < svc_layers_count; ++i) { + uint32_t ssrc = ssrcs_generator.GenerateId(); + primary_ssrcs.push_back(ssrc); + stream.add_ssrc(ssrc); + stream.AddFidSsrc(ssrc, ssrcs_generator.GenerateId()); + } + stream.ssrc_groups.push_back( + cricket::SsrcGroup(cricket::kSimSsrcGroupSemantics, primary_ssrcs)); + } + auto offer_for_remote = CloneSessionDescription(offer.get()); + return LocalAndRemoteSdp(std::move(offer), std::move(offer_for_remote)); +} + +LocalAndRemoteSdp SignalingInterceptor::PatchAnswer( + std::unique_ptr<SessionDescriptionInterface> answer, + const VideoCodecConfig& first_codec) { + for (auto& content : answer->description()->contents()) { + cricket::MediaContentDescription* media_desc = content.media_description(); + if (media_desc->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) { + continue; + } + if (content.media_description()->direction() != + RtpTransceiverDirection::kRecvOnly) { + continue; + } + media_desc->set_conference_mode(params_.use_conference_mode); + } + + if (!params_.stream_label_to_simulcast_streams_count.empty()) { + // Because simulcast enabled `params_.video_codecs` has only 1 element. + if (first_codec.name == cricket::kVp8CodecName) { + return PatchVp8Answer(std::move(answer)); + } + + if (first_codec.name == cricket::kVp9CodecName) { + return PatchVp9Answer(std::move(answer)); + } + } + + auto answer_for_remote = CloneSessionDescription(answer.get()); + return LocalAndRemoteSdp(std::move(answer), std::move(answer_for_remote)); +} + +LocalAndRemoteSdp SignalingInterceptor::PatchVp8Answer( + std::unique_ptr<SessionDescriptionInterface> answer) { + if (!context_.HasSimulcast()) { + auto answer_for_remote = CloneSessionDescription(answer.get()); + return LocalAndRemoteSdp(std::move(answer), std::move(answer_for_remote)); + } + + std::unique_ptr<cricket::SessionDescription> desc = + answer->description()->Clone(); + + for (auto& info : context_.simulcast_infos) { + cricket::ContentInfo* simulcast_content = + desc->GetContentByName(info.rids[0]); + RTC_CHECK(simulcast_content); + + // Get media description, which will be converted to simulcast answer. + std::unique_ptr<cricket::MediaContentDescription> media_desc = + simulcast_content->media_description()->Clone(); + // Set `simulcast_content` to nullptr, because then it will be removed, so + // it will point to deleted object. + simulcast_content = nullptr; + + // Remove separate media sections for simulcast streams. + for (auto& rid : info.rids) { + RTC_CHECK(desc->RemoveContentByName(rid)); + } + + // Patch `media_desc` to make it simulcast answer description. + // Restore mid/rid rtp header extensions + std::vector<webrtc::RtpExtension> extensions = + media_desc->rtp_header_extensions(); + // First remove existing rid/mid header extensions. + extensions.erase(std::remove_if(extensions.begin(), extensions.end(), + [](const webrtc::RtpExtension& e) { + return e.uri == RtpExtension::kMidUri || + e.uri == RtpExtension::kRidUri || + e.uri == + RtpExtension::kRepairedRidUri; + })); + + // Then add right ones. + extensions.push_back(info.mid_extension); + extensions.push_back(info.rid_extension); + // extensions.push_back(info.rrid_extension); + media_desc->ClearRtpHeaderExtensions(); + media_desc->set_rtp_header_extensions(extensions); + + // Add StreamParams with rids for receive. + RTC_CHECK_EQ(media_desc->mutable_streams().size(), 0); + std::vector<cricket::RidDescription> rids; + for (auto& rid : info.rids) { + rids.emplace_back(rid, cricket::RidDirection::kReceive); + } + cricket::StreamParams stream_params; + stream_params.set_rids(rids); + media_desc->mutable_streams().push_back(stream_params); + + // Restore SimulcastDescription. It should correspond to one from offer, + // but it have to have receive layers instead of send. So we need to put + // send layers from offer to receive layers in answer. + cricket::SimulcastDescription simulcast_description; + for (const auto& layer : info.simulcast_description.send_layers()) { + simulcast_description.receive_layers().AddLayerWithAlternatives(layer); + } + media_desc->set_simulcast_description(simulcast_description); + + // Add simulcast media section. + desc->AddContent(info.mid, info.media_protocol_type, std::move(media_desc)); + } + + desc = RestoreMediaSectionsOrder(std::move(desc)); + + // Now we need to add bundle line to have all media bundled together. + cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE); + for (auto& content : desc->contents()) { + bundle_group.AddContentName(content.mid()); + } + if (desc->HasGroup(cricket::GROUP_TYPE_BUNDLE)) { + desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); + } + desc->AddGroup(bundle_group); + + // Fix transport_infos: it have to have single info for simulcast section. + std::vector<cricket::TransportInfo> transport_infos = desc->transport_infos(); + std::map<std::string, cricket::TransportDescription> + mid_to_transport_description; + for (auto info_it = transport_infos.begin(); + info_it != transport_infos.end();) { + auto it = context_.simulcast_infos_by_rid.find(info_it->content_name); + if (it != context_.simulcast_infos_by_rid.end()) { + // This transport info correspond to some extra added media section. + mid_to_transport_description.insert( + {it->second->mid, info_it->description}); + info_it = transport_infos.erase(info_it); + } else { + ++info_it; + } + } + for (auto& info : context_.simulcast_infos) { + transport_infos.emplace_back(info.mid, + mid_to_transport_description.at(info.mid)); + } + desc->set_transport_infos(transport_infos); + + auto patched_answer = + std::make_unique<JsepSessionDescription>(SdpType::kAnswer); + patched_answer->Initialize(std::move(desc), answer->session_id(), + answer->session_version()); + return LocalAndRemoteSdp(std::move(answer), std::move(patched_answer)); +} + +std::unique_ptr<cricket::SessionDescription> +SignalingInterceptor::RestoreMediaSectionsOrder( + std::unique_ptr<cricket::SessionDescription> source) { + std::unique_ptr<cricket::SessionDescription> out = source->Clone(); + for (auto& mid : context_.mids_order) { + RTC_CHECK(out->RemoveContentByName(mid)); + } + RTC_CHECK_EQ(out->contents().size(), 0); + for (auto& mid : context_.mids_order) { + cricket::ContentInfo* content = source->GetContentByName(mid); + RTC_CHECK(content); + out->AddContent(mid, content->type, content->media_description()->Clone()); + } + return out; +} + +LocalAndRemoteSdp SignalingInterceptor::PatchVp9Answer( + std::unique_ptr<SessionDescriptionInterface> answer) { + auto answer_for_remote = CloneSessionDescription(answer.get()); + return LocalAndRemoteSdp(std::move(answer), std::move(answer_for_remote)); +} + +std::vector<std::unique_ptr<IceCandidateInterface>> +SignalingInterceptor::PatchOffererIceCandidates( + rtc::ArrayView<const IceCandidateInterface* const> candidates) { + std::vector<std::unique_ptr<IceCandidateInterface>> out; + for (auto* candidate : candidates) { + auto simulcast_info_it = + context_.simulcast_infos_by_mid.find(candidate->sdp_mid()); + if (simulcast_info_it != context_.simulcast_infos_by_mid.end()) { + // This is candidate for simulcast section, so it should be transformed + // into candidates for replicated sections. The sdpMLineIndex is set to + // -1 and ignored if the rid is present. + for (const std::string& rid : simulcast_info_it->second->rids) { + out.push_back(CreateIceCandidate(rid, -1, candidate->candidate())); + } + } else { + out.push_back(CreateIceCandidate(candidate->sdp_mid(), + candidate->sdp_mline_index(), + candidate->candidate())); + } + } + RTC_CHECK_GT(out.size(), 0); + return out; +} + +std::vector<std::unique_ptr<IceCandidateInterface>> +SignalingInterceptor::PatchAnswererIceCandidates( + rtc::ArrayView<const IceCandidateInterface* const> candidates) { + std::vector<std::unique_ptr<IceCandidateInterface>> out; + for (auto* candidate : candidates) { + auto simulcast_info_it = + context_.simulcast_infos_by_rid.find(candidate->sdp_mid()); + if (simulcast_info_it != context_.simulcast_infos_by_rid.end()) { + // This is candidate for replicated section, created from single simulcast + // section, so it should be transformed into candidates for simulcast + // section. + out.push_back(CreateIceCandidate(simulcast_info_it->second->mid, 0, + candidate->candidate())); + } else if (!context_.simulcast_infos_by_rid.empty()) { + // When using simulcast and bundle, put everything on the first m-line. + out.push_back(CreateIceCandidate("", 0, candidate->candidate())); + } else { + out.push_back(CreateIceCandidate(candidate->sdp_mid(), + candidate->sdp_mline_index(), + candidate->candidate())); + } + } + RTC_CHECK_GT(out.size(), 0); + return out; +} + +SignalingInterceptor::SimulcastSectionInfo::SimulcastSectionInfo( + const std::string& mid, + cricket::MediaProtocolType media_protocol_type, + const std::vector<cricket::RidDescription>& rids_desc) + : mid(mid), media_protocol_type(media_protocol_type) { + for (auto& rid : rids_desc) { + rids.push_back(rid.rid); + } +} + +void SignalingInterceptor::SignalingContext::AddSimulcastInfo( + const SimulcastSectionInfo& info) { + simulcast_infos.push_back(info); + bool inserted = + simulcast_infos_by_mid.insert({info.mid, &simulcast_infos.back()}).second; + RTC_CHECK(inserted); + for (auto& rid : info.rids) { + inserted = + simulcast_infos_by_rid.insert({rid, &simulcast_infos.back()}).second; + RTC_CHECK(inserted); + } +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/sdp/sdp_changer.h b/third_party/libwebrtc/test/pc/e2e/sdp/sdp_changer.h new file mode 100644 index 0000000000..6f68d03f52 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/sdp/sdp_changer.h @@ -0,0 +1,146 @@ +/* + * 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_PC_E2E_SDP_SDP_CHANGER_H_ +#define TEST_PC_E2E_SDP_SDP_CHANGER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/jsep.h" +#include "api/rtp_parameters.h" +#include "api/test/pclf/media_configuration.h" +#include "media/base/rid_description.h" +#include "pc/session_description.h" +#include "pc/simulcast_description.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Creates list of capabilities, which can be set on RtpTransceiverInterface via +// RtpTransceiverInterface::SetCodecPreferences(...) to negotiate use of codecs +// from list of `supported_codecs` which will match `video_codecs`. If flags +// `ulpfec` or `flexfec` set to true corresponding FEC codec will be added. +// FEC and RTX codecs will be added after required codecs. +// +// All codecs will be added only if they exists in the list of +// `supported_codecs`. If multiple codecs from this list will match +// `video_codecs`, then all of them will be added to the output +// vector and they will be added in the same order, as they were in +// `supported_codecs`. +std::vector<RtpCodecCapability> FilterVideoCodecCapabilities( + rtc::ArrayView<const VideoCodecConfig> video_codecs, + bool use_rtx, + bool use_ulpfec, + bool use_flexfec, + rtc::ArrayView<const RtpCodecCapability> supported_codecs); + +struct LocalAndRemoteSdp { + LocalAndRemoteSdp(std::unique_ptr<SessionDescriptionInterface> local_sdp, + std::unique_ptr<SessionDescriptionInterface> remote_sdp) + : local_sdp(std::move(local_sdp)), remote_sdp(std::move(remote_sdp)) {} + + // Sdp, that should be as local description on the peer, that created it. + std::unique_ptr<SessionDescriptionInterface> local_sdp; + // Sdp, that should be set as remote description on the peer opposite to the + // one, who created it. + std::unique_ptr<SessionDescriptionInterface> remote_sdp; +}; + +struct PatchingParams { + PatchingParams( + bool use_conference_mode, + std::map<std::string, int> stream_label_to_simulcast_streams_count) + : use_conference_mode(use_conference_mode), + stream_label_to_simulcast_streams_count( + stream_label_to_simulcast_streams_count) {} + + bool use_conference_mode; + std::map<std::string, int> stream_label_to_simulcast_streams_count; +}; + +class SignalingInterceptor { + public: + explicit SignalingInterceptor(PatchingParams params) : params_(params) {} + + LocalAndRemoteSdp PatchOffer( + std::unique_ptr<SessionDescriptionInterface> offer, + const VideoCodecConfig& first_codec); + LocalAndRemoteSdp PatchAnswer( + std::unique_ptr<SessionDescriptionInterface> answer, + const VideoCodecConfig& first_codec); + + std::vector<std::unique_ptr<IceCandidateInterface>> PatchOffererIceCandidates( + rtc::ArrayView<const IceCandidateInterface* const> candidates); + std::vector<std::unique_ptr<IceCandidateInterface>> + PatchAnswererIceCandidates( + rtc::ArrayView<const IceCandidateInterface* const> candidates); + + private: + // Contains information about simulcast section, that is required to perform + // modified offer/answer and ice candidates exchange. + struct SimulcastSectionInfo { + SimulcastSectionInfo(const std::string& mid, + cricket::MediaProtocolType media_protocol_type, + const std::vector<cricket::RidDescription>& rids_desc); + + const std::string mid; + const cricket::MediaProtocolType media_protocol_type; + std::vector<std::string> rids; + cricket::SimulcastDescription simulcast_description; + webrtc::RtpExtension mid_extension; + webrtc::RtpExtension rid_extension; + webrtc::RtpExtension rrid_extension; + cricket::TransportDescription transport_description; + }; + + struct SignalingContext { + SignalingContext() = default; + // SignalingContext is not copyable and movable. + SignalingContext(SignalingContext&) = delete; + SignalingContext& operator=(SignalingContext&) = delete; + SignalingContext(SignalingContext&&) = delete; + SignalingContext& operator=(SignalingContext&&) = delete; + + void AddSimulcastInfo(const SimulcastSectionInfo& info); + bool HasSimulcast() const { return !simulcast_infos.empty(); } + + std::vector<SimulcastSectionInfo> simulcast_infos; + std::map<std::string, SimulcastSectionInfo*> simulcast_infos_by_mid; + std::map<std::string, SimulcastSectionInfo*> simulcast_infos_by_rid; + + std::vector<std::string> mids_order; + }; + + LocalAndRemoteSdp PatchVp8Offer( + std::unique_ptr<SessionDescriptionInterface> offer); + LocalAndRemoteSdp PatchVp9Offer( + std::unique_ptr<SessionDescriptionInterface> offer); + LocalAndRemoteSdp PatchVp8Answer( + std::unique_ptr<SessionDescriptionInterface> answer); + LocalAndRemoteSdp PatchVp9Answer( + std::unique_ptr<SessionDescriptionInterface> answer); + + void FillSimulcastContext(SessionDescriptionInterface* offer); + std::unique_ptr<cricket::SessionDescription> RestoreMediaSectionsOrder( + std::unique_ptr<cricket::SessionDescription> source); + + PatchingParams params_; + SignalingContext context_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_SDP_SDP_CHANGER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc new file mode 100644 index 0000000000..65dca5b518 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc @@ -0,0 +1,592 @@ +/* + * 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/pc/e2e/stats_based_network_quality_metrics_reporter.h" + +#include <cstdint> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/sequence_checker.h" +#include "api/stats/rtc_stats.h" +#include "api/stats/rtcstats_objects.h" +#include "api/test/metrics/metric.h" +#include "api/test/network_emulation/network_emulation_interfaces.h" +#include "api/test/network_emulation_manager.h" +#include "api/units/data_rate.h" +#include "api/units/timestamp.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" +#include "rtc_base/ip_address.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" +#include "system_wrappers/include/field_trial.h" +#include "test/pc/e2e/metric_metadata_keys.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Unit; + +using NetworkLayerStats = + StatsBasedNetworkQualityMetricsReporter::NetworkLayerStats; + +constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1); + +// Field trial which controls whether to report standard-compliant bytes +// sent/received per stream. If enabled, padding and headers are not included +// in bytes sent or received. +constexpr char kUseStandardBytesStats[] = "WebRTC-UseStandardBytesStats"; + +EmulatedNetworkStats PopulateStats(std::vector<EmulatedEndpoint*> endpoints, + NetworkEmulationManager* network_emulation) { + rtc::Event stats_loaded; + EmulatedNetworkStats stats; + network_emulation->GetStats(endpoints, [&](EmulatedNetworkStats s) { + stats = std::move(s); + stats_loaded.Set(); + }); + bool stats_received = stats_loaded.Wait(kStatsWaitTimeout); + RTC_CHECK(stats_received); + return stats; +} + +std::map<rtc::IPAddress, std::string> PopulateIpToPeer( + const std::map<std::string, std::vector<EmulatedEndpoint*>>& + peer_endpoints) { + std::map<rtc::IPAddress, std::string> out; + for (const auto& entry : peer_endpoints) { + for (const EmulatedEndpoint* const endpoint : entry.second) { + RTC_CHECK(out.find(endpoint->GetPeerLocalAddress()) == out.end()) + << "Two peers can't share the same endpoint"; + out.emplace(endpoint->GetPeerLocalAddress(), entry.first); + } + } + return out; +} + +// Accumulates emulated network stats being executed on the network thread. +// When all stats are collected stores it in thread safe variable. +class EmulatedNetworkStatsAccumulator { + public: + // `expected_stats_count` - the number of calls to + // AddEndpointStats/AddUplinkStats/AddDownlinkStats the accumulator is going + // to wait. If called more than expected, the program will crash. + explicit EmulatedNetworkStatsAccumulator(size_t expected_stats_count) + : not_collected_stats_count_(expected_stats_count) { + RTC_DCHECK_GE(not_collected_stats_count_, 0); + if (not_collected_stats_count_ == 0) { + all_stats_collected_.Set(); + } + sequence_checker_.Detach(); + } + + // Has to be executed on network thread. + void AddEndpointStats(std::string peer_name, EmulatedNetworkStats stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + n_stats_[peer_name].endpoints_stats = std::move(stats); + DecrementNotCollectedStatsCount(); + } + + // Has to be executed on network thread. + void AddUplinkStats(std::string peer_name, EmulatedNetworkNodeStats stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + n_stats_[peer_name].uplink_stats = std::move(stats); + DecrementNotCollectedStatsCount(); + } + + // Has to be executed on network thread. + void AddDownlinkStats(std::string peer_name, EmulatedNetworkNodeStats stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + n_stats_[peer_name].downlink_stats = std::move(stats); + DecrementNotCollectedStatsCount(); + } + + // Can be executed on any thread. + // Returns true if count down was completed and false if timeout elapsed + // before. + bool Wait(TimeDelta timeout) { return all_stats_collected_.Wait(timeout); } + + // Can be called once. Returns all collected stats by moving underlying + // object. + std::map<std::string, NetworkLayerStats> ReleaseStats() { + RTC_DCHECK(!stats_released_); + stats_released_ = true; + MutexLock lock(&mutex_); + return std::move(stats_); + } + + private: + void DecrementNotCollectedStatsCount() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_CHECK_GT(not_collected_stats_count_, 0) + << "All stats are already collected"; + not_collected_stats_count_--; + if (not_collected_stats_count_ == 0) { + MutexLock lock(&mutex_); + stats_ = std::move(n_stats_); + all_stats_collected_.Set(); + } + } + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + size_t not_collected_stats_count_ RTC_GUARDED_BY(sequence_checker_); + // Collected on the network thread. Moved into `stats_` after all stats are + // collected. + std::map<std::string, NetworkLayerStats> n_stats_ + RTC_GUARDED_BY(sequence_checker_); + + rtc::Event all_stats_collected_; + Mutex mutex_; + std::map<std::string, NetworkLayerStats> stats_ RTC_GUARDED_BY(mutex_); + bool stats_released_ = false; +}; + +} // namespace + +StatsBasedNetworkQualityMetricsReporter:: + StatsBasedNetworkQualityMetricsReporter( + std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints, + NetworkEmulationManager* network_emulation, + test::MetricsLogger* metrics_logger) + : collector_(std::move(peer_endpoints), network_emulation), + clock_(network_emulation->time_controller()->GetClock()), + metrics_logger_(metrics_logger) { + RTC_CHECK(metrics_logger_); +} + +StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: + NetworkLayerStatsCollector( + std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints, + NetworkEmulationManager* network_emulation) + : peer_endpoints_(std::move(peer_endpoints)), + ip_to_peer_(PopulateIpToPeer(peer_endpoints_)), + network_emulation_(network_emulation) {} + +void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: + Start() { + MutexLock lock(&mutex_); + // Check that network stats are clean before test execution. + for (const auto& entry : peer_endpoints_) { + EmulatedNetworkStats stats = + PopulateStats(entry.second, network_emulation_); + RTC_CHECK_EQ(stats.overall_outgoing_stats.packets_sent, 0); + RTC_CHECK_EQ(stats.overall_incoming_stats.packets_received, 0); + } +} + +void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: + AddPeer(absl::string_view peer_name, + std::vector<EmulatedEndpoint*> endpoints, + std::vector<EmulatedNetworkNode*> uplink, + std::vector<EmulatedNetworkNode*> downlink) { + MutexLock lock(&mutex_); + // When new peer is added not in the constructor, don't check if it has empty + // stats, because their endpoint could be used for traffic before. + peer_endpoints_.emplace(peer_name, std::move(endpoints)); + peer_uplinks_.emplace(peer_name, std::move(uplink)); + peer_downlinks_.emplace(peer_name, std::move(downlink)); + for (const EmulatedEndpoint* const endpoint : endpoints) { + RTC_CHECK(ip_to_peer_.find(endpoint->GetPeerLocalAddress()) == + ip_to_peer_.end()) + << "Two peers can't share the same endpoint"; + ip_to_peer_.emplace(endpoint->GetPeerLocalAddress(), peer_name); + } +} + +std::map<std::string, NetworkLayerStats> +StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: + GetStats() { + MutexLock lock(&mutex_); + EmulatedNetworkStatsAccumulator stats_accumulator( + peer_endpoints_.size() + peer_uplinks_.size() + peer_downlinks_.size()); + for (const auto& entry : peer_endpoints_) { + network_emulation_->GetStats( + entry.second, [&stats_accumulator, + peer = entry.first](EmulatedNetworkStats s) mutable { + stats_accumulator.AddEndpointStats(std::move(peer), std::move(s)); + }); + } + for (const auto& entry : peer_uplinks_) { + network_emulation_->GetStats( + entry.second, [&stats_accumulator, + peer = entry.first](EmulatedNetworkNodeStats s) mutable { + stats_accumulator.AddUplinkStats(std::move(peer), std::move(s)); + }); + } + for (const auto& entry : peer_downlinks_) { + network_emulation_->GetStats( + entry.second, [&stats_accumulator, + peer = entry.first](EmulatedNetworkNodeStats s) mutable { + stats_accumulator.AddDownlinkStats(std::move(peer), std::move(s)); + }); + } + bool stats_collected = stats_accumulator.Wait(kStatsWaitTimeout); + RTC_CHECK(stats_collected); + std::map<std::string, NetworkLayerStats> peer_to_stats = + stats_accumulator.ReleaseStats(); + std::map<std::string, std::vector<std::string>> sender_to_receivers; + for (const auto& entry : peer_endpoints_) { + const std::string& peer_name = entry.first; + const NetworkLayerStats& stats = peer_to_stats[peer_name]; + for (const auto& income_stats_entry : + stats.endpoints_stats.incoming_stats_per_source) { + const rtc::IPAddress& source_ip = income_stats_entry.first; + auto it = ip_to_peer_.find(source_ip); + if (it == ip_to_peer_.end()) { + // Source IP is unknown for this collector, so will be skipped. + continue; + } + sender_to_receivers[it->second].push_back(peer_name); + } + } + for (auto& entry : peer_to_stats) { + const std::vector<std::string>& receivers = + sender_to_receivers[entry.first]; + entry.second.receivers = + std::set<std::string>(receivers.begin(), receivers.end()); + } + return peer_to_stats; +} + +void StatsBasedNetworkQualityMetricsReporter::AddPeer( + absl::string_view peer_name, + std::vector<EmulatedEndpoint*> endpoints) { + collector_.AddPeer(peer_name, std::move(endpoints), /*uplink=*/{}, + /*downlink=*/{}); +} + +void StatsBasedNetworkQualityMetricsReporter::AddPeer( + absl::string_view peer_name, + std::vector<EmulatedEndpoint*> endpoints, + std::vector<EmulatedNetworkNode*> uplink, + std::vector<EmulatedNetworkNode*> downlink) { + collector_.AddPeer(peer_name, std::move(endpoints), std::move(uplink), + std::move(downlink)); +} + +void StatsBasedNetworkQualityMetricsReporter::Start( + absl::string_view test_case_name, + const TrackIdStreamInfoMap* reporter_helper) { + test_case_name_ = std::string(test_case_name); + collector_.Start(); + start_time_ = clock_->CurrentTime(); +} + +void StatsBasedNetworkQualityMetricsReporter::OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) { + PCStats cur_stats; + + auto inbound_stats = report->GetStatsOfType<RTCInboundRTPStreamStats>(); + for (const auto& stat : inbound_stats) { + cur_stats.payload_received += + DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul) + + stat->header_bytes_received.ValueOrDefault(0ul)); + } + + auto outbound_stats = report->GetStatsOfType<RTCOutboundRTPStreamStats>(); + for (const auto& stat : outbound_stats) { + cur_stats.payload_sent += + DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul) + + stat->header_bytes_sent.ValueOrDefault(0ul)); + } + + auto candidate_pairs_stats = report->GetStatsOfType<RTCTransportStats>(); + for (const auto& stat : candidate_pairs_stats) { + cur_stats.total_received += + DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul)); + cur_stats.total_sent += + DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul)); + cur_stats.packets_received += stat->packets_received.ValueOrDefault(0ul); + cur_stats.packets_sent += stat->packets_sent.ValueOrDefault(0ul); + } + + MutexLock lock(&mutex_); + pc_stats_[std::string(pc_label)] = cur_stats; +} + +void StatsBasedNetworkQualityMetricsReporter::StopAndReportResults() { + Timestamp end_time = clock_->CurrentTime(); + + if (!webrtc::field_trial::IsEnabled(kUseStandardBytesStats)) { + RTC_LOG(LS_ERROR) + << "Non-standard GetStats; \"payload\" counts include RTP headers"; + } + + std::map<std::string, NetworkLayerStats> stats = collector_.GetStats(); + for (const auto& entry : stats) { + LogNetworkLayerStats(entry.first, entry.second); + } + MutexLock lock(&mutex_); + for (const auto& pair : pc_stats_) { + auto it = stats.find(pair.first); + RTC_CHECK(it != stats.end()) + << "Peer name used for PeerConnection stats collection and peer name " + "used for endpoints naming doesn't match. No endpoints found for " + "peer " + << pair.first; + const NetworkLayerStats& network_layer_stats = it->second; + int64_t total_packets_received = 0; + bool found = false; + for (const auto& dest_peer : network_layer_stats.receivers) { + auto pc_stats_it = pc_stats_.find(dest_peer); + if (pc_stats_it == pc_stats_.end()) { + continue; + } + found = true; + total_packets_received += pc_stats_it->second.packets_received; + } + int64_t packet_loss = -1; + if (found) { + packet_loss = pair.second.packets_sent - total_packets_received; + } + ReportStats(pair.first, pair.second, network_layer_stats, packet_loss, + end_time); + } +} + +void StatsBasedNetworkQualityMetricsReporter::ReportStats( + const std::string& pc_label, + const PCStats& pc_stats, + const NetworkLayerStats& network_layer_stats, + int64_t packet_loss, + const Timestamp& end_time) { + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> metric_metadata{ + {MetricMetadataKey::kPeerMetadataKey, pc_label}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; + metrics_logger_->LogSingleValueMetric( + "bytes_discarded_no_receiver", GetTestCaseName(pc_label), + network_layer_stats.endpoints_stats.overall_incoming_stats + .bytes_discarded_no_receiver.bytes(), + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "packets_discarded_no_receiver", GetTestCaseName(pc_label), + network_layer_stats.endpoints_stats.overall_incoming_stats + .packets_discarded_no_receiver, + Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); + + metrics_logger_->LogSingleValueMetric( + "payload_bytes_received", GetTestCaseName(pc_label), + pc_stats.payload_received.bytes(), Unit::kBytes, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "payload_bytes_sent", GetTestCaseName(pc_label), + pc_stats.payload_sent.bytes(), Unit::kBytes, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + + metrics_logger_->LogSingleValueMetric( + "bytes_sent", GetTestCaseName(pc_label), pc_stats.total_sent.bytes(), + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "packets_sent", GetTestCaseName(pc_label), pc_stats.packets_sent, + Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "average_send_rate", GetTestCaseName(pc_label), + (pc_stats.total_sent / (end_time - start_time_)).kbps<double>(), + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, + metric_metadata); + metrics_logger_->LogSingleValueMetric( + "bytes_received", GetTestCaseName(pc_label), + pc_stats.total_received.bytes(), Unit::kBytes, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "packets_received", GetTestCaseName(pc_label), pc_stats.packets_received, + Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); + metrics_logger_->LogSingleValueMetric( + "average_receive_rate", GetTestCaseName(pc_label), + (pc_stats.total_received / (end_time - start_time_)).kbps<double>(), + Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, + metric_metadata); + metrics_logger_->LogSingleValueMetric( + "sent_packets_loss", GetTestCaseName(pc_label), packet_loss, + Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); +} + +std::string StatsBasedNetworkQualityMetricsReporter::GetTestCaseName( + absl::string_view network_label) const { + rtc::StringBuilder builder; + builder << test_case_name_ << "/" << network_label.data(); + return builder.str(); +} + +void StatsBasedNetworkQualityMetricsReporter::LogNetworkLayerStats( + const std::string& peer_name, + const NetworkLayerStats& stats) const { + DataRate average_send_rate = + stats.endpoints_stats.overall_outgoing_stats.packets_sent >= 2 + ? stats.endpoints_stats.overall_outgoing_stats.AverageSendRate() + : DataRate::Zero(); + DataRate average_receive_rate = + stats.endpoints_stats.overall_incoming_stats.packets_received >= 2 + ? stats.endpoints_stats.overall_incoming_stats.AverageReceiveRate() + : DataRate::Zero(); + // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. + std::map<std::string, std::string> metric_metadata{ + {MetricMetadataKey::kPeerMetadataKey, peer_name}, + {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; + rtc::StringBuilder log; + log << "Raw network layer statistic for [" << peer_name << "]:\n" + << "Local IPs:\n"; + for (size_t i = 0; i < stats.endpoints_stats.local_addresses.size(); ++i) { + log << " " << stats.endpoints_stats.local_addresses[i].ToString() << "\n"; + } + if (!stats.endpoints_stats.overall_outgoing_stats.sent_packets_size + .IsEmpty()) { + metrics_logger_->LogMetric( + "sent_packets_size", GetTestCaseName(peer_name), + stats.endpoints_stats.overall_outgoing_stats.sent_packets_size, + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.endpoints_stats.overall_incoming_stats.received_packets_size + .IsEmpty()) { + metrics_logger_->LogMetric( + "received_packets_size", GetTestCaseName(peer_name), + stats.endpoints_stats.overall_incoming_stats.received_packets_size, + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.endpoints_stats.overall_incoming_stats + .packets_discarded_no_receiver_size.IsEmpty()) { + metrics_logger_->LogMetric( + "packets_discarded_no_receiver_size", GetTestCaseName(peer_name), + stats.endpoints_stats.overall_incoming_stats + .packets_discarded_no_receiver_size, + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.endpoints_stats.sent_packets_queue_wait_time_us.IsEmpty()) { + metrics_logger_->LogMetric( + "sent_packets_queue_wait_time_us", GetTestCaseName(peer_name), + stats.endpoints_stats.sent_packets_queue_wait_time_us, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + + log << "Send statistic:\n" + << " packets: " + << stats.endpoints_stats.overall_outgoing_stats.packets_sent << " bytes: " + << stats.endpoints_stats.overall_outgoing_stats.bytes_sent.bytes() + << " avg_rate (bytes/sec): " << average_send_rate.bytes_per_sec() + << " avg_rate (bps): " << average_send_rate.bps() << "\n" + << "Send statistic per destination:\n"; + + for (const auto& entry : + stats.endpoints_stats.outgoing_stats_per_destination) { + DataRate source_average_send_rate = entry.second.packets_sent >= 2 + ? entry.second.AverageSendRate() + : DataRate::Zero(); + log << "(" << entry.first.ToString() << "):\n" + << " packets: " << entry.second.packets_sent + << " bytes: " << entry.second.bytes_sent.bytes() + << " avg_rate (bytes/sec): " << source_average_send_rate.bytes_per_sec() + << " avg_rate (bps): " << source_average_send_rate.bps() << "\n"; + if (!entry.second.sent_packets_size.IsEmpty()) { + metrics_logger_->LogMetric( + "sent_packets_size", + GetTestCaseName(peer_name + "/" + entry.first.ToString()), + entry.second.sent_packets_size, Unit::kBytes, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + } + + if (!stats.uplink_stats.packet_transport_time.IsEmpty()) { + log << "[Debug stats] packet_transport_time=(" + << stats.uplink_stats.packet_transport_time.GetAverage() << ", " + << stats.uplink_stats.packet_transport_time.GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "uplink_packet_transport_time", GetTestCaseName(peer_name), + stats.uplink_stats.packet_transport_time, Unit::kMilliseconds, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.uplink_stats.size_to_packet_transport_time.IsEmpty()) { + log << "[Debug stats] size_to_packet_transport_time=(" + << stats.uplink_stats.size_to_packet_transport_time.GetAverage() << ", " + << stats.uplink_stats.size_to_packet_transport_time + .GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "uplink_size_to_packet_transport_time", GetTestCaseName(peer_name), + stats.uplink_stats.size_to_packet_transport_time, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + + log << "Receive statistic:\n" + << " packets: " + << stats.endpoints_stats.overall_incoming_stats.packets_received + << " bytes: " + << stats.endpoints_stats.overall_incoming_stats.bytes_received.bytes() + << " avg_rate (bytes/sec): " << average_receive_rate.bytes_per_sec() + << " avg_rate (bps): " << average_receive_rate.bps() << "\n" + << "Receive statistic per source:\n"; + + for (const auto& entry : stats.endpoints_stats.incoming_stats_per_source) { + DataRate source_average_receive_rate = + entry.second.packets_received >= 2 ? entry.second.AverageReceiveRate() + : DataRate::Zero(); + log << "(" << entry.first.ToString() << "):\n" + << " packets: " << entry.second.packets_received + << " bytes: " << entry.second.bytes_received.bytes() + << " avg_rate (bytes/sec): " + << source_average_receive_rate.bytes_per_sec() + << " avg_rate (bps): " << source_average_receive_rate.bps() << "\n"; + if (!entry.second.received_packets_size.IsEmpty()) { + metrics_logger_->LogMetric( + "received_packets_size", + GetTestCaseName(peer_name + "/" + entry.first.ToString()), + entry.second.received_packets_size, Unit::kBytes, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!entry.second.packets_discarded_no_receiver_size.IsEmpty()) { + metrics_logger_->LogMetric( + "packets_discarded_no_receiver_size", + GetTestCaseName(peer_name + "/" + entry.first.ToString()), + entry.second.packets_discarded_no_receiver_size, Unit::kBytes, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + } + if (!stats.downlink_stats.packet_transport_time.IsEmpty()) { + log << "[Debug stats] packet_transport_time=(" + << stats.downlink_stats.packet_transport_time.GetAverage() << ", " + << stats.downlink_stats.packet_transport_time.GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "downlink_packet_transport_time", GetTestCaseName(peer_name), + stats.downlink_stats.packet_transport_time, Unit::kMilliseconds, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.downlink_stats.size_to_packet_transport_time.IsEmpty()) { + log << "[Debug stats] size_to_packet_transport_time=(" + << stats.downlink_stats.size_to_packet_transport_time.GetAverage() + << ", " + << stats.downlink_stats.size_to_packet_transport_time + .GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "downlink_size_to_packet_transport_time", GetTestCaseName(peer_name), + stats.downlink_stats.size_to_packet_transport_time, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + + RTC_LOG(LS_INFO) << log.str(); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.h b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.h new file mode 100644 index 0000000000..60daf40c8c --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.h @@ -0,0 +1,136 @@ +/* + * 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_PC_E2E_STATS_BASED_NETWORK_QUALITY_METRICS_REPORTER_H_ +#define TEST_PC_E2E_STATS_BASED_NETWORK_QUALITY_METRICS_REPORTER_H_ + +#include <cstdint> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/network_emulation/network_emulation_interfaces.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "rtc_base/ip_address.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// TODO(titovartem): make this class testable and add tests. +class StatsBasedNetworkQualityMetricsReporter + : public PeerConnectionE2EQualityTestFixture::QualityMetricsReporter { + public: + // Emulated network layer stats for single peer. + struct NetworkLayerStats { + EmulatedNetworkStats endpoints_stats; + EmulatedNetworkNodeStats uplink_stats; + EmulatedNetworkNodeStats downlink_stats; + std::set<std::string> receivers; + }; + + // `networks` map peer name to network to report network layer stability stats + // and to log network layer metrics. + StatsBasedNetworkQualityMetricsReporter( + std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints, + NetworkEmulationManager* network_emulation, + test::MetricsLogger* metrics_logger); + ~StatsBasedNetworkQualityMetricsReporter() override = default; + + void AddPeer(absl::string_view peer_name, + std::vector<EmulatedEndpoint*> endpoints); + void AddPeer(absl::string_view peer_name, + std::vector<EmulatedEndpoint*> endpoints, + std::vector<EmulatedNetworkNode*> uplink, + std::vector<EmulatedNetworkNode*> downlink); + + // Network stats must be empty when this method will be invoked. + void Start(absl::string_view test_case_name, + const TrackIdStreamInfoMap* reporter_helper) override; + void OnStatsReports( + absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + void StopAndReportResults() override; + + private: + struct PCStats { + // TODO(bugs.webrtc.org/10525): Separate audio and video counters. Depends + // on standard stat counters, enabled by field trial + // "WebRTC-UseStandardBytesStats". + DataSize payload_received = DataSize::Zero(); + DataSize payload_sent = DataSize::Zero(); + + // Total bytes/packets sent/received in all RTCTransport's. + DataSize total_received = DataSize::Zero(); + DataSize total_sent = DataSize::Zero(); + int64_t packets_received = 0; + int64_t packets_sent = 0; + }; + + class NetworkLayerStatsCollector { + public: + NetworkLayerStatsCollector( + std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints, + NetworkEmulationManager* network_emulation); + + void Start(); + + void AddPeer(absl::string_view peer_name, + std::vector<EmulatedEndpoint*> endpoints, + std::vector<EmulatedNetworkNode*> uplink, + std::vector<EmulatedNetworkNode*> downlink); + + std::map<std::string, NetworkLayerStats> GetStats(); + + private: + Mutex mutex_; + std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints_ + RTC_GUARDED_BY(mutex_); + std::map<std::string, std::vector<EmulatedNetworkNode*>> peer_uplinks_ + RTC_GUARDED_BY(mutex_); + std::map<std::string, std::vector<EmulatedNetworkNode*>> peer_downlinks_ + RTC_GUARDED_BY(mutex_); + std::map<rtc::IPAddress, std::string> ip_to_peer_ RTC_GUARDED_BY(mutex_); + NetworkEmulationManager* const network_emulation_; + }; + + void ReportStats(const std::string& pc_label, + const PCStats& pc_stats, + const NetworkLayerStats& network_layer_stats, + int64_t packet_loss, + const Timestamp& end_time); + std::string GetTestCaseName(absl::string_view network_label) const; + void LogNetworkLayerStats(const std::string& peer_name, + const NetworkLayerStats& stats) const; + + NetworkLayerStatsCollector collector_; + Clock* const clock_; + test::MetricsLogger* const metrics_logger_; + + std::string test_case_name_; + Timestamp start_time_ = Timestamp::MinusInfinity(); + + Mutex mutex_; + std::map<std::string, PCStats> pc_stats_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_STATS_BASED_NETWORK_QUALITY_METRICS_REPORTER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc new file mode 100644 index 0000000000..be55149482 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc @@ -0,0 +1,150 @@ +/* + * 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/pc/e2e/stats_based_network_quality_metrics_reporter.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/test/create_network_emulation_manager.h" +#include "api/test/create_peer_connection_quality_test_frame_generator.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/metrics/stdout_metrics_exporter.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/units/time_delta.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/pc/e2e/metric_metadata_keys.h" +#include "test/pc/e2e/peer_connection_quality_test.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::UnorderedElementsAre; + +using ::webrtc::test::DefaultMetricsLogger; +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Metric; +using ::webrtc::test::Unit; +using ::webrtc::webrtc_pc_e2e::PeerConfigurer; + +// Adds a peer with some audio and video (the client should not care about +// details about audio and video configs). +void AddDefaultAudioVideoPeer( + absl::string_view peer_name, + absl::string_view audio_stream_label, + absl::string_view video_stream_label, + const PeerNetworkDependencies& network_dependencies, + PeerConnectionE2EQualityTestFixture& fixture) { + AudioConfig audio{std::string(audio_stream_label)}; + audio.sync_group = std::string(peer_name); + VideoConfig video(std::string(video_stream_label), 320, 180, 15); + video.sync_group = std::string(peer_name); + auto peer = std::make_unique<PeerConfigurer>(network_dependencies); + peer->SetName(peer_name); + peer->SetAudioConfig(std::move(audio)); + peer->AddVideoConfig(std::move(video)); + peer->SetVideoCodecs({VideoCodecConfig(cricket::kVp8CodecName)}); + fixture.AddPeer(std::move(peer)); +} + +absl::optional<Metric> FindMeetricByName(absl::string_view name, + rtc::ArrayView<const Metric> metrics) { + for (const Metric& metric : metrics) { + if (metric.name == name) { + return metric; + } + } + return absl::nullopt; +} + +TEST(StatsBasedNetworkQualityMetricsReporterTest, DebugStatsAreCollected) { + std::unique_ptr<NetworkEmulationManager> network_emulation = + CreateNetworkEmulationManager(TimeMode::kSimulated, + EmulatedNetworkStatsGatheringMode::kDebug); + DefaultMetricsLogger metrics_logger( + network_emulation->time_controller()->GetClock()); + PeerConnectionE2EQualityTest fixture( + "test_case", *network_emulation->time_controller(), + /*audio_quality_analyzer=*/nullptr, /*video_quality_analyzer=*/nullptr, + &metrics_logger); + + EmulatedEndpoint* alice_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + + EmulatedNetworkNode* alice_link = network_emulation->CreateEmulatedNode( + BuiltInNetworkBehaviorConfig{.link_capacity_kbps = 500}); + network_emulation->CreateRoute(alice_endpoint, {alice_link}, bob_endpoint); + EmulatedNetworkNode* bob_link = network_emulation->CreateEmulatedNode( + BuiltInNetworkBehaviorConfig{.link_capacity_kbps = 500}); + network_emulation->CreateRoute(bob_endpoint, {bob_link}, alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + network_emulation->CreateEmulatedNetworkManagerInterface( + {alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + network_emulation->CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + AddDefaultAudioVideoPeer("alice", "alice_audio", "alice_video", + alice_network->network_dependencies(), fixture); + AddDefaultAudioVideoPeer("bob", "bob_audio", "bob_video", + bob_network->network_dependencies(), fixture); + + auto network_stats_reporter = + std::make_unique<StatsBasedNetworkQualityMetricsReporter>( + /*peer_endpoints=*/std::map<std::string, + std::vector<EmulatedEndpoint*>>{}, + network_emulation.get(), &metrics_logger); + network_stats_reporter->AddPeer("alice", alice_network->endpoints(), + /*uplink=*/{alice_link}, + /*downlink=*/{bob_link}); + network_stats_reporter->AddPeer("bob", bob_network->endpoints(), + /*uplink=*/{bob_link}, + /*downlink=*/{alice_link}); + fixture.AddQualityMetricsReporter(std::move(network_stats_reporter)); + + fixture.Run(RunParams(TimeDelta::Seconds(4))); + + std::vector<Metric> metrics = metrics_logger.GetCollectedMetrics(); + absl::optional<Metric> uplink_packet_transport_time = + FindMeetricByName("uplink_packet_transport_time", metrics); + ASSERT_TRUE(uplink_packet_transport_time.has_value()); + ASSERT_FALSE(uplink_packet_transport_time->time_series.samples.empty()); + absl::optional<Metric> uplink_size_to_packet_transport_time = + FindMeetricByName("uplink_size_to_packet_transport_time", metrics); + ASSERT_TRUE(uplink_size_to_packet_transport_time.has_value()); + ASSERT_FALSE( + uplink_size_to_packet_transport_time->time_series.samples.empty()); + absl::optional<Metric> downlink_packet_transport_time = + FindMeetricByName("downlink_packet_transport_time", metrics); + ASSERT_TRUE(downlink_packet_transport_time.has_value()); + ASSERT_FALSE(downlink_packet_transport_time->time_series.samples.empty()); + absl::optional<Metric> downlink_size_to_packet_transport_time = + FindMeetricByName("downlink_size_to_packet_transport_time", metrics); + ASSERT_TRUE(downlink_size_to_packet_transport_time.has_value()); + ASSERT_FALSE( + downlink_size_to_packet_transport_time->time_series.samples.empty()); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/stats_poller.cc b/third_party/libwebrtc/test/pc/e2e/stats_poller.cc new file mode 100644 index 0000000000..c04805fb20 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_poller.cc @@ -0,0 +1,78 @@ +/* + * 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/pc/e2e/stats_poller.h" + +#include <utility> + +#include "rtc_base/logging.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +void InternalStatsObserver::PollStats() { + peer_->GetStats(this); +} + +void InternalStatsObserver::OnStatsDelivered( + const rtc::scoped_refptr<const RTCStatsReport>& report) { + for (auto* observer : observers_) { + observer->OnStatsReports(pc_label_, report); + } +} + +StatsPoller::StatsPoller(std::vector<StatsObserverInterface*> observers, + std::map<std::string, StatsProvider*> peers) + : observers_(std::move(observers)) { + webrtc::MutexLock lock(&mutex_); + for (auto& peer : peers) { + pollers_.push_back(rtc::make_ref_counted<InternalStatsObserver>( + peer.first, peer.second, observers_)); + } +} + +StatsPoller::StatsPoller(std::vector<StatsObserverInterface*> observers, + std::map<std::string, TestPeer*> peers) + : observers_(std::move(observers)) { + webrtc::MutexLock lock(&mutex_); + for (auto& peer : peers) { + pollers_.push_back(rtc::make_ref_counted<InternalStatsObserver>( + peer.first, peer.second, observers_)); + } +} + +void StatsPoller::PollStatsAndNotifyObservers() { + webrtc::MutexLock lock(&mutex_); + for (auto& poller : pollers_) { + poller->PollStats(); + } +} + +void StatsPoller::RegisterParticipantInCall(absl::string_view peer_name, + StatsProvider* peer) { + webrtc::MutexLock lock(&mutex_); + pollers_.push_back(rtc::make_ref_counted<InternalStatsObserver>( + peer_name, peer, observers_)); +} + +bool StatsPoller::UnregisterParticipantInCall(absl::string_view peer_name) { + webrtc::MutexLock lock(&mutex_); + for (auto it = pollers_.begin(); it != pollers_.end(); ++it) { + if ((*it)->pc_label() == peer_name) { + pollers_.erase(it); + return true; + } + } + return false; +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/stats_poller.h b/third_party/libwebrtc/test/pc/e2e/stats_poller.h new file mode 100644 index 0000000000..3576f1bf05 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_poller.h @@ -0,0 +1,80 @@ +/* + * 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_PC_E2E_STATS_POLLER_H_ +#define TEST_PC_E2E_STATS_POLLER_H_ + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "api/peer_connection_interface.h" +#include "api/stats/rtc_stats_collector_callback.h" +#include "api/test/stats_observer_interface.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "test/pc/e2e/stats_provider.h" +#include "test/pc/e2e/test_peer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Helper class that will notify all the webrtc::test::StatsObserverInterface +// objects subscribed. +class InternalStatsObserver : public RTCStatsCollectorCallback { + public: + InternalStatsObserver(absl::string_view pc_label, + StatsProvider* peer, + std::vector<StatsObserverInterface*> observers) + : pc_label_(pc_label), peer_(peer), observers_(std::move(observers)) {} + + std::string pc_label() const { return pc_label_; } + + void PollStats(); + + void OnStatsDelivered( + const rtc::scoped_refptr<const RTCStatsReport>& report) override; + + private: + std::string pc_label_; + StatsProvider* peer_; + std::vector<StatsObserverInterface*> observers_; +}; + +// Helper class to invoke GetStats on a PeerConnection by passing a +// webrtc::StatsObserver that will notify all the +// webrtc::test::StatsObserverInterface subscribed. +class StatsPoller { + public: + StatsPoller(std::vector<StatsObserverInterface*> observers, + std::map<std::string, StatsProvider*> peers_to_observe); + StatsPoller(std::vector<StatsObserverInterface*> observers, + std::map<std::string, TestPeer*> peers_to_observe); + + void PollStatsAndNotifyObservers(); + + void RegisterParticipantInCall(absl::string_view peer_name, + StatsProvider* peer); + // Unregister participant from stats poller. Returns true if participant was + // removed and false if participant wasn't found. + bool UnregisterParticipantInCall(absl::string_view peer_name); + + private: + const std::vector<StatsObserverInterface*> observers_; + webrtc::Mutex mutex_; + std::vector<rtc::scoped_refptr<InternalStatsObserver>> pollers_ + RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_STATS_POLLER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/stats_poller_test.cc b/third_party/libwebrtc/test/pc/e2e/stats_poller_test.cc new file mode 100644 index 0000000000..02a323127b --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_poller_test.cc @@ -0,0 +1,90 @@ +/* + * 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/pc/e2e/stats_poller.h" + +#include "api/stats/rtc_stats_collector_callback.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::Eq; + +class TestStatsProvider : public StatsProvider { + public: + ~TestStatsProvider() override = default; + + void GetStats(RTCStatsCollectorCallback* callback) override { + stats_collections_count_++; + } + + int stats_collections_count() const { return stats_collections_count_; } + + private: + int stats_collections_count_ = 0; +}; + +class MockStatsObserver : public StatsObserverInterface { + public: + ~MockStatsObserver() override = default; + + MOCK_METHOD(void, + OnStatsReports, + (absl::string_view pc_label, + const rtc::scoped_refptr<const RTCStatsReport>& report)); +}; + +TEST(StatsPollerTest, UnregisterParticipantAddedInCtor) { + TestStatsProvider alice; + TestStatsProvider bob; + + MockStatsObserver stats_observer; + + StatsPoller poller(/*observers=*/{&stats_observer}, + /*peers_to_observe=*/{{"alice", &alice}, {"bob", &bob}}); + poller.PollStatsAndNotifyObservers(); + + EXPECT_THAT(alice.stats_collections_count(), Eq(1)); + EXPECT_THAT(bob.stats_collections_count(), Eq(1)); + + poller.UnregisterParticipantInCall("bob"); + poller.PollStatsAndNotifyObservers(); + + EXPECT_THAT(alice.stats_collections_count(), Eq(2)); + EXPECT_THAT(bob.stats_collections_count(), Eq(1)); +} + +TEST(StatsPollerTest, UnregisterParticipantRegisteredInCall) { + TestStatsProvider alice; + TestStatsProvider bob; + + MockStatsObserver stats_observer; + + StatsPoller poller(/*observers=*/{&stats_observer}, + /*peers_to_observe=*/{{"alice", &alice}}); + poller.RegisterParticipantInCall("bob", &bob); + poller.PollStatsAndNotifyObservers(); + + EXPECT_THAT(alice.stats_collections_count(), Eq(1)); + EXPECT_THAT(bob.stats_collections_count(), Eq(1)); + + poller.UnregisterParticipantInCall("bob"); + poller.PollStatsAndNotifyObservers(); + + EXPECT_THAT(alice.stats_collections_count(), Eq(2)); + EXPECT_THAT(bob.stats_collections_count(), Eq(1)); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/stats_provider.h b/third_party/libwebrtc/test/pc/e2e/stats_provider.h new file mode 100644 index 0000000000..eef62d779c --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/stats_provider.h @@ -0,0 +1,29 @@ +/* + * 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_PC_E2E_STATS_PROVIDER_H_ +#define TEST_PC_E2E_STATS_PROVIDER_H_ + +#include "api/stats/rtc_stats_collector_callback.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class StatsProvider { + public: + virtual ~StatsProvider() = default; + + virtual void GetStats(RTCStatsCollectorCallback* callback) = 0; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_STATS_PROVIDER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/test_activities_executor.cc b/third_party/libwebrtc/test/pc/e2e/test_activities_executor.cc new file mode 100644 index 0000000000..7bcf7dd6c3 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/test_activities_executor.cc @@ -0,0 +1,122 @@ +/* + * 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/pc/e2e/test_activities_executor.h" + +#include <memory> +#include <utility> + +#include "absl/memory/memory.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/task_queue_for_test.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +void TestActivitiesExecutor::Start(TaskQueueBase* task_queue) { + RTC_DCHECK(task_queue); + task_queue_ = task_queue; + MutexLock lock(&lock_); + start_time_ = Now(); + while (!scheduled_activities_.empty()) { + PostActivity(std::move(scheduled_activities_.front())); + scheduled_activities_.pop(); + } +} + +void TestActivitiesExecutor::Stop() { + if (task_queue_ == nullptr) { + // Already stopped or not started. + return; + } + SendTask(task_queue_, [this]() { + MutexLock lock(&lock_); + for (auto& handle : repeating_task_handles_) { + handle.Stop(); + } + }); + task_queue_ = nullptr; +} + +void TestActivitiesExecutor::ScheduleActivity( + TimeDelta initial_delay_since_start, + absl::optional<TimeDelta> interval, + std::function<void(TimeDelta)> func) { + RTC_CHECK(initial_delay_since_start.IsFinite() && + initial_delay_since_start >= TimeDelta::Zero()); + RTC_CHECK(!interval || + (interval->IsFinite() && *interval > TimeDelta::Zero())); + MutexLock lock(&lock_); + ScheduledActivity activity(initial_delay_since_start, interval, func); + if (start_time_.IsInfinite()) { + scheduled_activities_.push(std::move(activity)); + } else { + PostActivity(std::move(activity)); + } +} + +void TestActivitiesExecutor::PostActivity(ScheduledActivity activity) { + // Because start_time_ will never change at this point copy it to local + // variable to capture in in lambda without requirement to hold a lock. + Timestamp start_time = start_time_; + + TimeDelta remaining_delay = + activity.initial_delay_since_start == TimeDelta::Zero() + ? TimeDelta::Zero() + : activity.initial_delay_since_start - (Now() - start_time); + if (remaining_delay < TimeDelta::Zero()) { + RTC_LOG(LS_WARNING) << "Executing late task immediately, late by=" + << ToString(remaining_delay.Abs()); + remaining_delay = TimeDelta::Zero(); + } + + if (activity.interval) { + if (remaining_delay == TimeDelta::Zero()) { + repeating_task_handles_.push_back(RepeatingTaskHandle::Start( + task_queue_, [activity, start_time, this]() { + activity.func(Now() - start_time); + return *activity.interval; + })); + return; + } + repeating_task_handles_.push_back(RepeatingTaskHandle::DelayedStart( + task_queue_, remaining_delay, [activity, start_time, this]() { + activity.func(Now() - start_time); + return *activity.interval; + })); + return; + } + + if (remaining_delay == TimeDelta::Zero()) { + task_queue_->PostTask( + [activity, start_time, this]() { activity.func(Now() - start_time); }); + return; + } + + task_queue_->PostDelayedTask( + [activity, start_time, this]() { activity.func(Now() - start_time); }, + remaining_delay); +} + +Timestamp TestActivitiesExecutor::Now() const { + return clock_->CurrentTime(); +} + +TestActivitiesExecutor::ScheduledActivity::ScheduledActivity( + TimeDelta initial_delay_since_start, + absl::optional<TimeDelta> interval, + std::function<void(TimeDelta)> func) + : initial_delay_since_start(initial_delay_since_start), + interval(interval), + func(std::move(func)) {} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/test_activities_executor.h b/third_party/libwebrtc/test/pc/e2e/test_activities_executor.h new file mode 100644 index 0000000000..2469ac7f36 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/test_activities_executor.h @@ -0,0 +1,85 @@ +/* + * 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_PC_E2E_TEST_ACTIVITIES_EXECUTOR_H_ +#define TEST_PC_E2E_TEST_ACTIVITIES_EXECUTOR_H_ + +#include <queue> +#include <vector> + +#include "absl/types/optional.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +class TestActivitiesExecutor { + public: + explicit TestActivitiesExecutor(Clock* clock) : clock_(clock) {} + ~TestActivitiesExecutor() { Stop(); } + + // Starts scheduled activities according to their schedule. All activities + // that will be scheduled after Start(...) was invoked will be executed + // immediately according to their schedule. + void Start(TaskQueueForTest* task_queue) { Start(task_queue->Get()); } + void Start(TaskQueueBase* task_queue); + void Stop(); + + // Schedule activity to be executed. If test isn't started yet, then activity + // will be executed according to its schedule after Start() will be invoked. + // If test is started, then it will be executed immediately according to its + // schedule. + void ScheduleActivity(TimeDelta initial_delay_since_start, + absl::optional<TimeDelta> interval, + std::function<void(TimeDelta)> func); + + private: + struct ScheduledActivity { + ScheduledActivity(TimeDelta initial_delay_since_start, + absl::optional<TimeDelta> interval, + std::function<void(TimeDelta)> func); + + TimeDelta initial_delay_since_start; + absl::optional<TimeDelta> interval; + std::function<void(TimeDelta)> func; + }; + + void PostActivity(ScheduledActivity activity) + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + Timestamp Now() const; + + Clock* const clock_; + + TaskQueueBase* task_queue_; + + Mutex lock_; + // Time when test was started. Minus infinity means that it wasn't started + // yet. + Timestamp start_time_ RTC_GUARDED_BY(lock_) = Timestamp::MinusInfinity(); + // Queue of activities that were added before test was started. + // Activities from this queue will be posted on the `task_queue_` after test + // will be set up and then this queue will be unused. + std::queue<ScheduledActivity> scheduled_activities_ RTC_GUARDED_BY(lock_); + // List of task handles for activities, that are posted on `task_queue_` as + // repeated during the call. + std::vector<RepeatingTaskHandle> repeating_task_handles_ + RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_TEST_ACTIVITIES_EXECUTOR_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/test_peer.cc b/third_party/libwebrtc/test/pc/e2e/test_peer.cc new file mode 100644 index 0000000000..b3a9e1c164 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/test_peer.cc @@ -0,0 +1,151 @@ +/* + * 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/pc/e2e/test_peer.h" + +#include <string> +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "api/scoped_refptr.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/peer_configurer.h" +#include "modules/audio_processing/include/audio_processing.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +class SetRemoteDescriptionCallback + : public webrtc::SetRemoteDescriptionObserverInterface { + public: + void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override { + is_called_ = true; + error_ = error; + } + + bool is_called() const { return is_called_; } + + webrtc::RTCError error() const { return error_; } + + private: + bool is_called_ = false; + webrtc::RTCError error_; +}; + +} // namespace + +ConfigurableParams TestPeer::configurable_params() const { + MutexLock lock(&mutex_); + return configurable_params_; +} + +void TestPeer::AddVideoConfig(VideoConfig config) { + MutexLock lock(&mutex_); + configurable_params_.video_configs.push_back(std::move(config)); +} + +void TestPeer::RemoveVideoConfig(absl::string_view stream_label) { + MutexLock lock(&mutex_); + bool config_removed = false; + for (auto it = configurable_params_.video_configs.begin(); + it != configurable_params_.video_configs.end(); ++it) { + if (*it->stream_label == stream_label) { + configurable_params_.video_configs.erase(it); + config_removed = true; + break; + } + } + RTC_CHECK(config_removed) << *params_.name << ": No video config with label [" + << stream_label << "] was found"; +} + +void TestPeer::SetVideoSubscription(VideoSubscription subscription) { + MutexLock lock(&mutex_); + configurable_params_.video_subscription = std::move(subscription); +} + +void TestPeer::GetStats(RTCStatsCollectorCallback* callback) { + pc()->signaling_thread()->PostTask( + SafeTask(signaling_thread_task_safety_, + [this, callback]() { pc()->GetStats(callback); })); +} + +bool TestPeer::SetRemoteDescription( + std::unique_ptr<SessionDescriptionInterface> desc, + std::string* error_out) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + + auto observer = rtc::make_ref_counted<SetRemoteDescriptionCallback>(); + // We're assuming (and asserting) that the PeerConnection implementation of + // SetRemoteDescription is synchronous when called on the signaling thread. + pc()->SetRemoteDescription(std::move(desc), observer); + RTC_CHECK(observer->is_called()); + if (!observer->error().ok()) { + RTC_LOG(LS_ERROR) << *params_.name << ": Failed to set remote description: " + << observer->error().message(); + if (error_out) { + *error_out = observer->error().message(); + } + } + return observer->error().ok(); +} + +bool TestPeer::AddIceCandidates( + std::vector<std::unique_ptr<IceCandidateInterface>> candidates) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + bool success = true; + for (auto& candidate : candidates) { + if (!pc()->AddIceCandidate(candidate.get())) { + std::string candidate_str; + bool res = candidate->ToString(&candidate_str); + RTC_CHECK(res); + RTC_LOG(LS_ERROR) << "Failed to add ICE candidate, candidate_str=" + << candidate_str; + success = false; + } else { + remote_ice_candidates_.push_back(std::move(candidate)); + } + } + return success; +} + +void TestPeer::Close() { + signaling_thread_task_safety_->SetNotAlive(); + wrapper_->pc()->Close(); + remote_ice_candidates_.clear(); + audio_processing_ = nullptr; + video_sources_.clear(); + wrapper_ = nullptr; + worker_thread_ = nullptr; +} + +TestPeer::TestPeer( + rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory, + rtc::scoped_refptr<PeerConnectionInterface> pc, + std::unique_ptr<MockPeerConnectionObserver> observer, + Params params, + ConfigurableParams configurable_params, + std::vector<PeerConfigurer::VideoSource> video_sources, + rtc::scoped_refptr<AudioProcessing> audio_processing, + std::unique_ptr<rtc::Thread> worker_thread) + : params_(std::move(params)), + configurable_params_(std::move(configurable_params)), + worker_thread_(std::move(worker_thread)), + wrapper_(std::make_unique<PeerConnectionWrapper>(std::move(pc_factory), + std::move(pc), + std::move(observer))), + video_sources_(std::move(video_sources)), + audio_processing_(audio_processing) { + signaling_thread_task_safety_ = PendingTaskSafetyFlag::CreateDetached(); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/test_peer.h b/third_party/libwebrtc/test/pc/e2e/test_peer.h new file mode 100644 index 0000000000..1088871817 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/test_peer.h @@ -0,0 +1,188 @@ +/* + * 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_PC_E2E_TEST_PEER_H_ +#define TEST_PC_E2E_TEST_PEER_H_ + +#include <memory> +#include <vector> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "api/function_view.h" +#include "api/scoped_refptr.h" +#include "api/sequence_checker.h" +#include "api/set_remote_description_observer_interface.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "api/test/frame_generator_interface.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "pc/peer_connection_wrapper.h" +#include "rtc_base/logging.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/pc/e2e/stats_provider.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +// Describes a single participant in the call. +class TestPeer final : public StatsProvider { + public: + ~TestPeer() override = default; + + const Params& params() const { return params_; } + + ConfigurableParams configurable_params() const; + void AddVideoConfig(VideoConfig config); + // Removes video config with specified name. Crashes if the config with + // specified name isn't found. + void RemoveVideoConfig(absl::string_view stream_label); + void SetVideoSubscription(VideoSubscription subscription); + + void GetStats(RTCStatsCollectorCallback* callback) override; + + PeerConfigurer::VideoSource ReleaseVideoSource(size_t i) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return std::move(video_sources_[i]); + } + + PeerConnectionFactoryInterface* pc_factory() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->pc_factory(); + } + PeerConnectionInterface* pc() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->pc(); + } + MockPeerConnectionObserver* observer() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->observer(); + } + + // Tell underlying `PeerConnection` to create an Offer. + // `observer` will be invoked on the signaling thread when offer is created. + void CreateOffer( + rtc::scoped_refptr<CreateSessionDescriptionObserver> observer) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + pc()->CreateOffer(observer.get(), params_.rtc_offer_answer_options); + } + std::unique_ptr<SessionDescriptionInterface> CreateOffer() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->CreateOffer(params_.rtc_offer_answer_options); + } + + std::unique_ptr<SessionDescriptionInterface> CreateAnswer() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->CreateAnswer(); + } + + bool SetLocalDescription(std::unique_ptr<SessionDescriptionInterface> desc, + std::string* error_out = nullptr) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->SetLocalDescription(std::move(desc), error_out); + } + + // `error_out` will be set only if returned value is false. + bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc, + std::string* error_out = nullptr); + + rtc::scoped_refptr<RtpTransceiverInterface> AddTransceiver( + cricket::MediaType media_type, + const RtpTransceiverInit& init) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->AddTransceiver(media_type, init); + } + + rtc::scoped_refptr<RtpSenderInterface> AddTrack( + rtc::scoped_refptr<MediaStreamTrackInterface> track, + const std::vector<std::string>& stream_ids = {}) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->AddTrack(track, stream_ids); + } + + rtc::scoped_refptr<DataChannelInterface> CreateDataChannel( + const std::string& label) { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->CreateDataChannel(label); + } + + PeerConnectionInterface::SignalingState signaling_state() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->signaling_state(); + } + + bool IsIceGatheringDone() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->IsIceGatheringDone(); + } + + bool IsIceConnected() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->IsIceConnected(); + } + + rtc::scoped_refptr<const RTCStatsReport> GetStats() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + return wrapper_->GetStats(); + } + + void DetachAecDump() { + RTC_CHECK(wrapper_) << "TestPeer is already closed"; + if (audio_processing_) { + audio_processing_->DetachAecDump(); + } + } + + // Adds provided `candidates` to the owned peer connection. + bool AddIceCandidates( + std::vector<std::unique_ptr<IceCandidateInterface>> candidates); + + // Closes underlying peer connection and destroys all related objects freeing + // up related resources. + void Close(); + + protected: + friend class TestPeerFactory; + TestPeer(rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory, + rtc::scoped_refptr<PeerConnectionInterface> pc, + std::unique_ptr<MockPeerConnectionObserver> observer, + Params params, + ConfigurableParams configurable_params, + std::vector<PeerConfigurer::VideoSource> video_sources, + rtc::scoped_refptr<AudioProcessing> audio_processing, + std::unique_ptr<rtc::Thread> worker_thread); + + private: + const Params params_; + + mutable Mutex mutex_; + ConfigurableParams configurable_params_ RTC_GUARDED_BY(mutex_); + + // Safety flag to protect all tasks posted on the signaling thread to not be + // executed after `wrapper_` object is destructed. + rtc::scoped_refptr<PendingTaskSafetyFlag> signaling_thread_task_safety_ = + nullptr; + + // Keeps ownership of worker thread. It has to be destroyed after `wrapper_`. + // `worker_thread_`can be null if the Peer use only one thread as both the + // worker thread and network thread. + std::unique_ptr<rtc::Thread> worker_thread_; + std::unique_ptr<PeerConnectionWrapper> wrapper_; + std::vector<PeerConfigurer::VideoSource> video_sources_; + rtc::scoped_refptr<AudioProcessing> audio_processing_; + + std::vector<std::unique_ptr<IceCandidateInterface>> remote_ice_candidates_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_TEST_PEER_H_ diff --git a/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc new file mode 100644 index 0000000000..7fc12f2c11 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.cc @@ -0,0 +1,374 @@ +/* + * 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/pc/e2e/test_peer_factory.h" + +#include <utility> + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "api/test/create_time_controller.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/time_controller.h" +#include "api/transport/field_trial_based_config.h" +#include "api/video_codecs/builtin_video_decoder_factory.h" +#include "api/video_codecs/builtin_video_encoder_factory.h" +#include "media/engine/webrtc_media_engine.h" +#include "media/engine/webrtc_media_engine_defaults.h" +#include "modules/audio_processing/aec_dump/aec_dump_factory.h" +#include "p2p/client/basic_port_allocator.h" +#include "rtc_base/thread.h" +#include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h" +#include "test/pc/e2e/echo/echo_emulation.h" +#include "test/testsupport/copy_to_file_audio_capturer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using EmulatedSFUConfigMap = + ::webrtc::webrtc_pc_e2e::QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap; + +constexpr int16_t kGeneratedAudioMaxAmplitude = 32000; +constexpr int kDefaultSamplingFrequencyInHz = 48000; + +// Sets mandatory entities in injectable components like `pcf_dependencies` +// and `pc_dependencies` if they are omitted. Also setup required +// dependencies, that won't be specially provided by factory and will be just +// transferred to peer connection creation code. +void SetMandatoryEntities(InjectableComponents* components, + TimeController& time_controller) { + RTC_DCHECK(components->pcf_dependencies); + RTC_DCHECK(components->pc_dependencies); + + // Setup required peer connection factory dependencies. + if (components->pcf_dependencies->task_queue_factory == nullptr) { + components->pcf_dependencies->task_queue_factory = + time_controller.CreateTaskQueueFactory(); + } + if (components->pcf_dependencies->call_factory == nullptr) { + components->pcf_dependencies->call_factory = + CreateTimeControllerBasedCallFactory(&time_controller); + } + if (components->pcf_dependencies->event_log_factory == nullptr) { + components->pcf_dependencies->event_log_factory = + std::make_unique<RtcEventLogFactory>( + components->pcf_dependencies->task_queue_factory.get()); + } + if (!components->pcf_dependencies->trials) { + components->pcf_dependencies->trials = + std::make_unique<FieldTrialBasedConfig>(); + } +} + +// Returns mapping from stream label to optional spatial index. +// If we have stream label "Foo" and mapping contains +// 1. `absl::nullopt` means all simulcast/SVC streams are required +// 2. Concrete value means that particular simulcast/SVC stream have to be +// analyzed. +EmulatedSFUConfigMap CalculateRequiredSpatialIndexPerStream( + const std::vector<VideoConfig>& video_configs) { + EmulatedSFUConfigMap result; + for (auto& video_config : video_configs) { + // Stream label should be set by fixture implementation here. + RTC_DCHECK(video_config.stream_label); + bool res = result + .insert({*video_config.stream_label, + video_config.emulated_sfu_config}) + .second; + RTC_DCHECK(res) << "Duplicate video_config.stream_label=" + << *video_config.stream_label; + } + return result; +} + +std::unique_ptr<TestAudioDeviceModule::Renderer> CreateAudioRenderer( + const absl::optional<RemotePeerAudioConfig>& config) { + if (!config) { + // Return default renderer because we always require some renderer. + return TestAudioDeviceModule::CreateDiscardRenderer( + kDefaultSamplingFrequencyInHz); + } + if (config->output_file_name) { + return TestAudioDeviceModule::CreateBoundedWavFileWriter( + config->output_file_name.value(), config->sampling_frequency_in_hz); + } + return TestAudioDeviceModule::CreateDiscardRenderer( + config->sampling_frequency_in_hz); +} + +std::unique_ptr<TestAudioDeviceModule::Capturer> CreateAudioCapturer( + const absl::optional<AudioConfig>& audio_config) { + if (!audio_config) { + // If we have no audio config we still need to provide some audio device. + // In such case use generated capturer. Despite of we provided audio here, + // in test media setup audio stream won't be added into peer connection. + return TestAudioDeviceModule::CreatePulsedNoiseCapturer( + kGeneratedAudioMaxAmplitude, kDefaultSamplingFrequencyInHz); + } + + switch (audio_config->mode) { + case AudioConfig::Mode::kGenerated: + return TestAudioDeviceModule::CreatePulsedNoiseCapturer( + kGeneratedAudioMaxAmplitude, audio_config->sampling_frequency_in_hz); + case AudioConfig::Mode::kFile: + RTC_DCHECK(audio_config->input_file_name); + return TestAudioDeviceModule::CreateWavFileReader( + audio_config->input_file_name.value(), /*repeat=*/true); + } +} + +rtc::scoped_refptr<AudioDeviceModule> CreateAudioDeviceModule( + absl::optional<AudioConfig> audio_config, + absl::optional<RemotePeerAudioConfig> remote_audio_config, + absl::optional<EchoEmulationConfig> echo_emulation_config, + TaskQueueFactory* task_queue_factory) { + std::unique_ptr<TestAudioDeviceModule::Renderer> renderer = + CreateAudioRenderer(remote_audio_config); + std::unique_ptr<TestAudioDeviceModule::Capturer> capturer = + CreateAudioCapturer(audio_config); + RTC_DCHECK(renderer); + RTC_DCHECK(capturer); + + // Setup echo emulation if required. + if (echo_emulation_config) { + capturer = std::make_unique<EchoEmulatingCapturer>(std::move(capturer), + *echo_emulation_config); + renderer = std::make_unique<EchoEmulatingRenderer>( + std::move(renderer), + static_cast<EchoEmulatingCapturer*>(capturer.get())); + } + + // Setup input stream dumping if required. + if (audio_config && audio_config->input_dump_file_name) { + capturer = std::make_unique<test::CopyToFileAudioCapturer>( + std::move(capturer), audio_config->input_dump_file_name.value()); + } + + return TestAudioDeviceModule::Create(task_queue_factory, std::move(capturer), + std::move(renderer), /*speed=*/1.f); +} + +std::unique_ptr<cricket::MediaEngineInterface> CreateMediaEngine( + PeerConnectionFactoryComponents* pcf_dependencies, + rtc::scoped_refptr<AudioDeviceModule> audio_device_module) { + cricket::MediaEngineDependencies media_deps; + media_deps.task_queue_factory = pcf_dependencies->task_queue_factory.get(); + media_deps.adm = audio_device_module; + media_deps.audio_processing = pcf_dependencies->audio_processing; + media_deps.audio_mixer = pcf_dependencies->audio_mixer; + media_deps.video_encoder_factory = + std::move(pcf_dependencies->video_encoder_factory); + media_deps.video_decoder_factory = + std::move(pcf_dependencies->video_decoder_factory); + webrtc::SetMediaEngineDefaults(&media_deps); + RTC_DCHECK(pcf_dependencies->trials); + media_deps.trials = pcf_dependencies->trials.get(); + + return cricket::CreateMediaEngine(std::move(media_deps)); +} + +void WrapVideoEncoderFactory( + absl::string_view peer_name, + double bitrate_multiplier, + EmulatedSFUConfigMap stream_to_sfu_config, + PeerConnectionFactoryComponents* pcf_dependencies, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper) { + std::unique_ptr<VideoEncoderFactory> video_encoder_factory; + if (pcf_dependencies->video_encoder_factory != nullptr) { + video_encoder_factory = std::move(pcf_dependencies->video_encoder_factory); + } else { + video_encoder_factory = CreateBuiltinVideoEncoderFactory(); + } + pcf_dependencies->video_encoder_factory = + video_analyzer_helper->WrapVideoEncoderFactory( + peer_name, std::move(video_encoder_factory), bitrate_multiplier, + std::move(stream_to_sfu_config)); +} + +void WrapVideoDecoderFactory( + absl::string_view peer_name, + PeerConnectionFactoryComponents* pcf_dependencies, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper) { + std::unique_ptr<VideoDecoderFactory> video_decoder_factory; + if (pcf_dependencies->video_decoder_factory != nullptr) { + video_decoder_factory = std::move(pcf_dependencies->video_decoder_factory); + } else { + video_decoder_factory = CreateBuiltinVideoDecoderFactory(); + } + pcf_dependencies->video_decoder_factory = + video_analyzer_helper->WrapVideoDecoderFactory( + peer_name, std::move(video_decoder_factory)); +} + +// Creates PeerConnectionFactoryDependencies objects, providing entities +// from InjectableComponents::PeerConnectionFactoryComponents. +PeerConnectionFactoryDependencies CreatePCFDependencies( + std::unique_ptr<PeerConnectionFactoryComponents> pcf_dependencies, + std::unique_ptr<cricket::MediaEngineInterface> media_engine, + rtc::Thread* signaling_thread, + rtc::Thread* worker_thread, + rtc::Thread* network_thread) { + PeerConnectionFactoryDependencies pcf_deps; + pcf_deps.signaling_thread = signaling_thread; + pcf_deps.worker_thread = worker_thread; + pcf_deps.network_thread = network_thread; + pcf_deps.media_engine = std::move(media_engine); + + pcf_deps.call_factory = std::move(pcf_dependencies->call_factory); + pcf_deps.event_log_factory = std::move(pcf_dependencies->event_log_factory); + pcf_deps.task_queue_factory = std::move(pcf_dependencies->task_queue_factory); + + if (pcf_dependencies->fec_controller_factory != nullptr) { + pcf_deps.fec_controller_factory = + std::move(pcf_dependencies->fec_controller_factory); + } + if (pcf_dependencies->network_controller_factory != nullptr) { + pcf_deps.network_controller_factory = + std::move(pcf_dependencies->network_controller_factory); + } + if (pcf_dependencies->neteq_factory != nullptr) { + pcf_deps.neteq_factory = std::move(pcf_dependencies->neteq_factory); + } + if (pcf_dependencies->trials != nullptr) { + pcf_deps.trials = std::move(pcf_dependencies->trials); + } + + return pcf_deps; +} + +// Creates PeerConnectionDependencies objects, providing entities +// from InjectableComponents::PeerConnectionComponents. +PeerConnectionDependencies CreatePCDependencies( + MockPeerConnectionObserver* observer, + uint32_t port_allocator_extra_flags, + std::unique_ptr<PeerConnectionComponents> pc_dependencies) { + PeerConnectionDependencies pc_deps(observer); + + auto port_allocator = std::make_unique<cricket::BasicPortAllocator>( + pc_dependencies->network_manager, pc_dependencies->packet_socket_factory); + + // This test does not support TCP + int flags = port_allocator_extra_flags | cricket::PORTALLOCATOR_DISABLE_TCP; + port_allocator->set_flags(port_allocator->flags() | flags); + + pc_deps.allocator = std::move(port_allocator); + + if (pc_dependencies->async_resolver_factory != nullptr) { + pc_deps.async_resolver_factory = + std::move(pc_dependencies->async_resolver_factory); + } + if (pc_dependencies->cert_generator != nullptr) { + pc_deps.cert_generator = std::move(pc_dependencies->cert_generator); + } + if (pc_dependencies->tls_cert_verifier != nullptr) { + pc_deps.tls_cert_verifier = std::move(pc_dependencies->tls_cert_verifier); + } + if (pc_dependencies->ice_transport_factory != nullptr) { + pc_deps.ice_transport_factory = + std::move(pc_dependencies->ice_transport_factory); + } + return pc_deps; +} + +} // namespace + +absl::optional<RemotePeerAudioConfig> RemotePeerAudioConfig::Create( + absl::optional<AudioConfig> config) { + if (!config) { + return absl::nullopt; + } + return RemotePeerAudioConfig(config.value()); +} + +std::unique_ptr<TestPeer> TestPeerFactory::CreateTestPeer( + std::unique_ptr<PeerConfigurer> configurer, + std::unique_ptr<MockPeerConnectionObserver> observer, + absl::optional<RemotePeerAudioConfig> remote_audio_config, + absl::optional<EchoEmulationConfig> echo_emulation_config) { + std::unique_ptr<InjectableComponents> components = + configurer->ReleaseComponents(); + std::unique_ptr<Params> params = configurer->ReleaseParams(); + std::unique_ptr<ConfigurableParams> configurable_params = + configurer->ReleaseConfigurableParams(); + std::vector<PeerConfigurer::VideoSource> video_sources = + configurer->ReleaseVideoSources(); + RTC_DCHECK(components); + RTC_DCHECK(params); + RTC_DCHECK(configurable_params); + RTC_DCHECK_EQ(configurable_params->video_configs.size(), + video_sources.size()); + SetMandatoryEntities(components.get(), time_controller_); + params->rtc_configuration.sdp_semantics = SdpSemantics::kUnifiedPlan; + + // Create peer connection factory. + if (components->pcf_dependencies->audio_processing == nullptr) { + components->pcf_dependencies->audio_processing = + webrtc::AudioProcessingBuilder().Create(); + } + if (params->aec_dump_path) { + components->pcf_dependencies->audio_processing->CreateAndAttachAecDump( + *params->aec_dump_path, -1, task_queue_); + } + rtc::scoped_refptr<AudioDeviceModule> audio_device_module = + CreateAudioDeviceModule( + params->audio_config, remote_audio_config, echo_emulation_config, + components->pcf_dependencies->task_queue_factory.get()); + WrapVideoEncoderFactory( + params->name.value(), params->video_encoder_bitrate_multiplier, + CalculateRequiredSpatialIndexPerStream( + configurable_params->video_configs), + components->pcf_dependencies.get(), video_analyzer_helper_); + WrapVideoDecoderFactory(params->name.value(), + components->pcf_dependencies.get(), + video_analyzer_helper_); + std::unique_ptr<cricket::MediaEngineInterface> media_engine = + CreateMediaEngine(components->pcf_dependencies.get(), + audio_device_module); + + std::unique_ptr<rtc::Thread> owned_worker_thread = + components->worker_thread != nullptr + ? nullptr + : time_controller_.CreateThread("worker_thread"); + if (components->worker_thread == nullptr) { + components->worker_thread = owned_worker_thread.get(); + } + + // Store `webrtc::AudioProcessing` into local variable before move of + // `components->pcf_dependencies` + rtc::scoped_refptr<webrtc::AudioProcessing> audio_processing = + components->pcf_dependencies->audio_processing; + PeerConnectionFactoryDependencies pcf_deps = CreatePCFDependencies( + std::move(components->pcf_dependencies), std::move(media_engine), + signaling_thread_, components->worker_thread, components->network_thread); + rtc::scoped_refptr<PeerConnectionFactoryInterface> peer_connection_factory = + CreateModularPeerConnectionFactory(std::move(pcf_deps)); + + // Create peer connection. + PeerConnectionDependencies pc_deps = + CreatePCDependencies(observer.get(), params->port_allocator_extra_flags, + std::move(components->pc_dependencies)); + rtc::scoped_refptr<PeerConnectionInterface> peer_connection = + peer_connection_factory + ->CreatePeerConnectionOrError(params->rtc_configuration, + std::move(pc_deps)) + .MoveValue(); + peer_connection->SetBitrate(params->bitrate_settings); + + return absl::WrapUnique( + new TestPeer(peer_connection_factory, peer_connection, + std::move(observer), std::move(*params), + std::move(*configurable_params), std::move(video_sources), + audio_processing, std::move(owned_worker_thread))); +} + +} // namespace webrtc_pc_e2e +} // namespace webrtc diff --git a/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h new file mode 100644 index 0000000000..f2698e2a15 --- /dev/null +++ b/third_party/libwebrtc/test/pc/e2e/test_peer_factory.h @@ -0,0 +1,84 @@ +/* + * 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_PC_E2E_TEST_PEER_FACTORY_H_ +#define TEST_PC_E2E_TEST_PEER_FACTORY_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/rtc_event_log/rtc_event_log_factory.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/time_controller.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "rtc_base/task_queue.h" +#include "test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h" +#include "test/pc/e2e/test_peer.h" + +namespace webrtc { +namespace webrtc_pc_e2e { + +struct RemotePeerAudioConfig { + explicit RemotePeerAudioConfig(AudioConfig config) + : sampling_frequency_in_hz(config.sampling_frequency_in_hz), + output_file_name(config.output_dump_file_name) {} + + static absl::optional<RemotePeerAudioConfig> Create( + absl::optional<AudioConfig> config); + + int sampling_frequency_in_hz; + absl::optional<std::string> output_file_name; +}; + +class TestPeerFactory { + public: + // Creates a test peer factory. + // `signaling_thread` will be used as a signaling thread for all peers created + // by this factory. + // `time_controller` will be used to create required threads, task queue + // factories and call factory. + // `video_analyzer_helper` will be used to setup video quality analysis for + // created peers. + // `task_queue` will be used for AEC dump if it is requested. + TestPeerFactory(rtc::Thread* signaling_thread, + TimeController& time_controller, + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper, + rtc::TaskQueue* task_queue) + : signaling_thread_(signaling_thread), + time_controller_(time_controller), + video_analyzer_helper_(video_analyzer_helper), + task_queue_(task_queue) {} + + // Setups all components, that should be provided to WebRTC + // PeerConnectionFactory and PeerConnection creation methods, + // also will setup dependencies, that are required for media analyzers + // injection. + std::unique_ptr<TestPeer> CreateTestPeer( + std::unique_ptr<PeerConfigurer> configurer, + std::unique_ptr<MockPeerConnectionObserver> observer, + absl::optional<RemotePeerAudioConfig> remote_audio_config, + absl::optional<EchoEmulationConfig> echo_emulation_config); + + private: + rtc::Thread* signaling_thread_; + TimeController& time_controller_; + VideoQualityAnalyzerInjectionHelper* video_analyzer_helper_; + rtc::TaskQueue* task_queue_; +}; + +} // namespace webrtc_pc_e2e +} // namespace webrtc + +#endif // TEST_PC_E2E_TEST_PEER_FACTORY_H_ diff --git a/third_party/libwebrtc/test/pc/sctp/BUILD.gn b/third_party/libwebrtc/test/pc/sctp/BUILD.gn new file mode 100644 index 0000000000..f088a5b20c --- /dev/null +++ b/third_party/libwebrtc/test/pc/sctp/BUILD.gn @@ -0,0 +1,18 @@ +# 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. + +import("../../../webrtc.gni") + +rtc_source_set("fake_sctp_transport") { + visibility = [ "*" ] + sources = [ "fake_sctp_transport.h" ] + deps = [ + "../../../api/transport:sctp_transport_factory_interface", + "../../../media:rtc_data_sctp_transport_internal", + ] +} diff --git a/third_party/libwebrtc/test/pc/sctp/fake_sctp_transport.h b/third_party/libwebrtc/test/pc/sctp/fake_sctp_transport.h new file mode 100644 index 0000000000..a1bb0e219c --- /dev/null +++ b/third_party/libwebrtc/test/pc/sctp/fake_sctp_transport.h @@ -0,0 +1,79 @@ +/* + * Copyright 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. + */ + +#ifndef TEST_PC_SCTP_FAKE_SCTP_TRANSPORT_H_ +#define TEST_PC_SCTP_FAKE_SCTP_TRANSPORT_H_ + +#include <memory> + +#include "api/transport/sctp_transport_factory_interface.h" +#include "media/sctp/sctp_transport_internal.h" + +// Used for tests in this file to verify that PeerConnection responds to signals +// from the SctpTransport correctly, and calls Start with the correct +// local/remote ports. +class FakeSctpTransport : public cricket::SctpTransportInternal { + public: + void SetOnConnectedCallback(std::function<void()> callback) override {} + void SetDataChannelSink(webrtc::DataChannelSink* sink) override {} + void SetDtlsTransport(rtc::PacketTransportInternal* transport) override {} + bool Start(int local_port, int remote_port, int max_message_size) override { + local_port_.emplace(local_port); + remote_port_.emplace(remote_port); + max_message_size_ = max_message_size; + return true; + } + bool OpenStream(int sid) override { return true; } + bool ResetStream(int sid) override { return true; } + bool SendData(int sid, + const webrtc::SendDataParams& params, + const rtc::CopyOnWriteBuffer& payload, + cricket::SendDataResult* result = nullptr) override { + return true; + } + bool ReadyToSendData() override { return true; } + void set_debug_name_for_testing(const char* debug_name) override {} + + int max_message_size() const { return max_message_size_; } + absl::optional<int> max_outbound_streams() const { return absl::nullopt; } + absl::optional<int> max_inbound_streams() const { return absl::nullopt; } + int local_port() const { + RTC_DCHECK(local_port_); + return *local_port_; + } + int remote_port() const { + RTC_DCHECK(remote_port_); + return *remote_port_; + } + + private: + absl::optional<int> local_port_; + absl::optional<int> remote_port_; + int max_message_size_; +}; + +class FakeSctpTransportFactory : public webrtc::SctpTransportFactoryInterface { + public: + std::unique_ptr<cricket::SctpTransportInternal> CreateSctpTransport( + rtc::PacketTransportInternal*) override { + last_fake_sctp_transport_ = new FakeSctpTransport(); + return std::unique_ptr<cricket::SctpTransportInternal>( + last_fake_sctp_transport_); + } + + FakeSctpTransport* last_fake_sctp_transport() { + return last_fake_sctp_transport_; + } + + private: + FakeSctpTransport* last_fake_sctp_transport_ = nullptr; +}; + +#endif // TEST_PC_SCTP_FAKE_SCTP_TRANSPORT_H_ diff --git a/third_party/libwebrtc/test/peer_scenario/BUILD.gn b/third_party/libwebrtc/test/peer_scenario/BUILD.gn new file mode 100644 index 0000000000..00492a18a9 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/BUILD.gn @@ -0,0 +1,68 @@ +# 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. + +import("../../webrtc.gni") + +if (rtc_include_tests) { + rtc_library("peer_scenario") { + testonly = true + sources = [ + "peer_scenario.cc", + "peer_scenario.h", + "peer_scenario_client.cc", + "peer_scenario_client.h", + "scenario_connection.cc", + "scenario_connection.h", + "signaling_route.cc", + "signaling_route.h", + ] + deps = [ + "..:fake_video_codecs", + "..:fileutils", + "..:test_support", + "../:video_test_common", + "../../api:candidate", + "../../api:create_time_controller", + "../../api:libjingle_peerconnection_api", + "../../api:network_emulation_manager_api", + "../../api:rtc_stats_api", + "../../api:time_controller", + "../../api/audio_codecs:builtin_audio_decoder_factory", + "../../api/audio_codecs:builtin_audio_encoder_factory", + "../../api/rtc_event_log:rtc_event_log_factory", + "../../api/task_queue:default_task_queue_factory", + "../../api/transport:field_trial_based_config", + "../../api/video_codecs:builtin_video_decoder_factory", + "../../api/video_codecs:builtin_video_encoder_factory", + "../../media:rtc_audio_video", + "../../media:rtc_media_base", + "../../media:rtp_utils", + "../../modules/audio_device:audio_device_impl", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../p2p:rtc_p2p", + "../../pc:channel", + "../../pc:jsep_transport_controller", + "../../pc:pc_test_utils", + "../../pc:rtp_transport_internal", + "../../pc:session_description", + "../../rtc_base:null_socket_server", + "../../rtc_base:stringutils", + "../../rtc_base:task_queue_for_test", + "../../test:explicit_key_value_config", + "../../test:scoped_key_value_config", + "../logging:log_writer", + "../network:emulated_network", + "../scenario", + "../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/flags:flag", + "//third_party/abseil-cpp/absl/memory", + ] + } +} diff --git a/third_party/libwebrtc/test/peer_scenario/DEPS b/third_party/libwebrtc/test/peer_scenario/DEPS new file mode 100644 index 0000000000..68e9f46087 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+pc", + "+p2p", +] + diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario.cc b/third_party/libwebrtc/test/peer_scenario/peer_scenario.cc new file mode 100644 index 0000000000..485e33f67f --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario.cc @@ -0,0 +1,127 @@ +/* + * 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/peer_scenario/peer_scenario.h" + +#include "absl/flags/flag.h" +#include "absl/memory/memory.h" +#include "rtc_base/null_socket_server.h" +#include "rtc_base/string_encode.h" +#include "rtc_base/strings/string_builder.h" +#include "test/logging/file_log_writer.h" +#include "test/testsupport/file_utils.h" +#include "test/time_controller/real_time_controller.h" +#include "test/time_controller/simulated_time_controller.h" + +ABSL_FLAG(bool, peer_logs, false, "Save logs from peer scenario framework."); +ABSL_FLAG(std::string, + peer_logs_root, + "", + "Output root path, based on project root if unset."); + +namespace webrtc { +namespace test { +namespace { +std::unique_ptr<FileLogWriterFactory> GetPeerScenarioLogManager( + std::string file_name) { + if (absl::GetFlag(FLAGS_peer_logs) && !file_name.empty()) { + std::string output_root = absl::GetFlag(FLAGS_peer_logs_root); + if (output_root.empty()) + output_root = OutputPath() + "output_data/"; + + auto base_filename = output_root + file_name + "."; + RTC_LOG(LS_INFO) << "Saving peer scenario logs to: " << base_filename; + return std::make_unique<FileLogWriterFactory>(base_filename); + } + return nullptr; +} +} // namespace + +PeerScenario::PeerScenario(const testing::TestInfo& test_info, TimeMode mode) + : PeerScenario( + std::string(test_info.test_suite_name()) + "/" + test_info.name(), + mode) {} + +PeerScenario::PeerScenario(std::string file_name, TimeMode mode) + : PeerScenario(GetPeerScenarioLogManager(file_name), mode) {} + +PeerScenario::PeerScenario( + std::unique_ptr<LogWriterFactoryInterface> log_writer_manager, + TimeMode mode) + : log_writer_manager_(std::move(log_writer_manager)), + net_(mode, EmulatedNetworkStatsGatheringMode::kDefault), + signaling_thread_(net_.time_controller()->GetMainThread()) {} + +PeerScenarioClient* PeerScenario::CreateClient( + PeerScenarioClient::Config config) { + return CreateClient( + std::string("client_") + rtc::ToString(peer_clients_.size() + 1), config); +} + +PeerScenarioClient* PeerScenario::CreateClient( + std::string name, + PeerScenarioClient::Config config) { + peer_clients_.emplace_back(net(), signaling_thread_, + GetLogWriterFactory(name), config); + return &peer_clients_.back(); +} + +SignalingRoute PeerScenario::ConnectSignaling( + PeerScenarioClient* caller, + PeerScenarioClient* callee, + std::vector<EmulatedNetworkNode*> send_link, + std::vector<EmulatedNetworkNode*> ret_link) { + return SignalingRoute(caller, callee, net_.CreateCrossTrafficRoute(send_link), + net_.CreateCrossTrafficRoute(ret_link)); +} + +void PeerScenario::SimpleConnection( + PeerScenarioClient* caller, + PeerScenarioClient* callee, + std::vector<EmulatedNetworkNode*> send_link, + std::vector<EmulatedNetworkNode*> ret_link) { + net()->CreateRoute(caller->endpoint(), send_link, callee->endpoint()); + net()->CreateRoute(callee->endpoint(), ret_link, caller->endpoint()); + auto signaling = ConnectSignaling(caller, callee, send_link, ret_link); + signaling.StartIceSignaling(); + std::atomic<bool> done(false); + signaling.NegotiateSdp( + [&](const SessionDescriptionInterface&) { done = true; }); + RTC_CHECK(WaitAndProcess(&done)); +} + +void PeerScenario::AttachVideoQualityAnalyzer(VideoQualityAnalyzer* analyzer, + VideoTrackInterface* send_track, + PeerScenarioClient* receiver) { + video_quality_pairs_.emplace_back(clock(), analyzer); + auto pair = &video_quality_pairs_.back(); + send_track->AddOrUpdateSink(&pair->capture_tap_, rtc::VideoSinkWants()); + receiver->AddVideoReceiveSink(send_track->id(), &pair->decode_tap_); +} + +bool PeerScenario::WaitAndProcess(std::atomic<bool>* event, + TimeDelta max_duration) { + return net_.time_controller()->Wait([event] { return event->load(); }, + max_duration); +} + +void PeerScenario::ProcessMessages(TimeDelta duration) { + net_.time_controller()->AdvanceTime(duration); +} + +std::unique_ptr<LogWriterFactoryInterface> PeerScenario::GetLogWriterFactory( + std::string name) { + if (!log_writer_manager_ || name.empty()) + return nullptr; + return std::make_unique<LogWriterFactoryAddPrefix>(log_writer_manager_.get(), + name); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario.h b/third_party/libwebrtc/test/peer_scenario/peer_scenario.h new file mode 100644 index 0000000000..a177eeaac6 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario.h @@ -0,0 +1,122 @@ +/* + * 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_PEER_SCENARIO_PEER_SCENARIO_H_ +#define TEST_PEER_SCENARIO_PEER_SCENARIO_H_ + +// The peer connection scenario test framework enables writing end to end unit +// tests on the peer connection level. It's similar to the Scenario test but +// uses the full stack, including SDP and ICE negotiation. This ensures that +// features work end to end. It's also diffferent from the other tests on peer +// connection level in that it does not rely on any mocks or fakes other than +// for media input and networking. Additionally it provides direct access to the +// underlying peer connection class. + +#include <list> +#include <vector> + +#include "api/test/time_controller.h" +#include "test/gtest.h" +#include "test/logging/log_writer.h" +#include "test/network/network_emulation_manager.h" +#include "test/peer_scenario/peer_scenario_client.h" +#include "test/peer_scenario/signaling_route.h" +#include "test/scenario/stats_collection.h" +#include "test/scenario/video_frame_matcher.h" + +namespace webrtc { +namespace test { +// The PeerScenario class represents a PeerConnection simulation scenario. The +// main purpose is to maintain ownership and ensure safe destruction order of +// clients and network emulation. Additionally it reduces the amount of boiler +// plate requited for some actions. For example usage see the existing tests +// using this class. Note that it should be used from a single calling thread. +// This thread will also be assigned as the signaling thread for all peer +// connections that are created. This means that the process methods must be +// used when waiting to ensure that messages are processed on the signaling +// thread. +class PeerScenario { + public: + // The name is used for log output when those are enabled by the --peer_logs + // command line flag. Optionally, the TestInfo struct available in gtest can + // be used to automatically generate a path based on the test name. + explicit PeerScenario(const testing::TestInfo& test_info, + TimeMode mode = TimeMode::kSimulated); + explicit PeerScenario(std::string file_name, + TimeMode mode = TimeMode::kSimulated); + explicit PeerScenario( + std::unique_ptr<LogWriterFactoryInterface> log_writer_manager, + TimeMode mode = TimeMode::kSimulated); + + NetworkEmulationManagerImpl* net() { return &net_; } + + // Creates a client wrapping a peer connection conforming to the given config. + // The client will share the signaling thread with the scenario. To maintain + // control of destruction order, ownership is kept within the scenario. + PeerScenarioClient* CreateClient(PeerScenarioClient::Config config); + PeerScenarioClient* CreateClient(std::string name, + PeerScenarioClient::Config config); + + // Sets up a signaling route that can be used for SDP and ICE. + SignalingRoute ConnectSignaling(PeerScenarioClient* caller, + PeerScenarioClient* callee, + std::vector<EmulatedNetworkNode*> send_link, + std::vector<EmulatedNetworkNode*> ret_link); + + // Connects two clients over given links. This will also start ICE signaling + // and SDP negotiation with default behavior. For customized behavior, + // ConnectSignaling should be used to allow more detailed control, for + // instance to allow different signaling and media routes. + void SimpleConnection(PeerScenarioClient* caller, + PeerScenarioClient* callee, + std::vector<EmulatedNetworkNode*> send_link, + std::vector<EmulatedNetworkNode*> ret_link); + + // Starts feeding the results of comparing captured frames from `send_track` + // with decoded frames on `receiver` to `analyzer`. + // TODO(srte): Provide a way to detach to allow removal of tracks. + void AttachVideoQualityAnalyzer(VideoQualityAnalyzer* analyzer, + VideoTrackInterface* send_track, + PeerScenarioClient* receiver); + + // Waits on `event` while processing messages on the signaling thread. + bool WaitAndProcess(std::atomic<bool>* event, + TimeDelta max_duration = TimeDelta::Seconds(5)); + + // Process messages on the signaling thread for the given duration. + void ProcessMessages(TimeDelta duration); + + private: + // Helper struct to maintain ownership of the matcher and taps. + struct PeerVideoQualityPair { + public: + PeerVideoQualityPair(Clock* capture_clock, VideoQualityAnalyzer* analyzer) + : matcher_({analyzer->Handler()}), + capture_tap_(capture_clock, &matcher_), + decode_tap_(capture_clock, &matcher_, 0) {} + VideoFrameMatcher matcher_; + CapturedFrameTap capture_tap_; + DecodedFrameTap decode_tap_; + }; + + Clock* clock() { return Clock::GetRealTimeClock(); } + + std::unique_ptr<LogWriterFactoryInterface> GetLogWriterFactory( + std::string name); + + const std::unique_ptr<LogWriterFactoryInterface> log_writer_manager_; + NetworkEmulationManagerImpl net_; + rtc::Thread* const signaling_thread_; + std::list<PeerVideoQualityPair> video_quality_pairs_; + std::list<PeerScenarioClient> peer_clients_; +}; + +} // namespace test +} // namespace webrtc +#endif // TEST_PEER_SCENARIO_PEER_SCENARIO_H_ diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc new file mode 100644 index 0000000000..5d77f17561 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.cc @@ -0,0 +1,428 @@ +/* + * 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/peer_scenario/peer_scenario_client.h" + +#include <limits> +#include <memory> +#include <utility> + +#include "absl/memory/memory.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/rtc_event_log/rtc_event_log_factory.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "api/test/create_time_controller.h" +#include "api/transport/field_trial_based_config.h" +#include "api/video_codecs/builtin_video_decoder_factory.h" +#include "api/video_codecs/builtin_video_encoder_factory.h" +#include "media/engine/webrtc_media_engine.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "p2p/client/basic_port_allocator.h" +#include "test/fake_decoder.h" +#include "test/fake_vp8_encoder.h" +#include "test/frame_generator_capturer.h" + +namespace webrtc { +namespace test { + +namespace { + +constexpr char kCommonStreamId[] = "stream_id"; + +std::map<int, EmulatedEndpoint*> CreateEndpoints( + NetworkEmulationManager* net, + std::map<int, EmulatedEndpointConfig> endpoint_configs) { + std::map<int, EmulatedEndpoint*> endpoints; + for (const auto& kv : endpoint_configs) + endpoints[kv.first] = net->CreateEndpoint(kv.second); + return endpoints; +} + +class LambdaPeerConnectionObserver final : public PeerConnectionObserver { + public: + explicit LambdaPeerConnectionObserver( + PeerScenarioClient::CallbackHandlers* handlers) + : handlers_(handlers) {} + void OnSignalingChange( + PeerConnectionInterface::SignalingState new_state) override { + for (const auto& handler : handlers_->on_signaling_change) + handler(new_state); + } + void OnDataChannel( + rtc::scoped_refptr<DataChannelInterface> data_channel) override { + for (const auto& handler : handlers_->on_data_channel) + handler(data_channel); + } + void OnRenegotiationNeeded() override { + for (const auto& handler : handlers_->on_renegotiation_needed) + handler(); + } + void OnStandardizedIceConnectionChange( + PeerConnectionInterface::IceConnectionState new_state) override { + for (const auto& handler : handlers_->on_standardized_ice_connection_change) + handler(new_state); + } + void OnConnectionChange( + PeerConnectionInterface::PeerConnectionState new_state) override { + for (const auto& handler : handlers_->on_connection_change) + handler(new_state); + } + void OnIceGatheringChange( + PeerConnectionInterface::IceGatheringState new_state) override { + for (const auto& handler : handlers_->on_ice_gathering_change) + handler(new_state); + } + void OnIceCandidate(const IceCandidateInterface* candidate) override { + for (const auto& handler : handlers_->on_ice_candidate) + handler(candidate); + } + void OnIceCandidateError(const std::string& address, + int port, + const std::string& url, + int error_code, + const std::string& error_text) override { + for (const auto& handler : handlers_->on_ice_candidate_error) + handler(address, port, url, error_code, error_text); + } + void OnIceCandidatesRemoved( + const std::vector<cricket::Candidate>& candidates) override { + for (const auto& handler : handlers_->on_ice_candidates_removed) + handler(candidates); + } + void OnAddTrack(rtc::scoped_refptr<RtpReceiverInterface> receiver, + const std::vector<rtc::scoped_refptr<MediaStreamInterface> >& + streams) override { + for (const auto& handler : handlers_->on_add_track) + handler(receiver, streams); + } + void OnTrack( + rtc::scoped_refptr<RtpTransceiverInterface> transceiver) override { + for (const auto& handler : handlers_->on_track) + handler(transceiver); + } + void OnRemoveTrack( + rtc::scoped_refptr<RtpReceiverInterface> receiver) override { + for (const auto& handler : handlers_->on_remove_track) + handler(receiver); + } + + private: + PeerScenarioClient::CallbackHandlers* handlers_; +}; + +class LambdaCreateSessionDescriptionObserver + : public CreateSessionDescriptionObserver { + public: + explicit LambdaCreateSessionDescriptionObserver( + std::function<void(std::unique_ptr<SessionDescriptionInterface> desc)> + on_success) + : on_success_(on_success) {} + void OnSuccess(SessionDescriptionInterface* desc) override { + // Takes ownership of answer, according to CreateSessionDescriptionObserver + // convention. + on_success_(absl::WrapUnique(desc)); + } + void OnFailure(RTCError error) override { + RTC_DCHECK_NOTREACHED() << error.message(); + } + + private: + std::function<void(std::unique_ptr<SessionDescriptionInterface> desc)> + on_success_; +}; + +class LambdaSetLocalDescriptionObserver + : public SetLocalDescriptionObserverInterface { + public: + explicit LambdaSetLocalDescriptionObserver( + std::function<void(RTCError)> on_complete) + : on_complete_(on_complete) {} + void OnSetLocalDescriptionComplete(RTCError error) override { + on_complete_(error); + } + + private: + std::function<void(RTCError)> on_complete_; +}; + +class LambdaSetRemoteDescriptionObserver + : public SetRemoteDescriptionObserverInterface { + public: + explicit LambdaSetRemoteDescriptionObserver( + std::function<void(RTCError)> on_complete) + : on_complete_(on_complete) {} + void OnSetRemoteDescriptionComplete(RTCError error) override { + on_complete_(error); + } + + private: + std::function<void(RTCError)> on_complete_; +}; + +class FakeVideoEncoderFactory : public VideoEncoderFactory { + public: + FakeVideoEncoderFactory(Clock* clock) : clock_(clock) {} + std::vector<SdpVideoFormat> GetSupportedFormats() const override { + return {SdpVideoFormat("VP8")}; + } + std::unique_ptr<VideoEncoder> CreateVideoEncoder( + const SdpVideoFormat& format) override { + RTC_CHECK_EQ(format.name, "VP8"); + return std::make_unique<FakeVp8Encoder>(clock_); + } + + private: + Clock* const clock_; +}; +class FakeVideoDecoderFactory : public VideoDecoderFactory { + public: + std::vector<SdpVideoFormat> GetSupportedFormats() const override { + return {SdpVideoFormat("VP8")}; + } + std::unique_ptr<VideoDecoder> CreateVideoDecoder( + const SdpVideoFormat& format) override { + return std::make_unique<FakeDecoder>(); + } +}; +} // namespace + +PeerScenarioClient::PeerScenarioClient( + NetworkEmulationManager* net, + rtc::Thread* signaling_thread, + std::unique_ptr<LogWriterFactoryInterface> log_writer_factory, + PeerScenarioClient::Config config) + : endpoints_(CreateEndpoints(net, config.endpoints)), + task_queue_factory_(net->time_controller()->GetTaskQueueFactory()), + signaling_thread_(signaling_thread), + log_writer_factory_(std::move(log_writer_factory)), + worker_thread_(net->time_controller()->CreateThread("worker")), + handlers_(config.handlers), + observer_(new LambdaPeerConnectionObserver(&handlers_)) { + handlers_.on_track.push_back( + [this](rtc::scoped_refptr<RtpTransceiverInterface> transceiver) { + auto track = transceiver->receiver()->track().get(); + if (track->kind() == MediaStreamTrackInterface::kVideoKind) { + auto* video = static_cast<VideoTrackInterface*>(track); + RTC_DCHECK_RUN_ON(signaling_thread_); + for (auto* sink : track_id_to_video_sinks_[track->id()]) { + video->AddOrUpdateSink(sink, rtc::VideoSinkWants()); + } + } + }); + handlers_.on_signaling_change.push_back( + [this](PeerConnectionInterface::SignalingState state) { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (state == PeerConnectionInterface::SignalingState::kStable && + peer_connection_->current_remote_description()) { + for (const auto& candidate : pending_ice_candidates_) { + RTC_CHECK(peer_connection_->AddIceCandidate(candidate.get())); + } + pending_ice_candidates_.clear(); + } + }); + + std::vector<EmulatedEndpoint*> endpoints_vector; + for (const auto& kv : endpoints_) + endpoints_vector.push_back(kv.second); + auto* manager = net->CreateEmulatedNetworkManagerInterface(endpoints_vector); + + PeerConnectionFactoryDependencies pcf_deps; + pcf_deps.network_thread = manager->network_thread(); + pcf_deps.signaling_thread = signaling_thread_; + pcf_deps.worker_thread = worker_thread_.get(); + pcf_deps.call_factory = + CreateTimeControllerBasedCallFactory(net->time_controller()); + pcf_deps.task_queue_factory = + net->time_controller()->CreateTaskQueueFactory(); + pcf_deps.event_log_factory = + std::make_unique<RtcEventLogFactory>(task_queue_factory_); + pcf_deps.trials = std::make_unique<FieldTrialBasedConfig>(); + + cricket::MediaEngineDependencies media_deps; + media_deps.task_queue_factory = task_queue_factory_; + media_deps.adm = TestAudioDeviceModule::Create( + task_queue_factory_, + TestAudioDeviceModule::CreatePulsedNoiseCapturer( + config.audio.pulsed_noise->amplitude * + std::numeric_limits<int16_t>::max(), + config.audio.sample_rate, config.audio.channels), + TestAudioDeviceModule::CreateDiscardRenderer(config.audio.sample_rate)); + + media_deps.audio_processing = AudioProcessingBuilder().Create(); + if (config.video.use_fake_codecs) { + media_deps.video_encoder_factory = + std::make_unique<FakeVideoEncoderFactory>( + net->time_controller()->GetClock()); + media_deps.video_decoder_factory = + std::make_unique<FakeVideoDecoderFactory>(); + } else { + media_deps.video_encoder_factory = CreateBuiltinVideoEncoderFactory(); + media_deps.video_decoder_factory = CreateBuiltinVideoDecoderFactory(); + } + media_deps.audio_encoder_factory = CreateBuiltinAudioEncoderFactory(); + media_deps.audio_decoder_factory = CreateBuiltinAudioDecoderFactory(); + media_deps.trials = pcf_deps.trials.get(); + + pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps)); + pcf_deps.fec_controller_factory = nullptr; + pcf_deps.network_controller_factory = nullptr; + pcf_deps.network_state_predictor_factory = nullptr; + + pc_factory_ = CreateModularPeerConnectionFactory(std::move(pcf_deps)); + PeerConnectionFactoryInterface::Options pc_options; + pc_options.disable_encryption = config.disable_encryption; + pc_factory_->SetOptions(pc_options); + + PeerConnectionDependencies pc_deps(observer_.get()); + pc_deps.allocator = std::make_unique<cricket::BasicPortAllocator>( + manager->network_manager(), manager->packet_socket_factory()); + pc_deps.allocator->set_flags(pc_deps.allocator->flags() | + cricket::PORTALLOCATOR_DISABLE_TCP); + peer_connection_ = + pc_factory_ + ->CreatePeerConnectionOrError(config.rtc_config, std::move(pc_deps)) + .MoveValue(); + if (log_writer_factory_) { + peer_connection_->StartRtcEventLog(log_writer_factory_->Create(".rtc.dat"), + /*output_period_ms=*/1000); + } +} + +EmulatedEndpoint* PeerScenarioClient::endpoint(int index) { + RTC_CHECK_GT(endpoints_.size(), index); + return endpoints_.at(index); +} + +PeerScenarioClient::AudioSendTrack PeerScenarioClient::CreateAudio( + std::string track_id, + cricket::AudioOptions options) { + RTC_DCHECK_RUN_ON(signaling_thread_); + AudioSendTrack res; + auto source = pc_factory_->CreateAudioSource(options); + auto track = pc_factory_->CreateAudioTrack(track_id, source.get()); + res.track = track; + res.sender = peer_connection_->AddTrack(track, {kCommonStreamId}).value(); + return res; +} + +PeerScenarioClient::VideoSendTrack PeerScenarioClient::CreateVideo( + std::string track_id, + VideoSendTrackConfig config) { + RTC_DCHECK_RUN_ON(signaling_thread_); + VideoSendTrack res; + auto capturer = FrameGeneratorCapturer::Create(clock(), *task_queue_factory_, + config.generator); + res.capturer = capturer.get(); + capturer->Init(); + res.source = rtc::make_ref_counted<FrameGeneratorCapturerVideoTrackSource>( + std::move(capturer), config.screencast); + auto track = pc_factory_->CreateVideoTrack(track_id, res.source.get()); + res.track = track.get(); + res.sender = + peer_connection_->AddTrack(track, {kCommonStreamId}).MoveValue().get(); + return res; +} + +void PeerScenarioClient::AddVideoReceiveSink( + std::string track_id, + rtc::VideoSinkInterface<VideoFrame>* video_sink) { + RTC_DCHECK_RUN_ON(signaling_thread_); + track_id_to_video_sinks_[track_id].push_back(video_sink); +} + +void PeerScenarioClient::CreateAndSetSdp( + std::function<void(SessionDescriptionInterface*)> munge_offer, + std::function<void(std::string)> offer_handler) { + RTC_DCHECK_RUN_ON(signaling_thread_); + peer_connection_->CreateOffer( + rtc::make_ref_counted<LambdaCreateSessionDescriptionObserver>( + [=](std::unique_ptr<SessionDescriptionInterface> offer) { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (munge_offer) { + munge_offer(offer.get()); + } + std::string sdp_offer; + RTC_CHECK(offer->ToString(&sdp_offer)); + peer_connection_->SetLocalDescription( + std::move(offer), + rtc::make_ref_counted<LambdaSetLocalDescriptionObserver>( + [sdp_offer, offer_handler](RTCError) { + offer_handler(sdp_offer); + })); + }) + .get(), + PeerConnectionInterface::RTCOfferAnswerOptions()); +} + +void PeerScenarioClient::SetSdpOfferAndGetAnswer( + std::string remote_offer, + std::function<void(std::string)> answer_handler) { + if (!signaling_thread_->IsCurrent()) { + signaling_thread_->PostTask( + [=] { SetSdpOfferAndGetAnswer(remote_offer, answer_handler); }); + return; + } + RTC_DCHECK_RUN_ON(signaling_thread_); + peer_connection_->SetRemoteDescription( + CreateSessionDescription(SdpType::kOffer, remote_offer), + rtc::make_ref_counted<LambdaSetRemoteDescriptionObserver>([=](RTCError) { + RTC_DCHECK_RUN_ON(signaling_thread_); + peer_connection_->CreateAnswer( + rtc::make_ref_counted<LambdaCreateSessionDescriptionObserver>( + [=](std::unique_ptr<SessionDescriptionInterface> answer) { + RTC_DCHECK_RUN_ON(signaling_thread_); + std::string sdp_answer; + answer->ToString(&sdp_answer); + RTC_LOG(LS_INFO) << sdp_answer; + peer_connection_->SetLocalDescription( + std::move(answer), + rtc::make_ref_counted<LambdaSetLocalDescriptionObserver>( + [answer_handler, sdp_answer](RTCError) { + answer_handler(sdp_answer); + })); + }) + .get(), + PeerConnectionInterface::RTCOfferAnswerOptions()); + })); +} + +void PeerScenarioClient::SetSdpAnswer( + std::string remote_answer, + std::function<void(const SessionDescriptionInterface&)> done_handler) { + if (!signaling_thread_->IsCurrent()) { + signaling_thread_->PostTask( + [=] { SetSdpAnswer(remote_answer, done_handler); }); + return; + } + RTC_DCHECK_RUN_ON(signaling_thread_); + peer_connection_->SetRemoteDescription( + CreateSessionDescription(SdpType::kAnswer, remote_answer), + rtc::make_ref_counted<LambdaSetRemoteDescriptionObserver>( + [remote_answer, done_handler](RTCError) { + auto answer = + CreateSessionDescription(SdpType::kAnswer, remote_answer); + done_handler(*answer); + })); +} + +void PeerScenarioClient::AddIceCandidate( + std::unique_ptr<IceCandidateInterface> candidate) { + RTC_DCHECK_RUN_ON(signaling_thread_); + if (peer_connection_->signaling_state() == + PeerConnectionInterface::SignalingState::kStable && + peer_connection_->current_remote_description()) { + RTC_CHECK(peer_connection_->AddIceCandidate(candidate.get())); + } else { + pending_ice_candidates_.push_back(std::move(candidate)); + } +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h new file mode 100644 index 0000000000..ab6aac9cf8 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/peer_scenario_client.h @@ -0,0 +1,179 @@ +/* + * 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_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_ +#define TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_ + +#include <functional> +#include <list> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "absl/memory/memory.h" +#include "api/peer_connection_interface.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/time_controller.h" +#include "pc/test/frame_generator_capturer_video_track_source.h" +#include "test/logging/log_writer.h" + +namespace webrtc { +namespace test { + +// Wrapper for a PeerConnection for use in PeerScenario tests. It's intended to +// be a minimal wrapper for a peer connection that's simple to use in testing. +// In particular the constructor hides a lot of the required setup for a peer +// connection. +class PeerScenarioClient { + public: + struct CallbackHandlers { + std::vector<std::function<void(PeerConnectionInterface::SignalingState)>> + on_signaling_change; + std::vector<std::function<void(rtc::scoped_refptr<DataChannelInterface>)>> + on_data_channel; + std::vector<std::function<void()>> on_renegotiation_needed; + std::vector< + std::function<void(PeerConnectionInterface::IceConnectionState)>> + on_standardized_ice_connection_change; + std::vector< + std::function<void(PeerConnectionInterface::PeerConnectionState)>> + on_connection_change; + std::vector<std::function<void(PeerConnectionInterface::IceGatheringState)>> + on_ice_gathering_change; + std::vector<std::function<void(const IceCandidateInterface*)>> + on_ice_candidate; + std::vector<std::function<void(const std::string&, + int, + const std::string&, + int, + const std::string&)>> + on_ice_candidate_error; + std::vector<std::function<void(const std::vector<cricket::Candidate>&)>> + on_ice_candidates_removed; + std::vector<std::function<void( + rtc::scoped_refptr<RtpReceiverInterface>, + const std::vector<rtc::scoped_refptr<MediaStreamInterface>>&)>> + on_add_track; + std::vector< + std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>> + on_track; + std::vector<std::function<void(rtc::scoped_refptr<RtpReceiverInterface>)>> + on_remove_track; + }; + struct Config { + // WebRTC only support one audio device that is setup up on construction, so + // we provide the audio generator configuration here rather than on creation + // of the tracks. This is unlike video, where multiple capture sources can + // be used at the same time. + struct AudioSource { + int sample_rate = 48000; + int channels = 1; + struct PulsedNoise { + double amplitude = 0.1; + }; + absl::optional<PulsedNoise> pulsed_noise = PulsedNoise(); + } audio; + struct Video { + bool use_fake_codecs = false; + } video; + // The created endpoints can be accessed using the map key as `index` in + // PeerScenarioClient::endpoint(index). + std::map<int, EmulatedEndpointConfig> endpoints = { + {0, EmulatedEndpointConfig()}}; + CallbackHandlers handlers; + PeerConnectionInterface::RTCConfiguration rtc_config; + bool disable_encryption = false; + Config() { rtc_config.sdp_semantics = SdpSemantics::kUnifiedPlan; } + }; + + struct VideoSendTrackConfig { + FrameGeneratorCapturerConfig generator; + bool screencast = false; + }; + + struct AudioSendTrack { + rtc::scoped_refptr<AudioTrackInterface> track; + rtc::scoped_refptr<RtpSenderInterface> sender; + }; + + struct VideoSendTrack { + // Raw pointer to the capturer owned by `source`. + FrameGeneratorCapturer* capturer; + rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource> source; + rtc::scoped_refptr<VideoTrackInterface> track; + rtc::scoped_refptr<RtpSenderInterface> sender; + }; + + PeerScenarioClient( + NetworkEmulationManager* net, + rtc::Thread* signaling_thread, + std::unique_ptr<LogWriterFactoryInterface> log_writer_factory, + Config config); + + PeerConnectionFactoryInterface* factory() { return pc_factory_.get(); } + PeerConnectionInterface* pc() { + RTC_DCHECK_RUN_ON(signaling_thread_); + return peer_connection_.get(); + } + rtc::Thread* thread() { return signaling_thread_; } + Clock* clock() { return Clock::GetRealTimeClock(); } + + // Returns the endpoint created from the EmulatedEndpointConfig with the same + // index in PeerScenarioClient::config. + EmulatedEndpoint* endpoint(int index = 0); + + AudioSendTrack CreateAudio(std::string track_id, + cricket::AudioOptions options); + VideoSendTrack CreateVideo(std::string track_id, VideoSendTrackConfig config); + + void AddVideoReceiveSink(std::string track_id, + rtc::VideoSinkInterface<VideoFrame>* video_sink); + + CallbackHandlers* handlers() { return &handlers_; } + + // The `munge_offer` function can be used to munge the SDP, i.e. modify a + // local description afer creating it but before setting it. Note that this is + // legacy behavior. It's added here only to be able to have test coverage for + // scenarios even if they are not spec compliant. + void CreateAndSetSdp( + std::function<void(SessionDescriptionInterface*)> munge_offer, + std::function<void(std::string)> offer_handler); + void SetSdpOfferAndGetAnswer(std::string remote_offer, + std::function<void(std::string)> answer_handler); + void SetSdpAnswer( + std::string remote_answer, + std::function<void(const SessionDescriptionInterface& answer)> + done_handler); + + // Adds the given ice candidate when the peer connection is ready. + void AddIceCandidate(std::unique_ptr<IceCandidateInterface> candidate); + + private: + const std::map<int, EmulatedEndpoint*> endpoints_; + TaskQueueFactory* const task_queue_factory_; + rtc::Thread* const signaling_thread_; + const std::unique_ptr<LogWriterFactoryInterface> log_writer_factory_; + const std::unique_ptr<rtc::Thread> worker_thread_; + CallbackHandlers handlers_ RTC_GUARDED_BY(signaling_thread_); + const std::unique_ptr<PeerConnectionObserver> observer_; + std::map<std::string, std::vector<rtc::VideoSinkInterface<VideoFrame>*>> + track_id_to_video_sinks_ RTC_GUARDED_BY(signaling_thread_); + std::list<std::unique_ptr<IceCandidateInterface>> pending_ice_candidates_ + RTC_GUARDED_BY(signaling_thread_); + + rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_; + rtc::scoped_refptr<PeerConnectionInterface> peer_connection_ + RTC_GUARDED_BY(signaling_thread_); +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_ diff --git a/third_party/libwebrtc/test/peer_scenario/scenario_connection.cc b/third_party/libwebrtc/test/peer_scenario/scenario_connection.cc new file mode 100644 index 0000000000..66eca275d1 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/scenario_connection.cc @@ -0,0 +1,242 @@ +/* + * 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/peer_scenario/scenario_connection.h" + +#include "absl/memory/memory.h" +#include "media/base/rtp_utils.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "p2p/client/basic_port_allocator.h" +#include "pc/channel.h" +#include "pc/jsep_transport_controller.h" +#include "pc/rtp_transport_internal.h" +#include "pc/session_description.h" +#include "rtc_base/task_queue_for_test.h" + +namespace webrtc { +class ScenarioIceConnectionImpl : public ScenarioIceConnection, + public sigslot::has_slots<>, + private JsepTransportController::Observer, + private RtpPacketSinkInterface { + public: + ScenarioIceConnectionImpl(test::NetworkEmulationManagerImpl* net, + IceConnectionObserver* observer); + ~ScenarioIceConnectionImpl() override; + + void SendRtpPacket(rtc::ArrayView<const uint8_t> packet_view) override; + void SendRtcpPacket(rtc::ArrayView<const uint8_t> packet_view) override; + + void SetRemoteSdp(SdpType type, const std::string& remote_sdp) override; + void SetLocalSdp(SdpType type, const std::string& local_sdp) override; + + EmulatedEndpoint* endpoint() override { return endpoint_; } + const cricket::TransportDescription& transport_description() const override { + return transport_description_; + } + + private: + JsepTransportController::Config CreateJsepConfig(); + bool OnTransportChanged( + const std::string& mid, + RtpTransportInternal* rtp_transport, + rtc::scoped_refptr<DtlsTransport> dtls_transport, + DataChannelTransportInterface* data_channel_transport) override; + + void OnRtpPacket(const RtpPacketReceived& packet) override; + void OnCandidates(const std::string& mid, + const std::vector<cricket::Candidate>& candidates); + + IceConnectionObserver* const observer_; + EmulatedEndpoint* const endpoint_; + EmulatedNetworkManagerInterface* const manager_; + rtc::Thread* const signaling_thread_; + rtc::Thread* const network_thread_; + rtc::scoped_refptr<rtc::RTCCertificate> const certificate_ + RTC_GUARDED_BY(network_thread_); + cricket::TransportDescription const transport_description_ + RTC_GUARDED_BY(signaling_thread_); + std::unique_ptr<cricket::BasicPortAllocator> port_allocator_ + RTC_GUARDED_BY(network_thread_); + std::unique_ptr<JsepTransportController> jsep_controller_; + RtpTransportInternal* rtp_transport_ RTC_GUARDED_BY(network_thread_) = + nullptr; + std::unique_ptr<SessionDescriptionInterface> remote_description_ + RTC_GUARDED_BY(signaling_thread_); + std::unique_ptr<SessionDescriptionInterface> local_description_ + RTC_GUARDED_BY(signaling_thread_); +}; + +std::unique_ptr<ScenarioIceConnection> ScenarioIceConnection::Create( + webrtc::test::NetworkEmulationManagerImpl* net, + IceConnectionObserver* observer) { + return std::make_unique<ScenarioIceConnectionImpl>(net, observer); +} + +ScenarioIceConnectionImpl::ScenarioIceConnectionImpl( + test::NetworkEmulationManagerImpl* net, + IceConnectionObserver* observer) + : observer_(observer), + endpoint_(net->CreateEndpoint(EmulatedEndpointConfig())), + manager_(net->CreateEmulatedNetworkManagerInterface({endpoint_})), + signaling_thread_(rtc::Thread::Current()), + network_thread_(manager_->network_thread()), + certificate_(rtc::RTCCertificate::Create( + rtc::SSLIdentity::Create("", ::rtc::KT_DEFAULT))), + transport_description_( + /*transport_options*/ {}, + rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), + rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), + cricket::IceMode::ICEMODE_FULL, + cricket::ConnectionRole::CONNECTIONROLE_PASSIVE, + rtc::SSLFingerprint::CreateFromCertificate(*certificate_.get()) + .get()), + port_allocator_( + new cricket::BasicPortAllocator(manager_->network_manager(), + manager_->packet_socket_factory())), + jsep_controller_( + new JsepTransportController(network_thread_, + port_allocator_.get(), + /*async_resolver_factory*/ nullptr, + CreateJsepConfig())) { + SendTask(network_thread_, [this] { + RTC_DCHECK_RUN_ON(network_thread_); + uint32_t flags = cricket::PORTALLOCATOR_DISABLE_TCP; + port_allocator_->set_flags(port_allocator_->flags() | flags); + port_allocator_->Initialize(); + RTC_CHECK(port_allocator_->SetConfiguration(/*stun_servers*/ {}, + /*turn_servers*/ {}, 0, + webrtc::NO_PRUNE)); + jsep_controller_->SetLocalCertificate(certificate_); + }); +} + +ScenarioIceConnectionImpl::~ScenarioIceConnectionImpl() { + SendTask(network_thread_, [this] { + RTC_DCHECK_RUN_ON(network_thread_); + jsep_controller_.reset(); + port_allocator_.reset(); + rtp_transport_ = nullptr; + }); +} + +JsepTransportController::Config ScenarioIceConnectionImpl::CreateJsepConfig() { + JsepTransportController::Config config; + config.transport_observer = this; + config.bundle_policy = + PeerConnectionInterface::BundlePolicy::kBundlePolicyMaxBundle; + config.rtcp_handler = [this](const rtc::CopyOnWriteBuffer& packet, + int64_t packet_time_us) { + RTC_DCHECK_RUN_ON(network_thread_); + observer_->OnPacketReceived(packet); + }; + config.field_trials = &field_trials; + return config; +} + +void ScenarioIceConnectionImpl::SendRtpPacket( + rtc::ArrayView<const uint8_t> packet_view) { + rtc::CopyOnWriteBuffer packet(packet_view.data(), packet_view.size(), + ::cricket::kMaxRtpPacketLen); + network_thread_->PostTask([this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(network_thread_); + if (rtp_transport_ != nullptr) + rtp_transport_->SendRtpPacket(&packet, rtc::PacketOptions(), + cricket::PF_SRTP_BYPASS); + }); +} + +void ScenarioIceConnectionImpl::SendRtcpPacket( + rtc::ArrayView<const uint8_t> packet_view) { + rtc::CopyOnWriteBuffer packet(packet_view.data(), packet_view.size(), + ::cricket::kMaxRtpPacketLen); + network_thread_->PostTask([this, packet = std::move(packet)]() mutable { + RTC_DCHECK_RUN_ON(network_thread_); + if (rtp_transport_ != nullptr) + rtp_transport_->SendRtcpPacket(&packet, rtc::PacketOptions(), + cricket::PF_SRTP_BYPASS); + }); +} +void ScenarioIceConnectionImpl::SetRemoteSdp(SdpType type, + const std::string& remote_sdp) { + RTC_DCHECK_RUN_ON(signaling_thread_); + remote_description_ = webrtc::CreateSessionDescription(type, remote_sdp); + jsep_controller_->SubscribeIceCandidateGathered( + [this](const std::string& transport, + const std::vector<cricket::Candidate>& candidate) { + ScenarioIceConnectionImpl::OnCandidates(transport, candidate); + }); + + auto res = jsep_controller_->SetRemoteDescription( + remote_description_->GetType(), remote_description_->description()); + RTC_CHECK(res.ok()) << res.message(); + RtpDemuxerCriteria criteria; + for (const auto& content : remote_description_->description()->contents()) { + if (content.media_description()->as_audio()) { + for (const auto& codec : + content.media_description()->as_audio()->codecs()) { + criteria.payload_types().insert(codec.id); + } + } + if (content.media_description()->as_video()) { + for (const auto& codec : + content.media_description()->as_video()->codecs()) { + criteria.payload_types().insert(codec.id); + } + } + } + + network_thread_->PostTask([this, criteria]() { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DCHECK(rtp_transport_); + rtp_transport_->RegisterRtpDemuxerSink(criteria, this); + }); +} + +void ScenarioIceConnectionImpl::SetLocalSdp(SdpType type, + const std::string& local_sdp) { + RTC_DCHECK_RUN_ON(signaling_thread_); + local_description_ = webrtc::CreateSessionDescription(type, local_sdp); + auto res = jsep_controller_->SetLocalDescription( + local_description_->GetType(), local_description_->description()); + RTC_CHECK(res.ok()) << res.message(); + jsep_controller_->MaybeStartGathering(); +} + +bool ScenarioIceConnectionImpl::OnTransportChanged( + const std::string& mid, + RtpTransportInternal* rtp_transport, + rtc::scoped_refptr<DtlsTransport> dtls_transport, + DataChannelTransportInterface* data_channel_transport) { + RTC_DCHECK_RUN_ON(network_thread_); + if (rtp_transport == nullptr) { + rtp_transport_->UnregisterRtpDemuxerSink(this); + } else { + RTC_DCHECK(rtp_transport_ == nullptr || rtp_transport_ == rtp_transport); + if (rtp_transport_ != rtp_transport) { + rtp_transport_ = rtp_transport; + } + RtpDemuxerCriteria criteria(mid); + rtp_transport_->RegisterRtpDemuxerSink(criteria, this); + } + return true; +} + +void ScenarioIceConnectionImpl::OnRtpPacket(const RtpPacketReceived& packet) { + RTC_DCHECK_RUN_ON(network_thread_); + observer_->OnPacketReceived(packet.Buffer()); +} + +void ScenarioIceConnectionImpl::OnCandidates( + const std::string& mid, + const std::vector<cricket::Candidate>& candidates) { + RTC_DCHECK_RUN_ON(signaling_thread_); + observer_->OnIceCandidates(mid, candidates); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/peer_scenario/scenario_connection.h b/third_party/libwebrtc/test/peer_scenario/scenario_connection.h new file mode 100644 index 0000000000..e8cef527c5 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/scenario_connection.h @@ -0,0 +1,66 @@ +/* + * 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_PEER_SCENARIO_SCENARIO_CONNECTION_H_ +#define TEST_PEER_SCENARIO_SCENARIO_CONNECTION_H_ + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "api/candidate.h" +#include "api/jsep.h" +#include "p2p/base/transport_description.h" +#include "test/network/network_emulation_manager.h" +#include "test/scoped_key_value_config.h" + +namespace webrtc { + +// ScenarioIceConnection provides the transport level functionality of a +// PeerConnection for use in peer connection scenario tests. This allows +// implementing custom server side behavior in tests. +class ScenarioIceConnection { + public: + class IceConnectionObserver { + public: + // Called on network thread. + virtual void OnPacketReceived(rtc::CopyOnWriteBuffer packet) = 0; + // Called on signaling thread. + virtual void OnIceCandidates( + const std::string& mid, + const std::vector<cricket::Candidate>& candidates) = 0; + + protected: + ~IceConnectionObserver() = default; + }; + static std::unique_ptr<ScenarioIceConnection> Create( + test::NetworkEmulationManagerImpl* net, + IceConnectionObserver* observer); + + virtual ~ScenarioIceConnection() = default; + + // Posts tasks to send packets to network thread. + virtual void SendRtpPacket(rtc::ArrayView<const uint8_t> packet_view) = 0; + virtual void SendRtcpPacket(rtc::ArrayView<const uint8_t> packet_view) = 0; + + // Used for ICE configuration, called on signaling thread. + virtual void SetRemoteSdp(SdpType type, const std::string& remote_sdp) = 0; + virtual void SetLocalSdp(SdpType type, const std::string& local_sdp) = 0; + + virtual EmulatedEndpoint* endpoint() = 0; + virtual const cricket::TransportDescription& transport_description() + const = 0; + + webrtc::test::ScopedKeyValueConfig field_trials; +}; + +} // namespace webrtc + +#endif // TEST_PEER_SCENARIO_SCENARIO_CONNECTION_H_ diff --git a/third_party/libwebrtc/test/peer_scenario/signaling_route.cc b/third_party/libwebrtc/test/peer_scenario/signaling_route.cc new file mode 100644 index 0000000000..eeec7c8657 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/signaling_route.cc @@ -0,0 +1,114 @@ +/* + * 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/peer_scenario/signaling_route.h" + +#include <memory> + +#include "test/network/network_emulation_manager.h" + +namespace webrtc { +namespace test { +namespace { +constexpr size_t kIcePacketSize = 400; +constexpr size_t kSdpPacketSize = 1200; + +struct IceMessage { + IceMessage() = default; + explicit IceMessage(const IceCandidateInterface* candidate) + : sdp_mid(candidate->sdp_mid()), + sdp_mline_index(candidate->sdp_mline_index()) { + RTC_CHECK(candidate->ToString(&sdp_line)); + } + std::unique_ptr<IceCandidateInterface> AsCandidate() const { + SdpParseError err; + std::unique_ptr<IceCandidateInterface> candidate( + CreateIceCandidate(sdp_mid, sdp_mline_index, sdp_line, &err)); + RTC_CHECK(candidate) << "Failed to parse: \"" << err.line + << "\". Reason: " << err.description; + return candidate; + } + std::string sdp_mid; + int sdp_mline_index; + std::string sdp_line; +}; + +void StartIceSignalingForRoute(PeerScenarioClient* caller, + PeerScenarioClient* callee, + CrossTrafficRoute* send_route) { + caller->handlers()->on_ice_candidate.push_back( + [=](const IceCandidateInterface* candidate) { + IceMessage msg(candidate); + send_route->NetworkDelayedAction(kIcePacketSize, [callee, msg]() { + callee->thread()->PostTask( + [callee, msg]() { callee->AddIceCandidate(msg.AsCandidate()); }); + }); + }); +} + +void StartSdpNegotiation( + PeerScenarioClient* caller, + PeerScenarioClient* callee, + CrossTrafficRoute* send_route, + CrossTrafficRoute* ret_route, + std::function<void(SessionDescriptionInterface* offer)> munge_offer, + std::function<void(SessionDescriptionInterface*)> modify_offer, + std::function<void(const SessionDescriptionInterface&)> exchange_finished) { + caller->CreateAndSetSdp(munge_offer, [=](std::string sdp_offer) { + if (modify_offer) { + auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer); + modify_offer(offer.get()); + RTC_CHECK(offer->ToString(&sdp_offer)); + } + send_route->NetworkDelayedAction(kSdpPacketSize, [=] { + callee->SetSdpOfferAndGetAnswer(sdp_offer, [=](std::string answer) { + ret_route->NetworkDelayedAction(kSdpPacketSize, [=] { + caller->SetSdpAnswer(std::move(answer), std::move(exchange_finished)); + }); + }); + }); + }); +} +} // namespace + +SignalingRoute::SignalingRoute(PeerScenarioClient* caller, + PeerScenarioClient* callee, + CrossTrafficRoute* send_route, + CrossTrafficRoute* ret_route) + : caller_(caller), + callee_(callee), + send_route_(send_route), + ret_route_(ret_route) {} + +void SignalingRoute::StartIceSignaling() { + StartIceSignalingForRoute(caller_, callee_, send_route_); + StartIceSignalingForRoute(callee_, caller_, ret_route_); +} + +void SignalingRoute::NegotiateSdp( + std::function<void(SessionDescriptionInterface*)> munge_offer, + std::function<void(SessionDescriptionInterface*)> modify_offer, + std::function<void(const SessionDescriptionInterface&)> exchange_finished) { + StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, munge_offer, + modify_offer, exchange_finished); +} + +void SignalingRoute::NegotiateSdp( + std::function<void(SessionDescriptionInterface*)> modify_offer, + std::function<void(const SessionDescriptionInterface&)> exchange_finished) { + NegotiateSdp({}, modify_offer, exchange_finished); +} + +void SignalingRoute::NegotiateSdp( + std::function<void(const SessionDescriptionInterface&)> exchange_finished) { + NegotiateSdp({}, {}, exchange_finished); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/peer_scenario/signaling_route.h b/third_party/libwebrtc/test/peer_scenario/signaling_route.h new file mode 100644 index 0000000000..a95ae5c9f7 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/signaling_route.h @@ -0,0 +1,67 @@ +/* + * 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_PEER_SCENARIO_SIGNALING_ROUTE_H_ +#define TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_ + +#include <string> +#include <utility> + +#include "test/network/network_emulation_manager.h" +#include "test/peer_scenario/peer_scenario_client.h" + +namespace webrtc { +namespace test { + +// Helper class to reduce the amount of boilerplate required for ICE signalling +// ad SDP negotiation. +class SignalingRoute { + public: + SignalingRoute(PeerScenarioClient* caller, + PeerScenarioClient* callee, + CrossTrafficRoute* send_route, + CrossTrafficRoute* ret_route); + + void StartIceSignaling(); + + // The `modify_offer` callback is used to modify an offer after the local + // description has been set. This is legal (but odd) behavior. + // The `munge_offer` callback is used to modify an offer between its creation + // and set local description. This behavior is forbidden according to the spec + // but available here in order to allow test coverage on corner cases. + // The `exchange_finished` callback is called with the answer produced after + // SDP negotations has completed. + // TODO(srte): Handle lossy links. + void NegotiateSdp( + std::function<void(SessionDescriptionInterface* offer)> munge_offer, + std::function<void(SessionDescriptionInterface* offer)> modify_offer, + std::function<void(const SessionDescriptionInterface& answer)> + exchange_finished); + void NegotiateSdp( + std::function<void(SessionDescriptionInterface* offer)> modify_offer, + std::function<void(const SessionDescriptionInterface& answer)> + exchange_finished); + void NegotiateSdp( + std::function<void(const SessionDescriptionInterface& answer)> + exchange_finished); + SignalingRoute reverse() { + return SignalingRoute(callee_, caller_, ret_route_, send_route_); + } + + private: + PeerScenarioClient* const caller_; + PeerScenarioClient* const callee_; + CrossTrafficRoute* const send_route_; + CrossTrafficRoute* const ret_route_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_ diff --git a/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn b/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn new file mode 100644 index 0000000000..ba6ec20e84 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn @@ -0,0 +1,30 @@ +# 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. + +import("../../../webrtc.gni") + +if (rtc_include_tests) { + rtc_library("tests") { + testonly = true + sources = [ + "peer_scenario_quality_test.cc", + "remote_estimate_test.cc", + "unsignaled_stream_test.cc", + ] + deps = [ + "..:peer_scenario", + "../../:field_trial", + "../../:test_support", + "../../../media:rtc_media_base", + "../../../media:stream_params", + "../../../modules/rtp_rtcp:rtp_rtcp_format", + "../../../pc:media_session", + "../../../pc:session_description", + ] + } +} diff --git a/third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc new file mode 100644 index 0000000000..911a68720f --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/tests/peer_scenario_quality_test.cc @@ -0,0 +1,46 @@ +/* + * 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/gtest.h" +#include "test/peer_scenario/peer_scenario.h" +#include "test/peer_scenario/peer_scenario_client.h" + +namespace webrtc { +namespace test { +#if defined(WEBRTC_WIN) +#define MAYBE_PsnrIsCollected DISABLED_PsnrIsCollected +#else +#define MAYBE_PsnrIsCollected PsnrIsCollected +#endif +TEST(PeerScenarioQualityTest, MAYBE_PsnrIsCollected) { + VideoQualityAnalyzer analyzer; + { + PeerScenario s(*test_info_); + auto caller = s.CreateClient(PeerScenarioClient::Config()); + auto callee = s.CreateClient(PeerScenarioClient::Config()); + PeerScenarioClient::VideoSendTrackConfig video_conf; + video_conf.generator.squares_video->framerate = 20; + auto video = caller->CreateVideo("VIDEO", video_conf); + auto link_builder = s.net()->NodeBuilder().delay_ms(100).capacity_kbps(600); + s.AttachVideoQualityAnalyzer(&analyzer, video.track.get(), callee); + s.SimpleConnection(caller, callee, {link_builder.Build().node}, + {link_builder.Build().node}); + s.ProcessMessages(TimeDelta::Seconds(2)); + // Exit scope to ensure that there's no pending tasks reporting to analyzer. + } + + // We expect ca 40 frames to be produced, but to avoid flakiness on slow + // machines we only test for 10. + EXPECT_GT(analyzer.stats().render.count, 10); + EXPECT_GT(analyzer.stats().psnr_with_freeze.Mean(), 20); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc new file mode 100644 index 0000000000..2dfbfdd3c9 --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/tests/remote_estimate_test.cc @@ -0,0 +1,112 @@ +/* + * 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 "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "pc/media_session.h" +#include "pc/session_description.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/peer_scenario/peer_scenario.h" + +namespace webrtc { +namespace test { +namespace { +RtpHeaderExtensionMap AudioExtensions( + const SessionDescriptionInterface& session) { + auto* audio_desc = + cricket::GetFirstAudioContentDescription(session.description()); + return RtpHeaderExtensionMap(audio_desc->rtp_header_extensions()); +} + +} // namespace + +TEST(RemoteEstimateEndToEnd, OfferedCapabilityIsInAnswer) { + PeerScenario s(*test_info_); + + auto* caller = s.CreateClient(PeerScenarioClient::Config()); + auto* callee = s.CreateClient(PeerScenarioClient::Config()); + + auto send_link = {s.net()->NodeBuilder().Build().node}; + auto ret_link = {s.net()->NodeBuilder().Build().node}; + + s.net()->CreateRoute(caller->endpoint(), send_link, callee->endpoint()); + s.net()->CreateRoute(callee->endpoint(), ret_link, caller->endpoint()); + + auto signaling = s.ConnectSignaling(caller, callee, send_link, ret_link); + caller->CreateVideo("VIDEO", PeerScenarioClient::VideoSendTrackConfig()); + std::atomic<bool> offer_exchange_done(false); + signaling.NegotiateSdp( + [](SessionDescriptionInterface* offer) { + for (auto& cont : offer->description()->contents()) { + cont.media_description()->set_remote_estimate(true); + } + }, + [&](const SessionDescriptionInterface& answer) { + for (auto& cont : answer.description()->contents()) { + EXPECT_TRUE(cont.media_description()->remote_estimate()); + } + offer_exchange_done = true; + }); + RTC_CHECK(s.WaitAndProcess(&offer_exchange_done)); +} + +TEST(RemoteEstimateEndToEnd, AudioUsesAbsSendTimeExtension) { + // Defined before PeerScenario so it gets destructed after, to avoid use after free. + std::atomic<bool> received_abs_send_time(false); + PeerScenario s(*test_info_); + + auto* caller = s.CreateClient(PeerScenarioClient::Config()); + auto* callee = s.CreateClient(PeerScenarioClient::Config()); + + auto send_node = s.net()->NodeBuilder().Build().node; + auto ret_node = s.net()->NodeBuilder().Build().node; + + s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint()); + s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint()); + + auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node}); + caller->CreateAudio("AUDIO", cricket::AudioOptions()); + signaling.StartIceSignaling(); + RtpHeaderExtensionMap extension_map; + std::atomic<bool> offer_exchange_done(false); + signaling.NegotiateSdp( + [&extension_map](SessionDescriptionInterface* offer) { + extension_map = AudioExtensions(*offer); + EXPECT_TRUE(extension_map.IsRegistered(kRtpExtensionAbsoluteSendTime)); + }, + [&](const SessionDescriptionInterface& answer) { + EXPECT_TRUE(AudioExtensions(answer).IsRegistered( + kRtpExtensionAbsoluteSendTime)); + offer_exchange_done = true; + }); + RTC_CHECK(s.WaitAndProcess(&offer_exchange_done)); + send_node->router()->SetWatcher( + [extension_map, &received_abs_send_time](const EmulatedIpPacket& packet) { + // The dummy packets used by the fake signaling are filled with 0. We + // want to ignore those and we can do that on the basis that the first + // byte of RTP packets are guaranteed to not be 0. + RtpPacket rtp_packet(&extension_map); + // TODO(bugs.webrtc.org/14525): Look why there are RTP packets with + // payload 72 or 73 (these don't have the RTP AbsoluteSendTime + // Extension). + if (rtp_packet.Parse(packet.data) && rtp_packet.PayloadType() == 111) { + EXPECT_TRUE(rtp_packet.HasExtension<AbsoluteSendTime>()); + received_abs_send_time = true; + } + }); + RTC_CHECK(s.WaitAndProcess(&received_abs_send_time)); + caller->pc()->Close(); + callee->pc()->Close(); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc new file mode 100644 index 0000000000..4f478b4b2a --- /dev/null +++ b/third_party/libwebrtc/test/peer_scenario/tests/unsignaled_stream_test.cc @@ -0,0 +1,270 @@ +/* + * 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 "media/base/stream_params.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "pc/media_session.h" +#include "pc/session_description.h" +#include "test/field_trial.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/peer_scenario/peer_scenario.h" + +namespace webrtc { +namespace test { +namespace { + +enum class MidTestConfiguration { + // Legacy endpoint setup where PT demuxing is used. + kMidNotNegotiated, + // MID is negotiated but missing from packets. PT demuxing is disabled, so + // SSRCs have to be added to the SDP for WebRTC to forward packets correctly. + // Happens when client is spec compliant but the SFU isn't. Popular legacy. + kMidNegotiatedButMissingFromPackets, + // Fully spec-compliant: MID is present so we can safely drop packets with + // unknown MIDs. + kMidNegotiatedAndPresentInPackets, +}; + +// Gives the parameterized test a readable suffix. +std::string TestParametersMidTestConfigurationToString( + testing::TestParamInfo<MidTestConfiguration> info) { + switch (info.param) { + case MidTestConfiguration::kMidNotNegotiated: + return "MidNotNegotiated"; + case MidTestConfiguration::kMidNegotiatedButMissingFromPackets: + return "MidNegotiatedButMissingFromPackets"; + case MidTestConfiguration::kMidNegotiatedAndPresentInPackets: + return "MidNegotiatedAndPresentInPackets"; + } +} + +class FrameObserver : public rtc::VideoSinkInterface<VideoFrame> { + public: + FrameObserver() : frame_observed_(false) {} + void OnFrame(const VideoFrame&) override { frame_observed_ = true; } + + std::atomic<bool> frame_observed_; +}; + +uint32_t get_ssrc(SessionDescriptionInterface* offer, size_t track_index) { + EXPECT_LT(track_index, offer->description()->contents().size()); + return offer->description() + ->contents()[track_index] + .media_description() + ->streams()[0] + .ssrcs[0]; +} + +void set_ssrc(SessionDescriptionInterface* offer, size_t index, uint32_t ssrc) { + EXPECT_LT(index, offer->description()->contents().size()); + cricket::StreamParams& new_stream_params = offer->description() + ->contents()[index] + .media_description() + ->mutable_streams()[0]; + new_stream_params.ssrcs[0] = ssrc; + new_stream_params.ssrc_groups[0].ssrcs[0] = ssrc; +} + +} // namespace + +class UnsignaledStreamTest + : public ::testing::Test, + public ::testing::WithParamInterface<MidTestConfiguration> {}; + +TEST_P(UnsignaledStreamTest, ReplacesUnsignaledStreamOnCompletedSignaling) { + // This test covers a scenario that might occur if a remote client starts + // sending media packets before negotiation has completed. Depending on setup, + // these packets either get dropped or trigger an unsignalled default stream + // to be created, and connects that to a default video sink. + // In some edge cases using Unified Plan and PT demuxing, the default stream + // is create in a different transceiver to where the media SSRC will actually + // be used. This test verifies that the default stream is removed properly, + // and that packets are demuxed and video frames reach the desired sink. + const MidTestConfiguration kMidTestConfiguration = GetParam(); + + // Defined before PeerScenario so it gets destructed after, to avoid use after + // free. + PeerScenario s(*::testing::UnitTest::GetInstance()->current_test_info()); + + PeerScenarioClient::Config config = PeerScenarioClient::Config(); + // Disable encryption so that we can inject a fake early media packet without + // triggering srtp failures. + config.disable_encryption = true; + auto* caller = s.CreateClient(config); + auto* callee = s.CreateClient(config); + + auto send_node = s.net()->NodeBuilder().Build().node; + auto ret_node = s.net()->NodeBuilder().Build().node; + + s.net()->CreateRoute(caller->endpoint(), {send_node}, callee->endpoint()); + s.net()->CreateRoute(callee->endpoint(), {ret_node}, caller->endpoint()); + + auto signaling = s.ConnectSignaling(caller, callee, {send_node}, {ret_node}); + PeerScenarioClient::VideoSendTrackConfig video_conf; + video_conf.generator.squares_video->framerate = 15; + + auto first_track = caller->CreateVideo("VIDEO", video_conf); + FrameObserver first_sink; + callee->AddVideoReceiveSink(first_track.track->id(), &first_sink); + + signaling.StartIceSignaling(); + std::atomic<bool> offer_exchange_done(false); + std::atomic<bool> got_unsignaled_packet(false); + + // We will capture the media ssrc of the first added stream, and preemptively + // inject a new media packet using a different ssrc. What happens depends on + // the test configuration. + // + // MidTestConfiguration::kMidNotNegotiated: + // - MID is not negotiated which means PT-based demuxing is enabled. Because + // the packets have no MID, the second ssrc packet gets forwarded to the + // first m= section. This will create a "default stream" for the second ssrc + // and connect it to the default video sink (not set in this test). The test + // verifies we can recover from this when we later get packets for the first + // ssrc. + // + // MidTestConfiguration::kMidNegotiatedButMissingFromPackets: + // - MID is negotiated wich means PT-based demuxing is disabled. Because we + // modify the packets not to contain the MID anyway (simulating a legacy SFU + // that does not negotiate properly) unknown SSRCs are dropped but do not + // otherwise cause any issues. + // + // MidTestConfiguration::kMidNegotiatedAndPresentInPackets: + // - MID is negotiated which means PT-based demuxing is enabled. In this case + // the packets have the MID so they either get forwarded or dropped + // depending on if the MID is known. The spec-compliant way is also the most + // straight-forward one. + + uint32_t first_ssrc = 0; + uint32_t second_ssrc = 0; + absl::optional<int> mid_header_extension_id = absl::nullopt; + + signaling.NegotiateSdp( + /* munge_sdp = */ + [&](SessionDescriptionInterface* offer) { + // Obtain the MID header extension ID and if we want the + // MidTestConfiguration::kMidNotNegotiated setup then we remove the MID + // header extension through SDP munging (otherwise SDP is not modified). + for (cricket::ContentInfo& content_info : + offer->description()->contents()) { + std::vector<RtpExtension> header_extensions = + content_info.media_description()->rtp_header_extensions(); + for (auto it = header_extensions.begin(); + it != header_extensions.end(); ++it) { + if (it->uri == RtpExtension::kMidUri) { + // MID header extension found! + mid_header_extension_id = it->id; + if (kMidTestConfiguration == + MidTestConfiguration::kMidNotNegotiated) { + // Munge away the extension. + header_extensions.erase(it); + } + break; + } + } + content_info.media_description()->set_rtp_header_extensions( + std::move(header_extensions)); + } + ASSERT_TRUE(mid_header_extension_id.has_value()); + }, + /* modify_sdp = */ + [&](SessionDescriptionInterface* offer) { + first_ssrc = get_ssrc(offer, 0); + second_ssrc = first_ssrc + 1; + + send_node->router()->SetWatcher([&](const EmulatedIpPacket& packet) { + if (IsRtpPacket(packet.data) && + ByteReader<uint32_t>::ReadBigEndian(&(packet.cdata()[8])) == + first_ssrc && + !got_unsignaled_packet) { + // Parse packet and modify the SSRC to simulate a second m= + // section that has not been negotiated yet. + std::vector<RtpExtension> extensions; + extensions.emplace_back(RtpExtension::kMidUri, + mid_header_extension_id.value()); + RtpHeaderExtensionMap extensions_map(extensions); + RtpPacket parsed_packet; + parsed_packet.IdentifyExtensions(extensions_map); + ASSERT_TRUE(parsed_packet.Parse(packet.data)); + parsed_packet.SetSsrc(second_ssrc); + // The MID extension is present if and only if it was negotiated. + // If present, we either want to remove it or modify it depending + // on setup. + switch (kMidTestConfiguration) { + case MidTestConfiguration::kMidNotNegotiated: + EXPECT_FALSE(parsed_packet.HasExtension<RtpMid>()); + break; + case MidTestConfiguration::kMidNegotiatedButMissingFromPackets: + EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>()); + ASSERT_TRUE(parsed_packet.RemoveExtension(RtpMid::kId)); + break; + case MidTestConfiguration::kMidNegotiatedAndPresentInPackets: + EXPECT_TRUE(parsed_packet.HasExtension<RtpMid>()); + // The simulated second m= section would have a different MID. + // If we don't modify it here then `second_ssrc` would end up + // being mapped to the first m= section which would cause SSRC + // conflicts if we later add the same SSRC to a second m= + // section. Hidden assumption: first m= section does not use + // MID:1. + ASSERT_TRUE(parsed_packet.SetExtension<RtpMid>("1")); + break; + } + // Inject the modified packet. + rtc::CopyOnWriteBuffer updated_buffer = parsed_packet.Buffer(); + EmulatedIpPacket updated_packet( + packet.from, packet.to, updated_buffer, packet.arrival_time); + send_node->OnPacketReceived(std::move(updated_packet)); + got_unsignaled_packet = true; + } + }); + }, + [&](const SessionDescriptionInterface& answer) { + EXPECT_EQ(answer.description()->contents().size(), 1u); + offer_exchange_done = true; + }); + EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done)); + EXPECT_TRUE(s.WaitAndProcess(&got_unsignaled_packet)); + EXPECT_TRUE(s.WaitAndProcess(&first_sink.frame_observed_)); + + auto second_track = caller->CreateVideo("VIDEO2", video_conf); + FrameObserver second_sink; + callee->AddVideoReceiveSink(second_track.track->id(), &second_sink); + + // Create a second video stream, munge the sdp to force it to use our fake + // early media ssrc. + offer_exchange_done = false; + signaling.NegotiateSdp( + /* munge_sdp = */ + [&](SessionDescriptionInterface* offer) { + set_ssrc(offer, 1, second_ssrc); + }, + /* modify_sdp = */ {}, + [&](const SessionDescriptionInterface& answer) { + EXPECT_EQ(answer.description()->contents().size(), 2u); + offer_exchange_done = true; + }); + EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done)); + EXPECT_TRUE(s.WaitAndProcess(&second_sink.frame_observed_)); + caller->pc()->Close(); + callee->pc()->Close(); +} + +INSTANTIATE_TEST_SUITE_P( + All, + UnsignaledStreamTest, + ::testing::Values(MidTestConfiguration::kMidNotNegotiated, + MidTestConfiguration::kMidNegotiatedButMissingFromPackets, + MidTestConfiguration::kMidNegotiatedAndPresentInPackets), + TestParametersMidTestConfigurationToString); + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/platform_video_capturer.cc b/third_party/libwebrtc/test/platform_video_capturer.cc new file mode 100644 index 0000000000..fb3392a052 --- /dev/null +++ b/third_party/libwebrtc/test/platform_video_capturer.cc @@ -0,0 +1,37 @@ +/* + * 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/platform_video_capturer.h" + +#include "absl/memory/memory.h" +#if defined(WEBRTC_MAC) +#include "test/mac_capturer.h" +#else +#include "test/vcm_capturer.h" +#endif + +namespace webrtc { +namespace test { + +std::unique_ptr<TestVideoCapturer> CreateVideoCapturer( + size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index) { +#if defined(WEBRTC_MAC) + return absl::WrapUnique<TestVideoCapturer>(test::MacCapturer::Create( + width, height, target_fps, capture_device_index)); +#else + return absl::WrapUnique<TestVideoCapturer>(test::VcmCapturer::Create( + width, height, target_fps, capture_device_index)); +#endif +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/platform_video_capturer.h b/third_party/libwebrtc/test/platform_video_capturer.h new file mode 100644 index 0000000000..241ba87df8 --- /dev/null +++ b/third_party/libwebrtc/test/platform_video_capturer.h @@ -0,0 +1,29 @@ +/* + * 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_PLATFORM_VIDEO_CAPTURER_H_ +#define TEST_PLATFORM_VIDEO_CAPTURER_H_ + +#include <memory> + +#include "test/test_video_capturer.h" + +namespace webrtc { +namespace test { + +std::unique_ptr<TestVideoCapturer> CreateVideoCapturer( + size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index); + +} // namespace test +} // namespace webrtc + +#endif // TEST_PLATFORM_VIDEO_CAPTURER_H_ diff --git a/third_party/libwebrtc/test/rtcp_packet_parser.cc b/third_party/libwebrtc/test/rtcp_packet_parser.cc new file mode 100644 index 0000000000..e286ec5a36 --- /dev/null +++ b/third_party/libwebrtc/test/rtcp_packet_parser.cc @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014 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/rtcp_packet_parser.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace test { + +RtcpPacketParser::RtcpPacketParser() = default; +RtcpPacketParser::~RtcpPacketParser() = default; + +bool RtcpPacketParser::Parse(const void* data, size_t length) { + ++processed_rtcp_packets_; + + const uint8_t* const buffer = static_cast<const uint8_t*>(data); + const uint8_t* const buffer_end = buffer + length; + + rtcp::CommonHeader header; + for (const uint8_t* next_packet = buffer; next_packet != buffer_end; + next_packet = header.NextPacket()) { + RTC_DCHECK_GT(buffer_end - next_packet, 0); + if (!header.Parse(next_packet, buffer_end - next_packet)) { + RTC_LOG(LS_WARNING) + << "Invalid rtcp header or unaligned rtcp packet at position " + << (next_packet - buffer); + return false; + } + switch (header.type()) { + case rtcp::App::kPacketType: + app_.Parse(header); + break; + case rtcp::Bye::kPacketType: + bye_.Parse(header, &sender_ssrc_); + break; + case rtcp::ExtendedReports::kPacketType: + xr_.Parse(header, &sender_ssrc_); + break; + case rtcp::Psfb::kPacketType: + switch (header.fmt()) { + case rtcp::Fir::kFeedbackMessageType: + fir_.Parse(header, &sender_ssrc_); + break; + case rtcp::Pli::kFeedbackMessageType: + pli_.Parse(header, &sender_ssrc_); + break; + case rtcp::Psfb::kAfbMessageType: + if (!loss_notification_.Parse(header, &sender_ssrc_) && + !remb_.Parse(header, &sender_ssrc_)) { + RTC_LOG(LS_WARNING) << "Unknown application layer FB message."; + } + break; + default: + RTC_LOG(LS_WARNING) + << "Unknown rtcp payload specific feedback type " + << header.fmt(); + break; + } + break; + case rtcp::ReceiverReport::kPacketType: + receiver_report_.Parse(header, &sender_ssrc_); + break; + case rtcp::Rtpfb::kPacketType: + switch (header.fmt()) { + case rtcp::Nack::kFeedbackMessageType: + nack_.Parse(header, &sender_ssrc_); + break; + case rtcp::RapidResyncRequest::kFeedbackMessageType: + rrr_.Parse(header, &sender_ssrc_); + break; + case rtcp::Tmmbn::kFeedbackMessageType: + tmmbn_.Parse(header, &sender_ssrc_); + break; + case rtcp::Tmmbr::kFeedbackMessageType: + tmmbr_.Parse(header, &sender_ssrc_); + break; + case rtcp::TransportFeedback::kFeedbackMessageType: + transport_feedback_.Parse(header, &sender_ssrc_); + break; + default: + RTC_LOG(LS_WARNING) + << "Unknown rtcp transport feedback type " << header.fmt(); + break; + } + break; + case rtcp::Sdes::kPacketType: + sdes_.Parse(header); + break; + case rtcp::SenderReport::kPacketType: + sender_report_.Parse(header, &sender_ssrc_); + break; + default: + RTC_LOG(LS_WARNING) << "Unknown rtcp packet type " << header.type(); + break; + } + } + return true; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/rtcp_packet_parser.h b/third_party/libwebrtc/test/rtcp_packet_parser.h new file mode 100644 index 0000000000..9e8c9685e9 --- /dev/null +++ b/third_party/libwebrtc/test/rtcp_packet_parser.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2014 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_RTCP_PACKET_PARSER_H_ +#define TEST_RTCP_PACKET_PARSER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtcp_packet/app.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" +#include "modules/rtp_rtcp/source/rtcp_packet/fir.h" +#include "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" +#include "modules/rtp_rtcp/source/rtcp_packet/nack.h" +#include "modules/rtp_rtcp/source/rtcp_packet/pli.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/remb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sdes.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmbn.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmbr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { +// Parse RTCP packet of given type. Assumes RTCP header is valid and that there +// is excatly one packet of correct type in the buffer. +template <typename Packet> +bool ParseSinglePacket(const uint8_t* buffer, size_t size, Packet* packet) { + rtcp::CommonHeader header; + RTC_CHECK(header.Parse(buffer, size)); + RTC_CHECK_EQ(size, header.NextPacket() - buffer); + return packet->Parse(header); +} +// Same function, but takes raw buffer as single argument instead of pair. +template <typename Packet> +bool ParseSinglePacket(rtc::ArrayView<const uint8_t> buffer, Packet* packet) { + return ParseSinglePacket(buffer.data(), buffer.size(), packet); +} + +class RtcpPacketParser { + public: + // Keeps last parsed packet, count number of parsed packets of given type. + template <typename TypedRtcpPacket> + class PacketCounter : public TypedRtcpPacket { + public: + int num_packets() const { return num_packets_; } + void Parse(const rtcp::CommonHeader& header) { + if (TypedRtcpPacket::Parse(header)) + ++num_packets_; + } + bool Parse(const rtcp::CommonHeader& header, uint32_t* sender_ssrc) { + const bool result = TypedRtcpPacket::Parse(header); + if (result) { + ++num_packets_; + if (*sender_ssrc == 0) // Use first sender ssrc in compound packet. + *sender_ssrc = TypedRtcpPacket::sender_ssrc(); + } + return result; + } + + private: + int num_packets_ = 0; + }; + + RtcpPacketParser(); + ~RtcpPacketParser(); + + bool Parse(const void* packet, size_t packet_len); + + PacketCounter<rtcp::App>* app() { return &app_; } + PacketCounter<rtcp::Bye>* bye() { return &bye_; } + PacketCounter<rtcp::ExtendedReports>* xr() { return &xr_; } + PacketCounter<rtcp::Fir>* fir() { return &fir_; } + PacketCounter<rtcp::Nack>* nack() { return &nack_; } + PacketCounter<rtcp::Pli>* pli() { return &pli_; } + PacketCounter<rtcp::RapidResyncRequest>* rrr() { return &rrr_; } + PacketCounter<rtcp::ReceiverReport>* receiver_report() { + return &receiver_report_; + } + PacketCounter<rtcp::LossNotification>* loss_notification() { + return &loss_notification_; + } + PacketCounter<rtcp::Remb>* remb() { return &remb_; } + PacketCounter<rtcp::Sdes>* sdes() { return &sdes_; } + PacketCounter<rtcp::SenderReport>* sender_report() { return &sender_report_; } + PacketCounter<rtcp::Tmmbn>* tmmbn() { return &tmmbn_; } + PacketCounter<rtcp::Tmmbr>* tmmbr() { return &tmmbr_; } + PacketCounter<rtcp::TransportFeedback>* transport_feedback() { + return &transport_feedback_; + } + uint32_t sender_ssrc() const { return sender_ssrc_; } + size_t processed_rtcp_packets() const { return processed_rtcp_packets_; } + + private: + PacketCounter<rtcp::App> app_; + PacketCounter<rtcp::Bye> bye_; + PacketCounter<rtcp::ExtendedReports> xr_; + PacketCounter<rtcp::Fir> fir_; + PacketCounter<rtcp::Nack> nack_; + PacketCounter<rtcp::Pli> pli_; + PacketCounter<rtcp::RapidResyncRequest> rrr_; + PacketCounter<rtcp::ReceiverReport> receiver_report_; + PacketCounter<rtcp::LossNotification> loss_notification_; + PacketCounter<rtcp::Remb> remb_; + PacketCounter<rtcp::Sdes> sdes_; + PacketCounter<rtcp::SenderReport> sender_report_; + PacketCounter<rtcp::Tmmbn> tmmbn_; + PacketCounter<rtcp::Tmmbr> tmmbr_; + PacketCounter<rtcp::TransportFeedback> transport_feedback_; + uint32_t sender_ssrc_ = 0; + size_t processed_rtcp_packets_ = 0; +}; + +} // namespace test +} // namespace webrtc +#endif // TEST_RTCP_PACKET_PARSER_H_ diff --git a/third_party/libwebrtc/test/rtp_file_reader.cc b/third_party/libwebrtc/test/rtp_file_reader.cc new file mode 100644 index 0000000000..b6f3cbbe5b --- /dev/null +++ b/third_party/libwebrtc/test/rtp_file_reader.cc @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2014 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/rtp_file_reader.h" + +#include <stdio.h> + +#include <map> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/system/arch.h" + +namespace webrtc { +namespace test { + +static const size_t kFirstLineLength = 80; +static uint16_t kPacketHeaderSize = 8; + +#define TRY(expr) \ + do { \ + if (!(expr)) { \ + RTC_LOG(LS_INFO) << "Failed to read"; \ + return false; \ + } \ + } while (0) + +bool ReadUint32(uint32_t* out, FILE* file) { + *out = 0; + for (size_t i = 0; i < 4; ++i) { + *out <<= 8; + uint8_t tmp; + if (fread(&tmp, 1, sizeof(uint8_t), file) != sizeof(uint8_t)) + return false; + *out |= tmp; + } + return true; +} + +bool ReadUint16(uint16_t* out, FILE* file) { + *out = 0; + for (size_t i = 0; i < 2; ++i) { + *out <<= 8; + uint8_t tmp; + if (fread(&tmp, 1, sizeof(uint8_t), file) != sizeof(uint8_t)) + return false; + *out |= tmp; + } + return true; +} + +class RtpFileReaderImpl : public RtpFileReader { + public: + virtual bool Init(FILE* file, const std::set<uint32_t>& ssrc_filter) = 0; +}; + +class InterleavedRtpFileReader : public RtpFileReaderImpl { + public: + ~InterleavedRtpFileReader() override { + if (file_ != nullptr) { + fclose(file_); + file_ = nullptr; + } + } + + bool Init(FILE* file, const std::set<uint32_t>& ssrc_filter) override { + file_ = file; + return true; + } + + bool NextPacket(RtpPacket* packet) override { + RTC_DCHECK(file_); + packet->length = RtpPacket::kMaxPacketBufferSize; + uint32_t len = 0; + TRY(ReadUint32(&len, file_)); + if (packet->length < len) { + RTC_FATAL() << "Packet is too large to fit: " << len << " bytes vs " + << packet->length + << " bytes allocated. Consider increasing the buffer " + << "size"; + } + if (fread(packet->data, 1, len, file_) != len) + return false; + + packet->length = len; + packet->original_length = len; + packet->time_ms = time_ms_; + time_ms_ += 5; + return true; + } + + private: + FILE* file_ = nullptr; + int64_t time_ms_ = 0; +}; + +// Read RTP packets from file in rtpdump format, as documented at: +// http://www.cs.columbia.edu/irt/software/rtptools/ +class RtpDumpReader : public RtpFileReaderImpl { + public: + RtpDumpReader() : file_(nullptr) {} + ~RtpDumpReader() override { + if (file_ != nullptr) { + fclose(file_); + file_ = nullptr; + } + } + + RtpDumpReader(const RtpDumpReader&) = delete; + RtpDumpReader& operator=(const RtpDumpReader&) = delete; + + bool Init(FILE* file, const std::set<uint32_t>& ssrc_filter) override { + file_ = file; + + char firstline[kFirstLineLength + 1] = {0}; + if (fgets(firstline, kFirstLineLength, file_) == nullptr) { + RTC_LOG(LS_INFO) << "Can't read from file"; + return false; + } + if (strncmp(firstline, "#!rtpplay", 9) == 0) { + if (strncmp(firstline, "#!rtpplay1.0", 12) != 0) { + RTC_LOG(LS_INFO) << "Wrong rtpplay version, must be 1.0"; + return false; + } + } else if (strncmp(firstline, "#!RTPencode", 11) == 0) { + if (strncmp(firstline, "#!RTPencode1.0", 14) != 0) { + RTC_LOG(LS_INFO) << "Wrong RTPencode version, must be 1.0"; + return false; + } + } else { + RTC_LOG(LS_INFO) << "Wrong file format of input file"; + return false; + } + + uint32_t start_sec; + uint32_t start_usec; + uint32_t source; + uint16_t port; + uint16_t padding; + TRY(ReadUint32(&start_sec, file_)); + TRY(ReadUint32(&start_usec, file_)); + TRY(ReadUint32(&source, file_)); + TRY(ReadUint16(&port, file_)); + TRY(ReadUint16(&padding, file_)); + + return true; + } + + bool NextPacket(RtpPacket* packet) override { + uint8_t* rtp_data = packet->data; + packet->length = RtpPacket::kMaxPacketBufferSize; + + uint16_t len; + uint16_t plen; + uint32_t offset; + TRY(ReadUint16(&len, file_)); + TRY(ReadUint16(&plen, file_)); + TRY(ReadUint32(&offset, file_)); + + // Use 'len' here because a 'plen' of 0 specifies rtcp. + len -= kPacketHeaderSize; + if (packet->length < len) { + RTC_LOG(LS_ERROR) << "Packet is too large to fit: " << len << " bytes vs " + << packet->length + << " bytes allocated. Consider increasing the buffer " + "size"; + return false; + } + if (fread(rtp_data, 1, len, file_) != len) { + return false; + } + + packet->length = len; + packet->original_length = plen; + packet->time_ms = offset; + return true; + } + + private: + FILE* file_; +}; + +enum { + kResultFail = -1, + kResultSuccess = 0, + kResultSkip = 1, + + kPcapVersionMajor = 2, + kPcapVersionMinor = 4, + kLinktypeNull = 0, + kLinktypeEthernet = 1, + kBsdNullLoopback1 = 0x00000002, + kBsdNullLoopback2 = 0x02000000, + kEthernetIIHeaderMacSkip = 12, + kEthertypeIp = 0x0800, + kIpVersion4 = 4, + kMinIpHeaderLength = 20, + kFragmentOffsetClear = 0x0000, + kFragmentOffsetDoNotFragment = 0x4000, + kProtocolTcp = 0x06, + kProtocolUdp = 0x11, + kUdpHeaderLength = 8, + kMaxReadBufferSize = 4096 +}; + +const uint32_t kPcapBOMSwapOrder = 0xd4c3b2a1UL; +const uint32_t kPcapBOMNoSwapOrder = 0xa1b2c3d4UL; + +#define TRY_PCAP(expr) \ + do { \ + int r = (expr); \ + if (r == kResultFail) { \ + RTC_LOG(LS_INFO) << "FAIL at " << __FILE__ << ":" << __LINE__; \ + return kResultFail; \ + } else if (r == kResultSkip) { \ + return kResultSkip; \ + } \ + } while (0) + +// Read RTP packets from file in tcpdump/libpcap format, as documented at: +// http://wiki.wireshark.org/Development/LibpcapFileFormat +class PcapReader : public RtpFileReaderImpl { + public: + PcapReader() + : file_(nullptr), + swap_pcap_byte_order_(false), +#ifdef WEBRTC_ARCH_BIG_ENDIAN + swap_network_byte_order_(false), +#else + swap_network_byte_order_(true), +#endif + read_buffer_(), + packets_by_ssrc_(), + packets_(), + next_packet_it_() { + } + + ~PcapReader() override { + if (file_ != nullptr) { + fclose(file_); + file_ = nullptr; + } + } + + PcapReader(const PcapReader&) = delete; + PcapReader& operator=(const PcapReader&) = delete; + + bool Init(FILE* file, const std::set<uint32_t>& ssrc_filter) override { + return Initialize(file, ssrc_filter) == kResultSuccess; + } + + int Initialize(FILE* file, const std::set<uint32_t>& ssrc_filter) { + file_ = file; + + if (ReadGlobalHeader() < 0) { + return kResultFail; + } + + int total_packet_count = 0; + uint32_t stream_start_ms = 0; + int32_t next_packet_pos = ftell(file_); + for (;;) { + TRY_PCAP(fseek(file_, next_packet_pos, SEEK_SET)); + int result = ReadPacket(&next_packet_pos, stream_start_ms, + ++total_packet_count, ssrc_filter); + if (result == kResultFail) { + break; + } else if (result == kResultSuccess && packets_.size() == 1) { + RTC_DCHECK_EQ(stream_start_ms, 0); + PacketIterator it = packets_.begin(); + stream_start_ms = it->time_offset_ms; + it->time_offset_ms = 0; + } + } + + if (feof(file_) == 0) { + printf("Failed reading file!\n"); + return kResultFail; + } + + printf("Total packets in file: %d\n", total_packet_count); + printf("Total RTP/RTCP packets: %zu\n", packets_.size()); + + for (SsrcMapIterator mit = packets_by_ssrc_.begin(); + mit != packets_by_ssrc_.end(); ++mit) { + uint32_t ssrc = mit->first; + const std::vector<uint32_t>& packet_indices = mit->second; + int pt = packets_[packet_indices[0]].payload_type; + printf("SSRC: %08x, %zu packets, pt=%d\n", ssrc, packet_indices.size(), + pt); + } + + // TODO(solenberg): Better validation of identified SSRC streams. + // + // Since we're dealing with raw network data here, we will wrongly identify + // some packets as RTP. When these packets are consumed by RtpPlayer, they + // are unlikely to cause issues as they will ultimately be filtered out by + // the RtpRtcp module. However, we should really do better filtering here, + // which we can accomplish in a number of ways, e.g.: + // + // - Verify that the time stamps and sequence numbers for RTP packets are + // both increasing/decreasing. If they move in different directions, the + // SSRC is likely bogus and can be dropped. (Normally they should be inc- + // reasing but we must allow packet reordering). + // - If RTP sequence number is not changing, drop the stream. + // - Can also use srcip:port->dstip:port pairs, assuming few SSRC collisions + // for up/down streams. + + next_packet_it_ = packets_.begin(); + return kResultSuccess; + } + + bool NextPacket(RtpPacket* packet) override { + uint32_t length = RtpPacket::kMaxPacketBufferSize; + if (NextPcap(packet->data, &length, &packet->time_ms) != kResultSuccess) + return false; + packet->length = static_cast<size_t>(length); + packet->original_length = packet->length; + return true; + } + + virtual int NextPcap(uint8_t* data, uint32_t* length, uint32_t* time_ms) { + RTC_DCHECK(data); + RTC_DCHECK(length); + RTC_DCHECK(time_ms); + + if (next_packet_it_ == packets_.end()) { + return -1; + } + if (*length < next_packet_it_->payload_length) { + return -1; + } + TRY_PCAP(fseek(file_, next_packet_it_->pos_in_file, SEEK_SET)); + TRY_PCAP(Read(data, next_packet_it_->payload_length)); + *length = next_packet_it_->payload_length; + *time_ms = next_packet_it_->time_offset_ms; + next_packet_it_++; + + return 0; + } + + private: + // A marker of an RTP packet within the file. + struct RtpPacketMarker { + uint32_t packet_number; // One-based index (like in WireShark) + uint32_t time_offset_ms; + uint32_t source_ip; + uint32_t dest_ip; + uint16_t source_port; + uint16_t dest_port; + // Payload type of the RTP packet, + // or RTCP packet type of the first RTCP packet in a compound RTCP packet. + int payload_type; + int32_t pos_in_file; // Byte offset of payload from start of file. + uint32_t payload_length; + }; + + typedef std::vector<RtpPacketMarker>::iterator PacketIterator; + typedef std::map<uint32_t, std::vector<uint32_t> > SsrcMap; + typedef std::map<uint32_t, std::vector<uint32_t> >::iterator SsrcMapIterator; + + int ReadGlobalHeader() { + uint32_t magic; + TRY_PCAP(Read(&magic, false)); + if (magic == kPcapBOMSwapOrder) { + swap_pcap_byte_order_ = true; + } else if (magic == kPcapBOMNoSwapOrder) { + swap_pcap_byte_order_ = false; + } else { + return kResultFail; + } + + uint16_t version_major; + uint16_t version_minor; + TRY_PCAP(Read(&version_major, false)); + TRY_PCAP(Read(&version_minor, false)); + if (version_major != kPcapVersionMajor || + version_minor != kPcapVersionMinor) { + return kResultFail; + } + + int32_t this_zone; // GMT to local correction. + uint32_t sigfigs; // Accuracy of timestamps. + uint32_t snaplen; // Max length of captured packets, in octets. + uint32_t network; // Data link type. + TRY_PCAP(Read(&this_zone, false)); + TRY_PCAP(Read(&sigfigs, false)); + TRY_PCAP(Read(&snaplen, false)); + TRY_PCAP(Read(&network, false)); + + // Accept only LINKTYPE_NULL and LINKTYPE_ETHERNET. + // See: http://www.tcpdump.org/linktypes.html + if (network != kLinktypeNull && network != kLinktypeEthernet) { + return kResultFail; + } + + return kResultSuccess; + } + + int ReadPacket(int32_t* next_packet_pos, + uint32_t stream_start_ms, + uint32_t number, + const std::set<uint32_t>& ssrc_filter) { + RTC_DCHECK(next_packet_pos); + + uint32_t ts_sec; // Timestamp seconds. + uint32_t ts_usec; // Timestamp microseconds. + uint32_t incl_len; // Number of octets of packet saved in file. + uint32_t orig_len; // Actual length of packet. + TRY_PCAP(Read(&ts_sec, false)); + TRY_PCAP(Read(&ts_usec, false)); + TRY_PCAP(Read(&incl_len, false)); + TRY_PCAP(Read(&orig_len, false)); + + *next_packet_pos = ftell(file_) + incl_len; + + RtpPacketMarker marker = {0}; + marker.packet_number = number; + marker.time_offset_ms = CalcTimeDelta(ts_sec, ts_usec, stream_start_ms); + TRY_PCAP(ReadPacketHeader(&marker)); + marker.pos_in_file = ftell(file_); + + if (marker.payload_length > sizeof(read_buffer_)) { + printf("Packet too large!\n"); + return kResultFail; + } + TRY_PCAP(Read(read_buffer_, marker.payload_length)); + + rtc::ArrayView<const uint8_t> packet(read_buffer_, marker.payload_length); + if (IsRtcpPacket(packet)) { + marker.payload_type = packet[1]; + packets_.push_back(marker); + } else if (IsRtpPacket(packet)) { + uint32_t ssrc = ParseRtpSsrc(packet); + marker.payload_type = ParseRtpPayloadType(packet); + if (ssrc_filter.empty() || ssrc_filter.find(ssrc) != ssrc_filter.end()) { + packets_by_ssrc_[ssrc].push_back( + static_cast<uint32_t>(packets_.size())); + packets_.push_back(marker); + } else { + return kResultSkip; + } + } else { + RTC_LOG(LS_INFO) << "Not recognized as RTP/RTCP"; + return kResultSkip; + } + + return kResultSuccess; + } + + int ReadPacketHeader(RtpPacketMarker* marker) { + int32_t file_pos = ftell(file_); + + // Check for BSD null/loopback frame header. The header is just 4 bytes in + // native byte order, so we check for both versions as we don't care about + // the header as such and will likely fail reading the IP header if this is + // something else than null/loopback. + uint32_t protocol; + TRY_PCAP(Read(&protocol, true)); + if (protocol == kBsdNullLoopback1 || protocol == kBsdNullLoopback2) { + int result = ReadXxpIpHeader(marker); + RTC_LOG(LS_INFO) << "Recognized loopback frame"; + if (result != kResultSkip) { + return result; + } + } + + TRY_PCAP(fseek(file_, file_pos, SEEK_SET)); + + // Check for Ethernet II, IP frame header. + uint16_t type; + TRY_PCAP(Skip(kEthernetIIHeaderMacSkip)); // Source+destination MAC. + TRY_PCAP(Read(&type, true)); + if (type == kEthertypeIp) { + int result = ReadXxpIpHeader(marker); + RTC_LOG(LS_INFO) << "Recognized ethernet 2 frame"; + if (result != kResultSkip) { + return result; + } + } + + return kResultSkip; + } + + uint32_t CalcTimeDelta(uint32_t ts_sec, uint32_t ts_usec, uint32_t start_ms) { + // Round to nearest ms. + uint64_t t2_ms = + ((static_cast<uint64_t>(ts_sec) * 1000000) + ts_usec + 500) / 1000; + uint64_t t1_ms = static_cast<uint64_t>(start_ms); + if (t2_ms < t1_ms) { + return 0; + } else { + return t2_ms - t1_ms; + } + } + + int ReadXxpIpHeader(RtpPacketMarker* marker) { + RTC_DCHECK(marker); + + uint16_t version; + uint16_t length; + uint16_t id; + uint16_t fragment; + uint16_t protocol; + uint16_t checksum; + TRY_PCAP(Read(&version, true)); + TRY_PCAP(Read(&length, true)); + TRY_PCAP(Read(&id, true)); + TRY_PCAP(Read(&fragment, true)); + TRY_PCAP(Read(&protocol, true)); + TRY_PCAP(Read(&checksum, true)); + TRY_PCAP(Read(&marker->source_ip, true)); + TRY_PCAP(Read(&marker->dest_ip, true)); + + if (((version >> 12) & 0x000f) != kIpVersion4) { + RTC_LOG(LS_INFO) << "IP header is not IPv4"; + return kResultSkip; + } + + if (fragment != kFragmentOffsetClear && + fragment != kFragmentOffsetDoNotFragment) { + RTC_LOG(LS_INFO) << "IP fragments cannot be handled"; + return kResultSkip; + } + + // Skip remaining fields of IP header. + uint16_t header_length = (version & 0x0f00) >> (8 - 2); + RTC_DCHECK_GE(header_length, kMinIpHeaderLength); + TRY_PCAP(Skip(header_length - kMinIpHeaderLength)); + + protocol = protocol & 0x00ff; + if (protocol == kProtocolTcp) { + RTC_LOG(LS_INFO) << "TCP packets are not handled"; + return kResultSkip; + } else if (protocol == kProtocolUdp) { + uint16_t length; + uint16_t checksum; + TRY_PCAP(Read(&marker->source_port, true)); + TRY_PCAP(Read(&marker->dest_port, true)); + TRY_PCAP(Read(&length, true)); + TRY_PCAP(Read(&checksum, true)); + marker->payload_length = length - kUdpHeaderLength; + } else { + RTC_LOG(LS_INFO) << "Unknown transport (expected UDP or TCP)"; + return kResultSkip; + } + + return kResultSuccess; + } + + int Read(uint32_t* out, bool expect_network_order) { + uint32_t tmp = 0; + if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) { + return kResultFail; + } + if ((!expect_network_order && swap_pcap_byte_order_) || + (expect_network_order && swap_network_byte_order_)) { + tmp = ((tmp >> 24) & 0x000000ff) | (tmp << 24) | + ((tmp >> 8) & 0x0000ff00) | ((tmp << 8) & 0x00ff0000); + } + *out = tmp; + return kResultSuccess; + } + + int Read(uint16_t* out, bool expect_network_order) { + uint16_t tmp = 0; + if (fread(&tmp, 1, sizeof(uint16_t), file_) != sizeof(uint16_t)) { + return kResultFail; + } + if ((!expect_network_order && swap_pcap_byte_order_) || + (expect_network_order && swap_network_byte_order_)) { + tmp = ((tmp >> 8) & 0x00ff) | (tmp << 8); + } + *out = tmp; + return kResultSuccess; + } + + int Read(uint8_t* out, uint32_t count) { + if (fread(out, 1, count, file_) != count) { + return kResultFail; + } + return kResultSuccess; + } + + int Read(int32_t* out, bool expect_network_order) { + int32_t tmp = 0; + if (fread(&tmp, 1, sizeof(uint32_t), file_) != sizeof(uint32_t)) { + return kResultFail; + } + if ((!expect_network_order && swap_pcap_byte_order_) || + (expect_network_order && swap_network_byte_order_)) { + tmp = ((tmp >> 24) & 0x000000ff) | (tmp << 24) | + ((tmp >> 8) & 0x0000ff00) | ((tmp << 8) & 0x00ff0000); + } + *out = tmp; + return kResultSuccess; + } + + int Skip(uint32_t length) { + if (fseek(file_, length, SEEK_CUR) != 0) { + return kResultFail; + } + return kResultSuccess; + } + + FILE* file_; + bool swap_pcap_byte_order_; + const bool swap_network_byte_order_; + uint8_t read_buffer_[kMaxReadBufferSize]; + + SsrcMap packets_by_ssrc_; + std::vector<RtpPacketMarker> packets_; + PacketIterator next_packet_it_; +}; + +RtpFileReaderImpl* CreateReaderForFormat(RtpFileReader::FileFormat format) { + RtpFileReaderImpl* reader = nullptr; + switch (format) { + case RtpFileReader::kPcap: + reader = new PcapReader(); + break; + case RtpFileReader::kRtpDump: + reader = new RtpDumpReader(); + break; + case RtpFileReader::kLengthPacketInterleaved: + reader = new InterleavedRtpFileReader(); + break; + } + return reader; +} + +RtpFileReader* RtpFileReader::Create(FileFormat format, + const uint8_t* data, + size_t size, + const std::set<uint32_t>& ssrc_filter) { + std::unique_ptr<RtpFileReaderImpl> reader(CreateReaderForFormat(format)); + + FILE* file = tmpfile(); + if (file == nullptr) { + printf("ERROR: Can't open file from memory buffer\n"); + return nullptr; + } + + if (fwrite(reinterpret_cast<const void*>(data), sizeof(uint8_t), size, + file) != size) { + return nullptr; + } + rewind(file); + + if (!reader->Init(file, ssrc_filter)) { + return nullptr; + } + return reader.release(); +} + +RtpFileReader* RtpFileReader::Create(FileFormat format, + absl::string_view filename, + const std::set<uint32_t>& ssrc_filter) { + RtpFileReaderImpl* reader = CreateReaderForFormat(format); + std::string filename_str = std::string(filename); + FILE* file = fopen(filename_str.c_str(), "rb"); + if (file == nullptr) { + printf("ERROR: Can't open file: %s\n", filename_str.c_str()); + return nullptr; + } + + if (!reader->Init(file, ssrc_filter)) { + delete reader; + return nullptr; + } + return reader; +} + +RtpFileReader* RtpFileReader::Create(FileFormat format, + absl::string_view filename) { + return RtpFileReader::Create(format, filename, std::set<uint32_t>()); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/rtp_file_reader.h b/third_party/libwebrtc/test/rtp_file_reader.h new file mode 100644 index 0000000000..4e26c71baa --- /dev/null +++ b/third_party/libwebrtc/test/rtp_file_reader.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014 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_RTP_FILE_READER_H_ +#define TEST_RTP_FILE_READER_H_ + +#include <set> +#include <string> + +#include "absl/strings/string_view.h" + +namespace webrtc { +namespace test { + +struct RtpPacket { + // Accommodate for 50 ms packets of 32 kHz PCM16 samples (3200 bytes) plus + // some overhead. + static const size_t kMaxPacketBufferSize = 3500; + uint8_t data[kMaxPacketBufferSize]; + size_t length; + // The length the packet had on wire. Will be different from `length` when + // reading a header-only RTP dump. + size_t original_length; + + uint32_t time_ms; +}; + +class RtpFileReader { + public: + enum FileFormat { kPcap, kRtpDump, kLengthPacketInterleaved }; + + virtual ~RtpFileReader() {} + static RtpFileReader* Create(FileFormat format, + const uint8_t* data, + size_t size, + const std::set<uint32_t>& ssrc_filter); + static RtpFileReader* Create(FileFormat format, absl::string_view filename); + static RtpFileReader* Create(FileFormat format, + absl::string_view filename, + const std::set<uint32_t>& ssrc_filter); + virtual bool NextPacket(RtpPacket* packet) = 0; +}; +} // namespace test +} // namespace webrtc +#endif // TEST_RTP_FILE_READER_H_ diff --git a/third_party/libwebrtc/test/rtp_file_reader_unittest.cc b/third_party/libwebrtc/test/rtp_file_reader_unittest.cc new file mode 100644 index 0000000000..995d9fbc9d --- /dev/null +++ b/third_party/libwebrtc/test/rtp_file_reader_unittest.cc @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013 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/rtp_file_reader.h" + +#include <map> +#include <memory> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { + +class TestRtpFileReader : public ::testing::Test { + public: + void Init(const std::string& filename, bool headers_only_file) { + std::string filepath = + test::ResourcePath("video_coding/" + filename, "rtp"); + rtp_packet_source_.reset( + test::RtpFileReader::Create(test::RtpFileReader::kRtpDump, filepath)); + ASSERT_TRUE(rtp_packet_source_.get() != NULL); + headers_only_file_ = headers_only_file; + } + + int CountRtpPackets() { + test::RtpPacket packet; + int c = 0; + while (rtp_packet_source_->NextPacket(&packet)) { + if (headers_only_file_) + EXPECT_LT(packet.length, packet.original_length); + else + EXPECT_EQ(packet.length, packet.original_length); + c++; + } + return c; + } + + private: + std::unique_ptr<test::RtpFileReader> rtp_packet_source_; + bool headers_only_file_; +}; + +TEST_F(TestRtpFileReader, Test60Packets) { + Init("pltype103", false); + EXPECT_EQ(60, CountRtpPackets()); +} + +TEST_F(TestRtpFileReader, Test60PacketsHeaderOnly) { + Init("pltype103_header_only", true); + EXPECT_EQ(60, CountRtpPackets()); +} + +typedef std::map<uint32_t, int> PacketsPerSsrc; + +class TestPcapFileReader : public ::testing::Test { + public: + void Init(const std::string& filename) { + std::string filepath = + test::ResourcePath("video_coding/" + filename, "pcap"); + rtp_packet_source_.reset( + test::RtpFileReader::Create(test::RtpFileReader::kPcap, filepath)); + ASSERT_TRUE(rtp_packet_source_.get() != NULL); + } + + int CountRtpPackets() { + int c = 0; + test::RtpPacket packet; + while (rtp_packet_source_->NextPacket(&packet)) { + EXPECT_EQ(packet.length, packet.original_length); + c++; + } + return c; + } + + PacketsPerSsrc CountRtpPacketsPerSsrc() { + PacketsPerSsrc pps; + test::RtpPacket packet; + while (rtp_packet_source_->NextPacket(&packet)) { + rtc::ArrayView<const uint8_t> raw(packet.data, packet.length); + if (IsRtpPacket(raw)) { + pps[ParseRtpSsrc(raw)]++; + } + } + return pps; + } + + private: + std::unique_ptr<test::RtpFileReader> rtp_packet_source_; +}; + +TEST_F(TestPcapFileReader, TestEthernetIIFrame) { + Init("frame-ethernet-ii"); + EXPECT_EQ(368, CountRtpPackets()); +} + +TEST_F(TestPcapFileReader, TestLoopbackFrame) { + Init("frame-loopback"); + EXPECT_EQ(491, CountRtpPackets()); +} + +TEST_F(TestPcapFileReader, TestTwoSsrc) { + Init("ssrcs-2"); + PacketsPerSsrc pps = CountRtpPacketsPerSsrc(); + EXPECT_EQ(2UL, pps.size()); + EXPECT_EQ(370, pps[0x78d48f61]); + EXPECT_EQ(60, pps[0xae94130b]); +} + +TEST_F(TestPcapFileReader, TestThreeSsrc) { + Init("ssrcs-3"); + PacketsPerSsrc pps = CountRtpPacketsPerSsrc(); + EXPECT_EQ(3UL, pps.size()); + EXPECT_EQ(162, pps[0x938c5eaa]); + EXPECT_EQ(113, pps[0x59fe6ef0]); + EXPECT_EQ(61, pps[0xed2bd2ac]); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/test/rtp_file_writer.cc b/third_party/libwebrtc/test/rtp_file_writer.cc new file mode 100644 index 0000000000..22f664abc8 --- /dev/null +++ b/third_party/libwebrtc/test/rtp_file_writer.cc @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2014 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/rtp_file_writer.h" + +#include <stdint.h> +#include <stdio.h> + +#include <string> + +#include "absl/types/optional.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +static const uint16_t kPacketHeaderSize = 8; +static const char kFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n"; + +// Write RTP packets to file in rtpdump format, as documented at: +// http://www.cs.columbia.edu/irt/software/rtptools/ +class RtpDumpWriter : public RtpFileWriter { + public: + explicit RtpDumpWriter(FILE* file) : file_(file) { + RTC_CHECK(file_ != NULL); + Init(); + } + ~RtpDumpWriter() override { + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + } + } + + RtpDumpWriter(const RtpDumpWriter&) = delete; + RtpDumpWriter& operator=(const RtpDumpWriter&) = delete; + + bool WritePacket(const RtpPacket* packet) override { + if (!first_packet_time_) { + first_packet_time_ = packet->time_ms; + } + uint16_t len = static_cast<uint16_t>(packet->length + kPacketHeaderSize); + uint16_t plen = static_cast<uint16_t>(packet->original_length); + uint32_t offset = packet->time_ms - *first_packet_time_; + RTC_CHECK(WriteUint16(len)); + RTC_CHECK(WriteUint16(plen)); + RTC_CHECK(WriteUint32(offset)); + return fwrite(packet->data, sizeof(uint8_t), packet->length, file_) == + packet->length; + } + + private: + bool Init() { + fprintf(file_, "%s", kFirstLine); + + RTC_CHECK(WriteUint32(0)); + RTC_CHECK(WriteUint32(0)); + RTC_CHECK(WriteUint32(0)); + RTC_CHECK(WriteUint16(0)); + RTC_CHECK(WriteUint16(0)); + + return true; + } + + bool WriteUint32(uint32_t in) { + // Loop through shifts = {24, 16, 8, 0}. + for (int shifts = 24; shifts >= 0; shifts -= 8) { + uint8_t tmp = static_cast<uint8_t>((in >> shifts) & 0xFF); + if (fwrite(&tmp, sizeof(uint8_t), 1, file_) != 1) + return false; + } + return true; + } + + bool WriteUint16(uint16_t in) { + // Write 8 MSBs. + uint8_t tmp = static_cast<uint8_t>((in >> 8) & 0xFF); + if (fwrite(&tmp, sizeof(uint8_t), 1, file_) != 1) + return false; + // Write 8 LSBs. + tmp = static_cast<uint8_t>(in & 0xFF); + if (fwrite(&tmp, sizeof(uint8_t), 1, file_) != 1) + return false; + return true; + } + + FILE* file_; + absl::optional<uint32_t> first_packet_time_; +}; + +RtpFileWriter* RtpFileWriter::Create(FileFormat format, + const std::string& filename) { + FILE* file = fopen(filename.c_str(), "wb"); + if (file == NULL) { + printf("ERROR: Can't open file: %s\n", filename.c_str()); + return NULL; + } + switch (format) { + case kRtpDump: + return new RtpDumpWriter(file); + } + fclose(file); + return NULL; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/rtp_file_writer.h b/third_party/libwebrtc/test/rtp_file_writer.h new file mode 100644 index 0000000000..5e560d7375 --- /dev/null +++ b/third_party/libwebrtc/test/rtp_file_writer.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014 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_RTP_FILE_WRITER_H_ +#define TEST_RTP_FILE_WRITER_H_ + +#include <string> + +#include "test/rtp_file_reader.h" + +namespace webrtc { +namespace test { +class RtpFileWriter { + public: + enum FileFormat { + kRtpDump, + }; + + virtual ~RtpFileWriter() {} + static RtpFileWriter* Create(FileFormat format, const std::string& filename); + + virtual bool WritePacket(const RtpPacket* packet) = 0; +}; +} // namespace test +} // namespace webrtc +#endif // TEST_RTP_FILE_WRITER_H_ diff --git a/third_party/libwebrtc/test/rtp_file_writer_unittest.cc b/third_party/libwebrtc/test/rtp_file_writer_unittest.cc new file mode 100644 index 0000000000..2396d7c346 --- /dev/null +++ b/third_party/libwebrtc/test/rtp_file_writer_unittest.cc @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014 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/rtp_file_writer.h" + +#include <stdint.h> +#include <string.h> + +#include <memory> + +#include "test/gtest.h" +#include "test/rtp_file_reader.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { + +class RtpFileWriterTest : public ::testing::Test { + public: + void Init(const std::string& filename) { + filename_ = test::OutputPath() + filename; + rtp_writer_.reset( + test::RtpFileWriter::Create(test::RtpFileWriter::kRtpDump, filename_)); + } + + void WriteRtpPackets(int num_packets, int time_ms_offset = 0) { + ASSERT_TRUE(rtp_writer_.get() != NULL); + test::RtpPacket packet; + for (int i = 1; i <= num_packets; ++i) { + packet.length = i; + packet.original_length = i; + packet.time_ms = i + time_ms_offset; + memset(packet.data, i, packet.length); + EXPECT_TRUE(rtp_writer_->WritePacket(&packet)); + } + } + + void CloseOutputFile() { rtp_writer_.reset(); } + + void VerifyFileContents(int expected_packets) { + ASSERT_TRUE(rtp_writer_.get() == NULL) + << "Must call CloseOutputFile before VerifyFileContents"; + std::unique_ptr<test::RtpFileReader> rtp_reader( + test::RtpFileReader::Create(test::RtpFileReader::kRtpDump, filename_)); + ASSERT_TRUE(rtp_reader.get() != NULL); + test::RtpPacket packet; + int i = 0; + while (rtp_reader->NextPacket(&packet)) { + ++i; + EXPECT_EQ(static_cast<size_t>(i), packet.length); + EXPECT_EQ(static_cast<size_t>(i), packet.original_length); + EXPECT_EQ(static_cast<uint32_t>(i - 1), packet.time_ms); + for (int j = 0; j < i; ++j) { + EXPECT_EQ(i, packet.data[j]); + } + } + EXPECT_EQ(expected_packets, i); + } + + private: + std::unique_ptr<test::RtpFileWriter> rtp_writer_; + std::string filename_; +}; + +TEST_F(RtpFileWriterTest, WriteToRtpDump) { + Init("test_rtp_file_writer.rtp"); + WriteRtpPackets(10); + CloseOutputFile(); + VerifyFileContents(10); +} + +TEST_F(RtpFileWriterTest, WriteToRtpDumpWithOffset) { + Init("test_rtp_file_writer.rtp"); + WriteRtpPackets(10, 100); + CloseOutputFile(); + VerifyFileContents(10); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/rtp_rtcp_observer.h b/third_party/libwebrtc/test/rtp_rtcp_observer.h new file mode 100644 index 0000000000..06a438e712 --- /dev/null +++ b/third_party/libwebrtc/test/rtp_rtcp_observer.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2013 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_RTP_RTCP_OBSERVER_H_ +#define TEST_RTP_RTCP_OBSERVER_H_ + +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/test/simulated_network.h" +#include "api/units/time_delta.h" +#include "call/simulated_packet_receiver.h" +#include "call/video_send_stream.h" +#include "modules/rtp_rtcp/source/rtp_util.h" +#include "rtc_base/event.h" +#include "system_wrappers/include/field_trial.h" +#include "test/direct_transport.h" +#include "test/gtest.h" + +namespace { +constexpr webrtc::TimeDelta kShortTimeout = webrtc::TimeDelta::Millis(500); +} + +namespace webrtc { +namespace test { + +class PacketTransport; + +class RtpRtcpObserver { + public: + enum Action { + SEND_PACKET, + DROP_PACKET, + }; + + virtual ~RtpRtcpObserver() {} + + virtual bool Wait() { + if (field_trial::IsEnabled("WebRTC-QuickPerfTest")) { + observation_complete_.Wait(kShortTimeout); + return true; + } + return observation_complete_.Wait(timeout_); + } + + virtual Action OnSendRtp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnSendRtcp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnReceiveRtp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + virtual Action OnReceiveRtcp(const uint8_t* packet, size_t length) { + return SEND_PACKET; + } + + protected: + RtpRtcpObserver() : RtpRtcpObserver(TimeDelta::Zero()) {} + explicit RtpRtcpObserver(TimeDelta event_timeout) : timeout_(event_timeout) {} + + rtc::Event observation_complete_; + + private: + const TimeDelta timeout_; +}; + +class PacketTransport : public test::DirectTransport { + public: + enum TransportType { kReceiver, kSender }; + + PacketTransport(TaskQueueBase* task_queue, + Call* send_call, + RtpRtcpObserver* observer, + TransportType transport_type, + const std::map<uint8_t, MediaType>& payload_type_map, + std::unique_ptr<SimulatedPacketReceiverInterface> nw_pipe, + rtc::ArrayView<const RtpExtension> audio_extensions, + rtc::ArrayView<const RtpExtension> video_extensions) + : test::DirectTransport(task_queue, + std::move(nw_pipe), + send_call, + payload_type_map, + audio_extensions, + video_extensions), + observer_(observer), + transport_type_(transport_type) {} + + private: + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override { + EXPECT_TRUE(IsRtpPacket(rtc::MakeArrayView(packet, length))); + RtpRtcpObserver::Action action = RtpRtcpObserver::SEND_PACKET; + if (observer_) { + if (transport_type_ == kSender) { + action = observer_->OnSendRtp(packet, length); + } else { + action = observer_->OnReceiveRtp(packet, length); + } + } + switch (action) { + case RtpRtcpObserver::DROP_PACKET: + // Drop packet silently. + return true; + case RtpRtcpObserver::SEND_PACKET: + return test::DirectTransport::SendRtp(packet, length, options); + } + return true; // Will never happen, makes compiler happy. + } + + bool SendRtcp(const uint8_t* packet, size_t length) override { + EXPECT_TRUE(IsRtcpPacket(rtc::MakeArrayView(packet, length))); + RtpRtcpObserver::Action action = RtpRtcpObserver::SEND_PACKET; + if (observer_) { + if (transport_type_ == kSender) { + action = observer_->OnSendRtcp(packet, length); + } else { + action = observer_->OnReceiveRtcp(packet, length); + } + } + switch (action) { + case RtpRtcpObserver::DROP_PACKET: + // Drop packet silently. + return true; + case RtpRtcpObserver::SEND_PACKET: + return test::DirectTransport::SendRtcp(packet, length); + } + return true; // Will never happen, makes compiler happy. + } + + RtpRtcpObserver* const observer_; + TransportType transport_type_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_RTP_RTCP_OBSERVER_H_ diff --git a/third_party/libwebrtc/test/rtp_test_utils_gn/moz.build b/third_party/libwebrtc/test/rtp_test_utils_gn/moz.build new file mode 100644 index 0000000000..25bb2ee0a1 --- /dev/null +++ b/third_party/libwebrtc/test/rtp_test_utils_gn/moz.build @@ -0,0 +1,216 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + + ### This moz.build was AUTOMATICALLY GENERATED from a GN config, ### + ### DO NOT edit it by hand. ### + +COMPILE_FLAGS["OS_INCLUDES"] = [] +AllowCompilerWarnings() + +DEFINES["ABSL_ALLOCATOR_NOTHROW"] = "1" +DEFINES["RTC_DAV1D_IN_INTERNAL_DECODER_FACTORY"] = True +DEFINES["RTC_ENABLE_VP9"] = True +DEFINES["WEBRTC_ENABLE_PROTOBUF"] = "0" +DEFINES["WEBRTC_LIBRARY_IMPL"] = True +DEFINES["WEBRTC_MOZILLA_BUILD"] = True +DEFINES["WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS"] = "0" +DEFINES["WEBRTC_STRICT_FIELD_TRIALS"] = "0" + +FINAL_LIBRARY = "webrtc" + + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "!/third_party/libwebrtc/gen", + "/ipc/chromium/src", + "/third_party/libwebrtc/", + "/third_party/libwebrtc/third_party/abseil-cpp/", + "/tools/profiler/public" +] + +if not CONFIG["MOZ_DEBUG"]: + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "0" + DEFINES["NDEBUG"] = True + DEFINES["NVALGRIND"] = True + +if CONFIG["MOZ_DEBUG"] == "1": + + DEFINES["DYNAMIC_ANNOTATIONS_ENABLED"] = "1" + +if CONFIG["OS_TARGET"] == "Android": + + DEFINES["ANDROID"] = True + DEFINES["ANDROID_NDK_VERSION_ROLL"] = "r22_1" + DEFINES["HAVE_SYS_UIO_H"] = True + DEFINES["WEBRTC_ANDROID"] = True + DEFINES["WEBRTC_ANDROID_OPENSLES"] = True + DEFINES["WEBRTC_LINUX"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_GNU_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + + OS_LIBS += [ + "log" + ] + +if CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["WEBRTC_MAC"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_LIBCPP_HAS_NO_ALIGNED_ALLOCATION"] = True + DEFINES["__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES"] = "0" + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_AURA"] = "1" + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_NSS_CERTS"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_UDEV"] = True + DEFINES["WEBRTC_LINUX"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + + OS_LIBS += [ + "rt" + ] + +if CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["USE_GLIB"] = "1" + DEFINES["USE_OZONE"] = "1" + DEFINES["USE_X11"] = "1" + DEFINES["WEBRTC_BSD"] = True + DEFINES["WEBRTC_POSIX"] = True + DEFINES["_FILE_OFFSET_BITS"] = "64" + DEFINES["_LARGEFILE64_SOURCE"] = True + DEFINES["_LARGEFILE_SOURCE"] = True + DEFINES["__STDC_CONSTANT_MACROS"] = True + DEFINES["__STDC_FORMAT_MACROS"] = True + +if CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["CERT_CHAIN_PARA_HAS_EXTRA_FIELDS"] = True + DEFINES["NOMINMAX"] = True + DEFINES["NTDDI_VERSION"] = "0x0A000000" + DEFINES["PSAPI_VERSION"] = "2" + DEFINES["UNICODE"] = True + DEFINES["USE_AURA"] = "1" + DEFINES["WEBRTC_WIN"] = True + DEFINES["WIN32"] = True + DEFINES["WIN32_LEAN_AND_MEAN"] = True + DEFINES["WINAPI_FAMILY"] = "WINAPI_FAMILY_DESKTOP_APP" + DEFINES["WINVER"] = "0x0A00" + DEFINES["_ATL_NO_OPENGL"] = True + DEFINES["_CRT_RAND_S"] = True + DEFINES["_CRT_SECURE_NO_DEPRECATE"] = True + DEFINES["_ENABLE_EXTENDED_ALIGNED_STORAGE"] = True + DEFINES["_HAS_EXCEPTIONS"] = "0" + DEFINES["_HAS_NODISCARD"] = True + DEFINES["_SCL_SECURE_NO_DEPRECATE"] = True + DEFINES["_SECURE_ATL"] = True + DEFINES["_UNICODE"] = True + DEFINES["_WIN32_WINNT"] = "0x0A00" + DEFINES["_WINDOWS"] = True + DEFINES["__STD_C"] = True + + OS_LIBS += [ + "crypt32", + "iphlpapi", + "secur32", + "winmm" + ] + +if CONFIG["CPU_ARCH"] == "aarch64": + + DEFINES["WEBRTC_ARCH_ARM64"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +if CONFIG["CPU_ARCH"] == "arm": + + DEFINES["WEBRTC_ARCH_ARM"] = True + DEFINES["WEBRTC_ARCH_ARM_V7"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +if CONFIG["CPU_ARCH"] == "mips32": + + DEFINES["MIPS32_LE"] = True + DEFINES["MIPS_FPU_LE"] = True + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "mips64": + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + +if CONFIG["CPU_ARCH"] == "x86_64": + + DEFINES["WEBRTC_ENABLE_AVX2"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Android": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Darwin": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "OpenBSD": + + DEFINES["_DEBUG"] = True + +if CONFIG["MOZ_DEBUG"] == "1" and CONFIG["OS_TARGET"] == "WINNT": + + DEFINES["_HAS_ITERATOR_DEBUGGING"] = "0" + +if CONFIG["MOZ_X11"] == "1" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["USE_X11"] = "1" + +if CONFIG["CPU_ARCH"] == "arm" and CONFIG["OS_TARGET"] == "Android": + + OS_LIBS += [ + "android_support", + "unwind" + ] + +if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Android": + + OS_LIBS += [ + "android_support" + ] + +if CONFIG["CPU_ARCH"] == "aarch64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "arm" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +Library("rtp_test_utils_gn") diff --git a/third_party/libwebrtc/test/run_loop.cc b/third_party/libwebrtc/test/run_loop.cc new file mode 100644 index 0000000000..7cc80ab481 --- /dev/null +++ b/third_party/libwebrtc/test/run_loop.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2013 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/run_loop.h" + +#include "rtc_base/time_utils.h" + +namespace webrtc { +namespace test { + +RunLoop::RunLoop() { + worker_thread_.WrapCurrent(); +} + +RunLoop::~RunLoop() { + worker_thread_.UnwrapCurrent(); +} + +TaskQueueBase* RunLoop::task_queue() { + return &worker_thread_; +} + +void RunLoop::Run() { + worker_thread_.ProcessMessages(WorkerThread::kForever); +} + +void RunLoop::Quit() { + socket_server_.FailNextWait(); +} + +void RunLoop::Flush() { + worker_thread_.PostTask([this]() { socket_server_.FailNextWait(); }); + // If a test clock is used, like with GlobalSimulatedTimeController then the + // thread will loop forever since time never increases. Since the clock is + // simulated, 0ms can be used as the loop delay, which will process all + // messages ready for execution. + int cms = rtc::GetClockForTesting() ? 0 : 1000; + worker_thread_.ProcessMessages(cms); +} + +RunLoop::FakeSocketServer::FakeSocketServer() = default; +RunLoop::FakeSocketServer::~FakeSocketServer() = default; + +void RunLoop::FakeSocketServer::FailNextWait() { + fail_next_wait_ = true; +} + +bool RunLoop::FakeSocketServer::Wait(webrtc::TimeDelta max_wait_duration, + bool process_io) { + if (fail_next_wait_) { + fail_next_wait_ = false; + return false; + } + return true; +} + +void RunLoop::FakeSocketServer::WakeUp() {} + +rtc::Socket* RunLoop::FakeSocketServer::CreateSocket(int family, int type) { + return nullptr; +} + +RunLoop::WorkerThread::WorkerThread(rtc::SocketServer* ss) + : rtc::Thread(ss), tq_setter_(this) {} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/run_loop.h b/third_party/libwebrtc/test/run_loop.h new file mode 100644 index 0000000000..8a2bf54402 --- /dev/null +++ b/third_party/libwebrtc/test/run_loop.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 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_RUN_LOOP_H_ +#define TEST_RUN_LOOP_H_ + +#include <utility> + +#include "absl/functional/any_invocable.h" +#include "api/task_queue/task_queue_base.h" +#include "rtc_base/thread.h" + +namespace webrtc { +namespace test { + +// This utility class allows you to run a TaskQueue supported interface on the +// main test thread, call Run() while doing things asynchonously and break +// the loop (from the same thread) from a callback by calling Quit(). +class RunLoop { + public: + RunLoop(); + ~RunLoop(); + + TaskQueueBase* task_queue(); + + void Run(); + void Quit(); + + void Flush(); + + void PostTask(absl::AnyInvocable<void() &&> task) { + task_queue()->PostTask(std::move(task)); + } + + private: + class FakeSocketServer : public rtc::SocketServer { + public: + FakeSocketServer(); + ~FakeSocketServer(); + + void FailNextWait(); + + private: + bool Wait(webrtc::TimeDelta max_wait_duration, bool process_io) override; + void WakeUp() override; + + rtc::Socket* CreateSocket(int family, int type) override; + + private: + bool fail_next_wait_ = false; + }; + + class WorkerThread : public rtc::Thread { + public: + explicit WorkerThread(rtc::SocketServer* ss); + + private: + CurrentTaskQueueSetter tq_setter_; + }; + + FakeSocketServer socket_server_; + WorkerThread worker_thread_{&socket_server_}; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_RUN_LOOP_H_ diff --git a/third_party/libwebrtc/test/run_loop_unittest.cc b/third_party/libwebrtc/test/run_loop_unittest.cc new file mode 100644 index 0000000000..80f0bcbdcc --- /dev/null +++ b/third_party/libwebrtc/test/run_loop_unittest.cc @@ -0,0 +1,60 @@ +/* + * 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/run_loop.h" + +#include "api/units/time_delta.h" +#include "rtc_base/task_queue.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(RunLoopTest, TaskQueueOnThread) { + test::RunLoop loop; + EXPECT_EQ(TaskQueueBase::Current(), loop.task_queue()); + EXPECT_TRUE(loop.task_queue()->IsCurrent()); +} + +TEST(RunLoopTest, Flush) { + test::RunLoop loop; + int counter = 0; + loop.PostTask([&counter]() { ++counter; }); + EXPECT_EQ(counter, 0); + loop.Flush(); + EXPECT_EQ(counter, 1); +} + +TEST(RunLoopTest, Delayed) { + test::RunLoop loop; + bool ran = false; + loop.task_queue()->PostDelayedTask( + [&ran, &loop]() { + ran = true; + loop.Quit(); + }, + TimeDelta::Millis(100)); + loop.Flush(); + EXPECT_FALSE(ran); + loop.Run(); + EXPECT_TRUE(ran); +} + +TEST(RunLoopTest, PostAndQuit) { + test::RunLoop loop; + bool ran = false; + loop.PostTask([&ran, &loop]() { + ran = true; + loop.Quit(); + }); + loop.Run(); + EXPECT_TRUE(ran); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/run_test.cc b/third_party/libwebrtc/test/run_test.cc new file mode 100644 index 0000000000..7af47b9faf --- /dev/null +++ b/third_party/libwebrtc/test/run_test.cc @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013 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/run_test.h" + +namespace webrtc { +namespace test { + +void RunTest(void (*test)()) { + (*test)(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/run_test.h b/third_party/libwebrtc/test/run_test.h new file mode 100644 index 0000000000..bd05584365 --- /dev/null +++ b/third_party/libwebrtc/test/run_test.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 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_RUN_TEST_H_ +#define TEST_RUN_TEST_H_ + +namespace webrtc { +namespace test { + +// Running a test function on a separate thread, if required by the OS. +void RunTest(void (*test)()); + +} // namespace test +} // namespace webrtc + +#endif // TEST_RUN_TEST_H_ diff --git a/third_party/libwebrtc/test/scenario/BUILD.gn b/third_party/libwebrtc/test/scenario/BUILD.gn new file mode 100644 index 0000000000..5da6dce87d --- /dev/null +++ b/third_party/libwebrtc/test/scenario/BUILD.gn @@ -0,0 +1,200 @@ +# 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("../../webrtc.gni") + +rtc_library("column_printer") { + testonly = true + sources = [ + "column_printer.cc", + "column_printer.h", + ] + deps = [ + "../../rtc_base:macromagic", + "../../rtc_base:stringutils", + "../logging:log_writer", + ] +} + +if (rtc_include_tests && !build_with_chromium) { + scenario_resources = [ + "../../resources/difficult_photo_1850_1110.yuv", + "../../resources/photo_1850_1110.yuv", + "../../resources/presentation_1850_1110.yuv", + "../../resources/web_screenshot_1850_1110.yuv", + ] + scenario_unittest_resources = [ "../../resources/foreman_cif.yuv" ] + + if (is_ios) { + bundle_data("scenario_resources_bundle_data") { + testonly = true + sources = scenario_resources + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + } + bundle_data("scenario_unittest_resources_bundle_data") { + testonly = true + sources = scenario_unittest_resources + outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ] + } + } + + rtc_library("scenario") { + testonly = true + sources = [ + "audio_stream.cc", + "audio_stream.h", + "call_client.cc", + "call_client.h", + "hardware_codecs.cc", + "hardware_codecs.h", + "network_node.cc", + "network_node.h", + "performance_stats.cc", + "performance_stats.h", + "scenario.cc", + "scenario.h", + "scenario_config.cc", + "scenario_config.h", + "stats_collection.cc", + "stats_collection.h", + "video_frame_matcher.cc", + "video_frame_matcher.h", + "video_stream.cc", + "video_stream.h", + ] + deps = [ + ":column_printer", + "../:fake_video_codecs", + "../:fileutils", + "../:test_common", + "../:test_support", + "../:video_test_common", + "../../api:array_view", + "../../api:create_frame_generator", + "../../api:fec_controller_api", + "../../api:frame_generator_api", + "../../api:libjingle_peerconnection_api", + "../../api:rtc_event_log_output_file", + "../../api:rtp_parameters", + "../../api:sequence_checker", + "../../api:time_controller", + "../../api:time_controller", + "../../api:transport_api", + "../../api/audio_codecs:builtin_audio_decoder_factory", + "../../api/audio_codecs:builtin_audio_encoder_factory", + "../../api/rtc_event_log", + "../../api/rtc_event_log:rtc_event_log_factory", + "../../api/task_queue", + "../../api/test/video:function_video_factory", + "../../api/transport:network_control", + "../../api/units:data_rate", + "../../api/units:data_size", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../api/video:builtin_video_bitrate_allocator_factory", + "../../api/video:video_frame", + "../../api/video:video_rtp_headers", + "../../api/video_codecs:scalability_mode", + "../../api/video_codecs:video_codecs_api", + "../../audio", + "../../call", + "../../call:call_interfaces", + "../../call:rtp_sender", + "../../call:simulated_network", + "../../call:video_stream_api", + "../../common_video", + "../../media:media_constants", + "../../media:rtc_audio_video", + "../../media:rtc_internal_video_codecs", + "../../media:rtc_media_base", + "../../modules/audio_device", + "../../modules/audio_device:audio_device_impl", + "../../modules/audio_device:mock_audio_device", + "../../modules/audio_mixer:audio_mixer_impl", + "../../modules/audio_processing", + "../../modules/congestion_controller/goog_cc:test_goog_cc_printer", + "../../modules/rtp_rtcp", + "../../modules/rtp_rtcp:mock_rtp_rtcp", + "../../modules/rtp_rtcp:rtp_rtcp_format", + "../../modules/video_coding:video_codec_interface", + "../../modules/video_coding:video_coding_utility", + "../../modules/video_coding:webrtc_h264", + "../../modules/video_coding:webrtc_multiplex", + "../../modules/video_coding:webrtc_vp8", + "../../modules/video_coding:webrtc_vp9", + "../../modules/video_coding/svc:scalability_mode_util", + "../../rtc_base:checks", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:net_helper", + "../../rtc_base:refcount", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_numerics", + "../../rtc_base:rtc_stats_counters", + "../../rtc_base:safe_minmax", + "../../rtc_base:socket_address", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/task_utils:repeating_task", + "../../system_wrappers", + "../../system_wrappers:field_trial", + "../../video/config:streams_config", + "../logging:log_writer", + "../network:emulated_network", + "../time_controller", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/flags:flag", + "//third_party/abseil-cpp/absl/flags:parse", + "//third_party/abseil-cpp/absl/functional:any_invocable", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + if (is_android) { + deps += [ "../../modules/video_coding:android_codec_factory_helper" ] + } else if (is_ios || is_mac) { + deps += [ "../../modules/video_coding:objc_codec_factory_helper" ] + } + if (rtc_enable_protobuf) { + deps += [ "../../modules/audio_coding:ana_config_proto" ] + } + data = scenario_resources + if (is_ios) { + deps += [ ":scenario_resources_bundle_data" ] + } + } + rtc_library("scenario_unittests") { + testonly = true + sources = [ + "performance_stats_unittest.cc", + "probing_test.cc", + "scenario_unittest.cc", + "stats_collection_unittest.cc", + "video_stream_unittest.cc", + ] + deps = [ + ":scenario", + "../../api/test/network_emulation", + "../../api/test/network_emulation:create_cross_traffic", + "../../logging:mocks", + "../../rtc_base:checks", + "../../system_wrappers", + "../../system_wrappers:field_trial", + "../../test:field_trial", + "../../test:test_support", + "../logging:log_writer", + "//testing/gmock", + ] + data = scenario_unittest_resources + if (is_ios) { + deps += [ ":scenario_unittest_resources_bundle_data" ] + } + } +} diff --git a/third_party/libwebrtc/test/scenario/OWNERS b/third_party/libwebrtc/test/scenario/OWNERS new file mode 100644 index 0000000000..6698afbf02 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/OWNERS @@ -0,0 +1,2 @@ +srte@webrtc.org +perkj@webrtc.org diff --git a/third_party/libwebrtc/test/scenario/audio_stream.cc b/third_party/libwebrtc/test/scenario/audio_stream.cc new file mode 100644 index 0000000000..5f2eff12ff --- /dev/null +++ b/third_party/libwebrtc/test/scenario/audio_stream.cc @@ -0,0 +1,241 @@ +/* + * Copyright 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/scenario/audio_stream.h" + +#include "absl/memory/memory.h" +#include "test/call_test.h" + +#if WEBRTC_ENABLE_PROTOBUF +RTC_PUSH_IGNORING_WUNDEF() +#ifdef WEBRTC_ANDROID_PLATFORM_BUILD +#include "external/webrtc/webrtc/modules/audio_coding/audio_network_adaptor/config.pb.h" +#else +#include "modules/audio_coding/audio_network_adaptor/config.pb.h" +#endif +RTC_POP_IGNORING_WUNDEF() +#endif + +namespace webrtc { +namespace test { +namespace { +enum : int { // The first valid value is 1. + kTransportSequenceNumberExtensionId = 1, + kAbsSendTimeExtensionId +}; + +absl::optional<std::string> CreateAdaptationString( + AudioStreamConfig::NetworkAdaptation config) { +#if WEBRTC_ENABLE_PROTOBUF + + audio_network_adaptor::config::ControllerManager cont_conf; + if (config.frame.max_rate_for_60_ms.IsFinite()) { + auto controller = + cont_conf.add_controllers()->mutable_frame_length_controller(); + controller->set_fl_decreasing_packet_loss_fraction( + config.frame.min_packet_loss_for_decrease); + controller->set_fl_increasing_packet_loss_fraction( + config.frame.max_packet_loss_for_increase); + + controller->set_fl_20ms_to_60ms_bandwidth_bps( + config.frame.min_rate_for_20_ms.bps<int32_t>()); + controller->set_fl_60ms_to_20ms_bandwidth_bps( + config.frame.max_rate_for_60_ms.bps<int32_t>()); + + if (config.frame.max_rate_for_120_ms.IsFinite()) { + controller->set_fl_60ms_to_120ms_bandwidth_bps( + config.frame.min_rate_for_60_ms.bps<int32_t>()); + controller->set_fl_120ms_to_60ms_bandwidth_bps( + config.frame.max_rate_for_120_ms.bps<int32_t>()); + } + } + cont_conf.add_controllers()->mutable_bitrate_controller(); + std::string config_string = cont_conf.SerializeAsString(); + return config_string; +#else + RTC_LOG(LS_ERROR) << "audio_network_adaptation is enabled" + " but WEBRTC_ENABLE_PROTOBUF is false.\n" + "Ignoring settings."; + return absl::nullopt; +#endif // WEBRTC_ENABLE_PROTOBUF +} +} // namespace + +std::vector<RtpExtension> GetAudioRtpExtensions( + const AudioStreamConfig& config) { + std::vector<RtpExtension> extensions; + if (config.stream.in_bandwidth_estimation) { + extensions.push_back({RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId}); + } + if (config.stream.abs_send_time) { + extensions.push_back( + {RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId}); + } + return extensions; +} + +SendAudioStream::SendAudioStream( + CallClient* sender, + AudioStreamConfig config, + rtc::scoped_refptr<AudioEncoderFactory> encoder_factory, + Transport* send_transport) + : sender_(sender), config_(config) { + AudioSendStream::Config send_config(send_transport); + ssrc_ = sender->GetNextAudioSsrc(); + send_config.rtp.ssrc = ssrc_; + SdpAudioFormat::Parameters sdp_params; + if (config.source.channels == 2) + sdp_params["stereo"] = "1"; + if (config.encoder.initial_frame_length != TimeDelta::Millis(20)) + sdp_params["ptime"] = + std::to_string(config.encoder.initial_frame_length.ms()); + if (config.encoder.enable_dtx) + sdp_params["usedtx"] = "1"; + + // SdpAudioFormat::num_channels indicates that the encoder is capable of + // stereo, but the actual channel count used is based on the "stereo" + // parameter. + send_config.send_codec_spec = AudioSendStream::Config::SendCodecSpec( + CallTest::kAudioSendPayloadType, {"opus", 48000, 2, sdp_params}); + RTC_DCHECK_LE(config.source.channels, 2); + send_config.encoder_factory = encoder_factory; + + bool use_fixed_rate = !config.encoder.min_rate && !config.encoder.max_rate; + if (use_fixed_rate) + send_config.send_codec_spec->target_bitrate_bps = + config.encoder.fixed_rate.bps(); + if (!config.adapt.binary_proto.empty()) { + send_config.audio_network_adaptor_config = config.adapt.binary_proto; + } else if (config.network_adaptation) { + send_config.audio_network_adaptor_config = + CreateAdaptationString(config.adapt); + } + if (config.encoder.allocate_bitrate || + config.stream.in_bandwidth_estimation) { + DataRate min_rate = DataRate::Infinity(); + DataRate max_rate = DataRate::Infinity(); + if (use_fixed_rate) { + min_rate = config.encoder.fixed_rate; + max_rate = config.encoder.fixed_rate; + } else { + min_rate = *config.encoder.min_rate; + max_rate = *config.encoder.max_rate; + } + send_config.min_bitrate_bps = min_rate.bps(); + send_config.max_bitrate_bps = max_rate.bps(); + } + + if (config.stream.in_bandwidth_estimation) { + send_config.send_codec_spec->transport_cc_enabled = true; + } + send_config.rtp.extensions = GetAudioRtpExtensions(config); + + sender_->SendTask([&] { + send_stream_ = sender_->call_->CreateAudioSendStream(send_config); + sender->call_->OnAudioTransportOverheadChanged( + sender_->transport_->packet_overhead().bytes()); + }); +} + +SendAudioStream::~SendAudioStream() { + sender_->SendTask( + [this] { sender_->call_->DestroyAudioSendStream(send_stream_); }); +} + +void SendAudioStream::Start() { + sender_->SendTask([this] { + send_stream_->Start(); + sender_->call_->SignalChannelNetworkState(MediaType::AUDIO, kNetworkUp); + }); +} + +void SendAudioStream::Stop() { + sender_->SendTask([this] { send_stream_->Stop(); }); +} + +void SendAudioStream::SetMuted(bool mute) { + sender_->SendTask([this, mute] { send_stream_->SetMuted(mute); }); +} + +ColumnPrinter SendAudioStream::StatsPrinter() { + return ColumnPrinter::Lambda( + "audio_target_rate", + [this](rtc::SimpleStringBuilder& sb) { + sender_->SendTask([this, &sb] { + AudioSendStream::Stats stats = send_stream_->GetStats(); + sb.AppendFormat("%.0lf", stats.target_bitrate_bps / 8.0); + }); + }, + 64); +} + +ReceiveAudioStream::ReceiveAudioStream( + CallClient* receiver, + AudioStreamConfig config, + SendAudioStream* send_stream, + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, + Transport* feedback_transport) + : receiver_(receiver), config_(config) { + AudioReceiveStreamInterface::Config recv_config; + recv_config.rtp.local_ssrc = receiver_->GetNextAudioLocalSsrc(); + recv_config.rtcp_send_transport = feedback_transport; + recv_config.rtp.remote_ssrc = send_stream->ssrc_; + receiver->ssrc_media_types_[recv_config.rtp.remote_ssrc] = MediaType::AUDIO; + recv_config.rtp.extensions = GetAudioRtpExtensions(config); + recv_config.decoder_factory = decoder_factory; + recv_config.decoder_map = { + {CallTest::kAudioSendPayloadType, {"opus", 48000, 2}}}; + recv_config.sync_group = config.render.sync_group; + receiver_->SendTask([&] { + receive_stream_ = receiver_->call_->CreateAudioReceiveStream(recv_config); + }); +} +ReceiveAudioStream::~ReceiveAudioStream() { + receiver_->SendTask( + [&] { receiver_->call_->DestroyAudioReceiveStream(receive_stream_); }); +} + +void ReceiveAudioStream::Start() { + receiver_->SendTask([&] { + receive_stream_->Start(); + receiver_->call_->SignalChannelNetworkState(MediaType::AUDIO, kNetworkUp); + }); +} + +void ReceiveAudioStream::Stop() { + receiver_->SendTask([&] { receive_stream_->Stop(); }); +} + +AudioReceiveStreamInterface::Stats ReceiveAudioStream::GetStats() const { + AudioReceiveStreamInterface::Stats result; + receiver_->SendTask([&] { + result = receive_stream_->GetStats(/*get_and_clear_legacy_stats=*/true); + }); + return result; +} + +AudioStreamPair::~AudioStreamPair() = default; + +AudioStreamPair::AudioStreamPair( + CallClient* sender, + rtc::scoped_refptr<AudioEncoderFactory> encoder_factory, + CallClient* receiver, + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, + AudioStreamConfig config) + : config_(config), + send_stream_(sender, config, encoder_factory, sender->transport_.get()), + receive_stream_(receiver, + config, + &send_stream_, + decoder_factory, + receiver->transport_.get()) {} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/audio_stream.h b/third_party/libwebrtc/test/scenario/audio_stream.h new file mode 100644 index 0000000000..cbaf9d29eb --- /dev/null +++ b/third_party/libwebrtc/test/scenario/audio_stream.h @@ -0,0 +1,110 @@ +/* + * Copyright 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_SCENARIO_AUDIO_STREAM_H_ +#define TEST_SCENARIO_AUDIO_STREAM_H_ +#include <memory> +#include <string> +#include <vector> + +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" + +namespace webrtc { +namespace test { + +// SendAudioStream represents sending of audio. It can be used for starting the +// stream if neccessary. +class SendAudioStream { + public: + ~SendAudioStream(); + + SendAudioStream(const SendAudioStream&) = delete; + SendAudioStream& operator=(const SendAudioStream&) = delete; + + void Start(); + void Stop(); + void SetMuted(bool mute); + ColumnPrinter StatsPrinter(); + + private: + friend class Scenario; + friend class AudioStreamPair; + friend class ReceiveAudioStream; + SendAudioStream(CallClient* sender, + AudioStreamConfig config, + rtc::scoped_refptr<AudioEncoderFactory> encoder_factory, + Transport* send_transport); + AudioSendStream* send_stream_ = nullptr; + CallClient* const sender_; + const AudioStreamConfig config_; + uint32_t ssrc_; +}; + +// ReceiveAudioStream represents an audio receiver. It can't be used directly. +class ReceiveAudioStream { + public: + ~ReceiveAudioStream(); + + ReceiveAudioStream(const ReceiveAudioStream&) = delete; + ReceiveAudioStream& operator=(const ReceiveAudioStream&) = delete; + + void Start(); + void Stop(); + AudioReceiveStreamInterface::Stats GetStats() const; + + private: + friend class Scenario; + friend class AudioStreamPair; + ReceiveAudioStream(CallClient* receiver, + AudioStreamConfig config, + SendAudioStream* send_stream, + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, + Transport* feedback_transport); + AudioReceiveStreamInterface* receive_stream_ = nullptr; + CallClient* const receiver_; + const AudioStreamConfig config_; +}; + +// AudioStreamPair represents an audio streaming session. It can be used to +// access underlying send and receive classes. It can also be used in calls to +// the Scenario class. +class AudioStreamPair { + public: + ~AudioStreamPair(); + + AudioStreamPair(const AudioStreamPair&) = delete; + AudioStreamPair& operator=(const AudioStreamPair&) = delete; + + SendAudioStream* send() { return &send_stream_; } + ReceiveAudioStream* receive() { return &receive_stream_; } + + private: + friend class Scenario; + AudioStreamPair(CallClient* sender, + rtc::scoped_refptr<AudioEncoderFactory> encoder_factory, + CallClient* receiver, + rtc::scoped_refptr<AudioDecoderFactory> decoder_factory, + AudioStreamConfig config); + + private: + const AudioStreamConfig config_; + SendAudioStream send_stream_; + ReceiveAudioStream receive_stream_; +}; + +std::vector<RtpExtension> GetAudioRtpExtensions( + const AudioStreamConfig& config); + +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_AUDIO_STREAM_H_ diff --git a/third_party/libwebrtc/test/scenario/call_client.cc b/third_party/libwebrtc/test/scenario/call_client.cc new file mode 100644 index 0000000000..c80f58eeeb --- /dev/null +++ b/third_party/libwebrtc/test/scenario/call_client.cc @@ -0,0 +1,386 @@ +/* + * Copyright 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/scenario/call_client.h" + +#include <iostream> +#include <memory> +#include <utility> + +#include "api/media_types.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/rtc_event_log/rtc_event_log_factory.h" +#include "api/transport/network_types.h" +#include "call/call.h" +#include "call/rtp_transport_controller_send_factory.h" +#include "modules/audio_mixer/audio_mixer_impl.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_util.h" + +namespace webrtc { +namespace test { +namespace { +static constexpr size_t kNumSsrcs = 6; +const uint32_t kSendRtxSsrcs[kNumSsrcs] = {0xBADCAFD, 0xBADCAFE, 0xBADCAFF, + 0xBADCB00, 0xBADCB01, 0xBADCB02}; +const uint32_t kVideoSendSsrcs[kNumSsrcs] = {0xC0FFED, 0xC0FFEE, 0xC0FFEF, + 0xC0FFF0, 0xC0FFF1, 0xC0FFF2}; +const uint32_t kVideoRecvLocalSsrcs[kNumSsrcs] = {0xDAB001, 0xDAB002, 0xDAB003, + 0xDAB004, 0xDAB005, 0xDAB006}; +const uint32_t kAudioSendSsrc = 0xDEADBEEF; +const uint32_t kReceiverLocalAudioSsrc = 0x1234567; + +constexpr int kEventLogOutputIntervalMs = 5000; + +CallClientFakeAudio InitAudio(TimeController* time_controller) { + CallClientFakeAudio setup; + auto capturer = TestAudioDeviceModule::CreatePulsedNoiseCapturer(256, 48000); + auto renderer = TestAudioDeviceModule::CreateDiscardRenderer(48000); + setup.fake_audio_device = TestAudioDeviceModule::Create( + time_controller->GetTaskQueueFactory(), std::move(capturer), + std::move(renderer), 1.f); + setup.apm = AudioProcessingBuilder().Create(); + setup.fake_audio_device->Init(); + AudioState::Config audio_state_config; + audio_state_config.audio_mixer = AudioMixerImpl::Create(); + audio_state_config.audio_processing = setup.apm; + audio_state_config.audio_device_module = setup.fake_audio_device; + setup.audio_state = AudioState::Create(audio_state_config); + setup.fake_audio_device->RegisterAudioCallback( + setup.audio_state->audio_transport()); + return setup; +} + +Call* CreateCall(TimeController* time_controller, + RtcEventLog* event_log, + CallClientConfig config, + LoggingNetworkControllerFactory* network_controller_factory, + rtc::scoped_refptr<AudioState> audio_state) { + CallConfig call_config(event_log); + call_config.bitrate_config.max_bitrate_bps = + config.transport.rates.max_rate.bps_or(-1); + call_config.bitrate_config.min_bitrate_bps = + config.transport.rates.min_rate.bps(); + call_config.bitrate_config.start_bitrate_bps = + config.transport.rates.start_rate.bps(); + call_config.task_queue_factory = time_controller->GetTaskQueueFactory(); + call_config.network_controller_factory = network_controller_factory; + call_config.audio_state = audio_state; + call_config.pacer_burst_interval = config.pacer_burst_interval; + call_config.trials = config.field_trials; + Clock* clock = time_controller->GetClock(); + return Call::Create(call_config, clock, + RtpTransportControllerSendFactory().Create( + call_config.ExtractTransportConfig(), clock)); +} + +std::unique_ptr<RtcEventLog> CreateEventLog( + TaskQueueFactory* task_queue_factory, + LogWriterFactoryInterface* log_writer_factory) { + if (!log_writer_factory) { + return std::make_unique<RtcEventLogNull>(); + } + auto event_log = RtcEventLogFactory(task_queue_factory) + .CreateRtcEventLog(RtcEventLog::EncodingType::NewFormat); + bool success = event_log->StartLogging(log_writer_factory->Create(".rtc.dat"), + kEventLogOutputIntervalMs); + RTC_CHECK(success); + return event_log; +} +} // namespace +NetworkControleUpdateCache::NetworkControleUpdateCache( + std::unique_ptr<NetworkControllerInterface> controller) + : controller_(std::move(controller)) {} +NetworkControlUpdate NetworkControleUpdateCache::OnNetworkAvailability( + NetworkAvailability msg) { + return Update(controller_->OnNetworkAvailability(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnNetworkRouteChange( + NetworkRouteChange msg) { + return Update(controller_->OnNetworkRouteChange(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnProcessInterval( + ProcessInterval msg) { + return Update(controller_->OnProcessInterval(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnRemoteBitrateReport( + RemoteBitrateReport msg) { + return Update(controller_->OnRemoteBitrateReport(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnRoundTripTimeUpdate( + RoundTripTimeUpdate msg) { + return Update(controller_->OnRoundTripTimeUpdate(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnSentPacket(SentPacket msg) { + return Update(controller_->OnSentPacket(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnReceivedPacket( + ReceivedPacket msg) { + return Update(controller_->OnReceivedPacket(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnStreamsConfig( + StreamsConfig msg) { + return Update(controller_->OnStreamsConfig(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnTargetRateConstraints( + TargetRateConstraints msg) { + return Update(controller_->OnTargetRateConstraints(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnTransportLossReport( + TransportLossReport msg) { + return Update(controller_->OnTransportLossReport(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnTransportPacketsFeedback( + TransportPacketsFeedback msg) { + return Update(controller_->OnTransportPacketsFeedback(msg)); +} +NetworkControlUpdate NetworkControleUpdateCache::OnNetworkStateEstimate( + NetworkStateEstimate msg) { + return Update(controller_->OnNetworkStateEstimate(msg)); +} + +NetworkControlUpdate NetworkControleUpdateCache::update_state() const { + return update_state_; +} +NetworkControlUpdate NetworkControleUpdateCache::Update( + NetworkControlUpdate update) { + if (update.target_rate) + update_state_.target_rate = update.target_rate; + if (update.pacer_config) + update_state_.pacer_config = update.pacer_config; + if (update.congestion_window) + update_state_.congestion_window = update.congestion_window; + if (!update.probe_cluster_configs.empty()) + update_state_.probe_cluster_configs = update.probe_cluster_configs; + return update; +} + +LoggingNetworkControllerFactory::LoggingNetworkControllerFactory( + LogWriterFactoryInterface* log_writer_factory, + TransportControllerConfig config) { + if (config.cc_factory) { + cc_factory_ = config.cc_factory; + if (log_writer_factory) + RTC_LOG(LS_WARNING) + << "Can't log controller state for injected network controllers"; + } else { + if (log_writer_factory) { + goog_cc_factory_.AttachWriter( + log_writer_factory->Create(".cc_state.txt")); + print_cc_state_ = true; + } + cc_factory_ = &goog_cc_factory_; + } +} + +LoggingNetworkControllerFactory::~LoggingNetworkControllerFactory() {} + +void LoggingNetworkControllerFactory::LogCongestionControllerStats( + Timestamp at_time) { + if (print_cc_state_) + goog_cc_factory_.PrintState(at_time); +} + +NetworkControlUpdate LoggingNetworkControllerFactory::GetUpdate() const { + if (last_controller_) + return last_controller_->update_state(); + return NetworkControlUpdate(); +} + +std::unique_ptr<NetworkControllerInterface> +LoggingNetworkControllerFactory::Create(NetworkControllerConfig config) { + auto controller = + std::make_unique<NetworkControleUpdateCache>(cc_factory_->Create(config)); + last_controller_ = controller.get(); + return controller; +} + +TimeDelta LoggingNetworkControllerFactory::GetProcessInterval() const { + return cc_factory_->GetProcessInterval(); +} + +void LoggingNetworkControllerFactory::SetRemoteBitrateEstimate( + RemoteBitrateReport msg) { + if (last_controller_) + last_controller_->OnRemoteBitrateReport(msg); +} + +CallClient::CallClient( + TimeController* time_controller, + std::unique_ptr<LogWriterFactoryInterface> log_writer_factory, + CallClientConfig config) + : time_controller_(time_controller), + clock_(time_controller->GetClock()), + log_writer_factory_(std::move(log_writer_factory)), + network_controller_factory_(log_writer_factory_.get(), config.transport), + task_queue_(time_controller->GetTaskQueueFactory()->CreateTaskQueue( + "CallClient", + TaskQueueFactory::Priority::NORMAL)) { + config.field_trials = &field_trials_; + SendTask([this, config] { + event_log_ = CreateEventLog(time_controller_->GetTaskQueueFactory(), + log_writer_factory_.get()); + fake_audio_setup_ = InitAudio(time_controller_); + + call_.reset(CreateCall(time_controller_, event_log_.get(), config, + &network_controller_factory_, + fake_audio_setup_.audio_state)); + transport_ = std::make_unique<NetworkNodeTransport>(clock_, call_.get()); + }); +} + +CallClient::~CallClient() { + SendTask([&] { + call_.reset(); + fake_audio_setup_ = {}; + rtc::Event done; + event_log_->StopLogging([&done] { done.Set(); }); + done.Wait(rtc::Event::kForever); + event_log_.reset(); + }); +} + +ColumnPrinter CallClient::StatsPrinter() { + return ColumnPrinter::Lambda( + "pacer_delay call_send_bw", + [this](rtc::SimpleStringBuilder& sb) { + Call::Stats call_stats = call_->GetStats(); + sb.AppendFormat("%.3lf %.0lf", call_stats.pacer_delay_ms / 1000.0, + call_stats.send_bandwidth_bps / 8.0); + }, + 64); +} + +Call::Stats CallClient::GetStats() { + // This call needs to be made on the thread that `call_` was constructed on. + Call::Stats stats; + SendTask([this, &stats] { stats = call_->GetStats(); }); + return stats; +} + +DataRate CallClient::target_rate() const { + return network_controller_factory_.GetUpdate().target_rate->target_rate; +} + +DataRate CallClient::stable_target_rate() const { + return network_controller_factory_.GetUpdate() + .target_rate->stable_target_rate; +} + +DataRate CallClient::padding_rate() const { + return network_controller_factory_.GetUpdate().pacer_config->pad_rate(); +} + +void CallClient::SetRemoteBitrate(DataRate bitrate) { + RemoteBitrateReport msg; + msg.bandwidth = bitrate; + msg.receive_time = clock_->CurrentTime(); + network_controller_factory_.SetRemoteBitrateEstimate(msg); +} + +void CallClient::UpdateBitrateConstraints( + const BitrateConstraints& constraints) { + SendTask([this, &constraints]() { + call_->GetTransportControllerSend()->SetSdpBitrateParameters(constraints); + }); +} + +void CallClient::SetAudioReceiveRtpHeaderExtensions( + rtc::ArrayView<RtpExtension> extensions) { + SendTask([this, &extensions]() { + audio_extensions_ = RtpHeaderExtensionMap(extensions); + }); +} + +void CallClient::SetVideoReceiveRtpHeaderExtensions( + rtc::ArrayView<RtpExtension> extensions) { + SendTask([this, &extensions]() { + video_extensions_ = RtpHeaderExtensionMap(extensions); + }); +} + +void CallClient::OnPacketReceived(EmulatedIpPacket packet) { + MediaType media_type = MediaType::ANY; + if (IsRtpPacket(packet.data)) { + media_type = ssrc_media_types_[ParseRtpSsrc(packet.data)]; + task_queue_.PostTask([this, media_type, + packet = std::move(packet)]() mutable { + RtpHeaderExtensionMap& extension_map = media_type == MediaType::AUDIO + ? audio_extensions_ + : video_extensions_; + RtpPacketReceived received_packet(&extension_map, packet.arrival_time); + RTC_CHECK(received_packet.Parse(packet.data)); + call_->Receiver()->DeliverRtpPacket(media_type, received_packet, + /*undemuxable_packet_handler=*/ + [](const RtpPacketReceived& packet) { + RTC_CHECK_NOTREACHED(); + return false; + }); + }); + } else { + task_queue_.PostTask( + [call = call_.get(), packet = std::move(packet)]() mutable { + call->Receiver()->DeliverRtcpPacket(packet.data); + }); + } +} + +std::unique_ptr<RtcEventLogOutput> CallClient::GetLogWriter(std::string name) { + if (!log_writer_factory_ || name.empty()) + return nullptr; + return log_writer_factory_->Create(name); +} + +uint32_t CallClient::GetNextVideoSsrc() { + RTC_CHECK_LT(next_video_ssrc_index_, kNumSsrcs); + return kVideoSendSsrcs[next_video_ssrc_index_++]; +} + +uint32_t CallClient::GetNextVideoLocalSsrc() { + RTC_CHECK_LT(next_video_local_ssrc_index_, kNumSsrcs); + return kVideoRecvLocalSsrcs[next_video_local_ssrc_index_++]; +} + +uint32_t CallClient::GetNextAudioSsrc() { + RTC_CHECK_LT(next_audio_ssrc_index_, 1); + next_audio_ssrc_index_++; + return kAudioSendSsrc; +} + +uint32_t CallClient::GetNextAudioLocalSsrc() { + RTC_CHECK_LT(next_audio_local_ssrc_index_, 1); + next_audio_local_ssrc_index_++; + return kReceiverLocalAudioSsrc; +} + +uint32_t CallClient::GetNextRtxSsrc() { + RTC_CHECK_LT(next_rtx_ssrc_index_, kNumSsrcs); + return kSendRtxSsrcs[next_rtx_ssrc_index_++]; +} + +void CallClient::SendTask(std::function<void()> task) { + task_queue_.SendTask(std::move(task)); +} + +int16_t CallClient::Bind(EmulatedEndpoint* endpoint) { + uint16_t port = endpoint->BindReceiver(0, this).value(); + endpoints_.push_back({endpoint, port}); + return port; +} + +void CallClient::UnBind() { + for (auto ep_port : endpoints_) + ep_port.first->UnbindReceiver(ep_port.second); +} + +CallClientPair::~CallClientPair() = default; + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/call_client.h b/third_party/libwebrtc/test/scenario/call_client.h new file mode 100644 index 0000000000..5d62fc75e7 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/call_client.h @@ -0,0 +1,204 @@ +/* + * Copyright 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_SCENARIO_CALL_CLIENT_H_ +#define TEST_SCENARIO_CALL_CLIENT_H_ + +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/rtp_parameters.h" +#include "api/test/time_controller.h" +#include "api/units/data_rate.h" +#include "call/call.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "modules/congestion_controller/goog_cc/test/goog_cc_printer.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "rtc_base/task_queue_for_test.h" +#include "test/logging/log_writer.h" +#include "test/network/network_emulation.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" + +namespace webrtc { + +namespace test { +// Helper class to capture network controller state. +class NetworkControleUpdateCache : public NetworkControllerInterface { + public: + explicit NetworkControleUpdateCache( + std::unique_ptr<NetworkControllerInterface> controller); + + NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override; + NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange msg) override; + NetworkControlUpdate OnProcessInterval(ProcessInterval msg) override; + NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport msg) override; + NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override; + NetworkControlUpdate OnSentPacket(SentPacket msg) override; + NetworkControlUpdate OnReceivedPacket(ReceivedPacket msg) override; + NetworkControlUpdate OnStreamsConfig(StreamsConfig msg) override; + NetworkControlUpdate OnTargetRateConstraints( + TargetRateConstraints msg) override; + NetworkControlUpdate OnTransportLossReport(TransportLossReport msg) override; + NetworkControlUpdate OnTransportPacketsFeedback( + TransportPacketsFeedback msg) override; + NetworkControlUpdate OnNetworkStateEstimate( + NetworkStateEstimate msg) override; + + NetworkControlUpdate update_state() const; + + private: + NetworkControlUpdate Update(NetworkControlUpdate update); + const std::unique_ptr<NetworkControllerInterface> controller_; + NetworkControlUpdate update_state_; +}; + +class LoggingNetworkControllerFactory + : public NetworkControllerFactoryInterface { + public: + LoggingNetworkControllerFactory(LogWriterFactoryInterface* log_writer_factory, + TransportControllerConfig config); + + ~LoggingNetworkControllerFactory(); + + LoggingNetworkControllerFactory(const LoggingNetworkControllerFactory&) = + delete; + LoggingNetworkControllerFactory& operator=( + const LoggingNetworkControllerFactory&) = delete; + + std::unique_ptr<NetworkControllerInterface> Create( + NetworkControllerConfig config) override; + TimeDelta GetProcessInterval() const override; + // TODO(srte): Consider using the Columnprinter interface for this. + void LogCongestionControllerStats(Timestamp at_time); + void SetRemoteBitrateEstimate(RemoteBitrateReport msg); + + NetworkControlUpdate GetUpdate() const; + + private: + GoogCcDebugFactory goog_cc_factory_; + NetworkControllerFactoryInterface* cc_factory_ = nullptr; + bool print_cc_state_ = false; + NetworkControleUpdateCache* last_controller_ = nullptr; +}; + +struct CallClientFakeAudio { + rtc::scoped_refptr<AudioProcessing> apm; + rtc::scoped_refptr<TestAudioDeviceModule> fake_audio_device; + rtc::scoped_refptr<AudioState> audio_state; +}; +// CallClient represents a participant in a call scenario. It is created by the +// Scenario class and is used as sender and receiver when setting up a media +// stream session. +class CallClient : public EmulatedNetworkReceiverInterface { + public: + CallClient(TimeController* time_controller, + std::unique_ptr<LogWriterFactoryInterface> log_writer_factory, + CallClientConfig config); + + ~CallClient(); + + CallClient(const CallClient&) = delete; + CallClient& operator=(const CallClient&) = delete; + + ColumnPrinter StatsPrinter(); + Call::Stats GetStats(); + DataRate send_bandwidth() { + return DataRate::BitsPerSec(GetStats().send_bandwidth_bps); + } + DataRate target_rate() const; + DataRate stable_target_rate() const; + DataRate padding_rate() const; + void UpdateBitrateConstraints(const BitrateConstraints& constraints); + void SetRemoteBitrate(DataRate bitrate); + + void SetAudioReceiveRtpHeaderExtensions( + rtc::ArrayView<RtpExtension> extensions); + void SetVideoReceiveRtpHeaderExtensions( + rtc::ArrayView<RtpExtension> extensions); + + void OnPacketReceived(EmulatedIpPacket packet) override; + std::unique_ptr<RtcEventLogOutput> GetLogWriter(std::string name); + + // Exposed publicly so that tests can execute tasks such as querying stats + // for media streams in the expected runtime environment (essentially what + // CallClient does internally for GetStats()). + void SendTask(std::function<void()> task); + + private: + friend class Scenario; + friend class CallClientPair; + friend class SendVideoStream; + friend class VideoStreamPair; + friend class ReceiveVideoStream; + friend class SendAudioStream; + friend class ReceiveAudioStream; + friend class AudioStreamPair; + friend class NetworkNodeTransport; + uint32_t GetNextVideoSsrc(); + uint32_t GetNextVideoLocalSsrc(); + uint32_t GetNextAudioSsrc(); + uint32_t GetNextAudioLocalSsrc(); + uint32_t GetNextRtxSsrc(); + int16_t Bind(EmulatedEndpoint* endpoint); + void UnBind(); + + TimeController* const time_controller_; + Clock* clock_; + const std::unique_ptr<LogWriterFactoryInterface> log_writer_factory_; + std::unique_ptr<RtcEventLog> event_log_; + LoggingNetworkControllerFactory network_controller_factory_; + CallClientFakeAudio fake_audio_setup_; + std::unique_ptr<Call> call_; + std::unique_ptr<NetworkNodeTransport> transport_; + std::vector<std::pair<EmulatedEndpoint*, uint16_t>> endpoints_; + RtpHeaderExtensionMap audio_extensions_; + RtpHeaderExtensionMap video_extensions_; + + int next_video_ssrc_index_ = 0; + int next_video_local_ssrc_index_ = 0; + int next_rtx_ssrc_index_ = 0; + int next_audio_ssrc_index_ = 0; + int next_audio_local_ssrc_index_ = 0; + std::map<uint32_t, MediaType> ssrc_media_types_; + // Defined last so it's destroyed first. + TaskQueueForTest task_queue_; + + const FieldTrialBasedConfig field_trials_; +}; + +class CallClientPair { + public: + ~CallClientPair(); + + CallClientPair(const CallClientPair&) = delete; + CallClientPair& operator=(const CallClientPair&) = delete; + + CallClient* first() { return first_; } + CallClient* second() { return second_; } + std::pair<CallClient*, CallClient*> forward() { return {first(), second()}; } + std::pair<CallClient*, CallClient*> reverse() { return {second(), first()}; } + + private: + friend class Scenario; + CallClientPair(CallClient* first, CallClient* second) + : first_(first), second_(second) {} + CallClient* const first_; + CallClient* const second_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_CALL_CLIENT_H_ diff --git a/third_party/libwebrtc/test/scenario/column_printer.cc b/third_party/libwebrtc/test/scenario/column_printer.cc new file mode 100644 index 0000000000..661c83bd0d --- /dev/null +++ b/third_party/libwebrtc/test/scenario/column_printer.cc @@ -0,0 +1,73 @@ +/* + * Copyright 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/scenario/column_printer.h" + +namespace webrtc { +namespace test { + +ColumnPrinter::ColumnPrinter(const ColumnPrinter&) = default; +ColumnPrinter::~ColumnPrinter() = default; + +ColumnPrinter::ColumnPrinter( + const char* headers, + std::function<void(rtc::SimpleStringBuilder&)> printer, + size_t max_length) + : headers_(headers), printer_(printer), max_length_(max_length) {} + +ColumnPrinter ColumnPrinter::Fixed(const char* headers, std::string fields) { + return ColumnPrinter( + headers, [fields](rtc::SimpleStringBuilder& sb) { sb << fields; }, + fields.size()); +} + +ColumnPrinter ColumnPrinter::Lambda( + const char* headers, + std::function<void(rtc::SimpleStringBuilder&)> printer, + size_t max_length) { + return ColumnPrinter(headers, printer, max_length); +} + +StatesPrinter::StatesPrinter(std::unique_ptr<RtcEventLogOutput> writer, + std::vector<ColumnPrinter> printers) + : writer_(std::move(writer)), printers_(printers) { + RTC_CHECK(!printers_.empty()); + for (auto& printer : printers_) + buffer_size_ += printer.max_length_ + 1; + buffer_.resize(buffer_size_); +} + +StatesPrinter::~StatesPrinter() = default; + +void StatesPrinter::PrintHeaders() { + if (!writer_) + return; + writer_->Write(printers_[0].headers_); + for (size_t i = 1; i < printers_.size(); ++i) { + writer_->Write(" "); + writer_->Write(printers_[i].headers_); + } + writer_->Write("\n"); +} + +void StatesPrinter::PrintRow() { + // Note that this is run for null output to preserve side effects, this allows + // setting break points etc. + rtc::SimpleStringBuilder sb(buffer_); + printers_[0].printer_(sb); + for (size_t i = 1; i < printers_.size(); ++i) { + sb << ' '; + printers_[i].printer_(sb); + } + sb << "\n"; + if (writer_) + writer_->Write(std::string(sb.str(), sb.size())); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/column_printer.h b/third_party/libwebrtc/test/scenario/column_printer.h new file mode 100644 index 0000000000..529f4597ec --- /dev/null +++ b/third_party/libwebrtc/test/scenario/column_printer.h @@ -0,0 +1,66 @@ +/* + * Copyright 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_SCENARIO_COLUMN_PRINTER_H_ +#define TEST_SCENARIO_COLUMN_PRINTER_H_ +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "rtc_base/strings/string_builder.h" +#include "test/logging/log_writer.h" + +namespace webrtc { +namespace test { +class ColumnPrinter { + public: + ColumnPrinter(const ColumnPrinter&); + ~ColumnPrinter(); + static ColumnPrinter Fixed(const char* headers, std::string fields); + static ColumnPrinter Lambda( + const char* headers, + std::function<void(rtc::SimpleStringBuilder&)> printer, + size_t max_length = 256); + + protected: + friend class StatesPrinter; + const char* headers_; + std::function<void(rtc::SimpleStringBuilder&)> printer_; + size_t max_length_; + + private: + ColumnPrinter(const char* headers, + std::function<void(rtc::SimpleStringBuilder&)> printer, + size_t max_length); +}; + +class StatesPrinter { + public: + StatesPrinter(std::unique_ptr<RtcEventLogOutput> writer, + std::vector<ColumnPrinter> printers); + + ~StatesPrinter(); + + StatesPrinter(const StatesPrinter&) = delete; + StatesPrinter& operator=(const StatesPrinter&) = delete; + + void PrintHeaders(); + void PrintRow(); + + private: + const std::unique_ptr<RtcEventLogOutput> writer_; + const std::vector<ColumnPrinter> printers_; + size_t buffer_size_ = 0; + std::vector<char> buffer_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_COLUMN_PRINTER_H_ diff --git a/third_party/libwebrtc/test/scenario/hardware_codecs.cc b/third_party/libwebrtc/test/scenario/hardware_codecs.cc new file mode 100644 index 0000000000..cac0f10dc9 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/hardware_codecs.cc @@ -0,0 +1,52 @@ +/* + * Copyright 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/scenario/hardware_codecs.h" + +#include "rtc_base/checks.h" + +#ifdef WEBRTC_ANDROID +#include "modules/video_coding/codecs/test/android_codec_factory_helper.h" +#endif +#ifdef WEBRTC_MAC +#include "modules/video_coding/codecs/test/objc_codec_factory_helper.h" +#endif + +namespace webrtc { +namespace test { +std::unique_ptr<VideoEncoderFactory> CreateHardwareEncoderFactory() { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); + return CreateAndroidEncoderFactory(); +#else +#ifdef WEBRTC_MAC + return CreateObjCEncoderFactory(); +#else + RTC_DCHECK_NOTREACHED() + << "Hardware encoder not implemented on this platform."; + return nullptr; +#endif +#endif +} +std::unique_ptr<VideoDecoderFactory> CreateHardwareDecoderFactory() { +#ifdef WEBRTC_ANDROID + InitializeAndroidObjects(); + return CreateAndroidDecoderFactory(); +#else +#ifdef WEBRTC_MAC + return CreateObjCDecoderFactory(); +#else + RTC_DCHECK_NOTREACHED() + << "Hardware decoder not implemented on this platform."; + return nullptr; +#endif +#endif +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/hardware_codecs.h b/third_party/libwebrtc/test/scenario/hardware_codecs.h new file mode 100644 index 0000000000..ae14a27d9e --- /dev/null +++ b/third_party/libwebrtc/test/scenario/hardware_codecs.h @@ -0,0 +1,24 @@ +/* + * Copyright 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_SCENARIO_HARDWARE_CODECS_H_ +#define TEST_SCENARIO_HARDWARE_CODECS_H_ + +#include <memory> + +#include "api/video_codecs/video_decoder_factory.h" +#include "api/video_codecs/video_encoder_factory.h" + +namespace webrtc { +namespace test { +std::unique_ptr<VideoEncoderFactory> CreateHardwareEncoderFactory(); +std::unique_ptr<VideoDecoderFactory> CreateHardwareDecoderFactory(); +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_HARDWARE_CODECS_H_ diff --git a/third_party/libwebrtc/test/scenario/network_node.cc b/third_party/libwebrtc/test/scenario/network_node.cc new file mode 100644 index 0000000000..e149bb11e0 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/network_node.cc @@ -0,0 +1,144 @@ +/* + * Copyright 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/scenario/network_node.h" + +#include <algorithm> +#include <vector> + +#include <memory> +#include "rtc_base/net_helper.h" +#include "rtc_base/numerics/safe_minmax.h" + +namespace webrtc { +namespace test { +namespace { +constexpr char kDummyTransportName[] = "dummy"; +SimulatedNetwork::Config CreateSimulationConfig( + NetworkSimulationConfig config) { + SimulatedNetwork::Config sim_config; + sim_config.link_capacity_kbps = config.bandwidth.kbps_or(0); + sim_config.loss_percent = config.loss_rate * 100; + sim_config.queue_delay_ms = config.delay.ms(); + sim_config.delay_standard_deviation_ms = config.delay_std_dev.ms(); + sim_config.packet_overhead = config.packet_overhead.bytes<int>(); + sim_config.queue_length_packets = + config.packet_queue_length_limit.value_or(0); + return sim_config; +} +} // namespace + +SimulationNode::SimulationNode(NetworkSimulationConfig config, + SimulatedNetwork* behavior, + EmulatedNetworkNode* network_node) + : config_(config), simulation_(behavior), network_node_(network_node) {} + +std::unique_ptr<SimulatedNetwork> SimulationNode::CreateBehavior( + NetworkSimulationConfig config) { + SimulatedNetwork::Config sim_config = CreateSimulationConfig(config); + return std::make_unique<SimulatedNetwork>(sim_config); +} + +void SimulationNode::UpdateConfig( + std::function<void(NetworkSimulationConfig*)> modifier) { + modifier(&config_); + SimulatedNetwork::Config sim_config = CreateSimulationConfig(config_); + simulation_->SetConfig(sim_config); +} + +void SimulationNode::PauseTransmissionUntil(Timestamp until) { + simulation_->PauseTransmissionUntil(until.us()); +} + +ColumnPrinter SimulationNode::ConfigPrinter() const { + return ColumnPrinter::Lambda( + "propagation_delay capacity loss_rate", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.3lf %.0lf %.2lf", config_.delay.seconds<double>(), + config_.bandwidth.bps() / 8.0, config_.loss_rate); + }); +} + +NetworkNodeTransport::NetworkNodeTransport(Clock* sender_clock, + Call* sender_call) + : sender_clock_(sender_clock), sender_call_(sender_call) {} + +NetworkNodeTransport::~NetworkNodeTransport() = default; + +bool NetworkNodeTransport::SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) { + int64_t send_time_ms = sender_clock_->TimeInMilliseconds(); + rtc::SentPacket sent_packet; + sent_packet.packet_id = options.packet_id; + sent_packet.info.included_in_feedback = options.included_in_feedback; + sent_packet.info.included_in_allocation = options.included_in_allocation; + sent_packet.send_time_ms = send_time_ms; + sent_packet.info.packet_size_bytes = length; + sent_packet.info.packet_type = rtc::PacketType::kData; + sender_call_->OnSentPacket(sent_packet); + + MutexLock lock(&mutex_); + if (!endpoint_) + return false; + rtc::CopyOnWriteBuffer buffer(packet, length); + endpoint_->SendPacket(local_address_, remote_address_, buffer, + packet_overhead_.bytes()); + return true; +} + +bool NetworkNodeTransport::SendRtcp(const uint8_t* packet, size_t length) { + rtc::CopyOnWriteBuffer buffer(packet, length); + MutexLock lock(&mutex_); + if (!endpoint_) + return false; + endpoint_->SendPacket(local_address_, remote_address_, buffer, + packet_overhead_.bytes()); + return true; +} + +void NetworkNodeTransport::Connect(EmulatedEndpoint* endpoint, + const rtc::SocketAddress& receiver_address, + DataSize packet_overhead) { + rtc::NetworkRoute route; + route.connected = true; + // We assume that the address will be unique in the lower bytes. + route.local = rtc::RouteEndpoint::CreateWithNetworkId(static_cast<uint16_t>( + receiver_address.ipaddr().v4AddressAsHostOrderInteger())); + route.remote = rtc::RouteEndpoint::CreateWithNetworkId(static_cast<uint16_t>( + receiver_address.ipaddr().v4AddressAsHostOrderInteger())); + route.packet_overhead = packet_overhead.bytes() + + receiver_address.ipaddr().overhead() + + cricket::kUdpHeaderSize; + { + // Only IPv4 address is supported. + RTC_CHECK_EQ(receiver_address.family(), AF_INET); + MutexLock lock(&mutex_); + endpoint_ = endpoint; + local_address_ = rtc::SocketAddress(endpoint_->GetPeerLocalAddress(), 0); + remote_address_ = receiver_address; + packet_overhead_ = packet_overhead; + current_network_route_ = route; + } + + sender_call_->GetTransportControllerSend()->OnNetworkRouteChanged( + kDummyTransportName, route); +} + +void NetworkNodeTransport::Disconnect() { + MutexLock lock(&mutex_); + current_network_route_.connected = false; + sender_call_->GetTransportControllerSend()->OnNetworkRouteChanged( + kDummyTransportName, current_network_route_); + current_network_route_ = {}; + endpoint_ = nullptr; +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/network_node.h b/third_party/libwebrtc/test/scenario/network_node.h new file mode 100644 index 0000000000..fe87cefa26 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/network_node.h @@ -0,0 +1,83 @@ +/* + * Copyright 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_SCENARIO_NETWORK_NODE_H_ +#define TEST_SCENARIO_NETWORK_NODE_H_ + +#include <deque> +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "api/call/transport.h" +#include "api/units/timestamp.h" +#include "call/call.h" +#include "call/simulated_network.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/network/network_emulation.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/scenario_config.h" + +namespace webrtc { +namespace test { + +class SimulationNode { + public: + SimulationNode(NetworkSimulationConfig config, + SimulatedNetwork* behavior, + EmulatedNetworkNode* network_node); + static std::unique_ptr<SimulatedNetwork> CreateBehavior( + NetworkSimulationConfig config); + + void UpdateConfig(std::function<void(NetworkSimulationConfig*)> modifier); + void PauseTransmissionUntil(Timestamp until); + ColumnPrinter ConfigPrinter() const; + EmulatedNetworkNode* node() { return network_node_; } + + private: + NetworkSimulationConfig config_; + SimulatedNetwork* const simulation_; + EmulatedNetworkNode* const network_node_; +}; + +class NetworkNodeTransport : public Transport { + public: + NetworkNodeTransport(Clock* sender_clock, Call* sender_call); + ~NetworkNodeTransport() override; + + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override; + bool SendRtcp(const uint8_t* packet, size_t length) override; + + void Connect(EmulatedEndpoint* endpoint, + const rtc::SocketAddress& receiver_address, + DataSize packet_overhead); + void Disconnect(); + + DataSize packet_overhead() { + MutexLock lock(&mutex_); + return packet_overhead_; + } + + private: + Mutex mutex_; + Clock* const sender_clock_; + Call* const sender_call_; + EmulatedEndpoint* endpoint_ RTC_GUARDED_BY(mutex_) = nullptr; + rtc::SocketAddress local_address_ RTC_GUARDED_BY(mutex_); + rtc::SocketAddress remote_address_ RTC_GUARDED_BY(mutex_); + DataSize packet_overhead_ RTC_GUARDED_BY(mutex_) = DataSize::Zero(); + rtc::NetworkRoute current_network_route_ RTC_GUARDED_BY(mutex_); +}; +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_NETWORK_NODE_H_ diff --git a/third_party/libwebrtc/test/scenario/performance_stats.cc b/third_party/libwebrtc/test/scenario/performance_stats.cc new file mode 100644 index 0000000000..e12be8a003 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/performance_stats.cc @@ -0,0 +1,47 @@ +/* + * Copyright 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/scenario/performance_stats.h" + +#include <algorithm> + +namespace webrtc { +namespace test { +void VideoFramesStats::AddFrameInfo(const VideoFrameBuffer& frame, + Timestamp at_time) { + ++count; + RTC_DCHECK(at_time.IsFinite()); + pixels.AddSample(frame.width() * frame.height()); + resolution.AddSample(std::max(frame.width(), frame.height())); + frames.AddEvent(at_time); +} + +void VideoFramesStats::AddStats(const VideoFramesStats& other) { + count += other.count; + pixels.AddSamples(other.pixels); + resolution.AddSamples(other.resolution); + frames.AddEvents(other.frames); +} + +void VideoQualityStats::AddStats(const VideoQualityStats& other) { + capture.AddStats(other.capture); + render.AddStats(other.render); + lost_count += other.lost_count; + freeze_count += other.freeze_count; + capture_to_decoded_delay.AddSamples(other.capture_to_decoded_delay); + end_to_end_delay.AddSamples(other.end_to_end_delay); + psnr.AddSamples(other.psnr); + psnr_with_freeze.AddSamples(other.psnr_with_freeze); + skipped_between_rendered.AddSamples(other.skipped_between_rendered); + freeze_duration.AddSamples(other.freeze_duration); + time_between_freezes.AddSamples(other.time_between_freezes); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/performance_stats.h b/third_party/libwebrtc/test/scenario/performance_stats.h new file mode 100644 index 0000000000..6974ab6d22 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/performance_stats.h @@ -0,0 +1,108 @@ +/* + * Copyright 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_SCENARIO_PERFORMANCE_STATS_H_ +#define TEST_SCENARIO_PERFORMANCE_STATS_H_ + +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame_buffer.h" +#include "rtc_base/numerics/event_rate_counter.h" +#include "rtc_base/numerics/sample_stats.h" + +namespace webrtc { +namespace test { + +struct VideoFramePair { + rtc::scoped_refptr<VideoFrameBuffer> captured; + rtc::scoped_refptr<VideoFrameBuffer> decoded; + Timestamp capture_time = Timestamp::MinusInfinity(); + Timestamp decoded_time = Timestamp::PlusInfinity(); + Timestamp render_time = Timestamp::PlusInfinity(); + // A unique identifier for the spatial/temporal layer the decoded frame + // belongs to. Note that this does not reflect the id as defined by the + // underlying layer setup. + int layer_id = 0; + int capture_id = 0; + int decode_id = 0; + // Indicates the repeat count for the decoded frame. Meaning that the same + // decoded frame has matched differend captured frames. + int repeated = 0; +}; + + +struct VideoFramesStats { + int count = 0; + SampleStats<double> pixels; + SampleStats<double> resolution; + EventRateCounter frames; + void AddFrameInfo(const VideoFrameBuffer& frame, Timestamp at_time); + void AddStats(const VideoFramesStats& other); +}; + +struct VideoQualityStats { + int lost_count = 0; + int freeze_count = 0; + VideoFramesStats capture; + VideoFramesStats render; + // Time from frame was captured on device to time frame was delivered from + // decoder. + SampleStats<TimeDelta> capture_to_decoded_delay; + // Time from frame was captured on device to time frame was displayed on + // device. + SampleStats<TimeDelta> end_to_end_delay; + // PSNR for delivered frames. Note that this might go up for a worse + // connection due to frame dropping. + SampleStats<double> psnr; + // PSNR for all frames, dropped or lost frames are compared to the last + // successfully delivered frame + SampleStats<double> psnr_with_freeze; + // Frames skipped between two nearest. + SampleStats<double> skipped_between_rendered; + // In the next 2 metrics freeze is a pause that is longer, than maximum: + // 1. 150ms + // 2. 3 * average time between two sequential frames. + // Item 1 will cover high fps video and is a duration, that is noticeable by + // human eye. Item 2 will cover low fps video like screen sharing. + SampleStats<TimeDelta> freeze_duration; + // Mean time between one freeze end and next freeze start. + SampleStats<TimeDelta> time_between_freezes; + void AddStats(const VideoQualityStats& other); +}; + +struct CollectedCallStats { + SampleStats<DataRate> target_rate; + SampleStats<TimeDelta> pacer_delay; + SampleStats<TimeDelta> round_trip_time; + SampleStats<double> memory_usage; +}; + +struct CollectedAudioReceiveStats { + SampleStats<double> expand_rate; + SampleStats<double> accelerate_rate; + SampleStats<TimeDelta> jitter_buffer; +}; +struct CollectedVideoSendStats { + SampleStats<double> encode_frame_rate; + SampleStats<TimeDelta> encode_time; + SampleStats<double> encode_usage; + SampleStats<DataRate> media_bitrate; + SampleStats<DataRate> fec_bitrate; +}; +struct CollectedVideoReceiveStats { + SampleStats<TimeDelta> decode_time; + SampleStats<TimeDelta> decode_time_max; + SampleStats<double> decode_pixels; + SampleStats<double> resolution; +}; + +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_PERFORMANCE_STATS_H_ diff --git a/third_party/libwebrtc/test/scenario/performance_stats_unittest.cc b/third_party/libwebrtc/test/scenario/performance_stats_unittest.cc new file mode 100644 index 0000000000..8d87c87745 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/performance_stats_unittest.cc @@ -0,0 +1,27 @@ +/* + * Copyright 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/scenario/performance_stats.h" + +#include "test/gtest.h" + +namespace webrtc { +namespace test { + +TEST(EventRateCounter, ReturnsCorrectTotalDuration) { + EventRateCounter event_rate_counter; + EXPECT_EQ(event_rate_counter.TotalDuration(), TimeDelta::Zero()); + event_rate_counter.AddEvent(Timestamp::Seconds(1)); + EXPECT_EQ(event_rate_counter.TotalDuration(), TimeDelta::Zero()); + event_rate_counter.AddEvent(Timestamp::Seconds(2)); + EXPECT_EQ(event_rate_counter.TotalDuration(), TimeDelta::Seconds(1)); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/probing_test.cc b/third_party/libwebrtc/test/scenario/probing_test.cc new file mode 100644 index 0000000000..86653ced9b --- /dev/null +++ b/third_party/libwebrtc/test/scenario/probing_test.cc @@ -0,0 +1,135 @@ +/* + * Copyright 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/gtest.h" +#include "test/scenario/scenario.h" + +namespace webrtc { +namespace test { + +TEST(ProbingTest, InitialProbingRampsUpTargetRateWhenNetworkIsGood) { + Scenario s; + NetworkSimulationConfig good_network; + good_network.bandwidth = DataRate::KilobitsPerSec(2000); + + VideoStreamConfig video_config; + video_config.encoder.codec = + VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + CallClientConfig send_config; + auto* caller = s.CreateClient("caller", send_config); + auto* callee = s.CreateClient("callee", CallClientConfig()); + auto route = + s.CreateRoutes(caller, {s.CreateSimulationNode(good_network)}, callee, + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), video_config); + + s.RunFor(TimeDelta::Seconds(1)); + EXPECT_GE(DataRate::BitsPerSec(caller->GetStats().send_bandwidth_bps), + 3 * send_config.transport.rates.start_rate); +} + +TEST(ProbingTest, MidCallProbingRampupTriggeredByUpdatedBitrateConstraints) { + Scenario s; + + const DataRate kStartRate = DataRate::KilobitsPerSec(300); + const DataRate kConstrainedRate = DataRate::KilobitsPerSec(100); + const DataRate kHighRate = DataRate::KilobitsPerSec(1500); + + VideoStreamConfig video_config; + video_config.encoder.codec = + VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + CallClientConfig send_call_config; + send_call_config.transport.rates.start_rate = kStartRate; + send_call_config.transport.rates.max_rate = kHighRate * 2; + auto* caller = s.CreateClient("caller", send_call_config); + auto* callee = s.CreateClient("callee", CallClientConfig()); + auto route = s.CreateRoutes( + caller, {s.CreateSimulationNode(NetworkSimulationConfig())}, callee, + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), video_config); + + // Wait until initial probing rampup is done and then set a low max bitrate. + s.RunFor(TimeDelta::Seconds(1)); + EXPECT_GE(DataRate::BitsPerSec(caller->GetStats().send_bandwidth_bps), + 5 * send_call_config.transport.rates.start_rate); + BitrateConstraints bitrate_config; + bitrate_config.max_bitrate_bps = kConstrainedRate.bps(); + caller->UpdateBitrateConstraints(bitrate_config); + + // Wait until the low send bitrate has taken effect, and then set a much + // higher max bitrate. + s.RunFor(TimeDelta::Seconds(2)); + EXPECT_LT(DataRate::BitsPerSec(caller->GetStats().send_bandwidth_bps), + kConstrainedRate * 1.1); + bitrate_config.max_bitrate_bps = 2 * kHighRate.bps(); + caller->UpdateBitrateConstraints(bitrate_config); + + // Check that the max send bitrate is reached quicker than would be possible + // with simple AIMD rate control. + s.RunFor(TimeDelta::Seconds(1)); + EXPECT_GE(DataRate::BitsPerSec(caller->GetStats().send_bandwidth_bps), + kHighRate); +} + +TEST(ProbingTest, ProbesRampsUpWhenVideoEncoderConfigChanges) { + Scenario s; + const DataRate kStartRate = DataRate::KilobitsPerSec(50); + const DataRate kHdRate = DataRate::KilobitsPerSec(3250); + + // Set up 3-layer simulcast. + VideoStreamConfig video_config; + video_config.encoder.codec = + VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + video_config.encoder.simulcast_streams = {webrtc::ScalabilityMode::kL1T3, + webrtc::ScalabilityMode::kL1T3, + webrtc::ScalabilityMode::kL1T3}; + video_config.source.generator.width = 1280; + video_config.source.generator.height = 720; + + CallClientConfig send_call_config; + send_call_config.transport.rates.start_rate = kStartRate; + send_call_config.transport.rates.max_rate = kHdRate * 2; + auto* caller = s.CreateClient("caller", send_call_config); + auto* callee = s.CreateClient("callee", CallClientConfig()); + auto send_net = + s.CreateMutableSimulationNode([&](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::KilobitsPerSec(200); + }); + auto route = + s.CreateRoutes(caller, {send_net->node()}, callee, + {s.CreateSimulationNode(NetworkSimulationConfig())}); + auto* video_stream = s.CreateVideoStream(route->forward(), video_config); + + // Only QVGA enabled initially. Run until initial probing is done and BWE + // has settled. + video_stream->send()->UpdateActiveLayers({true, false, false}); + s.RunFor(TimeDelta::Seconds(2)); + + // Remove network constraints and run for a while more, BWE should be much + // less than required HD rate. + send_net->UpdateConfig([&](NetworkSimulationConfig* c) { + c->bandwidth = DataRate::PlusInfinity(); + }); + s.RunFor(TimeDelta::Seconds(2)); + + DataRate bandwidth = + DataRate::BitsPerSec(caller->GetStats().send_bandwidth_bps); + EXPECT_LT(bandwidth, kHdRate / 4); + + // Enable all layers, triggering a probe. + video_stream->send()->UpdateActiveLayers({true, true, true}); + + // Run for a short while and verify BWE has ramped up fast. + s.RunFor(TimeDelta::Seconds(2)); + EXPECT_GT(DataRate::BitsPerSec(caller->GetStats().send_bandwidth_bps), + kHdRate); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/scenario.cc b/third_party/libwebrtc/test/scenario/scenario.cc new file mode 100644 index 0000000000..93377120a1 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/scenario.cc @@ -0,0 +1,355 @@ +/* + * Copyright 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/scenario/scenario.h" + +#include <algorithm> +#include <memory> + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/strings/string_view.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "rtc_base/socket_address.h" +#include "test/logging/file_log_writer.h" +#include "test/network/network_emulation.h" +#include "test/scenario/video_stream.h" +#include "test/testsupport/file_utils.h" + +ABSL_FLAG(bool, scenario_logs, false, "Save logs from scenario framework."); +ABSL_FLAG(std::string, + scenario_logs_root, + "", + "Output root path, based on project root if unset."); + +namespace webrtc { +namespace test { +namespace { + +std::unique_ptr<FileLogWriterFactory> GetScenarioLogManager( + absl::string_view file_name) { + if (absl::GetFlag(FLAGS_scenario_logs) && !file_name.empty()) { + std::string output_root = absl::GetFlag(FLAGS_scenario_logs_root); + if (output_root.empty()) + output_root = OutputPath() + "output_data/"; + + auto base_filename = output_root + std::string(file_name) + "."; + RTC_LOG(LS_INFO) << "Saving scenario logs to: " << base_filename; + return std::make_unique<FileLogWriterFactory>(base_filename); + } + return nullptr; +} +} // namespace + +Scenario::Scenario() + : Scenario(std::unique_ptr<LogWriterFactoryInterface>(), + /*real_time=*/false) {} + +Scenario::Scenario(const testing::TestInfo* test_info) + : Scenario(std::string(test_info->test_suite_name()) + "/" + + test_info->name()) {} + +Scenario::Scenario(absl::string_view file_name) + : Scenario(file_name, /*real_time=*/false) {} + +Scenario::Scenario(absl::string_view file_name, bool real_time) + : Scenario(GetScenarioLogManager(file_name), real_time) {} + +Scenario::Scenario( + std::unique_ptr<LogWriterFactoryInterface> log_writer_factory, + bool real_time) + : log_writer_factory_(std::move(log_writer_factory)), + network_manager_(real_time ? TimeMode::kRealTime : TimeMode::kSimulated, + EmulatedNetworkStatsGatheringMode::kDefault), + clock_(network_manager_.time_controller()->GetClock()), + audio_decoder_factory_(CreateBuiltinAudioDecoderFactory()), + audio_encoder_factory_(CreateBuiltinAudioEncoderFactory()), + task_queue_(network_manager_.time_controller() + ->GetTaskQueueFactory() + ->CreateTaskQueue("Scenario", + TaskQueueFactory::Priority::NORMAL)) {} + +Scenario::~Scenario() { + if (start_time_.IsFinite()) + Stop(); + for (auto& call_client : clients_) { + call_client->transport_->Disconnect(); + call_client->UnBind(); + } +} + +ColumnPrinter Scenario::TimePrinter() { + return ColumnPrinter::Lambda( + "time", + [this](rtc::SimpleStringBuilder& sb) { + sb.AppendFormat("%.3lf", Now().seconds<double>()); + }, + 32); +} + +StatesPrinter* Scenario::CreatePrinter(absl::string_view name, + TimeDelta interval, + std::vector<ColumnPrinter> printers) { + std::vector<ColumnPrinter> all_printers{TimePrinter()}; + for (auto& printer : printers) + all_printers.push_back(printer); + StatesPrinter* printer = new StatesPrinter(GetLogWriter(name), all_printers); + printers_.emplace_back(printer); + printer->PrintHeaders(); + if (interval.IsFinite()) + Every(interval, [printer] { printer->PrintRow(); }); + return printer; +} + +CallClient* Scenario::CreateClient(absl::string_view name, + CallClientConfig config) { + CallClient* client = new CallClient(network_manager_.time_controller(), + GetLogWriterFactory(name), config); + if (config.transport.state_log_interval.IsFinite()) { + Every(config.transport.state_log_interval, [this, client]() { + client->network_controller_factory_.LogCongestionControllerStats(Now()); + }); + } + clients_.emplace_back(client); + return client; +} + +CallClient* Scenario::CreateClient( + absl::string_view name, + std::function<void(CallClientConfig*)> config_modifier) { + CallClientConfig config; + config_modifier(&config); + return CreateClient(name, config); +} + +CallClientPair* Scenario::CreateRoutes( + CallClient* first, + std::vector<EmulatedNetworkNode*> send_link, + CallClient* second, + std::vector<EmulatedNetworkNode*> return_link) { + return CreateRoutes(first, send_link, + DataSize::Bytes(PacketOverhead::kDefault), second, + return_link, DataSize::Bytes(PacketOverhead::kDefault)); +} + +CallClientPair* Scenario::CreateRoutes( + CallClient* first, + std::vector<EmulatedNetworkNode*> send_link, + DataSize first_overhead, + CallClient* second, + std::vector<EmulatedNetworkNode*> return_link, + DataSize second_overhead) { + CallClientPair* client_pair = new CallClientPair(first, second); + ChangeRoute(client_pair->forward(), send_link, first_overhead); + ChangeRoute(client_pair->reverse(), return_link, second_overhead); + client_pairs_.emplace_back(client_pair); + return client_pair; +} + +void Scenario::ChangeRoute(std::pair<CallClient*, CallClient*> clients, + std::vector<EmulatedNetworkNode*> over_nodes) { + ChangeRoute(clients, over_nodes, DataSize::Bytes(PacketOverhead::kDefault)); +} + +void Scenario::ChangeRoute(std::pair<CallClient*, CallClient*> clients, + std::vector<EmulatedNetworkNode*> over_nodes, + DataSize overhead) { + EmulatedRoute* route = network_manager_.CreateRoute(over_nodes); + uint16_t port = clients.second->Bind(route->to); + auto addr = rtc::SocketAddress(route->to->GetPeerLocalAddress(), port); + clients.first->transport_->Connect(route->from, addr, overhead); +} + +EmulatedNetworkNode* Scenario::CreateSimulationNode( + std::function<void(NetworkSimulationConfig*)> config_modifier) { + NetworkSimulationConfig config; + config_modifier(&config); + return CreateSimulationNode(config); +} + +EmulatedNetworkNode* Scenario::CreateSimulationNode( + NetworkSimulationConfig config) { + return network_manager_.CreateEmulatedNode( + SimulationNode::CreateBehavior(config)); +} + +SimulationNode* Scenario::CreateMutableSimulationNode( + std::function<void(NetworkSimulationConfig*)> config_modifier) { + NetworkSimulationConfig config; + config_modifier(&config); + return CreateMutableSimulationNode(config); +} + +SimulationNode* Scenario::CreateMutableSimulationNode( + NetworkSimulationConfig config) { + std::unique_ptr<SimulatedNetwork> behavior = + SimulationNode::CreateBehavior(config); + SimulatedNetwork* behavior_ptr = behavior.get(); + auto* emulated_node = + network_manager_.CreateEmulatedNode(std::move(behavior)); + simulation_nodes_.emplace_back( + new SimulationNode(config, behavior_ptr, emulated_node)); + return simulation_nodes_.back().get(); +} + +void Scenario::TriggerPacketBurst(std::vector<EmulatedNetworkNode*> over_nodes, + size_t num_packets, + size_t packet_size) { + network_manager_.CreateCrossTrafficRoute(over_nodes) + ->TriggerPacketBurst(num_packets, packet_size); +} + +void Scenario::NetworkDelayedAction( + std::vector<EmulatedNetworkNode*> over_nodes, + size_t packet_size, + std::function<void()> action) { + network_manager_.CreateCrossTrafficRoute(over_nodes) + ->NetworkDelayedAction(packet_size, action); +} + +VideoStreamPair* Scenario::CreateVideoStream( + std::pair<CallClient*, CallClient*> clients, + std::function<void(VideoStreamConfig*)> config_modifier) { + VideoStreamConfig config; + config_modifier(&config); + return CreateVideoStream(clients, config); +} + +VideoStreamPair* Scenario::CreateVideoStream( + std::pair<CallClient*, CallClient*> clients, + VideoStreamConfig config) { + std::vector<RtpExtension> extensions = GetVideoRtpExtensions(config); + clients.first->SetVideoReceiveRtpHeaderExtensions(extensions); + clients.second->SetVideoReceiveRtpHeaderExtensions(extensions); + video_streams_.emplace_back( + new VideoStreamPair(clients.first, clients.second, config)); + return video_streams_.back().get(); +} + +AudioStreamPair* Scenario::CreateAudioStream( + std::pair<CallClient*, CallClient*> clients, + std::function<void(AudioStreamConfig*)> config_modifier) { + AudioStreamConfig config; + config_modifier(&config); + return CreateAudioStream(clients, config); +} + +AudioStreamPair* Scenario::CreateAudioStream( + std::pair<CallClient*, CallClient*> clients, + AudioStreamConfig config) { + std::vector<RtpExtension> extensions = GetAudioRtpExtensions(config); + clients.first->SetAudioReceiveRtpHeaderExtensions(extensions); + clients.second->SetAudioReceiveRtpHeaderExtensions(extensions); + audio_streams_.emplace_back( + new AudioStreamPair(clients.first, audio_encoder_factory_, clients.second, + audio_decoder_factory_, config)); + return audio_streams_.back().get(); +} + +void Scenario::Every(TimeDelta interval, + absl::AnyInvocable<void(TimeDelta)> function) { + RepeatingTaskHandle::DelayedStart( + task_queue_.get(), interval, + [interval, function = std::move(function)]() mutable { + function(interval); + return interval; + }); +} + +void Scenario::Every(TimeDelta interval, absl::AnyInvocable<void()> function) { + RepeatingTaskHandle::DelayedStart( + task_queue_.get(), interval, + [interval, function = std::move(function)]() mutable { + function(); + return interval; + }); +} + +void Scenario::Post(absl::AnyInvocable<void() &&> function) { + task_queue_->PostTask(std::move(function)); +} + +void Scenario::At(TimeDelta offset, absl::AnyInvocable<void() &&> function) { + RTC_DCHECK_GT(offset, TimeSinceStart()); + task_queue_->PostDelayedTask(std::move(function), TimeUntilTarget(offset)); +} + +void Scenario::RunFor(TimeDelta duration) { + if (start_time_.IsInfinite()) + Start(); + network_manager_.time_controller()->AdvanceTime(duration); +} + +void Scenario::RunUntil(TimeDelta target_time_since_start) { + RunFor(TimeUntilTarget(target_time_since_start)); +} + +void Scenario::RunUntil(TimeDelta target_time_since_start, + TimeDelta check_interval, + std::function<bool()> exit_function) { + if (start_time_.IsInfinite()) + Start(); + while (check_interval >= TimeUntilTarget(target_time_since_start)) { + network_manager_.time_controller()->AdvanceTime(check_interval); + if (exit_function()) + return; + } + network_manager_.time_controller()->AdvanceTime( + TimeUntilTarget(target_time_since_start)); +} + +void Scenario::Start() { + start_time_ = clock_->CurrentTime(); + for (auto& stream_pair : video_streams_) + stream_pair->receive()->Start(); + for (auto& stream_pair : audio_streams_) + stream_pair->receive()->Start(); + for (auto& stream_pair : video_streams_) { + if (stream_pair->config_.autostart) { + stream_pair->send()->Start(); + } + } + for (auto& stream_pair : audio_streams_) { + if (stream_pair->config_.autostart) { + stream_pair->send()->Start(); + } + } +} + +void Scenario::Stop() { + RTC_DCHECK(start_time_.IsFinite()); + for (auto& stream_pair : video_streams_) { + stream_pair->send()->Stop(); + } + for (auto& stream_pair : audio_streams_) + stream_pair->send()->Stop(); + for (auto& stream_pair : video_streams_) + stream_pair->receive()->Stop(); + for (auto& stream_pair : audio_streams_) + stream_pair->receive()->Stop(); + start_time_ = Timestamp::PlusInfinity(); +} + +Timestamp Scenario::Now() { + return clock_->CurrentTime(); +} + +TimeDelta Scenario::TimeSinceStart() { + if (start_time_.IsInfinite()) + return TimeDelta::Zero(); + return Now() - start_time_; +} + +TimeDelta Scenario::TimeUntilTarget(TimeDelta target_time_offset) { + return target_time_offset - TimeSinceStart(); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/scenario.h b/third_party/libwebrtc/test/scenario/scenario.h new file mode 100644 index 0000000000..cad9210002 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/scenario.h @@ -0,0 +1,189 @@ +/* + * Copyright 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_SCENARIO_SCENARIO_H_ +#define TEST_SCENARIO_SCENARIO_H_ +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "api/task_queue/task_queue_base.h" +#include "api/test/time_controller.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "test/gtest.h" +#include "test/logging/log_writer.h" +#include "test/network/network_emulation_manager.h" +#include "test/scenario/audio_stream.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" +#include "test/scenario/video_stream.h" + +namespace webrtc { +namespace test { +// Scenario is a class owning everything for a test scenario. It creates and +// holds network nodes, call clients and media streams. It also provides methods +// for changing behavior at runtime. Since it always keeps ownership of the +// created components, it generally returns non-owning pointers. It maintains +// the life of its objects until it is destroyed. +// For methods accepting configuration structs, a modifier function interface is +// generally provided. This allows simple partial overriding of the default +// configuration. +class Scenario { + public: + Scenario(); + explicit Scenario(const testing::TestInfo* test_info); + explicit Scenario(absl::string_view file_name); + Scenario(absl::string_view file_name, bool real_time); + Scenario(std::unique_ptr<LogWriterFactoryInterface> log_writer_manager, + bool real_time); + + ~Scenario(); + + Scenario(const Scenario&) = delete; + Scenario& operator=(const Scenario&) = delete; + + NetworkEmulationManagerImpl* net() { return &network_manager_; } + + EmulatedNetworkNode* CreateSimulationNode(NetworkSimulationConfig config); + EmulatedNetworkNode* CreateSimulationNode( + std::function<void(NetworkSimulationConfig*)> config_modifier); + + SimulationNode* CreateMutableSimulationNode(NetworkSimulationConfig config); + SimulationNode* CreateMutableSimulationNode( + std::function<void(NetworkSimulationConfig*)> config_modifier); + + CallClient* CreateClient(absl::string_view name, CallClientConfig config); + CallClient* CreateClient( + absl::string_view name, + std::function<void(CallClientConfig*)> config_modifier); + + CallClientPair* CreateRoutes(CallClient* first, + std::vector<EmulatedNetworkNode*> send_link, + CallClient* second, + std::vector<EmulatedNetworkNode*> return_link); + + CallClientPair* CreateRoutes(CallClient* first, + std::vector<EmulatedNetworkNode*> send_link, + DataSize first_overhead, + CallClient* second, + std::vector<EmulatedNetworkNode*> return_link, + DataSize second_overhead); + + void ChangeRoute(std::pair<CallClient*, CallClient*> clients, + std::vector<EmulatedNetworkNode*> over_nodes); + + void ChangeRoute(std::pair<CallClient*, CallClient*> clients, + std::vector<EmulatedNetworkNode*> over_nodes, + DataSize overhead); + + VideoStreamPair* CreateVideoStream( + std::pair<CallClient*, CallClient*> clients, + std::function<void(VideoStreamConfig*)> config_modifier); + VideoStreamPair* CreateVideoStream( + std::pair<CallClient*, CallClient*> clients, + VideoStreamConfig config); + + AudioStreamPair* CreateAudioStream( + std::pair<CallClient*, CallClient*> clients, + std::function<void(AudioStreamConfig*)> config_modifier); + AudioStreamPair* CreateAudioStream( + std::pair<CallClient*, CallClient*> clients, + AudioStreamConfig config); + + // Runs the provided function with a fixed interval. For real time tests, + // `function` starts being called after `interval` from the call to Every(). + void Every(TimeDelta interval, absl::AnyInvocable<void(TimeDelta)> function); + void Every(TimeDelta interval, absl::AnyInvocable<void()> function); + + // Runs the provided function on the internal task queue. This ensure that + // it's run on the main thread for simulated time tests. + void Post(absl::AnyInvocable<void() &&> function); + + // Runs the provided function after given duration has passed. For real time + // tests, `function` is called after `target_time_since_start` from the call + // to Every(). + void At(TimeDelta offset, absl::AnyInvocable<void() &&> function); + + // Sends a packet over the nodes and runs `action` when it has been delivered. + void NetworkDelayedAction(std::vector<EmulatedNetworkNode*> over_nodes, + size_t packet_size, + std::function<void()> action); + + // Runs the scenario for the given time. + void RunFor(TimeDelta duration); + // Runs the scenario until `target_time_since_start`. + void RunUntil(TimeDelta target_time_since_start); + // Runs the scenario until `target_time_since_start` or `exit_function` + // returns true. `exit_function` is polled after each `check_interval` has + // passed. + void RunUntil(TimeDelta target_time_since_start, + TimeDelta check_interval, + std::function<bool()> exit_function); + void Start(); + void Stop(); + + // Triggers sending of dummy packets over the given nodes. + void TriggerPacketBurst(std::vector<EmulatedNetworkNode*> over_nodes, + size_t num_packets, + size_t packet_size); + + ColumnPrinter TimePrinter(); + StatesPrinter* CreatePrinter(absl::string_view name, + TimeDelta interval, + std::vector<ColumnPrinter> printers); + + // Returns the current time. + Timestamp Now(); + // Return the duration of the current session so far. + TimeDelta TimeSinceStart(); + + std::unique_ptr<RtcEventLogOutput> GetLogWriter(absl::string_view name) { + if (!log_writer_factory_ || name.empty()) + return nullptr; + return log_writer_factory_->Create(name); + } + std::unique_ptr<LogWriterFactoryInterface> GetLogWriterFactory( + absl::string_view name) { + if (!log_writer_factory_ || name.empty()) + return nullptr; + return std::make_unique<LogWriterFactoryAddPrefix>( + log_writer_factory_.get(), name); + } + + private: + TimeDelta TimeUntilTarget(TimeDelta target_time_offset); + + const std::unique_ptr<LogWriterFactoryInterface> log_writer_factory_; + NetworkEmulationManagerImpl network_manager_; + Clock* clock_; + + std::vector<std::unique_ptr<CallClient>> clients_; + std::vector<std::unique_ptr<CallClientPair>> client_pairs_; + std::vector<std::unique_ptr<VideoStreamPair>> video_streams_; + std::vector<std::unique_ptr<AudioStreamPair>> audio_streams_; + std::vector<std::unique_ptr<SimulationNode>> simulation_nodes_; + std::vector<std::unique_ptr<StatesPrinter>> printers_; + + rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory_; + rtc::scoped_refptr<AudioEncoderFactory> audio_encoder_factory_; + + Timestamp start_time_ = Timestamp::PlusInfinity(); + // Defined last so it's destroyed first. + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_SCENARIO_H_ diff --git a/third_party/libwebrtc/test/scenario/scenario_config.cc b/third_party/libwebrtc/test/scenario/scenario_config.cc new file mode 100644 index 0000000000..3f8a70a162 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/scenario_config.cc @@ -0,0 +1,47 @@ +/* + * Copyright 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/scenario/scenario_config.h" + +namespace webrtc { +namespace test { + +TransportControllerConfig::Rates::Rates() = default; +TransportControllerConfig::Rates::Rates( + const TransportControllerConfig::Rates&) = default; +TransportControllerConfig::Rates::~Rates() = default; + +PacketStreamConfig::PacketStreamConfig() = default; +PacketStreamConfig::PacketStreamConfig(const PacketStreamConfig&) = default; +PacketStreamConfig::~PacketStreamConfig() = default; + +VideoStreamConfig::Encoder::Encoder() = default; +VideoStreamConfig::Encoder::Encoder(const VideoStreamConfig::Encoder&) = + default; +VideoStreamConfig::Encoder::~Encoder() = default; + +VideoStreamConfig::Stream::Stream() = default; +VideoStreamConfig::Stream::Stream(const VideoStreamConfig::Stream&) = default; +VideoStreamConfig::Stream::~Stream() = default; + +AudioStreamConfig::AudioStreamConfig() = default; +AudioStreamConfig::AudioStreamConfig(const AudioStreamConfig&) = default; +AudioStreamConfig::~AudioStreamConfig() = default; + +AudioStreamConfig::Encoder::Encoder() = default; +AudioStreamConfig::Encoder::Encoder(const AudioStreamConfig::Encoder&) = + default; +AudioStreamConfig::Encoder::~Encoder() = default; + +AudioStreamConfig::Stream::Stream() = default; +AudioStreamConfig::Stream::Stream(const AudioStreamConfig::Stream&) = default; +AudioStreamConfig::Stream::~Stream() = default; + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/scenario_config.h b/third_party/libwebrtc/test/scenario/scenario_config.h new file mode 100644 index 0000000000..9ce99401d7 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/scenario_config.h @@ -0,0 +1,231 @@ +/* + * Copyright 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_SCENARIO_SCENARIO_CONFIG_H_ +#define TEST_SCENARIO_SCENARIO_CONFIG_H_ + +#include <stddef.h> + +#include <string> + +#include "absl/types/optional.h" +#include "api/fec_controller.h" +#include "api/rtp_parameters.h" +#include "api/test/frame_generator_interface.h" +#include "api/transport/network_control.h" +#include "api/units/data_rate.h" +#include "api/units/data_size.h" +#include "api/units/time_delta.h" +#include "api/video/video_codec_type.h" +#include "api/video_codecs/scalability_mode.h" +#include "test/scenario/performance_stats.h" + +namespace webrtc { +namespace test { +struct PacketOverhead { + static constexpr size_t kSrtp = 10; + static constexpr size_t kStun = 4; + // TURN messages can be sent either with or without an establieshed channel. + // In the latter case, a TURN Send/Data Indication is sent which has + // significantly more overhead. + static constexpr size_t kTurnChannelMessage = 4; + static constexpr size_t kTurnIndicationMessage = 36; + static constexpr size_t kDefault = kSrtp; +}; +struct TransportControllerConfig { + struct Rates { + Rates(); + Rates(const Rates&); + ~Rates(); + DataRate min_rate = DataRate::KilobitsPerSec(30); + DataRate max_rate = DataRate::KilobitsPerSec(3000); + DataRate start_rate = DataRate::KilobitsPerSec(300); + } rates; + NetworkControllerFactoryInterface* cc_factory = nullptr; + TimeDelta state_log_interval = TimeDelta::Millis(100); +}; + +struct CallClientConfig { + TransportControllerConfig transport; + // Allows the pacer to send out multiple packets in a burst. + // The number of bites that can be sent in one burst is pacer_burst_interval * + // current bwe. 40ms is the default Chrome setting. + TimeDelta pacer_burst_interval = TimeDelta::Millis(40); + const FieldTrialsView* field_trials = nullptr; +}; + +struct PacketStreamConfig { + PacketStreamConfig(); + PacketStreamConfig(const PacketStreamConfig&); + ~PacketStreamConfig(); + int frame_rate = 30; + DataRate max_data_rate = DataRate::Infinity(); + DataSize max_packet_size = DataSize::Bytes(1400); + DataSize min_frame_size = DataSize::Bytes(100); + double keyframe_multiplier = 1; + DataSize packet_overhead = DataSize::Bytes(PacketOverhead::kDefault); +}; + +struct VideoStreamConfig { + bool autostart = true; + struct Source { + enum Capture { + kGenerator, + kVideoFile, + kGenerateSlides, + kImageSlides, + // Support for explicit frame triggers should be added here if needed. + } capture = Capture::kGenerator; + struct Slides { + TimeDelta change_interval = TimeDelta::Seconds(10); + struct Generator { + int width = 1600; + int height = 1200; + } generator; + struct Images { + struct Crop { + TimeDelta scroll_duration = TimeDelta::Seconds(0); + absl::optional<int> width; + absl::optional<int> height; + } crop; + int width = 1850; + int height = 1110; + std::vector<std::string> paths = { + "web_screenshot_1850_1110", + "presentation_1850_1110", + "photo_1850_1110", + "difficult_photo_1850_1110", + }; + } images; + } slides; + struct Generator { + using PixelFormat = FrameGeneratorInterface::OutputType; + PixelFormat pixel_format = PixelFormat::kI420; + int width = 320; + int height = 180; + } generator; + struct VideoFile { + std::string name; + // Must be set to width and height of the source video file. + int width = 0; + int height = 0; + } video_file; + int framerate = 30; + } source; + struct Encoder { + Encoder(); + Encoder(const Encoder&); + ~Encoder(); + enum class ContentType { + kVideo, + kScreen, + } content_type = ContentType::kVideo; + enum Implementation { kFake, kSoftware, kHardware } implementation = kFake; + struct Fake { + DataRate max_rate = DataRate::Infinity(); + } fake; + + using Codec = VideoCodecType; + Codec codec = Codec::kVideoCodecGeneric; + absl::optional<DataRate> max_data_rate; + absl::optional<DataRate> min_data_rate; + absl::optional<int> max_framerate; + // Counted in frame count. + absl::optional<int> key_frame_interval = 3000; + bool frame_dropping = true; + struct SingleLayer { + bool denoising = true; + bool automatic_scaling = true; + } single; + std::vector<webrtc::ScalabilityMode> simulcast_streams = { + webrtc::ScalabilityMode::kL1T1}; + + DegradationPreference degradation_preference = + DegradationPreference::MAINTAIN_FRAMERATE; + bool suspend_below_min_bitrate = false; + } encoder; + struct Stream { + Stream(); + Stream(const Stream&); + ~Stream(); + bool abs_send_time = false; + bool packet_feedback = true; + bool use_rtx = true; + DataRate pad_to_rate = DataRate::Zero(); + TimeDelta nack_history_time = TimeDelta::Millis(1000); + bool use_flexfec = false; + bool use_ulpfec = false; + FecControllerFactoryInterface* fec_controller_factory = nullptr; + } stream; + struct Rendering { + enum Type { kFake } type = kFake; + std::string sync_group; + } render; + struct Hooks { + std::vector<std::function<void(const VideoFramePair&)>> frame_pair_handlers; + } hooks; +}; + +struct AudioStreamConfig { + AudioStreamConfig(); + AudioStreamConfig(const AudioStreamConfig&); + ~AudioStreamConfig(); + bool autostart = true; + struct Source { + int channels = 1; + } source; + bool network_adaptation = false; + struct NetworkAdaptation { + struct FrameLength { + double min_packet_loss_for_decrease = 0; + double max_packet_loss_for_increase = 1; + DataRate min_rate_for_20_ms = DataRate::Zero(); + DataRate max_rate_for_60_ms = DataRate::Infinity(); + DataRate min_rate_for_60_ms = DataRate::Zero(); + DataRate max_rate_for_120_ms = DataRate::Infinity(); + } frame; + std::string binary_proto; + } adapt; + struct Encoder { + Encoder(); + Encoder(const Encoder&); + ~Encoder(); + bool allocate_bitrate = false; + bool enable_dtx = false; + DataRate fixed_rate = DataRate::KilobitsPerSec(32); + // Overrides fixed rate. + absl::optional<DataRate> min_rate; + absl::optional<DataRate> max_rate; + TimeDelta initial_frame_length = TimeDelta::Millis(20); + } encoder; + struct Stream { + Stream(); + Stream(const Stream&); + ~Stream(); + bool abs_send_time = true; + bool in_bandwidth_estimation = true; + } stream; + struct Rendering { + std::string sync_group; + } render; +}; + +// TODO(srte): Merge this with BuiltInNetworkBehaviorConfig. +struct NetworkSimulationConfig { + DataRate bandwidth = DataRate::Infinity(); + TimeDelta delay = TimeDelta::Zero(); + TimeDelta delay_std_dev = TimeDelta::Zero(); + double loss_rate = 0; + absl::optional<int> packet_queue_length_limit; + DataSize packet_overhead = DataSize::Zero(); +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_SCENARIO_CONFIG_H_ diff --git a/third_party/libwebrtc/test/scenario/scenario_unittest.cc b/third_party/libwebrtc/test/scenario/scenario_unittest.cc new file mode 100644 index 0000000000..6861151a2d --- /dev/null +++ b/third_party/libwebrtc/test/scenario/scenario_unittest.cc @@ -0,0 +1,196 @@ +/* + * Copyright 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/scenario/scenario.h" + +#include <atomic> + +#include "api/test/network_emulation/create_cross_traffic.h" +#include "api/test/network_emulation/cross_traffic.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/logging/memory_log_writer.h" +#include "test/scenario/stats_collection.h" + +namespace webrtc { +namespace test { +TEST(ScenarioTest, StartsAndStopsWithoutErrors) { + std::atomic<bool> packet_received(false); + std::atomic<bool> bitrate_changed(false); + Scenario s; + CallClientConfig call_client_config; + call_client_config.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + auto* alice = s.CreateClient("alice", call_client_config); + auto* bob = s.CreateClient("bob", call_client_config); + NetworkSimulationConfig network_config; + auto alice_net = s.CreateSimulationNode(network_config); + auto bob_net = s.CreateSimulationNode(network_config); + auto route = s.CreateRoutes(alice, {alice_net}, bob, {bob_net}); + + VideoStreamConfig video_stream_config; + s.CreateVideoStream(route->forward(), video_stream_config); + s.CreateVideoStream(route->reverse(), video_stream_config); + + AudioStreamConfig audio_stream_config; + audio_stream_config.encoder.min_rate = DataRate::KilobitsPerSec(6); + audio_stream_config.encoder.max_rate = DataRate::KilobitsPerSec(64); + audio_stream_config.encoder.allocate_bitrate = true; + audio_stream_config.stream.in_bandwidth_estimation = false; + s.CreateAudioStream(route->forward(), audio_stream_config); + s.CreateAudioStream(route->reverse(), audio_stream_config); + + RandomWalkConfig cross_traffic_config; + s.net()->StartCrossTraffic(CreateRandomWalkCrossTraffic( + s.net()->CreateCrossTrafficRoute({alice_net}), cross_traffic_config)); + + s.NetworkDelayedAction({alice_net, bob_net}, 100, + [&packet_received] { packet_received = true; }); + s.Every(TimeDelta::Millis(10), [alice, bob, &bitrate_changed] { + if (alice->GetStats().send_bandwidth_bps != 300000 && + bob->GetStats().send_bandwidth_bps != 300000) + bitrate_changed = true; + }); + s.RunUntil(TimeDelta::Seconds(2), TimeDelta::Millis(5), + [&bitrate_changed, &packet_received] { + return packet_received && bitrate_changed; + }); + EXPECT_TRUE(packet_received); + EXPECT_TRUE(bitrate_changed); +} +namespace { +void SetupVideoCall(Scenario& s, VideoQualityAnalyzer* analyzer) { + CallClientConfig call_config; + auto* alice = s.CreateClient("alice", call_config); + auto* bob = s.CreateClient("bob", call_config); + NetworkSimulationConfig network_config; + network_config.bandwidth = DataRate::KilobitsPerSec(1000); + network_config.delay = TimeDelta::Millis(50); + auto alice_net = s.CreateSimulationNode(network_config); + auto bob_net = s.CreateSimulationNode(network_config); + auto route = s.CreateRoutes(alice, {alice_net}, bob, {bob_net}); + VideoStreamConfig video; + if (analyzer) { + video.source.capture = VideoStreamConfig::Source::Capture::kVideoFile; + video.source.video_file.name = "foreman_cif"; + video.source.video_file.width = 352; + video.source.video_file.height = 288; + video.source.framerate = 30; + video.encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + video.encoder.implementation = + VideoStreamConfig::Encoder::Implementation::kSoftware; + video.hooks.frame_pair_handlers = {analyzer->Handler()}; + } + s.CreateVideoStream(route->forward(), video); + s.CreateAudioStream(route->forward(), AudioStreamConfig()); +} +} // namespace + +TEST(ScenarioTest, SimTimeEncoding) { + VideoQualityAnalyzerConfig analyzer_config; + analyzer_config.psnr_coverage = 0.1; + VideoQualityAnalyzer analyzer(analyzer_config); + { + Scenario s("scenario/encode_sim", false); + SetupVideoCall(s, &analyzer); + s.RunFor(TimeDelta::Seconds(2)); + } + // Regression tests based on previous runs. + EXPECT_EQ(analyzer.stats().lost_count, 0); + EXPECT_NEAR(analyzer.stats().psnr_with_freeze.Mean(), 38, 5); +} + +// TODO(bugs.webrtc.org/10515): Remove this when performance has been improved. +#if defined(WEBRTC_IOS) && defined(WEBRTC_ARCH_ARM64) && !defined(NDEBUG) +#define MAYBE_RealTimeEncoding DISABLED_RealTimeEncoding +#else +#define MAYBE_RealTimeEncoding RealTimeEncoding +#endif +TEST(ScenarioTest, MAYBE_RealTimeEncoding) { + VideoQualityAnalyzerConfig analyzer_config; + analyzer_config.psnr_coverage = 0.1; + VideoQualityAnalyzer analyzer(analyzer_config); + { + Scenario s("scenario/encode_real", true); + SetupVideoCall(s, &analyzer); + s.RunFor(TimeDelta::Seconds(2)); + } + // Regression tests based on previous runs. + EXPECT_LT(analyzer.stats().lost_count, 2); + // This far below expected but ensures that we get something. + EXPECT_GT(analyzer.stats().psnr_with_freeze.Mean(), 10); +} + +TEST(ScenarioTest, SimTimeFakeing) { + Scenario s("scenario/encode_sim", false); + SetupVideoCall(s, nullptr); + s.RunFor(TimeDelta::Seconds(2)); +} + +TEST(ScenarioTest, WritesToRtcEventLog) { + MemoryLogStorage storage; + { + Scenario s(storage.CreateFactory(), false); + SetupVideoCall(s, nullptr); + s.RunFor(TimeDelta::Seconds(1)); + } + auto logs = storage.logs(); + // We expect that a rtc event log has been created and that it has some data. + EXPECT_GE(storage.logs().at("alice.rtc.dat").size(), 1u); +} + +TEST(ScenarioTest, + RetransmitsVideoPacketsInAudioAndVideoCallWithSendSideBweAndLoss) { + // Make sure audio packets are included in transport feedback. + test::ScopedFieldTrials override_field_trials( + "WebRTC-Audio-ABWENoTWCC/Disabled/"); + + Scenario s; + CallClientConfig call_client_config; + call_client_config.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + auto* alice = s.CreateClient("alice", call_client_config); + auto* bob = s.CreateClient("bob", call_client_config); + NetworkSimulationConfig network_config; + // Add some loss and delay. + network_config.delay = TimeDelta::Millis(200); + network_config.loss_rate = 0.05; + auto alice_net = s.CreateSimulationNode(network_config); + auto bob_net = s.CreateSimulationNode(network_config); + auto route = s.CreateRoutes(alice, {alice_net}, bob, {bob_net}); + + // First add an audio stream, then a video stream. + // Needed to make sure audio RTP module is selected first when sending + // transport feedback message. + AudioStreamConfig audio_stream_config; + audio_stream_config.encoder.min_rate = DataRate::KilobitsPerSec(6); + audio_stream_config.encoder.max_rate = DataRate::KilobitsPerSec(64); + audio_stream_config.encoder.allocate_bitrate = true; + audio_stream_config.stream.in_bandwidth_estimation = true; + s.CreateAudioStream(route->forward(), audio_stream_config); + s.CreateAudioStream(route->reverse(), audio_stream_config); + + VideoStreamConfig video_stream_config; + auto video = s.CreateVideoStream(route->forward(), video_stream_config); + s.CreateVideoStream(route->reverse(), video_stream_config); + + // Run for 10 seconds. + s.RunFor(TimeDelta::Seconds(10)); + // Make sure retransmissions have happened. + int retransmit_packets = 0; + + VideoSendStream::Stats stats; + alice->SendTask([&]() { stats = video->send()->GetStats(); }); + + for (const auto& substream : stats.substreams) { + retransmit_packets += substream.second.rtp_stats.retransmitted.packets; + } + EXPECT_GT(retransmit_packets, 0); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/stats_collection.cc b/third_party/libwebrtc/test/scenario/stats_collection.cc new file mode 100644 index 0000000000..e32696de71 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/stats_collection.cc @@ -0,0 +1,190 @@ +/* + * Copyright 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/scenario/stats_collection.h" + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/memory_usage.h" +#include "rtc_base/thread.h" + +namespace webrtc { +namespace test { + +VideoQualityAnalyzer::VideoQualityAnalyzer( + VideoQualityAnalyzerConfig config, + std::unique_ptr<RtcEventLogOutput> writer) + : config_(config), writer_(std::move(writer)) { + if (writer_) { + PrintHeaders(); + } +} + +VideoQualityAnalyzer::~VideoQualityAnalyzer() = default; + +void VideoQualityAnalyzer::PrintHeaders() { + writer_->Write( + "capture_time render_time capture_width capture_height render_width " + "render_height psnr\n"); +} + +std::function<void(const VideoFramePair&)> VideoQualityAnalyzer::Handler() { + return [this](VideoFramePair pair) { HandleFramePair(pair); }; +} + +void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample, double psnr) { + layer_analyzers_[sample.layer_id].HandleFramePair(sample, psnr, + writer_.get()); + cached_.reset(); +} + +void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample) { + double psnr = NAN; + if (sample.decoded) + psnr = I420PSNR(*sample.captured->ToI420(), *sample.decoded->ToI420()); + + if (config_.thread) { + config_.thread->PostTask( + [this, sample, psnr] { HandleFramePair(std::move(sample), psnr); }); + } else { + HandleFramePair(std::move(sample), psnr); + } +} + +std::vector<VideoQualityStats> VideoQualityAnalyzer::layer_stats() const { + std::vector<VideoQualityStats> res; + for (auto& layer : layer_analyzers_) + res.push_back(layer.second.stats_); + return res; +} + +VideoQualityStats& VideoQualityAnalyzer::stats() { + if (!cached_) { + cached_ = VideoQualityStats(); + for (auto& layer : layer_analyzers_) + cached_->AddStats(layer.second.stats_); + } + return *cached_; +} + +void VideoLayerAnalyzer::HandleFramePair(VideoFramePair sample, + double psnr, + RtcEventLogOutput* writer) { + RTC_CHECK(sample.captured); + HandleCapturedFrame(sample); + if (!sample.decoded) { + // Can only happen in the beginning of a call or if the resolution is + // reduced. Otherwise we will detect a freeze. + ++stats_.lost_count; + ++skip_count_; + } else { + stats_.psnr_with_freeze.AddSample(psnr); + if (sample.repeated) { + ++stats_.freeze_count; + ++skip_count_; + } else { + stats_.psnr.AddSample(psnr); + HandleRenderedFrame(sample); + } + } + if (writer) { + LogWriteFormat(writer, "%.3f %.3f %.3f %i %i %i %i %.3f\n", + sample.capture_time.seconds<double>(), + sample.render_time.seconds<double>(), + sample.captured->width(), sample.captured->height(), + sample.decoded ? sample.decoded->width() : 0, + sample.decoded ? sample.decoded->height() : 0, psnr); + } +} + +void VideoLayerAnalyzer::HandleCapturedFrame(const VideoFramePair& sample) { + stats_.capture.AddFrameInfo(*sample.captured, sample.capture_time); + if (last_freeze_time_.IsInfinite()) + last_freeze_time_ = sample.capture_time; +} + +void VideoLayerAnalyzer::HandleRenderedFrame(const VideoFramePair& sample) { + stats_.capture_to_decoded_delay.AddSample(sample.decoded_time - + sample.capture_time); + stats_.end_to_end_delay.AddSample(sample.render_time - sample.capture_time); + stats_.render.AddFrameInfo(*sample.decoded, sample.render_time); + stats_.skipped_between_rendered.AddSample(skip_count_); + skip_count_ = 0; + + if (last_render_time_.IsFinite()) { + RTC_DCHECK(sample.render_time.IsFinite()); + TimeDelta render_interval = sample.render_time - last_render_time_; + TimeDelta mean_interval = stats_.render.frames.interval().Mean(); + if (render_interval > TimeDelta::Millis(150) + mean_interval || + render_interval > 3 * mean_interval) { + stats_.freeze_duration.AddSample(render_interval); + stats_.time_between_freezes.AddSample(last_render_time_ - + last_freeze_time_); + last_freeze_time_ = sample.render_time; + } + } + last_render_time_ = sample.render_time; +} + +void CallStatsCollector::AddStats(Call::Stats sample) { + if (sample.send_bandwidth_bps > 0) + stats_.target_rate.AddSampleBps(sample.send_bandwidth_bps); + if (sample.pacer_delay_ms > 0) + stats_.pacer_delay.AddSample(TimeDelta::Millis(sample.pacer_delay_ms)); + if (sample.rtt_ms > 0) + stats_.round_trip_time.AddSample(TimeDelta::Millis(sample.rtt_ms)); + stats_.memory_usage.AddSample(rtc::GetProcessResidentSizeBytes()); +} + +void AudioReceiveStatsCollector::AddStats( + AudioReceiveStreamInterface::Stats sample) { + stats_.expand_rate.AddSample(sample.expand_rate); + stats_.accelerate_rate.AddSample(sample.accelerate_rate); + stats_.jitter_buffer.AddSampleMs(sample.jitter_buffer_ms); +} + +void VideoSendStatsCollector::AddStats(VideoSendStream::Stats sample, + Timestamp at_time) { + // It's not certain that we yet have estimates for any of these stats. + // Check that they are positive before mixing them in. + if (sample.encode_frame_rate <= 0) + return; + + stats_.encode_frame_rate.AddSample(sample.encode_frame_rate); + stats_.encode_time.AddSampleMs(sample.avg_encode_time_ms); + stats_.encode_usage.AddSample(sample.encode_usage_percent / 100.0); + stats_.media_bitrate.AddSampleBps(sample.media_bitrate_bps); + + size_t fec_bytes = 0; + for (const auto& kv : sample.substreams) { + fec_bytes += kv.second.rtp_stats.fec.payload_bytes + + kv.second.rtp_stats.fec.padding_bytes; + } + if (last_update_.IsFinite()) { + auto fec_delta = DataSize::Bytes(fec_bytes - last_fec_bytes_); + auto time_delta = at_time - last_update_; + stats_.fec_bitrate.AddSample(fec_delta / time_delta); + } + last_fec_bytes_ = fec_bytes; + last_update_ = at_time; +} + +void VideoReceiveStatsCollector::AddStats( + VideoReceiveStreamInterface::Stats sample) { + if (sample.decode_ms > 0) + stats_.decode_time.AddSampleMs(sample.decode_ms); + if (sample.max_decode_ms > 0) + stats_.decode_time_max.AddSampleMs(sample.max_decode_ms); + if (sample.width > 0 && sample.height > 0) { + stats_.decode_pixels.AddSample(sample.width * sample.height); + stats_.resolution.AddSample(sample.height); + } +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/stats_collection.h b/third_party/libwebrtc/test/scenario/stats_collection.h new file mode 100644 index 0000000000..1f5d8daea7 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/stats_collection.h @@ -0,0 +1,110 @@ +/* + * Copyright 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_SCENARIO_STATS_COLLECTION_H_ +#define TEST_SCENARIO_STATS_COLLECTION_H_ + +#include <map> +#include <memory> + +#include "absl/types/optional.h" +#include "call/call.h" +#include "rtc_base/thread.h" +#include "test/logging/log_writer.h" +#include "test/scenario/performance_stats.h" + +namespace webrtc { +namespace test { + +struct VideoQualityAnalyzerConfig { + double psnr_coverage = 1; + rtc::Thread* thread = nullptr; +}; + +class VideoLayerAnalyzer { + public: + void HandleCapturedFrame(const VideoFramePair& sample); + void HandleRenderedFrame(const VideoFramePair& sample); + void HandleFramePair(VideoFramePair sample, + double psnr, + RtcEventLogOutput* writer); + VideoQualityStats stats_; + Timestamp last_capture_time_ = Timestamp::MinusInfinity(); + Timestamp last_render_time_ = Timestamp::MinusInfinity(); + Timestamp last_freeze_time_ = Timestamp::MinusInfinity(); + int skip_count_ = 0; +}; + +class VideoQualityAnalyzer { + public: + explicit VideoQualityAnalyzer( + VideoQualityAnalyzerConfig config = VideoQualityAnalyzerConfig(), + std::unique_ptr<RtcEventLogOutput> writer = nullptr); + ~VideoQualityAnalyzer(); + void HandleFramePair(VideoFramePair sample); + std::vector<VideoQualityStats> layer_stats() const; + VideoQualityStats& stats(); + void PrintHeaders(); + void PrintFrameInfo(const VideoFramePair& sample); + std::function<void(const VideoFramePair&)> Handler(); + + private: + void HandleFramePair(VideoFramePair sample, double psnr); + const VideoQualityAnalyzerConfig config_; + std::map<int, VideoLayerAnalyzer> layer_analyzers_; + const std::unique_ptr<RtcEventLogOutput> writer_; + absl::optional<VideoQualityStats> cached_; +}; + +class CallStatsCollector { + public: + void AddStats(Call::Stats sample); + CollectedCallStats& stats() { return stats_; } + + private: + CollectedCallStats stats_; +}; +class AudioReceiveStatsCollector { + public: + void AddStats(AudioReceiveStreamInterface::Stats sample); + CollectedAudioReceiveStats& stats() { return stats_; } + + private: + CollectedAudioReceiveStats stats_; +}; +class VideoSendStatsCollector { + public: + void AddStats(VideoSendStream::Stats sample, Timestamp at_time); + CollectedVideoSendStats& stats() { return stats_; } + + private: + CollectedVideoSendStats stats_; + Timestamp last_update_ = Timestamp::MinusInfinity(); + size_t last_fec_bytes_ = 0; +}; +class VideoReceiveStatsCollector { + public: + void AddStats(VideoReceiveStreamInterface::Stats sample); + CollectedVideoReceiveStats& stats() { return stats_; } + + private: + CollectedVideoReceiveStats stats_; +}; + +struct CallStatsCollectors { + CallStatsCollector call; + AudioReceiveStatsCollector audio_receive; + VideoSendStatsCollector video_send; + VideoReceiveStatsCollector video_receive; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_STATS_COLLECTION_H_ diff --git a/third_party/libwebrtc/test/scenario/stats_collection_unittest.cc b/third_party/libwebrtc/test/scenario/stats_collection_unittest.cc new file mode 100644 index 0000000000..9f46f10073 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/stats_collection_unittest.cc @@ -0,0 +1,114 @@ +/* + * Copyright 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/scenario/stats_collection.h" + +#include "test/gtest.h" +#include "test/scenario/scenario.h" + +namespace webrtc { +namespace test { +namespace { +void CreateAnalyzedStream(Scenario* s, + NetworkSimulationConfig network_config, + VideoQualityAnalyzer* analyzer, + CallStatsCollectors* collectors) { + VideoStreamConfig config; + config.encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + config.encoder.implementation = + VideoStreamConfig::Encoder::Implementation::kSoftware; + config.hooks.frame_pair_handlers = {analyzer->Handler()}; + auto* caller = s->CreateClient("caller", CallClientConfig()); + auto* callee = s->CreateClient("callee", CallClientConfig()); + auto route = + s->CreateRoutes(caller, {s->CreateSimulationNode(network_config)}, callee, + {s->CreateSimulationNode(NetworkSimulationConfig())}); + VideoStreamPair* video = s->CreateVideoStream(route->forward(), config); + auto* audio = s->CreateAudioStream(route->forward(), AudioStreamConfig()); + s->Every(TimeDelta::Seconds(1), [=] { + collectors->call.AddStats(caller->GetStats()); + + VideoSendStream::Stats send_stats; + caller->SendTask([&]() { send_stats = video->send()->GetStats(); }); + collectors->video_send.AddStats(send_stats, s->Now()); + + AudioReceiveStreamInterface::Stats receive_stats; + caller->SendTask([&]() { receive_stats = audio->receive()->GetStats(); }); + collectors->audio_receive.AddStats(receive_stats); + + // Querying the video stats from within the expected runtime environment + // (i.e. the TQ that belongs to the CallClient, not the Scenario TQ that + // we're currently on). + VideoReceiveStreamInterface::Stats video_receive_stats; + auto* video_stream = video->receive(); + callee->SendTask([&video_stream, &video_receive_stats]() { + video_receive_stats = video_stream->GetStats(); + }); + collectors->video_receive.AddStats(video_receive_stats); + }); +} +} // namespace + +TEST(ScenarioAnalyzerTest, PsnrIsHighWhenNetworkIsGood) { + VideoQualityAnalyzer analyzer; + CallStatsCollectors stats; + { + Scenario s; + NetworkSimulationConfig good_network; + good_network.bandwidth = DataRate::KilobitsPerSec(1000); + CreateAnalyzedStream(&s, good_network, &analyzer, &stats); + s.RunFor(TimeDelta::Seconds(3)); + } + // This is a change detecting test, the targets are based on previous runs and + // might change due to changes in configuration and encoder etc. The main + // purpose is to show how the stats can be used. To avoid being overly + // sensistive to change, the ranges are chosen to be quite large. + EXPECT_NEAR(analyzer.stats().psnr_with_freeze.Mean(), 43, 10); + EXPECT_NEAR(stats.call.stats().target_rate.Mean().kbps(), 700, 300); + EXPECT_NEAR(stats.video_send.stats().media_bitrate.Mean().kbps(), 500, 200); + EXPECT_NEAR(stats.video_receive.stats().resolution.Mean(), 180, 10); + EXPECT_NEAR(stats.audio_receive.stats().jitter_buffer.Mean().ms(), 40, 20); +} + +TEST(ScenarioAnalyzerTest, PsnrIsLowWhenNetworkIsBad) { + VideoQualityAnalyzer analyzer; + CallStatsCollectors stats; + { + Scenario s; + NetworkSimulationConfig bad_network; + bad_network.bandwidth = DataRate::KilobitsPerSec(100); + bad_network.loss_rate = 0.02; + CreateAnalyzedStream(&s, bad_network, &analyzer, &stats); + s.RunFor(TimeDelta::Seconds(3)); + } + // This is a change detecting test, the targets are based on previous runs and + // might change due to changes in configuration and encoder etc. + EXPECT_NEAR(analyzer.stats().psnr_with_freeze.Mean(), 20, 10); + EXPECT_NEAR(stats.call.stats().target_rate.Mean().kbps(), 75, 50); + EXPECT_NEAR(stats.video_send.stats().media_bitrate.Mean().kbps(), 70, 30); + EXPECT_NEAR(stats.video_receive.stats().resolution.Mean(), 180, 10); + EXPECT_NEAR(stats.audio_receive.stats().jitter_buffer.Mean().ms(), 250, 200); +} + +TEST(ScenarioAnalyzerTest, CountsCapturedButNotRendered) { + VideoQualityAnalyzer analyzer; + CallStatsCollectors stats; + { + Scenario s; + NetworkSimulationConfig long_delays; + long_delays.delay = TimeDelta::Seconds(5); + CreateAnalyzedStream(&s, long_delays, &analyzer, &stats); + // Enough time to send frames but not enough to deliver. + s.RunFor(TimeDelta::Millis(100)); + } + EXPECT_GE(analyzer.stats().capture.count, 1); + EXPECT_EQ(analyzer.stats().render.count, 0); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/video_frame_matcher.cc b/third_party/libwebrtc/test/scenario/video_frame_matcher.cc new file mode 100644 index 0000000000..dc8cd59756 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/video_frame_matcher.cc @@ -0,0 +1,188 @@ +/* + * Copyright 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/scenario/video_frame_matcher.h" + +#include <utility> + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" + +namespace webrtc { +namespace test { +namespace { +constexpr int kThumbWidth = 96; +constexpr int kThumbHeight = 96; +} // namespace + +VideoFrameMatcher::VideoFrameMatcher( + std::vector<std::function<void(const VideoFramePair&)> > + frame_pair_handlers) + : frame_pair_handlers_(std::move(frame_pair_handlers)), + task_queue_("VideoAnalyzer") {} + +VideoFrameMatcher::~VideoFrameMatcher() { + task_queue_.SendTask([this] { Finalize(); }); +} + +void VideoFrameMatcher::RegisterLayer(int layer_id) { + task_queue_.PostTask([this, layer_id] { layers_[layer_id] = VideoLayer(); }); +} + +void VideoFrameMatcher::OnCapturedFrame(const VideoFrame& frame, + Timestamp at_time) { + CapturedFrame captured; + captured.id = next_capture_id_++; + captured.capture_time = at_time; + captured.frame = frame.video_frame_buffer(); + captured.thumb = ScaleVideoFrameBuffer(*frame.video_frame_buffer()->ToI420(), + kThumbWidth, kThumbHeight), + task_queue_.PostTask([this, captured]() { + for (auto& layer : layers_) { + CapturedFrame copy = captured; + if (layer.second.last_decode && + layer.second.last_decode->frame->width() <= captured.frame->width()) { + copy.best_score = I420SSE(*captured.thumb->GetI420(), + *layer.second.last_decode->thumb->GetI420()); + copy.best_decode = layer.second.last_decode; + } + layer.second.captured_frames.push_back(std::move(copy)); + } + }); +} + +void VideoFrameMatcher::OnDecodedFrame(const VideoFrame& frame, + int layer_id, + Timestamp render_time, + Timestamp at_time) { + rtc::scoped_refptr<DecodedFrame> decoded(new DecodedFrame{}); + decoded->decoded_time = at_time; + decoded->render_time = render_time; + decoded->frame = frame.video_frame_buffer(); + decoded->thumb = ScaleVideoFrameBuffer(*frame.video_frame_buffer()->ToI420(), + kThumbWidth, kThumbHeight); + + task_queue_.PostTask([this, decoded, layer_id] { + auto& layer = layers_[layer_id]; + decoded->id = layer.next_decoded_id++; + layer.last_decode = decoded; + for (auto& captured : layer.captured_frames) { + // We can't match with a smaller capture. + if (captured.frame->width() < decoded->frame->width()) { + captured.matched = true; + continue; + } + double score = + I420SSE(*captured.thumb->GetI420(), *decoded->thumb->GetI420()); + if (score < captured.best_score) { + captured.best_score = score; + captured.best_decode = decoded; + captured.matched = false; + } else { + captured.matched = true; + } + } + while (!layer.captured_frames.empty() && + layer.captured_frames.front().matched) { + HandleMatch(std::move(layer.captured_frames.front()), layer_id); + layer.captured_frames.pop_front(); + } + }); +} + +bool VideoFrameMatcher::Active() const { + return !frame_pair_handlers_.empty(); +} + +void VideoFrameMatcher::HandleMatch(VideoFrameMatcher::CapturedFrame captured, + int layer_id) { + VideoFramePair frame_pair; + frame_pair.layer_id = layer_id; + frame_pair.captured = captured.frame; + frame_pair.capture_id = captured.id; + frame_pair.capture_time = captured.capture_time; + if (captured.best_decode) { + frame_pair.decode_id = captured.best_decode->id; + frame_pair.decoded = captured.best_decode->frame; + frame_pair.decoded_time = captured.best_decode->decoded_time; + // We can't render frames before they have been decoded. + frame_pair.render_time = std::max(captured.best_decode->render_time, + captured.best_decode->decoded_time); + frame_pair.repeated = captured.best_decode->repeat_count++; + } + for (auto& handler : frame_pair_handlers_) + handler(frame_pair); +} + +void VideoFrameMatcher::Finalize() { + for (auto& layer : layers_) { + while (!layer.second.captured_frames.empty()) { + HandleMatch(std::move(layer.second.captured_frames.front()), layer.first); + layer.second.captured_frames.pop_front(); + } + } +} + +CapturedFrameTap::CapturedFrameTap(Clock* clock, VideoFrameMatcher* matcher) + : clock_(clock), matcher_(matcher) {} + +void CapturedFrameTap::OnFrame(const VideoFrame& frame) { + matcher_->OnCapturedFrame(frame, clock_->CurrentTime()); +} +void CapturedFrameTap::OnDiscardedFrame() { + discarded_count_++; +} + +ForwardingCapturedFrameTap::ForwardingCapturedFrameTap( + Clock* clock, + VideoFrameMatcher* matcher, + rtc::VideoSourceInterface<VideoFrame>* source) + : clock_(clock), matcher_(matcher), source_(source) {} + +void ForwardingCapturedFrameTap::OnFrame(const VideoFrame& frame) { + RTC_CHECK(sink_); + matcher_->OnCapturedFrame(frame, clock_->CurrentTime()); + sink_->OnFrame(frame); +} +void ForwardingCapturedFrameTap::OnDiscardedFrame() { + RTC_CHECK(sink_); + discarded_count_++; + sink_->OnDiscardedFrame(); +} + +void ForwardingCapturedFrameTap::AddOrUpdateSink( + VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) { + if (!sink_) + sink_ = sink; + RTC_DCHECK_EQ(sink_, sink); + source_->AddOrUpdateSink(this, wants); +} +void ForwardingCapturedFrameTap::RemoveSink( + VideoSinkInterface<VideoFrame>* sink) { + source_->RemoveSink(this); + sink_ = nullptr; +} + +DecodedFrameTap::DecodedFrameTap(Clock* clock, + VideoFrameMatcher* matcher, + int layer_id) + : clock_(clock), matcher_(matcher), layer_id_(layer_id) { + matcher_->RegisterLayer(layer_id_); +} + +void DecodedFrameTap::OnFrame(const VideoFrame& frame) { + matcher_->OnDecodedFrame(frame, layer_id_, + Timestamp::Millis(frame.render_time_ms()), + clock_->CurrentTime()); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/video_frame_matcher.h b/third_party/libwebrtc/test/scenario/video_frame_matcher.h new file mode 100644 index 0000000000..a3aa85447d --- /dev/null +++ b/third_party/libwebrtc/test/scenario/video_frame_matcher.h @@ -0,0 +1,134 @@ +/* + * Copyright 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_SCENARIO_VIDEO_FRAME_MATCHER_H_ +#define TEST_SCENARIO_VIDEO_FRAME_MATCHER_H_ + +#include <deque> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "api/video/video_source_interface.h" +#include "rtc_base/ref_counted_object.h" +#include "rtc_base/task_queue_for_test.h" +#include "system_wrappers/include/clock.h" +#include "test/scenario/performance_stats.h" + +namespace webrtc { +namespace test { + +class VideoFrameMatcher { + public: + explicit VideoFrameMatcher( + std::vector<std::function<void(const VideoFramePair&)>> + frame_pair_handlers); + ~VideoFrameMatcher(); + void RegisterLayer(int layer_id); + void OnCapturedFrame(const VideoFrame& frame, Timestamp at_time); + void OnDecodedFrame(const VideoFrame& frame, + int layer_id, + Timestamp render_time, + Timestamp at_time); + bool Active() const; + + private: + struct DecodedFrameBase { + int id; + Timestamp decoded_time = Timestamp::PlusInfinity(); + Timestamp render_time = Timestamp::PlusInfinity(); + rtc::scoped_refptr<VideoFrameBuffer> frame; + rtc::scoped_refptr<VideoFrameBuffer> thumb; + int repeat_count = 0; + }; + using DecodedFrame = rtc::FinalRefCountedObject<DecodedFrameBase>; + struct CapturedFrame { + int id; + Timestamp capture_time = Timestamp::PlusInfinity(); + rtc::scoped_refptr<VideoFrameBuffer> frame; + rtc::scoped_refptr<VideoFrameBuffer> thumb; + double best_score = INFINITY; + rtc::scoped_refptr<DecodedFrame> best_decode; + bool matched = false; + }; + struct VideoLayer { + int layer_id; + std::deque<CapturedFrame> captured_frames; + rtc::scoped_refptr<DecodedFrame> last_decode; + int next_decoded_id = 1; + }; + void HandleMatch(CapturedFrame captured, int layer_id); + void Finalize(); + int next_capture_id_ = 1; + std::vector<std::function<void(const VideoFramePair&)>> frame_pair_handlers_; + std::map<int, VideoLayer> layers_; + TaskQueueForTest task_queue_; +}; + +class CapturedFrameTap : public rtc::VideoSinkInterface<VideoFrame> { + public: + CapturedFrameTap(Clock* clock, VideoFrameMatcher* matcher); + CapturedFrameTap(CapturedFrameTap&) = delete; + CapturedFrameTap& operator=(CapturedFrameTap&) = delete; + + void OnFrame(const VideoFrame& frame) override; + void OnDiscardedFrame() override; + + private: + Clock* const clock_; + VideoFrameMatcher* const matcher_; + int discarded_count_ = 0; +}; + +class ForwardingCapturedFrameTap + : public rtc::VideoSinkInterface<VideoFrame>, + public rtc::VideoSourceInterface<VideoFrame> { + public: + ForwardingCapturedFrameTap(Clock* clock, + VideoFrameMatcher* matcher, + rtc::VideoSourceInterface<VideoFrame>* source); + ForwardingCapturedFrameTap(ForwardingCapturedFrameTap&) = delete; + ForwardingCapturedFrameTap& operator=(ForwardingCapturedFrameTap&) = delete; + + // VideoSinkInterface interface + void OnFrame(const VideoFrame& frame) override; + void OnDiscardedFrame() override; + + // VideoSourceInterface interface + void AddOrUpdateSink(VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) override; + void RemoveSink(VideoSinkInterface<VideoFrame>* sink) override; + + private: + Clock* const clock_; + VideoFrameMatcher* const matcher_; + rtc::VideoSourceInterface<VideoFrame>* const source_; + VideoSinkInterface<VideoFrame>* sink_ = nullptr; + int discarded_count_ = 0; +}; + +class DecodedFrameTap : public rtc::VideoSinkInterface<VideoFrame> { + public: + DecodedFrameTap(Clock* clock, VideoFrameMatcher* matcher, int layer_id); + // VideoSinkInterface interface + void OnFrame(const VideoFrame& frame) override; + + private: + Clock* const clock_; + VideoFrameMatcher* const matcher_; + int layer_id_; +}; +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_VIDEO_FRAME_MATCHER_H_ diff --git a/third_party/libwebrtc/test/scenario/video_stream.cc b/third_party/libwebrtc/test/scenario/video_stream.cc new file mode 100644 index 0000000000..8d627d8893 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/video_stream.cc @@ -0,0 +1,636 @@ +/* + * Copyright 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/scenario/video_stream.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "absl/strings/match.h" +#include "api/test/create_frame_generator.h" +#include "api/test/frame_generator_interface.h" +#include "api/test/video/function_video_encoder_factory.h" +#include "api/video/builtin_video_bitrate_allocator_factory.h" +#include "media/base/media_constants.h" +#include "media/engine/internal_decoder_factory.h" +#include "media/engine/internal_encoder_factory.h" +#include "media/engine/webrtc_video_engine.h" +#include "modules/video_coding/svc/scalability_mode_util.h" +#include "test/call_test.h" +#include "test/fake_encoder.h" +#include "test/scenario/hardware_codecs.h" +#include "test/testsupport/file_utils.h" +#include "video/config/encoder_stream_factory.h" + +namespace webrtc { +namespace test { +namespace { +enum : int { // The first valid value is 1. + kTransportSequenceNumberExtensionId = 1, + kAbsSendTimeExtensionId, + kVideoContentTypeExtensionId, + kVideoRotationRtpExtensionId, +}; + +constexpr int kDefaultMaxQp = cricket::WebRtcVideoChannel::kDefaultQpMax; +uint8_t CodecTypeToPayloadType(VideoCodecType codec_type) { + switch (codec_type) { + case VideoCodecType::kVideoCodecGeneric: + return CallTest::kFakeVideoSendPayloadType; + case VideoCodecType::kVideoCodecVP8: + return CallTest::kPayloadTypeVP8; + case VideoCodecType::kVideoCodecVP9: + return CallTest::kPayloadTypeVP9; + case VideoCodecType::kVideoCodecH264: + return CallTest::kPayloadTypeH264; + default: + RTC_DCHECK_NOTREACHED(); + } + return {}; +} +std::string CodecTypeToCodecName(VideoCodecType codec_type) { + switch (codec_type) { + case VideoCodecType::kVideoCodecGeneric: + return ""; + case VideoCodecType::kVideoCodecVP8: + return cricket::kVp8CodecName; + case VideoCodecType::kVideoCodecVP9: + return cricket::kVp9CodecName; + case VideoCodecType::kVideoCodecH264: + return cricket::kH264CodecName; + default: + RTC_DCHECK_NOTREACHED(); + } + return {}; +} +VideoEncoderConfig::ContentType ConvertContentType( + VideoStreamConfig::Encoder::ContentType content_type) { + switch (content_type) { + case VideoStreamConfig::Encoder::ContentType::kVideo: + return VideoEncoderConfig::ContentType::kRealtimeVideo; + case VideoStreamConfig::Encoder::ContentType::kScreen: + return VideoEncoderConfig::ContentType::kScreen; + } +} + +std::string TransformFilePath(std::string path) { + static const std::string resource_prefix = "res://"; + int ext_pos = path.rfind('.'); + if (ext_pos < 0) { + return test::ResourcePath(path, "yuv"); + } else if (absl::StartsWith(path, resource_prefix)) { + std::string name = path.substr(resource_prefix.length(), ext_pos); + std::string ext = path.substr(ext_pos, path.size()); + return test::ResourcePath(name, ext); + } + return path; +} + +VideoSendStream::Config CreateVideoSendStreamConfig( + VideoStreamConfig config, + std::vector<uint32_t> ssrcs, + std::vector<uint32_t> rtx_ssrcs, + Transport* send_transport) { + VideoSendStream::Config send_config(send_transport); + send_config.rtp.payload_name = CodecTypeToPayloadString(config.encoder.codec); + send_config.rtp.payload_type = CodecTypeToPayloadType(config.encoder.codec); + send_config.rtp.nack.rtp_history_ms = + config.stream.nack_history_time.ms<int>(); + + send_config.rtp.ssrcs = ssrcs; + send_config.rtp.extensions = GetVideoRtpExtensions(config); + + if (config.stream.use_rtx) { + send_config.rtp.rtx.payload_type = CallTest::kSendRtxPayloadType; + send_config.rtp.rtx.ssrcs = rtx_ssrcs; + } + if (config.stream.use_flexfec) { + send_config.rtp.flexfec.payload_type = CallTest::kFlexfecPayloadType; + send_config.rtp.flexfec.ssrc = CallTest::kFlexfecSendSsrc; + send_config.rtp.flexfec.protected_media_ssrcs = ssrcs; + } + if (config.stream.use_ulpfec) { + send_config.rtp.ulpfec.red_payload_type = CallTest::kRedPayloadType; + send_config.rtp.ulpfec.ulpfec_payload_type = CallTest::kUlpfecPayloadType; + send_config.rtp.ulpfec.red_rtx_payload_type = CallTest::kRtxRedPayloadType; + } + return send_config; +} +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateVp9SpecificSettings(VideoStreamConfig video_config) { + constexpr auto kScreen = VideoStreamConfig::Encoder::ContentType::kScreen; + VideoStreamConfig::Encoder conf = video_config.encoder; + VideoCodecVP9 vp9 = VideoEncoder::GetDefaultVp9Settings(); + // TODO(bugs.webrtc.org/11607): Support separate scalability mode per + // simulcast stream. + ScalabilityMode scalability_mode = conf.simulcast_streams[0]; + vp9.keyFrameInterval = conf.key_frame_interval.value_or(0); + vp9.numberOfTemporalLayers = + ScalabilityModeToNumTemporalLayers(scalability_mode); + vp9.numberOfSpatialLayers = + ScalabilityModeToNumSpatialLayers(scalability_mode); + vp9.interLayerPred = ScalabilityModeToInterLayerPredMode(scalability_mode); + + if (conf.content_type == kScreen && + (video_config.source.framerate > 5 || vp9.numberOfSpatialLayers >= 3)) { + vp9.flexibleMode = true; + } + + if (conf.content_type == kScreen || vp9.numberOfTemporalLayers > 1 || + vp9.numberOfSpatialLayers > 1) { + vp9.automaticResizeOn = false; + vp9.denoisingOn = false; + } else { + vp9.automaticResizeOn = conf.single.automatic_scaling; + vp9.denoisingOn = conf.single.denoising; + } + return rtc::make_ref_counted<VideoEncoderConfig::Vp9EncoderSpecificSettings>( + vp9); +} + +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateVp8SpecificSettings(VideoStreamConfig config) { + VideoCodecVP8 vp8_settings = VideoEncoder::GetDefaultVp8Settings(); + vp8_settings.keyFrameInterval = config.encoder.key_frame_interval.value_or(0); + // TODO(bugs.webrtc.org/11607): Support separate scalability mode per + // simulcast stream. + ScalabilityMode scalability_mode = config.encoder.simulcast_streams[0]; + vp8_settings.numberOfTemporalLayers = + ScalabilityModeToNumTemporalLayers(scalability_mode); + if (vp8_settings.numberOfTemporalLayers > 1 || + config.encoder.simulcast_streams.size() > 1) { + vp8_settings.automaticResizeOn = false; + vp8_settings.denoisingOn = false; + } else { + vp8_settings.automaticResizeOn = config.encoder.single.automatic_scaling; + vp8_settings.denoisingOn = config.encoder.single.denoising; + } + return rtc::make_ref_counted<VideoEncoderConfig::Vp8EncoderSpecificSettings>( + vp8_settings); +} + +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateH264SpecificSettings(VideoStreamConfig config) { + RTC_DCHECK_EQ(config.encoder.simulcast_streams.size(), 1); + RTC_DCHECK(config.encoder.simulcast_streams[0] == ScalabilityMode::kL1T1); + // TODO(bugs.webrtc.org/6883): Set a key frame interval as a setting that + // isn't codec specific. + RTC_CHECK_EQ(0, config.encoder.key_frame_interval.value_or(0)); + return nullptr; +} + +rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> +CreateEncoderSpecificSettings(VideoStreamConfig config) { + using Codec = VideoStreamConfig::Encoder::Codec; + switch (config.encoder.codec) { + case Codec::kVideoCodecH264: + return CreateH264SpecificSettings(config); + case Codec::kVideoCodecVP8: + return CreateVp8SpecificSettings(config); + case Codec::kVideoCodecVP9: + return CreateVp9SpecificSettings(config); + case Codec::kVideoCodecGeneric: + case Codec::kVideoCodecAV1: + return nullptr; + case Codec::kVideoCodecMultiplex: + RTC_DCHECK_NOTREACHED(); + return nullptr; + } +} + +VideoEncoderConfig CreateVideoEncoderConfig(VideoStreamConfig config) { + webrtc::VideoEncoder::EncoderInfo encoder_info; + VideoEncoderConfig encoder_config; + encoder_config.codec_type = config.encoder.codec; + encoder_config.content_type = ConvertContentType(config.encoder.content_type); + encoder_config.video_format = + SdpVideoFormat(CodecTypeToPayloadString(config.encoder.codec), {}); + + encoder_config.number_of_streams = config.encoder.simulcast_streams.size(); + encoder_config.simulcast_layers = + std::vector<VideoStream>(encoder_config.number_of_streams); + encoder_config.min_transmit_bitrate_bps = config.stream.pad_to_rate.bps(); + + std::string cricket_codec = CodecTypeToCodecName(config.encoder.codec); + if (!cricket_codec.empty()) { + bool screenshare = config.encoder.content_type == + VideoStreamConfig::Encoder::ContentType::kScreen; + encoder_config.video_stream_factory = + rtc::make_ref_counted<cricket::EncoderStreamFactory>( + cricket_codec, kDefaultMaxQp, screenshare, screenshare, + encoder_info); + } else { + encoder_config.video_stream_factory = + rtc::make_ref_counted<DefaultVideoStreamFactory>(); + } + + // TODO(srte): Base this on encoder capabilities. + encoder_config.max_bitrate_bps = + config.encoder.max_data_rate.value_or(DataRate::KilobitsPerSec(10000)) + .bps(); + + encoder_config.frame_drop_enabled = config.encoder.frame_dropping; + encoder_config.encoder_specific_settings = + CreateEncoderSpecificSettings(config); + + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + auto& layer = encoder_config.simulcast_layers[i]; + if (config.encoder.max_framerate) { + layer.max_framerate = *config.encoder.max_framerate; + layer.min_bitrate_bps = config.encoder.min_data_rate->bps_or(-1); + } + layer.scalability_mode = config.encoder.simulcast_streams[i]; + } + + return encoder_config; +} + +std::unique_ptr<FrameGeneratorInterface> CreateImageSlideGenerator( + Clock* clock, + VideoStreamConfig::Source::Slides slides, + int framerate) { + std::vector<std::string> paths = slides.images.paths; + for (std::string& path : paths) + path = TransformFilePath(path); + if (slides.images.crop.width || slides.images.crop.height) { + TimeDelta pause_duration = + slides.change_interval - slides.images.crop.scroll_duration; + RTC_CHECK_GE(pause_duration, TimeDelta::Zero()); + int crop_width = slides.images.crop.width.value_or(slides.images.width); + int crop_height = slides.images.crop.height.value_or(slides.images.height); + RTC_CHECK_LE(crop_width, slides.images.width); + RTC_CHECK_LE(crop_height, slides.images.height); + return CreateScrollingInputFromYuvFilesFrameGenerator( + clock, paths, slides.images.width, slides.images.height, crop_width, + crop_height, slides.images.crop.scroll_duration.ms(), + pause_duration.ms()); + } else { + return CreateFromYuvFileFrameGenerator( + paths, slides.images.width, slides.images.height, + slides.change_interval.seconds<double>() * framerate); + } +} + +std::unique_ptr<FrameGeneratorInterface> CreateFrameGenerator( + Clock* clock, + VideoStreamConfig::Source source) { + using Capture = VideoStreamConfig::Source::Capture; + switch (source.capture) { + case Capture::kGenerator: + return CreateSquareFrameGenerator( + source.generator.width, source.generator.height, + source.generator.pixel_format, /*num_squares*/ absl::nullopt); + case Capture::kVideoFile: + RTC_CHECK(source.video_file.width && source.video_file.height); + return CreateFromYuvFileFrameGenerator( + {TransformFilePath(source.video_file.name)}, source.video_file.width, + source.video_file.height, /*frame_repeat_count*/ 1); + case Capture::kGenerateSlides: + return CreateSlideFrameGenerator( + source.slides.generator.width, source.slides.generator.height, + source.slides.change_interval.seconds<double>() * source.framerate); + case Capture::kImageSlides: + return CreateImageSlideGenerator(clock, source.slides, source.framerate); + } +} + +VideoReceiveStreamInterface::Config CreateVideoReceiveStreamConfig( + VideoStreamConfig config, + Transport* feedback_transport, + VideoDecoderFactory* decoder_factory, + VideoReceiveStreamInterface::Decoder decoder, + rtc::VideoSinkInterface<VideoFrame>* renderer, + uint32_t local_ssrc, + uint32_t ssrc, + uint32_t rtx_ssrc) { + VideoReceiveStreamInterface::Config recv(feedback_transport); + recv.rtp.local_ssrc = local_ssrc; + recv.rtp.extensions = GetVideoRtpExtensions(config); + + RTC_DCHECK(!config.stream.use_rtx || + config.stream.nack_history_time > TimeDelta::Zero()); + recv.rtp.nack.rtp_history_ms = config.stream.nack_history_time.ms(); + recv.rtp.protected_by_flexfec = config.stream.use_flexfec; + recv.rtp.remote_ssrc = ssrc; + recv.decoder_factory = decoder_factory; + recv.decoders.push_back(decoder); + recv.renderer = renderer; + if (config.stream.use_rtx) { + recv.rtp.rtx_ssrc = rtx_ssrc; + recv.rtp.rtx_associated_payload_types[CallTest::kSendRtxPayloadType] = + CodecTypeToPayloadType(config.encoder.codec); + } + if (config.stream.use_ulpfec) { + recv.rtp.red_payload_type = CallTest::kRedPayloadType; + recv.rtp.ulpfec_payload_type = CallTest::kUlpfecPayloadType; + recv.rtp.rtx_associated_payload_types[CallTest::kRtxRedPayloadType] = + CallTest::kRedPayloadType; + } + recv.sync_group = config.render.sync_group; + return recv; +} +} // namespace + +std::vector<RtpExtension> GetVideoRtpExtensions( + const VideoStreamConfig config) { + std::vector<RtpExtension> res = { + RtpExtension(RtpExtension::kVideoContentTypeUri, + kVideoContentTypeExtensionId), + RtpExtension(RtpExtension::kVideoRotationUri, + kVideoRotationRtpExtensionId)}; + if (config.stream.packet_feedback) { + res.push_back(RtpExtension(RtpExtension::kTransportSequenceNumberUri, + kTransportSequenceNumberExtensionId)); + } + if (config.stream.abs_send_time) { + res.push_back( + RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId)); + } + return res; +} + +SendVideoStream::SendVideoStream(CallClient* sender, + VideoStreamConfig config, + Transport* send_transport, + VideoFrameMatcher* matcher) + : sender_(sender), config_(config) { + video_capturer_ = std::make_unique<FrameGeneratorCapturer>( + sender_->clock_, CreateFrameGenerator(sender_->clock_, config.source), + config.source.framerate, + *sender->time_controller_->GetTaskQueueFactory()); + video_capturer_->Init(); + + using Encoder = VideoStreamConfig::Encoder; + using Codec = VideoStreamConfig::Encoder::Codec; + switch (config.encoder.implementation) { + case Encoder::Implementation::kFake: + encoder_factory_ = + std::make_unique<FunctionVideoEncoderFactory>([this]() { + MutexLock lock(&mutex_); + std::unique_ptr<FakeEncoder> encoder; + if (config_.encoder.codec == Codec::kVideoCodecVP8) { + encoder = std::make_unique<test::FakeVp8Encoder>(sender_->clock_); + } else if (config_.encoder.codec == Codec::kVideoCodecGeneric) { + encoder = std::make_unique<test::FakeEncoder>(sender_->clock_); + } else { + RTC_DCHECK_NOTREACHED(); + } + fake_encoders_.push_back(encoder.get()); + if (config_.encoder.fake.max_rate.IsFinite()) + encoder->SetMaxBitrate(config_.encoder.fake.max_rate.kbps()); + return encoder; + }); + break; + case VideoStreamConfig::Encoder::Implementation::kSoftware: + encoder_factory_.reset(new InternalEncoderFactory()); + break; + case VideoStreamConfig::Encoder::Implementation::kHardware: + encoder_factory_ = CreateHardwareEncoderFactory(); + break; + } + RTC_CHECK(encoder_factory_); + + bitrate_allocator_factory_ = CreateBuiltinVideoBitrateAllocatorFactory(); + RTC_CHECK(bitrate_allocator_factory_); + + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config); + for (size_t i = 0; i < encoder_config.number_of_streams; ++i) { + ssrcs_.push_back(sender->GetNextVideoSsrc()); + rtx_ssrcs_.push_back(sender->GetNextRtxSsrc()); + } + VideoSendStream::Config send_config = + CreateVideoSendStreamConfig(config, ssrcs_, rtx_ssrcs_, send_transport); + send_config.encoder_settings.encoder_factory = encoder_factory_.get(); + send_config.encoder_settings.bitrate_allocator_factory = + bitrate_allocator_factory_.get(); + send_config.suspend_below_min_bitrate = + config.encoder.suspend_below_min_bitrate; + + sender_->SendTask([&] { + if (config.stream.fec_controller_factory) { + send_stream_ = sender_->call_->CreateVideoSendStream( + std::move(send_config), std::move(encoder_config), + config.stream.fec_controller_factory->CreateFecController()); + } else { + send_stream_ = sender_->call_->CreateVideoSendStream( + std::move(send_config), std::move(encoder_config)); + } + + if (matcher->Active()) { + frame_tap_ = std::make_unique<ForwardingCapturedFrameTap>( + sender_->clock_, matcher, video_capturer_.get()); + send_stream_->SetSource(frame_tap_.get(), + config.encoder.degradation_preference); + } else { + send_stream_->SetSource(video_capturer_.get(), + config.encoder.degradation_preference); + } + }); +} + +SendVideoStream::~SendVideoStream() { + sender_->SendTask( + [this] { sender_->call_->DestroyVideoSendStream(send_stream_); }); +} + +void SendVideoStream::Start() { + sender_->SendTask([this] { + send_stream_->Start(); + sender_->call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + }); +} + +void SendVideoStream::Stop() { + sender_->SendTask([this] { send_stream_->Stop(); }); +} + +void SendVideoStream::UpdateConfig( + std::function<void(VideoStreamConfig*)> modifier) { + sender_->SendTask([&] { + MutexLock lock(&mutex_); + VideoStreamConfig prior_config = config_; + modifier(&config_); + if (prior_config.encoder.fake.max_rate != config_.encoder.fake.max_rate) { + for (auto* encoder : fake_encoders_) { + encoder->SetMaxBitrate(config_.encoder.fake.max_rate.kbps()); + } + } + // TODO(srte): Add more conditions that should cause reconfiguration. + if (prior_config.encoder.max_framerate != config_.encoder.max_framerate || + prior_config.encoder.max_data_rate != config_.encoder.max_data_rate) { + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config_); + send_stream_->ReconfigureVideoEncoder(std::move(encoder_config)); + } + if (prior_config.source.framerate != config_.source.framerate) { + SetCaptureFramerate(config_.source.framerate); + } + }); +} + +void SendVideoStream::UpdateActiveLayers(std::vector<bool> active_layers) { + sender_->task_queue_.PostTask([=] { + MutexLock lock(&mutex_); + if (config_.encoder.codec == + VideoStreamConfig::Encoder::Codec::kVideoCodecVP8) { + send_stream_->StartPerRtpStream(active_layers); + } + VideoEncoderConfig encoder_config = CreateVideoEncoderConfig(config_); + RTC_CHECK_EQ(encoder_config.simulcast_layers.size(), active_layers.size()); + for (size_t i = 0; i < encoder_config.simulcast_layers.size(); ++i) + encoder_config.simulcast_layers[i].active = active_layers[i]; + send_stream_->ReconfigureVideoEncoder(std::move(encoder_config)); + }); +} + +bool SendVideoStream::UsingSsrc(uint32_t ssrc) const { + for (uint32_t owned : ssrcs_) { + if (owned == ssrc) + return true; + } + return false; +} + +bool SendVideoStream::UsingRtxSsrc(uint32_t ssrc) const { + for (uint32_t owned : rtx_ssrcs_) { + if (owned == ssrc) + return true; + } + return false; +} + +void SendVideoStream::SetCaptureFramerate(int framerate) { + sender_->SendTask([&] { video_capturer_->ChangeFramerate(framerate); }); +} + +VideoSendStream::Stats SendVideoStream::GetStats() const { + return send_stream_->GetStats(); +} + +ColumnPrinter SendVideoStream::StatsPrinter() { + return ColumnPrinter::Lambda( + "video_target_rate video_sent_rate width height", + [this](rtc::SimpleStringBuilder& sb) { + VideoSendStream::Stats video_stats = send_stream_->GetStats(); + int width = 0; + int height = 0; + for (const auto& stream_stat : video_stats.substreams) { + width = std::max(width, stream_stat.second.width); + height = std::max(height, stream_stat.second.height); + } + sb.AppendFormat("%.0lf %.0lf %i %i", + video_stats.target_media_bitrate_bps / 8.0, + video_stats.media_bitrate_bps / 8.0, width, height); + }, + 64); +} + +ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver, + VideoStreamConfig config, + SendVideoStream* send_stream, + size_t chosen_stream, + Transport* feedback_transport, + VideoFrameMatcher* matcher) + : receiver_(receiver), config_(config) { + if (config.encoder.codec == + VideoStreamConfig::Encoder::Codec::kVideoCodecGeneric || + config.encoder.implementation == VideoStreamConfig::Encoder::kFake) { + decoder_factory_ = std::make_unique<FunctionVideoDecoderFactory>( + []() { return std::make_unique<FakeDecoder>(); }); + } else { + decoder_factory_ = std::make_unique<InternalDecoderFactory>(); + } + + VideoReceiveStreamInterface::Decoder decoder = + CreateMatchingDecoder(CodecTypeToPayloadType(config.encoder.codec), + CodecTypeToPayloadString(config.encoder.codec)); + size_t num_streams = config.encoder.simulcast_streams.size(); + for (size_t i = 0; i < num_streams; ++i) { + rtc::VideoSinkInterface<VideoFrame>* renderer = &fake_renderer_; + if (matcher->Active()) { + render_taps_.emplace_back( + std::make_unique<DecodedFrameTap>(receiver_->clock_, matcher, i)); + renderer = render_taps_.back().get(); + } + auto recv_config = CreateVideoReceiveStreamConfig( + config, feedback_transport, decoder_factory_.get(), decoder, renderer, + receiver_->GetNextVideoLocalSsrc(), send_stream->ssrcs_[i], + send_stream->rtx_ssrcs_[i]); + if (config.stream.use_flexfec) { + RTC_DCHECK(num_streams == 1); + FlexfecReceiveStream::Config flexfec(feedback_transport); + flexfec.payload_type = CallTest::kFlexfecPayloadType; + flexfec.rtp.remote_ssrc = CallTest::kFlexfecSendSsrc; + flexfec.protected_media_ssrcs = send_stream->rtx_ssrcs_; + flexfec.rtp.local_ssrc = recv_config.rtp.local_ssrc; + receiver_->ssrc_media_types_[flexfec.rtp.remote_ssrc] = MediaType::VIDEO; + + receiver_->SendTask([this, &flexfec] { + flecfec_stream_ = receiver_->call_->CreateFlexfecReceiveStream(flexfec); + }); + } + receiver_->ssrc_media_types_[recv_config.rtp.remote_ssrc] = + MediaType::VIDEO; + if (config.stream.use_rtx) + receiver_->ssrc_media_types_[recv_config.rtp.rtx_ssrc] = MediaType::VIDEO; + receiver_->SendTask([this, &recv_config] { + receive_streams_.push_back( + receiver_->call_->CreateVideoReceiveStream(std::move(recv_config))); + }); + } +} + +ReceiveVideoStream::~ReceiveVideoStream() { + receiver_->SendTask([this] { + for (auto* recv_stream : receive_streams_) + receiver_->call_->DestroyVideoReceiveStream(recv_stream); + if (flecfec_stream_) + receiver_->call_->DestroyFlexfecReceiveStream(flecfec_stream_); + }); +} + +void ReceiveVideoStream::Start() { + receiver_->SendTask([this] { + for (auto* recv_stream : receive_streams_) + recv_stream->Start(); + receiver_->call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + }); +} + +void ReceiveVideoStream::Stop() { + receiver_->SendTask([this] { + for (auto* recv_stream : receive_streams_) + recv_stream->Stop(); + }); +} + +VideoReceiveStreamInterface::Stats ReceiveVideoStream::GetStats() const { + if (receive_streams_.empty()) + return VideoReceiveStreamInterface::Stats(); + // TODO(srte): Handle multiple receive streams. + return receive_streams_.back()->GetStats(); +} + +VideoStreamPair::~VideoStreamPair() = default; + +VideoStreamPair::VideoStreamPair(CallClient* sender, + CallClient* receiver, + VideoStreamConfig config) + : config_(config), + matcher_(config.hooks.frame_pair_handlers), + send_stream_(sender, config, sender->transport_.get(), &matcher_), + receive_stream_(receiver, + config, + &send_stream_, + /*chosen_stream=*/0, + receiver->transport_.get(), + &matcher_) {} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scenario/video_stream.h b/third_party/libwebrtc/test/scenario/video_stream.h new file mode 100644 index 0000000000..43c51eab73 --- /dev/null +++ b/third_party/libwebrtc/test/scenario/video_stream.h @@ -0,0 +1,138 @@ +/* + * Copyright 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_SCENARIO_VIDEO_STREAM_H_ +#define TEST_SCENARIO_VIDEO_STREAM_H_ +#include <memory> +#include <string> +#include <vector> + +#include "rtc_base/synchronization/mutex.h" +#include "test/fake_encoder.h" +#include "test/fake_videorenderer.h" +#include "test/frame_generator_capturer.h" +#include "test/logging/log_writer.h" +#include "test/scenario/call_client.h" +#include "test/scenario/column_printer.h" +#include "test/scenario/network_node.h" +#include "test/scenario/scenario_config.h" +#include "test/scenario/video_frame_matcher.h" +#include "test/test_video_capturer.h" + +namespace webrtc { +namespace test { +// SendVideoStream provides an interface for changing parameters and retrieving +// states at run time. +class SendVideoStream { + public: + ~SendVideoStream(); + + SendVideoStream(const SendVideoStream&) = delete; + SendVideoStream& operator=(const SendVideoStream&) = delete; + + void SetCaptureFramerate(int framerate); + VideoSendStream::Stats GetStats() const; + ColumnPrinter StatsPrinter(); + void Start(); + void Stop(); + void UpdateConfig(std::function<void(VideoStreamConfig*)> modifier); + void UpdateActiveLayers(std::vector<bool> active_layers); + bool UsingSsrc(uint32_t ssrc) const; + bool UsingRtxSsrc(uint32_t ssrc) const; + + private: + friend class Scenario; + friend class VideoStreamPair; + friend class ReceiveVideoStream; + // Handles RTCP feedback for this stream. + SendVideoStream(CallClient* sender, + VideoStreamConfig config, + Transport* send_transport, + VideoFrameMatcher* matcher); + + Mutex mutex_; + std::vector<uint32_t> ssrcs_; + std::vector<uint32_t> rtx_ssrcs_; + VideoSendStream* send_stream_ = nullptr; + CallClient* const sender_; + VideoStreamConfig config_ RTC_GUARDED_BY(mutex_); + std::unique_ptr<VideoEncoderFactory> encoder_factory_; + std::vector<test::FakeEncoder*> fake_encoders_ RTC_GUARDED_BY(mutex_); + std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_; + std::unique_ptr<FrameGeneratorCapturer> video_capturer_; + std::unique_ptr<ForwardingCapturedFrameTap> frame_tap_; + int next_local_network_id_ = 0; + int next_remote_network_id_ = 0; +}; + +// ReceiveVideoStream represents a video receiver. It can't be used directly. +class ReceiveVideoStream { + public: + ~ReceiveVideoStream(); + + ReceiveVideoStream(const ReceiveVideoStream&) = delete; + ReceiveVideoStream& operator=(const ReceiveVideoStream&) = delete; + + void Start(); + void Stop(); + VideoReceiveStreamInterface::Stats GetStats() const; + + private: + friend class Scenario; + friend class VideoStreamPair; + ReceiveVideoStream(CallClient* receiver, + VideoStreamConfig config, + SendVideoStream* send_stream, + size_t chosen_stream, + Transport* feedback_transport, + VideoFrameMatcher* matcher); + + std::vector<VideoReceiveStreamInterface*> receive_streams_; + FlexfecReceiveStream* flecfec_stream_ = nullptr; + FakeVideoRenderer fake_renderer_; + std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> + render_taps_; + CallClient* const receiver_; + const VideoStreamConfig config_; + std::unique_ptr<VideoDecoderFactory> decoder_factory_; +}; + +// VideoStreamPair represents a video streaming session. It can be used to +// access underlying send and receive classes. It can also be used in calls to +// the Scenario class. +class VideoStreamPair { + public: + ~VideoStreamPair(); + + VideoStreamPair(const VideoStreamPair&) = delete; + VideoStreamPair& operator=(const VideoStreamPair&) = delete; + + SendVideoStream* send() { return &send_stream_; } + ReceiveVideoStream* receive() { return &receive_stream_; } + VideoFrameMatcher* matcher() { return &matcher_; } + + private: + friend class Scenario; + VideoStreamPair(CallClient* sender, + CallClient* receiver, + VideoStreamConfig config); + + const VideoStreamConfig config_; + + VideoFrameMatcher matcher_; + SendVideoStream send_stream_; + ReceiveVideoStream receive_stream_; +}; + +std::vector<RtpExtension> GetVideoRtpExtensions(const VideoStreamConfig config); + +} // namespace test +} // namespace webrtc + +#endif // TEST_SCENARIO_VIDEO_STREAM_H_ diff --git a/third_party/libwebrtc/test/scenario/video_stream_unittest.cc b/third_party/libwebrtc/test/scenario/video_stream_unittest.cc new file mode 100644 index 0000000000..e53af4ef2b --- /dev/null +++ b/third_party/libwebrtc/test/scenario/video_stream_unittest.cc @@ -0,0 +1,322 @@ +/* + * Copyright 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 <atomic> + +#include "api/test/network_emulation/create_cross_traffic.h" +#include "api/test/network_emulation/cross_traffic.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/scenario/scenario.h" + +namespace webrtc { +namespace test { +namespace { +using Capture = VideoStreamConfig::Source::Capture; +using ContentType = VideoStreamConfig::Encoder::ContentType; +using Codec = VideoStreamConfig::Encoder::Codec; +using CodecImpl = VideoStreamConfig::Encoder::Implementation; +} // namespace + +TEST(VideoStreamTest, ReceivesFramesFromFileBasedStreams) { + TimeDelta kRunTime = TimeDelta::Millis(500); + std::vector<int> kFrameRates = {15, 30}; + std::deque<std::atomic<int>> frame_counts(2); + frame_counts[0] = 0; + frame_counts[1] = 0; + { + Scenario s; + auto route = + s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + + s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + c->hooks.frame_pair_handlers = { + [&](const VideoFramePair&) { frame_counts[0]++; }}; + c->source.capture = Capture::kVideoFile; + c->source.video_file.name = "foreman_cif"; + c->source.video_file.width = 352; + c->source.video_file.height = 288; + c->source.framerate = kFrameRates[0]; + c->encoder.implementation = CodecImpl::kSoftware; + c->encoder.codec = Codec::kVideoCodecVP8; + }); + s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + c->hooks.frame_pair_handlers = { + [&](const VideoFramePair&) { frame_counts[1]++; }}; + c->source.capture = Capture::kImageSlides; + c->source.slides.images.crop.width = 320; + c->source.slides.images.crop.height = 240; + c->source.framerate = kFrameRates[1]; + c->encoder.implementation = CodecImpl::kSoftware; + c->encoder.codec = Codec::kVideoCodecVP9; + }); + s.RunFor(kRunTime); + } + std::vector<int> expected_counts; + for (int fps : kFrameRates) + expected_counts.push_back( + static_cast<int>(kRunTime.seconds<double>() * fps * 0.8)); + + EXPECT_GE(frame_counts[0], expected_counts[0]); + EXPECT_GE(frame_counts[1], expected_counts[1]); +} + +TEST(VideoStreamTest, ReceivesVp8SimulcastFrames) { + TimeDelta kRunTime = TimeDelta::Millis(500); + int kFrameRate = 30; + + std::deque<std::atomic<int>> frame_counts(3); + frame_counts[0] = 0; + frame_counts[1] = 0; + frame_counts[2] = 0; + { + Scenario s; + auto route = + s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + // TODO(srte): Replace with code checking for all simulcast streams when + // there's a hook available for that. + c->hooks.frame_pair_handlers = {[&](const VideoFramePair& info) { + frame_counts[info.layer_id]++; + RTC_DCHECK(info.decoded); + printf("%i: [%3i->%3i, %i], %i->%i, \n", info.layer_id, info.capture_id, + info.decode_id, info.repeated, info.captured->width(), + info.decoded->width()); + }}; + c->source.framerate = kFrameRate; + // The resolution must be high enough to allow smaller layers to be + // created. + c->source.generator.width = 1024; + c->source.generator.height = 768; + c->encoder.implementation = CodecImpl::kSoftware; + c->encoder.codec = Codec::kVideoCodecVP8; + // Enable simulcast. + c->encoder.simulcast_streams = {webrtc::ScalabilityMode::kL1T1, + webrtc::ScalabilityMode::kL1T1, + webrtc::ScalabilityMode::kL1T1}; + + }); + s.RunFor(kRunTime); + } + + // Using high error margin to avoid flakyness. + const int kExpectedCount = + static_cast<int>(kRunTime.seconds<double>() * kFrameRate * 0.5); + + EXPECT_GE(frame_counts[0], kExpectedCount); + EXPECT_GE(frame_counts[1], kExpectedCount); + EXPECT_GE(frame_counts[2], kExpectedCount); +} + +TEST(VideoStreamTest, SendsNacksOnLoss) { + Scenario s; + auto route = + s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode([](NetworkSimulationConfig* c) { + c->loss_rate = 0.2; + })}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + // NACK retransmissions are enabled by default. + auto video = s.CreateVideoStream(route->forward(), VideoStreamConfig()); + s.RunFor(TimeDelta::Seconds(1)); + int retransmit_packets = 0; + VideoSendStream::Stats stats; + route->first()->SendTask([&]() { stats = video->send()->GetStats(); }); + for (const auto& substream : stats.substreams) { + retransmit_packets += substream.second.rtp_stats.retransmitted.packets; + } + EXPECT_GT(retransmit_packets, 0); +} + +TEST(VideoStreamTest, SendsFecWithUlpFec) { + Scenario s; + auto route = + s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode([](NetworkSimulationConfig* c) { + c->loss_rate = 0.1; + c->delay = TimeDelta::Millis(100); + })}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + auto video = s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + // We do not allow NACK+ULPFEC for generic codec, using VP8. + c->encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + c->stream.use_ulpfec = true; + }); + s.RunFor(TimeDelta::Seconds(5)); + VideoSendStream::Stats video_stats; + route->first()->SendTask([&]() { video_stats = video->send()->GetStats(); }); + EXPECT_GT(video_stats.substreams.begin()->second.rtp_stats.fec.packets, 0u); +} +TEST(VideoStreamTest, SendsFecWithFlexFec) { + Scenario s; + auto route = + s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode([](NetworkSimulationConfig* c) { + c->loss_rate = 0.1; + c->delay = TimeDelta::Millis(100); + })}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkSimulationConfig())}); + auto video = s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + c->stream.use_flexfec = true; + }); + s.RunFor(TimeDelta::Seconds(5)); + VideoSendStream::Stats video_stats; + route->first()->SendTask([&]() { video_stats = video->send()->GetStats(); }); + EXPECT_GT(video_stats.substreams.begin()->second.rtp_stats.fec.packets, 0u); +} + +TEST(VideoStreamTest, ResolutionAdaptsToAvailableBandwidth) { + // Declared before scenario to avoid use after free. + std::atomic<size_t> num_qvga_frames_(0); + std::atomic<size_t> num_vga_frames_(0); + + Scenario s; + // Link has enough capacity for VGA. + NetworkSimulationConfig net_conf; + net_conf.bandwidth = DataRate::KilobitsPerSec(800); + net_conf.delay = TimeDelta::Millis(50); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + c->transport.rates.start_rate = DataRate::KilobitsPerSec(800); + }); + auto send_net = {s.CreateSimulationNode(net_conf)}; + auto ret_net = {s.CreateSimulationNode(net_conf)}; + auto* route = s.CreateRoutes( + client, send_net, s.CreateClient("return", CallClientConfig()), ret_net); + + s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + c->hooks.frame_pair_handlers = {[&](const VideoFramePair& info) { + if (info.decoded->width() == 640) { + ++num_vga_frames_; + } else if (info.decoded->width() == 320) { + ++num_qvga_frames_; + } else { + ADD_FAILURE() << "Unexpected resolution: " << info.decoded->width(); + } + }}; + c->source.framerate = 30; + // The resolution must be high enough to allow smaller layers to be + // created. + c->source.generator.width = 640; + c->source.generator.height = 480; + c->encoder.implementation = CodecImpl::kSoftware; + c->encoder.codec = Codec::kVideoCodecVP9; + // Enable SVC. + c->encoder.simulcast_streams = {webrtc::ScalabilityMode::kL2T1}; + }); + + // Run for a few seconds, until streams have stabilized, + // check that we are sending VGA. + s.RunFor(TimeDelta::Seconds(5)); + EXPECT_GT(num_vga_frames_, 0u); + + // Trigger cross traffic, run until we have seen 3 consecutive + // seconds with no VGA frames due to reduced available bandwidth. + auto cross_traffic = s.net()->StartCrossTraffic(CreateFakeTcpCrossTraffic( + s.net()->CreateRoute(send_net), s.net()->CreateRoute(ret_net), + FakeTcpConfig())); + + int num_seconds_without_vga = 0; + int num_iterations = 0; + do { + ASSERT_LE(++num_iterations, 100); + num_qvga_frames_ = 0; + num_vga_frames_ = 0; + s.RunFor(TimeDelta::Seconds(1)); + if (num_qvga_frames_ > 0 && num_vga_frames_ == 0) { + ++num_seconds_without_vga; + } else { + num_seconds_without_vga = 0; + } + } while (num_seconds_without_vga < 3); + + // Stop cross traffic, make sure we recover and get VGA frames agian. + s.net()->StopCrossTraffic(cross_traffic); + num_qvga_frames_ = 0; + num_vga_frames_ = 0; + + s.RunFor(TimeDelta::Seconds(40)); + EXPECT_GT(num_qvga_frames_, 0u); + EXPECT_GT(num_vga_frames_, 0u); +} + +TEST(VideoStreamTest, SuspendsBelowMinBitrate) { + const DataRate kMinVideoBitrate = DataRate::KilobitsPerSec(30); + + // Declared before scenario to avoid use after free. + std::atomic<Timestamp> last_frame_timestamp(Timestamp::MinusInfinity()); + + Scenario s; + NetworkSimulationConfig net_config; + net_config.bandwidth = kMinVideoBitrate * 4; + net_config.delay = TimeDelta::Millis(10); + auto* client = s.CreateClient("send", [&](CallClientConfig* c) { + // Min transmit rate needs to be lower than kMinVideoBitrate for this test + // to make sense. + c->transport.rates.min_rate = kMinVideoBitrate / 2; + c->transport.rates.start_rate = kMinVideoBitrate; + c->transport.rates.max_rate = kMinVideoBitrate * 2; + }); + auto send_net = s.CreateMutableSimulationNode( + [&](NetworkSimulationConfig* c) { *c = net_config; }); + auto ret_net = {s.CreateSimulationNode(net_config)}; + auto* route = + s.CreateRoutes(client, {send_net->node()}, + s.CreateClient("return", CallClientConfig()), ret_net); + + s.CreateVideoStream(route->forward(), [&](VideoStreamConfig* c) { + c->hooks.frame_pair_handlers = {[&](const VideoFramePair& pair) { + if (pair.repeated == 0) { + last_frame_timestamp = pair.capture_time; + } + }}; + c->source.framerate = 30; + c->source.generator.width = 320; + c->source.generator.height = 180; + c->encoder.implementation = CodecImpl::kFake; + c->encoder.codec = Codec::kVideoCodecVP8; + c->encoder.min_data_rate = kMinVideoBitrate; + c->encoder.suspend_below_min_bitrate = true; + c->stream.pad_to_rate = kMinVideoBitrate; + }); + + // Run for a few seconds, check we have received at least one frame. + s.RunFor(TimeDelta::Seconds(2)); + EXPECT_TRUE(last_frame_timestamp.load().IsFinite()); + + // Degrade network to below min bitrate. + send_net->UpdateConfig([&](NetworkSimulationConfig* c) { + c->bandwidth = kMinVideoBitrate * 0.9; + }); + + // Run for 20s, verify that no frames arrive that were captured after the + // first five seconds, allowing some margin for BWE backoff to trigger and + // packets already in the pipeline to potentially arrive. + s.RunFor(TimeDelta::Seconds(20)); + EXPECT_GT(s.Now() - last_frame_timestamp, TimeDelta::Seconds(15)); + + // Relax the network constraints and run for a while more, verify that we + // start receiving frames again. + send_net->UpdateConfig( + [&](NetworkSimulationConfig* c) { c->bandwidth = kMinVideoBitrate * 4; }); + last_frame_timestamp = Timestamp::MinusInfinity(); + s.RunFor(TimeDelta::Seconds(15)); + EXPECT_TRUE(last_frame_timestamp.load().IsFinite()); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scoped_key_value_config.cc b/third_party/libwebrtc/test/scoped_key_value_config.cc new file mode 100644 index 0000000000..df84462637 --- /dev/null +++ b/third_party/libwebrtc/test/scoped_key_value_config.cc @@ -0,0 +1,122 @@ +/* + * 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/scoped_key_value_config.h" + +#include "rtc_base/checks.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +namespace { + +// This part is copied from system_wrappers/field_trial.cc. +void InsertIntoMap( + std::map<std::string, std::string, std::less<>>& key_value_map, + absl::string_view s) { + std::string::size_type field_start = 0; + while (field_start < s.size()) { + std::string::size_type separator_pos = s.find('/', field_start); + RTC_CHECK_NE(separator_pos, std::string::npos) + << "Missing separator '/' after field trial key."; + RTC_CHECK_GT(separator_pos, field_start) + << "Field trial key cannot be empty."; + std::string key(s.substr(field_start, separator_pos - field_start)); + field_start = separator_pos + 1; + + RTC_CHECK_LT(field_start, s.size()) + << "Missing value after field trial key. String ended."; + separator_pos = s.find('/', field_start); + RTC_CHECK_NE(separator_pos, std::string::npos) + << "Missing terminating '/' in field trial string."; + RTC_CHECK_GT(separator_pos, field_start) + << "Field trial value cannot be empty."; + std::string value(s.substr(field_start, separator_pos - field_start)); + field_start = separator_pos + 1; + + key_value_map[key] = value; + } + // This check is technically redundant due to earlier checks. + // We nevertheless keep the check to make it clear that the entire + // string has been processed, and without indexing past the end. + RTC_CHECK_EQ(field_start, s.size()); +} + +} // namespace + +namespace webrtc { +namespace test { + +ScopedKeyValueConfig::ScopedKeyValueConfig() + : ScopedKeyValueConfig(nullptr, "") {} + +ScopedKeyValueConfig::ScopedKeyValueConfig(absl::string_view s) + : ScopedKeyValueConfig(nullptr, s) {} + +ScopedKeyValueConfig::ScopedKeyValueConfig(ScopedKeyValueConfig& parent, + absl::string_view s) + : ScopedKeyValueConfig(&parent, s) {} + +ScopedKeyValueConfig::ScopedKeyValueConfig(ScopedKeyValueConfig* parent, + absl::string_view s) + : parent_(parent), leaf_(nullptr) { + InsertIntoMap(key_value_map_, s); + + if (!s.empty()) { + // Also store field trials in global string (until we get rid of it). + scoped_field_trials_ = std::make_unique<ScopedFieldTrials>(s); + } + + if (parent == nullptr) { + // We are root, set leaf_. + leaf_ = this; + } else { + // Link root to new leaf. + GetRoot(parent)->leaf_ = this; + RTC_DCHECK(leaf_ == nullptr); + } +} + +ScopedKeyValueConfig::~ScopedKeyValueConfig() { + if (parent_) { + GetRoot(parent_)->leaf_ = parent_; + } +} + +ScopedKeyValueConfig* ScopedKeyValueConfig::GetRoot(ScopedKeyValueConfig* n) { + while (n->parent_ != nullptr) { + n = n->parent_; + } + return n; +} + +std::string ScopedKeyValueConfig::GetValue(absl::string_view key) const { + if (parent_ == nullptr) { + return leaf_->LookupRecurse(key); + } else { + return LookupRecurse(key); + } +} + +std::string ScopedKeyValueConfig::LookupRecurse(absl::string_view key) const { + auto it = key_value_map_.find(key); + if (it != key_value_map_.end()) + return it->second; + + if (parent_) { + return parent_->LookupRecurse(key); + } + + // When at the root, check the global string so that test programs using + // a mix between ScopedKeyValueConfig and the global string continue to work + return webrtc::field_trial::FindFullName(std::string(key)); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/scoped_key_value_config.h b/third_party/libwebrtc/test/scoped_key_value_config.h new file mode 100644 index 0000000000..c0023f8228 --- /dev/null +++ b/third_party/libwebrtc/test/scoped_key_value_config.h @@ -0,0 +1,54 @@ +/* + * 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_SCOPED_KEY_VALUE_CONFIG_H_ +#define TEST_SCOPED_KEY_VALUE_CONFIG_H_ + +#include <functional> +#include <map> +#include <memory> +#include <string> + +#include "absl/strings/string_view.h" +#include "api/field_trials_registry.h" +#include "test/field_trial.h" + +namespace webrtc { +namespace test { + +class ScopedKeyValueConfig : public FieldTrialsRegistry { + public: + virtual ~ScopedKeyValueConfig(); + ScopedKeyValueConfig(); + explicit ScopedKeyValueConfig(absl::string_view s); + ScopedKeyValueConfig(ScopedKeyValueConfig& parent, absl::string_view s); + + private: + ScopedKeyValueConfig(ScopedKeyValueConfig* parent, absl::string_view s); + ScopedKeyValueConfig* GetRoot(ScopedKeyValueConfig* n); + std::string GetValue(absl::string_view key) const override; + std::string LookupRecurse(absl::string_view key) const; + + ScopedKeyValueConfig* const parent_; + + // The leaf in a list of stacked ScopedKeyValueConfig. + // Only set on root (e.g with parent_ == nullptr). + const ScopedKeyValueConfig* leaf_; + + // Unlike std::less<std::string>, std::less<> is transparent and allows + // heterogeneous lookup directly with absl::string_view. + std::map<std::string, std::string, std::less<>> key_value_map_; + std::unique_ptr<ScopedFieldTrials> scoped_field_trials_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_SCOPED_KEY_VALUE_CONFIG_H_ diff --git a/third_party/libwebrtc/test/test_flags.cc b/third_party/libwebrtc/test/test_flags.cc new file mode 100644 index 0000000000..a0fff747fe --- /dev/null +++ b/third_party/libwebrtc/test/test_flags.cc @@ -0,0 +1,51 @@ +/* + * 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/test_flags.h" + +#include <string> +#include <vector> + +#include "absl/flags/flag.h" + +ABSL_FLAG(std::string, + force_fieldtrials, + "", + "Field trials control experimental feature code which can be forced. " + "E.g. running with --force_fieldtrials=WebRTC-FooFeature/Enable/" + " will assign the group Enable to field trial WebRTC-FooFeature."); + +ABSL_FLAG(std::vector<std::string>, + plot, + {}, + "List of metrics that should be exported for plotting (if they are " + "available). Example: psnr,ssim,encode_time. To plot all available " + " metrics pass 'all' as flag value"); + +ABSL_FLAG( + std::string, + isolated_script_test_perf_output, + "", + "Path where the perf results should be stored in proto format described " + "described by histogram.proto in " + "https://chromium.googlesource.com/catapult/."); + +ABSL_FLAG(std::string, + webrtc_test_metrics_output_path, + "", + "Path where the test perf metrics should be stored using " + "api/test/metrics/metric.proto proto format. File will contain " + "MetricsSet as a root proto. On iOS, this MUST be a file name " + "and the file will be stored under NSDocumentDirectory."); + +ABSL_FLAG(bool, + export_perf_results_new_api, + false, + "Tells to initialize new API for exporting performance metrics"); diff --git a/third_party/libwebrtc/test/test_flags.h b/third_party/libwebrtc/test/test_flags.h new file mode 100644 index 0000000000..30f918fc7d --- /dev/null +++ b/third_party/libwebrtc/test/test_flags.h @@ -0,0 +1,24 @@ +/* + * 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_TEST_FLAGS_H_ +#define TEST_TEST_FLAGS_H_ + +#include <string> +#include <vector> + +#include "absl/flags/declare.h" + +ABSL_DECLARE_FLAG(std::string, force_fieldtrials); +ABSL_DECLARE_FLAG(std::vector<std::string>, plot); +ABSL_DECLARE_FLAG(std::string, isolated_script_test_perf_output); +ABSL_DECLARE_FLAG(std::string, webrtc_test_metrics_output_path); +ABSL_DECLARE_FLAG(bool, export_perf_results_new_api); + +#endif // TEST_TEST_FLAGS_H_ diff --git a/third_party/libwebrtc/test/test_main.cc b/third_party/libwebrtc/test/test_main.cc new file mode 100644 index 0000000000..d811fd0e6d --- /dev/null +++ b/third_party/libwebrtc/test/test_main.cc @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2013 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 <memory> +#include <regex> +#include <string> +#include <vector> + +#include "absl/debugging/failure_signal_handler.h" +#include "absl/debugging/symbolize.h" +#include "absl/flags/parse.h" +#include "test/gmock.h" +#include "test/test_main_lib.h" + +namespace { + +std::vector<std::string> ReplaceDashesWithUnderscores(int argc, char* argv[]) { + std::vector<std::string> args(argv, argv + argc); + for (std::string& arg : args) { + // Only replace arguments that starts with a dash. + if (!arg.empty() && arg[0] == '-') { + // Don't replace the 2 first characters. + auto begin = arg.begin() + 2; + // Replace dashes on the left of '=' or on all the arg if no '=' is found. + auto end = std::find(arg.begin(), arg.end(), '='); + std::replace(begin, end, '-', '_'); + } + } + return args; +} + +std::vector<char*> VectorOfStringsToVectorOfPointers( + std::vector<std::string>& input) { + std::vector<char*> output(input.size()); + for (size_t i = 0; i < input.size(); ++i) { + output[i] = &(input[i][0]); + } + return output; +} + +} // namespace + +int main(int argc, char* argv[]) { + // Initialize the symbolizer to get a human-readable stack trace + absl::InitializeSymbolizer(argv[0]); + testing::InitGoogleMock(&argc, argv); + // Before parsing the arguments with the absl flag library, any internal '-' + // characters will be converted to '_' characters to make sure the string is a + // valid attribute name. + std::vector<std::string> new_argv = ReplaceDashesWithUnderscores(argc, argv); + std::vector<char*> raw_new_argv = VectorOfStringsToVectorOfPointers(new_argv); + absl::ParseCommandLine(argc, &raw_new_argv[0]); + +// This absl handler use unsupported features/instructions on Fuchsia +#if !defined(WEBRTC_FUCHSIA) + absl::FailureSignalHandlerOptions options; + absl::InstallFailureSignalHandler(options); +#endif + + std::unique_ptr<webrtc::TestMain> main = webrtc::TestMain::Create(); + int err_code = main->Init(); + if (err_code != 0) { + return err_code; + } + return main->Run(argc, argv); +} diff --git a/third_party/libwebrtc/test/test_main_lib.cc b/third_party/libwebrtc/test/test_main_lib.cc new file mode 100644 index 0000000000..4c80315ac5 --- /dev/null +++ b/third_party/libwebrtc/test/test_main_lib.cc @@ -0,0 +1,267 @@ +/* + * 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/test_main_lib.h" + +#include <fstream> +#include <memory> +#include <string> +#include <vector> + +#include "absl/flags/flag.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/test/metrics/chrome_perf_dashboard_metrics_exporter.h" +#include "api/test/metrics/global_metrics_logger_and_exporter.h" +#include "api/test/metrics/metrics_exporter.h" +#include "api/test/metrics/metrics_set_proto_file_exporter.h" +#include "api/test/metrics/print_result_proxy_metrics_exporter.h" +#include "api/test/metrics/stdout_metrics_exporter.h" +#include "rtc_base/checks.h" +#include "rtc_base/event_tracer.h" +#include "rtc_base/logging.h" +#include "rtc_base/ssl_adapter.h" +#include "rtc_base/ssl_stream_adapter.h" +#include "rtc_base/thread.h" +#include "system_wrappers/include/field_trial.h" +#include "system_wrappers/include/metrics.h" +#include "test/field_trial.h" +#include "test/gtest.h" +#include "test/test_flags.h" +#include "test/testsupport/perf_test.h" +#include "test/testsupport/resources_dir_flag.h" + +#if defined(WEBRTC_WIN) +#include "rtc_base/win32_socket_init.h" +#endif + +#if defined(WEBRTC_IOS) +#include "test/ios/test_support.h" + +ABSL_FLAG(std::string, + NSTreatUnknownArgumentsAsOpen, + "", + "Intentionally ignored flag intended for iOS test runner."); +ABSL_FLAG(std::string, + ApplePersistenceIgnoreState, + "", + "Intentionally ignored flag intended for iOS test runner."); +ABSL_FLAG(bool, + enable_run_ios_unittests_with_xctest, + false, + "Intentionally ignored flag intended for iOS test runner."); +ABSL_FLAG(bool, + write_compiled_tests_json_to_writable_path, + false, + "Intentionally ignored flag intended for iOS test runner."); + +// This is the cousin of isolated_script_test_perf_output, but we can't dictate +// where to write on iOS so the semantics of this flag are a bit different. +ABSL_FLAG( + bool, + write_perf_output_on_ios, + false, + "Store the perf results in Documents/perftest_result.pb in the format " + "described by histogram.proto in " + "https://chromium.googlesource.com/catapult/."); + +#elif defined(WEBRTC_FUCHSIA) +ABSL_FLAG(std::string, use_vulkan, "", "Intentionally ignored flag."); +#else +// TODO(bugs.webrtc.org/8115): Remove workaround when fixed. +ABSL_FLAG(bool, no_sandbox, false, "Intentionally ignored flag."); +ABSL_FLAG(bool, test_launcher_bot_mode, false, "Intentionally ignored flag."); +#endif + +ABSL_FLAG(std::string, + isolated_script_test_output, + "", + "Path to output an empty JSON file which Chromium infra requires."); + +ABSL_FLAG(bool, logs, true, "print logs to stderr"); +ABSL_FLAG(bool, verbose, false, "verbose logs to stderr"); + +ABSL_FLAG(std::string, + trace_event, + "", + "Path to collect trace events (json file) for chrome://tracing. " + "If not set, events aren't captured."); + +ABSL_FLAG(std::string, + test_launcher_shard_index, + "", + "Index of the test shard to run, from 0 to " + "the value specified with --test_launcher_total_shards."); + +ABSL_FLAG(std::string, + test_launcher_total_shards, + "", + "Total number of shards."); + +namespace webrtc { + +namespace { + +constexpr char kPlotAllMetrics[] = "all"; + +class TestMainImpl : public TestMain { + public: + int Init(int* argc, char* argv[]) override { return Init(); } + + int Init() override { + // Make sure we always pull in the --resources_dir flag, even if the test + // binary doesn't link with fileutils (downstream expects all test mains to + // have this flag). + (void)absl::GetFlag(FLAGS_resources_dir); + + // Default to LS_INFO, even for release builds to provide better test + // logging. + if (rtc::LogMessage::GetLogToDebug() > rtc::LS_INFO) + rtc::LogMessage::LogToDebug(rtc::LS_INFO); + + if (absl::GetFlag(FLAGS_verbose)) + rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE); + + rtc::LogMessage::SetLogToStderr(absl::GetFlag(FLAGS_logs) || + absl::GetFlag(FLAGS_verbose)); + + // The sharding arguments take precedence over the sharding environment + // variables. + if (!absl::GetFlag(FLAGS_test_launcher_shard_index).empty() && + !absl::GetFlag(FLAGS_test_launcher_total_shards).empty()) { + std::string shard_index = + "GTEST_SHARD_INDEX=" + absl::GetFlag(FLAGS_test_launcher_shard_index); + std::string total_shards = + "GTEST_TOTAL_SHARDS=" + + absl::GetFlag(FLAGS_test_launcher_total_shards); + putenv(shard_index.data()); + putenv(total_shards.data()); + } + + // InitFieldTrialsFromString stores the char*, so the char array must + // outlive the application. + field_trials_ = absl::GetFlag(FLAGS_force_fieldtrials); + webrtc::field_trial::InitFieldTrialsFromString(field_trials_.c_str()); + webrtc::metrics::Enable(); + +#if defined(WEBRTC_WIN) + winsock_init_ = std::make_unique<rtc::WinsockInitializer>(); +#endif + + // Initialize SSL which are used by several tests. + rtc::InitializeSSL(); + rtc::SSLStreamAdapter::EnableTimeCallbackForTesting(); + + return 0; + } + + int Run(int argc, char* argv[]) override { + std::string trace_event_path = absl::GetFlag(FLAGS_trace_event); + const bool capture_events = !trace_event_path.empty(); + if (capture_events) { + rtc::tracing::SetupInternalTracer(); + rtc::tracing::StartInternalCapture(trace_event_path); + } + + absl::optional<std::vector<std::string>> metrics_to_plot = + absl::GetFlag(FLAGS_plot); + + if (metrics_to_plot->empty()) { + metrics_to_plot = absl::nullopt; + } else { + if (metrics_to_plot->size() == 1 && + (*metrics_to_plot)[0] == kPlotAllMetrics) { + metrics_to_plot->clear(); + } + } + +#if defined(WEBRTC_IOS) + rtc::test::InitTestSuite( + RUN_ALL_TESTS, argc, argv, + absl::GetFlag(FLAGS_write_perf_output_on_ios), + absl::GetFlag(FLAGS_export_perf_results_new_api), + absl::GetFlag(FLAGS_webrtc_test_metrics_output_path), metrics_to_plot); + rtc::test::RunTestsFromIOSApp(); + int exit_code = 0; +#else + int exit_code = RUN_ALL_TESTS(); + + std::vector<std::unique_ptr<test::MetricsExporter>> exporters; + if (absl::GetFlag(FLAGS_export_perf_results_new_api)) { + exporters.push_back(std::make_unique<test::StdoutMetricsExporter>()); + if (!absl::GetFlag(FLAGS_webrtc_test_metrics_output_path).empty()) { + exporters.push_back( + std::make_unique<webrtc::test::MetricsSetProtoFileExporter>( + webrtc::test::MetricsSetProtoFileExporter::Options( + absl::GetFlag(FLAGS_webrtc_test_metrics_output_path)))); + } + if (!absl::GetFlag(FLAGS_isolated_script_test_perf_output).empty()) { + exporters.push_back( + std::make_unique<test::ChromePerfDashboardMetricsExporter>( + absl::GetFlag(FLAGS_isolated_script_test_perf_output))); + } + } else { + exporters.push_back( + std::make_unique<test::PrintResultProxyMetricsExporter>()); + } + test::ExportPerfMetric(*test::GetGlobalMetricsLogger(), + std::move(exporters)); + if (!absl::GetFlag(FLAGS_export_perf_results_new_api)) { + std::string perf_output_file = + absl::GetFlag(FLAGS_isolated_script_test_perf_output); + if (!perf_output_file.empty()) { + if (!webrtc::test::WritePerfResults(perf_output_file)) { + return 1; + } + } + if (metrics_to_plot) { + webrtc::test::PrintPlottableResults(*metrics_to_plot); + } + } + + std::string result_filename = + absl::GetFlag(FLAGS_isolated_script_test_output); + if (!result_filename.empty()) { + std::ofstream result_file(result_filename); + result_file << "{\"version\": 3}"; + } +#endif + + if (capture_events) { + rtc::tracing::StopInternalCapture(); + } + +#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \ + defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ + defined(UNDEFINED_SANITIZER) + // We want the test flagged as failed only for sanitizer defects, + // in which case the sanitizer will override exit code with 66. + exit_code = 0; +#endif + + return exit_code; + } + + ~TestMainImpl() override = default; + + private: +#if defined(WEBRTC_WIN) + std::unique_ptr<rtc::WinsockInitializer> winsock_init_; +#endif +}; + +} // namespace + +std::unique_ptr<TestMain> TestMain::Create() { + return std::make_unique<TestMainImpl>(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/test_main_lib.h b/third_party/libwebrtc/test/test_main_lib.h new file mode 100644 index 0000000000..2233171c60 --- /dev/null +++ b/third_party/libwebrtc/test/test_main_lib.h @@ -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. + */ +#ifndef TEST_TEST_MAIN_LIB_H_ +#define TEST_TEST_MAIN_LIB_H_ + +#include <memory> +#include <string> + +namespace webrtc { + +// Class to initialize test environment and run tests. +class TestMain { + public: + virtual ~TestMain() {} + + static std::unique_ptr<TestMain> Create(); + + // Initializes test environment. Clients can add their own initialization + // steps after call to this method and before running tests. + // Returns 0 if initialization was successful and non 0 otherwise. + virtual int Init() = 0; + // Temporary for backward compatibility + virtual int Init(int* argc, char* argv[]) = 0; + + // Runs test end return result error code. 0 - no errors. + virtual int Run(int argc, char* argv[]) = 0; + + protected: + TestMain() = default; + + std::string field_trials_; +}; + +} // namespace webrtc + +#endif // TEST_TEST_MAIN_LIB_H_ diff --git a/third_party/libwebrtc/test/test_video_capturer.cc b/third_party/libwebrtc/test/test_video_capturer.cc new file mode 100644 index 0000000000..4a4adc61d7 --- /dev/null +++ b/third_party/libwebrtc/test/test_video_capturer.cc @@ -0,0 +1,107 @@ +/* + * 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 "test/test_video_capturer.h" + +#include <algorithm> + +#include "api/scoped_refptr.h" +#include "api/video/i420_buffer.h" +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" + +namespace webrtc { +namespace test { +TestVideoCapturer::~TestVideoCapturer() = default; + +void TestVideoCapturer::OnOutputFormatRequest( + int width, + int height, + const absl::optional<int>& max_fps) { + absl::optional<std::pair<int, int>> target_aspect_ratio = + std::make_pair(width, height); + absl::optional<int> max_pixel_count = width * height; + video_adapter_.OnOutputFormatRequest(target_aspect_ratio, max_pixel_count, + max_fps); +} + +void TestVideoCapturer::OnFrame(const VideoFrame& original_frame) { + int cropped_width = 0; + int cropped_height = 0; + int out_width = 0; + int out_height = 0; + + VideoFrame frame = MaybePreprocess(original_frame); + + if (!video_adapter_.AdaptFrameResolution( + frame.width(), frame.height(), frame.timestamp_us() * 1000, + &cropped_width, &cropped_height, &out_width, &out_height)) { + // Drop frame in order to respect frame rate constraint. + return; + } + + if (out_height != frame.height() || out_width != frame.width()) { + // Video adapter has requested a down-scale. Allocate a new buffer and + // return scaled version. + // For simplicity, only scale here without cropping. + rtc::scoped_refptr<I420Buffer> scaled_buffer = + I420Buffer::Create(out_width, out_height); + scaled_buffer->ScaleFrom(*frame.video_frame_buffer()->ToI420()); + VideoFrame::Builder new_frame_builder = + VideoFrame::Builder() + .set_video_frame_buffer(scaled_buffer) + .set_rotation(kVideoRotation_0) + .set_timestamp_us(frame.timestamp_us()) + .set_id(frame.id()); + if (frame.has_update_rect()) { + VideoFrame::UpdateRect new_rect = frame.update_rect().ScaleWithFrame( + frame.width(), frame.height(), 0, 0, frame.width(), frame.height(), + out_width, out_height); + new_frame_builder.set_update_rect(new_rect); + } + broadcaster_.OnFrame(new_frame_builder.build()); + + } else { + // No adaptations needed, just return the frame as is. + broadcaster_.OnFrame(frame); + } +} + +rtc::VideoSinkWants TestVideoCapturer::GetSinkWants() { + return broadcaster_.wants(); +} + +void TestVideoCapturer::AddOrUpdateSink( + rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) { + broadcaster_.AddOrUpdateSink(sink, wants); + UpdateVideoAdapter(); +} + +void TestVideoCapturer::RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) { + broadcaster_.RemoveSink(sink); + UpdateVideoAdapter(); +} + +void TestVideoCapturer::UpdateVideoAdapter() { + video_adapter_.OnSinkWants(broadcaster_.wants()); +} + +VideoFrame TestVideoCapturer::MaybePreprocess(const VideoFrame& frame) { + MutexLock lock(&lock_); + if (preprocessor_ != nullptr) { + return preprocessor_->Preprocess(frame); + } else { + return frame; + } +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/test_video_capturer.h b/third_party/libwebrtc/test/test_video_capturer.h new file mode 100644 index 0000000000..6fafd96efb --- /dev/null +++ b/third_party/libwebrtc/test/test_video_capturer.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 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_TEST_VIDEO_CAPTURER_H_ +#define TEST_TEST_VIDEO_CAPTURER_H_ + +#include <stddef.h> + +#include <memory> + +#include "api/video/video_frame.h" +#include "api/video/video_source_interface.h" +#include "media/base/video_adapter.h" +#include "media/base/video_broadcaster.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace test { + +class TestVideoCapturer : public rtc::VideoSourceInterface<VideoFrame> { + public: + class FramePreprocessor { + public: + virtual ~FramePreprocessor() = default; + + virtual VideoFrame Preprocess(const VideoFrame& frame) = 0; + }; + + ~TestVideoCapturer() override; + + void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink, + const rtc::VideoSinkWants& wants) override; + void RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) override; + void SetFramePreprocessor(std::unique_ptr<FramePreprocessor> preprocessor) { + MutexLock lock(&lock_); + preprocessor_ = std::move(preprocessor); + } + void OnOutputFormatRequest(int width, + int height, + const absl::optional<int>& max_fps); + + protected: + void OnFrame(const VideoFrame& frame); + rtc::VideoSinkWants GetSinkWants(); + + private: + void UpdateVideoAdapter(); + VideoFrame MaybePreprocess(const VideoFrame& frame); + + Mutex lock_; + std::unique_ptr<FramePreprocessor> preprocessor_ RTC_GUARDED_BY(lock_); + rtc::VideoBroadcaster broadcaster_; + cricket::VideoAdapter video_adapter_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_TEST_VIDEO_CAPTURER_H_ 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..ff0d5a854c --- /dev/null +++ b/third_party/libwebrtc/test/testsupport/file_utils.cc @@ -0,0 +1,250 @@ +/* + * 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..92700e192f --- /dev/null +++ b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.cc @@ -0,0 +1,147 @@ +/* + * 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/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, /*missing_frames=*/false, + /*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; +} + +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(); + } + 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..4b6d116383 --- /dev/null +++ b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator.h @@ -0,0 +1,87 @@ +/* + * 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; + + 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..dd60f3d3fc --- /dev/null +++ b/third_party/libwebrtc/test/testsupport/ivf_video_frame_generator_unittest.cc @@ -0,0 +1,212 @@ +/* + * 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, 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(cricket::VideoCodec(cricket::kH264CodecName))); + 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_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..02c1a68008 --- /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 = 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..b9ea2d0c46 --- /dev/null +++ b/third_party/libwebrtc/test/testsupport/yuv_frame_reader_unittest.cc @@ -0,0 +1,146 @@ +/* + * 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 char kFrameContent[3][3] = {{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}; +constexpr int kNumFrames = sizeof(kFrameContent) / sizeof(kFrameContent[0]); +} // 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"); + FILE* file = fopen(filepath_.c_str(), "wb"); + fwrite(kFrameContent, 1, sizeof(kFrameContent), file); + fclose(file); + + reader_ = CreateYuvFrameReader(filepath_, kResolution); + } + + void TearDown() override { remove(filepath_.c_str()); } + + std::string filepath_; + std::unique_ptr<FrameReader> reader_; +}; + +TEST_F(YuvFrameReaderTest, num_frames) { + EXPECT_EQ(kNumFrames, reader_->num_frames()); +} + +TEST_F(YuvFrameReaderTest, 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(YuvFrameReaderTest, 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(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<RepeatMode, std::vector<int>>> {}; + +TEST_P(YuvFrameReaderRepeatModeTest, PullFrame) { + RepeatMode mode = std::get<0>(GetParam()); + std::vector<int> expected_frames = std::get<1>(GetParam()); + + reader_ = CreateYuvFrameReader(filepath_, kResolution, 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( + YuvFrameReaderTest, + YuvFrameReaderRepeatModeTest, + ::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 YuvFrameReaderFramerateScaleTest + : public YuvFrameReaderTest, + public ::testing::WithParamInterface< + std::tuple<Ratio, std::vector<int>>> {}; + +TEST_P(YuvFrameReaderFramerateScaleTest, 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(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 diff --git a/third_party/libwebrtc/test/time_controller/BUILD.gn b/third_party/libwebrtc/test/time_controller/BUILD.gn new file mode 100644 index 0000000000..b4b368a42a --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/BUILD.gn @@ -0,0 +1,70 @@ +# 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. + +import("../../webrtc.gni") + +rtc_library("time_controller") { + testonly = true + sources = [ + "external_time_controller.cc", + "external_time_controller.h", + "real_time_controller.cc", + "real_time_controller.h", + "simulated_task_queue.cc", + "simulated_task_queue.h", + "simulated_thread.cc", + "simulated_thread.h", + "simulated_time_controller.cc", + "simulated_time_controller.h", + ] + + deps = [ + "../../api:sequence_checker", + "../../api:time_controller", + "../../api/task_queue", + "../../api/task_queue:default_task_queue_factory", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../rtc_base:checks", + "../../rtc_base:null_socket_server", + "../../rtc_base:platform_thread_types", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_event", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/synchronization:yield_policy", + "../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/functional:any_invocable", + "//third_party/abseil-cpp/absl/strings", + ] +} + +if (rtc_include_tests) { + rtc_library("time_controller_unittests") { + testonly = true + sources = [ + "external_time_controller_unittest.cc", + "simulated_time_controller_unittest.cc", + "time_controller_conformance_test.cc", + ] + deps = [ + ":time_controller", + "../:test_support", + "../../api:time_controller", + "../../api/units:time_delta", + "../../rtc_base:macromagic", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_task_queue", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/task_utils:repeating_task", + ] + } +} diff --git a/third_party/libwebrtc/test/time_controller/external_time_controller.cc b/third_party/libwebrtc/test/time_controller/external_time_controller.cc new file mode 100644 index 0000000000..f652eb686c --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/external_time_controller.cc @@ -0,0 +1,134 @@ +/* + * Copyright 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/time_controller/external_time_controller.h" + +#include <algorithm> +#include <map> +#include <memory> +#include <utility> + +#include "absl/functional/any_invocable.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/checks.h" +#include "rtc_base/synchronization/yield_policy.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +// Wraps a TaskQueue so that it can reschedule the time controller whenever +// an external call schedules a new task. +class ExternalTimeController::TaskQueueWrapper : public TaskQueueBase { + public: + TaskQueueWrapper(ExternalTimeController* parent, + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> base) + : parent_(parent), base_(std::move(base)) {} + + void PostTask(absl::AnyInvocable<void() &&> task) override { + parent_->UpdateTime(); + base_->PostTask(TaskWrapper(std::move(task))); + parent_->ScheduleNext(); + } + + void PostDelayedTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) override { + parent_->UpdateTime(); + base_->PostDelayedTask(TaskWrapper(std::move(task)), delay); + parent_->ScheduleNext(); + } + + void PostDelayedHighPrecisionTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) override { + parent_->UpdateTime(); + base_->PostDelayedHighPrecisionTask(TaskWrapper(std::move(task)), delay); + parent_->ScheduleNext(); + } + + void Delete() override { delete this; } + + private: + absl::AnyInvocable<void() &&> TaskWrapper( + absl::AnyInvocable<void() &&> task) { + return [task = std::move(task), this]() mutable { + CurrentTaskQueueSetter current(this); + std::move(task)(); + }; + } + + ExternalTimeController* const parent_; + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> base_; +}; + +ExternalTimeController::ExternalTimeController(ControlledAlarmClock* alarm) + : alarm_(alarm), + impl_(alarm_->GetClock()->CurrentTime()), + yield_policy_(&impl_) { + global_clock_.SetTime(alarm_->GetClock()->CurrentTime()); + alarm_->SetCallback([this] { Run(); }); +} + +Clock* ExternalTimeController::GetClock() { + return alarm_->GetClock(); +} + +TaskQueueFactory* ExternalTimeController::GetTaskQueueFactory() { + return this; +} + +void ExternalTimeController::AdvanceTime(TimeDelta duration) { + alarm_->Sleep(duration); +} + +std::unique_ptr<rtc::Thread> ExternalTimeController::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + RTC_DCHECK_NOTREACHED(); + return nullptr; +} + +rtc::Thread* ExternalTimeController::GetMainThread() { + RTC_DCHECK_NOTREACHED(); + return nullptr; +} + +std::unique_ptr<TaskQueueBase, TaskQueueDeleter> +ExternalTimeController::CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const { + return std::unique_ptr<TaskQueueBase, TaskQueueDeleter>( + new TaskQueueWrapper(const_cast<ExternalTimeController*>(this), + impl_.CreateTaskQueue(name, priority))); +} + +void ExternalTimeController::Run() { + rtc::ScopedYieldPolicy yield_policy(&impl_); + UpdateTime(); + impl_.RunReadyRunners(); + ScheduleNext(); +} + +void ExternalTimeController::UpdateTime() { + Timestamp now = alarm_->GetClock()->CurrentTime(); + impl_.AdvanceTime(now); + global_clock_.SetTime(now); +} + +void ExternalTimeController::ScheduleNext() { + RTC_DCHECK_EQ(impl_.CurrentTime(), alarm_->GetClock()->CurrentTime()); + TimeDelta delay = + std::max(impl_.NextRunTime() - impl_.CurrentTime(), TimeDelta::Zero()); + if (delay.IsFinite()) { + alarm_->ScheduleAlarmAt(alarm_->GetClock()->CurrentTime() + delay); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/external_time_controller.h b/third_party/libwebrtc/test/time_controller/external_time_controller.h new file mode 100644 index 0000000000..a67f2557b4 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/external_time_controller.h @@ -0,0 +1,69 @@ +/* + * Copyright 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_TIME_CONTROLLER_EXTERNAL_TIME_CONTROLLER_H_ +#define TEST_TIME_CONTROLLER_EXTERNAL_TIME_CONTROLLER_H_ + +#include <functional> +#include <memory> + +#include "absl/strings/string_view.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "system_wrappers/include/clock.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +// TimeController implementation built on an external controlled alarm. +// This implementation is used to delegate scheduling and execution to an +// external run loop. +class ExternalTimeController : public TimeController, public TaskQueueFactory { + public: + explicit ExternalTimeController(ControlledAlarmClock* alarm); + + // Implementation of TimeController. + Clock* GetClock() override; + TaskQueueFactory* GetTaskQueueFactory() override; + void AdvanceTime(TimeDelta duration) override; + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) override; + rtc::Thread* GetMainThread() override; + + // Implementation of TaskQueueFactory. + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const override; + + private: + class TaskQueueWrapper; + + // Executes any tasks scheduled at or before the current time. May call + // `ScheduleNext` to schedule the next call to `Run`. + void Run(); + + void UpdateTime(); + void ScheduleNext(); + + ControlledAlarmClock* alarm_; + sim_time_impl::SimulatedTimeControllerImpl impl_; + rtc::ScopedYieldPolicy yield_policy_; + + // Overrides the global rtc::Clock to ensure that it reports the same times as + // the time controller. + rtc::ScopedBaseFakeClock global_clock_; +}; + +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_EXTERNAL_TIME_CONTROLLER_H_ diff --git a/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc b/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc new file mode 100644 index 0000000000..13d63fe8ed --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/external_time_controller_unittest.cc @@ -0,0 +1,179 @@ +/* + * Copyright 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/time_controller/external_time_controller.h" + +#include <atomic> +#include <memory> +#include <utility> + +#include "rtc_base/event.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "test/gmock.h" +#include "test/gtest.h" + +// NOTE: Since these tests rely on real time behavior, they will be flaky +// if run on heavily loaded systems. +namespace webrtc { +namespace { +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::NiceMock; +using ::testing::Return; +constexpr Timestamp kStartTime = Timestamp::Seconds(1000); + +class FakeAlarm : public ControlledAlarmClock { + public: + explicit FakeAlarm(Timestamp start_time); + + Clock* GetClock() override; + bool ScheduleAlarmAt(Timestamp deadline) override; + void SetCallback(std::function<void()> callback) override; + void Sleep(TimeDelta duration) override; + + private: + SimulatedClock clock_; + Timestamp deadline_; + std::function<void()> callback_; +}; + +FakeAlarm::FakeAlarm(Timestamp start_time) + : clock_(start_time), + deadline_(Timestamp::PlusInfinity()), + callback_([] {}) {} + +Clock* FakeAlarm::GetClock() { + return &clock_; +} + +bool FakeAlarm::ScheduleAlarmAt(Timestamp deadline) { + if (deadline < deadline_) { + deadline_ = deadline; + return true; + } + return false; +} + +void FakeAlarm::SetCallback(std::function<void()> callback) { + callback_ = callback; +} + +void FakeAlarm::Sleep(TimeDelta duration) { + Timestamp end_time = clock_.CurrentTime() + duration; + + while (deadline_ <= end_time) { + clock_.AdvanceTime(deadline_ - clock_.CurrentTime()); + deadline_ = Timestamp::PlusInfinity(); + callback_(); + } + + clock_.AdvanceTime(end_time - clock_.CurrentTime()); +} + +} // namespace + +TEST(ExternalTimeControllerTest, TaskIsStoppedOnStop) { + const TimeDelta kShortInterval = TimeDelta::Millis(5); + const TimeDelta kLongInterval = TimeDelta::Millis(20); + const int kShortIntervalCount = 4; + const int kMargin = 1; + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + std::atomic_int counter(0); + auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] { + if (++counter >= kShortIntervalCount) + return kLongInterval; + return kShortInterval; + }); + // Sleep long enough to go through the initial phase. + time_simulation.AdvanceTime(kShortInterval * (kShortIntervalCount + kMargin)); + EXPECT_EQ(counter.load(), kShortIntervalCount); + + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + + // Sleep long enough that the task would run at least once more if not + // stopped. + time_simulation.AdvanceTime(kLongInterval * 2); + EXPECT_EQ(counter.load(), kShortIntervalCount); +} + +TEST(ExternalTimeControllerTest, TaskCanStopItself) { + std::atomic_int counter(0); + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + RepeatingTaskHandle handle; + task_queue.PostTask([&] { + handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] { + ++counter; + handle.Stop(); + return TimeDelta::Millis(2); + }); + }); + time_simulation.AdvanceTime(TimeDelta::Millis(10)); + EXPECT_EQ(counter.load(), 1); +} + +TEST(ExternalTimeControllerTest, YieldForTask) { + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + rtc::Event event; + task_queue.PostTask([&] { event.Set(); }); + EXPECT_TRUE(event.Wait(TimeDelta::Millis(200))); +} + +TEST(ExternalTimeControllerTest, TasksYieldToEachOther) { + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + rtc::TaskQueue other_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "OtherQueue", TaskQueueFactory::Priority::NORMAL)); + + task_queue.PostTask([&] { + rtc::Event event; + other_queue.PostTask([&] { event.Set(); }); + EXPECT_TRUE(event.Wait(TimeDelta::Millis(200))); + }); + + time_simulation.AdvanceTime(TimeDelta::Millis(300)); +} + +TEST(ExternalTimeControllerTest, CurrentTaskQueue) { + FakeAlarm alarm(kStartTime); + ExternalTimeController time_simulation(&alarm); + + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + task_queue.PostTask([&] { EXPECT_TRUE(task_queue.IsCurrent()); }); + + time_simulation.AdvanceTime(TimeDelta::Millis(10)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/real_time_controller.cc b/third_party/libwebrtc/test/time_controller/real_time_controller.cc new file mode 100644 index 0000000000..7cc750d6d4 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/real_time_controller.cc @@ -0,0 +1,66 @@ +/* + * Copyright 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/time_controller/real_time_controller.h" + +#include "api/task_queue/default_task_queue_factory.h" +#include "rtc_base/null_socket_server.h" + +namespace webrtc { +namespace { +class MainThread : public rtc::Thread { + public: + MainThread() + : Thread(std::make_unique<rtc::NullSocketServer>(), false), + current_setter_(this) { + DoInit(); + } + ~MainThread() { + Stop(); + DoDestroy(); + } + + private: + CurrentThreadSetter current_setter_; +}; +} // namespace +RealTimeController::RealTimeController() + : task_queue_factory_(CreateDefaultTaskQueueFactory()), + main_thread_(std::make_unique<MainThread>()) { + main_thread_->SetName("Main", this); +} + +Clock* RealTimeController::GetClock() { + return Clock::GetRealTimeClock(); +} + +TaskQueueFactory* RealTimeController::GetTaskQueueFactory() { + return task_queue_factory_.get(); +} + +std::unique_ptr<rtc::Thread> RealTimeController::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + if (!socket_server) + socket_server = std::make_unique<rtc::NullSocketServer>(); + auto res = std::make_unique<rtc::Thread>(std::move(socket_server)); + res->SetName(name, nullptr); + res->Start(); + return res; +} + +rtc::Thread* RealTimeController::GetMainThread() { + return main_thread_.get(); +} + +void RealTimeController::AdvanceTime(TimeDelta duration) { + main_thread_->ProcessMessages(duration.ms()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/real_time_controller.h b/third_party/libwebrtc/test/time_controller/real_time_controller.h new file mode 100644 index 0000000000..5f02eaf85f --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/real_time_controller.h @@ -0,0 +1,41 @@ +/* + * Copyright 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_TIME_CONTROLLER_REAL_TIME_CONTROLLER_H_ +#define TEST_TIME_CONTROLLER_REAL_TIME_CONTROLLER_H_ + +#include <functional> +#include <memory> + +#include "api/task_queue/task_queue_factory.h" +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +class RealTimeController : public TimeController { + public: + RealTimeController(); + + Clock* GetClock() override; + TaskQueueFactory* GetTaskQueueFactory() override; + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) override; + rtc::Thread* GetMainThread() override; + void AdvanceTime(TimeDelta duration) override; + + private: + const std::unique_ptr<TaskQueueFactory> task_queue_factory_; + const std::unique_ptr<rtc::Thread> main_thread_; +}; + +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_REAL_TIME_CONTROLLER_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_task_queue.cc b/third_party/libwebrtc/test/time_controller/simulated_task_queue.cc new file mode 100644 index 0000000000..3c26721845 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_task_queue.cc @@ -0,0 +1,89 @@ +/* + * 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/time_controller/simulated_task_queue.h" + +#include <algorithm> +#include <utility> + +namespace webrtc { + +SimulatedTaskQueue::SimulatedTaskQueue( + sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name) + : handler_(handler), name_(new char[name.size()]) { + std::copy_n(name.begin(), name.size(), name_); +} + +SimulatedTaskQueue::~SimulatedTaskQueue() { + handler_->Unregister(this); + delete[] name_; +} + +void SimulatedTaskQueue::Delete() { + // Need to destroy the tasks outside of the lock because task destruction + // can lead to re-entry in SimulatedTaskQueue via custom destructors. + std::deque<absl::AnyInvocable<void() &&>> ready_tasks; + std::map<Timestamp, std::vector<absl::AnyInvocable<void() &&>>> delayed_tasks; + { + MutexLock lock(&lock_); + ready_tasks_.swap(ready_tasks); + delayed_tasks_.swap(delayed_tasks); + } + ready_tasks.clear(); + delayed_tasks.clear(); + delete this; +} + +void SimulatedTaskQueue::RunReady(Timestamp at_time) { + MutexLock lock(&lock_); + for (auto it = delayed_tasks_.begin(); + it != delayed_tasks_.end() && it->first <= at_time; + it = delayed_tasks_.erase(it)) { + for (auto& task : it->second) { + ready_tasks_.push_back(std::move(task)); + } + } + CurrentTaskQueueSetter set_current(this); + while (!ready_tasks_.empty()) { + absl::AnyInvocable<void()&&> ready = std::move(ready_tasks_.front()); + ready_tasks_.pop_front(); + lock_.Unlock(); + std::move(ready)(); + ready = nullptr; + lock_.Lock(); + } + if (!delayed_tasks_.empty()) { + next_run_time_ = delayed_tasks_.begin()->first; + } else { + next_run_time_ = Timestamp::PlusInfinity(); + } +} + +void SimulatedTaskQueue::PostTask(absl::AnyInvocable<void() &&> task) { + MutexLock lock(&lock_); + ready_tasks_.push_back(std::move(task)); + next_run_time_ = Timestamp::MinusInfinity(); +} + +void SimulatedTaskQueue::PostDelayedTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) { + PostDelayedHighPrecisionTask(std::move(task), delay); +} + +void SimulatedTaskQueue::PostDelayedHighPrecisionTask( + absl::AnyInvocable<void() &&> task, + TimeDelta delay) { + MutexLock lock(&lock_); + Timestamp target_time = handler_->CurrentTime() + delay; + delayed_tasks_[target_time].push_back(std::move(task)); + next_run_time_ = std::min(next_run_time_, target_time); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/simulated_task_queue.h b/third_party/libwebrtc/test/time_controller/simulated_task_queue.h new file mode 100644 index 0000000000..3c55f15dde --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_task_queue.h @@ -0,0 +1,65 @@ +/* + * 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_TIME_CONTROLLER_SIMULATED_TASK_QUEUE_H_ +#define TEST_TIME_CONTROLLER_SIMULATED_TASK_QUEUE_H_ + +#include <deque> +#include <map> +#include <memory> +#include <vector> + +#include "absl/functional/any_invocable.h" +#include "api/units/time_delta.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +class SimulatedTaskQueue : public TaskQueueBase, + public sim_time_impl::SimulatedSequenceRunner { + public: + SimulatedTaskQueue(sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name); + + ~SimulatedTaskQueue(); + + void RunReady(Timestamp at_time) override; + + Timestamp GetNextRunTime() const override { + MutexLock lock(&lock_); + return next_run_time_; + } + TaskQueueBase* GetAsTaskQueue() override { return this; } + + // TaskQueueBase interface + void Delete() override; + void PostTask(absl::AnyInvocable<void() &&> task) override; + void PostDelayedTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) override; + void PostDelayedHighPrecisionTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) override; + + private: + sim_time_impl::SimulatedTimeControllerImpl* const handler_; + // Using char* to be debugger friendly. + char* name_; + + mutable Mutex lock_; + + std::deque<absl::AnyInvocable<void() &&>> ready_tasks_ RTC_GUARDED_BY(lock_); + std::map<Timestamp, std::vector<absl::AnyInvocable<void() &&>>> delayed_tasks_ + RTC_GUARDED_BY(lock_); + + Timestamp next_run_time_ RTC_GUARDED_BY(lock_) = Timestamp::PlusInfinity(); +}; + +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_SIMULATED_TASK_QUEUE_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_thread.cc b/third_party/libwebrtc/test/time_controller/simulated_thread.cc new file mode 100644 index 0000000000..bdd1096327 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_thread.cc @@ -0,0 +1,118 @@ +/* + * 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/time_controller/simulated_thread.h" + +#include <algorithm> +#include <utility> + +namespace webrtc { +namespace { + +// A socket server that does nothing. It's different from NullSocketServer in +// that it does allow sleep/wakeup. This avoids usage of an Event instance which +// otherwise would cause issues with the simulated Yeild behavior. +class DummySocketServer : public rtc::SocketServer { + public: + rtc::Socket* CreateSocket(int family, int type) override { + RTC_DCHECK_NOTREACHED(); + return nullptr; + } + bool Wait(TimeDelta max_wait_duration, bool process_io) override { + RTC_CHECK(max_wait_duration.IsZero()); + return true; + } + void WakeUp() override {} +}; + +} // namespace + +SimulatedThread::SimulatedThread( + sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name, + std::unique_ptr<rtc::SocketServer> socket_server) + : rtc::Thread(socket_server ? std::move(socket_server) + : std::make_unique<DummySocketServer>()), + handler_(handler), + name_(new char[name.size()]) { + std::copy_n(name.begin(), name.size(), name_); +} + +SimulatedThread::~SimulatedThread() { + handler_->Unregister(this); + delete[] name_; +} + +void SimulatedThread::RunReady(Timestamp at_time) { + CurrentThreadSetter set_current(this); + ProcessMessages(0); + int delay_ms = GetDelay(); + MutexLock lock(&lock_); + if (delay_ms == kForever) { + next_run_time_ = Timestamp::PlusInfinity(); + } else { + next_run_time_ = at_time + TimeDelta::Millis(delay_ms); + } +} + +void SimulatedThread::BlockingCall(rtc::FunctionView<void()> functor) { + if (IsQuitting()) + return; + + if (IsCurrent()) { + functor(); + } else { + TaskQueueBase* yielding_from = TaskQueueBase::Current(); + handler_->StartYield(yielding_from); + RunReady(Timestamp::MinusInfinity()); + CurrentThreadSetter set_current(this); + functor(); + handler_->StopYield(yielding_from); + } +} + +void SimulatedThread::PostTask(absl::AnyInvocable<void() &&> task) { + rtc::Thread::PostTask(std::move(task)); + MutexLock lock(&lock_); + next_run_time_ = Timestamp::MinusInfinity(); +} + +void SimulatedThread::PostDelayedTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) { + rtc::Thread::PostDelayedTask(std::move(task), delay); + MutexLock lock(&lock_); + next_run_time_ = + std::min(next_run_time_, Timestamp::Millis(rtc::TimeMillis()) + delay); +} + +void SimulatedThread::PostDelayedHighPrecisionTask( + absl::AnyInvocable<void() &&> task, + TimeDelta delay) { + rtc::Thread::PostDelayedHighPrecisionTask(std::move(task), delay); + MutexLock lock(&lock_); + next_run_time_ = + std::min(next_run_time_, Timestamp::Millis(rtc::TimeMillis()) + delay); +} + +void SimulatedThread::Stop() { + Thread::Quit(); +} + +SimulatedMainThread::SimulatedMainThread( + sim_time_impl::SimulatedTimeControllerImpl* handler) + : SimulatedThread(handler, "main", nullptr), current_setter_(this) {} + +SimulatedMainThread::~SimulatedMainThread() { + // Removes pending tasks in case they keep shared pointer references to + // objects whose destructor expects to run before the Thread destructor. + Stop(); + DoDestroy(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/simulated_thread.h b/third_party/libwebrtc/test/time_controller/simulated_thread.h new file mode 100644 index 0000000000..e8e08c5000 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_thread.h @@ -0,0 +1,66 @@ +/* + * 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_TIME_CONTROLLER_SIMULATED_THREAD_H_ +#define TEST_TIME_CONTROLLER_SIMULATED_THREAD_H_ + +#include <memory> + +#include "rtc_base/synchronization/mutex.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +class SimulatedThread : public rtc::Thread, + public sim_time_impl::SimulatedSequenceRunner { + public: + using CurrentThreadSetter = CurrentThreadSetter; + SimulatedThread(sim_time_impl::SimulatedTimeControllerImpl* handler, + absl::string_view name, + std::unique_ptr<rtc::SocketServer> socket_server); + ~SimulatedThread() override; + + void RunReady(Timestamp at_time) override; + + Timestamp GetNextRunTime() const override { + MutexLock lock(&lock_); + return next_run_time_; + } + + TaskQueueBase* GetAsTaskQueue() override { return this; } + + // Thread interface + void BlockingCall(rtc::FunctionView<void()> functor) override; + void PostTask(absl::AnyInvocable<void() &&> task) override; + void PostDelayedTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) override; + void PostDelayedHighPrecisionTask(absl::AnyInvocable<void() &&> task, + TimeDelta delay) override; + + void Stop() override; + + private: + sim_time_impl::SimulatedTimeControllerImpl* const handler_; + // Using char* to be debugger friendly. + char* name_; + mutable Mutex lock_; + Timestamp next_run_time_ RTC_GUARDED_BY(lock_) = Timestamp::PlusInfinity(); +}; + +class SimulatedMainThread : public SimulatedThread { + public: + explicit SimulatedMainThread( + sim_time_impl::SimulatedTimeControllerImpl* handler); + ~SimulatedMainThread(); + + private: + CurrentThreadSetter current_setter_; +}; +} // namespace webrtc +#endif // TEST_TIME_CONTROLLER_SIMULATED_THREAD_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc b/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc new file mode 100644 index 0000000000..1ed2b30dc8 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller.cc @@ -0,0 +1,224 @@ +/* + * Copyright 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/time_controller/simulated_time_controller.h" + +#include <algorithm> +#include <deque> +#include <list> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +#include "absl/strings/string_view.h" +#include "test/time_controller/simulated_task_queue.h" +#include "test/time_controller/simulated_thread.h" + +namespace webrtc { +namespace { +// Helper function to remove from a std container by value. +template <class C> +bool RemoveByValue(C* vec, typename C::value_type val) { + auto it = std::find(vec->begin(), vec->end(), val); + if (it == vec->end()) + return false; + vec->erase(it); + return true; +} +} // namespace + +namespace sim_time_impl { + +SimulatedTimeControllerImpl::SimulatedTimeControllerImpl(Timestamp start_time) + : thread_id_(rtc::CurrentThreadId()), current_time_(start_time) {} + +SimulatedTimeControllerImpl::~SimulatedTimeControllerImpl() = default; + +std::unique_ptr<TaskQueueBase, TaskQueueDeleter> +SimulatedTimeControllerImpl::CreateTaskQueue( + absl::string_view name, + TaskQueueFactory::Priority priority) const { + // TODO(srte): Remove the const cast when the interface is made mutable. + auto mutable_this = const_cast<SimulatedTimeControllerImpl*>(this); + auto task_queue = std::unique_ptr<SimulatedTaskQueue, TaskQueueDeleter>( + new SimulatedTaskQueue(mutable_this, name)); + mutable_this->Register(task_queue.get()); + return task_queue; +} + +std::unique_ptr<rtc::Thread> SimulatedTimeControllerImpl::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + auto thread = + std::make_unique<SimulatedThread>(this, name, std::move(socket_server)); + Register(thread.get()); + return thread; +} + +void SimulatedTimeControllerImpl::YieldExecution() { + if (rtc::CurrentThreadId() == thread_id_) { + TaskQueueBase* yielding_from = TaskQueueBase::Current(); + // Since we might continue execution on a process thread, we should reset + // the thread local task queue reference. This ensures that thread checkers + // won't think we are executing on the yielding task queue. It also ensure + // that TaskQueueBase::Current() won't return the yielding task queue. + TokenTaskQueue::CurrentTaskQueueSetter reset_queue(nullptr); + // When we yield, we don't want to risk executing further tasks on the + // currently executing task queue. If there's a ready task that also yields, + // it's added to this set as well and only tasks on the remaining task + // queues are executed. + auto inserted = yielded_.insert(yielding_from); + RTC_DCHECK(inserted.second); + RunReadyRunners(); + yielded_.erase(inserted.first); + } +} + +void SimulatedTimeControllerImpl::RunReadyRunners() { + // Using a dummy thread rather than nullptr to avoid implicit thread creation + // by Thread::Current(). + SimulatedThread::CurrentThreadSetter set_current(dummy_thread_.get()); + MutexLock lock(&lock_); + RTC_DCHECK_EQ(rtc::CurrentThreadId(), thread_id_); + Timestamp current_time = CurrentTime(); + // Clearing `ready_runners_` in case this is a recursive call: + // RunReadyRunners -> Run -> Event::Wait -> Yield ->RunReadyRunners + ready_runners_.clear(); + + // We repeat until we have no ready left to handle tasks posted by ready + // runners. + while (true) { + for (auto* runner : runners_) { + if (yielded_.find(runner->GetAsTaskQueue()) == yielded_.end() && + runner->GetNextRunTime() <= current_time) { + ready_runners_.push_back(runner); + } + } + if (ready_runners_.empty()) + break; + while (!ready_runners_.empty()) { + auto* runner = ready_runners_.front(); + ready_runners_.pop_front(); + lock_.Unlock(); + // Note that the RunReady function might indirectly cause a call to + // Unregister() which will grab `lock_` again to remove items from + // `ready_runners_`. + runner->RunReady(current_time); + lock_.Lock(); + } + } +} + +Timestamp SimulatedTimeControllerImpl::CurrentTime() const { + MutexLock lock(&time_lock_); + return current_time_; +} + +Timestamp SimulatedTimeControllerImpl::NextRunTime() const { + Timestamp current_time = CurrentTime(); + Timestamp next_time = Timestamp::PlusInfinity(); + MutexLock lock(&lock_); + for (auto* runner : runners_) { + Timestamp next_run_time = runner->GetNextRunTime(); + if (next_run_time <= current_time) + return current_time; + next_time = std::min(next_time, next_run_time); + } + return next_time; +} + +void SimulatedTimeControllerImpl::AdvanceTime(Timestamp target_time) { + MutexLock time_lock(&time_lock_); + RTC_DCHECK_GE(target_time, current_time_); + current_time_ = target_time; +} + +void SimulatedTimeControllerImpl::Register(SimulatedSequenceRunner* runner) { + MutexLock lock(&lock_); + runners_.push_back(runner); +} + +void SimulatedTimeControllerImpl::Unregister(SimulatedSequenceRunner* runner) { + MutexLock lock(&lock_); + bool removed = RemoveByValue(&runners_, runner); + RTC_CHECK(removed); + RemoveByValue(&ready_runners_, runner); +} + +void SimulatedTimeControllerImpl::StartYield(TaskQueueBase* yielding_from) { + auto inserted = yielded_.insert(yielding_from); + RTC_DCHECK(inserted.second); +} + +void SimulatedTimeControllerImpl::StopYield(TaskQueueBase* yielding_from) { + yielded_.erase(yielding_from); +} + +} // namespace sim_time_impl + +GlobalSimulatedTimeController::GlobalSimulatedTimeController( + Timestamp start_time) + : sim_clock_(start_time.us()), impl_(start_time), yield_policy_(&impl_) { + global_clock_.SetTime(start_time); + auto main_thread = std::make_unique<SimulatedMainThread>(&impl_); + impl_.Register(main_thread.get()); + main_thread_ = std::move(main_thread); +} + +GlobalSimulatedTimeController::~GlobalSimulatedTimeController() = default; + +Clock* GlobalSimulatedTimeController::GetClock() { + return &sim_clock_; +} + +TaskQueueFactory* GlobalSimulatedTimeController::GetTaskQueueFactory() { + return &impl_; +} + +std::unique_ptr<rtc::Thread> GlobalSimulatedTimeController::CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) { + return impl_.CreateThread(name, std::move(socket_server)); +} + +rtc::Thread* GlobalSimulatedTimeController::GetMainThread() { + return main_thread_.get(); +} + +void GlobalSimulatedTimeController::AdvanceTime(TimeDelta duration) { + rtc::ScopedYieldPolicy yield_policy(&impl_); + Timestamp current_time = impl_.CurrentTime(); + Timestamp target_time = current_time + duration; + RTC_DCHECK_EQ(current_time.us(), rtc::TimeMicros()); + while (current_time < target_time) { + impl_.RunReadyRunners(); + Timestamp next_time = std::min(impl_.NextRunTime(), target_time); + impl_.AdvanceTime(next_time); + auto delta = next_time - current_time; + current_time = next_time; + sim_clock_.AdvanceTimeMicroseconds(delta.us()); + global_clock_.AdvanceTime(delta); + } + // After time has been simulated up until `target_time` we also need to run + // tasks meant to be executed at `target_time`. + impl_.RunReadyRunners(); +} + +void GlobalSimulatedTimeController::Register( + sim_time_impl::SimulatedSequenceRunner* runner) { + impl_.Register(runner); +} + +void GlobalSimulatedTimeController::Unregister( + sim_time_impl::SimulatedSequenceRunner* runner) { + impl_.Unregister(runner); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller.h b/third_party/libwebrtc/test/time_controller/simulated_time_controller.h new file mode 100644 index 0000000000..121b9171e8 --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller.h @@ -0,0 +1,162 @@ +/* + * Copyright 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_TIME_CONTROLLER_SIMULATED_TIME_CONTROLLER_H_ +#define TEST_TIME_CONTROLLER_SIMULATED_TIME_CONTROLLER_H_ + +#include <list> +#include <memory> +#include <unordered_set> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/sequence_checker.h" +#include "api/test/time_controller.h" +#include "api/units/timestamp.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/platform_thread_types.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/synchronization/yield_policy.h" + +namespace webrtc { +namespace sim_time_impl { +class SimulatedSequenceRunner { + public: + virtual ~SimulatedSequenceRunner() = default; + // Provides next run time. + virtual Timestamp GetNextRunTime() const = 0; + // Runs all ready tasks and modules and updates next run time. + virtual void RunReady(Timestamp at_time) = 0; + + // All implementations also implements TaskQueueBase in some form, but if we'd + // inherit from it in this interface we'd run into issues with double + // inheritance. Therefore we simply allow the implementations to provide a + // casted pointer to themself. + virtual TaskQueueBase* GetAsTaskQueue() = 0; +}; + +class SimulatedTimeControllerImpl : public TaskQueueFactory, + public rtc::YieldInterface { + public: + explicit SimulatedTimeControllerImpl(Timestamp start_time); + ~SimulatedTimeControllerImpl() override; + + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue( + absl::string_view name, + Priority priority) const RTC_LOCKS_EXCLUDED(time_lock_) override; + + // Implements the YieldInterface by running ready tasks on all task queues, + // except that if this method is called from a task, the task queue running + // that task is skipped. + void YieldExecution() RTC_LOCKS_EXCLUDED(time_lock_, lock_) override; + + // Create thread using provided `socket_server`. + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) + RTC_LOCKS_EXCLUDED(time_lock_, lock_); + + // Runs all runners in `runners_` that has tasks or modules ready for + // execution. + void RunReadyRunners() RTC_LOCKS_EXCLUDED(time_lock_, lock_); + // Return `current_time_`. + Timestamp CurrentTime() const RTC_LOCKS_EXCLUDED(time_lock_); + // Return min of runner->GetNextRunTime() for runner in `runners_`. + Timestamp NextRunTime() const RTC_LOCKS_EXCLUDED(lock_); + // Set `current_time_` to `target_time`. + void AdvanceTime(Timestamp target_time) RTC_LOCKS_EXCLUDED(time_lock_); + // Adds `runner` to `runners_`. + void Register(SimulatedSequenceRunner* runner) RTC_LOCKS_EXCLUDED(lock_); + // Removes `runner` from `runners_`. + void Unregister(SimulatedSequenceRunner* runner) RTC_LOCKS_EXCLUDED(lock_); + + // Indicates that `yielding_from` is not ready to run. + void StartYield(TaskQueueBase* yielding_from); + // Indicates that processing can be continued on `yielding_from`. + void StopYield(TaskQueueBase* yielding_from); + + private: + const rtc::PlatformThreadId thread_id_; + const std::unique_ptr<rtc::Thread> dummy_thread_ = rtc::Thread::Create(); + mutable Mutex time_lock_; + Timestamp current_time_ RTC_GUARDED_BY(time_lock_); + mutable Mutex lock_; + std::vector<SimulatedSequenceRunner*> runners_ RTC_GUARDED_BY(lock_); + // Used in RunReadyRunners() to keep track of ready runners that are to be + // processed in a round robin fashion. the reason it's a member is so that + // runners can removed from here by Unregister(). + std::list<SimulatedSequenceRunner*> ready_runners_ RTC_GUARDED_BY(lock_); + + // Runners on which YieldExecution has been called. + std::unordered_set<TaskQueueBase*> yielded_; +}; +} // namespace sim_time_impl + +// Used to satisfy sequence checkers for non task queue sequences. +class TokenTaskQueue : public TaskQueueBase { + public: + // Promoted to public + using CurrentTaskQueueSetter = TaskQueueBase::CurrentTaskQueueSetter; + + void Delete() override { RTC_DCHECK_NOTREACHED(); } + void PostTask(absl::AnyInvocable<void() &&> /*task*/) override { + RTC_DCHECK_NOTREACHED(); + } + void PostDelayedTask(absl::AnyInvocable<void() &&> /*task*/, + TimeDelta /*delay*/) override { + RTC_DCHECK_NOTREACHED(); + } + void PostDelayedHighPrecisionTask(absl::AnyInvocable<void() &&> /*task*/, + TimeDelta /*delay*/) override { + RTC_DCHECK_NOTREACHED(); + } +}; + +// TimeController implementation using completely simulated time. Task queues +// and process threads created by this controller will run delayed activities +// when AdvanceTime() is called. Overrides the global clock backing +// rtc::TimeMillis() and rtc::TimeMicros(). Note that this is not thread safe +// since it modifies global state. +class GlobalSimulatedTimeController : public TimeController { + public: + explicit GlobalSimulatedTimeController(Timestamp start_time); + ~GlobalSimulatedTimeController() override; + + Clock* GetClock() override; + TaskQueueFactory* GetTaskQueueFactory() override; + std::unique_ptr<rtc::Thread> CreateThread( + const std::string& name, + std::unique_ptr<rtc::SocketServer> socket_server) override; + rtc::Thread* GetMainThread() override; + + void AdvanceTime(TimeDelta duration) override; + + // Makes the simulated time controller aware of a custom + // SimulatedSequenceRunner. + // TODO(bugs.webrtc.org/11581): remove method once the ModuleRtpRtcpImpl2 unit + // test stops using it. + void Register(sim_time_impl::SimulatedSequenceRunner* runner); + // Removes a previously installed custom SimulatedSequenceRunner from the + // simulated time controller. + // TODO(bugs.webrtc.org/11581): remove method once the ModuleRtpRtcpImpl2 unit + // test stops using it. + void Unregister(sim_time_impl::SimulatedSequenceRunner* runner); + + private: + rtc::ScopedBaseFakeClock global_clock_; + // Provides simulated CurrentNtpInMilliseconds() + SimulatedClock sim_clock_; + sim_time_impl::SimulatedTimeControllerImpl impl_; + rtc::ScopedYieldPolicy yield_policy_; + std::unique_ptr<rtc::Thread> main_thread_; +}; +} // namespace webrtc + +#endif // TEST_TIME_CONTROLLER_SIMULATED_TIME_CONTROLLER_H_ diff --git a/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc b/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc new file mode 100644 index 0000000000..1ee592cc7c --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/simulated_time_controller_unittest.cc @@ -0,0 +1,149 @@ +/* + * Copyright 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/time_controller/simulated_time_controller.h" + +#include <atomic> +#include <memory> + +#include "rtc_base/event.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_queue_for_test.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "test/gmock.h" +#include "test/gtest.h" + +// NOTE: Since these tests rely on real time behavior, they will be flaky +// if run on heavily loaded systems. +namespace webrtc { +namespace { +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::NiceMock; +using ::testing::Return; +constexpr Timestamp kStartTime = Timestamp::Seconds(1000); +} // namespace + +TEST(SimulatedTimeControllerTest, TaskIsStoppedOnStop) { + const TimeDelta kShortInterval = TimeDelta::Millis(5); + const TimeDelta kLongInterval = TimeDelta::Millis(20); + const int kShortIntervalCount = 4; + const int kMargin = 1; + GlobalSimulatedTimeController time_simulation(kStartTime); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + std::atomic_int counter(0); + auto handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] { + if (++counter >= kShortIntervalCount) + return kLongInterval; + return kShortInterval; + }); + // Sleep long enough to go through the initial phase. + time_simulation.AdvanceTime(kShortInterval * (kShortIntervalCount + kMargin)); + EXPECT_EQ(counter.load(), kShortIntervalCount); + + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + + // Sleep long enough that the task would run at least once more if not + // stopped. + time_simulation.AdvanceTime(kLongInterval * 2); + EXPECT_EQ(counter.load(), kShortIntervalCount); +} + +TEST(SimulatedTimeControllerTest, TaskCanStopItself) { + std::atomic_int counter(0); + GlobalSimulatedTimeController time_simulation(kStartTime); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + + RepeatingTaskHandle handle; + task_queue.PostTask([&] { + handle = RepeatingTaskHandle::Start(task_queue.Get(), [&] { + ++counter; + handle.Stop(); + return TimeDelta::Millis(2); + }); + }); + time_simulation.AdvanceTime(TimeDelta::Millis(10)); + EXPECT_EQ(counter.load(), 1); +} + +TEST(SimulatedTimeControllerTest, Example) { + class ObjectOnTaskQueue { + public: + void DoPeriodicTask() {} + TimeDelta TimeUntilNextRun() { return TimeDelta::Millis(100); } + void StartPeriodicTask(RepeatingTaskHandle* handle, + rtc::TaskQueue* task_queue) { + *handle = RepeatingTaskHandle::Start(task_queue->Get(), [this] { + DoPeriodicTask(); + return TimeUntilNextRun(); + }); + } + }; + GlobalSimulatedTimeController time_simulation(kStartTime); + rtc::TaskQueue task_queue( + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL)); + auto object = std::make_unique<ObjectOnTaskQueue>(); + // Create and start the periodic task. + RepeatingTaskHandle handle; + object->StartPeriodicTask(&handle, &task_queue); + // Restart the task + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + object->StartPeriodicTask(&handle, &task_queue); + task_queue.PostTask( + [handle = std::move(handle)]() mutable { handle.Stop(); }); + + task_queue.PostTask([object = std::move(object)] {}); +} + +TEST(SimulatedTimeControllerTest, DelayTaskRunOnTime) { + GlobalSimulatedTimeController time_simulation(kStartTime); + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> task_queue = + time_simulation.GetTaskQueueFactory()->CreateTaskQueue( + "TestQueue", TaskQueueFactory::Priority::NORMAL); + + bool delay_task_executed = false; + task_queue->PostDelayedTask([&] { delay_task_executed = true; }, + TimeDelta::Millis(10)); + + time_simulation.AdvanceTime(TimeDelta::Millis(10)); + EXPECT_TRUE(delay_task_executed); +} + +TEST(SimulatedTimeControllerTest, ThreadYeildsOnSynchronousCall) { + GlobalSimulatedTimeController sim(kStartTime); + auto main_thread = sim.GetMainThread(); + auto t2 = sim.CreateThread("thread", nullptr); + bool task_has_run = false; + // Posting a task to the main thread, this should not run until AdvanceTime is + // called. + main_thread->PostTask([&] { task_has_run = true; }); + SendTask(t2.get(), [] { + rtc::Event yield_event; + // Wait() triggers YieldExecution() which will runs message processing on + // all threads that are not in the yielded set. + + yield_event.Wait(TimeDelta::Zero()); + }); + // Since we are doing an invoke from the main thread, we don't expect the main + // thread message loop to be processed. + EXPECT_FALSE(task_has_run); + sim.AdvanceTime(TimeDelta::Seconds(1)); + ASSERT_TRUE(task_has_run); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/test/time_controller/time_controller_conformance_test.cc b/third_party/libwebrtc/test/time_controller/time_controller_conformance_test.cc new file mode 100644 index 0000000000..300dd9175c --- /dev/null +++ b/third_party/libwebrtc/test/time_controller/time_controller_conformance_test.cc @@ -0,0 +1,181 @@ +/* + * Copyright 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 <memory> +#include <vector> + +#include "api/test/time_controller.h" +#include "api/units/time_delta.h" +#include "rtc_base/event.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread.h" +#include "rtc_base/thread_annotations.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/time_controller/real_time_controller.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::TestParamInfo; +using ::testing::TestWithParam; +using ::testing::Values; + +enum class TimeMode { kRealTime, kSimulated }; + +std::unique_ptr<TimeController> CreateTimeController(TimeMode mode) { + switch (mode) { + case TimeMode::kRealTime: + return std::make_unique<RealTimeController>(); + case TimeMode::kSimulated: + // Using an offset of 100000 to get nice fixed width and readable + // timestamps in typical test scenarios. + constexpr Timestamp kSimulatedStartTime = Timestamp::Seconds(100000); + return std::make_unique<GlobalSimulatedTimeController>( + kSimulatedStartTime); + } +} + +std::string ParamsToString(const TestParamInfo<webrtc::TimeMode>& param) { + switch (param.param) { + case webrtc::TimeMode::kRealTime: + return "RealTime"; + case webrtc::TimeMode::kSimulated: + return "SimulatedTime"; + default: + RTC_DCHECK_NOTREACHED() << "Time mode not supported"; + } +} + +// Keeps order of executions. May be called from different threads. +class ExecutionOrderKeeper { + public: + void Executed(int execution_id) { + MutexLock lock(&mutex_); + order_.push_back(execution_id); + } + + std::vector<int> order() const { + MutexLock lock(&mutex_); + return order_; + } + + private: + mutable Mutex mutex_; + std::vector<int> order_ RTC_GUARDED_BY(mutex_); +}; + +// Tests conformance between real time and simulated time time controller. +class SimulatedRealTimeControllerConformanceTest + : public TestWithParam<webrtc::TimeMode> {}; + +TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + // Tasks on thread have to be executed in order in which they were + // posted. + ExecutionOrderKeeper execution_order; + thread->PostTask([&]() { execution_order.Executed(1); }); + thread->PostTask([&]() { execution_order.Executed(2); }); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostDelayedOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + ExecutionOrderKeeper execution_order; + thread->PostDelayedTask([&]() { execution_order.Executed(2); }, + TimeDelta::Millis(500)); + thread->PostTask([&]() { execution_order.Executed(1); }); + time_controller->AdvanceTime(TimeDelta::Millis(600)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, ThreadPostInvokeOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + // Tasks on thread have to be executed in order in which they were + // posted/invoked. + ExecutionOrderKeeper execution_order; + thread->PostTask([&]() { execution_order.Executed(1); }); + thread->BlockingCall([&]() { execution_order.Executed(2); }); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, + ThreadPostInvokeFromThreadOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + std::unique_ptr<rtc::Thread> thread = time_controller->CreateThread("thread"); + + // If task is invoked from thread X on thread X it has to be executed + // immediately. + ExecutionOrderKeeper execution_order; + thread->PostTask([&]() { + thread->PostTask([&]() { execution_order.Executed(2); }); + thread->BlockingCall([&]() { execution_order.Executed(1); }); + }); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `thread` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + thread = nullptr; +} + +TEST_P(SimulatedRealTimeControllerConformanceTest, + TaskQueuePostEventWaitOrderTest) { + std::unique_ptr<TimeController> time_controller = + CreateTimeController(GetParam()); + auto task_queue = time_controller->GetTaskQueueFactory()->CreateTaskQueue( + "task_queue", webrtc::TaskQueueFactory::Priority::NORMAL); + + // Tasks on thread have to be executed in order in which they were + // posted/invoked. + ExecutionOrderKeeper execution_order; + rtc::Event event; + task_queue->PostTask([&]() { execution_order.Executed(1); }); + task_queue->PostTask([&]() { + execution_order.Executed(2); + event.Set(); + }); + EXPECT_TRUE(event.Wait(/*give_up_after=*/TimeDelta::Millis(100))); + time_controller->AdvanceTime(TimeDelta::Millis(100)); + EXPECT_THAT(execution_order.order(), ElementsAreArray({1, 2})); + // Destroy `task_queue` before `execution_order` to be sure `execution_order` + // is not accessed on the posted task after it is destroyed. + task_queue = nullptr; +} + +INSTANTIATE_TEST_SUITE_P(ConformanceTest, + SimulatedRealTimeControllerConformanceTest, + Values(TimeMode::kRealTime, TimeMode::kSimulated), + ParamsToString); + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/test/vcm_capturer.cc b/third_party/libwebrtc/test/vcm_capturer.cc new file mode 100644 index 0000000000..e02fc722b2 --- /dev/null +++ b/third_party/libwebrtc/test/vcm_capturer.cc @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2013 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/vcm_capturer.h" + +#include <stdint.h> + +#include <memory> + +#include "modules/video_capture/video_capture_factory.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace test { + +VcmCapturer::VcmCapturer() : vcm_(nullptr) {} + +bool VcmCapturer::Init(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index) { + std::unique_ptr<VideoCaptureModule::DeviceInfo> device_info( + VideoCaptureFactory::CreateDeviceInfo()); + + char device_name[256]; + char unique_name[256]; + if (device_info->GetDeviceName(static_cast<uint32_t>(capture_device_index), + device_name, sizeof(device_name), unique_name, + sizeof(unique_name)) != 0) { + Destroy(); + return false; + } + + vcm_ = webrtc::VideoCaptureFactory::Create(unique_name); + if (!vcm_) { + return false; + } + vcm_->RegisterCaptureDataCallback(this); + + device_info->GetCapability(vcm_->CurrentDeviceName(), 0, capability_); + + capability_.width = static_cast<int32_t>(width); + capability_.height = static_cast<int32_t>(height); + capability_.maxFPS = static_cast<int32_t>(target_fps); + capability_.videoType = VideoType::kI420; + + if (vcm_->StartCapture(capability_) != 0) { + Destroy(); + return false; + } + + RTC_CHECK(vcm_->CaptureStarted()); + + return true; +} + +VcmCapturer* VcmCapturer::Create(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index) { + std::unique_ptr<VcmCapturer> vcm_capturer(new VcmCapturer()); + if (!vcm_capturer->Init(width, height, target_fps, capture_device_index)) { + RTC_LOG(LS_WARNING) << "Failed to create VcmCapturer(w = " << width + << ", h = " << height << ", fps = " << target_fps + << ")"; + return nullptr; + } + return vcm_capturer.release(); +} + +void VcmCapturer::Destroy() { + if (!vcm_) + return; + + vcm_->StopCapture(); + vcm_->DeRegisterCaptureDataCallback(this); + // Release reference to VCM. + vcm_ = nullptr; +} + +VcmCapturer::~VcmCapturer() { + Destroy(); +} + +void VcmCapturer::OnFrame(const VideoFrame& frame) { + TestVideoCapturer::OnFrame(frame); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/vcm_capturer.h b/third_party/libwebrtc/test/vcm_capturer.h new file mode 100644 index 0000000000..5418dc9596 --- /dev/null +++ b/third_party/libwebrtc/test/vcm_capturer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 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_VCM_CAPTURER_H_ +#define TEST_VCM_CAPTURER_H_ + +#include <memory> +#include <vector> + +#include "api/scoped_refptr.h" +#include "modules/video_capture/video_capture.h" +#include "test/test_video_capturer.h" + +namespace webrtc { +namespace test { + +class VcmCapturer : public TestVideoCapturer, + public rtc::VideoSinkInterface<VideoFrame> { + public: + static VcmCapturer* Create(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index); + virtual ~VcmCapturer(); + + void OnFrame(const VideoFrame& frame) override; + + private: + VcmCapturer(); + bool Init(size_t width, + size_t height, + size_t target_fps, + size_t capture_device_index); + void Destroy(); + + rtc::scoped_refptr<VideoCaptureModule> vcm_; + VideoCaptureCapability capability_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_VCM_CAPTURER_H_ diff --git a/third_party/libwebrtc/test/video_codec_settings.h b/third_party/libwebrtc/test/video_codec_settings.h new file mode 100644 index 0000000000..5ef4ed3e4a --- /dev/null +++ b/third_party/libwebrtc/test/video_codec_settings.h @@ -0,0 +1,68 @@ +/* + * 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. + */ +#ifndef TEST_VIDEO_CODEC_SETTINGS_H_ +#define TEST_VIDEO_CODEC_SETTINGS_H_ + +#include "api/video_codecs/video_encoder.h" + +namespace webrtc { +namespace test { + +const uint16_t kTestWidth = 352; +const uint16_t kTestHeight = 288; +const uint32_t kTestFrameRate = 30; +const unsigned int kTestMinBitrateKbps = 30; +const unsigned int kTestStartBitrateKbps = 300; +const uint8_t kTestPayloadType = 100; +const int64_t kTestTimingFramesDelayMs = 200; +const uint16_t kTestOutlierFrameSizePercent = 250; + +static void CodecSettings(VideoCodecType codec_type, VideoCodec* settings) { + *settings = {}; + + settings->width = kTestWidth; + settings->height = kTestHeight; + + settings->startBitrate = kTestStartBitrateKbps; + settings->maxBitrate = 0; + settings->minBitrate = kTestMinBitrateKbps; + + settings->maxFramerate = kTestFrameRate; + + settings->active = true; + + settings->qpMax = 56; // See webrtcvideoengine.h. + settings->numberOfSimulcastStreams = 0; + + settings->timing_frame_thresholds = { + kTestTimingFramesDelayMs, + kTestOutlierFrameSizePercent, + }; + + settings->codecType = codec_type; + switch (codec_type) { + case kVideoCodecVP8: + *(settings->VP8()) = VideoEncoder::GetDefaultVp8Settings(); + return; + case kVideoCodecVP9: + *(settings->VP9()) = VideoEncoder::GetDefaultVp9Settings(); + return; + case kVideoCodecH264: + // TODO(brandtr): Set `qpMax` here, when the OpenH264 wrapper supports it. + *(settings->H264()) = VideoEncoder::GetDefaultH264Settings(); + return; + default: + return; + } +} +} // namespace test +} // namespace webrtc + +#endif // TEST_VIDEO_CODEC_SETTINGS_H_ diff --git a/third_party/libwebrtc/test/video_decoder_proxy_factory.h b/third_party/libwebrtc/test/video_decoder_proxy_factory.h new file mode 100644 index 0000000000..6fd3805cd6 --- /dev/null +++ b/third_party/libwebrtc/test/video_decoder_proxy_factory.h @@ -0,0 +1,79 @@ +/* + * 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_VIDEO_DECODER_PROXY_FACTORY_H_ +#define TEST_VIDEO_DECODER_PROXY_FACTORY_H_ + +#include <memory> +#include <vector> + +#include "api/video_codecs/video_decoder.h" +#include "api/video_codecs/video_decoder_factory.h" + +namespace webrtc { +namespace test { + +// A decoder factory with a single underlying VideoDecoder object, intended for +// test purposes. Each call to CreateVideoDecoder returns a proxy for the same +// decoder, typically an instance of FakeDecoder or MockEncoder. +class VideoDecoderProxyFactory final : public VideoDecoderFactory { + public: + explicit VideoDecoderProxyFactory(VideoDecoder* decoder) + : decoder_(decoder) {} + + // Unused by tests. + std::vector<SdpVideoFormat> GetSupportedFormats() const override { + RTC_DCHECK_NOTREACHED(); + return {}; + } + + std::unique_ptr<VideoDecoder> CreateVideoDecoder( + const SdpVideoFormat& format) override { + return std::make_unique<DecoderProxy>(decoder_); + } + + private: + // Wrapper class, since CreateVideoDecoder needs to surrender + // ownership to the object it returns. + class DecoderProxy final : public VideoDecoder { + public: + explicit DecoderProxy(VideoDecoder* decoder) : decoder_(decoder) {} + + private: + int32_t Decode(const EncodedImage& input_image, + bool missing_frames, + int64_t render_time_ms) override { + return decoder_->Decode(input_image, missing_frames, render_time_ms); + } + bool Configure(const Settings& settings) override { + return decoder_->Configure(settings); + } + int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) override { + return decoder_->RegisterDecodeCompleteCallback(callback); + } + int32_t Release() override { return decoder_->Release(); } + DecoderInfo GetDecoderInfo() const override { + return decoder_->GetDecoderInfo(); + } + const char* ImplementationName() const override { + return decoder_->ImplementationName(); + } + + VideoDecoder* const decoder_; + }; + + VideoDecoder* const decoder_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_VIDEO_DECODER_PROXY_FACTORY_H_ diff --git a/third_party/libwebrtc/test/video_encoder_nullable_proxy_factory.h b/third_party/libwebrtc/test/video_encoder_nullable_proxy_factory.h new file mode 100644 index 0000000000..da81fff343 --- /dev/null +++ b/third_party/libwebrtc/test/video_encoder_nullable_proxy_factory.h @@ -0,0 +1,45 @@ +/* + * 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_VIDEO_ENCODER_NULLABLE_PROXY_FACTORY_H_ +#define TEST_VIDEO_ENCODER_NULLABLE_PROXY_FACTORY_H_ + +#include <memory> +#include <vector> + +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "test/video_encoder_proxy_factory.h" + +namespace webrtc { +namespace test { + +class VideoEncoderNullableProxyFactory final : public VideoEncoderProxyFactory { + public: + explicit VideoEncoderNullableProxyFactory( + VideoEncoder* encoder, + EncoderSelectorInterface* encoder_selector) + : VideoEncoderProxyFactory(encoder, encoder_selector) {} + + ~VideoEncoderNullableProxyFactory() override = default; + + std::unique_ptr<VideoEncoder> CreateVideoEncoder( + const SdpVideoFormat& format) override { + if (!encoder_) { + return nullptr; + } + return VideoEncoderProxyFactory::CreateVideoEncoder(format); + } +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_VIDEO_ENCODER_NULLABLE_PROXY_FACTORY_H_ diff --git a/third_party/libwebrtc/test/video_encoder_proxy_factory.h b/third_party/libwebrtc/test/video_encoder_proxy_factory.h new file mode 100644 index 0000000000..cc485e993a --- /dev/null +++ b/third_party/libwebrtc/test/video_encoder_proxy_factory.h @@ -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. + */ + +#ifndef TEST_VIDEO_ENCODER_PROXY_FACTORY_H_ +#define TEST_VIDEO_ENCODER_PROXY_FACTORY_H_ + +#include <memory> +#include <vector> + +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/video_encoder_factory.h" + +namespace webrtc { +namespace test { + +namespace { +const VideoEncoder::Capabilities kCapabilities(false); +} + +// An encoder factory with a single underlying VideoEncoder object, +// intended for test purposes. Each call to CreateVideoEncoder returns +// a proxy for the same encoder, typically an instance of FakeEncoder. +class VideoEncoderProxyFactory : public VideoEncoderFactory { + public: + explicit VideoEncoderProxyFactory(VideoEncoder* encoder) + : VideoEncoderProxyFactory(encoder, nullptr) {} + + explicit VideoEncoderProxyFactory(VideoEncoder* encoder, + EncoderSelectorInterface* encoder_selector) + : encoder_(encoder), + encoder_selector_(encoder_selector), + num_simultaneous_encoder_instances_(0), + max_num_simultaneous_encoder_instances_(0) { + } + + // Unused by tests. + std::vector<SdpVideoFormat> GetSupportedFormats() const override { + RTC_DCHECK_NOTREACHED(); + return {}; + } + + std::unique_ptr<VideoEncoder> CreateVideoEncoder( + const SdpVideoFormat& format) override { + ++num_simultaneous_encoder_instances_; + max_num_simultaneous_encoder_instances_ = + std::max(max_num_simultaneous_encoder_instances_, + num_simultaneous_encoder_instances_); + return std::make_unique<EncoderProxy>(encoder_, this); + } + + std::unique_ptr<EncoderSelectorInterface> GetEncoderSelector() + const override { + if (encoder_selector_ != nullptr) { + return std::make_unique<EncoderSelectorProxy>(encoder_selector_); + } + + return nullptr; + } + + int GetMaxNumberOfSimultaneousEncoderInstances() { + return max_num_simultaneous_encoder_instances_; + } + + protected: + void OnDestroyVideoEncoder() { + RTC_CHECK_GT(num_simultaneous_encoder_instances_, 0); + --num_simultaneous_encoder_instances_; + } + + // Wrapper class, since CreateVideoEncoder needs to surrender + // ownership to the object it returns. + class EncoderProxy final : public VideoEncoder { + public: + explicit EncoderProxy(VideoEncoder* encoder, + VideoEncoderProxyFactory* encoder_factory) + : encoder_(encoder), encoder_factory_(encoder_factory) {} + ~EncoderProxy() { encoder_factory_->OnDestroyVideoEncoder(); } + + private: + void SetFecControllerOverride( + FecControllerOverride* fec_controller_override) override { + encoder_->SetFecControllerOverride(fec_controller_override); + } + + int32_t Encode(const VideoFrame& input_image, + const std::vector<VideoFrameType>* frame_types) override { + return encoder_->Encode(input_image, frame_types); + } + + int32_t InitEncode(const VideoCodec* config, + const Settings& settings) override { + return encoder_->InitEncode(config, settings); + } + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override { + return encoder_->RegisterEncodeCompleteCallback(callback); + } + + int32_t Release() override { return encoder_->Release(); } + + void SetRates(const RateControlParameters& parameters) override { + encoder_->SetRates(parameters); + } + + VideoEncoder::EncoderInfo GetEncoderInfo() const override { + return encoder_->GetEncoderInfo(); + } + + VideoEncoder* const encoder_; + VideoEncoderProxyFactory* const encoder_factory_; + }; + + class EncoderSelectorProxy final : public EncoderSelectorInterface { + public: + explicit EncoderSelectorProxy(EncoderSelectorInterface* encoder_selector) + : encoder_selector_(encoder_selector) {} + + void OnCurrentEncoder(const SdpVideoFormat& format) override { + encoder_selector_->OnCurrentEncoder(format); + } + + absl::optional<SdpVideoFormat> OnAvailableBitrate( + const DataRate& rate) override { + return encoder_selector_->OnAvailableBitrate(rate); + } + + absl::optional<SdpVideoFormat> OnResolutionChange( + const RenderResolution& resolution) override { + return encoder_selector_->OnResolutionChange(resolution); + } + + absl::optional<SdpVideoFormat> OnEncoderBroken() override { + return encoder_selector_->OnEncoderBroken(); + } + + private: + EncoderSelectorInterface* const encoder_selector_; + }; + + VideoEncoder* const encoder_; + EncoderSelectorInterface* const encoder_selector_; + + int num_simultaneous_encoder_instances_; + int max_num_simultaneous_encoder_instances_; +}; + +} // namespace test +} // namespace webrtc + +#endif // TEST_VIDEO_ENCODER_PROXY_FACTORY_H_ diff --git a/third_party/libwebrtc/test/video_renderer.cc b/third_party/libwebrtc/test/video_renderer.cc new file mode 100644 index 0000000000..75ab72a883 --- /dev/null +++ b/third_party/libwebrtc/test/video_renderer.cc @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 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/video_renderer.h" + +// TODO(pbos): Android renderer + +namespace webrtc { +namespace test { + +class NullRenderer : public VideoRenderer { + void OnFrame(const VideoFrame& video_frame) override {} +}; + +VideoRenderer* VideoRenderer::Create(const char* window_title, + size_t width, + size_t height) { + VideoRenderer* renderer = CreatePlatformRenderer(window_title, width, height); + if (renderer != nullptr) + return renderer; + + return new NullRenderer(); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/video_renderer.h b/third_party/libwebrtc/test/video_renderer.h new file mode 100644 index 0000000000..9e580f6f59 --- /dev/null +++ b/third_party/libwebrtc/test/video_renderer.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013 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_VIDEO_RENDERER_H_ +#define TEST_VIDEO_RENDERER_H_ + +#include <stddef.h> + +#include "api/video/video_sink_interface.h" + +namespace webrtc { +class VideoFrame; + +namespace test { +class VideoRenderer : public rtc::VideoSinkInterface<VideoFrame> { + public: + // Creates a platform-specific renderer if possible, or a null implementation + // if failing. + static VideoRenderer* Create(const char* window_title, + size_t width, + size_t height); + // Returns a renderer rendering to a platform specific window if possible, + // NULL if none can be created. + // Creates a platform-specific renderer if possible, returns NULL if a + // platform renderer could not be created. This occurs, for instance, when + // running without an X environment on Linux. + static VideoRenderer* CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height); + virtual ~VideoRenderer() {} + + protected: + VideoRenderer() {} +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_VIDEO_RENDERER_H_ diff --git a/third_party/libwebrtc/test/win/d3d_renderer.cc b/third_party/libwebrtc/test/win/d3d_renderer.cc new file mode 100644 index 0000000000..5ba90fad78 --- /dev/null +++ b/third_party/libwebrtc/test/win/d3d_renderer.cc @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2013 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/win/d3d_renderer.h" + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1) + +struct D3dCustomVertex { + float x, y, z; + float u, v; +}; + +const char kD3DClassName[] = "d3d_renderer"; + +VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title, + size_t width, + size_t height) { + return D3dRenderer::Create(window_title, width, height); +} + +D3dRenderer::D3dRenderer(size_t width, size_t height) + : width_(width), + height_(height), + hwnd_(NULL), + d3d_(nullptr), + d3d_device_(nullptr), + texture_(nullptr), + vertex_buffer_(nullptr) { + RTC_DCHECK_GT(width, 0); + RTC_DCHECK_GT(height, 0); +} + +D3dRenderer::~D3dRenderer() { + Destroy(); +} + +LRESULT WINAPI D3dRenderer::WindowProc(HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam) { + if (msg == WM_DESTROY || (msg == WM_CHAR && wparam == VK_RETURN)) { + PostQuitMessage(0); + return 0; + } + + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +void D3dRenderer::Destroy() { + texture_ = nullptr; + vertex_buffer_ = nullptr; + d3d_device_ = nullptr; + d3d_ = nullptr; + + if (hwnd_ != NULL) { + DestroyWindow(hwnd_); + RTC_DCHECK(!IsWindow(hwnd_)); + hwnd_ = NULL; + } +} + +bool D3dRenderer::Init(const char* window_title) { + hwnd_ = CreateWindowA(kD3DClassName, window_title, WS_OVERLAPPEDWINDOW, 0, 0, + static_cast<int>(width_), static_cast<int>(height_), + NULL, NULL, NULL, NULL); + + if (hwnd_ == NULL) { + Destroy(); + return false; + } + + d3d_ = Direct3DCreate9(D3D_SDK_VERSION); + if (d3d_ == nullptr) { + Destroy(); + return false; + } + + D3DPRESENT_PARAMETERS d3d_params = {}; + + d3d_params.Windowed = TRUE; + d3d_params.SwapEffect = D3DSWAPEFFECT_COPY; + + IDirect3DDevice9* d3d_device; + if (d3d_->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd_, + D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3d_params, + &d3d_device) != D3D_OK) { + Destroy(); + return false; + } + d3d_device_ = d3d_device; + d3d_device->Release(); + + IDirect3DVertexBuffer9* vertex_buffer; + const int kRectVertices = 4; + if (d3d_device_->CreateVertexBuffer(kRectVertices * sizeof(D3dCustomVertex), + 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, + &vertex_buffer, NULL) != D3D_OK) { + Destroy(); + return false; + } + vertex_buffer_ = vertex_buffer; + vertex_buffer->Release(); + + d3d_device_->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + d3d_device_->SetRenderState(D3DRS_LIGHTING, FALSE); + Resize(width_, height_); + + ShowWindow(hwnd_, SW_SHOWNOACTIVATE); + d3d_device_->Present(NULL, NULL, NULL, NULL); + + return true; +} + +D3dRenderer* D3dRenderer::Create(const char* window_title, + size_t width, + size_t height) { + static ATOM wc_atom = 0; + if (wc_atom == 0) { + WNDCLASSA wc = {}; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WindowProc; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW); + wc.lpszClassName = kD3DClassName; + + wc_atom = RegisterClassA(&wc); + if (wc_atom == 0) + return nullptr; + } + + D3dRenderer* d3d_renderer = new D3dRenderer(width, height); + if (!d3d_renderer->Init(window_title)) { + delete d3d_renderer; + return nullptr; + } + + return d3d_renderer; +} + +void D3dRenderer::Resize(size_t width, size_t height) { + width_ = width; + height_ = height; + IDirect3DTexture9* texture; + + d3d_device_->CreateTexture(static_cast<UINT>(width_), + static_cast<UINT>(height_), 1, 0, D3DFMT_A8R8G8B8, + D3DPOOL_MANAGED, &texture, NULL); + texture_ = texture; + texture->Release(); + + // Vertices for the video frame to be rendered to. + static const D3dCustomVertex rect[] = { + {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f}, + {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f}, + {1.0f, -1.0f, 0.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 0.0f, 1.0f, 0.0f}, + }; + + void* buf_data; + if (vertex_buffer_->Lock(0, 0, &buf_data, 0) != D3D_OK) + return; + + memcpy(buf_data, &rect, sizeof(rect)); + vertex_buffer_->Unlock(); +} + +void D3dRenderer::OnFrame(const webrtc::VideoFrame& frame) { + if (static_cast<size_t>(frame.width()) != width_ || + static_cast<size_t>(frame.height()) != height_) { + Resize(static_cast<size_t>(frame.width()), + static_cast<size_t>(frame.height())); + } + + D3DLOCKED_RECT lock_rect; + if (texture_->LockRect(0, &lock_rect, NULL, 0) != D3D_OK) + return; + + ConvertFromI420(frame, VideoType::kARGB, 0, + static_cast<uint8_t*>(lock_rect.pBits)); + texture_->UnlockRect(0); + + d3d_device_->BeginScene(); + d3d_device_->SetFVF(D3DFVF_CUSTOMVERTEX); + d3d_device_->SetStreamSource(0, vertex_buffer_.get(), 0, + sizeof(D3dCustomVertex)); + d3d_device_->SetTexture(0, texture_.get()); + d3d_device_->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + d3d_device_->EndScene(); + + d3d_device_->Present(NULL, NULL, NULL, NULL); +} +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/test/win/d3d_renderer.h b/third_party/libwebrtc/test/win/d3d_renderer.h new file mode 100644 index 0000000000..9e5e23c328 --- /dev/null +++ b/third_party/libwebrtc/test/win/d3d_renderer.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 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_WIN_D3D_RENDERER_H_ +#define TEST_WIN_D3D_RENDERER_H_ + +#include <Windows.h> +#include <d3d9.h> +#pragma comment(lib, "d3d9.lib") // located in DirectX SDK + +#include "api/scoped_refptr.h" +#include "test/video_renderer.h" + +namespace webrtc { +namespace test { + +class D3dRenderer : public VideoRenderer { + public: + static D3dRenderer* Create(const char* window_title, + size_t width, + size_t height); + virtual ~D3dRenderer(); + + void OnFrame(const webrtc::VideoFrame& frame) override; + + private: + D3dRenderer(size_t width, size_t height); + + static LRESULT WINAPI WindowProc(HWND hwnd, + UINT msg, + WPARAM wparam, + LPARAM lparam); + bool Init(const char* window_title); + void Resize(size_t width, size_t height); + void Destroy(); + + size_t width_, height_; + + HWND hwnd_; + rtc::scoped_refptr<IDirect3D9> d3d_; + rtc::scoped_refptr<IDirect3DDevice9> d3d_device_; + + rtc::scoped_refptr<IDirect3DTexture9> texture_; + rtc::scoped_refptr<IDirect3DVertexBuffer9> vertex_buffer_; +}; +} // namespace test +} // namespace webrtc + +#endif // TEST_WIN_D3D_RENDERER_H_ |