diff options
Diffstat (limited to 'third_party/libwebrtc/modules/rtp_rtcp')
300 files changed, 69927 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn b/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn new file mode 100644 index 0000000000..6014ae420b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn @@ -0,0 +1,713 @@ +# 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("../../webrtc.gni") + +rtc_library("leb128") { + public = [ "source/leb128.h" ] + sources = [ "source/leb128.cc" ] +} + +rtc_library("rtp_rtcp_format") { + visibility = [ "*" ] + public = [ + "include/recovered_packet_receiver.h", + "include/report_block_data.h", + "include/rtcp_statistics.h", + "include/rtp_cvo.h", + "include/rtp_header_extension_map.h", + "include/rtp_packet_sender.h", + "include/rtp_rtcp_defines.h", + "source/byte_io.h", + "source/rtcp_packet.h", + "source/rtcp_packet/app.h", + "source/rtcp_packet/bye.h", + "source/rtcp_packet/common_header.h", + "source/rtcp_packet/compound_packet.h", + "source/rtcp_packet/dlrr.h", + "source/rtcp_packet/extended_reports.h", + "source/rtcp_packet/fir.h", + "source/rtcp_packet/loss_notification.h", + "source/rtcp_packet/nack.h", + "source/rtcp_packet/pli.h", + "source/rtcp_packet/psfb.h", + "source/rtcp_packet/rapid_resync_request.h", + "source/rtcp_packet/receiver_report.h", + "source/rtcp_packet/remb.h", + "source/rtcp_packet/remote_estimate.h", + "source/rtcp_packet/report_block.h", + "source/rtcp_packet/rrtr.h", + "source/rtcp_packet/rtpfb.h", + "source/rtcp_packet/sdes.h", + "source/rtcp_packet/sender_report.h", + "source/rtcp_packet/target_bitrate.h", + "source/rtcp_packet/tmmb_item.h", + "source/rtcp_packet/tmmbn.h", + "source/rtcp_packet/tmmbr.h", + "source/rtcp_packet/transport_feedback.h", + "source/rtp_dependency_descriptor_extension.h", + "source/rtp_generic_frame_descriptor.h", + "source/rtp_generic_frame_descriptor_extension.h", + "source/rtp_header_extensions.h", + "source/rtp_packet.h", + "source/rtp_packet_received.h", + "source/rtp_packet_to_send.h", + "source/rtp_util.h", + "source/rtp_video_layers_allocation_extension.h", + ] + sources = [ + "include/report_block_data.cc", + "include/rtp_rtcp_defines.cc", + "source/rtcp_packet.cc", + "source/rtcp_packet/app.cc", + "source/rtcp_packet/bye.cc", + "source/rtcp_packet/common_header.cc", + "source/rtcp_packet/compound_packet.cc", + "source/rtcp_packet/dlrr.cc", + "source/rtcp_packet/extended_reports.cc", + "source/rtcp_packet/fir.cc", + "source/rtcp_packet/loss_notification.cc", + "source/rtcp_packet/nack.cc", + "source/rtcp_packet/pli.cc", + "source/rtcp_packet/psfb.cc", + "source/rtcp_packet/rapid_resync_request.cc", + "source/rtcp_packet/receiver_report.cc", + "source/rtcp_packet/remb.cc", + "source/rtcp_packet/remote_estimate.cc", + "source/rtcp_packet/report_block.cc", + "source/rtcp_packet/rrtr.cc", + "source/rtcp_packet/rtpfb.cc", + "source/rtcp_packet/sdes.cc", + "source/rtcp_packet/sender_report.cc", + "source/rtcp_packet/target_bitrate.cc", + "source/rtcp_packet/tmmb_item.cc", + "source/rtcp_packet/tmmbn.cc", + "source/rtcp_packet/tmmbr.cc", + "source/rtcp_packet/transport_feedback.cc", + "source/rtp_dependency_descriptor_extension.cc", + "source/rtp_dependency_descriptor_reader.cc", + "source/rtp_dependency_descriptor_reader.h", + "source/rtp_dependency_descriptor_writer.cc", + "source/rtp_dependency_descriptor_writer.h", + "source/rtp_generic_frame_descriptor.cc", + "source/rtp_generic_frame_descriptor_extension.cc", + "source/rtp_header_extension_map.cc", + "source/rtp_header_extensions.cc", + "source/rtp_packet.cc", + "source/rtp_packet_received.cc", + "source/rtp_packet_to_send.cc", + "source/rtp_util.cc", + "source/rtp_video_layers_allocation_extension.cc", + ] + + deps = [ + ":leb128", + "..:module_api_public", + "../../api:array_view", + "../../api:function_view", + "../../api:refcountedbase", + "../../api:rtp_headers", + "../../api:rtp_parameters", + "../../api:scoped_refptr", + "../../api/audio_codecs:audio_codecs_api", + "../../api/transport:network_control", + "../../api/transport/rtp:dependency_descriptor", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../api/video:video_frame", + "../../api/video:video_layers_allocation", + "../../api/video:video_rtp_headers", + "../../common_video", + "../../rtc_base:bit_buffer", + "../../rtc_base:bitstream_reader", + "../../rtc_base:buffer", + "../../rtc_base:checks", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:divide_round", + "../../rtc_base:event_tracer", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:safe_conversions", + "../../rtc_base:stringutils", + "../../system_wrappers", + "../video_coding:codec_globals_headers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + "//third_party/abseil-cpp/absl/types:variant", + ] +} + +rtc_library("rtp_rtcp") { + visibility = [ "*" ] + sources = [ + "include/flexfec_receiver.h", + "include/flexfec_sender.h", + "include/receive_statistics.h", + "include/remote_ntp_time_estimator.h", + "source/absolute_capture_time_interpolator.cc", + "source/absolute_capture_time_interpolator.h", + "source/absolute_capture_time_sender.cc", + "source/absolute_capture_time_sender.h", + "source/active_decode_targets_helper.cc", + "source/active_decode_targets_helper.h", + "source/capture_clock_offset_updater.cc", + "source/capture_clock_offset_updater.h", + "source/create_video_rtp_depacketizer.cc", + "source/create_video_rtp_depacketizer.h", + "source/dtmf_queue.cc", + "source/dtmf_queue.h", + "source/fec_private_tables_bursty.cc", + "source/fec_private_tables_bursty.h", + "source/fec_private_tables_random.cc", + "source/fec_private_tables_random.h", + "source/flexfec_header_reader_writer.cc", + "source/flexfec_header_reader_writer.h", + "source/flexfec_receiver.cc", + "source/flexfec_sender.cc", + "source/forward_error_correction.cc", + "source/forward_error_correction.h", + "source/forward_error_correction_internal.cc", + "source/forward_error_correction_internal.h", + "source/packet_loss_stats.cc", + "source/packet_loss_stats.h", + "source/packet_sequencer.cc", + "source/packet_sequencer.h", + "source/receive_statistics_impl.cc", + "source/receive_statistics_impl.h", + "source/remote_ntp_time_estimator.cc", + "source/rtcp_nack_stats.cc", + "source/rtcp_nack_stats.h", + "source/rtcp_receiver.cc", + "source/rtcp_receiver.h", + "source/rtcp_sender.cc", + "source/rtcp_sender.h", + "source/rtp_descriptor_authentication.cc", + "source/rtp_descriptor_authentication.h", + "source/rtp_format.cc", + "source/rtp_format.h", + "source/rtp_format_h264.cc", + "source/rtp_format_h264.h", + "source/rtp_format_video_generic.cc", + "source/rtp_format_video_generic.h", + "source/rtp_format_vp8.cc", + "source/rtp_format_vp8.h", + "source/rtp_format_vp9.cc", + "source/rtp_format_vp9.h", + "source/rtp_header_extension_size.cc", + "source/rtp_header_extension_size.h", + "source/rtp_packet_history.cc", + "source/rtp_packet_history.h", + "source/rtp_packetizer_av1.cc", + "source/rtp_packetizer_av1.h", + "source/rtp_rtcp_config.h", + "source/rtp_rtcp_impl2.cc", + "source/rtp_rtcp_impl2.h", + "source/rtp_rtcp_interface.h", + "source/rtp_sender.cc", + "source/rtp_sender.h", + "source/rtp_sender_audio.cc", + "source/rtp_sender_audio.h", + "source/rtp_sender_egress.cc", + "source/rtp_sender_egress.h", + "source/rtp_sender_video.cc", + "source/rtp_sender_video.h", + "source/rtp_sender_video_frame_transformer_delegate.cc", + "source/rtp_sender_video_frame_transformer_delegate.h", + "source/rtp_sequence_number_map.cc", + "source/rtp_sequence_number_map.h", + "source/source_tracker.cc", + "source/source_tracker.h", + "source/time_util.cc", + "source/time_util.h", + "source/tmmbr_help.cc", + "source/tmmbr_help.h", + "source/ulpfec_generator.cc", + "source/ulpfec_generator.h", + "source/ulpfec_header_reader_writer.cc", + "source/ulpfec_header_reader_writer.h", + "source/ulpfec_receiver.cc", + "source/ulpfec_receiver.h", + "source/video_fec_generator.h", + "source/video_rtp_depacketizer.cc", + "source/video_rtp_depacketizer.h", + "source/video_rtp_depacketizer_av1.cc", + "source/video_rtp_depacketizer_av1.h", + "source/video_rtp_depacketizer_generic.cc", + "source/video_rtp_depacketizer_generic.h", + "source/video_rtp_depacketizer_h264.cc", + "source/video_rtp_depacketizer_h264.h", + "source/video_rtp_depacketizer_raw.cc", + "source/video_rtp_depacketizer_raw.h", + "source/video_rtp_depacketizer_vp8.cc", + "source/video_rtp_depacketizer_vp8.h", + "source/video_rtp_depacketizer_vp9.cc", + "source/video_rtp_depacketizer_vp9.h", + ] + + if (rtc_enable_bwe_test_logging) { + defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=1" ] + } else { + defines = [ "BWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0" ] + } + + deps = [ + ":leb128", + ":rtp_rtcp_format", + ":rtp_video_header", + "..:module_api_public", + "..:module_fec_api", + "../../api:array_view", + "../../api:field_trials_view", + "../../api:frame_transformer_interface", + "../../api:function_view", + "../../api:libjingle_peerconnection_api", + "../../api:rtp_headers", + "../../api:rtp_packet_info", + "../../api:rtp_parameters", + "../../api:scoped_refptr", + "../../api:sequence_checker", + "../../api:transport_api", + "../../api/audio_codecs:audio_codecs_api", + "../../api/crypto:frame_encryptor_interface", + "../../api/rtc_event_log", + "../../api/task_queue:pending_task_safety_flag", + "../../api/task_queue:task_queue", + "../../api/transport:field_trial_based_config", + "../../api/transport/rtp:dependency_descriptor", + "../../api/transport/rtp:rtp_source", + "../../api/units:data_rate", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../api/video:encoded_frame", + "../../api/video:encoded_image", + "../../api/video:video_bitrate_allocation", + "../../api/video:video_bitrate_allocator", + "../../api/video:video_codec_constants", + "../../api/video:video_frame", + "../../api/video:video_frame_type", + "../../api/video:video_layers_allocation", + "../../api/video:video_rtp_headers", + "../../api/video_codecs:video_codecs_api", + "../../call:rtp_interfaces", + "../../common_video", + "../../logging:rtc_event_audio", + "../../logging:rtc_event_rtp_rtcp", + "../../modules/audio_coding:audio_coding_module_typedefs", + "../../rtc_base:bit_buffer", + "../../rtc_base:bitstream_reader", + "../../rtc_base:buffer", + "../../rtc_base:byte_buffer", + "../../rtc_base:checks", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:divide_round", + "../../rtc_base:event_tracer", + "../../rtc_base:gtest_prod", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:mod_ops", + "../../rtc_base:one_time_event", + "../../rtc_base:race_checker", + "../../rtc_base:random", + "../../rtc_base:rate_limiter", + "../../rtc_base:rate_statistics", + "../../rtc_base:rtc_numerics", + "../../rtc_base:safe_conversions", + "../../rtc_base:safe_minmax", + "../../rtc_base:timeutils", + "../../rtc_base/containers:flat_map", + "../../rtc_base/experiments:field_trial_parser", + "../../rtc_base/synchronization:mutex", + "../../rtc_base/system:no_unique_address", + "../../rtc_base/task_utils:repeating_task", + "../../system_wrappers", + "../../system_wrappers:metrics", + "../remote_bitrate_estimator", + "../video_coding:codec_globals_headers", + ] + 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", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + "//third_party/abseil-cpp/absl/types:variant", + ] +} + +rtc_source_set("rtp_rtcp_legacy") { + sources = [ + "include/rtp_rtcp.h", + "source/deprecated/deprecated_rtp_sender_egress.cc", + "source/deprecated/deprecated_rtp_sender_egress.h", + "source/rtp_rtcp_impl.cc", + "source/rtp_rtcp_impl.h", + ] + deps = [ + ":rtp_rtcp", + ":rtp_rtcp_format", + "..:module_fec_api", + "../../api:rtp_headers", + "../../api:transport_api", + "../../api/rtc_event_log", + "../../api/transport:field_trial_based_config", + "../../api/units:data_rate", + "../../api/units:timestamp", + "../../api/video:video_bitrate_allocation", + "../../logging:rtc_event_rtp_rtcp", + "../../rtc_base:checks", + "../../rtc_base:gtest_prod", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:rate_statistics", + "../../rtc_base/synchronization:mutex", + "../../system_wrappers", + "../remote_bitrate_estimator", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("rtcp_transceiver") { + visibility = [ "*" ] + public = [ + "source/rtcp_transceiver.h", + "source/rtcp_transceiver_config.h", + "source/rtcp_transceiver_impl.h", + ] + sources = [ + "source/rtcp_transceiver.cc", + "source/rtcp_transceiver_config.cc", + "source/rtcp_transceiver_impl.cc", + ] + deps = [ + ":rtp_rtcp", + ":rtp_rtcp_format", + "../../api:array_view", + "../../api:rtp_headers", + "../../api:transport_api", + "../../api/task_queue", + "../../api/units:data_rate", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../api/video:video_bitrate_allocation", + "../../rtc_base:checks", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:divide_round", + "../../rtc_base:logging", + "../../rtc_base:rtc_event", + "../../rtc_base:timeutils", + "../../rtc_base/containers:flat_map", + "../../rtc_base/task_utils:repeating_task", + "../../system_wrappers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/cleanup", + "//third_party/abseil-cpp/absl/functional:any_invocable", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("rtp_video_header") { + visibility = [ "*" ] + sources = [ + "source/rtp_video_header.cc", + "source/rtp_video_header.h", + ] + deps = [ + "../../api:rtp_headers", + "../../api/transport/rtp:dependency_descriptor", + "../../api/video:video_frame", + "../../api/video:video_frame_metadata", + "../../api/video:video_frame_type", + "../../api/video:video_rtp_headers", + "../../modules/video_coding:codec_globals_headers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/container:inlined_vector", + "//third_party/abseil-cpp/absl/types:optional", + "//third_party/abseil-cpp/absl/types:variant", + ] +} + +rtc_source_set("rtp_video_header_unittest") { + testonly = true + sources = [ "source/rtp_video_header_unittest.cc" ] + deps = [ + ":rtp_video_header", + "../../api/video:video_frame_metadata", + "../../api/video:video_frame_type", + "../../test:test_support", + ] +} + +rtc_library("fec_test_helper") { + testonly = true + sources = [ + "source/fec_test_helper.cc", + "source/fec_test_helper.h", + ] + deps = [ + ":rtp_rtcp", + ":rtp_rtcp_format", + "../../rtc_base:checks", + "../../rtc_base:random", + ] +} + +rtc_library("mock_rtp_rtcp") { + testonly = true + public = [ + "mocks/mock_recovered_packet_receiver.h", + "mocks/mock_rtcp_bandwidth_observer.h", + "mocks/mock_rtcp_rtt_stats.h", + "mocks/mock_rtp_rtcp.h", + ] + deps = [ + ":rtp_rtcp", + ":rtp_rtcp_format", + "../../api/video:video_bitrate_allocation", + "../../rtc_base:checks", + "../../test:test_support", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +rtc_library("rtp_packetizer_av1_test_helper") { + testonly = true + sources = [ + "source/rtp_packetizer_av1_test_helper.cc", + "source/rtp_packetizer_av1_test_helper.h", + ] +} + +if (rtc_include_tests) { + if (!build_with_chromium) { + rtc_executable("test_packet_masks_metrics") { + testonly = true + + sources = [ + "test/testFec/average_residual_loss_xor_codes.h", + "test/testFec/test_packet_masks_metrics.cc", + ] + + deps = [ + ":rtp_rtcp", + "../../test:fileutils", + "../../test:test_main", + "../../test:test_support", + "//testing/gtest", + ] + } # test_packet_masks_metrics + } + + rtc_library("rtp_rtcp_modules_tests") { + testonly = true + + sources = [ "test/testFec/test_fec.cc" ] + deps = [ + ":rtp_rtcp", + ":rtp_rtcp_format", + "../../rtc_base:random", + "../../test:fileutils", + "../../test:test_support", + ] + } + + rtc_library("rtp_rtcp_unittests") { + testonly = true + + sources = [ + "source/absolute_capture_time_interpolator_unittest.cc", + "source/absolute_capture_time_sender_unittest.cc", + "source/active_decode_targets_helper_unittest.cc", + "source/byte_io_unittest.cc", + "source/capture_clock_offset_updater_unittest.cc", + "source/fec_private_tables_bursty_unittest.cc", + "source/flexfec_header_reader_writer_unittest.cc", + "source/flexfec_receiver_unittest.cc", + "source/flexfec_sender_unittest.cc", + "source/leb128_unittest.cc", + "source/nack_rtx_unittest.cc", + "source/packet_loss_stats_unittest.cc", + "source/packet_sequencer_unittest.cc", + "source/receive_statistics_unittest.cc", + "source/remote_ntp_time_estimator_unittest.cc", + "source/rtcp_nack_stats_unittest.cc", + "source/rtcp_packet/app_unittest.cc", + "source/rtcp_packet/bye_unittest.cc", + "source/rtcp_packet/common_header_unittest.cc", + "source/rtcp_packet/compound_packet_unittest.cc", + "source/rtcp_packet/dlrr_unittest.cc", + "source/rtcp_packet/extended_reports_unittest.cc", + "source/rtcp_packet/fir_unittest.cc", + "source/rtcp_packet/loss_notification_unittest.cc", + "source/rtcp_packet/nack_unittest.cc", + "source/rtcp_packet/pli_unittest.cc", + "source/rtcp_packet/rapid_resync_request_unittest.cc", + "source/rtcp_packet/receiver_report_unittest.cc", + "source/rtcp_packet/remb_unittest.cc", + "source/rtcp_packet/remote_estimate_unittest.cc", + "source/rtcp_packet/report_block_unittest.cc", + "source/rtcp_packet/rrtr_unittest.cc", + "source/rtcp_packet/sdes_unittest.cc", + "source/rtcp_packet/sender_report_unittest.cc", + "source/rtcp_packet/target_bitrate_unittest.cc", + "source/rtcp_packet/tmmbn_unittest.cc", + "source/rtcp_packet/tmmbr_unittest.cc", + "source/rtcp_packet/transport_feedback_unittest.cc", + "source/rtcp_packet_unittest.cc", + "source/rtcp_receiver_unittest.cc", + "source/rtcp_sender_unittest.cc", + "source/rtcp_transceiver_impl_unittest.cc", + "source/rtcp_transceiver_unittest.cc", + "source/rtp_dependency_descriptor_extension_unittest.cc", + "source/rtp_fec_unittest.cc", + "source/rtp_format_h264_unittest.cc", + "source/rtp_format_unittest.cc", + "source/rtp_format_video_generic_unittest.cc", + "source/rtp_format_vp8_test_helper.cc", + "source/rtp_format_vp8_test_helper.h", + "source/rtp_format_vp8_unittest.cc", + "source/rtp_format_vp9_unittest.cc", + "source/rtp_generic_frame_descriptor_extension_unittest.cc", + "source/rtp_header_extension_map_unittest.cc", + "source/rtp_header_extension_size_unittest.cc", + "source/rtp_packet_history_unittest.cc", + "source/rtp_packet_unittest.cc", + "source/rtp_packetizer_av1_unittest.cc", + "source/rtp_rtcp_impl2_unittest.cc", + "source/rtp_rtcp_impl_unittest.cc", + "source/rtp_sender_audio_unittest.cc", + "source/rtp_sender_egress_unittest.cc", + "source/rtp_sender_unittest.cc", + "source/rtp_sender_video_unittest.cc", + "source/rtp_sequence_number_map_unittest.cc", + "source/rtp_util_unittest.cc", + "source/rtp_video_layers_allocation_extension_unittest.cc", + "source/source_tracker_unittest.cc", + "source/time_util_unittest.cc", + "source/ulpfec_generator_unittest.cc", + "source/ulpfec_header_reader_writer_unittest.cc", + "source/ulpfec_receiver_unittest.cc", + "source/video_rtp_depacketizer_av1_unittest.cc", + "source/video_rtp_depacketizer_generic_unittest.cc", + "source/video_rtp_depacketizer_h264_unittest.cc", + "source/video_rtp_depacketizer_raw_unittest.cc", + "source/video_rtp_depacketizer_vp8_unittest.cc", + "source/video_rtp_depacketizer_vp9_unittest.cc", + ] + deps = [ + ":fec_test_helper", + ":frame_transformer_factory_unittest", + ":leb128", + ":mock_rtp_rtcp", + ":rtcp_transceiver", + ":rtp_packetizer_av1_test_helper", + ":rtp_rtcp", + ":rtp_rtcp_format", + ":rtp_rtcp_legacy", + ":rtp_video_header_unittest", + "../../api:array_view", + "../../api:create_time_controller", + "../../api:field_trials_registry", + "../../api:frame_transformer_factory", + "../../api:libjingle_peerconnection_api", + "../../api:mock_frame_encryptor", + "../../api:rtp_headers", + "../../api:rtp_packet_info", + "../../api:rtp_parameters", + "../../api:scoped_refptr", + "../../api:time_controller", + "../../api:transport_api", + "../../api/rtc_event_log", + "../../api/task_queue", + "../../api/transport:field_trial_based_config", + "../../api/transport/rtp:dependency_descriptor", + "../../api/units:data_rate", + "../../api/units:data_size", + "../../api/units:time_delta", + "../../api/units:timestamp", + "../../api/video:encoded_image", + "../../api/video:video_bitrate_allocation", + "../../api/video:video_bitrate_allocator", + "../../api/video:video_codec_constants", + "../../api/video:video_frame", + "../../api/video:video_layers_allocation", + "../../api/video:video_rtp_headers", + "../../api/video_codecs:video_codecs_api", + "../../call:rtp_receiver", + "../../common_video", + "../../common_video/generic_frame_descriptor", + "../../common_video/test:utilities", + "../../logging:mocks", + "../../rtc_base:bit_buffer", + "../../rtc_base:buffer", + "../../rtc_base:checks", + "../../rtc_base:copy_on_write_buffer", + "../../rtc_base:logging", + "../../rtc_base:macromagic", + "../../rtc_base:random", + "../../rtc_base:rate_limiter", + "../../rtc_base:rtc_base_tests_utils", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_numerics", + "../../rtc_base:stringutils", + "../../rtc_base:task_queue_for_test", + "../../rtc_base:threading", + "../../rtc_base:timeutils", + "../../system_wrappers", + "../../test:explicit_key_value_config", + "../../test:field_trial", + "../../test:mock_frame_transformer", + "../../test:mock_transport", + "../../test:rtp_test_utils", + "../../test:run_loop", + "../../test:scoped_key_value_config", + "../../test:test_support", + "../../test/time_controller:time_controller", + "../video_coding:codec_globals_headers", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } +} + +rtc_source_set("frame_transformer_factory_unittest") { + testonly = true + sources = [ "source/frame_transformer_factory_unittest.cc" ] + deps = [ + "../../api:frame_transformer_factory", + "../../api:transport_api", + "../../call:video_stream_api", + "../../modules/rtp_rtcp", + "../../rtc_base:rtc_event", + "../../test:mock_frame_transformer", + "../../test:test_support", + "../../video", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/memory" ] +} diff --git a/third_party/libwebrtc/modules/rtp_rtcp/DEPS b/third_party/libwebrtc/modules/rtp_rtcp/DEPS new file mode 100644 index 0000000000..3eec1ca90d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+call", + "+common_video", + "+logging/rtc_event_log", + "+system_wrappers", + # Avoid directly using field_trial. Instead use FieldTrialsView. + "-system_wrappers/include/field_trial.h", +] diff --git a/third_party/libwebrtc/modules/rtp_rtcp/OWNERS b/third_party/libwebrtc/modules/rtp_rtcp/OWNERS new file mode 100644 index 0000000000..47d12c401f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/OWNERS @@ -0,0 +1,6 @@ +stefan@webrtc.org +henrik.lundin@webrtc.org +mflodman@webrtc.org +asapersson@webrtc.org +danilchap@webrtc.org +sprang@webrtc.org diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/flexfec_receiver.h b/third_party/libwebrtc/modules/rtp_rtcp/include/flexfec_receiver.h new file mode 100644 index 0000000000..29d9e72786 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/flexfec_receiver.h @@ -0,0 +1,80 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_FLEXFEC_RECEIVER_H_ +#define MODULES_RTP_RTCP_INCLUDE_FLEXFEC_RECEIVER_H_ + +#include <stdint.h> + +#include <memory> + +#include "api/sequence_checker.h" +#include "modules/rtp_rtcp/include/recovered_packet_receiver.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/ulpfec_receiver.h" +#include "rtc_base/system/no_unique_address.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class Clock; + +class FlexfecReceiver { + public: + /* Mozilla: Avoid this since it could use GetRealTimeClock(). + FlexfecReceiver(uint32_t ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* recovered_packet_receiver); + */ + FlexfecReceiver(Clock* clock, + uint32_t ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* recovered_packet_receiver); + ~FlexfecReceiver(); + + // Inserts a received packet (can be either media or FlexFEC) into the + // internal buffer, and sends the received packets to the erasure code. + // All newly recovered packets are sent back through the callback. + void OnRtpPacket(const RtpPacketReceived& packet); + + // Returns a counter describing the added and recovered packets. + FecPacketCounter GetPacketCounter() const; + + // Protected to aid testing. + protected: + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> AddReceivedPacket( + const RtpPacketReceived& packet); + void ProcessReceivedPacket( + const ForwardErrorCorrection::ReceivedPacket& received_packet); + + private: + // Config. + const uint32_t ssrc_; + const uint32_t protected_media_ssrc_; + + // Erasure code interfacing and callback. + std::unique_ptr<ForwardErrorCorrection> erasure_code_ + RTC_GUARDED_BY(sequence_checker_); + ForwardErrorCorrection::RecoveredPacketList recovered_packets_ + RTC_GUARDED_BY(sequence_checker_); + RecoveredPacketReceiver* const recovered_packet_receiver_; + + // Logging and stats. + Clock* const clock_; + int64_t last_recovered_packet_ms_ RTC_GUARDED_BY(sequence_checker_); + FecPacketCounter packet_counter_ RTC_GUARDED_BY(sequence_checker_); + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_FLEXFEC_RECEIVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/flexfec_sender.h b/third_party/libwebrtc/modules/rtp_rtcp/include/flexfec_sender.h new file mode 100644 index 0000000000..f0acfe6c3d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/flexfec_sender.h @@ -0,0 +1,103 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_FLEXFEC_SENDER_H_ +#define MODULES_RTP_RTCP_INCLUDE_FLEXFEC_SENDER_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/rtp_parameters.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_header_extension_size.h" +#include "modules/rtp_rtcp/source/ulpfec_generator.h" +#include "modules/rtp_rtcp/source/video_fec_generator.h" +#include "rtc_base/random.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +class Clock; +class RtpPacketToSend; + +// Note that this class is not thread safe, and thus requires external +// synchronization. Currently, this is done using the lock in PayloadRouter. + +class FlexfecSender : public VideoFecGenerator { + public: + FlexfecSender(int payload_type, + uint32_t ssrc, + uint32_t protected_media_ssrc, + absl::string_view mid, + const std::vector<RtpExtension>& rtp_header_extensions, + rtc::ArrayView<const RtpExtensionSize> extension_sizes, + const RtpState* rtp_state, + Clock* clock); + ~FlexfecSender(); + + FecType GetFecType() const override { + return VideoFecGenerator::FecType::kFlexFec; + } + absl::optional<uint32_t> FecSsrc() override { return ssrc_; } + + // Sets the FEC rate, max frames sent before FEC packets are sent, + // and what type of generator matrices are used. + void SetProtectionParameters(const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) override; + + // Adds a media packet to the internal buffer. When enough media packets + // have been added, the FEC packets are generated and stored internally. + // These FEC packets are then obtained by calling GetFecPackets(). + void AddPacketAndGenerateFec(const RtpPacketToSend& packet) override; + + // Returns generated FlexFEC packets. + std::vector<std::unique_ptr<RtpPacketToSend>> GetFecPackets() override; + + // Returns the overhead, per packet, for FlexFEC. + size_t MaxPacketOverhead() const override; + + DataRate CurrentFecRate() const override; + + // Only called on the VideoSendStream queue, after operation has shut down. + absl::optional<RtpState> GetRtpState() override; + + private: + // Utility. + Clock* const clock_; + Random random_; + int64_t last_generated_packet_ms_; + + // Config. + const int payload_type_; + const uint32_t timestamp_offset_; + const uint32_t ssrc_; + const uint32_t protected_media_ssrc_; + // MID value to send in the MID header extension. + const std::string mid_; + // Sequence number of next packet to generate. + uint16_t seq_num_; + + // Implementation. + UlpfecGenerator ulpfec_generator_; + const RtpHeaderExtensionMap rtp_header_extension_map_; + const size_t header_extensions_size_; + + mutable Mutex mutex_; + RateStatistics fec_bitrate_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_FLEXFEC_SENDER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/receive_statistics.h b/third_party/libwebrtc/modules/rtp_rtcp/include/receive_statistics.h new file mode 100644 index 0000000000..827fd3a7a8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/receive_statistics.h @@ -0,0 +1,83 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RECEIVE_STATISTICS_H_ +#define MODULES_RTP_RTCP_INCLUDE_RECEIVE_STATISTICS_H_ + +#include <map> +#include <memory> +#include <vector> + +#include "absl/types/optional.h" +#include "call/rtp_packet_sink_interface.h" +#include "modules/rtp_rtcp/include/rtcp_statistics.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" + +namespace webrtc { + +class Clock; + +class ReceiveStatisticsProvider { + public: + virtual ~ReceiveStatisticsProvider() = default; + // Collects receive statistic in a form of rtcp report blocks. + // Returns at most `max_blocks` report blocks. + virtual std::vector<rtcp::ReportBlock> RtcpReportBlocks( + size_t max_blocks) = 0; +}; + +class StreamStatistician { + public: + virtual ~StreamStatistician(); + + virtual RtpReceiveStats GetStats() const = 0; + + // Returns average over the stream life time. + virtual absl::optional<int> GetFractionLostInPercent() const = 0; + + // TODO(bugs.webrtc.org/10679): Delete, migrate users to the above GetStats + // method (and extend RtpReceiveStats if needed). + // Gets receive stream data counters. + virtual StreamDataCounters GetReceiveStreamDataCounters() const = 0; + + virtual uint32_t BitrateReceived() const = 0; +}; + +class ReceiveStatistics : public ReceiveStatisticsProvider, + public RtpPacketSinkInterface { + public: + ~ReceiveStatistics() override = default; + + // Returns a thread-safe instance of ReceiveStatistics. + // https://chromium.googlesource.com/chromium/src/+/lkgr/docs/threading_and_tasks.md#threading-lexicon + static std::unique_ptr<ReceiveStatistics> Create(Clock* clock); + // Returns a thread-compatible instance of ReceiveStatistics. + static std::unique_ptr<ReceiveStatistics> CreateThreadCompatible( + Clock* clock); + + // Returns a pointer to the statistician of an ssrc. + virtual StreamStatistician* GetStatistician(uint32_t ssrc) const = 0; + + // TODO(bugs.webrtc.org/10669): Deprecated, delete as soon as downstream + // projects are updated. This method sets the max reordering threshold of all + // current and future streams. + virtual void SetMaxReorderingThreshold(int max_reordering_threshold) = 0; + + // Sets the max reordering threshold in number of packets. + virtual void SetMaxReorderingThreshold(uint32_t ssrc, + int max_reordering_threshold) = 0; + // Detect retransmissions, enabling updates of the retransmitted counters. The + // default is false. + virtual void EnableRetransmitDetection(uint32_t ssrc, bool enable) = 0; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_INCLUDE_RECEIVE_STATISTICS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/recovered_packet_receiver.h b/third_party/libwebrtc/modules/rtp_rtcp/include/recovered_packet_receiver.h new file mode 100644 index 0000000000..4e92c486e2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/recovered_packet_receiver.h @@ -0,0 +1,30 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RECOVERED_PACKET_RECEIVER_H_ +#define MODULES_RTP_RTCP_INCLUDE_RECOVERED_PACKET_RECEIVER_H_ + +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +// Callback interface for packets recovered by FlexFEC or ULPFEC. In +// the FlexFEC case, the implementation should be able to demultiplex +// the recovered RTP packets based on SSRC. +class RecoveredPacketReceiver { + public: + virtual void OnRecoveredPacket(const RtpPacketReceived& packet) = 0; + + protected: + virtual ~RecoveredPacketReceiver() = default; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_INCLUDE_RECOVERED_PACKET_RECEIVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/remote_ntp_time_estimator.h b/third_party/libwebrtc/modules/rtp_rtcp/include/remote_ntp_time_estimator.h new file mode 100644 index 0000000000..01d0c85f94 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/remote_ntp_time_estimator.h @@ -0,0 +1,74 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_REMOTE_NTP_TIME_ESTIMATOR_H_ +#define MODULES_RTP_RTCP_INCLUDE_REMOTE_NTP_TIME_ESTIMATOR_H_ + +#include <stdint.h> + +#include "absl/types/optional.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/numerics/moving_percentile_filter.h" +#include "system_wrappers/include/rtp_to_ntp_estimator.h" + +namespace webrtc { + +class Clock; + +// RemoteNtpTimeEstimator can be used to estimate a given RTP timestamp's NTP +// time in local timebase. +// Note that it needs to be trained with at least 2 RTCP SR (by calling +// `UpdateRtcpTimestamp`) before it can be used. +class RemoteNtpTimeEstimator { + public: + explicit RemoteNtpTimeEstimator(Clock* clock); + RemoteNtpTimeEstimator(const RemoteNtpTimeEstimator&) = delete; + RemoteNtpTimeEstimator& operator=(const RemoteNtpTimeEstimator&) = delete; + ~RemoteNtpTimeEstimator() = default; + + // Updates the estimator with round trip time `rtt` and + // new NTP time <-> RTP timestamp mapping from an RTCP sender report. + bool UpdateRtcpTimestamp(TimeDelta rtt, + NtpTime sender_send_time, + uint32_t rtp_timestamp); + + // Estimates the NTP timestamp in local timebase from `rtp_timestamp`. + // Returns the NTP timestamp in ms when success. -1 if failed. + int64_t Estimate(uint32_t rtp_timestamp) { + NtpTime ntp_time = EstimateNtp(rtp_timestamp); + if (!ntp_time.Valid()) { + return -1; + } + return ntp_time.ToMs(); + } + + // Estimates the NTP timestamp in local timebase from `rtp_timestamp`. + // Returns invalid NtpTime (i.e. NtpTime(0)) on failure. + NtpTime EstimateNtp(uint32_t rtp_timestamp); + + // Estimates the offset between the remote clock and the + // local one. This is equal to local NTP clock - remote NTP clock. + // The offset is returned in ntp time resolution, i.e. 1/2^32 sec ~= 0.2 ns. + // Returns nullopt on failure. + absl::optional<int64_t> EstimateRemoteToLocalClockOffset(); + + private: + Clock* clock_; + // Offset is measured with the same precision as NtpTime: in 1/2^32 seconds ~= + // 0.2 ns. + MovingMedianFilter<int64_t> ntp_clocks_offset_estimator_; + RtpToNtpEstimator rtp_to_ntp_; + Timestamp last_timing_log_ = Timestamp::MinusInfinity(); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_REMOTE_NTP_TIME_ESTIMATOR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.cc b/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.cc new file mode 100644 index 0000000000..ec4d9d82e0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.cc @@ -0,0 +1,44 @@ +/* + * 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 "modules/rtp_rtcp/include/report_block_data.h" + +namespace webrtc { + +ReportBlockData::ReportBlockData() + : report_block_(), + report_block_timestamp_utc_us_(0), + last_rtt_ms_(0), + min_rtt_ms_(0), + max_rtt_ms_(0), + sum_rtt_ms_(0), + num_rtts_(0) {} + +double ReportBlockData::AvgRttMs() const { + return num_rtts_ ? static_cast<double>(sum_rtt_ms_) / num_rtts_ : 0.0; +} + +void ReportBlockData::SetReportBlock(RTCPReportBlock report_block, + int64_t report_block_timestamp_utc_us) { + report_block_ = report_block; + report_block_timestamp_utc_us_ = report_block_timestamp_utc_us; +} + +void ReportBlockData::AddRoundTripTimeSample(int64_t rtt_ms) { + if (rtt_ms > max_rtt_ms_) + max_rtt_ms_ = rtt_ms; + if (num_rtts_ == 0 || rtt_ms < min_rtt_ms_) + min_rtt_ms_ = rtt_ms; + last_rtt_ms_ = rtt_ms; + sum_rtt_ms_ += rtt_ms; + ++num_rtts_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.h b/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.h new file mode 100644 index 0000000000..2c4533ada8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.h @@ -0,0 +1,59 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_REPORT_BLOCK_DATA_H_ +#define MODULES_RTP_RTCP_INCLUDE_REPORT_BLOCK_DATA_H_ + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { + +class ReportBlockData { + public: + ReportBlockData(); + + const RTCPReportBlock& report_block() const { return report_block_; } + int64_t report_block_timestamp_utc_us() const { + return report_block_timestamp_utc_us_; + } + int64_t last_rtt_ms() const { return last_rtt_ms_; } + int64_t min_rtt_ms() const { return min_rtt_ms_; } + int64_t max_rtt_ms() const { return max_rtt_ms_; } + int64_t sum_rtt_ms() const { return sum_rtt_ms_; } + size_t num_rtts() const { return num_rtts_; } + bool has_rtt() const { return num_rtts_ != 0; } + + double AvgRttMs() const; + + void SetReportBlock(RTCPReportBlock report_block, + int64_t report_block_timestamp_utc_us); + void AddRoundTripTimeSample(int64_t rtt_ms); + + private: + RTCPReportBlock report_block_; + int64_t report_block_timestamp_utc_us_; + + int64_t last_rtt_ms_; + int64_t min_rtt_ms_; + int64_t max_rtt_ms_; + int64_t sum_rtt_ms_; + size_t num_rtts_; +}; + +class ReportBlockDataObserver { + public: + virtual ~ReportBlockDataObserver() = default; + + virtual void OnReportBlockDataUpdated(ReportBlockData report_block_data) = 0; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_REPORT_BLOCK_DATA_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtcp_statistics.h b/third_party/libwebrtc/modules/rtp_rtcp/include/rtcp_statistics.h new file mode 100644 index 0000000000..6d6246d8a8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtcp_statistics.h @@ -0,0 +1,77 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RTCP_STATISTICS_H_ +#define MODULES_RTP_RTCP_INCLUDE_RTCP_STATISTICS_H_ + +#include <stdint.h> + +#include "absl/strings/string_view.h" + +namespace webrtc { + +// Statistics for RTCP packet types. +struct RtcpPacketTypeCounter { + RtcpPacketTypeCounter() + : nack_packets(0), + fir_packets(0), + pli_packets(0), + nack_requests(0), + unique_nack_requests(0) {} + + void Add(const RtcpPacketTypeCounter& other) { + nack_packets += other.nack_packets; + fir_packets += other.fir_packets; + pli_packets += other.pli_packets; + nack_requests += other.nack_requests; + unique_nack_requests += other.unique_nack_requests; + } + + void Subtract(const RtcpPacketTypeCounter& other) { + nack_packets -= other.nack_packets; + fir_packets -= other.fir_packets; + pli_packets -= other.pli_packets; + nack_requests -= other.nack_requests; + unique_nack_requests -= other.unique_nack_requests; + } + + int UniqueNackRequestsInPercent() const { + if (nack_requests == 0) { + return 0; + } + return static_cast<int>((unique_nack_requests * 100.0f / nack_requests) + + 0.5f); + } + + uint32_t nack_packets; // Number of RTCP NACK packets. + uint32_t fir_packets; // Number of RTCP FIR packets. + uint32_t pli_packets; // Number of RTCP PLI packets. + uint32_t nack_requests; // Number of NACKed RTP packets. + uint32_t unique_nack_requests; // Number of unique NACKed RTP packets. +}; + +class RtcpPacketTypeCounterObserver { + public: + virtual ~RtcpPacketTypeCounterObserver() {} + virtual void RtcpPacketTypesCounterUpdated( + uint32_t ssrc, + const RtcpPacketTypeCounter& packet_counter) = 0; +}; + +// Invoked for each cname passed in RTCP SDES blocks. +class RtcpCnameCallback { + public: + virtual ~RtcpCnameCallback() = default; + + virtual void OnCname(uint32_t ssrc, absl::string_view cname) = 0; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_INCLUDE_RTCP_STATISTICS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_cvo.h b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_cvo.h new file mode 100644 index 0000000000..497946d6a7 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_cvo.h @@ -0,0 +1,56 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RTP_CVO_H_ +#define MODULES_RTP_RTCP_INCLUDE_RTP_CVO_H_ + +#include "api/video/video_rotation.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +// Please refer to http://www.etsi.org/deliver/etsi_ts/126100_126199/126114/ +// 12.07.00_60/ts_126114v120700p.pdf Section 7.4.5. The rotation of a frame is +// the clockwise angle the frames must be rotated in order to display the frames +// correctly if the display is rotated in its natural orientation. +inline uint8_t ConvertVideoRotationToCVOByte(VideoRotation rotation) { + switch (rotation) { + case kVideoRotation_0: + return 0; + case kVideoRotation_90: + return 1; + case kVideoRotation_180: + return 2; + case kVideoRotation_270: + return 3; + } + RTC_DCHECK_NOTREACHED(); + return 0; +} + +inline VideoRotation ConvertCVOByteToVideoRotation(uint8_t cvo_byte) { + // CVO byte: |0 0 0 0 C F R R|. + const uint8_t rotation_bits = cvo_byte & 0x3; + switch (rotation_bits) { + case 0: + return kVideoRotation_0; + case 1: + return kVideoRotation_90; + case 2: + return kVideoRotation_180; + case 3: + return kVideoRotation_270; + default: + RTC_DCHECK_NOTREACHED(); + return kVideoRotation_0; + } +} + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_INCLUDE_RTP_CVO_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_header_extension_map.h b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_header_extension_map.h new file mode 100644 index 0000000000..ff1ea61f52 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_header_extension_map.h @@ -0,0 +1,75 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RTP_HEADER_EXTENSION_MAP_H_ +#define MODULES_RTP_RTCP_INCLUDE_RTP_HEADER_EXTENSION_MAP_H_ + +#include <stdint.h> + +#include <string> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/rtp_parameters.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +class RtpHeaderExtensionMap { + public: + static constexpr RTPExtensionType kInvalidType = kRtpExtensionNone; + static constexpr int kInvalidId = 0; + + RtpHeaderExtensionMap(); + explicit RtpHeaderExtensionMap(bool extmap_allow_mixed); + explicit RtpHeaderExtensionMap(rtc::ArrayView<const RtpExtension> extensions); + + void Reset(rtc::ArrayView<const RtpExtension> extensions); + + template <typename Extension> + bool Register(int id) { + return Register(id, Extension::kId, Extension::Uri()); + } + bool RegisterByType(int id, RTPExtensionType type); + bool RegisterByUri(int id, absl::string_view uri); + + bool IsRegistered(RTPExtensionType type) const { + return GetId(type) != kInvalidId; + } + // Return kInvalidType if not found. + RTPExtensionType GetType(int id) const; + // Return kInvalidId if not found. + uint8_t GetId(RTPExtensionType type) const { + RTC_DCHECK_GT(type, kRtpExtensionNone); + RTC_DCHECK_LT(type, kRtpExtensionNumberOfExtensions); + return ids_[type]; + } + + void Deregister(absl::string_view uri); + + // Corresponds to the SDP attribute extmap-allow-mixed, see RFC8285. + // Set to true if it's allowed to mix one- and two-byte RTP header extensions + // in the same stream. + bool ExtmapAllowMixed() const { return extmap_allow_mixed_; } + void SetExtmapAllowMixed(bool extmap_allow_mixed) { + extmap_allow_mixed_ = extmap_allow_mixed; + } + + private: + bool Register(int id, RTPExtensionType type, absl::string_view uri); + + uint8_t ids_[kRtpExtensionNumberOfExtensions]; + bool extmap_allow_mixed_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_RTP_HEADER_EXTENSION_MAP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_packet_sender.h b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_packet_sender.h new file mode 100644 index 0000000000..ebc65298a5 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_packet_sender.h @@ -0,0 +1,40 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RTP_PACKET_SENDER_H_ +#define MODULES_RTP_RTCP_INCLUDE_RTP_PACKET_SENDER_H_ + +#include <memory> +#include <vector> + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" + +namespace webrtc { + +class RtpPacketSender { + public: + virtual ~RtpPacketSender() = default; + + // Insert a set of packets into queue, for eventual transmission. Based on the + // type of packets, they will be prioritized and scheduled relative to other + // packets and the current target send rate. + virtual void EnqueuePackets( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) = 0; + + // Clear any pending packets with the given SSRC from the queue. + // TODO(crbug.com/1395081): Make pure virtual when downstream code has been + // updated. + virtual void RemovePacketsForSsrc(uint32_t ssrc) {} +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_RTP_PACKET_SENDER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp.h b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp.h new file mode 100644 index 0000000000..e56d5ef637 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp.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 MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_H_ +#define MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_H_ + +#include <memory> + +#include "absl/base/attributes.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" + +namespace webrtc { + +class ABSL_DEPRECATED("") RtpRtcp : public RtpRtcpInterface { + public: + // Instantiates a deprecated version of the RtpRtcp module. + static std::unique_ptr<RtpRtcp> ABSL_DEPRECATED("") + Create(const Configuration& configuration) { + return DEPRECATED_Create(configuration); + } + + static std::unique_ptr<RtpRtcp> DEPRECATED_Create( + const Configuration& configuration); + + // Process any pending tasks such as timeouts. + virtual void Process() = 0; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc new file mode 100644 index 0000000000..e4aec93696 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc @@ -0,0 +1,75 @@ +/* + * 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/rtp_rtcp/include/rtp_rtcp_defines.h" + +#include <string.h> + +#include <type_traits> + +#include "absl/algorithm/container.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" + +namespace webrtc { + +namespace { +constexpr size_t kMidRsidMaxSize = 16; + +// Check if passed character is a "token-char" from RFC 4566. +// https://datatracker.ietf.org/doc/html/rfc4566#section-9 +// token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 +// / %x41-5A / %x5E-7E +bool IsTokenChar(char ch) { + return ch == 0x21 || (ch >= 0x23 && ch <= 0x27) || ch == 0x2a || ch == 0x2b || + ch == 0x2d || ch == 0x2e || (ch >= 0x30 && ch <= 0x39) || + (ch >= 0x41 && ch <= 0x5a) || (ch >= 0x5e && ch <= 0x7e); +} +} // namespace + +bool IsLegalMidName(absl::string_view name) { + return (name.size() <= kMidRsidMaxSize && !name.empty() && + absl::c_all_of(name, IsTokenChar)); +} + +bool IsLegalRsidName(absl::string_view name) { + return (name.size() <= kMidRsidMaxSize && !name.empty() && + absl::c_all_of(name, isalnum)); +} + +StreamDataCounters::StreamDataCounters() : first_packet_time_ms(-1) {} + +RtpPacketCounter::RtpPacketCounter(const RtpPacket& packet) + : header_bytes(packet.headers_size()), + payload_bytes(packet.payload_size()), + padding_bytes(packet.padding_size()), + packets(1) {} + +RtpPacketCounter::RtpPacketCounter(const RtpPacketToSend& packet_to_send) + : RtpPacketCounter(static_cast<const RtpPacket&>(packet_to_send)) { + total_packet_delay = + packet_to_send.time_in_send_queue().value_or(TimeDelta::Zero()); +} + +void RtpPacketCounter::AddPacket(const RtpPacket& packet) { + ++packets; + header_bytes += packet.headers_size(); + padding_bytes += packet.padding_size(); + payload_bytes += packet.payload_size(); +} + +void RtpPacketCounter::AddPacket(const RtpPacketToSend& packet_to_send) { + AddPacket(static_cast<const RtpPacket&>(packet_to_send)); + total_packet_delay += + packet_to_send.time_in_send_queue().value_or(TimeDelta::Zero()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h new file mode 100644 index 0000000000..882f861d0b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h @@ -0,0 +1,495 @@ +/* + * 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 MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_ +#define MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_ + +#include <stddef.h> + +#include <list> +#include <memory> +#include <vector> + +#include "absl/algorithm/container.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "api/array_view.h" +#include "api/audio_codecs/audio_format.h" +#include "api/rtp_headers.h" +#include "api/transport/network_types.h" +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h" +#include "system_wrappers/include/clock.h" + +#define RTCP_CNAME_SIZE 256 // RFC 3550 page 44, including null termination +#define IP_PACKET_SIZE 1500 // we assume ethernet + +namespace webrtc { +class RtpPacket; +class RtpPacketToSend; +namespace rtcp { +class TransportFeedback; +} + +const int kVideoPayloadTypeFrequency = 90000; + +// TODO(bugs.webrtc.org/6458): Remove this when all the depending projects are +// updated to correctly set rtp rate for RtcpSender. +const int kBogusRtpRateForAudioRtcp = 8000; + +// Minimum RTP header size in bytes. +const uint8_t kRtpHeaderSize = 12; + +bool IsLegalMidName(absl::string_view name); +bool IsLegalRsidName(absl::string_view name); + +// This enum must not have any gaps, i.e., all integers between +// kRtpExtensionNone and kRtpExtensionNumberOfExtensions must be valid enum +// entries. +enum RTPExtensionType : int { + kRtpExtensionNone, + kRtpExtensionTransmissionTimeOffset, + kRtpExtensionAudioLevel, + kRtpExtensionCsrcAudioLevel, + kRtpExtensionInbandComfortNoise, + kRtpExtensionAbsoluteSendTime, + kRtpExtensionAbsoluteCaptureTime, + kRtpExtensionVideoRotation, + kRtpExtensionTransportSequenceNumber, + kRtpExtensionTransportSequenceNumber02, + kRtpExtensionPlayoutDelay, + kRtpExtensionVideoContentType, + kRtpExtensionVideoLayersAllocation, + kRtpExtensionVideoTiming, + kRtpExtensionRtpStreamId, + kRtpExtensionRepairedRtpStreamId, + kRtpExtensionMid, + kRtpExtensionGenericFrameDescriptor, + kRtpExtensionGenericFrameDescriptor00 [[deprecated]] = + kRtpExtensionGenericFrameDescriptor, + kRtpExtensionDependencyDescriptor, + kRtpExtensionGenericFrameDescriptor02 [[deprecated]] = + kRtpExtensionDependencyDescriptor, + kRtpExtensionColorSpace, + kRtpExtensionVideoFrameTrackingId, + kRtpExtensionNumberOfExtensions // Must be the last entity in the enum. +}; + +enum RTCPAppSubTypes { kAppSubtypeBwe = 0x00 }; + +// TODO(sprang): Make this an enum class once rtcp_receiver has been cleaned up. +enum RTCPPacketType : uint32_t { + kRtcpReport = 0x0001, + kRtcpSr = 0x0002, + kRtcpRr = 0x0004, + kRtcpSdes = 0x0008, + kRtcpBye = 0x0010, + kRtcpPli = 0x0020, + kRtcpNack = 0x0040, + kRtcpFir = 0x0080, + kRtcpTmmbr = 0x0100, + kRtcpTmmbn = 0x0200, + kRtcpSrReq = 0x0400, + kRtcpLossNotification = 0x2000, + kRtcpRemb = 0x10000, + kRtcpTransmissionTimeOffset = 0x20000, + kRtcpXrReceiverReferenceTime = 0x40000, + kRtcpXrDlrrReportBlock = 0x80000, + kRtcpTransportFeedback = 0x100000, + kRtcpXrTargetBitrate = 0x200000 +}; + +enum class KeyFrameReqMethod : uint8_t { + kNone, // Don't request keyframes. + kPliRtcp, // Request keyframes through Picture Loss Indication. + kFirRtcp // Request keyframes through Full Intra-frame Request. +}; + +enum RtxMode { + kRtxOff = 0x0, + kRtxRetransmitted = 0x1, // Only send retransmissions over RTX. + kRtxRedundantPayloads = 0x2 // Preventively send redundant payloads + // instead of padding. +}; + +const size_t kRtxHeaderSize = 2; + +struct RTCPReportBlock { + RTCPReportBlock() + : sender_ssrc(0), + source_ssrc(0), + fraction_lost(0), + packets_lost(0), + extended_highest_sequence_number(0), + jitter(0), + last_sender_report_timestamp(0), + delay_since_last_sender_report(0) {} + + RTCPReportBlock(uint32_t sender_ssrc, + uint32_t source_ssrc, + uint8_t fraction_lost, + int32_t packets_lost, + uint32_t extended_highest_sequence_number, + uint32_t jitter, + uint32_t last_sender_report_timestamp, + uint32_t delay_since_last_sender_report) + : sender_ssrc(sender_ssrc), + source_ssrc(source_ssrc), + fraction_lost(fraction_lost), + packets_lost(packets_lost), + extended_highest_sequence_number(extended_highest_sequence_number), + jitter(jitter), + last_sender_report_timestamp(last_sender_report_timestamp), + delay_since_last_sender_report(delay_since_last_sender_report) {} + + // Fields as described by RFC 3550 6.4.2. + uint32_t sender_ssrc; // SSRC of sender of this report. + uint32_t source_ssrc; // SSRC of the RTP packet sender. + uint8_t fraction_lost; + int32_t packets_lost; // 24 bits valid. + uint32_t extended_highest_sequence_number; + uint32_t jitter; + uint32_t last_sender_report_timestamp; + uint32_t delay_since_last_sender_report; +}; + +typedef std::list<RTCPReportBlock> ReportBlockList; + +struct RtpState { + RtpState() + : sequence_number(0), + start_timestamp(0), + timestamp(0), + capture_time_ms(-1), + last_timestamp_time_ms(-1), + ssrc_has_acked(false) {} + uint16_t sequence_number; + uint32_t start_timestamp; + uint32_t timestamp; + int64_t capture_time_ms; + int64_t last_timestamp_time_ms; + bool ssrc_has_acked; +}; + +class RtcpIntraFrameObserver { + public: + virtual ~RtcpIntraFrameObserver() {} + + virtual void OnReceivedIntraFrameRequest(uint32_t ssrc) = 0; +}; + +// Observer for incoming LossNotification RTCP messages. +// See the documentation of LossNotification for details. +class RtcpLossNotificationObserver { + public: + virtual ~RtcpLossNotificationObserver() = default; + + virtual void OnReceivedLossNotification(uint32_t ssrc, + uint16_t seq_num_of_last_decodable, + uint16_t seq_num_of_last_received, + bool decodability_flag) = 0; +}; + +class RtcpBandwidthObserver { + public: + // REMB or TMMBR + virtual void OnReceivedEstimatedBitrate(uint32_t bitrate) = 0; + + virtual void OnReceivedRtcpReceiverReport( + const ReportBlockList& report_blocks, + int64_t rtt, + int64_t now_ms) = 0; + + virtual ~RtcpBandwidthObserver() {} +}; + +class RtcpEventObserver { + public: + virtual void OnRtcpBye() = 0; + virtual void OnRtcpTimeout() = 0; + + virtual ~RtcpEventObserver() {} +}; + +// NOTE! `kNumMediaTypes` must be kept in sync with RtpPacketMediaType! +static constexpr size_t kNumMediaTypes = 5; +enum class RtpPacketMediaType : size_t { + kAudio, // Audio media packets. + kVideo, // Video media packets. + kRetransmission, // Retransmisions, sent as response to NACK. + kForwardErrorCorrection, // FEC packets. + kPadding = kNumMediaTypes - 1, // RTX or plain padding sent to maintain BWE. + // Again, don't forget to udate `kNumMediaTypes` if you add another value! +}; + +struct RtpPacketSendInfo { + uint16_t transport_sequence_number = 0; + absl::optional<uint32_t> media_ssrc; + uint16_t rtp_sequence_number = 0; // Only valid if `media_ssrc` is set. + uint32_t rtp_timestamp = 0; + size_t length = 0; + absl::optional<RtpPacketMediaType> packet_type; + PacedPacketInfo pacing_info; +}; + +class NetworkStateEstimateObserver { + public: + virtual void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) = 0; + virtual ~NetworkStateEstimateObserver() = default; +}; + +class TransportFeedbackObserver { + public: + TransportFeedbackObserver() {} + virtual ~TransportFeedbackObserver() {} + + virtual void OnAddPacket(const RtpPacketSendInfo& packet_info) = 0; + virtual void OnTransportFeedback(const rtcp::TransportFeedback& feedback) = 0; +}; + +// Interface for PacketRouter to send rtcp feedback on behalf of +// congestion controller. +// TODO(bugs.webrtc.org/8239): Remove and use RtcpTransceiver directly +// when RtcpTransceiver always present in rtp transport. +class RtcpFeedbackSenderInterface { + public: + virtual ~RtcpFeedbackSenderInterface() = default; + virtual void SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) = 0; + virtual void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) = 0; + virtual void UnsetRemb() = 0; +}; + +class StreamFeedbackObserver { + public: + struct StreamPacketInfo { + bool received; + + // `rtp_sequence_number` and `is_retransmission` are only valid if `ssrc` + // is populated. + absl::optional<uint32_t> ssrc; + uint16_t rtp_sequence_number; + bool is_retransmission; + }; + virtual ~StreamFeedbackObserver() = default; + + virtual void OnPacketFeedbackVector( + std::vector<StreamPacketInfo> packet_feedback_vector) = 0; +}; + +class StreamFeedbackProvider { + public: + virtual void RegisterStreamFeedbackObserver( + std::vector<uint32_t> ssrcs, + StreamFeedbackObserver* observer) = 0; + virtual void DeRegisterStreamFeedbackObserver( + StreamFeedbackObserver* observer) = 0; + virtual ~StreamFeedbackProvider() = default; +}; + +class RtcpRttStats { + public: + virtual void OnRttUpdate(int64_t rtt) = 0; + + virtual int64_t LastProcessedRtt() const = 0; + + virtual ~RtcpRttStats() {} +}; + +struct RtpPacketCounter { + RtpPacketCounter() + : header_bytes(0), payload_bytes(0), padding_bytes(0), packets(0) {} + + explicit RtpPacketCounter(const RtpPacket& packet); + explicit RtpPacketCounter(const RtpPacketToSend& packet_to_send); + + void Add(const RtpPacketCounter& other) { + header_bytes += other.header_bytes; + payload_bytes += other.payload_bytes; + padding_bytes += other.padding_bytes; + packets += other.packets; + total_packet_delay += other.total_packet_delay; + } + + void Subtract(const RtpPacketCounter& other) { + RTC_DCHECK_GE(header_bytes, other.header_bytes); + header_bytes -= other.header_bytes; + RTC_DCHECK_GE(payload_bytes, other.payload_bytes); + payload_bytes -= other.payload_bytes; + RTC_DCHECK_GE(padding_bytes, other.padding_bytes); + padding_bytes -= other.padding_bytes; + RTC_DCHECK_GE(packets, other.packets); + packets -= other.packets; + RTC_DCHECK_GE(total_packet_delay, other.total_packet_delay); + total_packet_delay -= other.total_packet_delay; + } + + bool operator==(const RtpPacketCounter& other) const { + return header_bytes == other.header_bytes && + payload_bytes == other.payload_bytes && + padding_bytes == other.padding_bytes && packets == other.packets && + total_packet_delay == other.total_packet_delay; + } + + // Not inlined, since use of RtpPacket would result in circular includes. + void AddPacket(const RtpPacket& packet); + void AddPacket(const RtpPacketToSend& packet_to_send); + + size_t TotalBytes() const { + return header_bytes + payload_bytes + padding_bytes; + } + + size_t header_bytes; // Number of bytes used by RTP headers. + size_t payload_bytes; // Payload bytes, excluding RTP headers and padding. + size_t padding_bytes; // Number of padding bytes. + uint32_t packets; // Number of packets. + // The total delay of all `packets`. For RtpPacketToSend packets, this is + // `time_in_send_queue()`. For receive packets, this is zero. + webrtc::TimeDelta total_packet_delay = webrtc::TimeDelta::Zero(); +}; + +// Data usage statistics for a (rtp) stream. +struct StreamDataCounters { + StreamDataCounters(); + + void Add(const StreamDataCounters& other) { + transmitted.Add(other.transmitted); + retransmitted.Add(other.retransmitted); + fec.Add(other.fec); + if (other.first_packet_time_ms != -1 && + (other.first_packet_time_ms < first_packet_time_ms || + first_packet_time_ms == -1)) { + // Use oldest time. + first_packet_time_ms = other.first_packet_time_ms; + } + } + + void Subtract(const StreamDataCounters& other) { + transmitted.Subtract(other.transmitted); + retransmitted.Subtract(other.retransmitted); + fec.Subtract(other.fec); + if (other.first_packet_time_ms != -1 && + (other.first_packet_time_ms > first_packet_time_ms || + first_packet_time_ms == -1)) { + // Use youngest time. + first_packet_time_ms = other.first_packet_time_ms; + } + } + + int64_t TimeSinceFirstPacketInMs(int64_t now_ms) const { + return (first_packet_time_ms == -1) ? -1 : (now_ms - first_packet_time_ms); + } + + // Returns the number of bytes corresponding to the actual media payload (i.e. + // RTP headers, padding, retransmissions and fec packets are excluded). + // Note this function does not have meaning for an RTX stream. + size_t MediaPayloadBytes() const { + return transmitted.payload_bytes - retransmitted.payload_bytes - + fec.payload_bytes; + } + + int64_t first_packet_time_ms; // Time when first packet is sent/received. + // The timestamp at which the last packet was received, i.e. the time of the + // local clock when it was received - not the RTP timestamp of that packet. + // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-lastpacketreceivedtimestamp + absl::optional<int64_t> last_packet_received_timestamp_ms; + RtpPacketCounter transmitted; // Number of transmitted packets/bytes. + RtpPacketCounter retransmitted; // Number of retransmitted packets/bytes. + RtpPacketCounter fec; // Number of redundancy packets/bytes. +}; + +class RtpSendRates { + template <std::size_t... Is> + constexpr std::array<DataRate, sizeof...(Is)> make_zero_array( + std::index_sequence<Is...>) { + return {{(static_cast<void>(Is), DataRate::Zero())...}}; + } + + public: + RtpSendRates() + : send_rates_( + make_zero_array(std::make_index_sequence<kNumMediaTypes>())) {} + RtpSendRates(const RtpSendRates& rhs) = default; + RtpSendRates& operator=(const RtpSendRates&) = default; + + DataRate& operator[](RtpPacketMediaType type) { + return send_rates_[static_cast<size_t>(type)]; + } + const DataRate& operator[](RtpPacketMediaType type) const { + return send_rates_[static_cast<size_t>(type)]; + } + DataRate Sum() const { + return absl::c_accumulate(send_rates_, DataRate::Zero()); + } + + private: + std::array<DataRate, kNumMediaTypes> send_rates_; +}; + +// Callback, called whenever byte/packet counts have been updated. +class StreamDataCountersCallback { + public: + virtual ~StreamDataCountersCallback() {} + + virtual void DataCountersUpdated(const StreamDataCounters& counters, + uint32_t ssrc) = 0; +}; + +// Information exposed through the GetStats api. +struct RtpReceiveStats { + // `packets_lost` and `jitter` are defined by RFC 3550, and exposed in the + // RTCReceivedRtpStreamStats dictionary, see + // https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict* + int32_t packets_lost = 0; + // Interarrival jitter in samples. + uint32_t jitter = 0; + // Interarrival jitter in time. + webrtc::TimeDelta interarrival_jitter = webrtc::TimeDelta::Zero(); + + // Timestamp and counters exposed in RTCInboundRtpStreamStats, see + // https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict* + absl::optional<int64_t> last_packet_received_timestamp_ms; + RtpPacketCounter packet_counter; +}; + +// Callback, used to notify an observer whenever new rates have been estimated. +class BitrateStatisticsObserver { + public: + virtual ~BitrateStatisticsObserver() {} + + virtual void Notify(uint32_t total_bitrate_bps, + uint32_t retransmit_bitrate_bps, + uint32_t ssrc) = 0; +}; + +// Callback, used to notify an observer whenever the send-side delay is updated. +class SendSideDelayObserver { + public: + virtual ~SendSideDelayObserver() {} + virtual void SendSideDelayUpdated(int avg_delay_ms, + int max_delay_ms, + uint32_t ssrc) = 0; +}; + +// Callback, used to notify an observer whenever a packet is sent to the +// transport. +// TODO(asapersson): This class will remove the need for SendSideDelayObserver. +// Remove SendSideDelayObserver once possible. +class SendPacketObserver { + public: + virtual ~SendPacketObserver() {} + virtual void OnSendPacket(uint16_t packet_id, + int64_t capture_time_ms, + uint32_t ssrc) = 0; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/leb128_gn/moz.build b/third_party/libwebrtc/modules/rtp_rtcp/leb128_gn/moz.build new file mode 100644 index 0000000000..f03986e482 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/leb128_gn/moz.build @@ -0,0 +1,217 @@ +# 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" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.cc" +] + +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 + +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 + +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 + +if CONFIG["CPU_ARCH"] == "aarch64": + + DEFINES["WEBRTC_ARCH_ARM64"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +if CONFIG["CPU_ARCH"] == "arm": + + CXXFLAGS += [ + "-mfpu=neon" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +Library("leb128_gn") diff --git a/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h new file mode 100644 index 0000000000..d308b2cfa8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h @@ -0,0 +1,30 @@ +/* + * 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 MODULES_RTP_RTCP_MOCKS_MOCK_RECOVERED_PACKET_RECEIVER_H_ +#define MODULES_RTP_RTCP_MOCKS_MOCK_RECOVERED_PACKET_RECEIVER_H_ + +#include "modules/rtp_rtcp/include/flexfec_receiver.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockRecoveredPacketReceiver : public RecoveredPacketReceiver { + public: + MOCK_METHOD(void, + OnRecoveredPacket, + (const RtpPacketReceived& packet), + (override)); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_MOCKS_MOCK_RECOVERED_PACKET_RECEIVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtcp_bandwidth_observer.h b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtcp_bandwidth_observer.h new file mode 100644 index 0000000000..12f143ae8b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtcp_bandwidth_observer.h @@ -0,0 +1,28 @@ +/* + * 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 MODULES_RTP_RTCP_MOCKS_MOCK_RTCP_BANDWIDTH_OBSERVER_H_ +#define MODULES_RTP_RTCP_MOCKS_MOCK_RTCP_BANDWIDTH_OBSERVER_H_ + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockRtcpBandwidthObserver : public RtcpBandwidthObserver { + public: + MOCK_METHOD(void, OnReceivedEstimatedBitrate, (uint32_t), (override)); + MOCK_METHOD(void, + OnReceivedRtcpReceiverReport, + (const ReportBlockList&, int64_t, int64_t), + (override)); +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_MOCKS_MOCK_RTCP_BANDWIDTH_OBSERVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.h b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.h new file mode 100644 index 0000000000..e9a7d52691 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.h @@ -0,0 +1,25 @@ +/* + * 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 MODULES_RTP_RTCP_MOCKS_MOCK_RTCP_RTT_STATS_H_ +#define MODULES_RTP_RTCP_MOCKS_MOCK_RTCP_RTT_STATS_H_ + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockRtcpRttStats : public RtcpRttStats { + public: + MOCK_METHOD(void, OnRttUpdate, (int64_t rtt), (override)); + MOCK_METHOD(int64_t, LastProcessedRtt, (), (const, override)); +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_MOCKS_MOCK_RTCP_RTT_STATS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h new file mode 100644 index 0000000000..75c30742df --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h @@ -0,0 +1,192 @@ +/* + * 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 MODULES_RTP_RTCP_MOCKS_MOCK_RTP_RTCP_H_ +#define MODULES_RTP_RTCP_MOCKS_MOCK_RTP_RTCP_H_ + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockRtpRtcpInterface : public RtpRtcpInterface { + public: + MOCK_METHOD(void, + IncomingRtcpPacket, + (const uint8_t* incoming_packet, size_t packet_length), + (override)); + MOCK_METHOD(void, SetRemoteSSRC, (uint32_t ssrc), (override)); + MOCK_METHOD(void, SetLocalSsrc, (uint32_t ssrc), (override)); + MOCK_METHOD(void, SetMaxRtpPacketSize, (size_t size), (override)); + MOCK_METHOD(size_t, MaxRtpPacketSize, (), (const, override)); + MOCK_METHOD(void, + RegisterSendPayloadFrequency, + (int payload_type, int frequency), + (override)); + MOCK_METHOD(int32_t, + DeRegisterSendPayload, + (int8_t payload_type), + (override)); + MOCK_METHOD(void, SetExtmapAllowMixed, (bool extmap_allow_mixed), (override)); + MOCK_METHOD(void, + RegisterRtpHeaderExtension, + (absl::string_view uri, int id), + (override)); + MOCK_METHOD(void, + DeregisterSendRtpHeaderExtension, + (absl::string_view uri), + (override)); + MOCK_METHOD(bool, SupportsPadding, (), (const, override)); + MOCK_METHOD(bool, SupportsRtxPayloadPadding, (), (const, override)); + MOCK_METHOD(uint32_t, StartTimestamp, (), (const, override)); + MOCK_METHOD(void, SetStartTimestamp, (uint32_t timestamp), (override)); + MOCK_METHOD(uint16_t, SequenceNumber, (), (const, override)); + MOCK_METHOD(void, SetSequenceNumber, (uint16_t seq), (override)); + MOCK_METHOD(void, SetRtpState, (const RtpState& rtp_state), (override)); + MOCK_METHOD(void, SetRtxState, (const RtpState& rtp_state), (override)); + MOCK_METHOD(void, SetNonSenderRttMeasurement, (bool enabled), (override)); + MOCK_METHOD(RtpState, GetRtpState, (), (const, override)); + MOCK_METHOD(RtpState, GetRtxState, (), (const, override)); + MOCK_METHOD(uint32_t, SSRC, (), (const, override)); + MOCK_METHOD(void, SetMid, (absl::string_view mid), (override)); + MOCK_METHOD(void, SetRtxSendStatus, (int modes), (override)); + MOCK_METHOD(int, RtxSendStatus, (), (const, override)); + MOCK_METHOD(absl::optional<uint32_t>, RtxSsrc, (), (const, override)); + MOCK_METHOD(void, SetRtxSendPayloadType, (int, int), (override)); + MOCK_METHOD(absl::optional<uint32_t>, FlexfecSsrc, (), (const, override)); + MOCK_METHOD(int32_t, SetSendingStatus, (bool sending), (override)); + MOCK_METHOD(bool, Sending, (), (const, override)); + MOCK_METHOD(void, SetSendingMediaStatus, (bool sending), (override)); + MOCK_METHOD(bool, SendingMedia, (), (const, override)); + MOCK_METHOD(bool, IsAudioConfigured, (), (const, override)); + MOCK_METHOD(void, SetAsPartOfAllocation, (bool), (override)); + MOCK_METHOD(RtpSendRates, GetSendRates, (), (const, override)); + MOCK_METHOD(bool, + OnSendingRtpFrame, + (uint32_t, int64_t, int, bool), + (override)); + MOCK_METHOD(bool, + TrySendPacket, + (RtpPacketToSend * packet, const PacedPacketInfo& pacing_info), + (override)); + MOCK_METHOD(void, + SetFecProtectionParams, + (const FecProtectionParams& delta_params, + const FecProtectionParams& key_params), + (override)); + MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>, + FetchFecPackets, + (), + (override)); + MOCK_METHOD(void, + OnAbortedRetransmissions, + (rtc::ArrayView<const uint16_t>), + (override)); + MOCK_METHOD(void, + OnPacketsAcknowledged, + (rtc::ArrayView<const uint16_t>), + (override)); + MOCK_METHOD(std::vector<std::unique_ptr<RtpPacketToSend>>, + GeneratePadding, + (size_t target_size_bytes), + (override)); + MOCK_METHOD(std::vector<RtpSequenceNumberMap::Info>, + GetSentRtpPacketInfos, + (rtc::ArrayView<const uint16_t> sequence_numbers), + (const, override)); + MOCK_METHOD(size_t, ExpectedPerPacketOverhead, (), (const, override)); + MOCK_METHOD(void, OnPacketSendingThreadSwitched, (), (override)); + MOCK_METHOD(RtcpMode, RTCP, (), (const, override)); + MOCK_METHOD(void, SetRTCPStatus, (RtcpMode method), (override)); + MOCK_METHOD(int32_t, SetCNAME, (absl::string_view cname), (override)); + MOCK_METHOD(int32_t, + RemoteNTP, + (uint32_t * received_ntp_secs, + uint32_t* received_ntp_frac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp), + (const, override)); + MOCK_METHOD(int32_t, + RTT, + (uint32_t remote_ssrc, + int64_t* rtt, + int64_t* avg_rtt, + int64_t* min_rtt, + int64_t* max_rtt), + (const, override)); + MOCK_METHOD(int64_t, ExpectedRetransmissionTimeMs, (), (const, override)); + MOCK_METHOD(int32_t, SendRTCP, (RTCPPacketType packet_type), (override)); + MOCK_METHOD(void, + GetSendStreamDataCounters, + (StreamDataCounters*, StreamDataCounters*), + (const, override)); + MOCK_METHOD(std::vector<ReportBlockData>, + GetLatestReportBlockData, + (), + (const, override)); + MOCK_METHOD(absl::optional<SenderReportStats>, + GetSenderReportStats, + (), + (const, override)); + MOCK_METHOD(absl::optional<NonSenderRttStats>, + GetNonSenderRttStats, + (), + (const, override)); + MOCK_METHOD(void, + SetRemb, + (int64_t bitrate, std::vector<uint32_t> ssrcs), + (override)); + MOCK_METHOD(void, UnsetRemb, (), (override)); + MOCK_METHOD(int32_t, + SendNACK, + (const uint16_t* nack_list, uint16_t size), + (override)); + MOCK_METHOD(void, + SendNack, + (const std::vector<uint16_t>& sequence_numbers), + (override)); + MOCK_METHOD(void, + SetStorePacketsStatus, + (bool enable, uint16_t number_to_store), + (override)); + MOCK_METHOD(void, + SendCombinedRtcpPacket, + (std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets), + (override)); + MOCK_METHOD(int32_t, + SendLossNotification, + (uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed), + (override)); + MOCK_METHOD(void, + SetVideoBitrateAllocation, + (const VideoBitrateAllocation&), + (override)); + MOCK_METHOD(RTPSender*, RtpSender, (), (override)); + MOCK_METHOD(const RTPSender*, RtpSender, (), (const, override)); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_MOCKS_MOCK_RTP_RTCP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/rtp_rtcp_format_gn/moz.build b/third_party/libwebrtc/modules/rtp_rtcp/rtp_rtcp_format_gn/moz.build new file mode 100644 index 0000000000..da69b456d3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/rtp_rtcp_format_gn/moz.build @@ -0,0 +1,274 @@ +# 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" +] + +SOURCES += [ + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.cc" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/rtp_rtcp/include/report_block_data.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc" +] + +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": + + CXXFLAGS += [ + "-mfpu=neon" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +Library("rtp_rtcp_format_gn") diff --git a/third_party/libwebrtc/modules/rtp_rtcp/rtp_rtcp_gn/moz.build b/third_party/libwebrtc/modules/rtp_rtcp/rtp_rtcp_gn/moz.build new file mode 100644 index 0000000000..5683b643c0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/rtp_rtcp_gn/moz.build @@ -0,0 +1,284 @@ +# 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["BWE_TEST_LOGGING_COMPILE_TIME_ENABLE"] = "0" +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" +] + +SOURCES += [ + "/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.cc" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.cc", + "/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc" +] + +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": + + CXXFLAGS += [ + "-mfpu=neon" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +Library("rtp_rtcp_gn") diff --git a/third_party/libwebrtc/modules/rtp_rtcp/rtp_video_header_gn/moz.build b/third_party/libwebrtc/modules/rtp_rtcp/rtp_video_header_gn/moz.build new file mode 100644 index 0000000000..99447dd149 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/rtp_video_header_gn/moz.build @@ -0,0 +1,225 @@ +# 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" +] + +UNIFIED_SOURCES += [ + "/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.cc" +] + +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 + +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 += [ + "winmm" + ] + +if CONFIG["CPU_ARCH"] == "aarch64": + + DEFINES["WEBRTC_ARCH_ARM64"] = True + DEFINES["WEBRTC_HAS_NEON"] = True + +if CONFIG["CPU_ARCH"] == "arm": + + CXXFLAGS += [ + "-mfpu=neon" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + 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": + + CXXFLAGS += [ + "-msse2" + ] + + DEFINES["_GNU_SOURCE"] = True + +if CONFIG["CPU_ARCH"] == "x86_64" and CONFIG["OS_TARGET"] == "Linux": + + DEFINES["_GNU_SOURCE"] = True + +Library("rtp_video_header_gn") diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.cc new file mode 100644 index 0000000000..99fc030aca --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.cc @@ -0,0 +1,125 @@ +/* + * 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/absolute_capture_time_interpolator.h" + +#include <limits> + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +constexpr Timestamp kInvalidLastReceiveTime = Timestamp::MinusInfinity(); +} // namespace + +constexpr TimeDelta AbsoluteCaptureTimeInterpolator::kInterpolationMaxInterval; + +AbsoluteCaptureTimeInterpolator::AbsoluteCaptureTimeInterpolator(Clock* clock) + : clock_(clock), last_receive_time_(kInvalidLastReceiveTime) {} + +uint32_t AbsoluteCaptureTimeInterpolator::GetSource( + uint32_t ssrc, + rtc::ArrayView<const uint32_t> csrcs) { + if (csrcs.empty()) { + return ssrc; + } + + return csrcs[0]; +} + +absl::optional<AbsoluteCaptureTime> +AbsoluteCaptureTimeInterpolator::OnReceivePacket( + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + const absl::optional<AbsoluteCaptureTime>& received_extension) { + const Timestamp receive_time = clock_->CurrentTime(); + + MutexLock lock(&mutex_); + + AbsoluteCaptureTime extension; + if (received_extension == absl::nullopt) { + if (!ShouldInterpolateExtension(receive_time, source, rtp_timestamp, + rtp_clock_frequency)) { + last_receive_time_ = kInvalidLastReceiveTime; + return absl::nullopt; + } + + extension.absolute_capture_timestamp = InterpolateAbsoluteCaptureTimestamp( + rtp_timestamp, rtp_clock_frequency, last_rtp_timestamp_, + last_absolute_capture_timestamp_); + extension.estimated_capture_clock_offset = + last_estimated_capture_clock_offset_; + } else { + last_source_ = source; + last_rtp_timestamp_ = rtp_timestamp; + last_rtp_clock_frequency_ = rtp_clock_frequency; + last_absolute_capture_timestamp_ = + received_extension->absolute_capture_timestamp; + last_estimated_capture_clock_offset_ = + received_extension->estimated_capture_clock_offset; + + last_receive_time_ = receive_time; + + extension = *received_extension; + } + + return extension; +} + +uint64_t AbsoluteCaptureTimeInterpolator::InterpolateAbsoluteCaptureTimestamp( + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + uint32_t last_rtp_timestamp, + uint64_t last_absolute_capture_timestamp) { + RTC_DCHECK_GT(rtp_clock_frequency, 0); + + return last_absolute_capture_timestamp + + static_cast<int64_t>( + rtc::dchecked_cast<uint64_t>(rtp_timestamp - last_rtp_timestamp) + << 32) / + rtp_clock_frequency; +} + +bool AbsoluteCaptureTimeInterpolator::ShouldInterpolateExtension( + Timestamp receive_time, + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency) const { + // Shouldn't if we don't have a previously received extension stored. + if (last_receive_time_ == kInvalidLastReceiveTime) { + return false; + } + + // Shouldn't if the last received extension is too old. + if ((receive_time - last_receive_time_) > kInterpolationMaxInterval) { + return false; + } + + // Shouldn't if the source has changed. + if (last_source_ != source) { + return false; + } + + // Shouldn't if the RTP clock frequency has changed. + if (last_rtp_clock_frequency_ != rtp_clock_frequency) { + return false; + } + + // Shouldn't if the RTP clock frequency is invalid. + if (rtp_clock_frequency <= 0) { + return false; + } + + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.h b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.h new file mode 100644 index 0000000000..f5ec820dd5 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.h @@ -0,0 +1,86 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_ABSOLUTE_CAPTURE_TIME_INTERPOLATOR_H_ +#define MODULES_RTP_RTCP_SOURCE_ABSOLUTE_CAPTURE_TIME_INTERPOLATOR_H_ + +#include "api/array_view.h" +#include "api/rtp_headers.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +// +// Helper class for interpolating the `AbsoluteCaptureTime` header extension. +// +// Supports the "timestamp interpolation" optimization: +// A receiver SHOULD memorize the capture system (i.e. CSRC/SSRC), capture +// timestamp, and RTP timestamp of the most recently received abs-capture-time +// packet on each received stream. It can then use that information, in +// combination with RTP timestamps of packets without abs-capture-time, to +// extrapolate missing capture timestamps. +// +// See: https://webrtc.org/experiments/rtp-hdrext/abs-capture-time/ +// +class AbsoluteCaptureTimeInterpolator { + public: + static constexpr TimeDelta kInterpolationMaxInterval = + TimeDelta::Millis(5000); + + explicit AbsoluteCaptureTimeInterpolator(Clock* clock); + + // Returns the source (i.e. SSRC or CSRC) of the capture system. + static uint32_t GetSource(uint32_t ssrc, + rtc::ArrayView<const uint32_t> csrcs); + + // Returns a received header extension, an interpolated header extension, or + // `absl::nullopt` if it's not possible to interpolate a header extension. + absl::optional<AbsoluteCaptureTime> OnReceivePacket( + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + const absl::optional<AbsoluteCaptureTime>& received_extension); + + private: + friend class AbsoluteCaptureTimeSender; + + static uint64_t InterpolateAbsoluteCaptureTimestamp( + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + uint32_t last_rtp_timestamp, + uint64_t last_absolute_capture_timestamp); + + bool ShouldInterpolateExtension(Timestamp receive_time, + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + Clock* const clock_; + + Mutex mutex_; + + Timestamp last_receive_time_ RTC_GUARDED_BY(mutex_); + + uint32_t last_source_ RTC_GUARDED_BY(mutex_); + uint32_t last_rtp_timestamp_ RTC_GUARDED_BY(mutex_); + uint32_t last_rtp_clock_frequency_ RTC_GUARDED_BY(mutex_); + uint64_t last_absolute_capture_timestamp_ RTC_GUARDED_BY(mutex_); + absl::optional<int64_t> last_estimated_capture_clock_offset_ + RTC_GUARDED_BY(mutex_); +}; // AbsoluteCaptureTimeInterpolator + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_ABSOLUTE_CAPTURE_TIME_INTERPOLATOR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator_unittest.cc new file mode 100644 index 0000000000..6a312f9b43 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator_unittest.cc @@ -0,0 +1,353 @@ +/* + * 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/absolute_capture_time_interpolator.h" + +#include "system_wrappers/include/ntp_time.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(AbsoluteCaptureTimeInterpolatorTest, GetSourceWithoutCsrcs) { + constexpr uint32_t kSsrc = 12; + + EXPECT_EQ(AbsoluteCaptureTimeInterpolator::GetSource(kSsrc, nullptr), kSsrc); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, GetSourceWithCsrcs) { + constexpr uint32_t kSsrc = 12; + constexpr uint32_t kCsrcs[] = {34, 56, 78, 90}; + + EXPECT_EQ(AbsoluteCaptureTimeInterpolator::GetSource(kSsrc, kCsrcs), + kCsrcs[0]); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, ReceiveExtensionReturnsExtension) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9020), absl::nullopt}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp1, + kRtpClockFrequency, kExtension1), + kExtension1); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, + ReceiveNoExtensionReturnsNoExtension) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + absl::nullopt); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp1, + kRtpClockFrequency, kExtension1), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, InterpolateLaterPacketArrivingLater) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + absl::optional<AbsoluteCaptureTime> extension = interpolator.OnReceivePacket( + kSource, kRtpTimestamp1, kRtpClockFrequency, kExtension1); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) + 20); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); + + extension = interpolator.OnReceivePacket(kSource, kRtpTimestamp2, + kRtpClockFrequency, kExtension2); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) + 40); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, + InterpolateEarlierPacketArrivingLater) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 - 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 - 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + absl::optional<AbsoluteCaptureTime> extension = interpolator.OnReceivePacket( + kSource, kRtpTimestamp1, kRtpClockFrequency, kExtension1); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) - 20); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); + + extension = interpolator.OnReceivePacket(kSource, kRtpTimestamp2, + kRtpClockFrequency, kExtension2); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) - 40); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, + InterpolateLaterPacketArrivingLaterWithRtpTimestampWrapAround) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = ~uint32_t{0} - 79; + constexpr uint32_t kRtpTimestamp1 = 1280 - 80; + constexpr uint32_t kRtpTimestamp2 = 2560 - 80; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + absl::optional<AbsoluteCaptureTime> extension = interpolator.OnReceivePacket( + kSource, kRtpTimestamp1, kRtpClockFrequency, kExtension1); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) + 20); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); + + extension = interpolator.OnReceivePacket(kSource, kRtpTimestamp2, + kRtpClockFrequency, kExtension2); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) + 40); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, + InterpolateEarlierPacketArrivingLaterWithRtpTimestampWrapAround) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 799; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 - 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 - 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + absl::optional<AbsoluteCaptureTime> extension = interpolator.OnReceivePacket( + kSource, kRtpTimestamp1, kRtpClockFrequency, kExtension1); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) - 20); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); + + extension = interpolator.OnReceivePacket(kSource, kRtpTimestamp2, + kRtpClockFrequency, kExtension2); + EXPECT_TRUE(extension.has_value()); + EXPECT_EQ(UQ32x32ToInt64Ms(extension->absolute_capture_timestamp), + UQ32x32ToInt64Ms(kExtension0->absolute_capture_timestamp) - 40); + EXPECT_EQ(extension->estimated_capture_clock_offset, + kExtension0->estimated_capture_clock_offset); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, SkipInterpolateIfTooLate) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp1 + 1280; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + clock.AdvanceTime(AbsoluteCaptureTimeInterpolator::kInterpolationMaxInterval); + + EXPECT_TRUE(interpolator + .OnReceivePacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1) + .has_value()); + + clock.AdvanceTimeMilliseconds(1); + + EXPECT_FALSE(interpolator + .OnReceivePacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2) + .has_value()); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, SkipInterpolateIfSourceChanged) { + constexpr uint32_t kSource0 = 1337; + constexpr uint32_t kSource1 = 1338; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource0, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + EXPECT_FALSE(interpolator + .OnReceivePacket(kSource1, kRtpTimestamp1, + kRtpClockFrequency, kExtension1) + .has_value()); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, + SkipInterpolateIfRtpClockFrequencyChanged) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency0 = 64000; + constexpr uint32_t kRtpClockFrequency1 = 32000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 640; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency0, kExtension0), + kExtension0); + + EXPECT_FALSE(interpolator + .OnReceivePacket(kSource, kRtpTimestamp1, + kRtpClockFrequency1, kExtension1) + .has_value()); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, + SkipInterpolateIfRtpClockFrequencyIsInvalid) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 0; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 640; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + EXPECT_FALSE(interpolator + .OnReceivePacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1) + .has_value()); +} + +TEST(AbsoluteCaptureTimeInterpolatorTest, SkipInterpolateIsSticky) { + constexpr uint32_t kSource0 = 1337; + constexpr uint32_t kSource1 = 1338; + constexpr uint32_t kSource2 = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp1 + 1280; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = absl::nullopt; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = absl::nullopt; + + SimulatedClock clock(0); + AbsoluteCaptureTimeInterpolator interpolator(&clock); + + EXPECT_EQ(interpolator.OnReceivePacket(kSource0, kRtpTimestamp0, + kRtpClockFrequency, kExtension0), + kExtension0); + + EXPECT_FALSE(interpolator + .OnReceivePacket(kSource1, kRtpTimestamp1, + kRtpClockFrequency, kExtension1) + .has_value()); + + EXPECT_FALSE(interpolator + .OnReceivePacket(kSource2, kRtpTimestamp2, + kRtpClockFrequency, kExtension2) + .has_value()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.cc new file mode 100644 index 0000000000..28266769ff --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.cc @@ -0,0 +1,124 @@ +/* + * 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/absolute_capture_time_sender.h" + +#include <limits> + +#include "modules/rtp_rtcp/source/absolute_capture_time_interpolator.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace { + +constexpr Timestamp kInvalidLastSendTime = Timestamp::MinusInfinity(); + +} // namespace + +constexpr TimeDelta AbsoluteCaptureTimeSender::kInterpolationMaxInterval; +constexpr TimeDelta AbsoluteCaptureTimeSender::kInterpolationMaxError; + +static_assert( + AbsoluteCaptureTimeInterpolator::kInterpolationMaxInterval >= + AbsoluteCaptureTimeSender::kInterpolationMaxInterval, + "Receivers should be as willing to interpolate timestamps as senders."); + +AbsoluteCaptureTimeSender::AbsoluteCaptureTimeSender(Clock* clock) + : clock_(clock), last_send_time_(kInvalidLastSendTime) {} + +uint32_t AbsoluteCaptureTimeSender::GetSource( + uint32_t ssrc, + rtc::ArrayView<const uint32_t> csrcs) { + return AbsoluteCaptureTimeInterpolator::GetSource(ssrc, csrcs); +} + +absl::optional<AbsoluteCaptureTime> AbsoluteCaptureTimeSender::OnSendPacket( + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + uint64_t absolute_capture_timestamp, + absl::optional<int64_t> estimated_capture_clock_offset) { + const Timestamp send_time = clock_->CurrentTime(); + + MutexLock lock(&mutex_); + + if (!ShouldSendExtension(send_time, source, rtp_timestamp, + rtp_clock_frequency, absolute_capture_timestamp, + estimated_capture_clock_offset)) { + return absl::nullopt; + } + + last_source_ = source; + last_rtp_timestamp_ = rtp_timestamp; + last_rtp_clock_frequency_ = rtp_clock_frequency; + last_absolute_capture_timestamp_ = absolute_capture_timestamp; + last_estimated_capture_clock_offset_ = estimated_capture_clock_offset; + + last_send_time_ = send_time; + + AbsoluteCaptureTime extension; + extension.absolute_capture_timestamp = absolute_capture_timestamp; + extension.estimated_capture_clock_offset = estimated_capture_clock_offset; + return extension; +} + +bool AbsoluteCaptureTimeSender::ShouldSendExtension( + Timestamp send_time, + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + uint64_t absolute_capture_timestamp, + absl::optional<int64_t> estimated_capture_clock_offset) const { + // Should if we've never sent anything before. + if (last_send_time_ == kInvalidLastSendTime) { + return true; + } + + // Should if the last sent extension is too old. + if ((send_time - last_send_time_) > kInterpolationMaxInterval) { + return true; + } + + // Should if the source has changed. + if (last_source_ != source) { + return true; + } + + // Should if the RTP clock frequency has changed. + if (last_rtp_clock_frequency_ != rtp_clock_frequency) { + return true; + } + + // Should if the RTP clock frequency is invalid. + if (rtp_clock_frequency <= 0) { + return true; + } + + // Should if the estimated capture clock offset has changed. + if (last_estimated_capture_clock_offset_ != estimated_capture_clock_offset) { + return true; + } + + // Should if interpolation would introduce too much error. + const uint64_t interpolated_absolute_capture_timestamp = + AbsoluteCaptureTimeInterpolator::InterpolateAbsoluteCaptureTimestamp( + rtp_timestamp, rtp_clock_frequency, last_rtp_timestamp_, + last_absolute_capture_timestamp_); + const int64_t interpolation_error_ms = UQ32x32ToInt64Ms(std::min( + interpolated_absolute_capture_timestamp - absolute_capture_timestamp, + absolute_capture_timestamp - interpolated_absolute_capture_timestamp)); + if (interpolation_error_ms > kInterpolationMaxError.ms()) { + return true; + } + + return false; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.h b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.h new file mode 100644 index 0000000000..be5a77d5e1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.h @@ -0,0 +1,88 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_ABSOLUTE_CAPTURE_TIME_SENDER_H_ +#define MODULES_RTP_RTCP_SOURCE_ABSOLUTE_CAPTURE_TIME_SENDER_H_ + +#include "api/array_view.h" +#include "api/rtp_headers.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +// +// Helper class for sending the `AbsoluteCaptureTime` header extension. +// +// Supports the "timestamp interpolation" optimization: +// A sender SHOULD save bandwidth by not sending abs-capture-time with every +// RTP packet. It SHOULD still send them at regular intervals (e.g. every +// second) to help mitigate the impact of clock drift and packet loss. Mixers +// SHOULD always send abs-capture-time with the first RTP packet after +// changing capture system. +// +// Timestamp interpolation works fine as long as there’s reasonably low +// NTP/RTP clock drift. This is not always true. Senders that detect “jumps” +// between its NTP and RTP clock mappings SHOULD send abs-capture-time with +// the first RTP packet after such a thing happening. +// +// See: https://webrtc.org/experiments/rtp-hdrext/abs-capture-time/ +// +class AbsoluteCaptureTimeSender { + public: + static constexpr TimeDelta kInterpolationMaxInterval = + TimeDelta::Millis(1000); + static constexpr TimeDelta kInterpolationMaxError = TimeDelta::Millis(1); + + explicit AbsoluteCaptureTimeSender(Clock* clock); + + // Returns the source (i.e. SSRC or CSRC) of the capture system. + static uint32_t GetSource(uint32_t ssrc, + rtc::ArrayView<const uint32_t> csrcs); + + // Returns a header extension to be sent, or `absl::nullopt` if the header + // extension shouldn't be sent. + absl::optional<AbsoluteCaptureTime> OnSendPacket( + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + uint64_t absolute_capture_timestamp, + absl::optional<int64_t> estimated_capture_clock_offset); + + private: + bool ShouldSendExtension( + Timestamp send_time, + uint32_t source, + uint32_t rtp_timestamp, + uint32_t rtp_clock_frequency, + uint64_t absolute_capture_timestamp, + absl::optional<int64_t> estimated_capture_clock_offset) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + Clock* const clock_; + + Mutex mutex_; + + Timestamp last_send_time_ RTC_GUARDED_BY(mutex_); + + uint32_t last_source_ RTC_GUARDED_BY(mutex_); + uint32_t last_rtp_timestamp_ RTC_GUARDED_BY(mutex_); + uint32_t last_rtp_clock_frequency_ RTC_GUARDED_BY(mutex_); + uint64_t last_absolute_capture_timestamp_ RTC_GUARDED_BY(mutex_); + absl::optional<int64_t> last_estimated_capture_clock_offset_ + RTC_GUARDED_BY(mutex_); +}; // AbsoluteCaptureTimeSender + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_ABSOLUTE_CAPTURE_TIME_SENDER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender_unittest.cc new file mode 100644 index 0000000000..db3fc75100 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender_unittest.cc @@ -0,0 +1,374 @@ +/* + * 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/absolute_capture_time_sender.h" + +#include "system_wrappers/include/ntp_time.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(AbsoluteCaptureTimeSenderTest, GetSourceWithoutCsrcs) { + constexpr uint32_t kSsrc = 12; + + EXPECT_EQ(AbsoluteCaptureTimeSender::GetSource(kSsrc, nullptr), kSsrc); +} + +TEST(AbsoluteCaptureTimeSenderTest, GetSourceWithCsrcs) { + constexpr uint32_t kSsrc = 12; + constexpr uint32_t kCsrcs[] = {34, 56, 78, 90}; + + EXPECT_EQ(AbsoluteCaptureTimeSender::GetSource(kSsrc, kCsrcs), kCsrcs[0]); +} + +TEST(AbsoluteCaptureTimeSenderTest, InterpolateLaterPacketSentLater) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), Int64MsToQ32x32(-350)}; + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + absl::nullopt); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeSenderTest, InterpolateEarlierPacketSentLater) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 - 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 - 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 - 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 - 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + absl::nullopt); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeSenderTest, + InterpolateLaterPacketSentLaterWithRtpTimestampWrapAround) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = ~uint32_t{0} - 79; + constexpr uint32_t kRtpTimestamp1 = 1280 - 80; + constexpr uint32_t kRtpTimestamp2 = 2560 - 80; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + absl::nullopt); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeSenderTest, + InterpolateEarlierPacketSentLaterWithRtpTimestampWrapAround) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 799; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 - 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 - 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 - 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 - 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + absl::nullopt); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeSenderTest, SkipInterpolateIfTooLate) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + clock.AdvanceTime(AbsoluteCaptureTimeSender::kInterpolationMaxInterval); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + absl::nullopt); + + clock.AdvanceTimeMicroseconds(1); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + kExtension2); +} + +TEST(AbsoluteCaptureTimeSenderTest, SkipInterpolateIfSourceChanged) { + constexpr uint32_t kSource0 = 1337; + constexpr uint32_t kSource1 = 1338; + constexpr uint32_t kSource2 = 1338; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource0, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource1, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + kExtension1); + + EXPECT_EQ(sender.OnSendPacket(kSource2, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeSenderTest, SkipInterpolateIfRtpClockFrequencyChanged) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency0 = 64000; + constexpr uint32_t kRtpClockFrequency1 = 32000; + constexpr uint32_t kRtpClockFrequency2 = 32000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 640; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 1280; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency0, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency1, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + kExtension1); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency2, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeSenderTest, + SkipInterpolateIfRtpClockFrequencyIsInvalid) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency0 = 0; + constexpr uint32_t kRtpClockFrequency1 = 0; + constexpr uint32_t kRtpClockFrequency2 = 0; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency0, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency1, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + kExtension1); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency2, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + kExtension2); +} + +TEST(AbsoluteCaptureTimeSenderTest, + SkipInterpolateIfEstimatedCaptureClockOffsetChanged) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 20), Int64MsToQ32x32(370)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000 + 40), absl::nullopt}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + kExtension1); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + kExtension2); +} + +TEST(AbsoluteCaptureTimeSenderTest, + SkipInterpolateIfTooMuchInterpolationError) { + constexpr uint32_t kSource = 1337; + constexpr uint32_t kRtpClockFrequency = 64000; + constexpr uint32_t kRtpTimestamp0 = 1020300000; + constexpr uint32_t kRtpTimestamp1 = kRtpTimestamp0 + 1280; + constexpr uint32_t kRtpTimestamp2 = kRtpTimestamp0 + 2560; + static const absl::optional<AbsoluteCaptureTime> kExtension0 = + AbsoluteCaptureTime{Int64MsToUQ32x32(9000), Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension1 = + AbsoluteCaptureTime{ + Int64MsToUQ32x32( + 9000 + 20 + + AbsoluteCaptureTimeSender::kInterpolationMaxError.ms()), + Int64MsToQ32x32(-350)}; + static const absl::optional<AbsoluteCaptureTime> kExtension2 = + AbsoluteCaptureTime{ + Int64MsToUQ32x32( + 9000 + 40 + + AbsoluteCaptureTimeSender::kInterpolationMaxError.ms() + 1), + Int64MsToQ32x32(-350)}; + + SimulatedClock clock(0); + AbsoluteCaptureTimeSender sender(&clock); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp0, kRtpClockFrequency, + kExtension0->absolute_capture_timestamp, + kExtension0->estimated_capture_clock_offset), + kExtension0); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp1, kRtpClockFrequency, + kExtension1->absolute_capture_timestamp, + kExtension1->estimated_capture_clock_offset), + absl::nullopt); + + EXPECT_EQ(sender.OnSendPacket(kSource, kRtpTimestamp2, kRtpClockFrequency, + kExtension2->absolute_capture_timestamp, + kExtension2->estimated_capture_clock_offset), + kExtension2); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.cc new file mode 100644 index 0000000000..71e7e8cf78 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.cc @@ -0,0 +1,124 @@ +/* + * 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 "modules/rtp_rtcp/source/active_decode_targets_helper.h" + +#include <stdint.h> + +#include "api/array_view.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +// Returns mask of ids of chains previous frame is part of. +// Assumes for each chain frames are seen in order and no frame on any chain is +// missing. That assumptions allows a simple detection when previous frame is +// part of a chain. +std::bitset<32> LastSendOnChain(int frame_diff, + rtc::ArrayView<const int> chain_diffs) { + std::bitset<32> bitmask = 0; + for (size_t i = 0; i < chain_diffs.size(); ++i) { + if (frame_diff == chain_diffs[i]) { + bitmask.set(i); + } + } + return bitmask; +} + +// Returns bitmask with first `num` bits set to 1. +std::bitset<32> AllActive(size_t num) { + RTC_DCHECK_LE(num, 32); + return (~uint32_t{0}) >> (32 - num); +} + +// Returns bitmask of chains that protect at least one active decode target. +std::bitset<32> ActiveChains( + rtc::ArrayView<const int> decode_target_protected_by_chain, + int num_chains, + std::bitset<32> active_decode_targets) { + std::bitset<32> active_chains = 0; + for (size_t dt = 0; dt < decode_target_protected_by_chain.size(); ++dt) { + if (dt < active_decode_targets.size() && !active_decode_targets[dt]) { + continue; + } + int chain_idx = decode_target_protected_by_chain[dt]; + RTC_DCHECK_LT(chain_idx, num_chains); + active_chains.set(chain_idx); + } + return active_chains; +} + +} // namespace + +void ActiveDecodeTargetsHelper::OnFrame( + rtc::ArrayView<const int> decode_target_protected_by_chain, + std::bitset<32> active_decode_targets, + bool is_keyframe, + int64_t frame_id, + rtc::ArrayView<const int> chain_diffs) { + const int num_chains = chain_diffs.size(); + if (num_chains == 0) { + // Avoid printing the warning + // when already printed the warning for the same active decode targets, or + // when active_decode_targets are not changed from it's default value of + // all are active, including non-existent decode targets. + if (last_active_decode_targets_ != active_decode_targets && + !active_decode_targets.all()) { + RTC_LOG(LS_WARNING) << "No chains are configured, but some decode " + "targets might be inactive. Unsupported."; + } + last_active_decode_targets_ = active_decode_targets; + return; + } + const size_t num_decode_targets = decode_target_protected_by_chain.size(); + RTC_DCHECK_GT(num_decode_targets, 0); + std::bitset<32> all_decode_targets = AllActive(num_decode_targets); + // Default value for active_decode_targets is 'all are active', i.e. all bits + // are set. Default value is set before number of decode targets is known. + // It is up to this helper to make the value cleaner and unset unused bits. + active_decode_targets &= all_decode_targets; + + if (is_keyframe) { + // Key frame resets the state. + last_active_decode_targets_ = all_decode_targets; + last_active_chains_ = AllActive(num_chains); + unsent_on_chain_.reset(); + } else { + // Update state assuming previous frame was sent. + unsent_on_chain_ &= + ~LastSendOnChain(frame_id - last_frame_id_, chain_diffs); + } + // Save for the next call to OnFrame. + // Though usually `frame_id == last_frame_id_ + 1`, it might not be so when + // frame id space is shared by several simulcast rtp streams. + last_frame_id_ = frame_id; + + if (active_decode_targets == last_active_decode_targets_) { + return; + } + last_active_decode_targets_ = active_decode_targets; + + if (active_decode_targets.none()) { + RTC_LOG(LS_ERROR) << "It is invalid to produce a frame (" << frame_id + << ") while there are no active decode targets"; + return; + } + last_active_chains_ = ActiveChains(decode_target_protected_by_chain, + num_chains, active_decode_targets); + // Frames that are part of inactive chains might not be produced by the + // encoder. Thus stop sending `active_decode_target` bitmask when it is sent + // on all active chains rather than on all chains. + unsent_on_chain_ = last_active_chains_; + RTC_DCHECK(!unsent_on_chain_.none()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.h b/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.h new file mode 100644 index 0000000000..13755e8d80 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.h @@ -0,0 +1,63 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_ACTIVE_DECODE_TARGETS_HELPER_H_ +#define MODULES_RTP_RTCP_SOURCE_ACTIVE_DECODE_TARGETS_HELPER_H_ + +#include <stdint.h> + +#include <bitset> + +#include "absl/types/optional.h" +#include "api/array_view.h" + +namespace webrtc { + +// Helper class that decides when active_decode_target_bitmask should be written +// into the dependency descriptor rtp header extension. +// See: https://aomediacodec.github.io/av1-rtp-spec/#a44-switching +// This class is thread-compatible +class ActiveDecodeTargetsHelper { + public: + ActiveDecodeTargetsHelper() = default; + ActiveDecodeTargetsHelper(const ActiveDecodeTargetsHelper&) = delete; + ActiveDecodeTargetsHelper& operator=(const ActiveDecodeTargetsHelper&) = + delete; + ~ActiveDecodeTargetsHelper() = default; + + // Decides if active decode target bitmask should be attached to the frame + // that is about to be sent. + void OnFrame(rtc::ArrayView<const int> decode_target_protected_by_chain, + std::bitset<32> active_decode_targets, + bool is_keyframe, + int64_t frame_id, + rtc::ArrayView<const int> chain_diffs); + + // Returns active decode target to attach to the dependency descriptor. + absl::optional<uint32_t> ActiveDecodeTargetsBitmask() const { + if (unsent_on_chain_.none()) + return absl::nullopt; + return last_active_decode_targets_.to_ulong(); + } + + std::bitset<32> ActiveChainsBitmask() const { return last_active_chains_; } + + private: + // `unsent_on_chain_[i]` indicates last active decode + // target bitmask wasn't attached to a packet on the chain with id `i`. + std::bitset<32> unsent_on_chain_ = 0; + std::bitset<32> last_active_decode_targets_ = 0; + std::bitset<32> last_active_chains_ = 0; + int64_t last_frame_id_ = 0; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_ACTIVE_DECODE_TARGETS_HELPER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper_unittest.cc new file mode 100644 index 0000000000..6f64fd1418 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper_unittest.cc @@ -0,0 +1,272 @@ +/* + * 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 "modules/rtp_rtcp/source/active_decode_targets_helper.h" + +#include <vector> + +#include "absl/types/optional.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +constexpr std::bitset<32> kAll = ~uint32_t{0}; +} // namespace + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsNulloptOnKeyFrameWhenAllDecodeTargetsAreActive) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b11, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs); + + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); +} + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsNulloptOnKeyFrameWhenAllDecodeTargetsAreActiveAfterDeltaFrame) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b11, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs_key); + int chain_diffs_delta[] = {1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/false, /*frame_id=*/2, chain_diffs_delta); + + ASSERT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b01u); + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b11, + /*is_keyframe=*/true, /*frame_id=*/3, chain_diffs_key); + + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); +} + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsBitmaskOnKeyFrameWhenSomeDecodeTargetsAreInactive) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs); + + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b01u); +} + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsBitmaskOnKeyFrameWhenSomeDecodeTargetsAreInactiveAfterDeltaFrame) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs_key); + int chain_diffs_delta[] = {1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/false, /*frame_id=*/2, chain_diffs_delta); + + ASSERT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/true, /*frame_id=*/3, chain_diffs_key); + + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b01u); +} + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsNulloptWhenActiveDecodeTargetsAreUnused) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/kAll, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/kAll, + /*is_keyframe=*/false, /*frame_id=*/2, chain_diffs); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); +} + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsNulloptOnDeltaFrameAfterSentOnKeyFrame) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs_key); + int chain_diffs_delta[] = {1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/false, /*frame_id=*/2, chain_diffs_delta); + + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); +} + +TEST(ActiveDecodeTargetsHelperTest, ReturnsNewBitmaskOnDeltaFrame) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b11, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs_key); + ASSERT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + int chain_diffs_delta[] = {1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/false, /*frame_id=*/2, chain_diffs_delta); + + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b01u); +} + +TEST(ActiveDecodeTargetsHelperTest, + ReturnsBitmaskWhenAllDecodeTargetsReactivatedOnDeltaFrame) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 0}; + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/true, /*frame_id=*/1, chain_diffs_key); + ASSERT_NE(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + int chain_diffs_delta[] = {1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b01, + /*is_keyframe=*/false, /*frame_id=*/2, chain_diffs_delta); + ASSERT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + + // Reactive all the decode targets + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/kAll, + /*is_keyframe=*/false, /*frame_id=*/3, chain_diffs_delta); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b11u); +} + +TEST(ActiveDecodeTargetsHelperTest, ReturnsNulloptAfterSentOnAllActiveChains) { + // Active decode targets (0 and 1) are protected by chains 1 and 2. + const std::bitset<32> kSome = 0b011; + constexpr int kDecodeTargetProtectedByChain[] = {2, 1, 0}; + + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0, 0, 0}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b111, + /*is_keyframe=*/true, + /*frame_id=*/0, chain_diffs_key); + ASSERT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + + int chain_diffs_delta1[] = {1, 1, 1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/kSome, + /*is_keyframe=*/false, + /*frame_id=*/1, chain_diffs_delta1); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b011u); + + int chain_diffs_delta2[] = {2, 2, 1}; // Previous frame was part of chain#2 + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/kSome, + /*is_keyframe=*/false, + /*frame_id=*/2, chain_diffs_delta2); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b011u); + + // active_decode_targets_bitmask was send on chains 1 and 2. It was never sent + // on chain 0, but chain 0 only protects inactive decode target#2 + int chain_diffs_delta3[] = {3, 1, 2}; // Previous frame was part of chain#1 + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/kSome, + /*is_keyframe=*/false, + /*frame_id=*/3, chain_diffs_delta3); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); +} + +TEST(ActiveDecodeTargetsHelperTest, ReturnsBitmaskWhenChanged) { + constexpr int kDecodeTargetProtectedByChain[] = {0, 1, 1}; + + ActiveDecodeTargetsHelper helper; + int chain_diffs_key[] = {0, 0}; + helper.OnFrame(kDecodeTargetProtectedByChain, /*active_decode_targets=*/0b111, + /*is_keyframe=*/true, + /*frame_id=*/0, chain_diffs_key); + int chain_diffs_delta1[] = {1, 1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b011, + /*is_keyframe=*/false, + /*frame_id=*/1, chain_diffs_delta1); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b011u); + + int chain_diffs_delta2[] = {1, 2}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b101, + /*is_keyframe=*/false, + /*frame_id=*/2, chain_diffs_delta2); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b101u); + + // active_decode_target_bitmask was send on chain0, but it was an old one. + int chain_diffs_delta3[] = {2, 1}; + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b101, + /*is_keyframe=*/false, + /*frame_id=*/3, chain_diffs_delta3); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), 0b101u); +} + +TEST(ActiveDecodeTargetsHelperTest, ReturnsNulloptWhenChainsAreNotUsed) { + const rtc::ArrayView<const int> kDecodeTargetProtectedByChain; + const rtc::ArrayView<const int> kNoChainDiffs; + + ActiveDecodeTargetsHelper helper; + helper.OnFrame(kDecodeTargetProtectedByChain, /*active_decode_targets=*/kAll, + /*is_keyframe=*/true, + /*frame_id=*/0, kNoChainDiffs); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + + helper.OnFrame(kDecodeTargetProtectedByChain, + /*active_decode_targets=*/0b101, + /*is_keyframe=*/false, + /*frame_id=*/1, kNoChainDiffs); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); +} + +TEST(ActiveDecodeTargetsHelperTest, Supports32DecodeTargets) { + std::bitset<32> some; + std::vector<int> decode_target_protected_by_chain(32); + for (int i = 0; i < 32; ++i) { + decode_target_protected_by_chain[i] = i; + some[i] = i % 2 == 0; + } + + ActiveDecodeTargetsHelper helper; + std::vector<int> chain_diffs_key(32, 0); + helper.OnFrame(decode_target_protected_by_chain, + /*active_decode_targets=*/some, + /*is_keyframe=*/true, + /*frame_id=*/1, chain_diffs_key); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), some.to_ulong()); + std::vector<int> chain_diffs_delta(32, 1); + helper.OnFrame(decode_target_protected_by_chain, + /*active_decode_targets=*/some, + /*is_keyframe=*/false, + /*frame_id=*/2, chain_diffs_delta); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), absl::nullopt); + helper.OnFrame(decode_target_protected_by_chain, + /*active_decode_targets=*/kAll, + /*is_keyframe=*/false, + /*frame_id=*/2, chain_diffs_delta); + EXPECT_EQ(helper.ActiveDecodeTargetsBitmask(), kAll.to_ulong()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/byte_io.h b/third_party/libwebrtc/modules/rtp_rtcp/source/byte_io.h new file mode 100644 index 0000000000..a98eb3073b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/byte_io.h @@ -0,0 +1,402 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_ +#define MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_ + +// This file contains classes for reading and writing integer types from/to +// byte array representations. Signed/unsigned, partial (whole byte) sizes, +// and big/little endian byte order is all supported. +// +// Usage examples: +// +// uint8_t* buffer = ...; +// +// // Read an unsigned 4 byte integer in big endian format +// uint32_t val = ByteReader<uint32_t>::ReadBigEndian(buffer); +// +// // Read a signed 24-bit (3 byte) integer in little endian format +// int32_t val = ByteReader<int32_t, 3>::ReadLittle(buffer); +// +// // Write an unsigned 8 byte integer in little endian format +// ByteWriter<uint64_t>::WriteLittleEndian(buffer, val); +// +// Write an unsigned 40-bit (5 byte) integer in big endian format +// ByteWriter<uint64_t, 5>::WriteBigEndian(buffer, val); +// +// These classes are implemented as recursive templetizations, inteded to make +// it easy for the compiler to completely inline the reading/writing. + +#include <stdint.h> + +#include <limits> + +namespace webrtc { + +// According to ISO C standard ISO/IEC 9899, section 6.2.6.2 (2), the three +// representations of signed integers allowed are two's complement, one's +// complement and sign/magnitude. We can detect which is used by looking at +// the two last bits of -1, which will be 11 in two's complement, 10 in one's +// complement and 01 in sign/magnitude. +// TODO(sprang): In the unlikely event that we actually need to support a +// platform that doesn't use two's complement, implement conversion to/from +// wire format. + +// Assume the if any one signed integer type is two's complement, then all +// other will be too. +static_assert( + (-1 & 0x03) == 0x03, + "Only two's complement representation of signed integers supported."); + +// Plain const char* won't work for static_assert, use #define instead. +#define kSizeErrorMsg "Byte size must be less than or equal to data type size." + +// Utility class for getting the unsigned equivalent of a signed type. +template <typename T> +struct UnsignedOf; + +// Class for reading integers from a sequence of bytes. +// T = type of integer, B = bytes to read, is_signed = true if signed integer. +// If is_signed is true and B < sizeof(T), sign extension might be needed. +template <typename T, + unsigned int B = sizeof(T), + bool is_signed = std::numeric_limits<T>::is_signed> +class ByteReader; + +// Specialization of ByteReader for unsigned types. +template <typename T, unsigned int B> +class ByteReader<T, B, false> { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + return InternalReadBigEndian(data); + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + return InternalReadLittleEndian(data); + } + + private: + static T InternalReadBigEndian(const uint8_t* data) { + T val(0); + for (unsigned int i = 0; i < B; ++i) + val |= static_cast<T>(data[i]) << ((B - 1 - i) * 8); + return val; + } + + static T InternalReadLittleEndian(const uint8_t* data) { + T val(0); + for (unsigned int i = 0; i < B; ++i) + val |= static_cast<T>(data[i]) << (i * 8); + return val; + } +}; + +// Specialization of ByteReader for signed types. +template <typename T, unsigned int B> +class ByteReader<T, B, true> { + public: + typedef typename UnsignedOf<T>::Type U; + + static T ReadBigEndian(const uint8_t* data) { + U unsigned_val = ByteReader<T, B, false>::ReadBigEndian(data); + if (B < sizeof(T)) + unsigned_val = SignExtend(unsigned_val); + return ReinterpretAsSigned(unsigned_val); + } + + static T ReadLittleEndian(const uint8_t* data) { + U unsigned_val = ByteReader<T, B, false>::ReadLittleEndian(data); + if (B < sizeof(T)) + unsigned_val = SignExtend(unsigned_val); + return ReinterpretAsSigned(unsigned_val); + } + + private: + // As a hack to avoid implementation-specific or undefined behavior when + // bit-shifting or casting signed integers, read as a signed equivalent + // instead and convert to signed. This is safe since we have asserted that + // two's complement for is used. + static T ReinterpretAsSigned(U unsigned_val) { + // An unsigned value with only the highest order bit set (ex 0x80). + const U kUnsignedHighestBitMask = static_cast<U>(1) + << ((sizeof(U) * 8) - 1); + // A signed value with only the highest bit set. Since this is two's + // complement form, we can use the min value from std::numeric_limits. + const T kSignedHighestBitMask = std::numeric_limits<T>::min(); + + T val; + if ((unsigned_val & kUnsignedHighestBitMask) != 0) { + // Casting is only safe when unsigned value can be represented in the + // signed target type, so mask out highest bit and mask it back manually. + val = static_cast<T>(unsigned_val & ~kUnsignedHighestBitMask); + val |= kSignedHighestBitMask; + } else { + val = static_cast<T>(unsigned_val); + } + return val; + } + + // If number of bytes is less than native data type (eg 24 bit, in int32_t), + // and the most significant bit of the actual data is set, we must sign + // extend the remaining byte(s) with ones so that the correct negative + // number is retained. + // Ex: 0x810A0B -> 0xFF810A0B, but 0x710A0B -> 0x00710A0B + static U SignExtend(const U val) { + const uint8_t kMsb = static_cast<uint8_t>(val >> ((B - 1) * 8)); + if ((kMsb & 0x80) != 0) { + // Create a mask where all bits used by the B bytes are set to one, + // for instance 0x00FFFFFF for B = 3. Bit-wise invert that mask (to + // (0xFF000000 in the example above) and add it to the input value. + // The "B % sizeof(T)" is a workaround to undefined values warnings for + // B == sizeof(T), in which case this code won't be called anyway. + const U kUsedBitsMask = (1 << ((B % sizeof(T)) * 8)) - 1; + return ~kUsedBitsMask | val; + } + return val; + } +}; + +// Class for writing integers to a sequence of bytes +// T = type of integer, B = bytes to write +template <typename T, + unsigned int B = sizeof(T), + bool is_signed = std::numeric_limits<T>::is_signed> +class ByteWriter; + +// Specialization of ByteWriter for unsigned types. +template <typename T, unsigned int B> +class ByteWriter<T, B, false> { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + for (unsigned int i = 0; i < B; ++i) { + data[i] = val >> ((B - 1 - i) * 8); + } + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + for (unsigned int i = 0; i < B; ++i) { + data[i] = val >> (i * 8); + } + } +}; + +// Specialization of ByteWriter for signed types. +template <typename T, unsigned int B> +class ByteWriter<T, B, true> { + public: + typedef typename UnsignedOf<T>::Type U; + + static void WriteBigEndian(uint8_t* data, T val) { + ByteWriter<U, B, false>::WriteBigEndian(data, ReinterpretAsUnsigned(val)); + } + + static void WriteLittleEndian(uint8_t* data, T val) { + ByteWriter<U, B, false>::WriteLittleEndian(data, + ReinterpretAsUnsigned(val)); + } + + private: + static U ReinterpretAsUnsigned(T val) { + // According to ISO C standard ISO/IEC 9899, section 6.3.1.3 (1, 2) a + // conversion from signed to unsigned keeps the value if the new type can + // represent it, and otherwise adds one more than the max value of T until + // the value is in range. For two's complement, this fortunately means + // that the bit-wise value will be intact. Thus, since we have asserted that + // two's complement form is actually used, a simple cast is sufficient. + return static_cast<U>(val); + } +}; + +// ----- Below follows specializations of UnsignedOf utility class ----- + +template <> +struct UnsignedOf<int8_t> { + typedef uint8_t Type; +}; +template <> +struct UnsignedOf<int16_t> { + typedef uint16_t Type; +}; +template <> +struct UnsignedOf<int32_t> { + typedef uint32_t Type; +}; +template <> +struct UnsignedOf<int64_t> { + typedef uint64_t Type; +}; + +// ----- Below follows specializations for unsigned, B in { 1, 2, 4, 8 } ----- + +// TODO(sprang): Check if these actually help or if generic cases will be +// unrolled to and optimized to similar performance. + +// Specializations for single bytes +template <typename T> +class ByteReader<T, 1, false> { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + return data[0]; + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + return data[0]; + } +}; + +template <typename T> +class ByteWriter<T, 1, false> { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + data[0] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + data[0] = val; + } +}; + +// Specializations for two byte words +template <typename T> +class ByteReader<T, 2, false> { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + return (data[0] << 8) | data[1]; + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + return data[0] | (data[1] << 8); + } +}; + +template <typename T> +class ByteWriter<T, 2, false> { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + data[0] = val >> 8; + data[1] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + data[0] = val; + data[1] = val >> 8; + } +}; + +// Specializations for four byte words. +template <typename T> +class ByteReader<T, 4, false> { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + return (Get(data, 0) << 24) | (Get(data, 1) << 16) | (Get(data, 2) << 8) | + Get(data, 3); + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) | + (Get(data, 3) << 24); + } + + private: + inline static T Get(const uint8_t* data, unsigned int index) { + return static_cast<T>(data[index]); + } +}; + +// Specializations for four byte words. +template <typename T> +class ByteWriter<T, 4, false> { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + data[0] = val >> 24; + data[1] = val >> 16; + data[2] = val >> 8; + data[3] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + data[0] = val; + data[1] = val >> 8; + data[2] = val >> 16; + data[3] = val >> 24; + } +}; + +// Specializations for eight byte words. +template <typename T> +class ByteReader<T, 8, false> { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + return (Get(data, 0) << 56) | (Get(data, 1) << 48) | (Get(data, 2) << 40) | + (Get(data, 3) << 32) | (Get(data, 4) << 24) | (Get(data, 5) << 16) | + (Get(data, 6) << 8) | Get(data, 7); + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) | + (Get(data, 3) << 24) | (Get(data, 4) << 32) | (Get(data, 5) << 40) | + (Get(data, 6) << 48) | (Get(data, 7) << 56); + } + + private: + inline static T Get(const uint8_t* data, unsigned int index) { + return static_cast<T>(data[index]); + } +}; + +template <typename T> +class ByteWriter<T, 8, false> { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + data[0] = val >> 56; + data[1] = val >> 48; + data[2] = val >> 40; + data[3] = val >> 32; + data[4] = val >> 24; + data[5] = val >> 16; + data[6] = val >> 8; + data[7] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + data[0] = val; + data[1] = val >> 8; + data[2] = val >> 16; + data[3] = val >> 24; + data[4] = val >> 32; + data[5] = val >> 40; + data[6] = val >> 48; + data[7] = val >> 56; + } +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/byte_io_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/byte_io_unittest.cc new file mode 100644 index 0000000000..e4dea813b8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/byte_io_unittest.cc @@ -0,0 +1,270 @@ +/* + * 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 "modules/rtp_rtcp/source/byte_io.h" + +#include <limits> + +#include "test/gtest.h" + +namespace webrtc { +namespace { + +class ByteIoTest : public ::testing::Test { + protected: + ByteIoTest() = default; + ~ByteIoTest() override = default; + + enum { kAlignments = sizeof(uint64_t) - 1 }; + + // Method to create a test value that is not the same when byte reversed. + template <typename T> + T CreateTestValue(bool negative, uint8_t num_bytes) { + // Examples of output: + // T = int32_t, negative = false, num_bytes = 4: 0x00010203 + // T = int32_t, negative = true, num_bytes = 4: 0xFFFEFDFC + // T = int32_t, negative = false, num_bytes = 3: 0x000102 + // * T = int32_t, negative = true, num_bytes = 3: 0xFFFEFD + + T val = 0; + for (uint8_t i = 0; i != num_bytes; ++i) { + val = (val << 8) + (negative ? (0xFF - i) : (i + 1)); + } + + // This loop will create a sign extend mask if num_bytes if necessary. + // For the last example (marked * above), the number needs to be sign + // extended to be a valid int32_t. The sign extend mask is 0xFF000000. + // Comments for each step with this example below. + if (std::numeric_limits<T>::is_signed && negative && + num_bytes < sizeof(T)) { + // Start with mask = 0xFFFFFFFF. + T mask = static_cast<T>(-1); + // Create a temporary for the lowest byte (0x000000FF). + const T neg_byte = static_cast<T>(0xFF); + for (int i = 0; i < num_bytes; ++i) { + // And the inverse of the temporary and the mask: + // 0xFFFFFFFF & 0xFFFFFF00 = 0xFFFFFF00. + // 0xFFFFFF00 & 0xFFFF00FF = 0xFFFF0000. + // 0xFFFF0000 & 0xFF00FFFF = 0xFF000000. + mask &= ~(neg_byte << (i * 8)); + } + // Add the sign extension mask to the actual value. + val |= mask; + } + return val; + } + + // Populate byte buffer with value, in big endian format. + template <typename T> + void PopulateTestData(uint8_t* data, T value, int num_bytes, bool bigendian) { + if (bigendian) { + for (int i = 0; i < num_bytes; ++i) { + data[i] = (value >> ((num_bytes - i - 1) * 8)) & 0xFF; + } + } else { + for (int i = 0; i < num_bytes; ++i) { + data[i] = (value >> (i * 8)) & 0xFF; + } + } + } + + // Test reading big endian numbers. + // Template arguments: Type T, read method RM(buffer), B bytes of data. + template <typename T, T (*RM)(const uint8_t*), int B> + void TestRead(bool big_endian) { + // Test both for values that are positive and negative (if signed) + for (int neg = 0; neg < 2; ++neg) { + bool negative = neg > 0; + + // Write test value to byte buffer, in big endian format. + T test_value = CreateTestValue<T>(negative, B); + uint8_t bytes[B + kAlignments]; + + // Make one test for each alignment. + for (int i = 0; i < kAlignments; ++i) { + PopulateTestData(bytes + i, test_value, B, big_endian); + + // Check that test value is retrieved from buffer when used read method. + EXPECT_EQ(test_value, RM(bytes + i)); + } + } + } + + // Test writing big endian numbers. + // Template arguments: Type T, write method WM(buffer, value), B bytes of data + template <typename T, void (*WM)(uint8_t*, T), int B> + void TestWrite(bool big_endian) { + // Test both for values that are positive and negative (if signed). + for (int neg = 0; neg < 2; ++neg) { + bool negative = neg > 0; + + // Write test value to byte buffer, in big endian format. + T test_value = CreateTestValue<T>(negative, B); + uint8_t expected_bytes[B + kAlignments]; + uint8_t bytes[B + kAlignments]; + + // Make one test for each alignment. + for (int i = 0; i < kAlignments; ++i) { + PopulateTestData(expected_bytes + i, test_value, B, big_endian); + + // Zero initialize buffer and let WM populate it. + memset(bytes, 0, B + kAlignments); + WM(bytes + i, test_value); + + // Check that data produced by WM is big endian as expected. + for (int j = 0; j < B; ++j) { + EXPECT_EQ(expected_bytes[i + j], bytes[i + j]); + } + } + } + } +}; + +TEST_F(ByteIoTest, Test16UBitBigEndian) { + TestRead<uint16_t, ByteReader<uint16_t>::ReadBigEndian, sizeof(uint16_t)>( + true); + TestWrite<uint16_t, ByteWriter<uint16_t>::WriteBigEndian, sizeof(uint16_t)>( + true); +} + +TEST_F(ByteIoTest, Test24UBitBigEndian) { + TestRead<uint32_t, ByteReader<uint32_t, 3>::ReadBigEndian, 3>(true); + TestWrite<uint32_t, ByteWriter<uint32_t, 3>::WriteBigEndian, 3>(true); +} + +TEST_F(ByteIoTest, Test32UBitBigEndian) { + TestRead<uint32_t, ByteReader<uint32_t>::ReadBigEndian, sizeof(uint32_t)>( + true); + TestWrite<uint32_t, ByteWriter<uint32_t>::WriteBigEndian, sizeof(uint32_t)>( + true); +} + +TEST_F(ByteIoTest, Test64UBitBigEndian) { + TestRead<uint64_t, ByteReader<uint64_t>::ReadBigEndian, sizeof(uint64_t)>( + true); + TestWrite<uint64_t, ByteWriter<uint64_t>::WriteBigEndian, sizeof(uint64_t)>( + true); +} + +TEST_F(ByteIoTest, Test16SBitBigEndian) { + TestRead<int16_t, ByteReader<int16_t>::ReadBigEndian, sizeof(int16_t)>(true); + TestWrite<int16_t, ByteWriter<int16_t>::WriteBigEndian, sizeof(int16_t)>( + true); +} + +TEST_F(ByteIoTest, Test24SBitBigEndian) { + TestRead<int32_t, ByteReader<int32_t, 3>::ReadBigEndian, 3>(true); + TestWrite<int32_t, ByteWriter<int32_t, 3>::WriteBigEndian, 3>(true); +} + +TEST_F(ByteIoTest, Test32SBitBigEndian) { + TestRead<int32_t, ByteReader<int32_t>::ReadBigEndian, sizeof(int32_t)>(true); + TestWrite<int32_t, ByteWriter<int32_t>::WriteBigEndian, sizeof(int32_t)>( + true); +} + +TEST_F(ByteIoTest, Test64SBitBigEndian) { + TestRead<int64_t, ByteReader<int64_t>::ReadBigEndian, sizeof(int64_t)>(true); + TestWrite<int64_t, ByteWriter<int64_t>::WriteBigEndian, sizeof(int64_t)>( + true); +} + +TEST_F(ByteIoTest, Test16UBitLittleEndian) { + TestRead<uint16_t, ByteReader<uint16_t>::ReadLittleEndian, sizeof(uint16_t)>( + false); + TestWrite<uint16_t, ByteWriter<uint16_t>::WriteLittleEndian, + sizeof(uint16_t)>(false); +} + +TEST_F(ByteIoTest, Test24UBitLittleEndian) { + TestRead<uint32_t, ByteReader<uint32_t, 3>::ReadLittleEndian, 3>(false); + TestWrite<uint32_t, ByteWriter<uint32_t, 3>::WriteLittleEndian, 3>(false); +} + +TEST_F(ByteIoTest, Test32UBitLittleEndian) { + TestRead<uint32_t, ByteReader<uint32_t>::ReadLittleEndian, sizeof(uint32_t)>( + false); + TestWrite<uint32_t, ByteWriter<uint32_t>::WriteLittleEndian, + sizeof(uint32_t)>(false); +} + +TEST_F(ByteIoTest, Test64UBitLittleEndian) { + TestRead<uint64_t, ByteReader<uint64_t>::ReadLittleEndian, sizeof(uint64_t)>( + false); + TestWrite<uint64_t, ByteWriter<uint64_t>::WriteLittleEndian, + sizeof(uint64_t)>(false); +} + +TEST_F(ByteIoTest, Test16SBitLittleEndian) { + TestRead<int16_t, ByteReader<int16_t>::ReadLittleEndian, sizeof(int16_t)>( + false); + TestWrite<int16_t, ByteWriter<int16_t>::WriteLittleEndian, sizeof(int16_t)>( + false); +} + +TEST_F(ByteIoTest, Test24SBitLittleEndian) { + TestRead<int32_t, ByteReader<int32_t, 3>::ReadLittleEndian, 3>(false); + TestWrite<int32_t, ByteWriter<int32_t, 3>::WriteLittleEndian, 3>(false); +} + +TEST_F(ByteIoTest, Test32SBitLittleEndian) { + TestRead<int32_t, ByteReader<int32_t>::ReadLittleEndian, sizeof(int32_t)>( + false); + TestWrite<int32_t, ByteWriter<int32_t>::WriteLittleEndian, sizeof(int32_t)>( + false); +} + +TEST_F(ByteIoTest, Test64SBitLittleEndian) { + TestRead<int64_t, ByteReader<int64_t>::ReadLittleEndian, sizeof(int64_t)>( + false); + TestWrite<int64_t, ByteWriter<int64_t>::WriteLittleEndian, sizeof(int64_t)>( + false); +} + +// Sets up a fixed byte array and converts N bytes from the array into a +// uint64_t. Verifies the value with hard-coded reference. +TEST(ByteIo, SanityCheckFixedByteArrayUnsignedReadBigEndian) { + uint8_t data[8] = {0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88}; + uint64_t value = ByteReader<uint64_t, 2>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEE), value); + value = ByteReader<uint64_t, 3>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEEDD), value); + value = ByteReader<uint64_t, 4>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEEDDCC), value); + value = ByteReader<uint64_t, 5>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEEDDCCBB), value); + value = ByteReader<uint64_t, 6>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEEDDCCBBAA), value); + value = ByteReader<uint64_t, 7>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEEDDCCBBAA99), value); + value = ByteReader<uint64_t, 8>::ReadBigEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xFFEEDDCCBBAA9988), value); +} + +// Same as above, but for little-endian reading. +TEST(ByteIo, SanityCheckFixedByteArrayUnsignedReadLittleEndian) { + uint8_t data[8] = {0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88}; + uint64_t value = ByteReader<uint64_t, 2>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xEEFF), value); + value = ByteReader<uint64_t, 3>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xDDEEFF), value); + value = ByteReader<uint64_t, 4>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xCCDDEEFF), value); + value = ByteReader<uint64_t, 5>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xBBCCDDEEFF), value); + value = ByteReader<uint64_t, 6>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0xAABBCCDDEEFF), value); + value = ByteReader<uint64_t, 7>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0x99AABBCCDDEEFF), value); + value = ByteReader<uint64_t, 8>::ReadLittleEndian(data); + EXPECT_EQ(static_cast<uint64_t>(0x8899AABBCCDDEEFF), value); +} +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.cc new file mode 100644 index 0000000000..a5b12cb422 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.cc @@ -0,0 +1,33 @@ +/* + * 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 "modules/rtp_rtcp/source/capture_clock_offset_updater.h" + +namespace webrtc { + +absl::optional<int64_t> +CaptureClockOffsetUpdater::AdjustEstimatedCaptureClockOffset( + absl::optional<int64_t> remote_capture_clock_offset) const { + if (remote_capture_clock_offset == absl::nullopt || + remote_to_local_clock_offset_ == absl::nullopt) { + return absl::nullopt; + } + + // Do calculations as "unsigned" to make overflows deterministic. + return static_cast<uint64_t>(*remote_capture_clock_offset) + + static_cast<uint64_t>(*remote_to_local_clock_offset_); +} + +void CaptureClockOffsetUpdater::SetRemoteToLocalClockOffset( + absl::optional<int64_t> offset_q32x32) { + remote_to_local_clock_offset_ = offset_q32x32; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.h b/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.h new file mode 100644 index 0000000000..71d3eb4831 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.h @@ -0,0 +1,51 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_CAPTURE_CLOCK_OFFSET_UPDATER_H_ +#define MODULES_RTP_RTCP_SOURCE_CAPTURE_CLOCK_OFFSET_UPDATER_H_ + +#include <stdint.h> + +#include "absl/types/optional.h" + +namespace webrtc { + +// +// Helper class for calculating the clock offset against the capturer's clock. +// +// This is achieved by adjusting the estimated capture clock offset in received +// Absolute Capture Time RTP header extension (see +// https://webrtc.org/experiments/rtp-hdrext/abs-capture-time/), which +// represents the clock offset between a remote sender and the capturer, by +// adding local-to-remote clock offset. + +class CaptureClockOffsetUpdater { + public: + // Adjusts remote_capture_clock_offset, which originates from Absolute Capture + // Time RTP header extension, to get the local clock offset against the + // capturer's clock. + absl::optional<int64_t> AdjustEstimatedCaptureClockOffset( + absl::optional<int64_t> remote_capture_clock_offset) const; + + // Sets the NTP clock offset between the sender system (which may be different + // from the capture system) and the local system. This information is normally + // provided by passing half the value of the Round-Trip Time estimation given + // by RTCP sender reports (see DLSR/DLRR). + // + // Note that the value must be in Q32.32-formatted fixed-point seconds. + void SetRemoteToLocalClockOffset(absl::optional<int64_t> offset_q32x32); + + private: + absl::optional<int64_t> remote_to_local_clock_offset_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_CAPTURE_CLOCK_OFFSET_UPDATER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater_unittest.cc new file mode 100644 index 0000000000..43e1dd1379 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater_unittest.cc @@ -0,0 +1,58 @@ +/* + * 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 "modules/rtp_rtcp/source/capture_clock_offset_updater.h" + +#include "system_wrappers/include/ntp_time.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(AbsoluteCaptureTimeReceiverTest, + SkipEstimatedCaptureClockOffsetIfRemoteToLocalClockOffsetIsUnknown) { + static const absl::optional<int64_t> kRemoteCaptureClockOffset = + Int64MsToQ32x32(-350); + CaptureClockOffsetUpdater updater; + updater.SetRemoteToLocalClockOffset(absl::nullopt); + EXPECT_EQ( + updater.AdjustEstimatedCaptureClockOffset(kRemoteCaptureClockOffset), + absl::nullopt); +} + +TEST(AbsoluteCaptureTimeReceiverTest, + SkipEstimatedCaptureClockOffsetIfRemoteCaptureClockOffsetIsUnknown) { + static const absl::optional<int64_t> kCaptureClockOffsetNull = absl::nullopt; + CaptureClockOffsetUpdater updater; + updater.SetRemoteToLocalClockOffset(0); + EXPECT_EQ(updater.AdjustEstimatedCaptureClockOffset(kCaptureClockOffsetNull), + kCaptureClockOffsetNull); + + static const absl::optional<int64_t> kRemoteCaptureClockOffset = + Int64MsToQ32x32(-350); + EXPECT_EQ( + updater.AdjustEstimatedCaptureClockOffset(kRemoteCaptureClockOffset), + kRemoteCaptureClockOffset); +} + +TEST(AbsoluteCaptureTimeReceiverTest, EstimatedCaptureClockOffsetArithmetic) { + static const absl::optional<int64_t> kRemoteCaptureClockOffset = + Int64MsToQ32x32(-350); + static const absl::optional<int64_t> kRemoteToLocalClockOffset = + Int64MsToQ32x32(-7000007); + CaptureClockOffsetUpdater updater; + updater.SetRemoteToLocalClockOffset(kRemoteToLocalClockOffset); + EXPECT_THAT( + updater.AdjustEstimatedCaptureClockOffset(kRemoteCaptureClockOffset), + ::testing::Optional(::testing::Eq(*kRemoteCaptureClockOffset + + *kRemoteToLocalClockOffset))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc new file mode 100644 index 0000000000..f1e4eddb4b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc @@ -0,0 +1,43 @@ +/* + * 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/create_video_rtp_depacketizer.h" + +#include <memory> + +#include "api/video/video_codec_type.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" + +namespace webrtc { + +std::unique_ptr<VideoRtpDepacketizer> CreateVideoRtpDepacketizer( + VideoCodecType codec) { + switch (codec) { + case kVideoCodecH264: + return std::make_unique<VideoRtpDepacketizerH264>(); + case kVideoCodecVP8: + return std::make_unique<VideoRtpDepacketizerVp8>(); + case kVideoCodecVP9: + return std::make_unique<VideoRtpDepacketizerVp9>(); + case kVideoCodecAV1: + return std::make_unique<VideoRtpDepacketizerAv1>(); + case kVideoCodecGeneric: + case kVideoCodecMultiplex: + return std::make_unique<VideoRtpDepacketizerGeneric>(); + } + RTC_CHECK_NOTREACHED(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.h b/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.h new file mode 100644 index 0000000000..102cacf598 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_CREATE_VIDEO_RTP_DEPACKETIZER_H_ +#define MODULES_RTP_RTCP_SOURCE_CREATE_VIDEO_RTP_DEPACKETIZER_H_ + +#include <memory> + +#include "api/video/video_codec_type.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" + +namespace webrtc { + +std::unique_ptr<VideoRtpDepacketizer> CreateVideoRtpDepacketizer( + VideoCodecType codec); + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_CREATE_VIDEO_RTP_DEPACKETIZER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.cc new file mode 100644 index 0000000000..2e7e219f94 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.cc @@ -0,0 +1,465 @@ +/* + * 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/deprecated/deprecated_rtp_sender_egress.h" + +#include <limits> +#include <memory> +#include <utility> + +#include "absl/strings/match.h" +#include "api/transport/field_trial_based_config.h" +#include "api/units/timestamp.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { +constexpr uint32_t kTimestampTicksPerMs = 90; +constexpr int kSendSideDelayWindowMs = 1000; +constexpr int kBitrateStatisticsWindowMs = 1000; +constexpr size_t kRtpSequenceNumberMapMaxEntries = 1 << 13; + +} // namespace + +DEPRECATED_RtpSenderEgress::NonPacedPacketSender::NonPacedPacketSender( + DEPRECATED_RtpSenderEgress* sender, + PacketSequencer* sequence_number_assigner) + : transport_sequence_number_(0), + sender_(sender), + sequence_number_assigner_(sequence_number_assigner) { + RTC_DCHECK(sequence_number_assigner_); +} +DEPRECATED_RtpSenderEgress::NonPacedPacketSender::~NonPacedPacketSender() = + default; + +void DEPRECATED_RtpSenderEgress::NonPacedPacketSender::EnqueuePackets( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + for (auto& packet : packets) { + // Assign sequence numbers, but not for flexfec which is already running on + // an internally maintained sequence number series. + if (packet->Ssrc() != sender_->FlexFecSsrc()) { + sequence_number_assigner_->Sequence(*packet); + } + if (!packet->SetExtension<TransportSequenceNumber>( + ++transport_sequence_number_)) { + --transport_sequence_number_; + } + packet->ReserveExtension<TransmissionOffset>(); + packet->ReserveExtension<AbsoluteSendTime>(); + sender_->SendPacket(packet.get(), PacedPacketInfo()); + } +} + +DEPRECATED_RtpSenderEgress::DEPRECATED_RtpSenderEgress( + const RtpRtcpInterface::Configuration& config, + RtpPacketHistory* packet_history) + : ssrc_(config.local_media_ssrc), + rtx_ssrc_(config.rtx_send_ssrc), + flexfec_ssrc_(config.fec_generator ? config.fec_generator->FecSsrc() + : absl::nullopt), + populate_network2_timestamp_(config.populate_network2_timestamp), + clock_(config.clock), + packet_history_(packet_history), + transport_(config.outgoing_transport), + event_log_(config.event_log), + is_audio_(config.audio), + need_rtp_packet_infos_(config.need_rtp_packet_infos), + transport_feedback_observer_(config.transport_feedback_callback), + send_side_delay_observer_(config.send_side_delay_observer), + send_packet_observer_(config.send_packet_observer), + rtp_stats_callback_(config.rtp_stats_callback), + bitrate_callback_(config.send_bitrate_observer), + media_has_been_sent_(false), + force_part_of_allocation_(false), + timestamp_offset_(0), + max_delay_it_(send_delays_.end()), + sum_delays_ms_(0), + send_rates_(kNumMediaTypes, + {kBitrateStatisticsWindowMs, RateStatistics::kBpsScale}), + rtp_sequence_number_map_(need_rtp_packet_infos_ + ? std::make_unique<RtpSequenceNumberMap>( + kRtpSequenceNumberMapMaxEntries) + : nullptr) {} + +void DEPRECATED_RtpSenderEgress::SendPacket( + RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) { + RTC_DCHECK(packet); + + const uint32_t packet_ssrc = packet->Ssrc(); + RTC_DCHECK(packet->packet_type().has_value()); + RTC_DCHECK(HasCorrectSsrc(*packet)); + Timestamp now = clock_->CurrentTime(); + int64_t now_ms = now.ms(); + + if (is_audio_) { +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "AudioTotBitrate_kbps", now_ms, + GetSendRates().Sum().kbps(), packet_ssrc); + BWE_TEST_LOGGING_PLOT_WITH_SSRC( + 1, "AudioNackBitrate_kbps", now_ms, + GetSendRates()[RtpPacketMediaType::kRetransmission].kbps(), + packet_ssrc); +#endif + } else { +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "VideoTotBitrate_kbps", now_ms, + GetSendRates().Sum().kbps(), packet_ssrc); + BWE_TEST_LOGGING_PLOT_WITH_SSRC( + 1, "VideoNackBitrate_kbps", now_ms, + GetSendRates()[RtpPacketMediaType::kRetransmission].kbps(), + packet_ssrc); +#endif + } + + PacketOptions options; + { + MutexLock lock(&lock_); + options.included_in_allocation = force_part_of_allocation_; + + if (need_rtp_packet_infos_ && + packet->packet_type() == RtpPacketToSend::Type::kVideo) { + RTC_DCHECK(rtp_sequence_number_map_); + // Last packet of a frame, add it to sequence number info map. + const uint32_t timestamp = packet->Timestamp() - timestamp_offset_; + bool is_first_packet_of_frame = packet->is_first_packet_of_frame(); + bool is_last_packet_of_frame = packet->Marker(); + + rtp_sequence_number_map_->InsertPacket( + packet->SequenceNumber(), + RtpSequenceNumberMap::Info(timestamp, is_first_packet_of_frame, + is_last_packet_of_frame)); + } + } + + // Bug webrtc:7859. While FEC is invoked from rtp_sender_video, and not after + // the pacer, these modifications of the header below are happening after the + // FEC protection packets are calculated. This will corrupt recovered packets + // at the same place. It's not an issue for extensions, which are present in + // all the packets (their content just may be incorrect on recovered packets). + // In case of VideoTimingExtension, since it's present not in every packet, + // data after rtp header may be corrupted if these packets are protected by + // the FEC. + int64_t diff_ms = now_ms - packet->capture_time().ms(); + if (packet->HasExtension<TransmissionOffset>()) { + packet->SetExtension<TransmissionOffset>(kTimestampTicksPerMs * diff_ms); + } + if (packet->HasExtension<AbsoluteSendTime>()) { + packet->SetExtension<AbsoluteSendTime>(AbsoluteSendTime::To24Bits(now)); + } + + if (packet->HasExtension<VideoTimingExtension>()) { + if (populate_network2_timestamp_) { + packet->set_network2_time(now); + } else { + packet->set_pacer_exit_time(now); + } + } + + const bool is_media = packet->packet_type() == RtpPacketMediaType::kAudio || + packet->packet_type() == RtpPacketMediaType::kVideo; + + // Downstream code actually uses this flag to distinguish between media and + // everything else. + options.is_retransmit = !is_media; + if (auto packet_id = packet->GetExtension<TransportSequenceNumber>()) { + options.packet_id = *packet_id; + options.included_in_feedback = true; + options.included_in_allocation = true; + AddPacketToTransportFeedback(*packet_id, *packet, pacing_info); + } + + options.additional_data = packet->additional_data(); + + if (packet->packet_type() != RtpPacketMediaType::kPadding && + packet->packet_type() != RtpPacketMediaType::kRetransmission) { + UpdateDelayStatistics(packet->capture_time().ms(), now_ms, packet_ssrc); + UpdateOnSendPacket(options.packet_id, packet->capture_time().ms(), + packet_ssrc); + } + + const bool send_success = SendPacketToNetwork(*packet, options, pacing_info); + + // Put packet in retransmission history or update pending status even if + // actual sending fails. + if (is_media && packet->allow_retransmission()) { + packet_history_->PutRtpPacket(std::make_unique<RtpPacketToSend>(*packet), + now); + } else if (packet->retransmitted_sequence_number()) { + packet_history_->MarkPacketAsSent(*packet->retransmitted_sequence_number()); + } + + if (send_success) { + MutexLock lock(&lock_); + UpdateRtpStats(*packet); + media_has_been_sent_ = true; + } +} + +void DEPRECATED_RtpSenderEgress::ProcessBitrateAndNotifyObservers() { + if (!bitrate_callback_) + return; + + MutexLock lock(&lock_); + RtpSendRates send_rates = GetSendRatesLocked(); + bitrate_callback_->Notify( + send_rates.Sum().bps(), + send_rates[RtpPacketMediaType::kRetransmission].bps(), ssrc_); +} + +RtpSendRates DEPRECATED_RtpSenderEgress::GetSendRates() const { + MutexLock lock(&lock_); + return GetSendRatesLocked(); +} + +RtpSendRates DEPRECATED_RtpSenderEgress::GetSendRatesLocked() const { + const int64_t now_ms = clock_->TimeInMilliseconds(); + RtpSendRates current_rates; + for (size_t i = 0; i < kNumMediaTypes; ++i) { + RtpPacketMediaType type = static_cast<RtpPacketMediaType>(i); + current_rates[type] = + DataRate::BitsPerSec(send_rates_[i].Rate(now_ms).value_or(0)); + } + return current_rates; +} + +void DEPRECATED_RtpSenderEgress::GetDataCounters( + StreamDataCounters* rtp_stats, + StreamDataCounters* rtx_stats) const { + MutexLock lock(&lock_); + *rtp_stats = rtp_stats_; + *rtx_stats = rtx_rtp_stats_; +} + +void DEPRECATED_RtpSenderEgress::ForceIncludeSendPacketsInAllocation( + bool part_of_allocation) { + MutexLock lock(&lock_); + force_part_of_allocation_ = part_of_allocation; +} + +bool DEPRECATED_RtpSenderEgress::MediaHasBeenSent() const { + MutexLock lock(&lock_); + return media_has_been_sent_; +} + +void DEPRECATED_RtpSenderEgress::SetMediaHasBeenSent(bool media_sent) { + MutexLock lock(&lock_); + media_has_been_sent_ = media_sent; +} + +void DEPRECATED_RtpSenderEgress::SetTimestampOffset(uint32_t timestamp) { + MutexLock lock(&lock_); + timestamp_offset_ = timestamp; +} + +std::vector<RtpSequenceNumberMap::Info> +DEPRECATED_RtpSenderEgress::GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const { + RTC_DCHECK(!sequence_numbers.empty()); + if (!need_rtp_packet_infos_) { + return std::vector<RtpSequenceNumberMap::Info>(); + } + + std::vector<RtpSequenceNumberMap::Info> results; + results.reserve(sequence_numbers.size()); + + MutexLock lock(&lock_); + for (uint16_t sequence_number : sequence_numbers) { + const auto& info = rtp_sequence_number_map_->Get(sequence_number); + if (!info) { + // The empty vector will be returned. We can delay the clearing + // of the vector until after we exit the critical section. + return std::vector<RtpSequenceNumberMap::Info>(); + } + results.push_back(*info); + } + + return results; +} + +bool DEPRECATED_RtpSenderEgress::HasCorrectSsrc( + const RtpPacketToSend& packet) const { + switch (*packet.packet_type()) { + case RtpPacketMediaType::kAudio: + case RtpPacketMediaType::kVideo: + return packet.Ssrc() == ssrc_; + case RtpPacketMediaType::kRetransmission: + case RtpPacketMediaType::kPadding: + // Both padding and retransmission must be on either the media or the + // RTX stream. + return packet.Ssrc() == rtx_ssrc_ || packet.Ssrc() == ssrc_; + case RtpPacketMediaType::kForwardErrorCorrection: + // FlexFEC is on separate SSRC, ULPFEC uses media SSRC. + return packet.Ssrc() == ssrc_ || packet.Ssrc() == flexfec_ssrc_; + } + return false; +} + +void DEPRECATED_RtpSenderEgress::AddPacketToTransportFeedback( + uint16_t packet_id, + const RtpPacketToSend& packet, + const PacedPacketInfo& pacing_info) { + if (transport_feedback_observer_) { + RtpPacketSendInfo packet_info; + packet_info.media_ssrc = ssrc_; + packet_info.transport_sequence_number = packet_id; + packet_info.rtp_sequence_number = packet.SequenceNumber(); + packet_info.length = packet.size(); + packet_info.pacing_info = pacing_info; + packet_info.packet_type = packet.packet_type(); + transport_feedback_observer_->OnAddPacket(packet_info); + } +} + +void DEPRECATED_RtpSenderEgress::UpdateDelayStatistics(int64_t capture_time_ms, + int64_t now_ms, + uint32_t ssrc) { + if (!send_side_delay_observer_ || capture_time_ms <= 0) + return; + + int avg_delay_ms = 0; + int max_delay_ms = 0; + { + MutexLock lock(&lock_); + // Compute the max and average of the recent capture-to-send delays. + // The time complexity of the current approach depends on the distribution + // of the delay values. This could be done more efficiently. + + // Remove elements older than kSendSideDelayWindowMs. + auto lower_bound = + send_delays_.lower_bound(now_ms - kSendSideDelayWindowMs); + for (auto it = send_delays_.begin(); it != lower_bound; ++it) { + if (max_delay_it_ == it) { + max_delay_it_ = send_delays_.end(); + } + sum_delays_ms_ -= it->second; + } + send_delays_.erase(send_delays_.begin(), lower_bound); + if (max_delay_it_ == send_delays_.end()) { + // Removed the previous max. Need to recompute. + RecomputeMaxSendDelay(); + } + + // Add the new element. + RTC_DCHECK_GE(now_ms, 0); + RTC_DCHECK_LE(now_ms, std::numeric_limits<int64_t>::max() / 2); + RTC_DCHECK_GE(capture_time_ms, 0); + RTC_DCHECK_LE(capture_time_ms, std::numeric_limits<int64_t>::max() / 2); + int64_t diff_ms = now_ms - capture_time_ms; + RTC_DCHECK_GE(diff_ms, static_cast<int64_t>(0)); + RTC_DCHECK_LE(diff_ms, std::numeric_limits<int>::max()); + int new_send_delay = rtc::dchecked_cast<int>(now_ms - capture_time_ms); + SendDelayMap::iterator it; + bool inserted; + std::tie(it, inserted) = + send_delays_.insert(std::make_pair(now_ms, new_send_delay)); + if (!inserted) { + // TODO(terelius): If we have multiple delay measurements during the same + // millisecond then we keep the most recent one. It is not clear that this + // is the right decision, but it preserves an earlier behavior. + int previous_send_delay = it->second; + sum_delays_ms_ -= previous_send_delay; + it->second = new_send_delay; + if (max_delay_it_ == it && new_send_delay < previous_send_delay) { + RecomputeMaxSendDelay(); + } + } + if (max_delay_it_ == send_delays_.end() || + it->second >= max_delay_it_->second) { + max_delay_it_ = it; + } + sum_delays_ms_ += new_send_delay; + + size_t num_delays = send_delays_.size(); + RTC_DCHECK(max_delay_it_ != send_delays_.end()); + max_delay_ms = rtc::dchecked_cast<int>(max_delay_it_->second); + int64_t avg_ms = (sum_delays_ms_ + num_delays / 2) / num_delays; + RTC_DCHECK_GE(avg_ms, static_cast<int64_t>(0)); + RTC_DCHECK_LE(avg_ms, + static_cast<int64_t>(std::numeric_limits<int>::max())); + avg_delay_ms = + rtc::dchecked_cast<int>((sum_delays_ms_ + num_delays / 2) / num_delays); + } + send_side_delay_observer_->SendSideDelayUpdated(avg_delay_ms, max_delay_ms, + ssrc); +} + +void DEPRECATED_RtpSenderEgress::RecomputeMaxSendDelay() { + max_delay_it_ = send_delays_.begin(); + for (auto it = send_delays_.begin(); it != send_delays_.end(); ++it) { + if (it->second >= max_delay_it_->second) { + max_delay_it_ = it; + } + } +} + +void DEPRECATED_RtpSenderEgress::UpdateOnSendPacket(int packet_id, + int64_t capture_time_ms, + uint32_t ssrc) { + if (!send_packet_observer_ || capture_time_ms <= 0 || packet_id == -1) { + return; + } + + send_packet_observer_->OnSendPacket(packet_id, capture_time_ms, ssrc); +} + +bool DEPRECATED_RtpSenderEgress::SendPacketToNetwork( + const RtpPacketToSend& packet, + const PacketOptions& options, + const PacedPacketInfo& pacing_info) { + int bytes_sent = -1; + if (transport_) { + bytes_sent = transport_->SendRtp(packet.data(), packet.size(), options) + ? static_cast<int>(packet.size()) + : -1; + if (event_log_ && bytes_sent > 0) { + event_log_->Log(std::make_unique<RtcEventRtpPacketOutgoing>( + packet, pacing_info.probe_cluster_id)); + } + } + + if (bytes_sent <= 0) { + RTC_LOG(LS_WARNING) << "Transport failed to send packet."; + return false; + } + return true; +} + +void DEPRECATED_RtpSenderEgress::UpdateRtpStats(const RtpPacketToSend& packet) { + int64_t now_ms = clock_->TimeInMilliseconds(); + + StreamDataCounters* counters = + packet.Ssrc() == rtx_ssrc_ ? &rtx_rtp_stats_ : &rtp_stats_; + + if (counters->first_packet_time_ms == -1) { + counters->first_packet_time_ms = now_ms; + } + + if (packet.packet_type() == RtpPacketMediaType::kForwardErrorCorrection) { + counters->fec.AddPacket(packet); + } + + if (packet.packet_type() == RtpPacketMediaType::kRetransmission) { + counters->retransmitted.AddPacket(packet); + } + counters->transmitted.AddPacket(packet); + + RTC_DCHECK(packet.packet_type().has_value()); + send_rates_[static_cast<size_t>(*packet.packet_type())].Update(packet.size(), + now_ms); + + if (rtp_stats_callback_) { + rtp_stats_callback_->DataCountersUpdated(*counters, packet.Ssrc()); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.h b/third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.h new file mode 100644 index 0000000000..609a90d4fe --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_DEPRECATED_DEPRECATED_RTP_SENDER_EGRESS_H_ +#define MODULES_RTP_RTCP_SOURCE_DEPRECATED_DEPRECATED_RTP_SENDER_EGRESS_H_ + +#include <map> +#include <memory> +#include <vector> + +#include "absl/types/optional.h" +#include "api/call/transport.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/units/data_rate.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/packet_sequencer.h" +#include "modules/rtp_rtcp/source/rtp_packet_history.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "modules/rtp_rtcp/source/rtp_sequence_number_map.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class DEPRECATED_RtpSenderEgress { + public: + // Helper class that redirects packets directly to the send part of this class + // without passing through an actual paced sender. + class NonPacedPacketSender : public RtpPacketSender { + public: + NonPacedPacketSender(DEPRECATED_RtpSenderEgress* sender, + PacketSequencer* sequence_number_assigner); + virtual ~NonPacedPacketSender(); + + void EnqueuePackets( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) override; + void RemovePacketsForSsrc(uint32_t ssrc) override {} + + private: + uint16_t transport_sequence_number_; + DEPRECATED_RtpSenderEgress* const sender_; + PacketSequencer* sequence_number_assigner_; + }; + + DEPRECATED_RtpSenderEgress(const RtpRtcpInterface::Configuration& config, + RtpPacketHistory* packet_history); + ~DEPRECATED_RtpSenderEgress() = default; + + void SendPacket(RtpPacketToSend* packet, const PacedPacketInfo& pacing_info) + RTC_LOCKS_EXCLUDED(lock_); + uint32_t Ssrc() const { return ssrc_; } + absl::optional<uint32_t> RtxSsrc() const { return rtx_ssrc_; } + absl::optional<uint32_t> FlexFecSsrc() const { return flexfec_ssrc_; } + + void ProcessBitrateAndNotifyObservers() RTC_LOCKS_EXCLUDED(lock_); + RtpSendRates GetSendRates() const RTC_LOCKS_EXCLUDED(lock_); + void GetDataCounters(StreamDataCounters* rtp_stats, + StreamDataCounters* rtx_stats) const + RTC_LOCKS_EXCLUDED(lock_); + + void ForceIncludeSendPacketsInAllocation(bool part_of_allocation) + RTC_LOCKS_EXCLUDED(lock_); + bool MediaHasBeenSent() const RTC_LOCKS_EXCLUDED(lock_); + void SetMediaHasBeenSent(bool media_sent) RTC_LOCKS_EXCLUDED(lock_); + void SetTimestampOffset(uint32_t timestamp) RTC_LOCKS_EXCLUDED(lock_); + + // For each sequence number in `sequence_number`, recall the last RTP packet + // which bore it - its timestamp and whether it was the first and/or last + // packet in that frame. If all of the given sequence numbers could be + // recalled, return a vector with all of them (in corresponding order). + // If any could not be recalled, return an empty vector. + std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const + RTC_LOCKS_EXCLUDED(lock_); + + private: + // Maps capture time in milliseconds to send-side delay in milliseconds. + // Send-side delay is the difference between transmission time and capture + // time. + typedef std::map<int64_t, int> SendDelayMap; + + RtpSendRates GetSendRatesLocked() const RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + bool HasCorrectSsrc(const RtpPacketToSend& packet) const; + void AddPacketToTransportFeedback(uint16_t packet_id, + const RtpPacketToSend& packet, + const PacedPacketInfo& pacing_info); + void UpdateDelayStatistics(int64_t capture_time_ms, + int64_t now_ms, + uint32_t ssrc); + void RecomputeMaxSendDelay() RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + void UpdateOnSendPacket(int packet_id, + int64_t capture_time_ms, + uint32_t ssrc); + // Sends packet on to `transport_`, leaving the RTP module. + bool SendPacketToNetwork(const RtpPacketToSend& packet, + const PacketOptions& options, + const PacedPacketInfo& pacing_info); + void UpdateRtpStats(const RtpPacketToSend& packet) + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + + const uint32_t ssrc_; + const absl::optional<uint32_t> rtx_ssrc_; + const absl::optional<uint32_t> flexfec_ssrc_; + const bool populate_network2_timestamp_; + Clock* const clock_; + RtpPacketHistory* const packet_history_; + Transport* const transport_; + RtcEventLog* const event_log_; + const bool is_audio_; + const bool need_rtp_packet_infos_; + + TransportFeedbackObserver* const transport_feedback_observer_; + SendSideDelayObserver* const send_side_delay_observer_; + SendPacketObserver* const send_packet_observer_; + StreamDataCountersCallback* const rtp_stats_callback_; + BitrateStatisticsObserver* const bitrate_callback_; + + mutable Mutex lock_; + bool media_has_been_sent_ RTC_GUARDED_BY(lock_); + bool force_part_of_allocation_ RTC_GUARDED_BY(lock_); + uint32_t timestamp_offset_ RTC_GUARDED_BY(lock_); + + SendDelayMap send_delays_ RTC_GUARDED_BY(lock_); + SendDelayMap::const_iterator max_delay_it_ RTC_GUARDED_BY(lock_); + // The sum of delays over a kSendSideDelayWindowMs sliding window. + int64_t sum_delays_ms_ RTC_GUARDED_BY(lock_); + StreamDataCounters rtp_stats_ RTC_GUARDED_BY(lock_); + StreamDataCounters rtx_rtp_stats_ RTC_GUARDED_BY(lock_); + // One element per value in RtpPacketMediaType, with index matching value. + std::vector<RateStatistics> send_rates_ RTC_GUARDED_BY(lock_); + + // Maps sent packets' sequence numbers to a tuple consisting of: + // 1. The timestamp, without the randomizing offset mandated by the RFC. + // 2. Whether the packet was the first in its frame. + // 3. Whether the packet was the last in its frame. + const std::unique_ptr<RtpSequenceNumberMap> rtp_sequence_number_map_ + RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_DEPRECATED_DEPRECATED_RTP_SENDER_EGRESS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.cc new file mode 100644 index 0000000000..df06d2a2f3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.cc @@ -0,0 +1,51 @@ +/* + * 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 "modules/rtp_rtcp/source/dtmf_queue.h" + +#include <stddef.h> + +#include "rtc_base/checks.h" + +namespace { +constexpr size_t kDtmfOutbandMax = 20; +} + +namespace webrtc { +DtmfQueue::DtmfQueue() {} + +DtmfQueue::~DtmfQueue() {} + +bool DtmfQueue::AddDtmf(const Event& event) { + MutexLock lock(&dtmf_mutex_); + if (queue_.size() >= kDtmfOutbandMax) { + return false; + } + queue_.push_back(event); + return true; +} + +bool DtmfQueue::NextDtmf(Event* event) { + RTC_DCHECK(event); + MutexLock lock(&dtmf_mutex_); + if (queue_.empty()) { + return false; + } + + *event = queue_.front(); + queue_.pop_front(); + return true; +} + +bool DtmfQueue::PendingDtmf() const { + MutexLock lock(&dtmf_mutex_); + return !queue_.empty(); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.h b/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.h new file mode 100644 index 0000000000..1d1867fd27 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.h @@ -0,0 +1,43 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_DTMF_QUEUE_H_ +#define MODULES_RTP_RTCP_SOURCE_DTMF_QUEUE_H_ + +#include <stdint.h> + +#include <list> + +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +class DtmfQueue { + public: + struct Event { + uint16_t duration_ms = 0; + uint8_t payload_type = 0; + uint8_t key = 0; + uint8_t level = 0; + }; + + DtmfQueue(); + ~DtmfQueue(); + + bool AddDtmf(const Event& event); + bool NextDtmf(Event* event); + bool PendingDtmf() const; + + private: + mutable Mutex dtmf_mutex_; + std::list<Event> queue_; +}; +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_DTMF_QUEUE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.cc new file mode 100644 index 0000000000..9dbc012368 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.cc @@ -0,0 +1,660 @@ +/* + * 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 "modules/rtp_rtcp/source/fec_private_tables_bursty.h" + +namespace { +// clang-format off +#define kMaskBursty1_1 \ + 0x80, 0x00 + +#define kMaskBursty2_1 \ + 0xc0, 0x00 + +#define kMaskBursty2_2 \ + 0x80, 0x00, \ + 0xc0, 0x00 + +#define kMaskBursty3_1 \ + 0xe0, 0x00 + +#define kMaskBursty3_2 \ + 0xc0, 0x00, \ + 0xa0, 0x00 + +#define kMaskBursty3_3 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00 + +#define kMaskBursty4_1 \ + 0xf0, 0x00 + +#define kMaskBursty4_2 \ + 0xa0, 0x00, \ + 0xd0, 0x00 + +#define kMaskBursty4_3 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x90, 0x00 + +#define kMaskBursty4_4 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00 + +#define kMaskBursty5_1 \ + 0xf8, 0x00 + +#define kMaskBursty5_2 \ + 0xd0, 0x00, \ + 0xa8, 0x00 + +#define kMaskBursty5_3 \ + 0x70, 0x00, \ + 0x90, 0x00, \ + 0xc8, 0x00 + +#define kMaskBursty5_4 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x88, 0x00 + +#define kMaskBursty5_5 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00 + +#define kMaskBursty6_1 \ + 0xfc, 0x00 + +#define kMaskBursty6_2 \ + 0xa8, 0x00, \ + 0xd4, 0x00 + +#define kMaskBursty6_3 \ + 0x94, 0x00, \ + 0xc8, 0x00, \ + 0x64, 0x00 + +#define kMaskBursty6_4 \ + 0x60, 0x00, \ + 0x38, 0x00, \ + 0x88, 0x00, \ + 0xc4, 0x00 + +#define kMaskBursty6_5 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x84, 0x00 + +#define kMaskBursty6_6 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00 + +#define kMaskBursty7_1 \ + 0xfe, 0x00 + +#define kMaskBursty7_2 \ + 0xd4, 0x00, \ + 0xaa, 0x00 + +#define kMaskBursty7_3 \ + 0xc8, 0x00, \ + 0x74, 0x00, \ + 0x92, 0x00 + +#define kMaskBursty7_4 \ + 0x38, 0x00, \ + 0x8a, 0x00, \ + 0xc4, 0x00, \ + 0x62, 0x00 + +#define kMaskBursty7_5 \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x1c, 0x00, \ + 0x84, 0x00, \ + 0xc2, 0x00 + +#define kMaskBursty7_6 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x82, 0x00 + +#define kMaskBursty7_7 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00 + +#define kMaskBursty8_1 \ + 0xff, 0x00 + +#define kMaskBursty8_2 \ + 0xaa, 0x00, \ + 0xd5, 0x00 + +#define kMaskBursty8_3 \ + 0x74, 0x00, \ + 0x92, 0x00, \ + 0xc9, 0x00 + +#define kMaskBursty8_4 \ + 0x8a, 0x00, \ + 0xc5, 0x00, \ + 0x62, 0x00, \ + 0x31, 0x00 + +#define kMaskBursty8_5 \ + 0x30, 0x00, \ + 0x1c, 0x00, \ + 0x85, 0x00, \ + 0xc2, 0x00, \ + 0x61, 0x00 + +#define kMaskBursty8_6 \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0e, 0x00, \ + 0x82, 0x00, \ + 0xc1, 0x00 + +#define kMaskBursty8_7 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x81, 0x00 + +#define kMaskBursty8_8 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00 + +#define kMaskBursty9_1 \ + 0xff, 0x80 + +#define kMaskBursty9_2 \ + 0xd5, 0x00, \ + 0xaa, 0x80 + +#define kMaskBursty9_3 \ + 0x92, 0x00, \ + 0xc9, 0x00, \ + 0x74, 0x80 + +#define kMaskBursty9_4 \ + 0xc5, 0x00, \ + 0x62, 0x00, \ + 0x39, 0x00, \ + 0x8a, 0x80 + +#define kMaskBursty9_5 \ + 0x1c, 0x00, \ + 0x85, 0x00, \ + 0xc2, 0x80, \ + 0x61, 0x00, \ + 0x30, 0x80 + +#define kMaskBursty9_6 \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0e, 0x00, \ + 0x82, 0x80, \ + 0xc1, 0x00, \ + 0x60, 0x80 + +#define kMaskBursty9_7 \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x07, 0x00, \ + 0x81, 0x00, \ + 0xc0, 0x80 + +#define kMaskBursty9_8 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x80, 0x80 + +#define kMaskBursty9_9 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80 + +#define kMaskBursty10_1 \ + 0xff, 0xc0 + +#define kMaskBursty10_2 \ + 0xaa, 0x80, \ + 0xd5, 0x40 + +#define kMaskBursty10_3 \ + 0xc9, 0x00, \ + 0x74, 0x80, \ + 0x92, 0x40 + +#define kMaskBursty10_4 \ + 0x62, 0x00, \ + 0x39, 0x00, \ + 0x8a, 0x80, \ + 0xc5, 0x40 + +#define kMaskBursty10_5 \ + 0x85, 0x00, \ + 0xc2, 0x80, \ + 0x61, 0x40, \ + 0x30, 0x80, \ + 0x18, 0x40 + +#define kMaskBursty10_6 \ + 0x18, 0x00, \ + 0x0e, 0x00, \ + 0x82, 0x80, \ + 0xc1, 0x40, \ + 0x60, 0x80, \ + 0x30, 0x40 + +#define kMaskBursty10_7 \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x07, 0x00, \ + 0x81, 0x40, \ + 0xc0, 0x80, \ + 0x60, 0x40 + +#define kMaskBursty10_8 \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x80, 0x80, \ + 0xc0, 0x40 + +#define kMaskBursty10_9 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x80, 0x40 + +#define kMaskBursty10_10 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x00, 0xc0 + +#define kMaskBursty11_1 \ + 0xff, 0xe0 + +#define kMaskBursty11_2 \ + 0xd5, 0x40, \ + 0xaa, 0xa0 + +#define kMaskBursty11_3 \ + 0x74, 0x80, \ + 0x92, 0x40, \ + 0xc9, 0x20 + +#define kMaskBursty11_4 \ + 0x39, 0x00, \ + 0x8a, 0x80, \ + 0xc5, 0x40, \ + 0x62, 0x20 + +#define kMaskBursty11_5 \ + 0xc2, 0xc0, \ + 0x61, 0x00, \ + 0x30, 0xa0, \ + 0x1c, 0x40, \ + 0x85, 0x20 + +#define kMaskBursty11_6 \ + 0x0e, 0x00, \ + 0x82, 0x80, \ + 0xc1, 0x40, \ + 0x60, 0xa0, \ + 0x30, 0x40, \ + 0x18, 0x20 + +#define kMaskBursty11_7 \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x07, 0x00, \ + 0x81, 0x40, \ + 0xc0, 0xa0, \ + 0x60, 0x40, \ + 0x30, 0x20 + +#define kMaskBursty11_8 \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x40, \ + 0x80, 0xa0, \ + 0xc0, 0x40, \ + 0x60, 0x20 + +#define kMaskBursty11_9 \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x80, 0x40, \ + 0xc0, 0x20 + +#define kMaskBursty11_10 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x00, 0xc0, \ + 0x80, 0x20 + +#define kMaskBursty11_11 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x00, 0xc0, \ + 0x00, 0x60 + +#define kMaskBursty12_1 \ + 0xff, 0xf0 + +#define kMaskBursty12_2 \ + 0xaa, 0xa0, \ + 0xd5, 0x50 + +#define kMaskBursty12_3 \ + 0x92, 0x40, \ + 0xc9, 0x20, \ + 0x74, 0x90 + +#define kMaskBursty12_4 \ + 0x8a, 0x80, \ + 0xc5, 0x40, \ + 0x62, 0x20, \ + 0x39, 0x10 + +#define kMaskBursty12_5 \ + 0x61, 0x00, \ + 0x30, 0xa0, \ + 0x1c, 0x50, \ + 0x85, 0x20, \ + 0xc2, 0x90 + +#define kMaskBursty12_6 \ + 0x82, 0x90, \ + 0xc1, 0x40, \ + 0x60, 0xa0, \ + 0x30, 0x50, \ + 0x18, 0x20, \ + 0x0c, 0x10 + +#define kMaskBursty12_7 \ + 0x0c, 0x00, \ + 0x07, 0x00, \ + 0x81, 0x40, \ + 0xc0, 0xa0, \ + 0x60, 0x50, \ + 0x30, 0x20, \ + 0x18, 0x10 + +#define kMaskBursty12_8 \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x80, 0xa0, \ + 0xc0, 0x50, \ + 0x60, 0x20, \ + 0x30, 0x10 + +#define kMaskBursty12_9 \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x80, 0x50, \ + 0xc0, 0x20, \ + 0x60, 0x10 + +#define kMaskBursty12_10 \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x00, 0xc0, \ + 0x80, 0x20, \ + 0xc0, 0x10 + +#define kMaskBursty12_11 \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x00, 0xc0, \ + 0x00, 0x60, \ + 0x80, 0x10 + +#define kMaskBursty12_12 \ + 0x80, 0x00, \ + 0xc0, 0x00, \ + 0x60, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0x0c, 0x00, \ + 0x06, 0x00, \ + 0x03, 0x00, \ + 0x01, 0x80, \ + 0x00, 0xc0, \ + 0x00, 0x60, \ + 0x00, 0x30 + +#define kPacketMaskBursty1 1, \ + kMaskBursty1_1 + +#define kPacketMaskBursty2 2, \ + kMaskBursty2_1, \ + kMaskBursty2_2 + +#define kPacketMaskBursty3 3, \ + kMaskBursty3_1, \ + kMaskBursty3_2, \ + kMaskBursty3_3 + +#define kPacketMaskBursty4 4, \ + kMaskBursty4_1, \ + kMaskBursty4_2, \ + kMaskBursty4_3, \ + kMaskBursty4_4 + +#define kPacketMaskBursty5 5, \ + kMaskBursty5_1, \ + kMaskBursty5_2, \ + kMaskBursty5_3, \ + kMaskBursty5_4, \ + kMaskBursty5_5 + +#define kPacketMaskBursty6 6, \ + kMaskBursty6_1, \ + kMaskBursty6_2, \ + kMaskBursty6_3, \ + kMaskBursty6_4, \ + kMaskBursty6_5, \ + kMaskBursty6_6 + +#define kPacketMaskBursty7 7, \ + kMaskBursty7_1, \ + kMaskBursty7_2, \ + kMaskBursty7_3, \ + kMaskBursty7_4, \ + kMaskBursty7_5, \ + kMaskBursty7_6, \ + kMaskBursty7_7 + +#define kPacketMaskBursty8 8, \ + kMaskBursty8_1, \ + kMaskBursty8_2, \ + kMaskBursty8_3, \ + kMaskBursty8_4, \ + kMaskBursty8_5, \ + kMaskBursty8_6, \ + kMaskBursty8_7, \ + kMaskBursty8_8 + +#define kPacketMaskBursty9 9, \ + kMaskBursty9_1, \ + kMaskBursty9_2, \ + kMaskBursty9_3, \ + kMaskBursty9_4, \ + kMaskBursty9_5, \ + kMaskBursty9_6, \ + kMaskBursty9_7, \ + kMaskBursty9_8, \ + kMaskBursty9_9 + +#define kPacketMaskBursty10 10, \ + kMaskBursty10_1, \ + kMaskBursty10_2, \ + kMaskBursty10_3, \ + kMaskBursty10_4, \ + kMaskBursty10_5, \ + kMaskBursty10_6, \ + kMaskBursty10_7, \ + kMaskBursty10_8, \ + kMaskBursty10_9, \ + kMaskBursty10_10 + +#define kPacketMaskBursty11 11, \ + kMaskBursty11_1, \ + kMaskBursty11_2, \ + kMaskBursty11_3, \ + kMaskBursty11_4, \ + kMaskBursty11_5, \ + kMaskBursty11_6, \ + kMaskBursty11_7, \ + kMaskBursty11_8, \ + kMaskBursty11_9, \ + kMaskBursty11_10, \ + kMaskBursty11_11 + +#define kPacketMaskBursty12 12, \ + kMaskBursty12_1, \ + kMaskBursty12_2, \ + kMaskBursty12_3, \ + kMaskBursty12_4, \ + kMaskBursty12_5, \ + kMaskBursty12_6, \ + kMaskBursty12_7, \ + kMaskBursty12_8, \ + kMaskBursty12_9, \ + kMaskBursty12_10, \ + kMaskBursty12_11, \ + kMaskBursty12_12 + +// clang-format on +} // namespace + +namespace webrtc { +namespace fec_private_tables { + +const uint8_t kPacketMaskBurstyTbl[] = { + 12, + kPacketMaskBursty1, + kPacketMaskBursty2, + kPacketMaskBursty3, + kPacketMaskBursty4, + kPacketMaskBursty5, + kPacketMaskBursty6, + kPacketMaskBursty7, + kPacketMaskBursty8, + kPacketMaskBursty9, + kPacketMaskBursty10, + kPacketMaskBursty11, + kPacketMaskBursty12, +}; + +} // namespace fec_private_tables +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.h b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.h new file mode 100644 index 0000000000..217d9505e1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.h @@ -0,0 +1,37 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_FEC_PRIVATE_TABLES_BURSTY_H_ +#define MODULES_RTP_RTCP_SOURCE_FEC_PRIVATE_TABLES_BURSTY_H_ + +// This file contains a set of packets masks for the FEC code. The masks in +// this table are specifically designed to favor recovery of bursty/consecutive +// loss network conditions. The tradeoff is worse recovery for random losses. +// These packet masks are currently defined to protect up to 12 media packets. +// They have the following property: for any packet mask defined by the +// parameters (k,m), where k = number of media packets, m = number of FEC +// packets, all "consecutive" losses of size <= m are completely recoverable. +// By consecutive losses we mean consecutive with respect to the sequence +// number ordering of the list (media and FEC) of packets. The difference +// between these masks (`kFecMaskBursty`) and `kFecMaskRandom` type, defined +// in fec_private_tables.h, is more significant for longer codes +// (i.e., more packets/symbols in the code, so larger (k,m), i.e., k > 4, +// m > 3). + +#include <stdint.h> + +namespace webrtc { +namespace fec_private_tables { + +extern const uint8_t kPacketMaskBurstyTbl[]; + +} // namespace fec_private_tables +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_FEC_PRIVATE_TABLES_BURSTY_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc new file mode 100644 index 0000000000..c62f7d5606 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc @@ -0,0 +1,82 @@ +/* + * 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 "modules/rtp_rtcp/source/fec_private_tables_bursty.h" + +#include "modules/rtp_rtcp/source/fec_private_tables_random.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +constexpr uint8_t kMaskRandom15_6[] = {0x82, 0x08, 0x41, 0x04, 0x20, 0x82, + 0x10, 0x40, 0x08, 0x20, 0x04, 0x10}; +} + +namespace fec_private_tables { + +using internal::LookUpInFecTable; + +TEST(FecTable, TestBurstyLookup) { + rtc::ArrayView<const uint8_t> result; + result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 0, 0); + // Should match kMaskBursty1_1. + EXPECT_EQ(2u, result.size()); + EXPECT_EQ(0x80u, result[0]); + + result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 3, 0); + // Should match kMaskBursty4_1. + EXPECT_EQ(2u, result.size()); + EXPECT_EQ(0xf0u, result[0]); + EXPECT_EQ(0x00u, result[1]); + + result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 1, 1); + // Should match kMaskBursty2_2. + EXPECT_EQ(4u, result.size()); + EXPECT_EQ(0x80u, result[0]); + EXPECT_EQ(0xc0u, result[2]); + + result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 11, 11); + // Should match kMaskBursty12_12. + EXPECT_EQ(24u, result.size()); + EXPECT_EQ(0x80u, result[0]); + EXPECT_EQ(0x30u, result[23]); +} + +TEST(FecTable, TestRandomLookup) { + rtc::ArrayView<const uint8_t> result; + result = LookUpInFecTable(&kPacketMaskRandomTbl[0], 0, 0); + EXPECT_EQ(2u, result.size()); + EXPECT_EQ(0x80u, result[0]); + EXPECT_EQ(0x00u, result[1]); + + result = LookUpInFecTable(&kPacketMaskRandomTbl[0], 4, 1); + // kMaskRandom5_2. + EXPECT_EQ(4u, result.size()); + EXPECT_EQ(0xa8u, result[0]); + EXPECT_EQ(0xd0u, result[2]); +} + +TEST(FecTable, TestRandomGenerated) { + FecMaskType fec_mask_type = kFecMaskRandom; + int num_media_packets = 15; + int num_fec_packets = 6; + size_t mask_size = sizeof(kMaskRandom15_6) / sizeof(uint8_t); + internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets); + rtc::ArrayView<const uint8_t> mask = + mask_table.LookUp(num_media_packets, num_fec_packets); + EXPECT_EQ(mask.size(), mask_size); + for (size_t i = 0; i < mask_size; ++i) { + EXPECT_EQ(mask[i], kMaskRandom15_6[i]); + } +} + +} // namespace fec_private_tables +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.cc new file mode 100644 index 0000000000..3cac5db17b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.cc @@ -0,0 +1,660 @@ +/* + * 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 "modules/rtp_rtcp/source/fec_private_tables_random.h" + +namespace { +// clang-format off +#define kMaskRandom1_1 \ + 0x80, 0x00 + +#define kMaskRandom2_1 \ + 0xc0, 0x00 + +#define kMaskRandom2_2 \ + 0xc0, 0x00, \ + 0x80, 0x00 + +#define kMaskRandom3_1 \ + 0xe0, 0x00 + +#define kMaskRandom3_2 \ + 0xc0, 0x00, \ + 0xa0, 0x00 + +#define kMaskRandom3_3 \ + 0xc0, 0x00, \ + 0xa0, 0x00, \ + 0x60, 0x00 + +#define kMaskRandom4_1 \ + 0xf0, 0x00 + +#define kMaskRandom4_2 \ + 0xc0, 0x00, \ + 0xb0, 0x00 + +#define kMaskRandom4_3 \ + 0xc0, 0x00, \ + 0xb0, 0x00, \ + 0x60, 0x00 + +#define kMaskRandom4_4 \ + 0xc0, 0x00, \ + 0xa0, 0x00, \ + 0x30, 0x00, \ + 0x50, 0x00 + +#define kMaskRandom5_1 \ + 0xf8, 0x00 + +#define kMaskRandom5_2 \ + 0xa8, 0x00, \ + 0xd0, 0x00 + +#define kMaskRandom5_3 \ + 0xb0, 0x00, \ + 0xc8, 0x00, \ + 0x50, 0x00 + +#define kMaskRandom5_4 \ + 0xc8, 0x00, \ + 0xb0, 0x00, \ + 0x50, 0x00, \ + 0x28, 0x00 + +#define kMaskRandom5_5 \ + 0xc0, 0x00, \ + 0x30, 0x00, \ + 0x18, 0x00, \ + 0xa0, 0x00, \ + 0x48, 0x00 + +#define kMaskRandom6_1 \ + 0xfc, 0x00 + +#define kMaskRandom6_2 \ + 0xa8, 0x00, \ + 0xd4, 0x00 + +#define kMaskRandom6_3 \ + 0xd0, 0x00, \ + 0x68, 0x00, \ + 0xa4, 0x00 + +#define kMaskRandom6_4 \ + 0xa8, 0x00, \ + 0x58, 0x00, \ + 0x64, 0x00, \ + 0x94, 0x00 + +#define kMaskRandom6_5 \ + 0xa8, 0x00, \ + 0x84, 0x00, \ + 0x64, 0x00, \ + 0x90, 0x00, \ + 0x58, 0x00 + +#define kMaskRandom6_6 \ + 0x98, 0x00, \ + 0x64, 0x00, \ + 0x50, 0x00, \ + 0x14, 0x00, \ + 0xa8, 0x00, \ + 0xe0, 0x00 + +#define kMaskRandom7_1 \ + 0xfe, 0x00 + +#define kMaskRandom7_2 \ + 0xd4, 0x00, \ + 0xaa, 0x00 + +#define kMaskRandom7_3 \ + 0xd0, 0x00, \ + 0xaa, 0x00, \ + 0x64, 0x00 + +#define kMaskRandom7_4 \ + 0xd0, 0x00, \ + 0xaa, 0x00, \ + 0x64, 0x00, \ + 0x1c, 0x00 + +#define kMaskRandom7_5 \ + 0x0c, 0x00, \ + 0xb0, 0x00, \ + 0x1a, 0x00, \ + 0xc4, 0x00, \ + 0x62, 0x00 + +#define kMaskRandom7_6 \ + 0x8c, 0x00, \ + 0x4a, 0x00, \ + 0x64, 0x00, \ + 0xd0, 0x00, \ + 0xa0, 0x00, \ + 0x32, 0x00 + +#define kMaskRandom7_7 \ + 0x4a, 0x00, \ + 0x94, 0x00, \ + 0x1a, 0x00, \ + 0xc4, 0x00, \ + 0x28, 0x00, \ + 0xc2, 0x00, \ + 0x34, 0x00 + +#define kMaskRandom8_1 \ + 0xff, 0x00 + +#define kMaskRandom8_2 \ + 0xaa, 0x00, \ + 0xd5, 0x00 + +#define kMaskRandom8_3 \ + 0xc5, 0x00, \ + 0x92, 0x00, \ + 0x6a, 0x00 + +#define kMaskRandom8_4 \ + 0x45, 0x00, \ + 0xb4, 0x00, \ + 0x6a, 0x00, \ + 0x89, 0x00 + +#define kMaskRandom8_5 \ + 0x8c, 0x00, \ + 0x92, 0x00, \ + 0x2b, 0x00, \ + 0x51, 0x00, \ + 0x64, 0x00 + +#define kMaskRandom8_6 \ + 0xa1, 0x00, \ + 0x52, 0x00, \ + 0x91, 0x00, \ + 0x2a, 0x00, \ + 0xc4, 0x00, \ + 0x4c, 0x00 + +#define kMaskRandom8_7 \ + 0x15, 0x00, \ + 0xc2, 0x00, \ + 0x25, 0x00, \ + 0x62, 0x00, \ + 0x58, 0x00, \ + 0x8c, 0x00, \ + 0xa3, 0x00 + +#define kMaskRandom8_8 \ + 0x25, 0x00, \ + 0x8a, 0x00, \ + 0x91, 0x00, \ + 0x68, 0x00, \ + 0x32, 0x00, \ + 0x43, 0x00, \ + 0xc4, 0x00, \ + 0x1c, 0x00 + +#define kMaskRandom9_1 \ + 0xff, 0x80 + +#define kMaskRandom9_2 \ + 0xaa, 0x80, \ + 0xd5, 0x00 + +#define kMaskRandom9_3 \ + 0xa5, 0x00, \ + 0xc8, 0x00, \ + 0x52, 0x80 + +#define kMaskRandom9_4 \ + 0xa2, 0x00, \ + 0xc9, 0x00, \ + 0x52, 0x80, \ + 0x24, 0x80 + +#define kMaskRandom9_5 \ + 0x8c, 0x00, \ + 0x25, 0x00, \ + 0x92, 0x80, \ + 0x41, 0x80, \ + 0x58, 0x00 + +#define kMaskRandom9_6 \ + 0x84, 0x80, \ + 0x27, 0x00, \ + 0x51, 0x80, \ + 0x1a, 0x00, \ + 0x68, 0x00, \ + 0x89, 0x00 + +#define kMaskRandom9_7 \ + 0x8c, 0x00, \ + 0x47, 0x00, \ + 0x81, 0x80, \ + 0x12, 0x80, \ + 0x58, 0x00, \ + 0x28, 0x80, \ + 0xb4, 0x00 + +#define kMaskRandom9_8 \ + 0x2c, 0x00, \ + 0x91, 0x00, \ + 0x40, 0x80, \ + 0x06, 0x80, \ + 0xc8, 0x00, \ + 0x45, 0x00, \ + 0x30, 0x80, \ + 0xa2, 0x00 + +#define kMaskRandom9_9 \ + 0x4c, 0x00, \ + 0x62, 0x00, \ + 0x91, 0x00, \ + 0x42, 0x80, \ + 0xa4, 0x00, \ + 0x13, 0x00, \ + 0x30, 0x80, \ + 0x88, 0x80, \ + 0x09, 0x00 + +#define kMaskRandom10_1 \ + 0xff, 0xc0 + +#define kMaskRandom10_10 \ + 0x4c, 0x00, \ + 0x51, 0x00, \ + 0xa0, 0x40, \ + 0x04, 0xc0, \ + 0x03, 0x80, \ + 0x86, 0x00, \ + 0x29, 0x00, \ + 0x42, 0x40, \ + 0x98, 0x00, \ + 0x30, 0x80 + +#define kMaskRandom10_2 \ + 0xaa, 0x80, \ + 0xd5, 0x40 + +#define kMaskRandom10_3 \ + 0xa4, 0x40, \ + 0xc9, 0x00, \ + 0x52, 0x80 + +#define kMaskRandom10_4 \ + 0xca, 0x00, \ + 0x32, 0x80, \ + 0xa1, 0x40, \ + 0x55, 0x00 + +#define kMaskRandom10_5 \ + 0xca, 0x00, \ + 0x32, 0x80, \ + 0xa1, 0x40, \ + 0x55, 0x00, \ + 0x08, 0xc0 + +#define kMaskRandom10_6 \ + 0x0e, 0x00, \ + 0x33, 0x00, \ + 0x10, 0xc0, \ + 0x45, 0x40, \ + 0x88, 0x80, \ + 0xe0, 0x00 + +#define kMaskRandom10_7 \ + 0x46, 0x00, \ + 0x33, 0x00, \ + 0x80, 0xc0, \ + 0x0c, 0x40, \ + 0x28, 0x80, \ + 0x94, 0x00, \ + 0xc1, 0x00 + +#define kMaskRandom10_8 \ + 0x2c, 0x00, \ + 0x81, 0x80, \ + 0xa0, 0x40, \ + 0x05, 0x40, \ + 0x18, 0x80, \ + 0xc2, 0x00, \ + 0x22, 0x80, \ + 0x50, 0x40 + +#define kMaskRandom10_9 \ + 0x4c, 0x00, \ + 0x23, 0x00, \ + 0x88, 0xc0, \ + 0x21, 0x40, \ + 0x52, 0x80, \ + 0x94, 0x00, \ + 0x26, 0x00, \ + 0x48, 0x40, \ + 0x91, 0x80 + +#define kMaskRandom11_1 \ + 0xff, 0xe0 + +#define kMaskRandom11_10 \ + 0x64, 0x40, \ + 0x51, 0x40, \ + 0xa9, 0x00, \ + 0x04, 0xc0, \ + 0xd0, 0x00, \ + 0x82, 0x40, \ + 0x21, 0x20, \ + 0x0c, 0x20, \ + 0x4a, 0x00, \ + 0x12, 0xa0 + +#define kMaskRandom11_11 \ + 0x46, 0x40, \ + 0x33, 0x20, \ + 0x99, 0x00, \ + 0x05, 0x80, \ + 0x80, 0xa0, \ + 0x84, 0x40, \ + 0x40, 0x60, \ + 0x0a, 0x80, \ + 0x68, 0x00, \ + 0x10, 0x20, \ + 0x30, 0x40 + +#define kMaskRandom11_2 \ + 0xec, 0xc0, \ + 0x9b, 0xa0 + +#define kMaskRandom11_3 \ + 0xca, 0xc0, \ + 0xf1, 0x40, \ + 0xb6, 0x20 + +#define kMaskRandom11_4 \ + 0xc4, 0xc0, \ + 0x31, 0x60, \ + 0x4b, 0x20, \ + 0x2c, 0xa0 + +#define kMaskRandom11_5 \ + 0x86, 0x80, \ + 0x23, 0x20, \ + 0x16, 0x20, \ + 0x4c, 0x20, \ + 0x41, 0xc0 + +#define kMaskRandom11_6 \ + 0x64, 0x40, \ + 0x51, 0x40, \ + 0x0c, 0xa0, \ + 0xa1, 0x20, \ + 0x12, 0xa0, \ + 0x8a, 0x40 + +#define kMaskRandom11_7 \ + 0x46, 0x40, \ + 0x33, 0x20, \ + 0x91, 0x80, \ + 0xa4, 0x20, \ + 0x50, 0xa0, \ + 0x84, 0xc0, \ + 0x09, 0x60 + +#define kMaskRandom11_8 \ + 0x0c, 0x80, \ + 0x80, 0x60, \ + 0xa0, 0x80, \ + 0x05, 0x40, \ + 0x43, 0x00, \ + 0x1a, 0x00, \ + 0x60, 0x20, \ + 0x14, 0x20 + +#define kMaskRandom11_9 \ + 0x46, 0x40, \ + 0x62, 0x60, \ + 0x8c, 0x00, \ + 0x01, 0x60, \ + 0x07, 0x80, \ + 0xa0, 0x80, \ + 0x18, 0xa0, \ + 0x91, 0x00, \ + 0x78, 0x00 + +#define kMaskRandom12_1 \ + 0xff, 0xf0 + +#define kMaskRandom12_10 \ + 0x51, 0x40, \ + 0x45, 0x10, \ + 0x80, 0xd0, \ + 0x24, 0x20, \ + 0x0a, 0x20, \ + 0x00, 0xe0, \ + 0xb8, 0x00, \ + 0x09, 0x10, \ + 0x56, 0x00, \ + 0xa2, 0x80 + +#define kMaskRandom12_11 \ + 0x53, 0x60, \ + 0x21, 0x30, \ + 0x10, 0x90, \ + 0x00, 0x70, \ + 0x0c, 0x10, \ + 0x40, 0xc0, \ + 0x6a, 0x00, \ + 0x86, 0x00, \ + 0x24, 0x80, \ + 0x89, 0x00, \ + 0xc0, 0x20 + +#define kMaskRandom12_12 \ + 0x10, 0x60, \ + 0x02, 0x30, \ + 0x40, 0x50, \ + 0x21, 0x80, \ + 0x81, 0x10, \ + 0x14, 0x80, \ + 0x98, 0x00, \ + 0x08, 0x90, \ + 0x62, 0x00, \ + 0x24, 0x20, \ + 0x8a, 0x00, \ + 0x84, 0x40 + +#define kMaskRandom12_2 \ + 0xec, 0xc0, \ + 0x93, 0xb0 + +#define kMaskRandom12_3 \ + 0x9b, 0x80, \ + 0x4f, 0x10, \ + 0x3c, 0x60 + +#define kMaskRandom12_4 \ + 0x8b, 0x20, \ + 0x14, 0xb0, \ + 0x22, 0xd0, \ + 0x45, 0x50 + +#define kMaskRandom12_5 \ + 0x53, 0x60, \ + 0x64, 0x20, \ + 0x0c, 0xc0, \ + 0x82, 0xa0, \ + 0x09, 0x30 + +#define kMaskRandom12_6 \ + 0x51, 0x40, \ + 0xc5, 0x10, \ + 0x21, 0x80, \ + 0x12, 0x30, \ + 0x08, 0xe0, \ + 0x2e, 0x00 + +#define kMaskRandom12_7 \ + 0x53, 0x60, \ + 0x21, 0x30, \ + 0x90, 0x90, \ + 0x02, 0x50, \ + 0x06, 0xa0, \ + 0x2c, 0x00, \ + 0x88, 0x60 + +#define kMaskRandom12_8 \ + 0x20, 0x60, \ + 0x80, 0x30, \ + 0x42, 0x40, \ + 0x01, 0x90, \ + 0x14, 0x10, \ + 0x0a, 0x80, \ + 0x38, 0x00, \ + 0xc5, 0x00 + +#define kMaskRandom12_9 \ + 0x53, 0x60, \ + 0xe4, 0x20, \ + 0x24, 0x40, \ + 0xa1, 0x10, \ + 0x18, 0x30, \ + 0x03, 0x90, \ + 0x8a, 0x10, \ + 0x04, 0x90, \ + 0x00, 0xe0 + +#define kPacketMaskRandom1 1, \ + kMaskRandom1_1 + +#define kPacketMaskRandom2 2, \ + kMaskRandom2_1, \ + kMaskRandom2_2 + +#define kPacketMaskRandom3 3, \ + kMaskRandom3_1, \ + kMaskRandom3_2, \ + kMaskRandom3_3 + +#define kPacketMaskRandom4 4, \ + kMaskRandom4_1, \ + kMaskRandom4_2, \ + kMaskRandom4_3, \ + kMaskRandom4_4 + +#define kPacketMaskRandom5 5, \ + kMaskRandom5_1, \ + kMaskRandom5_2, \ + kMaskRandom5_3, \ + kMaskRandom5_4, \ + kMaskRandom5_5 + +#define kPacketMaskRandom6 6, \ + kMaskRandom6_1, \ + kMaskRandom6_2, \ + kMaskRandom6_3, \ + kMaskRandom6_4, \ + kMaskRandom6_5, \ + kMaskRandom6_6 + +#define kPacketMaskRandom7 7, \ + kMaskRandom7_1, \ + kMaskRandom7_2, \ + kMaskRandom7_3, \ + kMaskRandom7_4, \ + kMaskRandom7_5, \ + kMaskRandom7_6, \ + kMaskRandom7_7 + +#define kPacketMaskRandom8 8, \ + kMaskRandom8_1, \ + kMaskRandom8_2, \ + kMaskRandom8_3, \ + kMaskRandom8_4, \ + kMaskRandom8_5, \ + kMaskRandom8_6, \ + kMaskRandom8_7, \ + kMaskRandom8_8 + +#define kPacketMaskRandom9 9, \ + kMaskRandom9_1, \ + kMaskRandom9_2, \ + kMaskRandom9_3, \ + kMaskRandom9_4, \ + kMaskRandom9_5, \ + kMaskRandom9_6, \ + kMaskRandom9_7, \ + kMaskRandom9_8, \ + kMaskRandom9_9 + +#define kPacketMaskRandom10 10, \ + kMaskRandom10_1, \ + kMaskRandom10_2, \ + kMaskRandom10_3, \ + kMaskRandom10_4, \ + kMaskRandom10_5, \ + kMaskRandom10_6, \ + kMaskRandom10_7, \ + kMaskRandom10_8, \ + kMaskRandom10_9, \ + kMaskRandom10_10 + +#define kPacketMaskRandom11 11, \ + kMaskRandom11_1, \ + kMaskRandom11_2, \ + kMaskRandom11_3, \ + kMaskRandom11_4, \ + kMaskRandom11_5, \ + kMaskRandom11_6, \ + kMaskRandom11_7, \ + kMaskRandom11_8, \ + kMaskRandom11_9, \ + kMaskRandom11_10, \ + kMaskRandom11_11 + +#define kPacketMaskRandom12 12, \ + kMaskRandom12_1, \ + kMaskRandom12_2, \ + kMaskRandom12_3, \ + kMaskRandom12_4, \ + kMaskRandom12_5, \ + kMaskRandom12_6, \ + kMaskRandom12_7, \ + kMaskRandom12_8, \ + kMaskRandom12_9, \ + kMaskRandom12_10, \ + kMaskRandom12_11, \ + kMaskRandom12_12 + +// clang-format on +} // namespace + +namespace webrtc { +namespace fec_private_tables { + +const uint8_t kPacketMaskRandomTbl[] = { + 12, + kPacketMaskRandom1, // 2 byte entries. + kPacketMaskRandom2, + kPacketMaskRandom3, + kPacketMaskRandom4, + kPacketMaskRandom5, + kPacketMaskRandom6, + kPacketMaskRandom7, + kPacketMaskRandom8, + kPacketMaskRandom9, + kPacketMaskRandom10, + kPacketMaskRandom11, + kPacketMaskRandom12, +}; + +} // namespace fec_private_tables +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.h b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.h new file mode 100644 index 0000000000..cc7b92984c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.h @@ -0,0 +1,27 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_FEC_PRIVATE_TABLES_RANDOM_H_ +#define MODULES_RTP_RTCP_SOURCE_FEC_PRIVATE_TABLES_RANDOM_H_ + +// This file contains a set of packets masks for the FEC code. The masks in +// this table are specifically designed to favor recovery to random loss. +// These packet masks are defined to protect up to maximum of 48 media packets. + +#include <stdint.h> + +namespace webrtc { +namespace fec_private_tables { + +extern const uint8_t kPacketMaskRandomTbl[]; + +} // namespace fec_private_tables +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_FEC_PRIVATE_TABLES_RANDOM_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.cc new file mode 100644 index 0000000000..23e66c23bf --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.cc @@ -0,0 +1,230 @@ +/* + * 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 "modules/rtp_rtcp/source/fec_test_helper.h" + +#include <memory> +#include <utility> + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { +namespace fec { + +namespace { + +constexpr uint8_t kRtpMarkerBitMask = 0x80; + +constexpr uint8_t kFecPayloadType = 96; +constexpr uint8_t kRedPayloadType = 97; +constexpr uint8_t kVp8PayloadType = 120; + +constexpr int kPacketTimestampIncrement = 3000; +} // namespace + +MediaPacketGenerator::MediaPacketGenerator(uint32_t min_packet_size, + uint32_t max_packet_size, + uint32_t ssrc, + Random* random) + : min_packet_size_(min_packet_size), + max_packet_size_(max_packet_size), + ssrc_(ssrc), + random_(random) {} + +MediaPacketGenerator::~MediaPacketGenerator() = default; + +ForwardErrorCorrection::PacketList MediaPacketGenerator::ConstructMediaPackets( + int num_media_packets, + uint16_t start_seq_num) { + RTC_DCHECK_GT(num_media_packets, 0); + uint16_t seq_num = start_seq_num; + int time_stamp = random_->Rand<int>(); + + ForwardErrorCorrection::PacketList media_packets; + + for (int i = 0; i < num_media_packets; ++i) { + std::unique_ptr<ForwardErrorCorrection::Packet> media_packet( + new ForwardErrorCorrection::Packet()); + media_packet->data.SetSize( + random_->Rand(min_packet_size_, max_packet_size_)); + + uint8_t* data = media_packet->data.MutableData(); + // Generate random values for the first 2 bytes + data[0] = random_->Rand<uint8_t>(); + data[1] = random_->Rand<uint8_t>(); + + // The first two bits are assumed to be 10 by the FEC encoder. + // In fact the FEC decoder will set the two first bits to 10 regardless of + // what they actually were. Set the first two bits to 10 so that a memcmp + // can be performed for the whole restored packet. + data[0] |= 0x80; + data[0] &= 0xbf; + + // FEC is applied to a whole frame. + // A frame is signaled by multiple packets without the marker bit set + // followed by the last packet of the frame for which the marker bit is set. + // Only push one (fake) frame to the FEC. + data[1] &= 0x7f; + + webrtc::ByteWriter<uint16_t>::WriteBigEndian(&data[2], seq_num); + webrtc::ByteWriter<uint32_t>::WriteBigEndian(&data[4], time_stamp); + webrtc::ByteWriter<uint32_t>::WriteBigEndian(&data[8], ssrc_); + + // Generate random values for payload. + for (size_t j = 12; j < media_packet->data.size(); ++j) + data[j] = random_->Rand<uint8_t>(); + seq_num++; + media_packets.push_back(std::move(media_packet)); + } + // Last packet, set marker bit. + ForwardErrorCorrection::Packet* media_packet = media_packets.back().get(); + RTC_DCHECK(media_packet); + media_packet->data.MutableData()[1] |= 0x80; + + next_seq_num_ = seq_num; + + return media_packets; +} + +ForwardErrorCorrection::PacketList MediaPacketGenerator::ConstructMediaPackets( + int num_media_packets) { + return ConstructMediaPackets(num_media_packets, random_->Rand<uint16_t>()); +} + +uint16_t MediaPacketGenerator::GetNextSeqNum() { + return next_seq_num_; +} + +AugmentedPacketGenerator::AugmentedPacketGenerator(uint32_t ssrc) + : num_packets_(0), ssrc_(ssrc), seq_num_(0), timestamp_(0) {} + +void AugmentedPacketGenerator::NewFrame(size_t num_packets) { + num_packets_ = num_packets; + timestamp_ += kPacketTimestampIncrement; +} + +uint16_t AugmentedPacketGenerator::NextPacketSeqNum() { + return ++seq_num_; +} + +std::unique_ptr<AugmentedPacket> AugmentedPacketGenerator::NextPacket( + size_t offset, + size_t length) { + std::unique_ptr<AugmentedPacket> packet(new AugmentedPacket()); + + packet->data.SetSize(length + kRtpHeaderSize); + uint8_t* data = packet->data.MutableData(); + for (size_t i = 0; i < length; ++i) + data[i + kRtpHeaderSize] = offset + i; + packet->data.SetSize(length + kRtpHeaderSize); + packet->header.headerLength = kRtpHeaderSize; + packet->header.markerBit = (num_packets_ == 1); + packet->header.payloadType = kVp8PayloadType; + packet->header.sequenceNumber = seq_num_; + packet->header.timestamp = timestamp_; + packet->header.ssrc = ssrc_; + WriteRtpHeader(packet->header, data); + ++seq_num_; + --num_packets_; + + return packet; +} + +void AugmentedPacketGenerator::WriteRtpHeader(const RTPHeader& header, + uint8_t* data) { + data[0] = 0x80; // Version 2. + data[1] = header.payloadType; + data[1] |= (header.markerBit ? kRtpMarkerBitMask : 0); + ByteWriter<uint16_t>::WriteBigEndian(data + 2, header.sequenceNumber); + ByteWriter<uint32_t>::WriteBigEndian(data + 4, header.timestamp); + ByteWriter<uint32_t>::WriteBigEndian(data + 8, header.ssrc); +} + +FlexfecPacketGenerator::FlexfecPacketGenerator(uint32_t media_ssrc, + uint32_t flexfec_ssrc) + : AugmentedPacketGenerator(media_ssrc), + flexfec_ssrc_(flexfec_ssrc), + flexfec_seq_num_(0), + flexfec_timestamp_(0) {} + +std::unique_ptr<AugmentedPacket> FlexfecPacketGenerator::BuildFlexfecPacket( + const ForwardErrorCorrection::Packet& packet) { + RTC_DCHECK_LE(packet.data.size(), + static_cast<size_t>(IP_PACKET_SIZE - kRtpHeaderSize)); + + RTPHeader header; + header.sequenceNumber = flexfec_seq_num_; + ++flexfec_seq_num_; + header.timestamp = flexfec_timestamp_; + flexfec_timestamp_ += kPacketTimestampIncrement; + header.ssrc = flexfec_ssrc_; + + std::unique_ptr<AugmentedPacket> packet_with_rtp_header( + new AugmentedPacket()); + packet_with_rtp_header->data.SetSize(kRtpHeaderSize + packet.data.size()); + WriteRtpHeader(header, packet_with_rtp_header->data.MutableData()); + memcpy(packet_with_rtp_header->data.MutableData() + kRtpHeaderSize, + packet.data.cdata(), packet.data.size()); + + return packet_with_rtp_header; +} + +UlpfecPacketGenerator::UlpfecPacketGenerator(uint32_t ssrc) + : AugmentedPacketGenerator(ssrc) {} + +RtpPacketReceived UlpfecPacketGenerator::BuildMediaRedPacket( + const AugmentedPacket& packet, + bool is_recovered) { + // Create a temporary buffer used to wrap the media packet in RED. + rtc::CopyOnWriteBuffer red_buffer; + const size_t kHeaderLength = packet.header.headerLength; + // Append header. + red_buffer.SetData(packet.data.data(), kHeaderLength); + // Find payload type and add it as RED header. + uint8_t media_payload_type = red_buffer[1] & 0x7F; + red_buffer.AppendData({media_payload_type}); + // Append rest of payload/padding. + red_buffer.AppendData( + packet.data.Slice(kHeaderLength, packet.data.size() - kHeaderLength)); + + RtpPacketReceived red_packet; + RTC_CHECK(red_packet.Parse(std::move(red_buffer))); + red_packet.SetPayloadType(kRedPayloadType); + red_packet.set_recovered(is_recovered); + + return red_packet; +} + +RtpPacketReceived UlpfecPacketGenerator::BuildUlpfecRedPacket( + const ForwardErrorCorrection::Packet& packet) { + // Create a fake media packet to get a correct header. 1 byte RED header. + ++num_packets_; + std::unique_ptr<AugmentedPacket> fake_packet = + NextPacket(0, packet.data.size() + 1); + + RtpPacketReceived red_packet; + red_packet.Parse(fake_packet->data); + red_packet.SetMarker(false); + uint8_t* rtp_payload = red_packet.AllocatePayload(packet.data.size() + 1); + rtp_payload[0] = kFecPayloadType; + red_packet.SetPayloadType(kRedPayloadType); + memcpy(rtp_payload + 1, packet.data.cdata(), packet.data.size()); + red_packet.set_recovered(false); + + return red_packet; +} + +} // namespace fec +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.h b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.h new file mode 100644 index 0000000000..92e09fd44f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.h @@ -0,0 +1,125 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_FEC_TEST_HELPER_H_ +#define MODULES_RTP_RTCP_SOURCE_FEC_TEST_HELPER_H_ + +#include <memory> + +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/random.h" + +namespace webrtc { +namespace test { +namespace fec { + +struct AugmentedPacket : public ForwardErrorCorrection::Packet { + RTPHeader header; +}; + +// TODO(brandtr): Consider merging MediaPacketGenerator and +// AugmentedPacketGenerator into a single class, since their functionality is +// similar. + +// This class generates media packets corresponding to a single frame. +class MediaPacketGenerator { + public: + MediaPacketGenerator(uint32_t min_packet_size, + uint32_t max_packet_size, + uint32_t ssrc, + Random* random); + ~MediaPacketGenerator(); + + // Construct the media packets, up to `num_media_packets` packets. + ForwardErrorCorrection::PacketList ConstructMediaPackets( + int num_media_packets, + uint16_t start_seq_num); + ForwardErrorCorrection::PacketList ConstructMediaPackets( + int num_media_packets); + + uint16_t GetNextSeqNum(); + + private: + uint32_t min_packet_size_; + uint32_t max_packet_size_; + uint32_t ssrc_; + Random* random_; + + ForwardErrorCorrection::PacketList media_packets_; + uint16_t next_seq_num_; +}; + +// This class generates media packets with a certain structure of the payload. +class AugmentedPacketGenerator { + public: + explicit AugmentedPacketGenerator(uint32_t ssrc); + + // Prepare for generating a new set of packets, corresponding to a frame. + void NewFrame(size_t num_packets); + + // Increment and return the newly incremented sequence number. + uint16_t NextPacketSeqNum(); + + // Return the next packet in the current frame. + std::unique_ptr<AugmentedPacket> NextPacket(size_t offset, size_t length); + + protected: + // Given `header`, writes the appropriate RTP header fields in `data`. + static void WriteRtpHeader(const RTPHeader& header, uint8_t* data); + + // Number of packets left to generate, in the current frame. + size_t num_packets_; + + private: + uint32_t ssrc_; + uint16_t seq_num_; + uint32_t timestamp_; +}; + +// This class generates media and FlexFEC packets for a single frame. +class FlexfecPacketGenerator : public AugmentedPacketGenerator { + public: + FlexfecPacketGenerator(uint32_t media_ssrc, uint32_t flexfec_ssrc); + + // Creates a new AugmentedPacket (with RTP headers) from a + // FlexFEC packet (without RTP headers). + std::unique_ptr<AugmentedPacket> BuildFlexfecPacket( + const ForwardErrorCorrection::Packet& packet); + + private: + uint32_t flexfec_ssrc_; + uint16_t flexfec_seq_num_; + uint32_t flexfec_timestamp_; +}; + +// This class generates media and ULPFEC packets (both encapsulated in RED) +// for a single frame. +class UlpfecPacketGenerator : public AugmentedPacketGenerator { + public: + explicit UlpfecPacketGenerator(uint32_t ssrc); + + // Creates a new RtpPacket with the RED header added to the packet. + static RtpPacketReceived BuildMediaRedPacket(const AugmentedPacket& packet, + bool is_recovered); + + // Creates a new RtpPacket with FEC payload and RED header. Does this by + // creating a new fake media AugmentedPacket, clears the marker bit and adds a + // RED header. Finally replaces the payload with the content of + // `packet->data`. + RtpPacketReceived BuildUlpfecRedPacket( + const ForwardErrorCorrection::Packet& packet); +}; + +} // namespace fec +} // namespace test +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_FEC_TEST_HELPER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.cc new file mode 100644 index 0000000000..2fbb3caae8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.cc @@ -0,0 +1,320 @@ +/* + * 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/rtp_rtcp/source/flexfec_header_reader_writer.h" + +#include <string.h> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +// Maximum number of media packets that can be protected in one batch. +constexpr size_t kMaxMediaPackets = 48; // Since we are reusing ULPFEC masks. + +// Maximum number of media packets tracked by FEC decoder. +// Maintain a sufficiently larger tracking window than `kMaxMediaPackets` +// to account for packet reordering in pacer/ network. +constexpr size_t kMaxTrackedMediaPackets = 4 * kMaxMediaPackets; + +// Maximum number of FEC packets stored inside ForwardErrorCorrection. +constexpr size_t kMaxFecPackets = kMaxMediaPackets; + +// Size (in bytes) of packet masks, given number of K bits set. +constexpr size_t kFlexfecPacketMaskSizes[] = {2, 6, 14}; + +// Size (in bytes) of part of header which is not packet mask specific. +constexpr size_t kBaseHeaderSize = 12; + +// Size (in bytes) of part of header which is stream specific. +constexpr size_t kStreamSpecificHeaderSize = 6; + +// Size (in bytes) of header, given the single stream packet mask size, i.e. +// the number of K-bits set. +constexpr size_t kHeaderSizes[] = { + kBaseHeaderSize + kStreamSpecificHeaderSize + kFlexfecPacketMaskSizes[0], + kBaseHeaderSize + kStreamSpecificHeaderSize + kFlexfecPacketMaskSizes[1], + kBaseHeaderSize + kStreamSpecificHeaderSize + kFlexfecPacketMaskSizes[2]}; + +// We currently only support single-stream protection. +// TODO(brandtr): Update this when we support multistream protection. +constexpr uint8_t kSsrcCount = 1; + +// There are three reserved bytes that MUST be set to zero in the header. +constexpr uint32_t kReservedBits = 0; + +// TODO(brandtr): Update this when we support multistream protection. +constexpr size_t kPacketMaskOffset = + kBaseHeaderSize + kStreamSpecificHeaderSize; + +// Here we count the K-bits as belonging to the packet mask. +// This can be used in conjunction with FlexfecHeaderWriter::MinPacketMaskSize, +// which calculates a bound on the needed packet mask size including K-bits, +// given a packet mask without K-bits. +size_t FlexfecHeaderSize(size_t packet_mask_size) { + RTC_DCHECK_LE(packet_mask_size, kFlexfecPacketMaskSizes[2]); + if (packet_mask_size <= kFlexfecPacketMaskSizes[0]) { + return kHeaderSizes[0]; + } else if (packet_mask_size <= kFlexfecPacketMaskSizes[1]) { + return kHeaderSizes[1]; + } + return kHeaderSizes[2]; +} + +} // namespace + +FlexfecHeaderReader::FlexfecHeaderReader() + : FecHeaderReader(kMaxTrackedMediaPackets, kMaxFecPackets) {} + +FlexfecHeaderReader::~FlexfecHeaderReader() = default; + +// TODO(brandtr): Update this function when we support flexible masks, +// retransmissions, and/or several protected SSRCs. +bool FlexfecHeaderReader::ReadFecHeader( + ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const { + if (fec_packet->pkt->data.size() <= + kBaseHeaderSize + kStreamSpecificHeaderSize) { + RTC_LOG(LS_WARNING) << "Discarding truncated FlexFEC packet."; + return false; + } + uint8_t* const data = fec_packet->pkt->data.MutableData(); + bool r_bit = (data[0] & 0x80) != 0; + if (r_bit) { + RTC_LOG(LS_INFO) + << "FlexFEC packet with retransmission bit set. We do not yet " + "support this, thus discarding the packet."; + return false; + } + bool f_bit = (data[0] & 0x40) != 0; + if (f_bit) { + RTC_LOG(LS_INFO) + << "FlexFEC packet with inflexible generator matrix. We do " + "not yet support this, thus discarding packet."; + return false; + } + uint8_t ssrc_count = ByteReader<uint8_t>::ReadBigEndian(&data[8]); + if (ssrc_count != 1) { + RTC_LOG(LS_INFO) + << "FlexFEC packet protecting multiple media SSRCs. We do not " + "yet support this, thus discarding packet."; + return false; + } + uint32_t protected_ssrc = ByteReader<uint32_t>::ReadBigEndian(&data[12]); + uint16_t seq_num_base = ByteReader<uint16_t>::ReadBigEndian(&data[16]); + + // Parse the FlexFEC packet mask and remove the interleaved K-bits. + // (See FEC header schematic in flexfec_header_reader_writer.h.) + // We store the packed packet mask in-band, which "destroys" the standards + // compliance of the header. That is fine though, since the code that + // reads from the header (from this point and onwards) is aware of this. + // TODO(brandtr): When the FEC packet classes have been refactored, store + // the packed packet masks out-of-band, thus leaving the FlexFEC header as is. + // + // We treat the mask parts as unsigned integers with host order endianness + // in order to simplify the bit shifting between bytes. + if (fec_packet->pkt->data.size() < kHeaderSizes[0]) { + RTC_LOG(LS_WARNING) << "Discarding truncated FlexFEC packet."; + return false; + } + uint8_t* const packet_mask = data + kPacketMaskOffset; + bool k_bit0 = (packet_mask[0] & 0x80) != 0; + uint16_t mask_part0 = ByteReader<uint16_t>::ReadBigEndian(&packet_mask[0]); + // Shift away K-bit 0, implicitly clearing the last bit. + mask_part0 <<= 1; + ByteWriter<uint16_t>::WriteBigEndian(&packet_mask[0], mask_part0); + size_t packet_mask_size; + if (k_bit0) { + // The first K-bit is set, and the packet mask is thus only 2 bytes long. + // We have now read the entire FEC header, and the rest of the packet + // is payload. + packet_mask_size = kFlexfecPacketMaskSizes[0]; + } else { + if (fec_packet->pkt->data.size() < kHeaderSizes[1]) { + return false; + } + bool k_bit1 = (packet_mask[2] & 0x80) != 0; + // We have already shifted the first two bytes of the packet mask one step + // to the left, thus removing K-bit 0. We will now shift the next four bytes + // of the packet mask two steps to the left. (One step for the removed + // K-bit 0, and one step for the to be removed K-bit 1). + uint8_t bit15 = (packet_mask[2] >> 6) & 0x01; + packet_mask[1] |= bit15; + uint32_t mask_part1 = ByteReader<uint32_t>::ReadBigEndian(&packet_mask[2]); + // Shift away K-bit 1 and bit 15, implicitly clearing the last two bits. + mask_part1 <<= 2; + ByteWriter<uint32_t>::WriteBigEndian(&packet_mask[2], mask_part1); + if (k_bit1) { + // The first K-bit is clear, but the second K-bit is set. The packet + // mask is thus 6 bytes long. We have now read the entire FEC header, + // and the rest of the packet is payload. + packet_mask_size = kFlexfecPacketMaskSizes[1]; + } else { + if (fec_packet->pkt->data.size() < kHeaderSizes[2]) { + RTC_LOG(LS_WARNING) << "Discarding truncated FlexFEC packet."; + return false; + } + bool k_bit2 = (packet_mask[6] & 0x80) != 0; + if (k_bit2) { + // The first and second K-bits are clear, but the third K-bit is set. + // The packet mask is thus 14 bytes long. We have now read the entire + // FEC header, and the rest of the packet is payload. + packet_mask_size = kFlexfecPacketMaskSizes[2]; + } else { + RTC_LOG(LS_WARNING) + << "Discarding FlexFEC packet with malformed header."; + return false; + } + // At this point, K-bits 0 and 1 have been removed, and the front-most + // part of the FlexFEC packet mask has been packed accordingly. We will + // now shift the remaning part of the packet mask three steps to the left. + // This corresponds to the (in total) three K-bits, which have been + // removed. + uint8_t tail_bits = (packet_mask[6] >> 5) & 0x03; + packet_mask[5] |= tail_bits; + uint64_t mask_part2 = + ByteReader<uint64_t>::ReadBigEndian(&packet_mask[6]); + // Shift away K-bit 2, bit 46, and bit 47, implicitly clearing the last + // three bits. + mask_part2 <<= 3; + ByteWriter<uint64_t>::WriteBigEndian(&packet_mask[6], mask_part2); + } + } + + // Store "ULPFECized" packet mask info. + fec_packet->fec_header_size = FlexfecHeaderSize(packet_mask_size); + fec_packet->protected_ssrc = protected_ssrc; + fec_packet->seq_num_base = seq_num_base; + fec_packet->packet_mask_offset = kPacketMaskOffset; + fec_packet->packet_mask_size = packet_mask_size; + + // In FlexFEC, all media packets are protected in their entirety. + fec_packet->protection_length = + fec_packet->pkt->data.size() - fec_packet->fec_header_size; + + return true; +} + +FlexfecHeaderWriter::FlexfecHeaderWriter() + : FecHeaderWriter(kMaxMediaPackets, kMaxFecPackets, kHeaderSizes[2]) {} + +FlexfecHeaderWriter::~FlexfecHeaderWriter() = default; + +size_t FlexfecHeaderWriter::MinPacketMaskSize(const uint8_t* packet_mask, + size_t packet_mask_size) const { + if (packet_mask_size == kUlpfecPacketMaskSizeLBitClear && + (packet_mask[1] & 0x01) == 0) { + // Packet mask is 16 bits long, with bit 15 clear. + // It can be used as is. + return kFlexfecPacketMaskSizes[0]; + } else if (packet_mask_size == kUlpfecPacketMaskSizeLBitClear) { + // Packet mask is 16 bits long, with bit 15 set. + // We must expand the packet mask with zeros in the FlexFEC header. + return kFlexfecPacketMaskSizes[1]; + } else if (packet_mask_size == kUlpfecPacketMaskSizeLBitSet && + (packet_mask[5] & 0x03) == 0) { + // Packet mask is 48 bits long, with bits 46 and 47 clear. + // It can be used as is. + return kFlexfecPacketMaskSizes[1]; + } else if (packet_mask_size == kUlpfecPacketMaskSizeLBitSet) { + // Packet mask is 48 bits long, with at least one of bits 46 and 47 set. + // We must expand it with zeros. + return kFlexfecPacketMaskSizes[2]; + } + RTC_DCHECK_NOTREACHED() << "Incorrect packet mask size: " << packet_mask_size + << "."; + return kFlexfecPacketMaskSizes[2]; +} + +size_t FlexfecHeaderWriter::FecHeaderSize(size_t packet_mask_size) const { + return FlexfecHeaderSize(packet_mask_size); +} + +// This function adapts the precomputed ULPFEC packet masks to the +// FlexFEC header standard. Note that the header size is computed by +// FecHeaderSize(), so in this function we can be sure that we are +// writing in space that is intended for the header. +// +// TODO(brandtr): Update this function when we support offset-based masks, +// retransmissions, and protecting multiple SSRCs. +void FlexfecHeaderWriter::FinalizeFecHeader( + uint32_t media_ssrc, + uint16_t seq_num_base, + const uint8_t* packet_mask, + size_t packet_mask_size, + ForwardErrorCorrection::Packet* fec_packet) const { + uint8_t* data = fec_packet->data.MutableData(); + data[0] &= 0x7f; // Clear R bit. + data[0] &= 0xbf; // Clear F bit. + ByteWriter<uint8_t>::WriteBigEndian(&data[8], kSsrcCount); + ByteWriter<uint32_t, 3>::WriteBigEndian(&data[9], kReservedBits); + ByteWriter<uint32_t>::WriteBigEndian(&data[12], media_ssrc); + ByteWriter<uint16_t>::WriteBigEndian(&data[16], seq_num_base); + // Adapt ULPFEC packet mask to FlexFEC header. + // + // We treat the mask parts as unsigned integers with host order endianness + // in order to simplify the bit shifting between bytes. + uint8_t* const written_packet_mask = data + kPacketMaskOffset; + if (packet_mask_size == kUlpfecPacketMaskSizeLBitSet) { + // The packet mask is 48 bits long. + uint16_t tmp_mask_part0 = + ByteReader<uint16_t>::ReadBigEndian(&packet_mask[0]); + uint32_t tmp_mask_part1 = + ByteReader<uint32_t>::ReadBigEndian(&packet_mask[2]); + + tmp_mask_part0 >>= 1; // Shift, thus clearing K-bit 0. + ByteWriter<uint16_t>::WriteBigEndian(&written_packet_mask[0], + tmp_mask_part0); + tmp_mask_part1 >>= 2; // Shift, thus clearing K-bit 1 and bit 15. + ByteWriter<uint32_t>::WriteBigEndian(&written_packet_mask[2], + tmp_mask_part1); + bool bit15 = (packet_mask[1] & 0x01) != 0; + if (bit15) + written_packet_mask[2] |= 0x40; // Set bit 15. + bool bit46 = (packet_mask[5] & 0x02) != 0; + bool bit47 = (packet_mask[5] & 0x01) != 0; + if (!bit46 && !bit47) { + written_packet_mask[2] |= 0x80; // Set K-bit 1. + } else { + memset(&written_packet_mask[6], 0, 8); // Clear all trailing bits. + written_packet_mask[6] |= 0x80; // Set K-bit 2. + if (bit46) + written_packet_mask[6] |= 0x40; // Set bit 46. + if (bit47) + written_packet_mask[6] |= 0x20; // Set bit 47. + } + } else if (packet_mask_size == kUlpfecPacketMaskSizeLBitClear) { + // The packet mask is 16 bits long. + uint16_t tmp_mask_part0 = + ByteReader<uint16_t>::ReadBigEndian(&packet_mask[0]); + + tmp_mask_part0 >>= 1; // Shift, thus clearing K-bit 0. + ByteWriter<uint16_t>::WriteBigEndian(&written_packet_mask[0], + tmp_mask_part0); + bool bit15 = (packet_mask[1] & 0x01) != 0; + if (!bit15) { + written_packet_mask[0] |= 0x80; // Set K-bit 0. + } else { + memset(&written_packet_mask[2], 0U, 4); // Clear all trailing bits. + written_packet_mask[2] |= 0x80; // Set K-bit 1. + written_packet_mask[2] |= 0x40; // Set bit 15. + } + } else { + RTC_DCHECK_NOTREACHED() + << "Incorrect packet mask size: " << packet_mask_size << "."; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.h b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.h new file mode 100644 index 0000000000..d305c4c288 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.h @@ -0,0 +1,88 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_FLEXFEC_HEADER_READER_WRITER_H_ +#define MODULES_RTP_RTCP_SOURCE_FLEXFEC_HEADER_READER_WRITER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/forward_error_correction.h" + +namespace webrtc { + +// FlexFEC header, minimum 20 bytes. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 |R|F|P|X| CC |M| PT recovery | length recovery | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | TS recovery | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | SSRCCount | reserved | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 12 | SSRC_i | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | SN base_i |k| Mask [0-14] | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 20 |k| Mask [15-45] (optional) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 24 |k| | +// +-+ Mask [46-108] (optional) | +// 28 | | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// : ... next in SSRC_i ... : +// +// +// FlexFEC header in 'inflexible' mode (F = 1), 20 bytes. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 |0|1|P|X| CC |M| PT recovery | length recovery | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | TS recovery | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | SSRCCount | reserved | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | SSRC_i | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | SN base_i | M (columns) | N (rows) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +class FlexfecHeaderReader : public FecHeaderReader { + public: + FlexfecHeaderReader(); + ~FlexfecHeaderReader() override; + + bool ReadFecHeader( + ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const override; +}; + +class FlexfecHeaderWriter : public FecHeaderWriter { + public: + FlexfecHeaderWriter(); + ~FlexfecHeaderWriter() override; + + size_t MinPacketMaskSize(const uint8_t* packet_mask, + size_t packet_mask_size) const override; + + size_t FecHeaderSize(size_t packet_mask_row_size) const override; + + void FinalizeFecHeader( + uint32_t media_ssrc, + uint16_t seq_num_base, + const uint8_t* packet_mask, + size_t packet_mask_size, + ForwardErrorCorrection::Packet* fec_packet) const override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_FLEXFEC_HEADER_READER_WRITER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer_unittest.cc new file mode 100644 index 0000000000..4a24e90ec3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer_unittest.cc @@ -0,0 +1,560 @@ +/* + * 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/rtp_rtcp/source/flexfec_header_reader_writer.h" + +#include <string.h> + +#include <memory> +#include <utility> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/checks.h" +#include "rtc_base/random.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +using Packet = ForwardErrorCorrection::Packet; +using ReceivedFecPacket = ForwardErrorCorrection::ReceivedFecPacket; + +// General. Assume single-stream protection. +constexpr uint32_t kMediaSsrc = 1254983; +constexpr uint16_t kMediaStartSeqNum = 825; +constexpr size_t kMediaPacketLength = 1234; +constexpr uint32_t kFlexfecSsrc = 52142; + +constexpr size_t kFlexfecHeaderSizes[] = {20, 24, 32}; +constexpr size_t kFlexfecPacketMaskOffset = 18; +constexpr size_t kFlexfecPacketMaskSizes[] = {2, 6, 14}; +constexpr size_t kFlexfecMaxPacketSize = kFlexfecPacketMaskSizes[2]; + +// Reader tests. +constexpr uint8_t kNoRBit = 0 << 7; +constexpr uint8_t kNoFBit = 0 << 6; +constexpr uint8_t kPtRecovery = 123; +constexpr uint8_t kLengthRecov[] = {0xab, 0xcd}; +constexpr uint8_t kTsRecovery[] = {0x01, 0x23, 0x45, 0x67}; +constexpr uint8_t kSsrcCount = 1; +constexpr uint8_t kReservedBits = 0x00; +constexpr uint8_t kProtSsrc[] = {0x11, 0x22, 0x33, 0x44}; +constexpr uint8_t kSnBase[] = {0xaa, 0xbb}; +constexpr uint8_t kPayloadBits = 0x00; + +std::unique_ptr<uint8_t[]> GeneratePacketMask(size_t packet_mask_size, + uint64_t seed) { + Random random(seed); + std::unique_ptr<uint8_t[]> packet_mask(new uint8_t[kFlexfecMaxPacketSize]); + memset(packet_mask.get(), 0, kFlexfecMaxPacketSize); + for (size_t i = 0; i < packet_mask_size; ++i) { + packet_mask[i] = random.Rand<uint8_t>(); + } + return packet_mask; +} + +void ClearBit(size_t index, uint8_t* packet_mask) { + packet_mask[index / 8] &= ~(1 << (7 - index % 8)); +} + +void SetBit(size_t index, uint8_t* packet_mask) { + packet_mask[index / 8] |= (1 << (7 - index % 8)); +} + +rtc::scoped_refptr<Packet> WriteHeader(const uint8_t* packet_mask, + size_t packet_mask_size) { + FlexfecHeaderWriter writer; + rtc::scoped_refptr<Packet> written_packet(new Packet()); + written_packet->data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet->data.MutableData(); + for (size_t i = 0; i < written_packet->data.size(); ++i) { + data[i] = i; // Actual content doesn't matter. + } + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, packet_mask, + packet_mask_size, written_packet.get()); + return written_packet; +} + +std::unique_ptr<ReceivedFecPacket> ReadHeader(const Packet& written_packet) { + FlexfecHeaderReader reader; + std::unique_ptr<ReceivedFecPacket> read_packet(new ReceivedFecPacket()); + read_packet->ssrc = kFlexfecSsrc; + read_packet->pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet->pkt->data = written_packet.data; + EXPECT_TRUE(reader.ReadFecHeader(read_packet.get())); + return read_packet; +} + +void VerifyReadHeaders(size_t expected_fec_header_size, + const uint8_t* expected_packet_mask, + size_t expected_packet_mask_size, + const ReceivedFecPacket& read_packet) { + EXPECT_EQ(expected_fec_header_size, read_packet.fec_header_size); + EXPECT_EQ(ByteReader<uint32_t>::ReadBigEndian(kProtSsrc), + read_packet.protected_ssrc); + EXPECT_EQ(ByteReader<uint16_t>::ReadBigEndian(kSnBase), + read_packet.seq_num_base); + const size_t packet_mask_offset = read_packet.packet_mask_offset; + EXPECT_EQ(kFlexfecPacketMaskOffset, packet_mask_offset); + EXPECT_EQ(expected_packet_mask_size, read_packet.packet_mask_size); + EXPECT_EQ(read_packet.pkt->data.size() - expected_fec_header_size, + read_packet.protection_length); + // Ensure that the K-bits are removed and the packet mask has been packed. + EXPECT_THAT( + ::testing::make_tuple(read_packet.pkt->data.cdata() + packet_mask_offset, + read_packet.packet_mask_size), + ::testing::ElementsAreArray(expected_packet_mask, + expected_packet_mask_size)); +} + +void VerifyFinalizedHeaders(const uint8_t* expected_packet_mask, + size_t expected_packet_mask_size, + const Packet& written_packet) { + const uint8_t* packet = written_packet.data.cdata(); + EXPECT_EQ(0x00, packet[0] & 0x80); // F bit clear. + EXPECT_EQ(0x00, packet[0] & 0x40); // R bit clear. + EXPECT_EQ(0x01, packet[8]); // SSRCCount = 1. + EXPECT_EQ(kMediaSsrc, ByteReader<uint32_t>::ReadBigEndian(packet + 12)); + EXPECT_EQ(kMediaStartSeqNum, + ByteReader<uint16_t>::ReadBigEndian(packet + 16)); + EXPECT_THAT(::testing::make_tuple(packet + kFlexfecPacketMaskOffset, + expected_packet_mask_size), + ::testing::ElementsAreArray(expected_packet_mask, + expected_packet_mask_size)); +} + +void VerifyWrittenAndReadHeaders(size_t expected_fec_header_size, + const uint8_t* expected_packet_mask, + size_t expected_packet_mask_size, + const Packet& written_packet, + const ReceivedFecPacket& read_packet) { + EXPECT_EQ(kFlexfecSsrc, read_packet.ssrc); + EXPECT_EQ(expected_fec_header_size, read_packet.fec_header_size); + EXPECT_EQ(kMediaSsrc, read_packet.protected_ssrc); + EXPECT_EQ(kMediaStartSeqNum, read_packet.seq_num_base); + EXPECT_EQ(kFlexfecPacketMaskOffset, read_packet.packet_mask_offset); + ASSERT_EQ(expected_packet_mask_size, read_packet.packet_mask_size); + EXPECT_EQ(written_packet.data.size() - expected_fec_header_size, + read_packet.protection_length); + // Verify that the call to ReadFecHeader did normalize the packet masks. + EXPECT_THAT(::testing::make_tuple( + read_packet.pkt->data.cdata() + kFlexfecPacketMaskOffset, + read_packet.packet_mask_size), + ::testing::ElementsAreArray(expected_packet_mask, + expected_packet_mask_size)); + // Verify that the call to ReadFecHeader did not tamper with the payload. + EXPECT_THAT(::testing::make_tuple( + read_packet.pkt->data.cdata() + read_packet.fec_header_size, + read_packet.pkt->data.size() - read_packet.fec_header_size), + ::testing::ElementsAreArray( + written_packet.data.cdata() + expected_fec_header_size, + written_packet.data.size() - expected_fec_header_size)); +} + +} // namespace + +TEST(FlexfecHeaderReaderTest, ReadsHeaderWithKBit0Set) { + constexpr uint8_t kKBit0 = 1 << 7; + constexpr size_t kExpectedPacketMaskSize = 2; + constexpr size_t kExpectedFecHeaderSize = 20; + // clang-format off + constexpr uint8_t kFlexfecPktMask[] = {kKBit0 | 0x08, 0x81}; + constexpr uint8_t kUlpfecPacketMask[] = {0x11, 0x02}; + // clang-format on + constexpr uint8_t kPacketData[] = { + kNoRBit | kNoFBit, kPtRecovery, kLengthRecov[0], kLengthRecov[1], + kTsRecovery[0], kTsRecovery[1], kTsRecovery[2], kTsRecovery[3], + kSsrcCount, kReservedBits, kReservedBits, kReservedBits, + kProtSsrc[0], kProtSsrc[1], kProtSsrc[2], kProtSsrc[3], + kSnBase[0], kSnBase[1], kFlexfecPktMask[0], kFlexfecPktMask[1], + kPayloadBits, kPayloadBits, kPayloadBits, kPayloadBits}; + const size_t packet_length = sizeof(kPacketData); + ReceivedFecPacket read_packet; + read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet.pkt->data.SetData(kPacketData, packet_length); + + FlexfecHeaderReader reader; + EXPECT_TRUE(reader.ReadFecHeader(&read_packet)); + + VerifyReadHeaders(kExpectedFecHeaderSize, kUlpfecPacketMask, + kExpectedPacketMaskSize, read_packet); +} + +TEST(FlexfecHeaderReaderTest, ReadsHeaderWithKBit1Set) { + constexpr uint8_t kKBit0 = 0 << 7; + constexpr uint8_t kKBit1 = 1 << 7; + constexpr size_t kExpectedPacketMaskSize = 6; + constexpr size_t kExpectedFecHeaderSize = 24; + // clang-format off + constexpr uint8_t kFlxfecPktMsk[] = {kKBit0 | 0x48, 0x81, + kKBit1 | 0x02, 0x11, 0x00, 0x21}; + constexpr uint8_t kUlpfecPacketMask[] = {0x91, 0x02, + 0x08, 0x44, 0x00, 0x84}; + // clang-format on + constexpr uint8_t kPacketData[] = { + kNoRBit | kNoFBit, kPtRecovery, kLengthRecov[0], kLengthRecov[1], + kTsRecovery[0], kTsRecovery[1], kTsRecovery[2], kTsRecovery[3], + kSsrcCount, kReservedBits, kReservedBits, kReservedBits, + kProtSsrc[0], kProtSsrc[1], kProtSsrc[2], kProtSsrc[3], + kSnBase[0], kSnBase[1], kFlxfecPktMsk[0], kFlxfecPktMsk[1], + kFlxfecPktMsk[2], kFlxfecPktMsk[3], kFlxfecPktMsk[4], kFlxfecPktMsk[5], + kPayloadBits, kPayloadBits, kPayloadBits, kPayloadBits}; + const size_t packet_length = sizeof(kPacketData); + ReceivedFecPacket read_packet; + read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet.pkt->data.SetData(kPacketData, packet_length); + + FlexfecHeaderReader reader; + EXPECT_TRUE(reader.ReadFecHeader(&read_packet)); + + VerifyReadHeaders(kExpectedFecHeaderSize, kUlpfecPacketMask, + kExpectedPacketMaskSize, read_packet); +} + +TEST(FlexfecHeaderReaderTest, ReadsHeaderWithKBit2Set) { + constexpr uint8_t kKBit0 = 0 << 7; + constexpr uint8_t kKBit1 = 0 << 7; + constexpr uint8_t kKBit2 = 1 << 7; + constexpr size_t kExpectedPacketMaskSize = 14; + constexpr size_t kExpectedFecHeaderSize = 32; + // clang-format off + constexpr uint8_t kFlxfcPktMsk[] = {kKBit0 | 0x48, 0x81, + kKBit1 | 0x02, 0x11, 0x00, 0x21, + kKBit2 | 0x01, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11}; + constexpr uint8_t kUlpfecPacketMask[] = {0x91, 0x02, + 0x08, 0x44, 0x00, 0x84, + 0x08, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88}; + // clang-format on + constexpr uint8_t kPacketData[] = { + kNoRBit | kNoFBit, kPtRecovery, kLengthRecov[0], kLengthRecov[1], + kTsRecovery[0], kTsRecovery[1], kTsRecovery[2], kTsRecovery[3], + kSsrcCount, kReservedBits, kReservedBits, kReservedBits, + kProtSsrc[0], kProtSsrc[1], kProtSsrc[2], kProtSsrc[3], + kSnBase[0], kSnBase[1], kFlxfcPktMsk[0], kFlxfcPktMsk[1], + kFlxfcPktMsk[2], kFlxfcPktMsk[3], kFlxfcPktMsk[4], kFlxfcPktMsk[5], + kFlxfcPktMsk[6], kFlxfcPktMsk[7], kFlxfcPktMsk[8], kFlxfcPktMsk[9], + kFlxfcPktMsk[10], kFlxfcPktMsk[11], kFlxfcPktMsk[12], kFlxfcPktMsk[13], + kPayloadBits, kPayloadBits, kPayloadBits, kPayloadBits}; + const size_t packet_length = sizeof(kPacketData); + ReceivedFecPacket read_packet; + read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet.pkt->data.SetData(kPacketData, packet_length); + + FlexfecHeaderReader reader; + EXPECT_TRUE(reader.ReadFecHeader(&read_packet)); + + VerifyReadHeaders(kExpectedFecHeaderSize, kUlpfecPacketMask, + kExpectedPacketMaskSize, read_packet); +} + +TEST(FlexfecHeaderReaderTest, ReadPacketWithoutStreamSpecificHeaderShouldFail) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + + // Simulate short received packet. + ReceivedFecPacket read_packet; + read_packet.ssrc = kFlexfecSsrc; + read_packet.pkt = std::move(written_packet); + read_packet.pkt->data.SetSize(12); + + FlexfecHeaderReader reader; + EXPECT_FALSE(reader.ReadFecHeader(&read_packet)); +} + +TEST(FlexfecHeaderReaderTest, ReadShortPacketWithKBit0SetShouldFail) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + + // Simulate short received packet. + ReceivedFecPacket read_packet; + read_packet.ssrc = kFlexfecSsrc; + read_packet.pkt = std::move(written_packet); + read_packet.pkt->data.SetSize(18); + + FlexfecHeaderReader reader; + EXPECT_FALSE(reader.ReadFecHeader(&read_packet)); +} + +TEST(FlexfecHeaderReaderTest, ReadShortPacketWithKBit1SetShouldFail) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(15, packet_mask.get()); // This expands the packet mask "once". + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + + // Simulate short received packet. + ReceivedFecPacket read_packet; + read_packet.ssrc = kFlexfecSsrc; + read_packet.pkt = std::move(written_packet); + read_packet.pkt->data.SetSize(20); + + FlexfecHeaderReader reader; + EXPECT_FALSE(reader.ReadFecHeader(&read_packet)); +} + +TEST(FlexfecHeaderReaderTest, ReadShortPacketWithKBit2SetShouldFail) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(47, packet_mask.get()); // This expands the packet mask "twice". + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + + // Simulate short received packet. + ReceivedFecPacket read_packet; + read_packet.ssrc = kFlexfecSsrc; + read_packet.pkt = std::move(written_packet); + read_packet.pkt->data.SetSize(24); + + FlexfecHeaderReader reader; + EXPECT_FALSE(reader.ReadFecHeader(&read_packet)); +} + +TEST(FlexfecHeaderWriterTest, FinalizesHeaderWithKBit0Set) { + constexpr size_t kExpectedPacketMaskSize = 2; + constexpr uint8_t kFlexfecPacketMask[] = {0x88, 0x81}; + constexpr uint8_t kUlpfecPacketMask[] = {0x11, 0x02}; + Packet written_packet; + written_packet.data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet.data.MutableData(); + for (size_t i = 0; i < written_packet.data.size(); ++i) { + data[i] = i; + } + + FlexfecHeaderWriter writer; + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, kUlpfecPacketMask, + sizeof(kUlpfecPacketMask), &written_packet); + + VerifyFinalizedHeaders(kFlexfecPacketMask, kExpectedPacketMaskSize, + written_packet); +} + +TEST(FlexfecHeaderWriterTest, FinalizesHeaderWithKBit1Set) { + constexpr size_t kExpectedPacketMaskSize = 6; + constexpr uint8_t kFlexfecPacketMask[] = {0x48, 0x81, 0x82, 0x11, 0x00, 0x21}; + constexpr uint8_t kUlpfecPacketMask[] = {0x91, 0x02, 0x08, 0x44, 0x00, 0x84}; + Packet written_packet; + written_packet.data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet.data.MutableData(); + for (size_t i = 0; i < written_packet.data.size(); ++i) { + data[i] = i; + } + + FlexfecHeaderWriter writer; + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, kUlpfecPacketMask, + sizeof(kUlpfecPacketMask), &written_packet); + + VerifyFinalizedHeaders(kFlexfecPacketMask, kExpectedPacketMaskSize, + written_packet); +} + +TEST(FlexfecHeaderWriterTest, FinalizesHeaderWithKBit2Set) { + constexpr size_t kExpectedPacketMaskSize = 14; + constexpr uint8_t kFlexfecPacketMask[] = { + 0x11, 0x11, // K-bit 0 clear. + 0x11, 0x11, 0x11, 0x10, // K-bit 1 clear. + 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // K-bit 2 set. + }; + constexpr uint8_t kUlpfecPacketMask[] = {0x22, 0x22, 0x44, 0x44, 0x44, 0x41}; + Packet written_packet; + written_packet.data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet.data.MutableData(); + for (size_t i = 0; i < written_packet.data.size(); ++i) { + data[i] = i; + } + + FlexfecHeaderWriter writer; + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, kUlpfecPacketMask, + sizeof(kUlpfecPacketMask), &written_packet); + + VerifyFinalizedHeaders(kFlexfecPacketMask, kExpectedPacketMaskSize, + written_packet); +} + +TEST(FlexfecHeaderWriterTest, ContractsShortUlpfecPacketMaskWithBit15Clear) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + ClearBit(15, packet_mask.get()); + + FlexfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kFlexfecPacketMaskSizes[0], min_packet_mask_size); + EXPECT_EQ(kFlexfecHeaderSizes[0], writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(FlexfecHeaderWriterTest, ExpandsShortUlpfecPacketMaskWithBit15Set) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(15, packet_mask.get()); + + FlexfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kFlexfecPacketMaskSizes[1], min_packet_mask_size); + EXPECT_EQ(kFlexfecHeaderSizes[1], writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(FlexfecHeaderWriterTest, + ContractsLongUlpfecPacketMaskWithBit46ClearBit47Clear) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + ClearBit(46, packet_mask.get()); + ClearBit(47, packet_mask.get()); + + FlexfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kFlexfecPacketMaskSizes[1], min_packet_mask_size); + EXPECT_EQ(kFlexfecHeaderSizes[1], writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(FlexfecHeaderWriterTest, + ExpandsLongUlpfecPacketMaskWithBit46SetBit47Clear) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(46, packet_mask.get()); + ClearBit(47, packet_mask.get()); + + FlexfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kFlexfecPacketMaskSizes[2], min_packet_mask_size); + EXPECT_EQ(kFlexfecHeaderSizes[2], writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(FlexfecHeaderWriterTest, + ExpandsLongUlpfecPacketMaskWithBit46ClearBit47Set) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + ClearBit(46, packet_mask.get()); + SetBit(47, packet_mask.get()); + + FlexfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kFlexfecPacketMaskSizes[2], min_packet_mask_size); + EXPECT_EQ(kFlexfecHeaderSizes[2], writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(FlexfecHeaderWriterTest, ExpandsLongUlpfecPacketMaskWithBit46SetBit47Set) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(46, packet_mask.get()); + SetBit(47, packet_mask.get()); + + FlexfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kFlexfecPacketMaskSizes[2], min_packet_mask_size); + EXPECT_EQ(kFlexfecHeaderSizes[2], writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(FlexfecHeaderReaderWriterTest, + WriteAndReadSmallUlpfecPacketHeaderWithMaskBit15Clear) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + ClearBit(15, packet_mask.get()); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyWrittenAndReadHeaders(kFlexfecHeaderSizes[0], packet_mask.get(), + kFlexfecPacketMaskSizes[0], *written_packet, + *read_packet); +} + +TEST(FlexfecHeaderReaderWriterTest, + WriteAndReadSmallUlpfecPacketHeaderWithMaskBit15Set) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(15, packet_mask.get()); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyWrittenAndReadHeaders(kFlexfecHeaderSizes[1], packet_mask.get(), + kFlexfecPacketMaskSizes[1], *written_packet, + *read_packet); +} + +TEST(FlexfecHeaderReaderWriterTest, + WriteAndReadLargeUlpfecPacketHeaderWithMaskBits46And47Clear) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + ClearBit(46, packet_mask.get()); + ClearBit(47, packet_mask.get()); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyWrittenAndReadHeaders(kFlexfecHeaderSizes[1], packet_mask.get(), + kFlexfecPacketMaskSizes[1], *written_packet, + *read_packet); +} + +TEST(FlexfecHeaderReaderWriterTest, + WriteAndReadLargeUlpfecPacketHeaderWithMaskBit46SetBit47Clear) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(46, packet_mask.get()); + ClearBit(47, packet_mask.get()); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyWrittenAndReadHeaders(kFlexfecHeaderSizes[2], packet_mask.get(), + kFlexfecPacketMaskSizes[2], *written_packet, + *read_packet); +} + +TEST(FlexfecHeaderReaderWriterTest, + WriteAndReadLargeUlpfecPacketHeaderMaskWithBit46ClearBit47Set) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + ClearBit(46, packet_mask.get()); + SetBit(47, packet_mask.get()); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyWrittenAndReadHeaders(kFlexfecHeaderSizes[2], packet_mask.get(), + kFlexfecPacketMaskSizes[2], *written_packet, + *read_packet); +} + +TEST(FlexfecHeaderReaderWriterTest, + WriteAndReadLargeUlpfecPacketHeaderWithMaskBits46And47Set) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + SetBit(46, packet_mask.get()); + SetBit(47, packet_mask.get()); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyWrittenAndReadHeaders(kFlexfecHeaderSizes[2], packet_mask.get(), + kFlexfecPacketMaskSizes[2], *written_packet, + *read_packet); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver.cc new file mode 100644 index 0000000000..bd67de1f83 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver.cc @@ -0,0 +1,196 @@ +/* + * 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/rtp_rtcp/include/flexfec_receiver.h" + +#include <string.h> + +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +// Minimum header size (in bytes) of a well-formed non-singular FlexFEC packet. +constexpr size_t kMinFlexfecHeaderSize = 20; + +// How often to log the recovered packets to the text log. +constexpr int kPacketLogIntervalMs = 10000; + +} // namespace + +/* Mozilla: Avoid this since it could use GetRealTimeClock(). +FlexfecReceiver::FlexfecReceiver( + uint32_t ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* recovered_packet_receiver) + : FlexfecReceiver(Clock::GetRealTimeClock(), + ssrc, + protected_media_ssrc, + recovered_packet_receiver) {} + */ + +FlexfecReceiver::FlexfecReceiver( + Clock* clock, + uint32_t ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* recovered_packet_receiver) + : ssrc_(ssrc), + protected_media_ssrc_(protected_media_ssrc), + erasure_code_( + ForwardErrorCorrection::CreateFlexfec(ssrc, protected_media_ssrc)), + recovered_packet_receiver_(recovered_packet_receiver), + clock_(clock), + last_recovered_packet_ms_(-1) { + // It's OK to create this object on a different thread/task queue than + // the one used during main operation. + sequence_checker_.Detach(); +} + +FlexfecReceiver::~FlexfecReceiver() = default; + +void FlexfecReceiver::OnRtpPacket(const RtpPacketReceived& packet) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // If this packet was recovered, it might be originating from + // ProcessReceivedPacket in this object. To avoid lifetime issues with + // `recovered_packets_`, we therefore break the cycle here. + // This might reduce decoding efficiency a bit, since we can't disambiguate + // recovered packets by RTX from recovered packets by FlexFEC. + if (packet.recovered()) + return; + + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + AddReceivedPacket(packet); + if (!received_packet) + return; + + ProcessReceivedPacket(*received_packet); +} + +FecPacketCounter FlexfecReceiver::GetPacketCounter() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return packet_counter_; +} + +// TODO(eladalon): Consider using packet.recovered() to avoid processing +// recovered packets here. +std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> +FlexfecReceiver::AddReceivedPacket(const RtpPacketReceived& packet) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // RTP packets with a full base header (12 bytes), but without payload, + // could conceivably be useful in the decoding. Therefore we check + // with a non-strict inequality here. + RTC_DCHECK_GE(packet.size(), kRtpHeaderSize); + + // Demultiplex based on SSRC, and insert into erasure code decoder. + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet( + new ForwardErrorCorrection::ReceivedPacket()); + received_packet->seq_num = packet.SequenceNumber(); + received_packet->ssrc = packet.Ssrc(); + received_packet->extensions = packet.extension_manager(); + if (received_packet->ssrc == ssrc_) { + // This is a FlexFEC packet. + if (packet.payload_size() < kMinFlexfecHeaderSize) { + RTC_LOG(LS_WARNING) << "Truncated FlexFEC packet, discarding."; + return nullptr; + } + received_packet->is_fec = true; + ++packet_counter_.num_fec_packets; + + // Insert packet payload into erasure code. + received_packet->pkt = rtc::scoped_refptr<ForwardErrorCorrection::Packet>( + new ForwardErrorCorrection::Packet()); + received_packet->pkt->data = + packet.Buffer().Slice(packet.headers_size(), packet.payload_size()); + } else { + // This is a media packet, or a FlexFEC packet belonging to some + // other FlexFEC stream. + if (received_packet->ssrc != protected_media_ssrc_) { + return nullptr; + } + received_packet->is_fec = false; + + // Insert entire packet into erasure code. + // Create a copy and fill with zeros all mutable extensions. + received_packet->pkt = rtc::scoped_refptr<ForwardErrorCorrection::Packet>( + new ForwardErrorCorrection::Packet()); + RtpPacketReceived packet_copy(packet); + packet_copy.ZeroMutableExtensions(); + received_packet->pkt->data = packet_copy.Buffer(); + } + + ++packet_counter_.num_packets; + + return received_packet; +} + +// Note that the implementation of this member function and the implementation +// in UlpfecReceiver::ProcessReceivedFec() are slightly different. +// This implementation only returns _recovered_ media packets through the +// callback, whereas the implementation in UlpfecReceiver returns _all inserted_ +// media packets through the callback. The latter behaviour makes sense +// for ULPFEC, since the ULPFEC receiver is owned by the RtpVideoStreamReceiver. +// Here, however, the received media pipeline is more decoupled from the +// FlexFEC decoder, and we therefore do not interfere with the reception +// of non-recovered media packets. +void FlexfecReceiver::ProcessReceivedPacket( + const ForwardErrorCorrection::ReceivedPacket& received_packet) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // Decode. + erasure_code_->DecodeFec(received_packet, &recovered_packets_); + + // Return recovered packets through callback. + for (const auto& recovered_packet : recovered_packets_) { + RTC_CHECK(recovered_packet); + if (recovered_packet->returned) { + continue; + } + ++packet_counter_.num_recovered_packets; + // Set this flag first, since OnRecoveredPacket may end up here + // again, with the same packet. + recovered_packet->returned = true; + RTC_CHECK_GE(recovered_packet->pkt->data.size(), kRtpHeaderSize); + + RtpPacketReceived parsed_packet(&received_packet.extensions); + if (!parsed_packet.Parse(recovered_packet->pkt->data)) { + continue; + } + parsed_packet.set_recovered(true); + + // TODO(brandtr): Update here when we support protecting audio packets too. + parsed_packet.set_payload_type_frequency(kVideoPayloadTypeFrequency); + recovered_packet_receiver_->OnRecoveredPacket(parsed_packet); + + // Periodically log the incoming packets at LS_INFO. + int64_t now_ms = clock_->TimeInMilliseconds(); + bool should_log_periodically = + now_ms - last_recovered_packet_ms_ > kPacketLogIntervalMs; + if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE) || should_log_periodically) { + rtc::LoggingSeverity level = + should_log_periodically ? rtc::LS_INFO : rtc::LS_VERBOSE; + RTC_LOG_V(level) << "Recovered media packet with SSRC: " + << parsed_packet.Ssrc() << " seq " + << parsed_packet.SequenceNumber() << " recovered length " + << recovered_packet->pkt->data.size() + << " from FlexFEC stream with SSRC: " << ssrc_; + if (should_log_periodically) { + last_recovered_packet_ms_ = now_ms; + } + } + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc new file mode 100644 index 0000000000..1243858b6b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc @@ -0,0 +1,691 @@ +/* + * 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/rtp_rtcp/include/flexfec_receiver.h" + +#include <algorithm> +#include <memory> + +#include "modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h" +#include "modules/rtp_rtcp/source/fec_test_helper.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +using ::testing::_; +using ::testing::Eq; +using ::testing::Property; + +using test::fec::FlexfecPacketGenerator; +using Packet = ForwardErrorCorrection::Packet; +using PacketList = ForwardErrorCorrection::PacketList; + +constexpr size_t kPayloadLength = 500; +constexpr uint32_t kFlexfecSsrc = 42984; +constexpr uint32_t kMediaSsrc = 8353; + +RtpPacketReceived ParsePacket(const Packet& packet) { + RtpPacketReceived parsed_packet; + EXPECT_TRUE(parsed_packet.Parse(packet.data)); + return parsed_packet; +} + +} // namespace + +class FlexfecReceiverForTest : public FlexfecReceiver { + public: + FlexfecReceiverForTest(uint32_t ssrc, + uint32_t protected_media_ssrc, + RecoveredPacketReceiver* recovered_packet_receiver) + : FlexfecReceiver(Clock::GetRealTimeClock(), + ssrc, + protected_media_ssrc, + recovered_packet_receiver) {} + // Expose methods for tests. + using FlexfecReceiver::AddReceivedPacket; + using FlexfecReceiver::ProcessReceivedPacket; +}; + +class FlexfecReceiverTest : public ::testing::Test { + protected: + FlexfecReceiverTest() + : receiver_(kFlexfecSsrc, kMediaSsrc, &recovered_packet_receiver_), + erasure_code_( + ForwardErrorCorrection::CreateFlexfec(kFlexfecSsrc, kMediaSsrc)), + packet_generator_(kMediaSsrc, kFlexfecSsrc) {} + + // Generates `num_media_packets` corresponding to a single frame. + void PacketizeFrame(size_t num_media_packets, + size_t frame_offset, + PacketList* media_packets); + + // Generates `num_fec_packets` FEC packets, given `media_packets`. + std::list<Packet*> EncodeFec(const PacketList& media_packets, + size_t num_fec_packets); + + FlexfecReceiverForTest receiver_; + std::unique_ptr<ForwardErrorCorrection> erasure_code_; + + FlexfecPacketGenerator packet_generator_; + ::testing::StrictMock<MockRecoveredPacketReceiver> recovered_packet_receiver_; +}; + +void FlexfecReceiverTest::PacketizeFrame(size_t num_media_packets, + size_t frame_offset, + PacketList* media_packets) { + packet_generator_.NewFrame(num_media_packets); + for (size_t i = 0; i < num_media_packets; ++i) { + std::unique_ptr<Packet> next_packet( + packet_generator_.NextPacket(frame_offset + i, kPayloadLength)); + media_packets->push_back(std::move(next_packet)); + } +} + +std::list<Packet*> FlexfecReceiverTest::EncodeFec( + const PacketList& media_packets, + size_t num_fec_packets) { + const uint8_t protection_factor = + num_fec_packets * 255 / media_packets.size(); + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr FecMaskType kFecMaskType = kFecMaskRandom; + std::list<Packet*> fec_packets; + EXPECT_EQ(0, erasure_code_->EncodeFec( + media_packets, protection_factor, kNumImportantPackets, + kUseUnequalProtection, kFecMaskType, &fec_packets)); + EXPECT_EQ(num_fec_packets, fec_packets.size()); + return fec_packets; +} + +TEST_F(FlexfecReceiverTest, ReceivesMediaPacket) { + packet_generator_.NewFrame(1); + std::unique_ptr<Packet> media_packet( + packet_generator_.NextPacket(0, kPayloadLength)); + + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + receiver_.AddReceivedPacket(ParsePacket(*media_packet)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); +} + +TEST_F(FlexfecReceiverTest, ReceivesMediaAndFecPackets) { + const size_t kNumMediaPackets = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + const auto& media_packet = media_packets.front(); + auto fec_packet = packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + receiver_.AddReceivedPacket(ParsePacket(*media_packet)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); + received_packet = receiver_.AddReceivedPacket(ParsePacket(*fec_packet)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); +} + +TEST_F(FlexfecReceiverTest, FailsOnTruncatedFecPacket) { + const size_t kNumMediaPackets = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + const auto& media_packet = media_packets.front(); + // Simulate truncated FlexFEC payload. + fec_packets.front()->data.SetSize(1); + auto fec_packet = packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + receiver_.AddReceivedPacket(ParsePacket(*media_packet)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); + EXPECT_FALSE(receiver_.AddReceivedPacket(ParsePacket(*fec_packet))); +} + +TEST_F(FlexfecReceiverTest, FailsOnUnknownMediaSsrc) { + const size_t kNumMediaPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + auto& media_packet = media_packets.front(); + // Corrupt the SSRC. + media_packet->data.MutableData()[8] = 0; + media_packet->data.MutableData()[9] = 1; + media_packet->data.MutableData()[10] = 2; + media_packet->data.MutableData()[11] = 3; + + EXPECT_FALSE(receiver_.AddReceivedPacket(ParsePacket(*media_packet))); +} + +TEST_F(FlexfecReceiverTest, FailsOnUnknownFecSsrc) { + const size_t kNumMediaPackets = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + const auto& media_packet = media_packets.front(); + auto fec_packet = packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + // Corrupt the SSRC. + fec_packet->data.MutableData()[8] = 4; + fec_packet->data.MutableData()[9] = 5; + fec_packet->data.MutableData()[10] = 6; + fec_packet->data.MutableData()[11] = 7; + + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + receiver_.AddReceivedPacket(ParsePacket(*media_packet)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); + EXPECT_FALSE(receiver_.AddReceivedPacket(ParsePacket(*fec_packet))); +} + +TEST_F(FlexfecReceiverTest, ReceivesMultiplePackets) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive all media packets. + for (const auto& media_packet : media_packets) { + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + receiver_.AddReceivedPacket(ParsePacket(*media_packet)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); + } + + // Receive FEC packet. + auto* fec_packet = fec_packets.front(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(*fec_packet); + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet = + receiver_.AddReceivedPacket(ParsePacket(*packet_with_rtp_header)); + ASSERT_TRUE(received_packet); + receiver_.ProcessReceivedPacket(*received_packet); +} + +TEST_F(FlexfecReceiverTest, RecoversFromSingleMediaLoss) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + receiver_.OnRtpPacket(ParsePacket(**media_it)); + + // Receive FEC packet and ensure recovery of lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket( + Property(&RtpPacketReceived::Buffer, Eq((*media_it)->data)))); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); +} + +TEST_F(FlexfecReceiverTest, RecoversFromDoubleMediaLoss) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 2; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Drop both media packets. + + // Receive first FEC packet and recover first lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + auto media_it = media_packets.begin(); + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket( + Property(&RtpPacketReceived::Buffer, Eq((*media_it)->data)))); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + + // Receive second FEC packet and recover second lost media packet. + fec_it++; + packet_with_rtp_header = packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket( + Property(&RtpPacketReceived::Buffer, Eq((*media_it)->data)))); + + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); +} + +TEST_F(FlexfecReceiverTest, DoesNotRecoverFromMediaAndFecLoss) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet. + auto media_it = media_packets.begin(); + receiver_.OnRtpPacket(ParsePacket(**media_it)); + + // Drop second media packet and FEC packet. Do not expect call back. +} + +TEST_F(FlexfecReceiverTest, DoesNotCallbackTwice) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + receiver_.OnRtpPacket(ParsePacket(**media_it)); + + // Receive FEC packet and ensure recovery of lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket( + Property(&RtpPacketReceived::Buffer, Eq((*media_it)->data)))); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + + // Receive the FEC packet again, but do not call back. + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + + // Receive the first media packet again, but do not call back. + media_it = media_packets.begin(); + receiver_.OnRtpPacket(ParsePacket(**media_it)); + + // Receive the second media packet again (the one recovered above), + // but do not call back again. + media_it++; + receiver_.OnRtpPacket(ParsePacket(**media_it)); +} + +// Here we are implicitly assuming packet masks that are suitable for +// this type of 50% correlated loss. If we are changing our precomputed +// packet masks, this test might need to be updated. +TEST_F(FlexfecReceiverTest, RecoversFrom50PercentLoss) { + const size_t kNumFecPackets = 5; + const size_t kNumFrames = 2 * kNumFecPackets; + const size_t kNumMediaPacketsPerFrame = 1; + + PacketList media_packets; + for (size_t i = 0; i < kNumFrames; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Drop every second media packet. + auto media_it = media_packets.begin(); + while (media_it != media_packets.end()) { + receiver_.OnRtpPacket(ParsePacket(**media_it)); + ++media_it; + if (media_it == media_packets.end()) { + break; + } + ++media_it; + } + + // Receive all FEC packets. + media_it = media_packets.begin(); + for (const auto* fec_packet : fec_packets) { + std::unique_ptr<Packet> fec_packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(*fec_packet); + ++media_it; + if (media_it == media_packets.end()) { + break; + } + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(Property(&RtpPacketReceived::Buffer, + Eq((*media_it)->data)))); + receiver_.OnRtpPacket(ParsePacket(*fec_packet_with_rtp_header)); + ++media_it; + } +} + +TEST_F(FlexfecReceiverTest, DelayedFecPacketDoesHelp) { + // These values need to be updated if the underlying erasure code + // implementation changes. + // Delay FEC packet by maximum number of media packets tracked by receiver. + const size_t kNumFrames = 192; + const size_t kNumMediaPacketsPerFrame = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPacketsPerFrame, 0, &media_packets); + PacketizeFrame(kNumMediaPacketsPerFrame, 1, &media_packets); + // Protect two first frames. + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + for (size_t i = 2; i < kNumFrames; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + + // Drop first media packet and delay FEC packet. + auto media_it = media_packets.begin(); + ++media_it; + + // Receive all other media packets. + while (media_it != media_packets.end()) { + receiver_.OnRtpPacket(ParsePacket(**media_it)); + ++media_it; + } + + // Receive FEC packet and recover first media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it = media_packets.begin(); + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket( + Property(&RtpPacketReceived::Buffer, Eq((*media_it)->data)))); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); +} + +TEST_F(FlexfecReceiverTest, TooDelayedFecPacketDoesNotHelp) { + // These values need to be updated if the underlying erasure code + // implementation changes. + // Delay FEC packet by one more than maximum number of media packets + // tracked by receiver. + const size_t kNumFrames = 193; + const size_t kNumMediaPacketsPerFrame = 1; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPacketsPerFrame, 0, &media_packets); + PacketizeFrame(kNumMediaPacketsPerFrame, 1, &media_packets); + // Protect first two frames. + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + for (size_t i = 2; i < kNumFrames; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + + // Drop first media packet and delay FEC packet. + auto media_it = media_packets.begin(); + ++media_it; + + // Receive all other media packets. + while (media_it != media_packets.end()) { + receiver_.OnRtpPacket(ParsePacket(**media_it)); + ++media_it; + } + + // Receive FEC packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + + // Do not expect a call back. +} + +TEST_F(FlexfecReceiverTest, SurvivesOldRecoveredPacketBeingReinserted) { + // Simulates the behaviour of the + // Call->FlexfecReceiveStream->FlexfecReceiver->Call loop in production code. + class LoopbackRecoveredPacketReceiver : public RecoveredPacketReceiver { + public: + LoopbackRecoveredPacketReceiver() : receiver_(nullptr) {} + + void SetReceiver(FlexfecReceiver* receiver) { receiver_ = receiver; } + + // Implements RecoveredPacketReceiver. + void OnRecoveredPacket(const RtpPacketReceived& packet) override { + EXPECT_TRUE(packet.recovered()); + RTC_DCHECK(receiver_); + receiver_->OnRtpPacket(packet); + } + + private: + FlexfecReceiver* receiver_; + } loopback_recovered_packet_receiver; + + // Feed recovered packets back into `receiver`. + FlexfecReceiver receiver(Clock::GetRealTimeClock(), kFlexfecSsrc, kMediaSsrc, + &loopback_recovered_packet_receiver); + loopback_recovered_packet_receiver.SetReceiver(&receiver); + + // Receive first set of packets. + PacketList first_media_packets; + for (int i = 0; i < 46; ++i) { + PacketizeFrame(1, 0, &first_media_packets); + } + for (const auto& media_packet : first_media_packets) { + receiver.OnRtpPacket(ParsePacket(*media_packet)); + } + + // Protect one media packet. Lose the media packet, + // but do not receive FEC packet yet. + PacketList protected_media_packet; + PacketizeFrame(1, 0, &protected_media_packet); + const std::list<Packet*> fec_packets = EncodeFec(protected_media_packet, 1); + EXPECT_EQ(1u, fec_packets.size()); + std::unique_ptr<Packet> fec_packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(*fec_packets.front()); + + // Lose some packets, thus introducing a sequence number gap. + PacketList lost_packets; + for (int i = 0; i < 100; ++i) { + PacketizeFrame(1, 0, &lost_packets); + } + + // Receive one more packet. + PacketList second_media_packets; + PacketizeFrame(1, 0, &second_media_packets); + for (const auto& media_packet : second_media_packets) { + receiver.OnRtpPacket(ParsePacket(*media_packet)); + } + + // Receive delayed FEC packet. + receiver.OnRtpPacket(ParsePacket(*fec_packet_with_rtp_header)); + + // Expect no crash. +} + +TEST_F(FlexfecReceiverTest, RecoversWithMediaPacketsOutOfOrder) { + const size_t kNumMediaPackets = 6; + const size_t kNumFecPackets = 2; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Lose two media packets, and receive the others out of order. + auto media_it = media_packets.begin(); + auto media_packet0 = media_it++; + auto media_packet1 = media_it++; + auto media_packet2 = media_it++; + auto media_packet3 = media_it++; + auto media_packet4 = media_it++; + auto media_packet5 = media_it++; + receiver_.OnRtpPacket(ParsePacket(**media_packet5)); + receiver_.OnRtpPacket(ParsePacket(**media_packet2)); + receiver_.OnRtpPacket(ParsePacket(**media_packet3)); + receiver_.OnRtpPacket(ParsePacket(**media_packet0)); + + // Expect to recover lost media packets. + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(Property(&RtpPacketReceived::Buffer, + Eq((*media_packet1)->data)))); + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket(Property(&RtpPacketReceived::Buffer, + Eq((*media_packet4)->data)))); + // Add FEC packets. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header; + while (fec_it != fec_packets.end()) { + packet_with_rtp_header = packet_generator_.BuildFlexfecPacket(**fec_it); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + ++fec_it; + } +} + +// Recovered media packets may be fed back into the FlexfecReceiver by the +// callback. This test ensures the idempotency of such a situation. +TEST_F(FlexfecReceiverTest, RecoveryCallbackDoesNotLoopInfinitely) { + class LoopbackRecoveredPacketReceiver : public RecoveredPacketReceiver { + public: + const int kMaxRecursionDepth = 10; + + LoopbackRecoveredPacketReceiver() + : receiver_(nullptr), + did_receive_call_back_(false), + recursion_depth_(0), + deep_recursion_(false) {} + + void SetReceiver(FlexfecReceiver* receiver) { receiver_ = receiver; } + bool DidReceiveCallback() const { return did_receive_call_back_; } + bool DeepRecursion() const { return deep_recursion_; } + + // Implements RecoveredPacketReceiver. + void OnRecoveredPacket(const RtpPacketReceived& packet) override { + did_receive_call_back_ = true; + + if (recursion_depth_ > kMaxRecursionDepth) { + deep_recursion_ = true; + return; + } + ++recursion_depth_; + RTC_DCHECK(receiver_); + receiver_->OnRtpPacket(packet); + --recursion_depth_; + } + + private: + FlexfecReceiver* receiver_; + bool did_receive_call_back_; + int recursion_depth_; + bool deep_recursion_; + } loopback_recovered_packet_receiver; + + // Feed recovered packets back into `receiver`. + FlexfecReceiver receiver(Clock::GetRealTimeClock(), kFlexfecSsrc, kMediaSsrc, + &loopback_recovered_packet_receiver); + loopback_recovered_packet_receiver.SetReceiver(&receiver); + + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + receiver.OnRtpPacket(ParsePacket(**media_it)); + + // Receive FEC packet and verify that a packet was recovered. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + receiver.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + EXPECT_TRUE(loopback_recovered_packet_receiver.DidReceiveCallback()); + EXPECT_FALSE(loopback_recovered_packet_receiver.DeepRecursion()); +} + +TEST_F(FlexfecReceiverTest, CalculatesNumberOfPackets) { + const size_t kNumMediaPackets = 2; + const size_t kNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kNumMediaPackets, 0, &media_packets); + std::list<Packet*> fec_packets = EncodeFec(media_packets, kNumFecPackets); + + // Receive first media packet but drop second. + auto media_it = media_packets.begin(); + receiver_.OnRtpPacket(ParsePacket(**media_it)); + + // Receive FEC packet and ensure recovery of lost media packet. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + media_it++; + EXPECT_CALL(recovered_packet_receiver_, + OnRecoveredPacket( + Property(&RtpPacketReceived::Buffer, Eq((*media_it)->data)))); + receiver_.OnRtpPacket(ParsePacket(*packet_with_rtp_header)); + + // Check stats calculations. + FecPacketCounter packet_counter = receiver_.GetPacketCounter(); + EXPECT_EQ(2U, packet_counter.num_packets); + EXPECT_EQ(1U, packet_counter.num_fec_packets); + EXPECT_EQ(1U, packet_counter.num_recovered_packets); +} + +TEST_F(FlexfecReceiverTest, DoesNotDecodeWrappedMediaSequenceUsingOldFec) { + const size_t kFirstFrameNumMediaPackets = 2; + const size_t kFirstFrameNumFecPackets = 1; + + PacketList media_packets; + PacketizeFrame(kFirstFrameNumMediaPackets, 0, &media_packets); + + // Protect first frame (sequences 0 and 1) with 1 FEC packet. + std::list<Packet*> fec_packets = + EncodeFec(media_packets, kFirstFrameNumFecPackets); + + // Generate enough media packets to simulate media sequence number wraparound. + // Use no FEC for these frames to make sure old FEC is not purged due to age. + const size_t kNumFramesSequenceWrapAround = + std::numeric_limits<uint16_t>::max(); + const size_t kNumMediaPacketsPerFrame = 1; + + for (size_t i = 1; i <= kNumFramesSequenceWrapAround; ++i) { + PacketizeFrame(kNumMediaPacketsPerFrame, i, &media_packets); + } + + // Receive first (`kFirstFrameNumMediaPackets` + 192) media packets. + // Simulate an old FEC packet by separating it from its encoded media + // packets by at least 192 packets. + auto media_it = media_packets.begin(); + for (size_t i = 0; i < (kFirstFrameNumMediaPackets + 192); i++) { + if (i == 1) { + // Drop the second packet of the first frame. + media_it++; + } else { + receiver_.OnRtpPacket(ParsePacket(**media_it++)); + } + } + + // Receive FEC packet. Although a protected packet was dropped, + // expect no recovery callback since it is delayed from first frame + // by more than 192 packets. + auto fec_it = fec_packets.begin(); + std::unique_ptr<Packet> fec_packet_with_rtp_header = + packet_generator_.BuildFlexfecPacket(**fec_it); + receiver_.OnRtpPacket(ParsePacket(*fec_packet_with_rtp_header)); + + // Receive remaining media packets. + // NOTE: Because we sent enough to simulate wrap around, sequence 0 is + // received again, but is a different packet than the original first + // packet of first frame. + while (media_it != media_packets.end()) { + receiver_.OnRtpPacket(ParsePacket(**media_it++)); + } + + // Do not expect a recovery callback, the FEC packet is old + // and should not decode wrapped around media sequences. +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender.cc new file mode 100644 index 0000000000..292fe4a8dd --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender.cc @@ -0,0 +1,204 @@ +/* + * 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/rtp_rtcp/include/flexfec_sender.h" + +#include <string.h> + +#include <list> +#include <utility> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +// Let first sequence number be in the first half of the interval. +constexpr uint16_t kMaxInitRtpSeqNumber = 0x7fff; + +// See breakdown in flexfec_header_reader_writer.cc. +constexpr size_t kFlexfecMaxHeaderSize = 32; + +// Since we will mainly use FlexFEC to protect video streams, we use a 90 kHz +// clock for the RTP timestamps. (This is according to the RFC, which states +// that it is RECOMMENDED to use the same clock frequency for FlexFEC as for +// the protected media stream.) +// The constant converts from clock millisecond timestamps to the 90 kHz +// RTP timestamp. +const int kMsToRtpTimestamp = kVideoPayloadTypeFrequency / 1000; + +// How often to log the generated FEC packets to the text log. +constexpr int64_t kPacketLogIntervalMs = 10000; + +RtpHeaderExtensionMap RegisterSupportedExtensions( + const std::vector<RtpExtension>& rtp_header_extensions) { + RtpHeaderExtensionMap map; + for (const auto& extension : rtp_header_extensions) { + if (extension.uri == TransportSequenceNumber::Uri()) { + map.Register<TransportSequenceNumber>(extension.id); + } else if (extension.uri == AbsoluteSendTime::Uri()) { + map.Register<AbsoluteSendTime>(extension.id); + } else if (extension.uri == TransmissionOffset::Uri()) { + map.Register<TransmissionOffset>(extension.id); + } else if (extension.uri == RtpMid::Uri()) { + map.Register<RtpMid>(extension.id); + } else { + RTC_LOG(LS_INFO) + << "FlexfecSender only supports RTP header extensions for " + "BWE and MID, so the extension " + << extension.ToString() << " will not be used."; + } + } + return map; +} + +} // namespace + +FlexfecSender::FlexfecSender( + int payload_type, + uint32_t ssrc, + uint32_t protected_media_ssrc, + absl::string_view mid, + const std::vector<RtpExtension>& rtp_header_extensions, + rtc::ArrayView<const RtpExtensionSize> extension_sizes, + const RtpState* rtp_state, + Clock* clock) + : clock_(clock), + random_(clock_->TimeInMicroseconds()), + last_generated_packet_ms_(-1), + payload_type_(payload_type), + // Reset RTP state if this is not the first time we are operating. + // Otherwise, randomize the initial timestamp offset and RTP sequence + // numbers. (This is not intended to be cryptographically strong.) + timestamp_offset_(rtp_state ? rtp_state->start_timestamp + : random_.Rand<uint32_t>()), + ssrc_(ssrc), + protected_media_ssrc_(protected_media_ssrc), + mid_(mid), + seq_num_(rtp_state ? rtp_state->sequence_number + : random_.Rand(1, kMaxInitRtpSeqNumber)), + ulpfec_generator_( + ForwardErrorCorrection::CreateFlexfec(ssrc, protected_media_ssrc), + clock_), + rtp_header_extension_map_( + RegisterSupportedExtensions(rtp_header_extensions)), + header_extensions_size_( + RtpHeaderExtensionSize(extension_sizes, rtp_header_extension_map_)), + fec_bitrate_(/*max_window_size_ms=*/1000, RateStatistics::kBpsScale) { + // This object should not have been instantiated if FlexFEC is disabled. + RTC_DCHECK_GE(payload_type, 0); + RTC_DCHECK_LE(payload_type, 127); +} + +FlexfecSender::~FlexfecSender() = default; + +// We are reusing the implementation from UlpfecGenerator for SetFecParameters, +// AddRtpPacketAndGenerateFec, and FecAvailable. +void FlexfecSender::SetProtectionParameters( + const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) { + ulpfec_generator_.SetProtectionParameters(delta_params, key_params); +} + +void FlexfecSender::AddPacketAndGenerateFec(const RtpPacketToSend& packet) { + // TODO(brandtr): Generalize this SSRC check when we support multistream + // protection. + RTC_DCHECK_EQ(packet.Ssrc(), protected_media_ssrc_); + ulpfec_generator_.AddPacketAndGenerateFec(packet); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> FlexfecSender::GetFecPackets() { + RTC_CHECK_RUNS_SERIALIZED(&ulpfec_generator_.race_checker_); + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets_to_send; + fec_packets_to_send.reserve(ulpfec_generator_.generated_fec_packets_.size()); + size_t total_fec_data_bytes = 0; + for (const auto* fec_packet : ulpfec_generator_.generated_fec_packets_) { + std::unique_ptr<RtpPacketToSend> fec_packet_to_send( + new RtpPacketToSend(&rtp_header_extension_map_)); + fec_packet_to_send->set_packet_type( + RtpPacketMediaType::kForwardErrorCorrection); + fec_packet_to_send->set_allow_retransmission(false); + + // RTP header. + fec_packet_to_send->SetMarker(false); + fec_packet_to_send->SetPayloadType(payload_type_); + fec_packet_to_send->SetSequenceNumber(seq_num_++); + fec_packet_to_send->SetTimestamp( + timestamp_offset_ + + static_cast<uint32_t>(kMsToRtpTimestamp * + clock_->TimeInMilliseconds())); + // Set "capture time" so that the TransmissionOffset header extension + // can be set by the RTPSender. + fec_packet_to_send->set_capture_time(clock_->CurrentTime()); + fec_packet_to_send->SetSsrc(ssrc_); + // Reserve extensions, if registered. These will be set by the RTPSender. + fec_packet_to_send->ReserveExtension<AbsoluteSendTime>(); + fec_packet_to_send->ReserveExtension<TransmissionOffset>(); + fec_packet_to_send->ReserveExtension<TransportSequenceNumber>(); + // Possibly include the MID header extension. + if (!mid_.empty()) { + // This is a no-op if the MID header extension is not registered. + fec_packet_to_send->SetExtension<RtpMid>(mid_); + } + + // RTP payload. + uint8_t* payload = + fec_packet_to_send->AllocatePayload(fec_packet->data.size()); + memcpy(payload, fec_packet->data.cdata(), fec_packet->data.size()); + + total_fec_data_bytes += fec_packet_to_send->size(); + fec_packets_to_send.push_back(std::move(fec_packet_to_send)); + } + + if (!fec_packets_to_send.empty()) { + ulpfec_generator_.ResetState(); + } + + int64_t now_ms = clock_->TimeInMilliseconds(); + if (!fec_packets_to_send.empty() && + now_ms - last_generated_packet_ms_ > kPacketLogIntervalMs) { + RTC_LOG(LS_VERBOSE) << "Generated " << fec_packets_to_send.size() + << " FlexFEC packets with payload type: " + << payload_type_ << " and SSRC: " << ssrc_ << "."; + last_generated_packet_ms_ = now_ms; + } + + MutexLock lock(&mutex_); + fec_bitrate_.Update(total_fec_data_bytes, now_ms); + + return fec_packets_to_send; +} + +// The overhead is BWE RTP header extensions and FlexFEC header. +size_t FlexfecSender::MaxPacketOverhead() const { + return header_extensions_size_ + kFlexfecMaxHeaderSize; +} + +DataRate FlexfecSender::CurrentFecRate() const { + MutexLock lock(&mutex_); + return DataRate::BitsPerSec( + fec_bitrate_.Rate(clock_->TimeInMilliseconds()).value_or(0)); +} + +absl::optional<RtpState> FlexfecSender::GetRtpState() { + RtpState rtp_state; + rtp_state.sequence_number = seq_num_; + rtp_state.start_timestamp = timestamp_offset_; + return rtp_state; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender_unittest.cc new file mode 100644 index 0000000000..19614d2bbd --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender_unittest.cc @@ -0,0 +1,342 @@ +/* + * 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/rtp_rtcp/include/flexfec_sender.h" + +#include <vector> + +#include "api/rtp_parameters.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/fec_test_helper.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_sender.h" +#include "system_wrappers/include/clock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +using test::fec::AugmentedPacket; +using test::fec::AugmentedPacketGenerator; + +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; +// Assume a single protected media SSRC. +constexpr size_t kFlexfecMaxHeaderSize = 32; +constexpr size_t kPayloadLength = 50; + +constexpr int64_t kInitialSimulatedClockTime = 1; +// These values are deterministically given by the PRNG, due to our fixed seed. +// They should be updated if the PRNG implementation changes. +constexpr uint16_t kDeterministicSequenceNumber = 28732; +constexpr uint32_t kDeterministicTimestamp = 2305613085; + +// Round up to the nearest size that is a multiple of 4. +size_t Word32Align(size_t size) { + uint32_t remainder = size % 4; + if (remainder != 0) + return size + 4 - remainder; + return size; +} + +std::unique_ptr<RtpPacketToSend> GenerateSingleFlexfecPacket( + FlexfecSender* sender) { + // Parameters selected to generate a single FEC packet. + FecProtectionParams params; + params.fec_rate = 15; + params.max_fec_frames = 1; + params.fec_mask_type = kFecMaskRandom; + constexpr size_t kNumPackets = 4; + + sender->SetProtectionParameters(params, params); + AugmentedPacketGenerator packet_generator(kMediaSsrc); + packet_generator.NewFrame(kNumPackets); + for (size_t i = 0; i < kNumPackets; ++i) { + std::unique_ptr<AugmentedPacket> packet = + packet_generator.NextPacket(i, kPayloadLength); + RtpPacketToSend rtp_packet(nullptr); // No header extensions. + rtp_packet.Parse(packet->data); + sender->AddPacketAndGenerateFec(rtp_packet); + } + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + sender->GetFecPackets(); + EXPECT_EQ(1U, fec_packets.size()); + EXPECT_TRUE(sender->GetFecPackets().empty()); + + return std::move(fec_packets.front()); +} + +} // namespace + +TEST(FlexfecSenderTest, Ssrc) { + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + + EXPECT_EQ(kFlexfecSsrc, sender.FecSsrc()); +} + +TEST(FlexfecSenderTest, NoFecAvailableBeforeMediaAdded) { + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + + EXPECT_TRUE(sender.GetFecPackets().empty()); +} + +TEST(FlexfecSenderTest, ProtectOneFrameWithOneFecPacket) { + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + EXPECT_EQ(kRtpHeaderSize, fec_packet->headers_size()); + EXPECT_FALSE(fec_packet->Marker()); + EXPECT_EQ(kFlexfecPayloadType, fec_packet->PayloadType()); + EXPECT_EQ(kDeterministicSequenceNumber, fec_packet->SequenceNumber()); + EXPECT_EQ(kDeterministicTimestamp, fec_packet->Timestamp()); + EXPECT_EQ(kFlexfecSsrc, fec_packet->Ssrc()); + EXPECT_LE(kPayloadLength, fec_packet->payload_size()); +} + +TEST(FlexfecSenderTest, ProtectTwoFramesWithOneFecPacket) { + // FEC parameters selected to generate a single FEC packet per frame. + FecProtectionParams params; + params.fec_rate = 15; + params.max_fec_frames = 2; + params.fec_mask_type = kFecMaskRandom; + constexpr size_t kNumFrames = 2; + constexpr size_t kNumPacketsPerFrame = 2; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + sender.SetProtectionParameters(params, params); + + AugmentedPacketGenerator packet_generator(kMediaSsrc); + for (size_t i = 0; i < kNumFrames; ++i) { + packet_generator.NewFrame(kNumPacketsPerFrame); + for (size_t j = 0; j < kNumPacketsPerFrame; ++j) { + std::unique_ptr<AugmentedPacket> packet = + packet_generator.NextPacket(i, kPayloadLength); + RtpPacketToSend rtp_packet(nullptr); + rtp_packet.Parse(packet->data); + sender.AddPacketAndGenerateFec(rtp_packet); + } + } + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + sender.GetFecPackets(); + ASSERT_EQ(1U, fec_packets.size()); + EXPECT_TRUE(sender.GetFecPackets().empty()); + + RtpPacketToSend* fec_packet = fec_packets.front().get(); + EXPECT_EQ(kRtpHeaderSize, fec_packet->headers_size()); + EXPECT_FALSE(fec_packet->Marker()); + EXPECT_EQ(kFlexfecPayloadType, fec_packet->PayloadType()); + EXPECT_EQ(kDeterministicSequenceNumber, fec_packet->SequenceNumber()); + EXPECT_EQ(kDeterministicTimestamp, fec_packet->Timestamp()); + EXPECT_EQ(kFlexfecSsrc, fec_packet->Ssrc()); +} + +TEST(FlexfecSenderTest, ProtectTwoFramesWithTwoFecPackets) { + // FEC parameters selected to generate a single FEC packet per frame. + FecProtectionParams params; + params.fec_rate = 30; + params.max_fec_frames = 1; + params.fec_mask_type = kFecMaskRandom; + constexpr size_t kNumFrames = 2; + constexpr size_t kNumPacketsPerFrame = 2; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + sender.SetProtectionParameters(params, params); + + AugmentedPacketGenerator packet_generator(kMediaSsrc); + for (size_t i = 0; i < kNumFrames; ++i) { + packet_generator.NewFrame(kNumPacketsPerFrame); + for (size_t j = 0; j < kNumPacketsPerFrame; ++j) { + std::unique_ptr<AugmentedPacket> packet = + packet_generator.NextPacket(i, kPayloadLength); + RtpPacketToSend rtp_packet(nullptr); + rtp_packet.Parse(packet->data); + sender.AddPacketAndGenerateFec(rtp_packet); + } + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + sender.GetFecPackets(); + ASSERT_EQ(1U, fec_packets.size()); + EXPECT_TRUE(sender.GetFecPackets().empty()); + + RtpPacketToSend* fec_packet = fec_packets.front().get(); + EXPECT_EQ(kRtpHeaderSize, fec_packet->headers_size()); + EXPECT_FALSE(fec_packet->Marker()); + EXPECT_EQ(kFlexfecPayloadType, fec_packet->PayloadType()); + EXPECT_EQ(static_cast<uint16_t>(kDeterministicSequenceNumber + i), + fec_packet->SequenceNumber()); + EXPECT_EQ(kDeterministicTimestamp, fec_packet->Timestamp()); + EXPECT_EQ(kFlexfecSsrc, fec_packet->Ssrc()); + } +} + +// In the tests, we only consider RTP header extensions that are useful for BWE. +TEST(FlexfecSenderTest, NoRtpHeaderExtensionsForBweByDefault) { + const std::vector<RtpExtension> kRtpHeaderExtensions{}; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + EXPECT_FALSE(fec_packet->HasExtension<AbsoluteSendTime>()); + EXPECT_FALSE(fec_packet->HasExtension<TransmissionOffset>()); + EXPECT_FALSE(fec_packet->HasExtension<TransportSequenceNumber>()); +} + +TEST(FlexfecSenderTest, RegisterAbsoluteSendTimeRtpHeaderExtension) { + const std::vector<RtpExtension> kRtpHeaderExtensions{ + {RtpExtension::kAbsSendTimeUri, 1}}; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + EXPECT_TRUE(fec_packet->HasExtension<AbsoluteSendTime>()); + EXPECT_FALSE(fec_packet->HasExtension<TransmissionOffset>()); + EXPECT_FALSE(fec_packet->HasExtension<TransportSequenceNumber>()); +} + +TEST(FlexfecSenderTest, RegisterTransmissionOffsetRtpHeaderExtension) { + const std::vector<RtpExtension> kRtpHeaderExtensions{ + {RtpExtension::kTimestampOffsetUri, 1}}; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + EXPECT_FALSE(fec_packet->HasExtension<AbsoluteSendTime>()); + EXPECT_TRUE(fec_packet->HasExtension<TransmissionOffset>()); + EXPECT_FALSE(fec_packet->HasExtension<TransportSequenceNumber>()); +} + +TEST(FlexfecSenderTest, RegisterTransportSequenceNumberRtpHeaderExtension) { + const std::vector<RtpExtension> kRtpHeaderExtensions{ + {RtpExtension::kTransportSequenceNumberUri, 1}}; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + EXPECT_FALSE(fec_packet->HasExtension<AbsoluteSendTime>()); + EXPECT_FALSE(fec_packet->HasExtension<TransmissionOffset>()); + EXPECT_TRUE(fec_packet->HasExtension<TransportSequenceNumber>()); +} + +TEST(FlexfecSenderTest, RegisterAllRtpHeaderExtensionsForBwe) { + const std::vector<RtpExtension> kRtpHeaderExtensions{ + {RtpExtension::kAbsSendTimeUri, 1}, + {RtpExtension::kTimestampOffsetUri, 2}, + {RtpExtension::kTransportSequenceNumberUri, 3}}; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + EXPECT_TRUE(fec_packet->HasExtension<AbsoluteSendTime>()); + EXPECT_TRUE(fec_packet->HasExtension<TransmissionOffset>()); + EXPECT_TRUE(fec_packet->HasExtension<TransportSequenceNumber>()); +} + +TEST(FlexfecSenderTest, MaxPacketOverhead) { + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + nullptr /* rtp_state */, &clock); + + EXPECT_EQ(kFlexfecMaxHeaderSize, sender.MaxPacketOverhead()); +} + +TEST(FlexfecSenderTest, MaxPacketOverheadWithExtensions) { + const std::vector<RtpExtension> kRtpHeaderExtensions{ + {RtpExtension::kAbsSendTimeUri, 1}, + {RtpExtension::kTimestampOffsetUri, 2}, + {RtpExtension::kTransportSequenceNumberUri, 3}}; + SimulatedClock clock(kInitialSimulatedClockTime); + const size_t kExtensionHeaderLength = 1; + const size_t kRtpOneByteHeaderLength = 4; + const size_t kExtensionsTotalSize = + Word32Align(kRtpOneByteHeaderLength + kExtensionHeaderLength + + AbsoluteSendTime::kValueSizeBytes + kExtensionHeaderLength + + TransmissionOffset::kValueSizeBytes + kExtensionHeaderLength + + TransportSequenceNumber::kValueSizeBytes); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kRtpHeaderExtensions, RTPSender::FecExtensionSizes(), + nullptr /* rtp_state */, &clock); + + EXPECT_EQ(kExtensionsTotalSize + kFlexfecMaxHeaderSize, + sender.MaxPacketOverhead()); +} + +TEST(FlexfecSenderTest, MidIncludedInPacketsWhenSet) { + const std::vector<RtpExtension> kRtpHeaderExtensions{ + {RtpExtension::kMidUri, 1}}; + const char kMid[] = "mid"; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kMid, + kRtpHeaderExtensions, RTPSender::FecExtensionSizes(), + nullptr /* rtp_state */, &clock); + + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + + std::string mid; + ASSERT_TRUE(fec_packet->GetExtension<RtpMid>(&mid)); + EXPECT_EQ(kMid, mid); +} + +TEST(FlexfecSenderTest, SetsAndGetsRtpState) { + RtpState initial_rtp_state; + initial_rtp_state.sequence_number = 100; + initial_rtp_state.start_timestamp = 200; + SimulatedClock clock(kInitialSimulatedClockTime); + FlexfecSender sender(kFlexfecPayloadType, kFlexfecSsrc, kMediaSsrc, kNoMid, + kNoRtpHeaderExtensions, kNoRtpHeaderExtensionSizes, + &initial_rtp_state, &clock); + + auto fec_packet = GenerateSingleFlexfecPacket(&sender); + EXPECT_EQ(initial_rtp_state.sequence_number, fec_packet->SequenceNumber()); + EXPECT_EQ(initial_rtp_state.start_timestamp, fec_packet->Timestamp()); + + clock.AdvanceTimeMilliseconds(1000); + fec_packet = GenerateSingleFlexfecPacket(&sender); + EXPECT_EQ(initial_rtp_state.sequence_number + 1, + fec_packet->SequenceNumber()); + EXPECT_EQ(initial_rtp_state.start_timestamp + 1 * kVideoPayloadTypeFrequency, + fec_packet->Timestamp()); + + RtpState updated_rtp_state = sender.GetRtpState().value(); + EXPECT_EQ(initial_rtp_state.sequence_number + 2, + updated_rtp_state.sequence_number); + EXPECT_EQ(initial_rtp_state.start_timestamp, + updated_rtp_state.start_timestamp); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.cc new file mode 100644 index 0000000000..1462c2f481 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.cc @@ -0,0 +1,816 @@ +/* + * 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 "modules/rtp_rtcp/source/forward_error_correction.h" + +#include <string.h> + +#include <algorithm> +#include <utility> + +#include "absl/algorithm/container.h" +#include "modules/include/module_common_types_public.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/flexfec_header_reader_writer.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "modules/rtp_rtcp/source/ulpfec_header_reader_writer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/mod_ops.h" + +namespace webrtc { + +namespace { +// Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum. +constexpr size_t kTransportOverhead = 28; + +constexpr uint16_t kOldSequenceThreshold = 0x3fff; +} // namespace + +ForwardErrorCorrection::Packet::Packet() : data(0), ref_count_(0) {} +ForwardErrorCorrection::Packet::~Packet() = default; + +int32_t ForwardErrorCorrection::Packet::AddRef() { + return ++ref_count_; +} + +int32_t ForwardErrorCorrection::Packet::Release() { + int32_t ref_count; + ref_count = --ref_count_; + if (ref_count == 0) + delete this; + return ref_count; +} + +// This comparator is used to compare std::unique_ptr's pointing to +// subclasses of SortablePackets. It needs to be parametric since +// the std::unique_ptr's are not covariant w.r.t. the types that +// they are pointing to. +template <typename S, typename T> +bool ForwardErrorCorrection::SortablePacket::LessThan::operator()( + const S& first, + const T& second) { + RTC_DCHECK_EQ(first->ssrc, second->ssrc); + return IsNewerSequenceNumber(second->seq_num, first->seq_num); +} + +ForwardErrorCorrection::ReceivedPacket::ReceivedPacket() = default; +ForwardErrorCorrection::ReceivedPacket::~ReceivedPacket() = default; + +ForwardErrorCorrection::RecoveredPacket::RecoveredPacket() = default; +ForwardErrorCorrection::RecoveredPacket::~RecoveredPacket() = default; + +ForwardErrorCorrection::ProtectedPacket::ProtectedPacket() = default; +ForwardErrorCorrection::ProtectedPacket::~ProtectedPacket() = default; + +ForwardErrorCorrection::ReceivedFecPacket::ReceivedFecPacket() = default; +ForwardErrorCorrection::ReceivedFecPacket::~ReceivedFecPacket() = default; + +ForwardErrorCorrection::ForwardErrorCorrection( + std::unique_ptr<FecHeaderReader> fec_header_reader, + std::unique_ptr<FecHeaderWriter> fec_header_writer, + uint32_t ssrc, + uint32_t protected_media_ssrc) + : ssrc_(ssrc), + protected_media_ssrc_(protected_media_ssrc), + fec_header_reader_(std::move(fec_header_reader)), + fec_header_writer_(std::move(fec_header_writer)), + generated_fec_packets_(fec_header_writer_->MaxFecPackets()), + packet_mask_size_(0) {} + +ForwardErrorCorrection::~ForwardErrorCorrection() = default; + +std::unique_ptr<ForwardErrorCorrection> ForwardErrorCorrection::CreateUlpfec( + uint32_t ssrc) { + std::unique_ptr<FecHeaderReader> fec_header_reader(new UlpfecHeaderReader()); + std::unique_ptr<FecHeaderWriter> fec_header_writer(new UlpfecHeaderWriter()); + return std::unique_ptr<ForwardErrorCorrection>(new ForwardErrorCorrection( + std::move(fec_header_reader), std::move(fec_header_writer), ssrc, ssrc)); +} + +std::unique_ptr<ForwardErrorCorrection> ForwardErrorCorrection::CreateFlexfec( + uint32_t ssrc, + uint32_t protected_media_ssrc) { + std::unique_ptr<FecHeaderReader> fec_header_reader(new FlexfecHeaderReader()); + std::unique_ptr<FecHeaderWriter> fec_header_writer(new FlexfecHeaderWriter()); + return std::unique_ptr<ForwardErrorCorrection>(new ForwardErrorCorrection( + std::move(fec_header_reader), std::move(fec_header_writer), ssrc, + protected_media_ssrc)); +} + +int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets, + uint8_t protection_factor, + int num_important_packets, + bool use_unequal_protection, + FecMaskType fec_mask_type, + std::list<Packet*>* fec_packets) { + const size_t num_media_packets = media_packets.size(); + + // Sanity check arguments. + RTC_DCHECK_GT(num_media_packets, 0); + RTC_DCHECK_GE(num_important_packets, 0); + RTC_DCHECK_LE(num_important_packets, num_media_packets); + RTC_DCHECK(fec_packets->empty()); + const size_t max_media_packets = fec_header_writer_->MaxMediaPackets(); + if (num_media_packets > max_media_packets) { + RTC_LOG(LS_WARNING) << "Can't protect " << num_media_packets + << " media packets per frame. Max is " + << max_media_packets << "."; + return -1; + } + + // Error check the media packets. + for (const auto& media_packet : media_packets) { + RTC_DCHECK(media_packet); + if (media_packet->data.size() < kRtpHeaderSize) { + RTC_LOG(LS_WARNING) << "Media packet " << media_packet->data.size() + << " bytes " + "is smaller than RTP header."; + return -1; + } + // Ensure the FEC packets will fit in a typical MTU. + if (media_packet->data.size() + MaxPacketOverhead() + kTransportOverhead > + IP_PACKET_SIZE) { + RTC_LOG(LS_WARNING) << "Media packet " << media_packet->data.size() + << " bytes " + "with overhead is larger than " + << IP_PACKET_SIZE << " bytes."; + } + } + + // Prepare generated FEC packets. + int num_fec_packets = NumFecPackets(num_media_packets, protection_factor); + if (num_fec_packets == 0) { + return 0; + } + for (int i = 0; i < num_fec_packets; ++i) { + generated_fec_packets_[i].data.EnsureCapacity(IP_PACKET_SIZE); + memset(generated_fec_packets_[i].data.MutableData(), 0, IP_PACKET_SIZE); + // Use this as a marker for untouched packets. + generated_fec_packets_[i].data.SetSize(0); + fec_packets->push_back(&generated_fec_packets_[i]); + } + + internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets); + packet_mask_size_ = internal::PacketMaskSize(num_media_packets); + memset(packet_masks_, 0, num_fec_packets * packet_mask_size_); + internal::GeneratePacketMasks(num_media_packets, num_fec_packets, + num_important_packets, use_unequal_protection, + &mask_table, packet_masks_); + + // Adapt packet masks to missing media packets. + int num_mask_bits = InsertZerosInPacketMasks(media_packets, num_fec_packets); + if (num_mask_bits < 0) { + RTC_LOG(LS_INFO) << "Due to sequence number gaps, cannot protect media " + "packets with a single block of FEC packets."; + fec_packets->clear(); + return -1; + } + packet_mask_size_ = internal::PacketMaskSize(num_mask_bits); + + // Write FEC packets to `generated_fec_packets_`. + GenerateFecPayloads(media_packets, num_fec_packets); + // TODO(brandtr): Generalize this when multistream protection support is + // added. + const uint32_t media_ssrc = ParseSsrc(media_packets.front()->data.data()); + const uint16_t seq_num_base = + ParseSequenceNumber(media_packets.front()->data.data()); + FinalizeFecHeaders(num_fec_packets, media_ssrc, seq_num_base); + + return 0; +} + +int ForwardErrorCorrection::NumFecPackets(int num_media_packets, + int protection_factor) { + // Result in Q0 with an unsigned round. + int num_fec_packets = (num_media_packets * protection_factor + (1 << 7)) >> 8; + // Generate at least one FEC packet if we need protection. + if (protection_factor > 0 && num_fec_packets == 0) { + num_fec_packets = 1; + } + RTC_DCHECK_LE(num_fec_packets, num_media_packets); + return num_fec_packets; +} + +void ForwardErrorCorrection::GenerateFecPayloads( + const PacketList& media_packets, + size_t num_fec_packets) { + RTC_DCHECK(!media_packets.empty()); + for (size_t i = 0; i < num_fec_packets; ++i) { + Packet* const fec_packet = &generated_fec_packets_[i]; + size_t pkt_mask_idx = i * packet_mask_size_; + const size_t min_packet_mask_size = fec_header_writer_->MinPacketMaskSize( + &packet_masks_[pkt_mask_idx], packet_mask_size_); + const size_t fec_header_size = + fec_header_writer_->FecHeaderSize(min_packet_mask_size); + + size_t media_pkt_idx = 0; + auto media_packets_it = media_packets.cbegin(); + uint16_t prev_seq_num = + ParseSequenceNumber((*media_packets_it)->data.data()); + while (media_packets_it != media_packets.end()) { + Packet* const media_packet = media_packets_it->get(); + // Should `media_packet` be protected by `fec_packet`? + if (packet_masks_[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) { + size_t media_payload_length = + media_packet->data.size() - kRtpHeaderSize; + + size_t fec_packet_length = fec_header_size + media_payload_length; + if (fec_packet_length > fec_packet->data.size()) { + size_t old_size = fec_packet->data.size(); + fec_packet->data.SetSize(fec_packet_length); + memset(fec_packet->data.MutableData() + old_size, 0, + fec_packet_length - old_size); + } + XorHeaders(*media_packet, fec_packet); + XorPayloads(*media_packet, media_payload_length, fec_header_size, + fec_packet); + } + media_packets_it++; + if (media_packets_it != media_packets.end()) { + uint16_t seq_num = + ParseSequenceNumber((*media_packets_it)->data.data()); + media_pkt_idx += static_cast<uint16_t>(seq_num - prev_seq_num); + prev_seq_num = seq_num; + } + pkt_mask_idx += media_pkt_idx / 8; + media_pkt_idx %= 8; + } + RTC_DCHECK_GT(fec_packet->data.size(), 0) + << "Packet mask is wrong or poorly designed."; + } +} + +int ForwardErrorCorrection::InsertZerosInPacketMasks( + const PacketList& media_packets, + size_t num_fec_packets) { + size_t num_media_packets = media_packets.size(); + if (num_media_packets <= 1) { + return num_media_packets; + } + uint16_t last_seq_num = + ParseSequenceNumber(media_packets.back()->data.data()); + uint16_t first_seq_num = + ParseSequenceNumber(media_packets.front()->data.data()); + size_t total_missing_seq_nums = + static_cast<uint16_t>(last_seq_num - first_seq_num) - num_media_packets + + 1; + if (total_missing_seq_nums == 0) { + // All sequence numbers are covered by the packet mask. + // No zero insertion required. + return num_media_packets; + } + const size_t max_media_packets = fec_header_writer_->MaxMediaPackets(); + if (total_missing_seq_nums + num_media_packets > max_media_packets) { + return -1; + } + // Allocate the new mask. + size_t tmp_packet_mask_size = + internal::PacketMaskSize(total_missing_seq_nums + num_media_packets); + memset(tmp_packet_masks_, 0, num_fec_packets * tmp_packet_mask_size); + + auto media_packets_it = media_packets.cbegin(); + uint16_t prev_seq_num = first_seq_num; + ++media_packets_it; + + // Insert the first column. + internal::CopyColumn(tmp_packet_masks_, tmp_packet_mask_size, packet_masks_, + packet_mask_size_, num_fec_packets, 0, 0); + size_t new_bit_index = 1; + size_t old_bit_index = 1; + // Insert zeros in the bit mask for every hole in the sequence. + while (media_packets_it != media_packets.end()) { + if (new_bit_index == max_media_packets) { + // We can only cover up to 48 packets. + break; + } + uint16_t seq_num = ParseSequenceNumber((*media_packets_it)->data.data()); + const int num_zeros_to_insert = + static_cast<uint16_t>(seq_num - prev_seq_num - 1); + if (num_zeros_to_insert > 0) { + internal::InsertZeroColumns(num_zeros_to_insert, tmp_packet_masks_, + tmp_packet_mask_size, num_fec_packets, + new_bit_index); + } + new_bit_index += num_zeros_to_insert; + internal::CopyColumn(tmp_packet_masks_, tmp_packet_mask_size, packet_masks_, + packet_mask_size_, num_fec_packets, new_bit_index, + old_bit_index); + ++new_bit_index; + ++old_bit_index; + prev_seq_num = seq_num; + ++media_packets_it; + } + if (new_bit_index % 8 != 0) { + // We didn't fill the last byte. Shift bits to correct position. + for (uint16_t row = 0; row < num_fec_packets; ++row) { + int new_byte_index = row * tmp_packet_mask_size + new_bit_index / 8; + tmp_packet_masks_[new_byte_index] <<= (7 - (new_bit_index % 8)); + } + } + // Replace the old mask with the new. + memcpy(packet_masks_, tmp_packet_masks_, + num_fec_packets * tmp_packet_mask_size); + return new_bit_index; +} + +void ForwardErrorCorrection::FinalizeFecHeaders(size_t num_fec_packets, + uint32_t media_ssrc, + uint16_t seq_num_base) { + for (size_t i = 0; i < num_fec_packets; ++i) { + fec_header_writer_->FinalizeFecHeader( + media_ssrc, seq_num_base, &packet_masks_[i * packet_mask_size_], + packet_mask_size_, &generated_fec_packets_[i]); + } +} + +void ForwardErrorCorrection::ResetState( + RecoveredPacketList* recovered_packets) { + // Free the memory for any existing recovered packets, if the caller hasn't. + recovered_packets->clear(); + received_fec_packets_.clear(); +} + +void ForwardErrorCorrection::InsertMediaPacket( + RecoveredPacketList* recovered_packets, + const ReceivedPacket& received_packet) { + RTC_DCHECK_EQ(received_packet.ssrc, protected_media_ssrc_); + + // Search for duplicate packets. + for (const auto& recovered_packet : *recovered_packets) { + RTC_DCHECK_EQ(recovered_packet->ssrc, received_packet.ssrc); + if (recovered_packet->seq_num == received_packet.seq_num) { + // Duplicate packet, no need to add to list. + return; + } + } + + std::unique_ptr<RecoveredPacket> recovered_packet(new RecoveredPacket()); + // This "recovered packet" was not recovered using parity packets. + recovered_packet->was_recovered = false; + // This media packet has already been passed on. + recovered_packet->returned = true; + recovered_packet->ssrc = received_packet.ssrc; + recovered_packet->seq_num = received_packet.seq_num; + recovered_packet->pkt = received_packet.pkt; + // TODO(holmer): Consider replacing this with a binary search for the right + // position, and then just insert the new packet. Would get rid of the sort. + RecoveredPacket* recovered_packet_ptr = recovered_packet.get(); + recovered_packets->push_back(std::move(recovered_packet)); + recovered_packets->sort(SortablePacket::LessThan()); + UpdateCoveringFecPackets(*recovered_packet_ptr); +} + +void ForwardErrorCorrection::UpdateCoveringFecPackets( + const RecoveredPacket& packet) { + for (auto& fec_packet : received_fec_packets_) { + // Is this FEC packet protecting the media packet `packet`? + auto protected_it = absl::c_lower_bound( + fec_packet->protected_packets, &packet, SortablePacket::LessThan()); + if (protected_it != fec_packet->protected_packets.end() && + (*protected_it)->seq_num == packet.seq_num) { + // Found an FEC packet which is protecting `packet`. + (*protected_it)->pkt = packet.pkt; + } + } +} + +void ForwardErrorCorrection::InsertFecPacket( + const RecoveredPacketList& recovered_packets, + const ReceivedPacket& received_packet) { + RTC_DCHECK_EQ(received_packet.ssrc, ssrc_); + + // Check for duplicate. + for (const auto& existing_fec_packet : received_fec_packets_) { + RTC_DCHECK_EQ(existing_fec_packet->ssrc, received_packet.ssrc); + if (existing_fec_packet->seq_num == received_packet.seq_num) { + // Drop duplicate FEC packet data. + return; + } + } + + std::unique_ptr<ReceivedFecPacket> fec_packet(new ReceivedFecPacket()); + fec_packet->pkt = received_packet.pkt; + fec_packet->ssrc = received_packet.ssrc; + fec_packet->seq_num = received_packet.seq_num; + // Parse ULPFEC/FlexFEC header specific info. + bool ret = fec_header_reader_->ReadFecHeader(fec_packet.get()); + if (!ret) { + return; + } + + // TODO(brandtr): Update here when we support multistream protection. + if (fec_packet->protected_ssrc != protected_media_ssrc_) { + RTC_LOG(LS_INFO) + << "Received FEC packet is protecting an unknown media SSRC; dropping."; + return; + } + + if (fec_packet->packet_mask_offset + fec_packet->packet_mask_size > + fec_packet->pkt->data.size()) { + RTC_LOG(LS_INFO) << "Received corrupted FEC packet; dropping."; + return; + } + + // Parse packet mask from header and represent as protected packets. + for (uint16_t byte_idx = 0; byte_idx < fec_packet->packet_mask_size; + ++byte_idx) { + uint8_t packet_mask = + fec_packet->pkt->data[fec_packet->packet_mask_offset + byte_idx]; + for (uint16_t bit_idx = 0; bit_idx < 8; ++bit_idx) { + if (packet_mask & (1 << (7 - bit_idx))) { + std::unique_ptr<ProtectedPacket> protected_packet( + new ProtectedPacket()); + // This wraps naturally with the sequence number. + protected_packet->ssrc = protected_media_ssrc_; + protected_packet->seq_num = static_cast<uint16_t>( + fec_packet->seq_num_base + (byte_idx << 3) + bit_idx); + protected_packet->pkt = nullptr; + fec_packet->protected_packets.push_back(std::move(protected_packet)); + } + } + } + + if (fec_packet->protected_packets.empty()) { + // All-zero packet mask; we can discard this FEC packet. + RTC_LOG(LS_WARNING) << "Received FEC packet has an all-zero packet mask."; + } else { + AssignRecoveredPackets(recovered_packets, fec_packet.get()); + // TODO(holmer): Consider replacing this with a binary search for the right + // position, and then just insert the new packet. Would get rid of the sort. + received_fec_packets_.push_back(std::move(fec_packet)); + received_fec_packets_.sort(SortablePacket::LessThan()); + const size_t max_fec_packets = fec_header_reader_->MaxFecPackets(); + if (received_fec_packets_.size() > max_fec_packets) { + received_fec_packets_.pop_front(); + } + RTC_DCHECK_LE(received_fec_packets_.size(), max_fec_packets); + } +} + +void ForwardErrorCorrection::AssignRecoveredPackets( + const RecoveredPacketList& recovered_packets, + ReceivedFecPacket* fec_packet) { + ProtectedPacketList* protected_packets = &fec_packet->protected_packets; + std::vector<RecoveredPacket*> recovered_protected_packets; + + // Find intersection between the (sorted) containers `protected_packets` + // and `recovered_packets`, i.e. all protected packets that have already + // been recovered. Update the corresponding protected packets to point to + // the recovered packets. + auto it_p = protected_packets->cbegin(); + auto it_r = recovered_packets.cbegin(); + SortablePacket::LessThan less_than; + while (it_p != protected_packets->end() && it_r != recovered_packets.end()) { + if (less_than(*it_p, *it_r)) { + ++it_p; + } else if (less_than(*it_r, *it_p)) { + ++it_r; + } else { // *it_p == *it_r. + // This protected packet has already been recovered. + (*it_p)->pkt = (*it_r)->pkt; + ++it_p; + ++it_r; + } + } +} + +void ForwardErrorCorrection::InsertPacket( + const ReceivedPacket& received_packet, + RecoveredPacketList* recovered_packets) { + // Discard old FEC packets such that the sequence numbers in + // `received_fec_packets_` span at most 1/2 of the sequence number space. + // This is important for keeping `received_fec_packets_` sorted, and may + // also reduce the possibility of incorrect decoding due to sequence number + // wrap-around. + if (!received_fec_packets_.empty() && + received_packet.ssrc == received_fec_packets_.front()->ssrc) { + // It only makes sense to detect wrap-around when `received_packet` + // and `front_received_fec_packet` belong to the same sequence number + // space, i.e., the same SSRC. This happens when `received_packet` + // is a FEC packet, or if `received_packet` is a media packet and + // RED+ULPFEC is used. + auto it = received_fec_packets_.begin(); + while (it != received_fec_packets_.end()) { + uint16_t seq_num_diff = MinDiff(received_packet.seq_num, (*it)->seq_num); + if (seq_num_diff > kOldSequenceThreshold) { + it = received_fec_packets_.erase(it); + } else { + // No need to keep iterating, since `received_fec_packets_` is sorted. + break; + } + } + } + + if (received_packet.is_fec) { + InsertFecPacket(*recovered_packets, received_packet); + } else { + InsertMediaPacket(recovered_packets, received_packet); + } + + DiscardOldRecoveredPackets(recovered_packets); +} + +bool ForwardErrorCorrection::StartPacketRecovery( + const ReceivedFecPacket& fec_packet, + RecoveredPacket* recovered_packet) { + // Ensure pkt is initialized. + recovered_packet->pkt = new Packet(); + // Sanity check packet length. + if (fec_packet.pkt->data.size() < + fec_packet.fec_header_size + fec_packet.protection_length) { + RTC_LOG(LS_WARNING) + << "The FEC packet is truncated: it does not contain enough room " + "for its own header."; + return false; + } + if (fec_packet.protection_length > + std::min(size_t{IP_PACKET_SIZE - kRtpHeaderSize}, + IP_PACKET_SIZE - fec_packet.fec_header_size)) { + RTC_LOG(LS_WARNING) << "Incorrect protection length, dropping FEC packet."; + return false; + } + // Initialize recovered packet data. + recovered_packet->pkt->data.EnsureCapacity(IP_PACKET_SIZE); + recovered_packet->pkt->data.SetSize(fec_packet.protection_length + + kRtpHeaderSize); + recovered_packet->returned = false; + recovered_packet->was_recovered = true; + // Copy bytes corresponding to minimum RTP header size. + // Note that the sequence number and SSRC fields will be overwritten + // at the end of packet recovery. + memcpy(recovered_packet->pkt->data.MutableData(), + fec_packet.pkt->data.cdata(), kRtpHeaderSize); + // Copy remaining FEC payload. + if (fec_packet.protection_length > 0) { + memcpy(recovered_packet->pkt->data.MutableData() + kRtpHeaderSize, + fec_packet.pkt->data.cdata() + fec_packet.fec_header_size, + fec_packet.protection_length); + } + return true; +} + +bool ForwardErrorCorrection::FinishPacketRecovery( + const ReceivedFecPacket& fec_packet, + RecoveredPacket* recovered_packet) { + uint8_t* data = recovered_packet->pkt->data.MutableData(); + // Set the RTP version to 2. + data[0] |= 0x80; // Set the 1st bit. + data[0] &= 0xbf; // Clear the 2nd bit. + // Recover the packet length, from temporary location. + const size_t new_size = + ByteReader<uint16_t>::ReadBigEndian(&data[2]) + kRtpHeaderSize; + if (new_size > size_t{IP_PACKET_SIZE - kRtpHeaderSize}) { + RTC_LOG(LS_WARNING) << "The recovered packet had a length larger than a " + "typical IP packet, and is thus dropped."; + return false; + } + size_t old_size = recovered_packet->pkt->data.size(); + recovered_packet->pkt->data.SetSize(new_size); + data = recovered_packet->pkt->data.MutableData(); + if (new_size > old_size) { + memset(data + old_size, 0, new_size - old_size); + } + + // Set the SN field. + ByteWriter<uint16_t>::WriteBigEndian(&data[2], recovered_packet->seq_num); + // Set the SSRC field. + ByteWriter<uint32_t>::WriteBigEndian(&data[8], fec_packet.protected_ssrc); + recovered_packet->ssrc = fec_packet.protected_ssrc; + return true; +} + +void ForwardErrorCorrection::XorHeaders(const Packet& src, Packet* dst) { + uint8_t* dst_data = dst->data.MutableData(); + const uint8_t* src_data = src.data.cdata(); + // XOR the first 2 bytes of the header: V, P, X, CC, M, PT fields. + dst_data[0] ^= src_data[0]; + dst_data[1] ^= src_data[1]; + + // XOR the length recovery field. + uint8_t src_payload_length_network_order[2]; + ByteWriter<uint16_t>::WriteBigEndian(src_payload_length_network_order, + src.data.size() - kRtpHeaderSize); + dst_data[2] ^= src_payload_length_network_order[0]; + dst_data[3] ^= src_payload_length_network_order[1]; + + // XOR the 5th to 8th bytes of the header: the timestamp field. + dst_data[4] ^= src_data[4]; + dst_data[5] ^= src_data[5]; + dst_data[6] ^= src_data[6]; + dst_data[7] ^= src_data[7]; + + // Skip the 9th to 12th bytes of the header. +} + +void ForwardErrorCorrection::XorPayloads(const Packet& src, + size_t payload_length, + size_t dst_offset, + Packet* dst) { + // XOR the payload. + RTC_DCHECK_LE(kRtpHeaderSize + payload_length, src.data.size()); + RTC_DCHECK_LE(dst_offset + payload_length, dst->data.capacity()); + if (dst_offset + payload_length > dst->data.size()) { + size_t old_size = dst->data.size(); + size_t new_size = dst_offset + payload_length; + dst->data.SetSize(new_size); + memset(dst->data.MutableData() + old_size, 0, new_size - old_size); + } + uint8_t* dst_data = dst->data.MutableData(); + const uint8_t* src_data = src.data.cdata(); + for (size_t i = 0; i < payload_length; ++i) { + dst_data[dst_offset + i] ^= src_data[kRtpHeaderSize + i]; + } +} + +bool ForwardErrorCorrection::RecoverPacket(const ReceivedFecPacket& fec_packet, + RecoveredPacket* recovered_packet) { + if (!StartPacketRecovery(fec_packet, recovered_packet)) { + return false; + } + for (const auto& protected_packet : fec_packet.protected_packets) { + if (protected_packet->pkt == nullptr) { + // This is the packet we're recovering. + recovered_packet->seq_num = protected_packet->seq_num; + } else { + XorHeaders(*protected_packet->pkt, recovered_packet->pkt.get()); + XorPayloads(*protected_packet->pkt, + protected_packet->pkt->data.size() - kRtpHeaderSize, + kRtpHeaderSize, recovered_packet->pkt.get()); + } + } + if (!FinishPacketRecovery(fec_packet, recovered_packet)) { + return false; + } + return true; +} + +void ForwardErrorCorrection::AttemptRecovery( + RecoveredPacketList* recovered_packets) { + auto fec_packet_it = received_fec_packets_.begin(); + while (fec_packet_it != received_fec_packets_.end()) { + // Search for each FEC packet's protected media packets. + int packets_missing = NumCoveredPacketsMissing(**fec_packet_it); + + // We can only recover one packet with an FEC packet. + if (packets_missing == 1) { + // Recovery possible. + std::unique_ptr<RecoveredPacket> recovered_packet(new RecoveredPacket()); + recovered_packet->pkt = nullptr; + if (!RecoverPacket(**fec_packet_it, recovered_packet.get())) { + // Can't recover using this packet, drop it. + fec_packet_it = received_fec_packets_.erase(fec_packet_it); + continue; + } + + auto* recovered_packet_ptr = recovered_packet.get(); + // Add recovered packet to the list of recovered packets and update any + // FEC packets covering this packet with a pointer to the data. + // TODO(holmer): Consider replacing this with a binary search for the + // right position, and then just insert the new packet. Would get rid of + // the sort. + recovered_packets->push_back(std::move(recovered_packet)); + recovered_packets->sort(SortablePacket::LessThan()); + UpdateCoveringFecPackets(*recovered_packet_ptr); + DiscardOldRecoveredPackets(recovered_packets); + fec_packet_it = received_fec_packets_.erase(fec_packet_it); + + // A packet has been recovered. We need to check the FEC list again, as + // this may allow additional packets to be recovered. + // Restart for first FEC packet. + fec_packet_it = received_fec_packets_.begin(); + } else if (packets_missing == 0 || + IsOldFecPacket(**fec_packet_it, recovered_packets)) { + // Either all protected packets arrived or have been recovered, or the FEC + // packet is old. We can discard this FEC packet. + fec_packet_it = received_fec_packets_.erase(fec_packet_it); + } else { + fec_packet_it++; + } + } +} + +int ForwardErrorCorrection::NumCoveredPacketsMissing( + const ReceivedFecPacket& fec_packet) { + int packets_missing = 0; + for (const auto& protected_packet : fec_packet.protected_packets) { + if (protected_packet->pkt == nullptr) { + ++packets_missing; + if (packets_missing > 1) { + break; // We can't recover more than one packet. + } + } + } + return packets_missing; +} + +void ForwardErrorCorrection::DiscardOldRecoveredPackets( + RecoveredPacketList* recovered_packets) { + const size_t max_media_packets = fec_header_reader_->MaxMediaPackets(); + while (recovered_packets->size() > max_media_packets) { + recovered_packets->pop_front(); + } + RTC_DCHECK_LE(recovered_packets->size(), max_media_packets); +} + +bool ForwardErrorCorrection::IsOldFecPacket( + const ReceivedFecPacket& fec_packet, + const RecoveredPacketList* recovered_packets) { + if (recovered_packets->empty()) { + return false; + } + + const uint16_t back_recovered_seq_num = recovered_packets->back()->seq_num; + const uint16_t last_protected_seq_num = + fec_packet.protected_packets.back()->seq_num; + + // FEC packet is old if its last protected sequence number is much + // older than the latest protected sequence number received. + return (MinDiff(back_recovered_seq_num, last_protected_seq_num) > + kOldSequenceThreshold); +} + +uint16_t ForwardErrorCorrection::ParseSequenceNumber(const uint8_t* packet) { + return (packet[2] << 8) + packet[3]; +} + +uint32_t ForwardErrorCorrection::ParseSsrc(const uint8_t* packet) { + return (packet[8] << 24) + (packet[9] << 16) + (packet[10] << 8) + packet[11]; +} + +void ForwardErrorCorrection::DecodeFec(const ReceivedPacket& received_packet, + RecoveredPacketList* recovered_packets) { + RTC_DCHECK(recovered_packets); + + const size_t max_media_packets = fec_header_reader_->MaxMediaPackets(); + if (recovered_packets->size() == max_media_packets) { + const RecoveredPacket* back_recovered_packet = + recovered_packets->back().get(); + + if (received_packet.ssrc == back_recovered_packet->ssrc) { + const unsigned int seq_num_diff = + MinDiff(received_packet.seq_num, back_recovered_packet->seq_num); + if (seq_num_diff > max_media_packets) { + // A big gap in sequence numbers. The old recovered packets + // are now useless, so it's safe to do a reset. + RTC_LOG(LS_INFO) << "Big gap in media/ULPFEC sequence numbers. No need " + "to keep the old packets in the FEC buffers, thus " + "resetting them."; + ResetState(recovered_packets); + } + } + } + + InsertPacket(received_packet, recovered_packets); + AttemptRecovery(recovered_packets); +} + +size_t ForwardErrorCorrection::MaxPacketOverhead() const { + return fec_header_writer_->MaxPacketOverhead(); +} + +FecHeaderReader::FecHeaderReader(size_t max_media_packets, + size_t max_fec_packets) + : max_media_packets_(max_media_packets), + max_fec_packets_(max_fec_packets) {} + +FecHeaderReader::~FecHeaderReader() = default; + +size_t FecHeaderReader::MaxMediaPackets() const { + return max_media_packets_; +} + +size_t FecHeaderReader::MaxFecPackets() const { + return max_fec_packets_; +} + +FecHeaderWriter::FecHeaderWriter(size_t max_media_packets, + size_t max_fec_packets, + size_t max_packet_overhead) + : max_media_packets_(max_media_packets), + max_fec_packets_(max_fec_packets), + max_packet_overhead_(max_packet_overhead) {} + +FecHeaderWriter::~FecHeaderWriter() = default; + +size_t FecHeaderWriter::MaxMediaPackets() const { + return max_media_packets_; +} + +size_t FecHeaderWriter::MaxFecPackets() const { + return max_fec_packets_; +} + +size_t FecHeaderWriter::MaxPacketOverhead() const { + return max_packet_overhead_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.h b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.h new file mode 100644 index 0000000000..ac8f5361e4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.h @@ -0,0 +1,424 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_H_ +#define MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <list> +#include <memory> +#include <vector> + +#include "api/scoped_refptr.h" +#include "api/units/timestamp.h" +#include "modules/include/module_fec_types.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class FecHeaderReader; +class FecHeaderWriter; + +// Performs codec-independent forward error correction (FEC), based on RFC 5109. +// Option exists to enable unequal protection (UEP) across packets. +// This is not to be confused with protection within packets +// (referred to as uneven level protection (ULP) in RFC 5109). +// TODO(brandtr): Split this class into a separate encoder +// and a separate decoder. +class ForwardErrorCorrection { + public: + // TODO(holmer): As a next step all these struct-like packet classes should be + // refactored into proper classes, and their members should be made private. + // This will require parts of the functionality in forward_error_correction.cc + // and receiver_fec.cc to be refactored into the packet classes. + class Packet { + public: + Packet(); + virtual ~Packet(); + + // Add a reference. + virtual int32_t AddRef(); + + // Release a reference. Will delete the object if the reference count + // reaches zero. + virtual int32_t Release(); + + rtc::CopyOnWriteBuffer data; // Packet data. + + private: + int32_t ref_count_; // Counts the number of references to a packet. + }; + + // TODO(holmer): Refactor into a proper class. + class SortablePacket { + public: + // Functor which returns true if the sequence number of `first` + // is < the sequence number of `second`. Should only ever be called for + // packets belonging to the same SSRC. + struct LessThan { + template <typename S, typename T> + bool operator()(const S& first, const T& second); + }; + + uint32_t ssrc; + uint16_t seq_num; + }; + + // Used for the input to DecodeFec(). + class ReceivedPacket : public SortablePacket { + public: + ReceivedPacket(); + ~ReceivedPacket(); + + bool is_fec; // Set to true if this is an FEC packet and false + // otherwise. + bool is_recovered; + RtpHeaderExtensionMap extensions; + rtc::scoped_refptr<Packet> pkt; // Pointer to the packet storage. + }; + + // The recovered list parameter of DecodeFec() references structs of + // this type. + // TODO(holmer): Refactor into a proper class. + class RecoveredPacket : public SortablePacket { + public: + RecoveredPacket(); + ~RecoveredPacket(); + + bool was_recovered; // Will be true if this packet was recovered by + // the FEC. Otherwise it was a media packet passed in + // through the received packet list. + bool returned; // True when the packet already has been returned to the + // caller through the callback. + rtc::scoped_refptr<Packet> pkt; // Pointer to the packet storage. + }; + + // Used to link media packets to their protecting FEC packets. + // + // TODO(holmer): Refactor into a proper class. + class ProtectedPacket : public SortablePacket { + public: + ProtectedPacket(); + ~ProtectedPacket(); + + rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt; + }; + + using ProtectedPacketList = std::list<std::unique_ptr<ProtectedPacket>>; + + // Used for internal storage of received FEC packets in a list. + // + // TODO(holmer): Refactor into a proper class. + class ReceivedFecPacket : public SortablePacket { + public: + ReceivedFecPacket(); + ~ReceivedFecPacket(); + + // List of media packets that this FEC packet protects. + ProtectedPacketList protected_packets; + // RTP header fields. + uint32_t ssrc; + // FEC header fields. + size_t fec_header_size; + uint32_t protected_ssrc; + uint16_t seq_num_base; + size_t packet_mask_offset; // Relative start of FEC header. + size_t packet_mask_size; + size_t protection_length; + // Raw data. + rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt; + }; + + using PacketList = std::list<std::unique_ptr<Packet>>; + using RecoveredPacketList = std::list<std::unique_ptr<RecoveredPacket>>; + using ReceivedFecPacketList = std::list<std::unique_ptr<ReceivedFecPacket>>; + + ~ForwardErrorCorrection(); + + // Creates a ForwardErrorCorrection tailored for a specific FEC scheme. + static std::unique_ptr<ForwardErrorCorrection> CreateUlpfec(uint32_t ssrc); + static std::unique_ptr<ForwardErrorCorrection> CreateFlexfec( + uint32_t ssrc, + uint32_t protected_media_ssrc); + + // Generates a list of FEC packets from supplied media packets. + // + // Input: media_packets List of media packets to protect, of type + // Packet. All packets must belong to the + // same frame and the list must not be empty. + // Input: protection_factor FEC protection overhead in the [0, 255] + // domain. To obtain 100% overhead, or an + // equal number of FEC packets as + // media packets, use 255. + // Input: num_important_packets The number of "important" packets in the + // frame. These packets may receive greater + // protection than the remaining packets. + // The important packets must be located at the + // start of the media packet list. For codecs + // with data partitioning, the important + // packets may correspond to first partition + // packets. + // Input: use_unequal_protection Parameter to enable/disable unequal + // protection (UEP) across packets. Enabling + // UEP will allocate more protection to the + // num_important_packets from the start of the + // media_packets. + // Input: fec_mask_type The type of packet mask used in the FEC. + // Random or bursty type may be selected. The + // bursty type is only defined up to 12 media + // packets. If the number of media packets is + // above 12, the packet masks from the random + // table will be selected. + // Output: fec_packets List of pointers to generated FEC packets, + // of type Packet. Must be empty on entry. + // The memory available through the list will + // be valid until the next call to + // EncodeFec(). + // + // Returns 0 on success, -1 on failure. + // + int EncodeFec(const PacketList& media_packets, + uint8_t protection_factor, + int num_important_packets, + bool use_unequal_protection, + FecMaskType fec_mask_type, + std::list<Packet*>* fec_packets); + + // Decodes a list of received media and FEC packets. It will parse the + // `received_packets`, storing FEC packets internally, and move + // media packets to `recovered_packets`. The recovered list will be + // sorted by ascending sequence number and have duplicates removed. + // The function should be called as new packets arrive, and + // `recovered_packets` will be progressively assembled with each call. + // When the function returns, `received_packets` will be empty. + // + // The caller will allocate packets submitted through `received_packets`. + // The function will handle allocation of recovered packets. + // + // Input: received_packets List of new received packets, of type + // ReceivedPacket, belonging to a single + // frame. At output the list will be empty, + // with packets either stored internally, + // or accessible through the recovered list. + // Output: recovered_packets List of recovered media packets, of type + // RecoveredPacket, belonging to a single + // frame. The memory available through the + // list will be valid until the next call to + // DecodeFec(). + // + void DecodeFec(const ReceivedPacket& received_packet, + RecoveredPacketList* recovered_packets); + + // Get the number of generated FEC packets, given the number of media packets + // and the protection factor. + static int NumFecPackets(int num_media_packets, int protection_factor); + + // Gets the maximum size of the FEC headers in bytes, which must be + // accounted for as packet overhead. + size_t MaxPacketOverhead() const; + + // Reset internal states from last frame and clear `recovered_packets`. + // Frees all memory allocated by this class. + void ResetState(RecoveredPacketList* recovered_packets); + + // TODO(brandtr): Remove these functions when the Packet classes + // have been refactored. + static uint16_t ParseSequenceNumber(const uint8_t* packet); + static uint32_t ParseSsrc(const uint8_t* packet); + + protected: + ForwardErrorCorrection(std::unique_ptr<FecHeaderReader> fec_header_reader, + std::unique_ptr<FecHeaderWriter> fec_header_writer, + uint32_t ssrc, + uint32_t protected_media_ssrc); + + private: + // Analyzes `media_packets` for holes in the sequence and inserts zero columns + // into the `packet_mask` where those holes are found. Zero columns means that + // those packets will have no protection. + // Returns the number of bits used for one row of the new packet mask. + // Requires that `packet_mask` has at least 6 * `num_fec_packets` bytes + // allocated. + int InsertZerosInPacketMasks(const PacketList& media_packets, + size_t num_fec_packets); + + // Writes FEC payloads and some recovery fields in the FEC headers. + void GenerateFecPayloads(const PacketList& media_packets, + size_t num_fec_packets); + + // Writes the FEC header fields that are not written by GenerateFecPayloads. + // This includes writing the packet masks. + void FinalizeFecHeaders(size_t num_fec_packets, + uint32_t media_ssrc, + uint16_t seq_num_base); + + // Inserts the `received_packet` into the internal received FEC packet list + // or into `recovered_packets`. + void InsertPacket(const ReceivedPacket& received_packet, + RecoveredPacketList* recovered_packets); + + // Inserts the `received_packet` into `recovered_packets`. Deletes duplicates. + void InsertMediaPacket(RecoveredPacketList* recovered_packets, + const ReceivedPacket& received_packet); + + // Assigns pointers to the recovered packet from all FEC packets which cover + // it. + // Note: This reduces the complexity when we want to try to recover a packet + // since we don't have to find the intersection between recovered packets and + // packets covered by the FEC packet. + void UpdateCoveringFecPackets(const RecoveredPacket& packet); + + // Insert `received_packet` into internal FEC list. Deletes duplicates. + void InsertFecPacket(const RecoveredPacketList& recovered_packets, + const ReceivedPacket& received_packet); + + // Assigns pointers to already recovered packets covered by `fec_packet`. + static void AssignRecoveredPackets( + const RecoveredPacketList& recovered_packets, + ReceivedFecPacket* fec_packet); + + // Attempt to recover missing packets, using the internally stored + // received FEC packets. + void AttemptRecovery(RecoveredPacketList* recovered_packets); + + // Initializes headers and payload before the XOR operation + // that recovers a packet. + static bool StartPacketRecovery(const ReceivedFecPacket& fec_packet, + RecoveredPacket* recovered_packet); + + // Performs XOR between the first 8 bytes of `src` and `dst` and stores + // the result in `dst`. The 3rd and 4th bytes are used for storing + // the length recovery field. + static void XorHeaders(const Packet& src, Packet* dst); + + // Performs XOR between the payloads of `src` and `dst` and stores the result + // in `dst`. The parameter `dst_offset` determines at what byte the + // XOR operation starts in `dst`. In total, `payload_length` bytes are XORed. + static void XorPayloads(const Packet& src, + size_t payload_length, + size_t dst_offset, + Packet* dst); + + // Finalizes recovery of packet by setting RTP header fields. + // This is not specific to the FEC scheme used. + static bool FinishPacketRecovery(const ReceivedFecPacket& fec_packet, + RecoveredPacket* recovered_packet); + + // Recover a missing packet. + static bool RecoverPacket(const ReceivedFecPacket& fec_packet, + RecoveredPacket* recovered_packet); + + // Get the number of missing media packets which are covered by `fec_packet`. + // An FEC packet can recover at most one packet, and if zero packets are + // missing the FEC packet can be discarded. This function returns 2 when two + // or more packets are missing. + static int NumCoveredPacketsMissing(const ReceivedFecPacket& fec_packet); + + // Discards old packets in `recovered_packets`, which are no longer relevant + // for recovering lost packets. + void DiscardOldRecoveredPackets(RecoveredPacketList* recovered_packets); + + // Checks if the FEC packet is old enough and no longer relevant for + // recovering lost media packets. + bool IsOldFecPacket(const ReceivedFecPacket& fec_packet, + const RecoveredPacketList* recovered_packets); + + // These SSRCs are only used by the decoder. + const uint32_t ssrc_; + const uint32_t protected_media_ssrc_; + + std::unique_ptr<FecHeaderReader> fec_header_reader_; + std::unique_ptr<FecHeaderWriter> fec_header_writer_; + + std::vector<Packet> generated_fec_packets_; + ReceivedFecPacketList received_fec_packets_; + + // Arrays used to avoid dynamically allocating memory when generating + // the packet masks. + // (There are never more than `kUlpfecMaxMediaPackets` FEC packets generated.) + uint8_t packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize]; + uint8_t tmp_packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize]; + size_t packet_mask_size_; +}; + +// Classes derived from FecHeader{Reader,Writer} encapsulate the +// specifics of reading and writing FEC header for, e.g., ULPFEC +// and FlexFEC. +class FecHeaderReader { + public: + virtual ~FecHeaderReader(); + + // The maximum number of media packets that can be covered by one FEC packet. + size_t MaxMediaPackets() const; + + // The maximum number of FEC packets that is supported, per call + // to ForwardErrorCorrection::EncodeFec(). + size_t MaxFecPackets() const; + + // Parses FEC header and stores information in ReceivedFecPacket members. + virtual bool ReadFecHeader( + ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const = 0; + + protected: + FecHeaderReader(size_t max_media_packets, size_t max_fec_packets); + + const size_t max_media_packets_; + const size_t max_fec_packets_; +}; + +class FecHeaderWriter { + public: + virtual ~FecHeaderWriter(); + + // The maximum number of media packets that can be covered by one FEC packet. + size_t MaxMediaPackets() const; + + // The maximum number of FEC packets that is supported, per call + // to ForwardErrorCorrection::EncodeFec(). + size_t MaxFecPackets() const; + + // The maximum overhead (in bytes) per packet, due to FEC headers. + size_t MaxPacketOverhead() const; + + // Calculates the minimum packet mask size needed (in bytes), + // given the discrete options of the ULPFEC masks and the bits + // set in the current packet mask. + virtual size_t MinPacketMaskSize(const uint8_t* packet_mask, + size_t packet_mask_size) const = 0; + + // The header size (in bytes), given the packet mask size. + virtual size_t FecHeaderSize(size_t packet_mask_size) const = 0; + + // Writes FEC header. + virtual void FinalizeFecHeader( + uint32_t media_ssrc, + uint16_t seq_num_base, + const uint8_t* packet_mask, + size_t packet_mask_size, + ForwardErrorCorrection::Packet* fec_packet) const = 0; + + protected: + FecHeaderWriter(size_t max_media_packets, + size_t max_fec_packets, + size_t max_packet_overhead); + + const size_t max_media_packets_; + const size_t max_fec_packets_; + const size_t max_packet_overhead_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc new file mode 100644 index 0000000000..ac68162d26 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc @@ -0,0 +1,519 @@ +/* + * 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 "modules/rtp_rtcp/source/forward_error_correction_internal.h" + +#include <string.h> + +#include <algorithm> + +#include "modules/rtp_rtcp/source/fec_private_tables_bursty.h" +#include "modules/rtp_rtcp/source/fec_private_tables_random.h" +#include "rtc_base/checks.h" + +namespace { +// Allow for different modes of protection for packets in UEP case. +enum ProtectionMode { + kModeNoOverlap, + kModeOverlap, + kModeBiasFirstPacket, +}; + +// Fits an input mask (sub_mask) to an output mask. +// The mask is a matrix where the rows are the FEC packets, +// and the columns are the source packets the FEC is applied to. +// Each row of the mask is represented by a number of mask bytes. +// +// \param[in] num_mask_bytes The number of mask bytes of output mask. +// \param[in] num_sub_mask_bytes The number of mask bytes of input mask. +// \param[in] num_rows The number of rows of the input mask. +// \param[in] sub_mask A pointer to hold the input mask, of size +// [0, num_rows * num_sub_mask_bytes] +// \param[out] packet_mask A pointer to hold the output mask, of size +// [0, x * num_mask_bytes], where x >= num_rows. +void FitSubMask(int num_mask_bytes, + int num_sub_mask_bytes, + int num_rows, + const uint8_t* sub_mask, + uint8_t* packet_mask) { + if (num_mask_bytes == num_sub_mask_bytes) { + memcpy(packet_mask, sub_mask, num_rows * num_sub_mask_bytes); + } else { + for (int i = 0; i < num_rows; ++i) { + int pkt_mask_idx = i * num_mask_bytes; + int pkt_mask_idx2 = i * num_sub_mask_bytes; + for (int j = 0; j < num_sub_mask_bytes; ++j) { + packet_mask[pkt_mask_idx] = sub_mask[pkt_mask_idx2]; + pkt_mask_idx++; + pkt_mask_idx2++; + } + } + } +} + +// Shifts a mask by number of columns (bits), and fits it to an output mask. +// The mask is a matrix where the rows are the FEC packets, +// and the columns are the source packets the FEC is applied to. +// Each row of the mask is represented by a number of mask bytes. +// +// \param[in] num_mask_bytes The number of mask bytes of output mask. +// \param[in] num_sub_mask_bytes The number of mask bytes of input mask. +// \param[in] num_column_shift The number columns to be shifted, and +// the starting row for the output mask. +// \param[in] end_row The ending row for the output mask. +// \param[in] sub_mask A pointer to hold the input mask, of size +// [0, (end_row_fec - start_row_fec) * +// num_sub_mask_bytes] +// \param[out] packet_mask A pointer to hold the output mask, of size +// [0, x * num_mask_bytes], +// where x >= end_row_fec. +// TODO(marpan): This function is doing three things at the same time: +// shift within a byte, byte shift and resizing. +// Split up into subroutines. +void ShiftFitSubMask(int num_mask_bytes, + int res_mask_bytes, + int num_column_shift, + int end_row, + const uint8_t* sub_mask, + uint8_t* packet_mask) { + // Number of bit shifts within a byte + const int num_bit_shifts = (num_column_shift % 8); + const int num_byte_shifts = num_column_shift >> 3; + + // Modify new mask with sub-mask21. + + // Loop over the remaining FEC packets. + for (int i = num_column_shift; i < end_row; ++i) { + // Byte index of new mask, for row i and column res_mask_bytes, + // offset by the number of bytes shifts + int pkt_mask_idx = + i * num_mask_bytes + res_mask_bytes - 1 + num_byte_shifts; + // Byte index of sub_mask, for row i and column res_mask_bytes + int pkt_mask_idx2 = + (i - num_column_shift) * res_mask_bytes + res_mask_bytes - 1; + + uint8_t shift_right_curr_byte = 0; + uint8_t shift_left_prev_byte = 0; + uint8_t comb_new_byte = 0; + + // Handle case of num_mask_bytes > res_mask_bytes: + // For a given row, copy the rightmost "numBitShifts" bits + // of the last byte of sub_mask into output mask. + if (num_mask_bytes > res_mask_bytes) { + shift_left_prev_byte = (sub_mask[pkt_mask_idx2] << (8 - num_bit_shifts)); + packet_mask[pkt_mask_idx + 1] = shift_left_prev_byte; + } + + // For each row i (FEC packet), shift the bit-mask of the sub_mask. + // Each row of the mask contains "resMaskBytes" of bytes. + // We start from the last byte of the sub_mask and move to first one. + for (int j = res_mask_bytes - 1; j > 0; j--) { + // Shift current byte of sub21 to the right by "numBitShifts". + shift_right_curr_byte = sub_mask[pkt_mask_idx2] >> num_bit_shifts; + + // Fill in shifted bits with bits from the previous (left) byte: + // First shift the previous byte to the left by "8-numBitShifts". + shift_left_prev_byte = + (sub_mask[pkt_mask_idx2 - 1] << (8 - num_bit_shifts)); + + // Then combine both shifted bytes into new mask byte. + comb_new_byte = shift_right_curr_byte | shift_left_prev_byte; + + // Assign to new mask. + packet_mask[pkt_mask_idx] = comb_new_byte; + pkt_mask_idx--; + pkt_mask_idx2--; + } + // For the first byte in the row (j=0 case). + shift_right_curr_byte = sub_mask[pkt_mask_idx2] >> num_bit_shifts; + packet_mask[pkt_mask_idx] = shift_right_curr_byte; + } +} + +} // namespace + +namespace webrtc { +namespace internal { + +PacketMaskTable::PacketMaskTable(FecMaskType fec_mask_type, + int num_media_packets) + : table_(PickTable(fec_mask_type, num_media_packets)) {} + +PacketMaskTable::~PacketMaskTable() = default; + +rtc::ArrayView<const uint8_t> PacketMaskTable::LookUp(int num_media_packets, + int num_fec_packets) { + RTC_DCHECK_GT(num_media_packets, 0); + RTC_DCHECK_GT(num_fec_packets, 0); + RTC_DCHECK_LE(num_media_packets, kUlpfecMaxMediaPackets); + RTC_DCHECK_LE(num_fec_packets, num_media_packets); + + if (num_media_packets <= 12) { + return LookUpInFecTable(table_, num_media_packets - 1, num_fec_packets - 1); + } + int mask_length = + static_cast<int>(PacketMaskSize(static_cast<size_t>(num_media_packets))); + + // Generate FEC code mask for {num_media_packets(M), num_fec_packets(N)} (use + // N FEC packets to protect M media packets) In the mask, each FEC packet + // occupies one row, each bit / coloumn represent one media packet. E.g. Row + // A, Col/Bit B is set to 1, means FEC packet A will have protection for media + // packet B. + + // Loop through each fec packet. + for (int row = 0; row < num_fec_packets; row++) { + // Loop through each fec code in a row, one code has 8 bits. + // Bit X will be set to 1 if media packet X shall be protected by current + // FEC packet. In this implementation, the protection is interleaved, thus + // media packet X will be protected by FEC packet (X % N) + for (int col = 0; col < mask_length; col++) { + fec_packet_mask_[row * mask_length + col] = + ((col * 8) % num_fec_packets == row && (col * 8) < num_media_packets + ? 0x80 + : 0x00) | + ((col * 8 + 1) % num_fec_packets == row && + (col * 8 + 1) < num_media_packets + ? 0x40 + : 0x00) | + ((col * 8 + 2) % num_fec_packets == row && + (col * 8 + 2) < num_media_packets + ? 0x20 + : 0x00) | + ((col * 8 + 3) % num_fec_packets == row && + (col * 8 + 3) < num_media_packets + ? 0x10 + : 0x00) | + ((col * 8 + 4) % num_fec_packets == row && + (col * 8 + 4) < num_media_packets + ? 0x08 + : 0x00) | + ((col * 8 + 5) % num_fec_packets == row && + (col * 8 + 5) < num_media_packets + ? 0x04 + : 0x00) | + ((col * 8 + 6) % num_fec_packets == row && + (col * 8 + 6) < num_media_packets + ? 0x02 + : 0x00) | + ((col * 8 + 7) % num_fec_packets == row && + (col * 8 + 7) < num_media_packets + ? 0x01 + : 0x00); + } + } + return {&fec_packet_mask_[0], + static_cast<size_t>(num_fec_packets * mask_length)}; +} + +// If `num_media_packets` is larger than the maximum allowed by `fec_mask_type` +// for the bursty type, or the random table is explicitly asked for, then the +// random type is selected. Otherwise the bursty table callback is returned. +const uint8_t* PacketMaskTable::PickTable(FecMaskType fec_mask_type, + int num_media_packets) { + RTC_DCHECK_GE(num_media_packets, 0); + RTC_DCHECK_LE(static_cast<size_t>(num_media_packets), kUlpfecMaxMediaPackets); + + if (fec_mask_type != kFecMaskRandom && + num_media_packets <= + static_cast<int>(fec_private_tables::kPacketMaskBurstyTbl[0])) { + return &fec_private_tables::kPacketMaskBurstyTbl[0]; + } + + return &fec_private_tables::kPacketMaskRandomTbl[0]; +} + +// Remaining protection after important (first partition) packet protection +void RemainingPacketProtection(int num_media_packets, + int num_fec_remaining, + int num_fec_for_imp_packets, + int num_mask_bytes, + ProtectionMode mode, + uint8_t* packet_mask, + PacketMaskTable* mask_table) { + if (mode == kModeNoOverlap) { + // sub_mask21 + + const int res_mask_bytes = + PacketMaskSize(num_media_packets - num_fec_for_imp_packets); + + auto end_row = (num_fec_for_imp_packets + num_fec_remaining); + rtc::ArrayView<const uint8_t> packet_mask_sub_21 = mask_table->LookUp( + num_media_packets - num_fec_for_imp_packets, num_fec_remaining); + + ShiftFitSubMask(num_mask_bytes, res_mask_bytes, num_fec_for_imp_packets, + end_row, &packet_mask_sub_21[0], packet_mask); + + } else if (mode == kModeOverlap || mode == kModeBiasFirstPacket) { + // sub_mask22 + rtc::ArrayView<const uint8_t> packet_mask_sub_22 = + mask_table->LookUp(num_media_packets, num_fec_remaining); + + FitSubMask(num_mask_bytes, num_mask_bytes, num_fec_remaining, + &packet_mask_sub_22[0], + &packet_mask[num_fec_for_imp_packets * num_mask_bytes]); + + if (mode == kModeBiasFirstPacket) { + for (int i = 0; i < num_fec_remaining; ++i) { + int pkt_mask_idx = i * num_mask_bytes; + packet_mask[pkt_mask_idx] = packet_mask[pkt_mask_idx] | (1 << 7); + } + } + } else { + RTC_DCHECK_NOTREACHED(); + } +} + +// Protection for important (first partition) packets +void ImportantPacketProtection(int num_fec_for_imp_packets, + int num_imp_packets, + int num_mask_bytes, + uint8_t* packet_mask, + PacketMaskTable* mask_table) { + const int num_imp_mask_bytes = PacketMaskSize(num_imp_packets); + + // Get sub_mask1 from table + rtc::ArrayView<const uint8_t> packet_mask_sub_1 = + mask_table->LookUp(num_imp_packets, num_fec_for_imp_packets); + + FitSubMask(num_mask_bytes, num_imp_mask_bytes, num_fec_for_imp_packets, + &packet_mask_sub_1[0], packet_mask); +} + +// This function sets the protection allocation: i.e., how many FEC packets +// to use for num_imp (1st partition) packets, given the: number of media +// packets, number of FEC packets, and number of 1st partition packets. +int SetProtectionAllocation(int num_media_packets, + int num_fec_packets, + int num_imp_packets) { + // TODO(marpan): test different cases for protection allocation: + + // Use at most (alloc_par * num_fec_packets) for important packets. + float alloc_par = 0.5; + int max_num_fec_for_imp = alloc_par * num_fec_packets; + + int num_fec_for_imp_packets = (num_imp_packets < max_num_fec_for_imp) + ? num_imp_packets + : max_num_fec_for_imp; + + // Fall back to equal protection in this case + if (num_fec_packets == 1 && (num_media_packets > 2 * num_imp_packets)) { + num_fec_for_imp_packets = 0; + } + + return num_fec_for_imp_packets; +} + +// Modification for UEP: reuse the off-line tables for the packet masks. +// Note: these masks were designed for equal packet protection case, +// assuming random packet loss. + +// Current version has 3 modes (options) to build UEP mask from existing ones. +// Various other combinations may be added in future versions. +// Longer-term, we may add another set of tables specifically for UEP cases. +// TODO(marpan): also consider modification of masks for bursty loss cases. + +// Mask is characterized as (#packets_to_protect, #fec_for_protection). +// Protection factor defined as: (#fec_for_protection / #packets_to_protect). + +// Let k=num_media_packets, n=total#packets, (n-k)=num_fec_packets, +// m=num_imp_packets. + +// For ProtectionMode 0 and 1: +// one mask (sub_mask1) is used for 1st partition packets, +// the other mask (sub_mask21/22, for 0/1) is for the remaining FEC packets. + +// In both mode 0 and 1, the packets of 1st partition (num_imp_packets) are +// treated equally important, and are afforded more protection than the +// residual partition packets. + +// For num_imp_packets: +// sub_mask1 = (m, t): protection = t/(m), where t=F(k,n-k,m). +// t=F(k,n-k,m) is the number of packets used to protect first partition in +// sub_mask1. This is determined from the function SetProtectionAllocation(). + +// For the left-over protection: +// Mode 0: sub_mask21 = (k-m,n-k-t): protection = (n-k-t)/(k-m) +// mode 0 has no protection overlap between the two partitions. +// For mode 0, we would typically set t = min(m, n-k). + +// Mode 1: sub_mask22 = (k, n-k-t), with protection (n-k-t)/(k) +// mode 1 has protection overlap between the two partitions (preferred). + +// For ProtectionMode 2: +// This gives 1st packet of list (which is 1st packet of 1st partition) more +// protection. In mode 2, the equal protection mask (which is obtained from +// mode 1 for t=0) is modified (more "1s" added in 1st column of packet mask) +// to bias higher protection for the 1st source packet. + +// Protection Mode 2 may be extended for a sort of sliding protection +// (i.e., vary the number/density of "1s" across columns) across packets. + +void UnequalProtectionMask(int num_media_packets, + int num_fec_packets, + int num_imp_packets, + int num_mask_bytes, + uint8_t* packet_mask, + PacketMaskTable* mask_table) { + // Set Protection type and allocation + // TODO(marpan): test/update for best mode and some combinations thereof. + + ProtectionMode mode = kModeOverlap; + int num_fec_for_imp_packets = 0; + + if (mode != kModeBiasFirstPacket) { + num_fec_for_imp_packets = SetProtectionAllocation( + num_media_packets, num_fec_packets, num_imp_packets); + } + + int num_fec_remaining = num_fec_packets - num_fec_for_imp_packets; + // Done with setting protection type and allocation + + // + // Generate sub_mask1 + // + if (num_fec_for_imp_packets > 0) { + ImportantPacketProtection(num_fec_for_imp_packets, num_imp_packets, + num_mask_bytes, packet_mask, mask_table); + } + + // + // Generate sub_mask2 + // + if (num_fec_remaining > 0) { + RemainingPacketProtection(num_media_packets, num_fec_remaining, + num_fec_for_imp_packets, num_mask_bytes, mode, + packet_mask, mask_table); + } +} + +// This algorithm is tailored to look up data in the `kPacketMaskRandomTbl` and +// `kPacketMaskBurstyTbl` tables. These tables only cover fec code for up to 12 +// media packets. Starting from 13 media packets, the fec code will be generated +// at runtime. The format of those arrays is that they're essentially a 3 +// dimensional array with the following dimensions: * media packet +// * Size for kPacketMaskRandomTbl: 12 +// * Size for kPacketMaskBurstyTbl: 12 +// * fec index +// * Size for both random and bursty table increases from 1 to number of rows. +// (i.e. 1-48, or 1-12 respectively). +// * Fec data (what actually gets returned) +// * Size for kPacketMaskRandomTbl: 2 bytes. +// * For all entries: 2 * fec index (1 based) +// * Size for kPacketMaskBurstyTbl: 2 bytes. +// * For all entries: 2 * fec index (1 based) +rtc::ArrayView<const uint8_t> LookUpInFecTable(const uint8_t* table, + int media_packet_index, + int fec_index) { + RTC_DCHECK_LT(media_packet_index, table[0]); + + // Skip over the table size. + const uint8_t* entry = &table[1]; + + uint8_t entry_size_increment = 2; // 0-16 are 2 byte wide, then changes to 6. + + // Hop over un-interesting array entries. + for (int i = 0; i < media_packet_index; ++i) { + if (i == 16) + entry_size_increment = 6; + uint8_t count = entry[0]; + ++entry; // skip over the count. + for (int j = 0; j < count; ++j) { + entry += entry_size_increment * (j + 1); // skip over the data. + } + } + + if (media_packet_index == 16) + entry_size_increment = 6; + + RTC_DCHECK_LT(fec_index, entry[0]); + ++entry; // Skip over the size. + + // Find the appropriate data in the second dimension. + + // Find the specific data we're looking for. + for (int i = 0; i < fec_index; ++i) + entry += entry_size_increment * (i + 1); // skip over the data. + + size_t size = entry_size_increment * (fec_index + 1); + return {&entry[0], size}; +} + +void GeneratePacketMasks(int num_media_packets, + int num_fec_packets, + int num_imp_packets, + bool use_unequal_protection, + PacketMaskTable* mask_table, + uint8_t* packet_mask) { + RTC_DCHECK_GT(num_media_packets, 0); + RTC_DCHECK_GT(num_fec_packets, 0); + RTC_DCHECK_LE(num_fec_packets, num_media_packets); + RTC_DCHECK_LE(num_imp_packets, num_media_packets); + RTC_DCHECK_GE(num_imp_packets, 0); + + const int num_mask_bytes = PacketMaskSize(num_media_packets); + + // Equal-protection for these cases. + if (!use_unequal_protection || num_imp_packets == 0) { + // Retrieve corresponding mask table directly:for equal-protection case. + // Mask = (k,n-k), with protection factor = (n-k)/k, + // where k = num_media_packets, n=total#packets, (n-k)=num_fec_packets. + rtc::ArrayView<const uint8_t> mask = + mask_table->LookUp(num_media_packets, num_fec_packets); + memcpy(packet_mask, &mask[0], mask.size()); + } else { // UEP case + UnequalProtectionMask(num_media_packets, num_fec_packets, num_imp_packets, + num_mask_bytes, packet_mask, mask_table); + } // End of UEP modification +} // End of GetPacketMasks + +size_t PacketMaskSize(size_t num_sequence_numbers) { + RTC_DCHECK_LE(num_sequence_numbers, 8 * kUlpfecPacketMaskSizeLBitSet); + if (num_sequence_numbers > 8 * kUlpfecPacketMaskSizeLBitClear) { + return kUlpfecPacketMaskSizeLBitSet; + } + return kUlpfecPacketMaskSizeLBitClear; +} + +void InsertZeroColumns(int num_zeros, + uint8_t* new_mask, + int new_mask_bytes, + int num_fec_packets, + int new_bit_index) { + for (uint16_t row = 0; row < num_fec_packets; ++row) { + const int new_byte_index = row * new_mask_bytes + new_bit_index / 8; + const int max_shifts = (7 - (new_bit_index % 8)); + new_mask[new_byte_index] <<= std::min(num_zeros, max_shifts); + } +} + +void CopyColumn(uint8_t* new_mask, + int new_mask_bytes, + uint8_t* old_mask, + int old_mask_bytes, + int num_fec_packets, + int new_bit_index, + int old_bit_index) { + RTC_CHECK_LT(new_bit_index, 8 * new_mask_bytes); + + // Copy column from the old mask to the beginning of the new mask and shift it + // out from the old mask. + for (uint16_t row = 0; row < num_fec_packets; ++row) { + int new_byte_index = row * new_mask_bytes + new_bit_index / 8; + int old_byte_index = row * old_mask_bytes + old_bit_index / 8; + new_mask[new_byte_index] |= ((old_mask[old_byte_index] & 0x80) >> 7); + if (new_bit_index % 8 != 7) { + new_mask[new_byte_index] <<= 1; + } + old_mask[old_byte_index] <<= 1; + } +} + +} // namespace internal +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h new file mode 100644 index 0000000000..31acf73e3e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h @@ -0,0 +1,121 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_INTERNAL_H_ +#define MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_INTERNAL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "api/array_view.h" +#include "modules/include/module_fec_types.h" + +namespace webrtc { + +// Maximum number of media packets that can be protected +// by these packet masks. +constexpr size_t kUlpfecMaxMediaPackets = 48; + +// Packet mask size in bytes (given L bit). +constexpr size_t kUlpfecPacketMaskSizeLBitClear = 2; +constexpr size_t kUlpfecPacketMaskSizeLBitSet = 6; + +// Packet code mask maximum length. kFECPacketMaskMaxSize = MaxNumFECPackets * +// (kUlpfecMaxMediaPackets / 8), and MaxNumFECPackets is equal to maximum number +// of media packets (kUlpfecMaxMediaPackets) +constexpr size_t kFECPacketMaskMaxSize = 288; + +// Convenience constants. +constexpr size_t kUlpfecMinPacketMaskSize = kUlpfecPacketMaskSizeLBitClear; +constexpr size_t kUlpfecMaxPacketMaskSize = kUlpfecPacketMaskSizeLBitSet; + +namespace internal { + +class PacketMaskTable { + public: + PacketMaskTable(FecMaskType fec_mask_type, int num_media_packets); + ~PacketMaskTable(); + + rtc::ArrayView<const uint8_t> LookUp(int num_media_packets, + int num_fec_packets); + + private: + static const uint8_t* PickTable(FecMaskType fec_mask_type, + int num_media_packets); + const uint8_t* table_; + uint8_t fec_packet_mask_[kFECPacketMaskMaxSize]; +}; + +rtc::ArrayView<const uint8_t> LookUpInFecTable(const uint8_t* table, + int media_packet_index, + int fec_index); + +// Returns an array of packet masks. The mask of a single FEC packet +// corresponds to a number of mask bytes. The mask indicates which +// media packets should be protected by the FEC packet. + +// \param[in] num_media_packets The number of media packets to protect. +// [1, max_media_packets]. +// \param[in] num_fec_packets The number of FEC packets which will +// be generated. [1, num_media_packets]. +// \param[in] num_imp_packets The number of important packets. +// [0, num_media_packets]. +// num_imp_packets = 0 is the equal +// protection scenario. +// \param[in] use_unequal_protection Enables unequal protection: allocates +// more protection to the num_imp_packets. +// \param[in] mask_table An instance of the `PacketMaskTable` +// class, which contains the type of FEC +// packet mask used, and a pointer to the +// corresponding packet masks. +// \param[out] packet_mask A pointer to hold the packet mask array, +// of size: num_fec_packets * +// "number of mask bytes". +void GeneratePacketMasks(int num_media_packets, + int num_fec_packets, + int num_imp_packets, + bool use_unequal_protection, + PacketMaskTable* mask_table, + uint8_t* packet_mask); + +// Returns the required packet mask size, given the number of sequence numbers +// that will be covered. +size_t PacketMaskSize(size_t num_sequence_numbers); + +// Inserts `num_zeros` zero columns into `new_mask` at position +// `new_bit_index`. If the current byte of `new_mask` can't fit all zeros, the +// byte will be filled with zeros from `new_bit_index`, but the next byte will +// be untouched. +void InsertZeroColumns(int num_zeros, + uint8_t* new_mask, + int new_mask_bytes, + int num_fec_packets, + int new_bit_index); + +// Copies the left most bit column from the byte pointed to by +// `old_bit_index` in `old_mask` to the right most column of the byte pointed +// to by `new_bit_index` in `new_mask`. `old_mask_bytes` and `new_mask_bytes` +// represent the number of bytes used per row for each mask. `num_fec_packets` +// represent the number of rows of the masks. +// The copied bit is shifted out from `old_mask` and is shifted one step to +// the left in `new_mask`. `new_mask` will contain "xxxx xxn0" after this +// operation, where x are previously inserted bits and n is the new bit. +void CopyColumn(uint8_t* new_mask, + int new_mask_bytes, + uint8_t* old_mask, + int old_mask_bytes, + int num_fec_packets, + int new_bit_index, + int old_bit_index); + +} // namespace internal +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_INTERNAL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/frame_transformer_factory_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/frame_transformer_factory_unittest.cc new file mode 100644 index 0000000000..65a0e4cbb4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/frame_transformer_factory_unittest.cc @@ -0,0 +1,69 @@ +/* + * 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 "api/frame_transformer_factory.h" + +#include <cstdio> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "api/call/transport.h" +#include "call/video_receive_stream.h" +#include "modules/rtp_rtcp/source/rtp_descriptor_authentication.h" +#include "rtc_base/event.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_frame_transformer.h" + +namespace webrtc { +namespace { + +using testing::NiceMock; +using testing::Return; + +class MockTransformableVideoFrame + : public webrtc::TransformableVideoFrameInterface { + public: + MOCK_METHOD(rtc::ArrayView<const uint8_t>, GetData, (), (const override)); + MOCK_METHOD(void, SetData, (rtc::ArrayView<const uint8_t> data), (override)); + MOCK_METHOD(uint8_t, GetPayloadType, (), (const, override)); + MOCK_METHOD(uint32_t, GetSsrc, (), (const, override)); + MOCK_METHOD(uint32_t, GetTimestamp, (), (const, override)); + MOCK_METHOD(TransformableFrameInterface::Direction, + GetDirection, + (), + (const, override)); + MOCK_METHOD(bool, IsKeyFrame, (), (const, override)); + MOCK_METHOD(std::vector<uint8_t>, GetAdditionalData, (), (const, override)); + MOCK_METHOD(const webrtc::VideoFrameMetadata&, + GetMetadata, + (), + (const, override)); + MOCK_METHOD(void, + SetMetadata, + (const webrtc::VideoFrameMetadata&), + (override)); +}; + +TEST(FrameTransformerFactory, CloneVideoFrame) { + NiceMock<MockTransformableVideoFrame> original_frame; + uint8_t data[10]; + std::fill_n(data, 10, 5); + rtc::ArrayView<uint8_t> data_view(data); + EXPECT_CALL(original_frame, GetData()).WillRepeatedly(Return(data_view)); + auto cloned_frame = CloneVideoFrame(&original_frame); + EXPECT_EQ(cloned_frame->GetData().size(), 10u); + EXPECT_THAT(cloned_frame->GetData(), testing::Each(5u)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.cc new file mode 100644 index 0000000000..131ec4b523 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.cc @@ -0,0 +1,63 @@ +/* + * 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 "modules/rtp_rtcp/source/leb128.h" + +#include <cstdint> + +namespace webrtc { + +int Leb128Size(uint64_t value) { + int size = 0; + while (value >= 0x80) { + ++size; + value >>= 7; + } + return size + 1; +} + +uint64_t ReadLeb128(const uint8_t*& read_at, const uint8_t* end) { + uint64_t value = 0; + int fill_bits = 0; + while (read_at != end && fill_bits < 64 - 7) { + uint8_t leb128_byte = *read_at; + value |= uint64_t{leb128_byte & 0x7Fu} << fill_bits; + ++read_at; + fill_bits += 7; + if ((leb128_byte & 0x80) == 0) { + return value; + } + } + // Read 9 bytes and didn't find the terminator byte. Check if 10th byte + // is that terminator, however to fit result into uint64_t it may carry only + // single bit. + if (read_at != end && *read_at <= 1) { + value |= uint64_t{*read_at} << fill_bits; + ++read_at; + return value; + } + // Failed to find terminator leb128 byte. + read_at = nullptr; + return 0; +} + +int WriteLeb128(uint64_t value, uint8_t* buffer) { + int size = 0; + while (value >= 0x80) { + buffer[size] = 0x80 | (value & 0x7F); + ++size; + value >>= 7; + } + buffer[size] = value; + ++size; + return size; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.h b/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.h new file mode 100644 index 0000000000..6a793f23ed --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/leb128.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_LEB128_H_ +#define MODULES_RTP_RTCP_SOURCE_LEB128_H_ + +#include <cstdint> + +namespace webrtc { + +// Returns number of bytes needed to store `value` in leb128 format. +int Leb128Size(uint64_t value); + +// Reads leb128 encoded value and advance read_at by number of bytes consumed. +// Sets read_at to nullptr on error. +uint64_t ReadLeb128(const uint8_t*& read_at, const uint8_t* end); + +// Encodes `value` in leb128 format. Assumes buffer has size of at least +// Leb128Size(value). Returns number of bytes consumed. +int WriteLeb128(uint64_t value, uint8_t* buffer); + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_LEB128_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/leb128_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/leb128_unittest.cc new file mode 100644 index 0000000000..dbabcb36f2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/leb128_unittest.cc @@ -0,0 +1,138 @@ +/* + * 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 "modules/rtp_rtcp/source/leb128.h" + +#include <cstdint> +#include <iterator> +#include <limits> + +#include "api/array_view.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAre; + +TEST(Leb128Test, Size) { + EXPECT_EQ(Leb128Size(0), 1); + EXPECT_EQ(Leb128Size(0b0111'1111), 1); + EXPECT_EQ(Leb128Size(0b1000'0000), 2); + EXPECT_EQ(Leb128Size(std::numeric_limits<uint64_t>::max()), 10); +} + +TEST(Leb128Test, ReadZero) { + const uint8_t one_byte[] = {0}; + const uint8_t* read_at = one_byte; + EXPECT_EQ(ReadLeb128(read_at, std::end(one_byte)), uint64_t{0}); + EXPECT_EQ(std::distance(read_at, std::end(one_byte)), 0); +} + +TEST(Leb128Test, ReadOneByte) { + const uint8_t buffer[] = {0b0010'1100}; + const uint8_t* read_at = buffer; + EXPECT_EQ(ReadLeb128(read_at, std::end(buffer)), uint64_t{0b0010'1100}); + EXPECT_EQ(std::distance(read_at, std::end(buffer)), 0); +} + +TEST(Leb128Test, ReadTwoByte) { + const uint8_t buffer[] = {0b1010'1100, 0b0111'0000}; + const uint8_t* read_at = buffer; + EXPECT_EQ(ReadLeb128(read_at, std::end(buffer)), + uint64_t{0b111'0000'010'1100}); + EXPECT_EQ(std::distance(read_at, std::end(buffer)), 0); +} + +TEST(Leb128Test, ReadNearlyMaxValue1) { + const uint8_t buffer[] = {0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x7f}; + const uint8_t* read_at = buffer; + EXPECT_EQ(ReadLeb128(read_at, std::end(buffer)), + uint64_t{0x7fff'ffff'ffff'ffff}); + EXPECT_EQ(std::distance(read_at, std::end(buffer)), 0); +} + +TEST(Leb128Test, ReadNearlyMaxValue2) { + // This is valid, though not optimal way to store 63 bits of the value. + const uint8_t buffer[] = {0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0}; + const uint8_t* read_at = buffer; + EXPECT_EQ(ReadLeb128(read_at, std::end(buffer)), + uint64_t{0x7fff'ffff'ffff'ffff}); + EXPECT_EQ(std::distance(read_at, std::end(buffer)), 0); +} + +TEST(Leb128Test, ReadMaxValue) { + // This is valid, though not optimal way to store 63 bits of the value. + const uint8_t buffer[] = {0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x1}; + const uint8_t* read_at = buffer; + EXPECT_EQ(ReadLeb128(read_at, std::end(buffer)), 0xffff'ffff'ffff'ffff); + EXPECT_EQ(std::distance(read_at, std::end(buffer)), 0); +} + +TEST(Leb128Test, FailsToReadMoreThanMaxValue) { + const uint8_t buffer[] = {0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x2}; + const uint8_t* read_at = buffer; + ReadLeb128(read_at, std::end(buffer)); + EXPECT_EQ(read_at, nullptr); +} + +TEST(Leb128Test, DoesntReadMoreThan10Bytes) { + // Though this array represent leb128 encoded value that can fit in uint64_t, + // ReadLeb128 function discards it to avoid reading too many bytes from the + // buffer. + const uint8_t buffer[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x80, 0x00}; + const uint8_t* read_at = buffer; + ReadLeb128(read_at, std::end(buffer)); + EXPECT_EQ(read_at, nullptr); +} + +TEST(Leb128Test, WriteZero) { + uint8_t buffer[16]; + EXPECT_EQ(WriteLeb128(0, buffer), 1); + EXPECT_EQ(buffer[0], 0); +} + +TEST(Leb128Test, WriteOneByteValue) { + uint8_t buffer[16]; + EXPECT_EQ(WriteLeb128(0b0010'1100, buffer), 1); + EXPECT_EQ(buffer[0], 0b0010'1100); +} + +TEST(Leb128Test, WriteTwoByteValue) { + uint8_t buffer[16]; + EXPECT_EQ(WriteLeb128(0b11'1111'010'1100, buffer), 2); + EXPECT_EQ(buffer[0], 0b1010'1100); + EXPECT_EQ(buffer[1], 0b0011'1111); +} + +TEST(Leb128Test, WriteNearlyMaxValue) { + uint8_t buffer[16]; + EXPECT_EQ(WriteLeb128(0x7fff'ffff'ffff'ffff, buffer), 9); + EXPECT_THAT( + rtc::MakeArrayView(buffer, 9), + ElementsAre(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f)); +} + +TEST(Leb128Test, WriteMaxValue) { + uint8_t buffer[16]; + EXPECT_EQ(WriteLeb128(0xffff'ffff'ffff'ffff, buffer), 10); + EXPECT_THAT( + rtc::MakeArrayView(buffer, 10), + ElementsAre(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc new file mode 100644 index 0000000000..d0617f3804 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc @@ -0,0 +1,293 @@ +/* + * 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 <iterator> +#include <list> +#include <memory> +#include <set> + +#include "absl/algorithm/container.h" +#include "api/call/transport.h" +#include "api/transport/field_trial_based_config.h" +#include "call/rtp_stream_receiver_controller.h" +#include "call/rtx_receive_stream.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_impl2.h" +#include "modules/rtp_rtcp/source/rtp_sender_video.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/thread.h" +#include "test/gtest.h" + +namespace webrtc { + +const int kVideoNackListSize = 30; +const uint32_t kTestSsrc = 3456; +const uint32_t kTestRtxSsrc = kTestSsrc + 1; +const uint16_t kTestSequenceNumber = 2345; +const uint32_t kTestNumberOfPackets = 1350; +const int kTestNumberOfRtxPackets = 149; +const int kNumFrames = 30; +const int kPayloadType = 123; +const int kRtxPayloadType = 98; +const int64_t kMaxRttMs = 1000; + +class VerifyingMediaStream : public RtpPacketSinkInterface { + public: + VerifyingMediaStream() {} + + void OnRtpPacket(const RtpPacketReceived& packet) override { + if (!sequence_numbers_.empty()) + EXPECT_EQ(kTestSsrc, packet.Ssrc()); + + sequence_numbers_.push_back(packet.SequenceNumber()); + } + std::list<uint16_t> sequence_numbers_; +}; + +class RtxLoopBackTransport : public webrtc::Transport { + public: + explicit RtxLoopBackTransport(uint32_t rtx_ssrc) + : count_(0), + packet_loss_(0), + consecutive_drop_start_(0), + consecutive_drop_end_(0), + rtx_ssrc_(rtx_ssrc), + count_rtx_ssrc_(0), + module_(NULL) {} + + void SetSendModule(RtpRtcpInterface* rtpRtcpModule) { + module_ = rtpRtcpModule; + } + + void DropEveryNthPacket(int n) { packet_loss_ = n; } + + void DropConsecutivePackets(int start, int total) { + consecutive_drop_start_ = start; + consecutive_drop_end_ = start + total; + packet_loss_ = 0; + } + + bool SendRtp(const uint8_t* data, + size_t len, + const PacketOptions& options) override { + count_++; + RtpPacketReceived packet; + if (!packet.Parse(data, len)) + return false; + if (packet.Ssrc() == rtx_ssrc_) { + count_rtx_ssrc_++; + } else { + // For non-RTX packets only. + expected_sequence_numbers_.insert(expected_sequence_numbers_.end(), + packet.SequenceNumber()); + } + if (packet_loss_ > 0) { + if ((count_ % packet_loss_) == 0) { + return true; + } + } else if (count_ >= consecutive_drop_start_ && + count_ < consecutive_drop_end_) { + return true; + } + EXPECT_TRUE(stream_receiver_controller_.OnRtpPacket(packet)); + return true; + } + + bool SendRtcp(const uint8_t* data, size_t len) override { + module_->IncomingRtcpPacket((const uint8_t*)data, len); + return true; + } + int count_; + int packet_loss_; + int consecutive_drop_start_; + int consecutive_drop_end_; + uint32_t rtx_ssrc_; + int count_rtx_ssrc_; + RtpRtcpInterface* module_; + RtpStreamReceiverController stream_receiver_controller_; + std::set<uint16_t> expected_sequence_numbers_; +}; + +class RtpRtcpRtxNackTest : public ::testing::Test { + protected: + RtpRtcpRtxNackTest() + : rtp_rtcp_module_(nullptr), + transport_(kTestRtxSsrc), + rtx_stream_(&media_stream_, rtx_associated_payload_types_, kTestSsrc), + fake_clock(123456), + retransmission_rate_limiter_(&fake_clock, kMaxRttMs) {} + ~RtpRtcpRtxNackTest() override {} + + void SetUp() override { + RtpRtcpInterface::Configuration configuration; + configuration.audio = false; + configuration.clock = &fake_clock; + receive_statistics_ = ReceiveStatistics::Create(&fake_clock); + configuration.receive_statistics = receive_statistics_.get(); + configuration.outgoing_transport = &transport_; + configuration.retransmission_rate_limiter = &retransmission_rate_limiter_; + configuration.local_media_ssrc = kTestSsrc; + configuration.rtx_send_ssrc = kTestRtxSsrc; + rtp_rtcp_module_ = ModuleRtpRtcpImpl2::Create(configuration); + FieldTrialBasedConfig field_trials; + RTPSenderVideo::Config video_config; + video_config.clock = &fake_clock; + video_config.rtp_sender = rtp_rtcp_module_->RtpSender(); + video_config.field_trials = &field_trials; + rtp_sender_video_ = std::make_unique<RTPSenderVideo>(video_config); + rtp_rtcp_module_->SetRTCPStatus(RtcpMode::kCompound); + rtp_rtcp_module_->SetStorePacketsStatus(true, 600); + EXPECT_EQ(0, rtp_rtcp_module_->SetSendingStatus(true)); + rtp_rtcp_module_->SetSequenceNumber(kTestSequenceNumber); + rtp_rtcp_module_->SetStartTimestamp(111111); + + // Used for NACK processing. + rtp_rtcp_module_->SetRemoteSSRC(kTestSsrc); + + rtp_rtcp_module_->SetRtxSendPayloadType(kRtxPayloadType, kPayloadType); + transport_.SetSendModule(rtp_rtcp_module_.get()); + media_receiver_ = transport_.stream_receiver_controller_.CreateReceiver( + kTestSsrc, &media_stream_); + + for (size_t n = 0; n < sizeof(payload_data); n++) { + payload_data[n] = n % 10; + } + } + + int BuildNackList(uint16_t* nack_list) { + media_stream_.sequence_numbers_.sort(); + std::list<uint16_t> missing_sequence_numbers; + std::list<uint16_t>::iterator it = media_stream_.sequence_numbers_.begin(); + + while (it != media_stream_.sequence_numbers_.end()) { + uint16_t sequence_number_1 = *it; + ++it; + if (it != media_stream_.sequence_numbers_.end()) { + uint16_t sequence_number_2 = *it; + // Add all missing sequence numbers to list + for (uint16_t i = sequence_number_1 + 1; i < sequence_number_2; ++i) { + missing_sequence_numbers.push_back(i); + } + } + } + int n = 0; + for (it = missing_sequence_numbers.begin(); + it != missing_sequence_numbers.end(); ++it) { + nack_list[n++] = (*it); + } + return n; + } + + bool ExpectedPacketsReceived() { + std::list<uint16_t> received_sorted; + absl::c_copy(media_stream_.sequence_numbers_, + std::back_inserter(received_sorted)); + received_sorted.sort(); + return absl::c_equal(received_sorted, + transport_.expected_sequence_numbers_); + } + + void RunRtxTest(RtxMode rtx_method, int loss) { + rtx_receiver_ = transport_.stream_receiver_controller_.CreateReceiver( + kTestRtxSsrc, &rtx_stream_); + rtp_rtcp_module_->SetRtxSendStatus(rtx_method); + transport_.DropEveryNthPacket(loss); + uint32_t timestamp = 3000; + uint16_t nack_list[kVideoNackListSize]; + for (int frame = 0; frame < kNumFrames; ++frame) { + RTPVideoHeader video_header; + EXPECT_TRUE(rtp_rtcp_module_->OnSendingRtpFrame(timestamp, timestamp / 90, + kPayloadType, false)); + video_header.frame_type = VideoFrameType::kVideoFrameDelta; + EXPECT_TRUE(rtp_sender_video_->SendVideo( + kPayloadType, VideoCodecType::kVideoCodecGeneric, timestamp, + timestamp / 90, payload_data, video_header, 0)); + // Min required delay until retransmit = 5 + RTT ms (RTT = 0). + fake_clock.AdvanceTimeMilliseconds(5); + int length = BuildNackList(nack_list); + if (length > 0) + rtp_rtcp_module_->SendNACK(nack_list, length); + fake_clock.AdvanceTimeMilliseconds(28); // 33ms - 5ms delay. + // Prepare next frame. + timestamp += 3000; + } + media_stream_.sequence_numbers_.sort(); + } + + rtc::AutoThread main_thread_; + std::unique_ptr<ReceiveStatistics> receive_statistics_; + std::unique_ptr<ModuleRtpRtcpImpl2> rtp_rtcp_module_; + std::unique_ptr<RTPSenderVideo> rtp_sender_video_; + RtxLoopBackTransport transport_; + const std::map<int, int> rtx_associated_payload_types_ = { + {kRtxPayloadType, kPayloadType}}; + VerifyingMediaStream media_stream_; + RtxReceiveStream rtx_stream_; + uint8_t payload_data[65000]; + SimulatedClock fake_clock; + RateLimiter retransmission_rate_limiter_; + std::unique_ptr<RtpStreamReceiverInterface> media_receiver_; + std::unique_ptr<RtpStreamReceiverInterface> rtx_receiver_; +}; + +TEST_F(RtpRtcpRtxNackTest, LongNackList) { + const int kNumPacketsToDrop = 900; + const int kNumRequiredRtcp = 4; + uint32_t timestamp = 3000; + uint16_t nack_list[kNumPacketsToDrop]; + // Disable StorePackets to be able to set a larger packet history. + rtp_rtcp_module_->SetStorePacketsStatus(false, 0); + // Enable StorePackets with a packet history of 2000 packets. + rtp_rtcp_module_->SetStorePacketsStatus(true, 2000); + // Drop 900 packets from the second one so that we get a NACK list which is + // big enough to require 4 RTCP packets to be fully transmitted to the sender. + transport_.DropConsecutivePackets(2, kNumPacketsToDrop); + // Send 30 frames which at the default size is roughly what we need to get + // enough packets. + for (int frame = 0; frame < kNumFrames; ++frame) { + RTPVideoHeader video_header; + EXPECT_TRUE(rtp_rtcp_module_->OnSendingRtpFrame(timestamp, timestamp / 90, + kPayloadType, false)); + video_header.frame_type = VideoFrameType::kVideoFrameDelta; + EXPECT_TRUE(rtp_sender_video_->SendVideo( + kPayloadType, VideoCodecType::kVideoCodecGeneric, timestamp, + timestamp / 90, payload_data, video_header, 0)); + // Prepare next frame. + timestamp += 3000; + fake_clock.AdvanceTimeMilliseconds(33); + } + EXPECT_FALSE(transport_.expected_sequence_numbers_.empty()); + EXPECT_FALSE(media_stream_.sequence_numbers_.empty()); + size_t last_receive_count = media_stream_.sequence_numbers_.size(); + int length = BuildNackList(nack_list); + for (int i = 0; i < kNumRequiredRtcp - 1; ++i) { + rtp_rtcp_module_->SendNACK(nack_list, length); + EXPECT_GT(media_stream_.sequence_numbers_.size(), last_receive_count); + last_receive_count = media_stream_.sequence_numbers_.size(); + EXPECT_FALSE(ExpectedPacketsReceived()); + } + rtp_rtcp_module_->SendNACK(nack_list, length); + EXPECT_GT(media_stream_.sequence_numbers_.size(), last_receive_count); + EXPECT_TRUE(ExpectedPacketsReceived()); +} + +TEST_F(RtpRtcpRtxNackTest, RtxNack) { + RunRtxTest(kRtxRetransmitted, 10); + EXPECT_EQ(kTestSequenceNumber, *(media_stream_.sequence_numbers_.begin())); + EXPECT_EQ(kTestSequenceNumber + kTestNumberOfPackets - 1, + *(media_stream_.sequence_numbers_.rbegin())); + EXPECT_EQ(kTestNumberOfPackets, media_stream_.sequence_numbers_.size()); + EXPECT_EQ(kTestNumberOfRtxPackets, transport_.count_rtx_ssrc_); + EXPECT_TRUE(ExpectedPacketsReceived()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.cc new file mode 100644 index 0000000000..36f0a63d59 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.cc @@ -0,0 +1,141 @@ +/* + * 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/packet_loss_stats.h" + +#include <cstdint> +#include <iterator> +#include <vector> + +#include "rtc_base/checks.h" + +// After this many packets are added, adding additional packets will cause the +// oldest packets to be pruned from the buffer. +static const int kBufferSize = 100; + +namespace webrtc { + +PacketLossStats::PacketLossStats() + : single_loss_historic_count_(0), + multiple_loss_historic_event_count_(0), + multiple_loss_historic_packet_count_(0) {} + +PacketLossStats::~PacketLossStats() = default; + +void PacketLossStats::AddLostPacket(uint16_t sequence_number) { + // Detect sequence number wrap around. + if (!lost_packets_buffer_.empty() && + static_cast<int>(*(lost_packets_buffer_.rbegin())) - sequence_number > + 0x8000) { + // The buffer contains large numbers and this is a small number. + lost_packets_wrapped_buffer_.insert(sequence_number); + } else { + lost_packets_buffer_.insert(sequence_number); + } + if (lost_packets_wrapped_buffer_.size() + lost_packets_buffer_.size() > + kBufferSize || + (!lost_packets_wrapped_buffer_.empty() && + *(lost_packets_wrapped_buffer_.rbegin()) > 0x4000)) { + PruneBuffer(); + } +} + +int PacketLossStats::GetSingleLossCount() const { + int single_loss_count, unused1, unused2; + ComputeLossCounts(&single_loss_count, &unused1, &unused2); + return single_loss_count; +} + +int PacketLossStats::GetMultipleLossEventCount() const { + int event_count, unused1, unused2; + ComputeLossCounts(&unused1, &event_count, &unused2); + return event_count; +} + +int PacketLossStats::GetMultipleLossPacketCount() const { + int packet_count, unused1, unused2; + ComputeLossCounts(&unused1, &unused2, &packet_count); + return packet_count; +} + +void PacketLossStats::ComputeLossCounts( + int* out_single_loss_count, + int* out_multiple_loss_event_count, + int* out_multiple_loss_packet_count) const { + *out_single_loss_count = single_loss_historic_count_; + *out_multiple_loss_event_count = multiple_loss_historic_event_count_; + *out_multiple_loss_packet_count = multiple_loss_historic_packet_count_; + if (lost_packets_buffer_.empty()) { + RTC_DCHECK(lost_packets_wrapped_buffer_.empty()); + return; + } + uint16_t last_num = 0; + int sequential_count = 0; + std::vector<const std::set<uint16_t>*> buffers; + buffers.push_back(&lost_packets_buffer_); + buffers.push_back(&lost_packets_wrapped_buffer_); + for (const auto* buffer : buffers) { + for (auto it = buffer->begin(); it != buffer->end(); ++it) { + uint16_t current_num = *it; + if (sequential_count > 0 && current_num != ((last_num + 1) & 0xFFFF)) { + if (sequential_count == 1) { + (*out_single_loss_count)++; + } else { + (*out_multiple_loss_event_count)++; + *out_multiple_loss_packet_count += sequential_count; + } + sequential_count = 0; + } + sequential_count++; + last_num = current_num; + } + } + if (sequential_count == 1) { + (*out_single_loss_count)++; + } else if (sequential_count > 1) { + (*out_multiple_loss_event_count)++; + *out_multiple_loss_packet_count += sequential_count; + } +} + +void PacketLossStats::PruneBuffer() { + // Remove the oldest lost packet and any contiguous packets and move them + // into the historic counts. + auto it = lost_packets_buffer_.begin(); + uint16_t last_removed = 0; + int remove_count = 0; + // Count adjacent packets and continue counting if it is wrap around by + // swapping in the wrapped buffer and letting our value wrap as well. + while (remove_count == 0 || (!lost_packets_buffer_.empty() && + *it == ((last_removed + 1) & 0xFFFF))) { + last_removed = *it; + remove_count++; + auto to_erase = it++; + lost_packets_buffer_.erase(to_erase); + if (lost_packets_buffer_.empty()) { + lost_packets_buffer_.swap(lost_packets_wrapped_buffer_); + it = lost_packets_buffer_.begin(); + } + } + if (remove_count > 1) { + multiple_loss_historic_event_count_++; + multiple_loss_historic_packet_count_ += remove_count; + } else { + single_loss_historic_count_++; + } + // Continue pruning if the wrapped buffer is beyond a threshold and there are + // things left in the pre-wrapped buffer. + if (!lost_packets_wrapped_buffer_.empty() && + *(lost_packets_wrapped_buffer_.rbegin()) > 0x4000) { + PruneBuffer(); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.h b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.h new file mode 100644 index 0000000000..60d20294a8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.h @@ -0,0 +1,58 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_PACKET_LOSS_STATS_H_ +#define MODULES_RTP_RTCP_SOURCE_PACKET_LOSS_STATS_H_ + +#include <stdint.h> + +#include <set> + +namespace webrtc { + +// Keeps track of statistics of packet loss including whether losses are a +// single packet or multiple packets in a row. +class PacketLossStats { + public: + PacketLossStats(); + ~PacketLossStats(); + + // Adds a lost packet to the stats by sequence number. + void AddLostPacket(uint16_t sequence_number); + + // Queries the number of packets that were lost by themselves, no neighboring + // packets were lost. + int GetSingleLossCount() const; + + // Queries the number of times that multiple packets with sequential numbers + // were lost. This is the number of events with more than one packet lost, + // regardless of the size of the event; + int GetMultipleLossEventCount() const; + + // Queries the number of packets lost in multiple packet loss events. Combined + // with the event count, this can be used to determine the average event size. + int GetMultipleLossPacketCount() const; + + private: + std::set<uint16_t> lost_packets_buffer_; + std::set<uint16_t> lost_packets_wrapped_buffer_; + int single_loss_historic_count_; + int multiple_loss_historic_event_count_; + int multiple_loss_historic_packet_count_; + + void ComputeLossCounts(int* out_single_loss_count, + int* out_multiple_loss_event_count, + int* out_multiple_loss_packet_count) const; + void PruneBuffer(); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_PACKET_LOSS_STATS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats_unittest.cc new file mode 100644 index 0000000000..673b223867 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats_unittest.cc @@ -0,0 +1,198 @@ +/* + * 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/packet_loss_stats.h" + +#include "test/gtest.h" + +namespace webrtc { + +class PacketLossStatsTest : public ::testing::Test { + protected: + PacketLossStats stats_; +}; + +// Add a lost packet as every other packet, they should all count as single +// losses. +TEST_F(PacketLossStatsTest, EveryOtherPacket) { + for (int i = 0; i < 1000; i += 2) { + stats_.AddLostPacket(i); + } + EXPECT_EQ(500, stats_.GetSingleLossCount()); + EXPECT_EQ(0, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(0, stats_.GetMultipleLossPacketCount()); +} + +// Add a lost packet as every other packet, but such that the sequence numbers +// will wrap around while they are being added. +TEST_F(PacketLossStatsTest, EveryOtherPacketWrapped) { + for (int i = 65500; i < 66500; i += 2) { + stats_.AddLostPacket(i & 0xFFFF); + } + EXPECT_EQ(500, stats_.GetSingleLossCount()); + EXPECT_EQ(0, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(0, stats_.GetMultipleLossPacketCount()); +} + +// Add a lost packet as every other packet, but such that the sequence numbers +// will wrap around close to the very end, such that the buffer contains packets +// on either side of the wrapping. +TEST_F(PacketLossStatsTest, EveryOtherPacketWrappedAtEnd) { + for (int i = 64600; i < 65600; i += 2) { + stats_.AddLostPacket(i & 0xFFFF); + } + EXPECT_EQ(500, stats_.GetSingleLossCount()); + EXPECT_EQ(0, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(0, stats_.GetMultipleLossPacketCount()); +} + +// Add a lost packet as the first three of every eight packets. Each set of +// three should count as a multiple loss event and three multiple loss packets. +TEST_F(PacketLossStatsTest, FirstThreeOfEight) { + for (int i = 0; i < 1000; ++i) { + if ((i & 7) < 3) { + stats_.AddLostPacket(i); + } + } + EXPECT_EQ(0, stats_.GetSingleLossCount()); + EXPECT_EQ(125, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(375, stats_.GetMultipleLossPacketCount()); +} + +// Add a lost packet as the first three of every eight packets such that the +// sequence numbers wrap in the middle of adding them. +TEST_F(PacketLossStatsTest, FirstThreeOfEightWrapped) { + for (int i = 65500; i < 66500; ++i) { + if ((i & 7) < 3) { + stats_.AddLostPacket(i & 0xFFFF); + } + } + EXPECT_EQ(0, stats_.GetSingleLossCount()); + EXPECT_EQ(125, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(375, stats_.GetMultipleLossPacketCount()); +} + +// Add a lost packet as the first three of every eight packets such that the +// sequence numbers wrap near the end of adding them and there are still numbers +// in the buffer from before the wrapping. +TEST_F(PacketLossStatsTest, FirstThreeOfEightWrappedAtEnd) { + for (int i = 64600; i < 65600; ++i) { + if ((i & 7) < 3) { + stats_.AddLostPacket(i & 0xFFFF); + } + } + EXPECT_EQ(0, stats_.GetSingleLossCount()); + EXPECT_EQ(125, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(375, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets as the first three and the fifth of every eight packets. The +// set of three should be multiple loss and the fifth should be single loss. +TEST_F(PacketLossStatsTest, FirstThreeAndFifthOfEight) { + for (int i = 0; i < 1000; ++i) { + if ((i & 7) < 3 || (i & 7) == 4) { + stats_.AddLostPacket(i); + } + } + EXPECT_EQ(125, stats_.GetSingleLossCount()); + EXPECT_EQ(125, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(375, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets as the first three and the fifth of every eight packets such +// that the sequence numbers wrap in the middle of adding them. +TEST_F(PacketLossStatsTest, FirstThreeAndFifthOfEightWrapped) { + for (int i = 65500; i < 66500; ++i) { + if ((i & 7) < 3 || (i & 7) == 4) { + stats_.AddLostPacket(i & 0xFFFF); + } + } + EXPECT_EQ(125, stats_.GetSingleLossCount()); + EXPECT_EQ(125, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(375, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets as the first three and the fifth of every eight packets such +// that the sequence numbers wrap near the end of adding them and there are +// packets from before the wrapping still in the buffer. +TEST_F(PacketLossStatsTest, FirstThreeAndFifthOfEightWrappedAtEnd) { + for (int i = 64600; i < 65600; ++i) { + if ((i & 7) < 3 || (i & 7) == 4) { + stats_.AddLostPacket(i & 0xFFFF); + } + } + EXPECT_EQ(125, stats_.GetSingleLossCount()); + EXPECT_EQ(125, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(375, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets such that there is a multiple loss event that continues +// around the wrapping of sequence numbers. +TEST_F(PacketLossStatsTest, MultipleLossEventWrapped) { + for (int i = 60000; i < 60500; i += 2) { + stats_.AddLostPacket(i); + } + for (int i = 65530; i < 65540; ++i) { + stats_.AddLostPacket(i & 0xFFFF); + } + EXPECT_EQ(250, stats_.GetSingleLossCount()); + EXPECT_EQ(1, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(10, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets such that there is a multiple loss event that continues +// around the wrapping of sequence numbers and then is pushed out of the buffer. +TEST_F(PacketLossStatsTest, MultipleLossEventWrappedPushedOut) { + for (int i = 60000; i < 60500; i += 2) { + stats_.AddLostPacket(i); + } + for (int i = 65530; i < 65540; ++i) { + stats_.AddLostPacket(i & 0xFFFF); + } + for (int i = 1000; i < 1500; i += 2) { + stats_.AddLostPacket(i); + } + EXPECT_EQ(500, stats_.GetSingleLossCount()); + EXPECT_EQ(1, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(10, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets out of order and ensure that they still get counted +// correctly as single or multiple loss events. +TEST_F(PacketLossStatsTest, OutOfOrder) { + for (int i = 0; i < 1000; i += 10) { + stats_.AddLostPacket(i + 5); + stats_.AddLostPacket(i + 7); + stats_.AddLostPacket(i + 4); + stats_.AddLostPacket(i + 1); + stats_.AddLostPacket(i + 2); + } + EXPECT_EQ(100, stats_.GetSingleLossCount()); + EXPECT_EQ(200, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(400, stats_.GetMultipleLossPacketCount()); +} + +// Add loss packets out of order and ensure that they still get counted +// correctly as single or multiple loss events, and wrap in the middle of +// adding. +TEST_F(PacketLossStatsTest, OutOfOrderWrapped) { + for (int i = 65000; i < 66000; i += 10) { + stats_.AddLostPacket((i + 5) & 0xFFFF); + stats_.AddLostPacket((i + 7) & 0xFFFF); + stats_.AddLostPacket((i + 4) & 0xFFFF); + stats_.AddLostPacket((i + 1) & 0xFFFF); + stats_.AddLostPacket((i + 2) & 0xFFFF); + } + EXPECT_EQ(100, stats_.GetSingleLossCount()); + EXPECT_EQ(200, stats_.GetMultipleLossEventCount()); + EXPECT_EQ(400, stats_.GetMultipleLossPacketCount()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.cc new file mode 100644 index 0000000000..55edd768a8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.cc @@ -0,0 +1,156 @@ +/* + * 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 "modules/rtp_rtcp/source/packet_sequencer.h" + +#include "rtc_base/checks.h" +#include "rtc_base/random.h" + +namespace webrtc { + +namespace { +// RED header is first byte of payload, if present. +constexpr size_t kRedForFecHeaderLength = 1; + +// Timestamps use a 90kHz clock. +constexpr uint32_t kTimestampTicksPerMs = 90; +} // namespace + +PacketSequencer::PacketSequencer(uint32_t media_ssrc, + absl::optional<uint32_t> rtx_ssrc, + bool require_marker_before_media_padding, + Clock* clock) + : media_ssrc_(media_ssrc), + rtx_ssrc_(rtx_ssrc), + require_marker_before_media_padding_(require_marker_before_media_padding), + clock_(clock), + media_sequence_number_(0), + rtx_sequence_number_(0), + last_payload_type_(-1), + last_rtp_timestamp_(0), + last_capture_time_ms_(0), + last_timestamp_time_ms_(0), + last_packet_marker_bit_(false) { + Random random(clock_->TimeInMicroseconds()); + // Random start, 16 bits. Upper half of range is avoided in order to prevent + // wraparound issues during startup. Sequence number 0 is avoided for + // historical reasons, presumably to avoid debugability or test usage + // conflicts. + constexpr uint16_t kMaxInitRtpSeqNumber = 0x7fff; // 2^15 - 1. + media_sequence_number_ = random.Rand(1, kMaxInitRtpSeqNumber); + rtx_sequence_number_ = random.Rand(1, kMaxInitRtpSeqNumber); +} + +void PacketSequencer::Sequence(RtpPacketToSend& packet) { + if (packet.Ssrc() == media_ssrc_) { + if (packet.packet_type() == RtpPacketMediaType::kRetransmission) { + // Retransmission of an already sequenced packet, ignore. + return; + } else if (packet.packet_type() == RtpPacketMediaType::kPadding) { + PopulatePaddingFields(packet); + } + packet.SetSequenceNumber(media_sequence_number_++); + if (packet.packet_type() != RtpPacketMediaType::kPadding) { + UpdateLastPacketState(packet); + } + } else if (packet.Ssrc() == rtx_ssrc_) { + if (packet.packet_type() == RtpPacketMediaType::kPadding) { + PopulatePaddingFields(packet); + } + packet.SetSequenceNumber(rtx_sequence_number_++); + } else { + RTC_DCHECK_NOTREACHED() << "Unexpected ssrc " << packet.Ssrc(); + } +} + +void PacketSequencer::SetRtpState(const RtpState& state) { + media_sequence_number_ = state.sequence_number; + last_rtp_timestamp_ = state.timestamp; + last_capture_time_ms_ = state.capture_time_ms; + last_timestamp_time_ms_ = state.last_timestamp_time_ms; +} + +void PacketSequencer::PopulateRtpState(RtpState& state) const { + state.sequence_number = media_sequence_number_; + state.timestamp = last_rtp_timestamp_; + state.capture_time_ms = last_capture_time_ms_; + state.last_timestamp_time_ms = last_timestamp_time_ms_; +} + +void PacketSequencer::UpdateLastPacketState(const RtpPacketToSend& packet) { + // Remember marker bit to determine if padding can be inserted with + // sequence number following `packet`. + last_packet_marker_bit_ = packet.Marker(); + // Remember media payload type to use in the padding packet if rtx is + // disabled. + if (packet.is_red()) { + RTC_DCHECK_GE(packet.payload_size(), kRedForFecHeaderLength); + last_payload_type_ = packet.PayloadBuffer()[0]; + } else { + last_payload_type_ = packet.PayloadType(); + } + // Save timestamps to generate timestamp field and extensions for the padding. + last_rtp_timestamp_ = packet.Timestamp(); + last_timestamp_time_ms_ = clock_->TimeInMilliseconds(); + last_capture_time_ms_ = packet.capture_time().ms(); +} + +void PacketSequencer::PopulatePaddingFields(RtpPacketToSend& packet) { + if (packet.Ssrc() == media_ssrc_) { + RTC_DCHECK(CanSendPaddingOnMediaSsrc()); + + packet.SetTimestamp(last_rtp_timestamp_); + packet.set_capture_time(Timestamp::Millis(last_capture_time_ms_)); + packet.SetPayloadType(last_payload_type_); + return; + } + + RTC_DCHECK(packet.Ssrc() == rtx_ssrc_); + if (packet.payload_size() > 0) { + // This is payload padding packet, don't update timestamp fields. + return; + } + + packet.SetTimestamp(last_rtp_timestamp_); + packet.set_capture_time(Timestamp::Millis(last_capture_time_ms_)); + + // Only change the timestamp of padding packets sent over RTX. + // Padding only packets over RTP has to be sent as part of a media + // frame (and therefore the same timestamp). + int64_t now_ms = clock_->TimeInMilliseconds(); + if (last_timestamp_time_ms_ > 0) { + packet.SetTimestamp(packet.Timestamp() + + (now_ms - last_timestamp_time_ms_) * + kTimestampTicksPerMs); + if (packet.capture_time() > Timestamp::Zero()) { + packet.set_capture_time( + packet.capture_time() + + TimeDelta::Millis(now_ms - last_timestamp_time_ms_)); + } + } +} + +bool PacketSequencer::CanSendPaddingOnMediaSsrc() const { + if (last_payload_type_ == -1) { + return false; + } + + // Without RTX we can't send padding in the middle of frames. + // For audio marker bits doesn't mark the end of a frame and frames + // are usually a single packet, so for now we don't apply this rule + // for audio. + if (require_marker_before_media_padding_ && !last_packet_marker_bit_) { + return false; + } + + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.h b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.h new file mode 100644 index 0000000000..7c0dee7a5b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.h @@ -0,0 +1,77 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_PACKET_SEQUENCER_H_ +#define MODULES_RTP_RTCP_SOURCE_PACKET_SEQUENCER_H_ + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +// Helper class used to assign RTP sequence numbers and populate some fields for +// padding packets based on the last sequenced packets. +// This class is not thread safe, the caller must provide that. +class PacketSequencer { + public: + // If `require_marker_before_media_padding_` is true, padding packets on the + // media ssrc is not allowed unless the last sequenced media packet had the + // marker bit set (i.e. don't insert padding packets between the first and + // last packets of a video frame). + // Packets with unknown SSRCs will be ignored. + PacketSequencer(uint32_t media_ssrc, + absl::optional<uint32_t> rtx_ssrc, + bool require_marker_before_media_padding, + Clock* clock); + + // Assigns sequence number, and in the case of non-RTX padding also timestamps + // and payload type. + void Sequence(RtpPacketToSend& packet); + + void set_media_sequence_number(uint16_t sequence_number) { + media_sequence_number_ = sequence_number; + } + void set_rtx_sequence_number(uint16_t sequence_number) { + rtx_sequence_number_ = sequence_number; + } + + void SetRtpState(const RtpState& state); + void PopulateRtpState(RtpState& state) const; + + uint16_t media_sequence_number() const { return media_sequence_number_; } + uint16_t rtx_sequence_number() const { return rtx_sequence_number_; } + + // Checks whether it is allowed to send padding on the media SSRC at this + // time, e.g. that we don't send padding in the middle of a video frame. + bool CanSendPaddingOnMediaSsrc() const; + + private: + void UpdateLastPacketState(const RtpPacketToSend& packet); + void PopulatePaddingFields(RtpPacketToSend& packet); + + const uint32_t media_ssrc_; + const absl::optional<uint32_t> rtx_ssrc_; + const bool require_marker_before_media_padding_; + Clock* const clock_; + + uint16_t media_sequence_number_; + uint16_t rtx_sequence_number_; + + int8_t last_payload_type_; + uint32_t last_rtp_timestamp_; + int64_t last_capture_time_ms_; + int64_t last_timestamp_time_ms_; + bool last_packet_marker_bit_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_PACKET_SEQUENCER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer_unittest.cc new file mode 100644 index 0000000000..d892863768 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer_unittest.cc @@ -0,0 +1,250 @@ +/* + * 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 "modules/rtp_rtcp/source/packet_sequencer.h" + +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +constexpr Timestamp kStartTime = Timestamp::Millis(98765); +constexpr uint32_t kMediaSsrc = 123456; +constexpr uint32_t kRtxSsrc = 123457; +constexpr uint8_t kMediaPayloadType = 42; +constexpr uint16_t kMediaStartSequenceNumber = 123; +constexpr uint16_t kRtxStartSequenceNumber = 234; +constexpr uint16_t kDefaultSequenceNumber = 0x1234; +constexpr uint32_t kStartRtpTimestamp = 798; + +class PacketSequencerTest : public ::testing::Test { + public: + PacketSequencerTest() + : clock_(kStartTime), + sequencer_(kMediaSsrc, + kRtxSsrc, + /*require_marker_before_media_padding=*/true, + &clock_) {} + + RtpPacketToSend CreatePacket(RtpPacketMediaType type, uint32_t ssrc) { + RtpPacketToSend packet(/*extension_manager=*/nullptr); + packet.set_packet_type(type); + packet.SetSsrc(ssrc); + packet.SetSequenceNumber(kDefaultSequenceNumber); + packet.set_capture_time(clock_.CurrentTime()); + packet.SetTimestamp( + kStartRtpTimestamp + + static_cast<uint32_t>(packet.capture_time().ms() - kStartTime.ms())); + return packet; + } + + protected: + SimulatedClock clock_; + PacketSequencer sequencer_; +}; + +TEST_F(PacketSequencerTest, IgnoresMediaSsrcRetransmissions) { + RtpPacketToSend packet = + CreatePacket(RtpPacketMediaType::kRetransmission, kMediaSsrc); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(packet); + EXPECT_EQ(packet.SequenceNumber(), kDefaultSequenceNumber); + EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber); +} + +TEST_F(PacketSequencerTest, SequencesAudio) { + RtpPacketToSend packet = CreatePacket(RtpPacketMediaType::kAudio, kMediaSsrc); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(packet); + EXPECT_EQ(packet.SequenceNumber(), kMediaStartSequenceNumber); + EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber + 1); +} + +TEST_F(PacketSequencerTest, SequencesVideo) { + RtpPacketToSend packet = CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(packet); + EXPECT_EQ(packet.SequenceNumber(), kMediaStartSequenceNumber); + EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber + 1); +} + +TEST_F(PacketSequencerTest, SequencesUlpFec) { + RtpPacketToSend packet = + CreatePacket(RtpPacketMediaType::kForwardErrorCorrection, kMediaSsrc); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(packet); + EXPECT_EQ(packet.SequenceNumber(), kMediaStartSequenceNumber); + EXPECT_EQ(sequencer_.media_sequence_number(), kMediaStartSequenceNumber + 1); +} + +TEST_F(PacketSequencerTest, SequencesRtxRetransmissions) { + RtpPacketToSend packet = + CreatePacket(RtpPacketMediaType::kRetransmission, kRtxSsrc); + sequencer_.set_rtx_sequence_number(kRtxStartSequenceNumber); + sequencer_.Sequence(packet); + EXPECT_EQ(packet.SequenceNumber(), kRtxStartSequenceNumber); + EXPECT_EQ(sequencer_.rtx_sequence_number(), kRtxStartSequenceNumber + 1); +} + +TEST_F(PacketSequencerTest, ProhibitsPaddingWithinVideoFrame) { + // Send a video packet with the marker bit set to false (indicating it is not + // the last packet of a frame). + RtpPacketToSend media_packet = + CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc); + media_packet.SetPayloadType(kMediaPayloadType); + media_packet.SetMarker(false); + sequencer_.Sequence(media_packet); + + // Sending padding on the media SSRC should not be allowed at this point. + EXPECT_FALSE(sequencer_.CanSendPaddingOnMediaSsrc()); + + // Send a video packet with marker set to true, padding should be allowed + // again. + media_packet.SetMarker(true); + sequencer_.Sequence(media_packet); + EXPECT_TRUE(sequencer_.CanSendPaddingOnMediaSsrc()); +} + +TEST_F(PacketSequencerTest, AllowsPaddingAtAnyTimeIfSoConfigured) { + PacketSequencer packet_sequencer( + kMediaSsrc, kRtxSsrc, + /*require_marker_before_media_padding=*/false, &clock_); + + // Send an audio packet with the marker bit set to false. + RtpPacketToSend media_packet = + CreatePacket(RtpPacketMediaType::kAudio, kMediaSsrc); + media_packet.SetPayloadType(kMediaPayloadType); + media_packet.SetMarker(false); + packet_sequencer.Sequence(media_packet); + + // Sending padding on the media SSRC should be allowed despite no marker bit. + EXPECT_TRUE(packet_sequencer.CanSendPaddingOnMediaSsrc()); +} + +TEST_F(PacketSequencerTest, UpdatesPaddingBasedOnLastMediaPacket) { + // First send a media packet. + RtpPacketToSend media_packet = + CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc); + media_packet.SetPayloadType(kMediaPayloadType); + media_packet.SetMarker(true); + // Advance time so current time doesn't exactly match timestamp. + clock_.AdvanceTime(TimeDelta::Millis(5)); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(media_packet); + + // Next send a padding packet and verify the media packet's timestamps and + // payload type is transferred to the padding packet. + RtpPacketToSend padding_packet = + CreatePacket(RtpPacketMediaType::kPadding, kMediaSsrc); + padding_packet.SetPadding(/*padding_size=*/100); + sequencer_.Sequence(padding_packet); + + EXPECT_EQ(padding_packet.SequenceNumber(), kMediaStartSequenceNumber + 1); + EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType); + EXPECT_EQ(padding_packet.Timestamp(), media_packet.Timestamp()); + EXPECT_EQ(padding_packet.capture_time(), media_packet.capture_time()); +} + +TEST_F(PacketSequencerTest, UpdatesPaddingBasedOnLastRedPacket) { + // First send a media packet. + RtpPacketToSend media_packet = + CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc); + media_packet.SetPayloadType(kMediaPayloadType); + // Simulate a packet with RED encapsulation; + media_packet.set_is_red(true); + uint8_t* payload_buffer = media_packet.SetPayloadSize(1); + payload_buffer[0] = kMediaPayloadType + 1; + + media_packet.SetMarker(true); + // Advance time so current time doesn't exactly match timestamp. + clock_.AdvanceTime(TimeDelta::Millis(5)); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(media_packet); + + // Next send a padding packet and verify the media packet's timestamps and + // payload type is transferred to the padding packet. + RtpPacketToSend padding_packet = + CreatePacket(RtpPacketMediaType::kPadding, kMediaSsrc); + padding_packet.SetPadding(100); + sequencer_.Sequence(padding_packet); + + EXPECT_EQ(padding_packet.SequenceNumber(), kMediaStartSequenceNumber + 1); + EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType + 1); + EXPECT_EQ(padding_packet.Timestamp(), media_packet.Timestamp()); + EXPECT_EQ(padding_packet.capture_time(), media_packet.capture_time()); +} + +TEST_F(PacketSequencerTest, DoesNotUpdateFieldsOnPayloadPadding) { + // First send a media packet. + RtpPacketToSend media_packet = + CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc); + media_packet.SetPayloadType(kMediaPayloadType); + media_packet.SetMarker(true); + // Advance time so current time doesn't exactly match timestamp. + clock_.AdvanceTime(TimeDelta::Millis(5)); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(media_packet); + + // Simulate a payload padding packet on the RTX SSRC. + RtpPacketToSend padding_packet = + CreatePacket(RtpPacketMediaType::kPadding, kRtxSsrc); + padding_packet.SetPayloadSize(100); + padding_packet.SetPayloadType(kMediaPayloadType + 1); + padding_packet.SetTimestamp(kStartRtpTimestamp + 1); + padding_packet.set_capture_time(kStartTime + TimeDelta::Millis(1)); + sequencer_.set_rtx_sequence_number(kRtxStartSequenceNumber); + sequencer_.Sequence(padding_packet); + + // The sequence number should be updated, but timestamps kept. + EXPECT_EQ(padding_packet.SequenceNumber(), kRtxStartSequenceNumber); + EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType + 1); + EXPECT_EQ(padding_packet.Timestamp(), kStartRtpTimestamp + 1); + EXPECT_EQ(padding_packet.capture_time(), kStartTime + TimeDelta::Millis(1)); +} + +TEST_F(PacketSequencerTest, UpdatesRtxPaddingBasedOnLastMediaPacket) { + constexpr uint32_t kTimestampTicksPerMs = 90; + + // First send a media packet. + RtpPacketToSend media_packet = + CreatePacket(RtpPacketMediaType::kVideo, kMediaSsrc); + media_packet.SetPayloadType(kMediaPayloadType); + media_packet.SetMarker(true); + sequencer_.set_media_sequence_number(kMediaStartSequenceNumber); + sequencer_.Sequence(media_packet); + + // Advance time, this time delta will be used to interpolate padding + // timestamps. + constexpr TimeDelta kTimeDelta = TimeDelta::Millis(10); + clock_.AdvanceTime(kTimeDelta); + + RtpPacketToSend padding_packet = + CreatePacket(RtpPacketMediaType::kPadding, kRtxSsrc); + padding_packet.SetPadding(100); + padding_packet.SetPayloadType(kMediaPayloadType + 1); + sequencer_.set_rtx_sequence_number(kRtxStartSequenceNumber); + sequencer_.Sequence(padding_packet); + + // Assigned RTX sequence number, but payload type unchanged in this case. + EXPECT_EQ(padding_packet.SequenceNumber(), kRtxStartSequenceNumber); + EXPECT_EQ(padding_packet.PayloadType(), kMediaPayloadType + 1); + // Timestamps are offset realtive to last media packet. + EXPECT_EQ( + padding_packet.Timestamp(), + media_packet.Timestamp() + (kTimeDelta.ms() * kTimestampTicksPerMs)); + EXPECT_EQ(padding_packet.capture_time(), + media_packet.capture_time() + kTimeDelta); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc new file mode 100644 index 0000000000..1dc756d876 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc @@ -0,0 +1,431 @@ +/* + * 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 "modules/rtp_rtcp/source/receive_statistics_impl.h" + +#include <cmath> +#include <cstdlib> +#include <memory> +#include <utility> +#include <vector> + +#include "api/units/time_delta.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace { +constexpr int64_t kStatisticsTimeoutMs = 8000; +constexpr int64_t kStatisticsProcessIntervalMs = 1000; +} // namespace + +StreamStatistician::~StreamStatistician() {} + +StreamStatisticianImpl::StreamStatisticianImpl(uint32_t ssrc, Clock* clock, + int max_reordering_threshold) + : ssrc_(ssrc), + clock_(clock), + delta_internal_unix_epoch_ms_(clock_->CurrentNtpInMilliseconds() - + clock_->TimeInMilliseconds() - + rtc::kNtpJan1970Millisecs), + incoming_bitrate_(kStatisticsProcessIntervalMs, + RateStatistics::kBpsScale), + max_reordering_threshold_(max_reordering_threshold), + enable_retransmit_detection_(false), + cumulative_loss_is_capped_(false), + jitter_q4_(0), + cumulative_loss_(0), + cumulative_loss_rtcp_offset_(0), + last_receive_time_ms_(0), + last_received_timestamp_(0), + received_seq_first_(-1), + received_seq_max_(-1), + last_report_cumulative_loss_(0), + last_report_seq_max_(-1), + last_payload_type_frequency_(0) {} + +StreamStatisticianImpl::~StreamStatisticianImpl() = default; + +bool StreamStatisticianImpl::UpdateOutOfOrder(const RtpPacketReceived& packet, + int64_t sequence_number, + int64_t now_ms) { + // Check if `packet` is second packet of a stream restart. + if (received_seq_out_of_order_) { + // Count the previous packet as a received; it was postponed below. + --cumulative_loss_; + + uint16_t expected_sequence_number = *received_seq_out_of_order_ + 1; + received_seq_out_of_order_ = absl::nullopt; + if (packet.SequenceNumber() == expected_sequence_number) { + // Ignore sequence number gap caused by stream restart for packet loss + // calculation, by setting received_seq_max_ to the sequence number just + // before the out-of-order seqno. This gives a net zero change of + // `cumulative_loss_`, for the two packets interpreted as a stream reset. + // + // Fraction loss for the next report may get a bit off, since we don't + // update last_report_seq_max_ and last_report_cumulative_loss_ in a + // consistent way. + last_report_seq_max_ = sequence_number - 2; + received_seq_max_ = sequence_number - 2; + return false; + } + } + + if (std::abs(sequence_number - received_seq_max_) > + max_reordering_threshold_) { + // Sequence number gap looks too large, wait until next packet to check + // for a stream restart. + received_seq_out_of_order_ = packet.SequenceNumber(); + // Postpone counting this as a received packet until we know how to update + // `received_seq_max_`, otherwise we temporarily decrement + // `cumulative_loss_`. The + // ReceiveStatisticsTest.StreamRestartDoesntCountAsLoss test expects + // `cumulative_loss_` to be unchanged by the reception of the first packet + // after stream reset. + ++cumulative_loss_; + return true; + } + + if (sequence_number > received_seq_max_) + return false; + + // Old out of order packet, may be retransmit. + if (enable_retransmit_detection_ && IsRetransmitOfOldPacket(packet, now_ms)) + receive_counters_.retransmitted.AddPacket(packet); + return true; +} + +void StreamStatisticianImpl::UpdateCounters(const RtpPacketReceived& packet) { + RTC_DCHECK_EQ(ssrc_, packet.Ssrc()); + int64_t now_ms = clock_->TimeInMilliseconds(); + + incoming_bitrate_.Update(packet.size(), now_ms); + receive_counters_.last_packet_received_timestamp_ms = now_ms; + receive_counters_.transmitted.AddPacket(packet); + --cumulative_loss_; + + // Use PeekUnwrap and later update the state to avoid updating the state for + // out of order packets. + int64_t sequence_number = seq_unwrapper_.PeekUnwrap(packet.SequenceNumber()); + + if (!ReceivedRtpPacket()) { + received_seq_first_ = sequence_number; + last_report_seq_max_ = sequence_number - 1; + received_seq_max_ = sequence_number - 1; + receive_counters_.first_packet_time_ms = now_ms; + } else if (UpdateOutOfOrder(packet, sequence_number, now_ms)) { + return; + } + // In order packet. + cumulative_loss_ += sequence_number - received_seq_max_; + received_seq_max_ = sequence_number; + // Update the internal state of `seq_unwrapper_`. + seq_unwrapper_.Unwrap(packet.SequenceNumber()); + + // If new time stamp and more than one in-order packet received, calculate + // new jitter statistics. + if (packet.Timestamp() != last_received_timestamp_ && + (receive_counters_.transmitted.packets - + receive_counters_.retransmitted.packets) > 1) { + UpdateJitter(packet, now_ms); + } + last_received_timestamp_ = packet.Timestamp(); + last_receive_time_ms_ = now_ms; +} + +void StreamStatisticianImpl::UpdateJitter(const RtpPacketReceived& packet, + int64_t receive_time_ms) { + int64_t receive_diff_ms = receive_time_ms - last_receive_time_ms_; + RTC_DCHECK_GE(receive_diff_ms, 0); + uint32_t receive_diff_rtp = static_cast<uint32_t>( + (receive_diff_ms * packet.payload_type_frequency()) / 1000); + int32_t time_diff_samples = + receive_diff_rtp - (packet.Timestamp() - last_received_timestamp_); + + time_diff_samples = std::abs(time_diff_samples); + + ReviseFrequencyAndJitter(packet.payload_type_frequency()); + + // lib_jingle sometimes deliver crazy jumps in TS for the same stream. + // If this happens, don't update jitter value. Use 5 secs video frequency + // as the threshold. + if (time_diff_samples < 450000) { + // Note we calculate in Q4 to avoid using float. + int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_; + jitter_q4_ += ((jitter_diff_q4 + 8) >> 4); + } +} + +void StreamStatisticianImpl::ReviseFrequencyAndJitter( + int payload_type_frequency) { + if (payload_type_frequency == last_payload_type_frequency_) { + return; + } + + if (payload_type_frequency != 0) { + if (last_payload_type_frequency_ != 0) { + // Value in "jitter_q4_" variable is a number of samples. + // I.e. jitter = timestamp (ms) * frequency (kHz). + // Since the frequency has changed we have to update the number of samples + // accordingly. The new value should rely on a new frequency. + + // If we don't do such procedure we end up with the number of samples that + // cannot be converted into milliseconds correctly + // (i.e. jitter_ms = jitter_q4_ >> 4 / (payload_type_frequency / 1000)). + // In such case, the number of samples has a "mix". + + // Doing so we pretend that everything prior and including the current + // packet were computed on packet's frequency. + jitter_q4_ = static_cast<int>(static_cast<uint64_t>(jitter_q4_) * + payload_type_frequency / + last_payload_type_frequency_); + } + // If last_payload_type_frequency_ is not present, the jitter_q4_ + // variable has its initial value. + + // Keep last_payload_type_frequency_ up to date and non-zero (set). + last_payload_type_frequency_ = payload_type_frequency; + } +} + +void StreamStatisticianImpl::SetMaxReorderingThreshold( + int max_reordering_threshold) { + max_reordering_threshold_ = max_reordering_threshold; +} + +void StreamStatisticianImpl::EnableRetransmitDetection(bool enable) { + enable_retransmit_detection_ = enable; +} + +RtpReceiveStats StreamStatisticianImpl::GetStats() const { + RtpReceiveStats stats; + stats.packets_lost = cumulative_loss_; + // Note: internal jitter value is in Q4 and needs to be scaled by 1/16. + stats.jitter = jitter_q4_ >> 4; + if (last_payload_type_frequency_ > 0) { + // Divide value in fractional seconds by frequency to get jitter in + // fractional seconds. + stats.interarrival_jitter = + webrtc::TimeDelta::Seconds(stats.jitter) / last_payload_type_frequency_; + } + if (receive_counters_.last_packet_received_timestamp_ms.has_value()) { + stats.last_packet_received_timestamp_ms = + *receive_counters_.last_packet_received_timestamp_ms + + delta_internal_unix_epoch_ms_; + } + stats.packet_counter = receive_counters_.transmitted; + return stats; +} + +void StreamStatisticianImpl::MaybeAppendReportBlockAndReset( + std::vector<rtcp::ReportBlock>& report_blocks) { + int64_t now_ms = clock_->TimeInMilliseconds(); + if (now_ms - last_receive_time_ms_ >= kStatisticsTimeoutMs) { + // Not active. + return; + } + if (!ReceivedRtpPacket()) { + return; + } + + report_blocks.emplace_back(); + rtcp::ReportBlock& stats = report_blocks.back(); + stats.SetMediaSsrc(ssrc_); + // Calculate fraction lost. + int64_t exp_since_last = received_seq_max_ - last_report_seq_max_; + RTC_DCHECK_GE(exp_since_last, 0); + + int32_t lost_since_last = cumulative_loss_ - last_report_cumulative_loss_; + if (exp_since_last > 0 && lost_since_last > 0) { + // Scale 0 to 255, where 255 is 100% loss. + stats.SetFractionLost(255 * lost_since_last / exp_since_last); + } + + int packets_lost = cumulative_loss_ + cumulative_loss_rtcp_offset_; + if (packets_lost < 0) { + // Clamp to zero. Work around to accomodate for senders that misbehave with + // negative cumulative loss. + packets_lost = 0; + cumulative_loss_rtcp_offset_ = -cumulative_loss_; + } + if (packets_lost > 0x7fffff) { + // Packets lost is a 24 bit signed field, and thus should be clamped, as + // described in https://datatracker.ietf.org/doc/html/rfc3550#appendix-A.3 + if (!cumulative_loss_is_capped_) { + cumulative_loss_is_capped_ = true; + RTC_LOG(LS_WARNING) << "Cumulative loss reached maximum value for ssrc " + << ssrc_; + } + packets_lost = 0x7fffff; + } + stats.SetCumulativeLost(packets_lost); + stats.SetExtHighestSeqNum(received_seq_max_); + // Note: internal jitter value is in Q4 and needs to be scaled by 1/16. + stats.SetJitter(jitter_q4_ >> 4); + + // Only for report blocks in RTCP SR and RR. + last_report_cumulative_loss_ = cumulative_loss_; + last_report_seq_max_ = received_seq_max_; + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "cumulative_loss_pkts", now_ms, + cumulative_loss_, ssrc_); + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "received_seq_max_pkts", now_ms, + (received_seq_max_ - received_seq_first_), + ssrc_); +} + +absl::optional<int> StreamStatisticianImpl::GetFractionLostInPercent() const { + if (!ReceivedRtpPacket()) { + return absl::nullopt; + } + int64_t expected_packets = 1 + received_seq_max_ - received_seq_first_; + if (expected_packets <= 0) { + return absl::nullopt; + } + if (cumulative_loss_ <= 0) { + return 0; + } + return 100 * static_cast<int64_t>(cumulative_loss_) / expected_packets; +} + +StreamDataCounters StreamStatisticianImpl::GetReceiveStreamDataCounters() + const { + return receive_counters_; +} + +uint32_t StreamStatisticianImpl::BitrateReceived() const { + return incoming_bitrate_.Rate(clock_->TimeInMilliseconds()).value_or(0); +} + +bool StreamStatisticianImpl::IsRetransmitOfOldPacket( + const RtpPacketReceived& packet, + int64_t now_ms) const { + uint32_t frequency_khz = packet.payload_type_frequency() / 1000; + RTC_DCHECK_GT(frequency_khz, 0); + + int64_t time_diff_ms = now_ms - last_receive_time_ms_; + + // Diff in time stamp since last received in order. + uint32_t timestamp_diff = packet.Timestamp() - last_received_timestamp_; + uint32_t rtp_time_stamp_diff_ms = timestamp_diff / frequency_khz; + + int64_t max_delay_ms = 0; + + // Jitter standard deviation in samples. + float jitter_std = std::sqrt(static_cast<float>(jitter_q4_ >> 4)); + + // 2 times the standard deviation => 95% confidence. + // And transform to milliseconds by dividing by the frequency in kHz. + max_delay_ms = static_cast<int64_t>((2 * jitter_std) / frequency_khz); + + // Min max_delay_ms is 1. + if (max_delay_ms == 0) { + max_delay_ms = 1; + } + return time_diff_ms > rtp_time_stamp_diff_ms + max_delay_ms; +} + +std::unique_ptr<ReceiveStatistics> ReceiveStatistics::Create(Clock* clock) { + return std::make_unique<ReceiveStatisticsLocked>( + clock, [](uint32_t ssrc, Clock* clock, int max_reordering_threshold) { + return std::make_unique<StreamStatisticianLocked>( + ssrc, clock, max_reordering_threshold); + }); +} + +std::unique_ptr<ReceiveStatistics> ReceiveStatistics::CreateThreadCompatible( + Clock* clock) { + return std::make_unique<ReceiveStatisticsImpl>( + clock, [](uint32_t ssrc, Clock* clock, int max_reordering_threshold) { + return std::make_unique<StreamStatisticianImpl>( + ssrc, clock, max_reordering_threshold); + }); +} + +ReceiveStatisticsImpl::ReceiveStatisticsImpl( + Clock* clock, + std::function<std::unique_ptr<StreamStatisticianImplInterface>( + uint32_t ssrc, + Clock* clock, + int max_reordering_threshold)> stream_statistician_factory) + : clock_(clock), + stream_statistician_factory_(std::move(stream_statistician_factory)), + last_returned_ssrc_idx_(0), + max_reordering_threshold_(kDefaultMaxReorderingThreshold) {} + +void ReceiveStatisticsImpl::OnRtpPacket(const RtpPacketReceived& packet) { + // StreamStatisticianImpl instance is created once and only destroyed when + // this whole ReceiveStatisticsImpl is destroyed. StreamStatisticianImpl has + // it's own locking so don't hold receive_statistics_lock_ (potential + // deadlock). + GetOrCreateStatistician(packet.Ssrc())->UpdateCounters(packet); +} + +StreamStatistician* ReceiveStatisticsImpl::GetStatistician( + uint32_t ssrc) const { + const auto& it = statisticians_.find(ssrc); + if (it == statisticians_.end()) + return nullptr; + return it->second.get(); +} + +StreamStatisticianImplInterface* ReceiveStatisticsImpl::GetOrCreateStatistician( + uint32_t ssrc) { + std::unique_ptr<StreamStatisticianImplInterface>& impl = statisticians_[ssrc]; + if (impl == nullptr) { // new element + impl = + stream_statistician_factory_(ssrc, clock_, max_reordering_threshold_); + all_ssrcs_.push_back(ssrc); + } + return impl.get(); +} + +void ReceiveStatisticsImpl::SetMaxReorderingThreshold( + int max_reordering_threshold) { + max_reordering_threshold_ = max_reordering_threshold; + for (auto& statistician : statisticians_) { + statistician.second->SetMaxReorderingThreshold(max_reordering_threshold); + } +} + +void ReceiveStatisticsImpl::SetMaxReorderingThreshold( + uint32_t ssrc, + int max_reordering_threshold) { + GetOrCreateStatistician(ssrc)->SetMaxReorderingThreshold( + max_reordering_threshold); +} + +void ReceiveStatisticsImpl::EnableRetransmitDetection(uint32_t ssrc, + bool enable) { + GetOrCreateStatistician(ssrc)->EnableRetransmitDetection(enable); +} + +std::vector<rtcp::ReportBlock> ReceiveStatisticsImpl::RtcpReportBlocks( + size_t max_blocks) { + std::vector<rtcp::ReportBlock> result; + result.reserve(std::min(max_blocks, all_ssrcs_.size())); + + size_t ssrc_idx = 0; + for (size_t i = 0; i < all_ssrcs_.size() && result.size() < max_blocks; ++i) { + ssrc_idx = (last_returned_ssrc_idx_ + i + 1) % all_ssrcs_.size(); + const uint32_t media_ssrc = all_ssrcs_[ssrc_idx]; + auto statistician_it = statisticians_.find(media_ssrc); + RTC_DCHECK(statistician_it != statisticians_.end()); + statistician_it->second->MaybeAppendReportBlockAndReset(result); + } + last_returned_ssrc_idx_ = ssrc_idx; + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.h b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.h new file mode 100644 index 0000000000..11853b3244 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.h @@ -0,0 +1,250 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RECEIVE_STATISTICS_IMPL_H_ +#define MODULES_RTP_RTCP_SOURCE_RECEIVE_STATISTICS_IMPL_H_ + +#include <algorithm> +#include <functional> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "rtc_base/containers/flat_map.h" +#include "rtc_base/numerics/sequence_number_unwrapper.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +// Extends StreamStatistician with methods needed by the implementation. +class StreamStatisticianImplInterface : public StreamStatistician { + public: + virtual ~StreamStatisticianImplInterface() = default; + virtual void MaybeAppendReportBlockAndReset( + std::vector<rtcp::ReportBlock>& report_blocks) = 0; + virtual void SetMaxReorderingThreshold(int max_reordering_threshold) = 0; + virtual void EnableRetransmitDetection(bool enable) = 0; + virtual void UpdateCounters(const RtpPacketReceived& packet) = 0; +}; + +// Thread-compatible implementation of StreamStatisticianImplInterface. +class StreamStatisticianImpl : public StreamStatisticianImplInterface { + public: + StreamStatisticianImpl(uint32_t ssrc, + Clock* clock, + int max_reordering_threshold); + ~StreamStatisticianImpl() override; + + // Implements StreamStatistician + RtpReceiveStats GetStats() const override; + absl::optional<int> GetFractionLostInPercent() const override; + StreamDataCounters GetReceiveStreamDataCounters() const override; + uint32_t BitrateReceived() const override; + + // Implements StreamStatisticianImplInterface + void MaybeAppendReportBlockAndReset( + std::vector<rtcp::ReportBlock>& report_blocks) override; + void SetMaxReorderingThreshold(int max_reordering_threshold) override; + void EnableRetransmitDetection(bool enable) override; + // Updates StreamStatistician for incoming packets. + void UpdateCounters(const RtpPacketReceived& packet) override; + + private: + bool IsRetransmitOfOldPacket(const RtpPacketReceived& packet, + int64_t now_ms) const; + void UpdateJitter(const RtpPacketReceived& packet, int64_t receive_time_ms); + void ReviseFrequencyAndJitter(int payload_type_frequency); + // Updates StreamStatistician for out of order packets. + // Returns true if packet considered to be out of order. + bool UpdateOutOfOrder(const RtpPacketReceived& packet, + int64_t sequence_number, + int64_t now_ms); + // Checks if this StreamStatistician received any rtp packets. + bool ReceivedRtpPacket() const { return received_seq_first_ >= 0; } + + const uint32_t ssrc_; + Clock* const clock_; + // Delta used to map internal timestamps to Unix epoch ones. + const int64_t delta_internal_unix_epoch_ms_; + RateStatistics incoming_bitrate_; + // In number of packets or sequence numbers. + int max_reordering_threshold_; + bool enable_retransmit_detection_; + bool cumulative_loss_is_capped_; + + // Stats on received RTP packets. + uint32_t jitter_q4_; + // Cumulative loss according to RFC 3550, which may be negative (and often is, + // if packets are reordered and there are non-RTX retransmissions). + int32_t cumulative_loss_; + // Offset added to outgoing rtcp reports, to make ensure that the reported + // cumulative loss is non-negative. Reports with negative values confuse some + // senders, in particular, our own loss-based bandwidth estimator. + int32_t cumulative_loss_rtcp_offset_; + + int64_t last_receive_time_ms_; + uint32_t last_received_timestamp_; + RtpSequenceNumberUnwrapper seq_unwrapper_; + int64_t received_seq_first_; + int64_t received_seq_max_; + // Assume that the other side restarted when there are two sequential packets + // with large jump from received_seq_max_. + absl::optional<uint16_t> received_seq_out_of_order_; + + // Current counter values. + StreamDataCounters receive_counters_; + + // Counter values when we sent the last report. + int32_t last_report_cumulative_loss_; + int64_t last_report_seq_max_; + + // The sample frequency of the last received packet. + int last_payload_type_frequency_; +}; + +// Thread-safe implementation of StreamStatisticianImplInterface. +class StreamStatisticianLocked : public StreamStatisticianImplInterface { + public: + StreamStatisticianLocked(uint32_t ssrc, + Clock* clock, + int max_reordering_threshold) + : impl_(ssrc, clock, max_reordering_threshold) {} + ~StreamStatisticianLocked() override = default; + + RtpReceiveStats GetStats() const override { + MutexLock lock(&stream_lock_); + return impl_.GetStats(); + } + absl::optional<int> GetFractionLostInPercent() const override { + MutexLock lock(&stream_lock_); + return impl_.GetFractionLostInPercent(); + } + StreamDataCounters GetReceiveStreamDataCounters() const override { + MutexLock lock(&stream_lock_); + return impl_.GetReceiveStreamDataCounters(); + } + uint32_t BitrateReceived() const override { + MutexLock lock(&stream_lock_); + return impl_.BitrateReceived(); + } + void MaybeAppendReportBlockAndReset( + std::vector<rtcp::ReportBlock>& report_blocks) override { + MutexLock lock(&stream_lock_); + impl_.MaybeAppendReportBlockAndReset(report_blocks); + } + void SetMaxReorderingThreshold(int max_reordering_threshold) override { + MutexLock lock(&stream_lock_); + return impl_.SetMaxReorderingThreshold(max_reordering_threshold); + } + void EnableRetransmitDetection(bool enable) override { + MutexLock lock(&stream_lock_); + return impl_.EnableRetransmitDetection(enable); + } + void UpdateCounters(const RtpPacketReceived& packet) override { + MutexLock lock(&stream_lock_); + return impl_.UpdateCounters(packet); + } + + private: + mutable Mutex stream_lock_; + StreamStatisticianImpl impl_ RTC_GUARDED_BY(&stream_lock_); +}; + +// Thread-compatible implementation. +class ReceiveStatisticsImpl : public ReceiveStatistics { + public: + ReceiveStatisticsImpl( + Clock* clock, + std::function<std::unique_ptr<StreamStatisticianImplInterface>( + uint32_t ssrc, + Clock* clock, + int max_reordering_threshold)> stream_statistician_factory); + ~ReceiveStatisticsImpl() override = default; + + // Implements ReceiveStatisticsProvider. + std::vector<rtcp::ReportBlock> RtcpReportBlocks(size_t max_blocks) override; + + // Implements RtpPacketSinkInterface + void OnRtpPacket(const RtpPacketReceived& packet) override; + + // Implements ReceiveStatistics. + StreamStatistician* GetStatistician(uint32_t ssrc) const override; + void SetMaxReorderingThreshold(int max_reordering_threshold) override; + void SetMaxReorderingThreshold(uint32_t ssrc, + int max_reordering_threshold) override; + void EnableRetransmitDetection(uint32_t ssrc, bool enable) override; + + private: + StreamStatisticianImplInterface* GetOrCreateStatistician(uint32_t ssrc); + + Clock* const clock_; + std::function<std::unique_ptr<StreamStatisticianImplInterface>( + uint32_t ssrc, + Clock* clock, + int max_reordering_threshold)> + stream_statistician_factory_; + // The index within `all_ssrcs_` that was last returned. + size_t last_returned_ssrc_idx_; + std::vector<uint32_t> all_ssrcs_; + int max_reordering_threshold_; + flat_map<uint32_t /*ssrc*/, std::unique_ptr<StreamStatisticianImplInterface>> + statisticians_; +}; + +// Thread-safe implementation wrapping access to ReceiveStatisticsImpl with a +// mutex. +class ReceiveStatisticsLocked : public ReceiveStatistics { + public: + explicit ReceiveStatisticsLocked( + Clock* clock, + std::function<std::unique_ptr<StreamStatisticianImplInterface>( + uint32_t ssrc, + Clock* clock, + int max_reordering_threshold)> stream_statitician_factory) + : impl_(clock, std::move(stream_statitician_factory)) {} + ~ReceiveStatisticsLocked() override = default; + std::vector<rtcp::ReportBlock> RtcpReportBlocks(size_t max_blocks) override { + MutexLock lock(&receive_statistics_lock_); + return impl_.RtcpReportBlocks(max_blocks); + } + void OnRtpPacket(const RtpPacketReceived& packet) override { + MutexLock lock(&receive_statistics_lock_); + return impl_.OnRtpPacket(packet); + } + StreamStatistician* GetStatistician(uint32_t ssrc) const override { + MutexLock lock(&receive_statistics_lock_); + return impl_.GetStatistician(ssrc); + } + void SetMaxReorderingThreshold(int max_reordering_threshold) override { + MutexLock lock(&receive_statistics_lock_); + return impl_.SetMaxReorderingThreshold(max_reordering_threshold); + } + void SetMaxReorderingThreshold(uint32_t ssrc, + int max_reordering_threshold) override { + MutexLock lock(&receive_statistics_lock_); + return impl_.SetMaxReorderingThreshold(ssrc, max_reordering_threshold); + } + void EnableRetransmitDetection(uint32_t ssrc, bool enable) override { + MutexLock lock(&receive_statistics_lock_); + return impl_.EnableRetransmitDetection(ssrc, enable); + } + + private: + mutable Mutex receive_statistics_lock_; + ReceiveStatisticsImpl impl_ RTC_GUARDED_BY(&receive_statistics_lock_); +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RECEIVE_STATISTICS_IMPL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc new file mode 100644 index 0000000000..92c8f34196 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc @@ -0,0 +1,901 @@ +/* + * 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 "modules/rtp_rtcp/include/receive_statistics.h" + +#include <cstdint> +#include <memory> +#include <vector> + +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/random.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::SizeIs; +using ::testing::UnorderedElementsAre; + +const size_t kPacketSize1 = 100; +const size_t kPacketSize2 = 300; +const uint32_t kSsrc1 = 101; +const uint32_t kSsrc2 = 202; +const uint32_t kSsrc3 = 203; +const uint32_t kSsrc4 = 304; + +RtpPacketReceived CreateRtpPacket(uint32_t ssrc, + size_t header_size, + size_t payload_size, + size_t padding_size) { + RtpPacketReceived packet; + packet.SetSsrc(ssrc); + packet.SetSequenceNumber(100); + packet.set_payload_type_frequency(90000); + RTC_CHECK_GE(header_size, 12); + RTC_CHECK_EQ(header_size % 4, 0); + if (header_size > 12) { + // Insert csrcs to increase header size. + const int num_csrcs = (header_size - 12) / 4; + std::vector<uint32_t> csrcs(num_csrcs); + packet.SetCsrcs(csrcs); + } + packet.SetPayloadSize(payload_size); + packet.SetPadding(padding_size); + return packet; +} + +RtpPacketReceived MakeRtpPacket(int payload_type_frequency, + uint32_t timestamp) { + RtpPacketReceived packet = + CreateRtpPacket(kSsrc1, + /*header_size=*/12, kPacketSize1 - 12, + /*padding_size=*/0); + packet.SetTimestamp(timestamp); + packet.set_payload_type_frequency(payload_type_frequency); + return packet; +} + +RtpPacketReceived MakeNextRtpPacket(const RtpPacketReceived& previous_packet, + int payload_type_frequency, + uint32_t timestamp) { + RtpPacketReceived packet = MakeRtpPacket(payload_type_frequency, timestamp); + packet.SetSequenceNumber(previous_packet.SequenceNumber() + 1); + return packet; +} + +RtpPacketReceived CreateRtpPacket(uint32_t ssrc, size_t packet_size) { + return CreateRtpPacket(ssrc, 12, packet_size - 12, 0); +} + +void IncrementSequenceNumber(RtpPacketReceived* packet, uint16_t incr) { + packet->SetSequenceNumber(packet->SequenceNumber() + incr); +} + +void IncrementSequenceNumber(RtpPacketReceived* packet) { + IncrementSequenceNumber(packet, 1); +} + +uint32_t GetJitter(const ReceiveStatistics& stats) { + return stats.GetStatistician(kSsrc1)->GetStats().jitter; +} + +class ReceiveStatisticsTest : public ::testing::TestWithParam<bool> { + public: + ReceiveStatisticsTest() + : clock_(0), + receive_statistics_( + GetParam() ? ReceiveStatistics::Create(&clock_) + : ReceiveStatistics::CreateThreadCompatible(&clock_)) { + packet1_ = CreateRtpPacket(kSsrc1, kPacketSize1); + packet2_ = CreateRtpPacket(kSsrc2, kPacketSize2); + } + + protected: + SimulatedClock clock_; + std::unique_ptr<ReceiveStatistics> receive_statistics_; + RtpPacketReceived packet1_; + RtpPacketReceived packet2_; +}; + +INSTANTIATE_TEST_SUITE_P(All, + ReceiveStatisticsTest, + ::testing::Bool(), + [](::testing::TestParamInfo<bool> info) { + return info.param ? "WithMutex" : "WithoutMutex"; + }); + +TEST_P(ReceiveStatisticsTest, TwoIncomingSsrcs) { + receive_statistics_->OnRtpPacket(packet1_); + IncrementSequenceNumber(&packet1_); + receive_statistics_->OnRtpPacket(packet2_); + IncrementSequenceNumber(&packet2_); + clock_.AdvanceTimeMilliseconds(100); + receive_statistics_->OnRtpPacket(packet1_); + IncrementSequenceNumber(&packet1_); + receive_statistics_->OnRtpPacket(packet2_); + IncrementSequenceNumber(&packet2_); + + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + ASSERT_TRUE(statistician != NULL); + EXPECT_GT(statistician->BitrateReceived(), 0u); + StreamDataCounters counters = statistician->GetReceiveStreamDataCounters(); + EXPECT_EQ(176u, counters.transmitted.payload_bytes); + EXPECT_EQ(24u, counters.transmitted.header_bytes); + EXPECT_EQ(0u, counters.transmitted.padding_bytes); + EXPECT_EQ(2u, counters.transmitted.packets); + + statistician = receive_statistics_->GetStatistician(kSsrc2); + ASSERT_TRUE(statistician != NULL); + EXPECT_GT(statistician->BitrateReceived(), 0u); + counters = statistician->GetReceiveStreamDataCounters(); + EXPECT_EQ(576u, counters.transmitted.payload_bytes); + EXPECT_EQ(24u, counters.transmitted.header_bytes); + EXPECT_EQ(0u, counters.transmitted.padding_bytes); + EXPECT_EQ(2u, counters.transmitted.packets); + + EXPECT_EQ(2u, receive_statistics_->RtcpReportBlocks(3).size()); + // Add more incoming packets and verify that they are registered in both + // access methods. + receive_statistics_->OnRtpPacket(packet1_); + IncrementSequenceNumber(&packet1_); + receive_statistics_->OnRtpPacket(packet2_); + IncrementSequenceNumber(&packet2_); + + counters = receive_statistics_->GetStatistician(kSsrc1) + ->GetReceiveStreamDataCounters(); + EXPECT_EQ(264u, counters.transmitted.payload_bytes); + EXPECT_EQ(36u, counters.transmitted.header_bytes); + EXPECT_EQ(0u, counters.transmitted.padding_bytes); + EXPECT_EQ(3u, counters.transmitted.packets); + + counters = receive_statistics_->GetStatistician(kSsrc2) + ->GetReceiveStreamDataCounters(); + EXPECT_EQ(864u, counters.transmitted.payload_bytes); + EXPECT_EQ(36u, counters.transmitted.header_bytes); + EXPECT_EQ(0u, counters.transmitted.padding_bytes); + EXPECT_EQ(3u, counters.transmitted.packets); +} + +TEST_P(ReceiveStatisticsTest, + RtcpReportBlocksReturnsMaxBlocksWhenThereAreMoreStatisticians) { + RtpPacketReceived packet1 = CreateRtpPacket(kSsrc1, kPacketSize1); + RtpPacketReceived packet2 = CreateRtpPacket(kSsrc2, kPacketSize1); + RtpPacketReceived packet3 = CreateRtpPacket(kSsrc3, kPacketSize1); + receive_statistics_->OnRtpPacket(packet1); + receive_statistics_->OnRtpPacket(packet2); + receive_statistics_->OnRtpPacket(packet3); + + EXPECT_THAT(receive_statistics_->RtcpReportBlocks(2), SizeIs(2)); + EXPECT_THAT(receive_statistics_->RtcpReportBlocks(2), SizeIs(2)); + EXPECT_THAT(receive_statistics_->RtcpReportBlocks(2), SizeIs(2)); +} + +TEST_P(ReceiveStatisticsTest, + RtcpReportBlocksReturnsAllObservedSsrcsWithMultipleCalls) { + RtpPacketReceived packet1 = CreateRtpPacket(kSsrc1, kPacketSize1); + RtpPacketReceived packet2 = CreateRtpPacket(kSsrc2, kPacketSize1); + RtpPacketReceived packet3 = CreateRtpPacket(kSsrc3, kPacketSize1); + RtpPacketReceived packet4 = CreateRtpPacket(kSsrc4, kPacketSize1); + receive_statistics_->OnRtpPacket(packet1); + receive_statistics_->OnRtpPacket(packet2); + receive_statistics_->OnRtpPacket(packet3); + receive_statistics_->OnRtpPacket(packet4); + + std::vector<uint32_t> observed_ssrcs; + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(2); + ASSERT_THAT(report_blocks, SizeIs(2)); + observed_ssrcs.push_back(report_blocks[0].source_ssrc()); + observed_ssrcs.push_back(report_blocks[1].source_ssrc()); + + report_blocks = receive_statistics_->RtcpReportBlocks(2); + ASSERT_THAT(report_blocks, SizeIs(2)); + observed_ssrcs.push_back(report_blocks[0].source_ssrc()); + observed_ssrcs.push_back(report_blocks[1].source_ssrc()); + + EXPECT_THAT(observed_ssrcs, + UnorderedElementsAre(kSsrc1, kSsrc2, kSsrc3, kSsrc4)); +} + +TEST_P(ReceiveStatisticsTest, ActiveStatisticians) { + receive_statistics_->OnRtpPacket(packet1_); + IncrementSequenceNumber(&packet1_); + clock_.AdvanceTimeMilliseconds(1000); + receive_statistics_->OnRtpPacket(packet2_); + IncrementSequenceNumber(&packet2_); + // Nothing should time out since only 1000 ms has passed since the first + // packet came in. + EXPECT_EQ(2u, receive_statistics_->RtcpReportBlocks(3).size()); + + clock_.AdvanceTimeMilliseconds(7000); + // kSsrc1 should have timed out. + EXPECT_EQ(1u, receive_statistics_->RtcpReportBlocks(3).size()); + + clock_.AdvanceTimeMilliseconds(1000); + // kSsrc2 should have timed out. + EXPECT_EQ(0u, receive_statistics_->RtcpReportBlocks(3).size()); + + receive_statistics_->OnRtpPacket(packet1_); + IncrementSequenceNumber(&packet1_); + // kSsrc1 should be active again and the data counters should have survived. + EXPECT_EQ(1u, receive_statistics_->RtcpReportBlocks(3).size()); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + ASSERT_TRUE(statistician != NULL); + StreamDataCounters counters = statistician->GetReceiveStreamDataCounters(); + EXPECT_EQ(176u, counters.transmitted.payload_bytes); + EXPECT_EQ(24u, counters.transmitted.header_bytes); + EXPECT_EQ(0u, counters.transmitted.padding_bytes); + EXPECT_EQ(2u, counters.transmitted.packets); +} + +TEST_P(ReceiveStatisticsTest, + DoesntCreateRtcpReportBlockUntilFirstReceivedPacketForSsrc) { + // Creates a statistician object for the ssrc. + receive_statistics_->EnableRetransmitDetection(kSsrc1, true); + EXPECT_TRUE(receive_statistics_->GetStatistician(kSsrc1) != nullptr); + EXPECT_EQ(0u, receive_statistics_->RtcpReportBlocks(3).size()); + // Receive first packet + receive_statistics_->OnRtpPacket(packet1_); + EXPECT_EQ(1u, receive_statistics_->RtcpReportBlocks(3).size()); +} + +TEST_P(ReceiveStatisticsTest, GetReceiveStreamDataCounters) { + receive_statistics_->OnRtpPacket(packet1_); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + ASSERT_TRUE(statistician != NULL); + + StreamDataCounters counters = statistician->GetReceiveStreamDataCounters(); + EXPECT_GT(counters.first_packet_time_ms, -1); + EXPECT_EQ(1u, counters.transmitted.packets); + + receive_statistics_->OnRtpPacket(packet1_); + counters = statistician->GetReceiveStreamDataCounters(); + EXPECT_GT(counters.first_packet_time_ms, -1); + EXPECT_EQ(2u, counters.transmitted.packets); +} + +TEST_P(ReceiveStatisticsTest, SimpleLossComputation) { + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(3); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(4); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(5); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + // 20% = 51/255. + EXPECT_EQ(51u, report_blocks[0].fraction_lost()); + EXPECT_EQ(1, report_blocks[0].cumulative_lost_signed()); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + EXPECT_EQ(20, statistician->GetFractionLostInPercent()); +} + +TEST_P(ReceiveStatisticsTest, LossComputationWithReordering) { + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(3); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(2); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(5); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + // 20% = 51/255. + EXPECT_EQ(51u, report_blocks[0].fraction_lost()); + EXPECT_EQ(1, report_blocks[0].cumulative_lost_signed()); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + EXPECT_EQ(20, statistician->GetFractionLostInPercent()); +} + +TEST_P(ReceiveStatisticsTest, LossComputationWithDuplicates) { + // Lose 2 packets, but also receive 1 duplicate. Should actually count as + // only 1 packet being lost. + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(4); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(4); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(5); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + // 20% = 51/255. + EXPECT_EQ(51u, report_blocks[0].fraction_lost()); + EXPECT_EQ(1, report_blocks[0].cumulative_lost_signed()); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + EXPECT_EQ(20, statistician->GetFractionLostInPercent()); +} + +TEST_P(ReceiveStatisticsTest, LossComputationWithSequenceNumberWrapping) { + // First, test loss computation over a period that included a sequence number + // rollover. + packet1_.SetSequenceNumber(0xfffd); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(0); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(0xfffe); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + + // Only one packet was actually lost, 0xffff. + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + // 20% = 51/255. + EXPECT_EQ(51u, report_blocks[0].fraction_lost()); + EXPECT_EQ(1, report_blocks[0].cumulative_lost_signed()); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + EXPECT_EQ(20, statistician->GetFractionLostInPercent()); + + // Now test losing one packet *after* the rollover. + packet1_.SetSequenceNumber(3); + receive_statistics_->OnRtpPacket(packet1_); + + report_blocks = receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + // 50% = 127/255. + EXPECT_EQ(127u, report_blocks[0].fraction_lost()); + EXPECT_EQ(2, report_blocks[0].cumulative_lost_signed()); + // 2 packets lost, 7 expected + EXPECT_EQ(28, statistician->GetFractionLostInPercent()); +} + +TEST_P(ReceiveStatisticsTest, StreamRestartDoesntCountAsLoss) { + receive_statistics_->SetMaxReorderingThreshold(kSsrc1, 200); + + packet1_.SetSequenceNumber(0); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + + packet1_.SetSequenceNumber(400); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(0, report_blocks[0].fraction_lost()); + EXPECT_EQ(0, report_blocks[0].cumulative_lost_signed()); + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + EXPECT_EQ(0, statistician->GetFractionLostInPercent()); + + packet1_.SetSequenceNumber(401); + receive_statistics_->OnRtpPacket(packet1_); + report_blocks = receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(0, report_blocks[0].fraction_lost()); + EXPECT_EQ(0, report_blocks[0].cumulative_lost_signed()); + EXPECT_EQ(0, statistician->GetFractionLostInPercent()); +} + +TEST_P(ReceiveStatisticsTest, CountsLossAfterStreamRestart) { + receive_statistics_->SetMaxReorderingThreshold(kSsrc1, 200); + + packet1_.SetSequenceNumber(0); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + + packet1_.SetSequenceNumber(400); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(401); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(403); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(1, report_blocks[0].cumulative_lost_signed()); + + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + // Is this reasonable? */ + EXPECT_EQ(0, statistician->GetFractionLostInPercent()); +} + +TEST_P(ReceiveStatisticsTest, StreamCanRestartAtSequenceNumberWrapAround) { + receive_statistics_->SetMaxReorderingThreshold(kSsrc1, 200); + + packet1_.SetSequenceNumber(0xffff - 401); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(0xffff - 400); + receive_statistics_->OnRtpPacket(packet1_); + + packet1_.SetSequenceNumber(0xffff); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(0); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(2); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(1, report_blocks[0].cumulative_lost_signed()); +} + +TEST_P(ReceiveStatisticsTest, StreamRestartNeedsTwoConsecutivePackets) { + receive_statistics_->SetMaxReorderingThreshold(kSsrc1, 200); + + packet1_.SetSequenceNumber(400); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(401); + receive_statistics_->OnRtpPacket(packet1_); + + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(3); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(401u, report_blocks[0].extended_high_seq_num()); + + packet1_.SetSequenceNumber(4); + receive_statistics_->OnRtpPacket(packet1_); + + report_blocks = receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(4u, report_blocks[0].extended_high_seq_num()); +} + +TEST_P(ReceiveStatisticsTest, WrapsAroundExtendedHighestSequenceNumber) { + packet1_.SetSequenceNumber(0xffff); + receive_statistics_->OnRtpPacket(packet1_); + + std::vector<rtcp::ReportBlock> report_blocks = + receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(0xffffu, report_blocks[0].extended_high_seq_num()); + + // Wrap around. + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + + report_blocks = receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(0x10001u, report_blocks[0].extended_high_seq_num()); + + // Should be treated as out of order; shouldn't increment highest extended + // sequence number. + packet1_.SetSequenceNumber(0x10000 - 6); + report_blocks = receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(0x10001u, report_blocks[0].extended_high_seq_num()); + + // Receive a couple packets then wrap around again. + receive_statistics_->SetMaxReorderingThreshold(kSsrc1, 200); + for (int i = 10; i < 0xffff; i += 150) { + packet1_.SetSequenceNumber(i); + receive_statistics_->OnRtpPacket(packet1_); + } + packet1_.SetSequenceNumber(1); + receive_statistics_->OnRtpPacket(packet1_); + report_blocks = receive_statistics_->RtcpReportBlocks(1); + ASSERT_THAT(report_blocks, SizeIs(1)); + EXPECT_EQ(kSsrc1, report_blocks[0].source_ssrc()); + + EXPECT_EQ(0x20001u, report_blocks[0].extended_high_seq_num()); +} + +TEST_P(ReceiveStatisticsTest, StreamDataCounters) { + receive_statistics_->EnableRetransmitDetection(kSsrc1, true); + + const size_t kHeaderLength = 20; + const size_t kPaddingLength = 9; + + // One packet with payload size kPacketSize1. + RtpPacketReceived packet1 = + CreateRtpPacket(kSsrc1, kHeaderLength, kPacketSize1, 0); + receive_statistics_->OnRtpPacket(packet1); + StreamDataCounters counters = receive_statistics_->GetStatistician(kSsrc1) + ->GetReceiveStreamDataCounters(); + EXPECT_EQ(counters.transmitted.payload_bytes, kPacketSize1); + EXPECT_EQ(counters.transmitted.header_bytes, kHeaderLength); + EXPECT_EQ(counters.transmitted.padding_bytes, 0u); + EXPECT_EQ(counters.transmitted.packets, 1u); + EXPECT_EQ(counters.retransmitted.payload_bytes, 0u); + EXPECT_EQ(counters.retransmitted.header_bytes, 0u); + EXPECT_EQ(counters.retransmitted.padding_bytes, 0u); + EXPECT_EQ(counters.retransmitted.packets, 0u); + EXPECT_EQ(counters.fec.packets, 0u); + + // Another packet of size kPacketSize1 with 9 bytes padding. + RtpPacketReceived packet2 = + CreateRtpPacket(kSsrc1, kHeaderLength, kPacketSize1, 9); + packet2.SetSequenceNumber(packet1.SequenceNumber() + 1); + clock_.AdvanceTimeMilliseconds(5); + receive_statistics_->OnRtpPacket(packet2); + counters = receive_statistics_->GetStatistician(kSsrc1) + ->GetReceiveStreamDataCounters(); + EXPECT_EQ(counters.transmitted.payload_bytes, kPacketSize1 * 2); + EXPECT_EQ(counters.transmitted.header_bytes, kHeaderLength * 2); + EXPECT_EQ(counters.transmitted.padding_bytes, kPaddingLength); + EXPECT_EQ(counters.transmitted.packets, 2u); + + clock_.AdvanceTimeMilliseconds(5); + // Retransmit last packet. + receive_statistics_->OnRtpPacket(packet2); + counters = receive_statistics_->GetStatistician(kSsrc1) + ->GetReceiveStreamDataCounters(); + EXPECT_EQ(counters.transmitted.payload_bytes, kPacketSize1 * 3); + EXPECT_EQ(counters.transmitted.header_bytes, kHeaderLength * 3); + EXPECT_EQ(counters.transmitted.padding_bytes, kPaddingLength * 2); + EXPECT_EQ(counters.transmitted.packets, 3u); + EXPECT_EQ(counters.retransmitted.payload_bytes, kPacketSize1); + EXPECT_EQ(counters.retransmitted.header_bytes, kHeaderLength); + EXPECT_EQ(counters.retransmitted.padding_bytes, kPaddingLength); + EXPECT_EQ(counters.retransmitted.packets, 1u); +} + +TEST_P(ReceiveStatisticsTest, LastPacketReceivedTimestamp) { + clock_.AdvanceTimeMilliseconds(42); + receive_statistics_->OnRtpPacket(packet1_); + StreamDataCounters counters = receive_statistics_->GetStatistician(kSsrc1) + ->GetReceiveStreamDataCounters(); + + EXPECT_EQ(42, counters.last_packet_received_timestamp_ms); + + clock_.AdvanceTimeMilliseconds(3); + receive_statistics_->OnRtpPacket(packet1_); + counters = receive_statistics_->GetStatistician(kSsrc1) + ->GetReceiveStreamDataCounters(); + EXPECT_EQ(45, counters.last_packet_received_timestamp_ms); +} + +TEST_P(ReceiveStatisticsTest, SimpleJitterComputation) { + const int kMsPerPacket = 20; + const int kCodecSampleRate = 48'000; + const int kSamplesPerPacket = kMsPerPacket * kCodecSampleRate / 1'000; + const int kLateArrivalDeltaMs = 100; + const int kLateArrivalDeltaSamples = + kLateArrivalDeltaMs * kCodecSampleRate / 1'000; + + packet1_.set_payload_type_frequency(kCodecSampleRate); + packet1_.SetSequenceNumber(1); + packet1_.SetTimestamp(0); + receive_statistics_->OnRtpPacket(packet1_); + packet1_.SetSequenceNumber(2); + packet1_.SetTimestamp(kSamplesPerPacket); + // Arrives 100 ms late. + clock_.AdvanceTimeMilliseconds(kMsPerPacket + kLateArrivalDeltaMs); + receive_statistics_->OnRtpPacket(packet1_); + + StreamStatistician* statistician = + receive_statistics_->GetStatistician(kSsrc1); + // See jitter caluculation in https://www.rfc-editor.org/rfc/rfc3550 6.4.1. + const uint32_t expected_jitter = (kLateArrivalDeltaSamples) / 16; + EXPECT_EQ(expected_jitter, statistician->GetStats().jitter); + EXPECT_EQ(webrtc::TimeDelta::Seconds(expected_jitter) / kCodecSampleRate, + statistician->GetStats().interarrival_jitter); +} + +TEST(ReviseJitterTest, AllPacketsHaveSamePayloadTypeFrequency) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/8'000, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 160); + + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 2 * 160); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 240 + // packet3: jitter = 240[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 240[jitter] + 8) + // / 16 = 465 + // final jitter: 465 / 16 = 29 + EXPECT_EQ(GetJitter(*statistics), 29U); +} + +TEST(ReviseJitterTest, AllPacketsHaveDifferentPayloadTypeFrequency) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/8'000, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 160); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/48'000, /*timestamp=*/1 + 160 + 960); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 240 + // packet3: revised jitter: 240 * 48[frequency KHz] / 8[frequency KHz] = 1'440 + // jitter = 1'440[jitter] + (abs(50[receive time ms] * + // 48[frequency KHz] - 960[timestamp diff]) * 16 - 1'440[jitter] + 8) + // / 16 = 2'790 + // final jitter: 2'790 / 16 = 174 + EXPECT_EQ(GetJitter(*statistics), 174U); +} + +TEST(ReviseJitterTest, + FirstPacketPayloadTypeFrequencyIsZeroAndFrequencyChanged) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/0, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 160); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/48'000, /*timestamp=*/1 + 160 + 960); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 240 + // packet3: revised jitter: 240 * 48[frequency KHz] / 8[frequency KHz] = 1'440 + // jitter = 1'440[jitter] + (abs(50[receive time ms] * + // 48[frequency KHz] - 960[timestamp diff]) * 16 - 1'440[jitter] + 8) + // / 16 = 2'790 + // final jitter: 2'790 / 16 = 174 + EXPECT_EQ(GetJitter(*statistics), 174U); +} + +TEST(ReviseJitterTest, + FirstPacketPayloadTypeFrequencyIsZeroAndFrequencyNotChanged) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/0, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 160); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 160 + 160); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 240 + // packet3: jitter = 240[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 240[jitter] + 8) + // / 16 = 465 + // final jitter: 465 / 16 = 29 + EXPECT_EQ(GetJitter(*statistics), 29U); +} + +TEST(ReviseJitterTest, + TwoFirstPacketPayloadTypeFrequencyIsZeroAndFrequencyChanged) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/0, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/0, /*timestamp=*/1 + 160); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/48'000, /*timestamp=*/1 + 160 + 960); + RtpPacketReceived packet4 = + MakeNextRtpPacket(packet3, /*payload_type_frequency=*/8'000, + /*timestamp=*/1 + 160 + 960 + 160); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet4); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 0[frequency KHz] - 160[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 160 + // packet3: jitter = 160[jitter] + (abs(50[receive time ms] * + // 48[frequency KHz] - 960[timestamp diff]) * 16 - 160[jitter] + 8) + // / 16 = 1'590 + // packet4: revised jitter: 1'590 * 8[frequency KHz] / 48[frequency KHz] = 265 + // packet4: jitter = 265[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 265[jitter] + 8) + // / 16 = 488 + // final jitter: 488 / 16 = 30 + EXPECT_EQ(GetJitter(*statistics), 30U); +} + +TEST(ReviseJitterTest, + TwoFirstPacketPayloadTypeFrequencyIsZeroAndFrequencyNotChanged) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/0, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/0, /*timestamp=*/1 + 160); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/8'000, /*timestamp=*/1 + 160 + 160); + RtpPacketReceived packet4 = + MakeNextRtpPacket(packet3, /*payload_type_frequency=*/8'000, + /*timestamp=*/1 + 160 + 160 + 160); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet4); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 0[frequency KHz] - 160[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 160 + // packet3: jitter = 160[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 160[jitter] + 8) + // / 16 = 390 + // packet4: jitter = 390[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 390[jitter] + 8) + // / 16 = 606 + // final jitter: 606 / 16 = 37 + EXPECT_EQ(GetJitter(*statistics), 37U); +} + +TEST(ReviseJitterTest, + MiddlePacketPayloadTypeFrequencyIsZeroAndFrequencyChanged) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/48'000, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/48'000, /*timestamp=*/1 + 960); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/0, /*timestamp=*/1 + 960 + 55); + RtpPacketReceived packet4 = + MakeNextRtpPacket(packet3, /*payload_type_frequency=*/8'000, + /*timestamp=*/1 + 960 + 55 + 160); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet4); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 48[frequency KHz] - 960[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 1'440 + // packet3: jitter = 1'440[jitter] + (abs(50[receive time ms] * + // 0[frequency KHz] - 55[timestamp diff]) * 16 - 1'440[jitter] + 8) + // / 16 = 1'405 + // packet4: revised jitter: 1'405 * 8[frequency KHz] / 48[frequency KHz] = 234 + // jitter = 234[jitter] + (abs(50[receive time ms] * + // 8[frequency KHz] - 160[timestamp diff]) * 16 - 234[jitter] + 8) + // / 16 = 459 + // final jitter: 459 / 16 = 28 + EXPECT_EQ(GetJitter(*statistics), 28U); +} + +TEST(ReviseJitterTest, + MiddlePacketPayloadTypeFrequencyIsZeroAndFrequencyNotChanged) { + SimulatedClock clock(0); + std::unique_ptr<ReceiveStatistics> statistics = + ReceiveStatistics::Create(&clock); + RtpPacketReceived packet1 = MakeRtpPacket(/*payload_type_frequency=*/48'000, + /*timestamp=*/1); + RtpPacketReceived packet2 = MakeNextRtpPacket( + packet1, /*payload_type_frequency=*/48'000, /*timestamp=*/1 + 960); + RtpPacketReceived packet3 = MakeNextRtpPacket( + packet2, /*payload_type_frequency=*/0, /*timestamp=*/1 + 960 + 55); + RtpPacketReceived packet4 = + MakeNextRtpPacket(packet3, /*payload_type_frequency=*/48'000, + /*timestamp=*/1 + 960 + 55 + 960); + + statistics->OnRtpPacket(packet1); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet2); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet3); + clock.AdvanceTimeMilliseconds(50); + statistics->OnRtpPacket(packet4); + + // packet1: no jitter calculation + // packet2: jitter = 0[jitter] + (abs(50[receive time ms] * + // 48[frequency KHz] - 960[timestamp diff]) * 16 - 0[jitter] + 8) + // / 16 = 1'440 + // packet3: jitter = 1'440[jitter] + (abs(50[receive time ms] * + // 0[frequency KHz] - 55[timestamp diff]) * 16 - 1'440[jitter] + 8) + // / 16 = 1'405 + // packet4: jitter = 1'405[jitter] + (abs(50[receive time ms] * + // 48[frequency KHz] - 960[timestamp diff]) * 16 - 1'405[jitter] + 8) + // / 16 = 2'757 + // final jitter: 2'757 / 16 = 172 + EXPECT_EQ(GetJitter(*statistics), 172U); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc new file mode 100644 index 0000000000..6f90cd175c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc @@ -0,0 +1,109 @@ +/* + * 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 "modules/rtp_rtcp/include/remote_ntp_time_estimator.h" + +#include <cstdint> + +#include "modules/rtp_rtcp/source/time_util.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/clock.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { + +namespace { + +constexpr int kMinimumNumberOfSamples = 2; +constexpr TimeDelta kTimingLogInterval = TimeDelta::Seconds(10); +constexpr int kClocksOffsetSmoothingWindow = 100; + +// Subtracts two NtpTime values keeping maximum precision. +int64_t Subtract(NtpTime minuend, NtpTime subtrahend) { + uint64_t a = static_cast<uint64_t>(minuend); + uint64_t b = static_cast<uint64_t>(subtrahend); + return a >= b ? static_cast<int64_t>(a - b) : -static_cast<int64_t>(b - a); +} + +NtpTime Add(NtpTime lhs, int64_t rhs) { + uint64_t result = static_cast<uint64_t>(lhs); + if (rhs >= 0) { + result += static_cast<uint64_t>(rhs); + } else { + result -= static_cast<uint64_t>(-rhs); + } + return NtpTime(result); +} + +} // namespace + +// TODO(wu): Refactor this class so that it can be shared with +// vie_sync_module.cc. +RemoteNtpTimeEstimator::RemoteNtpTimeEstimator(Clock* clock) + : clock_(clock), + ntp_clocks_offset_estimator_(kClocksOffsetSmoothingWindow) {} + +bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(TimeDelta rtt, + NtpTime sender_send_time, + uint32_t rtp_timestamp) { + switch (rtp_to_ntp_.UpdateMeasurements(sender_send_time, rtp_timestamp)) { + case RtpToNtpEstimator::kInvalidMeasurement: + return false; + case RtpToNtpEstimator::kSameMeasurement: + // No new RTCP SR since last time this function was called. + return true; + case RtpToNtpEstimator::kNewMeasurement: + break; + } + + // Assume connection is symmetric and thus time to deliver the packet is half + // the round trip time. + int64_t deliver_time_ntp = ToNtpUnits(rtt) / 2; + + // Update extrapolator with the new arrival time. + NtpTime receiver_arrival_time = clock_->CurrentNtpTime(); + int64_t remote_to_local_clocks_offset = + Subtract(receiver_arrival_time, sender_send_time) - deliver_time_ntp; + ntp_clocks_offset_estimator_.Insert(remote_to_local_clocks_offset); + return true; +} + +NtpTime RemoteNtpTimeEstimator::EstimateNtp(uint32_t rtp_timestamp) { + NtpTime sender_capture = rtp_to_ntp_.Estimate(rtp_timestamp); + if (!sender_capture.Valid()) { + return sender_capture; + } + + int64_t remote_to_local_clocks_offset = + ntp_clocks_offset_estimator_.GetFilteredValue(); + NtpTime receiver_capture = Add(sender_capture, remote_to_local_clocks_offset); + + Timestamp now = clock_->CurrentTime(); + if (now - last_timing_log_ > kTimingLogInterval) { + RTC_LOG(LS_INFO) << "RTP timestamp: " << rtp_timestamp + << " in NTP clock: " << sender_capture.ToMs() + << " estimated time in receiver NTP clock: " + << receiver_capture.ToMs(); + last_timing_log_ = now; + } + + return receiver_capture; +} + +absl::optional<int64_t> +RemoteNtpTimeEstimator::EstimateRemoteToLocalClockOffset() { + if (ntp_clocks_offset_estimator_.GetNumberOfSamplesStored() < + kMinimumNumberOfSamples) { + return absl::nullopt; + } + return ntp_clocks_offset_estimator_.GetFilteredValue(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc new file mode 100644 index 0000000000..8dbfaec940 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc @@ -0,0 +1,128 @@ +/* + * 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 "modules/rtp_rtcp/include/remote_ntp_time_estimator.h" + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "system_wrappers/include/clock.h" +#include "system_wrappers/include/ntp_time.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +constexpr TimeDelta kTestRtt = TimeDelta::Millis(10); +constexpr Timestamp kLocalClockInitialTime = Timestamp::Millis(123); +constexpr Timestamp kRemoteClockInitialTime = Timestamp::Millis(373); +constexpr uint32_t kTimestampOffset = 567; +constexpr int64_t kRemoteToLocalClockOffsetNtp = + ToNtpUnits(kLocalClockInitialTime - kRemoteClockInitialTime); + +class RemoteNtpTimeEstimatorTest : public ::testing::Test { + protected: + void AdvanceTime(TimeDelta delta) { + local_clock_.AdvanceTime(delta); + remote_clock_.AdvanceTime(delta); + } + + uint32_t GetRemoteTimestamp() { + return static_cast<uint32_t>(remote_clock_.TimeInMilliseconds()) * 90 + + kTimestampOffset; + } + + void SendRtcpSr() { + uint32_t rtcp_timestamp = GetRemoteTimestamp(); + NtpTime ntp = remote_clock_.CurrentNtpTime(); + + AdvanceTime(kTestRtt / 2); + RTC_DCHECK(estimator_.UpdateRtcpTimestamp(kTestRtt, ntp, rtcp_timestamp)); + } + + void SendRtcpSrInaccurately(TimeDelta ntp_error, TimeDelta networking_delay) { + uint32_t rtcp_timestamp = GetRemoteTimestamp(); + int64_t ntp_error_fractions = ToNtpUnits(ntp_error); + NtpTime ntp(static_cast<uint64_t>(remote_clock_.CurrentNtpTime()) + + ntp_error_fractions); + AdvanceTime(kTestRtt / 2 + networking_delay); + RTC_DCHECK(estimator_.UpdateRtcpTimestamp(kTestRtt, ntp, rtcp_timestamp)); + } + + SimulatedClock local_clock_{kLocalClockInitialTime}; + SimulatedClock remote_clock_{kRemoteClockInitialTime}; + RemoteNtpTimeEstimator estimator_{&local_clock_}; +}; + +TEST_F(RemoteNtpTimeEstimatorTest, FailsWithoutValidNtpTime) { + EXPECT_FALSE( + estimator_.UpdateRtcpTimestamp(kTestRtt, NtpTime(), /*rtp_timestamp=*/0)); +} + +TEST_F(RemoteNtpTimeEstimatorTest, Estimate) { + // Remote peer sends first RTCP SR. + SendRtcpSr(); + + // Remote sends a RTP packet. + AdvanceTime(TimeDelta::Millis(15)); + uint32_t rtp_timestamp = GetRemoteTimestamp(); + int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds(); + + // Local peer needs at least 2 RTCP SR to calculate the capture time. + const int64_t kNotEnoughRtcpSr = -1; + EXPECT_EQ(kNotEnoughRtcpSr, estimator_.Estimate(rtp_timestamp)); + EXPECT_EQ(estimator_.EstimateRemoteToLocalClockOffset(), absl::nullopt); + + AdvanceTime(TimeDelta::Millis(800)); + // Remote sends second RTCP SR. + SendRtcpSr(); + + // Local peer gets enough RTCP SR to calculate the capture time. + EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp)); + EXPECT_EQ(estimator_.EstimateRemoteToLocalClockOffset(), + kRemoteToLocalClockOffsetNtp); +} + +TEST_F(RemoteNtpTimeEstimatorTest, AveragesErrorsOut) { + // Remote peer sends first 10 RTCP SR without errors. + for (int i = 0; i < 10; ++i) { + AdvanceTime(TimeDelta::Seconds(1)); + SendRtcpSr(); + } + + AdvanceTime(TimeDelta::Millis(150)); + uint32_t rtp_timestamp = GetRemoteTimestamp(); + int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds(); + // Local peer gets enough RTCP SR to calculate the capture time. + EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp)); + EXPECT_EQ(kRemoteToLocalClockOffsetNtp, + estimator_.EstimateRemoteToLocalClockOffset()); + + // Remote sends corrupted RTCP SRs + AdvanceTime(TimeDelta::Seconds(1)); + SendRtcpSrInaccurately(/*ntp_error=*/TimeDelta::Millis(2), + /*networking_delay=*/TimeDelta::Millis(-1)); + AdvanceTime(TimeDelta::Seconds(1)); + SendRtcpSrInaccurately(/*ntp_error=*/TimeDelta::Millis(-2), + /*networking_delay=*/TimeDelta::Millis(1)); + + // New RTP packet to estimate timestamp. + AdvanceTime(TimeDelta::Millis(150)); + rtp_timestamp = GetRemoteTimestamp(); + capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds(); + + // Errors should be averaged out. + EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp)); + EXPECT_EQ(kRemoteToLocalClockOffsetNtp, + estimator_.EstimateRemoteToLocalClockOffset()); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.cc new file mode 100644 index 0000000000..1d652d0b5b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.cc @@ -0,0 +1,29 @@ +/* + * 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/rtp_rtcp/source/rtcp_nack_stats.h" + +#include "modules/include/module_common_types_public.h" + +namespace webrtc { + +RtcpNackStats::RtcpNackStats() + : max_sequence_number_(0), requests_(0), unique_requests_(0) {} + +void RtcpNackStats::ReportRequest(uint16_t sequence_number) { + if (requests_ == 0 || + IsNewerSequenceNumber(sequence_number, max_sequence_number_)) { + max_sequence_number_ = sequence_number; + ++unique_requests_; + } + ++requests_; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.h new file mode 100644 index 0000000000..9da4351a59 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.h @@ -0,0 +1,40 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_NACK_STATS_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_NACK_STATS_H_ + +#include <stdint.h> + +namespace webrtc { + +class RtcpNackStats { + public: + RtcpNackStats(); + + // Updates stats with requested sequence number. + // This function should be called for each NACK request to calculate the + // number of unique NACKed RTP packets. + void ReportRequest(uint16_t sequence_number); + + // Gets the number of NACKed RTP packets. + uint32_t requests() const { return requests_; } + + // Gets the number of unique NACKed RTP packets. + uint32_t unique_requests() const { return unique_requests_; } + + private: + uint16_t max_sequence_number_; + uint32_t requests_; + uint32_t unique_requests_; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_NACK_STATS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats_unittest.cc new file mode 100644 index 0000000000..60858e197e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats_unittest.cc @@ -0,0 +1,64 @@ +/* + * 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/rtp_rtcp/source/rtcp_nack_stats.h" + +#include "test/gtest.h" + +namespace webrtc { + +TEST(RtcpNackStatsTest, Requests) { + RtcpNackStats stats; + EXPECT_EQ(0U, stats.unique_requests()); + EXPECT_EQ(0U, stats.requests()); + stats.ReportRequest(10); + EXPECT_EQ(1U, stats.unique_requests()); + EXPECT_EQ(1U, stats.requests()); + + stats.ReportRequest(10); + EXPECT_EQ(1U, stats.unique_requests()); + stats.ReportRequest(11); + EXPECT_EQ(2U, stats.unique_requests()); + + stats.ReportRequest(11); + EXPECT_EQ(2U, stats.unique_requests()); + stats.ReportRequest(13); + EXPECT_EQ(3U, stats.unique_requests()); + + stats.ReportRequest(11); + EXPECT_EQ(3U, stats.unique_requests()); + EXPECT_EQ(6U, stats.requests()); +} + +TEST(RtcpNackStatsTest, RequestsWithWrap) { + RtcpNackStats stats; + stats.ReportRequest(65534); + EXPECT_EQ(1U, stats.unique_requests()); + + stats.ReportRequest(65534); + EXPECT_EQ(1U, stats.unique_requests()); + stats.ReportRequest(65535); + EXPECT_EQ(2U, stats.unique_requests()); + + stats.ReportRequest(65535); + EXPECT_EQ(2U, stats.unique_requests()); + stats.ReportRequest(0); + EXPECT_EQ(3U, stats.unique_requests()); + + stats.ReportRequest(65535); + EXPECT_EQ(3U, stats.unique_requests()); + stats.ReportRequest(0); + EXPECT_EQ(3U, stats.unique_requests()); + stats.ReportRequest(1); + EXPECT_EQ(4U, stats.unique_requests()); + EXPECT_EQ(8U, stats.requests()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.cc new file mode 100644 index 0000000000..bac03e73d2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.cc @@ -0,0 +1,99 @@ +/* + * 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 "modules/rtp_rtcp/source/rtcp_packet.h" + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace rtcp { +constexpr size_t RtcpPacket::kHeaderLength; + +rtc::Buffer RtcpPacket::Build() const { + rtc::Buffer packet(BlockLength()); + + size_t length = 0; + bool created = Create(packet.data(), &length, packet.capacity(), nullptr); + RTC_DCHECK(created) << "Invalid packet is not supported."; + RTC_DCHECK_EQ(length, packet.size()) + << "BlockLength mispredicted size used by Create"; + + return packet; +} + +bool RtcpPacket::Build(size_t max_length, PacketReadyCallback callback) const { + RTC_CHECK_LE(max_length, IP_PACKET_SIZE); + uint8_t buffer[IP_PACKET_SIZE]; + size_t index = 0; + if (!Create(buffer, &index, max_length, callback)) + return false; + return OnBufferFull(buffer, &index, callback); +} + +bool RtcpPacket::OnBufferFull(uint8_t* packet, + size_t* index, + PacketReadyCallback callback) const { + if (*index == 0) + return false; + RTC_DCHECK(callback) << "Fragmentation not supported."; + callback(rtc::ArrayView<const uint8_t>(packet, *index)); + *index = 0; + return true; +} + +size_t RtcpPacket::HeaderLength() const { + size_t length_in_bytes = BlockLength(); + RTC_DCHECK_GT(length_in_bytes, 0); + RTC_DCHECK_EQ(length_in_bytes % 4, 0) + << "Padding must be handled by each subclass."; + // Length in 32-bit words without common header. + return (length_in_bytes - kHeaderLength) / 4; +} + +// From RFC 3550, RTP: A Transport Protocol for Real-Time Applications. +// +// RTP header format. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| RC/FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +void RtcpPacket::CreateHeader( + size_t count_or_format, // Depends on packet type. + uint8_t packet_type, + size_t length, + uint8_t* buffer, + size_t* pos) { + CreateHeader(count_or_format, packet_type, length, /*padding=*/false, buffer, + pos); +} + +void RtcpPacket::CreateHeader( + size_t count_or_format, // Depends on packet type. + uint8_t packet_type, + size_t length, + bool padding, + uint8_t* buffer, + size_t* pos) { + RTC_DCHECK_LE(length, 0xffffU); + RTC_DCHECK_LE(count_or_format, 0x1f); + constexpr uint8_t kVersionBits = 2 << 6; + uint8_t padding_bit = padding ? 1 << 5 : 0; + buffer[*pos + 0] = + kVersionBits | padding_bit | static_cast<uint8_t>(count_or_format); + buffer[*pos + 1] = packet_type; + buffer[*pos + 2] = (length >> 8) & 0xff; + buffer[*pos + 3] = length & 0xff; + *pos += kHeaderLength; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.h new file mode 100644 index 0000000000..07deb0f9bd --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.h @@ -0,0 +1,111 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "api/array_view.h" +#include "api/function_view.h" +#include "rtc_base/buffer.h" + +namespace webrtc { +namespace rtcp { +// Class for building RTCP packets. +// +// Example: +// ReportBlock report_block; +// report_block.SetMediaSsrc(234); +// report_block.SetFractionLost(10); +// +// ReceiverReport rr; +// rr.SetSenderSsrc(123); +// rr.AddReportBlock(report_block); +// +// Fir fir; +// fir.SetSenderSsrc(123); +// fir.AddRequestTo(234, 56); +// +// size_t length = 0; // Builds an intra frame request +// uint8_t packet[kPacketSize]; // with sequence number 56. +// fir.Build(packet, &length, kPacketSize); +// +// rtc::Buffer packet = fir.Build(); // Returns a RawPacket holding +// // the built rtcp packet. +// +// CompoundPacket compound; // Builds a compound RTCP packet with +// compound.Append(&rr); // a receiver report, report block +// compound.Append(&fir); // and fir message. +// rtc::Buffer packet = compound.Build(); + +class RtcpPacket { + public: + // Callback used to signal that an RTCP packet is ready. Note that this may + // not contain all data in this RtcpPacket; if a packet cannot fit in + // max_length bytes, it will be fragmented and multiple calls to this + // callback will be made. + using PacketReadyCallback = + rtc::FunctionView<void(rtc::ArrayView<const uint8_t> packet)>; + + virtual ~RtcpPacket() = default; + + void SetSenderSsrc(uint32_t ssrc) { sender_ssrc_ = ssrc; } + uint32_t sender_ssrc() const { return sender_ssrc_; } + + // Convenience method mostly used for test. Creates packet without + // fragmentation using BlockLength() to allocate big enough buffer. + rtc::Buffer Build() const; + + // Returns true if call to Create succeeded. + bool Build(size_t max_length, PacketReadyCallback callback) const; + + // Size of this packet in bytes (including headers). + virtual size_t BlockLength() const = 0; + + // Creates packet in the given buffer at the given position. + // Calls PacketReadyCallback::OnPacketReady if remaining buffer is too small + // and assume buffer can be reused after OnPacketReady returns. + virtual bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const = 0; + + protected: + // Size of the rtcp common header. + static constexpr size_t kHeaderLength = 4; + RtcpPacket() {} + + static void CreateHeader(size_t count_or_format, + uint8_t packet_type, + size_t block_length, // Payload size in 32bit words. + uint8_t* buffer, + size_t* pos); + + static void CreateHeader(size_t count_or_format, + uint8_t packet_type, + size_t block_length, // Payload size in 32bit words. + bool padding, // True if there are padding bytes. + uint8_t* buffer, + size_t* pos); + + bool OnBufferFull(uint8_t* packet, + size_t* index, + PacketReadyCallback callback) const; + // Size of the rtcp packet as written in header. + size_t HeaderLength() const; + + private: + uint32_t sender_ssrc_ = 0; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc new file mode 100644 index 0000000000..d5734c6dd5 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc @@ -0,0 +1,103 @@ +/* + * 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/app.h" + +#include <string.h> + +#include <cstdint> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t App::kPacketType; +constexpr size_t App::kMaxDataSize; +// Application-Defined packet (APP) (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| subtype | PT=APP=204 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC/CSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | name (ASCII) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | application-dependent data ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +App::App() : sub_type_(0), name_(0) {} + +App::~App() = default; + +bool App::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + if (packet.payload_size_bytes() < kAppBaseLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to be a valid APP packet"; + return false; + } + if (packet.payload_size_bytes() % 4 != 0) { + RTC_LOG(LS_WARNING) + << "Packet payload must be 32 bits aligned to make a valid APP packet"; + return false; + } + sub_type_ = packet.fmt(); + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&packet.payload()[0])); + name_ = ByteReader<uint32_t>::ReadBigEndian(&packet.payload()[4]); + data_.SetData(packet.payload() + kAppBaseLength, + packet.payload_size_bytes() - kAppBaseLength); + return true; +} + +void App::SetSubType(uint8_t subtype) { + RTC_DCHECK_LE(subtype, 0x1f); + sub_type_ = subtype; +} + +void App::SetData(const uint8_t* data, size_t data_length) { + RTC_DCHECK(data); + RTC_DCHECK_EQ(data_length % 4, 0) << "Data must be 32 bits aligned."; + RTC_DCHECK_LE(data_length, kMaxDataSize) + << "App data size " << data_length << " exceed maximum of " + << kMaxDataSize << " bytes."; + data_.SetData(data, data_length); +} + +size_t App::BlockLength() const { + return kHeaderLength + kAppBaseLength + data_.size(); +} + +bool App::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + CreateHeader(sub_type_, kPacketType, HeaderLength(), packet, index); + + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 4], name_); + if (!data_.empty()) { + memcpy(&packet[*index + 8], data_.data(), data_.size()); + } + *index += (8 + data_.size()); + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h new file mode 100644 index 0000000000..4518792e5a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h @@ -0,0 +1,67 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_APP_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_APP_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "rtc_base/buffer.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class App : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 204; + App(); + App(App&&) = default; + ~App() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetSubType(uint8_t subtype); + void SetName(uint32_t name) { name_ = name; } + void SetData(const uint8_t* data, size_t data_length); + + uint8_t sub_type() const { return sub_type_; } + uint32_t name() const { return name_; } + size_t data_size() const { return data_.size(); } + const uint8_t* data() const { return data_.data(); } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + static inline constexpr uint32_t NameToInt(const char name[5]) { + return static_cast<uint32_t>(name[0]) << 24 | + static_cast<uint32_t>(name[1]) << 16 | + static_cast<uint32_t>(name[2]) << 8 | static_cast<uint32_t>(name[3]); + } + + private: + static constexpr size_t kAppBaseLength = 8; // Ssrc and Name. + static constexpr size_t kMaxDataSize = 0xffff * 4 - kAppBaseLength; + + uint8_t sub_type_; + uint32_t name_; + rtc::Buffer data_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_APP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc new file mode 100644 index 0000000000..8690e8e5a0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc @@ -0,0 +1,110 @@ +/* + * 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/app.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::webrtc::rtcp::App; + +constexpr uint32_t kName = ((uint32_t)'n' << 24) | ((uint32_t)'a' << 16) | + ((uint32_t)'m' << 8) | (uint32_t)'e'; +constexpr uint8_t kSubtype = 0x1e; +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint8_t kData[] = {'t', 'e', 's', 't', 'd', 'a', 't', 'a'}; +constexpr uint8_t kVersionBits = 2 << 6; +constexpr uint8_t kPaddingBit = 1 << 5; +// clang-format off +constexpr uint8_t kPacketWithoutData[] = { + kVersionBits | kSubtype, App::kPacketType, 0x00, 0x02, + 0x12, 0x34, 0x56, 0x78, + 'n', 'a', 'm', 'e'}; +constexpr uint8_t kPacketWithData[] = { + kVersionBits | kSubtype, App::kPacketType, 0x00, 0x04, + 0x12, 0x34, 0x56, 0x78, + 'n', 'a', 'm', 'e', + 't', 'e', 's', 't', + 'd', 'a', 't', 'a'}; +constexpr uint8_t kTooSmallPacket[] = { + kVersionBits | kSubtype, App::kPacketType, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; +constexpr uint8_t kPaddingSize = 1; +constexpr uint8_t kPacketWithUnalignedPayload[] = { + kVersionBits | kPaddingBit | kSubtype, App::kPacketType, 0x00, 0x03, + 0x12, 0x34, 0x56, 0x78, + 'n', 'a', 'm', 'e', + 'd', 'a', 't', kPaddingSize}; +// clang-format on +} // namespace + +TEST(RtcpPacketAppTest, CreateWithoutData) { + App app; + app.SetSenderSsrc(kSenderSsrc); + app.SetSubType(kSubtype); + app.SetName(kName); + + rtc::Buffer raw = app.Build(); + + EXPECT_THAT(make_tuple(raw.data(), raw.size()), + ElementsAreArray(kPacketWithoutData)); +} + +TEST(RtcpPacketAppTest, ParseWithoutData) { + App parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacketWithoutData, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kSubtype, parsed.sub_type()); + EXPECT_EQ(kName, parsed.name()); + EXPECT_EQ(0u, parsed.data_size()); +} + +TEST(RtcpPacketAppTest, CreateWithData) { + App app; + app.SetSenderSsrc(kSenderSsrc); + app.SetSubType(kSubtype); + app.SetName(kName); + app.SetData(kData, sizeof(kData)); + + rtc::Buffer raw = app.Build(); + + EXPECT_THAT(make_tuple(raw.data(), raw.size()), + ElementsAreArray(kPacketWithData)); +} + +TEST(RtcpPacketAppTest, ParseWithData) { + App parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacketWithData, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kSubtype, parsed.sub_type()); + EXPECT_EQ(kName, parsed.name()); + EXPECT_THAT(make_tuple(parsed.data(), parsed.data_size()), + ElementsAreArray(kData)); +} + +TEST(RtcpPacketAppTest, ParseFailsOnTooSmallPacket) { + App parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +TEST(RtcpPacketAppTest, ParseFailsOnUnalignedPayload) { + App parsed; + EXPECT_FALSE(test::ParseSinglePacket(kPacketWithUnalignedPayload, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc new file mode 100644 index 0000000000..a6471772b1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc @@ -0,0 +1,141 @@ +/* + * 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/bye.h" + +#include <string.h> + +#include <cstdint> +#include <utility> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Bye::kPacketType; +// Bye packet (BYE) (RFC 3550). +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| SC | PT=BYE=203 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC/CSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// (opt) | length | reason for leaving ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Bye::Bye() = default; + +Bye::~Bye() = default; + +bool Bye::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + const uint8_t src_count = packet.count(); + // Validate packet. + if (packet.payload_size_bytes() < 4u * src_count) { + RTC_LOG(LS_WARNING) + << "Packet is too small to contain CSRCs it promise to have."; + return false; + } + const uint8_t* const payload = packet.payload(); + bool has_reason = packet.payload_size_bytes() > 4u * src_count; + uint8_t reason_length = 0; + if (has_reason) { + reason_length = payload[4u * src_count]; + if (packet.payload_size_bytes() - 4u * src_count < 1u + reason_length) { + RTC_LOG(LS_WARNING) << "Invalid reason length: " << reason_length; + return false; + } + } + // Once sure packet is valid, copy values. + if (src_count == 0) { // A count value of zero is valid, but useless. + SetSenderSsrc(0); + csrcs_.clear(); + } else { + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(payload)); + csrcs_.resize(src_count - 1); + for (size_t i = 1; i < src_count; ++i) + csrcs_[i - 1] = ByteReader<uint32_t>::ReadBigEndian(&payload[4 * i]); + } + + if (has_reason) { + reason_.assign(reinterpret_cast<const char*>(&payload[4u * src_count + 1]), + reason_length); + } else { + reason_.clear(); + } + + return true; +} + +bool Bye::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(1 + csrcs_.size(), kPacketType, HeaderLength(), packet, index); + // Store srcs of the leaving clients. + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index], sender_ssrc()); + *index += sizeof(uint32_t); + for (uint32_t csrc : csrcs_) { + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index], csrc); + *index += sizeof(uint32_t); + } + // Store the reason to leave. + if (!reason_.empty()) { + uint8_t reason_length = static_cast<uint8_t>(reason_.size()); + packet[(*index)++] = reason_length; + memcpy(&packet[*index], reason_.data(), reason_length); + *index += reason_length; + // Add padding bytes if needed. + size_t bytes_to_pad = index_end - *index; + RTC_DCHECK_LE(bytes_to_pad, 3); + if (bytes_to_pad > 0) { + memset(&packet[*index], 0, bytes_to_pad); + *index += bytes_to_pad; + } + } + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +bool Bye::SetCsrcs(std::vector<uint32_t> csrcs) { + if (csrcs.size() > kMaxNumberOfCsrcs) { + RTC_LOG(LS_WARNING) << "Too many CSRCs for Bye packet."; + return false; + } + csrcs_ = std::move(csrcs); + return true; +} + +void Bye::SetReason(absl::string_view reason) { + RTC_DCHECK_LE(reason.size(), 0xffu); + reason_ = std::string(reason); +} + +size_t Bye::BlockLength() const { + size_t src_count = (1 + csrcs_.size()); + size_t reason_size_in_32bits = reason_.empty() ? 0 : (reason_.size() / 4 + 1); + return kHeaderLength + 4 * (src_count + reason_size_in_32bits); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h new file mode 100644 index 0000000000..d31205793a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h @@ -0,0 +1,57 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_BYE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_BYE_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class Bye : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 203; + + Bye(); + ~Bye() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool SetCsrcs(std::vector<uint32_t> csrcs); + void SetReason(absl::string_view reason); + + const std::vector<uint32_t>& csrcs() const { return csrcs_; } + const std::string& reason() const { return reason_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static const int kMaxNumberOfCsrcs = 0x1f - 1; // First item is sender SSRC. + + std::vector<uint32_t> csrcs_; + std::string reason_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_BYE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc new file mode 100644 index 0000000000..448c2d4194 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc @@ -0,0 +1,147 @@ +/* + * 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/bye.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAre; +using webrtc::rtcp::Bye; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kCsrc1 = 0x22232425; +const uint32_t kCsrc2 = 0x33343536; +} // namespace + +TEST(RtcpPacketByeTest, CreateAndParseWithoutReason) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_TRUE(parsed_bye.csrcs().empty()); + EXPECT_TRUE(parsed_bye.reason().empty()); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithCsrcs) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(bye.SetCsrcs({kCsrc1, kCsrc2})); + EXPECT_TRUE(bye.reason().empty()); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_THAT(parsed_bye.csrcs(), ElementsAre(kCsrc1, kCsrc2)); + EXPECT_TRUE(parsed_bye.reason().empty()); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithCsrcsAndAReason) { + Bye bye; + const std::string kReason = "Some Reason"; + + bye.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(bye.SetCsrcs({kCsrc1, kCsrc2})); + bye.SetReason(kReason); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_THAT(parsed_bye.csrcs(), ElementsAre(kCsrc1, kCsrc2)); + EXPECT_EQ(kReason, parsed_bye.reason()); +} + +TEST(RtcpPacketByeTest, CreateWithTooManyCsrcs) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + const int kMaxCsrcs = (1 << 5) - 2; // 5 bit len, first item is sender SSRC. + EXPECT_TRUE(bye.SetCsrcs(std::vector<uint32_t>(kMaxCsrcs, kCsrc1))); + EXPECT_FALSE(bye.SetCsrcs(std::vector<uint32_t>(kMaxCsrcs + 1, kCsrc1))); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithAReason) { + Bye bye; + const std::string kReason = "Some Random Reason"; + + bye.SetSenderSsrc(kSenderSsrc); + bye.SetReason(kReason); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kSenderSsrc, parsed_bye.sender_ssrc()); + EXPECT_TRUE(parsed_bye.csrcs().empty()); + EXPECT_EQ(kReason, parsed_bye.reason()); +} + +TEST(RtcpPacketByeTest, CreateAndParseWithReasons) { + // Test that packet creation/parsing behave with reasons of different length + // both when it require padding and when it does not. + for (size_t reminder = 0; reminder < 4; ++reminder) { + const std::string kReason(4 + reminder, 'a' + reminder); + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + bye.SetReason(kReason); + + rtc::Buffer raw = bye.Build(); + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed_bye)); + + EXPECT_EQ(kReason, parsed_bye.reason()); + } +} + +TEST(RtcpPacketByeTest, ParseEmptyPacket) { + uint8_t kEmptyPacket[] = {0x80, Bye::kPacketType, 0, 0}; + Bye parsed_bye; + EXPECT_TRUE(test::ParseSinglePacket(kEmptyPacket, &parsed_bye)); + EXPECT_EQ(0u, parsed_bye.sender_ssrc()); + EXPECT_TRUE(parsed_bye.csrcs().empty()); + EXPECT_TRUE(parsed_bye.reason().empty()); +} + +TEST(RtcpPacketByeTest, ParseFailOnInvalidSrcCount) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer raw = bye.Build(); + raw[0]++; // Damage the packet: increase ssrc count by one. + + Bye parsed_bye; + EXPECT_FALSE(test::ParseSinglePacket(raw, &parsed_bye)); +} + +TEST(RtcpPacketByeTest, ParseFailOnInvalidReasonLength) { + Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + bye.SetReason("18 characters long"); + + rtc::Buffer raw = bye.Build(); + // Damage the packet: decrease payload size by 4 bytes + raw[3]--; + raw.SetSize(raw.size() - 4); + + Bye parsed_bye; + EXPECT_FALSE(test::ParseSinglePacket(raw, &parsed_bye)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc new file mode 100644 index 0000000000..5b54982220 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc @@ -0,0 +1,89 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/common_header.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr size_t CommonHeader::kHeaderSizeBytes; +// 0 1 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 |V=2|P| C/F | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 1 | Packet Type | +// ----------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 2 | length | +// --------------------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Common header for all RTCP packets, 4 octets. +bool CommonHeader::Parse(const uint8_t* buffer, size_t size_bytes) { + const uint8_t kVersion = 2; + + if (size_bytes < kHeaderSizeBytes) { + RTC_LOG(LS_WARNING) + << "Too little data (" << size_bytes << " byte" + << (size_bytes != 1 ? "s" : "") + << ") remaining in buffer to parse RTCP header (4 bytes)."; + return false; + } + + uint8_t version = buffer[0] >> 6; + if (version != kVersion) { + RTC_LOG(LS_WARNING) << "Invalid RTCP header: Version must be " + << static_cast<int>(kVersion) << " but was " + << static_cast<int>(version); + return false; + } + + bool has_padding = (buffer[0] & 0x20) != 0; + count_or_format_ = buffer[0] & 0x1F; + packet_type_ = buffer[1]; + payload_size_ = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]) * 4; + payload_ = buffer + kHeaderSizeBytes; + padding_size_ = 0; + + if (size_bytes < kHeaderSizeBytes + payload_size_) { + RTC_LOG(LS_WARNING) << "Buffer too small (" << size_bytes + << " bytes) to fit an RtcpPacket with a header and " + << payload_size_ << " bytes."; + return false; + } + + if (has_padding) { + if (payload_size_ == 0) { + RTC_LOG(LS_WARNING) + << "Invalid RTCP header: Padding bit set but 0 payload " + "size specified."; + return false; + } + + padding_size_ = payload_[payload_size_ - 1]; + if (padding_size_ == 0) { + RTC_LOG(LS_WARNING) + << "Invalid RTCP header: Padding bit set but 0 padding " + "size specified."; + return false; + } + if (padding_size_ > payload_size_) { + RTC_LOG(LS_WARNING) << "Invalid RTCP header: Too many padding bytes (" + << padding_size_ << ") for a packet payload size of " + << payload_size_ << " bytes."; + return false; + } + payload_size_ -= padding_size_; + } + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h new file mode 100644 index 0000000000..5416406091 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h @@ -0,0 +1,52 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMMON_HEADER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMMON_HEADER_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { +namespace rtcp { +class CommonHeader { + public: + static constexpr size_t kHeaderSizeBytes = 4; + + CommonHeader() {} + CommonHeader(const CommonHeader&) = default; + CommonHeader& operator=(const CommonHeader&) = default; + + bool Parse(const uint8_t* buffer, size_t size_bytes); + + uint8_t type() const { return packet_type_; } + // Depending on packet type same header field can be used either as count or + // as feedback message type (fmt). Caller expected to know how it is used. + uint8_t fmt() const { return count_or_format_; } + uint8_t count() const { return count_or_format_; } + size_t payload_size_bytes() const { return payload_size_; } + const uint8_t* payload() const { return payload_; } + size_t packet_size() const { + return kHeaderSizeBytes + payload_size_ + padding_size_; + } + // Returns pointer to the next RTCP packet in compound packet. + const uint8_t* NextPacket() const { + return payload_ + payload_size_ + padding_size_; + } + + private: + uint8_t packet_type_ = 0; + uint8_t count_or_format_ = 0; + uint8_t padding_size_ = 0; + uint32_t payload_size_ = 0; + const uint8_t* payload_ = nullptr; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMMON_HEADER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc new file mode 100644 index 0000000000..e8b4c52c68 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc @@ -0,0 +1,103 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/common_header.h" + +#include "test/gtest.h" + +using webrtc::rtcp::CommonHeader; + +namespace webrtc { + +TEST(RtcpCommonHeaderTest, TooSmallBuffer) { + uint8_t buffer[] = {0x80, 0x00, 0x00, 0x00}; + CommonHeader header; + // Buffer needs to be able to hold the header. + EXPECT_FALSE(header.Parse(buffer, 0)); + EXPECT_FALSE(header.Parse(buffer, 1)); + EXPECT_FALSE(header.Parse(buffer, 2)); + EXPECT_FALSE(header.Parse(buffer, 3)); + EXPECT_TRUE(header.Parse(buffer, 4)); +} + +TEST(RtcpCommonHeaderTest, Version) { + uint8_t buffer[] = {0x00, 0x00, 0x00, 0x00}; + CommonHeader header; + // Version 2 is the only allowed. + buffer[0] = 0 << 6; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + buffer[0] = 1 << 6; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + buffer[0] = 2 << 6; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + buffer[0] = 3 << 6; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); +} + +TEST(RtcpCommonHeaderTest, PacketSize) { + uint8_t buffer[] = {0x80, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + CommonHeader header; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer) - 1)); + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + EXPECT_EQ(8u, header.payload_size_bytes()); + EXPECT_EQ(buffer + sizeof(buffer), header.NextPacket()); + EXPECT_EQ(sizeof(buffer), header.packet_size()); +} + +TEST(RtcpCommonHeaderTest, PaddingAndPayloadSize) { + // Set v = 2, p = 1, but leave fmt, pt as 0. + uint8_t buffer[] = {0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + CommonHeader header; + // Padding bit set, but no byte for padding (can't specify padding length). + EXPECT_FALSE(header.Parse(buffer, 4)); + + buffer[3] = 2; // Set payload size to 2x32bit. + const size_t kPayloadSizeBytes = buffer[3] * 4; + const size_t kPaddingAddress = + CommonHeader::kHeaderSizeBytes + kPayloadSizeBytes - 1; + + // Padding one byte larger than possible. + buffer[kPaddingAddress] = kPayloadSizeBytes + 1; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + + // Invalid zero padding size. + buffer[kPaddingAddress] = 0; + EXPECT_FALSE(header.Parse(buffer, sizeof(buffer))); + + // Pure padding packet. + buffer[kPaddingAddress] = kPayloadSizeBytes; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + EXPECT_EQ(0u, header.payload_size_bytes()); + EXPECT_EQ(buffer + sizeof(buffer), header.NextPacket()); + EXPECT_EQ(header.payload(), buffer + CommonHeader::kHeaderSizeBytes); + EXPECT_EQ(header.packet_size(), sizeof(buffer)); + + // Single byte of actual data. + buffer[kPaddingAddress] = kPayloadSizeBytes - 1; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + EXPECT_EQ(1u, header.payload_size_bytes()); + EXPECT_EQ(buffer + sizeof(buffer), header.NextPacket()); + EXPECT_EQ(header.packet_size(), sizeof(buffer)); +} + +TEST(RtcpCommonHeaderTest, FormatAndPayloadType) { + uint8_t buffer[] = {0x9e, 0xab, 0x00, 0x00}; + CommonHeader header; + EXPECT_TRUE(header.Parse(buffer, sizeof(buffer))); + + EXPECT_EQ(header.count(), 0x1e); + EXPECT_EQ(header.fmt(), 0x1e); + EXPECT_EQ(header.type(), 0xab); + EXPECT_EQ(header.payload_size_bytes(), 0u); + EXPECT_EQ(header.payload(), buffer + CommonHeader::kHeaderSizeBytes); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc new file mode 100644 index 0000000000..54f3555fc6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc @@ -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. + */ + +#include "modules/rtp_rtcp/source/rtcp_packet/compound_packet.h" + +#include <memory> +#include <utility> + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace rtcp { + +CompoundPacket::CompoundPacket() = default; + +CompoundPacket::~CompoundPacket() = default; + +void CompoundPacket::Append(std::unique_ptr<RtcpPacket> packet) { + RTC_CHECK(packet); + appended_packets_.push_back(std::move(packet)); +} + +bool CompoundPacket::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + for (const auto& appended : appended_packets_) { + if (!appended->Create(packet, index, max_length, callback)) + return false; + } + return true; +} + +size_t CompoundPacket::BlockLength() const { + size_t block_length = 0; + for (const auto& appended : appended_packets_) { + block_length += appended->BlockLength(); + } + return block_length; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h new file mode 100644 index 0000000000..d98dbd088d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h @@ -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. + * + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMPOUND_PACKET_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMPOUND_PACKET_H_ + +#include <memory> +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { + +class CompoundPacket : public RtcpPacket { + public: + CompoundPacket(); + ~CompoundPacket() override; + + CompoundPacket(const CompoundPacket&) = delete; + CompoundPacket& operator=(const CompoundPacket&) = delete; + + void Append(std::unique_ptr<RtcpPacket> packet); + + // Size of this packet in bytes (i.e. total size of nested packets). + size_t BlockLength() const override; + // Returns true if all calls to Create succeeded. + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + protected: + std::vector<std::unique_ptr<RtcpPacket>> appended_packets_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_COMPOUND_PACKET_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc new file mode 100644 index 0000000000..9348aee7e4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc @@ -0,0 +1,155 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/compound_packet.h" + +#include <memory> +#include <utility> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtcp_packet/fir.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::_; +using ::testing::Invoke; +using ::testing::MockFunction; +using webrtc::rtcp::Bye; +using webrtc::rtcp::CompoundPacket; +using webrtc::rtcp::Fir; +using webrtc::rtcp::ReceiverReport; +using webrtc::rtcp::ReportBlock; +using webrtc::rtcp::SenderReport; +using webrtc::test::RtcpPacketParser; + +namespace webrtc { + +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint8_t kSeqNo = 13; + +TEST(RtcpCompoundPacketTest, AppendPacket) { + CompoundPacket compound; + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + ReportBlock rb; + auto rr = std::make_unique<ReceiverReport>(); + rr->SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr->AddReportBlock(rb)); + compound.Append(std::move(rr)); + compound.Append(std::move(fir)); + + rtc::Buffer packet = compound.Build(); + RtcpPacketParser parser; + parser.Parse(packet.data(), packet.size()); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser.receiver_report()->sender_ssrc()); + EXPECT_EQ(1u, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.fir()->num_packets()); +} + +TEST(RtcpCompoundPacketTest, AppendPacketWithOwnAppendedPacket) { + CompoundPacket root; + auto leaf = std::make_unique<CompoundPacket>(); + + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + auto bye = std::make_unique<Bye>(); + ReportBlock rb; + + auto rr = std::make_unique<ReceiverReport>(); + EXPECT_TRUE(rr->AddReportBlock(rb)); + leaf->Append(std::move(rr)); + leaf->Append(std::move(fir)); + + auto sr = std::make_unique<SenderReport>(); + root.Append(std::move(sr)); + root.Append(std::move(bye)); + root.Append(std::move(leaf)); + + rtc::Buffer packet = root.Build(); + RtcpPacketParser parser; + parser.Parse(packet.data(), packet.size()); + EXPECT_EQ(1, parser.sender_report()->num_packets()); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(1u, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.bye()->num_packets()); + EXPECT_EQ(1, parser.fir()->num_packets()); +} + +TEST(RtcpCompoundPacketTest, BuildWithInputBuffer) { + CompoundPacket compound; + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + ReportBlock rb; + auto rr = std::make_unique<ReceiverReport>(); + rr->SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr->AddReportBlock(rb)); + compound.Append(std::move(rr)); + compound.Append(std::move(fir)); + + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; + const size_t kFirLength = 20; + + const size_t kBufferSize = kRrLength + kReportBlockLength + kFirLength; + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + RtcpPacketParser parser; + parser.Parse(packet.data(), packet.size()); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(1u, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.fir()->num_packets()); + })); + + EXPECT_TRUE(compound.Build(kBufferSize, callback.AsStdFunction())); +} + +TEST(RtcpCompoundPacketTest, BuildWithTooSmallBuffer_FragmentedSend) { + CompoundPacket compound; + auto fir = std::make_unique<Fir>(); + fir->AddRequestTo(kRemoteSsrc, kSeqNo); + ReportBlock rb; + auto rr = std::make_unique<ReceiverReport>(); + rr->SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr->AddReportBlock(rb)); + compound.Append(std::move(rr)); + compound.Append(std::move(fir)); + + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; + + const size_t kBufferSize = kRrLength + kReportBlockLength; + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + RtcpPacketParser parser; + parser.Parse(packet.data(), packet.size()); + EXPECT_EQ(1, parser.receiver_report()->num_packets()); + EXPECT_EQ(1U, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(0, parser.fir()->num_packets()); + })) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + RtcpPacketParser parser; + parser.Parse(packet.data(), packet.size()); + EXPECT_EQ(0, parser.receiver_report()->num_packets()); + EXPECT_EQ(0U, parser.receiver_report()->report_blocks().size()); + EXPECT_EQ(1, parser.fir()->num_packets()); + })); + + EXPECT_TRUE(compound.Build(kBufferSize, callback.AsStdFunction())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc new file mode 100644 index 0000000000..6863def2fe --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc @@ -0,0 +1,94 @@ +/* + * 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/dlrr.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { +namespace rtcp { +// DLRR Report Block (RFC 3611). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=5 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_1 (SSRC of first receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// | last RR (LRR) | 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | delay since last RR (DLRR) | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | SSRC_2 (SSRC of second receiver) | sub- +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +// : ... : 2 + +Dlrr::Dlrr() = default; + +Dlrr::Dlrr(const Dlrr& other) = default; + +Dlrr::~Dlrr() = default; + +bool Dlrr::Parse(const uint8_t* buffer, uint16_t block_length_32bits) { + RTC_DCHECK(buffer[0] == kBlockType); + // kReserved = buffer[1]; + RTC_DCHECK_EQ(block_length_32bits, + ByteReader<uint16_t>::ReadBigEndian(&buffer[2])); + if (block_length_32bits % 3 != 0) { + RTC_LOG(LS_WARNING) << "Invalid size for dlrr block."; + return false; + } + + size_t blocks_count = block_length_32bits / 3; + const uint8_t* read_at = buffer + kBlockHeaderLength; + sub_blocks_.resize(blocks_count); + for (ReceiveTimeInfo& sub_block : sub_blocks_) { + sub_block.ssrc = ByteReader<uint32_t>::ReadBigEndian(&read_at[0]); + sub_block.last_rr = ByteReader<uint32_t>::ReadBigEndian(&read_at[4]); + sub_block.delay_since_last_rr = + ByteReader<uint32_t>::ReadBigEndian(&read_at[8]); + read_at += kSubBlockLength; + } + return true; +} + +size_t Dlrr::BlockLength() const { + if (sub_blocks_.empty()) + return 0; + return kBlockHeaderLength + kSubBlockLength * sub_blocks_.size(); +} + +void Dlrr::Create(uint8_t* buffer) const { + if (sub_blocks_.empty()) // No subblocks, no need to write header either. + return; + // Create block header. + const uint8_t kReserved = 0; + buffer[0] = kBlockType; + buffer[1] = kReserved; + ByteWriter<uint16_t>::WriteBigEndian( + &buffer[2], rtc::dchecked_cast<uint16_t>(3 * sub_blocks_.size())); + // Create sub blocks. + uint8_t* write_at = buffer + kBlockHeaderLength; + for (const ReceiveTimeInfo& sub_block : sub_blocks_) { + ByteWriter<uint32_t>::WriteBigEndian(&write_at[0], sub_block.ssrc); + ByteWriter<uint32_t>::WriteBigEndian(&write_at[4], sub_block.last_rr); + ByteWriter<uint32_t>::WriteBigEndian(&write_at[8], + sub_block.delay_since_last_rr); + write_at += kSubBlockLength; + } + RTC_DCHECK_EQ(buffer + BlockLength(), write_at); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h new file mode 100644 index 0000000000..ad91dfdcc6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_DLRR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_DLRR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +namespace webrtc { +namespace rtcp { +struct ReceiveTimeInfo { + // RFC 3611 4.5 + ReceiveTimeInfo() : ssrc(0), last_rr(0), delay_since_last_rr(0) {} + ReceiveTimeInfo(uint32_t ssrc, uint32_t last_rr, uint32_t delay) + : ssrc(ssrc), last_rr(last_rr), delay_since_last_rr(delay) {} + + uint32_t ssrc; + uint32_t last_rr; + uint32_t delay_since_last_rr; +}; + +inline bool operator==(const ReceiveTimeInfo& lhs, const ReceiveTimeInfo& rhs) { + return lhs.ssrc == rhs.ssrc && lhs.last_rr == rhs.last_rr && + lhs.delay_since_last_rr == rhs.delay_since_last_rr; +} + +inline bool operator!=(const ReceiveTimeInfo& lhs, const ReceiveTimeInfo& rhs) { + return !(lhs == rhs); +} + +// DLRR Report Block: Delay since the Last Receiver Report (RFC 3611). +class Dlrr { + public: + static const uint8_t kBlockType = 5; + + Dlrr(); + Dlrr(const Dlrr& other); + ~Dlrr(); + + Dlrr& operator=(const Dlrr& other) = default; + + // Dlrr without items treated same as no dlrr block. + explicit operator bool() const { return !sub_blocks_.empty(); } + + // Second parameter is value read from block header, + // i.e. size of block in 32bits excluding block header itself. + bool Parse(const uint8_t* buffer, uint16_t block_length_32bits); + + size_t BlockLength() const; + // Fills buffer with the Dlrr. + // Consumes BlockLength() bytes. + void Create(uint8_t* buffer) const; + + void ClearItems() { sub_blocks_.clear(); } + void AddDlrrItem(const ReceiveTimeInfo& time_info) { + sub_blocks_.push_back(time_info); + } + + const std::vector<ReceiveTimeInfo>& sub_blocks() const { return sub_blocks_; } + + private: + static const size_t kBlockHeaderLength = 4; + static const size_t kSubBlockLength = 12; + + std::vector<ReceiveTimeInfo> sub_blocks_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_DLRR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc new file mode 100644 index 0000000000..408d0011b8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc @@ -0,0 +1,92 @@ +/* + * 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/dlrr.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "test/gtest.h" + +using webrtc::rtcp::Dlrr; +using webrtc::rtcp::ReceiveTimeInfo; + +namespace webrtc { +namespace { +const uint32_t kSsrc = 0x12345678; +const uint32_t kLastRR = 0x23344556; +const uint32_t kDelay = 0x33343536; +const uint8_t kBlock[] = {0x05, 0x00, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + 0x23, 0x34, 0x45, 0x56, 0x33, 0x34, 0x35, 0x36}; +const size_t kBlockSizeBytes = sizeof(kBlock); +} // namespace + +TEST(RtcpPacketDlrrTest, Empty) { + Dlrr dlrr; + + EXPECT_EQ(0u, dlrr.BlockLength()); +} + +TEST(RtcpPacketDlrrTest, Create) { + Dlrr dlrr; + dlrr.AddDlrrItem(ReceiveTimeInfo(kSsrc, kLastRR, kDelay)); + + ASSERT_EQ(kBlockSizeBytes, dlrr.BlockLength()); + uint8_t buffer[kBlockSizeBytes]; + + dlrr.Create(buffer); + EXPECT_EQ(0, memcmp(buffer, kBlock, kBlockSizeBytes)); +} + +TEST(RtcpPacketDlrrTest, Parse) { + Dlrr dlrr; + uint16_t block_length = ByteReader<uint16_t>::ReadBigEndian(&kBlock[2]); + EXPECT_TRUE(dlrr.Parse(kBlock, block_length)); + + EXPECT_EQ(1u, dlrr.sub_blocks().size()); + const ReceiveTimeInfo& block = dlrr.sub_blocks().front(); + EXPECT_EQ(kSsrc, block.ssrc); + EXPECT_EQ(kLastRR, block.last_rr); + EXPECT_EQ(kDelay, block.delay_since_last_rr); +} + +TEST(RtcpPacketDlrrTest, ParseFailsOnBadSize) { + const size_t kBigBufferSize = 0x100; // More than enough. + uint8_t buffer[kBigBufferSize]; + buffer[0] = Dlrr::kBlockType; + buffer[1] = 0; // Reserved. + buffer[2] = 0; // Most significant size byte. + for (uint8_t size = 3; size < 6; ++size) { + buffer[3] = size; + Dlrr dlrr; + // Parse should be successful only when size is multiple of 3. + EXPECT_EQ(size % 3 == 0, dlrr.Parse(buffer, static_cast<uint16_t>(size))); + } +} + +TEST(RtcpPacketDlrrTest, CreateAndParseManySubBlocks) { + const size_t kBufferSize = 0x1000; // More than enough. + const size_t kManyDlrrItems = 50; + uint8_t buffer[kBufferSize]; + + // Create. + Dlrr dlrr; + for (size_t i = 1; i <= kManyDlrrItems; ++i) + dlrr.AddDlrrItem(ReceiveTimeInfo(kSsrc + i, kLastRR + i, kDelay + i)); + size_t used_buffer_size = dlrr.BlockLength(); + ASSERT_LE(used_buffer_size, kBufferSize); + dlrr.Create(buffer); + + // Parse. + Dlrr parsed; + uint16_t block_length = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]); + EXPECT_EQ(used_buffer_size, (block_length + 1) * 4u); + EXPECT_TRUE(parsed.Parse(buffer, block_length)); + EXPECT_EQ(kManyDlrrItems, parsed.sub_blocks().size()); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc new file mode 100644 index 0000000000..ce57bd5a88 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc @@ -0,0 +1,195 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/extended_reports.h" + +#include <vector> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t ExtendedReports::kPacketType; +constexpr size_t ExtendedReports::kMaxNumberOfDlrrItems; +// From RFC 3611: RTP Control Protocol Extended Reports (RTCP XR). +// +// Format for XR packets: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P|reserved | PT=XR=207 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : report blocks : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Extended report block: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Block Type | reserved | block length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : type-specific block contents : +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +ExtendedReports::ExtendedReports() = default; +ExtendedReports::ExtendedReports(const ExtendedReports& xr) = default; +ExtendedReports::~ExtendedReports() = default; + +bool ExtendedReports::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + if (packet.payload_size_bytes() < kXrBaseLength) { + RTC_LOG(LS_WARNING) + << "Packet is too small to be an ExtendedReports packet."; + return false; + } + + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(packet.payload())); + rrtr_block_.reset(); + dlrr_block_.ClearItems(); + target_bitrate_ = absl::nullopt; + + const uint8_t* current_block = packet.payload() + kXrBaseLength; + const uint8_t* const packet_end = + packet.payload() + packet.payload_size_bytes(); + constexpr size_t kBlockHeaderSizeBytes = 4; + while (current_block + kBlockHeaderSizeBytes <= packet_end) { + uint8_t block_type = ByteReader<uint8_t>::ReadBigEndian(current_block); + uint16_t block_length = + ByteReader<uint16_t>::ReadBigEndian(current_block + 2); + const uint8_t* next_block = + current_block + kBlockHeaderSizeBytes + block_length * 4; + if (next_block > packet_end) { + RTC_LOG(LS_WARNING) + << "Report block in extended report packet is too big."; + return false; + } + switch (block_type) { + case Rrtr::kBlockType: + ParseRrtrBlock(current_block, block_length); + break; + case Dlrr::kBlockType: + ParseDlrrBlock(current_block, block_length); + break; + case TargetBitrate::kBlockType: + ParseTargetBitrateBlock(current_block, block_length); + break; + default: + // Unknown block, ignore. + RTC_LOG(LS_WARNING) + << "Unknown extended report block type " << block_type; + break; + } + current_block = next_block; + } + + return true; +} + +void ExtendedReports::SetRrtr(const Rrtr& rrtr) { + if (rrtr_block_) + RTC_LOG(LS_WARNING) << "Rrtr already set, overwriting."; + rrtr_block_.emplace(rrtr); +} + +bool ExtendedReports::AddDlrrItem(const ReceiveTimeInfo& time_info) { + if (dlrr_block_.sub_blocks().size() >= kMaxNumberOfDlrrItems) { + RTC_LOG(LS_WARNING) << "Reached maximum number of DLRR items."; + return false; + } + dlrr_block_.AddDlrrItem(time_info); + return true; +} + +void ExtendedReports::SetTargetBitrate(const TargetBitrate& bitrate) { + if (target_bitrate_) + RTC_LOG(LS_WARNING) << "TargetBitrate already set, overwriting."; + + target_bitrate_ = bitrate; +} + +size_t ExtendedReports::BlockLength() const { + return kHeaderLength + kXrBaseLength + RrtrLength() + DlrrLength() + + TargetBitrateLength(); +} + +bool ExtendedReports::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + size_t index_end = *index + BlockLength(); + const uint8_t kReserved = 0; + CreateHeader(kReserved, kPacketType, HeaderLength(), packet, index); + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, sender_ssrc()); + *index += sizeof(uint32_t); + if (rrtr_block_) { + rrtr_block_->Create(packet + *index); + *index += Rrtr::kLength; + } + if (dlrr_block_) { + dlrr_block_.Create(packet + *index); + *index += dlrr_block_.BlockLength(); + } + if (target_bitrate_) { + target_bitrate_->Create(packet + *index); + *index += target_bitrate_->BlockLength(); + } + RTC_CHECK_EQ(*index, index_end); + return true; +} + +size_t ExtendedReports::TargetBitrateLength() const { + if (target_bitrate_) + return target_bitrate_->BlockLength(); + return 0; +} + +void ExtendedReports::ParseRrtrBlock(const uint8_t* block, + uint16_t block_length) { + if (block_length != Rrtr::kBlockLength) { + RTC_LOG(LS_WARNING) << "Incorrect rrtr block size " << block_length + << " Should be " << Rrtr::kBlockLength; + return; + } + if (rrtr_block_) { + RTC_LOG(LS_WARNING) + << "Two rrtr blocks found in same Extended Report packet"; + return; + } + rrtr_block_.emplace(); + rrtr_block_->Parse(block); +} + +void ExtendedReports::ParseDlrrBlock(const uint8_t* block, + uint16_t block_length) { + if (dlrr_block_) { + RTC_LOG(LS_WARNING) + << "Two Dlrr blocks found in same Extended Report packet"; + return; + } + dlrr_block_.Parse(block, block_length); +} + +void ExtendedReports::ParseTargetBitrateBlock(const uint8_t* block, + uint16_t block_length) { + target_bitrate_.emplace(); + target_bitrate_->Parse(block, block_length); +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h new file mode 100644 index 0000000000..6c804bbc7b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h @@ -0,0 +1,73 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_EXTENDED_REPORTS_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_EXTENDED_REPORTS_H_ + +#include <vector> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rrtr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// From RFC 3611: RTP Control Protocol Extended Reports (RTCP XR). +class ExtendedReports : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 207; + static constexpr size_t kMaxNumberOfDlrrItems = 50; + + ExtendedReports(); + ExtendedReports(const ExtendedReports& xr); + ~ExtendedReports() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetRrtr(const Rrtr& rrtr); + bool AddDlrrItem(const ReceiveTimeInfo& time_info); + void SetTargetBitrate(const TargetBitrate& target_bitrate); + + const absl::optional<Rrtr>& rrtr() const { return rrtr_block_; } + const Dlrr& dlrr() const { return dlrr_block_; } + const absl::optional<TargetBitrate>& target_bitrate() const { + return target_bitrate_; + } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kXrBaseLength = 4; + + size_t RrtrLength() const { return rrtr_block_ ? Rrtr::kLength : 0; } + size_t DlrrLength() const { return dlrr_block_.BlockLength(); } + size_t TargetBitrateLength() const; + + void ParseRrtrBlock(const uint8_t* block, uint16_t block_length); + void ParseDlrrBlock(const uint8_t* block, uint16_t block_length); + void ParseTargetBitrateBlock(const uint8_t* block, uint16_t block_length); + + absl::optional<Rrtr> rrtr_block_; + Dlrr dlrr_block_; // Dlrr without items treated same as no dlrr block. + absl::optional<TargetBitrate> target_bitrate_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_EXTENDED_REPORTS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc new file mode 100644 index 0000000000..3d9a2a3408 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc @@ -0,0 +1,169 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/extended_reports.h" + +#include "rtc_base/random.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::testing::SizeIs; +using webrtc::rtcp::ExtendedReports; +using webrtc::rtcp::ReceiveTimeInfo; +using webrtc::rtcp::Rrtr; + +namespace webrtc { +namespace { +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint8_t kEmptyPacket[] = {0x80, 207, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; +} // namespace + +class RtcpPacketExtendedReportsTest : public ::testing::Test { + public: + RtcpPacketExtendedReportsTest() : random_(0x123456789) {} + + protected: + template <typename T> + T Rand() { + return random_.Rand<T>(); + } + + private: + Random random_; +}; + +template <> +ReceiveTimeInfo RtcpPacketExtendedReportsTest::Rand<ReceiveTimeInfo>() { + uint32_t ssrc = Rand<uint32_t>(); + uint32_t last_rr = Rand<uint32_t>(); + uint32_t delay_since_last_rr = Rand<uint32_t>(); + return ReceiveTimeInfo(ssrc, last_rr, delay_since_last_rr); +} + +template <> +NtpTime RtcpPacketExtendedReportsTest::Rand<NtpTime>() { + uint32_t secs = Rand<uint32_t>(); + uint32_t frac = Rand<uint32_t>(); + return NtpTime(secs, frac); +} + +template <> +Rrtr RtcpPacketExtendedReportsTest::Rand<Rrtr>() { + Rrtr rrtr; + rrtr.SetNtp(Rand<NtpTime>()); + return rrtr; +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateWithoutReportBlocks) { + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer packet = xr.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kEmptyPacket)); +} + +TEST_F(RtcpPacketExtendedReportsTest, ParseWithoutReportBlocks) { + ExtendedReports parsed; + EXPECT_TRUE(test::ParseSinglePacket(kEmptyPacket, &parsed)); + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_FALSE(parsed.rrtr()); + EXPECT_FALSE(parsed.dlrr()); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithRrtrBlock) { + const Rrtr kRrtr = Rand<Rrtr>(); + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(kRrtr); + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRrtr, parsed.rrtr()); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithDlrrWithOneSubBlock) { + const ReceiveTimeInfo kTimeInfo = Rand<ReceiveTimeInfo>(); + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(kTimeInfo); + + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.dlrr().sub_blocks(), ElementsAre(kTimeInfo)); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithDlrrWithTwoSubBlocks) { + const ReceiveTimeInfo kTimeInfo1 = Rand<ReceiveTimeInfo>(); + const ReceiveTimeInfo kTimeInfo2 = Rand<ReceiveTimeInfo>(); + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(kTimeInfo1); + xr.AddDlrrItem(kTimeInfo2); + + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.dlrr().sub_blocks(), ElementsAre(kTimeInfo1, kTimeInfo2)); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateLimitsTheNumberOfDlrrSubBlocks) { + const ReceiveTimeInfo kTimeInfo = Rand<ReceiveTimeInfo>(); + ExtendedReports xr; + + for (size_t i = 0; i < ExtendedReports::kMaxNumberOfDlrrItems; ++i) + EXPECT_TRUE(xr.AddDlrrItem(kTimeInfo)); + EXPECT_FALSE(xr.AddDlrrItem(kTimeInfo)); + + EXPECT_THAT(xr.dlrr().sub_blocks(), + SizeIs(ExtendedReports::kMaxNumberOfDlrrItems)); +} + +TEST_F(RtcpPacketExtendedReportsTest, CreateAndParseWithMaximumReportBlocks) { + const Rrtr kRrtr = Rand<Rrtr>(); + + ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(kRrtr); + for (size_t i = 0; i < ExtendedReports::kMaxNumberOfDlrrItems; ++i) + xr.AddDlrrItem(Rand<ReceiveTimeInfo>()); + + rtc::Buffer packet = xr.Build(); + + ExtendedReports mparsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &mparsed)); + const ExtendedReports& parsed = mparsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRrtr, parsed.rrtr()); + EXPECT_THAT(parsed.dlrr().sub_blocks(), + ElementsAreArray(xr.dlrr().sub_blocks())); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc new file mode 100644 index 0000000000..fd4a4c947a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc @@ -0,0 +1,113 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/fir.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Fir::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source (unused) = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// Full intra request (FIR) (RFC 5104). +// The Feedback Control Information (FCI) for the Full Intra Request +// consists of one or more FCI entries. +// FCI: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Seq nr. | Reserved = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Fir::Fir() = default; + +Fir::Fir(const Fir& fir) = default; + +Fir::~Fir() = default; + +bool Fir::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + // The FCI field MUST contain one or more FIR entries. + if (packet.payload_size_bytes() < kCommonFeedbackLength + kFciLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to be a valid FIR packet."; + return false; + } + + if ((packet.payload_size_bytes() - kCommonFeedbackLength) % kFciLength != 0) { + RTC_LOG(LS_WARNING) << "Invalid size for a valid FIR packet."; + return false; + } + + ParseCommonFeedback(packet.payload()); + + size_t number_of_fci_items = + (packet.payload_size_bytes() - kCommonFeedbackLength) / kFciLength; + const uint8_t* next_fci = packet.payload() + kCommonFeedbackLength; + items_.resize(number_of_fci_items); + for (Request& request : items_) { + request.ssrc = ByteReader<uint32_t>::ReadBigEndian(next_fci); + request.seq_nr = ByteReader<uint8_t>::ReadBigEndian(next_fci + 4); + next_fci += kFciLength; + } + return true; +} + +size_t Fir::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + kFciLength * items_.size(); +} + +bool Fir::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + RTC_DCHECK(!items_.empty()); + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + size_t index_end = *index + BlockLength(); + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(Psfb::media_ssrc(), 0); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + constexpr uint32_t kReserved = 0; + for (const Request& request : items_) { + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, request.ssrc); + ByteWriter<uint8_t>::WriteBigEndian(packet + *index + 4, request.seq_nr); + ByteWriter<uint32_t, 3>::WriteBigEndian(packet + *index + 5, kReserved); + *index += kFciLength; + } + RTC_CHECK_EQ(*index, index_end); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h new file mode 100644 index 0000000000..383dc96114 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FIR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FIR_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; +// Full intra request (FIR) (RFC 5104). +class Fir : public Psfb { + public: + static constexpr uint8_t kFeedbackMessageType = 4; + struct Request { + Request() : ssrc(0), seq_nr(0) {} + Request(uint32_t ssrc, uint8_t seq_nr) : ssrc(ssrc), seq_nr(seq_nr) {} + uint32_t ssrc; + uint8_t seq_nr; + }; + + Fir(); + Fir(const Fir& fir); + ~Fir() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void AddRequestTo(uint32_t ssrc, uint8_t seq_num) { + items_.emplace_back(ssrc, seq_num); + } + const std::vector<Request>& requests() const { return items_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kFciLength = 8; + + // SSRC of media source is not used in FIR packet. Shadow base functions. + void SetMediaSsrc(uint32_t ssrc); + uint32_t media_ssrc() const; + + std::vector<Request> items_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_FIR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc new file mode 100644 index 0000000000..01593e12ba --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc @@ -0,0 +1,93 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/fir.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Field; +using ::testing::make_tuple; +using webrtc::rtcp::Fir; + +namespace webrtc { +namespace { + +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint32_t kRemoteSsrc = 0x23456789; +constexpr uint8_t kSeqNr = 13; +// Manually created Fir packet matching constants above. +constexpr uint8_t kPacket[] = {0x84, 206, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + 0x67, 0x89, 0x0d, 0x00, 0x00, 0x00}; +} // namespace + +TEST(RtcpPacketFirTest, Parse) { + Fir mutable_parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &mutable_parsed)); + const Fir& parsed = mutable_parsed; // Read values from constant object. + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.requests(), + ElementsAre(AllOf(Field(&Fir::Request::ssrc, Eq(kRemoteSsrc)), + Field(&Fir::Request::seq_nr, Eq(kSeqNr))))); +} + +TEST(RtcpPacketFirTest, Create) { + Fir fir; + fir.SetSenderSsrc(kSenderSsrc); + fir.AddRequestTo(kRemoteSsrc, kSeqNr); + + rtc::Buffer packet = fir.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketFirTest, TwoFciEntries) { + Fir fir; + fir.SetSenderSsrc(kSenderSsrc); + fir.AddRequestTo(kRemoteSsrc, kSeqNr); + fir.AddRequestTo(kRemoteSsrc + 1, kSeqNr + 1); + + rtc::Buffer packet = fir.Build(); + Fir parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.requests(), + ElementsAre(AllOf(Field(&Fir::Request::ssrc, Eq(kRemoteSsrc)), + Field(&Fir::Request::seq_nr, Eq(kSeqNr))), + AllOf(Field(&Fir::Request::ssrc, Eq(kRemoteSsrc + 1)), + Field(&Fir::Request::seq_nr, Eq(kSeqNr + 1))))); +} + +TEST(RtcpPacketFirTest, ParseFailsOnZeroFciEntries) { + constexpr uint8_t kPacketWithoutFci[] = {0x84, 206, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00}; + Fir parsed; + EXPECT_FALSE(test::ParseSinglePacket(kPacketWithoutFci, &parsed)); +} + +TEST(RtcpPacketFirTest, ParseFailsOnFractionalFciEntries) { + constexpr uint8_t kPacketWithOneAndHalfFci[] = { + 0x84, 206, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x45, 0x67, 0x89, 0x0d, 0x00, 0x00, 0x00, 'h', 'a', 'l', 'f'}; + + Fir parsed; + EXPECT_FALSE(test::ParseSinglePacket(kPacketWithOneAndHalfFci, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc new file mode 100644 index 0000000000..0817846f95 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc @@ -0,0 +1,133 @@ +/* + * 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/rtcp_packet/loss_notification.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { + +// Loss Notification +// ----------------- +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=206 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Unique identifier 'L' 'N' 'T' 'F' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | Last Decoded Sequence Number | Last Received SeqNum Delta |D| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +LossNotification::LossNotification() + : last_decoded_(0), last_received_(0), decodability_flag_(false) {} + +LossNotification::LossNotification(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag) + : last_decoded_(last_decoded), + last_received_(last_received), + decodability_flag_(decodability_flag) {} + +LossNotification::LossNotification(const LossNotification& rhs) = default; + +LossNotification::~LossNotification() = default; + +size_t LossNotification::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + kLossNotificationPayloadLength; +} + +bool LossNotification::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + const size_t index_end = *index + BlockLength(); + + // Note: `index` updated by the function below. + CreateHeader(Psfb::kAfbMessageType, kPacketType, HeaderLength(), packet, + index); + + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, kUniqueIdentifier); + *index += sizeof(uint32_t); + + ByteWriter<uint16_t>::WriteBigEndian(packet + *index, last_decoded_); + *index += sizeof(uint16_t); + + const uint16_t last_received_delta = last_received_ - last_decoded_; + RTC_DCHECK_LE(last_received_delta, 0x7fff); + const uint16_t last_received_delta_and_decodability = + (last_received_delta << 1) | (decodability_flag_ ? 0x0001 : 0x0000); + + ByteWriter<uint16_t>::WriteBigEndian(packet + *index, + last_received_delta_and_decodability); + *index += sizeof(uint16_t); + + RTC_DCHECK_EQ(index_end, *index); + return true; +} + +bool LossNotification::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), Psfb::kAfbMessageType); + + if (packet.payload_size_bytes() < + kCommonFeedbackLength + kLossNotificationPayloadLength) { + return false; + } + + const uint8_t* const payload = packet.payload(); + + if (ByteReader<uint32_t>::ReadBigEndian(&payload[8]) != kUniqueIdentifier) { + return false; + } + + ParseCommonFeedback(payload); + + last_decoded_ = ByteReader<uint16_t>::ReadBigEndian(&payload[12]); + + const uint16_t last_received_delta_and_decodability = + ByteReader<uint16_t>::ReadBigEndian(&payload[14]); + last_received_ = last_decoded_ + (last_received_delta_and_decodability >> 1); + decodability_flag_ = (last_received_delta_and_decodability & 0x0001); + + return true; +} + +bool LossNotification::Set(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag) { + const uint16_t delta = last_received - last_decoded; + if (delta > 0x7fff) { + return false; + } + last_received_ = last_received; + last_decoded_ = last_decoded; + decodability_flag_ = decodability_flag; + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h new file mode 100644 index 0000000000..0f70cf75c3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h @@ -0,0 +1,82 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ + +#include "absl/base/attributes.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { + +class LossNotification : public Psfb { + public: + LossNotification(); + LossNotification(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag); + LossNotification(const LossNotification& other); + ~LossNotification() override; + + size_t BlockLength() const override; + + ABSL_MUST_USE_RESULT + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + // Parse assumes header is already parsed and validated. + ABSL_MUST_USE_RESULT + bool Parse(const CommonHeader& packet); + + // Set all of the values transmitted by the loss notification message. + // If the values may not be represented by a loss notification message, + // false is returned, and no change is made to the object; this happens + // when `last_received` is ahead of `last_decoded` by more than 0x7fff. + // This is because `last_received` is represented on the wire as a delta, + // and only 15 bits are available for that delta. + ABSL_MUST_USE_RESULT + bool Set(uint16_t last_decoded, + uint16_t last_received, + bool decodability_flag); + + // RTP sequence number of the first packet belong to the last decoded + // non-discardable frame. + uint16_t last_decoded() const { return last_decoded_; } + + // RTP sequence number of the last received packet. + uint16_t last_received() const { return last_received_; } + + // A decodability flag, whose specific meaning depends on the last-received + // RTP sequence number. The decodability flag is true if and only if all of + // the frame's dependencies are known to be decodable, and the frame itself + // is not yet known to be unassemblable. + // * Clarification #1: In a multi-packet frame, the first packet's + // dependencies are known, but it is not yet known whether all parts + // of the current frame will be received. + // * Clarification #2: In a multi-packet frame, the dependencies would be + // unknown if the first packet was not received. Then, the packet will + // be known-unassemblable. + bool decodability_flag() const { return decodability_flag_; } + + private: + static constexpr uint32_t kUniqueIdentifier = 0x4C4E5446; // 'L' 'N' 'T' 'F'. + static constexpr size_t kLossNotificationPayloadLength = 8; + + uint16_t last_decoded_; + uint16_t last_received_; + bool decodability_flag_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_LOSS_NOTIFICATION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc new file mode 100644 index 0000000000..c38e7f4438 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc @@ -0,0 +1,136 @@ +/* + * 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/rtcp_packet/loss_notification.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using ::webrtc::rtcp::LossNotification; + +TEST(RtcpPacketLossNotificationTest, SetWithIllegalValuesFails) { + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + 0x7fff + 1; + constexpr bool kDecodabilityFlag = true; + LossNotification loss_notification; + EXPECT_FALSE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); +} + +TEST(RtcpPacketLossNotificationTest, SetWithLegalValuesSucceeds) { + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + 0x7fff; + constexpr bool kDecodabilityFlag = true; + LossNotification loss_notification; + EXPECT_TRUE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); +} + +TEST(RtcpPacketLossNotificationTest, CreateProducesExpectedWireFormat) { + // Note that (0x6542 >> 1) is used just to make the pattern in kPacket + // more apparent; there's nothing truly special about the value, + // it's only an implementation detail that last-received is represented + // as a delta from last-decoded, and that this delta is shifted before + // it's put on the wire. + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + (0x6542 >> 1); + constexpr bool kDecodabilityFlag = true; + + const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + loss_notification.SetSenderSsrc(0x12345678); + loss_notification.SetMediaSsrc(0xabcdef01); + ASSERT_TRUE( + loss_notification.Set(kLastDecoded, kLastReceived, kDecodabilityFlag)); + + rtc::Buffer packet = loss_notification.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseFailsOnTooSmallPacketToBeLossNotification) { + uint8_t packet[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + size_t packet_length_bytes = sizeof(packet); + + LossNotification loss_notification; + + // First, prove that the failure we're expecting to see happens because of + // the length, by showing that before the modification to the length, + // the packet was correctly parsed. + ASSERT_TRUE( + test::ParseSinglePacket(packet, packet_length_bytes, &loss_notification)); + + // Show that after shaving off a word, the packet is no longer parsable. + packet[3] -= 1; // Change the `length` field of the RTCP packet. + packet_length_bytes -= 4; // Effectively forget the last 32-bit word. + EXPECT_FALSE( + test::ParseSinglePacket(packet, packet_length_bytes, &loss_notification)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseFailsWhenUniqueIdentifierIsNotLossNotification) { + uint8_t packet[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + + // First, prove that the failure we're expecting to see happens because of + // the identifier, by showing that before the modification to the identifier, + // the packet was correctly parsed. + ASSERT_TRUE(test::ParseSinglePacket(packet, &loss_notification)); + + // Show that after changing the identifier, the packet is no longer parsable. + RTC_DCHECK_EQ(packet[12], 'L'); + RTC_DCHECK_EQ(packet[13], 'N'); + RTC_DCHECK_EQ(packet[14], 'T'); + RTC_DCHECK_EQ(packet[15], 'F'); + packet[14] = 'x'; + EXPECT_FALSE(test::ParseSinglePacket(packet, &loss_notification)); +} + +TEST(RtcpPacketLossNotificationTest, + ParseLegalLossNotificationMessagesCorrectly) { + // Note that (0x6542 >> 1) is used just to make the pattern in kPacket + // more apparent; there's nothing truly special about the value, + // it's only an implementation detail that last-received is represented + // as a delta from last-decoded, and that this delta is shifted before + // it's put on the wire. + constexpr uint16_t kLastDecoded = 0x3c7b; + constexpr uint16_t kLastReceived = kLastDecoded + (0x6542 >> 1); + constexpr bool kDecodabilityFlag = true; + + const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x04, 0x12, 0x34, 0x56, 0x78, // + 0xab, 0xcd, 0xef, 0x01, 'L', 'N', 'T', 'F', // + 0x3c, 0x7b, 0x65, 0x43}; + + LossNotification loss_notification; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &loss_notification)); + + EXPECT_EQ(loss_notification.sender_ssrc(), 0x12345678u); + EXPECT_EQ(loss_notification.media_ssrc(), 0xabcdef01u); + EXPECT_EQ(loss_notification.last_decoded(), kLastDecoded); + EXPECT_EQ(loss_notification.last_received(), kLastReceived); + EXPECT_EQ(loss_notification.decodability_flag(), kDecodabilityFlag); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc new file mode 100644 index 0000000000..6fe7eade62 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc @@ -0,0 +1,176 @@ +/* + * 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/nack.h" + +#include <algorithm> +#include <cstdint> +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Nack::kFeedbackMessageType; +constexpr size_t Nack::kNackItemLength; +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// +// Generic NACK (RFC 4585). +// +// FCI: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | PID | BLP | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Nack::Nack() = default; +Nack::Nack(const Nack& rhs) = default; +Nack::~Nack() = default; + +bool Nack::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength + kNackItemLength) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for a Nack."; + return false; + } + size_t nack_items = + (packet.payload_size_bytes() - kCommonFeedbackLength) / kNackItemLength; + + ParseCommonFeedback(packet.payload()); + const uint8_t* next_nack = packet.payload() + kCommonFeedbackLength; + + packet_ids_.clear(); + packed_.resize(nack_items); + for (size_t index = 0; index < nack_items; ++index) { + packed_[index].first_pid = ByteReader<uint16_t>::ReadBigEndian(next_nack); + packed_[index].bitmask = ByteReader<uint16_t>::ReadBigEndian(next_nack + 2); + next_nack += kNackItemLength; + } + Unpack(); + + return true; +} + +size_t Nack::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + + packed_.size() * kNackItemLength; +} + +bool Nack::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + RTC_DCHECK(!packed_.empty()); + // If nack list can't fit in packet, try to fragment. + constexpr size_t kNackHeaderLength = kHeaderLength + kCommonFeedbackLength; + for (size_t nack_index = 0; nack_index < packed_.size();) { + size_t bytes_left_in_buffer = max_length - *index; + if (bytes_left_in_buffer < kNackHeaderLength + kNackItemLength) { + if (!OnBufferFull(packet, index, callback)) + return false; + continue; + } + size_t num_nack_fields = + std::min((bytes_left_in_buffer - kNackHeaderLength) / kNackItemLength, + packed_.size() - nack_index); + + size_t payload_size_bytes = + kCommonFeedbackLength + (num_nack_fields * kNackItemLength); + size_t payload_size_32bits = + rtc::CheckedDivExact<size_t>(payload_size_bytes, 4); + CreateHeader(kFeedbackMessageType, kPacketType, payload_size_32bits, packet, + index); + + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + size_t nack_end_index = nack_index + num_nack_fields; + for (; nack_index < nack_end_index; ++nack_index) { + const PackedNack& item = packed_[nack_index]; + ByteWriter<uint16_t>::WriteBigEndian(packet + *index + 0, item.first_pid); + ByteWriter<uint16_t>::WriteBigEndian(packet + *index + 2, item.bitmask); + *index += kNackItemLength; + } + RTC_DCHECK_LE(*index, max_length); + } + + return true; +} + +void Nack::SetPacketIds(const uint16_t* nack_list, size_t length) { + RTC_DCHECK(nack_list); + SetPacketIds(std::vector<uint16_t>(nack_list, nack_list + length)); +} + +void Nack::SetPacketIds(std::vector<uint16_t> nack_list) { + RTC_DCHECK(packet_ids_.empty()); + RTC_DCHECK(packed_.empty()); + packet_ids_ = std::move(nack_list); + Pack(); +} + +void Nack::Pack() { + RTC_DCHECK(!packet_ids_.empty()); + RTC_DCHECK(packed_.empty()); + auto it = packet_ids_.begin(); + const auto end = packet_ids_.end(); + while (it != end) { + PackedNack item; + item.first_pid = *it++; + // Bitmask specifies losses in any of the 16 packets following the pid. + item.bitmask = 0; + while (it != end) { + uint16_t shift = static_cast<uint16_t>(*it - item.first_pid - 1); + if (shift <= 15) { + item.bitmask |= (1 << shift); + ++it; + } else { + break; + } + } + packed_.push_back(item); + } +} + +void Nack::Unpack() { + RTC_DCHECK(packet_ids_.empty()); + RTC_DCHECK(!packed_.empty()); + for (const PackedNack& item : packed_) { + packet_ids_.push_back(item.first_pid); + uint16_t pid = item.first_pid + 1; + for (uint16_t bitmask = item.bitmask; bitmask != 0; bitmask >>= 1, ++pid) { + if (bitmask & 1) + packet_ids_.push_back(pid); + } + } +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h new file mode 100644 index 0000000000..9153733fb9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h @@ -0,0 +1,59 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_NACK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_NACK_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class Nack : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 1; + Nack(); + Nack(const Nack&); + ~Nack() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetPacketIds(const uint16_t* nack_list, size_t length); + void SetPacketIds(std::vector<uint16_t> nack_list); + const std::vector<uint16_t>& packet_ids() const { return packet_ids_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kNackItemLength = 4; + struct PackedNack { + uint16_t first_pid; + uint16_t bitmask; + }; + + void Pack(); // Fills packed_ using packed_ids_. (used in SetPacketIds). + void Unpack(); // Fills packet_ids_ using packed_. (used in Parse). + + std::vector<PackedNack> packed_; + std::vector<uint16_t> packet_ids_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_NACK_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc new file mode 100644 index 0000000000..aabae0dc48 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc @@ -0,0 +1,171 @@ +/* + * 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/nack.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Invoke; +using ::testing::make_tuple; +using ::testing::MockFunction; +using ::testing::UnorderedElementsAreArray; +using ::webrtc::rtcp::Nack; + +constexpr uint32_t kSenderSsrc = 0x12345678; +constexpr uint32_t kRemoteSsrc = 0x23456789; + +constexpr uint16_t kList[] = {0, 1, 3, 8, 16}; +constexpr size_t kListLength = sizeof(kList) / sizeof(kList[0]); +constexpr uint8_t kVersionBits = 2 << 6; +// clang-format off +constexpr uint8_t kPacket[] = { + kVersionBits | Nack::kFeedbackMessageType, Nack::kPacketType, 0, 3, + 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89, + 0x00, 0x00, 0x80, 0x85}; + +constexpr uint16_t kWrapList[] = {0xffdc, 0xffec, 0xfffe, 0xffff, 0x0000, + 0x0001, 0x0003, 0x0014, 0x0064}; +constexpr size_t kWrapListLength = sizeof(kWrapList) / sizeof(kWrapList[0]); +constexpr uint8_t kWrapPacket[] = { + kVersionBits | Nack::kFeedbackMessageType, Nack::kPacketType, 0, 6, + 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89, + 0xff, 0xdc, 0x80, 0x00, + 0xff, 0xfe, 0x00, 0x17, + 0x00, 0x14, 0x00, 0x00, + 0x00, 0x64, 0x00, 0x00}; +constexpr uint8_t kTooSmallPacket[] = { + kVersionBits | Nack::kFeedbackMessageType, Nack::kPacketType, 0, 2, + 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89}; +// clang-format on +} // namespace + +TEST(RtcpPacketNackTest, Create) { + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kList, kListLength); + + rtc::Buffer packet = nack.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketNackTest, Parse) { + Nack parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &parsed)); + const Nack& const_parsed = parsed; + + EXPECT_EQ(kSenderSsrc, const_parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, const_parsed.media_ssrc()); + EXPECT_THAT(const_parsed.packet_ids(), ElementsAreArray(kList)); +} + +TEST(RtcpPacketNackTest, CreateWrap) { + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kWrapList, kWrapListLength); + + rtc::Buffer packet = nack.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kWrapPacket)); +} + +TEST(RtcpPacketNackTest, ParseWrap) { + Nack parsed; + EXPECT_TRUE(test::ParseSinglePacket(kWrapPacket, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); + EXPECT_THAT(parsed.packet_ids(), ElementsAreArray(kWrapList)); +} + +TEST(RtcpPacketNackTest, BadOrder) { + // Does not guarantee optimal packing, but should guarantee correctness. + const uint16_t kUnorderedList[] = {1, 25, 13, 12, 9, 27, 29}; + const size_t kUnorderedListLength = + sizeof(kUnorderedList) / sizeof(kUnorderedList[0]); + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kUnorderedList, kUnorderedListLength); + + rtc::Buffer packet = nack.Build(); + + Nack parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); + EXPECT_THAT(parsed.packet_ids(), UnorderedElementsAreArray(kUnorderedList)); +} + +TEST(RtcpPacketNackTest, CreateFragmented) { + Nack nack; + const uint16_t kList[] = {1, 100, 200, 300, 400}; + const uint16_t kListLength = sizeof(kList) / sizeof(kList[0]); + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kList, kListLength); + + const size_t kBufferSize = 12 + (3 * 4); // Fits common header + 3 nack items + + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + Nack nack; + EXPECT_TRUE(test::ParseSinglePacket(packet, &nack)); + EXPECT_EQ(kSenderSsrc, nack.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, nack.media_ssrc()); + EXPECT_THAT(nack.packet_ids(), ElementsAre(1, 100, 200)); + })) + .WillOnce(Invoke([&](rtc::ArrayView<const uint8_t> packet) { + Nack nack; + EXPECT_TRUE(test::ParseSinglePacket(packet, &nack)); + EXPECT_EQ(kSenderSsrc, nack.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, nack.media_ssrc()); + EXPECT_THAT(nack.packet_ids(), ElementsAre(300, 400)); + })); + + EXPECT_TRUE(nack.Build(kBufferSize, callback.AsStdFunction())); +} + +TEST(RtcpPacketNackTest, CreateFailsWithTooSmallBuffer) { + const uint16_t kList[] = {1}; + const size_t kMinNackBlockSize = 16; + Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kRemoteSsrc); + nack.SetPacketIds(kList, 1); + + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)).Times(0); + EXPECT_FALSE(nack.Build(kMinNackBlockSize - 1, callback.AsStdFunction())); +} + +TEST(RtcpPacketNackTest, ParseFailsWithTooSmallBuffer) { + Nack parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc new file mode 100644 index 0000000000..5b41aa5c2c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc @@ -0,0 +1,79 @@ +/* + * 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/pli.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Pli::kFeedbackMessageType; +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : + +Pli::Pli() = default; + +Pli::Pli(const Pli& pli) = default; + +Pli::~Pli() = default; + +// +// Picture loss indication (PLI) (RFC 4585). +// FCI: no feedback control information. +bool Pli::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to be a valid PLI packet"; + return false; + } + + ParseCommonFeedback(packet.payload()); + return true; +} + +size_t Pli::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength; +} + +bool Pli::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h new file mode 100644 index 0000000000..b9b9c45a9c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h @@ -0,0 +1,39 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PLI_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PLI_H_ + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; +// Picture loss indication (PLI) (RFC 4585). +class Pli : public Psfb { + public: + static constexpr uint8_t kFeedbackMessageType = 1; + + Pli(); + Pli(const Pli& pli); + ~Pli() override; + + bool Parse(const CommonHeader& packet); + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PLI_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc new file mode 100644 index 0000000000..c971e22bc1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc @@ -0,0 +1,58 @@ +/* + * 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/pli.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::Pli; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +// Manually created Pli packet matching constants above. +const uint8_t kPacket[] = {0x81, 206, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; +} // namespace + +TEST(RtcpPacketPliTest, Parse) { + Pli mutable_parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &mutable_parsed)); + const Pli& parsed = mutable_parsed; // Read values from constant object. + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); +} + +TEST(RtcpPacketPliTest, Create) { + Pli pli; + pli.SetSenderSsrc(kSenderSsrc); + pli.SetMediaSsrc(kRemoteSsrc); + + rtc::Buffer packet = pli.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketPliTest, ParseFailsOnTooSmallPacket) { + const uint8_t kTooSmallPacket[] = {0x81, 206, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; + + Pli parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc new file mode 100644 index 0000000000..384d8ba811 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc @@ -0,0 +1,47 @@ +/* + * 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/psfb.h" + +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Psfb::kPacketType; +constexpr uint8_t Psfb::kAfbMessageType; +constexpr size_t Psfb::kCommonFeedbackLength; +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : + +void Psfb::ParseCommonFeedback(const uint8_t* payload) { + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0])); + SetMediaSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[4])); +} + +void Psfb::CreateCommonFeedback(uint8_t* payload) const { + ByteWriter<uint32_t>::WriteBigEndian(&payload[0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&payload[4], media_ssrc()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h new file mode 100644 index 0000000000..d6b8bca7c4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h @@ -0,0 +1,48 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PSFB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PSFB_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { + +// PSFB: Payload-specific feedback message. +// RFC 4585, Section 6.3. +class Psfb : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 206; + static constexpr uint8_t kAfbMessageType = 15; + + Psfb() = default; + ~Psfb() override = default; + + void SetMediaSsrc(uint32_t ssrc) { media_ssrc_ = ssrc; } + + uint32_t media_ssrc() const { return media_ssrc_; } + + protected: + static constexpr size_t kCommonFeedbackLength = 8; + void ParseCommonFeedback(const uint8_t* payload); + void CreateCommonFeedback(uint8_t* payload) const; + + private: + uint32_t media_ssrc_ = 0; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_PSFB_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc new file mode 100644 index 0000000000..8563c28373 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc @@ -0,0 +1,68 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t RapidResyncRequest::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Rapid Resynchronisation Request (draft-perkins-avt-rapid-rtp-sync-03). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=5 | PT=205 | length=2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +bool RapidResyncRequest::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() != kCommonFeedbackLength) { + RTC_LOG(LS_WARNING) << "Packet payload size should be " + << kCommonFeedbackLength << " instead of " + << packet.payload_size_bytes() + << " to be a valid Rapid Resynchronisation Request"; + return false; + } + + ParseCommonFeedback(packet.payload()); + return true; +} + +size_t RapidResyncRequest::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength; +} + +bool RapidResyncRequest::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h new file mode 100644 index 0000000000..1955b98f5c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RAPID_RESYNC_REQUEST_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RAPID_RESYNC_REQUEST_H_ + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// draft-perkins-avt-rapid-rtp-sync-03 +class RapidResyncRequest : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 5; + + RapidResyncRequest() {} + ~RapidResyncRequest() override {} + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& header); + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RAPID_RESYNC_REQUEST_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc new file mode 100644 index 0000000000..d0e40fd83d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc @@ -0,0 +1,64 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::RapidResyncRequest; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +// Manually created packet matching constants above. +const uint8_t kPacket[] = {0x85, 205, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; +} // namespace + +TEST(RtcpPacketRapidResyncRequestTest, Parse) { + RapidResyncRequest mutable_parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &mutable_parsed)); + const RapidResyncRequest& parsed = mutable_parsed; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parsed.media_ssrc()); +} + +TEST(RtcpPacketRapidResyncRequestTest, Create) { + RapidResyncRequest rrr; + rrr.SetSenderSsrc(kSenderSsrc); + rrr.SetMediaSsrc(kRemoteSsrc); + + rtc::Buffer packet = rrr.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketRapidResyncRequestTest, ParseFailsOnTooSmallPacket) { + const uint8_t kTooSmallPacket[] = {0x85, 205, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; + RapidResyncRequest parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooSmallPacket, &parsed)); +} + +TEST(RtcpPacketRapidResyncRequestTest, ParseFailsOnTooLargePacket) { + const uint8_t kTooLargePacket[] = {0x85, 205, 0x00, 0x03, 0x12, 0x34, + 0x56, 0x78, 0x32, 0x21, 0x65, 0x87, + 0x23, 0x45, 0x67, 0x89}; + RapidResyncRequest parsed; + EXPECT_FALSE(test::ParseSinglePacket(kTooLargePacket, &parsed)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc new file mode 100644 index 0000000000..185011dff1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc @@ -0,0 +1,112 @@ +/* + * 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/receiver_report.h" + +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t ReceiverReport::kPacketType; +constexpr size_t ReceiverReport::kMaxNumberOfReportBlocks; +// RTCP receiver report (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| RC | PT=RR=201 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | report block(s) | +// | .... | + +ReceiverReport::ReceiverReport() = default; + +ReceiverReport::ReceiverReport(const ReceiverReport& rhs) = default; + +ReceiverReport::~ReceiverReport() = default; + +bool ReceiverReport::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + const uint8_t report_blocks_count = packet.count(); + + if (packet.payload_size_bytes() < + kRrBaseLength + report_blocks_count * ReportBlock::kLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to contain all the data."; + return false; + } + + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(packet.payload())); + + const uint8_t* next_report_block = packet.payload() + kRrBaseLength; + + report_blocks_.resize(report_blocks_count); + for (ReportBlock& block : report_blocks_) { + block.Parse(next_report_block, ReportBlock::kLength); + next_report_block += ReportBlock::kLength; + } + + RTC_DCHECK_LE(next_report_block - packet.payload(), + static_cast<ptrdiff_t>(packet.payload_size_bytes())); + return true; +} + +size_t ReceiverReport::BlockLength() const { + return kHeaderLength + kRrBaseLength + + report_blocks_.size() * ReportBlock::kLength; +} + +bool ReceiverReport::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + CreateHeader(report_blocks_.size(), kPacketType, HeaderLength(), packet, + index); + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, sender_ssrc()); + *index += kRrBaseLength; + for (const ReportBlock& block : report_blocks_) { + block.Create(packet + *index); + *index += ReportBlock::kLength; + } + return true; +} + +bool ReceiverReport::AddReportBlock(const ReportBlock& block) { + if (report_blocks_.size() >= kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Max report blocks reached."; + return false; + } + report_blocks_.push_back(block); + return true; +} + +bool ReceiverReport::SetReportBlocks(std::vector<ReportBlock> blocks) { + if (blocks.size() > kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Too many report blocks (" << blocks.size() + << ") for receiver report."; + return false; + } + report_blocks_ = std::move(blocks); + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h new file mode 100644 index 0000000000..b9c1c466c7 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h @@ -0,0 +1,60 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RECEIVER_REPORT_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RECEIVER_REPORT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class ReceiverReport : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 201; + static constexpr size_t kMaxNumberOfReportBlocks = 0x1f; + + ReceiverReport(); + ReceiverReport(const ReceiverReport&); + ~ReceiverReport() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool AddReportBlock(const ReportBlock& block); + bool SetReportBlocks(std::vector<ReportBlock> blocks); + + const std::vector<ReportBlock>& report_blocks() const { + return report_blocks_; + } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static const size_t kRrBaseLength = 4; + + std::vector<ReportBlock> report_blocks_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RECEIVER_REPORT_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc new file mode 100644 index 0000000000..23ea49622b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc @@ -0,0 +1,161 @@ +/* + * 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/receiver_report.h" + +#include <utility> + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::make_tuple; +using webrtc::rtcp::ReceiverReport; +using webrtc::rtcp::ReportBlock; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint8_t kFractionLost = 55; +const int32_t kCumulativeLost = 0x111213; +const uint32_t kExtHighestSeqNum = 0x22232425; +const uint32_t kJitter = 0x33343536; +const uint32_t kLastSr = 0x44454647; +const uint32_t kDelayLastSr = 0x55565758; +// Manually created ReceiverReport with one ReportBlock matching constants +// above. +// Having this block allows to test Create and Parse separately. +const uint8_t kPacket[] = {0x81, 201, 0x00, 0x07, 0x12, 0x34, 0x56, 0x78, + 0x23, 0x45, 0x67, 0x89, 55, 0x11, 0x12, 0x13, + 0x22, 0x23, 0x24, 0x25, 0x33, 0x34, 0x35, 0x36, + 0x44, 0x45, 0x46, 0x47, 0x55, 0x56, 0x57, 0x58}; +} // namespace + +TEST(RtcpPacketReceiverReportTest, ParseWithOneReportBlock) { + ReceiverReport rr; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &rr)); + const ReceiverReport& parsed = rr; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(1u, parsed.report_blocks().size()); + const ReportBlock& rb = parsed.report_blocks().front(); + EXPECT_EQ(kRemoteSsrc, rb.source_ssrc()); + EXPECT_EQ(kFractionLost, rb.fraction_lost()); + EXPECT_EQ(kCumulativeLost, rb.cumulative_lost_signed()); + EXPECT_EQ(kExtHighestSeqNum, rb.extended_high_seq_num()); + EXPECT_EQ(kJitter, rb.jitter()); + EXPECT_EQ(kLastSr, rb.last_sr()); + EXPECT_EQ(kDelayLastSr, rb.delay_since_last_sr()); +} + +TEST(RtcpPacketReceiverReportTest, ParseFailsOnIncorrectSize) { + rtc::Buffer damaged_packet(kPacket); + damaged_packet[0]++; // Damage the packet: increase count field. + ReceiverReport rr; + EXPECT_FALSE(test::ParseSinglePacket(damaged_packet, &rr)); +} + +TEST(RtcpPacketReceiverReportTest, CreateWithOneReportBlock) { + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + ReportBlock rb; + rb.SetMediaSsrc(kRemoteSsrc); + rb.SetFractionLost(kFractionLost); + rb.SetCumulativeLost(kCumulativeLost); + rb.SetExtHighestSeqNum(kExtHighestSeqNum); + rb.SetJitter(kJitter); + rb.SetLastSr(kLastSr); + rb.SetDelayLastSr(kDelayLastSr); + rr.AddReportBlock(rb); + + rtc::Buffer raw = rr.Build(); + + EXPECT_THAT(make_tuple(raw.data(), raw.size()), ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketReceiverReportTest, CreateAndParseWithoutReportBlocks) { + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer raw = rr.Build(); + ReceiverReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.report_blocks(), IsEmpty()); +} + +TEST(RtcpPacketReceiverReportTest, CreateAndParseWithTwoReportBlocks) { + ReceiverReport rr; + ReportBlock rb1; + rb1.SetMediaSsrc(kRemoteSsrc); + ReportBlock rb2; + rb2.SetMediaSsrc(kRemoteSsrc + 1); + + rr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr.AddReportBlock(rb1)); + EXPECT_TRUE(rr.AddReportBlock(rb2)); + + rtc::Buffer raw = rr.Build(); + ReceiverReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.report_blocks().size()); + EXPECT_EQ(kRemoteSsrc, parsed.report_blocks()[0].source_ssrc()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.report_blocks()[1].source_ssrc()); +} + +TEST(RtcpPacketReceiverReportTest, CreateWithTooManyReportBlocks) { + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + ReportBlock rb; + for (size_t i = 0; i < ReceiverReport::kMaxNumberOfReportBlocks; ++i) { + rb.SetMediaSsrc(kRemoteSsrc + i); + EXPECT_TRUE(rr.AddReportBlock(rb)); + } + rb.SetMediaSsrc(kRemoteSsrc + ReceiverReport::kMaxNumberOfReportBlocks); + EXPECT_FALSE(rr.AddReportBlock(rb)); +} + +TEST(RtcpPacketReceiverReportTest, SetReportBlocksOverwritesOldBlocks) { + ReceiverReport rr; + ReportBlock report_block; + // Use jitter field of the report blocks to distinguish them. + report_block.SetJitter(1001u); + rr.AddReportBlock(report_block); + ASSERT_EQ(rr.report_blocks().size(), 1u); + ASSERT_EQ(rr.report_blocks()[0].jitter(), 1001u); + + std::vector<ReportBlock> blocks(3u); + blocks[0].SetJitter(2001u); + blocks[1].SetJitter(3001u); + blocks[2].SetJitter(4001u); + EXPECT_TRUE(rr.SetReportBlocks(blocks)); + ASSERT_EQ(rr.report_blocks().size(), 3u); + EXPECT_EQ(rr.report_blocks()[0].jitter(), 2001u); + EXPECT_EQ(rr.report_blocks()[1].jitter(), 3001u); + EXPECT_EQ(rr.report_blocks()[2].jitter(), 4001u); +} + +TEST(RtcpPacketReceiverReportTest, SetReportBlocksMaxLimit) { + ReceiverReport rr; + std::vector<ReportBlock> max_blocks(ReceiverReport::kMaxNumberOfReportBlocks); + EXPECT_TRUE(rr.SetReportBlocks(std::move(max_blocks))); + + std::vector<ReportBlock> one_too_many_blocks( + ReceiverReport::kMaxNumberOfReportBlocks + 1); + EXPECT_FALSE(rr.SetReportBlocks(std::move(one_too_many_blocks))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc new file mode 100644 index 0000000000..1389ca7836 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc @@ -0,0 +1,143 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/remb.h" + +#include <cstdint> +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=206 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | Unused = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | Unique identifier 'R' 'E' 'M' 'B' | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | Num SSRC | BR Exp | BR Mantissa | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | SSRC feedback | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : + +Remb::Remb() : bitrate_bps_(0) {} + +Remb::Remb(const Remb& rhs) = default; + +Remb::~Remb() = default; + +bool Remb::Parse(const CommonHeader& packet) { + RTC_DCHECK(packet.type() == kPacketType); + RTC_DCHECK_EQ(packet.fmt(), Psfb::kAfbMessageType); + + if (packet.payload_size_bytes() < 16) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for Remb packet."; + return false; + } + const uint8_t* const payload = packet.payload(); + if (kUniqueIdentifier != ByteReader<uint32_t>::ReadBigEndian(&payload[8])) { + return false; + } + uint8_t number_of_ssrcs = payload[12]; + if (packet.payload_size_bytes() != + kCommonFeedbackLength + (2 + number_of_ssrcs) * 4) { + RTC_LOG(LS_WARNING) << "Payload size " << packet.payload_size_bytes() + << " does not match " << number_of_ssrcs << " ssrcs."; + return false; + } + + ParseCommonFeedback(payload); + uint8_t exponent = payload[13] >> 2; + uint64_t mantissa = (static_cast<uint32_t>(payload[13] & 0x03) << 16) | + ByteReader<uint16_t>::ReadBigEndian(&payload[14]); + bitrate_bps_ = (mantissa << exponent); + bool shift_overflow = + (static_cast<uint64_t>(bitrate_bps_) >> exponent) != mantissa; + if (bitrate_bps_ < 0 || shift_overflow) { + RTC_LOG(LS_ERROR) << "Invalid remb bitrate value : " << mantissa << "*2^" + << static_cast<int>(exponent); + return false; + } + + const uint8_t* next_ssrc = payload + 16; + ssrcs_.clear(); + ssrcs_.reserve(number_of_ssrcs); + for (uint8_t i = 0; i < number_of_ssrcs; ++i) { + ssrcs_.push_back(ByteReader<uint32_t>::ReadBigEndian(next_ssrc)); + next_ssrc += sizeof(uint32_t); + } + + return true; +} + +bool Remb::SetSsrcs(std::vector<uint32_t> ssrcs) { + if (ssrcs.size() > kMaxNumberOfSsrcs) { + RTC_LOG(LS_WARNING) << "Not enough space for all given SSRCs."; + return false; + } + ssrcs_ = std::move(ssrcs); + return true; +} + +size_t Remb::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + (2 + ssrcs_.size()) * 4; +} + +bool Remb::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + size_t index_end = *index + BlockLength(); + CreateHeader(Psfb::kAfbMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(0, Psfb::media_ssrc()); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, kUniqueIdentifier); + *index += sizeof(uint32_t); + const uint32_t kMaxMantissa = 0x3ffff; // 18 bits. + uint64_t mantissa = bitrate_bps_; + uint8_t exponenta = 0; + while (mantissa > kMaxMantissa) { + mantissa >>= 1; + ++exponenta; + } + packet[(*index)++] = static_cast<uint8_t>(ssrcs_.size()); + packet[(*index)++] = (exponenta << 2) | (mantissa >> 16); + ByteWriter<uint16_t>::WriteBigEndian(packet + *index, mantissa & 0xffff); + *index += sizeof(uint16_t); + + for (uint32_t ssrc : ssrcs_) { + ByteWriter<uint32_t>::WriteBigEndian(packet + *index, ssrc); + *index += sizeof(uint32_t); + } + RTC_DCHECK_EQ(index_end, *index); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h new file mode 100644 index 0000000000..b7075c0f23 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h @@ -0,0 +1,59 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/psfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// Receiver Estimated Max Bitrate (REMB) (draft-alvestrand-rmcat-remb). +class Remb : public Psfb { + public: + static constexpr size_t kMaxNumberOfSsrcs = 0xff; + + Remb(); + Remb(const Remb&); + ~Remb() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool SetSsrcs(std::vector<uint32_t> ssrcs); + void SetBitrateBps(int64_t bitrate_bps) { bitrate_bps_ = bitrate_bps; } + + int64_t bitrate_bps() const { return bitrate_bps_; } + const std::vector<uint32_t>& ssrcs() const { return ssrcs_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr uint32_t kUniqueIdentifier = 0x52454D42; // 'R' 'E' 'M' 'B'. + + // Media ssrc is unused, shadow base class setter and getter. + void SetMediaSsrc(uint32_t); + uint32_t media_ssrc() const; + + int64_t bitrate_bps_; + std::vector<uint32_t> ssrcs_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMB_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc new file mode 100644 index 0000000000..c439d9c5f6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc @@ -0,0 +1,141 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/remb.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::make_tuple; +using webrtc::rtcp::Remb; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrcs[] = {0x23456789, 0x2345678a, 0x2345678b}; +const uint32_t kBitrateBps = 0x3fb93 * 2; // 522022; +const int64_t kBitrateBps64bit = int64_t{0x3fb93} << 30; +const uint8_t kPacket[] = {0x8f, 206, 0x00, 0x07, 0x12, 0x34, 0x56, 0x78, + 0x00, 0x00, 0x00, 0x00, 'R', 'E', 'M', 'B', + 0x03, 0x07, 0xfb, 0x93, 0x23, 0x45, 0x67, 0x89, + 0x23, 0x45, 0x67, 0x8a, 0x23, 0x45, 0x67, 0x8b}; +const size_t kPacketLength = sizeof(kPacket); +} // namespace + +TEST(RtcpPacketRembTest, Create) { + Remb remb; + remb.SetSenderSsrc(kSenderSsrc); + remb.SetSsrcs( + std::vector<uint32_t>(std::begin(kRemoteSsrcs), std::end(kRemoteSsrcs))); + remb.SetBitrateBps(kBitrateBps); + + rtc::Buffer packet = remb.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketRembTest, Parse) { + Remb remb; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &remb)); + const Remb& parsed = remb; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kBitrateBps, parsed.bitrate_bps()); + EXPECT_THAT(parsed.ssrcs(), ElementsAreArray(kRemoteSsrcs)); +} + +TEST(RtcpPacketRembTest, CreateAndParseWithoutSsrcs) { + Remb remb; + remb.SetSenderSsrc(kSenderSsrc); + remb.SetBitrateBps(kBitrateBps); + rtc::Buffer packet = remb.Build(); + + Remb parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kBitrateBps, parsed.bitrate_bps()); + EXPECT_THAT(parsed.ssrcs(), IsEmpty()); +} + +TEST(RtcpPacketRembTest, CreateAndParse64bitBitrate) { + Remb remb; + remb.SetBitrateBps(kBitrateBps64bit); + rtc::Buffer packet = remb.Build(); + + Remb parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + EXPECT_EQ(kBitrateBps64bit, parsed.bitrate_bps()); +} + +TEST(RtcpPacketRembTest, ParseFailsOnTooSmallPacketToBeRemb) { + // Make it too small. + constexpr size_t kTooSmallSize = (1 + 3) * 4; + uint8_t packet[kTooSmallSize]; + memcpy(packet, kPacket, kTooSmallSize); + packet[3] = 3; + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenUniqueIdentifierIsNotRemb) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[12] = 'N'; // Swap 'R' -> 'N' in the 'REMB' unique identifier. + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenBitrateDoNotFitIn64bits) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[17] |= 0xfc; // Set exponent component to maximum of 63. + packet[19] |= 0x02; // Ensure mantissa is at least 2. + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenBitrateDoNotFitIn63bits) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[17] = 56 << 2; // Set exponent component to 56. + packet[18] = 0; // Set mantissa to 200 > 128 + packet[19] = 200; + + // Result value 200 * 2^56 can't be represented with int64_t and thus should + // be rejected. + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, ParseFailsWhenSsrcCountMismatchLength) { + uint8_t packet[kPacketLength]; + memcpy(packet, kPacket, kPacketLength); + packet[16]++; // Swap 3 -> 4 in the ssrcs count. + + Remb remb; + EXPECT_FALSE(test::ParseSinglePacket(packet, &remb)); +} + +TEST(RtcpPacketRembTest, TooManySsrcs) { + Remb remb; + EXPECT_FALSE(remb.SetSsrcs( + std::vector<uint32_t>(Remb::kMaxNumberOfSsrcs + 1, kRemoteSsrcs[0]))); + EXPECT_TRUE(remb.SetSsrcs( + std::vector<uint32_t>(Remb::kMaxNumberOfSsrcs, kRemoteSsrcs[0]))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc new file mode 100644 index 0000000000..ca59791248 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc @@ -0,0 +1,148 @@ +/* + * 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/rtcp_packet/remote_estimate.h" + +#include <algorithm> +#include <cmath> +#include <type_traits> +#include <utility> +#include <vector> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +namespace { + +static constexpr int kFieldValueSize = 3; +static constexpr int kFieldSize = 1 + kFieldValueSize; +static constexpr DataRate kDataRateResolution = DataRate::KilobitsPerSec(1); +constexpr int64_t kMaxEncoded = (1 << (kFieldValueSize * 8)) - 1; + +class DataRateSerializer { + public: + DataRateSerializer( + uint8_t id, + std::function<DataRate*(NetworkStateEstimate*)> field_getter) + : id_(id), field_getter_(field_getter) {} + + uint8_t id() const { return id_; } + + void Read(const uint8_t* src, NetworkStateEstimate* target) const { + int64_t scaled = ByteReader<uint32_t, kFieldValueSize>::ReadBigEndian(src); + if (scaled == kMaxEncoded) { + *field_getter_(target) = DataRate::PlusInfinity(); + } else { + *field_getter_(target) = kDataRateResolution * scaled; + } + } + + bool Write(const NetworkStateEstimate& src, uint8_t* target) const { + auto value = *field_getter_(const_cast<NetworkStateEstimate*>(&src)); + if (value.IsMinusInfinity()) { + RTC_LOG(LS_WARNING) << "Trying to serialize MinusInfinity"; + return false; + } + ByteWriter<uint8_t>::WriteBigEndian(target++, id_); + int64_t scaled; + if (value.IsPlusInfinity()) { + scaled = kMaxEncoded; + } else { + scaled = value / kDataRateResolution; + if (scaled >= kMaxEncoded) { + scaled = kMaxEncoded; + RTC_LOG(LS_WARNING) << ToString(value) << " is larger than max (" + << ToString(kMaxEncoded * kDataRateResolution) + << "), encoded as PlusInfinity."; + } + } + ByteWriter<uint32_t, kFieldValueSize>::WriteBigEndian(target, scaled); + return true; + } + + private: + const uint8_t id_; + const std::function<DataRate*(NetworkStateEstimate*)> field_getter_; +}; + +class RemoteEstimateSerializerImpl : public RemoteEstimateSerializer { + public: + explicit RemoteEstimateSerializerImpl(std::vector<DataRateSerializer> fields) + : fields_(fields) {} + + rtc::Buffer Serialize(const NetworkStateEstimate& src) const override { + size_t max_size = fields_.size() * kFieldSize; + size_t size = 0; + rtc::Buffer buf(max_size); + for (const auto& field : fields_) { + if (field.Write(src, buf.data() + size)) { + size += kFieldSize; + } + } + buf.SetSize(size); + return buf; + } + + bool Parse(rtc::ArrayView<const uint8_t> src, + NetworkStateEstimate* target) const override { + if (src.size() % kFieldSize != 0) + return false; + RTC_DCHECK_EQ(src.size() % kFieldSize, 0); + for (const uint8_t* data_ptr = src.data(); data_ptr < src.end(); + data_ptr += kFieldSize) { + uint8_t field_id = ByteReader<uint8_t>::ReadBigEndian(data_ptr); + for (const auto& field : fields_) { + if (field.id() == field_id) { + field.Read(data_ptr + 1, target); + break; + } + } + } + return true; + } + + private: + const std::vector<DataRateSerializer> fields_; +}; + +} // namespace + +const RemoteEstimateSerializer* GetRemoteEstimateSerializer() { + using E = NetworkStateEstimate; + static auto* serializer = new RemoteEstimateSerializerImpl({ + {1, [](E* e) { return &e->link_capacity_lower; }}, + {2, [](E* e) { return &e->link_capacity_upper; }}, + }); + return serializer; +} + +RemoteEstimate::RemoteEstimate() : serializer_(GetRemoteEstimateSerializer()) { + SetSubType(kSubType); + SetName(kName); + SetSenderSsrc(0); +} + +RemoteEstimate::RemoteEstimate(App&& app) + : App(std::move(app)), serializer_(GetRemoteEstimateSerializer()) {} + +bool RemoteEstimate::ParseData() { + return serializer_->Parse({data(), data_size()}, &estimate_); +} + +void RemoteEstimate::SetEstimate(NetworkStateEstimate estimate) { + estimate_ = estimate; + auto buf = serializer_->Serialize(estimate); + SetData(buf.data(), buf.size()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h new file mode 100644 index 0000000000..3400274568 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h @@ -0,0 +1,59 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMOTE_ESTIMATE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMOTE_ESTIMATE_H_ + +#include <memory> +#include <vector> + +#include "api/transport/network_types.h" +#include "modules/rtp_rtcp/source/rtcp_packet/app.h" + +namespace webrtc { +namespace rtcp { + +class CommonHeader; +class RemoteEstimateSerializer { + public: + virtual bool Parse(rtc::ArrayView<const uint8_t> src, + NetworkStateEstimate* target) const = 0; + virtual rtc::Buffer Serialize(const NetworkStateEstimate& src) const = 0; + virtual ~RemoteEstimateSerializer() = default; +}; + +// Using a static global implementation to avoid incurring initialization +// overhead of the serializer every time RemoteEstimate is created. +const RemoteEstimateSerializer* GetRemoteEstimateSerializer(); + +// The RemoteEstimate packet provides network estimation results from the +// receive side. This functionality is experimental and subject to change +// without notice. +class RemoteEstimate : public App { + public: + RemoteEstimate(); + explicit RemoteEstimate(App&& app); + // Note, sub type must be unique among all app messages with "goog" name. + static constexpr uint8_t kSubType = 13; + static constexpr uint32_t kName = NameToInt("goog"); + static TimeDelta GetTimestampPeriod(); + + bool ParseData(); + void SetEstimate(NetworkStateEstimate estimate); + NetworkStateEstimate estimate() const { return estimate_; } + + private: + NetworkStateEstimate estimate_; + const RemoteEstimateSerializer* const serializer_; +}; + +} // namespace rtcp +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REMOTE_ESTIMATE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc new file mode 100644 index 0000000000..bf0e0e2610 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc @@ -0,0 +1,56 @@ +/* + * 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/rtcp_packet/remote_estimate.h" + +#include "test/gtest.h" + +namespace webrtc { +namespace rtcp { +TEST(RemoteEstimateTest, EncodesCapacityBounds) { + NetworkStateEstimate src; + src.link_capacity_lower = DataRate::KilobitsPerSec(10); + src.link_capacity_upper = DataRate::KilobitsPerSec(1000000); + rtc::Buffer data = GetRemoteEstimateSerializer()->Serialize(src); + NetworkStateEstimate dst; + EXPECT_TRUE(GetRemoteEstimateSerializer()->Parse(data, &dst)); + EXPECT_EQ(src.link_capacity_lower, dst.link_capacity_lower); + EXPECT_EQ(src.link_capacity_upper, dst.link_capacity_upper); +} + +TEST(RemoteEstimateTest, ExpandsToPlusInfinity) { + NetworkStateEstimate src; + // White box testing: We know that the value is stored in an unsigned 24 int + // with kbps resolution. We expected it be represented as plus infinity. + src.link_capacity_lower = DataRate::KilobitsPerSec(2 << 24); + src.link_capacity_upper = DataRate::PlusInfinity(); + rtc::Buffer data = GetRemoteEstimateSerializer()->Serialize(src); + + NetworkStateEstimate dst; + EXPECT_TRUE(GetRemoteEstimateSerializer()->Parse(data, &dst)); + EXPECT_TRUE(dst.link_capacity_lower.IsPlusInfinity()); + EXPECT_TRUE(dst.link_capacity_upper.IsPlusInfinity()); +} + +TEST(RemoteEstimateTest, DoesNotEncodeNegative) { + NetworkStateEstimate src; + src.link_capacity_lower = DataRate::MinusInfinity(); + src.link_capacity_upper = DataRate::MinusInfinity(); + rtc::Buffer data = GetRemoteEstimateSerializer()->Serialize(src); + // Since MinusInfinity can't be represented, the buffer should be empty. + EXPECT_EQ(data.size(), 0u); + NetworkStateEstimate dst; + dst.link_capacity_lower = DataRate::KilobitsPerSec(300); + EXPECT_TRUE(GetRemoteEstimateSerializer()->Parse(data, &dst)); + // The fields will be left unchanged by the parser as they were not encoded. + EXPECT_EQ(dst.link_capacity_lower, DataRate::KilobitsPerSec(300)); + EXPECT_TRUE(dst.link_capacity_upper.IsMinusInfinity()); +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc new file mode 100644 index 0000000000..d4579fc8d6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc @@ -0,0 +1,100 @@ +/* + * 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/report_block.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { + +// From RFC 3550, RTP: A Transport Protocol for Real-Time Applications. +// +// RTCP report block (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 0 | SSRC_1 (SSRC of first source) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | fraction lost | cumulative number of packets lost | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | extended highest sequence number received | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | interarrival jitter | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | last SR (LSR) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 20 | delay since last SR (DLSR) | +// 24 +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +ReportBlock::ReportBlock() + : source_ssrc_(0), + fraction_lost_(0), + cumulative_lost_(0), + extended_high_seq_num_(0), + jitter_(0), + last_sr_(0), + delay_since_last_sr_(0) {} + +bool ReportBlock::Parse(const uint8_t* buffer, size_t length) { + RTC_DCHECK(buffer != nullptr); + if (length < ReportBlock::kLength) { + RTC_LOG(LS_ERROR) << "Report Block should be 24 bytes long"; + return false; + } + + source_ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[0]); + fraction_lost_ = buffer[4]; + cumulative_lost_ = ByteReader<int32_t, 3>::ReadBigEndian(&buffer[5]); + extended_high_seq_num_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]); + jitter_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[12]); + last_sr_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[16]); + delay_since_last_sr_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[20]); + + return true; +} + +void ReportBlock::Create(uint8_t* buffer) const { + // Runtime check should be done while setting cumulative_lost. + RTC_DCHECK_LT(cumulative_lost_signed(), + (1 << 23)); // Have only 3 bytes for it. + + ByteWriter<uint32_t>::WriteBigEndian(&buffer[0], source_ssrc()); + ByteWriter<uint8_t>::WriteBigEndian(&buffer[4], fraction_lost()); + ByteWriter<int32_t, 3>::WriteBigEndian(&buffer[5], cumulative_lost_signed()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[8], extended_high_seq_num()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[12], jitter()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[16], last_sr()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[20], delay_since_last_sr()); +} + +bool ReportBlock::SetCumulativeLost(int32_t cumulative_lost) { + // We have only 3 bytes to store it, and it's a signed value. + if (cumulative_lost >= (1 << 23) || cumulative_lost < -(1 << 23)) { + RTC_LOG(LS_WARNING) + << "Cumulative lost is too big to fit into Report Block"; + return false; + } + cumulative_lost_ = cumulative_lost; + return true; +} + +uint32_t ReportBlock::cumulative_lost() const { + if (cumulative_lost_ < 0) { + RTC_LOG(LS_VERBOSE) << "Ignoring negative value of cumulative_lost"; + return 0; + } + return cumulative_lost_; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h new file mode 100644 index 0000000000..eb16640ae2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h @@ -0,0 +1,72 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REPORT_BLOCK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REPORT_BLOCK_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { +namespace rtcp { + +// A ReportBlock represents the Sender Report packet from +// RFC 3550 section 6.4.1. +class ReportBlock { + public: + static const size_t kLength = 24; + + ReportBlock(); + ~ReportBlock() {} + + bool Parse(const uint8_t* buffer, size_t length); + + // Fills buffer with the ReportBlock. + // Consumes ReportBlock::kLength bytes. + void Create(uint8_t* buffer) const; + + void SetMediaSsrc(uint32_t ssrc) { source_ssrc_ = ssrc; } + void SetFractionLost(uint8_t fraction_lost) { + fraction_lost_ = fraction_lost; + } + bool SetCumulativeLost(int32_t cumulative_lost); + void SetExtHighestSeqNum(uint32_t ext_highest_seq_num) { + extended_high_seq_num_ = ext_highest_seq_num; + } + void SetJitter(uint32_t jitter) { jitter_ = jitter; } + void SetLastSr(uint32_t last_sr) { last_sr_ = last_sr; } + void SetDelayLastSr(uint32_t delay_last_sr) { + delay_since_last_sr_ = delay_last_sr; + } + + uint32_t source_ssrc() const { return source_ssrc_; } + uint8_t fraction_lost() const { return fraction_lost_; } + int32_t cumulative_lost_signed() const { return cumulative_lost_; } + // Deprecated - returns max(0, cumulative_lost_), not negative values. + uint32_t cumulative_lost() const; + uint32_t extended_high_seq_num() const { return extended_high_seq_num_; } + uint32_t jitter() const { return jitter_; } + uint32_t last_sr() const { return last_sr_; } + uint32_t delay_since_last_sr() const { return delay_since_last_sr_; } + + private: + uint32_t source_ssrc_; // 32 bits + uint8_t fraction_lost_; // 8 bits representing a fixed point value 0..1 + int32_t cumulative_lost_; // Signed 24-bit value + uint32_t extended_high_seq_num_; // 32 bits + uint32_t jitter_; // 32 bits + uint32_t last_sr_; // 32 bits + uint32_t delay_since_last_sr_; // 32 bits, units of 1/65536 seconds +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_REPORT_BLOCK_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc new file mode 100644 index 0000000000..5cc102fed0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc @@ -0,0 +1,110 @@ +/* + * 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/report_block.h" + +#include <limits> + +#include "rtc_base/random.h" +#include "test/gtest.h" + +using webrtc::rtcp::ReportBlock; + +namespace webrtc { +namespace { + +const uint32_t kRemoteSsrc = 0x23456789; +const uint8_t kFractionLost = 55; +// Use values that are streamed differently LE and BE. +const int32_t kCumulativeLost = 0x111213; +const uint32_t kExtHighestSeqNum = 0x22232425; +const uint32_t kJitter = 0x33343536; +const uint32_t kLastSr = 0x44454647; +const uint32_t kDelayLastSr = 0x55565758; +const size_t kBufferLength = ReportBlock::kLength; + +TEST(RtcpPacketReportBlockTest, ParseChecksLength) { + uint8_t buffer[kBufferLength]; + memset(buffer, 0, sizeof(buffer)); + + ReportBlock rb; + EXPECT_FALSE(rb.Parse(buffer, kBufferLength - 1)); + EXPECT_TRUE(rb.Parse(buffer, kBufferLength)); +} + +TEST(RtcpPacketReportBlockTest, ParseAnyData) { + uint8_t buffer[kBufferLength]; + // Fill buffer with semi-random data. + Random generator(0x256F8A285EC829ull); + for (size_t i = 0; i < kBufferLength; ++i) + buffer[i] = static_cast<uint8_t>(generator.Rand(0, 0xff)); + + ReportBlock rb; + EXPECT_TRUE(rb.Parse(buffer, kBufferLength)); +} + +TEST(RtcpPacketReportBlockTest, ParseMatchCreate) { + ReportBlock rb; + rb.SetMediaSsrc(kRemoteSsrc); + rb.SetFractionLost(kFractionLost); + rb.SetCumulativeLost(kCumulativeLost); + rb.SetExtHighestSeqNum(kExtHighestSeqNum); + rb.SetJitter(kJitter); + rb.SetLastSr(kLastSr); + rb.SetDelayLastSr(kDelayLastSr); + + uint8_t buffer[kBufferLength]; + rb.Create(buffer); + + ReportBlock parsed; + EXPECT_TRUE(parsed.Parse(buffer, kBufferLength)); + + EXPECT_EQ(kRemoteSsrc, parsed.source_ssrc()); + EXPECT_EQ(kFractionLost, parsed.fraction_lost()); + EXPECT_EQ(kCumulativeLost, parsed.cumulative_lost_signed()); + EXPECT_EQ(kExtHighestSeqNum, parsed.extended_high_seq_num()); + EXPECT_EQ(kJitter, parsed.jitter()); + EXPECT_EQ(kLastSr, parsed.last_sr()); + EXPECT_EQ(kDelayLastSr, parsed.delay_since_last_sr()); +} + +TEST(RtcpPacketReportBlockTest, ValidateCumulativeLost) { + // CumulativeLost is a signed 24-bit integer. + // However, existing code expects it to be an unsigned integer. + // The case of negative values should be unusual; we return 0 + // when caller wants an unsigned integer. + const int32_t kMaxCumulativeLost = 0x7fffff; + const int32_t kMinCumulativeLost = -0x800000; + ReportBlock rb; + EXPECT_FALSE(rb.SetCumulativeLost(kMaxCumulativeLost + 1)); + EXPECT_TRUE(rb.SetCumulativeLost(kMaxCumulativeLost)); + EXPECT_FALSE(rb.SetCumulativeLost(kMinCumulativeLost - 1)); + EXPECT_TRUE(rb.SetCumulativeLost(kMinCumulativeLost)); + EXPECT_EQ(kMinCumulativeLost, rb.cumulative_lost_signed()); + EXPECT_EQ(0u, rb.cumulative_lost()); +} + +TEST(RtcpPacketReportBlockTest, ParseNegativeCumulativeLost) { + // CumulativeLost is a signed 24-bit integer. + const int32_t kNegativeCumulativeLost = -123; + ReportBlock rb; + EXPECT_TRUE(rb.SetCumulativeLost(kNegativeCumulativeLost)); + + uint8_t buffer[kBufferLength]; + rb.Create(buffer); + + ReportBlock parsed; + EXPECT_TRUE(parsed.Parse(buffer, kBufferLength)); + + EXPECT_EQ(kNegativeCumulativeLost, parsed.cumulative_lost_signed()); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc new file mode 100644 index 0000000000..95fc890b19 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc @@ -0,0 +1,49 @@ +/* + * 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/rrtr.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace rtcp { +// Receiver Reference Time Report Block (RFC 3611). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=4 | reserved | block length = 2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, most significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | NTP timestamp, least significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +void Rrtr::Parse(const uint8_t* buffer) { + RTC_DCHECK(buffer[0] == kBlockType); + // reserved = buffer[1]; + RTC_DCHECK(ByteReader<uint16_t>::ReadBigEndian(&buffer[2]) == kBlockLength); + uint32_t seconds = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]); + uint32_t fraction = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]); + ntp_.Set(seconds, fraction); +} + +void Rrtr::Create(uint8_t* buffer) const { + const uint8_t kReserved = 0; + buffer[0] = kBlockType; + buffer[1] = kReserved; + ByteWriter<uint16_t>::WriteBigEndian(&buffer[2], kBlockLength); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[4], ntp_.seconds()); + ByteWriter<uint32_t>::WriteBigEndian(&buffer[8], ntp_.fractions()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h new file mode 100644 index 0000000000..827bd74399 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h @@ -0,0 +1,59 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace rtcp { + +class Rrtr { + public: + static const uint8_t kBlockType = 4; + static const uint16_t kBlockLength = 2; + static const size_t kLength = 4 * (kBlockLength + 1); // 12 + + Rrtr() {} + Rrtr(const Rrtr&) = default; + ~Rrtr() {} + + Rrtr& operator=(const Rrtr&) = default; + + void Parse(const uint8_t* buffer); + + // Fills buffer with the Rrtr. + // Consumes Rrtr::kLength bytes. + void Create(uint8_t* buffer) const; + + void SetNtp(NtpTime ntp) { ntp_ = ntp; } + + NtpTime ntp() const { return ntp_; } + + private: + NtpTime ntp_; +}; + +inline bool operator==(const Rrtr& rrtr1, const Rrtr& rrtr2) { + return rrtr1.ntp() == rrtr2.ntp(); +} + +inline bool operator!=(const Rrtr& rrtr1, const Rrtr& rrtr2) { + return !(rrtr1 == rrtr2); +} + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc new file mode 100644 index 0000000000..56622ea81a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc @@ -0,0 +1,50 @@ +/* + * 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/rrtr.h" + +#include "test/gtest.h" + +using webrtc::rtcp::Rrtr; + +namespace webrtc { +namespace { + +const uint32_t kNtpSec = 0x12345678; +const uint32_t kNtpFrac = 0x23456789; +const uint8_t kBlock[] = {0x04, 0x00, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x23, 0x45, 0x67, 0x89}; +const size_t kBlockSizeBytes = sizeof(kBlock); +static_assert( + kBlockSizeBytes == Rrtr::kLength, + "Size of manually created Rrtr block should match class constant"); + +TEST(RtcpPacketRrtrTest, Create) { + uint8_t buffer[Rrtr::kLength]; + Rrtr rrtr; + rrtr.SetNtp(NtpTime(kNtpSec, kNtpFrac)); + + rrtr.Create(buffer); + EXPECT_EQ(0, memcmp(buffer, kBlock, kBlockSizeBytes)); +} + +TEST(RtcpPacketRrtrTest, Parse) { + Rrtr read_rrtr; + read_rrtr.Parse(kBlock); + + // Run checks on const object to ensure all accessors have const modifier. + const Rrtr& parsed = read_rrtr; + + EXPECT_EQ(kNtpSec, parsed.ntp().seconds()); + EXPECT_EQ(kNtpFrac, parsed.ntp().fractions()); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc new file mode 100644 index 0000000000..18097de330 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc @@ -0,0 +1,45 @@ +/* + * 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/rtpfb.h" + +#include "modules/rtp_rtcp/source/byte_io.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Rtpfb::kPacketType; +// RFC 4585, Section 6.1: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : + +void Rtpfb::ParseCommonFeedback(const uint8_t* payload) { + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0])); + SetMediaSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[4])); +} + +void Rtpfb::CreateCommonFeedback(uint8_t* payload) const { + ByteWriter<uint32_t>::WriteBigEndian(&payload[0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&payload[4], media_ssrc()); +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h new file mode 100644 index 0000000000..973b429a2d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h @@ -0,0 +1,47 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RTPFB_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RTPFB_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { + +// RTPFB: Transport layer feedback message. +// RFC4585, Section 6.2 +class Rtpfb : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 205; + + Rtpfb() = default; + ~Rtpfb() override = default; + + void SetMediaSsrc(uint32_t ssrc) { media_ssrc_ = ssrc; } + + uint32_t media_ssrc() const { return media_ssrc_; } + + protected: + static constexpr size_t kCommonFeedbackLength = 8; + void ParseCommonFeedback(const uint8_t* payload); + void CreateCommonFeedback(uint8_t* payload) const; + + private: + uint32_t media_ssrc_ = 0; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RTPFB_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc new file mode 100644 index 0000000000..f244ec5f37 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc @@ -0,0 +1,199 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/sdes.h" + +#include <string.h> + +#include <utility> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Sdes::kPacketType; +constexpr size_t Sdes::kMaxNumberOfChunks; +// Source Description (SDES) (RFC 3550). +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// header |V=2|P| SC | PT=SDES=202 | length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_1 | +// 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// chunk | SSRC/CSRC_2 | +// 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SDES items | +// | ... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Canonical End-Point Identifier SDES Item (CNAME) +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | CNAME=1 | length | user and domain name ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +namespace { +const uint8_t kTerminatorTag = 0; +const uint8_t kCnameTag = 1; + +size_t ChunkSize(const Sdes::Chunk& chunk) { + // Chunk: + // SSRC/CSRC (4 bytes) | CNAME=1 (1 byte) | length (1 byte) | cname | padding. + size_t chunk_payload_size = 4 + 1 + 1 + chunk.cname.size(); + size_t padding_size = 4 - (chunk_payload_size % 4); // Minimum 1. + return chunk_payload_size + padding_size; +} +} // namespace + +Sdes::Sdes() : block_length_(RtcpPacket::kHeaderLength) {} + +Sdes::~Sdes() {} + +bool Sdes::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + uint8_t number_of_chunks = packet.count(); + std::vector<Chunk> chunks; // Read chunk into temporary array, so that in + // case of an error original array would stay + // unchanged. + size_t block_length = kHeaderLength; + + if (packet.payload_size_bytes() % 4 != 0) { + RTC_LOG(LS_WARNING) << "Invalid payload size " + << packet.payload_size_bytes() + << " bytes for a valid Sdes packet. Size should be" + " multiple of 4 bytes"; + } + const uint8_t* const payload_end = + packet.payload() + packet.payload_size_bytes(); + const uint8_t* looking_at = packet.payload(); + chunks.resize(number_of_chunks); + for (size_t i = 0; i < number_of_chunks;) { + // Each chunk consumes at least 8 bytes. + if (payload_end - looking_at < 8) { + RTC_LOG(LS_WARNING) << "Not enough space left for chunk #" << (i + 1); + return false; + } + chunks[i].ssrc = ByteReader<uint32_t>::ReadBigEndian(looking_at); + looking_at += sizeof(uint32_t); + bool cname_found = false; + + uint8_t item_type; + while ((item_type = *(looking_at++)) != kTerminatorTag) { + if (looking_at >= payload_end) { + RTC_LOG(LS_WARNING) + << "Unexpected end of packet while reading chunk #" << (i + 1) + << ". Expected to find size of the text."; + return false; + } + uint8_t item_length = *(looking_at++); + const size_t kTerminatorSize = 1; + if (looking_at + item_length + kTerminatorSize > payload_end) { + RTC_LOG(LS_WARNING) + << "Unexpected end of packet while reading chunk #" << (i + 1) + << ". Expected to find text of size " << item_length; + return false; + } + if (item_type == kCnameTag) { + if (cname_found) { + RTC_LOG(LS_WARNING) + << "Found extra CNAME for same ssrc in chunk #" << (i + 1); + return false; + } + cname_found = true; + chunks[i].cname.assign(reinterpret_cast<const char*>(looking_at), + item_length); + } + looking_at += item_length; + } + if (cname_found) { + // block_length calculates length of the packet that would be generated by + // Build/Create functions. Adjust it same way WithCName function does. + block_length += ChunkSize(chunks[i]); + ++i; + } else { + // RFC states CNAME item is mandatory. + // But same time it allows chunk without items. + // So while parsing, ignore all chunks without cname, + // but do not fail the parse. + RTC_LOG(LS_WARNING) << "CNAME not found for ssrc " << chunks[i].ssrc; + --number_of_chunks; + chunks.resize(number_of_chunks); + } + // Adjust to 32bit boundary. + looking_at += (payload_end - looking_at) % 4; + } + + chunks_ = std::move(chunks); + block_length_ = block_length; + return true; +} + +bool Sdes::AddCName(uint32_t ssrc, absl::string_view cname) { + RTC_DCHECK_LE(cname.length(), 0xffu); + if (chunks_.size() >= kMaxNumberOfChunks) { + RTC_LOG(LS_WARNING) << "Max SDES chunks reached."; + return false; + } + Chunk chunk; + chunk.ssrc = ssrc; + chunk.cname = std::string(cname); + chunks_.push_back(chunk); + block_length_ += ChunkSize(chunk); + return true; +} + +size_t Sdes::BlockLength() const { + return block_length_; +} + +bool Sdes::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + CreateHeader(chunks_.size(), kPacketType, HeaderLength(), packet, index); + + for (const Sdes::Chunk& chunk : chunks_) { + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], chunk.ssrc); + ByteWriter<uint8_t>::WriteBigEndian(&packet[*index + 4], kCnameTag); + ByteWriter<uint8_t>::WriteBigEndian( + &packet[*index + 5], static_cast<uint8_t>(chunk.cname.size())); + memcpy(&packet[*index + 6], chunk.cname.data(), chunk.cname.size()); + *index += (6 + chunk.cname.size()); + + // In each chunk, the list of items must be terminated by one or more null + // octets. The next chunk must start on a 32-bit boundary. + // CNAME (1 byte) | length (1 byte) | name | padding. + size_t padding_size = 4 - ((6 + chunk.cname.size()) % 4); + const int kPadding = 0; + memset(packet + *index, kPadding, padding_size); + *index += padding_size; + } + + RTC_CHECK_EQ(*index, index_end); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h new file mode 100644 index 0000000000..36b63ba29f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h @@ -0,0 +1,56 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SDES_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SDES_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; +// Source Description (SDES) (RFC 3550). +class Sdes : public RtcpPacket { + public: + struct Chunk { + uint32_t ssrc; + std::string cname; + }; + static constexpr uint8_t kPacketType = 202; + static constexpr size_t kMaxNumberOfChunks = 0x1f; + + Sdes(); + ~Sdes() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + bool AddCName(uint32_t ssrc, absl::string_view cname); + + const std::vector<Chunk>& chunks() const { return chunks_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + std::vector<Chunk> chunks_; + size_t block_length_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SDES_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc new file mode 100644 index 0000000000..15a39efe87 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc @@ -0,0 +1,244 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/sdes.h" + +#include "rtc_base/strings/string_builder.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using webrtc::rtcp::Sdes; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint8_t kPadding = 0; +const uint8_t kTerminatorTag = 0; +const uint8_t kCnameTag = 1; +const uint8_t kNameTag = 2; +const uint8_t kEmailTag = 3; +} // namespace + +TEST(RtcpPacketSdesTest, CreateAndParseWithoutChunks) { + Sdes sdes; + + rtc::Buffer packet = sdes.Build(); + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(0u, parsed.chunks().size()); +} + +TEST(RtcpPacketSdesTest, CreateAndParseWithOneChunk) { + const std::string kCname = "alice@host"; + + Sdes sdes; + EXPECT_TRUE(sdes.AddCName(kSenderSsrc, kCname)); + + rtc::Buffer packet = sdes.Build(); + Sdes sdes_parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &sdes_parsed)); + const Sdes& parsed = sdes_parsed; // Ensure accessors are const. + + EXPECT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc); + EXPECT_EQ(kCname, parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, CreateAndParseWithMultipleChunks) { + Sdes sdes; + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 0, "a")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 1, "ab")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 2, "abc")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 3, "abcd")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 4, "abcde")); + EXPECT_TRUE(sdes.AddCName(kSenderSsrc + 5, "abcdef")); + + rtc::Buffer packet = sdes.Build(); + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(6u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc + 5, parsed.chunks()[5].ssrc); + EXPECT_EQ("abcdef", parsed.chunks()[5].cname); +} + +TEST(RtcpPacketSdesTest, CreateWithTooManyChunks) { + const size_t kMaxChunks = (1 << 5) - 1; + Sdes sdes; + for (size_t i = 0; i < kMaxChunks; ++i) { + uint32_t ssrc = kSenderSsrc + i; + rtc::StringBuilder oss; + oss << "cname" << i; + EXPECT_TRUE(sdes.AddCName(ssrc, oss.str())); + } + EXPECT_FALSE(sdes.AddCName(kSenderSsrc + kMaxChunks, "foo")); +} + +TEST(RtcpPacketSdesTest, CreateAndParseCnameItemWithEmptyString) { + Sdes sdes; + EXPECT_TRUE(sdes.AddCName(kSenderSsrc, "")); + + rtc::Buffer packet = sdes.Build(); + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc); + EXPECT_EQ("", parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, ParseSkipsNonCNameField) { + const uint8_t kName[] = "abc"; + const uint8_t kCname[] = "de"; + const uint8_t kValidPacket[] = { + 0x81, 202, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, kNameTag, 3, kName[0], kName[1], kName[2], kCnameTag, + 2, kCname[0], kCname[1], kTerminatorTag, kPadding, kPadding}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kValidPacket) % 4); + ASSERT_EQ(kValidPacket[3] + 1u, sizeof(kValidPacket) / 4); + + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(kValidPacket, &parsed)); + + EXPECT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc); + EXPECT_EQ("de", parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, ParseSkipsChunksWithoutCName) { + const uint8_t kName[] = "ab"; + const uint8_t kEmail[] = "de"; + const uint8_t kCname[] = "def"; + const uint8_t kPacket[] = { + 0x82, 202, 0x00, 0x07, 0x12, + 0x34, 0x56, 0x78, // 1st chunk. + kNameTag, 3, kName[0], kName[1], kName[2], + kEmailTag, 2, kEmail[0], kEmail[1], kTerminatorTag, + kPadding, kPadding, 0x23, 0x45, 0x67, + 0x89, // 2nd chunk. + kCnameTag, 3, kCname[0], kCname[1], kCname[2], + kTerminatorTag, kPadding, kPadding}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kPacket) % 4); + ASSERT_EQ(kPacket[3] + 1u, sizeof(kPacket) / 4); + + Sdes parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &parsed)); + ASSERT_EQ(1u, parsed.chunks().size()); + EXPECT_EQ(0x23456789u, parsed.chunks()[0].ssrc); + EXPECT_EQ("def", parsed.chunks()[0].cname); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithoutChunkItemTerminator) { + const uint8_t kName[] = "abc"; + const uint8_t kCname[] = "d"; + // No place for next chunk item. + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + kNameTag, 3, kName[0], kName[1], kName[2], kCnameTag, 1, kCname[0]}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithDamagedChunkItem) { + const uint8_t kName[] = "ab"; + const uint8_t kCname[] = "d"; + // Next chunk item has non-terminator type, but not the size. + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + kNameTag, 2, kName[0], kName[1], kCnameTag, 1, kCname[0], kEmailTag}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithTooLongChunkItem) { + const uint8_t kName[] = "abc"; + const uint8_t kCname[] = "d"; + // Last chunk item has length that goes beyond the buffer end. + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, 0x78, + kNameTag, 3, kName[0], kName[1], kName[2], kCnameTag, 2, kCname[0]}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithTwoCNames) { + const uint8_t kCname1[] = "a"; + const uint8_t kCname2[] = "de"; + const uint8_t kInvalidPacket[] = { + 0x81, 202, 0x00, 0x03, 0x12, 0x34, 0x56, + 0x78, kCnameTag, 1, kCname1[0], kCnameTag, 2, kCname2[0], + kCname2[1], kTerminatorTag}; + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParseFailsWithTooLittleSpaceForNextChunk) { + const uint8_t kCname[] = "a"; + const uint8_t kEmail[] = "de"; + // Two chunks are promised in the header, but no place for the second chunk. + const uint8_t kInvalidPacket[] = { + 0x82, 202, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, // 1st chunk. + kCnameTag, 1, kCname[0], kEmailTag, 2, kEmail[0], kEmail[1], + kTerminatorTag, 0x23, 0x45, 0x67, 0x89}; // 2nd chunk. + // Sanity checks packet was assembled correctly. + ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4); + ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4); + + Sdes parsed; + EXPECT_FALSE(test::ParseSinglePacket(kInvalidPacket, &parsed)); +} + +TEST(RtcpPacketSdesTest, ParsedSdesCanBeReusedForBuilding) { + Sdes source; + const std::string kAlice = "alice@host"; + const std::string kBob = "bob@host"; + source.AddCName(kSenderSsrc, kAlice); + + rtc::Buffer packet1 = source.Build(); + Sdes middle; + test::ParseSinglePacket(packet1, &middle); + + EXPECT_EQ(source.BlockLength(), middle.BlockLength()); + + middle.AddCName(kSenderSsrc + 1, kBob); + + rtc::Buffer packet2 = middle.Build(); + Sdes destination; + test::ParseSinglePacket(packet2, &destination); + + EXPECT_EQ(middle.BlockLength(), destination.BlockLength()); + + EXPECT_EQ(2u, destination.chunks().size()); + EXPECT_EQ(kSenderSsrc, destination.chunks()[0].ssrc); + EXPECT_EQ(kAlice, destination.chunks()[0].cname); + EXPECT_EQ(kSenderSsrc + 1, destination.chunks()[1].ssrc); + EXPECT_EQ(kBob, destination.chunks()[1].cname); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc new file mode 100644 index 0000000000..73738376c3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc @@ -0,0 +1,141 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/sender_report.h" + +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t SenderReport::kPacketType; +constexpr size_t SenderReport::kMaxNumberOfReportBlocks; +constexpr size_t SenderReport::kSenderBaseLength; +// Sender report (SR) (RFC 3550). +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| RC | PT=SR=200 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of sender | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// 4 | NTP timestamp, most significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | NTP timestamp, least significant word | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | RTP timestamp | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | sender's packet count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 20 | sender's octet count | +// 24 +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + +SenderReport::SenderReport() + : rtp_timestamp_(0), sender_packet_count_(0), sender_octet_count_(0) {} + +SenderReport::SenderReport(const SenderReport&) = default; +SenderReport::SenderReport(SenderReport&&) = default; +SenderReport& SenderReport::operator=(const SenderReport&) = default; +SenderReport& SenderReport::operator=(SenderReport&&) = default; +SenderReport::~SenderReport() = default; + +bool SenderReport::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + + const uint8_t report_block_count = packet.count(); + if (packet.payload_size_bytes() < + kSenderBaseLength + report_block_count * ReportBlock::kLength) { + RTC_LOG(LS_WARNING) << "Packet is too small to contain all the data."; + return false; + } + // Read SenderReport header. + const uint8_t* const payload = packet.payload(); + SetSenderSsrc(ByteReader<uint32_t>::ReadBigEndian(&payload[0])); + uint32_t secs = ByteReader<uint32_t>::ReadBigEndian(&payload[4]); + uint32_t frac = ByteReader<uint32_t>::ReadBigEndian(&payload[8]); + ntp_.Set(secs, frac); + rtp_timestamp_ = ByteReader<uint32_t>::ReadBigEndian(&payload[12]); + sender_packet_count_ = ByteReader<uint32_t>::ReadBigEndian(&payload[16]); + sender_octet_count_ = ByteReader<uint32_t>::ReadBigEndian(&payload[20]); + report_blocks_.resize(report_block_count); + const uint8_t* next_block = payload + kSenderBaseLength; + for (ReportBlock& block : report_blocks_) { + bool block_parsed = block.Parse(next_block, ReportBlock::kLength); + RTC_DCHECK(block_parsed); + next_block += ReportBlock::kLength; + } + // Double check we didn't read beyond provided buffer. + RTC_DCHECK_LE(next_block - payload, + static_cast<ptrdiff_t>(packet.payload_size_bytes())); + return true; +} + +size_t SenderReport::BlockLength() const { + return kHeaderLength + kSenderBaseLength + + report_blocks_.size() * ReportBlock::kLength; +} + +bool SenderReport::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(report_blocks_.size(), kPacketType, HeaderLength(), packet, + index); + // Write SenderReport header. + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], sender_ssrc()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 4], ntp_.seconds()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 8], ntp_.fractions()); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 12], rtp_timestamp_); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 16], + sender_packet_count_); + ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 20], + sender_octet_count_); + *index += kSenderBaseLength; + // Write report blocks. + for (const ReportBlock& block : report_blocks_) { + block.Create(packet + *index); + *index += ReportBlock::kLength; + } + // Ensure bytes written match expected. + RTC_DCHECK_EQ(*index, index_end); + return true; +} + +bool SenderReport::AddReportBlock(const ReportBlock& block) { + if (report_blocks_.size() >= kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Max report blocks reached."; + return false; + } + report_blocks_.push_back(block); + return true; +} + +bool SenderReport::SetReportBlocks(std::vector<ReportBlock> blocks) { + if (blocks.size() > kMaxNumberOfReportBlocks) { + RTC_LOG(LS_WARNING) << "Too many report blocks (" << blocks.size() + << ") for sender report."; + return false; + } + report_blocks_ = std::move(blocks); + return true; +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h new file mode 100644 index 0000000000..66ced31721 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h @@ -0,0 +1,81 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SENDER_REPORT_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SENDER_REPORT_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class SenderReport : public RtcpPacket { + public: + static constexpr uint8_t kPacketType = 200; + static constexpr size_t kMaxNumberOfReportBlocks = 0x1f; + + SenderReport(); + SenderReport(const SenderReport&); + SenderReport(SenderReport&&); + SenderReport& operator=(const SenderReport&); + SenderReport& operator=(SenderReport&&); + ~SenderReport() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void SetNtp(NtpTime ntp) { ntp_ = ntp; } + void SetRtpTimestamp(uint32_t rtp_timestamp) { + rtp_timestamp_ = rtp_timestamp; + } + void SetPacketCount(uint32_t packet_count) { + sender_packet_count_ = packet_count; + } + void SetOctetCount(uint32_t octet_count) { + sender_octet_count_ = octet_count; + } + bool AddReportBlock(const ReportBlock& block); + bool SetReportBlocks(std::vector<ReportBlock> blocks); + void ClearReportBlocks() { report_blocks_.clear(); } + + NtpTime ntp() const { return ntp_; } + uint32_t rtp_timestamp() const { return rtp_timestamp_; } + uint32_t sender_packet_count() const { return sender_packet_count_; } + uint32_t sender_octet_count() const { return sender_octet_count_; } + + const std::vector<ReportBlock>& report_blocks() const { + return report_blocks_; + } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + static constexpr size_t kSenderBaseLength = 24; + + NtpTime ntp_; + uint32_t rtp_timestamp_; + uint32_t sender_packet_count_; + uint32_t sender_octet_count_; + std::vector<ReportBlock> report_blocks_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SENDER_REPORT_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc new file mode 100644 index 0000000000..37f268e6b4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc @@ -0,0 +1,142 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/sender_report.h" + +#include <utility> + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::ReportBlock; +using webrtc::rtcp::SenderReport; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const NtpTime kNtp(0x11121418, 0x22242628); +const uint32_t kRtpTimestamp = 0x33343536; +const uint32_t kPacketCount = 0x44454647; +const uint32_t kOctetCount = 0x55565758; +const uint8_t kPacket[] = {0x80, 200, 0x00, 0x06, 0x12, 0x34, 0x56, + 0x78, 0x11, 0x12, 0x14, 0x18, 0x22, 0x24, + 0x26, 0x28, 0x33, 0x34, 0x35, 0x36, 0x44, + 0x45, 0x46, 0x47, 0x55, 0x56, 0x57, 0x58}; +} // namespace + +TEST(RtcpPacketSenderReportTest, CreateWithoutReportBlocks) { + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.SetNtp(kNtp); + sr.SetRtpTimestamp(kRtpTimestamp); + sr.SetPacketCount(kPacketCount); + sr.SetOctetCount(kOctetCount); + + rtc::Buffer raw = sr.Build(); + EXPECT_THAT(make_tuple(raw.data(), raw.size()), ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketSenderReportTest, ParseWithoutReportBlocks) { + SenderReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(kNtp, parsed.ntp()); + EXPECT_EQ(kRtpTimestamp, parsed.rtp_timestamp()); + EXPECT_EQ(kPacketCount, parsed.sender_packet_count()); + EXPECT_EQ(kOctetCount, parsed.sender_octet_count()); + EXPECT_TRUE(parsed.report_blocks().empty()); +} + +TEST(RtcpPacketSenderReportTest, CreateAndParseWithOneReportBlock) { + ReportBlock rb; + rb.SetMediaSsrc(kRemoteSsrc); + + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(sr.AddReportBlock(rb)); + + rtc::Buffer raw = sr.Build(); + SenderReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(1u, parsed.report_blocks().size()); + EXPECT_EQ(kRemoteSsrc, parsed.report_blocks()[0].source_ssrc()); +} + +TEST(RtcpPacketSenderReportTest, CreateAndParseWithTwoReportBlocks) { + ReportBlock rb1; + rb1.SetMediaSsrc(kRemoteSsrc); + ReportBlock rb2; + rb2.SetMediaSsrc(kRemoteSsrc + 1); + + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(sr.AddReportBlock(rb1)); + EXPECT_TRUE(sr.AddReportBlock(rb2)); + + rtc::Buffer raw = sr.Build(); + SenderReport parsed; + EXPECT_TRUE(test::ParseSinglePacket(raw, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.report_blocks().size()); + EXPECT_EQ(kRemoteSsrc, parsed.report_blocks()[0].source_ssrc()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.report_blocks()[1].source_ssrc()); +} + +TEST(RtcpPacketSenderReportTest, CreateWithTooManyReportBlocks) { + SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + ReportBlock rb; + for (size_t i = 0; i < SenderReport::kMaxNumberOfReportBlocks; ++i) { + rb.SetMediaSsrc(kRemoteSsrc + i); + EXPECT_TRUE(sr.AddReportBlock(rb)); + } + rb.SetMediaSsrc(kRemoteSsrc + SenderReport::kMaxNumberOfReportBlocks); + EXPECT_FALSE(sr.AddReportBlock(rb)); +} + +TEST(RtcpPacketSenderReportTest, SetReportBlocksOverwritesOldBlocks) { + SenderReport sr; + ReportBlock report_block; + // Use jitter field of the report blocks to distinguish them. + report_block.SetJitter(1001u); + sr.AddReportBlock(report_block); + ASSERT_EQ(sr.report_blocks().size(), 1u); + ASSERT_EQ(sr.report_blocks()[0].jitter(), 1001u); + + std::vector<ReportBlock> blocks(3u); + blocks[0].SetJitter(2001u); + blocks[1].SetJitter(3001u); + blocks[2].SetJitter(4001u); + EXPECT_TRUE(sr.SetReportBlocks(blocks)); + ASSERT_EQ(sr.report_blocks().size(), 3u); + EXPECT_EQ(sr.report_blocks()[0].jitter(), 2001u); + EXPECT_EQ(sr.report_blocks()[1].jitter(), 3001u); + EXPECT_EQ(sr.report_blocks()[2].jitter(), 4001u); +} + +TEST(RtcpPacketSenderReportTest, SetReportBlocksMaxLimit) { + SenderReport sr; + std::vector<ReportBlock> max_blocks(SenderReport::kMaxNumberOfReportBlocks); + EXPECT_TRUE(sr.SetReportBlocks(std::move(max_blocks))); + + std::vector<ReportBlock> one_too_many_blocks( + SenderReport::kMaxNumberOfReportBlocks + 1); + EXPECT_FALSE(sr.SetReportBlocks(std::move(one_too_many_blocks))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc new file mode 100644 index 0000000000..601b24fe94 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc @@ -0,0 +1,127 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/target_bitrate.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { +namespace rtcp { +constexpr size_t kTargetBitrateHeaderSizeBytes = 4; +constexpr uint8_t TargetBitrate::kBlockType; +const size_t TargetBitrate::kBitrateItemSizeBytes = 4; + +TargetBitrate::BitrateItem::BitrateItem() + : spatial_layer(0), temporal_layer(0), target_bitrate_kbps(0) {} + +TargetBitrate::BitrateItem::BitrateItem(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps) + : spatial_layer(spatial_layer), + temporal_layer(temporal_layer), + target_bitrate_kbps(target_bitrate_kbps) {} + +// RFC 4585: Feedback format. +// +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | BT=42 | reserved | block length | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// Target bitrate item (repeat as many times as necessary). +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | S | T | Target Bitrate | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : ... : +// +// Spatial Layer (S): 4 bits +// Indicates which temporal layer this bitrate concerns. +// +// Temporal Layer (T): 4 bits +// Indicates which temporal layer this bitrate concerns. +// +// Target Bitrate: 24 bits +// The encoder target bitrate for this layer, in kbps. +// +// As an example of how S and T are intended to be used, VP8 simulcast will +// use a separate TargetBitrate message per stream, since they are transmitted +// on separate SSRCs, with temporal layers grouped by stream. +// If VP9 SVC is used, there will be only one SSRC, so each spatial and +// temporal layer combo used shall be specified in the TargetBitrate packet. + +TargetBitrate::TargetBitrate() = default; +TargetBitrate::TargetBitrate(const TargetBitrate&) = default; +TargetBitrate& TargetBitrate::operator=(const TargetBitrate&) = default; +TargetBitrate::~TargetBitrate() = default; + +void TargetBitrate::Parse(const uint8_t* block, uint16_t block_length) { + // Validate block header (should already have been parsed and checked). + RTC_DCHECK_EQ(block[0], kBlockType); + RTC_DCHECK_EQ(block_length, ByteReader<uint16_t>::ReadBigEndian(&block[2])); + + // Header specifies block length - 1, but since we ignore the header, which + // occupies exactly on block, we can just treat this as payload length. + const size_t payload_bytes = block_length * 4; + const size_t num_items = payload_bytes / kBitrateItemSizeBytes; + size_t index = kTargetBitrateHeaderSizeBytes; + bitrates_.clear(); + for (size_t i = 0; i < num_items; ++i) { + uint8_t layers = block[index]; + uint32_t bitrate_kbps = + ByteReader<uint32_t, 3>::ReadBigEndian(&block[index + 1]); + index += kBitrateItemSizeBytes; + AddTargetBitrate((layers >> 4) & 0x0F, layers & 0x0F, bitrate_kbps); + } +} + +void TargetBitrate::AddTargetBitrate(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps) { + RTC_DCHECK_LE(spatial_layer, 0x0F); + RTC_DCHECK_LE(temporal_layer, 0x0F); + RTC_DCHECK_LE(target_bitrate_kbps, 0x00FFFFFFU); + bitrates_.push_back( + BitrateItem(spatial_layer, temporal_layer, target_bitrate_kbps)); +} + +const std::vector<TargetBitrate::BitrateItem>& +TargetBitrate::GetTargetBitrates() const { + return bitrates_; +} + +size_t TargetBitrate::BlockLength() const { + return kTargetBitrateHeaderSizeBytes + + bitrates_.size() * kBitrateItemSizeBytes; +} + +void TargetBitrate::Create(uint8_t* buffer) const { + buffer[0] = kBlockType; + buffer[1] = 0; // Reserved. + uint16_t block_length_words = + rtc::dchecked_cast<uint16_t>((BlockLength() / 4) - 1); + ByteWriter<uint16_t>::WriteBigEndian(&buffer[2], block_length_words); + + size_t index = kTargetBitrateHeaderSizeBytes; + for (const BitrateItem& item : bitrates_) { + buffer[index] = (item.spatial_layer << 4) | item.temporal_layer; + ByteWriter<uint32_t, 3>::WriteBigEndian(&buffer[index + 1], + item.target_bitrate_kbps); + index += kBitrateItemSizeBytes; + } +} + +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h new file mode 100644 index 0000000000..07e5da1a49 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h @@ -0,0 +1,63 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +namespace webrtc { +namespace rtcp { + +class TargetBitrate { + public: + // TODO(sprang): This block type is just a place holder. We need to get an + // id assigned by IANA. + static constexpr uint8_t kBlockType = 42; + static const size_t kBitrateItemSizeBytes; + + struct BitrateItem { + BitrateItem(); + BitrateItem(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps); + + uint8_t spatial_layer; + uint8_t temporal_layer; + uint32_t target_bitrate_kbps; + }; + + TargetBitrate(); + TargetBitrate(const TargetBitrate&); + TargetBitrate& operator=(const TargetBitrate&); + ~TargetBitrate(); + + void AddTargetBitrate(uint8_t spatial_layer, + uint8_t temporal_layer, + uint32_t target_bitrate_kbps); + + const std::vector<BitrateItem>& GetTargetBitrates() const; + + void Parse(const uint8_t* block, uint16_t block_length); + + size_t BlockLength() const; + + void Create(uint8_t* buffer) const; + + private: + std::vector<BitrateItem> bitrates_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TARGET_BITRATE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc new file mode 100644 index 0000000000..b16bb5beaa --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc @@ -0,0 +1,96 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/target_bitrate.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" +#include "rtc_base/buffer.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { +using BitrateItem = rtcp::TargetBitrate::BitrateItem; +using rtcp::TargetBitrate; +using test::ParseSinglePacket; + +constexpr uint32_t kSsrc = 0x12345678; + +// clang-format off +const uint8_t kPacket[] = { TargetBitrate::kBlockType, // Block ID. + 0x00, // Reserved. + 0x00, 0x04, // Length = 4 words. + 0x00, 0x01, 0x02, 0x03, // S0T0 0x010203 kbps. + 0x01, 0x02, 0x03, 0x04, // S0T1 0x020304 kbps. + 0x10, 0x03, 0x04, 0x05, // S1T0 0x030405 kbps. + 0x11, 0x04, 0x05, 0x06 }; // S1T1 0x040506 kbps. +constexpr size_t kPacketLengthBlocks = ((sizeof(kPacket) + 3) / 4) - 1; +// clang-format on + +void ExpectBirateItemEquals(const BitrateItem& expected, + const BitrateItem& actual) { + EXPECT_EQ(expected.spatial_layer, actual.spatial_layer); + EXPECT_EQ(expected.temporal_layer, actual.temporal_layer); + EXPECT_EQ(expected.target_bitrate_kbps, actual.target_bitrate_kbps); +} + +void CheckBitrateItems(const std::vector<BitrateItem>& bitrates) { + EXPECT_EQ(4U, bitrates.size()); + ExpectBirateItemEquals(BitrateItem(0, 0, 0x010203), bitrates[0]); + ExpectBirateItemEquals(BitrateItem(0, 1, 0x020304), bitrates[1]); + ExpectBirateItemEquals(BitrateItem(1, 0, 0x030405), bitrates[2]); + ExpectBirateItemEquals(BitrateItem(1, 1, 0x040506), bitrates[3]); +} + +} // namespace + +TEST(TargetBitrateTest, Parse) { + TargetBitrate target_bitrate; + target_bitrate.Parse(kPacket, kPacketLengthBlocks); + CheckBitrateItems(target_bitrate.GetTargetBitrates()); +} + +TEST(TargetBitrateTest, FullPacket) { + const size_t kXRHeaderSize = 8; // RTCP header (4) + SSRC (4). + const size_t kTotalSize = kXRHeaderSize + sizeof(kPacket); + uint8_t kRtcpPacket[kTotalSize] = {2 << 6, 207, 0x00, (kTotalSize / 4) - 1, + 0x12, 0x34, 0x56, 0x78}; // SSRC. + memcpy(&kRtcpPacket[kXRHeaderSize], kPacket, sizeof(kPacket)); + rtcp::ExtendedReports xr; + EXPECT_TRUE(ParseSinglePacket(kRtcpPacket, &xr)); + EXPECT_EQ(kSsrc, xr.sender_ssrc()); + const absl::optional<TargetBitrate>& target_bitrate = xr.target_bitrate(); + ASSERT_TRUE(static_cast<bool>(target_bitrate)); + CheckBitrateItems(target_bitrate->GetTargetBitrates()); +} + +TEST(TargetBitrateTest, Create) { + TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, 0x010203); + target_bitrate.AddTargetBitrate(0, 1, 0x020304); + target_bitrate.AddTargetBitrate(1, 0, 0x030405); + target_bitrate.AddTargetBitrate(1, 1, 0x040506); + + uint8_t buffer[sizeof(kPacket)] = {}; + ASSERT_EQ(sizeof(kPacket), target_bitrate.BlockLength()); + target_bitrate.Create(buffer); + + EXPECT_EQ(0, memcmp(kPacket, buffer, sizeof(kPacket))); +} + +TEST(TargetBitrateTest, ParseNullBitratePacket) { + const uint8_t kNullPacket[] = {TargetBitrate::kBlockType, 0x00, 0x00, 0x00}; + TargetBitrate target_bitrate; + target_bitrate.Parse(kNullPacket, 0); + EXPECT_TRUE(target_bitrate.GetTargetBitrates().empty()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc new file mode 100644 index 0000000000..810e1e267a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.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 "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +TmmbItem::TmmbItem(uint32_t ssrc, uint64_t bitrate_bps, uint16_t overhead) + : ssrc_(ssrc), bitrate_bps_(bitrate_bps), packet_overhead_(overhead) { + RTC_DCHECK_LE(overhead, 0x1ffu); +} + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +bool TmmbItem::Parse(const uint8_t* buffer) { + ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[0]); + // Read 4 bytes into 1 block. + uint32_t compact = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]); + // Split 1 block into 3 components. + uint8_t exponent = compact >> 26; // 6 bits. + uint64_t mantissa = (compact >> 9) & 0x1ffff; // 17 bits. + uint16_t overhead = compact & 0x1ff; // 9 bits. + // Combine 3 components into 2 values. + bitrate_bps_ = (mantissa << exponent); + + bool shift_overflow = (bitrate_bps_ >> exponent) != mantissa; + if (shift_overflow) { + RTC_LOG(LS_ERROR) << "Invalid tmmb bitrate value : " << mantissa << "*2^" + << static_cast<int>(exponent); + return false; + } + packet_overhead_ = overhead; + return true; +} + +void TmmbItem::Create(uint8_t* buffer) const { + constexpr uint64_t kMaxMantissa = 0x1ffff; // 17 bits. + uint64_t mantissa = bitrate_bps_; + uint32_t exponent = 0; + while (mantissa > kMaxMantissa) { + mantissa >>= 1; + ++exponent; + } + + ByteWriter<uint32_t>::WriteBigEndian(&buffer[0], ssrc_); + uint32_t compact = (exponent << 26) | (mantissa << 9) | packet_overhead_; + ByteWriter<uint32_t>::WriteBigEndian(&buffer[4], compact); +} + +void TmmbItem::set_packet_overhead(uint16_t overhead) { + RTC_DCHECK_LE(overhead, 0x1ffu); + packet_overhead_ = overhead; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h new file mode 100644 index 0000000000..dc5d1b2c2d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h @@ -0,0 +1,52 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMB_ITEM_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMB_ITEM_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace webrtc { +namespace rtcp { +// RFC5104, Section 3.5.4 +// Temporary Maximum Media Stream Bitrate Request/Notification. +// Used both by TMMBR and TMMBN rtcp packets. +class TmmbItem { + public: + static const size_t kLength = 8; + + TmmbItem() : ssrc_(0), bitrate_bps_(0), packet_overhead_(0) {} + TmmbItem(uint32_t ssrc, uint64_t bitrate_bps, uint16_t overhead); + + bool Parse(const uint8_t* buffer); + void Create(uint8_t* buffer) const; + + void set_ssrc(uint32_t ssrc) { ssrc_ = ssrc; } + void set_bitrate_bps(uint64_t bitrate_bps) { bitrate_bps_ = bitrate_bps; } + void set_packet_overhead(uint16_t overhead); + + uint32_t ssrc() const { return ssrc_; } + uint64_t bitrate_bps() const { return bitrate_bps_; } + uint16_t packet_overhead() const { return packet_overhead_; } + + private: + // Media stream id. + uint32_t ssrc_; + // Maximum total media bit rate that the media receiver is + // currently prepared to accept for this media stream. + uint64_t bitrate_bps_; + // Per-packet overhead that the media receiver has observed + // for this media stream at its chosen reference protocol layer. + uint16_t packet_overhead_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMB_ITEM_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc new file mode 100644 index 0000000000..f57e5749c2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc @@ -0,0 +1,109 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/tmmbn.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Tmmbn::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source (unused) = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// Temporary Maximum Media Stream Bit Rate Notification (TMMBN) (RFC 5104). +// The Feedback Control Information (FCI) consists of zero, one, or more +// TMMBN FCI entries. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Tmmbn::Tmmbn() = default; + +Tmmbn::~Tmmbn() = default; + +bool Tmmbn::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for TMMBN."; + return false; + } + size_t items_size_bytes = packet.payload_size_bytes() - kCommonFeedbackLength; + if (items_size_bytes % TmmbItem::kLength != 0) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is not valid for TMMBN."; + return false; + } + ParseCommonFeedback(packet.payload()); + const uint8_t* next_item = packet.payload() + kCommonFeedbackLength; + + size_t number_of_items = items_size_bytes / TmmbItem::kLength; + items_.resize(number_of_items); + for (TmmbItem& item : items_) { + if (!item.Parse(next_item)) + return false; + next_item += TmmbItem::kLength; + } + return true; +} + +void Tmmbn::AddTmmbr(const TmmbItem& item) { + items_.push_back(item); +} + +size_t Tmmbn::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + + TmmbItem::kLength * items_.size(); +} + +bool Tmmbn::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(0, Rtpfb::media_ssrc()); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + for (const TmmbItem& item : items_) { + item.Create(packet + *index); + *index += TmmbItem::kLength; + } + RTC_CHECK_EQ(index_end, *index); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h new file mode 100644 index 0000000000..ff7779d8ac --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h @@ -0,0 +1,55 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBN_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBN_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// Temporary Maximum Media Stream Bit Rate Notification (TMMBN). +// RFC 5104, Section 4.2.2. +class Tmmbn : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 4; + + Tmmbn(); + ~Tmmbn() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void AddTmmbr(const TmmbItem& item); + + const std::vector<TmmbItem>& items() const { return items_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + // Media ssrc is unused, shadow base class setter and getter. + void SetMediaSsrc(uint32_t ssrc); + uint32_t media_ssrc() const; + + std::vector<TmmbItem> items_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBN_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc new file mode 100644 index 0000000000..3a37bb1c0e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc @@ -0,0 +1,105 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/tmmbn.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::make_tuple; +using webrtc::rtcp::TmmbItem; +using webrtc::rtcp::Tmmbn; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint32_t kBitrateBps = 312000; +const uint16_t kOverhead = 0x1fe; +const uint8_t kPacket[] = {0x84, 205, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; +} // namespace + +TEST(RtcpPacketTmmbnTest, Create) { + Tmmbn tmmbn; + tmmbn.SetSenderSsrc(kSenderSsrc); + tmmbn.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + + rtc::Buffer packet = tmmbn.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketTmmbnTest, Parse) { + Tmmbn tmmbn; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &tmmbn)); + + const Tmmbn& parsed = tmmbn; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + ASSERT_EQ(1u, parsed.items().size()); + EXPECT_EQ(kRemoteSsrc, parsed.items().front().ssrc()); + EXPECT_EQ(kBitrateBps, parsed.items().front().bitrate_bps()); + EXPECT_EQ(kOverhead, parsed.items().front().packet_overhead()); +} + +TEST(RtcpPacketTmmbnTest, CreateAndParseWithoutItems) { + Tmmbn tmmbn; + tmmbn.SetSenderSsrc(kSenderSsrc); + + rtc::Buffer packet = tmmbn.Build(); + Tmmbn parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_THAT(parsed.items(), IsEmpty()); +} + +TEST(RtcpPacketTmmbnTest, CreateAndParseWithTwoItems) { + Tmmbn tmmbn; + tmmbn.SetSenderSsrc(kSenderSsrc); + tmmbn.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + tmmbn.AddTmmbr(TmmbItem(kRemoteSsrc + 1, 4 * kBitrateBps, 40)); + + rtc::Buffer packet = tmmbn.Build(); + Tmmbn parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.items().size()); + EXPECT_EQ(kRemoteSsrc, parsed.items()[0].ssrc()); + EXPECT_EQ(kBitrateBps, parsed.items()[0].bitrate_bps()); + EXPECT_EQ(kOverhead, parsed.items()[0].packet_overhead()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.items()[1].ssrc()); + EXPECT_EQ(4 * kBitrateBps, parsed.items()[1].bitrate_bps()); + EXPECT_EQ(40U, parsed.items()[1].packet_overhead()); +} + +TEST(RtcpPacketTmmbnTest, ParseFailsOnTooSmallPacket) { + const uint8_t kSmallPacket[] = {0x84, 205, 0x00, 0x01, + 0x12, 0x34, 0x56, 0x78}; + Tmmbn tmmbn; + EXPECT_FALSE(test::ParseSinglePacket(kSmallPacket, &tmmbn)); +} + +TEST(RtcpPacketTmmbnTest, ParseFailsOnUnAlignedPacket) { + const uint8_t kUnalignedPacket[] = {0x84, 205, 0x00, 0x03, 0x12, 0x34, + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x45, 0x67, 0x89}; + + Tmmbn tmmbn; + EXPECT_FALSE(test::ParseSinglePacket(kUnalignedPacket, &tmmbn)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc new file mode 100644 index 0000000000..9dc745e509 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc @@ -0,0 +1,111 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/tmmbr.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace rtcp { +constexpr uint8_t Tmmbr::kFeedbackMessageType; +// RFC 4585: Feedback format. +// Common packet format: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT | PT | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source (unused) = 0 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// : Feedback Control Information (FCI) : +// : : +// Temporary Maximum Media Stream Bit Rate Request (TMMBR) (RFC 5104). +// The Feedback Control Information (FCI) for the TMMBR +// consists of one or more FCI entries. +// FCI: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Tmmbr::Tmmbr() = default; + +Tmmbr::~Tmmbr() = default; + +bool Tmmbr::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + + if (packet.payload_size_bytes() < kCommonFeedbackLength + TmmbItem::kLength) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is too small for a TMMBR."; + return false; + } + size_t items_size_bytes = packet.payload_size_bytes() - kCommonFeedbackLength; + if (items_size_bytes % TmmbItem::kLength != 0) { + RTC_LOG(LS_WARNING) << "Payload length " << packet.payload_size_bytes() + << " is not valid for a TMMBR."; + return false; + } + ParseCommonFeedback(packet.payload()); + + const uint8_t* next_item = packet.payload() + kCommonFeedbackLength; + size_t number_of_items = items_size_bytes / TmmbItem::kLength; + items_.resize(number_of_items); + for (TmmbItem& item : items_) { + if (!item.Parse(next_item)) + return false; + next_item += TmmbItem::kLength; + } + return true; +} + +void Tmmbr::AddTmmbr(const TmmbItem& item) { + items_.push_back(item); +} + +size_t Tmmbr::BlockLength() const { + return kHeaderLength + kCommonFeedbackLength + + TmmbItem::kLength * items_.size(); +} + +bool Tmmbr::Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const { + RTC_DCHECK(!items_.empty()); + while (*index + BlockLength() > max_length) { + if (!OnBufferFull(packet, index, callback)) + return false; + } + const size_t index_end = *index + BlockLength(); + + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), packet, + index); + RTC_DCHECK_EQ(0, Rtpfb::media_ssrc()); + CreateCommonFeedback(packet + *index); + *index += kCommonFeedbackLength; + for (const TmmbItem& item : items_) { + item.Create(packet + *index); + *index += TmmbItem::kLength; + } + RTC_CHECK_EQ(index_end, *index); + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h new file mode 100644 index 0000000000..7482cb75cc --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h @@ -0,0 +1,54 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBR_H_ + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +// Temporary Maximum Media Stream Bit Rate Request (TMMBR). +// RFC 5104, Section 4.2.1. +class Tmmbr : public Rtpfb { + public: + static constexpr uint8_t kFeedbackMessageType = 3; + + Tmmbr(); + ~Tmmbr() override; + + // Parse assumes header is already parsed and validated. + bool Parse(const CommonHeader& packet); + + void AddTmmbr(const TmmbItem& item); + + const std::vector<TmmbItem>& requests() const { return items_; } + + size_t BlockLength() const override; + + bool Create(uint8_t* packet, + size_t* index, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + // Media ssrc is unused, shadow base class setter. + void SetMediaSsrc(uint32_t ssrc); + + std::vector<TmmbItem> items_; +}; +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TMMBR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc new file mode 100644 index 0000000000..1bac808aa9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc @@ -0,0 +1,89 @@ +/* + * 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/rtp_rtcp/source/rtcp_packet/tmmbr.h" + +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAreArray; +using ::testing::make_tuple; +using webrtc::rtcp::TmmbItem; +using webrtc::rtcp::Tmmbr; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345678; +const uint32_t kRemoteSsrc = 0x23456789; +const uint32_t kBitrateBps = 312000; +const uint16_t kOverhead = 0x1fe; +const uint8_t kPacket[] = {0x83, 205, 0x00, 0x04, 0x12, 0x34, 0x56, + 0x78, 0x00, 0x00, 0x00, 0x00, 0x23, 0x45, + 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe}; +} // namespace + +TEST(RtcpPacketTmmbrTest, Create) { + Tmmbr tmmbr; + tmmbr.SetSenderSsrc(kSenderSsrc); + tmmbr.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + + rtc::Buffer packet = tmmbr.Build(); + + EXPECT_THAT(make_tuple(packet.data(), packet.size()), + ElementsAreArray(kPacket)); +} + +TEST(RtcpPacketTmmbrTest, Parse) { + Tmmbr tmmbr; + EXPECT_TRUE(test::ParseSinglePacket(kPacket, &tmmbr)); + const Tmmbr& parsed = tmmbr; + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + ASSERT_EQ(1u, parsed.requests().size()); + EXPECT_EQ(kRemoteSsrc, parsed.requests().front().ssrc()); + EXPECT_EQ(kBitrateBps, parsed.requests().front().bitrate_bps()); + EXPECT_EQ(kOverhead, parsed.requests().front().packet_overhead()); +} + +TEST(RtcpPacketTmmbrTest, CreateAndParseWithTwoEntries) { + Tmmbr tmmbr; + tmmbr.SetSenderSsrc(kSenderSsrc); + tmmbr.AddTmmbr(TmmbItem(kRemoteSsrc, kBitrateBps, kOverhead)); + tmmbr.AddTmmbr(TmmbItem(kRemoteSsrc + 1, 4 * kBitrateBps, kOverhead + 1)); + + rtc::Buffer packet = tmmbr.Build(); + + Tmmbr parsed; + EXPECT_TRUE(test::ParseSinglePacket(packet, &parsed)); + + EXPECT_EQ(kSenderSsrc, parsed.sender_ssrc()); + EXPECT_EQ(2u, parsed.requests().size()); + EXPECT_EQ(kRemoteSsrc, parsed.requests()[0].ssrc()); + EXPECT_EQ(kRemoteSsrc + 1, parsed.requests()[1].ssrc()); +} + +TEST(RtcpPacketTmmbrTest, ParseFailsWithoutItems) { + const uint8_t kZeroItemsPacket[] = {0x83, 205, 0x00, 0x02, 0x12, 0x34, + 0x56, 0x78, 0x00, 0x00, 0x00, 0x00}; + + Tmmbr tmmbr; + EXPECT_FALSE(test::ParseSinglePacket(kZeroItemsPacket, &tmmbr)); +} + +TEST(RtcpPacketTmmbrTest, ParseFailsOnUnAlignedPacket) { + const uint8_t kUnalignedPacket[] = { + 0x83, 205, 0x00, 0x05, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x45, 0x67, 0x89, 0x0a, 0x61, 0x61, 0xfe, 0x34, 0x56, 0x78, 0x9a}; + + Tmmbr tmmbr; + EXPECT_FALSE(test::ParseSinglePacket(kUnalignedPacket, &tmmbr)); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc new file mode 100644 index 0000000000..003effad29 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc @@ -0,0 +1,737 @@ +/* + * 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/transport_feedback.h" + +#include <algorithm> +#include <cstdint> +#include <numeric> +#include <utility> + +#include "absl/algorithm/container.h" +#include "modules/include/module_common_types_public.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { +namespace rtcp { +namespace { +// Header size: +// * 4 bytes Common RTCP Packet Header +// * 8 bytes Common Packet Format for RTCP Feedback Messages +// * 8 bytes FeedbackPacket header +constexpr size_t kTransportFeedbackHeaderSizeBytes = 4 + 8 + 8; +constexpr size_t kChunkSizeBytes = 2; +// TODO(sprang): Add support for dynamic max size for easier fragmentation, +// eg. set it to what's left in the buffer or IP_PACKET_SIZE. +// Size constraint imposed by RTCP common header: 16bit size field interpreted +// as number of four byte words minus the first header word. +constexpr size_t kMaxSizeBytes = (1 << 16) * 4; +// Payload size: +// * 8 bytes Common Packet Format for RTCP Feedback Messages +// * 8 bytes FeedbackPacket header. +// * 2 bytes for one chunk. +constexpr size_t kMinPayloadSizeBytes = 8 + 8 + 2; +constexpr TimeDelta kBaseTimeTick = TransportFeedback::kDeltaTick * (1 << 8); +constexpr TimeDelta kTimeWrapPeriod = kBaseTimeTick * (1 << 24); + +// Message format +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=205 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 0 | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 4 | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 8 | base sequence number | packet status count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 12 | reference time | fb pkt. count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// 16 | packet chunk | packet chunk | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | recv delta | recv delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | recv delta | recv delta | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +} // namespace +constexpr uint8_t TransportFeedback::kFeedbackMessageType; +constexpr size_t TransportFeedback::kMaxReportedPackets; + +constexpr size_t TransportFeedback::LastChunk::kMaxRunLengthCapacity; +constexpr size_t TransportFeedback::LastChunk::kMaxOneBitCapacity; +constexpr size_t TransportFeedback::LastChunk::kMaxTwoBitCapacity; +constexpr size_t TransportFeedback::LastChunk::kMaxVectorCapacity; + +TransportFeedback::LastChunk::LastChunk() { + Clear(); +} + +bool TransportFeedback::LastChunk::Empty() const { + return size_ == 0; +} + +void TransportFeedback::LastChunk::Clear() { + size_ = 0; + all_same_ = true; + has_large_delta_ = false; +} + +bool TransportFeedback::LastChunk::CanAdd(DeltaSize delta_size) const { + RTC_DCHECK_LE(delta_size, 2); + if (size_ < kMaxTwoBitCapacity) + return true; + if (size_ < kMaxOneBitCapacity && !has_large_delta_ && delta_size != kLarge) + return true; + if (size_ < kMaxRunLengthCapacity && all_same_ && + delta_sizes_[0] == delta_size) + return true; + return false; +} + +void TransportFeedback::LastChunk::Add(DeltaSize delta_size) { + RTC_DCHECK(CanAdd(delta_size)); + if (size_ < kMaxVectorCapacity) + delta_sizes_[size_] = delta_size; + size_++; + all_same_ = all_same_ && delta_size == delta_sizes_[0]; + has_large_delta_ = has_large_delta_ || delta_size == kLarge; +} + +void TransportFeedback::LastChunk::AddMissingPackets(size_t num_missing) { + RTC_DCHECK_EQ(size_, 0); + RTC_DCHECK(all_same_); + RTC_DCHECK(!has_large_delta_); + RTC_DCHECK_LT(num_missing, kMaxRunLengthCapacity); + absl::c_fill(delta_sizes_, DeltaSize(0)); + size_ = num_missing; +} + +uint16_t TransportFeedback::LastChunk::Emit() { + RTC_DCHECK(!CanAdd(0) || !CanAdd(1) || !CanAdd(2)); + if (all_same_) { + uint16_t chunk = EncodeRunLength(); + Clear(); + return chunk; + } + if (size_ == kMaxOneBitCapacity) { + uint16_t chunk = EncodeOneBit(); + Clear(); + return chunk; + } + RTC_DCHECK_GE(size_, kMaxTwoBitCapacity); + uint16_t chunk = EncodeTwoBit(kMaxTwoBitCapacity); + // Remove `kMaxTwoBitCapacity` encoded delta sizes: + // Shift remaining delta sizes and recalculate all_same_ && has_large_delta_. + size_ -= kMaxTwoBitCapacity; + all_same_ = true; + has_large_delta_ = false; + for (size_t i = 0; i < size_; ++i) { + DeltaSize delta_size = delta_sizes_[kMaxTwoBitCapacity + i]; + delta_sizes_[i] = delta_size; + all_same_ = all_same_ && delta_size == delta_sizes_[0]; + has_large_delta_ = has_large_delta_ || delta_size == kLarge; + } + + return chunk; +} + +uint16_t TransportFeedback::LastChunk::EncodeLast() const { + RTC_DCHECK_GT(size_, 0); + if (all_same_) + return EncodeRunLength(); + if (size_ <= kMaxTwoBitCapacity) + return EncodeTwoBit(size_); + return EncodeOneBit(); +} + +// Appends content of the Lastchunk to `deltas`. +void TransportFeedback::LastChunk::AppendTo( + std::vector<DeltaSize>* deltas) const { + if (all_same_) { + deltas->insert(deltas->end(), size_, delta_sizes_[0]); + } else { + deltas->insert(deltas->end(), delta_sizes_.begin(), + delta_sizes_.begin() + size_); + } +} + +void TransportFeedback::LastChunk::Decode(uint16_t chunk, size_t max_size) { + if ((chunk & 0x8000) == 0) { + DecodeRunLength(chunk, max_size); + } else if ((chunk & 0x4000) == 0) { + DecodeOneBit(chunk, max_size); + } else { + DecodeTwoBit(chunk, max_size); + } +} + +// One Bit Status Vector Chunk +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T|S| symbol list | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// T = 1 +// S = 0 +// Symbol list = 14 entries where 0 = not received, 1 = received 1-byte delta. +uint16_t TransportFeedback::LastChunk::EncodeOneBit() const { + RTC_DCHECK(!has_large_delta_); + RTC_DCHECK_LE(size_, kMaxOneBitCapacity); + uint16_t chunk = 0x8000; + for (size_t i = 0; i < size_; ++i) + chunk |= delta_sizes_[i] << (kMaxOneBitCapacity - 1 - i); + return chunk; +} + +void TransportFeedback::LastChunk::DecodeOneBit(uint16_t chunk, + size_t max_size) { + RTC_DCHECK_EQ(chunk & 0xc000, 0x8000); + size_ = std::min(kMaxOneBitCapacity, max_size); + has_large_delta_ = false; + all_same_ = false; + for (size_t i = 0; i < size_; ++i) + delta_sizes_[i] = (chunk >> (kMaxOneBitCapacity - 1 - i)) & 0x01; +} + +// Two Bit Status Vector Chunk +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T|S| symbol list | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// T = 1 +// S = 1 +// symbol list = 7 entries of two bits each. +uint16_t TransportFeedback::LastChunk::EncodeTwoBit(size_t size) const { + RTC_DCHECK_LE(size, size_); + uint16_t chunk = 0xc000; + for (size_t i = 0; i < size; ++i) + chunk |= delta_sizes_[i] << 2 * (kMaxTwoBitCapacity - 1 - i); + return chunk; +} + +void TransportFeedback::LastChunk::DecodeTwoBit(uint16_t chunk, + size_t max_size) { + RTC_DCHECK_EQ(chunk & 0xc000, 0xc000); + size_ = std::min(kMaxTwoBitCapacity, max_size); + has_large_delta_ = true; + all_same_ = false; + for (size_t i = 0; i < size_; ++i) + delta_sizes_[i] = (chunk >> 2 * (kMaxTwoBitCapacity - 1 - i)) & 0x03; +} + +// Run Length Status Vector Chunk +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T| S | Run Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// T = 0 +// S = symbol +// Run Length = Unsigned integer denoting the run length of the symbol +uint16_t TransportFeedback::LastChunk::EncodeRunLength() const { + RTC_DCHECK(all_same_); + RTC_DCHECK_LE(size_, kMaxRunLengthCapacity); + return (delta_sizes_[0] << 13) | static_cast<uint16_t>(size_); +} + +void TransportFeedback::LastChunk::DecodeRunLength(uint16_t chunk, + size_t max_count) { + RTC_DCHECK_EQ(chunk & 0x8000, 0); + size_ = std::min<size_t>(chunk & 0x1fff, max_count); + DeltaSize delta_size = (chunk >> 13) & 0x03; + has_large_delta_ = delta_size >= kLarge; + all_same_ = true; + // To make it consistent with Add function, populate delta_sizes_ beyond 1st. + for (size_t i = 0; i < std::min<size_t>(size_, kMaxVectorCapacity); ++i) + delta_sizes_[i] = delta_size; +} + +TransportFeedback::TransportFeedback() + : TransportFeedback(/*include_timestamps=*/true) {} + +TransportFeedback::TransportFeedback(bool include_timestamps) + : base_seq_no_(0), + num_seq_no_(0), + base_time_ticks_(0), + feedback_seq_(0), + include_timestamps_(include_timestamps), + last_timestamp_(Timestamp::Zero()), + size_bytes_(kTransportFeedbackHeaderSizeBytes) {} + +TransportFeedback::TransportFeedback(const TransportFeedback&) = default; + +TransportFeedback::TransportFeedback(TransportFeedback&& other) + : base_seq_no_(other.base_seq_no_), + num_seq_no_(other.num_seq_no_), + base_time_ticks_(other.base_time_ticks_), + feedback_seq_(other.feedback_seq_), + include_timestamps_(other.include_timestamps_), + last_timestamp_(other.last_timestamp_), + received_packets_(std::move(other.received_packets_)), + all_packets_(std::move(other.all_packets_)), + encoded_chunks_(std::move(other.encoded_chunks_)), + last_chunk_(other.last_chunk_), + size_bytes_(other.size_bytes_) { + other.Clear(); +} + +TransportFeedback::~TransportFeedback() {} + +void TransportFeedback::SetBase(uint16_t base_sequence, + Timestamp ref_timestamp) { + RTC_DCHECK_EQ(num_seq_no_, 0); + base_seq_no_ = base_sequence; + base_time_ticks_ = + (ref_timestamp.us() % kTimeWrapPeriod.us()) / kBaseTimeTick.us(); + last_timestamp_ = BaseTime(); +} + +void TransportFeedback::SetFeedbackSequenceNumber(uint8_t feedback_sequence) { + feedback_seq_ = feedback_sequence; +} + +bool TransportFeedback::AddReceivedPacket(uint16_t sequence_number, + Timestamp timestamp) { + // Set delta to zero if timestamps are not included, this will simplify the + // encoding process. + int16_t delta = 0; + if (include_timestamps_) { + // Convert to ticks and round. + if (last_timestamp_ > timestamp) { + timestamp += (last_timestamp_ - timestamp).RoundUpTo(kTimeWrapPeriod); + } + RTC_DCHECK_GE(timestamp, last_timestamp_); + int64_t delta_full = + (timestamp - last_timestamp_).us() % kTimeWrapPeriod.us(); + if (delta_full > kTimeWrapPeriod.us() / 2) { + delta_full -= kTimeWrapPeriod.us(); + delta_full -= kDeltaTick.us() / 2; + } else { + delta_full += kDeltaTick.us() / 2; + } + delta_full /= kDeltaTick.us(); + + delta = static_cast<int16_t>(delta_full); + // If larger than 16bit signed, we can't represent it - need new fb packet. + if (delta != delta_full) { + RTC_LOG(LS_WARNING) << "Delta value too large ( >= 2^16 ticks )"; + return false; + } + } + + uint16_t next_seq_no = base_seq_no_ + num_seq_no_; + if (sequence_number != next_seq_no) { + uint16_t last_seq_no = next_seq_no - 1; + if (!IsNewerSequenceNumber(sequence_number, last_seq_no)) + return false; + uint16_t num_missing_packets = sequence_number - next_seq_no; + if (!AddMissingPackets(num_missing_packets)) + return false; + } + + DeltaSize delta_size = (delta >= 0 && delta <= 0xff) ? 1 : 2; + if (!AddDeltaSize(delta_size)) + return false; + + received_packets_.emplace_back(sequence_number, delta); + last_timestamp_ += delta * kDeltaTick; + if (include_timestamps_) { + size_bytes_ += delta_size; + } + return true; +} + +const std::vector<TransportFeedback::ReceivedPacket>& +TransportFeedback::GetReceivedPackets() const { + return received_packets_; +} + +void TransportFeedback::ForAllPackets( + rtc::FunctionView<void(uint16_t, TimeDelta)> handler) const { + TimeDelta delta_since_base = TimeDelta::Zero(); + auto received_it = received_packets_.begin(); + const uint16_t last_seq_num = base_seq_no_ + num_seq_no_; + for (uint16_t seq_num = base_seq_no_; seq_num != last_seq_num; ++seq_num) { + if (received_it != received_packets_.end() && + received_it->sequence_number() == seq_num) { + delta_since_base += received_it->delta(); + handler(seq_num, delta_since_base); + ++received_it; + } else { + handler(seq_num, TimeDelta::PlusInfinity()); + } + } + RTC_DCHECK(received_it == received_packets_.end()); +} + +uint16_t TransportFeedback::GetBaseSequence() const { + return base_seq_no_; +} + +Timestamp TransportFeedback::BaseTime() const { + // Add an extra kTimeWrapPeriod to allow add received packets arrived earlier + // than the first added packet (and thus allow to record negative deltas) + // even when base_time_ticks_ == 0. + return Timestamp::Zero() + kTimeWrapPeriod + + int64_t{base_time_ticks_} * kBaseTimeTick; +} + +TimeDelta TransportFeedback::GetBaseDelta(Timestamp prev_timestamp) const { + TimeDelta delta = BaseTime() - prev_timestamp; + // Compensate for wrap around. + if ((delta - kTimeWrapPeriod).Abs() < delta.Abs()) { + delta -= kTimeWrapPeriod; // Wrap backwards. + } else if ((delta + kTimeWrapPeriod).Abs() < delta.Abs()) { + delta += kTimeWrapPeriod; // Wrap forwards. + } + return delta; +} + +// De-serialize packet. +bool TransportFeedback::Parse(const CommonHeader& packet) { + RTC_DCHECK_EQ(packet.type(), kPacketType); + RTC_DCHECK_EQ(packet.fmt(), kFeedbackMessageType); + TRACE_EVENT0("webrtc", "TransportFeedback::Parse"); + + if (packet.payload_size_bytes() < kMinPayloadSizeBytes) { + RTC_LOG(LS_WARNING) << "Buffer too small (" << packet.payload_size_bytes() + << " bytes) to fit a " + "FeedbackPacket. Minimum size = " + << kMinPayloadSizeBytes; + return false; + } + + const uint8_t* const payload = packet.payload(); + ParseCommonFeedback(payload); + + base_seq_no_ = ByteReader<uint16_t>::ReadBigEndian(&payload[8]); + uint16_t status_count = ByteReader<uint16_t>::ReadBigEndian(&payload[10]); + base_time_ticks_ = ByteReader<uint32_t, 3>::ReadBigEndian(&payload[12]); + feedback_seq_ = payload[15]; + Clear(); + size_t index = 16; + const size_t end_index = packet.payload_size_bytes(); + + if (status_count == 0) { + RTC_LOG(LS_WARNING) << "Empty feedback messages not allowed."; + return false; + } + + std::vector<uint8_t> delta_sizes; + delta_sizes.reserve(status_count); + while (delta_sizes.size() < status_count) { + if (index + kChunkSizeBytes > end_index) { + RTC_LOG(LS_WARNING) << "Buffer overflow while parsing packet."; + Clear(); + return false; + } + + uint16_t chunk = ByteReader<uint16_t>::ReadBigEndian(&payload[index]); + index += kChunkSizeBytes; + encoded_chunks_.push_back(chunk); + last_chunk_.Decode(chunk, status_count - delta_sizes.size()); + last_chunk_.AppendTo(&delta_sizes); + } + // Last chunk is stored in the `last_chunk_`. + encoded_chunks_.pop_back(); + RTC_DCHECK_EQ(delta_sizes.size(), status_count); + num_seq_no_ = status_count; + + uint16_t seq_no = base_seq_no_; + size_t recv_delta_size = absl::c_accumulate(delta_sizes, 0); + + // Determine if timestamps, that is, recv_delta are included in the packet. + if (end_index >= index + recv_delta_size) { + for (size_t delta_size : delta_sizes) { + RTC_DCHECK_LE(index + delta_size, end_index); + switch (delta_size) { + case 0: + break; + case 1: { + int16_t delta = payload[index]; + received_packets_.emplace_back(seq_no, delta); + last_timestamp_ += delta * kDeltaTick; + index += delta_size; + break; + } + case 2: { + int16_t delta = ByteReader<int16_t>::ReadBigEndian(&payload[index]); + received_packets_.emplace_back(seq_no, delta); + last_timestamp_ += delta * kDeltaTick; + index += delta_size; + break; + } + case 3: + Clear(); + RTC_LOG(LS_WARNING) << "Invalid delta_size for seq_no " << seq_no; + + return false; + default: + RTC_DCHECK_NOTREACHED(); + break; + } + ++seq_no; + } + } else { + // The packet does not contain receive deltas. + include_timestamps_ = false; + for (size_t delta_size : delta_sizes) { + // Use delta sizes to detect if packet was received. + if (delta_size > 0) { + received_packets_.emplace_back(seq_no, 0); + } + ++seq_no; + } + } + size_bytes_ = RtcpPacket::kHeaderLength + index; + RTC_DCHECK_LE(index, end_index); + return true; +} + +std::unique_ptr<TransportFeedback> TransportFeedback::ParseFrom( + const uint8_t* buffer, + size_t length) { + CommonHeader header; + if (!header.Parse(buffer, length)) + return nullptr; + if (header.type() != kPacketType || header.fmt() != kFeedbackMessageType) + return nullptr; + std::unique_ptr<TransportFeedback> parsed(new TransportFeedback); + if (!parsed->Parse(header)) + return nullptr; + return parsed; +} + +bool TransportFeedback::IsConsistent() const { + size_t packet_size = kTransportFeedbackHeaderSizeBytes; + std::vector<DeltaSize> delta_sizes; + LastChunk chunk_decoder; + for (uint16_t chunk : encoded_chunks_) { + chunk_decoder.Decode(chunk, kMaxReportedPackets); + chunk_decoder.AppendTo(&delta_sizes); + packet_size += kChunkSizeBytes; + } + if (!last_chunk_.Empty()) { + last_chunk_.AppendTo(&delta_sizes); + packet_size += kChunkSizeBytes; + } + if (num_seq_no_ != delta_sizes.size()) { + RTC_LOG(LS_ERROR) << delta_sizes.size() << " packets encoded. Expected " + << num_seq_no_; + return false; + } + Timestamp timestamp = BaseTime(); + auto packet_it = received_packets_.begin(); + uint16_t seq_no = base_seq_no_; + for (DeltaSize delta_size : delta_sizes) { + if (delta_size > 0) { + if (packet_it == received_packets_.end()) { + RTC_LOG(LS_ERROR) << "Failed to find delta for seq_no " << seq_no; + return false; + } + if (packet_it->sequence_number() != seq_no) { + RTC_LOG(LS_ERROR) << "Expected to find delta for seq_no " << seq_no + << ". Next delta is for " + << packet_it->sequence_number(); + return false; + } + if (delta_size == 1 && + (packet_it->delta_ticks() < 0 || packet_it->delta_ticks() > 0xff)) { + RTC_LOG(LS_ERROR) << "Delta " << packet_it->delta_ticks() + << " for seq_no " << seq_no + << " doesn't fit into one byte"; + return false; + } + timestamp += packet_it->delta(); + ++packet_it; + } + if (include_timestamps_) { + packet_size += delta_size; + } + ++seq_no; + } + if (packet_it != received_packets_.end()) { + RTC_LOG(LS_ERROR) << "Unencoded delta for seq_no " + << packet_it->sequence_number(); + return false; + } + if (timestamp != last_timestamp_) { + RTC_LOG(LS_ERROR) << "Last timestamp mismatch. Calculated: " + << ToLogString(timestamp) + << ". Saved: " << ToLogString(last_timestamp_); + return false; + } + if (size_bytes_ != packet_size) { + RTC_LOG(LS_ERROR) << "Rtcp packet size mismatch. Calculated: " + << packet_size << ". Saved: " << size_bytes_; + return false; + } + return true; +} + +size_t TransportFeedback::BlockLength() const { + // Round size_bytes_ up to multiple of 32bits. + return (size_bytes_ + 3) & (~static_cast<size_t>(3)); +} + +size_t TransportFeedback::PaddingLength() const { + return BlockLength() - size_bytes_; +} + +// Serialize packet. +bool TransportFeedback::Create(uint8_t* packet, + size_t* position, + size_t max_length, + PacketReadyCallback callback) const { + if (num_seq_no_ == 0) + return false; + + while (*position + BlockLength() > max_length) { + if (!OnBufferFull(packet, position, callback)) + return false; + } + const size_t position_end = *position + BlockLength(); + const size_t padding_length = PaddingLength(); + bool has_padding = padding_length > 0; + CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), has_padding, + packet, position); + CreateCommonFeedback(packet + *position); + *position += kCommonFeedbackLength; + + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], base_seq_no_); + *position += 2; + + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], num_seq_no_); + *position += 2; + + ByteWriter<uint32_t, 3>::WriteBigEndian(&packet[*position], base_time_ticks_); + *position += 3; + + packet[(*position)++] = feedback_seq_; + + for (uint16_t chunk : encoded_chunks_) { + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], chunk); + *position += 2; + } + if (!last_chunk_.Empty()) { + uint16_t chunk = last_chunk_.EncodeLast(); + ByteWriter<uint16_t>::WriteBigEndian(&packet[*position], chunk); + *position += 2; + } + + if (include_timestamps_) { + for (const auto& received_packet : received_packets_) { + int16_t delta = received_packet.delta_ticks(); + if (delta >= 0 && delta <= 0xFF) { + packet[(*position)++] = delta; + } else { + ByteWriter<int16_t>::WriteBigEndian(&packet[*position], delta); + *position += 2; + } + } + } + + if (padding_length > 0) { + for (size_t i = 0; i < padding_length - 1; ++i) { + packet[(*position)++] = 0; + } + packet[(*position)++] = padding_length; + } + RTC_DCHECK_EQ(*position, position_end); + return true; +} + +void TransportFeedback::Clear() { + num_seq_no_ = 0; + last_timestamp_ = BaseTime(); + received_packets_.clear(); + all_packets_.clear(); + encoded_chunks_.clear(); + last_chunk_.Clear(); + size_bytes_ = kTransportFeedbackHeaderSizeBytes; +} + +bool TransportFeedback::AddDeltaSize(DeltaSize delta_size) { + if (num_seq_no_ == kMaxReportedPackets) + return false; + size_t add_chunk_size = last_chunk_.Empty() ? kChunkSizeBytes : 0; + if (size_bytes_ + delta_size + add_chunk_size > kMaxSizeBytes) + return false; + + if (last_chunk_.CanAdd(delta_size)) { + size_bytes_ += add_chunk_size; + last_chunk_.Add(delta_size); + ++num_seq_no_; + return true; + } + if (size_bytes_ + delta_size + kChunkSizeBytes > kMaxSizeBytes) + return false; + + encoded_chunks_.push_back(last_chunk_.Emit()); + size_bytes_ += kChunkSizeBytes; + last_chunk_.Add(delta_size); + ++num_seq_no_; + return true; +} + +bool TransportFeedback::AddMissingPackets(size_t num_missing_packets) { + size_t new_num_seq_no = num_seq_no_ + num_missing_packets; + if (new_num_seq_no > kMaxReportedPackets) { + return false; + } + + if (!last_chunk_.Empty()) { + while (num_missing_packets > 0 && last_chunk_.CanAdd(0)) { + last_chunk_.Add(0); + --num_missing_packets; + } + if (num_missing_packets == 0) { + num_seq_no_ = new_num_seq_no; + return true; + } + encoded_chunks_.push_back(last_chunk_.Emit()); + } + RTC_DCHECK(last_chunk_.Empty()); + size_t full_chunks = num_missing_packets / LastChunk::kMaxRunLengthCapacity; + size_t partial_chunk = num_missing_packets % LastChunk::kMaxRunLengthCapacity; + size_t num_chunks = full_chunks + (partial_chunk > 0 ? 1 : 0); + if (size_bytes_ + kChunkSizeBytes * num_chunks > kMaxSizeBytes) { + num_seq_no_ = (new_num_seq_no - num_missing_packets); + return false; + } + size_bytes_ += kChunkSizeBytes * num_chunks; + // T = 0, S = 0, run length = kMaxRunLengthCapacity, see EncodeRunLength(). + encoded_chunks_.insert(encoded_chunks_.end(), full_chunks, + LastChunk::kMaxRunLengthCapacity); + last_chunk_.AddMissingPackets(partial_chunk); + num_seq_no_ = new_num_seq_no; + return true; +} +} // namespace rtcp +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h new file mode 100644 index 0000000000..4d17b54c3a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h @@ -0,0 +1,185 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ + +#include <array> +#include <memory> +#include <vector> + +#include "absl/base/attributes.h" +#include "api/function_view.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtcp_packet/rtpfb.h" + +namespace webrtc { +namespace rtcp { +class CommonHeader; + +class TransportFeedback : public Rtpfb { + public: + class ReceivedPacket { + public: + ReceivedPacket(uint16_t sequence_number, int16_t delta_ticks) + : sequence_number_(sequence_number), delta_ticks_(delta_ticks) {} + ReceivedPacket(const ReceivedPacket&) = default; + ReceivedPacket& operator=(const ReceivedPacket&) = default; + + uint16_t sequence_number() const { return sequence_number_; } + int16_t delta_ticks() const { return delta_ticks_; } + TimeDelta delta() const { return delta_ticks_ * kDeltaTick; } + + private: + uint16_t sequence_number_; + int16_t delta_ticks_; + }; + // TODO(sprang): IANA reg? + static constexpr uint8_t kFeedbackMessageType = 15; + // Convert to multiples of 0.25ms. + static constexpr TimeDelta kDeltaTick = TimeDelta::Micros(250); + // Maximum number of packets (including missing) TransportFeedback can report. + static constexpr size_t kMaxReportedPackets = 0xffff; + + TransportFeedback(); + + // If `include_timestamps` is set to false, the created packet will not + // contain the receive delta block. + explicit TransportFeedback(bool include_timestamps); + TransportFeedback(const TransportFeedback&); + TransportFeedback(TransportFeedback&&); + + ~TransportFeedback() override; + + void SetBase(uint16_t base_sequence, // Seq# of first packet in this msg. + Timestamp ref_timestamp); // Reference timestamp for this msg. + + void SetFeedbackSequenceNumber(uint8_t feedback_sequence); + // NOTE: This method requires increasing sequence numbers (excepting wraps). + bool AddReceivedPacket(uint16_t sequence_number, Timestamp timestamp); + const std::vector<ReceivedPacket>& GetReceivedPackets() const; + + // Calls `handler` for all packets this feedback describes. + // For received packets pass receieve time as `delta_since_base` since the + // `BaseTime()`. For missed packets calls `handler` with `delta_since_base = + // PlusInfinity()`. + void ForAllPackets( + rtc::FunctionView<void(uint16_t sequence_number, + TimeDelta delta_since_base)> handler) const; + + uint16_t GetBaseSequence() const; + + // Returns number of packets (including missing) this feedback describes. + size_t GetPacketStatusCount() const { return num_seq_no_; } + + // Get the reference time including any precision loss. + Timestamp BaseTime() const; + + // Get the unwrapped delta between current base time and `prev_timestamp`. + TimeDelta GetBaseDelta(Timestamp prev_timestamp) const; + + // Does the feedback packet contain timestamp information? + bool IncludeTimestamps() const { return include_timestamps_; } + + bool Parse(const CommonHeader& packet); + static std::unique_ptr<TransportFeedback> ParseFrom(const uint8_t* buffer, + size_t length); + // Pre and postcondition for all public methods. Should always return true. + // This function is for tests. + bool IsConsistent() const; + + size_t BlockLength() const override; + size_t PaddingLength() const; + + bool Create(uint8_t* packet, + size_t* position, + size_t max_length, + PacketReadyCallback callback) const override; + + private: + // Size in bytes of a delta time in rtcp packet. + // Valid values are 0 (packet wasn't received), 1 or 2. + using DeltaSize = uint8_t; + // Keeps DeltaSizes that can be encoded into single chunk if it is last chunk. + class LastChunk { + public: + using DeltaSize = TransportFeedback::DeltaSize; + static constexpr size_t kMaxRunLengthCapacity = 0x1fff; + + LastChunk(); + + bool Empty() const; + void Clear(); + // Return if delta sizes still can be encoded into single chunk with added + // `delta_size`. + bool CanAdd(DeltaSize delta_size) const; + // Add `delta_size`, assumes `CanAdd(delta_size)`, + void Add(DeltaSize delta_size); + // Equivalent to calling Add(0) `num_missing` times. Assumes `Empty()`. + void AddMissingPackets(size_t num_missing); + + // Encode chunk as large as possible removing encoded delta sizes. + // Assume CanAdd() == false for some valid delta_size. + uint16_t Emit(); + // Encode all stored delta_sizes into single chunk, pad with 0s if needed. + uint16_t EncodeLast() const; + + // Decode up to `max_size` delta sizes from `chunk`. + void Decode(uint16_t chunk, size_t max_size); + // Appends content of the Lastchunk to `deltas`. + void AppendTo(std::vector<DeltaSize>* deltas) const; + + private: + static constexpr size_t kMaxOneBitCapacity = 14; + static constexpr size_t kMaxTwoBitCapacity = 7; + static constexpr size_t kMaxVectorCapacity = kMaxOneBitCapacity; + static constexpr DeltaSize kLarge = 2; + + uint16_t EncodeOneBit() const; + void DecodeOneBit(uint16_t chunk, size_t max_size); + + uint16_t EncodeTwoBit(size_t size) const; + void DecodeTwoBit(uint16_t chunk, size_t max_size); + + uint16_t EncodeRunLength() const; + void DecodeRunLength(uint16_t chunk, size_t max_size); + + std::array<DeltaSize, kMaxVectorCapacity> delta_sizes_; + size_t size_; + bool all_same_; + bool has_large_delta_; + }; + + // Reset packet to consistent empty state. + void Clear(); + + bool AddDeltaSize(DeltaSize delta_size); + // Adds `num_missing_packets` deltas of size 0. + bool AddMissingPackets(size_t num_missing_packets); + + uint16_t base_seq_no_; + uint16_t num_seq_no_; + uint32_t base_time_ticks_; + uint8_t feedback_seq_; + bool include_timestamps_; + + Timestamp last_timestamp_; + std::vector<ReceivedPacket> received_packets_; + std::vector<ReceivedPacket> all_packets_; + // All but last encoded packet chunks. + std::vector<uint16_t> encoded_chunks_; + LastChunk last_chunk_; + size_t size_bytes_; +}; + +} // namespace rtcp +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc new file mode 100644 index 0000000000..4248a4d3ee --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc @@ -0,0 +1,667 @@ +/* + * 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/transport_feedback.h" + +#include <limits> +#include <memory> +#include <utility> + +#include "api/array_view.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using rtcp::TransportFeedback; +using ::testing::AllOf; +using ::testing::Each; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::Ne; +using ::testing::Property; +using ::testing::SizeIs; + +constexpr int kHeaderSize = 20; +constexpr int kStatusChunkSize = 2; +constexpr int kSmallDeltaSize = 1; +constexpr int kLargeDeltaSize = 2; + +constexpr TimeDelta kDeltaLimit = 0xFF * TransportFeedback::kDeltaTick; +constexpr TimeDelta kBaseTimeTick = TransportFeedback::kDeltaTick * (1 << 8); +constexpr TimeDelta kBaseTimeWrapPeriod = kBaseTimeTick * (1 << 24); + +MATCHER_P2(Near, value, max_abs_error, "") { + return value - max_abs_error <= arg && arg <= value + max_abs_error; +} + +MATCHER(IsValidFeedback, "") { + rtcp::CommonHeader rtcp_header; + TransportFeedback feedback; + return rtcp_header.Parse(std::data(arg), std::size(arg)) && + rtcp_header.type() == TransportFeedback::kPacketType && + rtcp_header.fmt() == TransportFeedback::kFeedbackMessageType && + feedback.Parse(rtcp_header); +} + +TransportFeedback Parse(rtc::ArrayView<const uint8_t> buffer) { + rtcp::CommonHeader header; + RTC_DCHECK(header.Parse(buffer.data(), buffer.size())); + RTC_DCHECK_EQ(header.type(), TransportFeedback::kPacketType); + RTC_DCHECK_EQ(header.fmt(), TransportFeedback::kFeedbackMessageType); + TransportFeedback feedback; + RTC_DCHECK(feedback.Parse(header)); + return feedback; +} + +class FeedbackTester { + public: + FeedbackTester() : FeedbackTester(true) {} + explicit FeedbackTester(bool include_timestamps) + : expected_size_(kAnySize), + default_delta_(TransportFeedback::kDeltaTick * 4), + include_timestamps_(include_timestamps) {} + + void WithExpectedSize(size_t expected_size) { + expected_size_ = expected_size; + } + + void WithDefaultDelta(TimeDelta delta) { default_delta_ = delta; } + + void WithInput(rtc::ArrayView<const uint16_t> received_seq, + rtc::ArrayView<const Timestamp> received_ts = {}) { + std::vector<Timestamp> temp_timestamps; + if (received_ts.empty()) { + temp_timestamps = GenerateReceiveTimestamps(received_seq); + received_ts = temp_timestamps; + } + RTC_DCHECK_EQ(received_seq.size(), received_ts.size()); + + expected_deltas_.clear(); + feedback_.emplace(include_timestamps_); + feedback_->SetBase(received_seq[0], received_ts[0]); + ASSERT_TRUE(feedback_->IsConsistent()); + // First delta is special: it doesn't represent the delta between two times, + // but a compensation for the reduced precision of the base time. + EXPECT_TRUE(feedback_->AddReceivedPacket(received_seq[0], received_ts[0])); + // GetBaseDelta suppose to return balanced diff between base time of the new + // feedback message (stored internally) and base time of the old feedback + // message (passed as parameter), but first delta is the difference between + // 1st timestamp (passed as parameter) and base time (stored internally), + // thus to get the first delta need to negate whatever GetBaseDelta returns. + expected_deltas_.push_back(-feedback_->GetBaseDelta(received_ts[0])); + + for (size_t i = 1; i < received_ts.size(); ++i) { + EXPECT_TRUE( + feedback_->AddReceivedPacket(received_seq[i], received_ts[i])); + expected_deltas_.push_back(received_ts[i] - received_ts[i - 1]); + } + ASSERT_TRUE(feedback_->IsConsistent()); + expected_seq_.assign(received_seq.begin(), received_seq.end()); + } + + void VerifyPacket() { + ASSERT_TRUE(feedback_->IsConsistent()); + serialized_ = feedback_->Build(); + VerifyInternal(); + + feedback_.emplace(Parse(serialized_)); + ASSERT_TRUE(feedback_->IsConsistent()); + EXPECT_EQ(include_timestamps_, feedback_->IncludeTimestamps()); + VerifyInternal(); + } + + static constexpr size_t kAnySize = static_cast<size_t>(0) - 1; + + private: + void VerifyInternal() { + if (expected_size_ != kAnySize) { + // Round up to whole 32-bit words. + size_t expected_size_words = (expected_size_ + 3) / 4; + size_t expected_size_bytes = expected_size_words * 4; + EXPECT_EQ(expected_size_bytes, serialized_.size()); + } + + std::vector<uint16_t> actual_seq_nos; + std::vector<TimeDelta> actual_deltas; + for (const auto& packet : feedback_->GetReceivedPackets()) { + actual_seq_nos.push_back(packet.sequence_number()); + actual_deltas.push_back(packet.delta()); + } + EXPECT_THAT(actual_seq_nos, ElementsAreArray(expected_seq_)); + if (include_timestamps_) { + EXPECT_THAT(actual_deltas, ElementsAreArray(expected_deltas_)); + } + } + + std::vector<Timestamp> GenerateReceiveTimestamps( + rtc::ArrayView<const uint16_t> seq_nums) { + RTC_DCHECK(!seq_nums.empty()); + uint16_t last_seq = seq_nums[0]; + Timestamp time = Timestamp::Zero(); + std::vector<Timestamp> result; + + for (uint16_t seq : seq_nums) { + if (seq < last_seq) + time += 0x10000 * default_delta_; + last_seq = seq; + + result.push_back(time + last_seq * default_delta_); + } + return result; + } + + std::vector<uint16_t> expected_seq_; + std::vector<TimeDelta> expected_deltas_; + size_t expected_size_; + TimeDelta default_delta_; + absl::optional<TransportFeedback> feedback_; + rtc::Buffer serialized_; + bool include_timestamps_; +}; + +// The following tests use FeedbackTester that simulates received packets as +// specified by the parameters `received_seq[]` and `received_ts[]` (optional). +// The following is verified in these tests: +// - Expected size of serialized packet. +// - Expected sequence numbers and receive deltas. +// - Sequence numbers and receive deltas are persistent after serialization +// followed by parsing. +// - The internal state of a feedback packet is consistent. +TEST(RtcpPacketTest, TransportFeedbackOneBitVector) { + const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackOneBitVectorNoRecvDelta) { + const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13}; + const size_t kExpectedSizeBytes = kHeaderSize + kStatusChunkSize; + + FeedbackTester test(/*include_timestamps=*/false); + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackFullOneBitVector) { + const uint16_t kReceived[] = {1, 2, 7, 8, 9, 10, 13, 14}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackOneBitVectorWrapReceived) { + const uint16_t kMax = 0xFFFF; + const uint16_t kReceived[] = {kMax - 2, kMax - 1, kMax, 0, 1, 2}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackOneBitVectorWrapMissing) { + const uint16_t kMax = 0xFFFF; + const uint16_t kReceived[] = {kMax - 2, kMax - 1, 1, 2}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + (kLength * kSmallDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackTwoBitVector) { + const uint16_t kReceived[] = {1, 2, 6, 7}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + (kLength * kLargeDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaTick); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackTwoBitVectorFull) { + const uint16_t kReceived[] = {1, 2, 6, 7, 8}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + (2 * kStatusChunkSize) + (kLength * kLargeDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithDefaultDelta(kDeltaLimit + TransportFeedback::kDeltaTick); + test.WithInput(kReceived); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackWithLargeBaseTimeIsConsistent) { + TransportFeedback tb; + constexpr Timestamp kTimestamp = + Timestamp::Zero() + int64_t{0x7fff'ffff} * TransportFeedback::kDeltaTick; + tb.SetBase(/*base_sequence=*/0, /*ref_timestamp=*/kTimestamp); + tb.AddReceivedPacket(/*base_sequence=*/0, /*ref_timestamp=*/kTimestamp); + EXPECT_TRUE(tb.IsConsistent()); +} + +TEST(RtcpPacketTest, TransportFeedbackLargeAndNegativeDeltas) { + const uint16_t kReceived[] = {1, 2, 6, 7, 8}; + const Timestamp kReceiveTimes[] = { + Timestamp::Millis(2), Timestamp::Millis(1), Timestamp::Millis(4), + Timestamp::Millis(3), + Timestamp::Millis(3) + TransportFeedback::kDeltaTick * (1 << 8)}; + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + (3 * kLargeDeltaSize) + kSmallDeltaSize; + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived, kReceiveTimes); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackMaxRle) { + // Expected chunks created: + // * 1-bit vector chunk (1xreceived + 13xdropped) + // * RLE chunk of max length for dropped symbol + // * 1-bit vector chunk (1xreceived + 13xdropped) + + const size_t kPacketCount = (1 << 13) - 1 + 14; + const uint16_t kReceived[] = {0, kPacketCount}; + const Timestamp kReceiveTimes[] = {Timestamp::Millis(1), + Timestamp::Millis(2)}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + (3 * kStatusChunkSize) + (kLength * kSmallDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived, kReceiveTimes); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackMinRle) { + // Expected chunks created: + // * 1-bit vector chunk (1xreceived + 13xdropped) + // * RLE chunk of length 15 for dropped symbol + // * 1-bit vector chunk (1xreceived + 13xdropped) + + const uint16_t kReceived[] = {0, (14 * 2) + 1}; + const Timestamp kReceiveTimes[] = {Timestamp::Millis(1), + Timestamp::Millis(2)}; + const size_t kLength = sizeof(kReceived) / sizeof(uint16_t); + const size_t kExpectedSizeBytes = + kHeaderSize + (3 * kStatusChunkSize) + (kLength * kSmallDeltaSize); + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived, kReceiveTimes); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVector) { + const size_t kTwoBitVectorCapacity = 7; + const uint16_t kReceived[] = {0, kTwoBitVectorCapacity - 1}; + const Timestamp kReceiveTimes[] = { + Timestamp::Zero(), + Timestamp::Zero() + kDeltaLimit + TransportFeedback::kDeltaTick}; + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + kSmallDeltaSize + kLargeDeltaSize; + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived, kReceiveTimes); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSimpleSplit) { + const size_t kTwoBitVectorCapacity = 7; + const uint16_t kReceived[] = {0, kTwoBitVectorCapacity}; + const Timestamp kReceiveTimes[] = { + Timestamp::Zero(), + Timestamp::Zero() + kDeltaLimit + TransportFeedback::kDeltaTick}; + const size_t kExpectedSizeBytes = + kHeaderSize + (kStatusChunkSize * 2) + kSmallDeltaSize + kLargeDeltaSize; + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived, kReceiveTimes); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackOneToTwoBitVectorSplit) { + // With received small delta = S, received large delta = L, use input + // SSSSSSSSLSSSSSSSSSSSS. This will cause a 1:2 split at the L. + // After split there will be two symbols in symbol_vec: SL. + + const TimeDelta kLargeDelta = TransportFeedback::kDeltaTick * (1 << 8); + const size_t kNumPackets = (3 * 7) + 1; + const size_t kExpectedSizeBytes = kHeaderSize + (kStatusChunkSize * 3) + + (kSmallDeltaSize * (kNumPackets - 1)) + + (kLargeDeltaSize * 1); + + uint16_t kReceived[kNumPackets]; + for (size_t i = 0; i < kNumPackets; ++i) + kReceived[i] = i; + + std::vector<Timestamp> receive_times; + receive_times.reserve(kNumPackets); + receive_times.push_back(Timestamp::Millis(1)); + for (size_t i = 1; i < kNumPackets; ++i) { + TimeDelta delta = (i == 8) ? kLargeDelta : TimeDelta::Millis(1); + receive_times.push_back(receive_times.back() + delta); + } + + FeedbackTester test; + test.WithExpectedSize(kExpectedSizeBytes); + test.WithInput(kReceived, receive_times); + test.VerifyPacket(); +} + +TEST(RtcpPacketTest, TransportFeedbackAliasing) { + TransportFeedback feedback; + feedback.SetBase(0, Timestamp::Zero()); + + const int kSamples = 100; + const TimeDelta kTooSmallDelta = TransportFeedback::kDeltaTick / 3; + + for (int i = 0; i < kSamples; ++i) + feedback.AddReceivedPacket(i, Timestamp::Zero() + i * kTooSmallDelta); + + feedback.Build(); + + TimeDelta accumulated_delta = TimeDelta::Zero(); + int num_samples = 0; + for (const auto& packet : feedback.GetReceivedPackets()) { + accumulated_delta += packet.delta(); + TimeDelta expected_time = num_samples * kTooSmallDelta; + ++num_samples; + + EXPECT_THAT(accumulated_delta, + Near(expected_time, TransportFeedback::kDeltaTick / 2)); + } +} + +TEST(RtcpPacketTest, TransportFeedbackLimits) { + // Sequence number wrap above 0x8000. + std::unique_ptr<TransportFeedback> packet(new TransportFeedback()); + packet->SetBase(0, Timestamp::Zero()); + EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero())); + EXPECT_TRUE(packet->AddReceivedPacket(0x8000, Timestamp::Millis(1))); + + packet.reset(new TransportFeedback()); + packet->SetBase(0, Timestamp::Zero()); + EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero())); + EXPECT_FALSE(packet->AddReceivedPacket(0x8000 + 1, Timestamp::Millis(1))); + + // Packet status count max 0xFFFF. + packet.reset(new TransportFeedback()); + packet->SetBase(0, Timestamp::Zero()); + EXPECT_TRUE(packet->AddReceivedPacket(0x0, Timestamp::Zero())); + EXPECT_TRUE(packet->AddReceivedPacket(0x8000, Timestamp::Millis(1))); + EXPECT_TRUE(packet->AddReceivedPacket(0xFFFE, Timestamp::Millis(2))); + EXPECT_FALSE(packet->AddReceivedPacket(0xFFFF, Timestamp::Millis(3))); + + // Too large delta. + packet.reset(new TransportFeedback()); + packet->SetBase(0, Timestamp::Zero()); + TimeDelta kMaxPositiveTimeDelta = + std::numeric_limits<int16_t>::max() * TransportFeedback::kDeltaTick; + EXPECT_FALSE(packet->AddReceivedPacket(1, Timestamp::Zero() + + kMaxPositiveTimeDelta + + TransportFeedback::kDeltaTick)); + EXPECT_TRUE( + packet->AddReceivedPacket(1, Timestamp::Zero() + kMaxPositiveTimeDelta)); + + // Too large negative delta. + packet.reset(new TransportFeedback()); + TimeDelta kMaxNegativeTimeDelta = + std::numeric_limits<int16_t>::min() * TransportFeedback::kDeltaTick; + // Use larger base time to avoid kBaseTime + kNegativeDelta to be negative. + Timestamp kBaseTime = Timestamp::Seconds(1'000'000); + packet->SetBase(0, kBaseTime); + EXPECT_FALSE(packet->AddReceivedPacket( + 1, kBaseTime + kMaxNegativeTimeDelta - TransportFeedback::kDeltaTick)); + EXPECT_TRUE(packet->AddReceivedPacket(1, kBaseTime + kMaxNegativeTimeDelta)); + + // TODO(sprang): Once we support max length lower than RTCP length limit, + // add back test for max size in bytes. +} + +TEST(RtcpPacketTest, BaseTimeIsConsistentAcrossMultiplePackets) { + constexpr Timestamp kMaxBaseTime = + Timestamp::Zero() + kBaseTimeWrapPeriod - kBaseTimeTick; + + TransportFeedback packet1; + packet1.SetBase(0, kMaxBaseTime); + packet1.AddReceivedPacket(0, kMaxBaseTime); + // Build and parse packet to simulate sending it over the wire. + TransportFeedback parsed_packet1 = Parse(packet1.Build()); + + TransportFeedback packet2; + packet2.SetBase(1, kMaxBaseTime + kBaseTimeTick); + packet2.AddReceivedPacket(1, kMaxBaseTime + kBaseTimeTick); + TransportFeedback parsed_packet2 = Parse(packet2.Build()); + + EXPECT_EQ(parsed_packet2.GetBaseDelta(parsed_packet1.BaseTime()), + kBaseTimeTick); +} + +TEST(RtcpPacketTest, SupportsMaximumNumberOfNegativeDeltas) { + TransportFeedback feedback; + // Use large base time to avoid hitting zero limit while filling the feedback, + // but use multiple of kBaseTimeWrapPeriod to hit edge case where base time + // is recorded as zero in the raw rtcp packet. + Timestamp time = Timestamp::Zero() + 1'000 * kBaseTimeWrapPeriod; + feedback.SetBase(0, time); + static constexpr TimeDelta kMinDelta = + TransportFeedback::kDeltaTick * std::numeric_limits<int16_t>::min(); + uint16_t num_received_rtp_packets = 0; + time += kMinDelta; + while (feedback.AddReceivedPacket(++num_received_rtp_packets, time)) { + ASSERT_GE(time, Timestamp::Zero() - kMinDelta); + time += kMinDelta; + } + // Subtract one last packet that failed to add. + --num_received_rtp_packets; + EXPECT_TRUE(feedback.IsConsistent()); + + TransportFeedback parsed = Parse(feedback.Build()); + EXPECT_EQ(parsed.GetReceivedPackets().size(), num_received_rtp_packets); + EXPECT_THAT(parsed.GetReceivedPackets(), + AllOf(SizeIs(num_received_rtp_packets), + Each(Property(&TransportFeedback::ReceivedPacket::delta, + Eq(kMinDelta))))); + EXPECT_GE(parsed.BaseTime(), + Timestamp::Zero() - kMinDelta * num_received_rtp_packets); +} + +TEST(RtcpPacketTest, TransportFeedbackPadding) { + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + kSmallDeltaSize; + const size_t kExpectedSizeWords = (kExpectedSizeBytes + 3) / 4; + const size_t kExpectedPaddingSizeBytes = + 4 * kExpectedSizeWords - kExpectedSizeBytes; + + TransportFeedback feedback; + feedback.SetBase(0, Timestamp::Zero()); + EXPECT_TRUE(feedback.AddReceivedPacket(0, Timestamp::Zero())); + + rtc::Buffer packet = feedback.Build(); + EXPECT_EQ(kExpectedSizeWords * 4, packet.size()); + ASSERT_GT(kExpectedSizeWords * 4, kExpectedSizeBytes); + for (size_t i = kExpectedSizeBytes; i < (kExpectedSizeWords * 4 - 1); ++i) + EXPECT_EQ(0u, packet[i]); + + EXPECT_EQ(kExpectedPaddingSizeBytes, packet[kExpectedSizeWords * 4 - 1]); + + // Modify packet by adding 4 bytes of padding at the end. Not currently used + // when we're sending, but need to be able to handle it when receiving. + + const int kPaddingBytes = 4; + const size_t kExpectedSizeWithPadding = + (kExpectedSizeWords * 4) + kPaddingBytes; + uint8_t mod_buffer[kExpectedSizeWithPadding]; + memcpy(mod_buffer, packet.data(), kExpectedSizeWords * 4); + memset(&mod_buffer[kExpectedSizeWords * 4], 0, kPaddingBytes - 1); + mod_buffer[kExpectedSizeWithPadding - 1] = + kPaddingBytes + kExpectedPaddingSizeBytes; + const uint8_t padding_flag = 1 << 5; + mod_buffer[0] |= padding_flag; + ByteWriter<uint16_t>::WriteBigEndian( + &mod_buffer[2], ByteReader<uint16_t>::ReadBigEndian(&mod_buffer[2]) + + ((kPaddingBytes + 3) / 4)); + + EXPECT_THAT(mod_buffer, IsValidFeedback()); +} + +TEST(RtcpPacketTest, TransportFeedbackPaddingBackwardsCompatibility) { + const size_t kExpectedSizeBytes = + kHeaderSize + kStatusChunkSize + kSmallDeltaSize; + const size_t kExpectedSizeWords = (kExpectedSizeBytes + 3) / 4; + const size_t kExpectedPaddingSizeBytes = + 4 * kExpectedSizeWords - kExpectedSizeBytes; + + TransportFeedback feedback; + feedback.SetBase(0, Timestamp::Zero()); + EXPECT_TRUE(feedback.AddReceivedPacket(0, Timestamp::Zero())); + + rtc::Buffer packet = feedback.Build(); + EXPECT_EQ(kExpectedSizeWords * 4, packet.size()); + ASSERT_GT(kExpectedSizeWords * 4, kExpectedSizeBytes); + for (size_t i = kExpectedSizeBytes; i < (kExpectedSizeWords * 4 - 1); ++i) + EXPECT_EQ(0u, packet[i]); + + EXPECT_GT(kExpectedPaddingSizeBytes, 0u); + EXPECT_EQ(kExpectedPaddingSizeBytes, packet[kExpectedSizeWords * 4 - 1]); + + // Modify packet by removing padding bit and writing zero at the last padding + // byte to verify that we can parse packets from old clients, where zero + // padding of up to three bytes was used without the padding bit being set. + uint8_t mod_buffer[kExpectedSizeWords * 4]; + memcpy(mod_buffer, packet.data(), kExpectedSizeWords * 4); + mod_buffer[kExpectedSizeWords * 4 - 1] = 0; + const uint8_t padding_flag = 1 << 5; + mod_buffer[0] &= ~padding_flag; // Unset padding flag. + + EXPECT_THAT(mod_buffer, IsValidFeedback()); +} + +TEST(RtcpPacketTest, TransportFeedbackCorrectlySplitsVectorChunks) { + const int kOneBitVectorCapacity = 14; + const TimeDelta kLargeTimeDelta = TransportFeedback::kDeltaTick * (1 << 8); + + // Test that a number of small deltas followed by a large delta results in a + // correct split into multiple chunks, as needed. + + for (int deltas = 0; deltas <= kOneBitVectorCapacity + 1; ++deltas) { + TransportFeedback feedback; + feedback.SetBase(0, Timestamp::Zero()); + for (int i = 0; i < deltas; ++i) + feedback.AddReceivedPacket(i, Timestamp::Millis(i)); + feedback.AddReceivedPacket(deltas, + Timestamp::Millis(deltas) + kLargeTimeDelta); + + EXPECT_THAT(feedback.Build(), IsValidFeedback()); + } +} + +TEST(RtcpPacketTest, TransportFeedbackMoveConstructor) { + const int kSamples = 100; + const uint16_t kBaseSeqNo = 7531; + const Timestamp kBaseTimestamp = Timestamp::Micros(123'456'789); + const uint8_t kFeedbackSeqNo = 90; + + TransportFeedback feedback; + feedback.SetBase(kBaseSeqNo, kBaseTimestamp); + feedback.SetFeedbackSequenceNumber(kFeedbackSeqNo); + for (int i = 0; i < kSamples; ++i) { + feedback.AddReceivedPacket( + kBaseSeqNo + i, kBaseTimestamp + i * TransportFeedback::kDeltaTick); + } + EXPECT_TRUE(feedback.IsConsistent()); + + TransportFeedback feedback_copy(feedback); + EXPECT_TRUE(feedback_copy.IsConsistent()); + EXPECT_TRUE(feedback.IsConsistent()); + EXPECT_EQ(feedback_copy.Build(), feedback.Build()); + + TransportFeedback moved(std::move(feedback)); + EXPECT_TRUE(moved.IsConsistent()); + EXPECT_TRUE(feedback.IsConsistent()); + EXPECT_EQ(moved.Build(), feedback_copy.Build()); +} + +TEST(TransportFeedbackTest, ReportsMissingPackets) { + const uint16_t kBaseSeqNo = 1000; + const Timestamp kBaseTimestamp = Timestamp::Millis(10); + const uint8_t kFeedbackSeqNo = 90; + TransportFeedback feedback_builder(/*include_timestamps*/ true); + feedback_builder.SetBase(kBaseSeqNo, kBaseTimestamp); + feedback_builder.SetFeedbackSequenceNumber(kFeedbackSeqNo); + feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, kBaseTimestamp); + // Packet losses indicated by jump in sequence number. + feedback_builder.AddReceivedPacket(kBaseSeqNo + 3, + kBaseTimestamp + TimeDelta::Millis(2)); + + MockFunction<void(uint16_t, TimeDelta)> handler; + InSequence s; + EXPECT_CALL(handler, Call(kBaseSeqNo + 0, Ne(TimeDelta::PlusInfinity()))); + EXPECT_CALL(handler, Call(kBaseSeqNo + 1, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 2, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 3, Ne(TimeDelta::PlusInfinity()))); + Parse(feedback_builder.Build()).ForAllPackets(handler.AsStdFunction()); +} + +TEST(TransportFeedbackTest, ReportsMissingPacketsWithoutTimestamps) { + const uint16_t kBaseSeqNo = 1000; + const uint8_t kFeedbackSeqNo = 90; + TransportFeedback feedback_builder(/*include_timestamps*/ false); + feedback_builder.SetBase(kBaseSeqNo, Timestamp::Millis(10)); + feedback_builder.SetFeedbackSequenceNumber(kFeedbackSeqNo); + feedback_builder.AddReceivedPacket(kBaseSeqNo + 0, Timestamp::Zero()); + // Packet losses indicated by jump in sequence number. + feedback_builder.AddReceivedPacket(kBaseSeqNo + 3, Timestamp::Zero()); + + MockFunction<void(uint16_t, TimeDelta)> handler; + InSequence s; + EXPECT_CALL(handler, Call(kBaseSeqNo + 0, Ne(TimeDelta::PlusInfinity()))); + EXPECT_CALL(handler, Call(kBaseSeqNo + 1, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 2, TimeDelta::PlusInfinity())); + EXPECT_CALL(handler, Call(kBaseSeqNo + 3, Ne(TimeDelta::PlusInfinity()))); + Parse(feedback_builder.Build()).ForAllPackets(handler.AsStdFunction()); +} +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc new file mode 100644 index 0000000000..dccd1354a9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc @@ -0,0 +1,42 @@ +/* + * 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 "modules/rtp_rtcp/source/rtcp_packet.h" + +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace { + +using ::testing::_; +using ::testing::MockFunction; +using ::webrtc::rtcp::ReceiverReport; +using ::webrtc::rtcp::ReportBlock; + +const uint32_t kSenderSsrc = 0x12345678; + +TEST(RtcpPacketTest, BuildWithTooSmallBuffer) { + ReportBlock rb; + ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + EXPECT_TRUE(rr.AddReportBlock(rb)); + + const size_t kRrLength = 8; + const size_t kReportBlockLength = 24; + + // No packet. + MockFunction<void(rtc::ArrayView<const uint8_t>)> callback; + EXPECT_CALL(callback, Call(_)).Times(0); + const size_t kBufferSize = kRrLength + kReportBlockLength - 1; + EXPECT_FALSE(rr.Build(kBufferSize, callback.AsStdFunction())); +} + +} // namespace diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc new file mode 100644 index 0000000000..936750c263 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc @@ -0,0 +1,1292 @@ +/* + * 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 "modules/rtp_rtcp/source/rtcp_receiver.h" + +#include <string.h> + +#include <algorithm> +#include <limits> +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "api/video/video_bitrate_allocation.h" +#include "api/video/video_bitrate_allocator.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/compound_packet.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/remote_estimate.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 "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_impl2.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "modules/rtp_rtcp/source/tmmbr_help.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/trace_event.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +namespace { + +using rtcp::CommonHeader; +using rtcp::ReportBlock; + +// The number of RTCP time intervals needed to trigger a timeout. +const int kRrTimeoutIntervals = 3; + +const int64_t kTmmbrTimeoutIntervalMs = 5 * 5000; + +const int64_t kMaxWarningLogIntervalMs = 10000; +const int64_t kRtcpMinFrameLengthMs = 17; + +// Maximum number of received RRTRs that will be stored. +const size_t kMaxNumberOfStoredRrtrs = 300; + +constexpr TimeDelta kDefaultVideoReportInterval = TimeDelta::Seconds(1); +constexpr TimeDelta kDefaultAudioReportInterval = TimeDelta::Seconds(5); + +// Returns true if the `timestamp` has exceeded the |interval * +// kRrTimeoutIntervals| period and was reset (set to PlusInfinity()). Returns +// false if the timer was either already reset or if it has not expired. +bool ResetTimestampIfExpired(const Timestamp now, + Timestamp& timestamp, + TimeDelta interval) { + if (timestamp.IsInfinite() || + now <= timestamp + interval * kRrTimeoutIntervals) { + return false; + } + + timestamp = Timestamp::PlusInfinity(); + return true; +} + +} // namespace + +constexpr size_t RTCPReceiver::RegisteredSsrcs::kMediaSsrcIndex; +constexpr size_t RTCPReceiver::RegisteredSsrcs::kMaxSsrcs; + +RTCPReceiver::RegisteredSsrcs::RegisteredSsrcs( + bool disable_sequence_checker, + const RtpRtcpInterface::Configuration& config) + : packet_sequence_checker_(disable_sequence_checker) { + packet_sequence_checker_.Detach(); + ssrcs_.push_back(config.local_media_ssrc); + if (config.rtx_send_ssrc) { + ssrcs_.push_back(*config.rtx_send_ssrc); + } + if (config.fec_generator) { + absl::optional<uint32_t> flexfec_ssrc = config.fec_generator->FecSsrc(); + if (flexfec_ssrc) { + ssrcs_.push_back(*flexfec_ssrc); + } + } + // Ensure that the RegisteredSsrcs can inline the SSRCs. + RTC_DCHECK_LE(ssrcs_.size(), RTCPReceiver::RegisteredSsrcs::kMaxSsrcs); +} + +bool RTCPReceiver::RegisteredSsrcs::contains(uint32_t ssrc) const { + RTC_DCHECK_RUN_ON(&packet_sequence_checker_); + return absl::c_linear_search(ssrcs_, ssrc); +} + +uint32_t RTCPReceiver::RegisteredSsrcs::media_ssrc() const { + RTC_DCHECK_RUN_ON(&packet_sequence_checker_); + return ssrcs_[kMediaSsrcIndex]; +} + +void RTCPReceiver::RegisteredSsrcs::set_media_ssrc(uint32_t ssrc) { + RTC_DCHECK_RUN_ON(&packet_sequence_checker_); + ssrcs_[kMediaSsrcIndex] = ssrc; +} + +struct RTCPReceiver::PacketInformation { + uint32_t packet_type_flags = 0; // RTCPPacketTypeFlags bit field. + + uint32_t remote_ssrc = 0; + std::vector<uint16_t> nack_sequence_numbers; + // TODO(hbos): Remove `report_blocks` in favor of `report_block_datas`. + ReportBlockList report_blocks; + std::vector<ReportBlockData> report_block_datas; + int64_t rtt_ms = 0; + uint32_t receiver_estimated_max_bitrate_bps = 0; + std::unique_ptr<rtcp::TransportFeedback> transport_feedback; + absl::optional<VideoBitrateAllocation> target_bitrate_allocation; + absl::optional<NetworkStateEstimate> network_state_estimate; + std::unique_ptr<rtcp::LossNotification> loss_notification; +}; + +RTCPReceiver::RTCPReceiver(const RtpRtcpInterface::Configuration& config, + ModuleRtpRtcpImpl2* owner) + : clock_(config.clock), + receiver_only_(config.receiver_only), + rtp_rtcp_(owner), + registered_ssrcs_(false, config), + rtcp_bandwidth_observer_(config.bandwidth_callback), + rtcp_event_observer_(config.rtcp_event_observer), + rtcp_intra_frame_observer_(config.intra_frame_callback), + rtcp_loss_notification_observer_(config.rtcp_loss_notification_observer), + network_state_estimate_observer_(config.network_state_estimate_observer), + transport_feedback_observer_(config.transport_feedback_callback), + bitrate_allocation_observer_(config.bitrate_allocation_observer), + report_interval_(config.rtcp_report_interval_ms > 0 + ? TimeDelta::Millis(config.rtcp_report_interval_ms) + : (config.audio ? kDefaultAudioReportInterval + : kDefaultVideoReportInterval)), + // TODO(bugs.webrtc.org/10774): Remove fallback. + remote_ssrc_(0), + remote_sender_rtp_time_(0), + remote_sender_packet_count_(0), + remote_sender_octet_count_(0), + remote_sender_reports_count_(0), + xr_rrtr_status_(config.non_sender_rtt_measurement), + xr_rr_rtt_ms_(0), + oldest_tmmbr_info_ms_(0), + cname_callback_(config.rtcp_cname_callback), + report_block_data_observer_(config.report_block_data_observer), + packet_type_counter_observer_(config.rtcp_packet_type_counter_observer), + num_skipped_packets_(0), + last_skipped_packets_warning_ms_(clock_->TimeInMilliseconds()) { + RTC_DCHECK(owner); +} + +RTCPReceiver::RTCPReceiver(const RtpRtcpInterface::Configuration& config, + ModuleRtpRtcp* owner) + : clock_(config.clock), + receiver_only_(config.receiver_only), + rtp_rtcp_(owner), + registered_ssrcs_(true, config), + rtcp_bandwidth_observer_(config.bandwidth_callback), + rtcp_event_observer_(config.rtcp_event_observer), + rtcp_intra_frame_observer_(config.intra_frame_callback), + rtcp_loss_notification_observer_(config.rtcp_loss_notification_observer), + network_state_estimate_observer_(config.network_state_estimate_observer), + transport_feedback_observer_(config.transport_feedback_callback), + bitrate_allocation_observer_(config.bitrate_allocation_observer), + report_interval_(config.rtcp_report_interval_ms > 0 + ? TimeDelta::Millis(config.rtcp_report_interval_ms) + : (config.audio ? kDefaultAudioReportInterval + : kDefaultVideoReportInterval)), + // TODO(bugs.webrtc.org/10774): Remove fallback. + remote_ssrc_(0), + remote_sender_rtp_time_(0), + remote_sender_packet_count_(0), + remote_sender_octet_count_(0), + remote_sender_reports_count_(0), + xr_rrtr_status_(config.non_sender_rtt_measurement), + xr_rr_rtt_ms_(0), + oldest_tmmbr_info_ms_(0), + cname_callback_(config.rtcp_cname_callback), + report_block_data_observer_(config.report_block_data_observer), + packet_type_counter_observer_(config.rtcp_packet_type_counter_observer), + num_skipped_packets_(0), + last_skipped_packets_warning_ms_(clock_->TimeInMilliseconds()) { + RTC_DCHECK(owner); + // Dear reader - if you're here because of this log statement and are + // wondering what this is about, chances are that you are using an instance + // of RTCPReceiver without using the webrtc APIs. This creates a bit of a + // problem for WebRTC because this class is a part of an internal + // implementation that is constantly changing and being improved. + // The intention of this log statement is to give a heads up that changes + // are coming and encourage you to use the public APIs or be prepared that + // things might break down the line as more changes land. A thing you could + // try out for now is to replace the `CustomSequenceChecker` in the header + // with a regular `SequenceChecker` and see if that triggers an + // error in your code. If it does, chances are you have your own threading + // model that is not the same as WebRTC internally has. + RTC_LOG(LS_INFO) << "************** !!!DEPRECATION WARNING!! **************"; +} + +RTCPReceiver::~RTCPReceiver() {} + +void RTCPReceiver::IncomingPacket(rtc::ArrayView<const uint8_t> packet) { + if (packet.empty()) { + RTC_LOG(LS_WARNING) << "Incoming empty RTCP packet"; + return; + } + + PacketInformation packet_information; + if (!ParseCompoundPacket(packet, &packet_information)) + return; + TriggerCallbacksFromRtcpPacket(packet_information); +} + +// This method is only used by test and legacy code, so we should be able to +// remove it soon. +int64_t RTCPReceiver::LastReceivedReportBlockMs() const { + MutexLock lock(&rtcp_receiver_lock_); + return last_received_rb_.IsFinite() ? last_received_rb_.ms() : 0; +} + +void RTCPReceiver::SetRemoteSSRC(uint32_t ssrc) { + MutexLock lock(&rtcp_receiver_lock_); + // New SSRC reset old reports. + last_received_sr_ntp_.Reset(); + remote_ssrc_ = ssrc; +} + +void RTCPReceiver::set_local_media_ssrc(uint32_t ssrc) { + registered_ssrcs_.set_media_ssrc(ssrc); +} + +uint32_t RTCPReceiver::local_media_ssrc() const { + return registered_ssrcs_.media_ssrc(); +} + +uint32_t RTCPReceiver::RemoteSSRC() const { + MutexLock lock(&rtcp_receiver_lock_); + return remote_ssrc_; +} + +void RTCPReceiver::RttStats::AddRtt(TimeDelta rtt) { + last_rtt_ = rtt; + if (rtt < min_rtt_) { + min_rtt_ = rtt; + } + if (rtt > max_rtt_) { + max_rtt_ = rtt; + } + sum_rtt_ += rtt; + ++num_rtts_; +} + +int32_t RTCPReceiver::RTT(uint32_t remote_ssrc, + int64_t* last_rtt_ms, + int64_t* avg_rtt_ms, + int64_t* min_rtt_ms, + int64_t* max_rtt_ms) const { + MutexLock lock(&rtcp_receiver_lock_); + + auto it = rtts_.find(remote_ssrc); + if (it == rtts_.end()) { + return -1; + } + + if (last_rtt_ms) { + *last_rtt_ms = it->second.last_rtt().ms(); + } + + if (avg_rtt_ms) { + *avg_rtt_ms = it->second.average_rtt().ms(); + } + + if (min_rtt_ms) { + *min_rtt_ms = it->second.min_rtt().ms(); + } + + if (max_rtt_ms) { + *max_rtt_ms = it->second.max_rtt().ms(); + } + + return 0; +} + +RTCPReceiver::NonSenderRttStats RTCPReceiver::GetNonSenderRTT() const { + MutexLock lock(&rtcp_receiver_lock_); + auto it = non_sender_rtts_.find(remote_ssrc_); + if (it == non_sender_rtts_.end()) { + return {}; + } + return it->second; +} + +void RTCPReceiver::SetNonSenderRttMeasurement(bool enabled) { + MutexLock lock(&rtcp_receiver_lock_); + xr_rrtr_status_ = enabled; +} + +bool RTCPReceiver::GetAndResetXrRrRtt(int64_t* rtt_ms) { + RTC_DCHECK(rtt_ms); + MutexLock lock(&rtcp_receiver_lock_); + if (xr_rr_rtt_ms_ == 0) { + return false; + } + *rtt_ms = xr_rr_rtt_ms_; + xr_rr_rtt_ms_ = 0; + return true; +} + +// Called regularly (1/sec) on the worker thread to do rtt calculations. +absl::optional<TimeDelta> RTCPReceiver::OnPeriodicRttUpdate( + Timestamp newer_than, + bool sending) { + // Running on the worker thread (same as construction thread). + absl::optional<TimeDelta> rtt; + + if (sending) { + // Check if we've received a report block within the last kRttUpdateInterval + // amount of time. + MutexLock lock(&rtcp_receiver_lock_); + if (last_received_rb_.IsInfinite() || last_received_rb_ > newer_than) { + TimeDelta max_rtt = TimeDelta::MinusInfinity(); + for (const auto& rtt_stats : rtts_) { + if (rtt_stats.second.last_rtt() > max_rtt) { + max_rtt = rtt_stats.second.last_rtt(); + } + } + if (max_rtt.IsFinite()) { + rtt = max_rtt; + } + } + + // Check for expired timers and if so, log and reset. + auto now = clock_->CurrentTime(); + if (RtcpRrTimeoutLocked(now)) { + RTC_LOG_F(LS_WARNING) << "Timeout: No RTCP RR received."; + } else if (RtcpRrSequenceNumberTimeoutLocked(now)) { + RTC_LOG_F(LS_WARNING) << "Timeout: No increase in RTCP RR extended " + "highest sequence number."; + } + } else { + // Report rtt from receiver. + int64_t rtt_ms; + if (GetAndResetXrRrRtt(&rtt_ms)) { + rtt.emplace(TimeDelta::Millis(rtt_ms)); + } + } + + return rtt; +} + +bool RTCPReceiver::NTP(uint32_t* received_ntp_secs, + uint32_t* received_ntp_frac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp, + uint32_t* remote_sender_packet_count, + uint64_t* remote_sender_octet_count, + uint64_t* remote_sender_reports_count) const { + MutexLock lock(&rtcp_receiver_lock_); + if (!last_received_sr_ntp_.Valid()) + return false; + + // NTP from incoming SenderReport. + if (received_ntp_secs) + *received_ntp_secs = remote_sender_ntp_time_.seconds(); + if (received_ntp_frac) + *received_ntp_frac = remote_sender_ntp_time_.fractions(); + // Rtp time from incoming SenderReport. + if (rtcp_timestamp) + *rtcp_timestamp = remote_sender_rtp_time_; + + // Local NTP time when we received a RTCP packet with a send block. + if (rtcp_arrival_time_secs) + *rtcp_arrival_time_secs = last_received_sr_ntp_.seconds(); + if (rtcp_arrival_time_frac) + *rtcp_arrival_time_frac = last_received_sr_ntp_.fractions(); + + // Counters. + if (remote_sender_packet_count) + *remote_sender_packet_count = remote_sender_packet_count_; + if (remote_sender_octet_count) + *remote_sender_octet_count = remote_sender_octet_count_; + if (remote_sender_reports_count) + *remote_sender_reports_count = remote_sender_reports_count_; + + return true; +} + +std::vector<rtcp::ReceiveTimeInfo> +RTCPReceiver::ConsumeReceivedXrReferenceTimeInfo() { + MutexLock lock(&rtcp_receiver_lock_); + + const size_t last_xr_rtis_size = std::min( + received_rrtrs_.size(), rtcp::ExtendedReports::kMaxNumberOfDlrrItems); + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis; + last_xr_rtis.reserve(last_xr_rtis_size); + + const uint32_t now_ntp = CompactNtp(clock_->CurrentNtpTime()); + + for (size_t i = 0; i < last_xr_rtis_size; ++i) { + RrtrInformation& rrtr = received_rrtrs_.front(); + last_xr_rtis.emplace_back(rrtr.ssrc, rrtr.received_remote_mid_ntp_time, + now_ntp - rrtr.local_receive_mid_ntp_time); + received_rrtrs_ssrc_it_.erase(rrtr.ssrc); + received_rrtrs_.pop_front(); + } + + return last_xr_rtis; +} + +void RTCPReceiver::RemoteRTCPSenderInfo(uint32_t* packet_count, + uint32_t* octet_count, + int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const { + MutexLock lock(&rtcp_receiver_lock_); + *packet_count = remote_sender_packet_count_; + *octet_count = remote_sender_octet_count_; + *ntp_timestamp_ms = last_received_sr_ntp_.ToMs(); + *remote_ntp_timestamp_ms = remote_sender_ntp_time_.ToMs(); +} + +std::vector<ReportBlockData> RTCPReceiver::GetLatestReportBlockData() const { + std::vector<ReportBlockData> result; + MutexLock lock(&rtcp_receiver_lock_); + for (const auto& report : received_report_blocks_) { + result.push_back(report.second); + } + return result; +} + +bool RTCPReceiver::ParseCompoundPacket(rtc::ArrayView<const uint8_t> packet, + PacketInformation* packet_information) { + MutexLock lock(&rtcp_receiver_lock_); + + CommonHeader rtcp_block; + // If a sender report is received but no DLRR, we need to reset the + // roundTripTime stat according to the standard, see + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime + struct RtcpReceivedBlock { + bool sender_report = false; + bool dlrr = false; + }; + // For each remote SSRC we store if we've received a sender report or a DLRR + // block. + flat_map<uint32_t, RtcpReceivedBlock> received_blocks; + for (const uint8_t* next_block = packet.begin(); next_block != packet.end(); + next_block = rtcp_block.NextPacket()) { + ptrdiff_t remaining_blocks_size = packet.end() - next_block; + RTC_DCHECK_GT(remaining_blocks_size, 0); + if (!rtcp_block.Parse(next_block, remaining_blocks_size)) { + if (next_block == packet.begin()) { + // Failed to parse 1st header, nothing was extracted from this packet. + RTC_LOG(LS_WARNING) << "Incoming invalid RTCP packet"; + return false; + } + ++num_skipped_packets_; + break; + } + + switch (rtcp_block.type()) { + case rtcp::SenderReport::kPacketType: + HandleSenderReport(rtcp_block, packet_information); + received_blocks[packet_information->remote_ssrc].sender_report = true; + break; + case rtcp::ReceiverReport::kPacketType: + HandleReceiverReport(rtcp_block, packet_information); + break; + case rtcp::Sdes::kPacketType: + HandleSdes(rtcp_block, packet_information); + break; + case rtcp::ExtendedReports::kPacketType: { + bool contains_dlrr = false; + uint32_t ssrc = 0; + HandleXr(rtcp_block, packet_information, contains_dlrr, ssrc); + if (contains_dlrr) { + received_blocks[ssrc].dlrr = true; + } + break; + } + case rtcp::Bye::kPacketType: + HandleBye(rtcp_block); + break; + case rtcp::App::kPacketType: + HandleApp(rtcp_block, packet_information); + break; + case rtcp::Rtpfb::kPacketType: + switch (rtcp_block.fmt()) { + case rtcp::Nack::kFeedbackMessageType: + HandleNack(rtcp_block, packet_information); + break; + case rtcp::Tmmbr::kFeedbackMessageType: + HandleTmmbr(rtcp_block, packet_information); + break; + case rtcp::Tmmbn::kFeedbackMessageType: + HandleTmmbn(rtcp_block, packet_information); + break; + case rtcp::RapidResyncRequest::kFeedbackMessageType: + HandleSrReq(rtcp_block, packet_information); + break; + case rtcp::TransportFeedback::kFeedbackMessageType: + HandleTransportFeedback(rtcp_block, packet_information); + break; + default: + ++num_skipped_packets_; + break; + } + break; + case rtcp::Psfb::kPacketType: + switch (rtcp_block.fmt()) { + case rtcp::Pli::kFeedbackMessageType: + HandlePli(rtcp_block, packet_information); + break; + case rtcp::Fir::kFeedbackMessageType: + HandleFir(rtcp_block, packet_information); + break; + case rtcp::Psfb::kAfbMessageType: + HandlePsfbApp(rtcp_block, packet_information); + break; + default: + ++num_skipped_packets_; + break; + } + break; + default: + ++num_skipped_packets_; + break; + } + } + + for (const auto& rb : received_blocks) { + if (rb.second.sender_report && !rb.second.dlrr) { + auto rtt_stats = non_sender_rtts_.find(rb.first); + if (rtt_stats != non_sender_rtts_.end()) { + rtt_stats->second.Invalidate(); + } + } + } + + if (packet_type_counter_observer_) { + packet_type_counter_observer_->RtcpPacketTypesCounterUpdated( + local_media_ssrc(), packet_type_counter_); + } + + if (num_skipped_packets_ > 0) { + const int64_t now_ms = clock_->TimeInMilliseconds(); + if (now_ms - last_skipped_packets_warning_ms_ >= kMaxWarningLogIntervalMs) { + last_skipped_packets_warning_ms_ = now_ms; + RTC_LOG(LS_WARNING) + << num_skipped_packets_ + << " RTCP blocks were skipped due to being malformed or of " + "unrecognized/unsupported type, during the past " + << (kMaxWarningLogIntervalMs / 1000) << " second period."; + } + } + + return true; +} + +void RTCPReceiver::HandleSenderReport(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::SenderReport sender_report; + if (!sender_report.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + const uint32_t remote_ssrc = sender_report.sender_ssrc(); + + packet_information->remote_ssrc = remote_ssrc; + + UpdateTmmbrRemoteIsAlive(remote_ssrc); + + // Have I received RTP packets from this party? + if (remote_ssrc_ == remote_ssrc) { + // Only signal that we have received a SR when we accept one. + packet_information->packet_type_flags |= kRtcpSr; + + remote_sender_ntp_time_ = sender_report.ntp(); + remote_sender_rtp_time_ = sender_report.rtp_timestamp(); + last_received_sr_ntp_ = clock_->CurrentNtpTime(); + remote_sender_packet_count_ = sender_report.sender_packet_count(); + remote_sender_octet_count_ = sender_report.sender_octet_count(); + remote_sender_reports_count_++; + } else { + // We will only store the send report from one source, but + // we will store all the receive blocks. + packet_information->packet_type_flags |= kRtcpRr; + } + + for (const rtcp::ReportBlock& report_block : sender_report.report_blocks()) + HandleReportBlock(report_block, packet_information, remote_ssrc); +} + +void RTCPReceiver::HandleReceiverReport(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::ReceiverReport receiver_report; + if (!receiver_report.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + const uint32_t remote_ssrc = receiver_report.sender_ssrc(); + + packet_information->remote_ssrc = remote_ssrc; + + UpdateTmmbrRemoteIsAlive(remote_ssrc); + + packet_information->packet_type_flags |= kRtcpRr; + + for (const ReportBlock& report_block : receiver_report.report_blocks()) + HandleReportBlock(report_block, packet_information, remote_ssrc); +} + +void RTCPReceiver::HandleReportBlock(const ReportBlock& report_block, + PacketInformation* packet_information, + uint32_t remote_ssrc) { + // This will be called once per report block in the RTCP packet. + // We filter out all report blocks that are not for us. + // Each packet has max 31 RR blocks. + // + // We can calc RTT if we send a send report and get a report block back. + + // `report_block.source_ssrc()` is the SSRC identifier of the source to + // which the information in this reception report block pertains. + + // Filter out all report blocks that are not for us. + if (!registered_ssrcs_.contains(report_block.source_ssrc())) + return; + + last_received_rb_ = clock_->CurrentTime(); + + ReportBlockData* report_block_data = + &received_report_blocks_[report_block.source_ssrc()]; + RTCPReportBlock rtcp_report_block; + rtcp_report_block.sender_ssrc = remote_ssrc; + rtcp_report_block.source_ssrc = report_block.source_ssrc(); + rtcp_report_block.fraction_lost = report_block.fraction_lost(); + rtcp_report_block.packets_lost = report_block.cumulative_lost_signed(); + if (report_block.extended_high_seq_num() > + report_block_data->report_block().extended_highest_sequence_number) { + // We have successfully delivered new RTP packets to the remote side after + // the last RR was sent from the remote side. + last_increased_sequence_number_ = last_received_rb_; + } + rtcp_report_block.extended_highest_sequence_number = + report_block.extended_high_seq_num(); + rtcp_report_block.jitter = report_block.jitter(); + rtcp_report_block.delay_since_last_sender_report = + report_block.delay_since_last_sr(); + rtcp_report_block.last_sender_report_timestamp = report_block.last_sr(); + // Number of seconds since 1900 January 1 00:00 GMT (see + // https://tools.ietf.org/html/rfc868). + report_block_data->SetReportBlock( + rtcp_report_block, + (clock_->CurrentNtpInMilliseconds() - rtc::kNtpJan1970Millisecs) * + rtc::kNumMicrosecsPerMillisec); + + uint32_t send_time_ntp = report_block.last_sr(); + // RFC3550, section 6.4.1, LSR field discription states: + // If no SR has been received yet, the field is set to zero. + // Receiver rtp_rtcp module is not expected to calculate rtt using + // Sender Reports even if it accidentally can. + if (send_time_ntp != 0) { + uint32_t delay_ntp = report_block.delay_since_last_sr(); + // Local NTP time. + uint32_t receive_time_ntp = + CompactNtp(clock_->ConvertTimestampToNtpTime(last_received_rb_)); + + // RTT in 1/(2^16) seconds. + uint32_t rtt_ntp = receive_time_ntp - delay_ntp - send_time_ntp; + // Convert to 1/1000 seconds (milliseconds). + TimeDelta rtt = CompactNtpRttToTimeDelta(rtt_ntp); + report_block_data->AddRoundTripTimeSample(rtt.ms()); + if (report_block.source_ssrc() == local_media_ssrc()) { + rtts_[remote_ssrc].AddRtt(rtt); + } + + packet_information->rtt_ms = rtt.ms(); + } + + packet_information->report_blocks.push_back( + report_block_data->report_block()); + packet_information->report_block_datas.push_back(*report_block_data); +} + +RTCPReceiver::TmmbrInformation* RTCPReceiver::FindOrCreateTmmbrInfo( + uint32_t remote_ssrc) { + // Create or find receive information. + TmmbrInformation* tmmbr_info = &tmmbr_infos_[remote_ssrc]; + // Update that this remote is alive. + tmmbr_info->last_time_received_ms = clock_->TimeInMilliseconds(); + return tmmbr_info; +} + +void RTCPReceiver::UpdateTmmbrRemoteIsAlive(uint32_t remote_ssrc) { + auto tmmbr_it = tmmbr_infos_.find(remote_ssrc); + if (tmmbr_it != tmmbr_infos_.end()) + tmmbr_it->second.last_time_received_ms = clock_->TimeInMilliseconds(); +} + +RTCPReceiver::TmmbrInformation* RTCPReceiver::GetTmmbrInformation( + uint32_t remote_ssrc) { + auto it = tmmbr_infos_.find(remote_ssrc); + if (it == tmmbr_infos_.end()) + return nullptr; + return &it->second; +} + +// These two methods (RtcpRrTimeout and RtcpRrSequenceNumberTimeout) only exist +// for tests and legacy code (rtp_rtcp_impl.cc). We should be able to to delete +// the methods and require that access to the locked variables only happens on +// the worker thread and thus no locking is needed. +bool RTCPReceiver::RtcpRrTimeout() { + MutexLock lock(&rtcp_receiver_lock_); + return RtcpRrTimeoutLocked(clock_->CurrentTime()); +} + +bool RTCPReceiver::RtcpRrSequenceNumberTimeout() { + MutexLock lock(&rtcp_receiver_lock_); + return RtcpRrSequenceNumberTimeoutLocked(clock_->CurrentTime()); +} + +bool RTCPReceiver::UpdateTmmbrTimers() { + MutexLock lock(&rtcp_receiver_lock_); + + int64_t now_ms = clock_->TimeInMilliseconds(); + int64_t timeout_ms = now_ms - kTmmbrTimeoutIntervalMs; + + if (oldest_tmmbr_info_ms_ >= timeout_ms) + return false; + + bool update_bounding_set = false; + oldest_tmmbr_info_ms_ = -1; + for (auto tmmbr_it = tmmbr_infos_.begin(); tmmbr_it != tmmbr_infos_.end();) { + TmmbrInformation* tmmbr_info = &tmmbr_it->second; + if (tmmbr_info->last_time_received_ms > 0) { + if (tmmbr_info->last_time_received_ms < timeout_ms) { + // No rtcp packet for the last 5 regular intervals, reset limitations. + tmmbr_info->tmmbr.clear(); + // Prevent that we call this over and over again. + tmmbr_info->last_time_received_ms = 0; + // Send new TMMBN to all channels using the default codec. + update_bounding_set = true; + } else if (oldest_tmmbr_info_ms_ == -1 || + tmmbr_info->last_time_received_ms < oldest_tmmbr_info_ms_) { + oldest_tmmbr_info_ms_ = tmmbr_info->last_time_received_ms; + } + ++tmmbr_it; + } else if (tmmbr_info->ready_for_delete) { + // When we dont have a last_time_received_ms and the object is marked + // ready_for_delete it's removed from the map. + tmmbr_it = tmmbr_infos_.erase(tmmbr_it); + } else { + ++tmmbr_it; + } + } + return update_bounding_set; +} + +std::vector<rtcp::TmmbItem> RTCPReceiver::BoundingSet(bool* tmmbr_owner) { + MutexLock lock(&rtcp_receiver_lock_); + TmmbrInformation* tmmbr_info = GetTmmbrInformation(remote_ssrc_); + if (!tmmbr_info) + return std::vector<rtcp::TmmbItem>(); + + *tmmbr_owner = TMMBRHelp::IsOwner(tmmbr_info->tmmbn, local_media_ssrc()); + return tmmbr_info->tmmbn; +} + +void RTCPReceiver::HandleSdes(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::Sdes sdes; + if (!sdes.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + for (const rtcp::Sdes::Chunk& chunk : sdes.chunks()) { + if (cname_callback_) + cname_callback_->OnCname(chunk.ssrc, chunk.cname); + } + packet_information->packet_type_flags |= kRtcpSdes; +} + +void RTCPReceiver::HandleNack(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::Nack nack; + if (!nack.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + if (receiver_only_ || local_media_ssrc() != nack.media_ssrc()) // Not to us. + return; + + packet_information->nack_sequence_numbers.insert( + packet_information->nack_sequence_numbers.end(), + nack.packet_ids().begin(), nack.packet_ids().end()); + for (uint16_t packet_id : nack.packet_ids()) + nack_stats_.ReportRequest(packet_id); + + if (!nack.packet_ids().empty()) { + packet_information->packet_type_flags |= kRtcpNack; + ++packet_type_counter_.nack_packets; + packet_type_counter_.nack_requests = nack_stats_.requests(); + packet_type_counter_.unique_nack_requests = nack_stats_.unique_requests(); + } +} + +void RTCPReceiver::HandleApp(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::App app; + if (app.Parse(rtcp_block)) { + if (app.name() == rtcp::RemoteEstimate::kName && + app.sub_type() == rtcp::RemoteEstimate::kSubType) { + rtcp::RemoteEstimate estimate(std::move(app)); + if (estimate.ParseData()) { + packet_information->network_state_estimate = estimate.estimate(); + return; + } + } + } + ++num_skipped_packets_; +} + +void RTCPReceiver::HandleBye(const CommonHeader& rtcp_block) { + rtcp::Bye bye; + if (!bye.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + if (rtcp_event_observer_) { + rtcp_event_observer_->OnRtcpBye(); + } + + // Clear our lists. + rtts_.erase(bye.sender_ssrc()); + EraseIf(received_report_blocks_, [&](const auto& elem) { + return elem.second.report_block().sender_ssrc == bye.sender_ssrc(); + }); + + TmmbrInformation* tmmbr_info = GetTmmbrInformation(bye.sender_ssrc()); + if (tmmbr_info) + tmmbr_info->ready_for_delete = true; + + last_fir_.erase(bye.sender_ssrc()); + auto it = received_rrtrs_ssrc_it_.find(bye.sender_ssrc()); + if (it != received_rrtrs_ssrc_it_.end()) { + received_rrtrs_.erase(it->second); + received_rrtrs_ssrc_it_.erase(it); + } + xr_rr_rtt_ms_ = 0; +} + +void RTCPReceiver::HandleXr(const CommonHeader& rtcp_block, + PacketInformation* packet_information, + bool& contains_dlrr, + uint32_t& ssrc) { + rtcp::ExtendedReports xr; + if (!xr.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + ssrc = xr.sender_ssrc(); + contains_dlrr = !xr.dlrr().sub_blocks().empty(); + + if (xr.rrtr()) + HandleXrReceiveReferenceTime(xr.sender_ssrc(), *xr.rrtr()); + + for (const rtcp::ReceiveTimeInfo& time_info : xr.dlrr().sub_blocks()) + HandleXrDlrrReportBlock(xr.sender_ssrc(), time_info); + + if (xr.target_bitrate()) { + HandleXrTargetBitrate(xr.sender_ssrc(), *xr.target_bitrate(), + packet_information); + } +} + +void RTCPReceiver::HandleXrReceiveReferenceTime(uint32_t sender_ssrc, + const rtcp::Rrtr& rrtr) { + uint32_t received_remote_mid_ntp_time = CompactNtp(rrtr.ntp()); + uint32_t local_receive_mid_ntp_time = CompactNtp(clock_->CurrentNtpTime()); + + auto it = received_rrtrs_ssrc_it_.find(sender_ssrc); + if (it != received_rrtrs_ssrc_it_.end()) { + it->second->received_remote_mid_ntp_time = received_remote_mid_ntp_time; + it->second->local_receive_mid_ntp_time = local_receive_mid_ntp_time; + } else { + if (received_rrtrs_.size() < kMaxNumberOfStoredRrtrs) { + received_rrtrs_.emplace_back(sender_ssrc, received_remote_mid_ntp_time, + local_receive_mid_ntp_time); + received_rrtrs_ssrc_it_[sender_ssrc] = std::prev(received_rrtrs_.end()); + } else { + RTC_LOG(LS_WARNING) << "Discarding received RRTR for ssrc " << sender_ssrc + << ", reached maximum number of stored RRTRs."; + } + } +} + +void RTCPReceiver::HandleXrDlrrReportBlock(uint32_t sender_ssrc, + const rtcp::ReceiveTimeInfo& rti) { + if (!registered_ssrcs_.contains(rti.ssrc)) // Not to us. + return; + + // Caller should explicitly enable rtt calculation using extended reports. + if (!xr_rrtr_status_) + return; + + // The send_time and delay_rr fields are in units of 1/2^16 sec. + uint32_t send_time_ntp = rti.last_rr; + // RFC3611, section 4.5, LRR field discription states: + // If no such block has been received, the field is set to zero. + if (send_time_ntp == 0) { + auto rtt_stats = non_sender_rtts_.find(sender_ssrc); + if (rtt_stats != non_sender_rtts_.end()) { + rtt_stats->second.Invalidate(); + } + return; + } + + uint32_t delay_ntp = rti.delay_since_last_rr; + uint32_t now_ntp = CompactNtp(clock_->CurrentNtpTime()); + + uint32_t rtt_ntp = now_ntp - delay_ntp - send_time_ntp; + TimeDelta rtt = CompactNtpRttToTimeDelta(rtt_ntp); + xr_rr_rtt_ms_ = rtt.ms(); + + non_sender_rtts_[sender_ssrc].Update(rtt); +} + +void RTCPReceiver::HandleXrTargetBitrate( + uint32_t ssrc, + const rtcp::TargetBitrate& target_bitrate, + PacketInformation* packet_information) { + if (ssrc != remote_ssrc_) { + return; // Not for us. + } + + VideoBitrateAllocation bitrate_allocation; + for (const auto& item : target_bitrate.GetTargetBitrates()) { + if (item.spatial_layer >= kMaxSpatialLayers || + item.temporal_layer >= kMaxTemporalStreams) { + RTC_LOG(LS_WARNING) + << "Invalid layer in XR target bitrate pack: spatial index " + << item.spatial_layer << ", temporal index " << item.temporal_layer + << ", dropping."; + } else { + bitrate_allocation.SetBitrate(item.spatial_layer, item.temporal_layer, + item.target_bitrate_kbps * 1000); + } + } + packet_information->target_bitrate_allocation.emplace(bitrate_allocation); +} + +void RTCPReceiver::HandlePli(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::Pli pli; + if (!pli.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + if (local_media_ssrc() == pli.media_ssrc()) { + ++packet_type_counter_.pli_packets; + // Received a signal that we need to send a new key frame. + packet_information->packet_type_flags |= kRtcpPli; + } +} + +void RTCPReceiver::HandleTmmbr(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::Tmmbr tmmbr; + if (!tmmbr.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + uint32_t sender_ssrc = tmmbr.sender_ssrc(); + if (tmmbr.media_ssrc()) { + // media_ssrc() SHOULD be 0 if same as SenderSSRC. + // In relay mode this is a valid number. + sender_ssrc = tmmbr.media_ssrc(); + } + + for (const rtcp::TmmbItem& request : tmmbr.requests()) { + if (local_media_ssrc() != request.ssrc() || request.bitrate_bps() == 0) + continue; + + TmmbrInformation* tmmbr_info = FindOrCreateTmmbrInfo(tmmbr.sender_ssrc()); + auto* entry = &tmmbr_info->tmmbr[sender_ssrc]; + entry->tmmbr_item = rtcp::TmmbItem(sender_ssrc, request.bitrate_bps(), + request.packet_overhead()); + // FindOrCreateTmmbrInfo always sets `last_time_received_ms` to + // `clock_->TimeInMilliseconds()`. + entry->last_updated_ms = tmmbr_info->last_time_received_ms; + + packet_information->packet_type_flags |= kRtcpTmmbr; + break; + } +} + +void RTCPReceiver::HandleTmmbn(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::Tmmbn tmmbn; + if (!tmmbn.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + TmmbrInformation* tmmbr_info = FindOrCreateTmmbrInfo(tmmbn.sender_ssrc()); + + packet_information->packet_type_flags |= kRtcpTmmbn; + + tmmbr_info->tmmbn = tmmbn.items(); +} + +void RTCPReceiver::HandleSrReq(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::RapidResyncRequest sr_req; + if (!sr_req.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + packet_information->packet_type_flags |= kRtcpSrReq; +} + +void RTCPReceiver::HandlePsfbApp(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + { + rtcp::Remb remb; + if (remb.Parse(rtcp_block)) { + packet_information->packet_type_flags |= kRtcpRemb; + packet_information->receiver_estimated_max_bitrate_bps = + remb.bitrate_bps(); + return; + } + } + + { + auto loss_notification = std::make_unique<rtcp::LossNotification>(); + if (loss_notification->Parse(rtcp_block)) { + packet_information->packet_type_flags |= kRtcpLossNotification; + packet_information->loss_notification = std::move(loss_notification); + return; + } + } + + RTC_LOG(LS_WARNING) << "Unknown PSFB-APP packet."; + + ++num_skipped_packets_; +} + +void RTCPReceiver::HandleFir(const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + rtcp::Fir fir; + if (!fir.Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + if (fir.requests().empty()) + return; + + const int64_t now_ms = clock_->TimeInMilliseconds(); + for (const rtcp::Fir::Request& fir_request : fir.requests()) { + // Is it our sender that is requested to generate a new keyframe. + if (local_media_ssrc() != fir_request.ssrc) + continue; + + ++packet_type_counter_.fir_packets; + + auto inserted = last_fir_.insert(std::make_pair( + fir.sender_ssrc(), LastFirStatus(now_ms, fir_request.seq_nr))); + if (!inserted.second) { // There was already an entry. + LastFirStatus* last_fir = &inserted.first->second; + + // Check if we have reported this FIRSequenceNumber before. + if (fir_request.seq_nr == last_fir->sequence_number) + continue; + + // Sanity: don't go crazy with the callbacks. + if (now_ms - last_fir->request_ms < kRtcpMinFrameLengthMs) + continue; + + last_fir->request_ms = now_ms; + last_fir->sequence_number = fir_request.seq_nr; + } + // Received signal that we need to send a new key frame. + packet_information->packet_type_flags |= kRtcpFir; + } +} + +void RTCPReceiver::HandleTransportFeedback( + const CommonHeader& rtcp_block, + PacketInformation* packet_information) { + std::unique_ptr<rtcp::TransportFeedback> transport_feedback( + new rtcp::TransportFeedback()); + if (!transport_feedback->Parse(rtcp_block)) { + ++num_skipped_packets_; + return; + } + + packet_information->packet_type_flags |= kRtcpTransportFeedback; + packet_information->transport_feedback = std::move(transport_feedback); +} + +void RTCPReceiver::NotifyTmmbrUpdated() { + // Find bounding set. + std::vector<rtcp::TmmbItem> bounding = + TMMBRHelp::FindBoundingSet(TmmbrReceived()); + + if (!bounding.empty() && rtcp_bandwidth_observer_) { + // We have a new bandwidth estimate on this channel. + uint64_t bitrate_bps = TMMBRHelp::CalcMinBitrateBps(bounding); + if (bitrate_bps <= std::numeric_limits<uint32_t>::max()) + rtcp_bandwidth_observer_->OnReceivedEstimatedBitrate(bitrate_bps); + } + + // Send tmmbn to inform remote clients about the new bandwidth. + rtp_rtcp_->SetTmmbn(std::move(bounding)); +} + +// Holding no Critical section. +void RTCPReceiver::TriggerCallbacksFromRtcpPacket( + const PacketInformation& packet_information) { + // Process TMMBR and REMB first to avoid multiple callbacks + // to OnNetworkChanged. + if (packet_information.packet_type_flags & kRtcpTmmbr) { + // Might trigger a OnReceivedBandwidthEstimateUpdate. + NotifyTmmbrUpdated(); + } + + if (!receiver_only_ && (packet_information.packet_type_flags & kRtcpSrReq)) { + rtp_rtcp_->OnRequestSendReport(); + } + if (!receiver_only_ && (packet_information.packet_type_flags & kRtcpNack)) { + if (!packet_information.nack_sequence_numbers.empty()) { + RTC_LOG(LS_VERBOSE) << "Incoming NACK length: " + << packet_information.nack_sequence_numbers.size(); + rtp_rtcp_->OnReceivedNack(packet_information.nack_sequence_numbers); + } + } + + // We need feedback that we have received a report block(s) so that we + // can generate a new packet in a conference relay scenario, one received + // report can generate several RTCP packets, based on number relayed/mixed + // a send report block should go out to all receivers. + if (rtcp_intra_frame_observer_) { + RTC_DCHECK(!receiver_only_); + if ((packet_information.packet_type_flags & kRtcpPli) || + (packet_information.packet_type_flags & kRtcpFir)) { + if (packet_information.packet_type_flags & kRtcpPli) { + RTC_LOG(LS_VERBOSE) + << "Incoming PLI from SSRC " << packet_information.remote_ssrc; + } else { + RTC_LOG(LS_VERBOSE) + << "Incoming FIR from SSRC " << packet_information.remote_ssrc; + } + rtcp_intra_frame_observer_->OnReceivedIntraFrameRequest( + local_media_ssrc()); + } + } + if (rtcp_loss_notification_observer_ && + (packet_information.packet_type_flags & kRtcpLossNotification)) { + rtcp::LossNotification* loss_notification = + packet_information.loss_notification.get(); + RTC_DCHECK(loss_notification); + if (loss_notification->media_ssrc() == local_media_ssrc()) { + rtcp_loss_notification_observer_->OnReceivedLossNotification( + loss_notification->media_ssrc(), loss_notification->last_decoded(), + loss_notification->last_received(), + loss_notification->decodability_flag()); + } + } + if (rtcp_bandwidth_observer_) { + RTC_DCHECK(!receiver_only_); + if (packet_information.packet_type_flags & kRtcpRemb) { + RTC_LOG(LS_VERBOSE) + << "Incoming REMB: " + << packet_information.receiver_estimated_max_bitrate_bps; + rtcp_bandwidth_observer_->OnReceivedEstimatedBitrate( + packet_information.receiver_estimated_max_bitrate_bps); + } + if ((packet_information.packet_type_flags & kRtcpSr) || + (packet_information.packet_type_flags & kRtcpRr)) { + int64_t now_ms = clock_->TimeInMilliseconds(); + rtcp_bandwidth_observer_->OnReceivedRtcpReceiverReport( + packet_information.report_blocks, packet_information.rtt_ms, now_ms); + } + } + if ((packet_information.packet_type_flags & kRtcpSr) || + (packet_information.packet_type_flags & kRtcpRr)) { + rtp_rtcp_->OnReceivedRtcpReportBlocks(packet_information.report_blocks); + } + + if (transport_feedback_observer_ && + (packet_information.packet_type_flags & kRtcpTransportFeedback)) { + uint32_t media_source_ssrc = + packet_information.transport_feedback->media_ssrc(); + if (media_source_ssrc == local_media_ssrc() || + registered_ssrcs_.contains(media_source_ssrc)) { + transport_feedback_observer_->OnTransportFeedback( + *packet_information.transport_feedback); + } + } + + if (network_state_estimate_observer_ && + packet_information.network_state_estimate) { + network_state_estimate_observer_->OnRemoteNetworkEstimate( + *packet_information.network_state_estimate); + } + + if (bitrate_allocation_observer_ && + packet_information.target_bitrate_allocation) { + bitrate_allocation_observer_->OnBitrateAllocationUpdated( + *packet_information.target_bitrate_allocation); + } + + if (!receiver_only_) { + if (report_block_data_observer_) { + for (const auto& report_block_data : + packet_information.report_block_datas) { + report_block_data_observer_->OnReportBlockDataUpdated( + report_block_data); + } + } + } +} + +std::vector<rtcp::TmmbItem> RTCPReceiver::TmmbrReceived() { + MutexLock lock(&rtcp_receiver_lock_); + std::vector<rtcp::TmmbItem> candidates; + + int64_t now_ms = clock_->TimeInMilliseconds(); + int64_t timeout_ms = now_ms - kTmmbrTimeoutIntervalMs; + + for (auto& kv : tmmbr_infos_) { + for (auto it = kv.second.tmmbr.begin(); it != kv.second.tmmbr.end();) { + if (it->second.last_updated_ms < timeout_ms) { + // Erase timeout entries. + it = kv.second.tmmbr.erase(it); + } else { + candidates.push_back(it->second.tmmbr_item); + ++it; + } + } + } + return candidates; +} + +bool RTCPReceiver::RtcpRrTimeoutLocked(Timestamp now) { + bool result = ResetTimestampIfExpired(now, last_received_rb_, report_interval_); + if (result && rtcp_event_observer_) { + rtcp_event_observer_->OnRtcpTimeout(); + } + return result; +} + +bool RTCPReceiver::RtcpRrSequenceNumberTimeoutLocked(Timestamp now) { + bool result = ResetTimestampIfExpired(now, last_increased_sequence_number_, + report_interval_); + if (result && rtcp_event_observer_) { + rtcp_event_observer_->OnRtcpTimeout(); + } + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.h new file mode 100644 index 0000000000..e3f5bc765c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.h @@ -0,0 +1,457 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_RECEIVER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_RECEIVER_H_ + +#include <list> +#include <map> +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/sequence_checker.h" +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/include/report_block_data.h" +#include "modules/rtp_rtcp/include/rtcp_statistics.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_nack_stats.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "rtc_base/containers/flat_map.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { + +class ModuleRtpRtcpImpl2; +class VideoBitrateAllocationObserver; + +namespace rtcp { +class CommonHeader; +class ReportBlock; +class Rrtr; +class TargetBitrate; +class TmmbItem; +} // namespace rtcp + +class RTCPReceiver final { + public: + class ModuleRtpRtcp { + public: + virtual void SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) = 0; + virtual void OnRequestSendReport() = 0; + virtual void OnReceivedNack( + const std::vector<uint16_t>& nack_sequence_numbers) = 0; + virtual void OnReceivedRtcpReportBlocks( + const ReportBlockList& report_blocks) = 0; + + protected: + virtual ~ModuleRtpRtcp() = default; + }; + // Standardized stats derived from the non-sender RTT. + class NonSenderRttStats { + public: + NonSenderRttStats() = default; + NonSenderRttStats(const NonSenderRttStats&) = default; + NonSenderRttStats& operator=(const NonSenderRttStats&) = default; + ~NonSenderRttStats() = default; + void Update(TimeDelta non_sender_rtt_seconds) { + round_trip_time_ = non_sender_rtt_seconds; + total_round_trip_time_ += non_sender_rtt_seconds; + round_trip_time_measurements_++; + } + void Invalidate() { round_trip_time_.reset(); } + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime + absl::optional<TimeDelta> round_trip_time() const { + return round_trip_time_; + } + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-totalroundtriptime + TimeDelta total_round_trip_time() const { return total_round_trip_time_; } + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptimemeasurements + int round_trip_time_measurements() const { + return round_trip_time_measurements_; + } + + private: + absl::optional<TimeDelta> round_trip_time_; + TimeDelta total_round_trip_time_ = TimeDelta::Zero(); + int round_trip_time_measurements_ = 0; + }; + + RTCPReceiver(const RtpRtcpInterface::Configuration& config, + ModuleRtpRtcp* owner); + + RTCPReceiver(const RtpRtcpInterface::Configuration& config, + ModuleRtpRtcpImpl2* owner); + + ~RTCPReceiver(); + + void IncomingPacket(const uint8_t* packet, size_t packet_size) { + IncomingPacket(rtc::MakeArrayView(packet, packet_size)); + } + void IncomingPacket(rtc::ArrayView<const uint8_t> packet); + + int64_t LastReceivedReportBlockMs() const; + + void set_local_media_ssrc(uint32_t ssrc); + uint32_t local_media_ssrc() const; + + void SetRemoteSSRC(uint32_t ssrc); + uint32_t RemoteSSRC() const; + + bool receiver_only() const { return receiver_only_; } + + // Get received NTP. + // The types for the arguments below derive from the specification: + // - `remote_sender_packet_count`: `RTCSentRtpStreamStats.packetsSent` [1] + // - `remote_sender_octet_count`: `RTCSentRtpStreamStats.bytesSent` [1] + // - `remote_sender_reports_count`: + // `RTCRemoteOutboundRtpStreamStats.reportsSent` [2] + // [1] https://www.w3.org/TR/webrtc-stats/#remoteoutboundrtpstats-dict* + // [2] https://www.w3.org/TR/webrtc-stats/#dom-rtcsentrtpstreamstats + bool NTP(uint32_t* received_ntp_secs, + uint32_t* received_ntp_frac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp, + uint32_t* remote_sender_packet_count, + uint64_t* remote_sender_octet_count, + uint64_t* remote_sender_reports_count) const; + + std::vector<rtcp::ReceiveTimeInfo> ConsumeReceivedXrReferenceTimeInfo(); + + // Get received sender packet and octet counts + void RemoteRTCPSenderInfo(uint32_t* packet_count, + uint32_t* octet_count, + int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const; + + // Get rtt. + int32_t RTT(uint32_t remote_ssrc, + int64_t* last_rtt_ms, + int64_t* avg_rtt_ms, + int64_t* min_rtt_ms, + int64_t* max_rtt_ms) const; + + // Returns non-sender RTT metrics for the remote SSRC. + NonSenderRttStats GetNonSenderRTT() const; + + void SetNonSenderRttMeasurement(bool enabled); + bool GetAndResetXrRrRtt(int64_t* rtt_ms); + + // Called once per second on the worker thread to do rtt calculations. + // Returns an optional rtt value if one is available. + absl::optional<TimeDelta> OnPeriodicRttUpdate(Timestamp newer_than, + bool sending); + + // A snapshot of Report Blocks with additional data of interest to statistics. + // Within this list, the source SSRC is unique and ReportBlockData represents + // the latest Report Block that was received for that SSRC. + std::vector<ReportBlockData> GetLatestReportBlockData() const; + + // Returns true if we haven't received an RTCP RR for several RTCP + // intervals, but only triggers true once. + bool RtcpRrTimeout(); + + // Returns true if we haven't received an RTCP RR telling the receive side + // has not received RTP packets for too long, i.e. extended highest sequence + // number hasn't increased for several RTCP intervals. The function only + // returns true once until a new RR is received. + bool RtcpRrSequenceNumberTimeout(); + + std::vector<rtcp::TmmbItem> TmmbrReceived(); + // Return true if new bandwidth should be set. + bool UpdateTmmbrTimers(); + std::vector<rtcp::TmmbItem> BoundingSet(bool* tmmbr_owner); + // Set new bandwidth and notify remote clients about it. + void NotifyTmmbrUpdated(); + + private: +#if RTC_DCHECK_IS_ON + class CustomSequenceChecker : public SequenceChecker { + public: + explicit CustomSequenceChecker(bool disable_checks) + : disable_checks_(disable_checks) {} + bool IsCurrent() const { + if (disable_checks_) + return true; + return SequenceChecker::IsCurrent(); + } + + private: + const bool disable_checks_; + }; +#else + class CustomSequenceChecker : public SequenceChecker { + public: + explicit CustomSequenceChecker(bool) {} + }; +#endif + + // A lightweight inlined set of local SSRCs. + class RegisteredSsrcs { + public: + static constexpr size_t kMediaSsrcIndex = 0; + static constexpr size_t kMaxSsrcs = 3; + // Initializes the set of registered local SSRCS by extracting them from the + // provided `config`. The `disable_sequence_checker` flag is a workaround + // to be able to use a sequence checker without breaking downstream + // code that currently doesn't follow the same threading rules as webrtc. + RegisteredSsrcs(bool disable_sequence_checker, + const RtpRtcpInterface::Configuration& config); + + // Indicates if `ssrc` is in the set of registered local SSRCs. + bool contains(uint32_t ssrc) const; + uint32_t media_ssrc() const; + void set_media_ssrc(uint32_t ssrc); + + private: + RTC_NO_UNIQUE_ADDRESS CustomSequenceChecker packet_sequence_checker_; + absl::InlinedVector<uint32_t, kMaxSsrcs> ssrcs_ + RTC_GUARDED_BY(packet_sequence_checker_); + }; + + struct PacketInformation; + + // Structure for handing TMMBR and TMMBN rtcp messages (RFC5104, + // section 3.5.4). + struct TmmbrInformation { + struct TimedTmmbrItem { + rtcp::TmmbItem tmmbr_item; + int64_t last_updated_ms; + }; + + int64_t last_time_received_ms = 0; + + bool ready_for_delete = false; + + std::vector<rtcp::TmmbItem> tmmbn; + std::map<uint32_t, TimedTmmbrItem> tmmbr; + }; + + // Structure for storing received RRTR RTCP messages (RFC3611, section 4.4). + struct RrtrInformation { + RrtrInformation(uint32_t ssrc, + uint32_t received_remote_mid_ntp_time, + uint32_t local_receive_mid_ntp_time) + : ssrc(ssrc), + received_remote_mid_ntp_time(received_remote_mid_ntp_time), + local_receive_mid_ntp_time(local_receive_mid_ntp_time) {} + + uint32_t ssrc; + // Received NTP timestamp in compact representation. + uint32_t received_remote_mid_ntp_time; + // NTP time when the report was received in compact representation. + uint32_t local_receive_mid_ntp_time; + }; + + struct LastFirStatus { + LastFirStatus(int64_t now_ms, uint8_t sequence_number) + : request_ms(now_ms), sequence_number(sequence_number) {} + int64_t request_ms; + uint8_t sequence_number; + }; + + class RttStats { + public: + RttStats() = default; + RttStats(const RttStats&) = default; + RttStats& operator=(const RttStats&) = default; + + void AddRtt(TimeDelta rtt); + + TimeDelta last_rtt() const { return last_rtt_; } + TimeDelta min_rtt() const { return min_rtt_; } + TimeDelta max_rtt() const { return max_rtt_; } + TimeDelta average_rtt() const { return sum_rtt_ / num_rtts_; } + + private: + TimeDelta last_rtt_ = TimeDelta::Zero(); + TimeDelta min_rtt_ = TimeDelta::PlusInfinity(); + TimeDelta max_rtt_ = TimeDelta::MinusInfinity(); + TimeDelta sum_rtt_ = TimeDelta::Zero(); + size_t num_rtts_ = 0; + }; + + bool ParseCompoundPacket(rtc::ArrayView<const uint8_t> packet, + PacketInformation* packet_information); + + void TriggerCallbacksFromRtcpPacket( + const PacketInformation& packet_information); + + TmmbrInformation* FindOrCreateTmmbrInfo(uint32_t remote_ssrc) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + // Update TmmbrInformation (if present) is alive. + void UpdateTmmbrRemoteIsAlive(uint32_t remote_ssrc) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + TmmbrInformation* GetTmmbrInformation(uint32_t remote_ssrc) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleSenderReport(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleReceiverReport(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleReportBlock(const rtcp::ReportBlock& report_block, + PacketInformation* packet_information, + uint32_t remote_ssrc) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleSdes(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleXr(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information, + bool& contains_dlrr, + uint32_t& ssrc) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleXrReceiveReferenceTime(uint32_t sender_ssrc, + const rtcp::Rrtr& rrtr) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleXrDlrrReportBlock(uint32_t ssrc, const rtcp::ReceiveTimeInfo& rti) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleXrTargetBitrate(uint32_t ssrc, + const rtcp::TargetBitrate& target_bitrate, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleNack(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleApp(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleBye(const rtcp::CommonHeader& rtcp_block) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandlePli(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandlePsfbApp(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleTmmbr(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleTmmbn(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleSrReq(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleFir(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + void HandleTransportFeedback(const rtcp::CommonHeader& rtcp_block, + PacketInformation* packet_information) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + bool RtcpRrTimeoutLocked(Timestamp now) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + bool RtcpRrSequenceNumberTimeoutLocked(Timestamp now) + RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_); + + Clock* const clock_; + const bool receiver_only_; + ModuleRtpRtcp* const rtp_rtcp_; + // The set of registered local SSRCs. + RegisteredSsrcs registered_ssrcs_; + + RtcpBandwidthObserver* const rtcp_bandwidth_observer_; + RtcpEventObserver* const rtcp_event_observer_; + RtcpIntraFrameObserver* const rtcp_intra_frame_observer_; + RtcpLossNotificationObserver* const rtcp_loss_notification_observer_; + NetworkStateEstimateObserver* const network_state_estimate_observer_; + TransportFeedbackObserver* const transport_feedback_observer_; + VideoBitrateAllocationObserver* const bitrate_allocation_observer_; + const TimeDelta report_interval_; + + mutable Mutex rtcp_receiver_lock_; + uint32_t remote_ssrc_ RTC_GUARDED_BY(rtcp_receiver_lock_); + + // Received sender report. + NtpTime remote_sender_ntp_time_ RTC_GUARDED_BY(rtcp_receiver_lock_); + uint32_t remote_sender_rtp_time_ RTC_GUARDED_BY(rtcp_receiver_lock_); + // When did we receive the last send report. + NtpTime last_received_sr_ntp_ RTC_GUARDED_BY(rtcp_receiver_lock_); + uint32_t remote_sender_packet_count_ RTC_GUARDED_BY(rtcp_receiver_lock_); + uint64_t remote_sender_octet_count_ RTC_GUARDED_BY(rtcp_receiver_lock_); + uint64_t remote_sender_reports_count_ RTC_GUARDED_BY(rtcp_receiver_lock_); + + // Received RRTR information in ascending receive time order. + std::list<RrtrInformation> received_rrtrs_ + RTC_GUARDED_BY(rtcp_receiver_lock_); + // Received RRTR information mapped by remote ssrc. + flat_map<uint32_t, std::list<RrtrInformation>::iterator> + received_rrtrs_ssrc_it_ RTC_GUARDED_BY(rtcp_receiver_lock_); + + // Estimated rtt, zero when there is no valid estimate. + bool xr_rrtr_status_ RTC_GUARDED_BY(rtcp_receiver_lock_); + int64_t xr_rr_rtt_ms_; + + int64_t oldest_tmmbr_info_ms_ RTC_GUARDED_BY(rtcp_receiver_lock_); + // Mapped by remote ssrc. + flat_map<uint32_t, TmmbrInformation> tmmbr_infos_ + RTC_GUARDED_BY(rtcp_receiver_lock_); + + // Round-Trip Time per remote sender ssrc. + flat_map<uint32_t, RttStats> rtts_ RTC_GUARDED_BY(rtcp_receiver_lock_); + // Non-sender Round-trip time per remote ssrc. + flat_map<uint32_t, NonSenderRttStats> non_sender_rtts_ + RTC_GUARDED_BY(rtcp_receiver_lock_); + + // Report blocks per local source ssrc. + flat_map<uint32_t, ReportBlockData> received_report_blocks_ + RTC_GUARDED_BY(rtcp_receiver_lock_); + flat_map<uint32_t, LastFirStatus> last_fir_ + RTC_GUARDED_BY(rtcp_receiver_lock_); + + // The last time we received an RTCP Report block for this module. + Timestamp last_received_rb_ RTC_GUARDED_BY(rtcp_receiver_lock_) = + Timestamp::PlusInfinity(); + + // The time we last received an RTCP RR telling we have successfully + // delivered RTP packet to the remote side. + Timestamp last_increased_sequence_number_ = Timestamp::PlusInfinity(); + + RtcpCnameCallback* const cname_callback_; + ReportBlockDataObserver* const report_block_data_observer_; + + RtcpPacketTypeCounterObserver* const packet_type_counter_observer_; + RtcpPacketTypeCounter packet_type_counter_; + + RtcpNackStats nack_stats_; + + size_t num_skipped_packets_; + int64_t last_skipped_packets_warning_ms_; +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_RECEIVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc new file mode 100644 index 0000000000..ff39ccca9c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc @@ -0,0 +1,2012 @@ +/* + * 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 "modules/rtp_rtcp/source/rtcp_receiver.h" + +#include <memory> +#include <set> +#include <utility> + +#include "api/array_view.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_bitrate_allocation.h" +#include "api/video/video_bitrate_allocator.h" +#include "modules/rtp_rtcp/include/report_block_data.h" +#include "modules/rtp_rtcp/mocks/mock_rtcp_bandwidth_observer.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet.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/compound_packet.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/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/tmmbr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/fake_clock.h" +#include "rtc_base/random.h" +#include "system_wrappers/include/ntp_time.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using rtcp::ReceiveTimeInfo; +using ::testing::_; +using ::testing::AllOf; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Field; +using ::testing::InSequence; +using ::testing::IsEmpty; +using ::testing::NiceMock; +using ::testing::Property; +using ::testing::SizeIs; +using ::testing::StrEq; +using ::testing::StrictMock; +using ::testing::UnorderedElementsAre; + +class MockRtcpPacketTypeCounterObserver : public RtcpPacketTypeCounterObserver { + public: + MOCK_METHOD(void, + RtcpPacketTypesCounterUpdated, + (uint32_t, const RtcpPacketTypeCounter&), + (override)); +}; + +class MockRtcpIntraFrameObserver : public RtcpIntraFrameObserver { + public: + MOCK_METHOD(void, OnReceivedIntraFrameRequest, (uint32_t), (override)); +}; + +class MockRtcpLossNotificationObserver : public RtcpLossNotificationObserver { + public: + ~MockRtcpLossNotificationObserver() override = default; + MOCK_METHOD(void, + OnReceivedLossNotification, + (uint32_t ssrc, + uint16_t seq_num_of_last_decodable, + uint16_t seq_num_of_last_received, + bool decodability_flag), + (override)); +}; + +class MockCnameCallbackImpl : public RtcpCnameCallback { + public: + MOCK_METHOD(void, OnCname, (uint32_t, absl::string_view), (override)); +}; + +class MockReportBlockDataObserverImpl : public ReportBlockDataObserver { + public: + MOCK_METHOD(void, OnReportBlockDataUpdated, (ReportBlockData), (override)); +}; + +class MockTransportFeedbackObserver : public TransportFeedbackObserver { + public: + MOCK_METHOD(void, OnAddPacket, (const RtpPacketSendInfo&), (override)); + MOCK_METHOD(void, + OnTransportFeedback, + (const rtcp::TransportFeedback&), + (override)); +}; + +class MockModuleRtpRtcp : public RTCPReceiver::ModuleRtpRtcp { + public: + MOCK_METHOD(void, SetTmmbn, (std::vector<rtcp::TmmbItem>), (override)); + MOCK_METHOD(void, OnRequestSendReport, (), (override)); + MOCK_METHOD(void, OnReceivedNack, (const std::vector<uint16_t>&), (override)); + MOCK_METHOD(void, + OnReceivedRtcpReportBlocks, + (const ReportBlockList&), + (override)); +}; + +class MockVideoBitrateAllocationObserver + : public VideoBitrateAllocationObserver { + public: + MOCK_METHOD(void, + OnBitrateAllocationUpdated, + (const VideoBitrateAllocation& allocation), + (override)); +}; + +// SSRC of remote peer, that sends rtcp packet to the rtcp receiver under test. +constexpr uint32_t kSenderSsrc = 0x10203; +// SSRCs of local peer, that rtcp packet addressed to. +constexpr uint32_t kReceiverMainSsrc = 0x123456; +// RtcpReceiver can accept several ssrc, e.g. regular and rtx streams. +constexpr uint32_t kReceiverExtraSsrc = 0x1234567; +// SSRCs to ignore (i.e. not configured in RtcpReceiver). +constexpr uint32_t kNotToUsSsrc = 0x654321; +constexpr uint32_t kUnknownSenderSsrc = 0x54321; + +constexpr int64_t kRtcpIntervalMs = 1000; + +} // namespace + +struct ReceiverMocks { + ReceiverMocks() : clock(1335900000) {} + + SimulatedClock clock; + // Callbacks to packet_type_counter_observer are frequent but most of the time + // are not interesting. + NiceMock<MockRtcpPacketTypeCounterObserver> packet_type_counter_observer; + StrictMock<MockRtcpBandwidthObserver> bandwidth_observer; + StrictMock<MockRtcpIntraFrameObserver> intra_frame_observer; + StrictMock<MockRtcpLossNotificationObserver> rtcp_loss_notification_observer; + StrictMock<MockTransportFeedbackObserver> transport_feedback_observer; + StrictMock<MockVideoBitrateAllocationObserver> bitrate_allocation_observer; + StrictMock<MockModuleRtpRtcp> rtp_rtcp_impl; +}; + +RtpRtcpInterface::Configuration DefaultConfiguration(ReceiverMocks* mocks) { + RtpRtcpInterface::Configuration config; + config.clock = &mocks->clock; + config.receiver_only = false; + config.rtcp_packet_type_counter_observer = + &mocks->packet_type_counter_observer; + config.bandwidth_callback = &mocks->bandwidth_observer; + config.intra_frame_callback = &mocks->intra_frame_observer; + config.rtcp_loss_notification_observer = + &mocks->rtcp_loss_notification_observer; + config.transport_feedback_callback = &mocks->transport_feedback_observer; + config.bitrate_allocation_observer = &mocks->bitrate_allocation_observer; + config.rtcp_report_interval_ms = kRtcpIntervalMs; + config.local_media_ssrc = kReceiverMainSsrc; + config.rtx_send_ssrc = kReceiverExtraSsrc; + return config; +} + +TEST(RtcpReceiverTest, BrokenPacketIsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + + const uint8_t bad_packet[] = {0, 0, 0, 0}; + EXPECT_CALL(mocks.packet_type_counter_observer, RtcpPacketTypesCounterUpdated) + .Times(0); + receiver.IncomingPacket(bad_packet); +} + +TEST(RtcpReceiverTest, InvalidFeedbackPacketIsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + + // Too short feedback packet. + const uint8_t bad_packet[] = {0x81, rtcp::Rtpfb::kPacketType, 0, 0}; + + // TODO(danilchap): Add expectation RtcpPacketTypesCounterUpdated + // is not called once parser would be adjusted to avoid that callback on + // semi-valid packets. + receiver.IncomingPacket(bad_packet); +} + +TEST(RtcpReceiverTest, InjectSrPacket) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + EXPECT_FALSE(receiver.NTP(nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr)); + + int64_t now = mocks.clock.TimeInMilliseconds(); + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(IsEmpty())); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(IsEmpty(), _, now)); + receiver.IncomingPacket(sr.Build()); + + EXPECT_TRUE(receiver.NTP(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr)); +} + +TEST(RtcpReceiverTest, InjectSrPacketFromUnknownSender) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t now = mocks.clock.TimeInMilliseconds(); + rtcp::SenderReport sr; + sr.SetSenderSsrc(kUnknownSenderSsrc); + + // The parser will handle report blocks in Sender Report from other than their + // expected peer. + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(_, _, now)); + receiver.IncomingPacket(sr.Build()); + + // But will not flag that he's gotten sender information. + EXPECT_FALSE(receiver.NTP(nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr)); +} + +TEST(RtcpReceiverTest, InjectSrPacketCalculatesRTT) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const TimeDelta kRtt = TimeDelta::Millis(123); + const uint32_t kDelayNtp = 0x4321; + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + + int64_t rtt_ms = 0; + EXPECT_EQ(-1, receiver.RTT(kSenderSsrc, &rtt_ms, nullptr, nullptr, nullptr)); + + uint32_t sent_ntp = CompactNtp(mocks.clock.CurrentNtpTime()); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + rtcp::ReportBlock block; + block.SetMediaSsrc(kReceiverMainSsrc); + block.SetLastSr(sent_ntp); + block.SetDelayLastSr(kDelayNtp); + sr.AddReportBlock(block); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(sr.Build()); + + EXPECT_EQ(0, receiver.RTT(kSenderSsrc, &rtt_ms, nullptr, nullptr, nullptr)); + EXPECT_NEAR(rtt_ms, kRtt.ms(), 1); +} + +TEST(RtcpReceiverTest, InjectSrPacketCalculatesNegativeRTTAsOne) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const TimeDelta kRtt = TimeDelta::Millis(-13); + const uint32_t kDelayNtp = 0x4321; + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + + int64_t rtt_ms = 0; + EXPECT_EQ(-1, receiver.RTT(kSenderSsrc, &rtt_ms, nullptr, nullptr, nullptr)); + + uint32_t sent_ntp = CompactNtp(mocks.clock.CurrentNtpTime()); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + rtcp::ReportBlock block; + block.SetMediaSsrc(kReceiverMainSsrc); + block.SetLastSr(sent_ntp); + block.SetDelayLastSr(kDelayNtp); + sr.AddReportBlock(block); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(1))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(1), _, _)); + receiver.IncomingPacket(sr.Build()); + + EXPECT_EQ(0, receiver.RTT(kSenderSsrc, &rtt_ms, nullptr, nullptr, nullptr)); + EXPECT_EQ(1, rtt_ms); +} + +TEST(RtcpReceiverTest, + TwoReportBlocksWithLastOneWithoutLastSrCalculatesRttForBandwidthObserver) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const TimeDelta kRtt = TimeDelta::Millis(120); + const uint32_t kDelayNtp = 123000; + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + + uint32_t sent_ntp = CompactNtp(mocks.clock.CurrentNtpTime()); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + rtcp::ReportBlock block; + block.SetMediaSsrc(kReceiverMainSsrc); + block.SetLastSr(sent_ntp); + block.SetDelayLastSr(kDelayNtp); + sr.AddReportBlock(block); + block.SetMediaSsrc(kReceiverExtraSsrc); + block.SetLastSr(0); + sr.AddReportBlock(block); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(2))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(2), kRtt.ms(), _)); + receiver.IncomingPacket(sr.Build()); +} + +TEST(RtcpReceiverTest, InjectRrPacket) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t now = mocks.clock.TimeInMilliseconds(); + rtcp::ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(IsEmpty())); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(IsEmpty(), _, now)); + receiver.IncomingPacket(rr.Build()); + + EXPECT_THAT(receiver.GetLatestReportBlockData(), IsEmpty()); +} + +TEST(RtcpReceiverTest, InjectRrPacketWithReportBlockNotToUsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t now = mocks.clock.TimeInMilliseconds(); + rtcp::ReportBlock rb; + rb.SetMediaSsrc(kNotToUsSsrc); + rtcp::ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + rr.AddReportBlock(rb); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(IsEmpty())); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(IsEmpty(), _, now)); + receiver.IncomingPacket(rr.Build()); + + EXPECT_EQ(0, receiver.LastReceivedReportBlockMs()); + EXPECT_THAT(receiver.GetLatestReportBlockData(), IsEmpty()); +} + +TEST(RtcpReceiverTest, InjectRrPacketWithOneReportBlock) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t now = mocks.clock.TimeInMilliseconds(); + + rtcp::ReportBlock rb; + rb.SetMediaSsrc(kReceiverMainSsrc); + rtcp::ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + rr.AddReportBlock(rb); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(1))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(1), _, now)); + receiver.IncomingPacket(rr.Build()); + + EXPECT_EQ(now, receiver.LastReceivedReportBlockMs()); + EXPECT_THAT(receiver.GetLatestReportBlockData(), SizeIs(1)); +} + +TEST(RtcpReceiverTest, InjectSrPacketWithOneReportBlock) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t now = mocks.clock.TimeInMilliseconds(); + + rtcp::ReportBlock rb; + rb.SetMediaSsrc(kReceiverMainSsrc); + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.AddReportBlock(rb); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(1))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(1), _, now)); + receiver.IncomingPacket(sr.Build()); + + EXPECT_EQ(now, receiver.LastReceivedReportBlockMs()); + EXPECT_THAT(receiver.GetLatestReportBlockData(), SizeIs(1)); +} + +TEST(RtcpReceiverTest, InjectRrPacketWithTwoReportBlocks) { + const uint16_t kSequenceNumbers[] = {10, 12423}; + const uint32_t kCumLost[] = {13, 555}; + const uint8_t kFracLost[] = {20, 11}; + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t now = mocks.clock.TimeInMilliseconds(); + + rtcp::ReportBlock rb1; + rb1.SetMediaSsrc(kReceiverMainSsrc); + rb1.SetExtHighestSeqNum(kSequenceNumbers[0]); + rb1.SetFractionLost(10); + + rtcp::ReportBlock rb2; + rb2.SetMediaSsrc(kReceiverExtraSsrc); + rb2.SetExtHighestSeqNum(kSequenceNumbers[1]); + rb2.SetFractionLost(0); + + rtcp::ReceiverReport rr1; + rr1.SetSenderSsrc(kSenderSsrc); + rr1.AddReportBlock(rb1); + rr1.AddReportBlock(rb2); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(2))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(2), _, now)); + receiver.IncomingPacket(rr1.Build()); + + EXPECT_EQ(now, receiver.LastReceivedReportBlockMs()); + EXPECT_THAT(receiver.GetLatestReportBlockData(), + UnorderedElementsAre( + Property(&ReportBlockData::report_block, + Field(&RTCPReportBlock::fraction_lost, 0)), + Property(&ReportBlockData::report_block, + Field(&RTCPReportBlock::fraction_lost, 10)))); + + // Insert next receiver report with same ssrc but new values. + rtcp::ReportBlock rb3; + rb3.SetMediaSsrc(kReceiverMainSsrc); + rb3.SetExtHighestSeqNum(kSequenceNumbers[0]); + rb3.SetFractionLost(kFracLost[0]); + rb3.SetCumulativeLost(kCumLost[0]); + + rtcp::ReportBlock rb4; + rb4.SetMediaSsrc(kReceiverExtraSsrc); + rb4.SetExtHighestSeqNum(kSequenceNumbers[1]); + rb4.SetFractionLost(kFracLost[1]); + rb4.SetCumulativeLost(kCumLost[1]); + + rtcp::ReceiverReport rr2; + rr2.SetSenderSsrc(kSenderSsrc); + rr2.AddReportBlock(rb3); + rr2.AddReportBlock(rb4); + + // Advance time to make 1st sent time and 2nd sent time different. + mocks.clock.AdvanceTimeMilliseconds(500); + now = mocks.clock.TimeInMilliseconds(); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(2))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(2), _, now)); + receiver.IncomingPacket(rr2.Build()); + + EXPECT_THAT( + receiver.GetLatestReportBlockData(), + UnorderedElementsAre( + Property( + &ReportBlockData::report_block, + AllOf(Field(&RTCPReportBlock::source_ssrc, kReceiverMainSsrc), + Field(&RTCPReportBlock::fraction_lost, kFracLost[0]), + Field(&RTCPReportBlock::packets_lost, kCumLost[0]), + Field(&RTCPReportBlock::extended_highest_sequence_number, + kSequenceNumbers[0]))), + Property( + &ReportBlockData::report_block, + AllOf(Field(&RTCPReportBlock::source_ssrc, kReceiverExtraSsrc), + Field(&RTCPReportBlock::fraction_lost, kFracLost[1]), + Field(&RTCPReportBlock::packets_lost, kCumLost[1]), + Field(&RTCPReportBlock::extended_highest_sequence_number, + kSequenceNumbers[1]))))); +} + +TEST(RtcpReceiverTest, + InjectRrPacketsFromTwoRemoteSsrcsReturnsLatestReportBlock) { + const uint32_t kSenderSsrc2 = 0x20304; + const uint16_t kSequenceNumbers[] = {10, 12423}; + const int32_t kCumLost[] = {13, 555}; + const uint8_t kFracLost[] = {20, 11}; + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::ReportBlock rb1; + rb1.SetMediaSsrc(kReceiverMainSsrc); + rb1.SetExtHighestSeqNum(kSequenceNumbers[0]); + rb1.SetFractionLost(kFracLost[0]); + rb1.SetCumulativeLost(kCumLost[0]); + rtcp::ReceiverReport rr1; + rr1.SetSenderSsrc(kSenderSsrc); + rr1.AddReportBlock(rb1); + + int64_t now = mocks.clock.TimeInMilliseconds(); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(1))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(1), _, now)); + receiver.IncomingPacket(rr1.Build()); + + EXPECT_EQ(now, receiver.LastReceivedReportBlockMs()); + + EXPECT_THAT( + receiver.GetLatestReportBlockData(), + ElementsAre(Property( + &ReportBlockData::report_block, + AllOf(Field(&RTCPReportBlock::source_ssrc, kReceiverMainSsrc), + Field(&RTCPReportBlock::sender_ssrc, kSenderSsrc), + Field(&RTCPReportBlock::fraction_lost, kFracLost[0]), + Field(&RTCPReportBlock::packets_lost, kCumLost[0]), + Field(&RTCPReportBlock::extended_highest_sequence_number, + kSequenceNumbers[0]))))); + + rtcp::ReportBlock rb2; + rb2.SetMediaSsrc(kReceiverMainSsrc); + rb2.SetExtHighestSeqNum(kSequenceNumbers[1]); + rb2.SetFractionLost(kFracLost[1]); + rb2.SetCumulativeLost(kCumLost[1]); + rtcp::ReceiverReport rr2; + rr2.SetSenderSsrc(kSenderSsrc2); + rr2.AddReportBlock(rb2); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks(SizeIs(1))); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedRtcpReceiverReport(SizeIs(1), _, now)); + receiver.IncomingPacket(rr2.Build()); + + EXPECT_THAT( + receiver.GetLatestReportBlockData(), + UnorderedElementsAre( + Property( + &ReportBlockData::report_block, + AllOf(Field(&RTCPReportBlock::source_ssrc, kReceiverMainSsrc), + Field(&RTCPReportBlock::sender_ssrc, kSenderSsrc2), + Field(&RTCPReportBlock::fraction_lost, kFracLost[1]), + Field(&RTCPReportBlock::packets_lost, kCumLost[1]), + Field(&RTCPReportBlock::extended_highest_sequence_number, + kSequenceNumbers[1]))))); +} + +TEST(RtcpReceiverTest, GetRtt) { + const uint32_t kSentCompactNtp = 0x1234; + const uint32_t kDelayCompactNtp = 0x222; + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + // No report block received. + EXPECT_EQ(-1, receiver.RTT(kSenderSsrc, nullptr, nullptr, nullptr, nullptr)); + + rtcp::ReportBlock rb; + rb.SetMediaSsrc(kReceiverMainSsrc); + rb.SetLastSr(kSentCompactNtp); + rb.SetDelayLastSr(kDelayCompactNtp); + + rtcp::ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + rr.AddReportBlock(rb); + int64_t now = mocks.clock.TimeInMilliseconds(); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr.Build()); + + EXPECT_EQ(now, receiver.LastReceivedReportBlockMs()); + EXPECT_EQ(0, receiver.RTT(kSenderSsrc, nullptr, nullptr, nullptr, nullptr)); +} + +// App packets are ignored. +TEST(RtcpReceiverTest, InjectApp) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::App app; + app.SetSubType(30); + app.SetName(0x17a177e); + const uint8_t kData[] = {'t', 'e', 's', 't', 'd', 'a', 't', 'a'}; + app.SetData(kData, sizeof(kData)); + + receiver.IncomingPacket(app.Build()); +} + +TEST(RtcpReceiverTest, InjectSdesWithOneChunk) { + ReceiverMocks mocks; + MockCnameCallbackImpl callback; + RtpRtcpInterface::Configuration config = DefaultConfiguration(&mocks); + config.rtcp_cname_callback = &callback; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const char kCname[] = "alice@host"; + rtcp::Sdes sdes; + sdes.AddCName(kSenderSsrc, kCname); + + EXPECT_CALL(callback, OnCname(kSenderSsrc, StrEq(kCname))); + receiver.IncomingPacket(sdes.Build()); +} + +TEST(RtcpReceiverTest, InjectByePacket_RemovesReportBlocks) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::ReportBlock rb1; + rb1.SetMediaSsrc(kReceiverMainSsrc); + rtcp::ReportBlock rb2; + rb2.SetMediaSsrc(kReceiverExtraSsrc); + rtcp::ReceiverReport rr; + rr.SetSenderSsrc(kSenderSsrc); + rr.AddReportBlock(rb1); + rr.AddReportBlock(rb2); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr.Build()); + + EXPECT_THAT(receiver.GetLatestReportBlockData(), SizeIs(2)); + + // Verify that BYE removes the report blocks. + rtcp::Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + + receiver.IncomingPacket(bye.Build()); + + EXPECT_THAT(receiver.GetLatestReportBlockData(), IsEmpty()); + + // Inject packet again. + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr.Build()); + + EXPECT_THAT(receiver.GetLatestReportBlockData(), SizeIs(2)); +} + +TEST(RtcpReceiverTest, InjectByePacketRemovesReferenceTimeInfo) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + rtcp::Rrtr rrtr; + rrtr.SetNtp(NtpTime(0x10203, 0x40506)); + xr.SetRrtr(rrtr); + receiver.IncomingPacket(xr.Build()); + + rtcp::Bye bye; + bye.SetSenderSsrc(kSenderSsrc); + receiver.IncomingPacket(bye.Build()); + + EXPECT_THAT(receiver.ConsumeReceivedXrReferenceTimeInfo(), IsEmpty()); +} + +TEST(RtcpReceiverTest, InjectPliPacket) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::Pli pli; + pli.SetMediaSsrc(kReceiverMainSsrc); + + EXPECT_CALL( + mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated( + kReceiverMainSsrc, Field(&RtcpPacketTypeCounter::pli_packets, 1))); + EXPECT_CALL(mocks.intra_frame_observer, + OnReceivedIntraFrameRequest(kReceiverMainSsrc)); + receiver.IncomingPacket(pli.Build()); +} + +TEST(RtcpReceiverTest, PliPacketNotToUsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::Pli pli; + pli.SetMediaSsrc(kNotToUsSsrc); + + EXPECT_CALL( + mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated( + kReceiverMainSsrc, Field(&RtcpPacketTypeCounter::pli_packets, 0))); + EXPECT_CALL(mocks.intra_frame_observer, OnReceivedIntraFrameRequest).Times(0); + receiver.IncomingPacket(pli.Build()); +} + +TEST(RtcpReceiverTest, InjectFirPacket) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::Fir fir; + fir.AddRequestTo(kReceiverMainSsrc, 13); + + EXPECT_CALL( + mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated( + kReceiverMainSsrc, Field(&RtcpPacketTypeCounter::fir_packets, 1))); + EXPECT_CALL(mocks.intra_frame_observer, + OnReceivedIntraFrameRequest(kReceiverMainSsrc)); + receiver.IncomingPacket(fir.Build()); +} + +TEST(RtcpReceiverTest, FirPacketNotToUsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::Fir fir; + fir.AddRequestTo(kNotToUsSsrc, 13); + + EXPECT_CALL(mocks.intra_frame_observer, OnReceivedIntraFrameRequest).Times(0); + receiver.IncomingPacket(fir.Build()); +} + +TEST(RtcpReceiverTest, ExtendedReportsPacketWithZeroReportBlocksIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + + receiver.IncomingPacket(xr.Build()); +} + +TEST(RtcpReceiverTest, InjectExtendedReportsReceiverReferenceTimePacket) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const NtpTime kNtp(0x10203, 0x40506); + rtcp::Rrtr rrtr; + rrtr.SetNtp(kNtp); + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(rrtr); + + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis = + receiver.ConsumeReceivedXrReferenceTimeInfo(); + EXPECT_THAT(last_xr_rtis, IsEmpty()); + + receiver.IncomingPacket(xr.Build()); + + last_xr_rtis = receiver.ConsumeReceivedXrReferenceTimeInfo(); + ASSERT_THAT(last_xr_rtis, SizeIs(1)); + EXPECT_EQ(kSenderSsrc, last_xr_rtis[0].ssrc); + EXPECT_EQ(CompactNtp(kNtp), last_xr_rtis[0].last_rr); + EXPECT_EQ(0U, last_xr_rtis[0].delay_since_last_rr); +} + +TEST(RtcpReceiverTest, ExtendedReportsDlrrPacketNotToUsIgnored) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + // Allow calculate rtt using dlrr/rrtr, simulating media receiver side. + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kNotToUsSsrc, 0x12345, 0x67890)); + + receiver.IncomingPacket(xr.Build()); + + int64_t rtt_ms = 0; + EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, InjectExtendedReportsDlrrPacketWithSubBlock) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint32_t kLastRR = 0x12345; + const uint32_t kDelay = 0x23456; + int64_t rtt_ms = 0; + EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, kLastRR, kDelay)); + + receiver.IncomingPacket(xr.Build()); + + uint32_t compact_ntp_now = CompactNtp(mocks.clock.CurrentNtpTime()); + EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + uint32_t rtt_ntp = compact_ntp_now - kDelay - kLastRR; + EXPECT_NEAR(CompactNtpRttToTimeDelta(rtt_ntp).ms(), rtt_ms, 1); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_GT(non_sender_rtt_stats.round_trip_time(), TimeDelta::Zero()); + EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, InjectExtendedReportsDlrrPacketWithMultipleSubBlocks) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint32_t kLastRR = 0x12345; + const uint32_t kDelay = 0x56789; + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, kLastRR, kDelay)); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc + 1, 0x12345, 0x67890)); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc + 2, 0x12345, 0x67890)); + + receiver.IncomingPacket(xr.Build()); + + uint32_t compact_ntp_now = CompactNtp(mocks.clock.CurrentNtpTime()); + int64_t rtt_ms = 0; + EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + uint32_t rtt_ntp = compact_ntp_now - kDelay - kLastRR; + EXPECT_NEAR(CompactNtpRttToTimeDelta(rtt_ntp).ms(), rtt_ms, 1); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_GT(non_sender_rtt_stats.round_trip_time(), TimeDelta::Zero()); + EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, InjectExtendedReportsPacketWithMultipleReportBlocks) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::Rrtr rrtr; + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(rrtr); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, 0x12345, 0x67890)); + + receiver.IncomingPacket(xr.Build()); + + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis = + receiver.ConsumeReceivedXrReferenceTimeInfo(); + EXPECT_THAT(last_xr_rtis, SizeIs(1)); + int64_t rtt_ms = 0; + EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms)); +} + +TEST(RtcpReceiverTest, InjectExtendedReportsPacketWithUnknownReportBlock) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::Rrtr rrtr; + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(rrtr); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, 0x12345, 0x67890)); + + rtc::Buffer packet = xr.Build(); + // Modify the DLRR block to have an unsupported block type, from 5 to 6. + ASSERT_EQ(5, packet.data()[20]); + packet.data()[20] = 6; + receiver.IncomingPacket(packet); + + // Validate Rrtr was received and processed. + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis = + receiver.ConsumeReceivedXrReferenceTimeInfo(); + EXPECT_THAT(last_xr_rtis, SizeIs(1)); + // Validate Dlrr report wasn't processed. + int64_t rtt_ms = 0; + EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, TestExtendedReportsRrRttInitiallyFalse) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + int64_t rtt_ms; + EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, RttCalculatedAfterExtendedReportsDlrr) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + int64_t rtt_ms = 0; + EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + EXPECT_NEAR(kRtt.ms(), rtt_ms, 1); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().value().IsZero()); + EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +// Same test as above but enables receive-side RTT using the setter instead of +// the config struct. +TEST(RtcpReceiverTest, SetterEnablesReceiverRtt) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = false; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + receiver.SetNonSenderRttMeasurement(true); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + int64_t rtt_ms = 0; + EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + EXPECT_NEAR(rtt_ms, kRtt.ms(), 1); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().value().IsZero()); + EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +// Same test as above but disables receive-side RTT using the setter instead of +// the config struct. +TEST(RtcpReceiverTest, DoesntCalculateRttOnReceivedDlrr) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + receiver.SetNonSenderRttMeasurement(false); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + // We expect that no RTT is available (because receive-side RTT was disabled). + int64_t rtt_ms = 0; + EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, XrDlrrCalculatesNegativeRttAsOne) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(-3600 * 1000, -1)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + int64_t rtt_ms = 0; + EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms)); + EXPECT_EQ(1, rtt_ms); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().value().IsZero()); + EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0); +} + +// Test receiver RTT stats with multiple measurements. +TEST(RtcpReceiverTest, ReceiverRttWithMultipleMeasurements) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + // Check that the non-sender RTT stats are valid and based on a single + // measurement. + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRtt.ms(), 1); + EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 1); + EXPECT_EQ(non_sender_rtt_stats.total_round_trip_time().ms(), + non_sender_rtt_stats.round_trip_time()->ms()); + + // Generate another XR report with the same RTT and delay. + NtpTime now2 = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp2 = CompactNtp(now2); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr2; + xr2.SetSenderSsrc(kSenderSsrc); + xr2.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp2, kDelayNtp)); + + receiver.IncomingPacket(xr2.Build()); + + // Check that the non-sender RTT stats are based on 2 measurements, and that + // the values are as expected. + non_sender_rtt_stats = receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRtt.ms(), 1); + EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 2); + EXPECT_NEAR(non_sender_rtt_stats.total_round_trip_time().ms(), 2 * kRtt.ms(), + 2); +} + +// Test that the receiver RTT stat resets when receiving a SR without XR. This +// behavior is described in the standard, see +// https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime. +TEST(RtcpReceiverTest, ReceiverRttResetOnSrWithoutXr) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRtt.ms(), 1); + + // Generate a SR without XR. + rtcp::ReportBlock rb; + rb.SetMediaSsrc(kReceiverMainSsrc); + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.AddReportBlock(rb); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + + receiver.IncomingPacket(sr.Build()); + + // Check that the non-sender RTT stat is not set. + non_sender_rtt_stats = receiver.GetNonSenderRTT(); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value()); +} + +// Test that the receiver RTT stat resets when receiving a DLRR with a timestamp +// of zero. This behavior is described in the standard, see +// https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime. +TEST(RtcpReceiverTest, ReceiverRttResetOnDlrrWithZeroTimestamp) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = true; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRtt.ms(), 1); + + // Generate an XR+DLRR with zero timestamp. + rtcp::ExtendedReports xr2; + xr2.SetSenderSsrc(kSenderSsrc); + xr2.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, 0, kDelayNtp)); + + receiver.IncomingPacket(xr2.Build()); + + // Check that the non-sender RTT stat is not set. + non_sender_rtt_stats = receiver.GetNonSenderRTT(); + EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value()); +} + +// Check that the receiver RTT works correctly when the remote SSRC changes. +TEST(RtcpReceiverTest, ReceiverRttWithMultipleRemoteSsrcs) { + ReceiverMocks mocks; + auto config = DefaultConfiguration(&mocks); + config.non_sender_rtt_measurement = false; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + receiver.SetNonSenderRttMeasurement(true); + + Random rand(0x0123456789abcdef); + const TimeDelta kRtt = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + NtpTime now = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp = CompactNtp(now); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp)); + + receiver.IncomingPacket(xr.Build()); + + // Generate an XR report for another SSRC. + const TimeDelta kRtt2 = TimeDelta::Millis(rand.Rand(1, 9 * 3600 * 1000)); + const uint32_t kDelayNtp2 = rand.Rand(0, 0x7fffffff); + const TimeDelta kDelay2 = CompactNtpRttToTimeDelta(kDelayNtp2); + NtpTime now2 = mocks.clock.CurrentNtpTime(); + uint32_t sent_ntp2 = CompactNtp(now2); + mocks.clock.AdvanceTime(kRtt2 + kDelay2); + + rtcp::ExtendedReports xr2; + xr2.SetSenderSsrc(kSenderSsrc + 1); + xr2.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp2, kDelayNtp2)); + + receiver.IncomingPacket(xr2.Build()); + + // Check that the non-sender RTT stats match the first XR. + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value()); + EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRtt.ms(), 1); + EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0); + + // Change the remote SSRC and check that the stats match the second XR. + receiver.SetRemoteSSRC(kSenderSsrc + 1); + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats2 = + receiver.GetNonSenderRTT(); + EXPECT_TRUE(non_sender_rtt_stats2.round_trip_time().has_value()); + EXPECT_NEAR(non_sender_rtt_stats2.round_trip_time()->ms(), kRtt2.ms(), 1); + EXPECT_FALSE(non_sender_rtt_stats2.total_round_trip_time().IsZero()); + EXPECT_GT(non_sender_rtt_stats2.round_trip_time_measurements(), 0); +} + +TEST(RtcpReceiverTest, ConsumeReceivedXrReferenceTimeInfoInitiallyEmpty) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + EXPECT_THAT(receiver.ConsumeReceivedXrReferenceTimeInfo(), IsEmpty()); +} + +TEST(RtcpReceiverTest, ConsumeReceivedXrReferenceTimeInfo) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const NtpTime kNtp(0x10203, 0x40506); + const uint32_t kNtpMid = CompactNtp(kNtp); + + rtcp::Rrtr rrtr; + rrtr.SetNtp(kNtp); + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + xr.SetRrtr(rrtr); + + receiver.IncomingPacket(xr.Build()); + + mocks.clock.AdvanceTimeMilliseconds(1000); + + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis = + receiver.ConsumeReceivedXrReferenceTimeInfo(); + ASSERT_THAT(last_xr_rtis, SizeIs(1)); + EXPECT_EQ(kSenderSsrc, last_xr_rtis[0].ssrc); + EXPECT_EQ(kNtpMid, last_xr_rtis[0].last_rr); + EXPECT_EQ(65536U, last_xr_rtis[0].delay_since_last_rr); +} + +TEST(RtcpReceiverTest, + ReceivedRrtrFromSameSsrcUpdatesReceivedReferenceTimeInfo) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const NtpTime kNtp1(0x10203, 0x40506); + const NtpTime kNtp2(0x11223, 0x44556); + const int64_t kDelayMs = 2000; + + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kSenderSsrc); + rtcp::Rrtr rrtr1; + rrtr1.SetNtp(kNtp1); + xr.SetRrtr(rrtr1); + receiver.IncomingPacket(xr.Build()); + mocks.clock.AdvanceTimeMilliseconds(kDelayMs); + rtcp::Rrtr rrtr2; + rrtr2.SetNtp(kNtp2); + xr.SetRrtr(rrtr2); + receiver.IncomingPacket(xr.Build()); + mocks.clock.AdvanceTimeMilliseconds(kDelayMs); + + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis = + receiver.ConsumeReceivedXrReferenceTimeInfo(); + ASSERT_THAT(last_xr_rtis, SizeIs(1)); + EXPECT_EQ(kSenderSsrc, last_xr_rtis[0].ssrc); + EXPECT_EQ(CompactNtp(kNtp2), last_xr_rtis[0].last_rr); + EXPECT_EQ(kDelayMs * 65536 / 1000, last_xr_rtis[0].delay_since_last_rr); +} + +TEST(RtcpReceiverTest, StoresLastReceivedRrtrPerSsrc) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const size_t kNumBufferedReports = 1; + const size_t kNumReports = + rtcp::ExtendedReports::kMaxNumberOfDlrrItems + kNumBufferedReports; + for (size_t i = 0; i < kNumReports; ++i) { + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(i * 100); + rtcp::Rrtr rrtr; + rrtr.SetNtp(NtpTime(i * 200, i * 300)); + xr.SetRrtr(rrtr); + receiver.IncomingPacket(xr.Build()); + mocks.clock.AdvanceTimeMilliseconds(1000); + } + + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis = + receiver.ConsumeReceivedXrReferenceTimeInfo(); + ASSERT_THAT(last_xr_rtis, + SizeIs(rtcp::ExtendedReports::kMaxNumberOfDlrrItems)); + for (size_t i = 0; i < rtcp::ExtendedReports::kMaxNumberOfDlrrItems; ++i) { + EXPECT_EQ(i * 100, last_xr_rtis[i].ssrc); + EXPECT_EQ(CompactNtp(NtpTime(i * 200, i * 300)), last_xr_rtis[i].last_rr); + EXPECT_EQ(65536U * (kNumReports - i), last_xr_rtis[i].delay_since_last_rr); + } + + last_xr_rtis = receiver.ConsumeReceivedXrReferenceTimeInfo(); + ASSERT_THAT(last_xr_rtis, SizeIs(kNumBufferedReports)); +} + +TEST(RtcpReceiverTest, ReceiveReportTimeout) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint16_t kSequenceNumber = 1234; + mocks.clock.AdvanceTimeMilliseconds(3 * kRtcpIntervalMs); + + // No RR received, shouldn't trigger a timeout. + EXPECT_FALSE(receiver.RtcpRrTimeout()); + EXPECT_FALSE(receiver.RtcpRrSequenceNumberTimeout()); + + // Add a RR and advance the clock just enough to not trigger a timeout. + rtcp::ReportBlock rb1; + rb1.SetMediaSsrc(kReceiverMainSsrc); + rb1.SetExtHighestSeqNum(kSequenceNumber); + rtcp::ReceiverReport rr1; + rr1.SetSenderSsrc(kSenderSsrc); + rr1.AddReportBlock(rb1); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr1.Build()); + + mocks.clock.AdvanceTimeMilliseconds(3 * kRtcpIntervalMs - 1); + EXPECT_FALSE(receiver.RtcpRrTimeout()); + EXPECT_FALSE(receiver.RtcpRrSequenceNumberTimeout()); + + // Add a RR with the same extended max as the previous RR to trigger a + // sequence number timeout, but not a RR timeout. + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr1.Build()); + + mocks.clock.AdvanceTimeMilliseconds(2); + EXPECT_FALSE(receiver.RtcpRrTimeout()); + EXPECT_TRUE(receiver.RtcpRrSequenceNumberTimeout()); + + // Advance clock enough to trigger an RR timeout too. + mocks.clock.AdvanceTimeMilliseconds(3 * kRtcpIntervalMs); + EXPECT_TRUE(receiver.RtcpRrTimeout()); + + // We should only get one timeout even though we still haven't received a new + // RR. + EXPECT_FALSE(receiver.RtcpRrTimeout()); + EXPECT_FALSE(receiver.RtcpRrSequenceNumberTimeout()); + + // Add a new RR with increase sequence number to reset timers. + rtcp::ReportBlock rb2; + rb2.SetMediaSsrc(kReceiverMainSsrc); + rb2.SetExtHighestSeqNum(kSequenceNumber + 1); + rtcp::ReceiverReport rr2; + rr2.SetSenderSsrc(kSenderSsrc); + rr2.AddReportBlock(rb2); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr2.Build()); + + EXPECT_FALSE(receiver.RtcpRrTimeout()); + EXPECT_FALSE(receiver.RtcpRrSequenceNumberTimeout()); + + // Verify we can get a timeout again once we've received new RR. + mocks.clock.AdvanceTimeMilliseconds(2 * kRtcpIntervalMs); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rr2.Build()); + + mocks.clock.AdvanceTimeMilliseconds(kRtcpIntervalMs + 1); + EXPECT_FALSE(receiver.RtcpRrTimeout()); + EXPECT_TRUE(receiver.RtcpRrSequenceNumberTimeout()); + + mocks.clock.AdvanceTimeMilliseconds(2 * kRtcpIntervalMs); + EXPECT_TRUE(receiver.RtcpRrTimeout()); +} + +TEST(RtcpReceiverTest, TmmbrReceivedWithNoIncomingPacket) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + EXPECT_THAT(receiver.TmmbrReceived(), IsEmpty()); +} + +TEST(RtcpReceiverTest, TmmbrPacketAccepted) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint32_t kBitrateBps = 30000; + auto tmmbr = std::make_unique<rtcp::Tmmbr>(); + tmmbr->SetSenderSsrc(kSenderSsrc); + tmmbr->AddTmmbr(rtcp::TmmbItem(kReceiverMainSsrc, kBitrateBps, 0)); + auto sr = std::make_unique<rtcp::SenderReport>(); + sr->SetSenderSsrc(kSenderSsrc); + rtcp::CompoundPacket compound; + compound.Append(std::move(sr)); + compound.Append(std::move(tmmbr)); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.rtp_rtcp_impl, SetTmmbn(SizeIs(1))); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedEstimatedBitrate(kBitrateBps)); + receiver.IncomingPacket(compound.Build()); + + std::vector<rtcp::TmmbItem> tmmbr_received = receiver.TmmbrReceived(); + ASSERT_EQ(1u, tmmbr_received.size()); + EXPECT_EQ(kBitrateBps, tmmbr_received[0].bitrate_bps()); + EXPECT_EQ(kSenderSsrc, tmmbr_received[0].ssrc()); +} + +TEST(RtcpReceiverTest, TmmbrPacketNotForUsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint32_t kBitrateBps = 30000; + auto tmmbr = std::make_unique<rtcp::Tmmbr>(); + tmmbr->SetSenderSsrc(kSenderSsrc); + tmmbr->AddTmmbr(rtcp::TmmbItem(kNotToUsSsrc, kBitrateBps, 0)); + + auto sr = std::make_unique<rtcp::SenderReport>(); + sr->SetSenderSsrc(kSenderSsrc); + rtcp::CompoundPacket compound; + compound.Append(std::move(sr)); + compound.Append(std::move(tmmbr)); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedEstimatedBitrate).Times(0); + receiver.IncomingPacket(compound.Build()); + + EXPECT_EQ(0u, receiver.TmmbrReceived().size()); +} + +TEST(RtcpReceiverTest, TmmbrPacketZeroRateIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + auto tmmbr = std::make_unique<rtcp::Tmmbr>(); + tmmbr->SetSenderSsrc(kSenderSsrc); + tmmbr->AddTmmbr(rtcp::TmmbItem(kReceiverMainSsrc, 0, 0)); + auto sr = std::make_unique<rtcp::SenderReport>(); + sr->SetSenderSsrc(kSenderSsrc); + rtcp::CompoundPacket compound; + compound.Append(std::move(sr)); + compound.Append(std::move(tmmbr)); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedEstimatedBitrate).Times(0); + receiver.IncomingPacket(compound.Build()); + + EXPECT_EQ(0u, receiver.TmmbrReceived().size()); +} + +TEST(RtcpReceiverTest, TmmbrThreeConstraintsTimeOut) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + // Inject 3 packets "from" kSenderSsrc, kSenderSsrc+1, kSenderSsrc+2. + // The times of arrival are starttime + 0, starttime + 5 and starttime + 10. + for (uint32_t ssrc = kSenderSsrc; ssrc < kSenderSsrc + 3; ++ssrc) { + auto tmmbr = std::make_unique<rtcp::Tmmbr>(); + tmmbr->SetSenderSsrc(ssrc); + tmmbr->AddTmmbr(rtcp::TmmbItem(kReceiverMainSsrc, 30000, 0)); + auto sr = std::make_unique<rtcp::SenderReport>(); + sr->SetSenderSsrc(ssrc); + rtcp::CompoundPacket compound; + compound.Append(std::move(sr)); + compound.Append(std::move(tmmbr)); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.rtp_rtcp_impl, SetTmmbn); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedEstimatedBitrate); + receiver.IncomingPacket(compound.Build()); + + // 5 seconds between each packet. + mocks.clock.AdvanceTimeMilliseconds(5000); + } + // It is now starttime + 15. + EXPECT_THAT(receiver.TmmbrReceived(), + AllOf(SizeIs(3), + Each(Property(&rtcp::TmmbItem::bitrate_bps, Eq(30'000U))))); + + // We expect the timeout to be 25 seconds. Advance the clock by 12 + // seconds, timing out the first packet. + mocks.clock.AdvanceTimeMilliseconds(12000); + EXPECT_THAT(receiver.TmmbrReceived(), + UnorderedElementsAre( + Property(&rtcp::TmmbItem::ssrc, Eq(kSenderSsrc + 1)), + Property(&rtcp::TmmbItem::ssrc, Eq(kSenderSsrc + 2)))); +} + +TEST(RtcpReceiverTest, + VerifyBlockAndTimestampObtainedFromReportBlockDataObserver) { + ReceiverMocks mocks; + MockReportBlockDataObserverImpl observer; + RtpRtcpInterface::Configuration config = DefaultConfiguration(&mocks); + config.report_block_data_observer = &observer; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint8_t kFractionLoss = 3; + const uint32_t kCumulativeLoss = 7; + const uint32_t kJitter = 9; + const uint16_t kSequenceNumber = 1234; + const int64_t kNtpNowMs = + mocks.clock.CurrentNtpInMilliseconds() - rtc::kNtpJan1970Millisecs; + + rtcp::ReportBlock rtcp_block; + rtcp_block.SetMediaSsrc(kReceiverMainSsrc); + rtcp_block.SetExtHighestSeqNum(kSequenceNumber); + rtcp_block.SetFractionLost(kFractionLoss); + rtcp_block.SetCumulativeLost(kCumulativeLoss); + rtcp_block.SetJitter(kJitter); + + rtcp::ReceiverReport rtcp_report; + rtcp_report.SetSenderSsrc(kSenderSsrc); + rtcp_report.AddReportBlock(rtcp_block); + EXPECT_CALL(observer, OnReportBlockDataUpdated) + .WillOnce([&](ReportBlockData report_block_data) { + const auto& report_block = report_block_data.report_block(); + EXPECT_EQ(rtcp_block.source_ssrc(), report_block.source_ssrc); + EXPECT_EQ(kSenderSsrc, report_block.sender_ssrc); + EXPECT_EQ(rtcp_block.fraction_lost(), report_block.fraction_lost); + EXPECT_EQ(rtcp_block.cumulative_lost_signed(), + report_block.packets_lost); + EXPECT_EQ(rtcp_block.extended_high_seq_num(), + report_block.extended_highest_sequence_number); + EXPECT_EQ(rtcp_block.jitter(), report_block.jitter); + EXPECT_EQ(kNtpNowMs * rtc::kNumMicrosecsPerMillisec, + report_block_data.report_block_timestamp_utc_us()); + // No RTT is calculated in this test. + EXPECT_EQ(0u, report_block_data.num_rtts()); + }); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rtcp_report.Build()); +} + +TEST(RtcpReceiverTest, VerifyRttObtainedFromReportBlockDataObserver) { + ReceiverMocks mocks; + MockReportBlockDataObserverImpl observer; + RtpRtcpInterface::Configuration config = DefaultConfiguration(&mocks); + config.report_block_data_observer = &observer; + RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const TimeDelta kRtt = TimeDelta::Millis(120); + const uint32_t kDelayNtp = 123000; + const TimeDelta kDelay = CompactNtpRttToTimeDelta(kDelayNtp); + + uint32_t sent_ntp = CompactNtp(mocks.clock.CurrentNtpTime()); + mocks.clock.AdvanceTime(kRtt + kDelay); + + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + rtcp::ReportBlock block; + block.SetMediaSsrc(kReceiverMainSsrc); + block.SetLastSr(sent_ntp); + block.SetDelayLastSr(kDelayNtp); + sr.AddReportBlock(block); + block.SetMediaSsrc(kReceiverExtraSsrc); + block.SetLastSr(0); + sr.AddReportBlock(block); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + InSequence sequence; + EXPECT_CALL(observer, OnReportBlockDataUpdated) + .WillOnce([&](ReportBlockData report_block_data) { + EXPECT_EQ(kReceiverMainSsrc, + report_block_data.report_block().source_ssrc); + EXPECT_EQ(1u, report_block_data.num_rtts()); + EXPECT_EQ(kRtt.ms(), report_block_data.min_rtt_ms()); + EXPECT_EQ(kRtt.ms(), report_block_data.max_rtt_ms()); + EXPECT_EQ(kRtt.ms(), report_block_data.sum_rtt_ms()); + EXPECT_EQ(kRtt.ms(), report_block_data.last_rtt_ms()); + }); + EXPECT_CALL(observer, OnReportBlockDataUpdated) + .WillOnce([](ReportBlockData report_block_data) { + EXPECT_EQ(kReceiverExtraSsrc, + report_block_data.report_block().source_ssrc); + EXPECT_EQ(0u, report_block_data.num_rtts()); + }); + receiver.IncomingPacket(sr.Build()); +} + +TEST(RtcpReceiverTest, GetReportBlockDataAfterOneReportBlock) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint16_t kSequenceNumber = 1234; + + rtcp::ReportBlock rtcp_block; + rtcp_block.SetMediaSsrc(kReceiverMainSsrc); + rtcp_block.SetExtHighestSeqNum(kSequenceNumber); + + rtcp::ReceiverReport rtcp_report; + rtcp_report.SetSenderSsrc(kSenderSsrc); + rtcp_report.AddReportBlock(rtcp_block); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rtcp_report.Build()); + + auto report_block_datas = receiver.GetLatestReportBlockData(); + ASSERT_THAT(report_block_datas, SizeIs(1)); + EXPECT_EQ(kReceiverMainSsrc, + report_block_datas[0].report_block().source_ssrc); + EXPECT_EQ( + kSequenceNumber, + report_block_datas[0].report_block().extended_highest_sequence_number); +} + +TEST(RtcpReceiverTest, GetReportBlockDataAfterTwoReportBlocksOfSameSsrc) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint16_t kSequenceNumber1 = 1234; + const uint16_t kSequenceNumber2 = 1235; + + rtcp::ReportBlock rtcp_block1; + rtcp_block1.SetMediaSsrc(kReceiverMainSsrc); + rtcp_block1.SetExtHighestSeqNum(kSequenceNumber1); + + rtcp::ReceiverReport rtcp_report1; + rtcp_report1.SetSenderSsrc(kSenderSsrc); + rtcp_report1.AddReportBlock(rtcp_block1); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rtcp_report1.Build()); + + // Inject a report block with an increased the sequence number for the same + // source SSRC. + rtcp::ReportBlock rtcp_block2; + rtcp_block2.SetMediaSsrc(kReceiverMainSsrc); + rtcp_block2.SetExtHighestSeqNum(kSequenceNumber2); + + rtcp::ReceiverReport rtcp_report2; + rtcp_report2.SetSenderSsrc(kSenderSsrc); + rtcp_report2.AddReportBlock(rtcp_block2); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rtcp_report2.Build()); + + // Only the latest block should be returned. + auto report_block_datas = receiver.GetLatestReportBlockData(); + ASSERT_THAT(report_block_datas, SizeIs(1)); + EXPECT_EQ(kReceiverMainSsrc, + report_block_datas[0].report_block().source_ssrc); + EXPECT_EQ( + kSequenceNumber2, + report_block_datas[0].report_block().extended_highest_sequence_number); +} + +TEST(RtcpReceiverTest, GetReportBlockDataAfterTwoReportBlocksOfDifferentSsrcs) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint16_t kSequenceNumber1 = 1234; + const uint16_t kSequenceNumber2 = 42; + + rtcp::ReportBlock rtcp_block1; + rtcp_block1.SetMediaSsrc(kReceiverMainSsrc); + rtcp_block1.SetExtHighestSeqNum(kSequenceNumber1); + + rtcp::ReceiverReport rtcp_report1; + rtcp_report1.SetSenderSsrc(kSenderSsrc); + rtcp_report1.AddReportBlock(rtcp_block1); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rtcp_report1.Build()); + + // Inject a report block for a different source SSRC. + rtcp::ReportBlock rtcp_block2; + rtcp_block2.SetMediaSsrc(kReceiverExtraSsrc); + rtcp_block2.SetExtHighestSeqNum(kSequenceNumber2); + + rtcp::ReceiverReport rtcp_report2; + rtcp_report2.SetSenderSsrc(kSenderSsrc); + rtcp_report2.AddReportBlock(rtcp_block2); + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks); + EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport); + receiver.IncomingPacket(rtcp_report2.Build()); + + // Both report blocks should be returned. + auto report_block_datas = receiver.GetLatestReportBlockData(); + ASSERT_THAT(report_block_datas, SizeIs(2)); + EXPECT_EQ(kReceiverMainSsrc, + report_block_datas[0].report_block().source_ssrc); + EXPECT_EQ( + kSequenceNumber1, + report_block_datas[0].report_block().extended_highest_sequence_number); + EXPECT_EQ(kReceiverExtraSsrc, + report_block_datas[1].report_block().source_ssrc); + EXPECT_EQ( + kSequenceNumber2, + report_block_datas[1].report_block().extended_highest_sequence_number); +} + +TEST(RtcpReceiverTest, ReceivesTransportFeedback) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::TransportFeedback packet; + packet.SetMediaSsrc(kReceiverMainSsrc); + packet.SetSenderSsrc(kSenderSsrc); + packet.SetBase(1, Timestamp::Millis(1)); + packet.AddReceivedPacket(1, Timestamp::Millis(1)); + + EXPECT_CALL( + mocks.transport_feedback_observer, + OnTransportFeedback(AllOf( + Property(&rtcp::TransportFeedback::media_ssrc, kReceiverMainSsrc), + Property(&rtcp::TransportFeedback::sender_ssrc, kSenderSsrc)))); + receiver.IncomingPacket(packet.Build()); +} + +TEST(RtcpReceiverTest, ReceivesRemb) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint32_t kBitrateBps = 500000; + rtcp::Remb remb; + remb.SetSenderSsrc(kSenderSsrc); + remb.SetBitrateBps(kBitrateBps); + + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedEstimatedBitrate(kBitrateBps)); + receiver.IncomingPacket(remb.Build()); +} + +TEST(RtcpReceiverTest, HandlesInvalidTransportFeedback) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + // Send a compound packet with a TransportFeedback followed by something else. + auto packet = std::make_unique<rtcp::TransportFeedback>(); + packet->SetMediaSsrc(kReceiverMainSsrc); + packet->SetSenderSsrc(kSenderSsrc); + packet->SetBase(1, Timestamp::Millis(1)); + packet->AddReceivedPacket(1, Timestamp::Millis(1)); + + static uint32_t kBitrateBps = 50000; + auto remb = std::make_unique<rtcp::Remb>(); + remb->SetSenderSsrc(kSenderSsrc); + remb->SetBitrateBps(kBitrateBps); + rtcp::CompoundPacket compound; + compound.Append(std::move(packet)); + compound.Append(std::move(remb)); + rtc::Buffer built_packet = compound.Build(); + + // Modify the TransportFeedback packet so that it is invalid. + const size_t kStatusCountOffset = 14; + ByteWriter<uint16_t>::WriteBigEndian(&built_packet.data()[kStatusCountOffset], + 42); + + // Stress no transport feedback is expected. + EXPECT_CALL(mocks.transport_feedback_observer, OnTransportFeedback).Times(0); + // But remb should be processed and cause a callback + EXPECT_CALL(mocks.bandwidth_observer, + OnReceivedEstimatedBitrate(kBitrateBps)); + receiver.IncomingPacket(built_packet); +} + +TEST(RtcpReceiverTest, Nack) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint16_t kNackList1[] = {1, 2, 3, 5}; + const uint16_t kNackList23[] = {5, 7, 30, 40, 41, 58, 59, 61, 63}; + const size_t kNackListLength2 = 4; + const size_t kNackListLength3 = arraysize(kNackList23) - kNackListLength2; + std::set<uint16_t> nack_set; + nack_set.insert(std::begin(kNackList1), std::end(kNackList1)); + nack_set.insert(std::begin(kNackList23), std::end(kNackList23)); + + auto nack1 = std::make_unique<rtcp::Nack>(); + nack1->SetSenderSsrc(kSenderSsrc); + nack1->SetMediaSsrc(kReceiverMainSsrc); + nack1->SetPacketIds(kNackList1, arraysize(kNackList1)); + + EXPECT_CALL(mocks.rtp_rtcp_impl, + OnReceivedNack(ElementsAreArray(kNackList1))); + EXPECT_CALL(mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated( + kReceiverMainSsrc, + AllOf(Field(&RtcpPacketTypeCounter::nack_requests, + arraysize(kNackList1)), + Field(&RtcpPacketTypeCounter::unique_nack_requests, + arraysize(kNackList1))))); + receiver.IncomingPacket(nack1->Build()); + + auto nack2 = std::make_unique<rtcp::Nack>(); + nack2->SetSenderSsrc(kSenderSsrc); + nack2->SetMediaSsrc(kReceiverMainSsrc); + nack2->SetPacketIds(kNackList23, kNackListLength2); + + auto nack3 = std::make_unique<rtcp::Nack>(); + nack3->SetSenderSsrc(kSenderSsrc); + nack3->SetMediaSsrc(kReceiverMainSsrc); + nack3->SetPacketIds(kNackList23 + kNackListLength2, kNackListLength3); + + rtcp::CompoundPacket two_nacks; + two_nacks.Append(std::move(nack2)); + two_nacks.Append(std::move(nack3)); + + EXPECT_CALL(mocks.rtp_rtcp_impl, + OnReceivedNack(ElementsAreArray(kNackList23))); + EXPECT_CALL(mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated( + kReceiverMainSsrc, + AllOf(Field(&RtcpPacketTypeCounter::nack_requests, + arraysize(kNackList1) + arraysize(kNackList23)), + Field(&RtcpPacketTypeCounter::unique_nack_requests, + nack_set.size())))); + receiver.IncomingPacket(two_nacks.Build()); +} + +TEST(RtcpReceiverTest, NackNotForUsIgnored) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + const uint16_t kNackList1[] = {1, 2, 3, 5}; + const size_t kNackListLength1 = std::end(kNackList1) - std::begin(kNackList1); + + rtcp::Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kNotToUsSsrc); + nack.SetPacketIds(kNackList1, kNackListLength1); + + EXPECT_CALL(mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated( + _, Field(&RtcpPacketTypeCounter::nack_requests, 0))); + receiver.IncomingPacket(nack.Build()); +} + +TEST(RtcpReceiverTest, ForceSenderReport) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + rtcp::RapidResyncRequest rr; + rr.SetSenderSsrc(kSenderSsrc); + rr.SetMediaSsrc(kReceiverMainSsrc); + + EXPECT_CALL(mocks.rtp_rtcp_impl, OnRequestSendReport()); + receiver.IncomingPacket(rr.Build()); +} + +TEST(RtcpReceiverTest, ReceivesTargetBitrate) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + VideoBitrateAllocation expected_allocation; + expected_allocation.SetBitrate(0, 0, 10000); + expected_allocation.SetBitrate(0, 1, 20000); + expected_allocation.SetBitrate(1, 0, 40000); + expected_allocation.SetBitrate(1, 1, 80000); + + rtcp::TargetBitrate bitrate; + bitrate.AddTargetBitrate(0, 0, expected_allocation.GetBitrate(0, 0) / 1000); + bitrate.AddTargetBitrate(0, 1, expected_allocation.GetBitrate(0, 1) / 1000); + bitrate.AddTargetBitrate(1, 0, expected_allocation.GetBitrate(1, 0) / 1000); + bitrate.AddTargetBitrate(1, 1, expected_allocation.GetBitrate(1, 1) / 1000); + + rtcp::ExtendedReports xr; + xr.SetTargetBitrate(bitrate); + + // Wrong sender ssrc, target bitrate should be discarded. + xr.SetSenderSsrc(kSenderSsrc + 1); + EXPECT_CALL(mocks.bitrate_allocation_observer, + OnBitrateAllocationUpdated(expected_allocation)) + .Times(0); + receiver.IncomingPacket(xr.Build()); + + // Set correct ssrc, callback should be called once. + xr.SetSenderSsrc(kSenderSsrc); + EXPECT_CALL(mocks.bitrate_allocation_observer, + OnBitrateAllocationUpdated(expected_allocation)); + receiver.IncomingPacket(xr.Build()); +} + +TEST(RtcpReceiverTest, HandlesIncorrectTargetBitrate) { + ReceiverMocks mocks; + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + VideoBitrateAllocation expected_allocation; + expected_allocation.SetBitrate(0, 0, 10000); + + rtcp::TargetBitrate bitrate; + bitrate.AddTargetBitrate(0, 0, expected_allocation.GetBitrate(0, 0) / 1000); + bitrate.AddTargetBitrate(0, kMaxTemporalStreams, 20000); + bitrate.AddTargetBitrate(kMaxSpatialLayers, 0, 40000); + + rtcp::ExtendedReports xr; + xr.SetTargetBitrate(bitrate); + xr.SetSenderSsrc(kSenderSsrc); + + EXPECT_CALL(mocks.bitrate_allocation_observer, + OnBitrateAllocationUpdated(expected_allocation)); + receiver.IncomingPacket(xr.Build()); +} + +TEST(RtcpReceiverTest, ChangeLocalMediaSsrc) { + ReceiverMocks mocks; + // Construct a receiver with `kReceiverMainSsrc` (default) local media ssrc. + RTCPReceiver receiver(DefaultConfiguration(&mocks), &mocks.rtp_rtcp_impl); + receiver.SetRemoteSSRC(kSenderSsrc); + + constexpr uint32_t kSecondarySsrc = kReceiverMainSsrc + 1; + + // Expect to only get the `OnReceivedNack()` callback once since we'll + // configure it for the `kReceiverMainSsrc` media ssrc. + EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedNack); + + // We'll get two callbacks to RtcpPacketTypesCounterUpdated, one for each + // call to `IncomingPacket`, differentiated by the local media ssrc. + EXPECT_CALL(mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated(kReceiverMainSsrc, _)); + EXPECT_CALL(mocks.packet_type_counter_observer, + RtcpPacketTypesCounterUpdated(kSecondarySsrc, _)); + + // Construct a test nack packet with media ssrc set to `kReceiverMainSsrc`. + rtcp::Nack nack; + nack.SetSenderSsrc(kSenderSsrc); + nack.SetMediaSsrc(kReceiverMainSsrc); + const uint16_t kNackList[] = {1, 2, 3, 5}; + nack.SetPacketIds(kNackList, std::size(kNackList)); + + // Deliver the first callback. + receiver.IncomingPacket(nack.Build()); + + // Change the set local media ssrc. + receiver.set_local_media_ssrc(kSecondarySsrc); + + // Deliver another packet - this time there will be no callback to + // OnReceivedNack due to the ssrc not matching. + receiver.IncomingPacket(nack.Build()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.cc new file mode 100644 index 0000000000..983851a55b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.cc @@ -0,0 +1,974 @@ +/* + * 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 "modules/rtp_rtcp/source/rtcp_sender.h" + +#include <string.h> // memcpy + +#include <algorithm> // std::min +#include <memory> +#include <utility> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/rtp_headers.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.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/compound_packet.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/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 "modules/rtp_rtcp/source/rtp_rtcp_impl2.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "modules/rtp_rtcp/source/tmmbr_help.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +namespace { +const uint32_t kRtcpAnyExtendedReports = kRtcpXrReceiverReferenceTime | + kRtcpXrDlrrReportBlock | + kRtcpXrTargetBitrate; +constexpr int32_t kDefaultVideoReportInterval = 1000; +constexpr int32_t kDefaultAudioReportInterval = 5000; +} // namespace + +// Helper to put several RTCP packets into lower layer datagram RTCP packet. +class RTCPSender::PacketSender { + public: + PacketSender(rtcp::RtcpPacket::PacketReadyCallback callback, + size_t max_packet_size) + : callback_(callback), max_packet_size_(max_packet_size) { + RTC_CHECK_LE(max_packet_size, IP_PACKET_SIZE); + } + ~PacketSender() { RTC_DCHECK_EQ(index_, 0) << "Unsent rtcp packet."; } + + // Appends a packet to pending compound packet. + // Sends rtcp packet if buffer is full and resets the buffer. + void AppendPacket(const rtcp::RtcpPacket& packet) { + packet.Create(buffer_, &index_, max_packet_size_, callback_); + } + + // Sends pending rtcp packet. + void Send() { + if (index_ > 0) { + callback_(rtc::ArrayView<const uint8_t>(buffer_, index_)); + index_ = 0; + } + } + + private: + const rtcp::RtcpPacket::PacketReadyCallback callback_; + const size_t max_packet_size_; + size_t index_ = 0; + uint8_t buffer_[IP_PACKET_SIZE]; +}; + +RTCPSender::FeedbackState::FeedbackState() + : packets_sent(0), + media_bytes_sent(0), + send_bitrate(0), + last_rr_ntp_secs(0), + last_rr_ntp_frac(0), + remote_sr(0), + receiver(nullptr) {} + +RTCPSender::FeedbackState::FeedbackState(const FeedbackState&) = default; + +RTCPSender::FeedbackState::FeedbackState(FeedbackState&&) = default; + +RTCPSender::FeedbackState::~FeedbackState() = default; + +class RTCPSender::RtcpContext { + public: + RtcpContext(const FeedbackState& feedback_state, + int32_t nack_size, + const uint16_t* nack_list, + Timestamp now) + : feedback_state_(feedback_state), + nack_size_(nack_size), + nack_list_(nack_list), + now_(now) {} + + const FeedbackState& feedback_state_; + const int32_t nack_size_; + const uint16_t* nack_list_; + const Timestamp now_; +}; + +RTCPSender::Configuration RTCPSender::Configuration::FromRtpRtcpConfiguration( + const RtpRtcpInterface::Configuration& configuration) { + RTCPSender::Configuration result; + result.audio = configuration.audio; + result.local_media_ssrc = configuration.local_media_ssrc; + result.clock = configuration.clock; + result.outgoing_transport = configuration.outgoing_transport; + result.non_sender_rtt_measurement = configuration.non_sender_rtt_measurement; + result.event_log = configuration.event_log; + if (configuration.rtcp_report_interval_ms) { + result.rtcp_report_interval = + TimeDelta::Millis(configuration.rtcp_report_interval_ms); + } + result.receive_statistics = configuration.receive_statistics; + result.rtcp_packet_type_counter_observer = + configuration.rtcp_packet_type_counter_observer; + return result; +} + +RTCPSender::RTCPSender(Configuration config) + : audio_(config.audio), + ssrc_(config.local_media_ssrc), + clock_(config.clock), + random_(clock_->TimeInMicroseconds()), + method_(RtcpMode::kOff), + event_log_(config.event_log), + transport_(config.outgoing_transport), + report_interval_(config.rtcp_report_interval.value_or( + TimeDelta::Millis(config.audio ? kDefaultAudioReportInterval + : kDefaultVideoReportInterval))), + schedule_next_rtcp_send_evaluation_function_( + std::move(config.schedule_next_rtcp_send_evaluation_function)), + sending_(false), + timestamp_offset_(0), + last_rtp_timestamp_(0), + remote_ssrc_(0), + receive_statistics_(config.receive_statistics), + + sequence_number_fir_(0), + + remb_bitrate_(0), + + tmmbr_send_bps_(0), + packet_oh_send_(0), + max_packet_size_(IP_PACKET_SIZE - 28), // IPv4 + UDP by default. + + xr_send_receiver_reference_time_enabled_( + config.non_sender_rtt_measurement), + packet_type_counter_observer_(config.rtcp_packet_type_counter_observer), + send_video_bitrate_allocation_(false), + last_payload_type_(-1) { + RTC_DCHECK(transport_ != nullptr); + + builders_[kRtcpSr] = &RTCPSender::BuildSR; + builders_[kRtcpRr] = &RTCPSender::BuildRR; + builders_[kRtcpSdes] = &RTCPSender::BuildSDES; + builders_[kRtcpPli] = &RTCPSender::BuildPLI; + builders_[kRtcpFir] = &RTCPSender::BuildFIR; + builders_[kRtcpRemb] = &RTCPSender::BuildREMB; + builders_[kRtcpBye] = &RTCPSender::BuildBYE; + builders_[kRtcpLossNotification] = &RTCPSender::BuildLossNotification; + builders_[kRtcpTmmbr] = &RTCPSender::BuildTMMBR; + builders_[kRtcpTmmbn] = &RTCPSender::BuildTMMBN; + builders_[kRtcpNack] = &RTCPSender::BuildNACK; + builders_[kRtcpAnyExtendedReports] = &RTCPSender::BuildExtendedReports; +} + +RTCPSender::~RTCPSender() {} + +RtcpMode RTCPSender::Status() const { + MutexLock lock(&mutex_rtcp_sender_); + return method_; +} + +void RTCPSender::SetRTCPStatus(RtcpMode new_method) { + MutexLock lock(&mutex_rtcp_sender_); + + if (new_method == RtcpMode::kOff) { + next_time_to_send_rtcp_ = absl::nullopt; + } else if (method_ == RtcpMode::kOff) { + // When switching on, reschedule the next packet + SetNextRtcpSendEvaluationDuration(RTCP_INTERVAL_RAPID_SYNC_MS / 2); + } + method_ = new_method; +} + +bool RTCPSender::Sending() const { + MutexLock lock(&mutex_rtcp_sender_); + return sending_; +} + +void RTCPSender::SetSendingStatus(const FeedbackState& feedback_state, + bool sending) { + bool sendRTCPBye = false; + { + MutexLock lock(&mutex_rtcp_sender_); + + if (method_ != RtcpMode::kOff) { + if (sending == false && sending_ == true) { + // Trigger RTCP bye + sendRTCPBye = true; + } + } + sending_ = sending; + } + if (sendRTCPBye) { + if (SendRTCP(feedback_state, kRtcpBye) != 0) { + RTC_LOG(LS_WARNING) << "Failed to send RTCP BYE"; + } + } +} + +void RTCPSender::SetNonSenderRttMeasurement(bool enabled) { + MutexLock lock(&mutex_rtcp_sender_); + xr_send_receiver_reference_time_enabled_ = enabled; +} + +int32_t RTCPSender::SendLossNotification(const FeedbackState& feedback_state, + uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) { + int32_t error_code = -1; + auto callback = [&](rtc::ArrayView<const uint8_t> packet) { + transport_->SendRtcp(packet.data(), packet.size()); + error_code = 0; + if (event_log_) { + event_log_->Log(std::make_unique<RtcEventRtcpPacketOutgoing>(packet)); + } + }; + absl::optional<PacketSender> sender; + { + MutexLock lock(&mutex_rtcp_sender_); + + if (!loss_notification_.Set(last_decoded_seq_num, last_received_seq_num, + decodability_flag)) { + return -1; + } + + SetFlag(kRtcpLossNotification, /*is_volatile=*/true); + + if (buffering_allowed) { + // The loss notification will be batched with additional feedback + // messages. + return 0; + } + + sender.emplace(callback, max_packet_size_); + auto result = ComputeCompoundRTCPPacket( + feedback_state, RTCPPacketType::kRtcpLossNotification, 0, nullptr, + *sender); + if (result) { + return *result; + } + } + sender->Send(); + + return error_code; +} + +void RTCPSender::SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) { + RTC_CHECK_GE(bitrate_bps, 0); + MutexLock lock(&mutex_rtcp_sender_); + if (method_ == RtcpMode::kOff) { + RTC_LOG(LS_WARNING) << "Can't send rtcp if it is disabled."; + return; + } + remb_bitrate_ = bitrate_bps; + remb_ssrcs_ = std::move(ssrcs); + + SetFlag(kRtcpRemb, /*is_volatile=*/false); + // Send a REMB immediately if we have a new REMB. The frequency of REMBs is + // throttled by the caller. + SetNextRtcpSendEvaluationDuration(TimeDelta::Zero()); +} + +void RTCPSender::UnsetRemb() { + MutexLock lock(&mutex_rtcp_sender_); + // Stop sending REMB each report until it is reenabled and REMB data set. + ConsumeFlag(kRtcpRemb, /*forced=*/true); +} + +bool RTCPSender::TMMBR() const { + MutexLock lock(&mutex_rtcp_sender_); + return IsFlagPresent(RTCPPacketType::kRtcpTmmbr); +} + +void RTCPSender::SetMaxRtpPacketSize(size_t max_packet_size) { + MutexLock lock(&mutex_rtcp_sender_); + max_packet_size_ = max_packet_size; +} + +void RTCPSender::SetTimestampOffset(uint32_t timestamp_offset) { + MutexLock lock(&mutex_rtcp_sender_); + timestamp_offset_ = timestamp_offset; +} + +void RTCPSender::SetLastRtpTime(uint32_t rtp_timestamp, + absl::optional<Timestamp> capture_time, + absl::optional<int8_t> payload_type) { + MutexLock lock(&mutex_rtcp_sender_); + // For compatibility with clients who don't set payload type correctly on all + // calls. + if (payload_type.has_value()) { + last_payload_type_ = *payload_type; + } + last_rtp_timestamp_ = rtp_timestamp; + if (!capture_time.has_value()) { + // We don't currently get a capture time from VoiceEngine. + last_frame_capture_time_ = clock_->CurrentTime(); + } else { + last_frame_capture_time_ = *capture_time; + } +} + +void RTCPSender::SetRtpClockRate(int8_t payload_type, int rtp_clock_rate_hz) { + MutexLock lock(&mutex_rtcp_sender_); + rtp_clock_rates_khz_[payload_type] = rtp_clock_rate_hz / 1000; +} + +uint32_t RTCPSender::SSRC() const { + MutexLock lock(&mutex_rtcp_sender_); + return ssrc_; +} + +void RTCPSender::SetSsrc(uint32_t ssrc) { + MutexLock lock(&mutex_rtcp_sender_); + ssrc_ = ssrc; +} + +void RTCPSender::SetRemoteSSRC(uint32_t ssrc) { + MutexLock lock(&mutex_rtcp_sender_); + remote_ssrc_ = ssrc; +} + +int32_t RTCPSender::SetCNAME(absl::string_view c_name) { + RTC_DCHECK_LT(c_name.size(), RTCP_CNAME_SIZE); + MutexLock lock(&mutex_rtcp_sender_); + cname_ = std::string(c_name); + return 0; +} + +bool RTCPSender::TimeToSendRTCPReport(bool sendKeyframeBeforeRTP) const { + /* + For audio we use a configurable interval (default: 5 seconds) + + For video we use a configurable interval (default: 1 second) for a BW + smaller than 360 kbit/s, technicaly we break the max 5% RTCP BW for + video below 10 kbit/s but that should be extremely rare + + + From RFC 3550 + + MAX RTCP BW is 5% if the session BW + A send report is approximately 65 bytes inc CNAME + A receiver report is approximately 28 bytes + + The RECOMMENDED value for the reduced minimum in seconds is 360 + divided by the session bandwidth in kilobits/second. This minimum + is smaller than 5 seconds for bandwidths greater than 72 kb/s. + + If the participant has not yet sent an RTCP packet (the variable + initial is true), the constant Tmin is set to half of the configured + interval. + + The interval between RTCP packets is varied randomly over the + range [0.5,1.5] times the calculated interval to avoid unintended + synchronization of all participants + + if we send + If the participant is a sender (we_sent true), the constant C is + set to the average RTCP packet size (avg_rtcp_size) divided by 25% + of the RTCP bandwidth (rtcp_bw), and the constant n is set to the + number of senders. + + if we receive only + If we_sent is not true, the constant C is set + to the average RTCP packet size divided by 75% of the RTCP + bandwidth. The constant n is set to the number of receivers + (members - senders). If the number of senders is greater than + 25%, senders and receivers are treated together. + + reconsideration NOT required for peer-to-peer + "timer reconsideration" is + employed. This algorithm implements a simple back-off mechanism + which causes users to hold back RTCP packet transmission if the + group sizes are increasing. + + n = number of members + C = avg_size/(rtcpBW/4) + + 3. The deterministic calculated interval Td is set to max(Tmin, n*C). + + 4. The calculated interval T is set to a number uniformly distributed + between 0.5 and 1.5 times the deterministic calculated interval. + + 5. The resulting value of T is divided by e-3/2=1.21828 to compensate + for the fact that the timer reconsideration algorithm converges to + a value of the RTCP bandwidth below the intended average + */ + + Timestamp now = clock_->CurrentTime(); + + MutexLock lock(&mutex_rtcp_sender_); + RTC_DCHECK( + (method_ == RtcpMode::kOff && !next_time_to_send_rtcp_.has_value()) || + (method_ != RtcpMode::kOff && next_time_to_send_rtcp_.has_value())); + if (method_ == RtcpMode::kOff) + return false; + + if (!audio_ && sendKeyframeBeforeRTP) { + // for video key-frames we want to send the RTCP before the large key-frame + // if we have a 100 ms margin + now += RTCP_SEND_BEFORE_KEY_FRAME; + } + + return now >= *next_time_to_send_rtcp_; +} + +void RTCPSender::BuildSR(const RtcpContext& ctx, PacketSender& sender) { + // Timestamp shouldn't be estimated before first media frame. + RTC_DCHECK(last_frame_capture_time_.has_value()); + // The timestamp of this RTCP packet should be estimated as the timestamp of + // the frame being captured at this moment. We are calculating that + // timestamp as the last frame's timestamp + the time since the last frame + // was captured. + int rtp_rate = rtp_clock_rates_khz_[last_payload_type_]; + if (rtp_rate <= 0) { + rtp_rate = + (audio_ ? kBogusRtpRateForAudioRtcp : kVideoPayloadTypeFrequency) / + 1000; + } + // Round now_us_ to the closest millisecond, because Ntp time is rounded + // when converted to milliseconds, + uint32_t rtp_timestamp = + timestamp_offset_ + last_rtp_timestamp_ + + ((ctx.now_.us() + 500) / 1000 - last_frame_capture_time_->ms()) * + rtp_rate; + + rtcp::SenderReport report; + report.SetSenderSsrc(ssrc_); + report.SetNtp(clock_->ConvertTimestampToNtpTime(ctx.now_)); + report.SetRtpTimestamp(rtp_timestamp); + report.SetPacketCount(ctx.feedback_state_.packets_sent); + report.SetOctetCount(ctx.feedback_state_.media_bytes_sent); + report.SetReportBlocks(CreateReportBlocks(ctx.feedback_state_)); + sender.AppendPacket(report); +} + +void RTCPSender::BuildSDES(const RtcpContext& ctx, PacketSender& sender) { + size_t length_cname = cname_.length(); + RTC_CHECK_LT(length_cname, RTCP_CNAME_SIZE); + + rtcp::Sdes sdes; + sdes.AddCName(ssrc_, cname_); + sender.AppendPacket(sdes); +} + +void RTCPSender::BuildRR(const RtcpContext& ctx, PacketSender& sender) { + rtcp::ReceiverReport report; + report.SetSenderSsrc(ssrc_); + report.SetReportBlocks(CreateReportBlocks(ctx.feedback_state_)); + if (method_ == RtcpMode::kCompound || !report.report_blocks().empty()) { + sender.AppendPacket(report); + } +} + +void RTCPSender::BuildPLI(const RtcpContext& ctx, PacketSender& sender) { + rtcp::Pli pli; + pli.SetSenderSsrc(ssrc_); + pli.SetMediaSsrc(remote_ssrc_); + + ++packet_type_counter_.pli_packets; + sender.AppendPacket(pli); +} + +void RTCPSender::BuildFIR(const RtcpContext& ctx, PacketSender& sender) { + ++sequence_number_fir_; + + rtcp::Fir fir; + fir.SetSenderSsrc(ssrc_); + fir.AddRequestTo(remote_ssrc_, sequence_number_fir_); + + ++packet_type_counter_.fir_packets; + sender.AppendPacket(fir); +} + +void RTCPSender::BuildREMB(const RtcpContext& ctx, PacketSender& sender) { + rtcp::Remb remb; + remb.SetSenderSsrc(ssrc_); + remb.SetBitrateBps(remb_bitrate_); + remb.SetSsrcs(remb_ssrcs_); + sender.AppendPacket(remb); +} + +void RTCPSender::SetTargetBitrate(unsigned int target_bitrate) { + MutexLock lock(&mutex_rtcp_sender_); + tmmbr_send_bps_ = target_bitrate; +} + +void RTCPSender::BuildTMMBR(const RtcpContext& ctx, PacketSender& sender) { + if (ctx.feedback_state_.receiver == nullptr) + return; + // Before sending the TMMBR check the received TMMBN, only an owner is + // allowed to raise the bitrate: + // * If the sender is an owner of the TMMBN -> send TMMBR + // * If not an owner but the TMMBR would enter the TMMBN -> send TMMBR + + // get current bounding set from RTCP receiver + bool tmmbr_owner = false; + + // holding mutex_rtcp_sender_ while calling RTCPreceiver which + // will accuire criticalSectionRTCPReceiver_ is a potental deadlock but + // since RTCPreceiver is not doing the reverse we should be fine + std::vector<rtcp::TmmbItem> candidates = + ctx.feedback_state_.receiver->BoundingSet(&tmmbr_owner); + + if (!candidates.empty()) { + for (const auto& candidate : candidates) { + if (candidate.bitrate_bps() == tmmbr_send_bps_ && + candidate.packet_overhead() == packet_oh_send_) { + // Do not send the same tuple. + return; + } + } + if (!tmmbr_owner) { + // Use received bounding set as candidate set. + // Add current tuple. + candidates.emplace_back(ssrc_, tmmbr_send_bps_, packet_oh_send_); + + // Find bounding set. + std::vector<rtcp::TmmbItem> bounding = + TMMBRHelp::FindBoundingSet(std::move(candidates)); + tmmbr_owner = TMMBRHelp::IsOwner(bounding, ssrc_); + if (!tmmbr_owner) { + // Did not enter bounding set, no meaning to send this request. + return; + } + } + } + + if (!tmmbr_send_bps_) + return; + + rtcp::Tmmbr tmmbr; + tmmbr.SetSenderSsrc(ssrc_); + rtcp::TmmbItem request; + request.set_ssrc(remote_ssrc_); + request.set_bitrate_bps(tmmbr_send_bps_); + request.set_packet_overhead(packet_oh_send_); + tmmbr.AddTmmbr(request); + sender.AppendPacket(tmmbr); +} + +void RTCPSender::BuildTMMBN(const RtcpContext& ctx, PacketSender& sender) { + rtcp::Tmmbn tmmbn; + tmmbn.SetSenderSsrc(ssrc_); + for (const rtcp::TmmbItem& tmmbr : tmmbn_to_send_) { + if (tmmbr.bitrate_bps() > 0) { + tmmbn.AddTmmbr(tmmbr); + } + } + sender.AppendPacket(tmmbn); +} + +void RTCPSender::BuildAPP(const RtcpContext& ctx, PacketSender& sender) { + rtcp::App app; + app.SetSenderSsrc(ssrc_); + sender.AppendPacket(app); +} + +void RTCPSender::BuildLossNotification(const RtcpContext& ctx, + PacketSender& sender) { + loss_notification_.SetSenderSsrc(ssrc_); + loss_notification_.SetMediaSsrc(remote_ssrc_); + sender.AppendPacket(loss_notification_); +} + +void RTCPSender::BuildNACK(const RtcpContext& ctx, PacketSender& sender) { + rtcp::Nack nack; + nack.SetSenderSsrc(ssrc_); + nack.SetMediaSsrc(remote_ssrc_); + nack.SetPacketIds(ctx.nack_list_, ctx.nack_size_); + + // Report stats. + for (int idx = 0; idx < ctx.nack_size_; ++idx) { + nack_stats_.ReportRequest(ctx.nack_list_[idx]); + } + packet_type_counter_.nack_requests = nack_stats_.requests(); + packet_type_counter_.unique_nack_requests = nack_stats_.unique_requests(); + + ++packet_type_counter_.nack_packets; + sender.AppendPacket(nack); +} + +void RTCPSender::BuildBYE(const RtcpContext& ctx, PacketSender& sender) { + rtcp::Bye bye; + bye.SetSenderSsrc(ssrc_); + bye.SetCsrcs(csrcs_); + sender.AppendPacket(bye); +} + +void RTCPSender::BuildExtendedReports(const RtcpContext& ctx, + PacketSender& sender) { + rtcp::ExtendedReports xr; + xr.SetSenderSsrc(ssrc_); + + if (!sending_ && xr_send_receiver_reference_time_enabled_) { + rtcp::Rrtr rrtr; + rrtr.SetNtp(clock_->ConvertTimestampToNtpTime(ctx.now_)); + xr.SetRrtr(rrtr); + } + + for (const rtcp::ReceiveTimeInfo& rti : ctx.feedback_state_.last_xr_rtis) { + xr.AddDlrrItem(rti); + } + + if (send_video_bitrate_allocation_) { + rtcp::TargetBitrate target_bitrate; + + for (int sl = 0; sl < kMaxSpatialLayers; ++sl) { + for (int tl = 0; tl < kMaxTemporalStreams; ++tl) { + if (video_bitrate_allocation_.HasBitrate(sl, tl)) { + target_bitrate.AddTargetBitrate( + sl, tl, video_bitrate_allocation_.GetBitrate(sl, tl) / 1000); + } + } + } + + xr.SetTargetBitrate(target_bitrate); + send_video_bitrate_allocation_ = false; + } + sender.AppendPacket(xr); +} + +int32_t RTCPSender::SendRTCP(const FeedbackState& feedback_state, + RTCPPacketType packet_type, + int32_t nack_size, + const uint16_t* nack_list) { + int32_t error_code = -1; + auto callback = [&](rtc::ArrayView<const uint8_t> packet) { + if (transport_->SendRtcp(packet.data(), packet.size())) { + error_code = 0; + if (event_log_) { + event_log_->Log(std::make_unique<RtcEventRtcpPacketOutgoing>(packet)); + } + } + }; + absl::optional<PacketSender> sender; + { + MutexLock lock(&mutex_rtcp_sender_); + sender.emplace(callback, max_packet_size_); + auto result = ComputeCompoundRTCPPacket(feedback_state, packet_type, + nack_size, nack_list, *sender); + if (result) { + return *result; + } + } + sender->Send(); + + return error_code; +} + +absl::optional<int32_t> RTCPSender::ComputeCompoundRTCPPacket( + const FeedbackState& feedback_state, + RTCPPacketType packet_type, + int32_t nack_size, + const uint16_t* nack_list, + PacketSender& sender) { + if (method_ == RtcpMode::kOff) { + RTC_LOG(LS_WARNING) << "Can't send rtcp if it is disabled."; + return -1; + } + // Add the flag as volatile. Non volatile entries will not be overwritten. + // The new volatile flag will be consumed by the end of this call. + SetFlag(packet_type, true); + + // Prevent sending streams to send SR before any media has been sent. + const bool can_calculate_rtp_timestamp = last_frame_capture_time_.has_value(); + if (!can_calculate_rtp_timestamp) { + bool consumed_sr_flag = ConsumeFlag(kRtcpSr); + bool consumed_report_flag = sending_ && ConsumeFlag(kRtcpReport); + bool sender_report = consumed_report_flag || consumed_sr_flag; + if (sender_report && AllVolatileFlagsConsumed()) { + // This call was for Sender Report and nothing else. + return 0; + } + if (sending_ && method_ == RtcpMode::kCompound) { + // Not allowed to send any RTCP packet without sender report. + return -1; + } + } + + // We need to send our NTP even if we haven't received any reports. + RtcpContext context(feedback_state, nack_size, nack_list, + clock_->CurrentTime()); + + PrepareReport(feedback_state); + + bool create_bye = false; + + auto it = report_flags_.begin(); + while (it != report_flags_.end()) { + uint32_t rtcp_packet_type = it->type; + + if (it->is_volatile) { + report_flags_.erase(it++); + } else { + ++it; + } + + // If there is a BYE, don't append now - save it and append it + // at the end later. + if (rtcp_packet_type == kRtcpBye) { + create_bye = true; + continue; + } + auto builder_it = builders_.find(rtcp_packet_type); + if (builder_it == builders_.end()) { + RTC_DCHECK_NOTREACHED() + << "Could not find builder for packet type " << rtcp_packet_type; + } else { + BuilderFunc func = builder_it->second; + (this->*func)(context, sender); + } + } + + // Append the BYE now at the end + if (create_bye) { + BuildBYE(context, sender); + } + + if (packet_type_counter_observer_ != nullptr) { + packet_type_counter_observer_->RtcpPacketTypesCounterUpdated( + remote_ssrc_, packet_type_counter_); + } + + RTC_DCHECK(AllVolatileFlagsConsumed()); + return absl::nullopt; +} + +void RTCPSender::PrepareReport(const FeedbackState& feedback_state) { + bool generate_report; + if (IsFlagPresent(kRtcpSr) || IsFlagPresent(kRtcpRr)) { + // Report type already explicitly set, don't automatically populate. + generate_report = true; + RTC_DCHECK(ConsumeFlag(kRtcpReport) == false); + } else { + generate_report = + (ConsumeFlag(kRtcpReport) && method_ == RtcpMode::kReducedSize) || + method_ == RtcpMode::kCompound; + if (generate_report) + SetFlag(sending_ ? kRtcpSr : kRtcpRr, true); + } + + if (IsFlagPresent(kRtcpSr) || (IsFlagPresent(kRtcpRr) && !cname_.empty())) + SetFlag(kRtcpSdes, true); + + if (generate_report) { + if ((!sending_ && xr_send_receiver_reference_time_enabled_) || + !feedback_state.last_xr_rtis.empty() || + send_video_bitrate_allocation_) { + SetFlag(kRtcpAnyExtendedReports, true); + } + + // generate next time to send an RTCP report + TimeDelta min_interval = report_interval_; + + if (!audio_ && sending_) { + // Calculate bandwidth for video; 360 / send bandwidth in kbit/s. + int send_bitrate_kbit = feedback_state.send_bitrate / 1000; + if (send_bitrate_kbit != 0) { + min_interval = std::min(TimeDelta::Millis(360000 / send_bitrate_kbit), + report_interval_); + } + } + + // The interval between RTCP packets is varied randomly over the + // range [1/2,3/2] times the calculated interval. + int min_interval_int = rtc::dchecked_cast<int>(min_interval.ms()); + TimeDelta time_to_next = TimeDelta::Millis( + random_.Rand(min_interval_int * 1 / 2, min_interval_int * 3 / 2)); + + RTC_DCHECK(!time_to_next.IsZero()); + SetNextRtcpSendEvaluationDuration(time_to_next); + + // RtcpSender expected to be used for sending either just sender reports + // or just receiver reports. + RTC_DCHECK(!(IsFlagPresent(kRtcpSr) && IsFlagPresent(kRtcpRr))); + } +} + +std::vector<rtcp::ReportBlock> RTCPSender::CreateReportBlocks( + const FeedbackState& feedback_state) { + std::vector<rtcp::ReportBlock> result; + if (!receive_statistics_) + return result; + + // TODO(danilchap): Support sending more than `RTCP_MAX_REPORT_BLOCKS` per + // compound rtcp packet when single rtcp module is used for multiple media + // streams. + result = receive_statistics_->RtcpReportBlocks(RTCP_MAX_REPORT_BLOCKS); + + if (!result.empty() && ((feedback_state.last_rr_ntp_secs != 0) || + (feedback_state.last_rr_ntp_frac != 0))) { + // Get our NTP as late as possible to avoid a race. + uint32_t now = CompactNtp(clock_->CurrentNtpTime()); + + uint32_t receive_time = feedback_state.last_rr_ntp_secs & 0x0000FFFF; + receive_time <<= 16; + receive_time += (feedback_state.last_rr_ntp_frac & 0xffff0000) >> 16; + + uint32_t delay_since_last_sr = now - receive_time; + // TODO(danilchap): Instead of setting same value on all report blocks, + // set only when media_ssrc match sender ssrc of the sender report + // remote times were taken from. + for (auto& report_block : result) { + report_block.SetLastSr(feedback_state.remote_sr); + report_block.SetDelayLastSr(delay_since_last_sr); + } + } + return result; +} + +void RTCPSender::SetCsrcs(const std::vector<uint32_t>& csrcs) { + RTC_DCHECK_LE(csrcs.size(), kRtpCsrcSize); + MutexLock lock(&mutex_rtcp_sender_); + csrcs_ = csrcs; +} + +void RTCPSender::SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) { + MutexLock lock(&mutex_rtcp_sender_); + tmmbn_to_send_ = std::move(bounding_set); + SetFlag(kRtcpTmmbn, true); +} + +void RTCPSender::SetFlag(uint32_t type, bool is_volatile) { + if (type & kRtcpAnyExtendedReports) { + report_flags_.insert(ReportFlag(kRtcpAnyExtendedReports, is_volatile)); + } else { + report_flags_.insert(ReportFlag(type, is_volatile)); + } +} + +bool RTCPSender::IsFlagPresent(uint32_t type) const { + return report_flags_.find(ReportFlag(type, false)) != report_flags_.end(); +} + +bool RTCPSender::ConsumeFlag(uint32_t type, bool forced) { + auto it = report_flags_.find(ReportFlag(type, false)); + if (it == report_flags_.end()) + return false; + if (it->is_volatile || forced) + report_flags_.erase((it)); + return true; +} + +bool RTCPSender::AllVolatileFlagsConsumed() const { + for (const ReportFlag& flag : report_flags_) { + if (flag.is_volatile) + return false; + } + return true; +} + +void RTCPSender::SetVideoBitrateAllocation( + const VideoBitrateAllocation& bitrate) { + MutexLock lock(&mutex_rtcp_sender_); + if (method_ == RtcpMode::kOff) { + RTC_LOG(LS_WARNING) << "Can't send rtcp if it is disabled."; + return; + } + // Check if this allocation is first ever, or has a different set of + // spatial/temporal layers signaled and enabled, if so trigger an rtcp report + // as soon as possible. + absl::optional<VideoBitrateAllocation> new_bitrate = + CheckAndUpdateLayerStructure(bitrate); + if (new_bitrate) { + video_bitrate_allocation_ = *new_bitrate; + RTC_LOG(LS_INFO) << "Emitting TargetBitrate XR for SSRC " << ssrc_ + << " with new layers enabled/disabled: " + << video_bitrate_allocation_.ToString(); + SetNextRtcpSendEvaluationDuration(TimeDelta::Zero()); + } else { + video_bitrate_allocation_ = bitrate; + } + + send_video_bitrate_allocation_ = true; + SetFlag(kRtcpAnyExtendedReports, true); +} + +absl::optional<VideoBitrateAllocation> RTCPSender::CheckAndUpdateLayerStructure( + const VideoBitrateAllocation& bitrate) const { + absl::optional<VideoBitrateAllocation> updated_bitrate; + for (size_t si = 0; si < kMaxSpatialLayers; ++si) { + for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) { + if (!updated_bitrate && + (bitrate.HasBitrate(si, ti) != + video_bitrate_allocation_.HasBitrate(si, ti) || + (bitrate.GetBitrate(si, ti) == 0) != + (video_bitrate_allocation_.GetBitrate(si, ti) == 0))) { + updated_bitrate = bitrate; + } + if (video_bitrate_allocation_.GetBitrate(si, ti) > 0 && + bitrate.GetBitrate(si, ti) == 0) { + // Make sure this stream disabling is explicitly signaled. + updated_bitrate->SetBitrate(si, ti, 0); + } + } + } + + return updated_bitrate; +} + +void RTCPSender::SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) { + size_t max_packet_size; + uint32_t ssrc; + { + MutexLock lock(&mutex_rtcp_sender_); + if (method_ == RtcpMode::kOff) { + RTC_LOG(LS_WARNING) << "Can't send rtcp if it is disabled."; + return; + } + + max_packet_size = max_packet_size_; + ssrc = ssrc_; + } + RTC_DCHECK_LE(max_packet_size, IP_PACKET_SIZE); + auto callback = [&](rtc::ArrayView<const uint8_t> packet) { + if (transport_->SendRtcp(packet.data(), packet.size())) { + if (event_log_) + event_log_->Log(std::make_unique<RtcEventRtcpPacketOutgoing>(packet)); + } + }; + PacketSender sender(callback, max_packet_size); + for (auto& rtcp_packet : rtcp_packets) { + rtcp_packet->SetSenderSsrc(ssrc); + sender.AppendPacket(*rtcp_packet); + } + sender.Send(); +} + +void RTCPSender::SetNextRtcpSendEvaluationDuration(TimeDelta duration) { + next_time_to_send_rtcp_ = clock_->CurrentTime() + duration; + // TODO(bugs.webrtc.org/11581): make unconditional once downstream consumers + // are using the callback method. + if (schedule_next_rtcp_send_evaluation_function_) + schedule_next_rtcp_send_evaluation_function_(duration); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.h new file mode 100644 index 0000000000..8f51e7947d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.h @@ -0,0 +1,332 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_SENDER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_SENDER_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/call/transport.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_nack_stats.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/compound_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/loss_notification.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "rtc_base/random.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class RTCPReceiver; +class RtcEventLog; + +class RTCPSender final { + public: + struct Configuration { + // TODO(bugs.webrtc.org/11581): Remove this temporary conversion utility + // once rtc_rtcp_impl.cc/h are gone. + static Configuration FromRtpRtcpConfiguration( + const RtpRtcpInterface::Configuration& config); + + // True for a audio version of the RTP/RTCP module object false will create + // a video version. + bool audio = false; + // SSRCs for media and retransmission, respectively. + // FlexFec SSRC is fetched from `flexfec_sender`. + uint32_t local_media_ssrc = 0; + // The clock to use to read time. If nullptr then system clock will be used. + Clock* clock = nullptr; + // Transport object that will be called when packets are ready to be sent + // out on the network. + Transport* outgoing_transport = nullptr; + // Estimate RTT as non-sender as described in + // https://tools.ietf.org/html/rfc3611#section-4.4 and #section-4.5 + bool non_sender_rtt_measurement = false; + // Optional callback which, if specified, is used by RTCPSender to schedule + // the next time to evaluate if RTCP should be sent by means of + // TimeToSendRTCPReport/SendRTCP. + // The RTCPSender client still needs to call TimeToSendRTCPReport/SendRTCP + // to actually get RTCP sent. + // + // Note: It's recommended to use the callback to ensure program design that + // doesn't use polling. + // TODO(bugs.webrtc.org/11581): Make mandatory once downstream consumers + // have migrated to the callback solution. + std::function<void(TimeDelta)> schedule_next_rtcp_send_evaluation_function; + + RtcEventLog* event_log = nullptr; + absl::optional<TimeDelta> rtcp_report_interval; + ReceiveStatisticsProvider* receive_statistics = nullptr; + RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer = nullptr; + }; + struct FeedbackState { + FeedbackState(); + FeedbackState(const FeedbackState&); + FeedbackState(FeedbackState&&); + + ~FeedbackState(); + + uint32_t packets_sent; + size_t media_bytes_sent; + uint32_t send_bitrate; + + uint32_t last_rr_ntp_secs; + uint32_t last_rr_ntp_frac; + uint32_t remote_sr; + + std::vector<rtcp::ReceiveTimeInfo> last_xr_rtis; + + // Used when generating TMMBR. + RTCPReceiver* receiver; + }; + + explicit RTCPSender(Configuration config); + + RTCPSender() = delete; + RTCPSender(const RTCPSender&) = delete; + RTCPSender& operator=(const RTCPSender&) = delete; + + virtual ~RTCPSender(); + + RtcpMode Status() const RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + void SetRTCPStatus(RtcpMode method) RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + bool Sending() const RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + void SetSendingStatus(const FeedbackState& feedback_state, + bool enabled) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); // combine the functions + + void SetNonSenderRttMeasurement(bool enabled) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetTimestampOffset(uint32_t timestamp_offset) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetLastRtpTime(uint32_t rtp_timestamp, + absl::optional<Timestamp> capture_time, + absl::optional<int8_t> payload_type) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetRtpClockRate(int8_t payload_type, int rtp_clock_rate_hz) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + uint32_t SSRC() const; + void SetSsrc(uint32_t ssrc); + + void SetRemoteSSRC(uint32_t ssrc) RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + int32_t SetCNAME(absl::string_view cName) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + bool TimeToSendRTCPReport(bool sendKeyframeBeforeRTP = false) const + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + int32_t SendRTCP(const FeedbackState& feedback_state, + RTCPPacketType packetType, + int32_t nackSize = 0, + const uint16_t* nackList = 0) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + int32_t SendLossNotification(const FeedbackState& feedback_state, + uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void UnsetRemb() RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + bool TMMBR() const RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetMaxRtpPacketSize(size_t max_packet_size) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetCsrcs(const std::vector<uint32_t>& csrcs) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + void SetTargetBitrate(unsigned int target_bitrate) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + void SetVideoBitrateAllocation(const VideoBitrateAllocation& bitrate) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + void SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) + RTC_LOCKS_EXCLUDED(mutex_rtcp_sender_); + + private: + class RtcpContext; + class PacketSender; + + absl::optional<int32_t> ComputeCompoundRTCPPacket( + const FeedbackState& feedback_state, + RTCPPacketType packet_type, + int32_t nack_size, + const uint16_t* nack_list, + PacketSender& sender) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + + // Determine which RTCP messages should be sent and setup flags. + void PrepareReport(const FeedbackState& feedback_state) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + + std::vector<rtcp::ReportBlock> CreateReportBlocks( + const FeedbackState& feedback_state) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + + void BuildSR(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildRR(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildSDES(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildPLI(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildREMB(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildTMMBR(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildTMMBN(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildAPP(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildLossNotification(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildExtendedReports(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildBYE(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildFIR(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + void BuildNACK(const RtcpContext& context, PacketSender& sender) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + + // `duration` being TimeDelta::Zero() means schedule immediately. + void SetNextRtcpSendEvaluationDuration(TimeDelta duration) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + + const bool audio_; + // TODO(bugs.webrtc.org/11581): `mutex_rtcp_sender_` shouldn't be required if + // we consistently run network related operations on the network thread. + // This is currently not possible due to callbacks from the process thread in + // ModuleRtpRtcpImpl2. + uint32_t ssrc_ RTC_GUARDED_BY(mutex_rtcp_sender_); + Clock* const clock_; + Random random_ RTC_GUARDED_BY(mutex_rtcp_sender_); + RtcpMode method_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + RtcEventLog* const event_log_; + Transport* const transport_; + + const TimeDelta report_interval_; + // Set from + // RTCPSender::Configuration::schedule_next_rtcp_send_evaluation_function. + const std::function<void(TimeDelta)> + schedule_next_rtcp_send_evaluation_function_; + + mutable Mutex mutex_rtcp_sender_; + bool sending_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + absl::optional<Timestamp> next_time_to_send_rtcp_ + RTC_GUARDED_BY(mutex_rtcp_sender_); + + uint32_t timestamp_offset_ RTC_GUARDED_BY(mutex_rtcp_sender_); + uint32_t last_rtp_timestamp_ RTC_GUARDED_BY(mutex_rtcp_sender_); + absl::optional<Timestamp> last_frame_capture_time_ + RTC_GUARDED_BY(mutex_rtcp_sender_); + // SSRC that we receive on our RTP channel + uint32_t remote_ssrc_ RTC_GUARDED_BY(mutex_rtcp_sender_); + std::string cname_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + ReceiveStatisticsProvider* receive_statistics_ + RTC_GUARDED_BY(mutex_rtcp_sender_); + + // send CSRCs + std::vector<uint32_t> csrcs_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + // Full intra request + uint8_t sequence_number_fir_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + rtcp::LossNotification loss_notification_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + // REMB + int64_t remb_bitrate_ RTC_GUARDED_BY(mutex_rtcp_sender_); + std::vector<uint32_t> remb_ssrcs_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + std::vector<rtcp::TmmbItem> tmmbn_to_send_ RTC_GUARDED_BY(mutex_rtcp_sender_); + uint32_t tmmbr_send_bps_ RTC_GUARDED_BY(mutex_rtcp_sender_); + uint32_t packet_oh_send_ RTC_GUARDED_BY(mutex_rtcp_sender_); + size_t max_packet_size_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + // True if sending of XR Receiver reference time report is enabled. + bool xr_send_receiver_reference_time_enabled_ + RTC_GUARDED_BY(mutex_rtcp_sender_); + + RtcpPacketTypeCounterObserver* const packet_type_counter_observer_; + RtcpPacketTypeCounter packet_type_counter_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + RtcpNackStats nack_stats_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + VideoBitrateAllocation video_bitrate_allocation_ + RTC_GUARDED_BY(mutex_rtcp_sender_); + bool send_video_bitrate_allocation_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + std::map<int8_t, int> rtp_clock_rates_khz_ RTC_GUARDED_BY(mutex_rtcp_sender_); + int8_t last_payload_type_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + absl::optional<VideoBitrateAllocation> CheckAndUpdateLayerStructure( + const VideoBitrateAllocation& bitrate) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + + void SetFlag(uint32_t type, bool is_volatile) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + bool IsFlagPresent(uint32_t type) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + bool ConsumeFlag(uint32_t type, bool forced = false) + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + bool AllVolatileFlagsConsumed() const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_rtcp_sender_); + struct ReportFlag { + ReportFlag(uint32_t type, bool is_volatile) + : type(type), is_volatile(is_volatile) {} + bool operator<(const ReportFlag& flag) const { return type < flag.type; } + bool operator==(const ReportFlag& flag) const { return type == flag.type; } + const uint32_t type; + const bool is_volatile; + }; + + std::set<ReportFlag> report_flags_ RTC_GUARDED_BY(mutex_rtcp_sender_); + + typedef void (RTCPSender::*BuilderFunc)(const RtcpContext&, PacketSender&); + // Map from RTCPPacketType to builder. + std::map<uint32_t, BuilderFunc> builders_; +}; +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_SENDER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc new file mode 100644 index 0000000000..ae59dc5b0c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc @@ -0,0 +1,844 @@ +/* + * 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 "modules/rtp_rtcp/source/rtcp_sender.h" + +#include <memory> +#include <utility> + +#include "absl/base/macros.h" +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.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/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_impl2.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/thread.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_transport.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::Property; +using ::testing::SizeIs; + +namespace webrtc { + +class RtcpPacketTypeCounterObserverImpl : public RtcpPacketTypeCounterObserver { + public: + RtcpPacketTypeCounterObserverImpl() : ssrc_(0) {} + ~RtcpPacketTypeCounterObserverImpl() override = default; + void RtcpPacketTypesCounterUpdated( + uint32_t ssrc, + const RtcpPacketTypeCounter& packet_counter) override { + ssrc_ = ssrc; + counter_ = packet_counter; + } + uint32_t ssrc_; + RtcpPacketTypeCounter counter_; +}; + +class TestTransport : public Transport { + public: + TestTransport() {} + + bool SendRtp(const uint8_t* /*data*/, + size_t /*len*/, + const PacketOptions& options) override { + return false; + } + bool SendRtcp(const uint8_t* data, size_t len) override { + parser_.Parse(data, len); + return true; + } + test::RtcpPacketParser parser_; +}; + +namespace { +static const uint32_t kSenderSsrc = 0x11111111; +static const uint32_t kRemoteSsrc = 0x22222222; +static const uint32_t kStartRtpTimestamp = 0x34567; +static const uint32_t kRtpTimestamp = 0x45678; + +std::unique_ptr<RTCPSender> CreateRtcpSender( + const RTCPSender::Configuration& config, + bool init_timestamps = true) { + auto rtcp_sender = std::make_unique<RTCPSender>(config); + rtcp_sender->SetRemoteSSRC(kRemoteSsrc); + if (init_timestamps) { + rtcp_sender->SetTimestampOffset(kStartRtpTimestamp); + rtcp_sender->SetLastRtpTime(kRtpTimestamp, config.clock->CurrentTime(), + /*payload_type=*/0); + } + return rtcp_sender; +} +} // namespace + +class RtcpSenderTest : public ::testing::Test { + protected: + RtcpSenderTest() + : clock_(1335900000), + receive_statistics_(ReceiveStatistics::Create(&clock_)) { + rtp_rtcp_impl_.reset(new ModuleRtpRtcpImpl2(GetDefaultRtpRtcpConfig())); + } + + RTCPSender::Configuration GetDefaultConfig() { + RTCPSender::Configuration configuration; + configuration.audio = false; + configuration.clock = &clock_; + configuration.outgoing_transport = &test_transport_; + configuration.rtcp_report_interval = TimeDelta::Millis(1000); + configuration.receive_statistics = receive_statistics_.get(); + configuration.local_media_ssrc = kSenderSsrc; + return configuration; + } + + RtpRtcpInterface::Configuration GetDefaultRtpRtcpConfig() { + RTCPSender::Configuration config = GetDefaultConfig(); + RtpRtcpInterface::Configuration result; + result.audio = config.audio; + result.clock = config.clock; + result.outgoing_transport = config.outgoing_transport; + result.rtcp_report_interval_ms = config.rtcp_report_interval->ms(); + result.receive_statistics = config.receive_statistics; + result.local_media_ssrc = config.local_media_ssrc; + return result; + } + + void InsertIncomingPacket(uint32_t remote_ssrc, uint16_t seq_num) { + RtpPacketReceived packet; + packet.SetSsrc(remote_ssrc); + packet.SetSequenceNumber(seq_num); + packet.SetTimestamp(12345); + packet.SetPayloadSize(100 - 12); + receive_statistics_->OnRtpPacket(packet); + } + + test::RtcpPacketParser* parser() { return &test_transport_.parser_; } + + RTCPSender::FeedbackState feedback_state() { + return rtp_rtcp_impl_->GetFeedbackState(); + } + + rtc::AutoThread main_thread_; + SimulatedClock clock_; + TestTransport test_transport_; + std::unique_ptr<ReceiveStatistics> receive_statistics_; + std::unique_ptr<ModuleRtpRtcpImpl2> rtp_rtcp_impl_; +}; + +TEST_F(RtcpSenderTest, SetRtcpStatus) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + EXPECT_EQ(RtcpMode::kOff, rtcp_sender->Status()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + EXPECT_EQ(RtcpMode::kReducedSize, rtcp_sender->Status()); +} + +TEST_F(RtcpSenderTest, SetSendingStatus) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + EXPECT_FALSE(rtcp_sender->Sending()); + rtcp_sender->SetSendingStatus(feedback_state(), true); + EXPECT_TRUE(rtcp_sender->Sending()); +} + +TEST_F(RtcpSenderTest, NoPacketSentIfOff) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kOff); + EXPECT_EQ(-1, rtcp_sender->SendRTCP(feedback_state(), kRtcpSr)); +} + +TEST_F(RtcpSenderTest, SendSr) { + const uint32_t kPacketCount = 0x12345; + const uint32_t kOctetCount = 0x23456; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + RTCPSender::FeedbackState feedback_state = rtp_rtcp_impl_->GetFeedbackState(); + rtcp_sender->SetSendingStatus(feedback_state, true); + feedback_state.packets_sent = kPacketCount; + feedback_state.media_bytes_sent = kOctetCount; + NtpTime ntp = clock_.CurrentNtpTime(); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state, kRtcpSr)); + EXPECT_EQ(1, parser()->sender_report()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->sender_report()->sender_ssrc()); + EXPECT_EQ(ntp, parser()->sender_report()->ntp()); + EXPECT_EQ(kPacketCount, parser()->sender_report()->sender_packet_count()); + EXPECT_EQ(kOctetCount, parser()->sender_report()->sender_octet_count()); + EXPECT_EQ(kStartRtpTimestamp + kRtpTimestamp, + parser()->sender_report()->rtp_timestamp()); + EXPECT_EQ(0U, parser()->sender_report()->report_blocks().size()); +} + +TEST_F(RtcpSenderTest, SendConsecutiveSrWithExactSlope) { + const uint32_t kPacketCount = 0x12345; + const uint32_t kOctetCount = 0x23456; + const int kTimeBetweenSRsUs = 10043; // Not exact value in milliseconds. + const int kExtraPackets = 30; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + // Make sure clock is not exactly at some milliseconds point. + clock_.AdvanceTimeMicroseconds(kTimeBetweenSRsUs); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + RTCPSender::FeedbackState feedback_state = rtp_rtcp_impl_->GetFeedbackState(); + rtcp_sender->SetSendingStatus(feedback_state, true); + feedback_state.packets_sent = kPacketCount; + feedback_state.media_bytes_sent = kOctetCount; + + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state, kRtcpSr)); + EXPECT_EQ(1, parser()->sender_report()->num_packets()); + NtpTime ntp1 = parser()->sender_report()->ntp(); + uint32_t rtp1 = parser()->sender_report()->rtp_timestamp(); + + // Send more SRs to ensure slope is always exact for different offsets + for (int packets = 1; packets <= kExtraPackets; ++packets) { + clock_.AdvanceTimeMicroseconds(kTimeBetweenSRsUs); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state, kRtcpSr)); + EXPECT_EQ(packets + 1, parser()->sender_report()->num_packets()); + + NtpTime ntp2 = parser()->sender_report()->ntp(); + uint32_t rtp2 = parser()->sender_report()->rtp_timestamp(); + + uint32_t ntp_diff_in_rtp_units = + (ntp2.ToMs() - ntp1.ToMs()) * (kVideoPayloadTypeFrequency / 1000); + EXPECT_EQ(rtp2 - rtp1, ntp_diff_in_rtp_units); + } +} + +TEST_F(RtcpSenderTest, DoNotSendSrBeforeRtp) { + RTCPSender::Configuration config; + config.clock = &clock_; + config.receive_statistics = receive_statistics_.get(); + config.outgoing_transport = &test_transport_; + config.rtcp_report_interval = TimeDelta::Millis(1000); + config.local_media_ssrc = kSenderSsrc; + auto rtcp_sender = CreateRtcpSender(config, /*init_timestamps=*/false); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + rtcp_sender->SetSendingStatus(feedback_state(), true); + + // Sender Report shouldn't be send as an SR nor as a Report. + rtcp_sender->SendRTCP(feedback_state(), kRtcpSr); + EXPECT_EQ(0, parser()->sender_report()->num_packets()); + rtcp_sender->SendRTCP(feedback_state(), kRtcpReport); + EXPECT_EQ(0, parser()->sender_report()->num_packets()); + // Other packets (e.g. Pli) are allowed, even if useless. + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpPli)); + EXPECT_EQ(1, parser()->pli()->num_packets()); +} + +TEST_F(RtcpSenderTest, DoNotSendCompundBeforeRtp) { + RTCPSender::Configuration config; + config.clock = &clock_; + config.receive_statistics = receive_statistics_.get(); + config.outgoing_transport = &test_transport_; + config.rtcp_report_interval = TimeDelta::Millis(1000); + config.local_media_ssrc = kSenderSsrc; + auto rtcp_sender = CreateRtcpSender(config, /*init_timestamps=*/false); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), true); + + // In compound mode no packets are allowed (e.g. Pli) because compound mode + // should start with Sender Report. + EXPECT_EQ(-1, rtcp_sender->SendRTCP(feedback_state(), kRtcpPli)); + EXPECT_EQ(0, parser()->pli()->num_packets()); +} + +TEST_F(RtcpSenderTest, SendRr) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpRr)); + EXPECT_EQ(1, parser()->receiver_report()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->receiver_report()->sender_ssrc()); + EXPECT_EQ(0U, parser()->receiver_report()->report_blocks().size()); +} + +TEST_F(RtcpSenderTest, DoesntSendEmptyRrInReducedSizeMode) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + rtcp_sender->SendRTCP(feedback_state(), kRtcpRr); + EXPECT_EQ(parser()->receiver_report()->num_packets(), 0); +} + +TEST_F(RtcpSenderTest, SendRrWithOneReportBlock) { + const uint16_t kSeqNum = 11111; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + InsertIncomingPacket(kRemoteSsrc, kSeqNum); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpRr)); + EXPECT_EQ(1, parser()->receiver_report()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->receiver_report()->sender_ssrc()); + ASSERT_EQ(1U, parser()->receiver_report()->report_blocks().size()); + const rtcp::ReportBlock& rb = parser()->receiver_report()->report_blocks()[0]; + EXPECT_EQ(kRemoteSsrc, rb.source_ssrc()); + EXPECT_EQ(0U, rb.fraction_lost()); + EXPECT_EQ(0, rb.cumulative_lost_signed()); + EXPECT_EQ(kSeqNum, rb.extended_high_seq_num()); +} + +TEST_F(RtcpSenderTest, SendRrWithTwoReportBlocks) { + const uint16_t kSeqNum = 11111; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + InsertIncomingPacket(kRemoteSsrc, kSeqNum); + InsertIncomingPacket(kRemoteSsrc + 1, kSeqNum + 1); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpRr)); + EXPECT_EQ(1, parser()->receiver_report()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->receiver_report()->sender_ssrc()); + EXPECT_THAT( + parser()->receiver_report()->report_blocks(), + UnorderedElementsAre( + Property(&rtcp::ReportBlock::source_ssrc, Eq(kRemoteSsrc)), + Property(&rtcp::ReportBlock::source_ssrc, Eq(kRemoteSsrc + 1)))); +} + +TEST_F(RtcpSenderTest, SendSdes) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + EXPECT_EQ(0, rtcp_sender->SetCNAME("alice@host")); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpSdes)); + EXPECT_EQ(1, parser()->sdes()->num_packets()); + EXPECT_EQ(1U, parser()->sdes()->chunks().size()); + EXPECT_EQ(kSenderSsrc, parser()->sdes()->chunks()[0].ssrc); + EXPECT_EQ("alice@host", parser()->sdes()->chunks()[0].cname); +} + +TEST_F(RtcpSenderTest, SdesIncludedInCompoundPacket) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + EXPECT_EQ(0, rtcp_sender->SetCNAME("alice@host")); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(1, parser()->receiver_report()->num_packets()); + EXPECT_EQ(1, parser()->sdes()->num_packets()); + EXPECT_EQ(1U, parser()->sdes()->chunks().size()); +} + +TEST_F(RtcpSenderTest, SendBye) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpBye)); + EXPECT_EQ(1, parser()->bye()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->bye()->sender_ssrc()); +} + +TEST_F(RtcpSenderTest, StopSendingTriggersBye) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + rtcp_sender->SetSendingStatus(feedback_state(), true); + rtcp_sender->SetSendingStatus(feedback_state(), false); + EXPECT_EQ(1, parser()->bye()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->bye()->sender_ssrc()); +} + +TEST_F(RtcpSenderTest, SendFir) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpFir)); + EXPECT_EQ(1, parser()->fir()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->fir()->sender_ssrc()); + EXPECT_EQ(1U, parser()->fir()->requests().size()); + EXPECT_EQ(kRemoteSsrc, parser()->fir()->requests()[0].ssrc); + uint8_t seq = parser()->fir()->requests()[0].seq_nr; + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpFir)); + EXPECT_EQ(2, parser()->fir()->num_packets()); + EXPECT_EQ(seq + 1, parser()->fir()->requests()[0].seq_nr); +} + +TEST_F(RtcpSenderTest, SendPli) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpPli)); + EXPECT_EQ(1, parser()->pli()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->pli()->sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parser()->pli()->media_ssrc()); +} + +TEST_F(RtcpSenderTest, SendNack) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + const uint16_t kList[] = {0, 1, 16}; + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpNack, + ABSL_ARRAYSIZE(kList), kList)); + EXPECT_EQ(1, parser()->nack()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->nack()->sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parser()->nack()->media_ssrc()); + EXPECT_THAT(parser()->nack()->packet_ids(), ElementsAre(0, 1, 16)); +} + +TEST_F(RtcpSenderTest, SendLossNotificationBufferingNotAllowed) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + constexpr uint16_t kLastDecoded = 0x1234; + constexpr uint16_t kLastReceived = 0x4321; + constexpr bool kDecodabilityFlag = true; + constexpr bool kBufferingAllowed = false; + EXPECT_EQ(rtcp_sender->SendLossNotification(feedback_state(), kLastDecoded, + kLastReceived, kDecodabilityFlag, + kBufferingAllowed), + 0); + EXPECT_EQ(parser()->processed_rtcp_packets(), 1u); + EXPECT_EQ(parser()->loss_notification()->num_packets(), 1); + EXPECT_EQ(kSenderSsrc, parser()->loss_notification()->sender_ssrc()); + EXPECT_EQ(kRemoteSsrc, parser()->loss_notification()->media_ssrc()); +} + +TEST_F(RtcpSenderTest, SendLossNotificationBufferingAllowed) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + constexpr uint16_t kLastDecoded = 0x1234; + constexpr uint16_t kLastReceived = 0x4321; + constexpr bool kDecodabilityFlag = true; + constexpr bool kBufferingAllowed = true; + EXPECT_EQ(rtcp_sender->SendLossNotification(feedback_state(), kLastDecoded, + kLastReceived, kDecodabilityFlag, + kBufferingAllowed), + 0); + + // No RTCP messages sent yet. + ASSERT_EQ(parser()->processed_rtcp_packets(), 0u); + + // Sending another messages triggers sending the LNTF messages as well. + const uint16_t kList[] = {0, 1, 16}; + EXPECT_EQ(rtcp_sender->SendRTCP(feedback_state(), kRtcpNack, + ABSL_ARRAYSIZE(kList), kList), + 0); + + // Exactly one packet was produced, and it contained both the buffered LNTF + // as well as the message that had triggered the packet. + EXPECT_EQ(parser()->processed_rtcp_packets(), 1u); + EXPECT_EQ(parser()->loss_notification()->num_packets(), 1); + EXPECT_EQ(parser()->loss_notification()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(parser()->loss_notification()->media_ssrc(), kRemoteSsrc); + EXPECT_EQ(parser()->nack()->num_packets(), 1); + EXPECT_EQ(parser()->nack()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(parser()->nack()->media_ssrc(), kRemoteSsrc); +} + +TEST_F(RtcpSenderTest, RembNotIncludedBeforeSet) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + + rtcp_sender->SendRTCP(feedback_state(), kRtcpRr); + + ASSERT_EQ(1, parser()->receiver_report()->num_packets()); + EXPECT_EQ(0, parser()->remb()->num_packets()); +} + +TEST_F(RtcpSenderTest, RembNotIncludedAfterUnset) { + const int64_t kBitrate = 261011; + const std::vector<uint32_t> kSsrcs = {kRemoteSsrc, kRemoteSsrc + 1}; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetRemb(kBitrate, kSsrcs); + rtcp_sender->SendRTCP(feedback_state(), kRtcpRr); + ASSERT_EQ(1, parser()->receiver_report()->num_packets()); + EXPECT_EQ(1, parser()->remb()->num_packets()); + + // Turn off REMB. rtcp_sender no longer should send it. + rtcp_sender->UnsetRemb(); + rtcp_sender->SendRTCP(feedback_state(), kRtcpRr); + ASSERT_EQ(2, parser()->receiver_report()->num_packets()); + EXPECT_EQ(1, parser()->remb()->num_packets()); +} + +TEST_F(RtcpSenderTest, SendRemb) { + const int64_t kBitrate = 261011; + const std::vector<uint32_t> kSsrcs = {kRemoteSsrc, kRemoteSsrc + 1}; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + rtcp_sender->SetRemb(kBitrate, kSsrcs); + + rtcp_sender->SendRTCP(feedback_state(), kRtcpRemb); + + EXPECT_EQ(1, parser()->remb()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->remb()->sender_ssrc()); + EXPECT_EQ(kBitrate, parser()->remb()->bitrate_bps()); + EXPECT_THAT(parser()->remb()->ssrcs(), + ElementsAre(kRemoteSsrc, kRemoteSsrc + 1)); +} + +TEST_F(RtcpSenderTest, RembIncludedInEachCompoundPacketAfterSet) { + const int kBitrate = 261011; + const std::vector<uint32_t> kSsrcs = {kRemoteSsrc, kRemoteSsrc + 1}; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetRemb(kBitrate, kSsrcs); + + rtcp_sender->SendRTCP(feedback_state(), kRtcpReport); + EXPECT_EQ(1, parser()->remb()->num_packets()); + // REMB should be included in each compound packet. + rtcp_sender->SendRTCP(feedback_state(), kRtcpReport); + EXPECT_EQ(2, parser()->remb()->num_packets()); +} + +TEST_F(RtcpSenderTest, SendXrWithDlrr) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + RTCPSender::FeedbackState feedback_state = rtp_rtcp_impl_->GetFeedbackState(); + rtcp::ReceiveTimeInfo last_xr_rr; + last_xr_rr.ssrc = 0x11111111; + last_xr_rr.last_rr = 0x22222222; + last_xr_rr.delay_since_last_rr = 0x33333333; + feedback_state.last_xr_rtis.push_back(last_xr_rr); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state, kRtcpReport)); + EXPECT_EQ(1, parser()->xr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->xr()->sender_ssrc()); + ASSERT_THAT(parser()->xr()->dlrr().sub_blocks(), SizeIs(1)); + EXPECT_EQ(last_xr_rr.ssrc, parser()->xr()->dlrr().sub_blocks()[0].ssrc); + EXPECT_EQ(last_xr_rr.last_rr, parser()->xr()->dlrr().sub_blocks()[0].last_rr); + EXPECT_EQ(last_xr_rr.delay_since_last_rr, + parser()->xr()->dlrr().sub_blocks()[0].delay_since_last_rr); +} + +TEST_F(RtcpSenderTest, SendXrWithMultipleDlrrSubBlocks) { + const size_t kNumReceivers = 2; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + RTCPSender::FeedbackState feedback_state = rtp_rtcp_impl_->GetFeedbackState(); + for (size_t i = 0; i < kNumReceivers; ++i) { + rtcp::ReceiveTimeInfo last_xr_rr; + last_xr_rr.ssrc = i; + last_xr_rr.last_rr = (i + 1) * 100; + last_xr_rr.delay_since_last_rr = (i + 2) * 200; + feedback_state.last_xr_rtis.push_back(last_xr_rr); + } + + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state, kRtcpReport)); + EXPECT_EQ(1, parser()->xr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->xr()->sender_ssrc()); + ASSERT_THAT(parser()->xr()->dlrr().sub_blocks(), SizeIs(kNumReceivers)); + for (size_t i = 0; i < kNumReceivers; ++i) { + EXPECT_EQ(feedback_state.last_xr_rtis[i].ssrc, + parser()->xr()->dlrr().sub_blocks()[i].ssrc); + EXPECT_EQ(feedback_state.last_xr_rtis[i].last_rr, + parser()->xr()->dlrr().sub_blocks()[i].last_rr); + EXPECT_EQ(feedback_state.last_xr_rtis[i].delay_since_last_rr, + parser()->xr()->dlrr().sub_blocks()[i].delay_since_last_rr); + } +} + +TEST_F(RtcpSenderTest, SendXrWithRrtr) { + RTCPSender::Configuration config = GetDefaultConfig(); + config.non_sender_rtt_measurement = true; + auto rtcp_sender = CreateRtcpSender(config); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), false); + NtpTime ntp = clock_.CurrentNtpTime(); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(1, parser()->xr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->xr()->sender_ssrc()); + EXPECT_FALSE(parser()->xr()->dlrr()); + ASSERT_TRUE(parser()->xr()->rrtr()); + EXPECT_EQ(ntp, parser()->xr()->rrtr()->ntp()); +} + +// Same test as above, but enable Rrtr with the setter. +TEST_F(RtcpSenderTest, SendXrWithRrtrUsingSetter) { + RTCPSender::Configuration config = GetDefaultConfig(); + config.non_sender_rtt_measurement = false; + auto rtcp_sender = CreateRtcpSender(config); + rtcp_sender->SetNonSenderRttMeasurement(true); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), false); + NtpTime ntp = clock_.CurrentNtpTime(); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(1, parser()->xr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->xr()->sender_ssrc()); + EXPECT_FALSE(parser()->xr()->dlrr()); + ASSERT_TRUE(parser()->xr()->rrtr()); + EXPECT_EQ(ntp, parser()->xr()->rrtr()->ntp()); +} + +// Same test as above, but disable Rrtr with the setter. +TEST_F(RtcpSenderTest, SendsNoRrtrUsingSetter) { + RTCPSender::Configuration config = GetDefaultConfig(); + config.non_sender_rtt_measurement = true; + auto rtcp_sender = CreateRtcpSender(config); + rtcp_sender->SetNonSenderRttMeasurement(false); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), false); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(0, parser()->xr()->num_packets()); +} + +TEST_F(RtcpSenderTest, TestNoXrRrtrSentIfSending) { + RTCPSender::Configuration config = GetDefaultConfig(); + config.non_sender_rtt_measurement = true; + auto rtcp_sender = CreateRtcpSender(config); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), true); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(0, parser()->xr()->num_packets()); +} + +TEST_F(RtcpSenderTest, TestNoXrRrtrSentIfNotEnabled) { + RTCPSender::Configuration config = GetDefaultConfig(); + config.non_sender_rtt_measurement = false; + auto rtcp_sender = CreateRtcpSender(config); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), false); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(0, parser()->xr()->num_packets()); +} + +TEST_F(RtcpSenderTest, TestRegisterRtcpPacketTypeObserver) { + RtcpPacketTypeCounterObserverImpl observer; + RTCPSender::Configuration config; + config.clock = &clock_; + config.receive_statistics = receive_statistics_.get(); + config.outgoing_transport = &test_transport_; + config.rtcp_packet_type_counter_observer = &observer; + config.rtcp_report_interval = TimeDelta::Millis(1000); + auto rtcp_sender = CreateRtcpSender(config); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpPli)); + EXPECT_EQ(1, parser()->pli()->num_packets()); + EXPECT_EQ(kRemoteSsrc, observer.ssrc_); + EXPECT_EQ(1U, observer.counter_.pli_packets); +} + +TEST_F(RtcpSenderTest, SendTmmbr) { + const unsigned int kBitrateBps = 312000; + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + rtcp_sender->SetTargetBitrate(kBitrateBps); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpTmmbr)); + EXPECT_EQ(1, parser()->tmmbr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->tmmbr()->sender_ssrc()); + EXPECT_EQ(1U, parser()->tmmbr()->requests().size()); + EXPECT_EQ(kBitrateBps, parser()->tmmbr()->requests()[0].bitrate_bps()); + // TODO(asapersson): tmmbr_item()->Overhead() looks broken, always zero. +} + +TEST_F(RtcpSenderTest, SendTmmbn) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), true); + std::vector<rtcp::TmmbItem> bounding_set; + const uint32_t kBitrateBps = 32768000; + const uint32_t kPacketOh = 40; + const uint32_t kSourceSsrc = 12345; + const rtcp::TmmbItem tmmbn(kSourceSsrc, kBitrateBps, kPacketOh); + bounding_set.push_back(tmmbn); + rtcp_sender->SetTmmbn(bounding_set); + + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpSr)); + EXPECT_EQ(1, parser()->sender_report()->num_packets()); + EXPECT_EQ(1, parser()->tmmbn()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->tmmbn()->sender_ssrc()); + EXPECT_EQ(1U, parser()->tmmbn()->items().size()); + EXPECT_EQ(kBitrateBps, parser()->tmmbn()->items()[0].bitrate_bps()); + EXPECT_EQ(kPacketOh, parser()->tmmbn()->items()[0].packet_overhead()); + EXPECT_EQ(kSourceSsrc, parser()->tmmbn()->items()[0].ssrc()); +} + +// This test is written to verify actual behaviour. It does not seem +// to make much sense to send an empty TMMBN, since there is no place +// to put an actual limit here. It's just information that no limit +// is set, which is kind of the starting assumption. +// See http://code.google.com/p/webrtc/issues/detail?id=468 for one +// situation where this caused confusion. +TEST_F(RtcpSenderTest, SendsTmmbnIfSetAndEmpty) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetSendingStatus(feedback_state(), true); + std::vector<rtcp::TmmbItem> bounding_set; + rtcp_sender->SetTmmbn(bounding_set); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpSr)); + EXPECT_EQ(1, parser()->sender_report()->num_packets()); + EXPECT_EQ(1, parser()->tmmbn()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->tmmbn()->sender_ssrc()); + EXPECT_EQ(0U, parser()->tmmbn()->items().size()); +} + +// This test is written to verify that BYE is always the last packet +// type in a RTCP compoud packet. The rtcp_sender is recreated with +// mock_transport, which is used to check for whether BYE at the end +// of a RTCP compound packet. +TEST_F(RtcpSenderTest, ByeMustBeLast) { + MockTransport mock_transport; + EXPECT_CALL(mock_transport, SendRtcp(_, _)) + .WillOnce(Invoke([](const uint8_t* data, size_t len) { + const uint8_t* next_packet = data; + const uint8_t* const packet_end = data + len; + rtcp::CommonHeader packet; + while (next_packet < packet_end) { + EXPECT_TRUE(packet.Parse(next_packet, packet_end - next_packet)); + next_packet = packet.NextPacket(); + if (packet.type() == + rtcp::Bye::kPacketType) // Main test expectation. + EXPECT_EQ(0, packet_end - next_packet) + << "Bye packet should be last in a compound RTCP packet."; + if (next_packet == packet_end) // Validate test was set correctly. + EXPECT_EQ(packet.type(), rtcp::Bye::kPacketType) + << "Last packet in this test expected to be Bye."; + } + + return true; + })); + + // Re-configure rtcp_sender with mock_transport_ + RTCPSender::Configuration config; + config.clock = &clock_; + config.receive_statistics = receive_statistics_.get(); + config.outgoing_transport = &mock_transport; + config.rtcp_report_interval = TimeDelta::Millis(1000); + config.local_media_ssrc = kSenderSsrc; + auto rtcp_sender = CreateRtcpSender(config); + + rtcp_sender->SetTimestampOffset(kStartRtpTimestamp); + rtcp_sender->SetLastRtpTime(kRtpTimestamp, clock_.CurrentTime(), + /*payload_type=*/0); + + // Set up REMB info to be included with BYE. + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + rtcp_sender->SetRemb(1234, {}); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpBye)); +} + +TEST_F(RtcpSenderTest, SendXrWithTargetBitrate) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + const size_t kNumSpatialLayers = 2; + const size_t kNumTemporalLayers = 2; + VideoBitrateAllocation allocation; + for (size_t sl = 0; sl < kNumSpatialLayers; ++sl) { + uint32_t start_bitrate_bps = (sl + 1) * 100000; + for (size_t tl = 0; tl < kNumTemporalLayers; ++tl) + allocation.SetBitrate(sl, tl, start_bitrate_bps + (tl * 20000)); + } + rtcp_sender->SetVideoBitrateAllocation(allocation); + + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + EXPECT_EQ(1, parser()->xr()->num_packets()); + EXPECT_EQ(kSenderSsrc, parser()->xr()->sender_ssrc()); + const absl::optional<rtcp::TargetBitrate>& target_bitrate = + parser()->xr()->target_bitrate(); + ASSERT_TRUE(target_bitrate); + const std::vector<rtcp::TargetBitrate::BitrateItem>& bitrates = + target_bitrate->GetTargetBitrates(); + EXPECT_EQ(kNumSpatialLayers * kNumTemporalLayers, bitrates.size()); + + for (size_t sl = 0; sl < kNumSpatialLayers; ++sl) { + uint32_t start_bitrate_bps = (sl + 1) * 100000; + for (size_t tl = 0; tl < kNumTemporalLayers; ++tl) { + size_t index = (sl * kNumSpatialLayers) + tl; + const rtcp::TargetBitrate::BitrateItem& item = bitrates[index]; + EXPECT_EQ(sl, item.spatial_layer); + EXPECT_EQ(tl, item.temporal_layer); + EXPECT_EQ(start_bitrate_bps + (tl * 20000), + item.target_bitrate_kbps * 1000); + } + } +} + +TEST_F(RtcpSenderTest, SendImmediateXrWithTargetBitrate) { + // Initialize. Send a first report right away. + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + clock_.AdvanceTimeMilliseconds(5); + + // Video bitrate allocation generated, save until next time we send a report. + VideoBitrateAllocation allocation; + allocation.SetBitrate(0, 0, 100000); + rtcp_sender->SetVideoBitrateAllocation(allocation); + // First seen instance will be sent immediately. + EXPECT_TRUE(rtcp_sender->TimeToSendRTCPReport(false)); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + clock_.AdvanceTimeMilliseconds(5); + + // Update bitrate of existing layer, does not quality for immediate sending. + allocation.SetBitrate(0, 0, 150000); + rtcp_sender->SetVideoBitrateAllocation(allocation); + EXPECT_FALSE(rtcp_sender->TimeToSendRTCPReport(false)); + + // A new spatial layer enabled, signal this as soon as possible. + allocation.SetBitrate(1, 0, 200000); + rtcp_sender->SetVideoBitrateAllocation(allocation); + EXPECT_TRUE(rtcp_sender->TimeToSendRTCPReport(false)); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + clock_.AdvanceTimeMilliseconds(5); + + // Explicitly disable top layer. The same set of layers now has a bitrate + // defined, but the explicit 0 indicates shutdown. Signal immediately. + allocation.SetBitrate(1, 0, 0); + EXPECT_FALSE(rtcp_sender->TimeToSendRTCPReport(false)); + rtcp_sender->SetVideoBitrateAllocation(allocation); + EXPECT_TRUE(rtcp_sender->TimeToSendRTCPReport(false)); +} + +TEST_F(RtcpSenderTest, SendTargetBitrateExplicitZeroOnStreamRemoval) { + // Set up and send a bitrate allocation with two layers. + + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kCompound); + VideoBitrateAllocation allocation; + allocation.SetBitrate(0, 0, 100000); + allocation.SetBitrate(1, 0, 200000); + rtcp_sender->SetVideoBitrateAllocation(allocation); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + absl::optional<rtcp::TargetBitrate> target_bitrate = + parser()->xr()->target_bitrate(); + ASSERT_TRUE(target_bitrate); + std::vector<rtcp::TargetBitrate::BitrateItem> bitrates = + target_bitrate->GetTargetBitrates(); + ASSERT_EQ(2u, bitrates.size()); + EXPECT_EQ(bitrates[0].target_bitrate_kbps, + allocation.GetBitrate(0, 0) / 1000); + EXPECT_EQ(bitrates[1].target_bitrate_kbps, + allocation.GetBitrate(1, 0) / 1000); + + // Create a new allocation, where the second stream is no longer available. + VideoBitrateAllocation new_allocation; + new_allocation.SetBitrate(0, 0, 150000); + rtcp_sender->SetVideoBitrateAllocation(new_allocation); + EXPECT_EQ(0, rtcp_sender->SendRTCP(feedback_state(), kRtcpReport)); + target_bitrate = parser()->xr()->target_bitrate(); + ASSERT_TRUE(target_bitrate); + bitrates = target_bitrate->GetTargetBitrates(); + + // Two bitrates should still be set, with an explicit entry indicating the + // removed stream is gone. + ASSERT_EQ(2u, bitrates.size()); + EXPECT_EQ(bitrates[0].target_bitrate_kbps, + new_allocation.GetBitrate(0, 0) / 1000); + EXPECT_EQ(bitrates[1].target_bitrate_kbps, 0u); +} + +TEST_F(RtcpSenderTest, DoesntSchedulesInitialReportWhenSsrcSetOnConstruction) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + rtcp_sender->SetRemoteSSRC(kRemoteSsrc); + // New report should not have been scheduled yet. + clock_.AdvanceTimeMilliseconds(100); + EXPECT_FALSE(rtcp_sender->TimeToSendRTCPReport(false)); +} + +TEST_F(RtcpSenderTest, SendsCombinedRtcpPacket) { + auto rtcp_sender = CreateRtcpSender(GetDefaultConfig()); + rtcp_sender->SetRTCPStatus(RtcpMode::kReducedSize); + + std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets; + auto transport_feedback = std::make_unique<rtcp::TransportFeedback>(); + transport_feedback->AddReceivedPacket(321, Timestamp::Millis(10)); + packets.push_back(std::move(transport_feedback)); + auto remote_estimate = std::make_unique<rtcp::RemoteEstimate>(); + packets.push_back(std::move(remote_estimate)); + rtcp_sender->SendCombinedRtcpPacket(std::move(packets)); + + EXPECT_EQ(parser()->transport_feedback()->num_packets(), 1); + EXPECT_EQ(parser()->transport_feedback()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(parser()->app()->num_packets(), 1); + EXPECT_EQ(parser()->app()->sender_ssrc(), kSenderSsrc); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.cc new file mode 100644 index 0000000000..f265bd5825 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.cc @@ -0,0 +1,150 @@ +/* + * 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/rtp_rtcp/source/rtcp_transceiver.h" + +#include <memory> +#include <utility> +#include <vector> + +#include "absl/cleanup/cleanup.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +RtcpTransceiver::RtcpTransceiver(const RtcpTransceiverConfig& config) + : clock_(config.clock), + task_queue_(config.task_queue), + rtcp_transceiver_(std::make_unique<RtcpTransceiverImpl>(config)) { + RTC_DCHECK(task_queue_); +} + +RtcpTransceiver::~RtcpTransceiver() { + if (!rtcp_transceiver_) + return; + auto rtcp_transceiver = std::move(rtcp_transceiver_); + task_queue_->PostTask([rtcp_transceiver = std::move(rtcp_transceiver)] { + rtcp_transceiver->StopPeriodicTask(); + }); + RTC_DCHECK(!rtcp_transceiver_); +} + +void RtcpTransceiver::Stop(absl::AnyInvocable<void() &&> on_destroyed) { + RTC_DCHECK(rtcp_transceiver_); + auto rtcp_transceiver = std::move(rtcp_transceiver_); + absl::Cleanup cleanup = std::move(on_destroyed); + task_queue_->PostTask( + [rtcp_transceiver = std::move(rtcp_transceiver), + cleanup = std::move(cleanup)] { rtcp_transceiver->StopPeriodicTask(); }); + RTC_DCHECK(!rtcp_transceiver_); +} + +void RtcpTransceiver::AddMediaReceiverRtcpObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr, remote_ssrc, observer] { + ptr->AddMediaReceiverRtcpObserver(remote_ssrc, observer); + }); +} + +void RtcpTransceiver::RemoveMediaReceiverRtcpObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer, + absl::AnyInvocable<void() &&> on_removed) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + absl::Cleanup cleanup = std::move(on_removed); + task_queue_->PostTask( + [ptr, remote_ssrc, observer, cleanup = std::move(cleanup)] { + ptr->RemoveMediaReceiverRtcpObserver(remote_ssrc, observer); + }); +} + +void RtcpTransceiver::SetReadyToSend(bool ready) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr, ready] { ptr->SetReadyToSend(ready); }); +} + +void RtcpTransceiver::ReceivePacket(rtc::CopyOnWriteBuffer packet) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + Timestamp now = clock_->CurrentTime(); + task_queue_->PostTask( + [ptr, packet, now] { ptr->ReceivePacket(packet, now); }); +} + +void RtcpTransceiver::SendCompoundPacket() { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr] { ptr->SendCompoundPacket(); }); +} + +void RtcpTransceiver::SetRemb(int64_t bitrate_bps, + std::vector<uint32_t> ssrcs) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr, bitrate_bps, ssrcs = std::move(ssrcs)]() mutable { + ptr->SetRemb(bitrate_bps, std::move(ssrcs)); + }); +} + +void RtcpTransceiver::UnsetRemb() { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr] { ptr->UnsetRemb(); }); +} + +void RtcpTransceiver::SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask( + [ptr, rtcp_packets = std::move(rtcp_packets)]() mutable { + ptr->SendCombinedRtcpPacket(std::move(rtcp_packets)); + }); +} + +void RtcpTransceiver::SendNack(uint32_t ssrc, + std::vector<uint16_t> sequence_numbers) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask( + [ptr, ssrc, sequence_numbers = std::move(sequence_numbers)]() mutable { + ptr->SendNack(ssrc, std::move(sequence_numbers)); + }); +} + +void RtcpTransceiver::SendPictureLossIndication(uint32_t ssrc) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr, ssrc] { ptr->SendPictureLossIndication(ssrc); }); +} + +void RtcpTransceiver::SendFullIntraRequest(std::vector<uint32_t> ssrcs) { + return SendFullIntraRequest(std::move(ssrcs), true); +} + +void RtcpTransceiver::SendFullIntraRequest(std::vector<uint32_t> ssrcs, + bool new_request) { + RTC_CHECK(rtcp_transceiver_); + RtcpTransceiverImpl* ptr = rtcp_transceiver_.get(); + task_queue_->PostTask([ptr, ssrcs = std::move(ssrcs), new_request] { + ptr->SendFullIntraRequest(ssrcs, new_request); + }); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.h new file mode 100644 index 0000000000..22fcc73337 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.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 MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/functional/any_invocable.h" +#include "api/task_queue/task_queue_base.h" +#include "modules/rtp_rtcp/source/rtcp_transceiver_config.h" +#include "modules/rtp_rtcp/source/rtcp_transceiver_impl.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +// +// Manage incoming and outgoing rtcp messages for multiple BUNDLED streams. +// +// This class is thread-safe wrapper of RtcpTransceiverImpl +class RtcpTransceiver : public RtcpFeedbackSenderInterface { + public: + explicit RtcpTransceiver(const RtcpTransceiverConfig& config); + RtcpTransceiver(const RtcpTransceiver&) = delete; + RtcpTransceiver& operator=(const RtcpTransceiver&) = delete; + // Note that interfaces provided in constructor still might be used after the + // destructor. However they can only be used on the confic.task_queue. + // Use Stop function to get notified when they are no longer used or + // ensure those objects outlive the task queue. + ~RtcpTransceiver() override; + + // Start asynchronious destruction of the RtcpTransceiver. + // It is safe to call destructor right after Stop exits. + // No other methods can be called. + // Note that interfaces provided in constructor or registered with AddObserver + // still might be used by the transceiver on the task queue + // until `on_destroyed` runs. + void Stop(absl::AnyInvocable<void() &&> on_destroyed); + + // Registers observer to be notified about incoming rtcp packets. + // Calls to observer will be done on the `config.task_queue`. + void AddMediaReceiverRtcpObserver(uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer); + // Deregisters the observer. Might return before observer is deregistered. + // Runs `on_removed` when observer is deregistered. + void RemoveMediaReceiverRtcpObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer, + absl::AnyInvocable<void() &&> on_removed); + + // Enables/disables sending rtcp packets eventually. + // Packets may be sent after the SetReadyToSend(false) returns, but no new + // packets will be scheduled. + void SetReadyToSend(bool ready); + + // Handles incoming rtcp packets. + void ReceivePacket(rtc::CopyOnWriteBuffer packet); + + // Sends RTCP packets starting with a sender or receiver report. + void SendCompoundPacket(); + + // (REMB) Receiver Estimated Max Bitrate. + // Includes REMB in following compound packets and sends a REMB message + // immediately if 'RtcpTransceiverConfig::send_remb_on_change' is set. + void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) override; + // Stops sending REMB in following compound packets. + void UnsetRemb() override; + + // TODO(bugs.webrtc.org/8239): Remove SendCombinedRtcpPacket + // and move generating of the TransportFeedback message inside + // RtcpTransceiverImpl when there is one RtcpTransceiver per rtp transport. + void SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) override; + + // Reports missing packets, https://tools.ietf.org/html/rfc4585#section-6.2.1 + void SendNack(uint32_t ssrc, std::vector<uint16_t> sequence_numbers); + + // Requests new key frame. + // using PLI, https://tools.ietf.org/html/rfc4585#section-6.3.1.1 + void SendPictureLossIndication(uint32_t ssrc); + // using FIR, https://tools.ietf.org/html/rfc5104#section-4.3.1.2 + // Use the SendFullIntraRequest(ssrcs, true) instead. + void SendFullIntraRequest(std::vector<uint32_t> ssrcs); + // If new_request is true then requested sequence no. will increase for each + // requested ssrc. + void SendFullIntraRequest(std::vector<uint32_t> ssrcs, bool new_request); + + private: + Clock* const clock_; + TaskQueueBase* const task_queue_; + std::unique_ptr<RtcpTransceiverImpl> rtcp_transceiver_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.cc new file mode 100644 index 0000000000..7acaa0f600 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.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 "modules/rtp_rtcp/source/rtcp_transceiver_config.h" + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +RtcpTransceiverConfig::RtcpTransceiverConfig() = default; +RtcpTransceiverConfig::RtcpTransceiverConfig(const RtcpTransceiverConfig&) = + default; +RtcpTransceiverConfig& RtcpTransceiverConfig::operator=( + const RtcpTransceiverConfig&) = default; +RtcpTransceiverConfig::~RtcpTransceiverConfig() = default; + +bool RtcpTransceiverConfig::Validate() const { + if (feedback_ssrc == 0) { + RTC_LOG(LS_WARNING) + << debug_id + << "Ssrc 0 may be treated by some implementation as invalid."; + } + if (cname.size() > 255) { + RTC_LOG(LS_ERROR) << debug_id << "cname can be maximum 255 characters."; + return false; + } + if (max_packet_size < 100) { + RTC_LOG(LS_ERROR) << debug_id << "max packet size " << max_packet_size + << " is too small."; + return false; + } + if (max_packet_size > IP_PACKET_SIZE) { + RTC_LOG(LS_ERROR) << debug_id << "max packet size " << max_packet_size + << " more than " << IP_PACKET_SIZE << " is unsupported."; + return false; + } + if (clock == nullptr) { + RTC_LOG(LS_ERROR) << debug_id << "clock must be set"; + return false; + } + if (!outgoing_transport) { + RTC_LOG(LS_ERROR) << debug_id << "outgoing transport must be set"; + return false; + } + if (initial_report_delay < TimeDelta::Zero()) { + RTC_LOG(LS_ERROR) << debug_id << "delay " << initial_report_delay.ms() + << "ms before first report shouldn't be negative."; + return false; + } + if (report_period <= TimeDelta::Zero()) { + RTC_LOG(LS_ERROR) << debug_id << "period " << report_period.ms() + << "ms between reports should be positive."; + return false; + } + if (schedule_periodic_compound_packets && task_queue == nullptr) { + RTC_LOG(LS_ERROR) << debug_id + << "missing task queue for periodic compound packets"; + return false; + } + if (rtcp_mode != RtcpMode::kCompound && rtcp_mode != RtcpMode::kReducedSize) { + RTC_LOG(LS_ERROR) << debug_id << "unsupported rtcp mode"; + return false; + } + if (non_sender_rtt_measurement && !network_link_observer) { + RTC_LOG(LS_WARNING) << debug_id + << "Enabled special feature to calculate rtt, but no " + "rtt observer is provided."; + } + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.h new file mode 100644 index 0000000000..3122ad5c36 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.h @@ -0,0 +1,188 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_CONFIG_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_CONFIG_H_ + +#include <string> + +#include "api/array_view.h" +#include "api/rtp_headers.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "system_wrappers/include/clock.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +class ReceiveStatisticsProvider; +class Transport; + +// Interface to watch incoming rtcp packets related to the link in general. +// All message handlers have default empty implementation. This way users only +// need to implement the ones they are interested in. +// All message handles pass `receive_time` parameter, which is receive time +// of the rtcp packet that triggered the update. +class NetworkLinkRtcpObserver { + public: + virtual ~NetworkLinkRtcpObserver() = default; + + virtual void OnTransportFeedback(Timestamp receive_time, + const rtcp::TransportFeedback& feedback) {} + virtual void OnReceiverEstimatedMaxBitrate(Timestamp receive_time, + DataRate bitrate) {} + virtual void OnReportBlocks( + Timestamp receive_time, + rtc::ArrayView<const rtcp::ReportBlock> report_blocks) {} + virtual void OnRttUpdate(Timestamp receive_time, TimeDelta rtt) {} +}; + +// Interface to watch incoming rtcp packets by media (rtp) receiver. +// All message handlers have default empty implementation. This way users only +// need to implement the ones they are interested in. +class MediaReceiverRtcpObserver { + public: + virtual ~MediaReceiverRtcpObserver() = default; + + virtual void OnSenderReport(uint32_t sender_ssrc, + NtpTime ntp_time, + uint32_t rtp_time) {} + virtual void OnBye(uint32_t sender_ssrc) {} + virtual void OnBitrateAllocation(uint32_t sender_ssrc, + const VideoBitrateAllocation& allocation) {} +}; + +// Handles RTCP related messages for a single RTP stream (i.e. single SSRC) +class RtpStreamRtcpHandler { + public: + virtual ~RtpStreamRtcpHandler() = default; + + // Statistic about sent RTP packets to propagate to RTCP sender report. + class RtpStats { + public: + RtpStats() = default; + RtpStats(const RtpStats&) = default; + RtpStats& operator=(const RtpStats&) = default; + ~RtpStats() = default; + + size_t num_sent_packets() const { return num_sent_packets_; } + size_t num_sent_bytes() const { return num_sent_bytes_; } + Timestamp last_capture_time() const { return last_capture_time_; } + uint32_t last_rtp_timestamp() const { return last_rtp_timestamp_; } + int last_clock_rate() const { return last_clock_rate_; } + + void set_num_sent_packets(size_t v) { num_sent_packets_ = v; } + void set_num_sent_bytes(size_t v) { num_sent_bytes_ = v; } + void set_last_capture_time(Timestamp v) { last_capture_time_ = v; } + void set_last_rtp_timestamp(uint32_t v) { last_rtp_timestamp_ = v; } + void set_last_clock_rate(int v) { last_clock_rate_ = v; } + + private: + size_t num_sent_packets_ = 0; + size_t num_sent_bytes_ = 0; + Timestamp last_capture_time_ = Timestamp::Zero(); + uint32_t last_rtp_timestamp_ = 0; + int last_clock_rate_ = 90'000; + }; + virtual RtpStats SentStats() = 0; + + virtual void OnNack(uint32_t sender_ssrc, + rtc::ArrayView<const uint16_t> sequence_numbers) {} + virtual void OnFir(uint32_t sender_ssrc) {} + virtual void OnPli(uint32_t sender_ssrc) {} + virtual void OnReportBlock(uint32_t sender_ssrc, + const rtcp::ReportBlock& report_block) {} +}; + +struct RtcpTransceiverConfig { + RtcpTransceiverConfig(); + RtcpTransceiverConfig(const RtcpTransceiverConfig&); + RtcpTransceiverConfig& operator=(const RtcpTransceiverConfig&); + ~RtcpTransceiverConfig(); + + // Logs the error and returns false if configuration miss key objects or + // is inconsistant. May log warnings. + bool Validate() const; + + // Used to prepend all log messages. Can be empty. + std::string debug_id; + + // Ssrc to use as default sender ssrc, e.g. for transport-wide feedbacks. + uint32_t feedback_ssrc = 1; + + // Canonical End-Point Identifier of the local particiapnt. + // Defined in rfc3550 section 6 note 2 and section 6.5.1. + std::string cname; + + // Maximum packet size outgoing transport accepts. + size_t max_packet_size = 1200; + + // The clock to use when querying for the NTP time. Should be set. + Clock* clock = nullptr; + + // Transport to send rtcp packets to. Should be set. + Transport* outgoing_transport = nullptr; + + // Queue for scheduling delayed tasks, e.g. sending periodic compound packets. + TaskQueueBase* task_queue = nullptr; + + // Rtcp report block generator for outgoing receiver reports. + ReceiveStatisticsProvider* receive_statistics = nullptr; + + // Should outlive RtcpTransceiver. + // Callbacks will be invoked on the `task_queue`. + NetworkLinkRtcpObserver* network_link_observer = nullptr; + + // Configures if sending should + // enforce compound packets: https://tools.ietf.org/html/rfc4585#section-3.1 + // or allow reduced size packets: https://tools.ietf.org/html/rfc5506 + // Receiving accepts both compound and reduced-size packets. + RtcpMode rtcp_mode = RtcpMode::kCompound; + // + // Tuning parameters. + // + // Initial state if `outgoing_transport` ready to accept packets. + bool initial_ready_to_send = true; + // Delay before 1st periodic compound packet. + TimeDelta initial_report_delay = TimeDelta::Millis(500); + + // Period between periodic compound packets. + TimeDelta report_period = TimeDelta::Seconds(1); + + // + // Flags for features and experiments. + // + bool schedule_periodic_compound_packets = true; + // Estimate RTT as non-sender as described in + // https://tools.ietf.org/html/rfc3611#section-4.4 and #section-4.5 + bool non_sender_rtt_measurement = false; + + // Reply to incoming RRTR messages so that remote endpoint may estimate RTT as + // non-sender as described in https://tools.ietf.org/html/rfc3611#section-4.4 + // and #section-4.5 + bool reply_to_non_sender_rtt_measurement = true; + + // Reply to incoming RRTR messages multiple times, one per sender SSRC, to + // support clients that calculate and process RTT per sender SSRC. + bool reply_to_non_sender_rtt_mesaurments_on_all_ssrcs = true; + + // Allows a REMB message to be sent immediately when SetRemb is called without + // having to wait for the next compount message to be sent. + bool send_remb_on_change = false; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_CONFIG_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc new file mode 100644 index 0000000000..bb4f96b970 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc @@ -0,0 +1,857 @@ +/* + * 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/rtp_rtcp/source/rtcp_transceiver_impl.h" + +#include <algorithm> +#include <utility> + +#include "absl/algorithm/container.h" +#include "absl/memory/memory.h" +#include "api/call/transport.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet.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/nack.h" +#include "modules/rtp_rtcp/source/rtcp_packet/pli.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.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/time_util.h" +#include "rtc_base/checks.h" +#include "rtc_base/containers/flat_map.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/divide_round.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace { + +struct SenderReportTimes { + Timestamp local_received_time; + NtpTime remote_sent_time; +}; + +} // namespace + +struct RtcpTransceiverImpl::RemoteSenderState { + uint8_t fir_sequence_number = 0; + absl::optional<SenderReportTimes> last_received_sender_report; + std::vector<MediaReceiverRtcpObserver*> observers; +}; + +struct RtcpTransceiverImpl::LocalSenderState { + uint32_t ssrc; + size_t last_num_sent_bytes = 0; + // Sequence number of the last FIR message per sender SSRC. + flat_map<uint32_t, uint8_t> last_fir; + RtpStreamRtcpHandler* handler = nullptr; +}; + +// Helper to put several RTCP packets into lower layer datagram composing +// Compound or Reduced-Size RTCP packet, as defined by RFC 5506 section 2. +// TODO(danilchap): When in compound mode and packets are so many that several +// compound RTCP packets need to be generated, ensure each packet is compound. +class RtcpTransceiverImpl::PacketSender { + public: + PacketSender(rtcp::RtcpPacket::PacketReadyCallback callback, + size_t max_packet_size) + : callback_(callback), max_packet_size_(max_packet_size) { + RTC_CHECK_LE(max_packet_size, IP_PACKET_SIZE); + } + ~PacketSender() { RTC_DCHECK_EQ(index_, 0) << "Unsent rtcp packet."; } + + // Appends a packet to pending compound packet. + // Sends rtcp compound packet if buffer was already full and resets buffer. + void AppendPacket(const rtcp::RtcpPacket& packet) { + packet.Create(buffer_, &index_, max_packet_size_, callback_); + } + + // Sends pending rtcp compound packet. + void Send() { + if (index_ > 0) { + callback_(rtc::ArrayView<const uint8_t>(buffer_, index_)); + index_ = 0; + } + } + + bool IsEmpty() const { return index_ == 0; } + + private: + const rtcp::RtcpPacket::PacketReadyCallback callback_; + const size_t max_packet_size_; + size_t index_ = 0; + uint8_t buffer_[IP_PACKET_SIZE]; +}; + +RtcpTransceiverImpl::RtcpTransceiverImpl(const RtcpTransceiverConfig& config) + : config_(config), ready_to_send_(config.initial_ready_to_send) { + RTC_CHECK(config_.Validate()); + if (ready_to_send_ && config_.schedule_periodic_compound_packets) { + SchedulePeriodicCompoundPackets(config_.initial_report_delay); + } +} + +RtcpTransceiverImpl::~RtcpTransceiverImpl() = default; + +void RtcpTransceiverImpl::AddMediaReceiverRtcpObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer) { + if (config_.receive_statistics == nullptr && remote_senders_.empty()) { + RTC_LOG(LS_WARNING) << config_.debug_id + << "receive statistic is not set. RTCP report blocks " + "will not be generated."; + } + auto& stored = remote_senders_[remote_ssrc].observers; + RTC_DCHECK(!absl::c_linear_search(stored, observer)); + stored.push_back(observer); +} + +void RtcpTransceiverImpl::RemoveMediaReceiverRtcpObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer) { + auto remote_sender_it = remote_senders_.find(remote_ssrc); + if (remote_sender_it == remote_senders_.end()) + return; + auto& stored = remote_sender_it->second.observers; + auto it = absl::c_find(stored, observer); + if (it == stored.end()) + return; + stored.erase(it); +} + +bool RtcpTransceiverImpl::AddMediaSender(uint32_t local_ssrc, + RtpStreamRtcpHandler* handler) { + RTC_DCHECK(handler != nullptr); + LocalSenderState state; + state.ssrc = local_ssrc; + state.handler = handler; + local_senders_.push_back(state); + auto it = std::prev(local_senders_.end()); + auto [unused, inserted] = local_senders_by_ssrc_.emplace(local_ssrc, it); + if (!inserted) { + local_senders_.pop_back(); + return false; + } + return true; +} + +bool RtcpTransceiverImpl::RemoveMediaSender(uint32_t local_ssrc) { + auto index_it = local_senders_by_ssrc_.find(local_ssrc); + if (index_it == local_senders_by_ssrc_.end()) { + return false; + } + local_senders_.erase(index_it->second); + local_senders_by_ssrc_.erase(index_it); + return true; +} + +void RtcpTransceiverImpl::SetReadyToSend(bool ready) { + if (config_.schedule_periodic_compound_packets) { + if (ready_to_send_ && !ready) + periodic_task_handle_.Stop(); + + if (!ready_to_send_ && ready) // Restart periodic sending. + SchedulePeriodicCompoundPackets(config_.report_period / 2); + } + ready_to_send_ = ready; +} + +void RtcpTransceiverImpl::ReceivePacket(rtc::ArrayView<const uint8_t> packet, + Timestamp now) { + // Report blocks may be spread across multiple sender and receiver reports. + std::vector<rtcp::ReportBlock> report_blocks; + + while (!packet.empty()) { + rtcp::CommonHeader rtcp_block; + if (!rtcp_block.Parse(packet.data(), packet.size())) + break; + + HandleReceivedPacket(rtcp_block, now, report_blocks); + + packet = packet.subview(rtcp_block.packet_size()); + } + + if (!report_blocks.empty()) { + ProcessReportBlocks(now, report_blocks); + } +} + +void RtcpTransceiverImpl::SendCompoundPacket() { + if (!ready_to_send_) + return; + SendPeriodicCompoundPacket(); + ReschedulePeriodicCompoundPackets(); +} + +void RtcpTransceiverImpl::SetRemb(int64_t bitrate_bps, + std::vector<uint32_t> ssrcs) { + RTC_DCHECK_GE(bitrate_bps, 0); + + bool send_now = config_.send_remb_on_change && + (!remb_.has_value() || bitrate_bps != remb_->bitrate_bps()); + remb_.emplace(); + remb_->SetSsrcs(std::move(ssrcs)); + remb_->SetBitrateBps(bitrate_bps); + remb_->SetSenderSsrc(config_.feedback_ssrc); + // TODO(bugs.webrtc.org/8239): Move logic from PacketRouter for sending remb + // immideately on large bitrate change when there is one RtcpTransceiver per + // rtp transport. + if (send_now) { + absl::optional<rtcp::Remb> remb; + remb.swap(remb_); + SendImmediateFeedback(*remb); + remb.swap(remb_); + } +} + +void RtcpTransceiverImpl::UnsetRemb() { + remb_.reset(); +} + +void RtcpTransceiverImpl::SendRawPacket(rtc::ArrayView<const uint8_t> packet) { + if (!ready_to_send_) + return; + // Unlike other senders, this functions just tries to send packet away and + // disregard rtcp_mode, max_packet_size or anything else. + // TODO(bugs.webrtc.org/8239): respect config_ by creating the + // TransportFeedback inside this class when there is one per rtp transport. + config_.outgoing_transport->SendRtcp(packet.data(), packet.size()); +} + +void RtcpTransceiverImpl::SendNack(uint32_t ssrc, + std::vector<uint16_t> sequence_numbers) { + RTC_DCHECK(!sequence_numbers.empty()); + if (!ready_to_send_) + return; + rtcp::Nack nack; + nack.SetSenderSsrc(config_.feedback_ssrc); + nack.SetMediaSsrc(ssrc); + nack.SetPacketIds(std::move(sequence_numbers)); + SendImmediateFeedback(nack); +} + +void RtcpTransceiverImpl::SendPictureLossIndication(uint32_t ssrc) { + if (!ready_to_send_) + return; + rtcp::Pli pli; + pli.SetSenderSsrc(config_.feedback_ssrc); + pli.SetMediaSsrc(ssrc); + SendImmediateFeedback(pli); +} + +void RtcpTransceiverImpl::SendFullIntraRequest( + rtc::ArrayView<const uint32_t> ssrcs, + bool new_request) { + RTC_DCHECK(!ssrcs.empty()); + if (!ready_to_send_) + return; + rtcp::Fir fir; + fir.SetSenderSsrc(config_.feedback_ssrc); + for (uint32_t media_ssrc : ssrcs) { + uint8_t& command_seq_num = remote_senders_[media_ssrc].fir_sequence_number; + if (new_request) + command_seq_num += 1; + fir.AddRequestTo(media_ssrc, command_seq_num); + } + SendImmediateFeedback(fir); +} + +void RtcpTransceiverImpl::HandleReceivedPacket( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now, + std::vector<rtcp::ReportBlock>& report_blocks) { + switch (rtcp_packet_header.type()) { + case rtcp::Bye::kPacketType: + HandleBye(rtcp_packet_header); + break; + case rtcp::SenderReport::kPacketType: + HandleSenderReport(rtcp_packet_header, now, report_blocks); + break; + case rtcp::ReceiverReport::kPacketType: + HandleReceiverReport(rtcp_packet_header, report_blocks); + break; + case rtcp::ExtendedReports::kPacketType: + HandleExtendedReports(rtcp_packet_header, now); + break; + case rtcp::Psfb::kPacketType: + HandlePayloadSpecificFeedback(rtcp_packet_header, now); + break; + case rtcp::Rtpfb::kPacketType: + HandleRtpFeedback(rtcp_packet_header, now); + break; + } +} + +void RtcpTransceiverImpl::HandleBye( + const rtcp::CommonHeader& rtcp_packet_header) { + rtcp::Bye bye; + if (!bye.Parse(rtcp_packet_header)) + return; + auto remote_sender_it = remote_senders_.find(bye.sender_ssrc()); + if (remote_sender_it == remote_senders_.end()) + return; + for (MediaReceiverRtcpObserver* observer : remote_sender_it->second.observers) + observer->OnBye(bye.sender_ssrc()); +} + +void RtcpTransceiverImpl::HandleSenderReport( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now, + std::vector<rtcp::ReportBlock>& report_blocks) { + rtcp::SenderReport sender_report; + if (!sender_report.Parse(rtcp_packet_header)) + return; + RemoteSenderState& remote_sender = + remote_senders_[sender_report.sender_ssrc()]; + remote_sender.last_received_sender_report = {{now, sender_report.ntp()}}; + const auto& received_report_blocks = sender_report.report_blocks(); + CallbackOnReportBlocks(sender_report.sender_ssrc(), received_report_blocks); + report_blocks.insert(report_blocks.end(), received_report_blocks.begin(), + received_report_blocks.end()); + + for (MediaReceiverRtcpObserver* observer : remote_sender.observers) + observer->OnSenderReport(sender_report.sender_ssrc(), sender_report.ntp(), + sender_report.rtp_timestamp()); +} + +void RtcpTransceiverImpl::HandleReceiverReport( + const rtcp::CommonHeader& rtcp_packet_header, + std::vector<rtcp::ReportBlock>& report_blocks) { + rtcp::ReceiverReport receiver_report; + if (!receiver_report.Parse(rtcp_packet_header)) { + return; + } + const auto& received_report_blocks = receiver_report.report_blocks(); + CallbackOnReportBlocks(receiver_report.sender_ssrc(), received_report_blocks); + report_blocks.insert(report_blocks.end(), received_report_blocks.begin(), + received_report_blocks.end()); +} + +void RtcpTransceiverImpl::CallbackOnReportBlocks( + uint32_t sender_ssrc, + rtc::ArrayView<const rtcp::ReportBlock> report_blocks) { + if (local_senders_.empty()) { + return; + } + for (const rtcp::ReportBlock& block : report_blocks) { + auto sender_it = local_senders_by_ssrc_.find(block.source_ssrc()); + if (sender_it != local_senders_by_ssrc_.end()) { + sender_it->second->handler->OnReportBlock(sender_ssrc, block); + } + } +} + +void RtcpTransceiverImpl::HandlePayloadSpecificFeedback( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now) { + switch (rtcp_packet_header.fmt()) { + case rtcp::Fir::kFeedbackMessageType: + HandleFir(rtcp_packet_header); + break; + case rtcp::Pli::kFeedbackMessageType: + HandlePli(rtcp_packet_header); + break; + case rtcp::Psfb::kAfbMessageType: + HandleRemb(rtcp_packet_header, now); + break; + } +} + +void RtcpTransceiverImpl::HandleFir( + const rtcp::CommonHeader& rtcp_packet_header) { + rtcp::Fir fir; + if (local_senders_.empty() || !fir.Parse(rtcp_packet_header)) { + return; + } + for (const rtcp::Fir::Request& r : fir.requests()) { + auto it = local_senders_by_ssrc_.find(r.ssrc); + if (it == local_senders_by_ssrc_.end()) { + continue; + } + auto [fir_it, is_new] = + it->second->last_fir.emplace(fir.sender_ssrc(), r.seq_nr); + if (is_new || fir_it->second != r.seq_nr) { + it->second->handler->OnFir(fir.sender_ssrc()); + fir_it->second = r.seq_nr; + } + } +} + +void RtcpTransceiverImpl::HandlePli( + const rtcp::CommonHeader& rtcp_packet_header) { + rtcp::Pli pli; + if (local_senders_.empty() || !pli.Parse(rtcp_packet_header)) { + return; + } + auto it = local_senders_by_ssrc_.find(pli.media_ssrc()); + if (it != local_senders_by_ssrc_.end()) { + it->second->handler->OnPli(pli.sender_ssrc()); + } +} + +void RtcpTransceiverImpl::HandleRemb( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now) { + rtcp::Remb remb; + if (config_.network_link_observer == nullptr || + !remb.Parse(rtcp_packet_header)) { + return; + } + config_.network_link_observer->OnReceiverEstimatedMaxBitrate( + now, DataRate::BitsPerSec(remb.bitrate_bps())); +} + +void RtcpTransceiverImpl::HandleRtpFeedback( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now) { + switch (rtcp_packet_header.fmt()) { + case rtcp::Nack::kFeedbackMessageType: + HandleNack(rtcp_packet_header); + break; + case rtcp::TransportFeedback::kFeedbackMessageType: + HandleTransportFeedback(rtcp_packet_header, now); + break; + } +} + +void RtcpTransceiverImpl::HandleNack( + const rtcp::CommonHeader& rtcp_packet_header) { + rtcp::Nack nack; + if (local_senders_.empty() || !nack.Parse(rtcp_packet_header)) { + return; + } + auto it = local_senders_by_ssrc_.find(nack.media_ssrc()); + if (it != local_senders_by_ssrc_.end()) { + it->second->handler->OnNack(nack.sender_ssrc(), nack.packet_ids()); + } +} + +void RtcpTransceiverImpl::HandleTransportFeedback( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now) { + RTC_DCHECK_EQ(rtcp_packet_header.fmt(), + rtcp::TransportFeedback::kFeedbackMessageType); + if (config_.network_link_observer == nullptr) { + return; + } + rtcp::TransportFeedback feedback; + if (feedback.Parse(rtcp_packet_header)) { + config_.network_link_observer->OnTransportFeedback(now, feedback); + } +} + +void RtcpTransceiverImpl::HandleExtendedReports( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now) { + rtcp::ExtendedReports extended_reports; + if (!extended_reports.Parse(rtcp_packet_header)) + return; + + if (config_.reply_to_non_sender_rtt_measurement && extended_reports.rrtr()) { + RrtrTimes& rrtr = received_rrtrs_[extended_reports.sender_ssrc()]; + rrtr.received_remote_mid_ntp_time = + CompactNtp(extended_reports.rrtr()->ntp()); + rrtr.local_receive_mid_ntp_time = + CompactNtp(config_.clock->ConvertTimestampToNtpTime(now)); + } + + if (extended_reports.dlrr()) + HandleDlrr(extended_reports.dlrr(), now); + + if (extended_reports.target_bitrate()) + HandleTargetBitrate(*extended_reports.target_bitrate(), + extended_reports.sender_ssrc()); +} + +void RtcpTransceiverImpl::HandleDlrr(const rtcp::Dlrr& dlrr, Timestamp now) { + if (!config_.non_sender_rtt_measurement || + config_.network_link_observer == nullptr) { + return; + } + + // Delay and last_rr are transferred using 32bit compact ntp resolution. + // Convert packet arrival time to same format through 64bit ntp format. + uint32_t receive_time_ntp = + CompactNtp(config_.clock->ConvertTimestampToNtpTime(now)); + for (const rtcp::ReceiveTimeInfo& rti : dlrr.sub_blocks()) { + if (rti.ssrc != config_.feedback_ssrc) + continue; + uint32_t rtt_ntp = receive_time_ntp - rti.delay_since_last_rr - rti.last_rr; + TimeDelta rtt = CompactNtpRttToTimeDelta(rtt_ntp); + config_.network_link_observer->OnRttUpdate(now, rtt); + } +} + +void RtcpTransceiverImpl::ProcessReportBlocks( + Timestamp now, + rtc::ArrayView<const rtcp::ReportBlock> report_blocks) { + RTC_DCHECK(!report_blocks.empty()); + if (config_.network_link_observer == nullptr) { + return; + } + // Round trip time calculated from different report blocks suppose to be about + // the same, as those blocks should be generated by the same remote sender. + // To avoid too many callbacks, this code accumulate multiple rtts into one. + TimeDelta rtt_sum = TimeDelta::Zero(); + size_t num_rtts = 0; + uint32_t receive_time_ntp = + CompactNtp(config_.clock->ConvertTimestampToNtpTime(now)); + for (const rtcp::ReportBlock& report_block : report_blocks) { + if (report_block.last_sr() == 0) { + continue; + } + + uint32_t rtt_ntp = receive_time_ntp - report_block.delay_since_last_sr() - + report_block.last_sr(); + rtt_sum += CompactNtpRttToTimeDelta(rtt_ntp); + ++num_rtts; + } + // For backward compatibility, do not report rtt based on report blocks to the + // `config_.rtt_observer` + if (num_rtts > 0) { + config_.network_link_observer->OnRttUpdate(now, rtt_sum / num_rtts); + } + config_.network_link_observer->OnReportBlocks(now, report_blocks); +} + +void RtcpTransceiverImpl::HandleTargetBitrate( + const rtcp::TargetBitrate& target_bitrate, + uint32_t remote_ssrc) { + auto remote_sender_it = remote_senders_.find(remote_ssrc); + if (remote_sender_it == remote_senders_.end() || + remote_sender_it->second.observers.empty()) + return; + + // Convert rtcp::TargetBitrate to VideoBitrateAllocation. + VideoBitrateAllocation bitrate_allocation; + for (const rtcp::TargetBitrate::BitrateItem& item : + target_bitrate.GetTargetBitrates()) { + if (item.spatial_layer >= kMaxSpatialLayers || + item.temporal_layer >= kMaxTemporalStreams) { + RTC_DLOG(LS_WARNING) + << config_.debug_id + << "Invalid incoming TargetBitrate with spatial layer " + << item.spatial_layer << ", temporal layer " << item.temporal_layer; + continue; + } + bitrate_allocation.SetBitrate(item.spatial_layer, item.temporal_layer, + item.target_bitrate_kbps * 1000); + } + + for (MediaReceiverRtcpObserver* observer : remote_sender_it->second.observers) + observer->OnBitrateAllocation(remote_ssrc, bitrate_allocation); +} + +void RtcpTransceiverImpl::ReschedulePeriodicCompoundPackets() { + if (!config_.schedule_periodic_compound_packets) + return; + periodic_task_handle_.Stop(); + RTC_DCHECK(ready_to_send_); + SchedulePeriodicCompoundPackets(config_.report_period); +} + +void RtcpTransceiverImpl::SchedulePeriodicCompoundPackets(TimeDelta delay) { + periodic_task_handle_ = RepeatingTaskHandle::DelayedStart( + config_.task_queue, delay, + [this] { + RTC_DCHECK(config_.schedule_periodic_compound_packets); + RTC_DCHECK(ready_to_send_); + SendPeriodicCompoundPacket(); + return config_.report_period; + }, + TaskQueueBase::DelayPrecision::kLow, config_.clock); +} + +std::vector<uint32_t> RtcpTransceiverImpl::FillReports( + Timestamp now, + ReservedBytes reserved, + PacketSender& rtcp_sender) { + // Sender/receiver reports should be first in the RTCP packet. + RTC_DCHECK(rtcp_sender.IsEmpty()); + + size_t available_bytes = config_.max_packet_size; + if (reserved.per_packet > available_bytes) { + // Because reserved.per_packet is unsigned, substracting would underflow and + // will not produce desired result. + available_bytes = 0; + } else { + available_bytes -= reserved.per_packet; + } + + const size_t sender_report_size_bytes = 28 + reserved.per_sender; + const size_t full_sender_report_size_bytes = + sender_report_size_bytes + + rtcp::SenderReport::kMaxNumberOfReportBlocks * rtcp::ReportBlock::kLength; + size_t max_full_sender_reports = + available_bytes / full_sender_report_size_bytes; + size_t max_report_blocks = + max_full_sender_reports * rtcp::SenderReport::kMaxNumberOfReportBlocks; + size_t available_bytes_for_last_sender_report = + available_bytes - max_full_sender_reports * full_sender_report_size_bytes; + if (available_bytes_for_last_sender_report >= sender_report_size_bytes) { + max_report_blocks += + (available_bytes_for_last_sender_report - sender_report_size_bytes) / + rtcp::ReportBlock::kLength; + } + + std::vector<rtcp::ReportBlock> report_blocks = + CreateReportBlocks(now, max_report_blocks); + // Previous calculation of max number of sender report made space for max + // number of report blocks per sender report, but if number of report blocks + // is low, more sender reports may fit in. + size_t max_sender_reports = + (available_bytes - report_blocks.size() * rtcp::ReportBlock::kLength) / + sender_report_size_bytes; + + auto last_handled_sender_it = local_senders_.end(); + auto report_block_it = report_blocks.begin(); + std::vector<uint32_t> sender_ssrcs; + for (auto it = local_senders_.begin(); + it != local_senders_.end() && sender_ssrcs.size() < max_sender_reports; + ++it) { + LocalSenderState& rtp_sender = *it; + RtpStreamRtcpHandler::RtpStats stats = rtp_sender.handler->SentStats(); + + if (stats.num_sent_bytes() < rtp_sender.last_num_sent_bytes) { + RTC_LOG(LS_ERROR) << "Inconsistent SR for SSRC " << rtp_sender.ssrc + << ". Number of total sent bytes decreased."; + rtp_sender.last_num_sent_bytes = 0; + } + if (stats.num_sent_bytes() == rtp_sender.last_num_sent_bytes) { + // Skip because no RTP packet was send for this SSRC since last report. + continue; + } + rtp_sender.last_num_sent_bytes = stats.num_sent_bytes(); + + last_handled_sender_it = it; + rtcp::SenderReport sender_report; + sender_report.SetSenderSsrc(rtp_sender.ssrc); + sender_report.SetPacketCount(stats.num_sent_packets()); + sender_report.SetOctetCount(stats.num_sent_bytes()); + sender_report.SetNtp(config_.clock->ConvertTimestampToNtpTime(now)); + RTC_DCHECK_GE(now, stats.last_capture_time()); + sender_report.SetRtpTimestamp( + stats.last_rtp_timestamp() + + ((now - stats.last_capture_time()) * stats.last_clock_rate()) + .seconds()); + if (report_block_it != report_blocks.end()) { + size_t num_blocks = + std::min<size_t>(rtcp::SenderReport::kMaxNumberOfReportBlocks, + report_blocks.end() - report_block_it); + std::vector<rtcp::ReportBlock> sub_blocks(report_block_it, + report_block_it + num_blocks); + sender_report.SetReportBlocks(std::move(sub_blocks)); + report_block_it += num_blocks; + } + rtcp_sender.AppendPacket(sender_report); + sender_ssrcs.push_back(rtp_sender.ssrc); + } + if (last_handled_sender_it != local_senders_.end()) { + // Rotate `local_senders_` so that the 1st unhandled sender become first in + // the list, and thus will be first to generate rtcp sender report for on + // the next call to `FillReports`. + local_senders_.splice(local_senders_.end(), local_senders_, + local_senders_.begin(), + std::next(last_handled_sender_it)); + } + + // Calculcate number of receiver reports to attach remaining report blocks to. + size_t num_receiver_reports = + DivideRoundUp(report_blocks.end() - report_block_it, + rtcp::ReceiverReport::kMaxNumberOfReportBlocks); + + // In compound mode each RTCP packet has to start with a sender or receiver + // report. + if (config_.rtcp_mode == RtcpMode::kCompound && sender_ssrcs.empty() && + num_receiver_reports == 0) { + num_receiver_reports = 1; + } + + uint32_t sender_ssrc = + sender_ssrcs.empty() ? config_.feedback_ssrc : sender_ssrcs.front(); + for (size_t i = 0; i < num_receiver_reports; ++i) { + rtcp::ReceiverReport receiver_report; + receiver_report.SetSenderSsrc(sender_ssrc); + size_t num_blocks = + std::min<size_t>(rtcp::ReceiverReport::kMaxNumberOfReportBlocks, + report_blocks.end() - report_block_it); + std::vector<rtcp::ReportBlock> sub_blocks(report_block_it, + report_block_it + num_blocks); + receiver_report.SetReportBlocks(std::move(sub_blocks)); + report_block_it += num_blocks; + rtcp_sender.AppendPacket(receiver_report); + } + // All report blocks should be attached at this point. + RTC_DCHECK_EQ(report_blocks.end() - report_block_it, 0); + return sender_ssrcs; +} + +void RtcpTransceiverImpl::CreateCompoundPacket(Timestamp now, + size_t reserved_bytes, + PacketSender& sender) { + RTC_DCHECK(sender.IsEmpty()); + ReservedBytes reserved = {.per_packet = reserved_bytes}; + absl::optional<rtcp::Sdes> sdes; + if (!config_.cname.empty()) { + sdes.emplace(); + bool added = sdes->AddCName(config_.feedback_ssrc, config_.cname); + RTC_DCHECK(added) << "Failed to add CNAME " << config_.cname + << " to RTCP SDES packet."; + reserved.per_packet += sdes->BlockLength(); + } + if (remb_.has_value()) { + reserved.per_packet += remb_->BlockLength(); + } + absl::optional<rtcp::ExtendedReports> xr_with_dlrr; + if (!received_rrtrs_.empty()) { + RTC_DCHECK(config_.reply_to_non_sender_rtt_measurement); + xr_with_dlrr.emplace(); + uint32_t now_ntp = + CompactNtp(config_.clock->ConvertTimestampToNtpTime(now)); + for (const auto& [ssrc, rrtr_info] : received_rrtrs_) { + rtcp::ReceiveTimeInfo reply; + reply.ssrc = ssrc; + reply.last_rr = rrtr_info.received_remote_mid_ntp_time; + reply.delay_since_last_rr = + now_ntp - rrtr_info.local_receive_mid_ntp_time; + xr_with_dlrr->AddDlrrItem(reply); + } + if (config_.reply_to_non_sender_rtt_mesaurments_on_all_ssrcs) { + reserved.per_sender += xr_with_dlrr->BlockLength(); + } else { + reserved.per_packet += xr_with_dlrr->BlockLength(); + } + } + if (config_.non_sender_rtt_measurement) { + // It looks like bytes for ExtendedReport header are reserved twice, but in + // practice the same RtcpTransceiver won't both produce RRTR (i.e. it is a + // receiver-only) and reply to RRTR (i.e. remote participant is a receiver + // only). If that happen, then `reserved_bytes` would be slightly larger + // than it should, which is not an issue. + + // 4 bytes for common RTCP header + 4 bytes for the ExtenedReports header. + reserved.per_packet += (4 + 4 + rtcp::Rrtr::kLength); + } + + std::vector<uint32_t> sender_ssrcs = FillReports(now, reserved, sender); + bool has_sender_report = !sender_ssrcs.empty(); + uint32_t sender_ssrc = + has_sender_report ? sender_ssrcs.front() : config_.feedback_ssrc; + + if (sdes.has_value() && !sender.IsEmpty()) { + sender.AppendPacket(*sdes); + } + if (remb_.has_value()) { + remb_->SetSenderSsrc(sender_ssrc); + sender.AppendPacket(*remb_); + } + if (!has_sender_report && config_.non_sender_rtt_measurement) { + rtcp::ExtendedReports xr_with_rrtr; + xr_with_rrtr.SetSenderSsrc(config_.feedback_ssrc); + rtcp::Rrtr rrtr; + rrtr.SetNtp(config_.clock->ConvertTimestampToNtpTime(now)); + xr_with_rrtr.SetRrtr(rrtr); + sender.AppendPacket(xr_with_rrtr); + } + if (xr_with_dlrr.has_value()) { + rtc::ArrayView<const uint32_t> ssrcs(&sender_ssrc, 1); + if (config_.reply_to_non_sender_rtt_mesaurments_on_all_ssrcs && + !sender_ssrcs.empty()) { + ssrcs = sender_ssrcs; + } + RTC_DCHECK(!ssrcs.empty()); + for (uint32_t ssrc : ssrcs) { + xr_with_dlrr->SetSenderSsrc(ssrc); + sender.AppendPacket(*xr_with_dlrr); + } + } +} + +void RtcpTransceiverImpl::SendPeriodicCompoundPacket() { + auto send_packet = [this](rtc::ArrayView<const uint8_t> packet) { + config_.outgoing_transport->SendRtcp(packet.data(), packet.size()); + }; + Timestamp now = config_.clock->CurrentTime(); + PacketSender sender(send_packet, config_.max_packet_size); + CreateCompoundPacket(now, /*reserved_bytes=*/0, sender); + sender.Send(); +} + +void RtcpTransceiverImpl::SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) { + auto send_packet = [this](rtc::ArrayView<const uint8_t> packet) { + config_.outgoing_transport->SendRtcp(packet.data(), packet.size()); + }; + PacketSender sender(send_packet, config_.max_packet_size); + + for (auto& rtcp_packet : rtcp_packets) { + rtcp_packet->SetSenderSsrc(config_.feedback_ssrc); + sender.AppendPacket(*rtcp_packet); + } + sender.Send(); +} + +void RtcpTransceiverImpl::SendImmediateFeedback( + const rtcp::RtcpPacket& rtcp_packet) { + auto send_packet = [this](rtc::ArrayView<const uint8_t> packet) { + config_.outgoing_transport->SendRtcp(packet.data(), packet.size()); + }; + PacketSender sender(send_packet, config_.max_packet_size); + // Compound mode requires every sent rtcp packet to be compound, i.e. start + // with a sender or receiver report. + if (config_.rtcp_mode == RtcpMode::kCompound) { + Timestamp now = config_.clock->CurrentTime(); + CreateCompoundPacket(now, /*reserved_bytes=*/rtcp_packet.BlockLength(), + sender); + } + + sender.AppendPacket(rtcp_packet); + sender.Send(); + + // If compound packet was sent, delay (reschedule) the periodic one. + if (config_.rtcp_mode == RtcpMode::kCompound) + ReschedulePeriodicCompoundPackets(); +} + +std::vector<rtcp::ReportBlock> RtcpTransceiverImpl::CreateReportBlocks( + Timestamp now, + size_t num_max_blocks) { + if (!config_.receive_statistics) + return {}; + std::vector<rtcp::ReportBlock> report_blocks = + config_.receive_statistics->RtcpReportBlocks(num_max_blocks); + uint32_t last_sr = 0; + uint32_t last_delay = 0; + for (rtcp::ReportBlock& report_block : report_blocks) { + auto it = remote_senders_.find(report_block.source_ssrc()); + if (it == remote_senders_.end() || + !it->second.last_received_sender_report) { + continue; + } + const SenderReportTimes& last_sender_report = + *it->second.last_received_sender_report; + last_sr = CompactNtp(last_sender_report.remote_sent_time); + last_delay = + SaturatedToCompactNtp(now - last_sender_report.local_received_time); + report_block.SetLastSr(last_sr); + report_block.SetDelayLastSr(last_delay); + } + return report_blocks; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.h new file mode 100644 index 0000000000..8a3333d45c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.h @@ -0,0 +1,170 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_IMPL_H_ +#define MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_IMPL_H_ + +#include <list> +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtcp_packet/remb.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h" +#include "modules/rtp_rtcp/source/rtcp_transceiver_config.h" +#include "rtc_base/containers/flat_map.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { +// +// Manage incoming and outgoing rtcp messages for multiple BUNDLED streams. +// +// This class is not thread-safe. +class RtcpTransceiverImpl { + public: + explicit RtcpTransceiverImpl(const RtcpTransceiverConfig& config); + RtcpTransceiverImpl(const RtcpTransceiverImpl&) = delete; + RtcpTransceiverImpl& operator=(const RtcpTransceiverImpl&) = delete; + ~RtcpTransceiverImpl(); + + void StopPeriodicTask() { periodic_task_handle_.Stop(); } + + void AddMediaReceiverRtcpObserver(uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer); + void RemoveMediaReceiverRtcpObserver(uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer); + + // Returns false on failure, e.g. when there is already an handler for the + // `local_ssrc`. + bool AddMediaSender(uint32_t local_ssrc, RtpStreamRtcpHandler* handler); + bool RemoveMediaSender(uint32_t local_ssrc); + + void SetReadyToSend(bool ready); + + void ReceivePacket(rtc::ArrayView<const uint8_t> packet, Timestamp now); + + void SendCompoundPacket(); + + void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs); + void UnsetRemb(); + // Temporary helpers to send pre-built TransportFeedback rtcp packet. + uint32_t sender_ssrc() const { return config_.feedback_ssrc; } + void SendRawPacket(rtc::ArrayView<const uint8_t> packet); + + void SendNack(uint32_t ssrc, std::vector<uint16_t> sequence_numbers); + + void SendPictureLossIndication(uint32_t ssrc); + // If new_request is true then requested sequence no. will increase for each + // requested ssrc. + void SendFullIntraRequest(rtc::ArrayView<const uint32_t> ssrcs, + bool new_request); + + // SendCombinedRtcpPacket ignores rtcp mode and does not send a compound + // message. https://tools.ietf.org/html/rfc4585#section-3.1 + void SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets); + + private: + class PacketSender; + struct RemoteSenderState; + struct LocalSenderState; + struct RrtrTimes { + // Received remote NTP timestamp in compact representation. + uint32_t received_remote_mid_ntp_time; + + // Local NTP time when the report was received in compact representation. + uint32_t local_receive_mid_ntp_time; + }; + + void HandleReceivedPacket(const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now, + std::vector<rtcp::ReportBlock>& report_blocks); + // Individual rtcp packet handlers. + void HandleBye(const rtcp::CommonHeader& rtcp_packet_header); + void HandleSenderReport(const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now, + std::vector<rtcp::ReportBlock>& report_blocks); + void HandleReceiverReport(const rtcp::CommonHeader& rtcp_packet_header, + std::vector<rtcp::ReportBlock>& report_blocks); + void CallbackOnReportBlocks( + uint32_t sender_ssrc, + rtc::ArrayView<const rtcp::ReportBlock> report_blocks); + void HandlePayloadSpecificFeedback( + const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now); + void HandleRtpFeedback(const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now); + void HandleFir(const rtcp::CommonHeader& rtcp_packet_header); + void HandlePli(const rtcp::CommonHeader& rtcp_packet_header); + void HandleRemb(const rtcp::CommonHeader& rtcp_packet_header, Timestamp now); + void HandleNack(const rtcp::CommonHeader& rtcp_packet_header); + void HandleTransportFeedback(const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now); + void HandleExtendedReports(const rtcp::CommonHeader& rtcp_packet_header, + Timestamp now); + // Extended Reports blocks handlers. + void HandleDlrr(const rtcp::Dlrr& dlrr, Timestamp now); + void HandleTargetBitrate(const rtcp::TargetBitrate& target_bitrate, + uint32_t remote_ssrc); + void ProcessReportBlocks( + Timestamp now, + rtc::ArrayView<const rtcp::ReportBlock> report_blocks); + + void ReschedulePeriodicCompoundPackets(); + void SchedulePeriodicCompoundPackets(TimeDelta delay); + // Appends RTCP sender and receiver reports to the `sender`. + // Both sender and receiver reports may have attached report blocks. + // Uses up to `config_.max_packet_size - reserved_bytes.per_packet` + // Returns list of sender ssrc in sender reports. + struct ReservedBytes { + size_t per_packet = 0; + size_t per_sender = 0; + }; + std::vector<uint32_t> FillReports(Timestamp now, + ReservedBytes reserved_bytes, + PacketSender& rtcp_sender); + + // Creates compound RTCP packet, as defined in + // https://tools.ietf.org/html/rfc5506#section-2 + void CreateCompoundPacket(Timestamp now, + size_t reserved_bytes, + PacketSender& rtcp_sender); + + // Sends RTCP packets. + void SendPeriodicCompoundPacket(); + void SendImmediateFeedback(const rtcp::RtcpPacket& rtcp_packet); + // Generate Report Blocks to be send in Sender or Receiver Reports. + std::vector<rtcp::ReportBlock> CreateReportBlocks(Timestamp now, + size_t num_max_blocks); + + const RtcpTransceiverConfig config_; + + bool ready_to_send_; + absl::optional<rtcp::Remb> remb_; + // TODO(danilchap): Remove entries from remote_senders_ that are no longer + // needed. + flat_map<uint32_t, RemoteSenderState> remote_senders_; + std::list<LocalSenderState> local_senders_; + flat_map<uint32_t, std::list<LocalSenderState>::iterator> + local_senders_by_ssrc_; + flat_map<uint32_t, RrtrTimes> received_rrtrs_; + RepeatingTaskHandle periodic_task_handle_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTCP_TRANSCEIVER_IMPL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc new file mode 100644 index 0000000000..ad9561f66b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc @@ -0,0 +1,1741 @@ +/* + * 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/rtp_rtcp/source/rtcp_transceiver_impl.h" + +#include <memory> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "api/rtp_headers.h" +#include "api/test/create_time_controller.h" +#include "api/test/time_controller.h" +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.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/compound_packet.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_transport.h" +#include "test/rtcp_packet_parser.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Ge; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SizeIs; +using ::testing::StrictMock; +using ::testing::UnorderedElementsAre; +using ::testing::WithArg; +using ::webrtc::rtcp::Bye; +using ::webrtc::rtcp::CompoundPacket; +using ::webrtc::rtcp::ReportBlock; +using ::webrtc::rtcp::SenderReport; +using ::webrtc::test::RtcpPacketParser; + +class MockReceiveStatisticsProvider : public ReceiveStatisticsProvider { + public: + MOCK_METHOD(std::vector<ReportBlock>, RtcpReportBlocks, (size_t), (override)); +}; + +class MockMediaReceiverRtcpObserver : public MediaReceiverRtcpObserver { + public: + MOCK_METHOD(void, OnSenderReport, (uint32_t, NtpTime, uint32_t), (override)); + MOCK_METHOD(void, OnBye, (uint32_t), (override)); + MOCK_METHOD(void, + OnBitrateAllocation, + (uint32_t, const VideoBitrateAllocation&), + (override)); +}; + +class MockRtpStreamRtcpHandler : public RtpStreamRtcpHandler { + public: + MockRtpStreamRtcpHandler() { + // With each next call increase number of sent packets and bytes to simulate + // active RTP sender. + ON_CALL(*this, SentStats).WillByDefault([this] { + RtpStats stats; + stats.set_num_sent_packets(++num_calls_); + stats.set_num_sent_bytes(1'000 * num_calls_); + return stats; + }); + } + + MOCK_METHOD(RtpStats, SentStats, (), (override)); + MOCK_METHOD(void, + OnNack, + (uint32_t, rtc::ArrayView<const uint16_t>), + (override)); + MOCK_METHOD(void, OnFir, (uint32_t), (override)); + MOCK_METHOD(void, OnPli, (uint32_t), (override)); + MOCK_METHOD(void, + OnReportBlock, + (uint32_t, const rtcp::ReportBlock&), + (override)); + + private: + int num_calls_ = 0; +}; + +class MockNetworkLinkRtcpObserver : public NetworkLinkRtcpObserver { + public: + MOCK_METHOD(void, + OnRttUpdate, + (Timestamp receive_time, TimeDelta rtt), + (override)); + MOCK_METHOD(void, + OnTransportFeedback, + (Timestamp receive_time, const rtcp::TransportFeedback& feedback), + (override)); + MOCK_METHOD(void, + OnReceiverEstimatedMaxBitrate, + (Timestamp receive_time, DataRate bitrate), + (override)); + MOCK_METHOD(void, + OnReportBlocks, + (Timestamp receive_time, + rtc::ArrayView<const rtcp::ReportBlock> report_blocks), + (override)); +}; + +constexpr TimeDelta kReportPeriod = TimeDelta::Seconds(1); +constexpr TimeDelta kAlmostForever = TimeDelta::Seconds(2); +constexpr TimeDelta kTimePrecision = TimeDelta::Millis(1); + +MATCHER_P(Near, value, "") { + return arg > value - kTimePrecision && arg < value + kTimePrecision; +} + +// Helper to wait for an rtcp packet produced on a different thread/task queue. +class FakeRtcpTransport : public webrtc::Transport { + public: + explicit FakeRtcpTransport(TimeController& time) : time_(time) {} + + bool SendRtcp(const uint8_t* data, size_t size) override { + sent_rtcp_ = true; + return true; + } + bool SendRtp(const uint8_t*, size_t, const webrtc::PacketOptions&) override { + ADD_FAILURE() << "RtcpTransciver shouldn't send rtp packets."; + return true; + } + + // Returns true when packet was received by the transport. + bool WaitPacket() { + bool got_packet = time_.Wait([this] { return sent_rtcp_; }, kAlmostForever); + // Clear the 'event' to allow waiting for multiple packets. + sent_rtcp_ = false; + return got_packet; + } + + private: + TimeController& time_; + bool sent_rtcp_ = false; +}; + +class RtcpParserTransport : public webrtc::Transport { + public: + explicit RtcpParserTransport(RtcpPacketParser* parser) : parser_(parser) {} + // Returns total number of rtcp packet received. + int num_packets() const { return num_packets_; } + + private: + bool SendRtcp(const uint8_t* data, size_t size) override { + ++num_packets_; + parser_->Parse(data, size); + return true; + } + + bool SendRtp(const uint8_t*, size_t, const webrtc::PacketOptions&) override { + ADD_FAILURE() << "RtcpTransciver shouldn't send rtp packets."; + return true; + } + + RtcpPacketParser* const parser_; + int num_packets_ = 0; +}; + +class RtcpTransceiverImplTest : public ::testing::Test { + public: + RtcpTransceiverConfig DefaultTestConfig() { + // RtcpTransceiverConfig default constructor sets default values for prod. + // Test doesn't need to support all key features: Default test config + // returns valid config with all features turned off. + RtcpTransceiverConfig config; + config.clock = time_->GetClock(); + config.outgoing_transport = &null_transport_; + config.schedule_periodic_compound_packets = false; + config.initial_report_delay = kReportPeriod / 2; + config.report_period = kReportPeriod; + return config; + } + + TimeController& time_controller() { return *time_; } + Timestamp CurrentTime() { return time_->GetClock()->CurrentTime(); } + void AdvanceTime(TimeDelta time) { time_->AdvanceTime(time); } + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue() { + return time_->GetTaskQueueFactory()->CreateTaskQueue( + "rtcp", TaskQueueFactory::Priority::NORMAL); + } + + private: + MockTransport null_transport_; + std::unique_ptr<TimeController> time_ = CreateSimulatedTimeController(); +}; + +TEST_F(RtcpTransceiverImplTest, NeedToStopPeriodicTaskToDestroyOnTaskQueue) { + FakeRtcpTransport transport(time_controller()); + auto queue = CreateTaskQueue(); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.task_queue = queue.get(); + config.schedule_periodic_compound_packets = true; + config.outgoing_transport = &transport; + auto* rtcp_transceiver = new RtcpTransceiverImpl(config); + // Wait for a periodic packet. + EXPECT_TRUE(transport.WaitPacket()); + + bool done = false; + queue->PostTask([rtcp_transceiver, &done] { + rtcp_transceiver->StopPeriodicTask(); + delete rtcp_transceiver; + done = true; + }); + ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); +} + +TEST_F(RtcpTransceiverImplTest, CanBeDestroyedRightAfterCreation) { + FakeRtcpTransport transport(time_controller()); + auto queue = CreateTaskQueue(); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.task_queue = queue.get(); + config.schedule_periodic_compound_packets = true; + config.outgoing_transport = &transport; + + bool done = false; + queue->PostTask([&] { + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.StopPeriodicTask(); + done = true; + }); + ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); +} + +TEST_F(RtcpTransceiverImplTest, CanDestroyAfterTaskQueue) { + FakeRtcpTransport transport(time_controller()); + auto queue = CreateTaskQueue(); + + RtcpTransceiverConfig config = DefaultTestConfig(); + config.task_queue = queue.get(); + config.schedule_periodic_compound_packets = true; + config.outgoing_transport = &transport; + auto* rtcp_transceiver = new RtcpTransceiverImpl(config); + // Wait for a periodic packet. + EXPECT_TRUE(transport.WaitPacket()); + + queue = nullptr; + delete rtcp_transceiver; +} + +TEST_F(RtcpTransceiverImplTest, DelaysSendingFirstCompondPacket) { + auto queue = CreateTaskQueue(); + FakeRtcpTransport transport(time_controller()); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = true; + config.outgoing_transport = &transport; + config.initial_report_delay = TimeDelta::Millis(10); + config.task_queue = queue.get(); + absl::optional<RtcpTransceiverImpl> rtcp_transceiver; + + Timestamp started = CurrentTime(); + queue->PostTask([&] { rtcp_transceiver.emplace(config); }); + EXPECT_TRUE(transport.WaitPacket()); + + EXPECT_GE(CurrentTime() - started, config.initial_report_delay); + + // Cleanup. + bool done = false; + queue->PostTask([&] { + rtcp_transceiver->StopPeriodicTask(); + rtcp_transceiver.reset(); + done = true; + }); + ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); +} + +TEST_F(RtcpTransceiverImplTest, PeriodicallySendsPackets) { + auto queue = CreateTaskQueue(); + FakeRtcpTransport transport(time_controller()); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = true; + config.outgoing_transport = &transport; + config.initial_report_delay = TimeDelta::Zero(); + config.report_period = kReportPeriod; + config.task_queue = queue.get(); + absl::optional<RtcpTransceiverImpl> rtcp_transceiver; + Timestamp time_just_before_1st_packet = Timestamp::MinusInfinity(); + queue->PostTask([&] { + // Because initial_report_delay_ms is set to 0, time_just_before_the_packet + // should be very close to the time_of_the_packet. + time_just_before_1st_packet = CurrentTime(); + rtcp_transceiver.emplace(config); + }); + + EXPECT_TRUE(transport.WaitPacket()); + EXPECT_TRUE(transport.WaitPacket()); + Timestamp time_just_after_2nd_packet = CurrentTime(); + + EXPECT_GE(time_just_after_2nd_packet - time_just_before_1st_packet, + config.report_period); + + // Cleanup. + bool done = false; + queue->PostTask([&] { + rtcp_transceiver->StopPeriodicTask(); + rtcp_transceiver.reset(); + done = true; + }); + ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); +} + +TEST_F(RtcpTransceiverImplTest, SendCompoundPacketDelaysPeriodicSendPackets) { + auto queue = CreateTaskQueue(); + FakeRtcpTransport transport(time_controller()); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = true; + config.outgoing_transport = &transport; + config.initial_report_delay = TimeDelta::Zero(); + config.report_period = kReportPeriod; + config.task_queue = queue.get(); + absl::optional<RtcpTransceiverImpl> rtcp_transceiver; + queue->PostTask([&] { rtcp_transceiver.emplace(config); }); + + // Wait for the first packet. + EXPECT_TRUE(transport.WaitPacket()); + // Send non periodic one after half period. + bool non_periodic = false; + Timestamp time_of_non_periodic_packet = Timestamp::MinusInfinity(); + queue->PostDelayedTask( + [&] { + time_of_non_periodic_packet = CurrentTime(); + rtcp_transceiver->SendCompoundPacket(); + non_periodic = true; + }, + config.report_period / 2); + // Though non-periodic packet is scheduled just in between periodic, due to + // small period and task queue flakiness it migth end-up 1ms after next + // periodic packet. To be sure duration after non-periodic packet is tested + // wait for transport after ensuring non-periodic packet was sent. + EXPECT_TRUE( + time_controller().Wait([&] { return non_periodic; }, kAlmostForever)); + EXPECT_TRUE(transport.WaitPacket()); + // Wait for next periodic packet. + EXPECT_TRUE(transport.WaitPacket()); + Timestamp time_of_last_periodic_packet = CurrentTime(); + EXPECT_GE(time_of_last_periodic_packet - time_of_non_periodic_packet, + config.report_period); + + // Cleanup. + bool done = false; + queue->PostTask([&] { + rtcp_transceiver->StopPeriodicTask(); + rtcp_transceiver.reset(); + done = true; + }); + ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); +} + +TEST_F(RtcpTransceiverImplTest, SendsNoRtcpWhenNetworkStateIsDown) { + MockTransport mock_transport; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.initial_ready_to_send = false; + config.outgoing_transport = &mock_transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + EXPECT_CALL(mock_transport, SendRtcp(_, _)).Times(0); + + const uint8_t raw[] = {1, 2, 3, 4}; + const std::vector<uint16_t> sequence_numbers = {45, 57}; + const uint32_t ssrcs[] = {123}; + rtcp_transceiver.SendCompoundPacket(); + rtcp_transceiver.SendRawPacket(raw); + rtcp_transceiver.SendNack(ssrcs[0], sequence_numbers); + rtcp_transceiver.SendPictureLossIndication(ssrcs[0]); + rtcp_transceiver.SendFullIntraRequest(ssrcs, true); +} + +TEST_F(RtcpTransceiverImplTest, SendsRtcpWhenNetworkStateIsUp) { + MockTransport mock_transport; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.initial_ready_to_send = false; + config.outgoing_transport = &mock_transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetReadyToSend(true); + + EXPECT_CALL(mock_transport, SendRtcp(_, _)).Times(5); + + const uint8_t raw[] = {1, 2, 3, 4}; + const std::vector<uint16_t> sequence_numbers = {45, 57}; + const uint32_t ssrcs[] = {123}; + rtcp_transceiver.SendCompoundPacket(); + rtcp_transceiver.SendRawPacket(raw); + rtcp_transceiver.SendNack(ssrcs[0], sequence_numbers); + rtcp_transceiver.SendPictureLossIndication(ssrcs[0]); + rtcp_transceiver.SendFullIntraRequest(ssrcs, true); +} + +TEST_F(RtcpTransceiverImplTest, SendsPeriodicRtcpWhenNetworkStateIsUp) { + auto queue = CreateTaskQueue(); + FakeRtcpTransport transport(time_controller()); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = true; + config.initial_ready_to_send = false; + config.outgoing_transport = &transport; + config.task_queue = queue.get(); + absl::optional<RtcpTransceiverImpl> rtcp_transceiver; + rtcp_transceiver.emplace(config); + + queue->PostTask([&] { rtcp_transceiver->SetReadyToSend(true); }); + + EXPECT_TRUE(transport.WaitPacket()); + + // Cleanup. + bool done = false; + queue->PostTask([&] { + rtcp_transceiver->StopPeriodicTask(); + rtcp_transceiver.reset(); + done = true; + }); + ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); +} + +TEST_F(RtcpTransceiverImplTest, SendsMinimalCompoundPacket) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.cname = "cname"; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendCompoundPacket(); + + // Minimal compound RTCP packet contains sender or receiver report and sdes + // with cname. + ASSERT_GT(rtcp_parser.receiver_report()->num_packets(), 0); + EXPECT_EQ(rtcp_parser.receiver_report()->sender_ssrc(), kSenderSsrc); + ASSERT_GT(rtcp_parser.sdes()->num_packets(), 0); + ASSERT_EQ(rtcp_parser.sdes()->chunks().size(), 1u); + EXPECT_EQ(rtcp_parser.sdes()->chunks()[0].ssrc, kSenderSsrc); + EXPECT_EQ(rtcp_parser.sdes()->chunks()[0].cname, config.cname); +} + +TEST_F(RtcpTransceiverImplTest, AvoidsEmptyPacketsInReducedMode) { + MockTransport transport; + EXPECT_CALL(transport, SendRtcp).Times(0); + NiceMock<MockReceiveStatisticsProvider> receive_statistics; + + RtcpTransceiverConfig config = DefaultTestConfig(); + config.outgoing_transport = &transport; + config.rtcp_mode = webrtc::RtcpMode::kReducedSize; + config.receive_statistics = &receive_statistics; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendCompoundPacket(); +} + +TEST_F(RtcpTransceiverImplTest, AvoidsEmptyReceiverReportsInReducedMode) { + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + NiceMock<MockReceiveStatisticsProvider> receive_statistics; + + RtcpTransceiverConfig config = DefaultTestConfig(); + config.outgoing_transport = &transport; + config.rtcp_mode = webrtc::RtcpMode::kReducedSize; + config.receive_statistics = &receive_statistics; + // Set it to produce something (RRTR) in the "periodic" rtcp packets. + config.non_sender_rtt_measurement = true; + RtcpTransceiverImpl rtcp_transceiver(config); + + // Rather than waiting for the right time to produce the periodic packet, + // trigger it manually. + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 0); + EXPECT_GT(rtcp_parser.xr()->num_packets(), 0); +} + +TEST_F(RtcpTransceiverImplTest, SendsNoRembInitially) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(transport.num_packets(), 1); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 0); +} + +TEST_F(RtcpTransceiverImplTest, SetRembIncludesRembInNextCompoundPacket) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.remb()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); + EXPECT_THAT(rtcp_parser.remb()->ssrcs(), ElementsAre(54321, 64321)); +} + +TEST_F(RtcpTransceiverImplTest, SetRembUpdatesValuesToSend) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); + EXPECT_THAT(rtcp_parser.remb()->ssrcs(), ElementsAre(54321, 64321)); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/70000, /*ssrcs=*/{67321}); + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 2); + EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 70000); + EXPECT_THAT(rtcp_parser.remb()->ssrcs(), ElementsAre(67321)); +} + +TEST_F(RtcpTransceiverImplTest, SetRembSendsImmediatelyIfSendRembOnChange) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.send_remb_on_change = true; + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{}); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.remb()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); + + // If there is no change, the packet is not sent immediately. + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{}); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/20000, /*ssrcs=*/{}); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 2); + EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 20000); +} + +TEST_F(RtcpTransceiverImplTest, + SetRembSendsImmediatelyIfSendRembOnChangeReducedSize) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.send_remb_on_change = true; + config.rtcp_mode = webrtc::RtcpMode::kReducedSize; + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{}); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.remb()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); +} + +TEST_F(RtcpTransceiverImplTest, SetRembIncludesRembInAllCompoundPackets) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); + rtcp_transceiver.SendCompoundPacket(); + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(transport.num_packets(), 2); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 2); +} + +TEST_F(RtcpTransceiverImplTest, SendsNoRembAfterUnset) { + const uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); + rtcp_transceiver.SendCompoundPacket(); + EXPECT_EQ(transport.num_packets(), 1); + ASSERT_EQ(rtcp_parser.remb()->num_packets(), 1); + + rtcp_transceiver.UnsetRemb(); + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(transport.num_packets(), 2); + EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); +} + +TEST_F(RtcpTransceiverImplTest, ReceiverReportUsesReceiveStatistics) { + const uint32_t kSenderSsrc = 12345; + const uint32_t kMediaSsrc = 54321; + MockReceiveStatisticsProvider receive_statistics; + std::vector<ReportBlock> report_blocks(1); + report_blocks[0].SetMediaSsrc(kMediaSsrc); + EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) + .WillRepeatedly(Return(report_blocks)); + + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.receive_statistics = &receive_statistics; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendCompoundPacket(); + + ASSERT_GT(rtcp_parser.receiver_report()->num_packets(), 0); + EXPECT_EQ(rtcp_parser.receiver_report()->sender_ssrc(), kSenderSsrc); + ASSERT_THAT(rtcp_parser.receiver_report()->report_blocks(), + SizeIs(report_blocks.size())); + EXPECT_EQ(rtcp_parser.receiver_report()->report_blocks()[0].source_ssrc(), + kMediaSsrc); +} + +TEST_F(RtcpTransceiverImplTest, MultipleObserversOnSameSsrc) { + const uint32_t kRemoteSsrc = 12345; + StrictMock<MockMediaReceiverRtcpObserver> observer1; + StrictMock<MockMediaReceiverRtcpObserver> observer2; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer1); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer2); + + const NtpTime kRemoteNtp(0x9876543211); + const uint32_t kRemoteRtp = 0x444555; + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc); + sr.SetNtp(kRemoteNtp); + sr.SetRtpTimestamp(kRemoteRtp); + auto raw_packet = sr.Build(); + + EXPECT_CALL(observer1, OnSenderReport(kRemoteSsrc, kRemoteNtp, kRemoteRtp)); + EXPECT_CALL(observer2, OnSenderReport(kRemoteSsrc, kRemoteNtp, kRemoteRtp)); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, DoesntCallsObserverAfterRemoved) { + const uint32_t kRemoteSsrc = 12345; + StrictMock<MockMediaReceiverRtcpObserver> observer1; + StrictMock<MockMediaReceiverRtcpObserver> observer2; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer1); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer2); + + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc); + auto raw_packet = sr.Build(); + + rtcp_transceiver.RemoveMediaReceiverRtcpObserver(kRemoteSsrc, &observer1); + + EXPECT_CALL(observer1, OnSenderReport(_, _, _)).Times(0); + EXPECT_CALL(observer2, OnSenderReport(_, _, _)); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, CallsObserverOnSenderReportBySenderSsrc) { + const uint32_t kRemoteSsrc1 = 12345; + const uint32_t kRemoteSsrc2 = 22345; + StrictMock<MockMediaReceiverRtcpObserver> observer1; + StrictMock<MockMediaReceiverRtcpObserver> observer2; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc1, &observer1); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc2, &observer2); + + const NtpTime kRemoteNtp(0x9876543211); + const uint32_t kRemoteRtp = 0x444555; + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc1); + sr.SetNtp(kRemoteNtp); + sr.SetRtpTimestamp(kRemoteRtp); + auto raw_packet = sr.Build(); + + EXPECT_CALL(observer1, OnSenderReport(kRemoteSsrc1, kRemoteNtp, kRemoteRtp)); + EXPECT_CALL(observer2, OnSenderReport).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, CallsObserverOnByeBySenderSsrc) { + const uint32_t kRemoteSsrc1 = 12345; + const uint32_t kRemoteSsrc2 = 22345; + StrictMock<MockMediaReceiverRtcpObserver> observer1; + StrictMock<MockMediaReceiverRtcpObserver> observer2; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc1, &observer1); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc2, &observer2); + + Bye bye; + bye.SetSenderSsrc(kRemoteSsrc1); + auto raw_packet = bye.Build(); + + EXPECT_CALL(observer1, OnBye(kRemoteSsrc1)); + EXPECT_CALL(observer2, OnBye(_)).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, CallsObserverOnTargetBitrateBySenderSsrc) { + const uint32_t kRemoteSsrc1 = 12345; + const uint32_t kRemoteSsrc2 = 22345; + StrictMock<MockMediaReceiverRtcpObserver> observer1; + StrictMock<MockMediaReceiverRtcpObserver> observer2; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc1, &observer1); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc2, &observer2); + + webrtc::rtcp::TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, /*target_bitrate_kbps=*/10); + target_bitrate.AddTargetBitrate(0, 1, /*target_bitrate_kbps=*/20); + target_bitrate.AddTargetBitrate(1, 0, /*target_bitrate_kbps=*/40); + target_bitrate.AddTargetBitrate(1, 1, /*target_bitrate_kbps=*/80); + webrtc::rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kRemoteSsrc1); + xr.SetTargetBitrate(target_bitrate); + auto raw_packet = xr.Build(); + + VideoBitrateAllocation bitrate_allocation; + bitrate_allocation.SetBitrate(0, 0, /*bitrate_bps=*/10000); + bitrate_allocation.SetBitrate(0, 1, /*bitrate_bps=*/20000); + bitrate_allocation.SetBitrate(1, 0, /*bitrate_bps=*/40000); + bitrate_allocation.SetBitrate(1, 1, /*bitrate_bps=*/80000); + EXPECT_CALL(observer1, OnBitrateAllocation(kRemoteSsrc1, bitrate_allocation)); + EXPECT_CALL(observer2, OnBitrateAllocation(_, _)).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, SkipsIncorrectTargetBitrateEntries) { + const uint32_t kRemoteSsrc = 12345; + MockMediaReceiverRtcpObserver observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer); + + webrtc::rtcp::TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, /*target_bitrate_kbps=*/10); + target_bitrate.AddTargetBitrate(0, webrtc::kMaxTemporalStreams, 20); + target_bitrate.AddTargetBitrate(webrtc::kMaxSpatialLayers, 0, 40); + + webrtc::rtcp::ExtendedReports xr; + xr.SetTargetBitrate(target_bitrate); + xr.SetSenderSsrc(kRemoteSsrc); + auto raw_packet = xr.Build(); + + VideoBitrateAllocation expected_allocation; + expected_allocation.SetBitrate(0, 0, /*bitrate_bps=*/10000); + EXPECT_CALL(observer, OnBitrateAllocation(kRemoteSsrc, expected_allocation)); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, CallsObserverOnByeBehindSenderReport) { + const uint32_t kRemoteSsrc = 12345; + MockMediaReceiverRtcpObserver observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer); + + CompoundPacket compound; + auto sr = std::make_unique<SenderReport>(); + sr->SetSenderSsrc(kRemoteSsrc); + compound.Append(std::move(sr)); + auto bye = std::make_unique<Bye>(); + bye->SetSenderSsrc(kRemoteSsrc); + compound.Append(std::move(bye)); + auto raw_packet = compound.Build(); + + EXPECT_CALL(observer, OnBye(kRemoteSsrc)); + EXPECT_CALL(observer, OnSenderReport(kRemoteSsrc, _, _)); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, CallsObserverOnByeBehindUnknownRtcpPacket) { + const uint32_t kRemoteSsrc = 12345; + MockMediaReceiverRtcpObserver observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer); + + CompoundPacket compound; + // Use Application-Defined rtcp packet as unknown. + auto app = std::make_unique<webrtc::rtcp::App>(); + compound.Append(std::move(app)); + auto bye = std::make_unique<Bye>(); + bye->SetSenderSsrc(kRemoteSsrc); + compound.Append(std::move(bye)); + auto raw_packet = compound.Build(); + + EXPECT_CALL(observer, OnBye(kRemoteSsrc)); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); +} + +TEST_F(RtcpTransceiverImplTest, + WhenSendsReceiverReportSetsLastSenderReportTimestampPerRemoteSsrc) { + const uint32_t kRemoteSsrc1 = 4321; + const uint32_t kRemoteSsrc2 = 5321; + std::vector<ReportBlock> statistics_report_blocks(2); + statistics_report_blocks[0].SetMediaSsrc(kRemoteSsrc1); + statistics_report_blocks[1].SetMediaSsrc(kRemoteSsrc2); + MockReceiveStatisticsProvider receive_statistics; + EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) + .WillOnce(Return(statistics_report_blocks)); + + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.receive_statistics = &receive_statistics; + RtcpTransceiverImpl rtcp_transceiver(config); + + const NtpTime kRemoteNtp(0x9876543211); + // Receive SenderReport for RemoteSsrc1, but no report for RemoteSsrc2. + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc1); + sr.SetNtp(kRemoteNtp); + auto raw_packet = sr.Build(); + rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); + + // Trigger sending ReceiverReport. + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 0); + const auto& report_blocks = rtcp_parser.receiver_report()->report_blocks(); + ASSERT_EQ(report_blocks.size(), 2u); + // RtcpTransceiverImpl doesn't guarantee order of the report blocks + // match result of ReceiveStatisticsProvider::RtcpReportBlocks callback, + // but for simplicity of the test asume it is the same. + ASSERT_EQ(report_blocks[0].source_ssrc(), kRemoteSsrc1); + EXPECT_EQ(report_blocks[0].last_sr(), CompactNtp(kRemoteNtp)); + + ASSERT_EQ(report_blocks[1].source_ssrc(), kRemoteSsrc2); + // No matching Sender Report for kRemoteSsrc2, LastSR fields has to be 0. + EXPECT_EQ(report_blocks[1].last_sr(), 0u); +} + +TEST_F(RtcpTransceiverImplTest, + WhenSendsReceiverReportCalculatesDelaySinceLastSenderReport) { + const uint32_t kRemoteSsrc1 = 4321; + const uint32_t kRemoteSsrc2 = 5321; + + std::vector<ReportBlock> statistics_report_blocks(2); + statistics_report_blocks[0].SetMediaSsrc(kRemoteSsrc1); + statistics_report_blocks[1].SetMediaSsrc(kRemoteSsrc2); + MockReceiveStatisticsProvider receive_statistics; + EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) + .WillOnce(Return(statistics_report_blocks)); + + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.receive_statistics = &receive_statistics; + RtcpTransceiverImpl rtcp_transceiver(config); + + auto receive_sender_report = [&](uint32_t remote_ssrc) { + SenderReport sr; + sr.SetSenderSsrc(remote_ssrc); + rtcp_transceiver.ReceivePacket(sr.Build(), CurrentTime()); + }; + + receive_sender_report(kRemoteSsrc1); + time_controller().AdvanceTime(TimeDelta::Millis(100)); + + receive_sender_report(kRemoteSsrc2); + time_controller().AdvanceTime(TimeDelta::Millis(100)); + + // Trigger ReceiverReport back. + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 0); + const auto& report_blocks = rtcp_parser.receiver_report()->report_blocks(); + ASSERT_EQ(report_blocks.size(), 2u); + // RtcpTransceiverImpl doesn't guarantee order of the report blocks + // match result of ReceiveStatisticsProvider::RtcpReportBlocks callback, + // but for simplicity of the test asume it is the same. + ASSERT_EQ(report_blocks[0].source_ssrc(), kRemoteSsrc1); + EXPECT_THAT(CompactNtpRttToTimeDelta(report_blocks[0].delay_since_last_sr()), + Near(TimeDelta::Millis(200))); + + ASSERT_EQ(report_blocks[1].source_ssrc(), kRemoteSsrc2); + EXPECT_THAT(CompactNtpRttToTimeDelta(report_blocks[1].delay_since_last_sr()), + Near(TimeDelta::Millis(100))); +} + +TEST_F(RtcpTransceiverImplTest, MaySendMultipleReceiverReportInSinglePacket) { + std::vector<ReportBlock> statistics_report_blocks(40); + MockReceiveStatisticsProvider receive_statistics; + EXPECT_CALL(receive_statistics, RtcpReportBlocks(/*max_blocks=*/Ge(40u))) + .WillOnce(Return(statistics_report_blocks)); + + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.receive_statistics = &receive_statistics; + RtcpTransceiverImpl rtcp_transceiver(config); + + // Trigger ReceiverReports. + rtcp_transceiver.SendCompoundPacket(); + + // Expect a single RTCP packet with multiple receiver reports in it. + EXPECT_EQ(transport.num_packets(), 1); + // Receiver report may contain up to 31 report blocks, thus 2 reports are + // needed to carry 40 blocks: 31 in the first, 9 in the last. + EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 2); + // RtcpParser remembers just the last receiver report, thus can't check number + // of blocks in the first receiver report. + EXPECT_THAT(rtcp_parser.receiver_report()->report_blocks(), SizeIs(9)); +} + +TEST_F(RtcpTransceiverImplTest, AttachMaxNumberOfReportBlocksToCompoundPacket) { + MockReceiveStatisticsProvider receive_statistics; + EXPECT_CALL(receive_statistics, RtcpReportBlocks) + .WillOnce([](size_t max_blocks) { + return std::vector<ReportBlock>(max_blocks); + }); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.rtcp_mode = RtcpMode::kCompound; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.receive_statistics = &receive_statistics; + RtcpTransceiverImpl rtcp_transceiver(config); + + EXPECT_EQ(transport.num_packets(), 0); + // Send some fast feedback message. Because of compound mode, report blocks + // should be attached. + rtcp_transceiver.SendPictureLossIndication(/*ssrc=*/123); + + // Expect single RTCP packet with multiple receiver reports and a PLI. + EXPECT_EQ(transport.num_packets(), 1); + EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.pli()->num_packets(), 1); +} + +TEST_F(RtcpTransceiverImplTest, SendsNack) { + const uint32_t kSenderSsrc = 1234; + const uint32_t kRemoteSsrc = 4321; + std::vector<uint16_t> kMissingSequenceNumbers = {34, 37, 38}; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendNack(kRemoteSsrc, kMissingSequenceNumbers); + + EXPECT_EQ(rtcp_parser.nack()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.nack()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.nack()->media_ssrc(), kRemoteSsrc); + EXPECT_EQ(rtcp_parser.nack()->packet_ids(), kMissingSequenceNumbers); +} + +TEST_F(RtcpTransceiverImplTest, ReceivesNack) { + static constexpr uint32_t kRemoteSsrc = 4321; + static constexpr uint32_t kMediaSsrc1 = 1234; + static constexpr uint32_t kMediaSsrc2 = 1235; + std::vector<uint16_t> kMissingSequenceNumbers = {34, 37, 38}; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_stream1; + MockRtpStreamRtcpHandler local_stream2; + EXPECT_CALL(local_stream1, + OnNack(kRemoteSsrc, ElementsAreArray(kMissingSequenceNumbers))); + EXPECT_CALL(local_stream2, OnNack).Times(0); + + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); + + rtcp::Nack nack; + nack.SetSenderSsrc(kRemoteSsrc); + nack.SetMediaSsrc(kMediaSsrc1); + nack.SetPacketIds(kMissingSequenceNumbers); + rtcp_transceiver.ReceivePacket(nack.Build(), config.clock->CurrentTime()); +} + +TEST_F(RtcpTransceiverImplTest, RequestKeyFrameWithPictureLossIndication) { + const uint32_t kSenderSsrc = 1234; + const uint32_t kRemoteSsrc = 4321; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendPictureLossIndication(kRemoteSsrc); + + EXPECT_EQ(transport.num_packets(), 1); + EXPECT_EQ(rtcp_parser.pli()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.pli()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.pli()->media_ssrc(), kRemoteSsrc); +} + +TEST_F(RtcpTransceiverImplTest, ReceivesPictureLossIndication) { + static constexpr uint32_t kRemoteSsrc = 4321; + static constexpr uint32_t kMediaSsrc1 = 1234; + static constexpr uint32_t kMediaSsrc2 = 1235; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_stream1; + MockRtpStreamRtcpHandler local_stream2; + EXPECT_CALL(local_stream1, OnPli(kRemoteSsrc)); + EXPECT_CALL(local_stream2, OnPli).Times(0); + + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); + + rtcp::Pli pli; + pli.SetSenderSsrc(kRemoteSsrc); + pli.SetMediaSsrc(kMediaSsrc1); + rtcp_transceiver.ReceivePacket(pli.Build(), config.clock->CurrentTime()); +} + +TEST_F(RtcpTransceiverImplTest, RequestKeyFrameWithFullIntraRequest) { + const uint32_t kSenderSsrc = 1234; + const uint32_t kRemoteSsrcs[] = {4321, 5321}; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendFullIntraRequest(kRemoteSsrcs, true); + + EXPECT_EQ(rtcp_parser.fir()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.fir()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kRemoteSsrcs[0]); + EXPECT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kRemoteSsrcs[1]); +} + +TEST_F(RtcpTransceiverImplTest, RequestKeyFrameWithFirIncreaseSeqNoPerSsrc) { + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + const uint32_t kBothRemoteSsrcs[] = {4321, 5321}; + const uint32_t kOneRemoteSsrc[] = {4321}; + + rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, true); + ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); + uint8_t fir_sequence_number0 = rtcp_parser.fir()->requests()[0].seq_nr; + ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); + uint8_t fir_sequence_number1 = rtcp_parser.fir()->requests()[1].seq_nr; + + rtcp_transceiver.SendFullIntraRequest(kOneRemoteSsrc, true); + ASSERT_EQ(rtcp_parser.fir()->requests().size(), 1u); + ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, fir_sequence_number0 + 1); + + rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, true); + ASSERT_EQ(rtcp_parser.fir()->requests().size(), 2u); + ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, fir_sequence_number0 + 2); + ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); + EXPECT_EQ(rtcp_parser.fir()->requests()[1].seq_nr, fir_sequence_number1 + 1); +} + +TEST_F(RtcpTransceiverImplTest, SendFirDoesNotIncreaseSeqNoIfOldRequest) { + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + const uint32_t kBothRemoteSsrcs[] = {4321, 5321}; + + rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, true); + ASSERT_EQ(rtcp_parser.fir()->requests().size(), 2u); + ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); + uint8_t fir_sequence_number0 = rtcp_parser.fir()->requests()[0].seq_nr; + ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); + uint8_t fir_sequence_number1 = rtcp_parser.fir()->requests()[1].seq_nr; + + rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, false); + ASSERT_EQ(rtcp_parser.fir()->requests().size(), 2u); + ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, fir_sequence_number0); + ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); + EXPECT_EQ(rtcp_parser.fir()->requests()[1].seq_nr, fir_sequence_number1); +} + +TEST_F(RtcpTransceiverImplTest, ReceivesFir) { + static constexpr uint32_t kRemoteSsrc = 4321; + static constexpr uint32_t kMediaSsrc1 = 1234; + static constexpr uint32_t kMediaSsrc2 = 1235; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_stream1; + MockRtpStreamRtcpHandler local_stream2; + EXPECT_CALL(local_stream1, OnFir(kRemoteSsrc)); + EXPECT_CALL(local_stream2, OnFir).Times(0); + + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); + + rtcp::Fir fir; + fir.SetSenderSsrc(kRemoteSsrc); + fir.AddRequestTo(kMediaSsrc1, /*seq_num=*/13); + + rtcp_transceiver.ReceivePacket(fir.Build(), config.clock->CurrentTime()); +} + +TEST_F(RtcpTransceiverImplTest, IgnoresReceivedFirWithRepeatedSequenceNumber) { + static constexpr uint32_t kRemoteSsrc = 4321; + static constexpr uint32_t kMediaSsrc1 = 1234; + static constexpr uint32_t kMediaSsrc2 = 1235; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_stream1; + MockRtpStreamRtcpHandler local_stream2; + EXPECT_CALL(local_stream1, OnFir(kRemoteSsrc)).Times(1); + EXPECT_CALL(local_stream2, OnFir(kRemoteSsrc)).Times(2); + + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); + + rtcp::Fir fir1; + fir1.SetSenderSsrc(kRemoteSsrc); + fir1.AddRequestTo(kMediaSsrc1, /*seq_num=*/132); + fir1.AddRequestTo(kMediaSsrc2, /*seq_num=*/10); + rtcp_transceiver.ReceivePacket(fir1.Build(), config.clock->CurrentTime()); + + // Repeat request for MediaSsrc1 - expect it to be ignored, + // Change FIR sequence number for MediaSsrc2 - expect a 2nd callback. + rtcp::Fir fir2; + fir2.SetSenderSsrc(kRemoteSsrc); + fir2.AddRequestTo(kMediaSsrc1, /*seq_num=*/132); + fir2.AddRequestTo(kMediaSsrc2, /*seq_num=*/13); + rtcp_transceiver.ReceivePacket(fir2.Build(), config.clock->CurrentTime()); +} + +TEST_F(RtcpTransceiverImplTest, ReceivedFirTracksSequenceNumberPerRemoteSsrc) { + static constexpr uint32_t kRemoteSsrc1 = 4321; + static constexpr uint32_t kRemoteSsrc2 = 4323; + static constexpr uint32_t kMediaSsrc = 1234; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_stream; + EXPECT_CALL(local_stream, OnFir(kRemoteSsrc1)); + EXPECT_CALL(local_stream, OnFir(kRemoteSsrc2)); + + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc, &local_stream)); + + rtcp::Fir fir1; + fir1.SetSenderSsrc(kRemoteSsrc1); + fir1.AddRequestTo(kMediaSsrc, /*seq_num=*/13); + rtcp_transceiver.ReceivePacket(fir1.Build(), config.clock->CurrentTime()); + + // Use the same FIR sequence number, but different sender SSRC. + rtcp::Fir fir2; + fir2.SetSenderSsrc(kRemoteSsrc2); + fir2.AddRequestTo(kMediaSsrc, /*seq_num=*/13); + rtcp_transceiver.ReceivePacket(fir2.Build(), config.clock->CurrentTime()); +} + +TEST_F(RtcpTransceiverImplTest, KeyFrameRequestCreatesCompoundPacket) { + const uint32_t kRemoteSsrcs[] = {4321}; + RtcpTransceiverConfig config = DefaultTestConfig(); + // Turn periodic off to ensure sent rtcp packet is explicitly requested. + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + + config.rtcp_mode = webrtc::RtcpMode::kCompound; + + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.SendFullIntraRequest(kRemoteSsrcs, true); + + // Test sent packet is compound by expecting presense of receiver report. + EXPECT_EQ(transport.num_packets(), 1); + EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 1); +} + +TEST_F(RtcpTransceiverImplTest, KeyFrameRequestCreatesReducedSizePacket) { + const uint32_t kRemoteSsrcs[] = {4321}; + RtcpTransceiverConfig config = DefaultTestConfig(); + // Turn periodic off to ensure sent rtcp packet is explicitly requested. + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + + config.rtcp_mode = webrtc::RtcpMode::kReducedSize; + + RtcpTransceiverImpl rtcp_transceiver(config); + rtcp_transceiver.SendFullIntraRequest(kRemoteSsrcs, true); + + // Test sent packet is reduced size by expecting absense of receiver report. + EXPECT_EQ(transport.num_packets(), 1); + EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 0); +} + +TEST_F(RtcpTransceiverImplTest, SendsXrRrtrWhenEnabled) { + const uint32_t kSenderSsrc = 4321; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.non_sender_rtt_measurement = true; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendCompoundPacket(); + NtpTime ntp_time_now = config.clock->CurrentNtpTime(); + + EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.xr()->sender_ssrc(), kSenderSsrc); + ASSERT_TRUE(rtcp_parser.xr()->rrtr()); + EXPECT_EQ(rtcp_parser.xr()->rrtr()->ntp(), ntp_time_now); +} + +TEST_F(RtcpTransceiverImplTest, RepliesToRrtrWhenEnabled) { + static constexpr uint32_t kSenderSsrc[] = {4321, 9876}; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.reply_to_non_sender_rtt_measurement = true; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp::ExtendedReports xr; + rtcp::Rrtr rrtr; + rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444})); + xr.SetRrtr(rrtr); + xr.SetSenderSsrc(kSenderSsrc[0]); + rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); + AdvanceTime(TimeDelta::Millis(1'500)); + + rrtr.SetNtp(NtpTime(uint64_t{0x4444'5555'6666'7777})); + xr.SetRrtr(rrtr); + xr.SetSenderSsrc(kSenderSsrc[1]); + rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); + AdvanceTime(TimeDelta::Millis(500)); + + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1); + static constexpr uint32_t kComactNtpOneSecond = 0x0001'0000; + EXPECT_THAT(rtcp_parser.xr()->dlrr().sub_blocks(), + UnorderedElementsAre( + rtcp::ReceiveTimeInfo(kSenderSsrc[0], 0x2222'3333, + /*delay=*/2 * kComactNtpOneSecond), + rtcp::ReceiveTimeInfo(kSenderSsrc[1], 0x5555'6666, + /*delay=*/kComactNtpOneSecond / 2))); +} + +TEST_F(RtcpTransceiverImplTest, CanReplyToRrtrOnceForAllLocalSsrcs) { + static constexpr uint32_t kRemoteSsrc = 4321; + static constexpr uint32_t kLocalSsrcs[] = {1234, 5678}; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.reply_to_non_sender_rtt_measurement = true; + config.reply_to_non_sender_rtt_mesaurments_on_all_ssrcs = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_sender0; + MockRtpStreamRtcpHandler local_sender1; + rtcp_transceiver.AddMediaSender(kLocalSsrcs[0], &local_sender0); + rtcp_transceiver.AddMediaSender(kLocalSsrcs[1], &local_sender1); + + rtcp::ExtendedReports xr; + rtcp::Rrtr rrtr; + rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444})); + xr.SetRrtr(rrtr); + xr.SetSenderSsrc(kRemoteSsrc); + rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); + AdvanceTime(TimeDelta::Millis(1'500)); + + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1); +} + +TEST_F(RtcpTransceiverImplTest, CanReplyToRrtrForEachLocalSsrc) { + static constexpr uint32_t kRemoteSsrc = 4321; + static constexpr uint32_t kLocalSsrc[] = {1234, 5678}; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.reply_to_non_sender_rtt_measurement = true; + config.reply_to_non_sender_rtt_mesaurments_on_all_ssrcs = true; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_sender0; + MockRtpStreamRtcpHandler local_sender1; + rtcp_transceiver.AddMediaSender(kLocalSsrc[0], &local_sender0); + rtcp_transceiver.AddMediaSender(kLocalSsrc[1], &local_sender1); + + rtcp::ExtendedReports xr; + rtcp::Rrtr rrtr; + rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444})); + xr.SetRrtr(rrtr); + xr.SetSenderSsrc(kRemoteSsrc); + rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); + AdvanceTime(TimeDelta::Millis(1'500)); + + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(rtcp_parser.xr()->num_packets(), 2); +} + +TEST_F(RtcpTransceiverImplTest, SendsNoXrRrtrWhenDisabled) { + RtcpTransceiverConfig config = DefaultTestConfig(); + config.schedule_periodic_compound_packets = false; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.non_sender_rtt_measurement = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + rtcp_transceiver.SendCompoundPacket(); + + EXPECT_EQ(transport.num_packets(), 1); + // Extended reports rtcp packet might be included for another reason, + // but it shouldn't contain rrtr block. + EXPECT_FALSE(rtcp_parser.xr()->rrtr()); +} + +TEST_F(RtcpTransceiverImplTest, PassRttFromDlrrToLinkObserver) { + const uint32_t kSenderSsrc = 4321; + MockNetworkLinkRtcpObserver link_observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.network_link_observer = &link_observer; + config.non_sender_rtt_measurement = true; + RtcpTransceiverImpl rtcp_transceiver(config); + + Timestamp send_time = Timestamp::Seconds(5678); + Timestamp receive_time = send_time + TimeDelta::Millis(110); + rtcp::ReceiveTimeInfo rti; + rti.ssrc = kSenderSsrc; + rti.last_rr = CompactNtp(config.clock->ConvertTimestampToNtpTime(send_time)); + rti.delay_since_last_rr = SaturatedToCompactNtp(TimeDelta::Millis(10)); + rtcp::ExtendedReports xr; + xr.AddDlrrItem(rti); + + EXPECT_CALL(link_observer, + OnRttUpdate(receive_time, Near(TimeDelta::Millis(100)))); + rtcp_transceiver.ReceivePacket(xr.Build(), receive_time); +} + +TEST_F(RtcpTransceiverImplTest, CalculatesRoundTripTimeFromReportBlocks) { + MockNetworkLinkRtcpObserver link_observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.network_link_observer = &link_observer; + RtcpTransceiverImpl rtcp_transceiver(config); + + TimeDelta rtt = TimeDelta::Millis(100); + Timestamp send_time = Timestamp::Seconds(5678); + Timestamp receive_time = send_time + TimeDelta::Millis(110); + rtcp::ReceiverReport rr; + rtcp::ReportBlock rb1; + rb1.SetLastSr(CompactNtp(config.clock->ConvertTimestampToNtpTime( + receive_time - rtt - TimeDelta::Millis(10)))); + rb1.SetDelayLastSr(SaturatedToCompactNtp(TimeDelta::Millis(10))); + rr.AddReportBlock(rb1); + rtcp::ReportBlock rb2; + rb2.SetLastSr(CompactNtp(config.clock->ConvertTimestampToNtpTime( + receive_time - rtt - TimeDelta::Millis(20)))); + rb2.SetDelayLastSr(SaturatedToCompactNtp(TimeDelta::Millis(20))); + rr.AddReportBlock(rb2); + + EXPECT_CALL(link_observer, OnRttUpdate(receive_time, Near(rtt))); + rtcp_transceiver.ReceivePacket(rr.Build(), receive_time); +} + +TEST_F(RtcpTransceiverImplTest, IgnoresUnknownSsrcInDlrr) { + const uint32_t kSenderSsrc = 4321; + const uint32_t kUnknownSsrc = 4322; + MockNetworkLinkRtcpObserver link_observer; + MockTransport null_transport; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kSenderSsrc; + config.schedule_periodic_compound_packets = false; + config.outgoing_transport = &null_transport; + config.non_sender_rtt_measurement = true; + config.network_link_observer = &link_observer; + RtcpTransceiverImpl rtcp_transceiver(config); + + Timestamp time = Timestamp::Micros(12345678); + webrtc::rtcp::ReceiveTimeInfo rti; + rti.ssrc = kUnknownSsrc; + rti.last_rr = CompactNtp(config.clock->ConvertTimestampToNtpTime(time)); + webrtc::rtcp::ExtendedReports xr; + xr.AddDlrrItem(rti); + auto raw_packet = xr.Build(); + + EXPECT_CALL(link_observer, OnRttUpdate).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, time + TimeDelta::Millis(100)); +} + +TEST_F(RtcpTransceiverImplTest, ParsesTransportFeedback) { + MockNetworkLinkRtcpObserver link_observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.network_link_observer = &link_observer; + Timestamp receive_time = Timestamp::Seconds(5678); + RtcpTransceiverImpl rtcp_transceiver(config); + + EXPECT_CALL(link_observer, OnTransportFeedback(receive_time, _)) + .WillOnce(WithArg<1>([](const rtcp::TransportFeedback& message) { + EXPECT_EQ(message.GetBaseSequence(), 321); + EXPECT_THAT(message.GetReceivedPackets(), SizeIs(2)); + })); + + rtcp::TransportFeedback tb; + tb.SetBase(/*base_sequence=*/321, Timestamp::Micros(15)); + tb.AddReceivedPacket(/*base_sequence=*/321, Timestamp::Micros(15)); + tb.AddReceivedPacket(/*base_sequence=*/322, Timestamp::Micros(17)); + rtcp_transceiver.ReceivePacket(tb.Build(), receive_time); +} + +TEST_F(RtcpTransceiverImplTest, ParsesRemb) { + MockNetworkLinkRtcpObserver link_observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.network_link_observer = &link_observer; + Timestamp receive_time = Timestamp::Seconds(5678); + RtcpTransceiverImpl rtcp_transceiver(config); + + EXPECT_CALL(link_observer, + OnReceiverEstimatedMaxBitrate(receive_time, + DataRate::BitsPerSec(1'234'000))); + + rtcp::Remb remb; + remb.SetBitrateBps(1'234'000); + rtcp_transceiver.ReceivePacket(remb.Build(), receive_time); +} + +TEST_F(RtcpTransceiverImplTest, + CombinesReportBlocksFromSenderAndRecieverReports) { + MockNetworkLinkRtcpObserver link_observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.network_link_observer = &link_observer; + Timestamp receive_time = Timestamp::Seconds(5678); + RtcpTransceiverImpl rtcp_transceiver(config); + + // Assemble compound packet with multiple rtcp packets in it. + rtcp::CompoundPacket packet; + auto sr = std::make_unique<rtcp::SenderReport>(); + sr->SetSenderSsrc(1234); + sr->SetReportBlocks(std::vector<ReportBlock>(31)); + packet.Append(std::move(sr)); + auto rr1 = std::make_unique<rtcp::ReceiverReport>(); + rr1->SetReportBlocks(std::vector<ReportBlock>(31)); + packet.Append(std::move(rr1)); + auto rr2 = std::make_unique<rtcp::ReceiverReport>(); + rr2->SetReportBlocks(std::vector<ReportBlock>(2)); + packet.Append(std::move(rr2)); + + EXPECT_CALL(link_observer, OnReportBlocks(receive_time, SizeIs(64))); + + rtcp_transceiver.ReceivePacket(packet.Build(), receive_time); +} + +TEST_F(RtcpTransceiverImplTest, + CallbackOnReportBlocksFromSenderAndReceiverReports) { + static constexpr uint32_t kRemoteSsrc = 5678; + // Has registered sender, report block attached to sender report. + static constexpr uint32_t kMediaSsrc1 = 1234; + // No registered sender, report block attached to receiver report. + // Such report block shouldn't prevent handling following report block. + static constexpr uint32_t kMediaSsrc2 = 1235; + // Has registered sender, no report block attached. + static constexpr uint32_t kMediaSsrc3 = 1236; + // Has registered sender, report block attached to receiver report. + static constexpr uint32_t kMediaSsrc4 = 1237; + + MockNetworkLinkRtcpObserver link_observer; + RtcpTransceiverConfig config = DefaultTestConfig(); + Timestamp receive_time = Timestamp::Seconds(5678); + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler local_stream1; + MockRtpStreamRtcpHandler local_stream3; + MockRtpStreamRtcpHandler local_stream4; + EXPECT_CALL(local_stream1, OnReportBlock(kRemoteSsrc, _)); + EXPECT_CALL(local_stream3, OnReportBlock).Times(0); + EXPECT_CALL(local_stream4, OnReportBlock(kRemoteSsrc, _)); + + ASSERT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); + ASSERT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc3, &local_stream3)); + ASSERT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc4, &local_stream4)); + + // Assemble compound packet with multiple RTCP packets in it. + rtcp::CompoundPacket packet; + auto sr = std::make_unique<rtcp::SenderReport>(); + sr->SetSenderSsrc(kRemoteSsrc); + std::vector<ReportBlock> rb(1); + rb[0].SetMediaSsrc(kMediaSsrc1); + sr->SetReportBlocks(std::move(rb)); + packet.Append(std::move(sr)); + auto rr = std::make_unique<rtcp::ReceiverReport>(); + rr->SetSenderSsrc(kRemoteSsrc); + rb = std::vector<ReportBlock>(2); + rb[0].SetMediaSsrc(kMediaSsrc2); + rb[1].SetMediaSsrc(kMediaSsrc4); + rr->SetReportBlocks(std::move(rb)); + packet.Append(std::move(rr)); + + rtcp_transceiver.ReceivePacket(packet.Build(), receive_time); +} + +TEST_F(RtcpTransceiverImplTest, FailsToRegisterTwoSendersWithTheSameSsrc) { + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + MockRtpStreamRtcpHandler sender1; + MockRtpStreamRtcpHandler sender2; + + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(/*local_ssrc=*/10001, &sender1)); + EXPECT_FALSE(rtcp_transceiver.AddMediaSender(/*local_ssrc=*/10001, &sender2)); + EXPECT_TRUE(rtcp_transceiver.AddMediaSender(/*local_ssrc=*/10002, &sender2)); + + EXPECT_TRUE(rtcp_transceiver.RemoveMediaSender(/*local_ssrc=*/10001)); + EXPECT_FALSE(rtcp_transceiver.RemoveMediaSender(/*local_ssrc=*/10001)); +} + +TEST_F(RtcpTransceiverImplTest, SendsSenderReport) { + static constexpr uint32_t kFeedbackSsrc = 123; + static constexpr uint32_t kSenderSsrc = 12345; + RtcpTransceiverConfig config = DefaultTestConfig(); + config.feedback_ssrc = kFeedbackSsrc; + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + config.schedule_periodic_compound_packets = false; + RtcpTransceiverImpl rtcp_transceiver(config); + + RtpStreamRtcpHandler::RtpStats sender_stats; + sender_stats.set_num_sent_packets(10); + sender_stats.set_num_sent_bytes(1000); + sender_stats.set_last_rtp_timestamp(0x3333); + sender_stats.set_last_capture_time(CurrentTime() - TimeDelta::Seconds(2)); + sender_stats.set_last_clock_rate(0x1000); + MockRtpStreamRtcpHandler sender; + ON_CALL(sender, SentStats).WillByDefault(Return(sender_stats)); + rtcp_transceiver.AddMediaSender(kSenderSsrc, &sender); + + rtcp_transceiver.SendCompoundPacket(); + + ASSERT_GT(rtcp_parser.sender_report()->num_packets(), 0); + EXPECT_EQ(rtcp_parser.sender_report()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.sender_report()->ntp(), + time_controller().GetClock()->CurrentNtpTime()); + EXPECT_EQ(rtcp_parser.sender_report()->rtp_timestamp(), 0x3333u + 0x2000u); + EXPECT_EQ(rtcp_parser.sender_report()->sender_packet_count(), 10u); + EXPECT_EQ(rtcp_parser.sender_report()->sender_octet_count(), 1000u); +} + +TEST_F(RtcpTransceiverImplTest, + MaySendBothSenderReportAndReceiverReportInTheSamePacket) { + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + std::vector<ReportBlock> statistics_report_blocks(40); + MockReceiveStatisticsProvider receive_statistics; + EXPECT_CALL(receive_statistics, RtcpReportBlocks(/*max_blocks=*/Ge(40u))) + .WillOnce(Return(statistics_report_blocks)); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.outgoing_transport = &transport; + config.receive_statistics = &receive_statistics; + RtcpTransceiverImpl rtcp_transceiver(config); + + MockRtpStreamRtcpHandler sender; + rtcp_transceiver.AddMediaSender(/*ssrc=*/12345, &sender); + + rtcp_transceiver.SendCompoundPacket(); + + // Expect a single RTCP packet with a sender and a receiver reports in it. + EXPECT_EQ(transport.num_packets(), 1); + ASSERT_EQ(rtcp_parser.sender_report()->num_packets(), 1); + ASSERT_EQ(rtcp_parser.receiver_report()->num_packets(), 1); + // Sender report may contain up to 31 report blocks, thus remaining 9 report + // block should be attached to the receiver report. + EXPECT_THAT(rtcp_parser.sender_report()->report_blocks(), SizeIs(31)); + EXPECT_THAT(rtcp_parser.receiver_report()->report_blocks(), SizeIs(9)); +} + +TEST_F(RtcpTransceiverImplTest, RotatesSendersWhenAllSenderReportDoNotFit) { + // Send 6 compound packet, each should contain 5 sender reports, + // each of 6 senders should be mentioned 5 times. + static constexpr int kNumSenders = 6; + static constexpr uint32_t kSenderSsrc[kNumSenders] = {10, 20, 30, 40, 50, 60}; + static constexpr int kSendersPerPacket = 5; + // RtcpPacketParser remembers only latest block for each type, but this test + // is about sending multiple sender reports in the same packet, thus need + // a more advance parser: RtcpTranceiver + RtcpTransceiverConfig receiver_config = DefaultTestConfig(); + RtcpTransceiverImpl rtcp_receiver(receiver_config); + // Main expectatation: all senders are spread equally across multiple packets. + NiceMock<MockMediaReceiverRtcpObserver> receiver[kNumSenders]; + for (int i = 0; i < kNumSenders; ++i) { + SCOPED_TRACE(i); + EXPECT_CALL(receiver[i], OnSenderReport(kSenderSsrc[i], _, _)) + .Times(kSendersPerPacket); + rtcp_receiver.AddMediaReceiverRtcpObserver(kSenderSsrc[i], &receiver[i]); + } + + MockTransport transport; + EXPECT_CALL(transport, SendRtcp) + .Times(kNumSenders) + .WillRepeatedly([&](const uint8_t* data, size_t size) { + rtcp_receiver.ReceivePacket(rtc::MakeArrayView(data, size), + CurrentTime()); + return true; + }); + RtcpTransceiverConfig config = DefaultTestConfig(); + config.outgoing_transport = &transport; + // Limit packet to have space just for kSendersPerPacket sender reports. + // Sender report without report blocks require 28 bytes. + config.max_packet_size = kSendersPerPacket * 28; + RtcpTransceiverImpl rtcp_transceiver(config); + NiceMock<MockRtpStreamRtcpHandler> sender[kNumSenders]; + for (int i = 0; i < kNumSenders; ++i) { + rtcp_transceiver.AddMediaSender(kSenderSsrc[i], &sender[i]); + } + + for (int i = 1; i <= kNumSenders; ++i) { + SCOPED_TRACE(i); + rtcp_transceiver.SendCompoundPacket(); + } +} + +TEST_F(RtcpTransceiverImplTest, SkipsSenderReportForInactiveSender) { + static constexpr uint32_t kSenderSsrc[] = {12345, 23456}; + RtcpTransceiverConfig config = DefaultTestConfig(); + RtcpPacketParser rtcp_parser; + RtcpParserTransport transport(&rtcp_parser); + config.outgoing_transport = &transport; + RtcpTransceiverImpl rtcp_transceiver(config); + + RtpStreamRtcpHandler::RtpStats sender_stats[2]; + NiceMock<MockRtpStreamRtcpHandler> sender[2]; + ON_CALL(sender[0], SentStats).WillByDefault([&] { return sender_stats[0]; }); + ON_CALL(sender[1], SentStats).WillByDefault([&] { return sender_stats[1]; }); + rtcp_transceiver.AddMediaSender(kSenderSsrc[0], &sender[0]); + rtcp_transceiver.AddMediaSender(kSenderSsrc[1], &sender[1]); + + // Start with both senders beeing active. + sender_stats[0].set_num_sent_packets(10); + sender_stats[0].set_num_sent_bytes(1'000); + sender_stats[1].set_num_sent_packets(5); + sender_stats[1].set_num_sent_bytes(2'000); + rtcp_transceiver.SendCompoundPacket(); + EXPECT_EQ(transport.num_packets(), 1); + EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 2); + + // Keep 1st sender active, but make 2nd second look inactive by returning the + // same RtpStats. + sender_stats[0].set_num_sent_packets(15); + sender_stats[0].set_num_sent_bytes(2'000); + rtcp_transceiver.SendCompoundPacket(); + EXPECT_EQ(transport.num_packets(), 2); + EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 3); + EXPECT_EQ(rtcp_parser.sender_report()->sender_ssrc(), kSenderSsrc[0]); + + // Swap active sender. + sender_stats[1].set_num_sent_packets(20); + sender_stats[1].set_num_sent_bytes(3'000); + rtcp_transceiver.SendCompoundPacket(); + EXPECT_EQ(transport.num_packets(), 3); + EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 4); + EXPECT_EQ(rtcp_parser.sender_report()->sender_ssrc(), kSenderSsrc[1]); + + // Activate both senders again. + sender_stats[0].set_num_sent_packets(20); + sender_stats[0].set_num_sent_bytes(3'000); + sender_stats[1].set_num_sent_packets(25); + sender_stats[1].set_num_sent_bytes(3'500); + rtcp_transceiver.SendCompoundPacket(); + EXPECT_EQ(transport.num_packets(), 4); + EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 6); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_unittest.cc new file mode 100644 index 0000000000..611ba9d75d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_unittest.cc @@ -0,0 +1,366 @@ +/* + * 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/rtp_rtcp/source/rtcp_transceiver.h" + +#include <memory> +#include <utility> + +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h" +#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" +#include "rtc_base/event.h" +#include "rtc_base/task_queue_for_test.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_transport.h" +#include "test/rtcp_packet_parser.h" + +namespace { + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::IsNull; +using ::testing::NiceMock; +using ::webrtc::MockTransport; +using ::webrtc::RtcpTransceiver; +using ::webrtc::RtcpTransceiverConfig; +using ::webrtc::SimulatedClock; +using ::webrtc::TaskQueueForTest; +using ::webrtc::Timestamp; +using ::webrtc::rtcp::RemoteEstimate; +using ::webrtc::rtcp::RtcpPacket; +using ::webrtc::rtcp::TransportFeedback; +using ::webrtc::test::RtcpPacketParser; + +class MockMediaReceiverRtcpObserver : public webrtc::MediaReceiverRtcpObserver { + public: + MOCK_METHOD(void, + OnSenderReport, + (uint32_t, webrtc::NtpTime, uint32_t), + (override)); +}; + +constexpr webrtc::TimeDelta kTimeout = webrtc::TimeDelta::Seconds(1); + +void WaitPostedTasks(TaskQueueForTest* queue) { + rtc::Event done; + queue->PostTask([&done] { done.Set(); }); + ASSERT_TRUE(done.Wait(kTimeout)); +} + +TEST(RtcpTransceiverTest, SendsRtcpOnTaskQueueWhenCreatedOffTaskQueue) { + SimulatedClock clock(0); + MockTransport outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + EXPECT_CALL(outgoing_transport, SendRtcp(_, _)) + .WillRepeatedly(InvokeWithoutArgs([&] { + EXPECT_TRUE(queue.IsCurrent()); + return true; + })); + + RtcpTransceiver rtcp_transceiver(config); + rtcp_transceiver.SendCompoundPacket(); + WaitPostedTasks(&queue); +} + +TEST(RtcpTransceiverTest, SendsRtcpOnTaskQueueWhenCreatedOnTaskQueue) { + SimulatedClock clock(0); + MockTransport outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + EXPECT_CALL(outgoing_transport, SendRtcp(_, _)) + .WillRepeatedly(InvokeWithoutArgs([&] { + EXPECT_TRUE(queue.IsCurrent()); + return true; + })); + + std::unique_ptr<RtcpTransceiver> rtcp_transceiver; + queue.PostTask([&] { + rtcp_transceiver = std::make_unique<RtcpTransceiver>(config); + rtcp_transceiver->SendCompoundPacket(); + }); + WaitPostedTasks(&queue); +} + +TEST(RtcpTransceiverTest, CanBeDestroyedOnTaskQueue) { + SimulatedClock clock(0); + NiceMock<MockTransport> outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + auto rtcp_transceiver = std::make_unique<RtcpTransceiver>(config); + + queue.PostTask([&] { + // Insert a packet just before destruction to test for races. + rtcp_transceiver->SendCompoundPacket(); + rtcp_transceiver.reset(); + }); + WaitPostedTasks(&queue); +} + +TEST(RtcpTransceiverTest, CanBeDestroyedWithoutBlocking) { + SimulatedClock clock(0); + TaskQueueForTest queue("rtcp"); + NiceMock<MockTransport> outgoing_transport; + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + auto* rtcp_transceiver = new RtcpTransceiver(config); + rtcp_transceiver->SendCompoundPacket(); + + rtc::Event done; + rtc::Event heavy_task; + queue.PostTask([&] { + EXPECT_TRUE(heavy_task.Wait(kTimeout)); + done.Set(); + }); + delete rtcp_transceiver; + + heavy_task.Set(); + EXPECT_TRUE(done.Wait(kTimeout)); +} + +TEST(RtcpTransceiverTest, MaySendPacketsAfterDestructor) { // i.e. Be careful! + SimulatedClock clock(0); + NiceMock<MockTransport> outgoing_transport; // Must outlive queue below. + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + auto* rtcp_transceiver = new RtcpTransceiver(config); + + rtc::Event heavy_task; + queue.PostTask([&] { EXPECT_TRUE(heavy_task.Wait(kTimeout)); }); + rtcp_transceiver->SendCompoundPacket(); + delete rtcp_transceiver; + + EXPECT_CALL(outgoing_transport, SendRtcp); + heavy_task.Set(); + + WaitPostedTasks(&queue); +} + +// Use rtp timestamp to distinguish different incoming sender reports. +rtc::CopyOnWriteBuffer CreateSenderReport(uint32_t ssrc, uint32_t rtp_time) { + webrtc::rtcp::SenderReport sr; + sr.SetSenderSsrc(ssrc); + sr.SetRtpTimestamp(rtp_time); + rtc::Buffer buffer = sr.Build(); + // Switch to an efficient way creating CopyOnWriteBuffer from RtcpPacket when + // there is one. Until then do not worry about extra memcpy in test. + return rtc::CopyOnWriteBuffer(buffer.data(), buffer.size()); +} + +TEST(RtcpTransceiverTest, DoesntPostToRtcpObserverAfterCallToRemove) { + const uint32_t kRemoteSsrc = 1234; + SimulatedClock clock(0); + MockTransport null_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &null_transport; + config.task_queue = queue.Get(); + RtcpTransceiver rtcp_transceiver(config); + rtc::Event observer_deleted; + + auto observer = std::make_unique<MockMediaReceiverRtcpObserver>(); + EXPECT_CALL(*observer, OnSenderReport(kRemoteSsrc, _, 1)); + EXPECT_CALL(*observer, OnSenderReport(kRemoteSsrc, _, 2)).Times(0); + + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, observer.get()); + rtcp_transceiver.ReceivePacket(CreateSenderReport(kRemoteSsrc, 1)); + rtcp_transceiver.RemoveMediaReceiverRtcpObserver(kRemoteSsrc, observer.get(), + /*on_removed=*/[&] { + observer.reset(); + observer_deleted.Set(); + }); + rtcp_transceiver.ReceivePacket(CreateSenderReport(kRemoteSsrc, 2)); + + EXPECT_TRUE(observer_deleted.Wait(kTimeout)); + WaitPostedTasks(&queue); +} + +TEST(RtcpTransceiverTest, RemoveMediaReceiverRtcpObserverIsNonBlocking) { + const uint32_t kRemoteSsrc = 1234; + SimulatedClock clock(0); + MockTransport null_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &null_transport; + config.task_queue = queue.Get(); + RtcpTransceiver rtcp_transceiver(config); + auto observer = std::make_unique<MockMediaReceiverRtcpObserver>(); + rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, observer.get()); + + rtc::Event queue_blocker; + rtc::Event observer_deleted; + queue.PostTask([&] { EXPECT_TRUE(queue_blocker.Wait(kTimeout)); }); + rtcp_transceiver.RemoveMediaReceiverRtcpObserver(kRemoteSsrc, observer.get(), + /*on_removed=*/[&] { + observer.reset(); + observer_deleted.Set(); + }); + + EXPECT_THAT(observer, Not(IsNull())); + queue_blocker.Set(); + EXPECT_TRUE(observer_deleted.Wait(kTimeout)); +} + +TEST(RtcpTransceiverTest, CanCallSendCompoundPacketFromAnyThread) { + SimulatedClock clock(0); + MockTransport outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + + EXPECT_CALL(outgoing_transport, SendRtcp(_, _)) + // If test is slow, a periodic task may send an extra packet. + .Times(AtLeast(3)) + .WillRepeatedly(InvokeWithoutArgs([&] { + EXPECT_TRUE(queue.IsCurrent()); + return true; + })); + + RtcpTransceiver rtcp_transceiver(config); + + // Call from the construction thread. + rtcp_transceiver.SendCompoundPacket(); + // Call from the same queue transceiver use for processing. + queue.PostTask([&] { rtcp_transceiver.SendCompoundPacket(); }); + // Call from unrelated task queue. + TaskQueueForTest queue_send("send_packet"); + queue_send.PostTask([&] { rtcp_transceiver.SendCompoundPacket(); }); + + WaitPostedTasks(&queue_send); + WaitPostedTasks(&queue); +} + +TEST(RtcpTransceiverTest, DoesntSendPacketsAfterStopCallback) { + SimulatedClock clock(0); + NiceMock<MockTransport> outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + config.schedule_periodic_compound_packets = true; + + auto rtcp_transceiver = std::make_unique<RtcpTransceiver>(config); + rtc::Event done; + rtcp_transceiver->SendCompoundPacket(); + rtcp_transceiver->Stop([&] { + EXPECT_CALL(outgoing_transport, SendRtcp).Times(0); + done.Set(); + }); + rtcp_transceiver = nullptr; + EXPECT_TRUE(done.Wait(kTimeout)); +} + +TEST(RtcpTransceiverTest, SendsCombinedRtcpPacketOnTaskQueue) { + static constexpr uint32_t kSenderSsrc = 12345; + + SimulatedClock clock(0); + MockTransport outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.feedback_ssrc = kSenderSsrc; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + config.schedule_periodic_compound_packets = false; + RtcpTransceiver rtcp_transceiver(config); + + EXPECT_CALL(outgoing_transport, SendRtcp) + .WillOnce([&](const uint8_t* buffer, size_t size) { + EXPECT_TRUE(queue.IsCurrent()); + RtcpPacketParser rtcp_parser; + rtcp_parser.Parse(buffer, size); + EXPECT_EQ(rtcp_parser.transport_feedback()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.transport_feedback()->sender_ssrc(), kSenderSsrc); + EXPECT_EQ(rtcp_parser.app()->num_packets(), 1); + EXPECT_EQ(rtcp_parser.app()->sender_ssrc(), kSenderSsrc); + return true; + }); + + // Create minimalistic transport feedback packet. + std::vector<std::unique_ptr<RtcpPacket>> packets; + auto transport_feedback = std::make_unique<TransportFeedback>(); + transport_feedback->AddReceivedPacket(321, Timestamp::Millis(10)); + packets.push_back(std::move(transport_feedback)); + + auto remote_estimate = std::make_unique<RemoteEstimate>(); + packets.push_back(std::move(remote_estimate)); + + rtcp_transceiver.SendCombinedRtcpPacket(std::move(packets)); + WaitPostedTasks(&queue); +} + +TEST(RtcpTransceiverTest, SendFrameIntraRequestDefaultsToNewRequest) { + static constexpr uint32_t kSenderSsrc = 12345; + + SimulatedClock clock(0); + MockTransport outgoing_transport; + TaskQueueForTest queue("rtcp"); + RtcpTransceiverConfig config; + config.clock = &clock; + config.feedback_ssrc = kSenderSsrc; + config.outgoing_transport = &outgoing_transport; + config.task_queue = queue.Get(); + config.schedule_periodic_compound_packets = false; + RtcpTransceiver rtcp_transceiver(config); + + uint8_t first_seq_nr; + EXPECT_CALL(outgoing_transport, SendRtcp) + .WillOnce([&](const uint8_t* buffer, size_t size) { + EXPECT_TRUE(queue.IsCurrent()); + RtcpPacketParser rtcp_parser; + rtcp_parser.Parse(buffer, size); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kSenderSsrc); + first_seq_nr = rtcp_parser.fir()->requests()[0].seq_nr; + return true; + }) + .WillOnce([&](const uint8_t* buffer, size_t size) { + EXPECT_TRUE(queue.IsCurrent()); + RtcpPacketParser rtcp_parser; + rtcp_parser.Parse(buffer, size); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kSenderSsrc); + EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, first_seq_nr + 1); + return true; + }); + + // Send 2 FIR packets because the sequence numbers are incremented after, + // sending. One wouldn't be able to differentiate the new_request. + rtcp_transceiver.SendFullIntraRequest({kSenderSsrc}); + rtcp_transceiver.SendFullIntraRequest({kSenderSsrc}); + + WaitPostedTasks(&queue); +} + +} // namespace diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc new file mode 100644 index 0000000000..fd42b798d4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc @@ -0,0 +1,55 @@ +/* + * 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/rtp_dependency_descriptor_extension.h" + +#include <bitset> +#include <cstdint> + +#include "api/array_view.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.h" +#include "rtc_base/numerics/divide_round.h" + +namespace webrtc { + +constexpr RTPExtensionType RtpDependencyDescriptorExtension::kId; +constexpr std::bitset<32> RtpDependencyDescriptorExtension::kAllChainsAreActive; + +bool RtpDependencyDescriptorExtension::Parse( + rtc::ArrayView<const uint8_t> data, + const FrameDependencyStructure* structure, + DependencyDescriptor* descriptor) { + RtpDependencyDescriptorReader reader(data, structure, descriptor); + return reader.ParseSuccessful(); +} + +size_t RtpDependencyDescriptorExtension::ValueSize( + const FrameDependencyStructure& structure, + std::bitset<32> active_chains, + const DependencyDescriptor& descriptor) { + RtpDependencyDescriptorWriter writer(/*data=*/{}, structure, active_chains, + descriptor); + return DivideRoundUp(writer.ValueSizeBits(), 8); +} + +bool RtpDependencyDescriptorExtension::Write( + rtc::ArrayView<uint8_t> data, + const FrameDependencyStructure& structure, + std::bitset<32> active_chains, + const DependencyDescriptor& descriptor) { + RtpDependencyDescriptorWriter writer(data, structure, active_chains, + descriptor); + return writer.Write(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h new file mode 100644 index 0000000000..8d6e4b8d37 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h @@ -0,0 +1,59 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_EXTENSION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_EXTENSION_H_ + +#include <bitset> +#include <cstdint> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/rtp_parameters.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { +// Trait to read/write the dependency descriptor extension as described in +// https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension +class RtpDependencyDescriptorExtension { + public: + static constexpr RTPExtensionType kId = kRtpExtensionDependencyDescriptor; + static constexpr absl::string_view Uri() { + return RtpExtension::kDependencyDescriptorUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + const FrameDependencyStructure* structure, + DependencyDescriptor* descriptor); + + static size_t ValueSize(const FrameDependencyStructure& structure, + const DependencyDescriptor& descriptor) { + return ValueSize(structure, kAllChainsAreActive, descriptor); + } + static size_t ValueSize(const FrameDependencyStructure& structure, + std::bitset<32> active_chains, + const DependencyDescriptor& descriptor); + static bool Write(rtc::ArrayView<uint8_t> data, + const FrameDependencyStructure& structure, + const DependencyDescriptor& descriptor) { + return Write(data, structure, kAllChainsAreActive, descriptor); + } + static bool Write(rtc::ArrayView<uint8_t> data, + const FrameDependencyStructure& structure, + std::bitset<32> active_chains, + const DependencyDescriptor& descriptor); + + private: + static constexpr std::bitset<32> kAllChainsAreActive = ~uint32_t{0}; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_EXTENSION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension_unittest.cc new file mode 100644 index 0000000000..974557ce6e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension_unittest.cc @@ -0,0 +1,137 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" + +#include "api/array_view.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "common_video/generic_frame_descriptor/generic_frame_info.h" + +#include "test/gmock.h" + +namespace webrtc { +namespace { + +using ::testing::Each; + +TEST(RtpDependencyDescriptorExtensionTest, Writer3BytesForPerfectTemplate) { + uint8_t buffer[3]; + FrameDependencyStructure structure; + structure.num_decode_targets = 2; + structure.num_chains = 2; + structure.templates = { + FrameDependencyTemplate().Dtis("SR").FrameDiffs({1}).ChainDiffs({2, 2})}; + DependencyDescriptor descriptor; + descriptor.frame_dependencies = structure.templates[0]; + + EXPECT_EQ(RtpDependencyDescriptorExtension::ValueSize(structure, descriptor), + 3u); + EXPECT_TRUE( + RtpDependencyDescriptorExtension::Write(buffer, structure, descriptor)); +} + +TEST(RtpDependencyDescriptorExtensionTest, WriteZeroInUnusedBits) { + uint8_t buffer[32]; + std::memset(buffer, 0xff, sizeof(buffer)); + FrameDependencyStructure structure; + structure.num_decode_targets = 2; + structure.num_chains = 2; + structure.templates = { + FrameDependencyTemplate().Dtis("SR").FrameDiffs({1}).ChainDiffs({1, 1})}; + DependencyDescriptor descriptor; + descriptor.frame_dependencies = structure.templates[0]; + descriptor.frame_dependencies.frame_diffs = {2}; + + // To test unused bytes are zeroed, need a buffer large enough. + size_t value_size = + RtpDependencyDescriptorExtension::ValueSize(structure, descriptor); + ASSERT_LT(value_size, sizeof(buffer)); + + ASSERT_TRUE( + RtpDependencyDescriptorExtension::Write(buffer, structure, descriptor)); + + const uint8_t* unused_bytes = buffer + value_size; + size_t num_unused_bytes = buffer + sizeof(buffer) - unused_bytes; + // Check remaining bytes are zeroed. + EXPECT_THAT(rtc::MakeArrayView(unused_bytes, num_unused_bytes), Each(0)); +} + +// In practice chain diff for inactive chain will grow uboundly because no +// frames are produced for it, that shouldn't block writing the extension. +TEST(RtpDependencyDescriptorExtensionTest, + TemplateMatchingSkipsInactiveChains) { + uint8_t buffer[3]; + FrameDependencyStructure structure; + structure.num_decode_targets = 2; + structure.num_chains = 2; + structure.templates = { + FrameDependencyTemplate().Dtis("SR").ChainDiffs({2, 2})}; + DependencyDescriptor descriptor; + descriptor.frame_dependencies = structure.templates[0]; + + // Set only 1st chain as active. + std::bitset<32> active_chains = 0b01; + descriptor.frame_dependencies.chain_diffs[1] = 1000; + + // Expect perfect template match since the only difference is for an inactive + // chain. Pefect template match consumes 3 bytes. + EXPECT_EQ(RtpDependencyDescriptorExtension::ValueSize( + structure, active_chains, descriptor), + 3u); + EXPECT_TRUE(RtpDependencyDescriptorExtension::Write( + buffer, structure, active_chains, descriptor)); +} + +TEST(RtpDependencyDescriptorExtensionTest, + AcceptsInvalidChainDiffForInactiveChainWhenChainsAreCustom) { + uint8_t buffer[256]; + FrameDependencyStructure structure; + structure.num_decode_targets = 2; + structure.num_chains = 2; + structure.templates = { + FrameDependencyTemplate().Dtis("SR").ChainDiffs({2, 2})}; + DependencyDescriptor descriptor; + descriptor.frame_dependencies = structure.templates[0]; + + // Set only 1st chain as active. + std::bitset<32> active_chains = 0b01; + // Set chain_diff different to the template to make it custom. + descriptor.frame_dependencies.chain_diffs[0] = 1; + // Set chain diff for inactive chain beyound limit of 255 max chain diff. + descriptor.frame_dependencies.chain_diffs[1] = 1000; + + // Because chains are custom, should use more than base 3 bytes. + EXPECT_GT(RtpDependencyDescriptorExtension::ValueSize( + structure, active_chains, descriptor), + 3u); + EXPECT_TRUE(RtpDependencyDescriptorExtension::Write( + buffer, structure, active_chains, descriptor)); +} + +TEST(RtpDependencyDescriptorExtensionTest, FailsToWriteInvalidDescriptor) { + uint8_t buffer[256]; + FrameDependencyStructure structure; + structure.num_decode_targets = 2; + structure.num_chains = 2; + structure.templates = { + FrameDependencyTemplate().T(0).Dtis("SR").ChainDiffs({2, 2})}; + DependencyDescriptor descriptor; + descriptor.frame_dependencies = structure.templates[0]; + descriptor.frame_dependencies.temporal_id = 1; + + EXPECT_EQ( + RtpDependencyDescriptorExtension::ValueSize(structure, 0b11, descriptor), + 0u); + EXPECT_FALSE(RtpDependencyDescriptorExtension::Write(buffer, structure, 0b11, + descriptor)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.cc new file mode 100644 index 0000000000..1a56efd9b3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.cc @@ -0,0 +1,239 @@ +/* + * 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/rtp_dependency_descriptor_reader.h" + +#include <memory> +#include <utility> +#include <vector> + +#include "api/transport/rtp/dependency_descriptor.h" +#include "rtc_base/bitstream_reader.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +RtpDependencyDescriptorReader::RtpDependencyDescriptorReader( + rtc::ArrayView<const uint8_t> raw_data, + const FrameDependencyStructure* structure, + DependencyDescriptor* descriptor) + : descriptor_(descriptor), buffer_(raw_data) { + RTC_DCHECK(descriptor); + + ReadMandatoryFields(); + if (raw_data.size() > 3) + ReadExtendedFields(); + + structure_ = descriptor->attached_structure + ? descriptor->attached_structure.get() + : structure; + if (structure_ == nullptr) { + buffer_.Invalidate(); + return; + } + if (active_decode_targets_present_flag_) { + descriptor->active_decode_targets_bitmask = + buffer_.ReadBits(structure_->num_decode_targets); + } + + ReadFrameDependencyDefinition(); +} + +void RtpDependencyDescriptorReader::ReadTemplateDependencyStructure() { + descriptor_->attached_structure = + std::make_unique<FrameDependencyStructure>(); + descriptor_->attached_structure->structure_id = buffer_.ReadBits(6); + descriptor_->attached_structure->num_decode_targets = buffer_.ReadBits(5) + 1; + + ReadTemplateLayers(); + ReadTemplateDtis(); + ReadTemplateFdiffs(); + ReadTemplateChains(); + + if (buffer_.Read<bool>()) + ReadResolutions(); +} + +void RtpDependencyDescriptorReader::ReadTemplateLayers() { + enum NextLayerIdc { + kSameLayer = 0, + kNextTemporalLayer = 1, + kNextSpatialLayer = 2, + kNoMoreTemplates = 3, + }; + std::vector<FrameDependencyTemplate> templates; + + int temporal_id = 0; + int spatial_id = 0; + NextLayerIdc next_layer_idc; + do { + if (templates.size() == DependencyDescriptor::kMaxTemplates) { + buffer_.Invalidate(); + break; + } + templates.emplace_back(); + FrameDependencyTemplate& last_template = templates.back(); + last_template.temporal_id = temporal_id; + last_template.spatial_id = spatial_id; + + next_layer_idc = static_cast<NextLayerIdc>(buffer_.ReadBits(2)); + if (next_layer_idc == kNextTemporalLayer) { + temporal_id++; + if (temporal_id >= DependencyDescriptor::kMaxTemporalIds) { + buffer_.Invalidate(); + break; + } + } else if (next_layer_idc == kNextSpatialLayer) { + temporal_id = 0; + spatial_id++; + if (spatial_id >= DependencyDescriptor::kMaxSpatialIds) { + buffer_.Invalidate(); + break; + } + } + } while (next_layer_idc != kNoMoreTemplates && buffer_.Ok()); + + descriptor_->attached_structure->templates = std::move(templates); +} + +void RtpDependencyDescriptorReader::ReadTemplateDtis() { + FrameDependencyStructure* structure = descriptor_->attached_structure.get(); + for (FrameDependencyTemplate& current_template : structure->templates) { + current_template.decode_target_indications.resize( + structure->num_decode_targets); + for (int i = 0; i < structure->num_decode_targets; ++i) { + current_template.decode_target_indications[i] = + static_cast<DecodeTargetIndication>(buffer_.ReadBits(2)); + } + } +} + +void RtpDependencyDescriptorReader::ReadTemplateFdiffs() { + for (FrameDependencyTemplate& current_template : + descriptor_->attached_structure->templates) { + for (bool fdiff_follows = buffer_.Read<bool>(); fdiff_follows; + fdiff_follows = buffer_.Read<bool>()) { + uint64_t fdiff_minus_one = buffer_.ReadBits(4); + current_template.frame_diffs.push_back(fdiff_minus_one + 1); + } + } +} + +void RtpDependencyDescriptorReader::ReadTemplateChains() { + FrameDependencyStructure* structure = descriptor_->attached_structure.get(); + structure->num_chains = + buffer_.ReadNonSymmetric(structure->num_decode_targets + 1); + if (structure->num_chains == 0) + return; + for (int i = 0; i < structure->num_decode_targets; ++i) { + uint32_t protected_by_chain = + buffer_.ReadNonSymmetric(structure->num_chains); + structure->decode_target_protected_by_chain.push_back(protected_by_chain); + } + for (FrameDependencyTemplate& frame_template : structure->templates) { + for (int chain_id = 0; chain_id < structure->num_chains; ++chain_id) { + frame_template.chain_diffs.push_back(buffer_.ReadBits(4)); + } + } +} + +void RtpDependencyDescriptorReader::ReadResolutions() { + FrameDependencyStructure* structure = descriptor_->attached_structure.get(); + // The way templates are bitpacked, they are always ordered by spatial_id. + int spatial_layers = structure->templates.back().spatial_id + 1; + structure->resolutions.reserve(spatial_layers); + for (int sid = 0; sid < spatial_layers; ++sid) { + uint16_t width_minus_1 = buffer_.Read<uint16_t>(); + uint16_t height_minus_1 = buffer_.Read<uint16_t>(); + structure->resolutions.emplace_back(width_minus_1 + 1, height_minus_1 + 1); + } +} + +void RtpDependencyDescriptorReader::ReadMandatoryFields() { + descriptor_->first_packet_in_frame = buffer_.Read<bool>(); + descriptor_->last_packet_in_frame = buffer_.Read<bool>(); + frame_dependency_template_id_ = buffer_.ReadBits(6); + descriptor_->frame_number = buffer_.Read<uint16_t>(); +} + +void RtpDependencyDescriptorReader::ReadExtendedFields() { + bool template_dependency_structure_present_flag = buffer_.Read<bool>(); + active_decode_targets_present_flag_ = buffer_.Read<bool>(); + custom_dtis_flag_ = buffer_.Read<bool>(); + custom_fdiffs_flag_ = buffer_.Read<bool>(); + custom_chains_flag_ = buffer_.Read<bool>(); + if (template_dependency_structure_present_flag) { + ReadTemplateDependencyStructure(); + RTC_DCHECK(descriptor_->attached_structure); + descriptor_->active_decode_targets_bitmask = + (uint64_t{1} << descriptor_->attached_structure->num_decode_targets) - + 1; + } +} + +void RtpDependencyDescriptorReader::ReadFrameDependencyDefinition() { + size_t template_index = + (frame_dependency_template_id_ + DependencyDescriptor::kMaxTemplates - + structure_->structure_id) % + DependencyDescriptor::kMaxTemplates; + + if (template_index >= structure_->templates.size()) { + buffer_.Invalidate(); + return; + } + + // Copy all the fields from the matching template + descriptor_->frame_dependencies = structure_->templates[template_index]; + + if (custom_dtis_flag_) + ReadFrameDtis(); + if (custom_fdiffs_flag_) + ReadFrameFdiffs(); + if (custom_chains_flag_) + ReadFrameChains(); + + if (structure_->resolutions.empty()) { + descriptor_->resolution = absl::nullopt; + } else { + // Format guarantees that if there were resolutions in the last structure, + // then each spatial layer got one. + RTC_DCHECK_LE(descriptor_->frame_dependencies.spatial_id, + structure_->resolutions.size()); + descriptor_->resolution = + structure_->resolutions[descriptor_->frame_dependencies.spatial_id]; + } +} + +void RtpDependencyDescriptorReader::ReadFrameDtis() { + RTC_DCHECK_EQ( + descriptor_->frame_dependencies.decode_target_indications.size(), + structure_->num_decode_targets); + for (auto& dti : descriptor_->frame_dependencies.decode_target_indications) { + dti = static_cast<DecodeTargetIndication>(buffer_.ReadBits(2)); + } +} + +void RtpDependencyDescriptorReader::ReadFrameFdiffs() { + descriptor_->frame_dependencies.frame_diffs.clear(); + for (uint64_t next_fdiff_size = buffer_.ReadBits(2); next_fdiff_size > 0; + next_fdiff_size = buffer_.ReadBits(2)) { + uint64_t fdiff_minus_one = buffer_.ReadBits(4 * next_fdiff_size); + descriptor_->frame_dependencies.frame_diffs.push_back(fdiff_minus_one + 1); + } +} + +void RtpDependencyDescriptorReader::ReadFrameChains() { + RTC_DCHECK_EQ(descriptor_->frame_dependencies.chain_diffs.size(), + structure_->num_chains); + for (auto& chain_diff : descriptor_->frame_dependencies.chain_diffs) { + chain_diff = buffer_.Read<uint8_t>(); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.h new file mode 100644 index 0000000000..f79d3d1d07 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.h @@ -0,0 +1,69 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_READER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_READER_H_ + +#include <cstdint> +#include <memory> +#include <vector> + +#include "api/array_view.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "rtc_base/bitstream_reader.h" + +namespace webrtc { +// Deserializes DependencyDescriptor rtp header extension. +class RtpDependencyDescriptorReader { + public: + // Parses the dependency descriptor. + RtpDependencyDescriptorReader(rtc::ArrayView<const uint8_t> raw_data, + const FrameDependencyStructure* structure, + DependencyDescriptor* descriptor); + RtpDependencyDescriptorReader(const RtpDependencyDescriptorReader&) = delete; + RtpDependencyDescriptorReader& operator=( + const RtpDependencyDescriptorReader&) = delete; + + // Returns true if parse was successful. + bool ParseSuccessful() { return buffer_.Ok(); } + + private: + // Functions to read template dependency structure. + void ReadTemplateDependencyStructure(); + void ReadTemplateLayers(); + void ReadTemplateDtis(); + void ReadTemplateFdiffs(); + void ReadTemplateChains(); + void ReadResolutions(); + + // Function to read details for the current frame. + void ReadMandatoryFields(); + void ReadExtendedFields(); + void ReadFrameDependencyDefinition(); + + void ReadFrameDtis(); + void ReadFrameFdiffs(); + void ReadFrameChains(); + + // Output. + DependencyDescriptor* const descriptor_; + // Values that are needed while reading the descriptor, but can be discarded + // when reading is complete. + BitstreamReader buffer_; + int frame_dependency_template_id_ = 0; + bool active_decode_targets_present_flag_ = false; + bool custom_dtis_flag_ = false; + bool custom_fdiffs_flag_ = false; + bool custom_chains_flag_ = false; + const FrameDependencyStructure* structure_ = nullptr; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_READER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.cc new file mode 100644 index 0000000000..31df783064 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.cc @@ -0,0 +1,396 @@ +/* + * 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/rtp_dependency_descriptor_writer.h" + +#include <bitset> +#include <cstddef> +#include <cstdint> +#include <iterator> +#include <vector> + +#include "absl/algorithm/container.h" +#include "api/array_view.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "rtc_base/bit_buffer.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +enum class NextLayerIdc : uint64_t { + kSameLayer = 0, + kNextTemporal = 1, + kNewSpatial = 2, + kNoMoreLayers = 3, + kInvalid = 4 +}; + +NextLayerIdc GetNextLayerIdc(const FrameDependencyTemplate& previous, + const FrameDependencyTemplate& next) { + RTC_DCHECK_LT(next.spatial_id, DependencyDescriptor::kMaxSpatialIds); + RTC_DCHECK_LT(next.temporal_id, DependencyDescriptor::kMaxTemporalIds); + + if (next.spatial_id == previous.spatial_id && + next.temporal_id == previous.temporal_id) { + return NextLayerIdc::kSameLayer; + } else if (next.spatial_id == previous.spatial_id && + next.temporal_id == previous.temporal_id + 1) { + return NextLayerIdc::kNextTemporal; + } else if (next.spatial_id == previous.spatial_id + 1 && + next.temporal_id == 0) { + return NextLayerIdc::kNewSpatial; + } + // Everything else is unsupported. + return NextLayerIdc::kInvalid; +} + +} // namespace + +RtpDependencyDescriptorWriter::RtpDependencyDescriptorWriter( + rtc::ArrayView<uint8_t> data, + const FrameDependencyStructure& structure, + std::bitset<32> active_chains, + const DependencyDescriptor& descriptor) + : descriptor_(descriptor), + structure_(structure), + active_chains_(active_chains), + bit_writer_(data.data(), data.size()) { + FindBestTemplate(); +} + +bool RtpDependencyDescriptorWriter::Write() { + if (build_failed_) { + return false; + } + WriteMandatoryFields(); + if (HasExtendedFields()) { + WriteExtendedFields(); + WriteFrameDependencyDefinition(); + } + size_t remaining_bits = bit_writer_.RemainingBitCount(); + // Zero remaining memory to avoid leaving it uninitialized. + if (remaining_bits % 64 != 0) { + WriteBits(/*val=*/0, remaining_bits % 64); + } + for (size_t i = 0; i < remaining_bits / 64; ++i) { + WriteBits(/*val=*/0, 64); + } + return !build_failed_; +} + +int RtpDependencyDescriptorWriter::ValueSizeBits() const { + if (build_failed_) { + return 0; + } + static constexpr int kMandatoryFields = 1 + 1 + 6 + 16; + int value_size_bits = kMandatoryFields + best_template_.extra_size_bits; + if (HasExtendedFields()) { + value_size_bits += 5; + if (descriptor_.attached_structure) + value_size_bits += StructureSizeBits(); + if (ShouldWriteActiveDecodeTargetsBitmask()) + value_size_bits += structure_.num_decode_targets; + } + return value_size_bits; +} + +int RtpDependencyDescriptorWriter::StructureSizeBits() const { + // template_id offset (6 bits) and number of decode targets (5 bits) + int bits = 11; + // template layers. + bits += 2 * structure_.templates.size(); + // dtis. + bits += 2 * structure_.templates.size() * structure_.num_decode_targets; + // fdiffs. each templates uses 1 + 5 * sizeof(fdiff) bits. + bits += structure_.templates.size(); + for (const FrameDependencyTemplate& frame_template : structure_.templates) { + bits += 5 * frame_template.frame_diffs.size(); + } + bits += rtc::BitBufferWriter::SizeNonSymmetricBits( + structure_.num_chains, structure_.num_decode_targets + 1); + if (structure_.num_chains > 0) { + for (int protected_by : structure_.decode_target_protected_by_chain) { + bits += rtc::BitBufferWriter::SizeNonSymmetricBits(protected_by, + structure_.num_chains); + } + bits += 4 * structure_.templates.size() * structure_.num_chains; + } + // Resolutions. + bits += 1 + 32 * structure_.resolutions.size(); + return bits; +} + +RtpDependencyDescriptorWriter::TemplateMatch +RtpDependencyDescriptorWriter::CalculateMatch( + TemplateIterator frame_template) const { + TemplateMatch result; + result.template_position = frame_template; + result.need_custom_fdiffs = + descriptor_.frame_dependencies.frame_diffs != frame_template->frame_diffs; + result.need_custom_dtis = + descriptor_.frame_dependencies.decode_target_indications != + frame_template->decode_target_indications; + result.need_custom_chains = false; + for (int i = 0; i < structure_.num_chains; ++i) { + if (active_chains_[i] && descriptor_.frame_dependencies.chain_diffs[i] != + frame_template->chain_diffs[i]) { + result.need_custom_chains = true; + break; + } + } + + result.extra_size_bits = 0; + if (result.need_custom_fdiffs) { + result.extra_size_bits += + 2 * (1 + descriptor_.frame_dependencies.frame_diffs.size()); + for (int fdiff : descriptor_.frame_dependencies.frame_diffs) { + if (fdiff <= (1 << 4)) + result.extra_size_bits += 4; + else if (fdiff <= (1 << 8)) + result.extra_size_bits += 8; + else + result.extra_size_bits += 12; + } + } + if (result.need_custom_dtis) { + result.extra_size_bits += + 2 * descriptor_.frame_dependencies.decode_target_indications.size(); + } + if (result.need_custom_chains) + result.extra_size_bits += 8 * structure_.num_chains; + return result; +} + +void RtpDependencyDescriptorWriter::FindBestTemplate() { + const std::vector<FrameDependencyTemplate>& templates = structure_.templates; + // Find range of templates with matching spatial/temporal id. + auto same_layer = [&](const FrameDependencyTemplate& frame_template) { + return descriptor_.frame_dependencies.spatial_id == + frame_template.spatial_id && + descriptor_.frame_dependencies.temporal_id == + frame_template.temporal_id; + }; + auto first = absl::c_find_if(templates, same_layer); + if (first == templates.end()) { + build_failed_ = true; + return; + } + auto last = std::find_if_not(first, templates.end(), same_layer); + + best_template_ = CalculateMatch(first); + // Search if there any better template than the first one. + for (auto next = std::next(first); next != last; ++next) { + TemplateMatch match = CalculateMatch(next); + if (match.extra_size_bits < best_template_.extra_size_bits) + best_template_ = match; + } +} + +bool RtpDependencyDescriptorWriter::ShouldWriteActiveDecodeTargetsBitmask() + const { + if (!descriptor_.active_decode_targets_bitmask) + return false; + const uint64_t all_decode_targets_bitmask = + (uint64_t{1} << structure_.num_decode_targets) - 1; + if (descriptor_.attached_structure && + descriptor_.active_decode_targets_bitmask == all_decode_targets_bitmask) + return false; + return true; +} + +bool RtpDependencyDescriptorWriter::HasExtendedFields() const { + return best_template_.extra_size_bits > 0 || descriptor_.attached_structure || + descriptor_.active_decode_targets_bitmask; +} + +uint64_t RtpDependencyDescriptorWriter::TemplateId() const { + return (best_template_.template_position - structure_.templates.begin() + + structure_.structure_id) % + DependencyDescriptor::kMaxTemplates; +} + +void RtpDependencyDescriptorWriter::WriteBits(uint64_t val, size_t bit_count) { + if (!bit_writer_.WriteBits(val, bit_count)) + build_failed_ = true; +} + +void RtpDependencyDescriptorWriter::WriteNonSymmetric(uint32_t value, + uint32_t num_values) { + if (!bit_writer_.WriteNonSymmetric(value, num_values)) + build_failed_ = true; +} + +void RtpDependencyDescriptorWriter::WriteTemplateDependencyStructure() { + RTC_DCHECK_GE(structure_.structure_id, 0); + RTC_DCHECK_LT(structure_.structure_id, DependencyDescriptor::kMaxTemplates); + RTC_DCHECK_GT(structure_.num_decode_targets, 0); + RTC_DCHECK_LE(structure_.num_decode_targets, + DependencyDescriptor::kMaxDecodeTargets); + + WriteBits(structure_.structure_id, 6); + WriteBits(structure_.num_decode_targets - 1, 5); + WriteTemplateLayers(); + WriteTemplateDtis(); + WriteTemplateFdiffs(); + WriteTemplateChains(); + uint64_t has_resolutions = structure_.resolutions.empty() ? 0 : 1; + WriteBits(has_resolutions, 1); + if (has_resolutions) + WriteResolutions(); +} + +void RtpDependencyDescriptorWriter::WriteTemplateLayers() { + const auto& templates = structure_.templates; + RTC_DCHECK(!templates.empty()); + RTC_DCHECK_LE(templates.size(), DependencyDescriptor::kMaxTemplates); + RTC_DCHECK_EQ(templates[0].spatial_id, 0); + RTC_DCHECK_EQ(templates[0].temporal_id, 0); + + for (size_t i = 1; i < templates.size(); ++i) { + uint64_t next_layer_idc = + static_cast<uint64_t>(GetNextLayerIdc(templates[i - 1], templates[i])); + RTC_DCHECK_LE(next_layer_idc, 3); + WriteBits(next_layer_idc, 2); + } + WriteBits(static_cast<uint64_t>(NextLayerIdc::kNoMoreLayers), 2); +} + +void RtpDependencyDescriptorWriter::WriteTemplateDtis() { + for (const FrameDependencyTemplate& current_template : structure_.templates) { + RTC_DCHECK_EQ(current_template.decode_target_indications.size(), + structure_.num_decode_targets); + for (DecodeTargetIndication dti : + current_template.decode_target_indications) { + WriteBits(static_cast<uint32_t>(dti), 2); + } + } +} + +void RtpDependencyDescriptorWriter::WriteTemplateFdiffs() { + for (const FrameDependencyTemplate& current_template : structure_.templates) { + for (int fdiff : current_template.frame_diffs) { + RTC_DCHECK_GE(fdiff - 1, 0); + RTC_DCHECK_LT(fdiff - 1, 1 << 4); + WriteBits((1u << 4) | (fdiff - 1), 1 + 4); + } + // No more diffs for current template. + WriteBits(/*val=*/0, /*bit_count=*/1); + } +} + +void RtpDependencyDescriptorWriter::WriteTemplateChains() { + RTC_DCHECK_GE(structure_.num_chains, 0); + RTC_DCHECK_LE(structure_.num_chains, structure_.num_decode_targets); + + WriteNonSymmetric(structure_.num_chains, structure_.num_decode_targets + 1); + if (structure_.num_chains == 0) + return; + + RTC_DCHECK_EQ(structure_.decode_target_protected_by_chain.size(), + structure_.num_decode_targets); + for (int protected_by : structure_.decode_target_protected_by_chain) { + RTC_DCHECK_GE(protected_by, 0); + RTC_DCHECK_LT(protected_by, structure_.num_chains); + WriteNonSymmetric(protected_by, structure_.num_chains); + } + for (const auto& frame_template : structure_.templates) { + RTC_DCHECK_EQ(frame_template.chain_diffs.size(), structure_.num_chains); + for (int chain_diff : frame_template.chain_diffs) { + RTC_DCHECK_GE(chain_diff, 0); + RTC_DCHECK_LT(chain_diff, 1 << 4); + WriteBits(chain_diff, 4); + } + } +} + +void RtpDependencyDescriptorWriter::WriteResolutions() { + int max_spatial_id = structure_.templates.back().spatial_id; + RTC_DCHECK_EQ(structure_.resolutions.size(), max_spatial_id + 1); + for (const RenderResolution& resolution : structure_.resolutions) { + RTC_DCHECK_GT(resolution.Width(), 0); + RTC_DCHECK_LE(resolution.Width(), 1 << 16); + RTC_DCHECK_GT(resolution.Height(), 0); + RTC_DCHECK_LE(resolution.Height(), 1 << 16); + + WriteBits(resolution.Width() - 1, 16); + WriteBits(resolution.Height() - 1, 16); + } +} + +void RtpDependencyDescriptorWriter::WriteMandatoryFields() { + WriteBits(descriptor_.first_packet_in_frame, 1); + WriteBits(descriptor_.last_packet_in_frame, 1); + WriteBits(TemplateId(), 6); + WriteBits(descriptor_.frame_number, 16); +} + +void RtpDependencyDescriptorWriter::WriteExtendedFields() { + uint64_t template_dependency_structure_present_flag = + descriptor_.attached_structure ? 1u : 0u; + WriteBits(template_dependency_structure_present_flag, 1); + uint64_t active_decode_targets_present_flag = + ShouldWriteActiveDecodeTargetsBitmask() ? 1u : 0u; + WriteBits(active_decode_targets_present_flag, 1); + WriteBits(best_template_.need_custom_dtis, 1); + WriteBits(best_template_.need_custom_fdiffs, 1); + WriteBits(best_template_.need_custom_chains, 1); + if (template_dependency_structure_present_flag) + WriteTemplateDependencyStructure(); + if (active_decode_targets_present_flag) + WriteBits(*descriptor_.active_decode_targets_bitmask, + structure_.num_decode_targets); +} + +void RtpDependencyDescriptorWriter::WriteFrameDependencyDefinition() { + if (best_template_.need_custom_dtis) + WriteFrameDtis(); + if (best_template_.need_custom_fdiffs) + WriteFrameFdiffs(); + if (best_template_.need_custom_chains) + WriteFrameChains(); +} + +void RtpDependencyDescriptorWriter::WriteFrameDtis() { + RTC_DCHECK_EQ(descriptor_.frame_dependencies.decode_target_indications.size(), + structure_.num_decode_targets); + for (DecodeTargetIndication dti : + descriptor_.frame_dependencies.decode_target_indications) { + WriteBits(static_cast<uint32_t>(dti), 2); + } +} + +void RtpDependencyDescriptorWriter::WriteFrameFdiffs() { + for (int fdiff : descriptor_.frame_dependencies.frame_diffs) { + RTC_DCHECK_GT(fdiff, 0); + RTC_DCHECK_LE(fdiff, 1 << 12); + if (fdiff <= (1 << 4)) + WriteBits((1u << 4) | (fdiff - 1), 2 + 4); + else if (fdiff <= (1 << 8)) + WriteBits((2u << 8) | (fdiff - 1), 2 + 8); + else // fdiff <= (1 << 12) + WriteBits((3u << 12) | (fdiff - 1), 2 + 12); + } + // No more diffs. + WriteBits(/*val=*/0, /*bit_count=*/2); +} + +void RtpDependencyDescriptorWriter::WriteFrameChains() { + RTC_DCHECK_EQ(descriptor_.frame_dependencies.chain_diffs.size(), + structure_.num_chains); + for (int i = 0; i < structure_.num_chains; ++i) { + int chain_diff = + active_chains_[i] ? descriptor_.frame_dependencies.chain_diffs[i] : 0; + RTC_DCHECK_GE(chain_diff, 0); + RTC_DCHECK_LT(chain_diff, 1 << 8); + WriteBits(chain_diff, 8); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.h new file mode 100644 index 0000000000..568e0a8aab --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.h @@ -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. + */ +#ifndef MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_WRITER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_WRITER_H_ + +#include <bitset> +#include <cstddef> +#include <cstdint> +#include <vector> + +#include "api/array_view.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "rtc_base/bit_buffer.h" + +namespace webrtc { +class RtpDependencyDescriptorWriter { + public: + // Assumes `structure` and `descriptor` are valid and + // `descriptor` matches the `structure`. + RtpDependencyDescriptorWriter(rtc::ArrayView<uint8_t> data, + const FrameDependencyStructure& structure, + std::bitset<32> active_chains, + const DependencyDescriptor& descriptor); + + // Serializes DependencyDescriptor rtp header extension. + // Returns false if `data` is too small to serialize the `descriptor`. + bool Write(); + + // Returns minimum number of bits needed to serialize descriptor with respect + // to the `structure`. Returns 0 if `descriptor` can't be serialized. + int ValueSizeBits() const; + + private: + // Used both as pointer to the template and as index in the templates vector. + using TemplateIterator = std::vector<FrameDependencyTemplate>::const_iterator; + struct TemplateMatch { + TemplateIterator template_position; + bool need_custom_dtis; + bool need_custom_fdiffs; + bool need_custom_chains; + // Size in bits to store frame-specific details, i.e. + // excluding mandatory fields and template dependency structure. + int extra_size_bits; + }; + int StructureSizeBits() const; + TemplateMatch CalculateMatch(TemplateIterator frame_template) const; + void FindBestTemplate(); + bool ShouldWriteActiveDecodeTargetsBitmask() const; + bool HasExtendedFields() const; + uint64_t TemplateId() const; + + void WriteBits(uint64_t val, size_t bit_count); + void WriteNonSymmetric(uint32_t value, uint32_t num_values); + + // Functions to read template dependency structure. + void WriteTemplateDependencyStructure(); + void WriteTemplateLayers(); + void WriteTemplateDtis(); + void WriteTemplateFdiffs(); + void WriteTemplateChains(); + void WriteResolutions(); + + // Function to read details for the current frame. + void WriteMandatoryFields(); + void WriteExtendedFields(); + void WriteFrameDependencyDefinition(); + + void WriteFrameDtis(); + void WriteFrameFdiffs(); + void WriteFrameChains(); + + bool build_failed_ = false; + const DependencyDescriptor& descriptor_; + const FrameDependencyStructure& structure_; + std::bitset<32> active_chains_; + rtc::BitBufferWriter bit_writer_; + TemplateMatch best_template_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_DEPENDENCY_DESCRIPTOR_WRITER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.cc new file mode 100644 index 0000000000..f4525f0db1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.cc @@ -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. + */ + +#include "modules/rtp_rtcp/source/rtp_descriptor_authentication.h" + +#include <cstdint> +#include <vector> + +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h" +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" + +namespace webrtc { + +std::vector<uint8_t> RtpDescriptorAuthentication( + const RTPVideoHeader& rtp_video_header) { + if (!rtp_video_header.generic) { + return {}; + } + const RTPVideoHeader::GenericDescriptorInfo& descriptor = + *rtp_video_header.generic; + // Default way of creating additional data for an encrypted frame. + if (descriptor.spatial_index < 0 || descriptor.temporal_index < 0 || + descriptor.spatial_index >= + RtpGenericFrameDescriptor::kMaxSpatialLayers || + descriptor.temporal_index >= + RtpGenericFrameDescriptor::kMaxTemporalLayers || + descriptor.dependencies.size() > + RtpGenericFrameDescriptor::kMaxNumFrameDependencies) { + return {}; + } + RtpGenericFrameDescriptor frame_descriptor; + frame_descriptor.SetFirstPacketInSubFrame(true); + frame_descriptor.SetLastPacketInSubFrame(false); + frame_descriptor.SetTemporalLayer(descriptor.temporal_index); + frame_descriptor.SetSpatialLayersBitmask(1 << descriptor.spatial_index); + frame_descriptor.SetFrameId(descriptor.frame_id & 0xFFFF); + for (int64_t dependency : descriptor.dependencies) { + frame_descriptor.AddFrameDependencyDiff(descriptor.frame_id - dependency); + } + if (descriptor.dependencies.empty()) { + frame_descriptor.SetResolution(rtp_video_header.width, + rtp_video_header.height); + } + std::vector<uint8_t> result( + RtpGenericFrameDescriptorExtension00::ValueSize(frame_descriptor)); + RtpGenericFrameDescriptorExtension00::Write(result, frame_descriptor); + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.h new file mode 100644 index 0000000000..1791abecd8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.h @@ -0,0 +1,27 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_DESCRIPTOR_AUTHENTICATION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_DESCRIPTOR_AUTHENTICATION_H_ + +#include <cstdint> +#include <vector> + +#include "modules/rtp_rtcp/source/rtp_video_header.h" + +namespace webrtc { + +// Converts frame dependencies into array of bytes for authentication. +std::vector<uint8_t> RtpDescriptorAuthentication( + const RTPVideoHeader& rtp_video_header); + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_DESCRIPTOR_AUTHENTICATION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc new file mode 100644 index 0000000000..2c01a0d40a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc @@ -0,0 +1,1129 @@ +/* + * 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 <list> +#include <memory> + +#include "absl/algorithm/container.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/fec_test_helper.h" +#include "modules/rtp_rtcp/source/flexfec_header_reader_writer.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/ulpfec_header_reader_writer.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +// Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum. +constexpr size_t kTransportOverhead = 28; + +constexpr uint32_t kMediaSsrc = 83542; +constexpr uint32_t kFlexfecSsrc = 43245; + +constexpr size_t kMaxMediaPackets = 48; + +// Deep copies `src` to `dst`, but only keeps every Nth packet. +void DeepCopyEveryNthPacket(const ForwardErrorCorrection::PacketList& src, + int n, + ForwardErrorCorrection::PacketList* dst) { + RTC_DCHECK_GT(n, 0); + int i = 0; + for (const auto& packet : src) { + if (i % n == 0) { + dst->emplace_back(new ForwardErrorCorrection::Packet(*packet)); + } + ++i; + } +} + +} // namespace + +using ::testing::Types; + +template <typename ForwardErrorCorrectionType> +class RtpFecTest : public ::testing::Test { + protected: + RtpFecTest() + : random_(0xabcdef123456), + media_packet_generator_( + kRtpHeaderSize, // Minimum packet size. + IP_PACKET_SIZE - kRtpHeaderSize - kTransportOverhead - + fec_.MaxPacketOverhead(), // Maximum packet size. + kMediaSsrc, + &random_) {} + + // Construct `received_packets_`: a subset of the media and FEC packets. + // + // Media packet "i" is lost if media_loss_mask_[i] = 1, received if + // media_loss_mask_[i] = 0. + // FEC packet "i" is lost if fec_loss_mask_[i] = 1, received if + // fec_loss_mask_[i] = 0. + void NetworkReceivedPackets(int* media_loss_mask, int* fec_loss_mask); + + // Add packet from `packet_list` to list of received packets, using the + // `loss_mask`. + // The `packet_list` may be a media packet list (is_fec = false), or a + // FEC packet list (is_fec = true). + template <typename T> + void ReceivedPackets(const T& packet_list, int* loss_mask, bool is_fec); + + // Check for complete recovery after FEC decoding. + bool IsRecoveryComplete(); + + ForwardErrorCorrectionType fec_; + + Random random_; + test::fec::MediaPacketGenerator media_packet_generator_; + + ForwardErrorCorrection::PacketList media_packets_; + std::list<ForwardErrorCorrection::Packet*> generated_fec_packets_; + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>> + received_packets_; + ForwardErrorCorrection::RecoveredPacketList recovered_packets_; + + int media_loss_mask_[kUlpfecMaxMediaPackets]; + int fec_loss_mask_[kUlpfecMaxMediaPackets]; +}; + +template <typename ForwardErrorCorrectionType> +void RtpFecTest<ForwardErrorCorrectionType>::NetworkReceivedPackets( + int* media_loss_mask, + int* fec_loss_mask) { + constexpr bool kFecPacket = true; + this->received_packets_.clear(); + ReceivedPackets(media_packets_, media_loss_mask, !kFecPacket); + ReceivedPackets(generated_fec_packets_, fec_loss_mask, kFecPacket); +} + +template <typename ForwardErrorCorrectionType> +template <typename PacketListType> +void RtpFecTest<ForwardErrorCorrectionType>::ReceivedPackets( + const PacketListType& packet_list, + int* loss_mask, + bool is_fec) { + uint16_t fec_seq_num = ForwardErrorCorrectionType::GetFirstFecSeqNum( + media_packet_generator_.GetNextSeqNum()); + int packet_idx = 0; + + for (const auto& packet : packet_list) { + if (loss_mask[packet_idx] == 0) { + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> received_packet( + new ForwardErrorCorrection::ReceivedPacket()); + received_packet->pkt = new ForwardErrorCorrection::Packet(); + received_packet->pkt->data = packet->data; + received_packet->is_fec = is_fec; + if (!is_fec) { + received_packet->ssrc = kMediaSsrc; + // For media packets, the sequence number is obtained from the + // RTP header as written by MediaPacketGenerator::ConstructMediaPackets. + received_packet->seq_num = + ByteReader<uint16_t>::ReadBigEndian(packet->data.data() + 2); + } else { + received_packet->ssrc = ForwardErrorCorrectionType::kFecSsrc; + // For FEC packets, we simulate the sequence numbers differently + // depending on if ULPFEC or FlexFEC is used. See the definition of + // ForwardErrorCorrectionType::GetFirstFecSeqNum. + received_packet->seq_num = fec_seq_num; + } + received_packets_.push_back(std::move(received_packet)); + } + packet_idx++; + // Sequence number of FEC packets are defined as increment by 1 from + // last media packet in frame. + if (is_fec) + fec_seq_num++; + } +} + +template <typename ForwardErrorCorrectionType> +bool RtpFecTest<ForwardErrorCorrectionType>::IsRecoveryComplete() { + // We must have equally many recovered packets as original packets and all + // recovered packets must be identical to the corresponding original packets. + return absl::c_equal( + media_packets_, recovered_packets_, + [](const std::unique_ptr<ForwardErrorCorrection::Packet>& media_packet, + const std::unique_ptr<ForwardErrorCorrection::RecoveredPacket>& + recovered_packet) { + if (media_packet->data.size() != recovered_packet->pkt->data.size()) { + return false; + } + if (memcmp(media_packet->data.cdata(), + recovered_packet->pkt->data.cdata(), + media_packet->data.size()) != 0) { + return false; + } + return true; + }); +} + +// Define gTest typed test to loop over both ULPFEC and FlexFEC. +// Since the tests now are parameterized, we need to access +// member variables using `this`, thereby enforcing runtime +// resolution. + +class FlexfecForwardErrorCorrection : public ForwardErrorCorrection { + public: + static const uint32_t kFecSsrc = kFlexfecSsrc; + + FlexfecForwardErrorCorrection() + : ForwardErrorCorrection( + std::unique_ptr<FecHeaderReader>(new FlexfecHeaderReader()), + std::unique_ptr<FecHeaderWriter>(new FlexfecHeaderWriter()), + kFecSsrc, + kMediaSsrc) {} + + // For FlexFEC we let the FEC packet sequence numbers be independent of + // the media packet sequence numbers. + static uint16_t GetFirstFecSeqNum(uint16_t next_media_seq_num) { + Random random(0xbe110); + return random.Rand<uint16_t>(); + } +}; + +class UlpfecForwardErrorCorrection : public ForwardErrorCorrection { + public: + static const uint32_t kFecSsrc = kMediaSsrc; + + UlpfecForwardErrorCorrection() + : ForwardErrorCorrection( + std::unique_ptr<FecHeaderReader>(new UlpfecHeaderReader()), + std::unique_ptr<FecHeaderWriter>(new UlpfecHeaderWriter()), + kFecSsrc, + kMediaSsrc) {} + + // For ULPFEC we assume that the FEC packets are subsequent to the media + // packets in terms of sequence number. + static uint16_t GetFirstFecSeqNum(uint16_t next_media_seq_num) { + return next_media_seq_num; + } +}; + +using FecTypes = + Types<FlexfecForwardErrorCorrection, UlpfecForwardErrorCorrection>; +TYPED_TEST_SUITE(RtpFecTest, FecTypes); + +TYPED_TEST(RtpFecTest, WillProtectMediaPacketsWithLargeSequenceNumberGap) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 2; + constexpr uint8_t kProtectionFactor = 127; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + // Create |kMaxMediaPackets - 1| sequence number difference. + ByteWriter<uint16_t>::WriteBigEndian( + this->media_packets_.front()->data.MutableData() + 2, 1); + ByteWriter<uint16_t>::WriteBigEndian( + this->media_packets_.back()->data.MutableData() + 2, kMaxMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + EXPECT_EQ(1u, this->generated_fec_packets_.size()); +} + +TYPED_TEST(RtpFecTest, + WillNotProtectMediaPacketsWithTooLargeSequenceNumberGap) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 2; + constexpr uint8_t kProtectionFactor = 127; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + // Create `kMaxMediaPackets` sequence number difference. + ByteWriter<uint16_t>::WriteBigEndian( + this->media_packets_.front()->data.MutableData() + 2, 1); + ByteWriter<uint16_t>::WriteBigEndian( + this->media_packets_.back()->data.MutableData() + 2, + kMaxMediaPackets + 1); + + EXPECT_EQ( + -1, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + EXPECT_TRUE(this->generated_fec_packets_.empty()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryNoLoss) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 60; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // No packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // No packets lost, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryWithLoss) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 60; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // 1 media packet lost + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 2 media packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // 2 packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +// Verify that we don't use an old FEC packet for FEC decoding. +TYPED_TEST(RtpFecTest, NoFecRecoveryWithOldFecPacket) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr uint8_t kProtectionFactor = 20; + + // Two frames: first frame (old) with two media packets and 1 FEC packet. + // Third frame (new) with 3 media packets, and no FEC packets. + // + // #0(media) #1(media) #2(FEC) ----Frame 1----- + // #32767(media) 32768(media) 32769(media) ----Frame 2----- + // #65535(media) #0(media) #1(media). ----Frame 3----- + // If we lose either packet 0 or 1 of third frame, FEC decoding should not + // try to decode using "old" FEC packet #2. + + // Construct media packets for first frame, starting at sequence number 0. + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(2, 0); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + // Add FEC packet (seq#2) of this first frame to received list (i.e., assume + // the two media packet were lost). + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->ReceivedPackets(this->generated_fec_packets_, this->fec_loss_mask_, + true); + + // Construct media packets for second frame, with sequence number wrap. + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 32767); + + // Expect 3 media packets for this frame. + EXPECT_EQ(3u, this->media_packets_.size()); + + // No packets lost + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + this->ReceivedPackets(this->media_packets_, this->media_loss_mask_, false); + + // Construct media packets for third frame, with sequence number wrap. + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 65535); + + // Expect 3 media packets for this frame. + EXPECT_EQ(3u, this->media_packets_.size()); + + // Second media packet lost (seq#0). + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + this->media_loss_mask_[1] = 1; + // Add packets #65535, and #1 to received list. + this->ReceivedPackets(this->media_packets_, this->media_loss_mask_, false); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Expect that no decoding is done to get missing packet (seq#0) of third + // frame, using old FEC packet (seq#2) from first (old) frame. So number of + // recovered packets is 5 (0 from first frame, three from second frame, and 2 + // for the third frame, with no packets recovered via FEC). + EXPECT_EQ(5u, this->recovered_packets_.size()); + EXPECT_TRUE(this->recovered_packets_.size() != this->media_packets_.size()); +} + +// Verify we can still recover frame if sequence number wrap occurs within +// the frame and FEC packet following wrap is received after media packets. +TYPED_TEST(RtpFecTest, FecRecoveryWithSeqNumGapOneFrameRecovery) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr uint8_t kProtectionFactor = 20; + + // One frame, with sequence number wrap in media packets. + // -----Frame 1---- + // #65534(media) #65535(media) #0(media) #1(FEC). + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 65534); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // Lose one media packet (seq# 65535). + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->ReceivedPackets(this->media_packets_, this->media_loss_mask_, false); + // Add FEC packet to received list following the media packets. + this->ReceivedPackets(this->generated_fec_packets_, this->fec_loss_mask_, + true); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Expect 3 media packets in recovered list, and complete recovery. + // Wrap-around won't remove FEC packet, as it follows the wrap. + EXPECT_EQ(3u, this->recovered_packets_.size()); + EXPECT_TRUE(this->IsRecoveryComplete()); +} + +// Sequence number wrap occurs within the ULPFEC packets for the frame. +// Same problem will occur if wrap is within media packets but ULPFEC packet is +// received before the media packets. This may be improved if timing information +// is used to detect old ULPFEC packets. + +// TODO(nisse): There's some logic to discard ULPFEC packets at wrap-around, +// however, that is not actually exercised by this test: When the first FEC +// packet is processed, it results in full recovery of one media packet and the +// FEC packet is forgotten. And then the wraparound isn't noticed when the next +// FEC packet is received. We should fix wraparound handling, which currently +// appears broken, and then figure out how to test it properly. +using RtpFecTestUlpfecOnly = RtpFecTest<UlpfecForwardErrorCorrection>; +TEST_F(RtpFecTestUlpfecOnly, FecRecoveryWithSeqNumGapOneFrameRecovery) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr uint8_t kProtectionFactor = 200; + + // 1 frame: 3 media packets and 2 FEC packets. + // Sequence number wrap in FEC packets. + // -----Frame 1---- + // #65532(media) #65533(media) #65534(media) #65535(FEC) #0(FEC). + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 65532); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 2 FEC packets. + EXPECT_EQ(2u, this->generated_fec_packets_.size()); + + // Lose the last two media packets (seq# 65533, 65534). + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[2] = 1; + this->ReceivedPackets(this->media_packets_, this->media_loss_mask_, false); + this->ReceivedPackets(this->generated_fec_packets_, this->fec_loss_mask_, + true); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // The two FEC packets are received and should allow for complete recovery, + // but because of the wrap the first FEC packet will be discarded, and only + // one media packet is recoverable. So expect 2 media packets on recovered + // list and no complete recovery. + EXPECT_EQ(3u, this->recovered_packets_.size()); + EXPECT_EQ(this->recovered_packets_.size(), this->media_packets_.size()); + EXPECT_TRUE(this->IsRecoveryComplete()); +} + +// TODO(brandtr): This test mimics the one above, ensuring that the recovery +// strategy of FlexFEC matches the recovery strategy of ULPFEC. Since FlexFEC +// does not share the sequence number space with the media, however, having a +// matching recovery strategy may be suboptimal. Study this further. +// TODO(nisse): In this test, recovery based on the first FEC packet fails with +// the log message "The recovered packet had a length larger than a typical IP +// packet, and is thus dropped." This is probably not intended, and needs +// investigation. +using RtpFecTestFlexfecOnly = RtpFecTest<FlexfecForwardErrorCorrection>; +TEST_F(RtpFecTestFlexfecOnly, FecRecoveryWithSeqNumGapOneFrameNoRecovery) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr uint8_t kProtectionFactor = 200; + + // 1 frame: 3 media packets and 2 FEC packets. + // Sequence number wrap in FEC packets. + // -----Frame 1---- + // #65532(media) #65533(media) #65534(media) #65535(FEC) #0(FEC). + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 65532); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 2 FEC packets. + EXPECT_EQ(2u, this->generated_fec_packets_.size()); + + // Overwrite the sequence numbers generated by ConstructMediaPackets, + // to make sure that we do have a wrap. + auto it = this->generated_fec_packets_.begin(); + ByteWriter<uint16_t>::WriteBigEndian(&(*it)->data.MutableData()[2], 65535); + ++it; + ByteWriter<uint16_t>::WriteBigEndian(&(*it)->data.MutableData()[2], 0); + + // Lose the last two media packets (seq# 65533, 65534). + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[2] = 1; + this->ReceivedPackets(this->media_packets_, this->media_loss_mask_, false); + this->ReceivedPackets(this->generated_fec_packets_, this->fec_loss_mask_, + true); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // The two FEC packets are received and should allow for complete recovery, + // but because of the wrap the first FEC packet will be discarded, and only + // one media packet is recoverable. So expect 2 media packets on recovered + // list and no complete recovery. + EXPECT_EQ(2u, this->recovered_packets_.size()); + EXPECT_TRUE(this->recovered_packets_.size() != this->media_packets_.size()); + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +// Verify we can still recover frame if media packets are reordered. +TYPED_TEST(RtpFecTest, FecRecoveryWithMediaOutOfOrder) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr uint8_t kProtectionFactor = 20; + + // One frame: 3 media packets, 1 FEC packet. + // -----Frame 1---- + // #0(media) #1(media) #2(media) #3(FEC). + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 0); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // Lose one media packet (seq# 1). + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + // Reorder received media packets. + auto it0 = this->received_packets_.begin(); + auto it1 = this->received_packets_.begin(); + it1++; + std::swap(*it0, *it1); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Expect 3 media packets in recovered list, and complete recovery. + EXPECT_EQ(3u, this->recovered_packets_.size()); + EXPECT_TRUE(this->IsRecoveryComplete()); +} + +// Verify we can still recover frame if FEC is received before media packets. +TYPED_TEST(RtpFecTest, FecRecoveryWithFecOutOfOrder) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr uint8_t kProtectionFactor = 20; + + // One frame: 3 media packets, 1 FEC packet. + // -----Frame 1---- + // #0(media) #1(media) #2(media) #3(FEC). + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(3, 0); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // Lose one media packet (seq# 1). + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + // Add FEC packet to received list before the media packets. + this->ReceivedPackets(this->generated_fec_packets_, this->fec_loss_mask_, + true); + // Add media packets to received list. + this->ReceivedPackets(this->media_packets_, this->media_loss_mask_, false); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Expect 3 media packets in recovered list, and complete recovery. + EXPECT_EQ(3u, this->recovered_packets_.size()); + EXPECT_TRUE(this->IsRecoveryComplete()); +} + +// Test 50% protection with random mask type: Two cases are considered: +// a 50% non-consecutive loss which can be fully recovered, and a 50% +// consecutive loss which cannot be fully recovered. +TYPED_TEST(RtpFecTest, FecRecoveryWithLoss50percRandomMask) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 255; + + // Packet Mask for (4,4,0) code, from random mask table. + // (kNumMediaPackets = 4; num_fec_packets = 4, kNumImportantPackets = 0) + + // media#0 media#1 media#2 media#3 + // fec#0: 1 1 0 0 + // fec#1: 1 0 1 0 + // fec#2: 0 0 1 1 + // fec#3: 0 1 0 1 + // + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskRandom, &this->generated_fec_packets_)); + + // Expect 4 FEC packets. + EXPECT_EQ(4u, this->generated_fec_packets_.size()); + + // 4 packets lost: 3 media packets (0, 2, 3), and one FEC packet (0) lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->fec_loss_mask_[0] = 1; + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[2] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // With media packet#1 and FEC packets #1, #2, #3, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 4 consecutive packets lost: media packets 0, 1, 2, 3. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[2] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Cannot get complete recovery for this loss configuration with random mask. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +// Test 50% protection with bursty type: Three cases are considered: +// two 50% consecutive losses which can be fully recovered, and one +// non-consecutive which cannot be fully recovered. +TYPED_TEST(RtpFecTest, FecRecoveryWithLoss50percBurstyMask) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 255; + + // Packet Mask for (4,4,0) code, from bursty mask table. + // (kNumMediaPackets = 4; num_fec_packets = 4, kNumImportantPackets = 0) + + // media#0 media#1 media#2 media#3 + // fec#0: 1 0 0 0 + // fec#1: 1 1 0 0 + // fec#2: 0 1 1 0 + // fec#3: 0 0 1 1 + // + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 4 FEC packets. + EXPECT_EQ(4u, this->generated_fec_packets_.size()); + + // 4 consecutive packets lost: media packets 0,1,2,3. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[2] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Expect complete recovery for consecutive packet loss <= 50%. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 4 consecutive packets lost: media packets 1,2, 3, and FEC packet 0. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->fec_loss_mask_[0] = 1; + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[2] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Expect complete recovery for consecutive packet loss <= 50%. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 4 packets lost (non-consecutive loss): media packets 0, 3, and FEC# 0, 3. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->fec_loss_mask_[0] = 1; + this->fec_loss_mask_[3] = 1; + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Cannot get complete recovery for this loss configuration. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryNoLossUep) { + constexpr int kNumImportantPackets = 2; + constexpr bool kUseUnequalProtection = true; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 60; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // No packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // No packets lost, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryWithLossUep) { + constexpr int kNumImportantPackets = 2; + constexpr bool kUseUnequalProtection = true; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 60; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // 1 media packet lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 2 media packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // 2 packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +// Test 50% protection with random mask type for UEP on. +TYPED_TEST(RtpFecTest, FecRecoveryWithLoss50percUepRandomMask) { + constexpr int kNumImportantPackets = 1; + constexpr bool kUseUnequalProtection = true; + constexpr int kNumMediaPackets = 4; + constexpr uint8_t kProtectionFactor = 255; + + // Packet Mask for (4,4,1) code, from random mask table. + // (kNumMediaPackets = 4; num_fec_packets = 4, kNumImportantPackets = 1) + + // media#0 media#1 media#2 media#3 + // fec#0: 1 0 0 0 + // fec#1: 1 1 0 0 + // fec#2: 1 0 1 1 + // fec#3: 0 1 1 0 + // + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(this->media_packets_, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskRandom, &this->generated_fec_packets_)); + + // Expect 4 FEC packets. + EXPECT_EQ(4u, this->generated_fec_packets_.size()); + + // 4 packets lost: 3 media packets and FEC packet#1 lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->fec_loss_mask_[1] = 1; + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[2] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // With media packet#3 and FEC packets #0, #1, #3, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 5 packets lost: 4 media packets and one FEC packet#2 lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->fec_loss_mask_[2] = 1; + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[1] = 1; + this->media_loss_mask_[2] = 1; + this->media_loss_mask_[3] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Cannot get complete recovery for this loss configuration. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryNonConsecutivePackets) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 5; + constexpr uint8_t kProtectionFactor = 60; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + // Create a new temporary packet list for generating FEC packets. + // This list should have every other packet removed. + ForwardErrorCorrection::PacketList protected_media_packets; + DeepCopyEveryNthPacket(this->media_packets_, 2, &protected_media_packets); + + EXPECT_EQ( + 0, this->fec_.EncodeFec(protected_media_packets, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1u, this->generated_fec_packets_.size()); + + // 1 protected media packet lost + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[2] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // Unprotected packet lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[1] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Unprotected packet lost. Recovery not possible. + EXPECT_FALSE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 2 media packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[0] = 1; + this->media_loss_mask_[2] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // 2 protected packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryNonConsecutivePacketsExtension) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 21; + uint8_t kProtectionFactor = 127; + + this->media_packets_ = + this->media_packet_generator_.ConstructMediaPackets(kNumMediaPackets); + + // Create a new temporary packet list for generating FEC packets. + // This list should have every other packet removed. + ForwardErrorCorrection::PacketList protected_media_packets; + DeepCopyEveryNthPacket(this->media_packets_, 2, &protected_media_packets); + + // Zero column insertion will have to extend the size of the packet + // mask since the number of actual packets are 21, while the number + // of protected packets are 11. + EXPECT_EQ( + 0, this->fec_.EncodeFec(protected_media_packets, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 5 FEC packet. + EXPECT_EQ(5u, this->generated_fec_packets_.size()); + + // Last protected media packet lost + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[kNumMediaPackets - 1] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // Last unprotected packet lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[kNumMediaPackets - 2] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Unprotected packet lost. Recovery not possible. + EXPECT_FALSE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 6 media packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[kNumMediaPackets - 11] = 1; + this->media_loss_mask_[kNumMediaPackets - 9] = 1; + this->media_loss_mask_[kNumMediaPackets - 7] = 1; + this->media_loss_mask_[kNumMediaPackets - 5] = 1; + this->media_loss_mask_[kNumMediaPackets - 3] = 1; + this->media_loss_mask_[kNumMediaPackets - 1] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // 5 protected packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +TYPED_TEST(RtpFecTest, FecRecoveryNonConsecutivePacketsWrap) { + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr int kNumMediaPackets = 21; + uint8_t kProtectionFactor = 127; + + this->media_packets_ = this->media_packet_generator_.ConstructMediaPackets( + kNumMediaPackets, 0xFFFF - 5); + + // Create a new temporary packet list for generating FEC packets. + // This list should have every other packet removed. + ForwardErrorCorrection::PacketList protected_media_packets; + DeepCopyEveryNthPacket(this->media_packets_, 2, &protected_media_packets); + + // Zero column insertion will have to extend the size of the packet + // mask since the number of actual packets are 21, while the number + // of protected packets are 11. + EXPECT_EQ( + 0, this->fec_.EncodeFec(protected_media_packets, kProtectionFactor, + kNumImportantPackets, kUseUnequalProtection, + kFecMaskBursty, &this->generated_fec_packets_)); + + // Expect 5 FEC packet. + EXPECT_EQ(5u, this->generated_fec_packets_.size()); + + // Last protected media packet lost + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[kNumMediaPackets - 1] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // Last unprotected packet lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[kNumMediaPackets - 2] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // Unprotected packet lost. Recovery not possible. + EXPECT_FALSE(this->IsRecoveryComplete()); + this->recovered_packets_.clear(); + + // 6 media packets lost. + memset(this->media_loss_mask_, 0, sizeof(this->media_loss_mask_)); + memset(this->fec_loss_mask_, 0, sizeof(this->fec_loss_mask_)); + this->media_loss_mask_[kNumMediaPackets - 11] = 1; + this->media_loss_mask_[kNumMediaPackets - 9] = 1; + this->media_loss_mask_[kNumMediaPackets - 7] = 1; + this->media_loss_mask_[kNumMediaPackets - 5] = 1; + this->media_loss_mask_[kNumMediaPackets - 3] = 1; + this->media_loss_mask_[kNumMediaPackets - 1] = 1; + this->NetworkReceivedPackets(this->media_loss_mask_, this->fec_loss_mask_); + + for (const auto& received_packet : this->received_packets_) { + this->fec_.DecodeFec(*received_packet, &this->recovered_packets_); + } + + // 5 protected packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(this->IsRecoveryComplete()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.cc new file mode 100644 index 0000000000..7550b70f69 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.cc @@ -0,0 +1,144 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format.h" + +#include <memory> + +#include "absl/types/variant.h" +#include "modules/rtp_rtcp/source/rtp_format_h264.h" +#include "modules/rtp_rtcp/source/rtp_format_video_generic.h" +#include "modules/rtp_rtcp/source/rtp_format_vp8.h" +#include "modules/rtp_rtcp/source/rtp_format_vp9.h" +#include "modules/rtp_rtcp/source/rtp_packetizer_av1.h" +#include "modules/video_coding/codecs/h264/include/h264_globals.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 { + +std::unique_ptr<RtpPacketizer> RtpPacketizer::Create( + absl::optional<VideoCodecType> type, + rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + // Codec-specific details. + const RTPVideoHeader& rtp_video_header) { + if (!type) { + // Use raw packetizer. + return std::make_unique<RtpPacketizerGeneric>(payload, limits); + } + + switch (*type) { + case kVideoCodecH264: { + const auto& h264 = + absl::get<RTPVideoHeaderH264>(rtp_video_header.video_type_header); + return std::make_unique<RtpPacketizerH264>(payload, limits, + h264.packetization_mode); + } + case kVideoCodecVP8: { + const auto& vp8 = + absl::get<RTPVideoHeaderVP8>(rtp_video_header.video_type_header); + return std::make_unique<RtpPacketizerVp8>(payload, limits, vp8); + } + case kVideoCodecVP9: { + const auto& vp9 = + absl::get<RTPVideoHeaderVP9>(rtp_video_header.video_type_header); + return std::make_unique<RtpPacketizerVp9>(payload, limits, vp9); + } + case kVideoCodecAV1: + return std::make_unique<RtpPacketizerAv1>( + payload, limits, rtp_video_header.frame_type, + rtp_video_header.is_last_frame_in_picture); + default: { + return std::make_unique<RtpPacketizerGeneric>(payload, limits, + rtp_video_header); + } + } +} + +std::vector<int> RtpPacketizer::SplitAboutEqually( + int payload_len, + const PayloadSizeLimits& limits) { + RTC_DCHECK_GT(payload_len, 0); + // First or last packet larger than normal are unsupported. + RTC_DCHECK_GE(limits.first_packet_reduction_len, 0); + RTC_DCHECK_GE(limits.last_packet_reduction_len, 0); + + std::vector<int> result; + if (limits.max_payload_len >= + limits.single_packet_reduction_len + payload_len) { + result.push_back(payload_len); + return result; + } + if (limits.max_payload_len - limits.first_packet_reduction_len < 1 || + limits.max_payload_len - limits.last_packet_reduction_len < 1) { + // Capacity is not enough to put a single byte into one of the packets. + return result; + } + // First and last packet of the frame can be smaller. Pretend that it's + // the same size, but we must write more payload to it. + // Assume frame fits in single packet if packet has extra space for sum + // of first and last packets reductions. + int total_bytes = payload_len + limits.first_packet_reduction_len + + limits.last_packet_reduction_len; + // Integer divisions with rounding up. + int num_packets_left = + (total_bytes + limits.max_payload_len - 1) / limits.max_payload_len; + if (num_packets_left == 1) { + // Single packet is a special case handled above. + num_packets_left = 2; + } + + if (payload_len < num_packets_left) { + // Edge case where limits force to have more packets than there are payload + // bytes. This may happen when there is single byte of payload that can't be + // put into single packet if + // first_packet_reduction + last_packet_reduction >= max_payload_len. + return result; + } + + int bytes_per_packet = total_bytes / num_packets_left; + int num_larger_packets = total_bytes % num_packets_left; + int remaining_data = payload_len; + + result.reserve(num_packets_left); + bool first_packet = true; + while (remaining_data > 0) { + // Last num_larger_packets are 1 byte wider than the rest. Increase + // per-packet payload size when needed. + if (num_packets_left == num_larger_packets) + ++bytes_per_packet; + int current_packet_bytes = bytes_per_packet; + if (first_packet) { + if (current_packet_bytes > limits.first_packet_reduction_len + 1) + current_packet_bytes -= limits.first_packet_reduction_len; + else + current_packet_bytes = 1; + } + if (current_packet_bytes > remaining_data) { + current_packet_bytes = remaining_data; + } + // This is not the last packet in the whole payload, but there's no data + // left for the last packet. Leave at least one byte for the last packet. + if (num_packets_left == 2 && current_packet_bytes == remaining_data) { + --current_packet_bytes; + } + result.push_back(current_packet_bytes); + + remaining_data -= current_packet_bytes; + --num_packets_left; + first_packet = false; + } + + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.h new file mode 100644 index 0000000000..19abd3feb2 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.h @@ -0,0 +1,61 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_H_ + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" + +namespace webrtc { + +class RtpPacketToSend; + +class RtpPacketizer { + public: + struct PayloadSizeLimits { + int max_payload_len = 1200; + int first_packet_reduction_len = 0; + int last_packet_reduction_len = 0; + // Reduction len for packet that is first & last at the same time. + int single_packet_reduction_len = 0; + }; + + // If type is not set, returns a raw packetizer. + static std::unique_ptr<RtpPacketizer> Create( + absl::optional<VideoCodecType> type, + rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + // Codec-specific details. + const RTPVideoHeader& rtp_video_header); + + virtual ~RtpPacketizer() = default; + + // Returns number of remaining packets to produce by the packetizer. + virtual size_t NumPackets() const = 0; + + // Get the next payload with payload header. + // Write payload and set marker bit of the `packet`. + // Returns true on success, false otherwise. + virtual bool NextPacket(RtpPacketToSend* packet) = 0; + + // Split payload_len into sum of integers with respect to `limits`. + // Returns empty vector on failure. + static std::vector<int> SplitAboutEqually(int payload_len, + const PayloadSizeLimits& limits); +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc new file mode 100644 index 0000000000..cc8d1bff34 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc @@ -0,0 +1,313 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format_h264.h" + +#include <string.h> + +#include <cstddef> +#include <cstdint> +#include <iterator> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "common_video/h264/h264_common.h" +#include "common_video/h264/pps_parser.h" +#include "common_video/h264/sps_parser.h" +#include "common_video/h264/sps_vui_rewriter.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +static const size_t kNalHeaderSize = 1; +static const size_t kFuAHeaderSize = 2; +static const size_t kLengthFieldSize = 2; + +} // namespace + +RtpPacketizerH264::RtpPacketizerH264(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + H264PacketizationMode packetization_mode) + : limits_(limits), num_packets_left_(0) { + // Guard against uninitialized memory in packetization_mode. + RTC_CHECK(packetization_mode == H264PacketizationMode::NonInterleaved || + packetization_mode == H264PacketizationMode::SingleNalUnit); + + for (const auto& nalu : + H264::FindNaluIndices(payload.data(), payload.size())) { + input_fragments_.push_back( + payload.subview(nalu.payload_start_offset, nalu.payload_size)); + } + + if (!GeneratePackets(packetization_mode)) { + // If failed to generate all the packets, discard already generated + // packets in case the caller would ignore return value and still try to + // call NextPacket(). + num_packets_left_ = 0; + while (!packets_.empty()) { + packets_.pop(); + } + } +} + +RtpPacketizerH264::~RtpPacketizerH264() = default; + +size_t RtpPacketizerH264::NumPackets() const { + return num_packets_left_; +} + +bool RtpPacketizerH264::GeneratePackets( + H264PacketizationMode packetization_mode) { + for (size_t i = 0; i < input_fragments_.size();) { + switch (packetization_mode) { + case H264PacketizationMode::SingleNalUnit: + if (!PacketizeSingleNalu(i)) + return false; + ++i; + break; + case H264PacketizationMode::NonInterleaved: + int fragment_len = input_fragments_[i].size(); + int single_packet_capacity = limits_.max_payload_len; + if (input_fragments_.size() == 1) + single_packet_capacity -= limits_.single_packet_reduction_len; + else if (i == 0) + single_packet_capacity -= limits_.first_packet_reduction_len; + else if (i + 1 == input_fragments_.size()) + single_packet_capacity -= limits_.last_packet_reduction_len; + + if (fragment_len > single_packet_capacity) { + if (!PacketizeFuA(i)) + return false; + ++i; + } else { + i = PacketizeStapA(i); + } + break; + } + } + return true; +} + +bool RtpPacketizerH264::PacketizeFuA(size_t fragment_index) { + // Fragment payload into packets (FU-A). + rtc::ArrayView<const uint8_t> fragment = input_fragments_[fragment_index]; + + PayloadSizeLimits limits = limits_; + // Leave room for the FU-A header. + limits.max_payload_len -= kFuAHeaderSize; + // Update single/first/last packet reductions unless it is single/first/last + // fragment. + if (input_fragments_.size() != 1) { + // if this fragment is put into a single packet, it might still be the + // first or the last packet in the whole sequence of packets. + if (fragment_index == input_fragments_.size() - 1) { + limits.single_packet_reduction_len = limits_.last_packet_reduction_len; + } else if (fragment_index == 0) { + limits.single_packet_reduction_len = limits_.first_packet_reduction_len; + } else { + limits.single_packet_reduction_len = 0; + } + } + if (fragment_index != 0) + limits.first_packet_reduction_len = 0; + if (fragment_index != input_fragments_.size() - 1) + limits.last_packet_reduction_len = 0; + + // Strip out the original header. + size_t payload_left = fragment.size() - kNalHeaderSize; + int offset = kNalHeaderSize; + + std::vector<int> payload_sizes = SplitAboutEqually(payload_left, limits); + if (payload_sizes.empty()) + return false; + + for (size_t i = 0; i < payload_sizes.size(); ++i) { + int packet_length = payload_sizes[i]; + RTC_CHECK_GT(packet_length, 0); + packets_.push(PacketUnit(fragment.subview(offset, packet_length), + /*first_fragment=*/i == 0, + /*last_fragment=*/i == payload_sizes.size() - 1, + false, fragment[0])); + offset += packet_length; + payload_left -= packet_length; + } + num_packets_left_ += payload_sizes.size(); + RTC_CHECK_EQ(0, payload_left); + return true; +} + +size_t RtpPacketizerH264::PacketizeStapA(size_t fragment_index) { + // Aggregate fragments into one packet (STAP-A). + size_t payload_size_left = limits_.max_payload_len; + if (input_fragments_.size() == 1) + payload_size_left -= limits_.single_packet_reduction_len; + else if (fragment_index == 0) + payload_size_left -= limits_.first_packet_reduction_len; + int aggregated_fragments = 0; + size_t fragment_headers_length = 0; + rtc::ArrayView<const uint8_t> fragment = input_fragments_[fragment_index]; + RTC_CHECK_GE(payload_size_left, fragment.size()); + ++num_packets_left_; + + auto payload_size_needed = [&] { + size_t fragment_size = fragment.size() + fragment_headers_length; + if (input_fragments_.size() == 1) { + // Single fragment, single packet, payload_size_left already adjusted + // with limits_.single_packet_reduction_len. + return fragment_size; + } + if (fragment_index == input_fragments_.size() - 1) { + // Last fragment, so STAP-A might be the last packet. + return fragment_size + limits_.last_packet_reduction_len; + } + return fragment_size; + }; + + while (payload_size_left >= payload_size_needed()) { + RTC_CHECK_GT(fragment.size(), 0); + packets_.push(PacketUnit(fragment, aggregated_fragments == 0, false, true, + fragment[0])); + payload_size_left -= fragment.size(); + payload_size_left -= fragment_headers_length; + + fragment_headers_length = kLengthFieldSize; + // If we are going to try to aggregate more fragments into this packet + // we need to add the STAP-A NALU header and a length field for the first + // NALU of this packet. + if (aggregated_fragments == 0) + fragment_headers_length += kNalHeaderSize + kLengthFieldSize; + ++aggregated_fragments; + + // Next fragment. + ++fragment_index; + if (fragment_index == input_fragments_.size()) + break; + fragment = input_fragments_[fragment_index]; + } + RTC_CHECK_GT(aggregated_fragments, 0); + packets_.back().last_fragment = true; + return fragment_index; +} + +bool RtpPacketizerH264::PacketizeSingleNalu(size_t fragment_index) { + // Add a single NALU to the queue, no aggregation. + size_t payload_size_left = limits_.max_payload_len; + if (input_fragments_.size() == 1) + payload_size_left -= limits_.single_packet_reduction_len; + else if (fragment_index == 0) + payload_size_left -= limits_.first_packet_reduction_len; + else if (fragment_index + 1 == input_fragments_.size()) + payload_size_left -= limits_.last_packet_reduction_len; + rtc::ArrayView<const uint8_t> fragment = input_fragments_[fragment_index]; + if (payload_size_left < fragment.size()) { + RTC_LOG(LS_ERROR) << "Failed to fit a fragment to packet in SingleNalu " + "packetization mode. Payload size left " + << payload_size_left << ", fragment length " + << fragment.size() << ", packet capacity " + << limits_.max_payload_len; + return false; + } + RTC_CHECK_GT(fragment.size(), 0u); + packets_.push(PacketUnit(fragment, true /* first */, true /* last */, + false /* aggregated */, fragment[0])); + ++num_packets_left_; + return true; +} + +bool RtpPacketizerH264::NextPacket(RtpPacketToSend* rtp_packet) { + RTC_DCHECK(rtp_packet); + if (packets_.empty()) { + return false; + } + + PacketUnit packet = packets_.front(); + if (packet.first_fragment && packet.last_fragment) { + // Single NAL unit packet. + size_t bytes_to_send = packet.source_fragment.size(); + uint8_t* buffer = rtp_packet->AllocatePayload(bytes_to_send); + memcpy(buffer, packet.source_fragment.data(), bytes_to_send); + packets_.pop(); + input_fragments_.pop_front(); + } else if (packet.aggregated) { + NextAggregatePacket(rtp_packet); + } else { + NextFragmentPacket(rtp_packet); + } + rtp_packet->SetMarker(packets_.empty()); + --num_packets_left_; + return true; +} + +void RtpPacketizerH264::NextAggregatePacket(RtpPacketToSend* rtp_packet) { + // Reserve maximum available payload, set actual payload size later. + size_t payload_capacity = rtp_packet->FreeCapacity(); + RTC_CHECK_GE(payload_capacity, kNalHeaderSize); + uint8_t* buffer = rtp_packet->AllocatePayload(payload_capacity); + RTC_DCHECK(buffer); + PacketUnit* packet = &packets_.front(); + RTC_CHECK(packet->first_fragment); + // STAP-A NALU header. + buffer[0] = + (packet->header & (kH264FBit | kH264NriMask)) | H264::NaluType::kStapA; + size_t index = kNalHeaderSize; + bool is_last_fragment = packet->last_fragment; + while (packet->aggregated) { + rtc::ArrayView<const uint8_t> fragment = packet->source_fragment; + RTC_CHECK_LE(index + kLengthFieldSize + fragment.size(), payload_capacity); + // Add NAL unit length field. + ByteWriter<uint16_t>::WriteBigEndian(&buffer[index], fragment.size()); + index += kLengthFieldSize; + // Add NAL unit. + memcpy(&buffer[index], fragment.data(), fragment.size()); + index += fragment.size(); + packets_.pop(); + input_fragments_.pop_front(); + if (is_last_fragment) + break; + packet = &packets_.front(); + is_last_fragment = packet->last_fragment; + } + RTC_CHECK(is_last_fragment); + rtp_packet->SetPayloadSize(index); +} + +void RtpPacketizerH264::NextFragmentPacket(RtpPacketToSend* rtp_packet) { + PacketUnit* packet = &packets_.front(); + // NAL unit fragmented over multiple packets (FU-A). + // We do not send original NALU header, so it will be replaced by the + // FU indicator header of the first packet. + uint8_t fu_indicator = + (packet->header & (kH264FBit | kH264NriMask)) | H264::NaluType::kFuA; + uint8_t fu_header = 0; + + // S | E | R | 5 bit type. + fu_header |= (packet->first_fragment ? kH264SBit : 0); + fu_header |= (packet->last_fragment ? kH264EBit : 0); + uint8_t type = packet->header & kH264TypeMask; + fu_header |= type; + rtc::ArrayView<const uint8_t> fragment = packet->source_fragment; + uint8_t* buffer = + rtp_packet->AllocatePayload(kFuAHeaderSize + fragment.size()); + buffer[0] = fu_indicator; + buffer[1] = fu_header; + memcpy(buffer + kFuAHeaderSize, fragment.data(), fragment.size()); + if (packet->last_fragment) + input_fragments_.pop_front(); + packets_.pop(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.h new file mode 100644 index 0000000000..f95c3b6c6b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.h @@ -0,0 +1,99 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_H264_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_H264_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <deque> +#include <memory> +#include <queue> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/video_coding/codecs/h264/include/h264_globals.h" +#include "rtc_base/buffer.h" + +namespace webrtc { + +// Bit masks for NAL (F, NRI, Type) indicators. +constexpr uint8_t kH264FBit = 0x80; +constexpr uint8_t kH264NriMask = 0x60; +constexpr uint8_t kH264TypeMask = 0x1F; + +// Bit masks for FU (A and B) headers. +constexpr uint8_t kH264SBit = 0x80; +constexpr uint8_t kH264EBit = 0x40; +constexpr uint8_t kH264RBit = 0x20; + +class RtpPacketizerH264 : public RtpPacketizer { + public: + // Initialize with payload from encoder. + // The payload_data must be exactly one encoded H264 frame. + RtpPacketizerH264(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + H264PacketizationMode packetization_mode); + + ~RtpPacketizerH264() override; + + RtpPacketizerH264(const RtpPacketizerH264&) = delete; + RtpPacketizerH264& operator=(const RtpPacketizerH264&) = delete; + + size_t NumPackets() const override; + + // Get the next payload with H264 payload header. + // Write payload and set marker bit of the `packet`. + // Returns true on success, false otherwise. + bool NextPacket(RtpPacketToSend* rtp_packet) override; + + private: + // A packet unit (H264 packet), to be put into an RTP packet: + // If a NAL unit is too large for an RTP packet, this packet unit will + // represent a FU-A packet of a single fragment of the NAL unit. + // If a NAL unit is small enough to fit within a single RTP packet, this + // packet unit may represent a single NAL unit or a STAP-A packet, of which + // there may be multiple in a single RTP packet (if so, aggregated = true). + struct PacketUnit { + PacketUnit(rtc::ArrayView<const uint8_t> source_fragment, + bool first_fragment, + bool last_fragment, + bool aggregated, + uint8_t header) + : source_fragment(source_fragment), + first_fragment(first_fragment), + last_fragment(last_fragment), + aggregated(aggregated), + header(header) {} + + rtc::ArrayView<const uint8_t> source_fragment; + bool first_fragment; + bool last_fragment; + bool aggregated; + uint8_t header; + }; + + bool GeneratePackets(H264PacketizationMode packetization_mode); + bool PacketizeFuA(size_t fragment_index); + size_t PacketizeStapA(size_t fragment_index); + bool PacketizeSingleNalu(size_t fragment_index); + + void NextAggregatePacket(RtpPacketToSend* rtp_packet); + void NextFragmentPacket(RtpPacketToSend* rtp_packet); + + const PayloadSizeLimits limits_; + size_t num_packets_left_; + std::deque<rtc::ArrayView<const uint8_t>> input_fragments_; + std::queue<PacketUnit> packets_; +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_H264_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc new file mode 100644 index 0000000000..80d8801437 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc @@ -0,0 +1,496 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format_h264.h" + +#include <memory> +#include <vector> + +#include "absl/algorithm/container.h" +#include "api/array_view.h" +#include "common_video/h264/h264_common.h" +#include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::SizeIs; + +constexpr RtpPacketToSend::ExtensionManager* kNoExtensions = nullptr; +constexpr size_t kMaxPayloadSize = 1200; +constexpr size_t kLengthFieldLength = 2; +constexpr RtpPacketizer::PayloadSizeLimits kNoLimits; + +enum Nalu { + kSlice = 1, + kIdr = 5, + kSei = 6, + kSps = 7, + kPps = 8, + kStapA = 24, + kFuA = 28 +}; + +static const size_t kNalHeaderSize = 1; +static const size_t kFuAHeaderSize = 2; + +// Creates Buffer that looks like nal unit of given size. +rtc::Buffer GenerateNalUnit(size_t size) { + RTC_CHECK_GT(size, 0); + rtc::Buffer buffer(size); + // Set some valid header. + buffer[0] = kSlice; + for (size_t i = 1; i < size; ++i) { + buffer[i] = static_cast<uint8_t>(i); + } + // Last byte shouldn't be 0, or it may be counted as part of next 4-byte start + // sequence. + buffer[size - 1] |= 0x10; + return buffer; +} + +// Create frame consisting of nalus of given size. +rtc::Buffer CreateFrame(std::initializer_list<size_t> nalu_sizes) { + static constexpr int kStartCodeSize = 3; + rtc::Buffer frame(absl::c_accumulate(nalu_sizes, size_t{0}) + + kStartCodeSize * nalu_sizes.size()); + size_t offset = 0; + for (size_t nalu_size : nalu_sizes) { + EXPECT_GE(nalu_size, 1u); + // Insert nalu start code + frame[offset] = 0; + frame[offset + 1] = 0; + frame[offset + 2] = 1; + // Set some valid header. + frame[offset + 3] = 1; + // Fill payload avoiding accidental start codes + if (nalu_size > 1) { + memset(frame.data() + offset + 4, 0x3f, nalu_size - 1); + } + offset += (kStartCodeSize + nalu_size); + } + return frame; +} + +// Create frame consisting of given nalus. +rtc::Buffer CreateFrame(rtc::ArrayView<const rtc::Buffer> nalus) { + static constexpr int kStartCodeSize = 3; + int frame_size = 0; + for (const rtc::Buffer& nalu : nalus) { + frame_size += (kStartCodeSize + nalu.size()); + } + rtc::Buffer frame(frame_size); + size_t offset = 0; + for (const rtc::Buffer& nalu : nalus) { + // Insert nalu start code + frame[offset] = 0; + frame[offset + 1] = 0; + frame[offset + 2] = 1; + // Copy the nalu unit. + memcpy(frame.data() + offset + 3, nalu.data(), nalu.size()); + offset += (kStartCodeSize + nalu.size()); + } + return frame; +} + +std::vector<RtpPacketToSend> FetchAllPackets(RtpPacketizerH264* packetizer) { + std::vector<RtpPacketToSend> result; + size_t num_packets = packetizer->NumPackets(); + result.reserve(num_packets); + RtpPacketToSend packet(kNoExtensions); + while (packetizer->NextPacket(&packet)) { + result.push_back(packet); + } + EXPECT_THAT(result, SizeIs(num_packets)); + return result; +} + +// Tests that should work with both packetization mode 0 and +// packetization mode 1. +class RtpPacketizerH264ModeTest + : public ::testing::TestWithParam<H264PacketizationMode> {}; + +TEST_P(RtpPacketizerH264ModeTest, SingleNalu) { + const uint8_t frame[] = {0, 0, 1, kIdr, 0xFF}; + + RtpPacketizerH264 packetizer(frame, kNoLimits, GetParam()); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(1)); + EXPECT_THAT(packets[0].payload(), ElementsAre(kIdr, 0xFF)); +} + +TEST_P(RtpPacketizerH264ModeTest, SingleNaluTwoPackets) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = kMaxPayloadSize; + rtc::Buffer nalus[] = {GenerateNalUnit(kMaxPayloadSize), + GenerateNalUnit(100)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, GetParam()); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_THAT(packets[0].payload(), ElementsAreArray(nalus[0])); + EXPECT_THAT(packets[1].payload(), ElementsAreArray(nalus[1])); +} + +TEST_P(RtpPacketizerH264ModeTest, + SingleNaluFirstPacketReductionAppliesOnlyToFirstFragment) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 200; + limits.first_packet_reduction_len = 5; + rtc::Buffer nalus[] = {GenerateNalUnit(/*size=*/195), + GenerateNalUnit(/*size=*/200), + GenerateNalUnit(/*size=*/200)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, GetParam()); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(3)); + EXPECT_THAT(packets[0].payload(), ElementsAreArray(nalus[0])); + EXPECT_THAT(packets[1].payload(), ElementsAreArray(nalus[1])); + EXPECT_THAT(packets[2].payload(), ElementsAreArray(nalus[2])); +} + +TEST_P(RtpPacketizerH264ModeTest, + SingleNaluLastPacketReductionAppliesOnlyToLastFragment) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 200; + limits.last_packet_reduction_len = 5; + rtc::Buffer nalus[] = {GenerateNalUnit(/*size=*/200), + GenerateNalUnit(/*size=*/200), + GenerateNalUnit(/*size=*/195)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, GetParam()); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(3)); + EXPECT_THAT(packets[0].payload(), ElementsAreArray(nalus[0])); + EXPECT_THAT(packets[1].payload(), ElementsAreArray(nalus[1])); + EXPECT_THAT(packets[2].payload(), ElementsAreArray(nalus[2])); +} + +TEST_P(RtpPacketizerH264ModeTest, + SingleNaluFirstAndLastPacketReductionSumsForSinglePacket) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 200; + limits.first_packet_reduction_len = 20; + limits.last_packet_reduction_len = 30; + rtc::Buffer frame = CreateFrame({150}); + + RtpPacketizerH264 packetizer(frame, limits, GetParam()); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + EXPECT_THAT(packets, SizeIs(1)); +} + +INSTANTIATE_TEST_SUITE_P( + PacketMode, + RtpPacketizerH264ModeTest, + ::testing::Values(H264PacketizationMode::SingleNalUnit, + H264PacketizationMode::NonInterleaved)); + +// Aggregation tests. +TEST(RtpPacketizerH264Test, StapA) { + rtc::Buffer nalus[] = {GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/0x123)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, kNoLimits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(1)); + auto payload = packets[0].payload(); + EXPECT_EQ(payload.size(), + kNalHeaderSize + 3 * kLengthFieldLength + 2 + 2 + 0x123); + + EXPECT_EQ(payload[0], kStapA); + payload = payload.subview(kNalHeaderSize); + // 1st fragment. + EXPECT_THAT(payload.subview(0, kLengthFieldLength), + ElementsAre(0, 2)); // Size. + EXPECT_THAT(payload.subview(kLengthFieldLength, 2), + ElementsAreArray(nalus[0])); + payload = payload.subview(kLengthFieldLength + 2); + // 2nd fragment. + EXPECT_THAT(payload.subview(0, kLengthFieldLength), + ElementsAre(0, 2)); // Size. + EXPECT_THAT(payload.subview(kLengthFieldLength, 2), + ElementsAreArray(nalus[1])); + payload = payload.subview(kLengthFieldLength + 2); + // 3rd fragment. + EXPECT_THAT(payload.subview(0, kLengthFieldLength), + ElementsAre(0x1, 0x23)); // Size. + EXPECT_THAT(payload.subview(kLengthFieldLength), ElementsAreArray(nalus[2])); +} + +TEST(RtpPacketizerH264Test, SingleNalUnitModeHasNoStapA) { + // This is the same setup as for the StapA test. + rtc::Buffer frame = CreateFrame({2, 2, 0x123}); + + RtpPacketizerH264 packetizer(frame, kNoLimits, + H264PacketizationMode::SingleNalUnit); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + // The three fragments should be returned as three packets. + ASSERT_THAT(packets, SizeIs(3)); + EXPECT_EQ(packets[0].payload_size(), 2u); + EXPECT_EQ(packets[1].payload_size(), 2u); + EXPECT_EQ(packets[2].payload_size(), 0x123u); +} + +TEST(RtpPacketizerH264Test, StapARespectsFirstPacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1000; + limits.first_packet_reduction_len = 100; + const size_t kFirstFragmentSize = + limits.max_payload_len - limits.first_packet_reduction_len; + rtc::Buffer nalus[] = {GenerateNalUnit(/*size=*/kFirstFragmentSize), + GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/2)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(2)); + // Expect 1st packet is single nalu. + EXPECT_THAT(packets[0].payload(), ElementsAreArray(nalus[0])); + // Expect 2nd packet is aggregate of last two fragments. + EXPECT_THAT(packets[1].payload(), + ElementsAre(kStapA, // + 0, 2, nalus[1][0], nalus[1][1], // + 0, 2, nalus[2][0], nalus[2][1])); +} + +TEST(RtpPacketizerH264Test, StapARespectsLastPacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1000; + limits.last_packet_reduction_len = 100; + const size_t kLastFragmentSize = + limits.max_payload_len - limits.last_packet_reduction_len; + rtc::Buffer nalus[] = {GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/kLastFragmentSize)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(2)); + // Expect 1st packet is aggregate of 1st two fragments. + EXPECT_THAT(packets[0].payload(), + ElementsAre(kStapA, // + 0, 2, nalus[0][0], nalus[0][1], // + 0, 2, nalus[1][0], nalus[1][1])); + // Expect 2nd packet is single nalu. + EXPECT_THAT(packets[1].payload(), ElementsAreArray(nalus[2])); +} + +TEST(RtpPacketizerH264Test, TooSmallForStapAHeaders) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1000; + const size_t kLastFragmentSize = + limits.max_payload_len - 3 * kLengthFieldLength - 4; + rtc::Buffer nalus[] = {GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/2), + GenerateNalUnit(/*size=*/kLastFragmentSize)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(2)); + // Expect 1st packet is aggregate of 1st two fragments. + EXPECT_THAT(packets[0].payload(), + ElementsAre(kStapA, // + 0, 2, nalus[0][0], nalus[0][1], // + 0, 2, nalus[1][0], nalus[1][1])); + // Expect 2nd packet is single nalu. + EXPECT_THAT(packets[1].payload(), ElementsAreArray(nalus[2])); +} + +// Fragmentation + aggregation. +TEST(RtpPacketizerH264Test, MixedStapAFUA) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + const size_t kFuaPayloadSize = 70; + const size_t kFuaNaluSize = kNalHeaderSize + 2 * kFuaPayloadSize; + const size_t kStapANaluSize = 20; + rtc::Buffer nalus[] = {GenerateNalUnit(kFuaNaluSize), + GenerateNalUnit(kStapANaluSize), + GenerateNalUnit(kStapANaluSize)}; + rtc::Buffer frame = CreateFrame(nalus); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + ASSERT_THAT(packets, SizeIs(3)); + // First expect two FU-A packets. + EXPECT_THAT(packets[0].payload().subview(0, kFuAHeaderSize), + ElementsAre(kFuA, kH264SBit | nalus[0][0])); + EXPECT_THAT( + packets[0].payload().subview(kFuAHeaderSize), + ElementsAreArray(nalus[0].data() + kNalHeaderSize, kFuaPayloadSize)); + + EXPECT_THAT(packets[1].payload().subview(0, kFuAHeaderSize), + ElementsAre(kFuA, kH264EBit | nalus[0][0])); + EXPECT_THAT( + packets[1].payload().subview(kFuAHeaderSize), + ElementsAreArray(nalus[0].data() + kNalHeaderSize + kFuaPayloadSize, + kFuaPayloadSize)); + + // Then expect one STAP-A packet with two nal units. + EXPECT_THAT(packets[2].payload()[0], kStapA); + auto payload = packets[2].payload().subview(kNalHeaderSize); + EXPECT_THAT(payload.subview(0, kLengthFieldLength), + ElementsAre(0, kStapANaluSize)); + EXPECT_THAT(payload.subview(kLengthFieldLength, kStapANaluSize), + ElementsAreArray(nalus[1])); + payload = payload.subview(kLengthFieldLength + kStapANaluSize); + EXPECT_THAT(payload.subview(0, kLengthFieldLength), + ElementsAre(0, kStapANaluSize)); + EXPECT_THAT(payload.subview(kLengthFieldLength), ElementsAreArray(nalus[2])); +} + +TEST(RtpPacketizerH264Test, LastFragmentFitsInSingleButNotLastPacket) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1178; + limits.first_packet_reduction_len = 0; + limits.last_packet_reduction_len = 20; + limits.single_packet_reduction_len = 20; + // Actual sizes, which triggered this bug. + rtc::Buffer frame = CreateFrame({20, 8, 18, 1161}); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + // Last packet has to be of correct size. + // Incorrect implementation might miss this constraint and not split the last + // fragment in two packets. + EXPECT_LE(static_cast<int>(packets.back().payload_size()), + limits.max_payload_len - limits.last_packet_reduction_len); +} + +// Splits frame with payload size `frame_payload_size` without fragmentation, +// Returns sizes of the payloads excluding fua headers. +std::vector<int> TestFua(size_t frame_payload_size, + const RtpPacketizer::PayloadSizeLimits& limits) { + rtc::Buffer nalu[] = {GenerateNalUnit(kNalHeaderSize + frame_payload_size)}; + rtc::Buffer frame = CreateFrame(nalu); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::NonInterleaved); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + EXPECT_GE(packets.size(), 2u); // Single packet indicates it is not FuA. + std::vector<uint16_t> fua_header; + std::vector<int> payload_sizes; + + for (const RtpPacketToSend& packet : packets) { + auto payload = packet.payload(); + EXPECT_GT(payload.size(), kFuAHeaderSize); + fua_header.push_back((payload[0] << 8) | payload[1]); + payload_sizes.push_back(payload.size() - kFuAHeaderSize); + } + + EXPECT_TRUE(fua_header.front() & kH264SBit); + EXPECT_TRUE(fua_header.back() & kH264EBit); + // Clear S and E bits before testing all are duplicating same original header. + fua_header.front() &= ~kH264SBit; + fua_header.back() &= ~kH264EBit; + EXPECT_THAT(fua_header, Each(Eq((kFuA << 8) | nalu[0][0]))); + + return payload_sizes; +} + +// Fragmentation tests. +TEST(RtpPacketizerH264Test, FUAOddSize) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1200; + EXPECT_THAT(TestFua(1200, limits), ElementsAre(600, 600)); +} + +TEST(RtpPacketizerH264Test, FUAWithFirstPacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1200; + limits.first_packet_reduction_len = 4; + limits.single_packet_reduction_len = 4; + EXPECT_THAT(TestFua(1198, limits), ElementsAre(597, 601)); +} + +TEST(RtpPacketizerH264Test, FUAWithLastPacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1200; + limits.last_packet_reduction_len = 4; + limits.single_packet_reduction_len = 4; + EXPECT_THAT(TestFua(1198, limits), ElementsAre(601, 597)); +} + +TEST(RtpPacketizerH264Test, FUAWithSinglePacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1199; + limits.single_packet_reduction_len = 200; + EXPECT_THAT(TestFua(1000, limits), ElementsAre(500, 500)); +} + +TEST(RtpPacketizerH264Test, FUAEvenSize) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1200; + EXPECT_THAT(TestFua(1201, limits), ElementsAre(600, 601)); +} + +TEST(RtpPacketizerH264Test, FUARounding) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1448; + EXPECT_THAT(TestFua(10123, limits), + ElementsAre(1265, 1265, 1265, 1265, 1265, 1266, 1266, 1266)); +} + +TEST(RtpPacketizerH264Test, FUABig) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 1200; + // Generate 10 full sized packets, leave room for FU-A headers. + EXPECT_THAT( + TestFua(10 * (1200 - kFuAHeaderSize), limits), + ElementsAre(1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198, 1198)); +} + +TEST(RtpPacketizerH264Test, RejectsOverlongDataInPacketizationMode0) { + RtpPacketizer::PayloadSizeLimits limits; + rtc::Buffer frame = CreateFrame({kMaxPayloadSize + 1}); + + RtpPacketizerH264 packetizer(frame, limits, + H264PacketizationMode::SingleNalUnit); + std::vector<RtpPacketToSend> packets = FetchAllPackets(&packetizer); + + EXPECT_THAT(packets, IsEmpty()); +} +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_unittest.cc new file mode 100644 index 0000000000..53264c6609 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_unittest.cc @@ -0,0 +1,283 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format.h" + +#include <memory> +#include <numeric> + +#include "absl/algorithm/container.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::Gt; +using ::testing::IsEmpty; +using ::testing::Le; +using ::testing::Not; +using ::testing::SizeIs; + +// Calculate difference between largest and smallest packets respecting sizes +// adjustement provided by limits, +// i.e. last packet expected to be smaller than 'average' by reduction_len. +int EffectivePacketsSizeDifference( + std::vector<int> sizes, + const RtpPacketizer::PayloadSizeLimits& limits) { + // Account for larger last packet header. + sizes.back() += limits.last_packet_reduction_len; + + auto minmax = absl::c_minmax_element(sizes); + // MAX-MIN + return *minmax.second - *minmax.first; +} + +int Sum(const std::vector<int>& sizes) { + return absl::c_accumulate(sizes, 0); +} + +TEST(RtpPacketizerSplitAboutEqually, AllPacketsAreEqualSumToPayloadLen) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.last_packet_reduction_len = 2; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(13, limits); + + EXPECT_THAT(Sum(payload_sizes), 13); +} + +TEST(RtpPacketizerSplitAboutEqually, AllPacketsAreEqualRespectsMaxPayloadSize) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.last_packet_reduction_len = 2; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(13, limits); + + EXPECT_THAT(payload_sizes, Each(Le(limits.max_payload_len))); +} + +TEST(RtpPacketizerSplitAboutEqually, + AllPacketsAreEqualRespectsFirstPacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.first_packet_reduction_len = 2; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(13, limits); + + ASSERT_THAT(payload_sizes, Not(IsEmpty())); + EXPECT_EQ(payload_sizes.front() + limits.first_packet_reduction_len, + limits.max_payload_len); +} + +TEST(RtpPacketizerSplitAboutEqually, + AllPacketsAreEqualRespectsLastPacketReductionLength) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.last_packet_reduction_len = 2; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(13, limits); + + ASSERT_THAT(payload_sizes, Not(IsEmpty())); + EXPECT_LE(payload_sizes.back() + limits.last_packet_reduction_len, + limits.max_payload_len); +} + +TEST(RtpPacketizerSplitAboutEqually, AllPacketsAreEqualInSize) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.last_packet_reduction_len = 2; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(13, limits); + + EXPECT_EQ(EffectivePacketsSizeDifference(payload_sizes, limits), 0); +} + +TEST(RtpPacketizerSplitAboutEqually, + AllPacketsAreEqualGeneratesMinimumNumberOfPackets) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.last_packet_reduction_len = 2; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(13, limits); + // Computed by hand. 3 packets would have exactly capacity 3*5-2=13 + // (max length - for each packet minus last packet reduction). + EXPECT_THAT(payload_sizes, SizeIs(3)); +} + +TEST(RtpPacketizerSplitAboutEqually, SomePacketsAreSmallerSumToPayloadLen) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 7; + limits.last_packet_reduction_len = 5; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(28, limits); + + EXPECT_THAT(Sum(payload_sizes), 28); +} + +TEST(RtpPacketizerSplitAboutEqually, + SomePacketsAreSmallerRespectsMaxPayloadSize) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 7; + limits.last_packet_reduction_len = 5; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(28, limits); + + EXPECT_THAT(payload_sizes, Each(Le(limits.max_payload_len))); +} + +TEST(RtpPacketizerSplitAboutEqually, + SomePacketsAreSmallerRespectsFirstPacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 7; + limits.first_packet_reduction_len = 5; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(28, limits); + + EXPECT_LE(payload_sizes.front() + limits.first_packet_reduction_len, + limits.max_payload_len); +} + +TEST(RtpPacketizerSplitAboutEqually, + SomePacketsAreSmallerRespectsLastPacketReductionLength) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 7; + limits.last_packet_reduction_len = 5; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(28, limits); + + EXPECT_LE(payload_sizes.back(), + limits.max_payload_len - limits.last_packet_reduction_len); +} + +TEST(RtpPacketizerSplitAboutEqually, + SomePacketsAreSmallerPacketsAlmostEqualInSize) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 7; + limits.last_packet_reduction_len = 5; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(28, limits); + + EXPECT_LE(EffectivePacketsSizeDifference(payload_sizes, limits), 1); +} + +TEST(RtpPacketizerSplitAboutEqually, + SomePacketsAreSmallerGeneratesMinimumNumberOfPackets) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 7; + limits.last_packet_reduction_len = 5; + + std::vector<int> payload_sizes = RtpPacketizer::SplitAboutEqually(24, limits); + // Computed by hand. 4 packets would have capacity 4*7-5=23 (max length - + // for each packet minus last packet reduction). + // 5 packets is enough for kPayloadSize. + EXPECT_THAT(payload_sizes, SizeIs(5)); +} + +TEST(RtpPacketizerSplitAboutEqually, GivesNonZeroPayloadLengthEachPacket) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 600; + limits.first_packet_reduction_len = 500; + limits.last_packet_reduction_len = 550; + + // Naive implementation would split 1450 payload + 1050 reduction bytes into 5 + // packets 500 bytes each, thus leaving first packet zero bytes and even less + // to last packet. + std::vector<int> payload_sizes = + RtpPacketizer::SplitAboutEqually(1450, limits); + + EXPECT_EQ(Sum(payload_sizes), 1450); + EXPECT_THAT(payload_sizes, Each(Gt(0))); +} + +TEST(RtpPacketizerSplitAboutEqually, + IgnoresFirstAndLastPacketReductionWhenPayloadFitsIntoSinglePacket) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 30; + limits.first_packet_reduction_len = 29; + limits.last_packet_reduction_len = 29; + limits.single_packet_reduction_len = 10; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), ElementsAre(20)); +} + +TEST(RtpPacketizerSplitAboutEqually, + OnePacketWhenExtraSpaceIsEnoughForSinglePacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 30; + limits.single_packet_reduction_len = 10; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), ElementsAre(20)); +} + +TEST(RtpPacketizerSplitAboutEqually, + TwoPacketsWhenExtraSpaceIsTooSmallForSinglePacketReduction) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 29; + limits.first_packet_reduction_len = 3; + limits.last_packet_reduction_len = 1; + limits.single_packet_reduction_len = 10; + + // First packet needs two more extra bytes compared to last one, + // so should have two less payload bytes. + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), ElementsAre(9, 11)); +} + +TEST(RtpPacketizerSplitAboutEqually, RejectsZeroMaxPayloadLen) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 0; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), IsEmpty()); +} + +TEST(RtpPacketizerSplitAboutEqually, RejectsZeroFirstPacketLen) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.first_packet_reduction_len = 5; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), IsEmpty()); +} + +TEST(RtpPacketizerSplitAboutEqually, RejectsZeroLastPacketLen) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 5; + limits.last_packet_reduction_len = 5; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), IsEmpty()); +} + +TEST(RtpPacketizerSplitAboutEqually, CantPutSinglePayloadByteInTwoPackets) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 10; + limits.single_packet_reduction_len = 10; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(1, limits), IsEmpty()); +} + +TEST(RtpPacketizerSplitAboutEqually, CanPutTwoPayloadBytesInTwoPackets) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 10; + limits.single_packet_reduction_len = 10; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(2, limits), ElementsAre(1, 1)); +} + +TEST(RtpPacketizerSplitAboutEqually, CanPutSinglePayloadByteInOnePacket) { + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 11; + limits.single_packet_reduction_len = 10; + + EXPECT_THAT(RtpPacketizer::SplitAboutEqually(1, limits), ElementsAre(1)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.cc new file mode 100644 index 0000000000..f5c7f2ee29 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.cc @@ -0,0 +1,100 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format_video_generic.h" + +#include <string.h> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +static const size_t kGenericHeaderLength = 1; +static const size_t kExtendedHeaderLength = 2; + +RtpPacketizerGeneric::RtpPacketizerGeneric( + rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + const RTPVideoHeader& rtp_video_header) + : remaining_payload_(payload) { + BuildHeader(rtp_video_header); + + limits.max_payload_len -= header_size_; + payload_sizes_ = SplitAboutEqually(payload.size(), limits); + current_packet_ = payload_sizes_.begin(); +} + +RtpPacketizerGeneric::RtpPacketizerGeneric( + rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits) + : header_size_(0), remaining_payload_(payload) { + payload_sizes_ = SplitAboutEqually(payload.size(), limits); + current_packet_ = payload_sizes_.begin(); +} + +RtpPacketizerGeneric::~RtpPacketizerGeneric() = default; + +size_t RtpPacketizerGeneric::NumPackets() const { + return payload_sizes_.end() - current_packet_; +} + +bool RtpPacketizerGeneric::NextPacket(RtpPacketToSend* packet) { + RTC_DCHECK(packet); + if (current_packet_ == payload_sizes_.end()) + return false; + + size_t next_packet_payload_len = *current_packet_; + + uint8_t* out_ptr = + packet->AllocatePayload(header_size_ + next_packet_payload_len); + RTC_CHECK(out_ptr); + + if (header_size_ > 0) { + memcpy(out_ptr, header_, header_size_); + // Remove first-packet bit, following packets are intermediate. + header_[0] &= ~RtpFormatVideoGeneric::kFirstPacketBit; + } + + memcpy(out_ptr + header_size_, remaining_payload_.data(), + next_packet_payload_len); + + remaining_payload_ = remaining_payload_.subview(next_packet_payload_len); + + ++current_packet_; + + // Packets left to produce and data left to split should end at the same time. + RTC_DCHECK_EQ(current_packet_ == payload_sizes_.end(), + remaining_payload_.empty()); + + packet->SetMarker(remaining_payload_.empty()); + return true; +} + +void RtpPacketizerGeneric::BuildHeader(const RTPVideoHeader& rtp_video_header) { + header_size_ = kGenericHeaderLength; + header_[0] = RtpFormatVideoGeneric::kFirstPacketBit; + if (rtp_video_header.frame_type == VideoFrameType::kVideoFrameKey) { + header_[0] |= RtpFormatVideoGeneric::kKeyFrameBit; + } + if (const auto* generic_header = absl::get_if<RTPVideoHeaderLegacyGeneric>( + &rtp_video_header.video_type_header)) { + // Store bottom 15 bits of the picture id. Only 15 bits are used for + // compatibility with other packetizer implemenetations. + uint16_t picture_id = generic_header->picture_id; + header_[0] |= RtpFormatVideoGeneric::kExtendedHeaderBit; + header_[1] = (picture_id >> 8) & 0x7F; + header_[2] = picture_id & 0xFF; + header_size_ += kExtendedHeaderLength; + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h new file mode 100644 index 0000000000..fd44bd1980 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h @@ -0,0 +1,71 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VIDEO_GENERIC_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VIDEO_GENERIC_H_ + +#include <stdint.h> + +#include <vector> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format.h" + +namespace webrtc { + +class RtpPacketToSend; +struct RTPVideoHeader; + +namespace RtpFormatVideoGeneric { +static const uint8_t kKeyFrameBit = 0x01; +static const uint8_t kFirstPacketBit = 0x02; +// If this bit is set, there will be an extended header contained in this +// packet. This was added later so old clients will not send this. +static const uint8_t kExtendedHeaderBit = 0x04; +} // namespace RtpFormatVideoGeneric + +class RtpPacketizerGeneric : public RtpPacketizer { + public: + // Initialize with payload from encoder. + // The payload_data must be exactly one encoded generic frame. + // Packets returned by `NextPacket` will contain the generic payload header. + RtpPacketizerGeneric(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + const RTPVideoHeader& rtp_video_header); + // Initialize with payload from encoder. + // The payload_data must be exactly one encoded generic frame. + // Packets returned by `NextPacket` will contain raw payload without the + // generic payload header. + RtpPacketizerGeneric(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits); + + ~RtpPacketizerGeneric() override; + + RtpPacketizerGeneric(const RtpPacketizerGeneric&) = delete; + RtpPacketizerGeneric& operator=(const RtpPacketizerGeneric&) = delete; + + size_t NumPackets() const override; + + // Get the next payload. + // Write payload and set marker bit of the `packet`. + // Returns true on success, false otherwise. + bool NextPacket(RtpPacketToSend* packet) override; + + private: + // Fills header_ and header_size_ members. + void BuildHeader(const RTPVideoHeader& rtp_video_header); + + uint8_t header_[3]; + size_t header_size_; + rtc::ArrayView<const uint8_t> remaining_payload_; + std::vector<int> payload_sizes_; + std::vector<int>::const_iterator current_packet_; +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VIDEO_GENERIC_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic_unittest.cc new file mode 100644 index 0000000000..d83c3b03c9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic_unittest.cc @@ -0,0 +1,172 @@ +/* + * 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/rtp_rtcp/source/rtp_format_video_generic.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <vector> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Contains; +using ::testing::Each; +using ::testing::ElementsAreArray; +using ::testing::Le; + +constexpr RtpPacketizer::PayloadSizeLimits kNoSizeLimits; + +std::vector<int> NextPacketFillPayloadSizes(RtpPacketizerGeneric* packetizer) { + RtpPacketToSend packet(nullptr); + std::vector<int> result; + while (packetizer->NextPacket(&packet)) { + result.push_back(packet.payload_size()); + } + return result; +} + +TEST(RtpPacketizerVideoGeneric, RespectsMaxPayloadSize) { + const size_t kPayloadSize = 50; + const uint8_t kPayload[kPayloadSize] = {}; + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + RtpPacketizerGeneric packetizer(kPayload, limits, RTPVideoHeader()); + + std::vector<int> payload_sizes = NextPacketFillPayloadSizes(&packetizer); + + EXPECT_THAT(payload_sizes, Each(Le(limits.max_payload_len))); +} + +TEST(RtpPacketizerVideoGeneric, UsesMaxPayloadSize) { + const size_t kPayloadSize = 50; + const uint8_t kPayload[kPayloadSize] = {}; + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + RtpPacketizerGeneric packetizer(kPayload, limits, RTPVideoHeader()); + + std::vector<int> payload_sizes = NextPacketFillPayloadSizes(&packetizer); + + // With kPayloadSize > max_payload_len^2, there should be packets that use + // all the payload, otherwise it is possible to use less packets. + EXPECT_THAT(payload_sizes, Contains(limits.max_payload_len)); +} + +TEST(RtpPacketizerVideoGeneric, WritesExtendedHeaderWhenPictureIdIsSet) { + const size_t kPayloadSize = 13; + const uint8_t kPayload[kPayloadSize] = {}; + + RTPVideoHeader rtp_video_header; + rtp_video_header.video_type_header.emplace<RTPVideoHeaderLegacyGeneric>() + .picture_id = 37; + rtp_video_header.frame_type = VideoFrameType::kVideoFrameKey; + RtpPacketizerGeneric packetizer(kPayload, kNoSizeLimits, rtp_video_header); + + RtpPacketToSend packet(nullptr); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + + rtc::ArrayView<const uint8_t> payload = packet.payload(); + EXPECT_EQ(payload.size(), 3 + kPayloadSize); + EXPECT_TRUE(payload[0] & 0x04); // Extended header bit is set. + // Frame id is 37. + EXPECT_EQ(0u, payload[1]); + EXPECT_EQ(37u, payload[2]); +} + +TEST(RtpPacketizerVideoGeneric, RespectsMaxPayloadSizeWithExtendedHeader) { + const int kPayloadSize = 50; + const uint8_t kPayload[kPayloadSize] = {}; + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + RTPVideoHeader rtp_video_header; + rtp_video_header.video_type_header.emplace<RTPVideoHeaderLegacyGeneric>() + .picture_id = 37; + RtpPacketizerGeneric packetizer(kPayload, limits, rtp_video_header); + + std::vector<int> payload_sizes = NextPacketFillPayloadSizes(&packetizer); + + EXPECT_THAT(payload_sizes, Each(Le(limits.max_payload_len))); +} + +TEST(RtpPacketizerVideoGeneric, UsesMaxPayloadSizeWithExtendedHeader) { + const int kPayloadSize = 50; + const uint8_t kPayload[kPayloadSize] = {}; + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + RTPVideoHeader rtp_video_header; + rtp_video_header.video_type_header.emplace<RTPVideoHeaderLegacyGeneric>() + .picture_id = 37; + RtpPacketizerGeneric packetizer(kPayload, limits, rtp_video_header); + std::vector<int> payload_sizes = NextPacketFillPayloadSizes(&packetizer); + + // With kPayloadSize > max_payload_len^2, there should be packets that use + // all the payload, otherwise it is possible to use less packets. + EXPECT_THAT(payload_sizes, Contains(limits.max_payload_len)); +} + +TEST(RtpPacketizerVideoGeneric, FrameIdOver15bitsWrapsAround) { + const int kPayloadSize = 13; + const uint8_t kPayload[kPayloadSize] = {}; + + RTPVideoHeader rtp_video_header; + rtp_video_header.video_type_header.emplace<RTPVideoHeaderLegacyGeneric>() + .picture_id = 0x8137; + rtp_video_header.frame_type = VideoFrameType::kVideoFrameKey; + RtpPacketizerGeneric packetizer(kPayload, kNoSizeLimits, rtp_video_header); + + RtpPacketToSend packet(nullptr); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + + rtc::ArrayView<const uint8_t> payload = packet.payload(); + EXPECT_TRUE(payload[0] & 0x04); // Extended header bit is set. + // Frame id is 0x137. + EXPECT_EQ(0x01u, payload[1]); + EXPECT_EQ(0x37u, payload[2]); +} + +TEST(RtpPacketizerVideoGeneric, NoFrameIdDoesNotWriteExtendedHeader) { + const int kPayloadSize = 13; + const uint8_t kPayload[kPayloadSize] = {}; + + RtpPacketizerGeneric packetizer(kPayload, kNoSizeLimits, RTPVideoHeader()); + + RtpPacketToSend packet(nullptr); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + + rtc::ArrayView<const uint8_t> payload = packet.payload(); + EXPECT_FALSE(payload[0] & 0x04); +} + +TEST(RtpPacketizerVideoGeneric, DoesNotWriteHeaderForRawPayload) { + const uint8_t kPayload[] = {0x05, 0x25, 0x52}; + + RtpPacketizerGeneric packetizer(kPayload, kNoSizeLimits); + + RtpPacketToSend packet(nullptr); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + + rtc::ArrayView<const uint8_t> payload = packet.payload(); + EXPECT_THAT(payload, ElementsAreArray(kPayload)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc new file mode 100644 index 0000000000..ae5f4e50a4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc @@ -0,0 +1,169 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format_vp8.h" + +#include <stdint.h> +#include <string.h> // memcpy + +#include <vector> + +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h" +#include "modules/video_coding/codecs/interface/common_constants.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +constexpr int kXBit = 0x80; +constexpr int kNBit = 0x20; +constexpr int kSBit = 0x10; +constexpr int kKeyIdxField = 0x1F; +constexpr int kIBit = 0x80; +constexpr int kLBit = 0x40; +constexpr int kTBit = 0x20; +constexpr int kKBit = 0x10; +constexpr int kYBit = 0x20; + +bool ValidateHeader(const RTPVideoHeaderVP8& hdr_info) { + if (hdr_info.pictureId != kNoPictureId) { + RTC_DCHECK_GE(hdr_info.pictureId, 0); + RTC_DCHECK_LE(hdr_info.pictureId, 0x7FFF); + } + if (hdr_info.tl0PicIdx != kNoTl0PicIdx) { + RTC_DCHECK_GE(hdr_info.tl0PicIdx, 0); + RTC_DCHECK_LE(hdr_info.tl0PicIdx, 0xFF); + } + if (hdr_info.temporalIdx != kNoTemporalIdx) { + RTC_DCHECK_GE(hdr_info.temporalIdx, 0); + RTC_DCHECK_LE(hdr_info.temporalIdx, 3); + } else { + RTC_DCHECK(!hdr_info.layerSync); + } + if (hdr_info.keyIdx != kNoKeyIdx) { + RTC_DCHECK_GE(hdr_info.keyIdx, 0); + RTC_DCHECK_LE(hdr_info.keyIdx, 0x1F); + } + return true; +} + +} // namespace + +RtpPacketizerVp8::RtpPacketizerVp8(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + const RTPVideoHeaderVP8& hdr_info) + : hdr_(BuildHeader(hdr_info)), remaining_payload_(payload) { + limits.max_payload_len -= hdr_.size(); + payload_sizes_ = SplitAboutEqually(payload.size(), limits); + current_packet_ = payload_sizes_.begin(); +} + +RtpPacketizerVp8::~RtpPacketizerVp8() = default; + +size_t RtpPacketizerVp8::NumPackets() const { + return payload_sizes_.end() - current_packet_; +} + +bool RtpPacketizerVp8::NextPacket(RtpPacketToSend* packet) { + RTC_DCHECK(packet); + if (current_packet_ == payload_sizes_.end()) { + return false; + } + + size_t packet_payload_len = *current_packet_; + ++current_packet_; + + uint8_t* buffer = packet->AllocatePayload(hdr_.size() + packet_payload_len); + RTC_CHECK(buffer); + + memcpy(buffer, hdr_.data(), hdr_.size()); + memcpy(buffer + hdr_.size(), remaining_payload_.data(), packet_payload_len); + + remaining_payload_ = remaining_payload_.subview(packet_payload_len); + hdr_[0] &= (~kSBit); // Clear 'Start of partition' bit. + packet->SetMarker(current_packet_ == payload_sizes_.end()); + return true; +} + +RtpPacketizerVp8::RawHeader RtpPacketizerVp8::BuildHeader( + const RTPVideoHeaderVP8& header) { + // VP8 payload descriptor + // https://datatracker.ietf.org/doc/html/rfc7741#section-4.2 + // + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // |X|R|N|S|R| PID | (REQUIRED) + // +-+-+-+-+-+-+-+-+ + // X: |I|L|T|K| RSV | (OPTIONAL) + // +-+-+-+-+-+-+-+-+ + // I: |M| PictureID | (OPTIONAL) + // +-+-+-+-+-+-+-+-+ + // | PictureID | + // +-+-+-+-+-+-+-+-+ + // L: | TL0PICIDX | (OPTIONAL) + // +-+-+-+-+-+-+-+-+ + // T/K: |TID|Y| KEYIDX | (OPTIONAL) + // +-+-+-+-+-+-+-+-+ + RTC_DCHECK(ValidateHeader(header)); + + RawHeader result; + bool tid_present = header.temporalIdx != kNoTemporalIdx; + bool keyid_present = header.keyIdx != kNoKeyIdx; + bool tl0_pid_present = header.tl0PicIdx != kNoTl0PicIdx; + bool pid_present = header.pictureId != kNoPictureId; + uint8_t x_field = 0; + if (pid_present) + x_field |= kIBit; + if (tl0_pid_present) + x_field |= kLBit; + if (tid_present) + x_field |= kTBit; + if (keyid_present) + x_field |= kKBit; + + uint8_t flags = 0; + if (x_field != 0) + flags |= kXBit; + if (header.nonReference) + flags |= kNBit; + // Create header as first packet in the frame. NextPacket() will clear it + // after first use. + flags |= kSBit; + result.push_back(flags); + if (x_field == 0) { + return result; + } + result.push_back(x_field); + if (pid_present) { + const uint16_t pic_id = static_cast<uint16_t>(header.pictureId); + result.push_back(0x80 | ((pic_id >> 8) & 0x7F)); + result.push_back(pic_id & 0xFF); + } + if (tl0_pid_present) { + result.push_back(header.tl0PicIdx); + } + if (tid_present || keyid_present) { + uint8_t data_field = 0; + if (tid_present) { + data_field |= header.temporalIdx << 6; + if (header.layerSync) + data_field |= kYBit; + } + if (keyid_present) { + data_field |= (header.keyIdx & kKeyIdxField); + } + result.push_back(data_field); + } + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.h new file mode 100644 index 0000000000..d1f569a946 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +/* + * This file contains the declaration of the VP8 packetizer class. + * A packetizer object is created for each encoded video frame. The + * constructor is called with the payload data and size, + * together with the fragmentation information and a packetizer mode + * of choice. Alternatively, if no fragmentation info is available, the + * second constructor can be used with only payload data and size; in that + * case the mode kEqualSize is used. + * + * After creating the packetizer, the method NextPacket is called + * repeatedly to get all packets for the frame. The method returns + * false as long as there are more packets left to fetch. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_H_ + +#include <stddef.h> + +#include <cstdint> +#include <vector> + +#include "absl/container/inlined_vector.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/video_coding/codecs/vp8/include/vp8_globals.h" + +namespace webrtc { + +// Packetizer for VP8. +class RtpPacketizerVp8 : public RtpPacketizer { + public: + // Initialize with payload from encoder. + // The payload_data must be exactly one encoded VP8 frame. + RtpPacketizerVp8(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + const RTPVideoHeaderVP8& hdr_info); + + ~RtpPacketizerVp8() override; + + RtpPacketizerVp8(const RtpPacketizerVp8&) = delete; + RtpPacketizerVp8& operator=(const RtpPacketizerVp8&) = delete; + + size_t NumPackets() const override; + + // Get the next payload with VP8 payload header. + // Write payload and set marker bit of the `packet`. + // Returns true on success, false otherwise. + bool NextPacket(RtpPacketToSend* packet) override; + + private: + // VP8 header can use up to 6 bytes. + using RawHeader = absl::InlinedVector<uint8_t, 6>; + static RawHeader BuildHeader(const RTPVideoHeaderVP8& header); + + RawHeader hdr_; + rtc::ArrayView<const uint8_t> remaining_payload_; + std::vector<int> payload_sizes_; + std::vector<int>::const_iterator current_packet_; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.cc new file mode 100644 index 0000000000..0088ff8f31 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.cc @@ -0,0 +1,174 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h" + +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "test/gmock.h" +#include "test/gtest.h" + +// VP8 payload descriptor +// https://datatracker.ietf.org/doc/html/rfc7741#section-4.2 +// +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|R| PID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T|K| RSV | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: |M| PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// | PictureID | +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T/K: |TID|Y| KEYIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ + +namespace webrtc { +namespace { + +using ::testing::ElementsAreArray; + +constexpr RtpPacketToSend::ExtensionManager* kNoExtensions = nullptr; + +int Bit(uint8_t byte, int position) { + return (byte >> position) & 0x01; +} + +} // namespace + +RtpFormatVp8TestHelper::RtpFormatVp8TestHelper(const RTPVideoHeaderVP8* hdr, + size_t payload_len) + : hdr_info_(hdr), payload_(payload_len) { + for (size_t i = 0; i < payload_.size(); ++i) { + payload_[i] = i; + } +} + +RtpFormatVp8TestHelper::~RtpFormatVp8TestHelper() = default; + +void RtpFormatVp8TestHelper::GetAllPacketsAndCheck( + RtpPacketizerVp8* packetizer, + rtc::ArrayView<const size_t> expected_sizes) { + EXPECT_EQ(packetizer->NumPackets(), expected_sizes.size()); + const uint8_t* data_ptr = payload_.begin(); + RtpPacketToSend packet(kNoExtensions); + for (size_t i = 0; i < expected_sizes.size(); ++i) { + EXPECT_TRUE(packetizer->NextPacket(&packet)); + auto rtp_payload = packet.payload(); + EXPECT_EQ(rtp_payload.size(), expected_sizes[i]); + + int payload_offset = CheckHeader(rtp_payload, /*first=*/i == 0); + // Verify that the payload (i.e., after the headers) of the packet is + // identical to the expected (as found in data_ptr). + auto vp8_payload = rtp_payload.subview(payload_offset); + ASSERT_GE(payload_.end() - data_ptr, static_cast<int>(vp8_payload.size())); + EXPECT_THAT(vp8_payload, ElementsAreArray(data_ptr, vp8_payload.size())); + data_ptr += vp8_payload.size(); + } + EXPECT_EQ(payload_.end() - data_ptr, 0); +} + +int RtpFormatVp8TestHelper::CheckHeader(rtc::ArrayView<const uint8_t> buffer, + bool first) { + int x_bit = Bit(buffer[0], 7); + EXPECT_EQ(Bit(buffer[0], 6), 0); // Reserved. + EXPECT_EQ(Bit(buffer[0], 5), hdr_info_->nonReference ? 1 : 0); + EXPECT_EQ(Bit(buffer[0], 4), first ? 1 : 0); + EXPECT_EQ(buffer[0] & 0x0f, 0); // RtpPacketizerVp8 always uses partition 0. + + int payload_offset = 1; + if (hdr_info_->pictureId != kNoPictureId || + hdr_info_->temporalIdx != kNoTemporalIdx || + hdr_info_->tl0PicIdx != kNoTl0PicIdx || hdr_info_->keyIdx != kNoKeyIdx) { + EXPECT_EQ(x_bit, 1); + ++payload_offset; + CheckPictureID(buffer, &payload_offset); + CheckTl0PicIdx(buffer, &payload_offset); + CheckTIDAndKeyIdx(buffer, &payload_offset); + EXPECT_EQ(buffer[1] & 0x07, 0); // Reserved. + } else { + EXPECT_EQ(x_bit, 0); + } + + return payload_offset; +} + +// Verify that the I bit and the PictureID field are both set in accordance +// with the information in hdr_info_->pictureId. +void RtpFormatVp8TestHelper::CheckPictureID( + rtc::ArrayView<const uint8_t> buffer, + int* offset) { + int i_bit = Bit(buffer[1], 7); + if (hdr_info_->pictureId != kNoPictureId) { + EXPECT_EQ(i_bit, 1); + int two_byte_picture_id = Bit(buffer[*offset], 7); + EXPECT_EQ(two_byte_picture_id, 1); + EXPECT_EQ(buffer[*offset] & 0x7F, (hdr_info_->pictureId >> 8) & 0x7F); + EXPECT_EQ(buffer[(*offset) + 1], hdr_info_->pictureId & 0xFF); + (*offset) += 2; + } else { + EXPECT_EQ(i_bit, 0); + } +} + +// Verify that the L bit and the TL0PICIDX field are both set in accordance +// with the information in hdr_info_->tl0PicIdx. +void RtpFormatVp8TestHelper::CheckTl0PicIdx( + rtc::ArrayView<const uint8_t> buffer, + int* offset) { + int l_bit = Bit(buffer[1], 6); + if (hdr_info_->tl0PicIdx != kNoTl0PicIdx) { + EXPECT_EQ(l_bit, 1); + EXPECT_EQ(buffer[*offset], hdr_info_->tl0PicIdx); + ++*offset; + } else { + EXPECT_EQ(l_bit, 0); + } +} + +// Verify that the T bit and the TL0PICIDX field, and the K bit and KEYIDX +// field are all set in accordance with the information in +// hdr_info_->temporalIdx and hdr_info_->keyIdx, respectively. +void RtpFormatVp8TestHelper::CheckTIDAndKeyIdx( + rtc::ArrayView<const uint8_t> buffer, + int* offset) { + int t_bit = Bit(buffer[1], 5); + int k_bit = Bit(buffer[1], 4); + if (hdr_info_->temporalIdx == kNoTemporalIdx && + hdr_info_->keyIdx == kNoKeyIdx) { + EXPECT_EQ(t_bit, 0); + EXPECT_EQ(k_bit, 0); + return; + } + int temporal_id = (buffer[*offset] & 0xC0) >> 6; + int y_bit = Bit(buffer[*offset], 5); + int key_idx = buffer[*offset] & 0x1f; + if (hdr_info_->temporalIdx != kNoTemporalIdx) { + EXPECT_EQ(t_bit, 1); + EXPECT_EQ(temporal_id, hdr_info_->temporalIdx); + EXPECT_EQ(y_bit, hdr_info_->layerSync ? 1 : 0); + } else { + EXPECT_EQ(t_bit, 0); + EXPECT_EQ(temporal_id, 0); + EXPECT_EQ(y_bit, 0); + } + if (hdr_info_->keyIdx != kNoKeyIdx) { + EXPECT_EQ(k_bit, 1); + EXPECT_EQ(key_idx, hdr_info_->keyIdx); + } else { + EXPECT_EQ(k_bit, 0); + EXPECT_EQ(key_idx, 0); + } + ++*offset; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h new file mode 100644 index 0000000000..3ecaa476da --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +// This file contains the class RtpFormatVp8TestHelper. The class is +// responsible for setting up a fake VP8 bitstream according to the +// RTPVideoHeaderVP8 header. The packetizer can then be provided to this helper +// class, which will then extract all packets and compare to the expected +// outcome. + +#ifndef MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_TEST_HELPER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_TEST_HELPER_H_ + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format_vp8.h" +#include "modules/video_coding/codecs/vp8/include/vp8_globals.h" +#include "rtc_base/buffer.h" + +namespace webrtc { + +class RtpFormatVp8TestHelper { + public: + RtpFormatVp8TestHelper(const RTPVideoHeaderVP8* hdr, size_t payload_len); + ~RtpFormatVp8TestHelper(); + + RtpFormatVp8TestHelper(const RtpFormatVp8TestHelper&) = delete; + RtpFormatVp8TestHelper& operator=(const RtpFormatVp8TestHelper&) = delete; + + void GetAllPacketsAndCheck(RtpPacketizerVp8* packetizer, + rtc::ArrayView<const size_t> expected_sizes); + + rtc::ArrayView<const uint8_t> payload() const { return payload_; } + size_t payload_size() const { return payload_.size(); } + + private: + // Returns header size, i.e. payload offset. + int CheckHeader(rtc::ArrayView<const uint8_t> rtp_payload, bool first); + void CheckPictureID(rtc::ArrayView<const uint8_t> rtp_payload, int* offset); + void CheckTl0PicIdx(rtc::ArrayView<const uint8_t> rtp_payload, int* offset); + void CheckTIDAndKeyIdx(rtc::ArrayView<const uint8_t> rtp_payload, + int* offset); + void CheckPayload(const uint8_t* data_ptr); + + const RTPVideoHeaderVP8* const hdr_info_; + rtc::Buffer payload_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP8_TEST_HELPER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc new file mode 100644 index 0000000000..7934ff8ea9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc @@ -0,0 +1,115 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_format_vp8.h" + +#include <memory> + +#include "modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +constexpr RtpPacketizer::PayloadSizeLimits kNoSizeLimits; + +TEST(RtpPacketizerVp8Test, ResultPacketsAreAlmostEqualSize) { + RTPVideoHeaderVP8 hdr_info; + hdr_info.InitRTPVideoHeaderVP8(); + hdr_info.pictureId = 200; + RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/30); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 12; // Small enough to produce 4 packets. + RtpPacketizerVp8 packetizer(helper.payload(), limits, hdr_info); + + const size_t kExpectedSizes[] = {11, 11, 12, 12}; + helper.GetAllPacketsAndCheck(&packetizer, kExpectedSizes); +} + +TEST(RtpPacketizerVp8Test, EqualSizeWithLastPacketReduction) { + RTPVideoHeaderVP8 hdr_info; + hdr_info.InitRTPVideoHeaderVP8(); + hdr_info.pictureId = 200; + RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/43); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 15; // Small enough to produce 5 packets. + limits.last_packet_reduction_len = 5; + RtpPacketizerVp8 packetizer(helper.payload(), limits, hdr_info); + + // Calculated by hand. VP8 payload descriptors are 4 byte each. 5 packets is + // minimum possible to fit 43 payload bytes into packets with capacity of + // 15 - 4 = 11 and leave 5 free bytes in the last packet. All packets are + // almost equal in size, even last packet if counted with free space (which + // will be filled up the stack by extra long RTP header). + const size_t kExpectedSizes[] = {13, 13, 14, 14, 9}; + helper.GetAllPacketsAndCheck(&packetizer, kExpectedSizes); +} + +// Verify that non-reference bit is set. +TEST(RtpPacketizerVp8Test, NonReferenceBit) { + RTPVideoHeaderVP8 hdr_info; + hdr_info.InitRTPVideoHeaderVP8(); + hdr_info.nonReference = true; + RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/30); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 25; // Small enough to produce two packets. + RtpPacketizerVp8 packetizer(helper.payload(), limits, hdr_info); + + const size_t kExpectedSizes[] = {16, 16}; + helper.GetAllPacketsAndCheck(&packetizer, kExpectedSizes); +} + +// Verify Tl0PicIdx and TID fields, and layerSync bit. +TEST(RtpPacketizerVp8Test, Tl0PicIdxAndTID) { + RTPVideoHeaderVP8 hdr_info; + hdr_info.InitRTPVideoHeaderVP8(); + hdr_info.tl0PicIdx = 117; + hdr_info.temporalIdx = 2; + hdr_info.layerSync = true; + RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/30); + + RtpPacketizerVp8 packetizer(helper.payload(), kNoSizeLimits, hdr_info); + + const size_t kExpectedSizes[1] = {helper.payload_size() + 4}; + helper.GetAllPacketsAndCheck(&packetizer, kExpectedSizes); +} + +TEST(RtpPacketizerVp8Test, KeyIdx) { + RTPVideoHeaderVP8 hdr_info; + hdr_info.InitRTPVideoHeaderVP8(); + hdr_info.keyIdx = 17; + RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/30); + + RtpPacketizerVp8 packetizer(helper.payload(), kNoSizeLimits, hdr_info); + + const size_t kExpectedSizes[1] = {helper.payload_size() + 3}; + helper.GetAllPacketsAndCheck(&packetizer, kExpectedSizes); +} + +// Verify TID field and KeyIdx field in combination. +TEST(RtpPacketizerVp8Test, TIDAndKeyIdx) { + RTPVideoHeaderVP8 hdr_info; + hdr_info.InitRTPVideoHeaderVP8(); + hdr_info.temporalIdx = 1; + hdr_info.keyIdx = 5; + RtpFormatVp8TestHelper helper(&hdr_info, /*payload_len=*/30); + + RtpPacketizerVp8 packetizer(helper.payload(), kNoSizeLimits, hdr_info); + + const size_t kExpectedSizes[1] = {helper.payload_size() + 3}; + helper.GetAllPacketsAndCheck(&packetizer, kExpectedSizes); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc new file mode 100644 index 0000000000..15e059e85c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc @@ -0,0 +1,451 @@ +/* + * 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/rtp_format_vp9.h" + +#include <string.h> + +#include "api/video/video_codec_constants.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" +#include "modules/video_coding/codecs/interface/common_constants.h" +#include "rtc_base/bit_buffer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +#define RETURN_FALSE_ON_ERROR(x) \ + if (!(x)) { \ + return false; \ + } + +namespace webrtc { +namespace { +// Length of VP9 payload descriptors' fixed part. +const size_t kFixedPayloadDescriptorBytes = 1; + +const uint32_t kReservedBitValue0 = 0; + +uint8_t TemporalIdxField(const RTPVideoHeaderVP9& hdr, uint8_t def) { + return (hdr.temporal_idx == kNoTemporalIdx) ? def : hdr.temporal_idx; +} + +uint8_t SpatialIdxField(const RTPVideoHeaderVP9& hdr, uint8_t def) { + return (hdr.spatial_idx == kNoSpatialIdx) ? def : hdr.spatial_idx; +} + +int16_t Tl0PicIdxField(const RTPVideoHeaderVP9& hdr, uint8_t def) { + return (hdr.tl0_pic_idx == kNoTl0PicIdx) ? def : hdr.tl0_pic_idx; +} + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +size_t PictureIdLength(const RTPVideoHeaderVP9& hdr) { + if (hdr.picture_id == kNoPictureId) + return 0; + return (hdr.max_picture_id == kMaxOneBytePictureId) ? 1 : 2; +} + +bool PictureIdPresent(const RTPVideoHeaderVP9& hdr) { + return PictureIdLength(hdr) > 0; +} + +// Layer indices: +// +// Flexible mode (F=1): Non-flexible mode (F=0): +// +// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// +size_t LayerInfoLength(const RTPVideoHeaderVP9& hdr) { + if (hdr.temporal_idx == kNoTemporalIdx && hdr.spatial_idx == kNoSpatialIdx) { + return 0; + } + return hdr.flexible_mode ? 1 : 2; +} + +bool LayerInfoPresent(const RTPVideoHeaderVP9& hdr) { + return LayerInfoLength(hdr) > 0; +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +size_t RefIndicesLength(const RTPVideoHeaderVP9& hdr) { + if (!hdr.inter_pic_predicted || !hdr.flexible_mode) + return 0; + + RTC_DCHECK_GT(hdr.num_ref_pics, 0U); + RTC_DCHECK_LE(hdr.num_ref_pics, kMaxVp9RefPics); + return hdr.num_ref_pics; +} + +// Scalability structure (SS). +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +size_t SsDataLength(const RTPVideoHeaderVP9& hdr) { + if (!hdr.ss_data_available) + return 0; + + RTC_DCHECK_GT(hdr.num_spatial_layers, 0U); + RTC_DCHECK_LE(hdr.num_spatial_layers, kMaxVp9NumberOfSpatialLayers); + RTC_DCHECK_LE(hdr.gof.num_frames_in_gof, kMaxVp9FramesInGof); + size_t length = 1; // V + if (hdr.spatial_layer_resolution_present) { + length += 4 * hdr.num_spatial_layers; // Y + } + if (hdr.gof.num_frames_in_gof > 0) { + ++length; // G + } + // N_G + length += hdr.gof.num_frames_in_gof; // T, U, R + for (size_t i = 0; i < hdr.gof.num_frames_in_gof; ++i) { + RTC_DCHECK_LE(hdr.gof.num_ref_pics[i], kMaxVp9RefPics); + length += hdr.gof.num_ref_pics[i]; // R times + } + return length; +} + +size_t PayloadDescriptorLengthMinusSsData(const RTPVideoHeaderVP9& hdr) { + return kFixedPayloadDescriptorBytes + PictureIdLength(hdr) + + LayerInfoLength(hdr) + RefIndicesLength(hdr); +} + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +bool WritePictureId(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + bool m_bit = (PictureIdLength(vp9) == 2); + RETURN_FALSE_ON_ERROR(writer->WriteBits(m_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.picture_id, m_bit ? 15 : 7)); + return true; +} + +// Layer indices: +// +// Flexible mode (F=1): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// +bool WriteLayerInfoCommon(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + RETURN_FALSE_ON_ERROR(writer->WriteBits(TemporalIdxField(vp9, 0), 3)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.temporal_up_switch ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(SpatialIdxField(vp9, 0), 3)); + RETURN_FALSE_ON_ERROR( + writer->WriteBits(vp9.inter_layer_predicted ? 1 : 0, 1)); + return true; +} + +// Non-flexible mode (F=0): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// +bool WriteLayerInfoNonFlexibleMode(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt8(Tl0PicIdxField(vp9, 0))); + return true; +} + +bool WriteLayerInfo(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + if (!WriteLayerInfoCommon(vp9, writer)) + return false; + + if (vp9.flexible_mode) + return true; + + return WriteLayerInfoNonFlexibleMode(vp9, writer); +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +bool WriteRefIndices(const RTPVideoHeaderVP9& vp9, + rtc::BitBufferWriter* writer) { + if (!PictureIdPresent(vp9) || vp9.num_ref_pics == 0 || + vp9.num_ref_pics > kMaxVp9RefPics) { + return false; + } + for (uint8_t i = 0; i < vp9.num_ref_pics; ++i) { + bool n_bit = !(i == vp9.num_ref_pics - 1); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.pid_diff[i], 7)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(n_bit ? 1 : 0, 1)); + } + return true; +} + +// Scalability structure (SS). +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +bool WriteSsData(const RTPVideoHeaderVP9& vp9, rtc::BitBufferWriter* writer) { + RTC_DCHECK_GT(vp9.num_spatial_layers, 0U); + RTC_DCHECK_LE(vp9.num_spatial_layers, kMaxVp9NumberOfSpatialLayers); + RTC_DCHECK_LE(vp9.gof.num_frames_in_gof, kMaxVp9FramesInGof); + bool g_bit = vp9.gof.num_frames_in_gof > 0; + + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.num_spatial_layers - 1, 3)); + RETURN_FALSE_ON_ERROR( + writer->WriteBits(vp9.spatial_layer_resolution_present ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(g_bit ? 1 : 0, 1)); // G + RETURN_FALSE_ON_ERROR(writer->WriteBits(kReservedBitValue0, 3)); + + if (vp9.spatial_layer_resolution_present) { + for (size_t i = 0; i < vp9.num_spatial_layers; ++i) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt16(vp9.width[i])); + RETURN_FALSE_ON_ERROR(writer->WriteUInt16(vp9.height[i])); + } + } + if (g_bit) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt8(vp9.gof.num_frames_in_gof)); + } + for (size_t i = 0; i < vp9.gof.num_frames_in_gof; ++i) { + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.gof.temporal_idx[i], 3)); + RETURN_FALSE_ON_ERROR( + writer->WriteBits(vp9.gof.temporal_up_switch[i] ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(vp9.gof.num_ref_pics[i], 2)); + RETURN_FALSE_ON_ERROR(writer->WriteBits(kReservedBitValue0, 2)); + for (uint8_t r = 0; r < vp9.gof.num_ref_pics[i]; ++r) { + RETURN_FALSE_ON_ERROR(writer->WriteUInt8(vp9.gof.pid_diff[i][r])); + } + } + return true; +} + +// TODO(https://bugs.webrtc.org/11319): +// Workaround for switching off spatial layers on the fly. +// Sent layers must start from SL0 on RTP layer, but can start from any +// spatial layer because WebRTC-SVC api isn't implemented yet and +// current API to invoke SVC is not flexible enough. +RTPVideoHeaderVP9 RemoveInactiveSpatialLayers( + const RTPVideoHeaderVP9& original_header) { + RTPVideoHeaderVP9 hdr(original_header); + if (original_header.first_active_layer == 0) + return hdr; + for (size_t i = hdr.first_active_layer; i < hdr.num_spatial_layers; ++i) { + hdr.width[i - hdr.first_active_layer] = hdr.width[i]; + hdr.height[i - hdr.first_active_layer] = hdr.height[i]; + } + for (size_t i = hdr.num_spatial_layers - hdr.first_active_layer; + i < hdr.num_spatial_layers; ++i) { + hdr.width[i] = 0; + hdr.height[i] = 0; + } + hdr.num_spatial_layers -= hdr.first_active_layer; + hdr.spatial_idx -= hdr.first_active_layer; + hdr.first_active_layer = 0; + return hdr; +} +} // namespace + +RtpPacketizerVp9::RtpPacketizerVp9(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + const RTPVideoHeaderVP9& hdr) + : hdr_(RemoveInactiveSpatialLayers(hdr)), + header_size_(PayloadDescriptorLengthMinusSsData(hdr_)), + first_packet_extra_header_size_(SsDataLength(hdr_)), + remaining_payload_(payload) { + RTC_DCHECK_EQ(hdr_.first_active_layer, 0); + + limits.max_payload_len -= header_size_; + limits.first_packet_reduction_len += first_packet_extra_header_size_; + limits.single_packet_reduction_len += first_packet_extra_header_size_; + + payload_sizes_ = SplitAboutEqually(payload.size(), limits); + current_packet_ = payload_sizes_.begin(); +} + +RtpPacketizerVp9::~RtpPacketizerVp9() = default; + +size_t RtpPacketizerVp9::NumPackets() const { + return payload_sizes_.end() - current_packet_; +} + +bool RtpPacketizerVp9::NextPacket(RtpPacketToSend* packet) { + RTC_DCHECK(packet); + if (current_packet_ == payload_sizes_.end()) { + return false; + } + + bool layer_begin = current_packet_ == payload_sizes_.begin(); + int packet_payload_len = *current_packet_; + ++current_packet_; + bool layer_end = current_packet_ == payload_sizes_.end(); + + int header_size = header_size_; + if (layer_begin) + header_size += first_packet_extra_header_size_; + + uint8_t* buffer = packet->AllocatePayload(header_size + packet_payload_len); + RTC_CHECK(buffer); + + if (!WriteHeader(layer_begin, layer_end, + rtc::MakeArrayView(buffer, header_size))) + return false; + + memcpy(buffer + header_size, remaining_payload_.data(), packet_payload_len); + remaining_payload_ = remaining_payload_.subview(packet_payload_len); + + // Ensure end_of_picture is always set on top spatial layer when it is not + // dropped. + RTC_DCHECK(hdr_.spatial_idx < hdr_.num_spatial_layers - 1 || + hdr_.end_of_picture); + + packet->SetMarker(layer_end && hdr_.end_of_picture); + return true; +} + +// VP9 format: +// +// Payload descriptor for F = 1 (flexible mode) +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|Z| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ -| +// P,F: | P_DIFF |N| (CONDITIONALLY RECOMMENDED) . up to 3 times +// +-+-+-+-+-+-+-+-+ -| +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ +// +// Payload descriptor for F = 0 (non-flexible mode) +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|Z| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | (CONDITIONALLY REQUIRED) +// +-+-+-+-+-+-+-+-+ +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ +bool RtpPacketizerVp9::WriteHeader(bool layer_begin, + bool layer_end, + rtc::ArrayView<uint8_t> buffer) const { + // Required payload descriptor byte. + bool i_bit = PictureIdPresent(hdr_); + bool p_bit = hdr_.inter_pic_predicted; + bool l_bit = LayerInfoPresent(hdr_); + bool f_bit = hdr_.flexible_mode; + bool b_bit = layer_begin; + bool e_bit = layer_end; + bool v_bit = hdr_.ss_data_available && b_bit; + bool z_bit = hdr_.non_ref_for_inter_layer_pred; + + rtc::BitBufferWriter writer(buffer.data(), buffer.size()); + RETURN_FALSE_ON_ERROR(writer.WriteBits(i_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(p_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(l_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(f_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(b_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(e_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(v_bit ? 1 : 0, 1)); + RETURN_FALSE_ON_ERROR(writer.WriteBits(z_bit ? 1 : 0, 1)); + + // Add fields that are present. + if (i_bit && !WritePictureId(hdr_, &writer)) { + RTC_LOG(LS_ERROR) << "Failed writing VP9 picture id."; + return false; + } + if (l_bit && !WriteLayerInfo(hdr_, &writer)) { + RTC_LOG(LS_ERROR) << "Failed writing VP9 layer info."; + return false; + } + if (p_bit && f_bit && !WriteRefIndices(hdr_, &writer)) { + RTC_LOG(LS_ERROR) << "Failed writing VP9 ref indices."; + return false; + } + if (v_bit && !WriteSsData(hdr_, &writer)) { + RTC_LOG(LS_ERROR) << "Failed writing VP9 SS data."; + return false; + } + + size_t offset_bytes = 0; + size_t offset_bits = 0; + writer.GetCurrentOffset(&offset_bytes, &offset_bits); + RTC_DCHECK_EQ(offset_bits, 0); + RTC_DCHECK_EQ(offset_bytes, buffer.size()); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.h new file mode 100644 index 0000000000..3cf4dd56e5 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.h @@ -0,0 +1,72 @@ +/* + * 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 contains the declaration of the VP9 packetizer class. +// A packetizer object is created for each encoded video frame. The +// constructor is called with the payload data and size. +// +// After creating the packetizer, the method NextPacket is called +// repeatedly to get all packets for the frame. The method returns +// false as long as there are more packets left to fetch. +// + +#ifndef MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP9_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP9_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/video_coding/codecs/vp9/include/vp9_globals.h" + +namespace webrtc { + +class RtpPacketizerVp9 : public RtpPacketizer { + public: + // The `payload` must be one encoded VP9 layer frame. + RtpPacketizerVp9(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + const RTPVideoHeaderVP9& hdr); + + ~RtpPacketizerVp9() override; + + RtpPacketizerVp9(const RtpPacketizerVp9&) = delete; + RtpPacketizerVp9& operator=(const RtpPacketizerVp9&) = delete; + + size_t NumPackets() const override; + + // Gets the next payload with VP9 payload header. + // Write payload and set marker bit of the `packet`. + // Returns true on success, false otherwise. + bool NextPacket(RtpPacketToSend* packet) override; + + private: + // Writes the payload descriptor header. + // `layer_begin` and `layer_end` indicates the postision of the packet in + // the layer frame. Returns false on failure. + bool WriteHeader(bool layer_begin, + bool layer_end, + rtc::ArrayView<uint8_t> rtp_payload) const; + + const RTPVideoHeaderVP9 hdr_; + const int header_size_; + const int first_packet_extra_header_size_; + rtc::ArrayView<const uint8_t> remaining_payload_; + std::vector<int> payload_sizes_; + std::vector<int>::const_iterator current_packet_; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_FORMAT_VP9_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc new file mode 100644 index 0000000000..e18b8a803f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc @@ -0,0 +1,608 @@ +/* + * 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/rtp_format_vp9.h" + +#include <memory> +#include <vector> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +void VerifyHeader(const RTPVideoHeaderVP9& expected, + const RTPVideoHeaderVP9& actual) { + EXPECT_EQ(expected.inter_layer_predicted, actual.inter_layer_predicted); + EXPECT_EQ(expected.inter_pic_predicted, actual.inter_pic_predicted); + EXPECT_EQ(expected.flexible_mode, actual.flexible_mode); + EXPECT_EQ(expected.beginning_of_frame, actual.beginning_of_frame); + EXPECT_EQ(expected.end_of_frame, actual.end_of_frame); + EXPECT_EQ(expected.ss_data_available, actual.ss_data_available); + EXPECT_EQ(expected.non_ref_for_inter_layer_pred, + actual.non_ref_for_inter_layer_pred); + EXPECT_EQ(expected.picture_id, actual.picture_id); + EXPECT_EQ(expected.max_picture_id, actual.max_picture_id); + EXPECT_EQ(expected.temporal_idx, actual.temporal_idx); + EXPECT_EQ(expected.spatial_idx, actual.spatial_idx); + EXPECT_EQ(expected.gof_idx, actual.gof_idx); + EXPECT_EQ(expected.tl0_pic_idx, actual.tl0_pic_idx); + EXPECT_EQ(expected.temporal_up_switch, actual.temporal_up_switch); + + EXPECT_EQ(expected.num_ref_pics, actual.num_ref_pics); + for (uint8_t i = 0; i < expected.num_ref_pics; ++i) { + EXPECT_EQ(expected.pid_diff[i], actual.pid_diff[i]); + EXPECT_EQ(expected.ref_picture_id[i], actual.ref_picture_id[i]); + } + if (expected.ss_data_available) { + EXPECT_EQ(expected.spatial_layer_resolution_present, + actual.spatial_layer_resolution_present); + EXPECT_EQ(expected.num_spatial_layers, actual.num_spatial_layers); + if (expected.spatial_layer_resolution_present) { + for (size_t i = 0; i < expected.num_spatial_layers; i++) { + EXPECT_EQ(expected.width[i], actual.width[i]); + EXPECT_EQ(expected.height[i], actual.height[i]); + } + } + EXPECT_EQ(expected.gof.num_frames_in_gof, actual.gof.num_frames_in_gof); + for (size_t i = 0; i < expected.gof.num_frames_in_gof; i++) { + EXPECT_EQ(expected.gof.temporal_up_switch[i], + actual.gof.temporal_up_switch[i]); + EXPECT_EQ(expected.gof.temporal_idx[i], actual.gof.temporal_idx[i]); + EXPECT_EQ(expected.gof.num_ref_pics[i], actual.gof.num_ref_pics[i]); + for (uint8_t j = 0; j < expected.gof.num_ref_pics[i]; j++) { + EXPECT_EQ(expected.gof.pid_diff[i][j], actual.gof.pid_diff[i][j]); + } + } + } +} + +void ParseAndCheckPacket(const uint8_t* packet, + const RTPVideoHeaderVP9& expected, + int expected_hdr_length, + size_t expected_length) { + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp9::ParseRtpPayload( + rtc::MakeArrayView(packet, expected_length), &video_header), + expected_hdr_length); + EXPECT_EQ(kVideoCodecVP9, video_header.codec); + auto& vp9_header = + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header); + VerifyHeader(expected, vp9_header); +} + +// Payload descriptor for flexible mode +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|Z| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ -| +// P,F: | P_DIFF |N| (CONDITIONALLY RECOMMENDED) . up to 3 times +// +-+-+-+-+-+-+-+-+ -| +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ +// +// Payload descriptor for non-flexible mode +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |I|P|L|F|B|E|V|Z| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// M: | EXTENDED PID | (RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| (CONDITIONALLY RECOMMENDED) +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | (CONDITIONALLY REQUIRED) +// +-+-+-+-+-+-+-+-+ +// V: | SS | +// | .. | +// +-+-+-+-+-+-+-+-+ + +class RtpPacketizerVp9Test : public ::testing::Test { + protected: + static constexpr RtpPacketToSend::ExtensionManager* kNoExtensions = nullptr; + static constexpr size_t kMaxPacketSize = 1200; + + RtpPacketizerVp9Test() : packet_(kNoExtensions, kMaxPacketSize) {} + void SetUp() override { expected_.InitRTPVideoHeaderVP9(); } + + RtpPacketToSend packet_; + std::vector<uint8_t> payload_; + size_t payload_pos_; + RTPVideoHeaderVP9 expected_; + std::unique_ptr<RtpPacketizerVp9> packetizer_; + size_t num_packets_; + + void Init(size_t payload_size, size_t packet_size) { + payload_.assign(payload_size, 7); + payload_pos_ = 0; + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = packet_size; + packetizer_.reset(new RtpPacketizerVp9(payload_, limits, expected_)); + num_packets_ = packetizer_->NumPackets(); + } + + void CheckPayload(const uint8_t* packet, + size_t start_pos, + size_t end_pos, + bool last) { + for (size_t i = start_pos; i < end_pos; ++i) { + EXPECT_EQ(packet[i], payload_[payload_pos_++]); + } + EXPECT_EQ(last, payload_pos_ == payload_.size()); + } + + void CreateParseAndCheckPackets( + rtc::ArrayView<const size_t> expected_hdr_sizes, + rtc::ArrayView<const size_t> expected_sizes) { + ASSERT_EQ(expected_hdr_sizes.size(), expected_sizes.size()); + ASSERT_TRUE(packetizer_ != nullptr); + EXPECT_EQ(expected_sizes.size(), num_packets_); + for (size_t i = 0; i < expected_sizes.size(); ++i) { + EXPECT_TRUE(packetizer_->NextPacket(&packet_)); + auto rtp_payload = packet_.payload(); + EXPECT_EQ(expected_sizes[i], rtp_payload.size()); + RTPVideoHeaderVP9 hdr = expected_; + hdr.beginning_of_frame = (i == 0); + hdr.end_of_frame = (i + 1) == expected_sizes.size(); + ParseAndCheckPacket(rtp_payload.data(), hdr, expected_hdr_sizes[i], + rtp_payload.size()); + CheckPayload(rtp_payload.data(), expected_hdr_sizes[i], + rtp_payload.size(), (i + 1) == expected_sizes.size()); + expected_.ss_data_available = false; + } + } + + void CreateParseAndCheckPacketsLayers(size_t num_spatial_layers, + size_t expected_layer) { + ASSERT_TRUE(packetizer_ != nullptr); + for (size_t i = 0; i < num_packets_; ++i) { + EXPECT_TRUE(packetizer_->NextPacket(&packet_)); + RTPVideoHeader video_header; + VideoRtpDepacketizerVp9::ParseRtpPayload(packet_.payload(), + &video_header); + const auto& vp9_header = + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header); + EXPECT_EQ(vp9_header.spatial_idx, expected_layer); + EXPECT_EQ(vp9_header.num_spatial_layers, num_spatial_layers); + } + } +}; + +TEST_F(RtpPacketizerVp9Test, TestEqualSizedMode_OnePacket) { + const size_t kFrameSize = 25; + const size_t kPacketSize = 26; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:0, Z:0 (1hdr + 25 payload) + const size_t kExpectedHdrSizes[] = {1}; + const size_t kExpectedSizes[] = {26}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestEqualSizedMode_TwoPackets) { + const size_t kFrameSize = 27; + const size_t kPacketSize = 27; + Init(kFrameSize, kPacketSize); + + // Two packets: + // I:0, P:0, L:0, F:0, B:1, E:0, V:0, Z:0 (1hdr + 14 payload) + // I:0, P:0, L:0, F:0, B:0, E:1, V:0, Z:0 (1hdr + 13 payload) + const size_t kExpectedHdrSizes[] = {1, 1}; + const size_t kExpectedSizes[] = {14, 15}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestTooShortBufferToFitPayload) { + const size_t kFrameSize = 1; + const size_t kPacketSize = 1; + Init(kFrameSize, kPacketSize); // 1hdr + 1 payload + + EXPECT_FALSE(packetizer_->NextPacket(&packet_)); +} + +TEST_F(RtpPacketizerVp9Test, TestOneBytePictureId) { + const size_t kFrameSize = 30; + const size_t kPacketSize = 12; + + expected_.picture_id = kMaxOneBytePictureId; // 2 byte payload descriptor + expected_.max_picture_id = kMaxOneBytePictureId; + Init(kFrameSize, kPacketSize); + + // Three packets: + // I:1, P:0, L:0, F:0, B:1, E:0, V:0, Z:0 (2hdr + 10 payload) + // I:1, P:0, L:0, F:0, B:0, E:0, V:0, Z:0 (2hdr + 10 payload) + // I:1, P:0, L:0, F:0, B:0, E:1, V:0, Z:0 (2hdr + 10 payload) + const size_t kExpectedHdrSizes[] = {2, 2, 2}; + const size_t kExpectedSizes[] = {12, 12, 12}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestTwoBytePictureId) { + const size_t kFrameSize = 31; + const size_t kPacketSize = 13; + + expected_.picture_id = kMaxTwoBytePictureId; // 3 byte payload descriptor + Init(kFrameSize, kPacketSize); + + // Four packets: + // I:1, P:0, L:0, F:0, B:1, E:0, V:0, Z:0 (3hdr + 8 payload) + // I:1, P:0, L:0, F:0, B:0, E:0, V:0, Z:0 (3hdr + 8 payload) + // I:1, P:0, L:0, F:0, B:0, E:0, V:0, Z:0 (3hdr + 8 payload) + // I:1, P:0, L:0, F:0, B:0, E:1, V:0, Z:0 (3hdr + 7 payload) + const size_t kExpectedHdrSizes[] = {3, 3, 3, 3}; + const size_t kExpectedSizes[] = {10, 11, 11, 11}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestLayerInfoWithNonFlexibleMode) { + const size_t kFrameSize = 30; + const size_t kPacketSize = 25; + + expected_.temporal_idx = 3; + expected_.temporal_up_switch = true; // U + expected_.num_spatial_layers = 3; + expected_.spatial_idx = 2; + expected_.inter_layer_predicted = true; // D + expected_.tl0_pic_idx = 117; + Init(kFrameSize, kPacketSize); + + // Two packets: + // | I:0, P:0, L:1, F:0, B:1, E:0, V:0 Z:0 | (3hdr + 15 payload) + // L: | T:3, U:1, S:2, D:1 | TL0PICIDX:117 | + // | I:0, P:0, L:1, F:0, B:0, E:1, V:0 Z:0 | (3hdr + 15 payload) + // L: | T:3, U:1, S:2, D:1 | TL0PICIDX:117 | + const size_t kExpectedHdrSizes[] = {3, 3}; + const size_t kExpectedSizes[] = {18, 18}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestLayerInfoWithFlexibleMode) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 23; + + expected_.flexible_mode = true; + expected_.temporal_idx = 3; + expected_.temporal_up_switch = true; // U + expected_.num_spatial_layers = 3; + expected_.spatial_idx = 2; + expected_.inter_layer_predicted = false; // D + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:1, F:1, B:1, E:1, V:0 (2hdr + 21 payload) + // L: T:3, U:1, S:2, D:0 + const size_t kExpectedHdrSizes[] = {2}; + const size_t kExpectedSizes[] = {23}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestRefIdx) { + const size_t kFrameSize = 16; + const size_t kPacketSize = 21; + + expected_.inter_pic_predicted = true; // P + expected_.flexible_mode = true; // F + expected_.picture_id = 2; + expected_.max_picture_id = kMaxOneBytePictureId; + + expected_.num_ref_pics = 3; + expected_.pid_diff[0] = 1; + expected_.pid_diff[1] = 3; + expected_.pid_diff[2] = 127; + expected_.ref_picture_id[0] = 1; // 2 - 1 = 1 + expected_.ref_picture_id[1] = 127; // (kMaxPictureId + 1) + 2 - 3 = 127 + expected_.ref_picture_id[2] = 3; // (kMaxPictureId + 1) + 2 - 127 = 3 + Init(kFrameSize, kPacketSize); + + // Two packets: + // I:1, P:1, L:0, F:1, B:1, E:1, V:0, Z:0 (5hdr + 16 payload) + // I: 2 + // P,F: P_DIFF:1, N:1 + // P_DIFF:3, N:1 + // P_DIFF:127, N:0 + const size_t kExpectedHdrSizes[] = {5}; + const size_t kExpectedSizes[] = {21}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestRefIdxFailsWithoutPictureId) { + const size_t kFrameSize = 16; + const size_t kPacketSize = 21; + + expected_.inter_pic_predicted = true; + expected_.flexible_mode = true; + expected_.num_ref_pics = 1; + expected_.pid_diff[0] = 3; + Init(kFrameSize, kPacketSize); + + EXPECT_FALSE(packetizer_->NextPacket(&packet_)); +} + +TEST_F(RtpPacketizerVp9Test, TestSsDataWithoutSpatialResolutionPresent) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 26; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 1; + expected_.spatial_layer_resolution_present = false; + expected_.gof.num_frames_in_gof = 1; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.num_ref_pics[0] = 1; + expected_.gof.pid_diff[0][0] = 4; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1, Z:0 (5hdr + 21 payload) + // N_S:0, Y:0, G:1 + // N_G:1 + // T:0, U:1, R:1 | P_DIFF[0][0]:4 + const size_t kExpectedHdrSizes[] = {5}; + const size_t kExpectedSizes[] = {26}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestSsDataWithoutGbitPresent) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 23; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 1; + expected_.spatial_layer_resolution_present = false; + expected_.gof.num_frames_in_gof = 0; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1, Z:0 (2hdr + 21 payload) + // N_S:0, Y:0, G:0 + const size_t kExpectedHdrSizes[] = {2}; + const size_t kExpectedSizes[] = {23}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestSsData) { + const size_t kFrameSize = 21; + const size_t kPacketSize = 40; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 2; + expected_.spatial_layer_resolution_present = true; + expected_.width[0] = 640; + expected_.width[1] = 1280; + expected_.height[0] = 360; + expected_.height[1] = 720; + expected_.gof.num_frames_in_gof = 3; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_idx[1] = 1; + expected_.gof.temporal_idx[2] = 2; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.temporal_up_switch[1] = true; + expected_.gof.temporal_up_switch[2] = false; + expected_.gof.num_ref_pics[0] = 0; + expected_.gof.num_ref_pics[1] = 3; + expected_.gof.num_ref_pics[2] = 2; + expected_.gof.pid_diff[1][0] = 5; + expected_.gof.pid_diff[1][1] = 6; + expected_.gof.pid_diff[1][2] = 7; + expected_.gof.pid_diff[2][0] = 8; + expected_.gof.pid_diff[2][1] = 9; + Init(kFrameSize, kPacketSize); + + // One packet: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1, Z:0 (19hdr + 21 payload) + // N_S:1, Y:1, G:1 + // WIDTH:640 // 2 bytes + // HEIGHT:360 // 2 bytes + // WIDTH:1280 // 2 bytes + // HEIGHT:720 // 2 bytes + // N_G:3 + // T:0, U:1, R:0 + // T:1, U:1, R:3 | P_DIFF[1][0]:5 | P_DIFF[1][1]:6 | P_DIFF[1][2]:7 + // T:2, U:0, R:2 | P_DIFF[2][0]:8 | P_DIFF[2][0]:9 + const size_t kExpectedHdrSizes[] = {19}; + const size_t kExpectedSizes[] = {40}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, TestSsDataDoesNotFitInAveragePacket) { + const size_t kFrameSize = 24; + const size_t kPacketSize = 20; + + expected_.ss_data_available = true; + expected_.num_spatial_layers = 2; + expected_.spatial_layer_resolution_present = true; + expected_.width[0] = 640; + expected_.width[1] = 1280; + expected_.height[0] = 360; + expected_.height[1] = 720; + expected_.gof.num_frames_in_gof = 3; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_idx[1] = 1; + expected_.gof.temporal_idx[2] = 2; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.temporal_up_switch[1] = true; + expected_.gof.temporal_up_switch[2] = false; + expected_.gof.num_ref_pics[0] = 0; + expected_.gof.num_ref_pics[1] = 3; + expected_.gof.num_ref_pics[2] = 2; + expected_.gof.pid_diff[1][0] = 5; + expected_.gof.pid_diff[1][1] = 6; + expected_.gof.pid_diff[1][2] = 7; + expected_.gof.pid_diff[2][0] = 8; + expected_.gof.pid_diff[2][1] = 9; + Init(kFrameSize, kPacketSize); + + // Three packets: + // I:0, P:0, L:0, F:0, B:1, E:1, V:1, Z:0 (19hdr + 1 payload) + // N_S:1, Y:1, G:1 + // WIDTH:640 // 2 bytes + // HEIGHT:360 // 2 bytes + // WIDTH:1280 // 2 bytes + // HEIGHT:720 // 2 bytes + // N_G:3 + // T:0, U:1, R:0 + // T:1, U:1, R:3 | P_DIFF[1][0]:5 | P_DIFF[1][1]:6 | P_DIFF[1][2]:7 + // T:2, U:0, R:2 | P_DIFF[2][0]:8 | P_DIFF[2][0]:9 + // Last two packets 1 bytes vp9 hdrs and the rest of payload 14 and 9 bytes. + const size_t kExpectedHdrSizes[] = {19, 1, 1}; + const size_t kExpectedSizes[] = {20, 15, 10}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, EndOfPictureSetsSetMarker) { + const size_t kFrameSize = 10; + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + const uint8_t kFrame[kFrameSize] = {7}; + + RTPVideoHeaderVP9 vp9_header; + vp9_header.InitRTPVideoHeaderVP9(); + vp9_header.flexible_mode = true; + vp9_header.num_spatial_layers = 3; + + RtpPacketToSend packet(kNoExtensions); + + // Drop top layer and ensure that marker bit is set on last encoded layer. + for (size_t spatial_idx = 0; spatial_idx < vp9_header.num_spatial_layers - 1; + ++spatial_idx) { + const bool end_of_picture = + spatial_idx + 1 == vp9_header.num_spatial_layers - 1; + vp9_header.spatial_idx = spatial_idx; + vp9_header.end_of_picture = end_of_picture; + RtpPacketizerVp9 packetizer(kFrame, limits, vp9_header); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + EXPECT_FALSE(packet.Marker()); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + EXPECT_EQ(packet.Marker(), end_of_picture); + } +} + +TEST_F(RtpPacketizerVp9Test, TestGeneratesMinimumNumberOfPackets) { + const size_t kFrameSize = 10; + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + // Calculated by hand. One packet can contain + // `kPacketSize` - `kVp9MinDiscriptorSize` = 6 bytes of the frame payload, + // thus to fit 10 bytes two packets are required. + const size_t kMinNumberOfPackets = 2; + const uint8_t kFrame[kFrameSize] = {7}; + + RTPVideoHeaderVP9 vp9_header; + vp9_header.InitRTPVideoHeaderVP9(); + + RtpPacketToSend packet(kNoExtensions); + + RtpPacketizerVp9 packetizer(kFrame, limits, vp9_header); + EXPECT_EQ(packetizer.NumPackets(), kMinNumberOfPackets); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + EXPECT_FALSE(packet.Marker()); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + EXPECT_TRUE(packet.Marker()); +} + +TEST_F(RtpPacketizerVp9Test, TestRespectsLastPacketReductionLen) { + const size_t kFrameSize = 10; + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + limits.last_packet_reduction_len = 5; + // Calculated by hand. VP9 payload descriptor is 2 bytes. Like in the test + // above, 1 packet is not enough. 2 packets can contain + // 2*(`kPacketSize` - `kVp9MinDiscriptorSize`) - `kLastPacketReductionLen` = 7 + // But three packets are enough, since they have capacity of 3*(8-2)-5=13 + // bytes. + const size_t kMinNumberOfPackets = 3; + const uint8_t kFrame[kFrameSize] = {7}; + + RTPVideoHeaderVP9 vp9_header; + vp9_header.InitRTPVideoHeaderVP9(); + vp9_header.flexible_mode = true; + + RtpPacketToSend packet(kNoExtensions); + + RtpPacketizerVp9 packetizer0(kFrame, limits, vp9_header); + EXPECT_EQ(packetizer0.NumPackets(), kMinNumberOfPackets); + ASSERT_TRUE(packetizer0.NextPacket(&packet)); + EXPECT_FALSE(packet.Marker()); + ASSERT_TRUE(packetizer0.NextPacket(&packet)); + EXPECT_FALSE(packet.Marker()); + ASSERT_TRUE(packetizer0.NextPacket(&packet)); + EXPECT_TRUE(packet.Marker()); +} + +TEST_F(RtpPacketizerVp9Test, TestNonRefForInterLayerPred) { + const size_t kFrameSize = 25; + const size_t kPacketSize = 26; + + expected_.non_ref_for_inter_layer_pred = true; + Init(kFrameSize, kPacketSize); + + // I:0, P:0, L:0, F:0, B:1, E:1, V:0, Z:1 (1hdr + 25 payload) + const size_t kExpectedHdrSizes[] = {1}; + const size_t kExpectedSizes[] = {26}; + CreateParseAndCheckPackets(kExpectedHdrSizes, kExpectedSizes); +} + +TEST_F(RtpPacketizerVp9Test, + ShiftsSpatialLayersTowardZeroWhenFirstLayersAreDisabled) { + const size_t kFrameSize = 25; + const size_t kPacketSize = 1024; + + expected_.width[0] = 0; + expected_.height[0] = 0; + expected_.width[1] = 640; + expected_.height[1] = 360; + expected_.width[2] = 1280; + expected_.height[2] = 720; + expected_.num_spatial_layers = 3; + expected_.first_active_layer = 1; + expected_.ss_data_available = true; + expected_.spatial_layer_resolution_present = true; + expected_.gof.num_frames_in_gof = 3; + expected_.gof.temporal_idx[0] = 0; + expected_.gof.temporal_idx[1] = 1; + expected_.gof.temporal_idx[2] = 2; + expected_.gof.temporal_up_switch[0] = true; + expected_.gof.temporal_up_switch[1] = true; + expected_.gof.temporal_up_switch[2] = false; + expected_.gof.num_ref_pics[0] = 0; + expected_.gof.num_ref_pics[1] = 3; + expected_.gof.num_ref_pics[2] = 2; + expected_.gof.pid_diff[1][0] = 5; + expected_.gof.pid_diff[1][1] = 6; + expected_.gof.pid_diff[1][2] = 7; + expected_.gof.pid_diff[2][0] = 8; + expected_.gof.pid_diff[2][1] = 9; + + expected_.spatial_idx = 1; + Init(kFrameSize, kPacketSize); + CreateParseAndCheckPacketsLayers(/*num_spatial_layers=*/2, + /*expected_layer=*/0); + + // Now check for SL 2; + expected_.spatial_idx = 2; + Init(kFrameSize, kPacketSize); + CreateParseAndCheckPacketsLayers(/*num_spatial_layers=*/2, + /*expected_layer=*/1); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.cc new file mode 100644 index 0000000000..465308ec45 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.cc @@ -0,0 +1,100 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h" + +#include <cstdint> + +#include "rtc_base/checks.h" + +namespace webrtc { + +constexpr int RtpGenericFrameDescriptor::kMaxNumFrameDependencies; +constexpr int RtpGenericFrameDescriptor::kMaxTemporalLayers; +constexpr int RtpGenericFrameDescriptor::kMaxSpatialLayers; + +RtpGenericFrameDescriptor::RtpGenericFrameDescriptor() = default; +RtpGenericFrameDescriptor::RtpGenericFrameDescriptor( + const RtpGenericFrameDescriptor&) = default; +RtpGenericFrameDescriptor::~RtpGenericFrameDescriptor() = default; + +int RtpGenericFrameDescriptor::TemporalLayer() const { + RTC_DCHECK(FirstPacketInSubFrame()); + return temporal_layer_; +} + +void RtpGenericFrameDescriptor::SetTemporalLayer(int temporal_layer) { + RTC_DCHECK_GE(temporal_layer, 0); + RTC_DCHECK_LT(temporal_layer, kMaxTemporalLayers); + temporal_layer_ = temporal_layer; +} + +int RtpGenericFrameDescriptor::SpatialLayer() const { + RTC_DCHECK(FirstPacketInSubFrame()); + int layer = 0; + uint8_t spatial_layers = spatial_layers_; + while (spatial_layers_ != 0 && !(spatial_layers & 1)) { + spatial_layers >>= 1; + layer++; + } + return layer; +} + +uint8_t RtpGenericFrameDescriptor::SpatialLayersBitmask() const { + RTC_DCHECK(FirstPacketInSubFrame()); + return spatial_layers_; +} + +void RtpGenericFrameDescriptor::SetSpatialLayersBitmask( + uint8_t spatial_layers) { + RTC_DCHECK(FirstPacketInSubFrame()); + spatial_layers_ = spatial_layers; +} + +void RtpGenericFrameDescriptor::SetResolution(int width, int height) { + RTC_DCHECK(FirstPacketInSubFrame()); + RTC_DCHECK_GE(width, 0); + RTC_DCHECK_LE(width, 0xFFFF); + RTC_DCHECK_GE(height, 0); + RTC_DCHECK_LE(height, 0xFFFF); + width_ = width; + height_ = height; +} + +uint16_t RtpGenericFrameDescriptor::FrameId() const { + RTC_DCHECK(FirstPacketInSubFrame()); + return frame_id_; +} + +void RtpGenericFrameDescriptor::SetFrameId(uint16_t frame_id) { + RTC_DCHECK(FirstPacketInSubFrame()); + frame_id_ = frame_id; +} + +rtc::ArrayView<const uint16_t> +RtpGenericFrameDescriptor::FrameDependenciesDiffs() const { + RTC_DCHECK(FirstPacketInSubFrame()); + return rtc::MakeArrayView(frame_deps_id_diffs_, num_frame_deps_); +} + +bool RtpGenericFrameDescriptor::AddFrameDependencyDiff(uint16_t fdiff) { + RTC_DCHECK(FirstPacketInSubFrame()); + if (num_frame_deps_ == kMaxNumFrameDependencies) + return false; + if (fdiff == 0) + return false; + RTC_DCHECK_LT(fdiff, 1 << 14); + RTC_DCHECK_GT(fdiff, 0); + frame_deps_id_diffs_[num_frame_deps_] = fdiff; + num_frame_deps_++; + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h new file mode 100644 index 0000000000..8760acca2a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.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 MODULES_RTP_RTCP_SOURCE_RTP_GENERIC_FRAME_DESCRIPTOR_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_GENERIC_FRAME_DESCRIPTOR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" + +namespace webrtc { + +class RtpGenericFrameDescriptorExtension; + +// Data to put on the wire for FrameDescriptor rtp header extension. +class RtpGenericFrameDescriptor { + public: + static constexpr int kMaxNumFrameDependencies = 8; + static constexpr int kMaxTemporalLayers = 8; + static constexpr int kMaxSpatialLayers = 8; + + RtpGenericFrameDescriptor(); + RtpGenericFrameDescriptor(const RtpGenericFrameDescriptor&); + ~RtpGenericFrameDescriptor(); + + bool FirstPacketInSubFrame() const { return beginning_of_subframe_; } + void SetFirstPacketInSubFrame(bool first) { beginning_of_subframe_ = first; } + bool LastPacketInSubFrame() const { return end_of_subframe_; } + void SetLastPacketInSubFrame(bool last) { end_of_subframe_ = last; } + + // Properties below undefined if !FirstPacketInSubFrame() + // Valid range for temporal layer: [0, 7] + int TemporalLayer() const; + void SetTemporalLayer(int temporal_layer); + + // Frame might by used, possible indirectly, for spatial layer sid iff + // (bitmask & (1 << sid)) != 0 + int SpatialLayer() const; + uint8_t SpatialLayersBitmask() const; + void SetSpatialLayersBitmask(uint8_t spatial_layers); + + int Width() const { return width_; } + int Height() const { return height_; } + void SetResolution(int width, int height); + + uint16_t FrameId() const; + void SetFrameId(uint16_t frame_id); + + rtc::ArrayView<const uint16_t> FrameDependenciesDiffs() const; + void ClearFrameDependencies() { num_frame_deps_ = 0; } + // Returns false on failure, i.e. number of dependencies is too large. + bool AddFrameDependencyDiff(uint16_t fdiff); + + private: + bool beginning_of_subframe_ = false; + bool end_of_subframe_ = false; + + uint16_t frame_id_ = 0; + uint8_t spatial_layers_ = 1; + uint8_t temporal_layer_ = 0; + size_t num_frame_deps_ = 0; + uint16_t frame_deps_id_diffs_[kMaxNumFrameDependencies]; + int width_ = 0; + int height_ = 0; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_GENERIC_FRAME_DESCRIPTOR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.cc new file mode 100644 index 0000000000..8a0810f445 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.cc @@ -0,0 +1,173 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h" + +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +constexpr uint8_t kFlagBeginOfSubframe = 0x80; +constexpr uint8_t kFlagEndOfSubframe = 0x40; + +// In version 00, the flags F and L in the first byte correspond to +// kFlagFirstSubframeV00 and kFlagLastSubframeV00. In practice, they were +// always set to `true`. +constexpr uint8_t kFlagFirstSubframeV00 = 0x20; +constexpr uint8_t kFlagLastSubframeV00 = 0x10; + +constexpr uint8_t kFlagDependencies = 0x08; +constexpr uint8_t kMaskTemporalLayer = 0x07; + +constexpr uint8_t kFlagMoreDependencies = 0x01; +constexpr uint8_t kFlageXtendedOffset = 0x02; +} // namespace +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |B|E|F|L|D| T | +// +-+-+-+-+-+-+-+-+ +// B: | S | +// +-+-+-+-+-+-+-+-+ +// | | +// B: + FID + +// | | +// +-+-+-+-+-+-+-+-+ +// | | +// + Width + +// B=1 | | +// and +-+-+-+-+-+-+-+-+ +// D=0 | | +// + Height + +// | | +// +-+-+-+-+-+-+-+-+ +// D: | FDIFF |X|M| +// +---------------+ +// X: | ... | +// +-+-+-+-+-+-+-+-+ +// M: | FDIFF |X|M| +// +---------------+ +// | ... | +// +-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType RtpGenericFrameDescriptorExtension00::kId; + +bool RtpGenericFrameDescriptorExtension00::Parse( + rtc::ArrayView<const uint8_t> data, + RtpGenericFrameDescriptor* descriptor) { + if (data.empty()) { + return false; + } + + bool begins_subframe = (data[0] & kFlagBeginOfSubframe) != 0; + descriptor->SetFirstPacketInSubFrame(begins_subframe); + descriptor->SetLastPacketInSubFrame((data[0] & kFlagEndOfSubframe) != 0); + + // Parse Subframe details provided in 1st packet of subframe. + if (!begins_subframe) { + return data.size() == 1; + } + if (data.size() < 4) { + return false; + } + descriptor->SetTemporalLayer(data[0] & kMaskTemporalLayer); + descriptor->SetSpatialLayersBitmask(data[1]); + descriptor->SetFrameId(data[2] | (data[3] << 8)); + + // Parse dependencies. + descriptor->ClearFrameDependencies(); + size_t offset = 4; + bool has_more_dependencies = (data[0] & kFlagDependencies) != 0; + if (!has_more_dependencies && data.size() >= offset + 4) { + uint16_t width = (data[offset] << 8) | data[offset + 1]; + uint16_t height = (data[offset + 2] << 8) | data[offset + 3]; + descriptor->SetResolution(width, height); + offset += 4; + } + while (has_more_dependencies) { + if (data.size() == offset) + return false; + has_more_dependencies = (data[offset] & kFlagMoreDependencies) != 0; + bool extended = (data[offset] & kFlageXtendedOffset) != 0; + uint16_t fdiff = data[offset] >> 2; + offset++; + if (extended) { + if (data.size() == offset) + return false; + fdiff |= (data[offset] << 6); + offset++; + } + if (!descriptor->AddFrameDependencyDiff(fdiff)) + return false; + } + return true; +} + +size_t RtpGenericFrameDescriptorExtension00::ValueSize( + const RtpGenericFrameDescriptor& descriptor) { + if (!descriptor.FirstPacketInSubFrame()) + return 1; + + size_t size = 4; + for (uint16_t fdiff : descriptor.FrameDependenciesDiffs()) { + size += (fdiff >= (1 << 6)) ? 2 : 1; + } + if (descriptor.FirstPacketInSubFrame() && + descriptor.FrameDependenciesDiffs().empty() && descriptor.Width() > 0 && + descriptor.Height() > 0) { + size += 4; + } + return size; +} + +bool RtpGenericFrameDescriptorExtension00::Write( + rtc::ArrayView<uint8_t> data, + const RtpGenericFrameDescriptor& descriptor) { + RTC_CHECK_EQ(data.size(), ValueSize(descriptor)); + uint8_t base_header = + (descriptor.FirstPacketInSubFrame() ? kFlagBeginOfSubframe : 0) | + (descriptor.LastPacketInSubFrame() ? kFlagEndOfSubframe : 0); + base_header |= kFlagFirstSubframeV00; + base_header |= kFlagLastSubframeV00; + + if (!descriptor.FirstPacketInSubFrame()) { + data[0] = base_header; + return true; + } + data[0] = + base_header | + (descriptor.FrameDependenciesDiffs().empty() ? 0 : kFlagDependencies) | + descriptor.TemporalLayer(); + data[1] = descriptor.SpatialLayersBitmask(); + uint16_t frame_id = descriptor.FrameId(); + data[2] = frame_id & 0xff; + data[3] = frame_id >> 8; + rtc::ArrayView<const uint16_t> fdiffs = descriptor.FrameDependenciesDiffs(); + size_t offset = 4; + if (descriptor.FirstPacketInSubFrame() && fdiffs.empty() && + descriptor.Width() > 0 && descriptor.Height() > 0) { + data[offset++] = (descriptor.Width() >> 8); + data[offset++] = (descriptor.Width() & 0xFF); + data[offset++] = (descriptor.Height() >> 8); + data[offset++] = (descriptor.Height() & 0xFF); + } + for (size_t i = 0; i < fdiffs.size(); i++) { + bool extended = fdiffs[i] >= (1 << 6); + bool more = i < fdiffs.size() - 1; + data[offset++] = ((fdiffs[i] & 0x3f) << 2) | + (extended ? kFlageXtendedOffset : 0) | + (more ? kFlagMoreDependencies : 0); + if (extended) { + data[offset++] = fdiffs[i] >> 6; + } + } + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h new file mode 100644 index 0000000000..b4f686565f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h @@ -0,0 +1,45 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_GENERIC_FRAME_DESCRIPTOR_EXTENSION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_GENERIC_FRAME_DESCRIPTOR_EXTENSION_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/rtp_parameters.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h" + +namespace webrtc { + +// Trait to read/write the generic frame descriptor, the early version of the +// dependency descriptor extension. Usage of this rtp header extension is +// discouraged in favor of the dependency descriptor. +class RtpGenericFrameDescriptorExtension00 { + public: + using value_type = RtpGenericFrameDescriptor; + static constexpr RTPExtensionType kId = kRtpExtensionGenericFrameDescriptor; + static constexpr absl::string_view Uri() { + return RtpExtension::kGenericFrameDescriptorUri00; + } + static constexpr int kMaxSizeBytes = 16; + + static bool Parse(rtc::ArrayView<const uint8_t> data, + RtpGenericFrameDescriptor* descriptor); + static size_t ValueSize(const RtpGenericFrameDescriptor& descriptor); + static bool Write(rtc::ArrayView<uint8_t> data, + const RtpGenericFrameDescriptor& descriptor); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_GENERIC_FRAME_DESCRIPTOR_EXTENSION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension_unittest.cc new file mode 100644 index 0000000000..7c27326f75 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension_unittest.cc @@ -0,0 +1,264 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h" + +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; + +constexpr uint8_t kDeprecatedFlags = 0x30; + +TEST(RtpGenericFrameDescriptorExtensionTest, + ParseFirstPacketOfIndependenSubFrame) { + const int kTemporalLayer = 5; + constexpr uint8_t kRaw[] = {0x80 | kTemporalLayer, 0x49, 0x12, 0x34}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + + EXPECT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_FALSE(descriptor.LastPacketInSubFrame()); + + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), IsEmpty()); + EXPECT_EQ(descriptor.TemporalLayer(), kTemporalLayer); + EXPECT_EQ(descriptor.SpatialLayersBitmask(), 0x49); + EXPECT_EQ(descriptor.FrameId(), 0x3412); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, + WriteFirstPacketOfIndependenSubFrame) { + const int kTemporalLayer = 5; + uint8_t kRaw[] = {0x80 | kTemporalLayer | kDeprecatedFlags, 0x49, 0x12, 0x34}; + RtpGenericFrameDescriptor descriptor; + + descriptor.SetFirstPacketInSubFrame(true); + descriptor.SetTemporalLayer(kTemporalLayer); + descriptor.SetSpatialLayersBitmask(0x49); + descriptor.SetFrameId(0x3412); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, ParseLastPacketOfSubFrame) { + constexpr uint8_t kRaw[] = {0x40}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + + EXPECT_FALSE(descriptor.FirstPacketInSubFrame()); + EXPECT_TRUE(descriptor.LastPacketInSubFrame()); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, WriteLastPacketOfSubFrame) { + uint8_t kRaw[] = {0x40 | kDeprecatedFlags}; + + RtpGenericFrameDescriptor descriptor; + descriptor.SetLastPacketInSubFrame(true); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, ParseMinShortFrameDependencies) { + constexpr uint16_t kDiff = 1; + constexpr uint8_t kRaw[] = {0x88, 0x01, 0x00, 0x00, 0x04}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + ASSERT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), ElementsAre(kDiff)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, WriteMinShortFrameDependencies) { + constexpr uint16_t kDiff = 1; + uint8_t kRaw[] = {0x88 | kDeprecatedFlags, 0x01, 0x00, 0x00, 0x04}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.AddFrameDependencyDiff(kDiff); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, ParseMaxShortFrameDependencies) { + constexpr uint16_t kDiff = 0x3f; + constexpr uint8_t kRaw[] = {0xb8, 0x01, 0x00, 0x00, 0xfc}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + ASSERT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), ElementsAre(kDiff)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, WriteMaxShortFrameDependencies) { + constexpr uint16_t kDiff = 0x3f; + uint8_t kRaw[] = {0x88 | kDeprecatedFlags, 0x01, 0x00, 0x00, 0xfc}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.AddFrameDependencyDiff(kDiff); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, ParseMinLongFrameDependencies) { + constexpr uint16_t kDiff = 0x40; + constexpr uint8_t kRaw[] = {0xb8, 0x01, 0x00, 0x00, 0x02, 0x01}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + ASSERT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), ElementsAre(kDiff)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, WriteMinLongFrameDependencies) { + constexpr uint16_t kDiff = 0x40; + uint8_t kRaw[] = {0x88 | kDeprecatedFlags, 0x01, 0x00, 0x00, 0x02, 0x01}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.AddFrameDependencyDiff(kDiff); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, + ParseLongFrameDependenciesAsBigEndian) { + constexpr uint16_t kDiff = 0x7654 >> 2; + constexpr uint8_t kRaw[] = {0xb8, 0x01, 0x00, 0x00, 0x54 | 0x02, 0x76}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + ASSERT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), ElementsAre(kDiff)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, + WriteLongFrameDependenciesAsBigEndian) { + constexpr uint16_t kDiff = 0x7654 >> 2; + uint8_t kRaw[] = { + 0x88 | kDeprecatedFlags, 0x01, 0x00, 0x00, 0x54 | 0x02, 0x76}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.AddFrameDependencyDiff(kDiff); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, ParseMaxLongFrameDependencies) { + constexpr uint16_t kDiff = 0x3fff; + constexpr uint8_t kRaw[] = {0xb8, 0x01, 0x00, 0x00, 0xfe, 0xff}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + ASSERT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), ElementsAre(kDiff)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, WriteMaxLongFrameDependencies) { + constexpr uint16_t kDiff = 0x3fff; + uint8_t kRaw[] = {0x88 | kDeprecatedFlags, 0x01, 0x00, 0x00, 0xfe, 0xff}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.AddFrameDependencyDiff(kDiff); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, ParseTwoFrameDependencies) { + constexpr uint16_t kDiff1 = 9; + constexpr uint16_t kDiff2 = 15; + constexpr uint8_t kRaw[] = { + 0xb8, 0x01, 0x00, 0x00, (kDiff1 << 2) | 0x01, kDiff2 << 2}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + ASSERT_TRUE(descriptor.FirstPacketInSubFrame()); + EXPECT_THAT(descriptor.FrameDependenciesDiffs(), ElementsAre(kDiff1, kDiff2)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, WriteTwoFrameDependencies) { + constexpr uint16_t kDiff1 = 9; + constexpr uint16_t kDiff2 = 15; + uint8_t kRaw[] = {0x88 | kDeprecatedFlags, 0x01, 0x00, 0x00, + (kDiff1 << 2) | 0x01, kDiff2 << 2}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.AddFrameDependencyDiff(kDiff1); + descriptor.AddFrameDependencyDiff(kDiff2); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, + ParseResolutionOnIndependentFrame) { + constexpr int kWidth = 0x2468; + constexpr int kHeight = 0x6543; + constexpr uint8_t kRaw[] = {0xb0, 0x01, 0x00, 0x00, 0x24, 0x68, 0x65, 0x43}; + RtpGenericFrameDescriptor descriptor; + + ASSERT_TRUE(RtpGenericFrameDescriptorExtension00::Parse(kRaw, &descriptor)); + EXPECT_EQ(descriptor.Width(), kWidth); + EXPECT_EQ(descriptor.Height(), kHeight); +} + +TEST(RtpGenericFrameDescriptorExtensionTest, + WriteResolutionOnIndependentFrame) { + constexpr int kWidth = 0x2468; + constexpr int kHeight = 0x6543; + uint8_t kRaw[] = { + 0x80 | kDeprecatedFlags, 0x01, 0x00, 0x00, 0x24, 0x68, 0x65, 0x43}; + RtpGenericFrameDescriptor descriptor; + descriptor.SetFirstPacketInSubFrame(true); + descriptor.SetResolution(kWidth, kHeight); + + ASSERT_EQ(RtpGenericFrameDescriptorExtension00::ValueSize(descriptor), + sizeof(kRaw)); + uint8_t buffer[sizeof(kRaw)]; + EXPECT_TRUE(RtpGenericFrameDescriptorExtension00::Write(buffer, descriptor)); + EXPECT_THAT(buffer, ElementsAreArray(kRaw)); +} +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc new file mode 100644 index 0000000000..4b8c7b5385 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc @@ -0,0 +1,169 @@ +/* + * 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 "modules/rtp_rtcp/include/rtp_header_extension_map.h" + +#include "absl/strings/string_view.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_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +struct ExtensionInfo { + RTPExtensionType type; + absl::string_view uri; +}; + +template <typename Extension> +constexpr ExtensionInfo CreateExtensionInfo() { + return {Extension::kId, Extension::Uri()}; +} + +constexpr ExtensionInfo kExtensions[] = { + CreateExtensionInfo<TransmissionOffset>(), + CreateExtensionInfo<AudioLevel>(), + CreateExtensionInfo<CsrcAudioLevel>(), + CreateExtensionInfo<AbsoluteSendTime>(), + CreateExtensionInfo<AbsoluteCaptureTimeExtension>(), + CreateExtensionInfo<VideoOrientation>(), + CreateExtensionInfo<TransportSequenceNumber>(), + CreateExtensionInfo<TransportSequenceNumberV2>(), + CreateExtensionInfo<PlayoutDelayLimits>(), + CreateExtensionInfo<VideoContentTypeExtension>(), + CreateExtensionInfo<RtpVideoLayersAllocationExtension>(), + CreateExtensionInfo<VideoTimingExtension>(), + CreateExtensionInfo<RtpStreamId>(), + CreateExtensionInfo<RepairedRtpStreamId>(), + CreateExtensionInfo<RtpMid>(), + CreateExtensionInfo<RtpGenericFrameDescriptorExtension00>(), + CreateExtensionInfo<RtpDependencyDescriptorExtension>(), + CreateExtensionInfo<ColorSpaceExtension>(), + CreateExtensionInfo<InbandComfortNoiseExtension>(), + CreateExtensionInfo<VideoFrameTrackingIdExtension>(), +}; + +// Because of kRtpExtensionNone, NumberOfExtension is 1 bigger than the actual +// number of known extensions. +static_assert(arraysize(kExtensions) == + static_cast<int>(kRtpExtensionNumberOfExtensions) - 1, + "kExtensions expect to list all known extensions"); + +} // namespace + +constexpr RTPExtensionType RtpHeaderExtensionMap::kInvalidType; +constexpr int RtpHeaderExtensionMap::kInvalidId; + +RtpHeaderExtensionMap::RtpHeaderExtensionMap() : RtpHeaderExtensionMap(false) {} + +RtpHeaderExtensionMap::RtpHeaderExtensionMap(bool extmap_allow_mixed) + : extmap_allow_mixed_(extmap_allow_mixed) { + for (auto& id : ids_) + id = kInvalidId; +} + +RtpHeaderExtensionMap::RtpHeaderExtensionMap( + rtc::ArrayView<const RtpExtension> extensions) + : RtpHeaderExtensionMap(false) { + for (const RtpExtension& extension : extensions) + RegisterByUri(extension.id, extension.uri); +} + +void RtpHeaderExtensionMap::Reset( + rtc::ArrayView<const RtpExtension> extensions) { + for (auto& id : ids_) + id = kInvalidId; + for (const RtpExtension& extension : extensions) + RegisterByUri(extension.id, extension.uri); +} + +bool RtpHeaderExtensionMap::RegisterByType(int id, RTPExtensionType type) { + for (const ExtensionInfo& extension : kExtensions) + if (type == extension.type) + return Register(id, extension.type, extension.uri); + RTC_DCHECK_NOTREACHED(); + return false; +} + +bool RtpHeaderExtensionMap::RegisterByUri(int id, absl::string_view uri) { + for (const ExtensionInfo& extension : kExtensions) + if (uri == extension.uri) + return Register(id, extension.type, extension.uri); + RTC_LOG(LS_WARNING) << "Unknown extension uri:'" << uri << "', id: " << id + << '.'; + return false; +} + +RTPExtensionType RtpHeaderExtensionMap::GetType(int id) const { + RTC_DCHECK_GE(id, RtpExtension::kMinId); + RTC_DCHECK_LE(id, RtpExtension::kMaxId); + for (int type = kRtpExtensionNone + 1; type < kRtpExtensionNumberOfExtensions; + ++type) { + if (ids_[type] == id) { + return static_cast<RTPExtensionType>(type); + } + } + return kInvalidType; +} + +void RtpHeaderExtensionMap::Deregister(absl::string_view uri) { + for (const ExtensionInfo& extension : kExtensions) { + if (extension.uri == uri) { + ids_[extension.type] = kInvalidId; + break; + } + } +} + +bool RtpHeaderExtensionMap::Register(int id, + RTPExtensionType type, + absl::string_view uri) { + RTC_DCHECK_GT(type, kRtpExtensionNone); + RTC_DCHECK_LT(type, kRtpExtensionNumberOfExtensions); + + if (id < RtpExtension::kMinId || id > RtpExtension::kMaxId) { + RTC_LOG(LS_WARNING) << "Failed to register extension uri:'" << uri + << "' with invalid id:" << id << "."; + return false; + } + + RTPExtensionType registered_type = GetType(id); + if (registered_type == type) { // Same type/id pair already registered. + RTC_LOG(LS_VERBOSE) << "Reregistering extension uri:'" << uri + << "', id:" << id; + return true; + } + + if (registered_type != + kInvalidType) { // `id` used by another extension type. + RTC_LOG(LS_WARNING) << "Failed to register extension uri:'" << uri + << "', id:" << id + << ". Id already in use by extension type " + << static_cast<int>(registered_type); + return false; + } + if (IsRegistered(type)) { + RTC_LOG(LS_WARNING) << "Illegal reregistration for uri: " << uri + << " is previously registered with id " << GetId(type) + << " and cannot be reregistered with id " << id; + return false; + } + + // There is a run-time check above id fits into uint8_t. + ids_[type] = static_cast<uint8_t>(id); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map_unittest.cc new file mode 100644 index 0000000000..42842cc876 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map_unittest.cc @@ -0,0 +1,115 @@ +/* + * 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 "modules/rtp_rtcp/include/rtp_header_extension_map.h" + +#include <vector> + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(RtpHeaderExtensionTest, RegisterByType) { + RtpHeaderExtensionMap map; + EXPECT_FALSE(map.IsRegistered(TransmissionOffset::kId)); + + EXPECT_TRUE(map.RegisterByType(3, TransmissionOffset::kId)); + + EXPECT_TRUE(map.IsRegistered(TransmissionOffset::kId)); + EXPECT_EQ(3, map.GetId(TransmissionOffset::kId)); + EXPECT_EQ(TransmissionOffset::kId, map.GetType(3)); +} + +TEST(RtpHeaderExtensionTest, RegisterByUri) { + RtpHeaderExtensionMap map; + + EXPECT_TRUE(map.RegisterByUri(3, TransmissionOffset::Uri())); + + EXPECT_TRUE(map.IsRegistered(TransmissionOffset::kId)); + EXPECT_EQ(3, map.GetId(TransmissionOffset::kId)); + EXPECT_EQ(TransmissionOffset::kId, map.GetType(3)); +} + +TEST(RtpHeaderExtensionTest, RegisterWithTrait) { + RtpHeaderExtensionMap map; + + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + + EXPECT_TRUE(map.IsRegistered(TransmissionOffset::kId)); + EXPECT_EQ(3, map.GetId(TransmissionOffset::kId)); + EXPECT_EQ(TransmissionOffset::kId, map.GetType(3)); +} + +TEST(RtpHeaderExtensionTest, RegisterDuringContruction) { + const std::vector<RtpExtension> config = {{TransmissionOffset::Uri(), 1}, + {AbsoluteSendTime::Uri(), 3}}; + const RtpHeaderExtensionMap map(config); + + EXPECT_EQ(1, map.GetId(TransmissionOffset::kId)); + EXPECT_EQ(3, map.GetId(AbsoluteSendTime::kId)); +} + +TEST(RtpHeaderExtensionTest, RegisterTwoByteHeaderExtensions) { + RtpHeaderExtensionMap map; + // Two-byte header extension needed for id: [15-255]. + EXPECT_TRUE(map.Register<TransmissionOffset>(18)); + EXPECT_TRUE(map.Register<AbsoluteSendTime>(255)); +} + +TEST(RtpHeaderExtensionTest, RegisterIllegalArg) { + RtpHeaderExtensionMap map; + // Valid range for id: [1-255]. + EXPECT_FALSE(map.Register<TransmissionOffset>(0)); + EXPECT_FALSE(map.Register<TransmissionOffset>(256)); +} + +TEST(RtpHeaderExtensionTest, Idempotent) { + RtpHeaderExtensionMap map; + + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + + map.Deregister(TransmissionOffset::Uri()); + map.Deregister(TransmissionOffset::Uri()); +} + +TEST(RtpHeaderExtensionTest, NonUniqueId) { + RtpHeaderExtensionMap map; + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + + EXPECT_FALSE(map.Register<AudioLevel>(3)); + EXPECT_TRUE(map.Register<AudioLevel>(4)); +} + +TEST(RtpHeaderExtensionTest, GetType) { + RtpHeaderExtensionMap map; + EXPECT_EQ(RtpHeaderExtensionMap::kInvalidType, map.GetType(3)); + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + + EXPECT_EQ(TransmissionOffset::kId, map.GetType(3)); +} + +TEST(RtpHeaderExtensionTest, GetId) { + RtpHeaderExtensionMap map; + EXPECT_EQ(RtpHeaderExtensionMap::kInvalidId, + map.GetId(TransmissionOffset::kId)); + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + + EXPECT_EQ(3, map.GetId(TransmissionOffset::kId)); +} + +TEST(RtpHeaderExtensionTest, RemapFails) { + RtpHeaderExtensionMap map; + EXPECT_TRUE(map.Register<TransmissionOffset>(3)); + EXPECT_FALSE(map.Register<TransmissionOffset>(4)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.cc new file mode 100644 index 0000000000..4acbcf4e6b --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.cc @@ -0,0 +1,48 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_header_extension_size.h" + +#include "api/rtp_parameters.h" + +namespace webrtc { + +int RtpHeaderExtensionSize(rtc::ArrayView<const RtpExtensionSize> extensions, + const RtpHeaderExtensionMap& registered_extensions) { + // RFC3550 Section 5.3.1 + static constexpr int kExtensionBlockHeaderSize = 4; + + int values_size = 0; + int num_extensions = 0; + int each_extension_header_size = 1; + for (const RtpExtensionSize& extension : extensions) { + int id = registered_extensions.GetId(extension.type); + if (id == RtpHeaderExtensionMap::kInvalidId) + continue; + // All extensions should use same size header. Check if the `extension` + // forces to switch to two byte header that allows larger id and value size. + if (id > RtpExtension::kOneByteHeaderExtensionMaxId || + extension.value_size > + RtpExtension::kOneByteHeaderExtensionMaxValueSize) { + each_extension_header_size = 2; + } + values_size += extension.value_size; + num_extensions++; + } + if (values_size == 0) + return 0; + int size = kExtensionBlockHeaderSize + + each_extension_header_size * num_extensions + values_size; + // Extension size specified in 32bit words, + // so result must be multiple of 4 bytes. Round up. + return size + 3 - (size + 3) % 4; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.h new file mode 100644 index 0000000000..1fb2eb2a1e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.h @@ -0,0 +1,32 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSION_SIZE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSION_SIZE_H_ + +#include "api/array_view.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { + +struct RtpExtensionSize { + RTPExtensionType type; + int value_size; +}; + +// Calculates rtp header extension size in bytes assuming packet contain +// all `extensions` with provided `value_size`. +// Counts only extensions present among `registered_extensions`. +int RtpHeaderExtensionSize(rtc::ArrayView<const RtpExtensionSize> extensions, + const RtpHeaderExtensionMap& registered_extensions); + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSION_SIZE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size_unittest.cc new file mode 100644 index 0000000000..5cc26bc652 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size_unittest.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 "modules/rtp_rtcp/source/rtp_header_extension_size.h" + +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "test/gtest.h" + +namespace { + +using ::webrtc::RtpExtensionSize; +using ::webrtc::RtpHeaderExtensionMap; +using ::webrtc::RtpHeaderExtensionSize; +using ::webrtc::RtpMid; +using ::webrtc::RtpStreamId; + +// id for 1-byte header extension. actual value is irrelevant for these tests. +constexpr int kId = 1; +// id that forces to use 2-byte header extension. +constexpr int kIdForceTwoByteHeader = 15; + +TEST(RtpHeaderExtensionSizeTest, ReturnsZeroIfNoExtensionsAreRegistered) { + constexpr RtpExtensionSize kExtensionSizes[] = {{RtpMid::kId, 3}}; + // Register different extension than ask size for. + RtpHeaderExtensionMap registered; + registered.Register<RtpStreamId>(kId); + + EXPECT_EQ(RtpHeaderExtensionSize(kExtensionSizes, registered), 0); +} + +TEST(RtpHeaderExtensionSizeTest, IncludesSizeOfExtensionHeaders) { + constexpr RtpExtensionSize kExtensionSizes[] = {{RtpMid::kId, 3}}; + RtpHeaderExtensionMap registered; + registered.Register<RtpMid>(kId); + + // 4 bytes for extension block header + 1 byte for individual extension header + // + 3 bytes for the value. + EXPECT_EQ(RtpHeaderExtensionSize(kExtensionSizes, registered), 8); +} + +TEST(RtpHeaderExtensionSizeTest, RoundsUpTo32bitAlignmant) { + constexpr RtpExtensionSize kExtensionSizes[] = {{RtpMid::kId, 5}}; + RtpHeaderExtensionMap registered; + registered.Register<RtpMid>(kId); + + // 10 bytes of data including headers + 2 bytes of padding. + EXPECT_EQ(RtpHeaderExtensionSize(kExtensionSizes, registered), 12); +} + +TEST(RtpHeaderExtensionSizeTest, SumsSeveralExtensions) { + constexpr RtpExtensionSize kExtensionSizes[] = {{RtpMid::kId, 16}, + {RtpStreamId::kId, 2}}; + RtpHeaderExtensionMap registered; + registered.Register<RtpMid>(kId); + registered.Register<RtpStreamId>(14); + + // 4 bytes for extension block header + 18 bytes of value + + // 2 bytes for two headers + EXPECT_EQ(RtpHeaderExtensionSize(kExtensionSizes, registered), 24); +} + +TEST(RtpHeaderExtensionSizeTest, LargeIdForce2BytesHeader) { + constexpr RtpExtensionSize kExtensionSizes[] = {{RtpMid::kId, 3}, + {RtpStreamId::kId, 2}}; + RtpHeaderExtensionMap registered; + registered.Register<RtpMid>(kId); + registered.Register<RtpStreamId>(kIdForceTwoByteHeader); + + // 4 bytes for extension block header + 5 bytes of value + + // 2*2 bytes for two headers + 3 bytes of padding. + EXPECT_EQ(RtpHeaderExtensionSize(kExtensionSizes, registered), 16); +} + +TEST(RtpHeaderExtensionSizeTest, LargeValueForce2BytesHeader) { + constexpr RtpExtensionSize kExtensionSizes[] = {{RtpMid::kId, 17}, + {RtpStreamId::kId, 4}}; + RtpHeaderExtensionMap registered; + registered.Register<RtpMid>(1); + registered.Register<RtpStreamId>(2); + + // 4 bytes for extension block header + 21 bytes of value + + // 2*2 bytes for two headers + 3 byte of padding. + EXPECT_EQ(RtpHeaderExtensionSize(kExtensionSizes, registered), 32); +} + +} // namespace diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc new file mode 100644 index 0000000000..de29fd2075 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc @@ -0,0 +1,934 @@ +/* + * 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/rtp_rtcp/source/rtp_header_extensions.h" + +#include <string.h> + +#include <cmath> +#include <cstdint> +#include <limits> + +#include "absl/strings/string_view.h" +#include "modules/rtp_rtcp/include/rtp_cvo.h" +#include "modules/rtp_rtcp/source/byte_io.h" +// TODO(bug:9855) Move kNoSpatialIdx from vp9_globals.h to common_constants +#include "modules/video_coding/codecs/interface/common_constants.h" +#include "modules/video_coding/codecs/vp9/include/vp9_globals.h" +#include "rtc_base/checks.h" + +namespace webrtc { +// Absolute send time in RTP streams. +// +// The absolute send time is signaled to the receiver in-band using the +// general mechanism for RTP header extensions [RFC8285]. The payload +// of this extension (the transmitted value) is a 24-bit unsigned integer +// containing the sender's current time in seconds as a fixed point number +// with 18 bits fractional part. +// +// The form of the absolute send time extension block: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 | absolute send time | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType AbsoluteSendTime::kId; +constexpr uint8_t AbsoluteSendTime::kValueSizeBytes; + +bool AbsoluteSendTime::Parse(rtc::ArrayView<const uint8_t> data, + uint32_t* time_24bits) { + if (data.size() != 3) + return false; + *time_24bits = ByteReader<uint32_t, 3>::ReadBigEndian(data.data()); + return true; +} + +bool AbsoluteSendTime::Write(rtc::ArrayView<uint8_t> data, + uint32_t time_24bits) { + RTC_DCHECK_EQ(data.size(), 3); + RTC_DCHECK_LE(time_24bits, 0x00FFFFFF); + ByteWriter<uint32_t, 3>::WriteBigEndian(data.data(), time_24bits); + return true; +} + +// Absolute Capture Time +// +// The Absolute Capture Time extension is used to stamp RTP packets with a NTP +// timestamp showing when the first audio or video frame in a packet was +// originally captured. The intent of this extension is to provide a way to +// accomplish audio-to-video synchronization when RTCP-terminating intermediate +// systems (e.g. mixers) are involved. +// +// Data layout of the shortened version of abs-capture-time: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=7 | absolute capture timestamp (bit 0-23) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | absolute capture timestamp (bit 24-55) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... (56-63) | +// +-+-+-+-+-+-+-+-+ +// +// Data layout of the extended version of abs-capture-time: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=15| absolute capture timestamp (bit 0-23) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | absolute capture timestamp (bit 24-55) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... (56-63) | estimated capture clock offset (bit 0-23) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | estimated capture clock offset (bit 24-55) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... (56-63) | +// +-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType AbsoluteCaptureTimeExtension::kId; +constexpr uint8_t AbsoluteCaptureTimeExtension::kValueSizeBytes; +constexpr uint8_t AbsoluteCaptureTimeExtension:: + kValueSizeBytesWithoutEstimatedCaptureClockOffset; + +bool AbsoluteCaptureTimeExtension::Parse(rtc::ArrayView<const uint8_t> data, + AbsoluteCaptureTime* extension) { + if (data.size() != kValueSizeBytes && + data.size() != kValueSizeBytesWithoutEstimatedCaptureClockOffset) { + return false; + } + + extension->absolute_capture_timestamp = + ByteReader<uint64_t>::ReadBigEndian(data.data()); + + if (data.size() != kValueSizeBytesWithoutEstimatedCaptureClockOffset) { + extension->estimated_capture_clock_offset = + ByteReader<int64_t>::ReadBigEndian(data.data() + 8); + } + + return true; +} + +size_t AbsoluteCaptureTimeExtension::ValueSize( + const AbsoluteCaptureTime& extension) { + if (extension.estimated_capture_clock_offset != absl::nullopt) { + return kValueSizeBytes; + } else { + return kValueSizeBytesWithoutEstimatedCaptureClockOffset; + } +} + +bool AbsoluteCaptureTimeExtension::Write(rtc::ArrayView<uint8_t> data, + const AbsoluteCaptureTime& extension) { + RTC_DCHECK_EQ(data.size(), ValueSize(extension)); + + ByteWriter<uint64_t>::WriteBigEndian(data.data(), + extension.absolute_capture_timestamp); + + if (data.size() != kValueSizeBytesWithoutEstimatedCaptureClockOffset) { + ByteWriter<int64_t>::WriteBigEndian( + data.data() + 8, extension.estimated_capture_clock_offset.value()); + } + + return true; +} + +// An RTP Header Extension for Client-to-Mixer Audio Level Indication +// +// https://tools.ietf.org/html/rfc6464 +// +// The form of the audio level extension block: +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=0 |V| level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Sample Audio Level Encoding Using the One-Byte Header Format +// +// 0 1 2 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=1 |V| level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Sample Audio Level Encoding Using the Two-Byte Header Format + +constexpr RTPExtensionType AudioLevel::kId; +constexpr uint8_t AudioLevel::kValueSizeBytes; + +bool AudioLevel::Parse(rtc::ArrayView<const uint8_t> data, + bool* voice_activity, + uint8_t* audio_level) { + // One-byte and two-byte format share the same data definition. + if (data.size() != 1) + return false; + *voice_activity = (data[0] & 0x80) != 0; + *audio_level = data[0] & 0x7F; + return true; +} + +bool AudioLevel::Write(rtc::ArrayView<uint8_t> data, + bool voice_activity, + uint8_t audio_level) { + // One-byte and two-byte format share the same data definition. + RTC_DCHECK_EQ(data.size(), 1); + RTC_CHECK_LE(audio_level, 0x7f); + data[0] = (voice_activity ? 0x80 : 0x00) | audio_level; + return true; +} + +#if !defined(WEBRTC_MOZILLA_BUILD) +// An RTP Header Extension for Mixer-to-Client Audio Level Indication +// +// https://tools.ietf.org/html/rfc6465 +// +// The form of the audio level extension block: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 |0| level 1 |0| level 2 |0| level 3 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Sample Audio Level Encoding Using the One-Byte Header Format +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=3 |0| level 1 |0| level 2 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |0| level 3 | 0 (pad) | ... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Sample Audio Level Encoding Using the Two-Byte Header Format +constexpr RTPExtensionType CsrcAudioLevel::kId; +constexpr uint8_t CsrcAudioLevel::kMaxValueSizeBytes; + +bool CsrcAudioLevel::Parse(rtc::ArrayView<const uint8_t> data, + std::vector<uint8_t>* csrc_audio_levels) { + if (data.size() > kRtpCsrcSize) { + return false; + } + csrc_audio_levels->resize(data.size()); + for (size_t i = 0; i < data.size(); i++) { + (*csrc_audio_levels)[i] = data[i] & 0x7F; + } + return true; +} + +size_t CsrcAudioLevel::ValueSize( + rtc::ArrayView<const uint8_t> csrc_audio_levels) { + return csrc_audio_levels.size(); +} + +bool CsrcAudioLevel::Write(rtc::ArrayView<uint8_t> data, + rtc::ArrayView<const uint8_t> csrc_audio_levels) { + RTC_CHECK_LE(csrc_audio_levels.size(), kRtpCsrcSize); + if (csrc_audio_levels.size() != data.size()) { + return false; + } + for (size_t i = 0; i < csrc_audio_levels.size(); i++) { + data[i] = csrc_audio_levels[i] & 0x7F; + } + return true; +} +#endif + +// From RFC 5450: Transmission Time Offsets in RTP Streams. +// +// The transmission time is signaled to the receiver in-band using the +// general mechanism for RTP header extensions [RFC8285]. The payload +// of this extension (the transmitted value) is a 24-bit signed integer. +// When added to the RTP timestamp of the packet, it represents the +// "effective" RTP transmission time of the packet, on the RTP +// timescale. +// +// The form of the transmission offset extension block: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 | transmission offset | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType TransmissionOffset::kId; +constexpr uint8_t TransmissionOffset::kValueSizeBytes; + +bool TransmissionOffset::Parse(rtc::ArrayView<const uint8_t> data, + int32_t* rtp_time) { + if (data.size() != 3) + return false; + *rtp_time = ByteReader<int32_t, 3>::ReadBigEndian(data.data()); + return true; +} + +bool TransmissionOffset::Write(rtc::ArrayView<uint8_t> data, int32_t rtp_time) { + RTC_DCHECK_EQ(data.size(), 3); + RTC_DCHECK_LE(rtp_time, 0x00ffffff); + ByteWriter<int32_t, 3>::WriteBigEndian(data.data(), rtp_time); + return true; +} + +// TransportSequenceNumber +// +// 0 1 2 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=1 |transport-wide sequence number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType TransportSequenceNumber::kId; +constexpr uint8_t TransportSequenceNumber::kValueSizeBytes; + +bool TransportSequenceNumber::Parse(rtc::ArrayView<const uint8_t> data, + uint16_t* transport_sequence_number) { + if (data.size() != kValueSizeBytes) + return false; + *transport_sequence_number = ByteReader<uint16_t>::ReadBigEndian(data.data()); + return true; +} + +bool TransportSequenceNumber::Write(rtc::ArrayView<uint8_t> data, + uint16_t transport_sequence_number) { + RTC_DCHECK_EQ(data.size(), ValueSize(transport_sequence_number)); + ByteWriter<uint16_t>::WriteBigEndian(data.data(), transport_sequence_number); + return true; +} + +// TransportSequenceNumberV2 +// +// In addition to the format used for TransportSequencNumber, V2 also supports +// the following packet format where two extra bytes are used to specify that +// the sender requests immediate feedback. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=3 |transport-wide sequence number |T| seq count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |seq count cont.| +// +-+-+-+-+-+-+-+-+ +// +// The bit `T` determines whether the feedback should include timing information +// or not and `seq_count` determines how many packets the feedback packet should +// cover including the current packet. If `seq_count` is zero no feedback is +// requested. +constexpr RTPExtensionType TransportSequenceNumberV2::kId; +constexpr uint8_t TransportSequenceNumberV2::kValueSizeBytes; +constexpr uint8_t + TransportSequenceNumberV2::kValueSizeBytesWithoutFeedbackRequest; +constexpr uint16_t TransportSequenceNumberV2::kIncludeTimestampsBit; + +bool TransportSequenceNumberV2::Parse( + rtc::ArrayView<const uint8_t> data, + uint16_t* transport_sequence_number, + absl::optional<FeedbackRequest>* feedback_request) { + if (data.size() != kValueSizeBytes && + data.size() != kValueSizeBytesWithoutFeedbackRequest) + return false; + + *transport_sequence_number = ByteReader<uint16_t>::ReadBigEndian(data.data()); + + *feedback_request = absl::nullopt; + if (data.size() == kValueSizeBytes) { + uint16_t feedback_request_raw = + ByteReader<uint16_t>::ReadBigEndian(data.data() + 2); + bool include_timestamps = + (feedback_request_raw & kIncludeTimestampsBit) != 0; + uint16_t sequence_count = feedback_request_raw & ~kIncludeTimestampsBit; + + // If `sequence_count` is zero no feedback is requested. + if (sequence_count != 0) { + *feedback_request = {include_timestamps, sequence_count}; + } + } + return true; +} + +bool TransportSequenceNumberV2::Write( + rtc::ArrayView<uint8_t> data, + uint16_t transport_sequence_number, + const absl::optional<FeedbackRequest>& feedback_request) { + RTC_DCHECK_EQ(data.size(), + ValueSize(transport_sequence_number, feedback_request)); + + ByteWriter<uint16_t>::WriteBigEndian(data.data(), transport_sequence_number); + + if (feedback_request) { + RTC_DCHECK_GE(feedback_request->sequence_count, 0); + RTC_DCHECK_LT(feedback_request->sequence_count, kIncludeTimestampsBit); + uint16_t feedback_request_raw = + feedback_request->sequence_count | + (feedback_request->include_timestamps ? kIncludeTimestampsBit : 0); + ByteWriter<uint16_t>::WriteBigEndian(data.data() + 2, feedback_request_raw); + } + return true; +} + +// Coordination of Video Orientation in RTP streams. +// +// Coordination of Video Orientation consists in signaling of the current +// orientation of the image captured on the sender side to the receiver for +// appropriate rendering and displaying. +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=0 |0 0 0 0 C F R R| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType VideoOrientation::kId; +constexpr uint8_t VideoOrientation::kValueSizeBytes; + +bool VideoOrientation::Parse(rtc::ArrayView<const uint8_t> data, + VideoRotation* rotation) { + if (data.size() != 1) + return false; + *rotation = ConvertCVOByteToVideoRotation(data[0]); + return true; +} + +bool VideoOrientation::Write(rtc::ArrayView<uint8_t> data, + VideoRotation rotation) { + RTC_DCHECK_EQ(data.size(), 1); + data[0] = ConvertVideoRotationToCVOByte(rotation); + return true; +} + +bool VideoOrientation::Parse(rtc::ArrayView<const uint8_t> data, + uint8_t* value) { + if (data.size() != 1) + return false; + *value = data[0]; + return true; +} + +bool VideoOrientation::Write(rtc::ArrayView<uint8_t> data, uint8_t value) { + RTC_DCHECK_EQ(data.size(), 1); + data[0] = value; + return true; +} + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 | MIN delay | MAX delay | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType PlayoutDelayLimits::kId; +constexpr uint8_t PlayoutDelayLimits::kValueSizeBytes; + +bool PlayoutDelayLimits::Parse(rtc::ArrayView<const uint8_t> data, + VideoPlayoutDelay* playout_delay) { + RTC_DCHECK(playout_delay); + if (data.size() != 3) + return false; + uint32_t raw = ByteReader<uint32_t, 3>::ReadBigEndian(data.data()); + uint16_t min_raw = (raw >> 12); + uint16_t max_raw = (raw & 0xfff); + if (min_raw > max_raw) + return false; + playout_delay->min_ms = min_raw * kGranularityMs; + playout_delay->max_ms = max_raw * kGranularityMs; + return true; +} + +bool PlayoutDelayLimits::Write(rtc::ArrayView<uint8_t> data, + const VideoPlayoutDelay& playout_delay) { + RTC_DCHECK_EQ(data.size(), 3); + RTC_DCHECK_LE(0, playout_delay.min_ms); + RTC_DCHECK_LE(playout_delay.min_ms, playout_delay.max_ms); + RTC_DCHECK_LE(playout_delay.max_ms, kMaxMs); + // Convert MS to value to be sent on extension header. + uint32_t min_delay = playout_delay.min_ms / kGranularityMs; + uint32_t max_delay = playout_delay.max_ms / kGranularityMs; + ByteWriter<uint32_t, 3>::WriteBigEndian(data.data(), + (min_delay << 12) | max_delay); + return true; +} + +#if defined(WEBRTC_MOZILLA_BUILD) +// CSRCAudioLevel +// Sample Audio Level Encoding Using the One-Byte Header Format +// Note that the range of len is 1 to 15 which is encoded as 0 to 14 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 |0| level 1 |0| level 2 |0| level 3 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +constexpr RTPExtensionType CsrcAudioLevel::kId; +constexpr const char* CsrcAudioLevel::kUri; + +bool CsrcAudioLevel::Parse(rtc::ArrayView<const uint8_t> data, + CsrcAudioLevelList* csrcAudioLevels) { + if (data.size() < 1 || data.size() > kRtpCsrcSize) + return false; + csrcAudioLevels->numAudioLevels = data.size(); + for(uint8_t i = 0; i < csrcAudioLevels->numAudioLevels; i++) { + // Ensure range is 0 to 127 inclusive + csrcAudioLevels->arrOfAudioLevels[i] = 0x7f & data[i]; + } + return true; +} + +size_t CsrcAudioLevel::ValueSize(const CsrcAudioLevelList& csrcAudioLevels) { + return csrcAudioLevels.numAudioLevels; +} + +bool CsrcAudioLevel::Write(rtc::ArrayView<uint8_t> data, + const CsrcAudioLevelList& csrcAudioLevels) { + RTC_DCHECK_GE(csrcAudioLevels.numAudioLevels, 0); + for(uint8_t i = 0; i < csrcAudioLevels.numAudioLevels; i++) { + data[i] = csrcAudioLevels.arrOfAudioLevels[i] & 0x7f; + } + // This extension if used must have at least one audio level + return csrcAudioLevels.numAudioLevels; +} +#endif + +// Video Content Type. +// +// E.g. default video or screenshare. +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=0 | Content type | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +constexpr RTPExtensionType VideoContentTypeExtension::kId; +constexpr uint8_t VideoContentTypeExtension::kValueSizeBytes; + +bool VideoContentTypeExtension::Parse(rtc::ArrayView<const uint8_t> data, + VideoContentType* content_type) { + if (data.size() == 1 && + videocontenttypehelpers::IsValidContentType(data[0])) { + *content_type = static_cast<VideoContentType>(data[0]); + return true; + } + return false; +} + +bool VideoContentTypeExtension::Write(rtc::ArrayView<uint8_t> data, + VideoContentType content_type) { + RTC_DCHECK_EQ(data.size(), 1); + data[0] = static_cast<uint8_t>(content_type); + return true; +} + +// Video Timing. +// 6 timestamps in milliseconds counted from capture time stored in rtp header: +// encode start/finish, packetization complete, pacer exit and reserved for +// modification by the network modification. `flags` is a bitmask and has the +// following allowed values: +// 0 = Valid data, but no flags available (backwards compatibility) +// 1 = Frame marked as timing frame due to cyclic timer. +// 2 = Frame marked as timing frame due to size being outside limit. +// 255 = Invalid. The whole timing frame extension should be ignored. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=12| flags | encode start ms delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | encode finish ms delta | packetizer finish ms delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | pacer exit ms delta | network timestamp ms delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | network2 timestamp ms delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +constexpr RTPExtensionType VideoTimingExtension::kId; +constexpr uint8_t VideoTimingExtension::kValueSizeBytes; +constexpr uint8_t VideoTimingExtension::kFlagsOffset; +constexpr uint8_t VideoTimingExtension::kEncodeStartDeltaOffset; +constexpr uint8_t VideoTimingExtension::kEncodeFinishDeltaOffset; +constexpr uint8_t VideoTimingExtension::kPacketizationFinishDeltaOffset; +constexpr uint8_t VideoTimingExtension::kPacerExitDeltaOffset; +constexpr uint8_t VideoTimingExtension::kNetworkTimestampDeltaOffset; +constexpr uint8_t VideoTimingExtension::kNetwork2TimestampDeltaOffset; + +bool VideoTimingExtension::Parse(rtc::ArrayView<const uint8_t> data, + VideoSendTiming* timing) { + RTC_DCHECK(timing); + // TODO(sprang): Deprecate support for old wire format. + ptrdiff_t off = 0; + switch (data.size()) { + case kValueSizeBytes - 1: + timing->flags = 0; + off = 1; // Old wire format without the flags field. + break; + case kValueSizeBytes: + timing->flags = ByteReader<uint8_t>::ReadBigEndian(data.data()); + break; + default: + return false; + } + + timing->encode_start_delta_ms = ByteReader<uint16_t>::ReadBigEndian( + data.data() + kEncodeStartDeltaOffset - off); + timing->encode_finish_delta_ms = ByteReader<uint16_t>::ReadBigEndian( + data.data() + kEncodeFinishDeltaOffset - off); + timing->packetization_finish_delta_ms = ByteReader<uint16_t>::ReadBigEndian( + data.data() + kPacketizationFinishDeltaOffset - off); + timing->pacer_exit_delta_ms = ByteReader<uint16_t>::ReadBigEndian( + data.data() + kPacerExitDeltaOffset - off); + timing->network_timestamp_delta_ms = ByteReader<uint16_t>::ReadBigEndian( + data.data() + kNetworkTimestampDeltaOffset - off); + timing->network2_timestamp_delta_ms = ByteReader<uint16_t>::ReadBigEndian( + data.data() + kNetwork2TimestampDeltaOffset - off); + return true; +} + +bool VideoTimingExtension::Write(rtc::ArrayView<uint8_t> data, + const VideoSendTiming& timing) { + RTC_DCHECK_EQ(data.size(), 1 + 2 * 6); + ByteWriter<uint8_t>::WriteBigEndian(data.data() + kFlagsOffset, timing.flags); + ByteWriter<uint16_t>::WriteBigEndian(data.data() + kEncodeStartDeltaOffset, + timing.encode_start_delta_ms); + ByteWriter<uint16_t>::WriteBigEndian(data.data() + kEncodeFinishDeltaOffset, + timing.encode_finish_delta_ms); + ByteWriter<uint16_t>::WriteBigEndian( + data.data() + kPacketizationFinishDeltaOffset, + timing.packetization_finish_delta_ms); + ByteWriter<uint16_t>::WriteBigEndian(data.data() + kPacerExitDeltaOffset, + timing.pacer_exit_delta_ms); + ByteWriter<uint16_t>::WriteBigEndian( + data.data() + kNetworkTimestampDeltaOffset, + timing.network_timestamp_delta_ms); + ByteWriter<uint16_t>::WriteBigEndian( + data.data() + kNetwork2TimestampDeltaOffset, + timing.network2_timestamp_delta_ms); + return true; +} + +bool VideoTimingExtension::Write(rtc::ArrayView<uint8_t> data, + uint16_t time_delta_ms, + uint8_t offset) { + RTC_DCHECK_GE(data.size(), offset + 2); + RTC_DCHECK_LE(offset, kValueSizeBytes - sizeof(uint16_t)); + ByteWriter<uint16_t>::WriteBigEndian(data.data() + offset, time_delta_ms); + return true; +} + +// Color space including HDR metadata as an optional field. +// +// RTP header extension to carry color space information and optionally HDR +// metadata. The float values in the HDR metadata struct are upscaled by a +// static factor and transmitted as unsigned integers. +// +// Data layout of color space with HDR metadata (two-byte RTP header extension) +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | length=28 | primaries | transfer | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | matrix |range+chr.sit. | luminance_max | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | luminance_min | mastering_metadata.| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |primary_r.x and .y | mastering_metadata.| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |primary_g.x and .y | mastering_metadata.| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |primary_b.x and .y | mastering_metadata.| +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |white.x and .y | max_content_light_level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | max_frame_average_light_level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Data layout of color space w/o HDR metadata (one-byte RTP header extension) +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L = 3 | primaries | transfer | matrix | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |range+chr.sit. | +// +-+-+-+-+-+-+-+-+ + +constexpr RTPExtensionType ColorSpaceExtension::kId; +constexpr uint8_t ColorSpaceExtension::kValueSizeBytes; + +bool ColorSpaceExtension::Parse(rtc::ArrayView<const uint8_t> data, + ColorSpace* color_space) { + RTC_DCHECK(color_space); + if (data.size() != kValueSizeBytes && + data.size() != kValueSizeBytesWithoutHdrMetadata) + return false; + + size_t offset = 0; + // Read color space information. + if (!color_space->set_primaries_from_uint8(data[offset++])) + return false; + if (!color_space->set_transfer_from_uint8(data[offset++])) + return false; + if (!color_space->set_matrix_from_uint8(data[offset++])) + return false; + + uint8_t range_and_chroma_siting = data[offset++]; + if (!color_space->set_range_from_uint8((range_and_chroma_siting >> 4) & 0x03)) + return false; + if (!color_space->set_chroma_siting_horizontal_from_uint8( + (range_and_chroma_siting >> 2) & 0x03)) + return false; + if (!color_space->set_chroma_siting_vertical_from_uint8( + range_and_chroma_siting & 0x03)) + return false; + + // Read HDR metadata if it exists, otherwise clear it. + if (data.size() == kValueSizeBytesWithoutHdrMetadata) { + color_space->set_hdr_metadata(nullptr); + } else { + HdrMetadata hdr_metadata; + offset += ParseHdrMetadata(data.subview(offset), &hdr_metadata); + if (!hdr_metadata.Validate()) + return false; + color_space->set_hdr_metadata(&hdr_metadata); + } + RTC_DCHECK_EQ(ValueSize(*color_space), offset); + return true; +} + +bool ColorSpaceExtension::Write(rtc::ArrayView<uint8_t> data, + const ColorSpace& color_space) { + RTC_DCHECK_EQ(data.size(), ValueSize(color_space)); + size_t offset = 0; + // Write color space information. + data[offset++] = static_cast<uint8_t>(color_space.primaries()); + data[offset++] = static_cast<uint8_t>(color_space.transfer()); + data[offset++] = static_cast<uint8_t>(color_space.matrix()); + data[offset++] = CombineRangeAndChromaSiting( + color_space.range(), color_space.chroma_siting_horizontal(), + color_space.chroma_siting_vertical()); + + // Write HDR metadata if it exists. + if (color_space.hdr_metadata()) { + offset += + WriteHdrMetadata(data.subview(offset), *color_space.hdr_metadata()); + } + RTC_DCHECK_EQ(ValueSize(color_space), offset); + return true; +} + +// Combines range and chroma siting into one byte with the following bit layout: +// bits 0-1 Chroma siting vertical. +// 2-3 Chroma siting horizontal. +// 4-5 Range. +// 6-7 Unused. +uint8_t ColorSpaceExtension::CombineRangeAndChromaSiting( + ColorSpace::RangeID range, + ColorSpace::ChromaSiting chroma_siting_horizontal, + ColorSpace::ChromaSiting chroma_siting_vertical) { + RTC_DCHECK_LE(static_cast<uint8_t>(range), 3); + RTC_DCHECK_LE(static_cast<uint8_t>(chroma_siting_horizontal), 3); + RTC_DCHECK_LE(static_cast<uint8_t>(chroma_siting_vertical), 3); + return (static_cast<uint8_t>(range) << 4) | + (static_cast<uint8_t>(chroma_siting_horizontal) << 2) | + static_cast<uint8_t>(chroma_siting_vertical); +} + +size_t ColorSpaceExtension::ParseHdrMetadata(rtc::ArrayView<const uint8_t> data, + HdrMetadata* hdr_metadata) { + RTC_DCHECK_EQ(data.size(), + kValueSizeBytes - kValueSizeBytesWithoutHdrMetadata); + size_t offset = 0; + offset += ParseLuminance(data.data() + offset, + &hdr_metadata->mastering_metadata.luminance_max, + kLuminanceMaxDenominator); + offset += ParseLuminance(data.data() + offset, + &hdr_metadata->mastering_metadata.luminance_min, + kLuminanceMinDenominator); + offset += ParseChromaticity(data.data() + offset, + &hdr_metadata->mastering_metadata.primary_r); + offset += ParseChromaticity(data.data() + offset, + &hdr_metadata->mastering_metadata.primary_g); + offset += ParseChromaticity(data.data() + offset, + &hdr_metadata->mastering_metadata.primary_b); + offset += ParseChromaticity(data.data() + offset, + &hdr_metadata->mastering_metadata.white_point); + hdr_metadata->max_content_light_level = + ByteReader<uint16_t>::ReadBigEndian(data.data() + offset); + offset += 2; + hdr_metadata->max_frame_average_light_level = + ByteReader<uint16_t>::ReadBigEndian(data.data() + offset); + offset += 2; + return offset; +} + +size_t ColorSpaceExtension::ParseChromaticity( + const uint8_t* data, + HdrMasteringMetadata::Chromaticity* p) { + uint16_t chromaticity_x_scaled = ByteReader<uint16_t>::ReadBigEndian(data); + uint16_t chromaticity_y_scaled = + ByteReader<uint16_t>::ReadBigEndian(data + 2); + p->x = static_cast<float>(chromaticity_x_scaled) / kChromaticityDenominator; + p->y = static_cast<float>(chromaticity_y_scaled) / kChromaticityDenominator; + return 4; // Return number of bytes read. +} + +size_t ColorSpaceExtension::ParseLuminance(const uint8_t* data, + float* f, + int denominator) { + uint16_t luminance_scaled = ByteReader<uint16_t>::ReadBigEndian(data); + *f = static_cast<float>(luminance_scaled) / denominator; + return 2; // Return number of bytes read. +} + +size_t ColorSpaceExtension::WriteHdrMetadata(rtc::ArrayView<uint8_t> data, + const HdrMetadata& hdr_metadata) { + RTC_DCHECK_EQ(data.size(), + kValueSizeBytes - kValueSizeBytesWithoutHdrMetadata); + RTC_DCHECK(hdr_metadata.Validate()); + size_t offset = 0; + offset += WriteLuminance(data.data() + offset, + hdr_metadata.mastering_metadata.luminance_max, + kLuminanceMaxDenominator); + offset += WriteLuminance(data.data() + offset, + hdr_metadata.mastering_metadata.luminance_min, + kLuminanceMinDenominator); + offset += WriteChromaticity(data.data() + offset, + hdr_metadata.mastering_metadata.primary_r); + offset += WriteChromaticity(data.data() + offset, + hdr_metadata.mastering_metadata.primary_g); + offset += WriteChromaticity(data.data() + offset, + hdr_metadata.mastering_metadata.primary_b); + offset += WriteChromaticity(data.data() + offset, + hdr_metadata.mastering_metadata.white_point); + + ByteWriter<uint16_t>::WriteBigEndian(data.data() + offset, + hdr_metadata.max_content_light_level); + offset += 2; + ByteWriter<uint16_t>::WriteBigEndian( + data.data() + offset, hdr_metadata.max_frame_average_light_level); + offset += 2; + return offset; +} + +size_t ColorSpaceExtension::WriteChromaticity( + uint8_t* data, + const HdrMasteringMetadata::Chromaticity& p) { + RTC_DCHECK_GE(p.x, 0.0f); + RTC_DCHECK_LE(p.x, 1.0f); + RTC_DCHECK_GE(p.y, 0.0f); + RTC_DCHECK_LE(p.y, 1.0f); + ByteWriter<uint16_t>::WriteBigEndian( + data, std::round(p.x * kChromaticityDenominator)); + ByteWriter<uint16_t>::WriteBigEndian( + data + 2, std::round(p.y * kChromaticityDenominator)); + return 4; // Return number of bytes written. +} + +size_t ColorSpaceExtension::WriteLuminance(uint8_t* data, + float f, + int denominator) { + RTC_DCHECK_GE(f, 0.0f); + float upscaled_value = f * denominator; + RTC_DCHECK_LE(upscaled_value, std::numeric_limits<uint16_t>::max()); + ByteWriter<uint16_t>::WriteBigEndian(data, std::round(upscaled_value)); + return 2; // Return number of bytes written. +} + +bool BaseRtpStringExtension::Parse(rtc::ArrayView<const uint8_t> data, + std::string* str) { + if (data.empty() || data[0] == 0) // Valid string extension can't be empty. + return false; + const char* cstr = reinterpret_cast<const char*>(data.data()); + // If there is a \0 character in the middle of the `data`, treat it as end + // of the string. Well-formed string extensions shouldn't contain it. + str->assign(cstr, strnlen(cstr, data.size())); + RTC_DCHECK(!str->empty()); + return true; +} + +bool BaseRtpStringExtension::Write(rtc::ArrayView<uint8_t> data, + absl::string_view str) { + if (str.size() > kMaxValueSizeBytes) { + return false; + } + RTC_DCHECK_EQ(data.size(), str.size()); + RTC_DCHECK_GE(str.size(), 1); + memcpy(data.data(), str.data(), str.size()); + return true; +} + +// Constant declarations for RTP header extension types. +constexpr RTPExtensionType RtpStreamId::kId; +constexpr RTPExtensionType RepairedRtpStreamId::kId; +constexpr RTPExtensionType RtpMid::kId; + +// An RTP Header Extension for Inband Comfort Noise +// +// The form of the audio level extension block: +// +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=0 |N| level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Sample Audio Level Encoding Using the One-Byte Header Format +// +// 0 1 2 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=1 |N| level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// Sample Audio Level Encoding Using the Two-Byte Header Format + +constexpr RTPExtensionType InbandComfortNoiseExtension::kId; +constexpr uint8_t InbandComfortNoiseExtension::kValueSizeBytes; +constexpr const char InbandComfortNoiseExtension::kUri[]; + +bool InbandComfortNoiseExtension::Parse(rtc::ArrayView<const uint8_t> data, + absl::optional<uint8_t>* level) { + if (data.size() != kValueSizeBytes) + return false; + *level = (data[0] & 0b1000'0000) != 0 + ? absl::nullopt + : absl::make_optional(data[0] & 0b0111'1111); + return true; +} + +bool InbandComfortNoiseExtension::Write(rtc::ArrayView<uint8_t> data, + absl::optional<uint8_t> level) { + RTC_DCHECK_EQ(data.size(), kValueSizeBytes); + data[0] = 0b0000'0000; + if (level) { + if (*level > 127) { + return false; + } + data[0] = 0b1000'0000 | *level; + } + return true; +} + +// VideoFrameTrackingIdExtension +// +// 0 1 2 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=1 | video-frame-tracking-id | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +constexpr RTPExtensionType VideoFrameTrackingIdExtension::kId; +constexpr uint8_t VideoFrameTrackingIdExtension::kValueSizeBytes; + +bool VideoFrameTrackingIdExtension::Parse(rtc::ArrayView<const uint8_t> data, + uint16_t* video_frame_tracking_id) { + if (data.size() != kValueSizeBytes) { + return false; + } + *video_frame_tracking_id = ByteReader<uint16_t>::ReadBigEndian(data.data()); + return true; +} + +bool VideoFrameTrackingIdExtension::Write(rtc::ArrayView<uint8_t> data, + uint16_t video_frame_tracking_id) { + RTC_DCHECK_EQ(data.size(), kValueSizeBytes); + ByteWriter<uint16_t>::WriteBigEndian(data.data(), video_frame_tracking_id); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.h new file mode 100644 index 0000000000..4b4984bf6d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.h @@ -0,0 +1,386 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/rtp_headers.h" +#include "api/rtp_parameters.h" +#include "api/units/timestamp.h" +#include "api/video/color_space.h" +#include "api/video/video_content_type.h" +#include "api/video/video_rotation.h" +#include "api/video/video_timing.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { + +class AbsoluteSendTime { + public: + using value_type = uint32_t; + static constexpr RTPExtensionType kId = kRtpExtensionAbsoluteSendTime; + static constexpr uint8_t kValueSizeBytes = 3; + static constexpr absl::string_view Uri() { + return RtpExtension::kAbsSendTimeUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, uint32_t* time_24bits); + static size_t ValueSize(uint32_t time_24bits) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, uint32_t time_24bits); + + static constexpr uint32_t To24Bits(Timestamp time) { + int64_t time_us = time.us() % (int64_t{1 << 6} * 1'000'000); + int64_t time6x18 = (time_us << 18) / 1'000'000; + RTC_DCHECK_GE(time6x18, 0); + RTC_DCHECK_LT(time6x18, 1 << 24); + return static_cast<uint32_t>(time6x18); + } +}; + +class AbsoluteCaptureTimeExtension { + public: + using value_type = AbsoluteCaptureTime; + static constexpr RTPExtensionType kId = kRtpExtensionAbsoluteCaptureTime; + static constexpr uint8_t kValueSizeBytes = 16; + static constexpr uint8_t kValueSizeBytesWithoutEstimatedCaptureClockOffset = + 8; + static constexpr absl::string_view Uri() { + return RtpExtension::kAbsoluteCaptureTimeUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + AbsoluteCaptureTime* extension); + static size_t ValueSize(const AbsoluteCaptureTime& extension); + static bool Write(rtc::ArrayView<uint8_t> data, + const AbsoluteCaptureTime& extension); +}; + +class AudioLevel { + public: + static constexpr RTPExtensionType kId = kRtpExtensionAudioLevel; + static constexpr uint8_t kValueSizeBytes = 1; + static constexpr absl::string_view Uri() { + return RtpExtension::kAudioLevelUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + bool* voice_activity, + uint8_t* audio_level); + static size_t ValueSize(bool voice_activity, uint8_t audio_level) { + return kValueSizeBytes; + } + static bool Write(rtc::ArrayView<uint8_t> data, + bool voice_activity, + uint8_t audio_level); +}; + +#if !defined(WEBRTC_MOZILLA_BUILD) +class CsrcAudioLevel { + public: + static constexpr RTPExtensionType kId = kRtpExtensionCsrcAudioLevel; + static constexpr uint8_t kMaxValueSizeBytes = 15; + static constexpr absl::string_view Uri() { + return RtpExtension::kCsrcAudioLevelsUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + std::vector<uint8_t>* csrc_audio_levels); + static size_t ValueSize(rtc::ArrayView<const uint8_t> csrc_audio_levels); + static bool Write(rtc::ArrayView<uint8_t> data, + rtc::ArrayView<const uint8_t> csrc_audio_levels); +}; +#endif + +class TransmissionOffset { + public: + using value_type = int32_t; + static constexpr RTPExtensionType kId = kRtpExtensionTransmissionTimeOffset; + static constexpr uint8_t kValueSizeBytes = 3; + static constexpr absl::string_view Uri() { + return RtpExtension::kTimestampOffsetUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, int32_t* rtp_time); + static size_t ValueSize(int32_t rtp_time) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, int32_t rtp_time); +}; + +class TransportSequenceNumber { + public: + using value_type = uint16_t; + static constexpr RTPExtensionType kId = kRtpExtensionTransportSequenceNumber; + static constexpr uint8_t kValueSizeBytes = 2; + static constexpr absl::string_view Uri() { + return RtpExtension::kTransportSequenceNumberUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + uint16_t* transport_sequence_number); + static size_t ValueSize(uint16_t /*transport_sequence_number*/) { + return kValueSizeBytes; + } + static bool Write(rtc::ArrayView<uint8_t> data, + uint16_t transport_sequence_number); +}; + +class TransportSequenceNumberV2 { + public: + static constexpr RTPExtensionType kId = + kRtpExtensionTransportSequenceNumber02; + static constexpr uint8_t kValueSizeBytes = 4; + static constexpr uint8_t kValueSizeBytesWithoutFeedbackRequest = 2; + static constexpr absl::string_view Uri() { + return RtpExtension::kTransportSequenceNumberV2Uri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + uint16_t* transport_sequence_number, + absl::optional<FeedbackRequest>* feedback_request); + static size_t ValueSize( + uint16_t /*transport_sequence_number*/, + const absl::optional<FeedbackRequest>& feedback_request) { + return feedback_request ? kValueSizeBytes + : kValueSizeBytesWithoutFeedbackRequest; + } + static bool Write(rtc::ArrayView<uint8_t> data, + uint16_t transport_sequence_number, + const absl::optional<FeedbackRequest>& feedback_request); + + private: + static constexpr uint16_t kIncludeTimestampsBit = 1 << 15; +}; + +class VideoOrientation { + public: + using value_type = VideoRotation; + static constexpr RTPExtensionType kId = kRtpExtensionVideoRotation; + static constexpr uint8_t kValueSizeBytes = 1; + static constexpr absl::string_view Uri() { + return RtpExtension::kVideoRotationUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, VideoRotation* value); + static size_t ValueSize(VideoRotation) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, VideoRotation value); + static bool Parse(rtc::ArrayView<const uint8_t> data, uint8_t* value); + static size_t ValueSize(uint8_t value) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, uint8_t value); +}; + +class PlayoutDelayLimits { + public: + using value_type = VideoPlayoutDelay; + static constexpr RTPExtensionType kId = kRtpExtensionPlayoutDelay; + static constexpr uint8_t kValueSizeBytes = 3; + static constexpr absl::string_view Uri() { + return RtpExtension::kPlayoutDelayUri; + } + + // Playout delay in milliseconds. A playout delay limit (min or max) + // has 12 bits allocated. This allows a range of 0-4095 values which + // translates to a range of 0-40950 in milliseconds. + static constexpr int kGranularityMs = 10; + // Maximum playout delay value in milliseconds. + static constexpr int kMaxMs = 0xfff * kGranularityMs; // 40950. + + static bool Parse(rtc::ArrayView<const uint8_t> data, + VideoPlayoutDelay* playout_delay); + static size_t ValueSize(const VideoPlayoutDelay&) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, + const VideoPlayoutDelay& playout_delay); +}; + +class VideoContentTypeExtension { + public: + using value_type = VideoContentType; + static constexpr RTPExtensionType kId = kRtpExtensionVideoContentType; + static constexpr uint8_t kValueSizeBytes = 1; + static constexpr absl::string_view Uri() { + return RtpExtension::kVideoContentTypeUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + VideoContentType* content_type); + static size_t ValueSize(VideoContentType) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, + VideoContentType content_type); +}; + +class VideoTimingExtension { + public: + using value_type = VideoSendTiming; + static constexpr RTPExtensionType kId = kRtpExtensionVideoTiming; + static constexpr uint8_t kValueSizeBytes = 13; + static constexpr absl::string_view Uri() { + return RtpExtension::kVideoTimingUri; + } + + // Offsets of the fields in the RTP header extension, counting from the first + // byte after the one-byte header. + static constexpr uint8_t kFlagsOffset = 0; + static constexpr uint8_t kEncodeStartDeltaOffset = 1; + static constexpr uint8_t kEncodeFinishDeltaOffset = 3; + static constexpr uint8_t kPacketizationFinishDeltaOffset = 5; + static constexpr uint8_t kPacerExitDeltaOffset = 7; + static constexpr uint8_t kNetworkTimestampDeltaOffset = 9; + static constexpr uint8_t kNetwork2TimestampDeltaOffset = 11; + + static bool Parse(rtc::ArrayView<const uint8_t> data, + VideoSendTiming* timing); + static size_t ValueSize(const VideoSendTiming&) { return kValueSizeBytes; } + static bool Write(rtc::ArrayView<uint8_t> data, + const VideoSendTiming& timing); + + static size_t ValueSize(uint16_t time_delta_ms, uint8_t idx) { + return kValueSizeBytes; + } + // Writes only single time delta to position idx. + static bool Write(rtc::ArrayView<uint8_t> data, + uint16_t time_delta_ms, + uint8_t offset); +}; + +class ColorSpaceExtension { + public: + using value_type = ColorSpace; + static constexpr RTPExtensionType kId = kRtpExtensionColorSpace; + static constexpr uint8_t kValueSizeBytes = 28; + static constexpr uint8_t kValueSizeBytesWithoutHdrMetadata = 4; + static constexpr absl::string_view Uri() { + return RtpExtension::kColorSpaceUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + ColorSpace* color_space); + static size_t ValueSize(const ColorSpace& color_space) { + return color_space.hdr_metadata() ? kValueSizeBytes + : kValueSizeBytesWithoutHdrMetadata; + } + static bool Write(rtc::ArrayView<uint8_t> data, + const ColorSpace& color_space); + + private: + static constexpr int kChromaticityDenominator = 50000; // 0.00002 resolution. + static constexpr int kLuminanceMaxDenominator = 1; // 1 resolution. + static constexpr int kLuminanceMinDenominator = 10000; // 0.0001 resolution. + + static uint8_t CombineRangeAndChromaSiting( + ColorSpace::RangeID range, + ColorSpace::ChromaSiting chroma_siting_horizontal, + ColorSpace::ChromaSiting chroma_siting_vertical); + static size_t ParseHdrMetadata(rtc::ArrayView<const uint8_t> data, + HdrMetadata* hdr_metadata); + static size_t ParseChromaticity(const uint8_t* data, + HdrMasteringMetadata::Chromaticity* p); + static size_t ParseLuminance(const uint8_t* data, float* f, int denominator); + static size_t WriteHdrMetadata(rtc::ArrayView<uint8_t> data, + const HdrMetadata& hdr_metadata); + static size_t WriteChromaticity(uint8_t* data, + const HdrMasteringMetadata::Chromaticity& p); + static size_t WriteLuminance(uint8_t* data, float f, int denominator); +}; + +#if defined(WEBRTC_MOZILLA_BUILD) +class CsrcAudioLevel { + public: + static constexpr RTPExtensionType kId = kRtpExtensionCsrcAudioLevel; + static constexpr absl::string_view Uri() { + return RtpExtension::kCsrcAudioLevelsUri; + } + static constexpr const char* kUri = + "urn:ietf:params:rtp-hdrext:csrc-audio-level"; + + static bool Parse(rtc::ArrayView<const uint8_t> data, + CsrcAudioLevelList* csrcAudioLevels); + static size_t ValueSize(const CsrcAudioLevelList& csrcAudioLevels); + static bool Write(rtc::ArrayView<uint8_t> data, const CsrcAudioLevelList& csrcAudioLevels); +}; +#endif + +// Base extension class for RTP header extensions which are strings. +// Subclasses must defined kId and kUri static constexpr members. +class BaseRtpStringExtension { + public: + using value_type = std::string; + // String RTP header extensions are limited to 16 bytes because it is the + // maximum length that can be encoded with one-byte header extensions. + static constexpr uint8_t kMaxValueSizeBytes = 16; + + static bool Parse(rtc::ArrayView<const uint8_t> data, std::string* str); + static size_t ValueSize(absl::string_view str) { return str.size(); } + static bool Write(rtc::ArrayView<uint8_t> data, absl::string_view str); +}; + +class RtpStreamId : public BaseRtpStringExtension { + public: + static constexpr RTPExtensionType kId = kRtpExtensionRtpStreamId; + static constexpr absl::string_view Uri() { return RtpExtension::kRidUri; } +}; + +class RepairedRtpStreamId : public BaseRtpStringExtension { + public: + static constexpr RTPExtensionType kId = kRtpExtensionRepairedRtpStreamId; + static constexpr absl::string_view Uri() { + return RtpExtension::kRepairedRidUri; + } +}; + +class RtpMid : public BaseRtpStringExtension { + public: + static constexpr RTPExtensionType kId = kRtpExtensionMid; + static constexpr absl::string_view Uri() { return RtpExtension::kMidUri; } +}; + +class InbandComfortNoiseExtension { + public: + using value_type = absl::optional<uint8_t>; + + static constexpr RTPExtensionType kId = kRtpExtensionInbandComfortNoise; + static constexpr uint8_t kValueSizeBytes = 1; + static constexpr const char kUri[] = + "http://www.webrtc.org/experiments/rtp-hdrext/inband-cn"; + static constexpr absl::string_view Uri() { return kUri; } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + absl::optional<uint8_t>* level); + static size_t ValueSize(absl::optional<uint8_t> level) { + return kValueSizeBytes; + } + static bool Write(rtc::ArrayView<uint8_t> data, + absl::optional<uint8_t> level); +}; + +class VideoFrameTrackingIdExtension { + public: + using value_type = uint16_t; + static constexpr RTPExtensionType kId = kRtpExtensionVideoFrameTrackingId; + static constexpr uint8_t kValueSizeBytes = 2; + static constexpr absl::string_view Uri() { + return RtpExtension::kVideoFrameTrackingIdUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + uint16_t* video_frame_tracking_id); + static size_t ValueSize(uint16_t /*video_frame_tracking_id*/) { + return kValueSizeBytes; + } + static bool Write(rtc::ArrayView<uint8_t> data, + uint16_t video_frame_tracking_id); +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_HEADER_EXTENSIONS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.cc new file mode 100644 index 0000000000..fd2f5c5ae8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.cc @@ -0,0 +1,712 @@ +/* + * 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/rtp_rtcp/source/rtp_packet.h" + +#include <cstdint> +#include <cstring> +#include <utility> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +namespace { +constexpr size_t kFixedHeaderSize = 12; +constexpr uint8_t kRtpVersion = 2; +constexpr uint16_t kOneByteExtensionProfileId = 0xBEDE; +constexpr uint16_t kTwoByteExtensionProfileId = 0x1000; +constexpr uint16_t kTwobyteExtensionProfileIdAppBitsFilter = 0xfff0; +constexpr size_t kOneByteExtensionHeaderLength = 1; +constexpr size_t kTwoByteExtensionHeaderLength = 2; +constexpr size_t kDefaultPacketSize = 1500; +} // namespace + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P|X| CC |M| PT | sequence number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | timestamp | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | synchronization source (SSRC) identifier | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | Contributing source (CSRC) identifiers | +// | .... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | header eXtension profile id | length in 32bits | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Extensions | +// | .... | +// +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// | Payload | +// | .... : padding... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | padding | Padding size | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +RtpPacket::RtpPacket() : RtpPacket(nullptr, kDefaultPacketSize) {} + +RtpPacket::RtpPacket(const ExtensionManager* extensions) + : RtpPacket(extensions, kDefaultPacketSize) {} + +RtpPacket::RtpPacket(const ExtensionManager* extensions, size_t capacity) + : extensions_(extensions ? *extensions : ExtensionManager()), + buffer_(capacity) { + RTC_DCHECK_GE(capacity, kFixedHeaderSize); + Clear(); +} + +RtpPacket::RtpPacket(const RtpPacket&) = default; +RtpPacket::RtpPacket(RtpPacket&&) = default; +RtpPacket& RtpPacket::operator=(const RtpPacket&) = default; +RtpPacket& RtpPacket::operator=(RtpPacket&&) = default; +RtpPacket::~RtpPacket() = default; + +void RtpPacket::IdentifyExtensions(ExtensionManager extensions) { + extensions_ = std::move(extensions); +} + +bool RtpPacket::Parse(const uint8_t* buffer, size_t buffer_size) { + if (!ParseBuffer(buffer, buffer_size)) { + Clear(); + return false; + } + buffer_.SetData(buffer, buffer_size); + RTC_DCHECK_EQ(size(), buffer_size); + return true; +} + +bool RtpPacket::Parse(rtc::ArrayView<const uint8_t> packet) { + return Parse(packet.data(), packet.size()); +} + +bool RtpPacket::Parse(rtc::CopyOnWriteBuffer buffer) { + if (!ParseBuffer(buffer.cdata(), buffer.size())) { + Clear(); + return false; + } + size_t buffer_size = buffer.size(); + buffer_ = std::move(buffer); + RTC_DCHECK_EQ(size(), buffer_size); + return true; +} + +std::vector<uint32_t> RtpPacket::Csrcs() const { + size_t num_csrc = data()[0] & 0x0F; + RTC_DCHECK_GE(capacity(), kFixedHeaderSize + num_csrc * 4); + std::vector<uint32_t> csrcs(num_csrc); + for (size_t i = 0; i < num_csrc; ++i) { + csrcs[i] = + ByteReader<uint32_t>::ReadBigEndian(&data()[kFixedHeaderSize + i * 4]); + } + return csrcs; +} + +void RtpPacket::CopyHeaderFrom(const RtpPacket& packet) { + marker_ = packet.marker_; + payload_type_ = packet.payload_type_; + sequence_number_ = packet.sequence_number_; + timestamp_ = packet.timestamp_; + ssrc_ = packet.ssrc_; + payload_offset_ = packet.payload_offset_; + extensions_ = packet.extensions_; + extension_entries_ = packet.extension_entries_; + extensions_size_ = packet.extensions_size_; + buffer_ = packet.buffer_.Slice(0, packet.headers_size()); + // Reset payload and padding. + payload_size_ = 0; + padding_size_ = 0; +} + +void RtpPacket::SetMarker(bool marker_bit) { + marker_ = marker_bit; + if (marker_) { + WriteAt(1, data()[1] | 0x80); + } else { + WriteAt(1, data()[1] & 0x7F); + } +} + +void RtpPacket::SetPayloadType(uint8_t payload_type) { + RTC_DCHECK_LE(payload_type, 0x7Fu); + payload_type_ = payload_type; + WriteAt(1, (data()[1] & 0x80) | payload_type); +} + +void RtpPacket::SetSequenceNumber(uint16_t seq_no) { + sequence_number_ = seq_no; + ByteWriter<uint16_t>::WriteBigEndian(WriteAt(2), seq_no); +} + +void RtpPacket::SetTimestamp(uint32_t timestamp) { + timestamp_ = timestamp; + ByteWriter<uint32_t>::WriteBigEndian(WriteAt(4), timestamp); +} + +void RtpPacket::SetSsrc(uint32_t ssrc) { + ssrc_ = ssrc; + ByteWriter<uint32_t>::WriteBigEndian(WriteAt(8), ssrc); +} + +void RtpPacket::ZeroMutableExtensions() { + for (const ExtensionInfo& extension : extension_entries_) { + switch (extensions_.GetType(extension.id)) { + case RTPExtensionType::kRtpExtensionNone: { + RTC_LOG(LS_WARNING) << "Unidentified extension in the packet."; + break; + } + case RTPExtensionType::kRtpExtensionVideoTiming: { + // Nullify last entries, starting at pacer delay. + // These are set by pacer and SFUs + if (VideoTimingExtension::kPacerExitDeltaOffset < extension.length) { + memset( + WriteAt(extension.offset + + VideoTimingExtension::kPacerExitDeltaOffset), + 0, + extension.length - VideoTimingExtension::kPacerExitDeltaOffset); + } + break; + } + case RTPExtensionType::kRtpExtensionTransportSequenceNumber: + case RTPExtensionType::kRtpExtensionTransportSequenceNumber02: + case RTPExtensionType::kRtpExtensionTransmissionTimeOffset: + case RTPExtensionType::kRtpExtensionAbsoluteSendTime: { + // Nullify whole extension, as it's filled in the pacer. + memset(WriteAt(extension.offset), 0, extension.length); + break; + } + case RTPExtensionType::kRtpExtensionAudioLevel: +#if !defined(WEBRTC_MOZILLA_BUILD) + case RTPExtensionType::kRtpExtensionCsrcAudioLevel: +#endif + case RTPExtensionType::kRtpExtensionAbsoluteCaptureTime: + case RTPExtensionType::kRtpExtensionColorSpace: + case RTPExtensionType::kRtpExtensionGenericFrameDescriptor: + case RTPExtensionType::kRtpExtensionDependencyDescriptor: + case RTPExtensionType::kRtpExtensionMid: + case RTPExtensionType::kRtpExtensionNumberOfExtensions: + case RTPExtensionType::kRtpExtensionPlayoutDelay: + case RTPExtensionType::kRtpExtensionRepairedRtpStreamId: + case RTPExtensionType::kRtpExtensionRtpStreamId: + case RTPExtensionType::kRtpExtensionVideoContentType: + case RTPExtensionType::kRtpExtensionVideoLayersAllocation: + case RTPExtensionType::kRtpExtensionVideoRotation: + case RTPExtensionType::kRtpExtensionInbandComfortNoise: + case RTPExtensionType::kRtpExtensionVideoFrameTrackingId: { + // Non-mutable extension. Don't change it. + break; + } +#if defined(WEBRTC_MOZILLA_BUILD) + case RTPExtensionType::kRtpExtensionCsrcAudioLevel: { + // TODO: This is a Mozilla addition, we need to add a handler for this. + RTC_CHECK(false); + } +#endif + } + } +} + +void RtpPacket::SetCsrcs(rtc::ArrayView<const uint32_t> csrcs) { + RTC_DCHECK_EQ(extensions_size_, 0); + RTC_DCHECK_EQ(payload_size_, 0); + RTC_DCHECK_EQ(padding_size_, 0); + RTC_DCHECK_LE(csrcs.size(), 0x0fu); + RTC_DCHECK_LE(kFixedHeaderSize + 4 * csrcs.size(), capacity()); + payload_offset_ = kFixedHeaderSize + 4 * csrcs.size(); + WriteAt(0, (data()[0] & 0xF0) | rtc::dchecked_cast<uint8_t>(csrcs.size())); + size_t offset = kFixedHeaderSize; + for (uint32_t csrc : csrcs) { + ByteWriter<uint32_t>::WriteBigEndian(WriteAt(offset), csrc); + offset += 4; + } + buffer_.SetSize(payload_offset_); +} + +rtc::ArrayView<uint8_t> RtpPacket::AllocateRawExtension(int id, size_t length) { + RTC_DCHECK_GE(id, RtpExtension::kMinId); + RTC_DCHECK_LE(id, RtpExtension::kMaxId); + RTC_DCHECK_GE(length, 1); + RTC_DCHECK_LE(length, RtpExtension::kMaxValueSize); + const ExtensionInfo* extension_entry = FindExtensionInfo(id); + if (extension_entry != nullptr) { + // Extension already reserved. Check if same length is used. + if (extension_entry->length == length) + return rtc::MakeArrayView(WriteAt(extension_entry->offset), length); + + RTC_LOG(LS_ERROR) << "Length mismatch for extension id " << id + << ": expected " + << static_cast<int>(extension_entry->length) + << ". received " << length; + return nullptr; + } + if (payload_size_ > 0) { + RTC_LOG(LS_ERROR) << "Can't add new extension id " << id + << " after payload was set."; + return nullptr; + } + if (padding_size_ > 0) { + RTC_LOG(LS_ERROR) << "Can't add new extension id " << id + << " after padding was set."; + return nullptr; + } + + const size_t num_csrc = data()[0] & 0x0F; + const size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4; + // Determine if two-byte header is required for the extension based on id and + // length. Please note that a length of 0 also requires two-byte header + // extension. See RFC8285 Section 4.2-4.3. + const bool two_byte_header_required = + id > RtpExtension::kOneByteHeaderExtensionMaxId || + length > RtpExtension::kOneByteHeaderExtensionMaxValueSize || length == 0; + RTC_CHECK(!two_byte_header_required || extensions_.ExtmapAllowMixed()); + + uint16_t profile_id; + if (extensions_size_ > 0) { + profile_id = + ByteReader<uint16_t>::ReadBigEndian(data() + extensions_offset - 4); + if (profile_id == kOneByteExtensionProfileId && two_byte_header_required) { + // Is buffer size big enough to fit promotion and new data field? + // The header extension will grow with one byte per already allocated + // extension + the size of the extension that is about to be allocated. + size_t expected_new_extensions_size = + extensions_size_ + extension_entries_.size() + + kTwoByteExtensionHeaderLength + length; + if (extensions_offset + expected_new_extensions_size > capacity()) { + RTC_LOG(LS_ERROR) + << "Extension cannot be registered: Not enough space left in " + "buffer to change to two-byte header extension and add new " + "extension."; + return nullptr; + } + // Promote already written data to two-byte header format. + PromoteToTwoByteHeaderExtension(); + profile_id = kTwoByteExtensionProfileId; + } + } else { + // Profile specific ID, set to OneByteExtensionHeader unless + // TwoByteExtensionHeader is required. + profile_id = two_byte_header_required ? kTwoByteExtensionProfileId + : kOneByteExtensionProfileId; + } + + const size_t extension_header_size = profile_id == kOneByteExtensionProfileId + ? kOneByteExtensionHeaderLength + : kTwoByteExtensionHeaderLength; + size_t new_extensions_size = + extensions_size_ + extension_header_size + length; + if (extensions_offset + new_extensions_size > capacity()) { + RTC_LOG(LS_ERROR) + << "Extension cannot be registered: Not enough space left in buffer."; + return nullptr; + } + + // All checks passed, write down the extension headers. + if (extensions_size_ == 0) { + RTC_DCHECK_EQ(payload_offset_, kFixedHeaderSize + (num_csrc * 4)); + WriteAt(0, data()[0] | 0x10); // Set extension bit. + ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 4), + profile_id); + } + + if (profile_id == kOneByteExtensionProfileId) { + uint8_t one_byte_header = rtc::dchecked_cast<uint8_t>(id) << 4; + one_byte_header |= rtc::dchecked_cast<uint8_t>(length - 1); + WriteAt(extensions_offset + extensions_size_, one_byte_header); + } else { + // TwoByteHeaderExtension. + uint8_t extension_id = rtc::dchecked_cast<uint8_t>(id); + WriteAt(extensions_offset + extensions_size_, extension_id); + uint8_t extension_length = rtc::dchecked_cast<uint8_t>(length); + WriteAt(extensions_offset + extensions_size_ + 1, extension_length); + } + + const uint16_t extension_info_offset = rtc::dchecked_cast<uint16_t>( + extensions_offset + extensions_size_ + extension_header_size); + const uint8_t extension_info_length = rtc::dchecked_cast<uint8_t>(length); + extension_entries_.emplace_back(id, extension_info_length, + extension_info_offset); + + extensions_size_ = new_extensions_size; + + uint16_t extensions_size_padded = + SetExtensionLengthMaybeAddZeroPadding(extensions_offset); + payload_offset_ = extensions_offset + extensions_size_padded; + buffer_.SetSize(payload_offset_); + return rtc::MakeArrayView(WriteAt(extension_info_offset), + extension_info_length); +} + +void RtpPacket::PromoteToTwoByteHeaderExtension() { + size_t num_csrc = data()[0] & 0x0F; + size_t extensions_offset = kFixedHeaderSize + (num_csrc * 4) + 4; + + RTC_CHECK_GT(extension_entries_.size(), 0); + RTC_CHECK_EQ(payload_size_, 0); + RTC_CHECK_EQ(kOneByteExtensionProfileId, ByteReader<uint16_t>::ReadBigEndian( + data() + extensions_offset - 4)); + // Rewrite data. + // Each extension adds one to the offset. The write-read delta for the last + // extension is therefore the same as the number of extension entries. + size_t write_read_delta = extension_entries_.size(); + for (auto extension_entry = extension_entries_.rbegin(); + extension_entry != extension_entries_.rend(); ++extension_entry) { + size_t read_index = extension_entry->offset; + size_t write_index = read_index + write_read_delta; + // Update offset. + extension_entry->offset = rtc::dchecked_cast<uint16_t>(write_index); + // Copy data. Use memmove since read/write regions may overlap. + memmove(WriteAt(write_index), data() + read_index, extension_entry->length); + // Rewrite id and length. + WriteAt(--write_index, extension_entry->length); + WriteAt(--write_index, extension_entry->id); + --write_read_delta; + } + + // Update profile header, extensions length, and zero padding. + ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 4), + kTwoByteExtensionProfileId); + extensions_size_ += extension_entries_.size(); + uint16_t extensions_size_padded = + SetExtensionLengthMaybeAddZeroPadding(extensions_offset); + payload_offset_ = extensions_offset + extensions_size_padded; + buffer_.SetSize(payload_offset_); +} + +uint16_t RtpPacket::SetExtensionLengthMaybeAddZeroPadding( + size_t extensions_offset) { + // Update header length field. + uint16_t extensions_words = rtc::dchecked_cast<uint16_t>( + (extensions_size_ + 3) / 4); // Wrap up to 32bit. + ByteWriter<uint16_t>::WriteBigEndian(WriteAt(extensions_offset - 2), + extensions_words); + // Fill extension padding place with zeroes. + size_t extension_padding_size = 4 * extensions_words - extensions_size_; + memset(WriteAt(extensions_offset + extensions_size_), 0, + extension_padding_size); + return 4 * extensions_words; +} + +uint8_t* RtpPacket::AllocatePayload(size_t size_bytes) { + // Reset payload size to 0. If CopyOnWrite buffer_ was shared, this will cause + // reallocation and memcpy. Keeping just header reduces memcpy size. + SetPayloadSize(0); + return SetPayloadSize(size_bytes); +} + +uint8_t* RtpPacket::SetPayloadSize(size_t size_bytes) { + RTC_DCHECK_EQ(padding_size_, 0); + if (payload_offset_ + size_bytes > capacity()) { + RTC_LOG(LS_WARNING) << "Cannot set payload, not enough space in buffer."; + return nullptr; + } + payload_size_ = size_bytes; + buffer_.SetSize(payload_offset_ + payload_size_); + return WriteAt(payload_offset_); +} + +bool RtpPacket::SetPadding(size_t padding_bytes) { + if (payload_offset_ + payload_size_ + padding_bytes > capacity()) { + RTC_LOG(LS_WARNING) << "Cannot set padding size " << padding_bytes + << ", only " + << (capacity() - payload_offset_ - payload_size_) + << " bytes left in buffer."; + return false; + } + padding_size_ = rtc::dchecked_cast<uint8_t>(padding_bytes); + buffer_.SetSize(payload_offset_ + payload_size_ + padding_size_); + if (padding_size_ > 0) { + size_t padding_offset = payload_offset_ + payload_size_; + size_t padding_end = padding_offset + padding_size_; + memset(WriteAt(padding_offset), 0, padding_size_ - 1); + WriteAt(padding_end - 1, padding_size_); + WriteAt(0, data()[0] | 0x20); // Set padding bit. + } else { + WriteAt(0, data()[0] & ~0x20); // Clear padding bit. + } + return true; +} + +void RtpPacket::Clear() { + marker_ = false; + payload_type_ = 0; + sequence_number_ = 0; + timestamp_ = 0; + ssrc_ = 0; + payload_offset_ = kFixedHeaderSize; + payload_size_ = 0; + padding_size_ = 0; + extensions_size_ = 0; + extension_entries_.clear(); + + memset(WriteAt(0), 0, kFixedHeaderSize); + buffer_.SetSize(kFixedHeaderSize); + WriteAt(0, kRtpVersion << 6); +} + +bool RtpPacket::ParseBuffer(const uint8_t* buffer, size_t size) { + if (size < kFixedHeaderSize) { + return false; + } + const uint8_t version = buffer[0] >> 6; + if (version != kRtpVersion) { + return false; + } + const bool has_padding = (buffer[0] & 0x20) != 0; + const bool has_extension = (buffer[0] & 0x10) != 0; + const uint8_t number_of_crcs = buffer[0] & 0x0f; + marker_ = (buffer[1] & 0x80) != 0; + payload_type_ = buffer[1] & 0x7f; + + sequence_number_ = ByteReader<uint16_t>::ReadBigEndian(&buffer[2]); + timestamp_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[4]); + ssrc_ = ByteReader<uint32_t>::ReadBigEndian(&buffer[8]); + if (size < kFixedHeaderSize + number_of_crcs * 4) { + return false; + } + payload_offset_ = kFixedHeaderSize + number_of_crcs * 4; + + extensions_size_ = 0; + extension_entries_.clear(); + if (has_extension) { + /* RTP header extension, RFC 3550. + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined by profile | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | header extension | + | .... | + */ + size_t extension_offset = payload_offset_ + 4; + if (extension_offset > size) { + return false; + } + uint16_t profile = + ByteReader<uint16_t>::ReadBigEndian(&buffer[payload_offset_]); + size_t extensions_capacity = + ByteReader<uint16_t>::ReadBigEndian(&buffer[payload_offset_ + 2]); + extensions_capacity *= 4; + if (extension_offset + extensions_capacity > size) { + return false; + } + if (profile != kOneByteExtensionProfileId && + (profile & kTwobyteExtensionProfileIdAppBitsFilter) != + kTwoByteExtensionProfileId) { + RTC_LOG(LS_WARNING) << "Unsupported rtp extension " << profile; + } else { + size_t extension_header_length = profile == kOneByteExtensionProfileId + ? kOneByteExtensionHeaderLength + : kTwoByteExtensionHeaderLength; + constexpr uint8_t kPaddingByte = 0; + constexpr uint8_t kPaddingId = 0; + constexpr uint8_t kOneByteHeaderExtensionReservedId = 15; + while (extensions_size_ + extension_header_length < extensions_capacity) { + if (buffer[extension_offset + extensions_size_] == kPaddingByte) { + extensions_size_++; + continue; + } + int id; + uint8_t length; + if (profile == kOneByteExtensionProfileId) { + id = buffer[extension_offset + extensions_size_] >> 4; + length = 1 + (buffer[extension_offset + extensions_size_] & 0xf); + if (id == kOneByteHeaderExtensionReservedId || + (id == kPaddingId && length != 1)) { + break; + } + } else { + id = buffer[extension_offset + extensions_size_]; + length = buffer[extension_offset + extensions_size_ + 1]; + } + + if (extensions_size_ + extension_header_length + length > + extensions_capacity) { + RTC_LOG(LS_WARNING) << "Oversized rtp header extension."; + break; + } + + ExtensionInfo& extension_info = FindOrCreateExtensionInfo(id); + if (extension_info.length != 0) { + RTC_LOG(LS_VERBOSE) + << "Duplicate rtp header extension id " << id << ". Overwriting."; + } + + size_t offset = + extension_offset + extensions_size_ + extension_header_length; + if (!rtc::IsValueInRangeForNumericType<uint16_t>(offset)) { + RTC_DLOG(LS_WARNING) << "Oversized rtp header extension."; + break; + } + extension_info.offset = static_cast<uint16_t>(offset); + extension_info.length = length; + extensions_size_ += extension_header_length + length; + } + } + payload_offset_ = extension_offset + extensions_capacity; + } + + if (has_padding && payload_offset_ < size) { + padding_size_ = buffer[size - 1]; + if (padding_size_ == 0) { + RTC_LOG(LS_WARNING) << "Padding was set, but padding size is zero"; + return false; + } + } else { + padding_size_ = 0; + } + + if (payload_offset_ + padding_size_ > size) { + return false; + } + payload_size_ = size - payload_offset_ - padding_size_; + return true; +} + +const RtpPacket::ExtensionInfo* RtpPacket::FindExtensionInfo(int id) const { + for (const ExtensionInfo& extension : extension_entries_) { + if (extension.id == id) { + return &extension; + } + } + return nullptr; +} + +RtpPacket::ExtensionInfo& RtpPacket::FindOrCreateExtensionInfo(int id) { + for (ExtensionInfo& extension : extension_entries_) { + if (extension.id == id) { + return extension; + } + } + extension_entries_.emplace_back(id); + return extension_entries_.back(); +} + +rtc::ArrayView<const uint8_t> RtpPacket::FindExtension( + ExtensionType type) const { + uint8_t id = extensions_.GetId(type); + if (id == ExtensionManager::kInvalidId) { + // Extension not registered. + return nullptr; + } + ExtensionInfo const* extension_info = FindExtensionInfo(id); + if (extension_info == nullptr) { + return nullptr; + } + return rtc::MakeArrayView(data() + extension_info->offset, + extension_info->length); +} + +rtc::ArrayView<uint8_t> RtpPacket::AllocateExtension(ExtensionType type, + size_t length) { + // TODO(webrtc:7990): Add support for empty extensions (length==0). + if (length == 0 || length > RtpExtension::kMaxValueSize || + (!extensions_.ExtmapAllowMixed() && + length > RtpExtension::kOneByteHeaderExtensionMaxValueSize)) { + return nullptr; + } + + uint8_t id = extensions_.GetId(type); + if (id == ExtensionManager::kInvalidId) { + // Extension not registered. + return nullptr; + } + if (!extensions_.ExtmapAllowMixed() && + id > RtpExtension::kOneByteHeaderExtensionMaxId) { + return nullptr; + } + return AllocateRawExtension(id, length); +} + +bool RtpPacket::HasExtension(ExtensionType type) const { + uint8_t id = extensions_.GetId(type); + if (id == ExtensionManager::kInvalidId) { + // Extension not registered. + return false; + } + return FindExtensionInfo(id) != nullptr; +} + +bool RtpPacket::RemoveExtension(ExtensionType type) { + uint8_t id_to_remove = extensions_.GetId(type); + if (id_to_remove == ExtensionManager::kInvalidId) { + // Extension not registered. + RTC_LOG(LS_ERROR) << "Extension not registered, type=" << type + << ", packet=" << ToString(); + return false; + } + + // Rebuild new packet from scratch. + RtpPacket new_packet; + + new_packet.SetMarker(Marker()); + new_packet.SetPayloadType(PayloadType()); + new_packet.SetSequenceNumber(SequenceNumber()); + new_packet.SetTimestamp(Timestamp()); + new_packet.SetSsrc(Ssrc()); + new_packet.IdentifyExtensions(extensions_); + + // Copy all extensions, except the one we are removing. + bool found_extension = false; + for (const ExtensionInfo& ext : extension_entries_) { + if (ext.id == id_to_remove) { + found_extension = true; + } else { + auto extension_data = new_packet.AllocateRawExtension(ext.id, ext.length); + if (extension_data.size() != ext.length) { + RTC_LOG(LS_ERROR) << "Failed to allocate extension id=" << ext.id + << ", length=" << ext.length + << ", packet=" << ToString(); + return false; + } + + // Copy extension data to new packet. + memcpy(extension_data.data(), ReadAt(ext.offset), ext.length); + } + } + + if (!found_extension) { + RTC_LOG(LS_WARNING) << "Extension not present in RTP packet, type=" << type + << ", packet=" << ToString(); + return false; + } + + // Copy payload data to new packet. + if (payload_size() > 0) { + memcpy(new_packet.AllocatePayload(payload_size()), payload().data(), + payload_size()); + } else { + new_packet.SetPayloadSize(0); + } + + // Allocate padding -- must be last! + new_packet.SetPadding(padding_size()); + + // Success, replace current packet with newly built packet. + *this = new_packet; + return true; +} + +std::string RtpPacket::ToString() const { + rtc::StringBuilder result; + result << "{payload_type=" << payload_type_ << ", marker=" << marker_ + << ", sequence_number=" << sequence_number_ + << ", padding_size=" << padding_size_ << ", timestamp=" << timestamp_ + << ", ssrc=" << ssrc_ << ", payload_offset=" << payload_offset_ + << ", payload_size=" << payload_size_ << ", total_size=" << size() + << "}"; + + return result.Release(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h new file mode 100644 index 0000000000..1db4a13017 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h @@ -0,0 +1,283 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_PACKET_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_PACKET_H_ + +#include <string> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class RtpPacket { + public: + using ExtensionType = RTPExtensionType; + using ExtensionManager = RtpHeaderExtensionMap; + + // `extensions` required for SetExtension/ReserveExtension functions during + // packet creating and used if available in Parse function. + // Adding and getting extensions will fail until `extensions` is + // provided via constructor or IdentifyExtensions function. + // |*extensions| is only accessed during construction; the pointer is not + // stored. + RtpPacket(); + explicit RtpPacket(const ExtensionManager* extensions); + RtpPacket(const ExtensionManager* extensions, size_t capacity); + + RtpPacket(const RtpPacket&); + RtpPacket(RtpPacket&&); + RtpPacket& operator=(const RtpPacket&); + RtpPacket& operator=(RtpPacket&&); + + ~RtpPacket(); + + // Parse and copy given buffer into Packet. + // Does not require extension map to be registered (map is only required to + // read or allocate extensions in methods GetExtension, AllocateExtension, + // etc.) + bool Parse(const uint8_t* buffer, size_t size); + bool Parse(rtc::ArrayView<const uint8_t> packet); + + // Parse and move given buffer into Packet. + bool Parse(rtc::CopyOnWriteBuffer packet); + + // Maps extensions id to their types. + void IdentifyExtensions(ExtensionManager extensions); + + // Returns the extension map used for identifying extensions in this packet. + const ExtensionManager& extension_manager() const { return extensions_; } + + // Header. + bool Marker() const { return marker_; } + uint8_t PayloadType() const { return payload_type_; } + uint16_t SequenceNumber() const { return sequence_number_; } + uint32_t Timestamp() const { return timestamp_; } + uint32_t Ssrc() const { return ssrc_; } + std::vector<uint32_t> Csrcs() const; + + size_t headers_size() const { return payload_offset_; } + + // Payload. + size_t payload_size() const { return payload_size_; } + bool has_padding() const { return buffer_[0] & 0x20; } + size_t padding_size() const { return padding_size_; } + rtc::ArrayView<const uint8_t> payload() const { + return rtc::MakeArrayView(data() + payload_offset_, payload_size_); + } + rtc::CopyOnWriteBuffer PayloadBuffer() const { + return buffer_.Slice(payload_offset_, payload_size_); + } + + // Buffer. + rtc::CopyOnWriteBuffer Buffer() const { return buffer_; } + size_t capacity() const { return buffer_.capacity(); } + size_t size() const { + return payload_offset_ + payload_size_ + padding_size_; + } + const uint8_t* data() const { return buffer_.cdata(); } + size_t FreeCapacity() const { return capacity() - size(); } + size_t MaxPayloadSize() const { return capacity() - headers_size(); } + + // Reset fields and buffer. + void Clear(); + + // Header setters. + void CopyHeaderFrom(const RtpPacket& packet); + void SetMarker(bool marker_bit); + void SetPayloadType(uint8_t payload_type); + void SetSequenceNumber(uint16_t seq_no); + void SetTimestamp(uint32_t timestamp); + void SetSsrc(uint32_t ssrc); + + // Fills with zeroes mutable extensions, + // which are modified after FEC protection is generated. + void ZeroMutableExtensions(); + + // Removes extension of given `type`, returns false is extension was not + // registered in packet's extension map or not present in the packet. Only + // extension that should be removed must be registered, other extensions may + // not be registered and will be preserved as is. + bool RemoveExtension(ExtensionType type); + + // Writes csrc list. Assumes: + // a) There is enough room left in buffer. + // b) Extension headers, payload or padding data has not already been added. + void SetCsrcs(rtc::ArrayView<const uint32_t> csrcs); + + // Header extensions. + template <typename Extension> + bool HasExtension() const; + bool HasExtension(ExtensionType type) const; + + // Returns whether there is an associated id for the extension and thus it is + // possible to set the extension. + template <typename Extension> + bool IsRegistered() const; + + template <typename Extension, typename FirstValue, typename... Values> + bool GetExtension(FirstValue, Values...) const; + + template <typename Extension> + absl::optional<typename Extension::value_type> GetExtension() const; + + // Returns view of the raw extension or empty view on failure. + template <typename Extension> + rtc::ArrayView<const uint8_t> GetRawExtension() const; + + template <typename Extension, typename... Values> + bool SetExtension(const Values&...); + + template <typename Extension> + bool SetRawExtension(rtc::ArrayView<const uint8_t> data); + + template <typename Extension> + bool ReserveExtension(); + + // Find or allocate an extension `type`. Returns view of size `length` + // to write raw extension to or an empty view on failure. + rtc::ArrayView<uint8_t> AllocateExtension(ExtensionType type, size_t length); + + // Find an extension `type`. + // Returns view of the raw extension or empty view on failure. + rtc::ArrayView<const uint8_t> FindExtension(ExtensionType type) const; + + // Reserve size_bytes for payload. Returns nullptr on failure. + uint8_t* SetPayloadSize(size_t size_bytes); + // Same as SetPayloadSize but doesn't guarantee to keep current payload. + uint8_t* AllocatePayload(size_t size_bytes); + + bool SetPadding(size_t padding_size); + + // Returns debug string of RTP packet (without detailed extension info). + std::string ToString() const; + + private: + struct ExtensionInfo { + explicit ExtensionInfo(uint8_t id) : ExtensionInfo(id, 0, 0) {} + ExtensionInfo(uint8_t id, uint8_t length, uint16_t offset) + : id(id), length(length), offset(offset) {} + uint8_t id; + uint8_t length; + uint16_t offset; + }; + + // Helper function for Parse. Fill header fields using data in given buffer, + // but does not touch packet own buffer, leaving packet in invalid state. + bool ParseBuffer(const uint8_t* buffer, size_t size); + + // Returns pointer to extension info for a given id. Returns nullptr if not + // found. + const ExtensionInfo* FindExtensionInfo(int id) const; + + // Returns reference to extension info for a given id. Creates a new entry + // with the specified id if not found. + ExtensionInfo& FindOrCreateExtensionInfo(int id); + + // Allocates and returns place to store rtp header extension. + // Returns empty arrayview on failure. + rtc::ArrayView<uint8_t> AllocateRawExtension(int id, size_t length); + + // Promotes existing one-byte header extensions to two-byte header extensions + // by rewriting the data and updates the corresponding extension offsets. + void PromoteToTwoByteHeaderExtension(); + + uint16_t SetExtensionLengthMaybeAddZeroPadding(size_t extensions_offset); + + uint8_t* WriteAt(size_t offset) { return buffer_.MutableData() + offset; } + void WriteAt(size_t offset, uint8_t byte) { + buffer_.MutableData()[offset] = byte; + } + const uint8_t* ReadAt(size_t offset) const { return buffer_.data() + offset; } + + // Header. + bool marker_; + uint8_t payload_type_; + uint8_t padding_size_; + uint16_t sequence_number_; + uint32_t timestamp_; + uint32_t ssrc_; + size_t payload_offset_; // Match header size with csrcs and extensions. + size_t payload_size_; + + ExtensionManager extensions_; + std::vector<ExtensionInfo> extension_entries_; + size_t extensions_size_ = 0; // Unaligned. + rtc::CopyOnWriteBuffer buffer_; +}; + +template <typename Extension> +bool RtpPacket::HasExtension() const { + return HasExtension(Extension::kId); +} + +template <typename Extension> +bool RtpPacket::IsRegistered() const { + return extensions_.IsRegistered(Extension::kId); +} + +template <typename Extension, typename FirstValue, typename... Values> +bool RtpPacket::GetExtension(FirstValue first, Values... values) const { + auto raw = FindExtension(Extension::kId); + if (raw.empty()) + return false; + return Extension::Parse(raw, first, values...); +} + +template <typename Extension> +absl::optional<typename Extension::value_type> RtpPacket::GetExtension() const { + absl::optional<typename Extension::value_type> result; + auto raw = FindExtension(Extension::kId); + if (raw.empty() || !Extension::Parse(raw, &result.emplace())) + result = absl::nullopt; + return result; +} + +template <typename Extension> +rtc::ArrayView<const uint8_t> RtpPacket::GetRawExtension() const { + return FindExtension(Extension::kId); +} + +template <typename Extension, typename... Values> +bool RtpPacket::SetExtension(const Values&... values) { + const size_t value_size = Extension::ValueSize(values...); + auto buffer = AllocateExtension(Extension::kId, value_size); + if (buffer.empty()) + return false; + return Extension::Write(buffer, values...); +} + +template <typename Extension> +bool RtpPacket::SetRawExtension(rtc::ArrayView<const uint8_t> data) { + rtc::ArrayView<uint8_t> buffer = + AllocateExtension(Extension::kId, data.size()); + if (buffer.empty()) { + return false; + } + std::memcpy(buffer.data(), data.data(), data.size()); + return true; +} + +template <typename Extension> +bool RtpPacket::ReserveExtension() { + auto buffer = AllocateExtension(Extension::kId, Extension::kValueSizeBytes); + if (buffer.empty()) + return false; + memset(buffer.data(), 0, Extension::kValueSizeBytes); + return true; +} + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_PACKET_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.cc new file mode 100644 index 0000000000..c8d400a985 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.cc @@ -0,0 +1,428 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_packet_history.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <utility> + +#include "modules/include/module_common_types_public.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +RtpPacketHistory::StoredPacket::StoredPacket( + std::unique_ptr<RtpPacketToSend> packet, + Timestamp send_time, + uint64_t insert_order) + : packet_(std::move(packet)), + pending_transmission_(false), + send_time_(send_time), + insert_order_(insert_order), + times_retransmitted_(0) {} + +RtpPacketHistory::StoredPacket::StoredPacket(StoredPacket&&) = default; +RtpPacketHistory::StoredPacket& RtpPacketHistory::StoredPacket::operator=( + RtpPacketHistory::StoredPacket&&) = default; +RtpPacketHistory::StoredPacket::~StoredPacket() = default; + +void RtpPacketHistory::StoredPacket::IncrementTimesRetransmitted( + PacketPrioritySet* priority_set) { + // Check if this StoredPacket is in the priority set. If so, we need to remove + // it before updating `times_retransmitted_` since that is used in sorting, + // and then add it back. + const bool in_priority_set = priority_set && priority_set->erase(this) > 0; + ++times_retransmitted_; + if (in_priority_set) { + auto it = priority_set->insert(this); + RTC_DCHECK(it.second) + << "ERROR: Priority set already contains matching packet! In set: " + "insert order = " + << (*it.first)->insert_order_ + << ", times retransmitted = " << (*it.first)->times_retransmitted_ + << ". Trying to add: insert order = " << insert_order_ + << ", times retransmitted = " << times_retransmitted_; + } +} + +bool RtpPacketHistory::MoreUseful::operator()(StoredPacket* lhs, + StoredPacket* rhs) const { + // Prefer to send packets we haven't already sent as padding. + if (lhs->times_retransmitted() != rhs->times_retransmitted()) { + return lhs->times_retransmitted() < rhs->times_retransmitted(); + } + // All else being equal, prefer newer packets. + return lhs->insert_order() > rhs->insert_order(); +} + +RtpPacketHistory::RtpPacketHistory(Clock* clock, bool enable_padding_prio) + : clock_(clock), + enable_padding_prio_(enable_padding_prio), + number_to_store_(0), + mode_(StorageMode::kDisabled), + rtt_(TimeDelta::MinusInfinity()), + packets_inserted_(0) {} + +RtpPacketHistory::~RtpPacketHistory() {} + +void RtpPacketHistory::SetStorePacketsStatus(StorageMode mode, + size_t number_to_store) { + RTC_DCHECK_LE(number_to_store, kMaxCapacity); + MutexLock lock(&lock_); + if (mode != StorageMode::kDisabled && mode_ != StorageMode::kDisabled) { + RTC_LOG(LS_WARNING) << "Purging packet history in order to re-set status."; + } + Reset(); + mode_ = mode; + number_to_store_ = std::min(kMaxCapacity, number_to_store); +} + +RtpPacketHistory::StorageMode RtpPacketHistory::GetStorageMode() const { + MutexLock lock(&lock_); + return mode_; +} + +void RtpPacketHistory::SetRtt(TimeDelta rtt) { + MutexLock lock(&lock_); + RTC_DCHECK_GE(rtt, TimeDelta::Zero()); + rtt_ = rtt; + // If storage is not disabled, packets will be removed after a timeout + // that depends on the RTT. Changing the RTT may thus cause some packets + // become "old" and subject to removal. + if (mode_ != StorageMode::kDisabled) { + CullOldPackets(); + } +} + +void RtpPacketHistory::PutRtpPacket(std::unique_ptr<RtpPacketToSend> packet, + Timestamp send_time) { + RTC_DCHECK(packet); + MutexLock lock(&lock_); + if (mode_ == StorageMode::kDisabled) { + return; + } + + RTC_DCHECK(packet->allow_retransmission()); + CullOldPackets(); + + // Store packet. + const uint16_t rtp_seq_no = packet->SequenceNumber(); + int packet_index = GetPacketIndex(rtp_seq_no); + if (packet_index >= 0 && + static_cast<size_t>(packet_index) < packet_history_.size() && + packet_history_[packet_index].packet_ != nullptr) { + RTC_LOG(LS_WARNING) << "Duplicate packet inserted: " << rtp_seq_no; + // Remove previous packet to avoid inconsistent state. + RemovePacket(packet_index); + packet_index = GetPacketIndex(rtp_seq_no); + } + + // Packet to be inserted ahead of first packet, expand front. + for (; packet_index < 0; ++packet_index) { + packet_history_.emplace_front(); + } + // Packet to be inserted behind last packet, expand back. + while (static_cast<int>(packet_history_.size()) <= packet_index) { + packet_history_.emplace_back(); + } + + RTC_DCHECK_GE(packet_index, 0); + RTC_DCHECK_LT(packet_index, packet_history_.size()); + RTC_DCHECK(packet_history_[packet_index].packet_ == nullptr); + + packet_history_[packet_index] = + StoredPacket(std::move(packet), send_time, packets_inserted_++); + + if (enable_padding_prio_) { + if (padding_priority_.size() >= kMaxPaddingHistory - 1) { + padding_priority_.erase(std::prev(padding_priority_.end())); + } + auto prio_it = padding_priority_.insert(&packet_history_[packet_index]); + RTC_DCHECK(prio_it.second) << "Failed to insert packet into prio set."; + } +} + +std::unique_ptr<RtpPacketToSend> RtpPacketHistory::GetPacketAndMarkAsPending( + uint16_t sequence_number) { + return GetPacketAndMarkAsPending( + sequence_number, [](const RtpPacketToSend& packet) { + return std::make_unique<RtpPacketToSend>(packet); + }); +} + +std::unique_ptr<RtpPacketToSend> RtpPacketHistory::GetPacketAndMarkAsPending( + uint16_t sequence_number, + rtc::FunctionView<std::unique_ptr<RtpPacketToSend>(const RtpPacketToSend&)> + encapsulate) { + MutexLock lock(&lock_); + if (mode_ == StorageMode::kDisabled) { + return nullptr; + } + + StoredPacket* packet = GetStoredPacket(sequence_number); + if (packet == nullptr) { + return nullptr; + } + + if (packet->pending_transmission_) { + // Packet already in pacer queue, ignore this request. + return nullptr; + } + + if (!VerifyRtt(*packet)) { + // Packet already resent within too short a time window, ignore. + return nullptr; + } + + // Copy and/or encapsulate packet. + std::unique_ptr<RtpPacketToSend> encapsulated_packet = + encapsulate(*packet->packet_); + if (encapsulated_packet) { + packet->pending_transmission_ = true; + } + + return encapsulated_packet; +} + +void RtpPacketHistory::MarkPacketAsSent(uint16_t sequence_number) { + MutexLock lock(&lock_); + if (mode_ == StorageMode::kDisabled) { + return; + } + + StoredPacket* packet = GetStoredPacket(sequence_number); + if (packet == nullptr) { + return; + } + + // Update send-time, mark as no longer in pacer queue, and increment + // transmission count. + packet->set_send_time(clock_->CurrentTime()); + packet->pending_transmission_ = false; + packet->IncrementTimesRetransmitted(enable_padding_prio_ ? &padding_priority_ + : nullptr); +} + +bool RtpPacketHistory::GetPacketState(uint16_t sequence_number) const { + MutexLock lock(&lock_); + if (mode_ == StorageMode::kDisabled) { + return false; + } + + int packet_index = GetPacketIndex(sequence_number); + if (packet_index < 0 || + static_cast<size_t>(packet_index) >= packet_history_.size()) { + return false; + } + const StoredPacket& packet = packet_history_[packet_index]; + if (packet.packet_ == nullptr) { + return false; + } + + if (!VerifyRtt(packet)) { + return false; + } + + return true; +} + +bool RtpPacketHistory::VerifyRtt( + const RtpPacketHistory::StoredPacket& packet) const { + if (packet.times_retransmitted() > 0 && + clock_->CurrentTime() - packet.send_time() < rtt_) { + // This packet has already been retransmitted once, and the time since + // that even is lower than on RTT. Ignore request as this packet is + // likely already in the network pipe. + return false; + } + + return true; +} + +std::unique_ptr<RtpPacketToSend> RtpPacketHistory::GetPayloadPaddingPacket() { + // Default implementation always just returns a copy of the packet. + return GetPayloadPaddingPacket([](const RtpPacketToSend& packet) { + return std::make_unique<RtpPacketToSend>(packet); + }); +} + +std::unique_ptr<RtpPacketToSend> RtpPacketHistory::GetPayloadPaddingPacket( + rtc::FunctionView<std::unique_ptr<RtpPacketToSend>(const RtpPacketToSend&)> + encapsulate) { + MutexLock lock(&lock_); + if (mode_ == StorageMode::kDisabled) { + return nullptr; + } + + StoredPacket* best_packet = nullptr; + if (enable_padding_prio_ && !padding_priority_.empty()) { + auto best_packet_it = padding_priority_.begin(); + best_packet = *best_packet_it; + } else if (!enable_padding_prio_ && !packet_history_.empty()) { + // Prioritization not available, pick the last packet. + for (auto it = packet_history_.rbegin(); it != packet_history_.rend(); + ++it) { + if (it->packet_ != nullptr) { + best_packet = &(*it); + break; + } + } + } + if (best_packet == nullptr) { + return nullptr; + } + + if (best_packet->pending_transmission_) { + // Because PacedSender releases it's lock when it calls + // GeneratePadding() there is the potential for a race where a new + // packet ends up here instead of the regular transmit path. In such a + // case, just return empty and it will be picked up on the next + // Process() call. + return nullptr; + } + + auto padding_packet = encapsulate(*best_packet->packet_); + if (!padding_packet) { + return nullptr; + } + + best_packet->set_send_time(clock_->CurrentTime()); + best_packet->IncrementTimesRetransmitted( + enable_padding_prio_ ? &padding_priority_ : nullptr); + + return padding_packet; +} + +void RtpPacketHistory::CullAcknowledgedPackets( + rtc::ArrayView<const uint16_t> sequence_numbers) { + MutexLock lock(&lock_); + for (uint16_t sequence_number : sequence_numbers) { + int packet_index = GetPacketIndex(sequence_number); + if (packet_index < 0 || + static_cast<size_t>(packet_index) >= packet_history_.size()) { + continue; + } + RemovePacket(packet_index); + } +} + +void RtpPacketHistory::Clear() { + MutexLock lock(&lock_); + Reset(); +} + +void RtpPacketHistory::Reset() { + packet_history_.clear(); + padding_priority_.clear(); +} + +void RtpPacketHistory::CullOldPackets() { + Timestamp now = clock_->CurrentTime(); + TimeDelta packet_duration = + rtt_.IsFinite() + ? std::max(kMinPacketDurationRtt * rtt_, kMinPacketDuration) + : kMinPacketDuration; + while (!packet_history_.empty()) { + if (packet_history_.size() >= kMaxCapacity) { + // We have reached the absolute max capacity, remove one packet + // unconditionally. + RemovePacket(0); + continue; + } + + const StoredPacket& stored_packet = packet_history_.front(); + if (stored_packet.pending_transmission_) { + // Don't remove packets in the pacer queue, pending tranmission. + return; + } + + if (stored_packet.send_time() + packet_duration > now) { + // Don't cull packets too early to avoid failed retransmission requests. + return; + } + + if (packet_history_.size() >= number_to_store_ || + stored_packet.send_time() + + (packet_duration * kPacketCullingDelayFactor) <= + now) { + // Too many packets in history, or this packet has timed out. Remove it + // and continue. + RemovePacket(0); + } else { + // No more packets can be removed right now. + return; + } + } +} + +std::unique_ptr<RtpPacketToSend> RtpPacketHistory::RemovePacket( + int packet_index) { + // Move the packet out from the StoredPacket container. + std::unique_ptr<RtpPacketToSend> rtp_packet = + std::move(packet_history_[packet_index].packet_); + + // Erase from padding priority set, if eligible. + if (enable_padding_prio_) { + padding_priority_.erase(&packet_history_[packet_index]); + } + + if (packet_index == 0) { + while (!packet_history_.empty() && + packet_history_.front().packet_ == nullptr) { + packet_history_.pop_front(); + } + } + + return rtp_packet; +} + +int RtpPacketHistory::GetPacketIndex(uint16_t sequence_number) const { + if (packet_history_.empty()) { + return 0; + } + + RTC_DCHECK(packet_history_.front().packet_ != nullptr); + int first_seq = packet_history_.front().packet_->SequenceNumber(); + if (first_seq == sequence_number) { + return 0; + } + + int packet_index = sequence_number - first_seq; + constexpr int kSeqNumSpan = std::numeric_limits<uint16_t>::max() + 1; + + if (IsNewerSequenceNumber(sequence_number, first_seq)) { + if (sequence_number < first_seq) { + // Forward wrap. + packet_index += kSeqNumSpan; + } + } else if (sequence_number > first_seq) { + // Backwards wrap. + packet_index -= kSeqNumSpan; + } + + return packet_index; +} + +RtpPacketHistory::StoredPacket* RtpPacketHistory::GetStoredPacket( + uint16_t sequence_number) { + int index = GetPacketIndex(sequence_number); + if (index < 0 || static_cast<size_t>(index) >= packet_history_.size() || + packet_history_[index].packet_ == nullptr) { + return nullptr; + } + return &packet_history_[index]; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.h new file mode 100644 index 0000000000..7475a35be3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.h @@ -0,0 +1,196 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_PACKET_HISTORY_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_PACKET_HISTORY_H_ + +#include <deque> +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include "api/function_view.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class Clock; +class RtpPacketToSend; + +class RtpPacketHistory { + public: + enum class StorageMode { + kDisabled, // Don't store any packets. + kStoreAndCull // Store up to `number_to_store` packets, but try to remove + // packets as they time out or as signaled as received. + }; + + // Maximum number of packets we ever allow in the history. + static constexpr size_t kMaxCapacity = 9600; + // Maximum number of entries in prioritized queue of padding packets. + static constexpr size_t kMaxPaddingHistory = 63; + // Don't remove packets within max(1 second, 3x RTT). + static constexpr TimeDelta kMinPacketDuration = TimeDelta::Seconds(1); + static constexpr int kMinPacketDurationRtt = 3; + // With kStoreAndCull, always remove packets after 3x max(1000ms, 3x rtt). + static constexpr int kPacketCullingDelayFactor = 3; + + RtpPacketHistory(Clock* clock, bool enable_padding_prio); + + RtpPacketHistory() = delete; + RtpPacketHistory(const RtpPacketHistory&) = delete; + RtpPacketHistory& operator=(const RtpPacketHistory&) = delete; + + ~RtpPacketHistory(); + + // Set/get storage mode. Note that setting the state will clear the history, + // even if setting the same state as is currently used. + void SetStorePacketsStatus(StorageMode mode, size_t number_to_store); + StorageMode GetStorageMode() const; + + // Set RTT, used to avoid premature retransmission and to prevent over-writing + // a packet in the history before we are reasonably sure it has been received. + void SetRtt(TimeDelta rtt); + + void PutRtpPacket(std::unique_ptr<RtpPacketToSend> packet, + Timestamp send_time); + + // Gets stored RTP packet corresponding to the input |sequence number|. + // Returns nullptr if packet is not found or was (re)sent too recently. + // If a packet copy is returned, it will be marked as pending transmission but + // does not update send time, that must be done by MarkPacketAsSent(). + std::unique_ptr<RtpPacketToSend> GetPacketAndMarkAsPending( + uint16_t sequence_number); + + // In addition to getting packet and marking as sent, this method takes an + // encapsulator function that takes a reference to the packet and outputs a + // copy that may be wrapped in a container, eg RTX. + // If the the encapsulator returns nullptr, the retransmit is aborted and the + // packet will not be marked as pending. + std::unique_ptr<RtpPacketToSend> GetPacketAndMarkAsPending( + uint16_t sequence_number, + rtc::FunctionView<std::unique_ptr<RtpPacketToSend>( + const RtpPacketToSend&)> encapsulate); + + // Updates the send time for the given packet and increments the transmission + // counter. Marks the packet as no longer being in the pacer queue. + void MarkPacketAsSent(uint16_t sequence_number); + + // Returns true if history contains packet with `sequence_number` and it can + // be retransmitted. + bool GetPacketState(uint16_t sequence_number) const; + + // Get the packet (if any) from the history, that is deemed most likely to + // the remote side. This is calculated from heuristics such as packet age + // and times retransmitted. Updated the send time of the packet, so is not + // a const method. + std::unique_ptr<RtpPacketToSend> GetPayloadPaddingPacket(); + + // Same as GetPayloadPaddingPacket(void), but adds an encapsulation + // that can be used for instance to encapsulate the packet in an RTX + // container, or to abort getting the packet if the function returns + // nullptr. + std::unique_ptr<RtpPacketToSend> GetPayloadPaddingPacket( + rtc::FunctionView<std::unique_ptr<RtpPacketToSend>( + const RtpPacketToSend&)> encapsulate); + + // Cull packets that have been acknowledged as received by the remote end. + void CullAcknowledgedPackets(rtc::ArrayView<const uint16_t> sequence_numbers); + + // Remove all pending packets from the history, but keep storage mode and + // capacity. + void Clear(); + + private: + struct MoreUseful; + class StoredPacket; + using PacketPrioritySet = std::set<StoredPacket*, MoreUseful>; + + class StoredPacket { + public: + StoredPacket() = default; + StoredPacket(std::unique_ptr<RtpPacketToSend> packet, + Timestamp send_time, + uint64_t insert_order); + StoredPacket(StoredPacket&&); + StoredPacket& operator=(StoredPacket&&); + ~StoredPacket(); + + uint64_t insert_order() const { return insert_order_; } + size_t times_retransmitted() const { return times_retransmitted_; } + void IncrementTimesRetransmitted(PacketPrioritySet* priority_set); + + // The time of last transmission, including retransmissions. + Timestamp send_time() const { return send_time_; } + void set_send_time(Timestamp value) { send_time_ = value; } + + // The actual packet. + std::unique_ptr<RtpPacketToSend> packet_; + + // True if the packet is currently in the pacer queue pending transmission. + bool pending_transmission_; + + private: + Timestamp send_time_ = Timestamp::Zero(); + + // Unique number per StoredPacket, incremented by one for each added + // packet. Used to sort on insert order. + uint64_t insert_order_; + + // Number of times RE-transmitted, ie excluding the first transmission. + size_t times_retransmitted_; + }; + struct MoreUseful { + bool operator()(StoredPacket* lhs, StoredPacket* rhs) const; + }; + + // Helper method to check if packet has too recently been sent. + bool VerifyRtt(const StoredPacket& packet) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + void Reset() RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + void CullOldPackets() RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + // Removes the packet from the history, and context/mapping that has been + // stored. Returns the RTP packet instance contained within the StoredPacket. + std::unique_ptr<RtpPacketToSend> RemovePacket(int packet_index) + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + int GetPacketIndex(uint16_t sequence_number) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + StoredPacket* GetStoredPacket(uint16_t sequence_number) + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + + Clock* const clock_; + const bool enable_padding_prio_; + mutable Mutex lock_; + size_t number_to_store_ RTC_GUARDED_BY(lock_); + StorageMode mode_ RTC_GUARDED_BY(lock_); + TimeDelta rtt_ RTC_GUARDED_BY(lock_); + + // Queue of stored packets, ordered by sequence number, with older packets in + // the front and new packets being added to the back. Note that there may be + // wrap-arounds so the back may have a lower sequence number. + // Packets may also be removed out-of-order, in which case there will be + // instances of StoredPacket with `packet_` set to nullptr. The first and last + // entry in the queue will however always be populated. + std::deque<StoredPacket> packet_history_ RTC_GUARDED_BY(lock_); + + // Total number of packets with inserted. + uint64_t packets_inserted_ RTC_GUARDED_BY(lock_); + // Objects from `packet_history_` ordered by "most likely to be useful", used + // in GetPayloadPaddingPacket(). + PacketPrioritySet padding_priority_ RTC_GUARDED_BY(lock_); +}; +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_PACKET_HISTORY_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc new file mode 100644 index 0000000000..f50541849e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc @@ -0,0 +1,681 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_packet_history.h" + +#include <memory> +#include <utility> + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +// Set a high sequence number so we'll suffer a wrap-around. +constexpr uint16_t kStartSeqNum = 65534u; + +// Utility method for truncating sequence numbers to uint16. +uint16_t To16u(size_t sequence_number) { + return static_cast<uint16_t>(sequence_number & 0xFFFF); +} +} // namespace + +using StorageMode = RtpPacketHistory::StorageMode; + +class RtpPacketHistoryTest : public ::testing::TestWithParam<bool> { + protected: + RtpPacketHistoryTest() + : fake_clock_(123456), + hist_(&fake_clock_, /*enable_padding_prio=*/GetParam()) {} + + SimulatedClock fake_clock_; + RtpPacketHistory hist_; + + std::unique_ptr<RtpPacketToSend> CreateRtpPacket(uint16_t seq_num) { + // Payload, ssrc, timestamp and extensions are irrelevant for this tests. + std::unique_ptr<RtpPacketToSend> packet(new RtpPacketToSend(nullptr)); + packet->SetSequenceNumber(seq_num); + packet->set_capture_time(fake_clock_.CurrentTime()); + packet->set_allow_retransmission(true); + return packet; + } +}; + +TEST_P(RtpPacketHistoryTest, SetStoreStatus) { + EXPECT_EQ(StorageMode::kDisabled, hist_.GetStorageMode()); + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + EXPECT_EQ(StorageMode::kStoreAndCull, hist_.GetStorageMode()); + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + EXPECT_EQ(StorageMode::kStoreAndCull, hist_.GetStorageMode()); + hist_.SetStorePacketsStatus(StorageMode::kDisabled, 0); + EXPECT_EQ(StorageMode::kDisabled, hist_.GetStorageMode()); +} + +TEST_P(RtpPacketHistoryTest, ClearsHistoryAfterSetStoreStatus) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), + /*send_time=*/fake_clock_.CurrentTime()); + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // Changing store status, even to the current one, will clear the history. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, StartSeqResetAfterReset) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), + /*send_time=*/fake_clock_.CurrentTime()); + // Mark packet as pending so it won't be removed. + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); + + // Changing store status, to clear the history. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + + // Add a new packet. + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 1)), + /*send_time=*/fake_clock_.CurrentTime()); + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(To16u(kStartSeqNum + 1))); + + // Advance time past where packet expires. + fake_clock_.AdvanceTime(RtpPacketHistory::kPacketCullingDelayFactor * + RtpPacketHistory::kMinPacketDuration); + + // Add one more packet and verify no state left from packet before reset. + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 2)), + /*send_time=*/fake_clock_.CurrentTime()); + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 2))); +} + +TEST_P(RtpPacketHistoryTest, NoStoreStatus) { + EXPECT_EQ(StorageMode::kDisabled, hist_.GetStorageMode()); + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(kStartSeqNum); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + // Packet should not be stored. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, GetRtpPacket_NotStored) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + EXPECT_FALSE(hist_.GetPacketState(0)); +} + +TEST_P(RtpPacketHistoryTest, PutRtpPacket) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(kStartSeqNum); + + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, GetRtpPacket) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + Timestamp capture_time = Timestamp::Millis(1); + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(kStartSeqNum); + packet->set_capture_time(capture_time); + rtc::CopyOnWriteBuffer buffer = packet->Buffer(); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + + std::unique_ptr<RtpPacketToSend> packet_out = + hist_.GetPacketAndMarkAsPending(kStartSeqNum); + ASSERT_TRUE(packet_out); + EXPECT_EQ(buffer, packet_out->Buffer()); + EXPECT_EQ(capture_time, packet_out->capture_time()); +} + +TEST_P(RtpPacketHistoryTest, MinResendTime) { + static const TimeDelta kMinRetransmitInterval = TimeDelta::Millis(100); + + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + hist_.SetRtt(kMinRetransmitInterval); + Timestamp capture_time = fake_clock_.CurrentTime(); + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(kStartSeqNum); + size_t len = packet->size(); + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + + // First retransmission - allow early retransmission. + fake_clock_.AdvanceTimeMilliseconds(1); + packet = hist_.GetPacketAndMarkAsPending(kStartSeqNum); + ASSERT_TRUE(packet); + EXPECT_EQ(len, packet->size()); + EXPECT_EQ(packet->capture_time(), capture_time); + hist_.MarkPacketAsSent(kStartSeqNum); + + // Second retransmission - advance time to just before retransmission OK. + fake_clock_.AdvanceTime(kMinRetransmitInterval - TimeDelta::Millis(1)); + EXPECT_FALSE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); + + // Advance time to just after retransmission OK. + fake_clock_.AdvanceTimeMilliseconds(1); + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, RemovesOldestSentPacketWhenAtMaxSize) { + const size_t kMaxNumPackets = 10; + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, kMaxNumPackets); + + // History does not allow removing packets within kMinPacketDuration, + // so in order to test capacity, make sure insertion spans this time. + const TimeDelta kPacketInterval = + RtpPacketHistory::kMinPacketDuration / kMaxNumPackets; + + // Add packets until the buffer is full. + for (size_t i = 0; i < kMaxNumPackets; ++i) { + std::unique_ptr<RtpPacketToSend> packet = + CreateRtpPacket(To16u(kStartSeqNum + i)); + // Immediate mark packet as sent. + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTime(kPacketInterval); + } + + // First packet should still be there. + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // History is full, oldest one should be overwritten. + std::unique_ptr<RtpPacketToSend> packet = + CreateRtpPacket(To16u(kStartSeqNum + kMaxNumPackets)); + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + + // Oldest packet should be gone, but packet after than one still present. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); +} + +TEST_P(RtpPacketHistoryTest, RemovesOldestPacketWhenAtMaxCapacity) { + // Tests the absolute upper bound on number of stored packets. Don't allow + // storing more than this, even if packets have not yet been sent. + const size_t kMaxNumPackets = RtpPacketHistory::kMaxCapacity; + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, + RtpPacketHistory::kMaxCapacity); + + // Add packets until the buffer is full. + for (size_t i = 0; i < kMaxNumPackets; ++i) { + std::unique_ptr<RtpPacketToSend> packet = + CreateRtpPacket(To16u(kStartSeqNum + i)); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + // Mark packets as pending, preventing it from being removed. + hist_.GetPacketAndMarkAsPending(To16u(kStartSeqNum + i)); + } + + // First packet should still be there. + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // History is full, oldest one should be overwritten. + std::unique_ptr<RtpPacketToSend> packet = + CreateRtpPacket(To16u(kStartSeqNum + kMaxNumPackets)); + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + + // Oldest packet should be gone, but packet after than one still present. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); +} + +TEST_P(RtpPacketHistoryTest, RemovesLowestPrioPaddingWhenAtMaxCapacity) { + if (!GetParam()) { + // Padding prioritization is off, ignore this test. + return; + } + + // Tests the absolute upper bound on number of packets in the prioritized + // set of potential padding packets. + const size_t kMaxNumPackets = RtpPacketHistory::kMaxPaddingHistory; + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, kMaxNumPackets * 2); + hist_.SetRtt(TimeDelta::Millis(1)); + + // Add packets until the max is reached, and then yet another one. + for (size_t i = 0; i < kMaxNumPackets + 1; ++i) { + std::unique_ptr<RtpPacketToSend> packet = + CreateRtpPacket(To16u(kStartSeqNum + i)); + // Don't mark packets as sent, preventing them from being removed. + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + } + + // Advance time to allow retransmission/padding. + fake_clock_.AdvanceTimeMilliseconds(1); + + // The oldest packet will be least prioritized and has fallen out of the + // priority set. + for (size_t i = kMaxNumPackets - 1; i > 0; --i) { + auto packet = hist_.GetPayloadPaddingPacket(); + ASSERT_TRUE(packet); + EXPECT_EQ(packet->SequenceNumber(), To16u(kStartSeqNum + i + 1)); + } + + // Wrap around to newest padding packet again. + auto packet = hist_.GetPayloadPaddingPacket(); + ASSERT_TRUE(packet); + EXPECT_EQ(packet->SequenceNumber(), To16u(kStartSeqNum + kMaxNumPackets)); +} + +TEST_P(RtpPacketHistoryTest, DontRemoveTooRecentlyTransmittedPackets) { + // Set size to remove old packets as soon as possible. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + // Add a packet, marked as send, and advance time to just before removal time. + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTime(RtpPacketHistory::kMinPacketDuration - + TimeDelta::Millis(1)); + + // Add a new packet to trigger culling. + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 1)), + fake_clock_.CurrentTime()); + // First packet should still be there. + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // Advance time to where packet will be eligible for removal and try again. + fake_clock_.AdvanceTimeMilliseconds(1); + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 2)), + fake_clock_.CurrentTime()); + // First packet should no be gone, but next one still there. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); +} + +TEST_P(RtpPacketHistoryTest, DontRemoveTooRecentlyTransmittedPacketsHighRtt) { + const TimeDelta kRtt = RtpPacketHistory::kMinPacketDuration * 2; + const TimeDelta kPacketTimeout = + kRtt * RtpPacketHistory::kMinPacketDurationRtt; + + // Set size to remove old packets as soon as possible. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + hist_.SetRtt(kRtt); + + // Add a packet, marked as send, and advance time to just before removal time. + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTime(kPacketTimeout - TimeDelta::Millis(1)); + + // Add a new packet to trigger culling. + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 1)), + fake_clock_.CurrentTime()); + // First packet should still be there. + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // Advance time to where packet will be eligible for removal and try again. + fake_clock_.AdvanceTimeMilliseconds(1); + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 2)), + fake_clock_.CurrentTime()); + // First packet should no be gone, but next one still there. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); +} + +TEST_P(RtpPacketHistoryTest, RemovesOldWithCulling) { + const size_t kMaxNumPackets = 10; + // Enable culling. Even without feedback, this can trigger early removal. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, kMaxNumPackets); + + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + + TimeDelta kMaxPacketDuration = RtpPacketHistory::kMinPacketDuration * + RtpPacketHistory::kPacketCullingDelayFactor; + fake_clock_.AdvanceTime(kMaxPacketDuration - TimeDelta::Millis(1)); + + // First packet should still be there. + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // Advance to where packet can be culled, even if buffer is not full. + fake_clock_.AdvanceTimeMilliseconds(1); + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 1)), + fake_clock_.CurrentTime()); + + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, RemovesOldWithCullingHighRtt) { + const size_t kMaxNumPackets = 10; + const TimeDelta kRtt = RtpPacketHistory::kMinPacketDuration * 2; + // Enable culling. Even without feedback, this can trigger early removal. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, kMaxNumPackets); + hist_.SetRtt(kRtt); + + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + + TimeDelta kMaxPacketDuration = kRtt * + RtpPacketHistory::kMinPacketDurationRtt * + RtpPacketHistory::kPacketCullingDelayFactor; + fake_clock_.AdvanceTime(kMaxPacketDuration - TimeDelta::Millis(1)); + + // First packet should still be there. + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // Advance to where packet can be culled, even if buffer is not full. + fake_clock_.AdvanceTimeMilliseconds(1); + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + 1)), + fake_clock_.CurrentTime()); + + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, CullWithAcks) { + const TimeDelta kPacketLifetime = RtpPacketHistory::kMinPacketDuration * + RtpPacketHistory::kPacketCullingDelayFactor; + + const Timestamp start_time = fake_clock_.CurrentTime(); + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + + // Insert three packets 33ms apart, immediately mark them as sent. + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(kStartSeqNum); + packet->SetPayloadSize(50); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(33); + packet = CreateRtpPacket(To16u(kStartSeqNum + 1)); + packet->SetPayloadSize(50); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(33); + packet = CreateRtpPacket(To16u(kStartSeqNum + 2)); + packet->SetPayloadSize(50); + hist_.PutRtpPacket(std::move(packet), + /*send_time=*/fake_clock_.CurrentTime()); + + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 2))); + + // Remove middle one using ack, check that only that one is gone. + std::vector<uint16_t> acked_sequence_numbers = {To16u(kStartSeqNum + 1)}; + hist_.CullAcknowledgedPackets(acked_sequence_numbers); + + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_FALSE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 2))); + + // Advance time to where second packet would have expired, verify first packet + // is removed. + Timestamp second_packet_expiry_time = + start_time + kPacketLifetime + TimeDelta::Millis(33 + 1); + fake_clock_.AdvanceTime(second_packet_expiry_time - + fake_clock_.CurrentTime()); + hist_.SetRtt(TimeDelta::Millis(1)); // Trigger culling of old packets. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_FALSE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); + EXPECT_TRUE(hist_.GetPacketState(To16u(kStartSeqNum + 2))); + + // Advance to where last packet expires, verify all gone. + fake_clock_.AdvanceTimeMilliseconds(33); + hist_.SetRtt(TimeDelta::Millis(1)); // Trigger culling of old packets. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); + EXPECT_FALSE(hist_.GetPacketState(To16u(kStartSeqNum + 1))); + EXPECT_FALSE(hist_.GetPacketState(To16u(kStartSeqNum + 2))); +} + +TEST_P(RtpPacketHistoryTest, GetPacketAndSetSent) { + const TimeDelta kRtt = RtpPacketHistory::kMinPacketDuration * 2; + hist_.SetRtt(kRtt); + + // Set size to remove old packets as soon as possible. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + // Add a sent packet to the history. + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + + // Retransmission request, first retransmission is allowed immediately. + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); + + // Packet not yet sent, new retransmission not allowed. + fake_clock_.AdvanceTime(kRtt); + EXPECT_FALSE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); + + // Mark as sent, but too early for retransmission. + hist_.MarkPacketAsSent(kStartSeqNum); + EXPECT_FALSE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); + + // Enough time has passed, retransmission is allowed again. + fake_clock_.AdvanceTime(kRtt); + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, GetPacketWithEncapsulation) { + const uint32_t kSsrc = 92384762; + const TimeDelta kRtt = RtpPacketHistory::kMinPacketDuration * 2; + hist_.SetRtt(kRtt); + + // Set size to remove old packets as soon as possible. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + // Add a sent packet to the history, with a set SSRC. + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(kStartSeqNum); + packet->SetSsrc(kSsrc); + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + + // Retransmission request, simulate an RTX-like encapsulation, were the packet + // is sent on a different SSRC. + std::unique_ptr<RtpPacketToSend> retransmit_packet = + hist_.GetPacketAndMarkAsPending( + kStartSeqNum, [](const RtpPacketToSend& packet) { + auto encapsulated_packet = + std::make_unique<RtpPacketToSend>(packet); + encapsulated_packet->SetSsrc(packet.Ssrc() + 1); + return encapsulated_packet; + }); + ASSERT_TRUE(retransmit_packet); + EXPECT_EQ(retransmit_packet->Ssrc(), kSsrc + 1); +} + +TEST_P(RtpPacketHistoryTest, GetPacketWithEncapsulationAbortOnNullptr) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + + // Retransmission request, but the encapsulator determines that this packet is + // not suitable for retransmission (bandwidth exhausted?) so the retransmit is + // aborted and the packet is not marked as pending. + EXPECT_FALSE(hist_.GetPacketAndMarkAsPending( + kStartSeqNum, [](const RtpPacketToSend&) { return nullptr; })); + + // New try, this time getting the packet should work, and it should not be + // blocked due to any pending status. + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, DontRemovePendingTransmissions) { + const TimeDelta kRtt = RtpPacketHistory::kMinPacketDuration * 2; + const TimeDelta kPacketTimeout = + kRtt * RtpPacketHistory::kMinPacketDurationRtt; + + // Set size to remove old packets as soon as possible. + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + hist_.SetRtt(kRtt); + + // Add a sent packet. + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + + // Advance clock to just before packet timeout. + fake_clock_.AdvanceTime(kPacketTimeout - TimeDelta::Millis(1)); + // Mark as enqueued in pacer. + EXPECT_TRUE(hist_.GetPacketAndMarkAsPending(kStartSeqNum)); + + // Advance clock to where packet would have timed out. It should still + // be there and pending. + fake_clock_.AdvanceTimeMilliseconds(1); + EXPECT_TRUE(hist_.GetPacketState(kStartSeqNum)); + + // Packet sent. Now it can be removed. + hist_.MarkPacketAsSent(kStartSeqNum); + hist_.SetRtt(kRtt); // Force culling of old packets. + EXPECT_FALSE(hist_.GetPacketState(kStartSeqNum)); +} + +TEST_P(RtpPacketHistoryTest, PrioritizedPayloadPadding) { + if (!GetParam()) { + // Padding prioritization is off, ignore this test. + return; + } + + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + // Add two sent packets, one millisecond apart. + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(1); + + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum + 1), + fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(1); + + // Latest packet given equal retransmission count. + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + kStartSeqNum + 1); + + // Older packet has lower retransmission count. + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), kStartSeqNum); + + // Equal retransmission count again, use newest packet. + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + kStartSeqNum + 1); + + // Older packet has lower retransmission count. + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), kStartSeqNum); + + // Remove newest packet. + hist_.CullAcknowledgedPackets(std::vector<uint16_t>{kStartSeqNum + 1}); + + // Only older packet left. + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), kStartSeqNum); + + hist_.CullAcknowledgedPackets(std::vector<uint16_t>{kStartSeqNum}); + + EXPECT_EQ(hist_.GetPayloadPaddingPacket(), nullptr); +} + +TEST_P(RtpPacketHistoryTest, NoPendingPacketAsPadding) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(1); + + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), kStartSeqNum); + + // If packet is pending retransmission, don't try to use it as padding. + hist_.GetPacketAndMarkAsPending(kStartSeqNum); + EXPECT_EQ(nullptr, hist_.GetPayloadPaddingPacket()); + + // Market it as no longer pending, should be usable as padding again. + hist_.MarkPacketAsSent(kStartSeqNum); + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), kStartSeqNum); +} + +TEST_P(RtpPacketHistoryTest, PayloadPaddingWithEncapsulation) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 1); + + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(1); + + // Aborted padding. + EXPECT_EQ(nullptr, hist_.GetPayloadPaddingPacket( + [](const RtpPacketToSend&) { return nullptr; })); + + // Get copy of packet, but with sequence number modified. + auto padding_packet = + hist_.GetPayloadPaddingPacket([&](const RtpPacketToSend& packet) { + auto encapsulated_packet = std::make_unique<RtpPacketToSend>(packet); + encapsulated_packet->SetSequenceNumber(kStartSeqNum + 1); + return encapsulated_packet; + }); + ASSERT_TRUE(padding_packet); + EXPECT_EQ(padding_packet->SequenceNumber(), kStartSeqNum + 1); +} + +TEST_P(RtpPacketHistoryTest, NackAfterAckIsNoop) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 2); + // Add two sent packets. + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum), fake_clock_.CurrentTime()); + hist_.PutRtpPacket(CreateRtpPacket(kStartSeqNum + 1), + fake_clock_.CurrentTime()); + // Remove newest one. + hist_.CullAcknowledgedPackets(std::vector<uint16_t>{kStartSeqNum + 1}); + // Retransmission request for already acked packet, should be noop. + auto packet = hist_.GetPacketAndMarkAsPending(kStartSeqNum + 1); + EXPECT_EQ(packet.get(), nullptr); +} + +TEST_P(RtpPacketHistoryTest, OutOfOrderInsertRemoval) { + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, 10); + + // Insert packets, out of order, including both forwards and backwards + // sequence number wraps. + const int seq_offsets[] = {0, 1, -1, 2, -2, 3, -3}; + + for (int offset : seq_offsets) { + uint16_t seq_no = To16u(kStartSeqNum + offset); + std::unique_ptr<RtpPacketToSend> packet = CreateRtpPacket(seq_no); + packet->SetPayloadSize(50); + hist_.PutRtpPacket(std::move(packet), fake_clock_.CurrentTime()); + fake_clock_.AdvanceTimeMilliseconds(33); + } + + // Check packet are there and remove them in the same out-of-order fashion. + for (int offset : seq_offsets) { + uint16_t seq_no = To16u(kStartSeqNum + offset); + EXPECT_TRUE(hist_.GetPacketState(seq_no)); + std::vector<uint16_t> acked_sequence_numbers = {seq_no}; + hist_.CullAcknowledgedPackets(acked_sequence_numbers); + EXPECT_FALSE(hist_.GetPacketState(seq_no)); + } +} + +TEST_P(RtpPacketHistoryTest, UsesLastPacketAsPaddingWithPrioOff) { + if (GetParam()) { + // Padding prioritization is enabled, ignore this test. + return; + } + + const size_t kHistorySize = 10; + hist_.SetStorePacketsStatus(StorageMode::kStoreAndCull, kHistorySize); + + EXPECT_EQ(hist_.GetPayloadPaddingPacket(), nullptr); + + for (size_t i = 0; i < kHistorySize; ++i) { + hist_.PutRtpPacket(CreateRtpPacket(To16u(kStartSeqNum + i)), + fake_clock_.CurrentTime()); + hist_.MarkPacketAsSent(To16u(kStartSeqNum + i)); + fake_clock_.AdvanceTimeMilliseconds(1); + + // Last packet always returned. + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + To16u(kStartSeqNum + i)); + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + To16u(kStartSeqNum + i)); + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + To16u(kStartSeqNum + i)); + } + + // Remove packets from the end, last in the list should be returned. + for (size_t i = kHistorySize - 1; i > 0; --i) { + hist_.CullAcknowledgedPackets( + std::vector<uint16_t>{To16u(kStartSeqNum + i)}); + + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + To16u(kStartSeqNum + i - 1)); + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + To16u(kStartSeqNum + i - 1)); + EXPECT_EQ(hist_.GetPayloadPaddingPacket()->SequenceNumber(), + To16u(kStartSeqNum + i - 1)); + } + + hist_.CullAcknowledgedPackets(std::vector<uint16_t>{kStartSeqNum}); + EXPECT_EQ(hist_.GetPayloadPaddingPacket(), nullptr); +} + +INSTANTIATE_TEST_SUITE_P(WithAndWithoutPaddingPrio, + RtpPacketHistoryTest, + ::testing::Bool()); +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.cc new file mode 100644 index 0000000000..6b2cc76981 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.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 "modules/rtp_rtcp/source/rtp_packet_received.h" + +#include <stddef.h> + +#include <cstdint> +#include <vector> + +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { + +RtpPacketReceived::RtpPacketReceived() = default; +RtpPacketReceived::RtpPacketReceived( + const ExtensionManager* extensions, + webrtc::Timestamp arrival_time /*= webrtc::Timestamp::MinusInfinity()*/) + : RtpPacket(extensions), arrival_time_(arrival_time) {} +RtpPacketReceived::RtpPacketReceived(const RtpPacketReceived& packet) = default; +RtpPacketReceived::RtpPacketReceived(RtpPacketReceived&& packet) = default; + +RtpPacketReceived& RtpPacketReceived::operator=( + const RtpPacketReceived& packet) = default; +RtpPacketReceived& RtpPacketReceived::operator=(RtpPacketReceived&& packet) = + default; + +RtpPacketReceived::~RtpPacketReceived() {} + +void RtpPacketReceived::GetHeader(RTPHeader* header) const { + header->markerBit = Marker(); + header->payloadType = PayloadType(); + header->sequenceNumber = SequenceNumber(); + header->timestamp = Timestamp(); + header->ssrc = Ssrc(); + std::vector<uint32_t> csrcs = Csrcs(); + header->numCSRCs = rtc::dchecked_cast<uint8_t>(csrcs.size()); + for (size_t i = 0; i < csrcs.size(); ++i) { + header->arrOfCSRCs[i] = csrcs[i]; + } + header->paddingLength = padding_size(); + header->headerLength = headers_size(); + header->payload_type_frequency = payload_type_frequency(); + header->extension.hasTransmissionTimeOffset = + GetExtension<TransmissionOffset>( + &header->extension.transmissionTimeOffset); + header->extension.hasAbsoluteSendTime = + GetExtension<AbsoluteSendTime>(&header->extension.absoluteSendTime); + header->extension.absolute_capture_time = + GetExtension<AbsoluteCaptureTimeExtension>(); + header->extension.hasTransportSequenceNumber = + GetExtension<TransportSequenceNumberV2>( + &header->extension.transportSequenceNumber, + &header->extension.feedback_request) || + GetExtension<TransportSequenceNumber>( + &header->extension.transportSequenceNumber); + header->extension.hasAudioLevel = GetExtension<AudioLevel>( + &header->extension.voiceActivity, &header->extension.audioLevel); + header->extension.hasVideoRotation = + GetExtension<VideoOrientation>(&header->extension.videoRotation); + header->extension.hasVideoContentType = + GetExtension<VideoContentTypeExtension>( + &header->extension.videoContentType); + header->extension.has_video_timing = + GetExtension<VideoTimingExtension>(&header->extension.video_timing); + GetExtension<RtpStreamId>(&header->extension.stream_id); + GetExtension<RepairedRtpStreamId>(&header->extension.repaired_stream_id); + GetExtension<RtpMid>(&header->extension.mid); + GetExtension<PlayoutDelayLimits>(&header->extension.playout_delay); + header->extension.color_space = GetExtension<ColorSpaceExtension>(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.h new file mode 100644 index 0000000000..f290a643a4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.h @@ -0,0 +1,77 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_PACKET_RECEIVED_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_PACKET_RECEIVED_H_ + +#include <stdint.h> + +#include <utility> + +#include "api/array_view.h" +#include "api/ref_counted_base.h" +#include "api/rtp_headers.h" +#include "api/scoped_refptr.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" + +namespace webrtc { +// Class to hold rtp packet with metadata for receiver side. +// The metadata is not parsed from the rtp packet, but may be derived from the +// data that is parsed from the rtp packet. +class RtpPacketReceived : public RtpPacket { + public: + RtpPacketReceived(); + explicit RtpPacketReceived( + const ExtensionManager* extensions, + webrtc::Timestamp arrival_time = webrtc::Timestamp::MinusInfinity()); + RtpPacketReceived(const RtpPacketReceived& packet); + RtpPacketReceived(RtpPacketReceived&& packet); + + RtpPacketReceived& operator=(const RtpPacketReceived& packet); + RtpPacketReceived& operator=(RtpPacketReceived&& packet); + + ~RtpPacketReceived(); + + // TODO(danilchap): Remove this function when all code update to use RtpPacket + // directly. Function is there just for easier backward compatibilty. + void GetHeader(RTPHeader* header) const; + + // Time in local time base as close as it can to packet arrived on the + // network. + webrtc::Timestamp arrival_time() const { return arrival_time_; } + void set_arrival_time(webrtc::Timestamp time) { arrival_time_ = time; } + + // Flag if packet was recovered via RTX or FEC. + bool recovered() const { return recovered_; } + void set_recovered(bool value) { recovered_ = value; } + + int payload_type_frequency() const { return payload_type_frequency_; } + void set_payload_type_frequency(int value) { + payload_type_frequency_ = value; + } + + // An application can attach arbitrary data to an RTP packet using + // `additional_data`. The additional data does not affect WebRTC processing. + rtc::scoped_refptr<rtc::RefCountedBase> additional_data() const { + return additional_data_; + } + void set_additional_data(rtc::scoped_refptr<rtc::RefCountedBase> data) { + additional_data_ = std::move(data); + } + + private: + webrtc::Timestamp arrival_time_ = Timestamp::MinusInfinity(); + int payload_type_frequency_ = 0; + bool recovered_ = false; + rtc::scoped_refptr<rtc::RefCountedBase> additional_data_; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_PACKET_RECEIVED_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc new file mode 100644 index 0000000000..b55e74aaf0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc @@ -0,0 +1,31 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_packet_to_send.h" + +#include <cstdint> + +namespace webrtc { + +RtpPacketToSend::RtpPacketToSend(const ExtensionManager* extensions) + : RtpPacket(extensions) {} +RtpPacketToSend::RtpPacketToSend(const ExtensionManager* extensions, + size_t capacity) + : RtpPacket(extensions, capacity) {} +RtpPacketToSend::RtpPacketToSend(const RtpPacketToSend& packet) = default; +RtpPacketToSend::RtpPacketToSend(RtpPacketToSend&& packet) = default; + +RtpPacketToSend& RtpPacketToSend::operator=(const RtpPacketToSend& packet) = + default; +RtpPacketToSend& RtpPacketToSend::operator=(RtpPacketToSend&& packet) = default; + +RtpPacketToSend::~RtpPacketToSend() = default; + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h new file mode 100644 index 0000000000..438ca354ed --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h @@ -0,0 +1,147 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_PACKET_TO_SEND_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_PACKET_TO_SEND_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <utility> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/ref_counted_base.h" +#include "api/scoped_refptr.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "api/video/video_timing.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" + +namespace webrtc { +// Class to hold rtp packet with metadata for sender side. +// The metadata is not send over the wire, but packet sender may use it to +// create rtp header extensions or other data that is sent over the wire. +class RtpPacketToSend : public RtpPacket { + public: + // RtpPacketToSend::Type is deprecated. Use RtpPacketMediaType directly. + using Type = RtpPacketMediaType; + + explicit RtpPacketToSend(const ExtensionManager* extensions); + RtpPacketToSend(const ExtensionManager* extensions, size_t capacity); + RtpPacketToSend(const RtpPacketToSend& packet); + RtpPacketToSend(RtpPacketToSend&& packet); + + RtpPacketToSend& operator=(const RtpPacketToSend& packet); + RtpPacketToSend& operator=(RtpPacketToSend&& packet); + + ~RtpPacketToSend(); + + // Time in local time base as close as it can to frame capture time. + webrtc::Timestamp capture_time() const { return capture_time_; } + void set_capture_time(webrtc::Timestamp time) { capture_time_ = time; } + + void set_packet_type(RtpPacketMediaType type) { packet_type_ = type; } + absl::optional<RtpPacketMediaType> packet_type() const { + return packet_type_; + } + + // If this is a retransmission, indicates the sequence number of the original + // media packet that this packet represents. If RTX is used this will likely + // be different from SequenceNumber(). + void set_retransmitted_sequence_number(uint16_t sequence_number) { + retransmitted_sequence_number_ = sequence_number; + } + absl::optional<uint16_t> retransmitted_sequence_number() const { + return retransmitted_sequence_number_; + } + + void set_allow_retransmission(bool allow_retransmission) { + allow_retransmission_ = allow_retransmission; + } + bool allow_retransmission() const { return allow_retransmission_; } + + // An application can attach arbitrary data to an RTP packet using + // `additional_data`. The additional data does not affect WebRTC processing. + rtc::scoped_refptr<rtc::RefCountedBase> additional_data() const { + return additional_data_; + } + void set_additional_data(rtc::scoped_refptr<rtc::RefCountedBase> data) { + additional_data_ = std::move(data); + } + + void set_packetization_finish_time(webrtc::Timestamp time) { + SetExtension<VideoTimingExtension>( + VideoSendTiming::GetDeltaCappedMs(time - capture_time_), + VideoTimingExtension::kPacketizationFinishDeltaOffset); + } + + void set_pacer_exit_time(webrtc::Timestamp time) { + SetExtension<VideoTimingExtension>( + VideoSendTiming::GetDeltaCappedMs(time - capture_time_), + VideoTimingExtension::kPacerExitDeltaOffset); + } + + void set_network_time(webrtc::Timestamp time) { + SetExtension<VideoTimingExtension>( + VideoSendTiming::GetDeltaCappedMs(time - capture_time_), + VideoTimingExtension::kNetworkTimestampDeltaOffset); + } + + void set_network2_time(webrtc::Timestamp time) { + SetExtension<VideoTimingExtension>( + VideoSendTiming::GetDeltaCappedMs(time - capture_time_), + VideoTimingExtension::kNetwork2TimestampDeltaOffset); + } + + // Indicates if packet is the first packet of a video frame. + void set_first_packet_of_frame(bool is_first_packet) { + is_first_packet_of_frame_ = is_first_packet; + } + bool is_first_packet_of_frame() const { return is_first_packet_of_frame_; } + + // Indicates if packet contains payload for a video key-frame. + void set_is_key_frame(bool is_key_frame) { is_key_frame_ = is_key_frame; } + bool is_key_frame() const { return is_key_frame_; } + + // Indicates if packets should be protected by FEC (Forward Error Correction). + void set_fec_protect_packet(bool protect) { fec_protect_packet_ = protect; } + bool fec_protect_packet() const { return fec_protect_packet_; } + + // Indicates if packet is using RED encapsulation, in accordance with + // https://tools.ietf.org/html/rfc2198 + void set_is_red(bool is_red) { is_red_ = is_red; } + bool is_red() const { return is_red_; } + + // The amount of time spent in the send queue, used for totalPacketSendDelay. + // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalpacketsenddelay + void set_time_in_send_queue(TimeDelta time_in_send_queue) { + time_in_send_queue_ = time_in_send_queue; + } + absl::optional<TimeDelta> time_in_send_queue() const { + return time_in_send_queue_; + } + + private: + webrtc::Timestamp capture_time_ = webrtc::Timestamp::Zero(); + absl::optional<RtpPacketMediaType> packet_type_; + bool allow_retransmission_ = false; + absl::optional<uint16_t> retransmitted_sequence_number_; + rtc::scoped_refptr<rtc::RefCountedBase> additional_data_; + bool is_first_packet_of_frame_ = false; + bool is_key_frame_ = false; + bool fec_protect_packet_ = false; + bool is_red_ = false; + absl::optional<TimeDelta> time_in_send_queue_; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_PACKET_TO_SEND_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc new file mode 100644 index 0000000000..41bc114efb --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc @@ -0,0 +1,1285 @@ +/* + * 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 "common_video/test/utilities.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_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/random.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; + +constexpr int8_t kPayloadType = 100; +constexpr uint32_t kSsrc = 0x12345678; +constexpr uint16_t kSeqNum = 0x1234; +constexpr uint8_t kSeqNumFirstByte = kSeqNum >> 8; +constexpr uint8_t kSeqNumSecondByte = kSeqNum & 0xff; +constexpr uint32_t kTimestamp = 0x65431278; +constexpr uint8_t kTransmissionOffsetExtensionId = 1; +constexpr uint8_t kDependencyDescriptorExtensionId = 2; +constexpr uint8_t kAudioLevelExtensionId = 9; +constexpr uint8_t kRtpStreamIdExtensionId = 0xa; +constexpr uint8_t kRtpMidExtensionId = 0xb; +constexpr uint8_t kVideoTimingExtensionId = 0xc; +constexpr uint8_t kTwoByteExtensionId = 0xf0; +constexpr int32_t kTimeOffset = 0x56ce; +constexpr bool kVoiceActive = true; +constexpr uint8_t kAudioLevel = 0x5a; +constexpr char kStreamId[] = "streamid"; +constexpr char kMid[] = "mid"; +constexpr char kLongMid[] = "extra-long string to test two-byte header"; +constexpr size_t kMaxPaddingSize = 224u; +// clang-format off +constexpr uint8_t kMinimumPacket[] = { + 0x80, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78}; + +constexpr uint8_t kPacketWithTO[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x01, + 0x12, 0x00, 0x56, 0xce}; + +constexpr uint8_t kPacketWithTOAndAL[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x02, + 0x12, 0x00, 0x56, 0xce, + 0x90, 0x80|kAudioLevel, 0x00, 0x00}; + +constexpr uint8_t kPacketWithTwoByteExtensionIdLast[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x04, + 0x01, 0x03, 0x00, 0x56, + 0xce, 0x09, 0x01, 0x80|kAudioLevel, + kTwoByteExtensionId, 0x03, 0x00, 0x30, // => 0x00 0x30 0x22 + 0x22, 0x00, 0x00, 0x00}; // => Playout delay.min_ms = 3*10 + // => Playout delay.max_ms = 34*10 + +constexpr uint8_t kPacketWithTwoByteExtensionIdFirst[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x04, + kTwoByteExtensionId, 0x03, 0x00, 0x30, // => 0x00 0x30 0x22 + 0x22, 0x01, 0x03, 0x00, // => Playout delay.min_ms = 3*10 + 0x56, 0xce, 0x09, 0x01, // => Playout delay.max_ms = 34*10 + 0x80|kAudioLevel, 0x00, 0x00, 0x00}; + +constexpr uint8_t kPacketWithTOAndALInvalidPadding[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x03, + 0x12, 0x00, 0x56, 0xce, + 0x00, 0x02, 0x00, 0x00, // 0x02 is invalid padding, parsing should stop. + 0x90, 0x80|kAudioLevel, 0x00, 0x00}; + +constexpr uint8_t kPacketWithTOAndALReservedExtensionId[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x03, + 0x12, 0x00, 0x56, 0xce, + 0x00, 0xF0, 0x00, 0x00, // F is a reserved id, parsing should stop. + 0x90, 0x80|kAudioLevel, 0x00, 0x00}; + +constexpr uint8_t kPacketWithRsid[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x03, + 0xa7, 's', 't', 'r', + 'e', 'a', 'm', 'i', + 'd' , 0x00, 0x00, 0x00}; + +constexpr uint8_t kPacketWithMid[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x01, + 0xb2, 'm', 'i', 'd'}; + +constexpr uint8_t kCsrcAudioLevelExtensionId = 0xc; +constexpr uint8_t kCsrcAudioLevelsSize = 4; +constexpr uint8_t kCsrcAudioLevels[] = {0x7f, 0x00, 0x10, 0x08}; +constexpr uint8_t kPacketWithCsrcAudioLevels[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x02, + (kCsrcAudioLevelExtensionId << 4) | (kCsrcAudioLevelsSize - 1), + 0x7f, 0x00, 0x10, + 0x08, 0x00, 0x00, 0x00}; + +constexpr uint32_t kCsrcs[] = {0x34567890, 0x32435465}; +constexpr uint8_t kPayload[] = {'p', 'a', 'y', 'l', 'o', 'a', 'd'}; +constexpr uint8_t kPacketPaddingSize = 8; +constexpr uint8_t kPacket[] = { + 0xb2, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x34, 0x56, 0x78, 0x90, + 0x32, 0x43, 0x54, 0x65, + 0xbe, 0xde, 0x00, 0x01, + 0x12, 0x00, 0x56, 0xce, + 'p', 'a', 'y', 'l', 'o', 'a', 'd', + 'p', 'a', 'd', 'd', 'i', 'n', 'g', kPacketPaddingSize}; + +constexpr uint8_t kPacketWithTwoByteHeaderExtension[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x02, // Two-byte header extension profile id + length. + kTwoByteExtensionId, 0x03, 0x00, 0x56, + 0xce, 0x00, 0x00, 0x00}; + +constexpr uint8_t kPacketWithLongTwoByteHeaderExtension[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x0B, // Two-byte header extension profile id + length. + kTwoByteExtensionId, 0x29, 'e', 'x', + 't', 'r', 'a', '-', 'l', 'o', 'n', 'g', + ' ', 's', 't', 'r', 'i', 'n', 'g', ' ', + 't', 'o', ' ', 't', 'e', 's', 't', ' ', + 't', 'w', 'o', '-', 'b', 'y', 't', 'e', + ' ', 'h', 'e', 'a', 'd', 'e', 'r', 0x00}; + +constexpr uint8_t kPacketWithTwoByteHeaderExtensionWithPadding[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0x10, 0x00, 0x00, 0x03, // Two-byte header extension profile id + length. + kTwoByteExtensionId, 0x03, 0x00, 0x56, + 0xce, 0x00, 0x00, 0x00, // Three padding bytes. + kAudioLevelExtensionId, 0x01, 0x80|kAudioLevel, 0x00}; + +constexpr uint8_t kPacketWithInvalidExtension[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, // kTimestamp. + 0x12, 0x34, 0x56, 0x78, // kSSrc. + 0xbe, 0xde, 0x00, 0x02, // Extension block of size 2 x 32bit words. + (kTransmissionOffsetExtensionId << 4) | 6, // (6+1)-byte extension, but + 'e', 'x', 't', // Transmission Offset + 'd', 'a', 't', 'a', // expected to be 3-bytes. + 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; + +constexpr uint8_t kPacketWithLegacyTimingExtension[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, // kTimestamp. + 0x12, 0x34, 0x56, 0x78, // kSSrc. + 0xbe, 0xde, 0x00, 0x04, // Extension block of size 4 x 32bit words. + (kVideoTimingExtensionId << 4) + | VideoTimingExtension::kValueSizeBytes - 2, // Old format without flags. + 0x00, 0x01, 0x00, + 0x02, 0x00, 0x03, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; +// clang-format on + +void TestCreateAndParseColorSpaceExtension(bool with_hdr_metadata) { + // Create packet with extension. + RtpPacket::ExtensionManager extensions(/*extmap-allow-mixed=*/true); + extensions.Register<ColorSpaceExtension>(1); + RtpPacket packet(&extensions); + const ColorSpace kColorSpace = CreateTestColorSpace(with_hdr_metadata); + EXPECT_TRUE(packet.SetExtension<ColorSpaceExtension>(kColorSpace)); + packet.SetPayloadSize(42); + + // Read packet with the extension. + RtpPacketReceived parsed(&extensions); + EXPECT_TRUE(parsed.Parse(packet.Buffer())); + ColorSpace parsed_color_space; + EXPECT_TRUE(parsed.GetExtension<ColorSpaceExtension>(&parsed_color_space)); + EXPECT_EQ(kColorSpace, parsed_color_space); +} + +TEST(RtpPacketTest, CreateMinimum) { + RtpPacketToSend packet(nullptr); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + EXPECT_THAT(kMinimumPacket, ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, CreateWithExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, CreateWith2Extensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel); + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, CreateWithTwoByteHeaderExtensionFirst) { + RtpPacketToSend::ExtensionManager extensions(true); + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + extensions.Register<PlayoutDelayLimits>(kTwoByteExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + // Set extension that requires two-byte header. + VideoPlayoutDelay playoutDelay = {30, 340}; + ASSERT_TRUE(packet.SetExtension<PlayoutDelayLimits>(playoutDelay)); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel); + EXPECT_THAT(kPacketWithTwoByteExtensionIdFirst, + ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, CreateWithTwoByteHeaderExtensionLast) { + // This test will trigger RtpPacket::PromoteToTwoByteHeaderExtension(). + RtpPacketToSend::ExtensionManager extensions(true); + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + extensions.Register<PlayoutDelayLimits>(kTwoByteExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel); + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); + // Set extension that requires two-byte header. + VideoPlayoutDelay playoutDelay = {30, 340}; + ASSERT_TRUE(packet.SetExtension<PlayoutDelayLimits>(playoutDelay)); + EXPECT_THAT(kPacketWithTwoByteExtensionIdLast, + ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, CreateWithDynamicSizedExtensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpStreamId>(kRtpStreamIdExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<RtpStreamId>(kStreamId); + EXPECT_THAT(kPacketWithRsid, ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, TryToCreateWithEmptyRsid) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpStreamId>(kRtpStreamIdExtensionId); + RtpPacketToSend packet(&extensions); + EXPECT_FALSE(packet.SetExtension<RtpStreamId>("")); +} + +TEST(RtpPacketTest, TryToCreateWithLongRsid) { + RtpPacketToSend::ExtensionManager extensions; + constexpr char kLongStreamId[] = "LoooooooooongRsid"; + ASSERT_EQ(strlen(kLongStreamId), 17u); + extensions.Register<RtpStreamId>(kRtpStreamIdExtensionId); + RtpPacketToSend packet(&extensions); + EXPECT_FALSE(packet.SetExtension<RtpStreamId>(kLongStreamId)); +} + +TEST(RtpPacketTest, TryToCreateWithEmptyMid) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpMid>(kRtpMidExtensionId); + RtpPacketToSend packet(&extensions); + EXPECT_FALSE(packet.SetExtension<RtpMid>("")); +} + +TEST(RtpPacketTest, TryToCreateWithLongMid) { + RtpPacketToSend::ExtensionManager extensions; + constexpr char kLongMid[] = "LoooooooooonogMid"; + ASSERT_EQ(strlen(kLongMid), 17u); + extensions.Register<RtpMid>(kRtpMidExtensionId); + RtpPacketToSend packet(&extensions); + EXPECT_FALSE(packet.SetExtension<RtpMid>(kLongMid)); +} + +TEST(RtpPacketTest, TryToCreateTwoByteHeaderNotSupported) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<AudioLevel>(kTwoByteExtensionId); + RtpPacketToSend packet(&extensions); + // Set extension that requires two-byte header. + EXPECT_FALSE(packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel)); +} + +TEST(RtpPacketTest, CreateWithMaxSizeHeaderExtension) { + const std::string kValue = "123456789abcdef"; + RtpPacket::ExtensionManager extensions; + extensions.Register<RtpMid>(1); + extensions.Register<RtpStreamId>(2); + + RtpPacket packet(&extensions); + EXPECT_TRUE(packet.SetExtension<RtpMid>(kValue)); + + packet.SetPayloadSize(42); + // Rewriting allocated extension is allowed. + EXPECT_TRUE(packet.SetExtension<RtpMid>(kValue)); + // Adding another extension after payload is set is not allowed. + EXPECT_FALSE(packet.SetExtension<RtpStreamId>(kValue)); + + // Read packet with the extension. + RtpPacketReceived parsed(&extensions); + EXPECT_TRUE(parsed.Parse(packet.Buffer())); + std::string read; + EXPECT_TRUE(parsed.GetExtension<RtpMid>(&read)); + EXPECT_EQ(read, kValue); +} + +TEST(RtpPacketTest, SetsRegisteredExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + RtpPacketToSend packet(&extensions); + + EXPECT_TRUE(packet.IsRegistered<TransmissionOffset>()); + EXPECT_FALSE(packet.HasExtension<TransmissionOffset>()); + + // Try to set the extensions. + EXPECT_TRUE(packet.SetExtension<TransmissionOffset>(kTimeOffset)); + + EXPECT_TRUE(packet.HasExtension<TransmissionOffset>()); + EXPECT_EQ(packet.GetExtension<TransmissionOffset>(), kTimeOffset); +} + +TEST(RtpPacketTest, FailsToSetUnregisteredExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + RtpPacketToSend packet(&extensions); + + EXPECT_FALSE(packet.IsRegistered<TransportSequenceNumber>()); + EXPECT_FALSE(packet.HasExtension<TransportSequenceNumber>()); + + EXPECT_FALSE(packet.SetExtension<TransportSequenceNumber>(42)); + + EXPECT_FALSE(packet.HasExtension<TransportSequenceNumber>()); + EXPECT_EQ(packet.GetExtension<TransportSequenceNumber>(), absl::nullopt); +} + +TEST(RtpPacketTest, CreateWithDynamicSizedExtensionCsrcAudioLevel) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<CsrcAudioLevel>(kCsrcAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + CsrcAudioLevelList levels; + levels.numAudioLevels = kCsrcAudioLevelsSize; + for (uint8_t i = 0; i < kCsrcAudioLevelsSize; i++) { + levels.arrOfAudioLevels[i] = kCsrcAudioLevels[i]; + } + packet.SetExtension<CsrcAudioLevel>(levels); + EXPECT_THAT(kPacketWithCsrcAudioLevels, + ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, SetReservedExtensionsAfterPayload) { + const size_t kPayloadSize = 4; + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + + EXPECT_TRUE(packet.ReserveExtension<TransmissionOffset>()); + packet.SetPayloadSize(kPayloadSize); + // Can't set extension after payload. + EXPECT_FALSE(packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel)); + // Unless reserved. + EXPECT_TRUE(packet.SetExtension<TransmissionOffset>(kTimeOffset)); +} + +TEST(RtpPacketTest, CreatePurePadding) { + const size_t kPaddingSize = kMaxPaddingSize - 1; + RtpPacketToSend packet(nullptr, 12 + kPaddingSize); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + + EXPECT_LT(packet.size(), packet.capacity()); + EXPECT_FALSE(packet.SetPadding(kPaddingSize + 1)); + EXPECT_TRUE(packet.SetPadding(kPaddingSize)); + EXPECT_EQ(packet.size(), packet.capacity()); +} + +TEST(RtpPacketTest, CreateUnalignedPadding) { + const size_t kPayloadSize = 3; // Make padding start at unaligned address. + RtpPacketToSend packet(nullptr, 12 + kPayloadSize + kMaxPaddingSize); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetPayloadSize(kPayloadSize); + + EXPECT_LT(packet.size(), packet.capacity()); + EXPECT_TRUE(packet.SetPadding(kMaxPaddingSize)); + EXPECT_EQ(packet.size(), packet.capacity()); +} + +TEST(RtpPacketTest, WritesPaddingSizeToLastByte) { + const size_t kPaddingSize = 5; + RtpPacket packet; + + EXPECT_TRUE(packet.SetPadding(kPaddingSize)); + EXPECT_EQ(packet.data()[packet.size() - 1], kPaddingSize); +} + +TEST(RtpPacketTest, UsesZerosForPadding) { + const size_t kPaddingSize = 5; + RtpPacket packet; + + EXPECT_TRUE(packet.SetPadding(kPaddingSize)); + EXPECT_THAT(rtc::MakeArrayView(packet.data() + 12, kPaddingSize - 1), + Each(0)); +} + +TEST(RtpPacketTest, CreateOneBytePadding) { + size_t kPayloadSize = 123; + RtpPacket packet(nullptr, 12 + kPayloadSize + 1); + packet.SetPayloadSize(kPayloadSize); + + EXPECT_TRUE(packet.SetPadding(1)); + + EXPECT_EQ(packet.size(), 12 + kPayloadSize + 1); + EXPECT_EQ(packet.padding_size(), 1u); +} + +TEST(RtpPacketTest, FailsToAddPaddingWithoutCapacity) { + size_t kPayloadSize = 123; + RtpPacket packet(nullptr, 12 + kPayloadSize); + packet.SetPayloadSize(kPayloadSize); + + EXPECT_FALSE(packet.SetPadding(1)); +} + +TEST(RtpPacketTest, ParseMinimum) { + RtpPacketReceived packet; + EXPECT_TRUE(packet.Parse(kMinimumPacket, sizeof(kMinimumPacket))); + EXPECT_EQ(kPayloadType, packet.PayloadType()); + EXPECT_EQ(kSeqNum, packet.SequenceNumber()); + EXPECT_EQ(kTimestamp, packet.Timestamp()); + EXPECT_EQ(kSsrc, packet.Ssrc()); + EXPECT_EQ(0u, packet.padding_size()); + EXPECT_EQ(0u, packet.payload_size()); +} + +TEST(RtpPacketTest, ParseBuffer) { + rtc::CopyOnWriteBuffer unparsed(kMinimumPacket); + const uint8_t* raw = unparsed.data(); + + RtpPacketReceived packet; + EXPECT_TRUE(packet.Parse(std::move(unparsed))); + EXPECT_EQ(raw, packet.data()); // Expect packet take the buffer without copy. + EXPECT_EQ(kSeqNum, packet.SequenceNumber()); + EXPECT_EQ(kTimestamp, packet.Timestamp()); + EXPECT_EQ(kSsrc, packet.Ssrc()); + EXPECT_EQ(0u, packet.padding_size()); + EXPECT_EQ(0u, packet.payload_size()); +} + +TEST(RtpPacketTest, ParseWithExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTO, sizeof(kPacketWithTO))); + EXPECT_EQ(kPayloadType, packet.PayloadType()); + EXPECT_EQ(kSeqNum, packet.SequenceNumber()); + EXPECT_EQ(kTimestamp, packet.Timestamp()); + EXPECT_EQ(kSsrc, packet.Ssrc()); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); + EXPECT_EQ(0u, packet.payload_size()); + EXPECT_EQ(0u, packet.padding_size()); +} + +TEST(RtpPacketTest, ParseHeaderOnly) { + // clang-format off + constexpr uint8_t kPaddingHeader[] = { + 0x80, 0x62, 0x35, 0x79, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78}; + // clang-format on + + RtpPacket packet; + EXPECT_TRUE(packet.Parse(rtc::CopyOnWriteBuffer(kPaddingHeader))); + EXPECT_EQ(packet.PayloadType(), 0x62u); + EXPECT_EQ(packet.SequenceNumber(), 0x3579u); + EXPECT_EQ(packet.Timestamp(), 0x65431278u); + EXPECT_EQ(packet.Ssrc(), 0x12345678u); + + EXPECT_FALSE(packet.has_padding()); + EXPECT_EQ(packet.padding_size(), 0u); + EXPECT_EQ(packet.payload_size(), 0u); +} + +TEST(RtpPacketTest, ParseHeaderOnlyWithPadding) { + // clang-format off + constexpr uint8_t kPaddingHeader[] = { + 0xa0, 0x62, 0x35, 0x79, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78}; + // clang-format on + + RtpPacket packet; + EXPECT_TRUE(packet.Parse(rtc::CopyOnWriteBuffer(kPaddingHeader))); + + EXPECT_TRUE(packet.has_padding()); + EXPECT_EQ(packet.padding_size(), 0u); + EXPECT_EQ(packet.payload_size(), 0u); +} + +TEST(RtpPacketTest, ParseHeaderOnlyWithExtensionAndPadding) { + // clang-format off + constexpr uint8_t kPaddingHeader[] = { + 0xb0, 0x62, 0x35, 0x79, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0xbe, 0xde, 0x00, 0x01, + 0x11, 0x00, 0x00, 0x00}; + // clang-format on + + RtpHeaderExtensionMap extensions; + extensions.Register<TransmissionOffset>(1); + RtpPacket packet(&extensions); + EXPECT_TRUE(packet.Parse(rtc::CopyOnWriteBuffer(kPaddingHeader))); + EXPECT_TRUE(packet.has_padding()); + EXPECT_TRUE(packet.HasExtension<TransmissionOffset>()); + EXPECT_EQ(packet.padding_size(), 0u); +} + +TEST(RtpPacketTest, ParsePaddingOnlyPacket) { + // clang-format off + constexpr uint8_t kPaddingHeader[] = { + 0xa0, 0x62, 0x35, 0x79, + 0x65, 0x43, 0x12, 0x78, + 0x12, 0x34, 0x56, 0x78, + 0, 0, 3}; + // clang-format on + + RtpPacket packet; + EXPECT_TRUE(packet.Parse(rtc::CopyOnWriteBuffer(kPaddingHeader))); + EXPECT_TRUE(packet.has_padding()); + EXPECT_EQ(packet.padding_size(), 3u); +} + +TEST(RtpPacketTest, GetExtensionWithoutParametersReturnsOptionalValue) { + RtpPacket::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<RtpStreamId>(kRtpStreamIdExtensionId); + + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTO, sizeof(kPacketWithTO))); + + auto time_offset = packet.GetExtension<TransmissionOffset>(); + static_assert( + std::is_same<decltype(time_offset), + absl::optional<TransmissionOffset::value_type>>::value, + ""); + EXPECT_EQ(time_offset, kTimeOffset); + EXPECT_FALSE(packet.GetExtension<RtpStreamId>().has_value()); +} + +TEST(RtpPacketTest, GetRawExtensionWhenPresent) { + constexpr uint8_t kRawPacket[] = { + // comment for clang-format to align kRawPacket nicer. + 0x90, 100, 0x5e, 0x04, // + 0x65, 0x43, 0x12, 0x78, // Timestamp. + 0x12, 0x34, 0x56, 0x78, // Ssrc + 0xbe, 0xde, 0x00, 0x01, // Extension header + 0x12, 'm', 'i', 'd', // 3-byte extension with id=1. + 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpMid>(1); + RtpPacket packet(&extensions); + ASSERT_TRUE(packet.Parse(kRawPacket, sizeof(kRawPacket))); + EXPECT_THAT(packet.GetRawExtension<RtpMid>(), ElementsAre('m', 'i', 'd')); +} + +TEST(RtpPacketTest, GetRawExtensionWhenAbsent) { + constexpr uint8_t kRawPacket[] = { + // comment for clang-format to align kRawPacket nicer. + 0x90, 100, 0x5e, 0x04, // + 0x65, 0x43, 0x12, 0x78, // Timestamp. + 0x12, 0x34, 0x56, 0x78, // Ssrc + 0xbe, 0xde, 0x00, 0x01, // Extension header + 0x12, 'm', 'i', 'd', // 3-byte extension with id=1. + 'p', 'a', 'y', 'l', 'o', 'a', 'd'}; + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpMid>(2); + RtpPacket packet(&extensions); + ASSERT_TRUE(packet.Parse(kRawPacket, sizeof(kRawPacket))); + EXPECT_THAT(packet.GetRawExtension<RtpMid>(), IsEmpty()); +} + +TEST(RtpPacketTest, ParseWithInvalidSizedExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithInvalidExtension, + sizeof(kPacketWithInvalidExtension))); + + // Extension should be ignored. + int32_t time_offset; + EXPECT_FALSE(packet.GetExtension<TransmissionOffset>(&time_offset)); + + // But shouldn't prevent reading payload. + EXPECT_THAT(packet.payload(), ElementsAreArray(kPayload)); +} + +TEST(RtpPacketTest, ParseWithOverSizedExtension) { + // clang-format off + const uint8_t bad_packet[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, // kTimestamp. + 0x12, 0x34, 0x56, 0x78, // kSsrc. + 0xbe, 0xde, 0x00, 0x01, // Extension of size 1x32bit word. + 0x00, // Add a byte of padding. + 0x12, // Extension id 1 size (2+1). + 0xda, 0x1a // Only 2 bytes of extension payload. + }; + // clang-format on + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(1); + RtpPacketReceived packet(&extensions); + + // Parse should ignore bad extension and proceed. + EXPECT_TRUE(packet.Parse(bad_packet, sizeof(bad_packet))); + int32_t time_offset; + // But extracting extension should fail. + EXPECT_FALSE(packet.GetExtension<TransmissionOffset>(&time_offset)); +} + +TEST(RtpPacketTest, ParseWith2Extensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTOAndAL, sizeof(kPacketWithTOAndAL))); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); + bool voice_active; + uint8_t audio_level; + EXPECT_TRUE(packet.GetExtension<AudioLevel>(&voice_active, &audio_level)); + EXPECT_EQ(kVoiceActive, voice_active); + EXPECT_EQ(kAudioLevel, audio_level); +} + +TEST(RtpPacketTest, ParseSecondPacketWithFewerExtensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTOAndAL, sizeof(kPacketWithTOAndAL))); + EXPECT_TRUE(packet.HasExtension<TransmissionOffset>()); + EXPECT_TRUE(packet.HasExtension<AudioLevel>()); + + // Second packet without audio level. + EXPECT_TRUE(packet.Parse(kPacketWithTO, sizeof(kPacketWithTO))); + EXPECT_TRUE(packet.HasExtension<TransmissionOffset>()); + EXPECT_FALSE(packet.HasExtension<AudioLevel>()); +} + +TEST(RtpPacketTest, ParseWith2ExtensionsInvalidPadding) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTOAndALInvalidPadding, + sizeof(kPacketWithTOAndALInvalidPadding))); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); + bool voice_active; + uint8_t audio_level; + EXPECT_FALSE(packet.GetExtension<AudioLevel>(&voice_active, &audio_level)); +} + +TEST(RtpPacketTest, ParseWith2ExtensionsReservedExtensionId) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTOAndALReservedExtensionId, + sizeof(kPacketWithTOAndALReservedExtensionId))); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); + bool voice_active; + uint8_t audio_level; + EXPECT_FALSE(packet.GetExtension<AudioLevel>(&voice_active, &audio_level)); +} + +TEST(RtpPacketTest, ParseWithAllFeatures) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacket, sizeof(kPacket))); + EXPECT_EQ(kPayloadType, packet.PayloadType()); + EXPECT_EQ(kSeqNum, packet.SequenceNumber()); + EXPECT_EQ(kTimestamp, packet.Timestamp()); + EXPECT_EQ(kSsrc, packet.Ssrc()); + EXPECT_THAT(packet.Csrcs(), ElementsAreArray(kCsrcs)); + EXPECT_THAT(packet.payload(), ElementsAreArray(kPayload)); + EXPECT_EQ(kPacketPaddingSize, packet.padding_size()); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); +} + +TEST(RtpPacketTest, ParseTwoByteHeaderExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTwoByteExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithTwoByteHeaderExtension, + sizeof(kPacketWithTwoByteHeaderExtension))); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); +} + +TEST(RtpPacketTest, ParseLongTwoByteHeaderExtension) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpMid>(kTwoByteExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithLongTwoByteHeaderExtension, + sizeof(kPacketWithLongTwoByteHeaderExtension))); + std::string long_rtp_mid; + EXPECT_TRUE(packet.GetExtension<RtpMid>(&long_rtp_mid)); + EXPECT_EQ(kLongMid, long_rtp_mid); +} + +TEST(RtpPacketTest, ParseTwoByteHeaderExtensionWithPadding) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTwoByteExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE( + packet.Parse(kPacketWithTwoByteHeaderExtensionWithPadding, + sizeof(kPacketWithTwoByteHeaderExtensionWithPadding))); + int32_t time_offset; + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); + bool voice_active; + uint8_t audio_level; + EXPECT_TRUE(packet.GetExtension<AudioLevel>(&voice_active, &audio_level)); + EXPECT_EQ(kVoiceActive, voice_active); + EXPECT_EQ(kAudioLevel, audio_level); +} + +TEST(RtpPacketTest, ParseWithExtensionDelayed) { + RtpPacketReceived packet; + EXPECT_TRUE(packet.Parse(kPacketWithTO, sizeof(kPacketWithTO))); + EXPECT_EQ(kPayloadType, packet.PayloadType()); + EXPECT_EQ(kSeqNum, packet.SequenceNumber()); + EXPECT_EQ(kTimestamp, packet.Timestamp()); + EXPECT_EQ(kSsrc, packet.Ssrc()); + + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + + int32_t time_offset; + EXPECT_FALSE(packet.GetExtension<TransmissionOffset>(&time_offset)); + packet.IdentifyExtensions(extensions); + EXPECT_TRUE(packet.GetExtension<TransmissionOffset>(&time_offset)); + EXPECT_EQ(kTimeOffset, time_offset); + EXPECT_EQ(0u, packet.payload_size()); + EXPECT_EQ(0u, packet.padding_size()); +} + +TEST(RtpPacketTest, ParseDynamicSizeExtension) { + // clang-format off + const uint8_t kPacket1[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, // Timestamp. + 0x12, 0x34, 0x56, 0x78, // Ssrc. + 0xbe, 0xde, 0x00, 0x02, // Extensions block of size 2x32bit words. + 0x21, 'H', 'D', // Extension with id = 2, size = (1+1). + 0x12, 'r', 't', 'x', // Extension with id = 1, size = (2+1). + 0x00}; // Extension padding. + const uint8_t kPacket2[] = { + 0x90, kPayloadType, kSeqNumFirstByte, kSeqNumSecondByte, + 0x65, 0x43, 0x12, 0x78, // Timestamp. + 0x12, 0x34, 0x56, 0x79, // Ssrc. + 0xbe, 0xde, 0x00, 0x01, // Extensions block of size 1x32bit words. + 0x11, 'H', 'D', // Extension with id = 1, size = (1+1). + 0x00}; // Extension padding. + // clang-format on + RtpPacketReceived::ExtensionManager extensions; + extensions.Register<RtpStreamId>(1); + extensions.Register<RepairedRtpStreamId>(2); + RtpPacketReceived packet(&extensions); + ASSERT_TRUE(packet.Parse(kPacket1, sizeof(kPacket1))); + + std::string rsid; + EXPECT_TRUE(packet.GetExtension<RtpStreamId>(&rsid)); + EXPECT_EQ(rsid, "rtx"); + + std::string repaired_rsid; + EXPECT_TRUE(packet.GetExtension<RepairedRtpStreamId>(&repaired_rsid)); + EXPECT_EQ(repaired_rsid, "HD"); + + // Parse another packet with RtpStreamId extension of different size. + ASSERT_TRUE(packet.Parse(kPacket2, sizeof(kPacket2))); + EXPECT_TRUE(packet.GetExtension<RtpStreamId>(&rsid)); + EXPECT_EQ(rsid, "HD"); + EXPECT_FALSE(packet.GetExtension<RepairedRtpStreamId>(&repaired_rsid)); +} + +TEST(RtpPacketTest, ParseWithMid) { + RtpPacketReceived::ExtensionManager extensions; + extensions.Register<RtpMid>(kRtpMidExtensionId); + RtpPacketReceived packet(&extensions); + ASSERT_TRUE(packet.Parse(kPacketWithMid, sizeof(kPacketWithMid))); + + std::string mid; + EXPECT_TRUE(packet.GetExtension<RtpMid>(&mid)); + EXPECT_EQ(mid, kMid); +} + +struct UncopyableValue { + UncopyableValue() = default; + UncopyableValue(const UncopyableValue&) = delete; + UncopyableValue& operator=(const UncopyableValue&) = delete; +}; +struct UncopyableExtension { + static constexpr RTPExtensionType kId = kRtpExtensionDependencyDescriptor; + static constexpr absl::string_view Uri() { return "uri"; } + + static size_t ValueSize(const UncopyableValue& value) { return 1; } + static bool Write(rtc::ArrayView<uint8_t> data, + const UncopyableValue& value) { + return true; + } + static bool Parse(rtc::ArrayView<const uint8_t> data, + UncopyableValue* value) { + return true; + } +}; + +TEST(RtpPacketTest, SetUncopyableExtension) { + RtpPacket::ExtensionManager extensions; + extensions.Register<UncopyableExtension>(1); + RtpPacket rtp_packet(&extensions); + + UncopyableValue value; + EXPECT_TRUE(rtp_packet.SetExtension<UncopyableExtension>(value)); +} + +TEST(RtpPacketTest, GetUncopyableExtension) { + RtpPacket::ExtensionManager extensions; + extensions.Register<UncopyableExtension>(1); + RtpPacket rtp_packet(&extensions); + UncopyableValue value; + rtp_packet.SetExtension<UncopyableExtension>(value); + + UncopyableValue value2; + EXPECT_TRUE(rtp_packet.GetExtension<UncopyableExtension>(&value2)); +} + +TEST(RtpPacketTest, CreateAndParseTimingFrameExtension) { + // Create a packet with video frame timing extension populated. + RtpPacketToSend::ExtensionManager send_extensions; + send_extensions.Register<VideoTimingExtension>(kVideoTimingExtensionId); + RtpPacketToSend send_packet(&send_extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + VideoSendTiming timing; + timing.encode_start_delta_ms = 1; + timing.encode_finish_delta_ms = 2; + timing.packetization_finish_delta_ms = 3; + timing.pacer_exit_delta_ms = 4; + timing.flags = + VideoSendTiming::kTriggeredByTimer | VideoSendTiming::kTriggeredBySize; + + send_packet.SetExtension<VideoTimingExtension>(timing); + + // Serialize the packet and then parse it again. + RtpPacketReceived::ExtensionManager extensions; + extensions.Register<VideoTimingExtension>(kVideoTimingExtensionId); + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + VideoSendTiming receivied_timing; + EXPECT_TRUE( + receive_packet.GetExtension<VideoTimingExtension>(&receivied_timing)); + + // Only check first and last timestamp (covered by other tests) plus flags. + EXPECT_EQ(receivied_timing.encode_start_delta_ms, + timing.encode_start_delta_ms); + EXPECT_EQ(receivied_timing.pacer_exit_delta_ms, timing.pacer_exit_delta_ms); + EXPECT_EQ(receivied_timing.flags, timing.flags); +} + +TEST(RtpPacketTest, ParseLegacyTimingFrameExtension) { + // Parse the modified packet. + RtpPacketReceived::ExtensionManager extensions; + extensions.Register<VideoTimingExtension>(kVideoTimingExtensionId); + RtpPacketReceived packet(&extensions); + EXPECT_TRUE(packet.Parse(kPacketWithLegacyTimingExtension, + sizeof(kPacketWithLegacyTimingExtension))); + VideoSendTiming receivied_timing; + EXPECT_TRUE(packet.GetExtension<VideoTimingExtension>(&receivied_timing)); + + // Check first and last timestamp are still OK. Flags should now be 0. + EXPECT_EQ(receivied_timing.encode_start_delta_ms, 1); + EXPECT_EQ(receivied_timing.pacer_exit_delta_ms, 4); + EXPECT_EQ(receivied_timing.flags, 0); +} + +TEST(RtpPacketTest, CreateAndParseColorSpaceExtension) { + TestCreateAndParseColorSpaceExtension(/*with_hdr_metadata=*/true); +} + +TEST(RtpPacketTest, CreateAndParseColorSpaceExtensionWithoutHdrMetadata) { + TestCreateAndParseColorSpaceExtension(/*with_hdr_metadata=*/false); +} + +TEST(RtpPacketTest, CreateAndParseAbsoluteCaptureTime) { + // Create a packet with absolute capture time extension populated. + RtpPacketToSend::ExtensionManager extensions; + constexpr int kExtensionId = 1; + extensions.Register<AbsoluteCaptureTimeExtension>(kExtensionId); + RtpPacketToSend send_packet(&extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + constexpr AbsoluteCaptureTime kAbsoluteCaptureTime{ + /*absolute_capture_timestamp=*/9876543210123456789ULL, + /*estimated_capture_clock_offset=*/-1234567890987654321LL}; + send_packet.SetExtension<AbsoluteCaptureTimeExtension>(kAbsoluteCaptureTime); + + // Serialize the packet and then parse it again. + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + AbsoluteCaptureTime received_absolute_capture_time; + EXPECT_TRUE(receive_packet.GetExtension<AbsoluteCaptureTimeExtension>( + &received_absolute_capture_time)); + EXPECT_EQ(kAbsoluteCaptureTime.absolute_capture_timestamp, + received_absolute_capture_time.absolute_capture_timestamp); + EXPECT_EQ(kAbsoluteCaptureTime.estimated_capture_clock_offset, + received_absolute_capture_time.estimated_capture_clock_offset); +} + +TEST(RtpPacketTest, + CreateAndParseAbsoluteCaptureTimeWithoutEstimatedCaptureClockOffset) { + // Create a packet with absolute capture time extension populated. + RtpPacketToSend::ExtensionManager extensions; + constexpr int kExtensionId = 1; + extensions.Register<AbsoluteCaptureTimeExtension>(kExtensionId); + RtpPacketToSend send_packet(&extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + constexpr AbsoluteCaptureTime kAbsoluteCaptureTime{ + /*absolute_capture_timestamp=*/9876543210123456789ULL, + /*estimated_capture_clock_offset=*/absl::nullopt}; + send_packet.SetExtension<AbsoluteCaptureTimeExtension>(kAbsoluteCaptureTime); + + // Serialize the packet and then parse it again. + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + AbsoluteCaptureTime received_absolute_capture_time; + EXPECT_TRUE(receive_packet.GetExtension<AbsoluteCaptureTimeExtension>( + &received_absolute_capture_time)); + EXPECT_EQ(kAbsoluteCaptureTime.absolute_capture_timestamp, + received_absolute_capture_time.absolute_capture_timestamp); + EXPECT_EQ(kAbsoluteCaptureTime.estimated_capture_clock_offset, + received_absolute_capture_time.estimated_capture_clock_offset); +} + +TEST(RtpPacketTest, CreateAndParseTransportSequenceNumber) { + // Create a packet with transport sequence number extension populated. + RtpPacketToSend::ExtensionManager extensions; + constexpr int kExtensionId = 1; + extensions.Register<TransportSequenceNumber>(kExtensionId); + RtpPacketToSend send_packet(&extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + constexpr int kTransportSequenceNumber = 12345; + send_packet.SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + + // Serialize the packet and then parse it again. + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + uint16_t received_transport_sequeunce_number; + EXPECT_TRUE(receive_packet.GetExtension<TransportSequenceNumber>( + &received_transport_sequeunce_number)); + EXPECT_EQ(received_transport_sequeunce_number, kTransportSequenceNumber); +} + +TEST(RtpPacketTest, CreateAndParseTransportSequenceNumberV2) { + // Create a packet with transport sequence number V2 extension populated. + // No feedback request means that the extension will be two bytes unless it's + // pre-allocated. + RtpPacketToSend::ExtensionManager extensions; + constexpr int kExtensionId = 1; + extensions.Register<TransportSequenceNumberV2>(kExtensionId); + RtpPacketToSend send_packet(&extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + constexpr int kTransportSequenceNumber = 12345; + send_packet.SetExtension<TransportSequenceNumberV2>(kTransportSequenceNumber, + absl::nullopt); + EXPECT_EQ(send_packet.GetRawExtension<TransportSequenceNumberV2>().size(), + 2u); + + // Serialize the packet and then parse it again. + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + uint16_t received_transport_sequeunce_number; + absl::optional<FeedbackRequest> received_feedback_request; + EXPECT_TRUE(receive_packet.GetExtension<TransportSequenceNumberV2>( + &received_transport_sequeunce_number, &received_feedback_request)); + EXPECT_EQ(received_transport_sequeunce_number, kTransportSequenceNumber); + EXPECT_FALSE(received_feedback_request); +} + +TEST(RtpPacketTest, CreateAndParseTransportSequenceNumberV2Preallocated) { + // Create a packet with transport sequence number V2 extension populated. + // No feedback request means that the extension could be two bytes, but since + // it's pre-allocated we don't know if it is with or without feedback request + // therefore the size is four bytes. + RtpPacketToSend::ExtensionManager extensions; + constexpr int kExtensionId = 1; + extensions.Register<TransportSequenceNumberV2>(kExtensionId); + RtpPacketToSend send_packet(&extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + constexpr int kTransportSequenceNumber = 12345; + constexpr absl::optional<FeedbackRequest> kNoFeedbackRequest = + FeedbackRequest{/*include_timestamps=*/false, /*sequence_count=*/0}; + send_packet.ReserveExtension<TransportSequenceNumberV2>(); + send_packet.SetExtension<TransportSequenceNumberV2>(kTransportSequenceNumber, + kNoFeedbackRequest); + EXPECT_EQ(send_packet.GetRawExtension<TransportSequenceNumberV2>().size(), + 4u); + + // Serialize the packet and then parse it again. + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + uint16_t received_transport_sequeunce_number; + absl::optional<FeedbackRequest> received_feedback_request; + EXPECT_TRUE(receive_packet.GetExtension<TransportSequenceNumberV2>( + &received_transport_sequeunce_number, &received_feedback_request)); + EXPECT_EQ(received_transport_sequeunce_number, kTransportSequenceNumber); + EXPECT_FALSE(received_feedback_request); +} + +TEST(RtpPacketTest, + CreateAndParseTransportSequenceNumberV2WithFeedbackRequest) { + // Create a packet with TransportSequenceNumberV2 extension populated. + RtpPacketToSend::ExtensionManager extensions; + constexpr int kExtensionId = 1; + extensions.Register<TransportSequenceNumberV2>(kExtensionId); + RtpPacketToSend send_packet(&extensions); + send_packet.SetPayloadType(kPayloadType); + send_packet.SetSequenceNumber(kSeqNum); + send_packet.SetTimestamp(kTimestamp); + send_packet.SetSsrc(kSsrc); + + constexpr int kTransportSequenceNumber = 12345; + constexpr absl::optional<FeedbackRequest> kFeedbackRequest = + FeedbackRequest{/*include_timestamps=*/true, /*sequence_count=*/3}; + send_packet.SetExtension<TransportSequenceNumberV2>(kTransportSequenceNumber, + kFeedbackRequest); + + // Serialize the packet and then parse it again. + RtpPacketReceived receive_packet(&extensions); + EXPECT_TRUE(receive_packet.Parse(send_packet.Buffer())); + + // Parse transport sequence number and feedback request. + uint16_t received_transport_sequeunce_number; + absl::optional<FeedbackRequest> received_feedback_request; + EXPECT_TRUE(receive_packet.GetExtension<TransportSequenceNumberV2>( + &received_transport_sequeunce_number, &received_feedback_request)); + EXPECT_EQ(received_transport_sequeunce_number, kTransportSequenceNumber); + ASSERT_TRUE(received_feedback_request); + EXPECT_EQ(received_feedback_request->include_timestamps, + kFeedbackRequest->include_timestamps); + EXPECT_EQ(received_feedback_request->sequence_count, + kFeedbackRequest->sequence_count); +} + +TEST(RtpPacketTest, ReservedExtensionsCountedAsSetExtension) { + // Register two extensions. + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + + RtpPacketReceived packet(&extensions); + + // Reserve slot for only one of them. + EXPECT_TRUE(packet.ReserveExtension<TransmissionOffset>()); + // Non-registered extension cannot be reserved. + EXPECT_FALSE(packet.ReserveExtension<VideoContentTypeExtension>()); + + // Only the extension that is both registered and reserved matches + // IsExtensionReserved(). + EXPECT_FALSE(packet.HasExtension<VideoContentTypeExtension>()); + EXPECT_FALSE(packet.HasExtension<AudioLevel>()); + EXPECT_TRUE(packet.HasExtension<TransmissionOffset>()); +} + +// Tests that RtpPacket::RemoveExtension can successfully remove extensions. +TEST(RtpPacketTest, RemoveMultipleExtensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel); + + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); + + // Remove one of two extensions. + EXPECT_TRUE(packet.RemoveExtension(kRtpExtensionAudioLevel)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); + + // Remove remaining extension. + EXPECT_TRUE(packet.RemoveExtension(kRtpExtensionTransmissionTimeOffset)); + + EXPECT_THAT(kMinimumPacket, ElementsAreArray(packet.data(), packet.size())); +} + +// Tests that RtpPacket::RemoveExtension can successfully remove extension when +// other extensions are present but not registered. +TEST(RtpPacketTest, RemoveExtensionPreservesOtherUnregisteredExtensions) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + packet.SetExtension<AudioLevel>(kVoiceActive, kAudioLevel); + + EXPECT_THAT(kPacketWithTOAndAL, + ElementsAreArray(packet.data(), packet.size())); + + // "Unregister" kRtpExtensionTransmissionTimeOffset. + RtpPacketToSend::ExtensionManager extensions1; + extensions1.Register<AudioLevel>(kAudioLevelExtensionId); + packet.IdentifyExtensions(extensions1); + + // Make sure we can not delete extension which is set but not registered. + EXPECT_FALSE(packet.RemoveExtension(kRtpExtensionTransmissionTimeOffset)); + + // Remove registered extension. + EXPECT_TRUE(packet.RemoveExtension(kRtpExtensionAudioLevel)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); +} + +// Tests that RtpPacket::RemoveExtension fails if extension is not present or +// not registered and does not modify packet. +TEST(RtpPacketTest, RemoveExtensionFailure) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<TransmissionOffset>(kTransmissionOffsetExtensionId); + extensions.Register<AudioLevel>(kAudioLevelExtensionId); + RtpPacketToSend packet(&extensions); + packet.SetPayloadType(kPayloadType); + packet.SetSequenceNumber(kSeqNum); + packet.SetTimestamp(kTimestamp); + packet.SetSsrc(kSsrc); + packet.SetExtension<TransmissionOffset>(kTimeOffset); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); + + // Try to remove extension, which was registered, but not set. + EXPECT_FALSE(packet.RemoveExtension(kRtpExtensionAudioLevel)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); + + // Try to remove extension, which was not registered. + EXPECT_FALSE(packet.RemoveExtension(kRtpExtensionPlayoutDelay)); + + EXPECT_THAT(kPacketWithTO, ElementsAreArray(packet.data(), packet.size())); +} + +TEST(RtpPacketTest, SetExtensionWithArray) { + RtpPacketToSend::ExtensionManager extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + RtpPacketToSend packet(&extensions); + const uint8_t extension_data[] = {1, 2, 3, 4, 5}; + packet.SetRawExtension<RtpDependencyDescriptorExtension>(extension_data); + EXPECT_THAT(packet.GetRawExtension<RtpDependencyDescriptorExtension>(), + ElementsAreArray(extension_data)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.cc new file mode 100644 index 0000000000..95dbaf364c --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.cc @@ -0,0 +1,402 @@ +/* + * 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/rtp_packetizer_av1.h" + +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> + +#include "api/array_view.h" +#include "api/video/video_frame_type.h" +#include "modules/rtp_rtcp/source/leb128.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/byte_buffer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { +constexpr int kAggregationHeaderSize = 1; +// when there are 3 or less OBU (fragments) in a packet, size of the last one +// can be omited. +constexpr int kMaxNumObusToOmitSize = 3; +constexpr uint8_t kObuSizePresentBit = 0b0'0000'010; +constexpr int kObuTypeSequenceHeader = 1; +constexpr int kObuTypeTemporalDelimiter = 2; +constexpr int kObuTypeTileList = 8; +constexpr int kObuTypePadding = 15; + +bool ObuHasExtension(uint8_t obu_header) { + return obu_header & 0b0'0000'100; +} + +bool ObuHasSize(uint8_t obu_header) { + return obu_header & kObuSizePresentBit; +} + +int ObuType(uint8_t obu_header) { + return (obu_header & 0b0'1111'000) >> 3; +} + +// Given `remaining_bytes` free bytes left in a packet, returns max size of an +// OBU fragment that can fit into the packet. +// i.e. MaxFragmentSize + Leb128Size(MaxFragmentSize) <= remaining_bytes. +int MaxFragmentSize(int remaining_bytes) { + if (remaining_bytes <= 1) { + return 0; + } + for (int i = 1;; ++i) { + if (remaining_bytes < (1 << 7 * i) + i) { + return remaining_bytes - i; + } + } +} + +} // namespace + +RtpPacketizerAv1::RtpPacketizerAv1(rtc::ArrayView<const uint8_t> payload, + RtpPacketizer::PayloadSizeLimits limits, + VideoFrameType frame_type, + bool is_last_frame_in_picture) + : frame_type_(frame_type), + obus_(ParseObus(payload)), + packets_(Packetize(obus_, limits)), + is_last_frame_in_picture_(is_last_frame_in_picture) {} + +std::vector<RtpPacketizerAv1::Obu> RtpPacketizerAv1::ParseObus( + rtc::ArrayView<const uint8_t> payload) { + std::vector<Obu> result; + rtc::ByteBufferReader payload_reader( + reinterpret_cast<const char*>(payload.data()), payload.size()); + while (payload_reader.Length() > 0) { + Obu obu; + payload_reader.ReadUInt8(&obu.header); + obu.size = 1; + if (ObuHasExtension(obu.header)) { + if (payload_reader.Length() == 0) { + RTC_DLOG(LS_ERROR) << "Malformed AV1 input: expected extension_header, " + "no more bytes in the buffer. Offset: " + << (payload.size() - payload_reader.Length()); + return {}; + } + payload_reader.ReadUInt8(&obu.extension_header); + ++obu.size; + } + if (!ObuHasSize(obu.header)) { + obu.payload = rtc::MakeArrayView( + reinterpret_cast<const uint8_t*>(payload_reader.Data()), + payload_reader.Length()); + payload_reader.Consume(payload_reader.Length()); + } else { + uint64_t size = 0; + if (!payload_reader.ReadUVarint(&size) || + size > payload_reader.Length()) { + RTC_DLOG(LS_ERROR) << "Malformed AV1 input: declared size " << size + << " is larger than remaining buffer size " + << payload_reader.Length(); + return {}; + } + obu.payload = rtc::MakeArrayView( + reinterpret_cast<const uint8_t*>(payload_reader.Data()), size); + payload_reader.Consume(size); + } + obu.size += obu.payload.size(); + // Skip obus that shouldn't be transfered over rtp. + int obu_type = ObuType(obu.header); + if (obu_type != kObuTypeTemporalDelimiter && // + obu_type != kObuTypeTileList && // + obu_type != kObuTypePadding) { + result.push_back(obu); + } + } + return result; +} + +int RtpPacketizerAv1::AdditionalBytesForPreviousObuElement( + const Packet& packet) { + if (packet.packet_size == 0) { + // Packet is still empty => no last OBU element, no need to reserve space + // for it. + return 0; + } + if (packet.num_obu_elements > kMaxNumObusToOmitSize) { + // There is so many obu elements in the packet, all of them must be + // prepended with the length field. That imply space for the length of the + // last obu element is already reserved. + return 0; + } + // No space was reserved for length field of the last OBU element, but that + // element becoming non-last, so it now requires explicit length field. + // Calculate how many bytes are needed to store the length in leb128 format. + return Leb128Size(packet.last_obu_size); +} + +std::vector<RtpPacketizerAv1::Packet> RtpPacketizerAv1::Packetize( + rtc::ArrayView<const Obu> obus, + PayloadSizeLimits limits) { + std::vector<Packet> packets; + if (obus.empty()) { + return packets; + } + // Ignore certian edge cases where packets should be very small. They are + // inpractical but adds complexity to handle. + if (limits.max_payload_len - limits.last_packet_reduction_len < 3 || + limits.max_payload_len - limits.first_packet_reduction_len < 3) { + RTC_DLOG(LS_ERROR) << "Failed to packetize AV1 frame: requested packet " + "size is unreasonable small."; + return packets; + } + // Aggregation header is present in all packets. + limits.max_payload_len -= kAggregationHeaderSize; + + // Assemble packets. Push to current packet as much as it can hold before + // considering next one. That would normally cause uneven distribution across + // packets, specifically last one would be generally smaller. + packets.emplace_back(/*first_obu_index=*/0); + int packet_remaining_bytes = + limits.max_payload_len - limits.first_packet_reduction_len; + for (size_t obu_index = 0; obu_index < obus.size(); ++obu_index) { + const bool is_last_obu = obu_index == obus.size() - 1; + const Obu& obu = obus[obu_index]; + + // Putting `obu` into the last packet would make last obu element stored in + // that packet not last. All not last OBU elements must be prepend with the + // element length. AdditionalBytesForPreviousObuElement calculates how many + // bytes are needed to store that length. + int previous_obu_extra_size = + AdditionalBytesForPreviousObuElement(packets.back()); + int min_required_size = + packets.back().num_obu_elements >= kMaxNumObusToOmitSize ? 2 : 1; + if (packet_remaining_bytes < previous_obu_extra_size + min_required_size) { + // Start a new packet. + packets.emplace_back(/*first_obu_index=*/obu_index); + packet_remaining_bytes = limits.max_payload_len; + previous_obu_extra_size = 0; + } + Packet& packet = packets.back(); + // Start inserting current obu into the packet. + packet.packet_size += previous_obu_extra_size; + packet_remaining_bytes -= previous_obu_extra_size; + packet.num_obu_elements++; + + bool must_write_obu_element_size = + packet.num_obu_elements > kMaxNumObusToOmitSize; + // Can fit all of the obu into the packet? + int required_bytes = obu.size; + if (must_write_obu_element_size) { + required_bytes += Leb128Size(obu.size); + } + int available_bytes = packet_remaining_bytes; + if (is_last_obu) { + // If this packet would be the last packet, available size is smaller. + if (packets.size() == 1) { + available_bytes += limits.first_packet_reduction_len; + available_bytes -= limits.single_packet_reduction_len; + } else { + available_bytes -= limits.last_packet_reduction_len; + } + } + if (required_bytes <= available_bytes) { + // Insert the obu into the packet unfragmented. + packet.last_obu_size = obu.size; + packet.packet_size += required_bytes; + packet_remaining_bytes -= required_bytes; + continue; + } + + // Fragment the obu. + int max_first_fragment_size = must_write_obu_element_size + ? MaxFragmentSize(packet_remaining_bytes) + : packet_remaining_bytes; + // Because available_bytes might be different than + // packet_remaining_bytes it might happen that max_first_fragment_size >= + // obu.size. Also, since checks above verified `obu` should not be put + // completely into the `packet`, leave at least 1 byte for later packet. + int first_fragment_size = std::min(obu.size - 1, max_first_fragment_size); + if (first_fragment_size == 0) { + // Rather than writing 0-size element at the tail of the packet, + // 'uninsert' the `obu` from the `packet`. + packet.num_obu_elements--; + packet.packet_size -= previous_obu_extra_size; + } else { + packet.packet_size += first_fragment_size; + if (must_write_obu_element_size) { + packet.packet_size += Leb128Size(first_fragment_size); + } + packet.last_obu_size = first_fragment_size; + } + + // Add middle fragments that occupy all of the packet. + // These are easy because + // - one obu per packet imply no need to store the size of the obu. + // - this packets are nor the first nor the last packets of the frame, so + // packet capacity is always limits.max_payload_len. + int obu_offset; + for (obu_offset = first_fragment_size; + obu_offset + limits.max_payload_len < obu.size; + obu_offset += limits.max_payload_len) { + packets.emplace_back(/*first_obu_index=*/obu_index); + Packet& packet = packets.back(); + packet.num_obu_elements = 1; + packet.first_obu_offset = obu_offset; + int middle_fragment_size = limits.max_payload_len; + packet.last_obu_size = middle_fragment_size; + packet.packet_size = middle_fragment_size; + } + + // Add the last fragment of the obu. + int last_fragment_size = obu.size - obu_offset; + // Check for corner case where last fragment of the last obu is too large + // to fit into last packet, but may fully fit into semi-last packet. + if (is_last_obu && + last_fragment_size > + limits.max_payload_len - limits.last_packet_reduction_len) { + // Split last fragments into two. + RTC_DCHECK_GE(last_fragment_size, 2); + // Try to even packet sizes rather than payload sizes across the last + // two packets. + int semi_last_fragment_size = + (last_fragment_size + limits.last_packet_reduction_len) / 2; + // But leave at least one payload byte for the last packet to avoid + // weird scenarios where size of the fragment is zero and rtp payload has + // nothing except for an aggregation header. + if (semi_last_fragment_size >= last_fragment_size) { + semi_last_fragment_size = last_fragment_size - 1; + } + last_fragment_size -= semi_last_fragment_size; + + packets.emplace_back(/*first_obu_index=*/obu_index); + Packet& packet = packets.back(); + packet.num_obu_elements = 1; + packet.first_obu_offset = obu_offset; + packet.last_obu_size = semi_last_fragment_size; + packet.packet_size = semi_last_fragment_size; + obu_offset += semi_last_fragment_size; + } + packets.emplace_back(/*first_obu_index=*/obu_index); + Packet& last_packet = packets.back(); + last_packet.num_obu_elements = 1; + last_packet.first_obu_offset = obu_offset; + last_packet.last_obu_size = last_fragment_size; + last_packet.packet_size = last_fragment_size; + packet_remaining_bytes = limits.max_payload_len - last_fragment_size; + } + return packets; +} + +uint8_t RtpPacketizerAv1::AggregationHeader() const { + const Packet& packet = packets_[packet_index_]; + uint8_t aggregation_header = 0; + + // Set Z flag: first obu element is continuation of the previous OBU. + bool first_obu_element_is_fragment = packet.first_obu_offset > 0; + if (first_obu_element_is_fragment) + aggregation_header |= (1 << 7); + + // Set Y flag: last obu element will be continuated in the next packet. + int last_obu_offset = + packet.num_obu_elements == 1 ? packet.first_obu_offset : 0; + bool last_obu_is_fragment = + last_obu_offset + packet.last_obu_size < + obus_[packet.first_obu + packet.num_obu_elements - 1].size; + if (last_obu_is_fragment) + aggregation_header |= (1 << 6); + + // Set W field: number of obu elements in the packet (when not too large). + if (packet.num_obu_elements <= kMaxNumObusToOmitSize) + aggregation_header |= packet.num_obu_elements << 4; + + // Set N flag: beginning of a new coded video sequence. + // Encoder may produce key frame without a sequence header, thus double check + // incoming frame includes the sequence header. Since Temporal delimiter is + // already filtered out, sequence header should be the first obu when present. + if (frame_type_ == VideoFrameType::kVideoFrameKey && packet_index_ == 0 && + ObuType(obus_.front().header) == kObuTypeSequenceHeader) { + aggregation_header |= (1 << 3); + } + return aggregation_header; +} + +bool RtpPacketizerAv1::NextPacket(RtpPacketToSend* packet) { + if (packet_index_ >= packets_.size()) { + return false; + } + const Packet& next_packet = packets_[packet_index_]; + + RTC_DCHECK_GT(next_packet.num_obu_elements, 0); + RTC_DCHECK_LT(next_packet.first_obu_offset, + obus_[next_packet.first_obu].size); + RTC_DCHECK_LE( + next_packet.last_obu_size, + obus_[next_packet.first_obu + next_packet.num_obu_elements - 1].size); + + uint8_t* const rtp_payload = + packet->AllocatePayload(kAggregationHeaderSize + next_packet.packet_size); + uint8_t* write_at = rtp_payload; + + *write_at++ = AggregationHeader(); + + int obu_offset = next_packet.first_obu_offset; + // Store all OBU elements except the last one. + for (int i = 0; i < next_packet.num_obu_elements - 1; ++i) { + const Obu& obu = obus_[next_packet.first_obu + i]; + size_t fragment_size = obu.size - obu_offset; + write_at += WriteLeb128(fragment_size, write_at); + if (obu_offset == 0) { + *write_at++ = obu.header & ~kObuSizePresentBit; + } + if (obu_offset <= 1 && ObuHasExtension(obu.header)) { + *write_at++ = obu.extension_header; + } + int payload_offset = + std::max(0, obu_offset - (ObuHasExtension(obu.header) ? 2 : 1)); + size_t payload_size = obu.payload.size() - payload_offset; + if (!obu.payload.empty() && payload_size > 0) { + memcpy(write_at, obu.payload.data() + payload_offset, payload_size); + } + write_at += payload_size; + // All obus are stored from the beginning, except, may be, the first one. + obu_offset = 0; + } + // Store the last OBU element. + const Obu& last_obu = + obus_[next_packet.first_obu + next_packet.num_obu_elements - 1]; + int fragment_size = next_packet.last_obu_size; + RTC_DCHECK_GT(fragment_size, 0); + if (next_packet.num_obu_elements > kMaxNumObusToOmitSize) { + write_at += WriteLeb128(fragment_size, write_at); + } + if (obu_offset == 0 && fragment_size > 0) { + *write_at++ = last_obu.header & ~kObuSizePresentBit; + --fragment_size; + } + if (obu_offset <= 1 && ObuHasExtension(last_obu.header) && + fragment_size > 0) { + *write_at++ = last_obu.extension_header; + --fragment_size; + } + RTC_DCHECK_EQ(write_at - rtp_payload + fragment_size, + kAggregationHeaderSize + next_packet.packet_size); + int payload_offset = + std::max(0, obu_offset - (ObuHasExtension(last_obu.header) ? 2 : 1)); + memcpy(write_at, last_obu.payload.data() + payload_offset, fragment_size); + write_at += fragment_size; + + RTC_DCHECK_EQ(write_at - rtp_payload, + kAggregationHeaderSize + next_packet.packet_size); + + ++packet_index_; + bool is_last_packet_in_frame = packet_index_ == packets_.size(); + packet->SetMarker(is_last_packet_in_frame && is_last_frame_in_picture_); + return true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.h new file mode 100644 index 0000000000..520e746eac --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.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 MODULES_RTP_RTCP_SOURCE_RTP_PACKETIZER_AV1_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_PACKETIZER_AV1_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "api/array_view.h" +#include "api/video/video_frame_type.h" +#include "modules/rtp_rtcp/source/rtp_format.h" + +namespace webrtc { + +class RtpPacketizerAv1 : public RtpPacketizer { + public: + RtpPacketizerAv1(rtc::ArrayView<const uint8_t> payload, + PayloadSizeLimits limits, + VideoFrameType frame_type, + bool is_last_frame_in_picture); + ~RtpPacketizerAv1() override = default; + + size_t NumPackets() const override { return packets_.size() - packet_index_; } + bool NextPacket(RtpPacketToSend* packet) override; + + private: + struct Obu { + uint8_t header; + uint8_t extension_header; // undefined if (header & kXbit) == 0 + rtc::ArrayView<const uint8_t> payload; + int size; // size of the header and payload combined. + }; + struct Packet { + explicit Packet(int first_obu_index) : first_obu(first_obu_index) {} + // Indexes into obus_ vector of the first and last obus that should put into + // the packet. + int first_obu; + int num_obu_elements = 0; + int first_obu_offset = 0; + int last_obu_size; + // Total size consumed by the packet. + int packet_size = 0; + }; + + // Parses the payload into serie of OBUs. + static std::vector<Obu> ParseObus(rtc::ArrayView<const uint8_t> payload); + // Returns the number of additional bytes needed to store the previous OBU + // element if an additonal OBU element is added to the packet. + static int AdditionalBytesForPreviousObuElement(const Packet& packet); + static std::vector<Packet> Packetize(rtc::ArrayView<const Obu> obus, + PayloadSizeLimits limits); + uint8_t AggregationHeader() const; + + const VideoFrameType frame_type_; + const std::vector<Obu> obus_; + const std::vector<Packet> packets_; + const bool is_last_frame_in_picture_; + size_t packet_index_ = 0; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_PACKETIZER_AV1_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.cc new file mode 100644 index 0000000000..3d62bcef44 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.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 "modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h" + +#include <stdint.h> + +#include <initializer_list> +#include <vector> + +namespace webrtc { + +Av1Obu::Av1Obu(uint8_t obu_type) : header_(obu_type | kAv1ObuSizePresentBit) {} + +Av1Obu& Av1Obu::WithExtension(uint8_t extension) { + extension_ = extension; + header_ |= kAv1ObuExtensionPresentBit; + return *this; +} +Av1Obu& Av1Obu::WithoutSize() { + header_ &= ~kAv1ObuSizePresentBit; + return *this; +} +Av1Obu& Av1Obu::WithPayload(std::vector<uint8_t> payload) { + payload_ = std::move(payload); + return *this; +} + +std::vector<uint8_t> BuildAv1Frame(std::initializer_list<Av1Obu> obus) { + std::vector<uint8_t> raw; + for (const Av1Obu& obu : obus) { + raw.push_back(obu.header_); + if (obu.header_ & kAv1ObuExtensionPresentBit) { + raw.push_back(obu.extension_); + } + if (obu.header_ & kAv1ObuSizePresentBit) { + // write size in leb128 format. + size_t payload_size = obu.payload_.size(); + while (payload_size >= 0x80) { + raw.push_back(0x80 | (payload_size & 0x7F)); + payload_size >>= 7; + } + raw.push_back(payload_size); + } + raw.insert(raw.end(), obu.payload_.begin(), obu.payload_.end()); + } + return raw; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h new file mode 100644 index 0000000000..04a902fe56 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h @@ -0,0 +1,51 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_PACKETIZER_AV1_TEST_HELPER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_PACKETIZER_AV1_TEST_HELPER_H_ + +#include <stdint.h> + +#include <initializer_list> +#include <utility> +#include <vector> + +namespace webrtc { +// All obu types offset by 3 to take correct position in the obu_header. +constexpr uint8_t kAv1ObuTypeSequenceHeader = 1 << 3; +constexpr uint8_t kAv1ObuTypeTemporalDelimiter = 2 << 3; +constexpr uint8_t kAv1ObuTypeFrameHeader = 3 << 3; +constexpr uint8_t kAv1ObuTypeTileGroup = 4 << 3; +constexpr uint8_t kAv1ObuTypeMetadata = 5 << 3; +constexpr uint8_t kAv1ObuTypeFrame = 6 << 3; +constexpr uint8_t kAv1ObuTypeTileList = 8 << 3; +constexpr uint8_t kAv1ObuExtensionPresentBit = 0b0'0000'100; +constexpr uint8_t kAv1ObuSizePresentBit = 0b0'0000'010; +constexpr uint8_t kAv1ObuExtensionS1T1 = 0b001'01'000; + +class Av1Obu { + public: + explicit Av1Obu(uint8_t obu_type); + + Av1Obu& WithExtension(uint8_t extension); + Av1Obu& WithoutSize(); + Av1Obu& WithPayload(std::vector<uint8_t> payload); + + private: + friend std::vector<uint8_t> BuildAv1Frame(std::initializer_list<Av1Obu> obus); + uint8_t header_; + uint8_t extension_ = 0; + std::vector<uint8_t> payload_; +}; + +std::vector<uint8_t> BuildAv1Frame(std::initializer_list<Av1Obu> obus); + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_PACKETIZER_AV1_TEST_HELPER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc new file mode 100644 index 0000000000..2151a59295 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc @@ -0,0 +1,342 @@ +/* + * 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/rtp_packetizer_av1.h" + +#include <stddef.h> +#include <stdint.h> + +#include <initializer_list> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/video/encoded_image.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Le; +using ::testing::SizeIs; + +constexpr uint8_t kNewCodedVideoSequenceBit = 0b00'00'1000; + +// Wrapper around rtp_packet to make it look like container of payload bytes. +struct RtpPayload { + using value_type = rtc::ArrayView<const uint8_t>::value_type; + using const_iterator = rtc::ArrayView<const uint8_t>::const_iterator; + + RtpPayload() : rtp_packet(/*extensions=*/nullptr) {} + RtpPayload& operator=(RtpPayload&&) = default; + RtpPayload(RtpPayload&&) = default; + + const_iterator begin() const { return rtp_packet.payload().begin(); } + const_iterator end() const { return rtp_packet.payload().end(); } + const uint8_t* data() const { return rtp_packet.payload().data(); } + size_t size() const { return rtp_packet.payload().size(); } + + uint8_t aggregation_header() const { return rtp_packet.payload()[0]; } + + RtpPacketToSend rtp_packet; +}; + +// Wrapper around frame pointer to make it look like container of bytes with +// nullptr frame look like empty container. +class Av1Frame { + public: + using value_type = uint8_t; + using const_iterator = const uint8_t*; + + explicit Av1Frame(rtc::scoped_refptr<EncodedImageBuffer> frame) + : frame_(std::move(frame)) {} + + const_iterator begin() const { return frame_ ? frame_->data() : nullptr; } + const_iterator end() const { + return frame_ ? (frame_->data() + frame_->size()) : nullptr; + } + + private: + rtc::scoped_refptr<EncodedImageBuffer> frame_; +}; + +std::vector<RtpPayload> Packetize( + rtc::ArrayView<const uint8_t> payload, + RtpPacketizer::PayloadSizeLimits limits, + VideoFrameType frame_type = VideoFrameType::kVideoFrameDelta, + bool is_last_frame_in_picture = true) { + // Run code under test. + RtpPacketizerAv1 packetizer(payload, limits, frame_type, + is_last_frame_in_picture); + // Convert result into structure that is easier to run expectation against. + std::vector<RtpPayload> result(packetizer.NumPackets()); + for (RtpPayload& rtp_payload : result) { + EXPECT_TRUE(packetizer.NextPacket(&rtp_payload.rtp_packet)); + } + return result; +} + +Av1Frame ReassembleFrame(rtc::ArrayView<const RtpPayload> rtp_payloads) { + std::vector<rtc::ArrayView<const uint8_t>> payloads(rtp_payloads.size()); + for (size_t i = 0; i < rtp_payloads.size(); ++i) { + payloads[i] = rtp_payloads[i]; + } + return Av1Frame(VideoRtpDepacketizerAv1().AssembleFrame(payloads)); +} + +TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) { + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithoutSize() + .WithPayload({1, 2, 3, 4, 5, 6, 7})}); + EXPECT_THAT(Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame, 1, 2, 3, 4, 5, 6, 7))); +} + +TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeWithExtension) { + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithoutSize() + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({2, 3, 4, 5, 6, 7})}); + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit, + kAv1ObuExtensionS1T1, 2, 3, 4, 5, 6, 7))); +} + +TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithoutExtension) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload({11, 12, 13, 14, 15, 16, 17})}); + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame, 11, 12, 13, 14, 15, 16, 17))); +} + +TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithExtension) { + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({1, 2, 3, 4, 5, 6, 7})}); + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit, + kAv1ObuExtensionS1T1, 1, 2, 3, 4, 5, 6, 7))); +} + +TEST(RtpPacketizerAv1Test, OmitsSizeForLastObuWhenThreeObusFitsIntoThePacket) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6}), + Av1Obu(kAv1ObuTypeMetadata).WithPayload({11, 12, 13, 14}), + Av1Obu(kAv1ObuTypeFrame).WithPayload({21, 22, 23, 24, 25, 26})}); + EXPECT_THAT(Packetize(kFrame, {}), + ElementsAre(ElementsAre( + 0b00'11'0000, // aggregation header + 7, kAv1ObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, // + 5, kAv1ObuTypeMetadata, 11, 12, 13, 14, // + kAv1ObuTypeFrame, 21, 22, 23, 24, 25, 26))); +} + +TEST(RtpPacketizerAv1Test, UseSizeForAllObusWhenFourObusFitsIntoThePacket) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6}), + Av1Obu(kAv1ObuTypeMetadata).WithPayload({11, 12, 13, 14}), + Av1Obu(kAv1ObuTypeFrameHeader).WithPayload({21, 22, 23}), + Av1Obu(kAv1ObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})}); + EXPECT_THAT(Packetize(kFrame, {}), + ElementsAre(ElementsAre( + 0b00'00'0000, // aggregation header + 7, kAv1ObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, // + 5, kAv1ObuTypeMetadata, 11, 12, 13, 14, // + 4, kAv1ObuTypeFrameHeader, 21, 22, 23, // + 7, kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36))); +} + +TEST(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeTemporalDelimiter), Av1Obu(kAv1ObuTypeMetadata), + Av1Obu(kAv1ObuTypeTileList).WithPayload({1, 2, 3, 4, 5, 6}), + Av1Obu(kAv1ObuTypeFrameHeader).WithPayload({21, 22, 23}), + Av1Obu(kAv1ObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})}); + + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'11'0000, // aggregation header + 1, + kAv1ObuTypeMetadata, // + 4, kAv1ObuTypeFrameHeader, 21, 22, + 23, // + kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36))); +} + +TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPacketForceSplitObuHeader) { + // Craft expected payloads so that there is only one way to split original + // frame into two packets. + const uint8_t kExpectPayload1[6] = { + 0b01'10'0000, // aggregation_header + 3, + kAv1ObuTypeFrameHeader | kAv1ObuExtensionPresentBit, + kAv1ObuExtensionS1T1, + 21, // + kAv1ObuTypeTileGroup | kAv1ObuExtensionPresentBit}; + const uint8_t kExpectPayload2[6] = {0b10'01'0000, // aggregation_header + kAv1ObuExtensionS1T1, 11, 12, 13, 14}; + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrameHeader) + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({21}), + Av1Obu(kAv1ObuTypeTileGroup) + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({11, 12, 13, 14})}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(ElementsAreArray(kExpectPayload1), + ElementsAreArray(kExpectPayload2))); +} + +TEST(RtpPacketizerAv1Test, + SetsNbitAtTheFirstPacketOfAKeyFrameWithSequenceHeader) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6, 7})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto packets = Packetize(kFrame, limits, VideoFrameType::kVideoFrameKey); + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_TRUE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); + EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); +} + +TEST(RtpPacketizerAv1Test, + DoesntSetNbitAtThePacketsOfAKeyFrameWithoutSequenceHeader) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload({1, 2, 3, 4, 5, 6, 7})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto packets = Packetize(kFrame, limits, VideoFrameType::kVideoFrameKey); + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); + EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); +} + +TEST(RtpPacketizerAv1Test, DoesntSetNbitAtThePacketsOfADeltaFrame) { + // Even when that delta frame starts with a (redundant) sequence header. + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6, 7})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto packets = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta); + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); + EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); +} + +// There are multiple valid reasonable ways to split payload into multiple +// packets, do not validate current choice, instead use RtpDepacketizer +// to validate frame is reconstracted to the same one. Note: since +// RtpDepacketizer always inserts obu_size fields in the output, use frame where +// each obu has obu_size fields for more streight forward validation. +TEST(RtpPacketizerAv1Test, SplitSingleObuIntoTwoPackets) { + auto kFrame = + BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithPayload({11, 12, 13, 14, 15, 16, 17, 18, 19})}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(8u)), SizeIs(Le(8u)))); + + // Use RtpDepacketizer to validate the split. + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +TEST(RtpPacketizerAv1Test, SplitSingleObuIntoManyPackets) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(1200, 27))}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, SizeIs(13u)); + EXPECT_THAT(payloads, Each(SizeIs(Le(100u)))); + + // Use RtpDepacketizer to validate the split. + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +TEST(RtpPacketizerAv1Test, SetMarkerBitForLastPacketInEndOfPictureFrame) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(200, 27))}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + auto payloads = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta, + /*is_last_frame_in_picture=*/true); + ASSERT_THAT(payloads, SizeIs(3u)); + EXPECT_FALSE(payloads[0].rtp_packet.Marker()); + EXPECT_FALSE(payloads[1].rtp_packet.Marker()); + EXPECT_TRUE(payloads[2].rtp_packet.Marker()); +} + +TEST(RtpPacketizerAv1Test, DoesntSetMarkerBitForPacketsNotInEndOfPictureFrame) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(200, 27))}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + auto payloads = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta, + /*is_last_frame_in_picture=*/false); + ASSERT_THAT(payloads, SizeIs(3u)); + EXPECT_FALSE(payloads[0].rtp_packet.Marker()); + EXPECT_FALSE(payloads[1].rtp_packet.Marker()); + EXPECT_FALSE(payloads[2].rtp_packet.Marker()); +} + +TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPackets) { + // 2nd OBU is too large to fit into one packet, so its head would be in the + // same packet as the 1st OBU. + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({11, 12}), + Av1Obu(kAv1ObuTypeFrame).WithPayload({1, 2, 3, 4, 5, 6, 7, 8, 9})}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(8u)), SizeIs(Le(8u)))); + + // Use RtpDepacketizer to validate the split. + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +TEST(RtpPacketizerAv1Test, + SplitSingleObuIntoTwoPacketsBecauseOfSinglePacketLimit) { + auto kFrame = + BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithPayload({11, 12, 13, 14, 15, 16, 17, 18, 19})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 10; + limits.single_packet_reduction_len = 8; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(10u)), SizeIs(Le(10u)))); + + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h new file mode 100644 index 0000000000..9ac7696ce9 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h @@ -0,0 +1,27 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_RTCP_CONFIG_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_RTCP_CONFIG_H_ + +#include "api/units/time_delta.h" + +// Configuration file for RTP utilities (RTPSender, RTPReceiver ...) +namespace webrtc { +constexpr int kDefaultMaxReorderingThreshold = 50; // In sequence numbers. +constexpr int kRtcpMaxNackFields = 253; + +constexpr TimeDelta RTCP_INTERVAL_RAPID_SYNC_MS = + TimeDelta::Millis(100); // RFX 6051 +constexpr TimeDelta RTCP_SEND_BEFORE_KEY_FRAME = TimeDelta::Millis(100); +constexpr int RTCP_MAX_REPORT_BLOCKS = 31; // RFC 3550 page 37 +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_RTCP_CONFIG_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc new file mode 100644 index 0000000000..1c31611409 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -0,0 +1,768 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_rtcp_impl.h" + +#include <string.h> + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <set> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "api/transport/field_trial_based_config.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtcp_sender.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/ntp_time.h" + +#ifdef _WIN32 +// Disable warning C4355: 'this' : used in base member initializer list. +#pragma warning(disable : 4355) +#endif + +namespace webrtc { +namespace { +const int64_t kRtpRtcpRttProcessTimeMs = 1000; +const int64_t kRtpRtcpBitrateProcessTimeMs = 10; +const int64_t kDefaultExpectedRetransmissionTimeMs = 125; +} // namespace + +ModuleRtpRtcpImpl::RtpSenderContext::RtpSenderContext( + const RtpRtcpInterface::Configuration& config) + : packet_history(config.clock, config.enable_rtx_padding_prioritization), + sequencer_(config.local_media_ssrc, + config.rtx_send_ssrc, + /*require_marker_before_media_padding=*/!config.audio, + config.clock), + packet_sender(config, &packet_history), + non_paced_sender(&packet_sender, &sequencer_), + packet_generator( + config, + &packet_history, + config.paced_sender ? config.paced_sender : &non_paced_sender) {} + +std::unique_ptr<RtpRtcp> RtpRtcp::DEPRECATED_Create( + const Configuration& configuration) { + RTC_DCHECK(configuration.clock); + return std::make_unique<ModuleRtpRtcpImpl>(configuration); +} + +ModuleRtpRtcpImpl::ModuleRtpRtcpImpl(const Configuration& configuration) + : rtcp_sender_( + RTCPSender::Configuration::FromRtpRtcpConfiguration(configuration)), + rtcp_receiver_(configuration, this), + clock_(configuration.clock), + last_bitrate_process_time_(clock_->TimeInMilliseconds()), + last_rtt_process_time_(clock_->TimeInMilliseconds()), + packet_overhead_(28), // IPV4 UDP. + nack_last_time_sent_full_ms_(0), + nack_last_seq_number_sent_(0), + rtt_stats_(configuration.rtt_stats), + rtt_ms_(0) { + if (!configuration.receiver_only) { + rtp_sender_ = std::make_unique<RtpSenderContext>(configuration); + // Make sure rtcp sender use same timestamp offset as rtp sender. + rtcp_sender_.SetTimestampOffset( + rtp_sender_->packet_generator.TimestampOffset()); + } + + // Set default packet size limit. + // TODO(nisse): Kind-of duplicates + // webrtc::VideoSendStream::Config::Rtp::kDefaultMaxPacketSize. + const size_t kTcpOverIpv4HeaderSize = 40; + SetMaxRtpPacketSize(IP_PACKET_SIZE - kTcpOverIpv4HeaderSize); +} + +ModuleRtpRtcpImpl::~ModuleRtpRtcpImpl() = default; + +// Process any pending tasks such as timeouts (non time critical events). +void ModuleRtpRtcpImpl::Process() { + const int64_t now = clock_->TimeInMilliseconds(); + + if (rtp_sender_) { + if (now >= last_bitrate_process_time_ + kRtpRtcpBitrateProcessTimeMs) { + rtp_sender_->packet_sender.ProcessBitrateAndNotifyObservers(); + last_bitrate_process_time_ = now; + } + } + + // TODO(bugs.webrtc.org/11581): We update the RTT once a second, whereas other + // things that run in this method are updated much more frequently. Move the + // RTT checking over to the worker thread, which matches better with where the + // stats are maintained. + bool process_rtt = now >= last_rtt_process_time_ + kRtpRtcpRttProcessTimeMs; + if (rtcp_sender_.Sending()) { + // Process RTT if we have received a report block and we haven't + // processed RTT for at least `kRtpRtcpRttProcessTimeMs` milliseconds. + // Note that LastReceivedReportBlockMs() grabs a lock, so check + // `process_rtt` first. + if (process_rtt && rtt_stats_ != nullptr && + rtcp_receiver_.LastReceivedReportBlockMs() > last_rtt_process_time_) { + int64_t max_rtt_ms = 0; + for (const auto& block : rtcp_receiver_.GetLatestReportBlockData()) { + if (block.last_rtt_ms() > max_rtt_ms) { + max_rtt_ms = block.last_rtt_ms(); + } + } + // Report the rtt. + if (max_rtt_ms > 0) { + rtt_stats_->OnRttUpdate(max_rtt_ms); + } + } + + // Verify receiver reports are delivered and the reported sequence number + // is increasing. + // TODO(bugs.webrtc.org/11581): The timeout value needs to be checked every + // few seconds (see internals of RtcpRrTimeout). Here, we may be polling it + // a couple of hundred times a second, which isn't great since it grabs a + // lock. Note also that LastReceivedReportBlockMs() (called above) and + // RtcpRrTimeout() both grab the same lock and check the same timer, so + // it should be possible to consolidate that work somehow. + if (rtcp_receiver_.RtcpRrTimeout()) { + RTC_LOG_F(LS_WARNING) << "Timeout: No RTCP RR received."; + } else if (rtcp_receiver_.RtcpRrSequenceNumberTimeout()) { + RTC_LOG_F(LS_WARNING) << "Timeout: No increase in RTCP RR extended " + "highest sequence number."; + } + } else { + // Report rtt from receiver. + if (process_rtt) { + int64_t rtt_ms; + if (rtt_stats_ && rtcp_receiver_.GetAndResetXrRrRtt(&rtt_ms)) { + rtt_stats_->OnRttUpdate(rtt_ms); + } + } + } + + // Get processed rtt. + if (process_rtt) { + last_rtt_process_time_ = now; + if (rtt_stats_) { + // Make sure we have a valid RTT before setting. + int64_t last_rtt = rtt_stats_->LastProcessedRtt(); + if (last_rtt >= 0) + set_rtt_ms(last_rtt); + } + } + + if (rtcp_sender_.TimeToSendRTCPReport()) + rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpReport); + + if (rtcp_sender_.TMMBR() && rtcp_receiver_.UpdateTmmbrTimers()) { + rtcp_receiver_.NotifyTmmbrUpdated(); + } +} + +void ModuleRtpRtcpImpl::SetRtxSendStatus(int mode) { + rtp_sender_->packet_generator.SetRtxStatus(mode); +} + +int ModuleRtpRtcpImpl::RtxSendStatus() const { + return rtp_sender_ ? rtp_sender_->packet_generator.RtxStatus() : kRtxOff; +} + +void ModuleRtpRtcpImpl::SetRtxSendPayloadType(int payload_type, + int associated_payload_type) { + rtp_sender_->packet_generator.SetRtxPayloadType(payload_type, + associated_payload_type); +} + +absl::optional<uint32_t> ModuleRtpRtcpImpl::RtxSsrc() const { + return rtp_sender_ ? rtp_sender_->packet_generator.RtxSsrc() : absl::nullopt; +} + +absl::optional<uint32_t> ModuleRtpRtcpImpl::FlexfecSsrc() const { + if (rtp_sender_) { + return rtp_sender_->packet_generator.FlexfecSsrc(); + } + return absl::nullopt; +} + +void ModuleRtpRtcpImpl::IncomingRtcpPacket(const uint8_t* rtcp_packet, + const size_t length) { + rtcp_receiver_.IncomingPacket(rtcp_packet, length); +} + +void ModuleRtpRtcpImpl::RegisterSendPayloadFrequency(int payload_type, + int payload_frequency) { + rtcp_sender_.SetRtpClockRate(payload_type, payload_frequency); +} + +int32_t ModuleRtpRtcpImpl::DeRegisterSendPayload(const int8_t payload_type) { + return 0; +} + +uint32_t ModuleRtpRtcpImpl::StartTimestamp() const { + return rtp_sender_->packet_generator.TimestampOffset(); +} + +// Configure start timestamp, default is a random number. +void ModuleRtpRtcpImpl::SetStartTimestamp(const uint32_t timestamp) { + rtcp_sender_.SetTimestampOffset(timestamp); + rtp_sender_->packet_generator.SetTimestampOffset(timestamp); + rtp_sender_->packet_sender.SetTimestampOffset(timestamp); +} + +uint16_t ModuleRtpRtcpImpl::SequenceNumber() const { + MutexLock lock(&rtp_sender_->sequencer_mutex); + return rtp_sender_->sequencer_.media_sequence_number(); +} + +// Set SequenceNumber, default is a random number. +void ModuleRtpRtcpImpl::SetSequenceNumber(const uint16_t seq_num) { + MutexLock lock(&rtp_sender_->sequencer_mutex); + rtp_sender_->sequencer_.set_media_sequence_number(seq_num); +} + +void ModuleRtpRtcpImpl::SetRtpState(const RtpState& rtp_state) { + MutexLock lock(&rtp_sender_->sequencer_mutex); + rtp_sender_->packet_generator.SetRtpState(rtp_state); + rtp_sender_->sequencer_.SetRtpState(rtp_state); + rtcp_sender_.SetTimestampOffset(rtp_state.start_timestamp); +} + +void ModuleRtpRtcpImpl::SetRtxState(const RtpState& rtp_state) { + MutexLock lock(&rtp_sender_->sequencer_mutex); + rtp_sender_->packet_generator.SetRtxRtpState(rtp_state); + rtp_sender_->sequencer_.set_rtx_sequence_number(rtp_state.sequence_number); +} + +RtpState ModuleRtpRtcpImpl::GetRtpState() const { + MutexLock lock(&rtp_sender_->sequencer_mutex); + RtpState state = rtp_sender_->packet_generator.GetRtpState(); + rtp_sender_->sequencer_.PopulateRtpState(state); + return state; +} + +RtpState ModuleRtpRtcpImpl::GetRtxState() const { + MutexLock lock(&rtp_sender_->sequencer_mutex); + RtpState state = rtp_sender_->packet_generator.GetRtxRtpState(); + state.sequence_number = rtp_sender_->sequencer_.rtx_sequence_number(); + return state; +} + +void ModuleRtpRtcpImpl::SetMid(absl::string_view mid) { + if (rtp_sender_) { + rtp_sender_->packet_generator.SetMid(mid); + } + // TODO(bugs.webrtc.org/4050): If we end up supporting the MID SDES item for + // RTCP, this will need to be passed down to the RTCPSender also. +} + +// TODO(pbos): Handle media and RTX streams separately (separate RTCP +// feedbacks). +RTCPSender::FeedbackState ModuleRtpRtcpImpl::GetFeedbackState() { + RTCPSender::FeedbackState state; + // This is called also when receiver_only is true. Hence below + // checks that rtp_sender_ exists. + if (rtp_sender_) { + StreamDataCounters rtp_stats; + StreamDataCounters rtx_stats; + rtp_sender_->packet_sender.GetDataCounters(&rtp_stats, &rtx_stats); + state.packets_sent = + rtp_stats.transmitted.packets + rtx_stats.transmitted.packets; + state.media_bytes_sent = rtp_stats.transmitted.payload_bytes + + rtx_stats.transmitted.payload_bytes; + state.send_bitrate = + rtp_sender_->packet_sender.GetSendRates().Sum().bps<uint32_t>(); + } + state.receiver = &rtcp_receiver_; + + uint32_t received_ntp_secs = 0; + uint32_t received_ntp_frac = 0; + state.remote_sr = 0; + if (rtcp_receiver_.NTP(&received_ntp_secs, &received_ntp_frac, + /*rtcp_arrival_time_secs=*/&state.last_rr_ntp_secs, + /*rtcp_arrival_time_frac=*/&state.last_rr_ntp_frac, + /*rtcp_timestamp=*/nullptr, + /*remote_sender_packet_count=*/nullptr, + /*remote_sender_octet_count=*/nullptr, + /*remote_sender_reports_count=*/nullptr)) { + state.remote_sr = ((received_ntp_secs & 0x0000ffff) << 16) + + ((received_ntp_frac & 0xffff0000) >> 16); + } + + state.last_xr_rtis = rtcp_receiver_.ConsumeReceivedXrReferenceTimeInfo(); + + return state; +} + +int32_t ModuleRtpRtcpImpl::SetSendingStatus(const bool sending) { + if (rtcp_sender_.Sending() != sending) { + // Sends RTCP BYE when going from true to false + rtcp_sender_.SetSendingStatus(GetFeedbackState(), sending); + } + return 0; +} + +bool ModuleRtpRtcpImpl::Sending() const { + return rtcp_sender_.Sending(); +} + +void ModuleRtpRtcpImpl::SetSendingMediaStatus(const bool sending) { + rtp_sender_->packet_generator.SetSendingMediaStatus(sending); +} + +bool ModuleRtpRtcpImpl::SendingMedia() const { + return rtp_sender_ ? rtp_sender_->packet_generator.SendingMedia() : false; +} + +bool ModuleRtpRtcpImpl::IsAudioConfigured() const { + return rtp_sender_ ? rtp_sender_->packet_generator.IsAudioConfigured() + : false; +} + +void ModuleRtpRtcpImpl::SetAsPartOfAllocation(bool part_of_allocation) { + RTC_CHECK(rtp_sender_); + rtp_sender_->packet_sender.ForceIncludeSendPacketsInAllocation( + part_of_allocation); +} + +bool ModuleRtpRtcpImpl::OnSendingRtpFrame(uint32_t timestamp, + int64_t capture_time_ms, + int payload_type, + bool force_sender_report) { + if (!Sending()) + return false; + + // TODO(bugs.webrtc.org/12873): Migrate this method and it's users to use + // optional Timestamps. + absl::optional<Timestamp> capture_time; + if (capture_time_ms > 0) { + capture_time = Timestamp::Millis(capture_time_ms); + } + absl::optional<int> payload_type_optional; + if (payload_type >= 0) + payload_type_optional = payload_type; + rtcp_sender_.SetLastRtpTime(timestamp, capture_time, payload_type_optional); + // Make sure an RTCP report isn't queued behind a key frame. + if (rtcp_sender_.TimeToSendRTCPReport(force_sender_report)) + rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpReport); + + return true; +} + +bool ModuleRtpRtcpImpl::TrySendPacket(RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) { + RTC_DCHECK(rtp_sender_); + // TODO(sprang): Consider if we can remove this check. + if (!rtp_sender_->packet_generator.SendingMedia()) { + return false; + } + { + MutexLock lock(&rtp_sender_->sequencer_mutex); + if (packet->packet_type() == RtpPacketMediaType::kPadding && + packet->Ssrc() == rtp_sender_->packet_generator.SSRC() && + !rtp_sender_->sequencer_.CanSendPaddingOnMediaSsrc()) { + // New media packet preempted this generated padding packet, discard it. + return false; + } + bool is_flexfec = + packet->packet_type() == RtpPacketMediaType::kForwardErrorCorrection && + packet->Ssrc() == rtp_sender_->packet_generator.FlexfecSsrc(); + if (!is_flexfec) { + rtp_sender_->sequencer_.Sequence(*packet); + } + } + rtp_sender_->packet_sender.SendPacket(packet, pacing_info); + return true; +} + +void ModuleRtpRtcpImpl::SetFecProtectionParams(const FecProtectionParams&, + const FecProtectionParams&) { + // Deferred FEC not supported in deprecated RTP module. +} + +std::vector<std::unique_ptr<RtpPacketToSend>> +ModuleRtpRtcpImpl::FetchFecPackets() { + // Deferred FEC not supported in deprecated RTP module. + return {}; +} + +void ModuleRtpRtcpImpl::OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers) { + RTC_DCHECK_NOTREACHED() + << "Stream flushing not supported with legacy rtp modules."; +} + +void ModuleRtpRtcpImpl::OnPacketsAcknowledged( + rtc::ArrayView<const uint16_t> sequence_numbers) { + RTC_DCHECK(rtp_sender_); + rtp_sender_->packet_history.CullAcknowledgedPackets(sequence_numbers); +} + +bool ModuleRtpRtcpImpl::SupportsPadding() const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_generator.SupportsPadding(); +} + +bool ModuleRtpRtcpImpl::SupportsRtxPayloadPadding() const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_generator.SupportsRtxPayloadPadding(); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> +ModuleRtpRtcpImpl::GeneratePadding(size_t target_size_bytes) { + RTC_DCHECK(rtp_sender_); + MutexLock lock(&rtp_sender_->sequencer_mutex); + return rtp_sender_->packet_generator.GeneratePadding( + target_size_bytes, rtp_sender_->packet_sender.MediaHasBeenSent(), + rtp_sender_->sequencer_.CanSendPaddingOnMediaSsrc()); +} + +std::vector<RtpSequenceNumberMap::Info> +ModuleRtpRtcpImpl::GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_sender.GetSentRtpPacketInfos(sequence_numbers); +} + +size_t ModuleRtpRtcpImpl::ExpectedPerPacketOverhead() const { + if (!rtp_sender_) { + return 0; + } + return rtp_sender_->packet_generator.ExpectedPerPacketOverhead(); +} + +void ModuleRtpRtcpImpl::OnPacketSendingThreadSwitched() {} + +size_t ModuleRtpRtcpImpl::MaxRtpPacketSize() const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_generator.MaxRtpPacketSize(); +} + +void ModuleRtpRtcpImpl::SetMaxRtpPacketSize(size_t rtp_packet_size) { + RTC_DCHECK_LE(rtp_packet_size, IP_PACKET_SIZE) + << "rtp packet size too large: " << rtp_packet_size; + RTC_DCHECK_GT(rtp_packet_size, packet_overhead_) + << "rtp packet size too small: " << rtp_packet_size; + + rtcp_sender_.SetMaxRtpPacketSize(rtp_packet_size); + if (rtp_sender_) { + rtp_sender_->packet_generator.SetMaxRtpPacketSize(rtp_packet_size); + } +} + +RtcpMode ModuleRtpRtcpImpl::RTCP() const { + return rtcp_sender_.Status(); +} + +// Configure RTCP status i.e on/off. +void ModuleRtpRtcpImpl::SetRTCPStatus(const RtcpMode method) { + rtcp_sender_.SetRTCPStatus(method); +} + +int32_t ModuleRtpRtcpImpl::SetCNAME(absl::string_view c_name) { + return rtcp_sender_.SetCNAME(c_name); +} + +int32_t ModuleRtpRtcpImpl::RemoteNTP(uint32_t* received_ntpsecs, + uint32_t* received_ntpfrac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp) const { + return rtcp_receiver_.NTP(received_ntpsecs, received_ntpfrac, + rtcp_arrival_time_secs, rtcp_arrival_time_frac, + rtcp_timestamp, + /*remote_sender_packet_count=*/nullptr, + /*remote_sender_octet_count=*/nullptr, + /*remote_sender_reports_count=*/nullptr) + ? 0 + : -1; +} + +// Get RoundTripTime. +int32_t ModuleRtpRtcpImpl::RTT(const uint32_t remote_ssrc, + int64_t* rtt, + int64_t* avg_rtt, + int64_t* min_rtt, + int64_t* max_rtt) const { + int32_t ret = rtcp_receiver_.RTT(remote_ssrc, rtt, avg_rtt, min_rtt, max_rtt); + if (rtt && *rtt == 0) { + // Try to get RTT from RtcpRttStats class. + *rtt = rtt_ms(); + } + return ret; +} + +int64_t ModuleRtpRtcpImpl::ExpectedRetransmissionTimeMs() const { + int64_t expected_retransmission_time_ms = rtt_ms(); + if (expected_retransmission_time_ms > 0) { + return expected_retransmission_time_ms; + } + // No rtt available (`kRtpRtcpRttProcessTimeMs` not yet passed?), so try to + // poll avg_rtt_ms directly from rtcp receiver. + if (rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), nullptr, + &expected_retransmission_time_ms, nullptr, + nullptr) == 0) { + return expected_retransmission_time_ms; + } + return kDefaultExpectedRetransmissionTimeMs; +} + +// Force a send of an RTCP packet. +// Normal SR and RR are triggered via the process function. +int32_t ModuleRtpRtcpImpl::SendRTCP(RTCPPacketType packet_type) { + return rtcp_sender_.SendRTCP(GetFeedbackState(), packet_type); +} + +void ModuleRtpRtcpImpl::GetSendStreamDataCounters( + StreamDataCounters* rtp_counters, + StreamDataCounters* rtx_counters) const { + rtp_sender_->packet_sender.GetDataCounters(rtp_counters, rtx_counters); +} + +// Received RTCP report. +void ModuleRtpRtcpImpl::RemoteRTCPSenderInfo( + uint32_t* packet_count, uint32_t* octet_count, int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const { + return rtcp_receiver_.RemoteRTCPSenderInfo( + packet_count, octet_count, ntp_timestamp_ms, remote_ntp_timestamp_ms); +} + +std::vector<ReportBlockData> ModuleRtpRtcpImpl::GetLatestReportBlockData() + const { + return rtcp_receiver_.GetLatestReportBlockData(); +} + +absl::optional<RtpRtcpInterface::SenderReportStats> +ModuleRtpRtcpImpl::GetSenderReportStats() const { + SenderReportStats stats; + uint32_t remote_timestamp_secs; + uint32_t remote_timestamp_frac; + uint32_t arrival_timestamp_secs; + uint32_t arrival_timestamp_frac; + if (rtcp_receiver_.NTP(&remote_timestamp_secs, &remote_timestamp_frac, + &arrival_timestamp_secs, &arrival_timestamp_frac, + /*rtcp_timestamp=*/nullptr, &stats.packets_sent, + &stats.bytes_sent, &stats.reports_count)) { + stats.last_remote_timestamp.Set(remote_timestamp_secs, + remote_timestamp_frac); + stats.last_arrival_timestamp.Set(arrival_timestamp_secs, + arrival_timestamp_frac); + return stats; + } + return absl::nullopt; +} + +absl::optional<RtpRtcpInterface::NonSenderRttStats> +ModuleRtpRtcpImpl::GetNonSenderRttStats() const { + // This is not implemented for this legacy class. + return absl::nullopt; +} + +// (REMB) Receiver Estimated Max Bitrate. +void ModuleRtpRtcpImpl::SetRemb(int64_t bitrate_bps, + std::vector<uint32_t> ssrcs) { + rtcp_sender_.SetRemb(bitrate_bps, std::move(ssrcs)); +} + +void ModuleRtpRtcpImpl::UnsetRemb() { + rtcp_sender_.UnsetRemb(); +} + +void ModuleRtpRtcpImpl::SetExtmapAllowMixed(bool extmap_allow_mixed) { + rtp_sender_->packet_generator.SetExtmapAllowMixed(extmap_allow_mixed); +} + +void ModuleRtpRtcpImpl::RegisterRtpHeaderExtension(absl::string_view uri, + int id) { + bool registered = + rtp_sender_->packet_generator.RegisterRtpHeaderExtension(uri, id); + RTC_CHECK(registered); +} + +void ModuleRtpRtcpImpl::DeregisterSendRtpHeaderExtension( + absl::string_view uri) { + rtp_sender_->packet_generator.DeregisterRtpHeaderExtension(uri); +} + +void ModuleRtpRtcpImpl::SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) { + rtcp_sender_.SetTmmbn(std::move(bounding_set)); +} + +// Send a Negative acknowledgment packet. +int32_t ModuleRtpRtcpImpl::SendNACK(const uint16_t* nack_list, + const uint16_t size) { + uint16_t nack_length = size; + uint16_t start_id = 0; + int64_t now_ms = clock_->TimeInMilliseconds(); + if (TimeToSendFullNackList(now_ms)) { + nack_last_time_sent_full_ms_ = now_ms; + } else { + // Only send extended list. + if (nack_last_seq_number_sent_ == nack_list[size - 1]) { + // Last sequence number is the same, do not send list. + return 0; + } + // Send new sequence numbers. + for (int i = 0; i < size; ++i) { + if (nack_last_seq_number_sent_ == nack_list[i]) { + start_id = i + 1; + break; + } + } + nack_length = size - start_id; + } + + // Our RTCP NACK implementation is limited to kRtcpMaxNackFields sequence + // numbers per RTCP packet. + if (nack_length > kRtcpMaxNackFields) { + nack_length = kRtcpMaxNackFields; + } + nack_last_seq_number_sent_ = nack_list[start_id + nack_length - 1]; + + return rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpNack, nack_length, + &nack_list[start_id]); +} + +void ModuleRtpRtcpImpl::SendNack( + const std::vector<uint16_t>& sequence_numbers) { + rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpNack, sequence_numbers.size(), + sequence_numbers.data()); +} + +bool ModuleRtpRtcpImpl::TimeToSendFullNackList(int64_t now) const { + // Use RTT from RtcpRttStats class if provided. + int64_t rtt = rtt_ms(); + if (rtt == 0) { + rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), NULL, &rtt, NULL, NULL); + } + + const int64_t kStartUpRttMs = 100; + int64_t wait_time = 5 + ((rtt * 3) >> 1); // 5 + RTT * 1.5. + if (rtt == 0) { + wait_time = kStartUpRttMs; + } + + // Send a full NACK list once within every `wait_time`. + return now - nack_last_time_sent_full_ms_ > wait_time; +} + +// Store the sent packets, needed to answer to Negative acknowledgment requests. +void ModuleRtpRtcpImpl::SetStorePacketsStatus(const bool enable, + const uint16_t number_to_store) { + rtp_sender_->packet_history.SetStorePacketsStatus( + enable ? RtpPacketHistory::StorageMode::kStoreAndCull + : RtpPacketHistory::StorageMode::kDisabled, + number_to_store); +} + +bool ModuleRtpRtcpImpl::StorePackets() const { + return rtp_sender_->packet_history.GetStorageMode() != + RtpPacketHistory::StorageMode::kDisabled; +} + +void ModuleRtpRtcpImpl::SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) { + rtcp_sender_.SendCombinedRtcpPacket(std::move(rtcp_packets)); +} + +int32_t ModuleRtpRtcpImpl::SendLossNotification(uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) { + return rtcp_sender_.SendLossNotification( + GetFeedbackState(), last_decoded_seq_num, last_received_seq_num, + decodability_flag, buffering_allowed); +} + +void ModuleRtpRtcpImpl::SetRemoteSSRC(const uint32_t ssrc) { + // Inform about the incoming SSRC. + rtcp_sender_.SetRemoteSSRC(ssrc); + rtcp_receiver_.SetRemoteSSRC(ssrc); +} + +void ModuleRtpRtcpImpl::SetLocalSsrc(uint32_t local_ssrc) { + rtcp_receiver_.set_local_media_ssrc(local_ssrc); + rtcp_sender_.SetSsrc(local_ssrc); +} + +RtpSendRates ModuleRtpRtcpImpl::GetSendRates() const { + return rtp_sender_->packet_sender.GetSendRates(); +} + +void ModuleRtpRtcpImpl::OnRequestSendReport() { + SendRTCP(kRtcpSr); +} + +void ModuleRtpRtcpImpl::OnReceivedNack( + const std::vector<uint16_t>& nack_sequence_numbers) { + if (!rtp_sender_) + return; + + if (!StorePackets() || nack_sequence_numbers.empty()) { + return; + } + // Use RTT from RtcpRttStats class if provided. + int64_t rtt = rtt_ms(); + if (rtt == 0) { + rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), NULL, &rtt, NULL, NULL); + } + rtp_sender_->packet_generator.OnReceivedNack(nack_sequence_numbers, rtt); +} + +void ModuleRtpRtcpImpl::OnReceivedRtcpReportBlocks( + const ReportBlockList& report_blocks) { + if (rtp_sender_) { + uint32_t ssrc = SSRC(); + absl::optional<uint32_t> rtx_ssrc; + if (rtp_sender_->packet_generator.RtxStatus() != kRtxOff) { + rtx_ssrc = rtp_sender_->packet_generator.RtxSsrc(); + } + + for (const RTCPReportBlock& report_block : report_blocks) { + if (ssrc == report_block.source_ssrc) { + rtp_sender_->packet_generator.OnReceivedAckOnSsrc( + report_block.extended_highest_sequence_number); + } else if (rtx_ssrc && *rtx_ssrc == report_block.source_ssrc) { + rtp_sender_->packet_generator.OnReceivedAckOnRtxSsrc( + report_block.extended_highest_sequence_number); + } + } + } +} + +void ModuleRtpRtcpImpl::set_rtt_ms(int64_t rtt_ms) { + { + MutexLock lock(&mutex_rtt_); + rtt_ms_ = rtt_ms; + } + if (rtp_sender_) { + rtp_sender_->packet_history.SetRtt(TimeDelta::Millis(rtt_ms)); + } +} + +int64_t ModuleRtpRtcpImpl::rtt_ms() const { + MutexLock lock(&mutex_rtt_); + return rtt_ms_; +} + +void ModuleRtpRtcpImpl::SetVideoBitrateAllocation( + const VideoBitrateAllocation& bitrate) { + rtcp_sender_.SetVideoBitrateAllocation(bitrate); +} + +RTPSender* ModuleRtpRtcpImpl::RtpSender() { + return rtp_sender_ ? &rtp_sender_->packet_generator : nullptr; +} + +const RTPSender* ModuleRtpRtcpImpl::RtpSender() const { + return rtp_sender_ ? &rtp_sender_->packet_generator : nullptr; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h new file mode 100644 index 0000000000..6070b67d44 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -0,0 +1,332 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_RTCP_IMPL_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_RTCP_IMPL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/rtp_headers.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/include/module_fec_types.h" +#include "modules/rtp_rtcp/include/rtp_rtcp.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" // RTCPPacketType +#include "modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.h" +#include "modules/rtp_rtcp/source/packet_sequencer.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" +#include "modules/rtp_rtcp/source/rtcp_receiver.h" +#include "modules/rtp_rtcp/source/rtcp_sender.h" +#include "modules/rtp_rtcp/source/rtp_packet_history.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_sender.h" +#include "rtc_base/gtest_prod_util.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +class Clock; +struct PacedPacketInfo; +struct RTPVideoHeader; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +class ABSL_DEPRECATED("") ModuleRtpRtcpImpl + : public RtpRtcp, + public RTCPReceiver::ModuleRtpRtcp { +#pragma clang diagnostic pop + public: + explicit ModuleRtpRtcpImpl( + const RtpRtcpInterface::Configuration& configuration); + ~ModuleRtpRtcpImpl() override; + + // Process any pending tasks such as timeouts. + void Process() override; + + // Receiver part. + + // Called when we receive an RTCP packet. + void IncomingRtcpPacket(const uint8_t* incoming_packet, + size_t incoming_packet_length) override; + + void SetRemoteSSRC(uint32_t ssrc) override; + void SetLocalSsrc(uint32_t ssrc) override; + + // Sender part. + void RegisterSendPayloadFrequency(int payload_type, + int payload_frequency) override; + + int32_t DeRegisterSendPayload(int8_t payload_type) override; + + void SetExtmapAllowMixed(bool extmap_allow_mixed) override; + + // Register RTP header extension. + void RegisterRtpHeaderExtension(absl::string_view uri, int id) override; + void DeregisterSendRtpHeaderExtension(absl::string_view uri) override; + + bool SupportsPadding() const override; + bool SupportsRtxPayloadPadding() const override; + + // Get start timestamp. + uint32_t StartTimestamp() const override; + + // Configure start timestamp, default is a random number. + void SetStartTimestamp(uint32_t timestamp) override; + + uint16_t SequenceNumber() const override; + + // Set SequenceNumber, default is a random number. + void SetSequenceNumber(uint16_t seq) override; + + void SetRtpState(const RtpState& rtp_state) override; + void SetRtxState(const RtpState& rtp_state) override; + RtpState GetRtpState() const override; + RtpState GetRtxState() const override; + + void SetNonSenderRttMeasurement(bool enabled) override {} + + uint32_t SSRC() const override { return rtcp_sender_.SSRC(); } + + void SetMid(absl::string_view mid) override; + + RTCPSender::FeedbackState GetFeedbackState(); + + void SetRtxSendStatus(int mode) override; + int RtxSendStatus() const override; + absl::optional<uint32_t> RtxSsrc() const override; + + void SetRtxSendPayloadType(int payload_type, + int associated_payload_type) override; + + absl::optional<uint32_t> FlexfecSsrc() const override; + + // Sends kRtcpByeCode when going from true to false. + int32_t SetSendingStatus(bool sending) override; + + bool Sending() const override; + + // Drops or relays media packets. + void SetSendingMediaStatus(bool sending) override; + + bool SendingMedia() const override; + + bool IsAudioConfigured() const override; + + void SetAsPartOfAllocation(bool part_of_allocation) override; + + bool OnSendingRtpFrame(uint32_t timestamp, + int64_t capture_time_ms, + int payload_type, + bool force_sender_report) override; + + bool TrySendPacket(RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) override; + + void SetFecProtectionParams(const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) override; + + std::vector<std::unique_ptr<RtpPacketToSend>> FetchFecPackets() override; + + void OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers) override; + + void OnPacketsAcknowledged( + rtc::ArrayView<const uint16_t> sequence_numbers) override; + + std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding( + size_t target_size_bytes) override; + + std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const override; + + size_t ExpectedPerPacketOverhead() const override; + + void OnPacketSendingThreadSwitched() override; + + // RTCP part. + + // Get RTCP status. + RtcpMode RTCP() const override; + + // Configure RTCP status i.e on/off. + void SetRTCPStatus(RtcpMode method) override; + + // Set RTCP CName. + int32_t SetCNAME(absl::string_view c_name) override; + + // Get remote NTP. + int32_t RemoteNTP(uint32_t* received_ntp_secs, + uint32_t* received_ntp_frac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp) const override; + + // Get RoundTripTime. + int32_t RTT(uint32_t remote_ssrc, + int64_t* rtt, + int64_t* avg_rtt, + int64_t* min_rtt, + int64_t* max_rtt) const override; + + int64_t ExpectedRetransmissionTimeMs() const override; + + // Force a send of an RTCP packet. + // Normal SR and RR are triggered via the process function. + int32_t SendRTCP(RTCPPacketType rtcpPacketType) override; + + void GetSendStreamDataCounters( + StreamDataCounters* rtp_counters, + StreamDataCounters* rtx_counters) const override; + + void RemoteRTCPSenderInfo(uint32_t* packet_count, + uint32_t* octet_count, + int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const override; + + // A snapshot of the most recent Report Block with additional data of + // interest to statistics. Used to implement RTCRemoteInboundRtpStreamStats. + // Within this list, the ReportBlockData::RTCPReportBlock::source_ssrc(), + // which is the SSRC of the corresponding outbound RTP stream, is unique. + std::vector<ReportBlockData> GetLatestReportBlockData() const override; + absl::optional<SenderReportStats> GetSenderReportStats() const override; + // Round trip time statistics computed from the XR block contained in the last + // report. + absl::optional<NonSenderRttStats> GetNonSenderRttStats() const override; + + // (REMB) Receiver Estimated Max Bitrate. + void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) override; + void UnsetRemb() override; + + void SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) override; + + size_t MaxRtpPacketSize() const override; + + void SetMaxRtpPacketSize(size_t max_packet_size) override; + + // (NACK) Negative acknowledgment part. + + // Send a Negative acknowledgment packet. + // TODO(philipel): Deprecate SendNACK and use SendNack instead. + int32_t SendNACK(const uint16_t* nack_list, uint16_t size) override; + + void SendNack(const std::vector<uint16_t>& sequence_numbers) override; + + // Store the sent packets, needed to answer to a negative acknowledgment + // requests. + void SetStorePacketsStatus(bool enable, uint16_t number_to_store) override; + + void SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) override; + + // Video part. + int32_t SendLossNotification(uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) override; + + RtpSendRates GetSendRates() const override; + + void OnReceivedNack( + const std::vector<uint16_t>& nack_sequence_numbers) override; + void OnReceivedRtcpReportBlocks( + const ReportBlockList& report_blocks) override; + void OnRequestSendReport() override; + + void SetVideoBitrateAllocation( + const VideoBitrateAllocation& bitrate) override; + + RTPSender* RtpSender() override; + const RTPSender* RtpSender() const override; + + protected: + bool UpdateRTCPReceiveInformationTimers(); + + RTPSender* rtp_sender() { + return rtp_sender_ ? &rtp_sender_->packet_generator : nullptr; + } + const RTPSender* rtp_sender() const { + return rtp_sender_ ? &rtp_sender_->packet_generator : nullptr; + } + + RTCPSender* rtcp_sender() { return &rtcp_sender_; } + const RTCPSender* rtcp_sender() const { return &rtcp_sender_; } + + RTCPReceiver* rtcp_receiver() { return &rtcp_receiver_; } + const RTCPReceiver* rtcp_receiver() const { return &rtcp_receiver_; } + + void SetMediaHasBeenSent(bool media_has_been_sent) { + rtp_sender_->packet_sender.SetMediaHasBeenSent(media_has_been_sent); + } + + Clock* clock() const { return clock_; } + + private: + FRIEND_TEST_ALL_PREFIXES(RtpRtcpImplTest, Rtt); + FRIEND_TEST_ALL_PREFIXES(RtpRtcpImplTest, RttForReceiverOnly); + + struct RtpSenderContext { + explicit RtpSenderContext(const RtpRtcpInterface::Configuration& config); + // Storage of packets, for retransmissions and padding, if applicable. + RtpPacketHistory packet_history; + // Handles sequence number assignment and padding timestamp generation. + mutable Mutex sequencer_mutex; + PacketSequencer sequencer_ RTC_GUARDED_BY(sequencer_mutex); + // Handles final time timestamping/stats/etc and handover to Transport. + DEPRECATED_RtpSenderEgress packet_sender; + // If no paced sender configured, this class will be used to pass packets + // from `packet_generator_` to `packet_sender_`. + DEPRECATED_RtpSenderEgress::NonPacedPacketSender non_paced_sender; + // Handles creation of RTP packets to be sent. + RTPSender packet_generator; + }; + + void set_rtt_ms(int64_t rtt_ms); + int64_t rtt_ms() const; + + bool TimeToSendFullNackList(int64_t now) const; + + // Returns true if the module is configured to store packets. + bool StorePackets() const; + + // Returns current Receiver Reference Time Report (RTTR) status. + bool RtcpXrRrtrStatus() const; + + std::unique_ptr<RtpSenderContext> rtp_sender_; + + RTCPSender rtcp_sender_; + RTCPReceiver rtcp_receiver_; + + Clock* const clock_; + + int64_t last_bitrate_process_time_; + int64_t last_rtt_process_time_; + uint16_t packet_overhead_; + + // Send side + int64_t nack_last_time_sent_full_ms_; + uint16_t nack_last_seq_number_sent_; + + RtcpRttStats* const rtt_stats_; + + // The processed RTT from RtcpRttStats. + mutable Mutex mutex_rtt_; + int64_t rtt_ms_ RTC_GUARDED_BY(mutex_rtt_); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_RTCP_IMPL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc new file mode 100644 index 0000000000..66d2e7a44e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc @@ -0,0 +1,832 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_rtcp_impl2.h" + +#include <string.h> + +#include <algorithm> +#include <cstdint> +#include <memory> +#include <set> +#include <string> +#include <utility> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/sequence_checker.h" +#include "api/transport/field_trial_based_config.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/ntp_time.h" + +#ifdef _WIN32 +// Disable warning C4355: 'this' : used in base member initializer list. +#pragma warning(disable : 4355) +#endif + +namespace webrtc { +namespace { +const int64_t kDefaultExpectedRetransmissionTimeMs = 125; + +constexpr TimeDelta kRttUpdateInterval = TimeDelta::Millis(1000); + +RTCPSender::Configuration AddRtcpSendEvaluationCallback( + RTCPSender::Configuration config, + std::function<void(TimeDelta)> send_evaluation_callback) { + config.schedule_next_rtcp_send_evaluation_function = + std::move(send_evaluation_callback); + return config; +} + +} // namespace + +ModuleRtpRtcpImpl2::RtpSenderContext::RtpSenderContext( + const RtpRtcpInterface::Configuration& config) + : packet_history(config.clock, config.enable_rtx_padding_prioritization), + sequencer(config.local_media_ssrc, + config.rtx_send_ssrc, + /*require_marker_before_media_padding=*/!config.audio, + config.clock), + packet_sender(config, &packet_history), + non_paced_sender(&packet_sender, &sequencer), + packet_generator( + config, + &packet_history, + config.paced_sender ? config.paced_sender : &non_paced_sender) {} + +ModuleRtpRtcpImpl2::ModuleRtpRtcpImpl2(const Configuration& configuration) + : worker_queue_(TaskQueueBase::Current()), + rtcp_sender_(AddRtcpSendEvaluationCallback( + RTCPSender::Configuration::FromRtpRtcpConfiguration(configuration), + [this](TimeDelta duration) { + ScheduleRtcpSendEvaluation(duration); + })), + rtcp_receiver_(configuration, this), + clock_(configuration.clock), + packet_overhead_(28), // IPV4 UDP. + nack_last_time_sent_full_ms_(0), + nack_last_seq_number_sent_(0), + rtt_stats_(configuration.rtt_stats), + rtt_ms_(0) { + RTC_DCHECK(worker_queue_); + rtcp_thread_checker_.Detach(); + if (!configuration.receiver_only) { + rtp_sender_ = std::make_unique<RtpSenderContext>(configuration); + rtp_sender_->sequencing_checker.Detach(); + // Make sure rtcp sender use same timestamp offset as rtp sender. + rtcp_sender_.SetTimestampOffset( + rtp_sender_->packet_generator.TimestampOffset()); + rtp_sender_->packet_sender.SetTimestampOffset( + rtp_sender_->packet_generator.TimestampOffset()); + } + + // Set default packet size limit. + // TODO(nisse): Kind-of duplicates + // webrtc::VideoSendStream::Config::Rtp::kDefaultMaxPacketSize. + const size_t kTcpOverIpv4HeaderSize = 40; + SetMaxRtpPacketSize(IP_PACKET_SIZE - kTcpOverIpv4HeaderSize); + rtt_update_task_ = RepeatingTaskHandle::DelayedStart( + worker_queue_, kRttUpdateInterval, [this]() { + PeriodicUpdate(); + return kRttUpdateInterval; + }); +} + +ModuleRtpRtcpImpl2::~ModuleRtpRtcpImpl2() { + RTC_DCHECK_RUN_ON(worker_queue_); + rtt_update_task_.Stop(); +} + +// static +std::unique_ptr<ModuleRtpRtcpImpl2> ModuleRtpRtcpImpl2::Create( + const Configuration& configuration) { + RTC_DCHECK(configuration.clock); + RTC_DCHECK(TaskQueueBase::Current()); + return std::make_unique<ModuleRtpRtcpImpl2>(configuration); +} + +void ModuleRtpRtcpImpl2::SetRtxSendStatus(int mode) { + rtp_sender_->packet_generator.SetRtxStatus(mode); +} + +int ModuleRtpRtcpImpl2::RtxSendStatus() const { + return rtp_sender_ ? rtp_sender_->packet_generator.RtxStatus() : kRtxOff; +} + +void ModuleRtpRtcpImpl2::SetRtxSendPayloadType(int payload_type, + int associated_payload_type) { + rtp_sender_->packet_generator.SetRtxPayloadType(payload_type, + associated_payload_type); +} + +absl::optional<uint32_t> ModuleRtpRtcpImpl2::RtxSsrc() const { + return rtp_sender_ ? rtp_sender_->packet_generator.RtxSsrc() : absl::nullopt; +} + +absl::optional<uint32_t> ModuleRtpRtcpImpl2::FlexfecSsrc() const { + if (rtp_sender_) { + return rtp_sender_->packet_generator.FlexfecSsrc(); + } + return absl::nullopt; +} + +void ModuleRtpRtcpImpl2::IncomingRtcpPacket(const uint8_t* rtcp_packet, + const size_t length) { + RTC_DCHECK_RUN_ON(&rtcp_thread_checker_); + rtcp_receiver_.IncomingPacket(rtcp_packet, length); +} + +void ModuleRtpRtcpImpl2::RegisterSendPayloadFrequency(int payload_type, + int payload_frequency) { + rtcp_sender_.SetRtpClockRate(payload_type, payload_frequency); +} + +int32_t ModuleRtpRtcpImpl2::DeRegisterSendPayload(const int8_t payload_type) { + return 0; +} + +uint32_t ModuleRtpRtcpImpl2::StartTimestamp() const { + return rtp_sender_->packet_generator.TimestampOffset(); +} + +// Configure start timestamp, default is a random number. +void ModuleRtpRtcpImpl2::SetStartTimestamp(const uint32_t timestamp) { + rtcp_sender_.SetTimestampOffset(timestamp); + rtp_sender_->packet_generator.SetTimestampOffset(timestamp); + rtp_sender_->packet_sender.SetTimestampOffset(timestamp); +} + +uint16_t ModuleRtpRtcpImpl2::SequenceNumber() const { + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + return rtp_sender_->sequencer.media_sequence_number(); +} + +// Set SequenceNumber, default is a random number. +void ModuleRtpRtcpImpl2::SetSequenceNumber(const uint16_t seq_num) { + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + if (rtp_sender_->sequencer.media_sequence_number() != seq_num) { + rtp_sender_->sequencer.set_media_sequence_number(seq_num); + rtp_sender_->packet_history.Clear(); + } +} + +void ModuleRtpRtcpImpl2::SetRtpState(const RtpState& rtp_state) { + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + rtp_sender_->packet_generator.SetRtpState(rtp_state); + rtp_sender_->sequencer.SetRtpState(rtp_state); + rtcp_sender_.SetTimestampOffset(rtp_state.start_timestamp); + rtp_sender_->packet_sender.SetTimestampOffset(rtp_state.start_timestamp); +} + +void ModuleRtpRtcpImpl2::SetRtxState(const RtpState& rtp_state) { + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + rtp_sender_->packet_generator.SetRtxRtpState(rtp_state); + rtp_sender_->sequencer.set_rtx_sequence_number(rtp_state.sequence_number); +} + +RtpState ModuleRtpRtcpImpl2::GetRtpState() const { + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + RtpState state = rtp_sender_->packet_generator.GetRtpState(); + rtp_sender_->sequencer.PopulateRtpState(state); + return state; +} + +RtpState ModuleRtpRtcpImpl2::GetRtxState() const { + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + RtpState state = rtp_sender_->packet_generator.GetRtxRtpState(); + state.sequence_number = rtp_sender_->sequencer.rtx_sequence_number(); + return state; +} + +void ModuleRtpRtcpImpl2::SetNonSenderRttMeasurement(bool enabled) { + rtcp_sender_.SetNonSenderRttMeasurement(enabled); + rtcp_receiver_.SetNonSenderRttMeasurement(enabled); +} + +uint32_t ModuleRtpRtcpImpl2::local_media_ssrc() const { + RTC_DCHECK_RUN_ON(&rtcp_thread_checker_); + RTC_DCHECK_EQ(rtcp_receiver_.local_media_ssrc(), rtcp_sender_.SSRC()); + return rtcp_receiver_.local_media_ssrc(); +} + +void ModuleRtpRtcpImpl2::SetMid(absl::string_view mid) { + if (rtp_sender_) { + rtp_sender_->packet_generator.SetMid(mid); + } + // TODO(bugs.webrtc.org/4050): If we end up supporting the MID SDES item for + // RTCP, this will need to be passed down to the RTCPSender also. +} + +// TODO(pbos): Handle media and RTX streams separately (separate RTCP +// feedbacks). +RTCPSender::FeedbackState ModuleRtpRtcpImpl2::GetFeedbackState() { + // TODO(bugs.webrtc.org/11581): Called by potentially multiple threads. + // Mostly "Send*" methods. Make sure it's only called on the + // construction thread. + + RTCPSender::FeedbackState state; + // This is called also when receiver_only is true. Hence below + // checks that rtp_sender_ exists. + if (rtp_sender_) { + StreamDataCounters rtp_stats; + StreamDataCounters rtx_stats; + rtp_sender_->packet_sender.GetDataCounters(&rtp_stats, &rtx_stats); + state.packets_sent = + rtp_stats.transmitted.packets + rtx_stats.transmitted.packets; + state.media_bytes_sent = rtp_stats.transmitted.payload_bytes + + rtx_stats.transmitted.payload_bytes; + state.send_bitrate = + rtp_sender_->packet_sender.GetSendRates().Sum().bps<uint32_t>(); + } + state.receiver = &rtcp_receiver_; + + uint32_t received_ntp_secs = 0; + uint32_t received_ntp_frac = 0; + state.remote_sr = 0; + if (rtcp_receiver_.NTP(&received_ntp_secs, &received_ntp_frac, + /*rtcp_arrival_time_secs=*/&state.last_rr_ntp_secs, + /*rtcp_arrival_time_frac=*/&state.last_rr_ntp_frac, + /*rtcp_timestamp=*/nullptr, + /*remote_sender_packet_count=*/nullptr, + /*remote_sender_octet_count=*/nullptr, + /*remote_sender_reports_count=*/nullptr)) { + state.remote_sr = ((received_ntp_secs & 0x0000ffff) << 16) + + ((received_ntp_frac & 0xffff0000) >> 16); + } + + state.last_xr_rtis = rtcp_receiver_.ConsumeReceivedXrReferenceTimeInfo(); + + return state; +} + +int32_t ModuleRtpRtcpImpl2::SetSendingStatus(const bool sending) { + if (rtcp_sender_.Sending() != sending) { + // Sends RTCP BYE when going from true to false + rtcp_sender_.SetSendingStatus(GetFeedbackState(), sending); + } + return 0; +} + +bool ModuleRtpRtcpImpl2::Sending() const { + return rtcp_sender_.Sending(); +} + +void ModuleRtpRtcpImpl2::SetSendingMediaStatus(const bool sending) { + rtp_sender_->packet_generator.SetSendingMediaStatus(sending); +} + +bool ModuleRtpRtcpImpl2::SendingMedia() const { + return rtp_sender_ ? rtp_sender_->packet_generator.SendingMedia() : false; +} + +bool ModuleRtpRtcpImpl2::IsAudioConfigured() const { + return rtp_sender_ ? rtp_sender_->packet_generator.IsAudioConfigured() + : false; +} + +void ModuleRtpRtcpImpl2::SetAsPartOfAllocation(bool part_of_allocation) { + RTC_CHECK(rtp_sender_); + rtp_sender_->packet_sender.ForceIncludeSendPacketsInAllocation( + part_of_allocation); +} + +bool ModuleRtpRtcpImpl2::OnSendingRtpFrame(uint32_t timestamp, + int64_t capture_time_ms, + int payload_type, + bool force_sender_report) { + if (!Sending()) + return false; + + // TODO(bugs.webrtc.org/12873): Migrate this method and it's users to use + // optional Timestamps. + absl::optional<Timestamp> capture_time; + if (capture_time_ms > 0) { + capture_time = Timestamp::Millis(capture_time_ms); + } + absl::optional<int> payload_type_optional; + if (payload_type >= 0) + payload_type_optional = payload_type; + rtcp_sender_.SetLastRtpTime(timestamp, capture_time, payload_type_optional); + // Make sure an RTCP report isn't queued behind a key frame. + if (rtcp_sender_.TimeToSendRTCPReport(force_sender_report)) + rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpReport); + + return true; +} + +bool ModuleRtpRtcpImpl2::TrySendPacket(RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) { + RTC_DCHECK(rtp_sender_); + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + if (!rtp_sender_->packet_generator.SendingMedia()) { + return false; + } + if (packet->packet_type() == RtpPacketMediaType::kPadding && + packet->Ssrc() == rtp_sender_->packet_generator.SSRC() && + !rtp_sender_->sequencer.CanSendPaddingOnMediaSsrc()) { + // New media packet preempted this generated padding packet, discard it. + return false; + } + bool is_flexfec = + packet->packet_type() == RtpPacketMediaType::kForwardErrorCorrection && + packet->Ssrc() == rtp_sender_->packet_generator.FlexfecSsrc(); + if (!is_flexfec) { + rtp_sender_->sequencer.Sequence(*packet); + } + + rtp_sender_->packet_sender.SendPacket(packet, pacing_info); + return true; +} + +void ModuleRtpRtcpImpl2::SetFecProtectionParams( + const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) { + RTC_DCHECK(rtp_sender_); + rtp_sender_->packet_sender.SetFecProtectionParameters(delta_params, + key_params); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> +ModuleRtpRtcpImpl2::FetchFecPackets() { + RTC_DCHECK(rtp_sender_); + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + return rtp_sender_->packet_sender.FetchFecPackets(); +} + +void ModuleRtpRtcpImpl2::OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers) { + RTC_DCHECK(rtp_sender_); + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + rtp_sender_->packet_sender.OnAbortedRetransmissions(sequence_numbers); +} + +void ModuleRtpRtcpImpl2::OnPacketsAcknowledged( + rtc::ArrayView<const uint16_t> sequence_numbers) { + RTC_DCHECK(rtp_sender_); + rtp_sender_->packet_history.CullAcknowledgedPackets(sequence_numbers); +} + +bool ModuleRtpRtcpImpl2::SupportsPadding() const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_generator.SupportsPadding(); +} + +bool ModuleRtpRtcpImpl2::SupportsRtxPayloadPadding() const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_generator.SupportsRtxPayloadPadding(); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> +ModuleRtpRtcpImpl2::GeneratePadding(size_t target_size_bytes) { + RTC_DCHECK(rtp_sender_); + RTC_DCHECK_RUN_ON(&rtp_sender_->sequencing_checker); + + return rtp_sender_->packet_generator.GeneratePadding( + target_size_bytes, rtp_sender_->packet_sender.MediaHasBeenSent(), + rtp_sender_->sequencer.CanSendPaddingOnMediaSsrc()); +} + +std::vector<RtpSequenceNumberMap::Info> +ModuleRtpRtcpImpl2::GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_sender.GetSentRtpPacketInfos(sequence_numbers); +} + +size_t ModuleRtpRtcpImpl2::ExpectedPerPacketOverhead() const { + if (!rtp_sender_) { + return 0; + } + return rtp_sender_->packet_generator.ExpectedPerPacketOverhead(); +} + +void ModuleRtpRtcpImpl2::OnPacketSendingThreadSwitched() { + // Ownership of sequencing is being transferred to another thread. + rtp_sender_->sequencing_checker.Detach(); +} + +size_t ModuleRtpRtcpImpl2::MaxRtpPacketSize() const { + RTC_DCHECK(rtp_sender_); + return rtp_sender_->packet_generator.MaxRtpPacketSize(); +} + +void ModuleRtpRtcpImpl2::SetMaxRtpPacketSize(size_t rtp_packet_size) { + RTC_DCHECK_LE(rtp_packet_size, IP_PACKET_SIZE) + << "rtp packet size too large: " << rtp_packet_size; + RTC_DCHECK_GT(rtp_packet_size, packet_overhead_) + << "rtp packet size too small: " << rtp_packet_size; + + rtcp_sender_.SetMaxRtpPacketSize(rtp_packet_size); + if (rtp_sender_) { + rtp_sender_->packet_generator.SetMaxRtpPacketSize(rtp_packet_size); + } +} + +RtcpMode ModuleRtpRtcpImpl2::RTCP() const { + return rtcp_sender_.Status(); +} + +// Configure RTCP status i.e on/off. +void ModuleRtpRtcpImpl2::SetRTCPStatus(const RtcpMode method) { + rtcp_sender_.SetRTCPStatus(method); +} + +int32_t ModuleRtpRtcpImpl2::SetCNAME(absl::string_view c_name) { + return rtcp_sender_.SetCNAME(c_name); +} + +int32_t ModuleRtpRtcpImpl2::RemoteNTP(uint32_t* received_ntpsecs, + uint32_t* received_ntpfrac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp) const { + return rtcp_receiver_.NTP(received_ntpsecs, received_ntpfrac, + rtcp_arrival_time_secs, rtcp_arrival_time_frac, + rtcp_timestamp, + /*remote_sender_packet_count=*/nullptr, + /*remote_sender_octet_count=*/nullptr, + /*remote_sender_reports_count=*/nullptr) + ? 0 + : -1; +} + +// TODO(tommi): Check if `avg_rtt_ms`, `min_rtt_ms`, `max_rtt_ms` params are +// actually used in practice (some callers ask for it but don't use it). It +// could be that only `rtt` is needed and if so, then the fast path could be to +// just call rtt_ms() and rely on the calculation being done periodically. +int32_t ModuleRtpRtcpImpl2::RTT(const uint32_t remote_ssrc, + int64_t* rtt, + int64_t* avg_rtt, + int64_t* min_rtt, + int64_t* max_rtt) const { + int32_t ret = rtcp_receiver_.RTT(remote_ssrc, rtt, avg_rtt, min_rtt, max_rtt); + if (rtt && *rtt == 0) { + // Try to get RTT from RtcpRttStats class. + *rtt = rtt_ms(); + } + return ret; +} + +int64_t ModuleRtpRtcpImpl2::ExpectedRetransmissionTimeMs() const { + int64_t expected_retransmission_time_ms = rtt_ms(); + if (expected_retransmission_time_ms > 0) { + return expected_retransmission_time_ms; + } + // No rtt available (`kRttUpdateInterval` not yet passed?), so try to + // poll avg_rtt_ms directly from rtcp receiver. + if (rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), nullptr, + &expected_retransmission_time_ms, nullptr, + nullptr) == 0) { + return expected_retransmission_time_ms; + } + return kDefaultExpectedRetransmissionTimeMs; +} + +// Force a send of an RTCP packet. +// Normal SR and RR are triggered via the process function. +int32_t ModuleRtpRtcpImpl2::SendRTCP(RTCPPacketType packet_type) { + return rtcp_sender_.SendRTCP(GetFeedbackState(), packet_type); +} + +void ModuleRtpRtcpImpl2::GetSendStreamDataCounters( + StreamDataCounters* rtp_counters, + StreamDataCounters* rtx_counters) const { + rtp_sender_->packet_sender.GetDataCounters(rtp_counters, rtx_counters); +} + +// Received RTCP report. +void ModuleRtpRtcpImpl2::RemoteRTCPSenderInfo( + uint32_t* packet_count, uint32_t* octet_count, int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const { + return rtcp_receiver_.RemoteRTCPSenderInfo( + packet_count, octet_count, ntp_timestamp_ms, remote_ntp_timestamp_ms); +} + +std::vector<ReportBlockData> ModuleRtpRtcpImpl2::GetLatestReportBlockData() + const { + return rtcp_receiver_.GetLatestReportBlockData(); +} + +absl::optional<RtpRtcpInterface::SenderReportStats> +ModuleRtpRtcpImpl2::GetSenderReportStats() const { + SenderReportStats stats; + uint32_t remote_timestamp_secs; + uint32_t remote_timestamp_frac; + uint32_t arrival_timestamp_secs; + uint32_t arrival_timestamp_frac; + if (rtcp_receiver_.NTP(&remote_timestamp_secs, &remote_timestamp_frac, + &arrival_timestamp_secs, &arrival_timestamp_frac, + /*rtcp_timestamp=*/nullptr, &stats.packets_sent, + &stats.bytes_sent, &stats.reports_count)) { + stats.last_remote_timestamp.Set(remote_timestamp_secs, + remote_timestamp_frac); + stats.last_arrival_timestamp.Set(arrival_timestamp_secs, + arrival_timestamp_frac); + return stats; + } + return absl::nullopt; +} + +absl::optional<RtpRtcpInterface::NonSenderRttStats> +ModuleRtpRtcpImpl2::GetNonSenderRttStats() const { + RTCPReceiver::NonSenderRttStats non_sender_rtt_stats = + rtcp_receiver_.GetNonSenderRTT(); + return {{ + non_sender_rtt_stats.round_trip_time(), + non_sender_rtt_stats.total_round_trip_time(), + non_sender_rtt_stats.round_trip_time_measurements(), + }}; +} + +// (REMB) Receiver Estimated Max Bitrate. +void ModuleRtpRtcpImpl2::SetRemb(int64_t bitrate_bps, + std::vector<uint32_t> ssrcs) { + rtcp_sender_.SetRemb(bitrate_bps, std::move(ssrcs)); +} + +void ModuleRtpRtcpImpl2::UnsetRemb() { + rtcp_sender_.UnsetRemb(); +} + +void ModuleRtpRtcpImpl2::SetExtmapAllowMixed(bool extmap_allow_mixed) { + rtp_sender_->packet_generator.SetExtmapAllowMixed(extmap_allow_mixed); +} + +void ModuleRtpRtcpImpl2::RegisterRtpHeaderExtension(absl::string_view uri, + int id) { + bool registered = + rtp_sender_->packet_generator.RegisterRtpHeaderExtension(uri, id); + RTC_CHECK(registered); +} + +void ModuleRtpRtcpImpl2::DeregisterSendRtpHeaderExtension( + absl::string_view uri) { + rtp_sender_->packet_generator.DeregisterRtpHeaderExtension(uri); +} + +void ModuleRtpRtcpImpl2::SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) { + rtcp_sender_.SetTmmbn(std::move(bounding_set)); +} + +// Send a Negative acknowledgment packet. +int32_t ModuleRtpRtcpImpl2::SendNACK(const uint16_t* nack_list, + const uint16_t size) { + uint16_t nack_length = size; + uint16_t start_id = 0; + int64_t now_ms = clock_->TimeInMilliseconds(); + if (TimeToSendFullNackList(now_ms)) { + nack_last_time_sent_full_ms_ = now_ms; + } else { + // Only send extended list. + if (nack_last_seq_number_sent_ == nack_list[size - 1]) { + // Last sequence number is the same, do not send list. + return 0; + } + // Send new sequence numbers. + for (int i = 0; i < size; ++i) { + if (nack_last_seq_number_sent_ == nack_list[i]) { + start_id = i + 1; + break; + } + } + nack_length = size - start_id; + } + + // Our RTCP NACK implementation is limited to kRtcpMaxNackFields sequence + // numbers per RTCP packet. + if (nack_length > kRtcpMaxNackFields) { + nack_length = kRtcpMaxNackFields; + } + nack_last_seq_number_sent_ = nack_list[start_id + nack_length - 1]; + + return rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpNack, nack_length, + &nack_list[start_id]); +} + +void ModuleRtpRtcpImpl2::SendNack( + const std::vector<uint16_t>& sequence_numbers) { + rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpNack, sequence_numbers.size(), + sequence_numbers.data()); +} + +bool ModuleRtpRtcpImpl2::TimeToSendFullNackList(int64_t now) const { + // Use RTT from RtcpRttStats class if provided. + int64_t rtt = rtt_ms(); + if (rtt == 0) { + rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), NULL, &rtt, NULL, NULL); + } + + const int64_t kStartUpRttMs = 100; + int64_t wait_time = 5 + ((rtt * 3) >> 1); // 5 + RTT * 1.5. + if (rtt == 0) { + wait_time = kStartUpRttMs; + } + + // Send a full NACK list once within every `wait_time`. + return now - nack_last_time_sent_full_ms_ > wait_time; +} + +// Store the sent packets, needed to answer to Negative acknowledgment requests. +void ModuleRtpRtcpImpl2::SetStorePacketsStatus(const bool enable, + const uint16_t number_to_store) { + rtp_sender_->packet_history.SetStorePacketsStatus( + enable ? RtpPacketHistory::StorageMode::kStoreAndCull + : RtpPacketHistory::StorageMode::kDisabled, + number_to_store); +} + +bool ModuleRtpRtcpImpl2::StorePackets() const { + return rtp_sender_->packet_history.GetStorageMode() != + RtpPacketHistory::StorageMode::kDisabled; +} + +void ModuleRtpRtcpImpl2::SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) { + rtcp_sender_.SendCombinedRtcpPacket(std::move(rtcp_packets)); +} + +int32_t ModuleRtpRtcpImpl2::SendLossNotification(uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) { + return rtcp_sender_.SendLossNotification( + GetFeedbackState(), last_decoded_seq_num, last_received_seq_num, + decodability_flag, buffering_allowed); +} + +void ModuleRtpRtcpImpl2::SetRemoteSSRC(const uint32_t ssrc) { + // Inform about the incoming SSRC. + rtcp_sender_.SetRemoteSSRC(ssrc); + rtcp_receiver_.SetRemoteSSRC(ssrc); +} + +void ModuleRtpRtcpImpl2::SetLocalSsrc(uint32_t local_ssrc) { + RTC_DCHECK_RUN_ON(&rtcp_thread_checker_); + rtcp_receiver_.set_local_media_ssrc(local_ssrc); + rtcp_sender_.SetSsrc(local_ssrc); +} + +RtpSendRates ModuleRtpRtcpImpl2::GetSendRates() const { + // Typically called on the `rtp_transport_queue_` owned by an + // RtpTransportControllerSendInterface instance. + return rtp_sender_->packet_sender.GetSendRates(); +} + +void ModuleRtpRtcpImpl2::OnRequestSendReport() { + SendRTCP(kRtcpSr); +} + +void ModuleRtpRtcpImpl2::OnReceivedNack( + const std::vector<uint16_t>& nack_sequence_numbers) { + if (!rtp_sender_) + return; + + if (!StorePackets() || nack_sequence_numbers.empty()) { + return; + } + // Use RTT from RtcpRttStats class if provided. + int64_t rtt = rtt_ms(); + if (rtt == 0) { + rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), NULL, &rtt, NULL, NULL); + } + rtp_sender_->packet_generator.OnReceivedNack(nack_sequence_numbers, rtt); +} + +void ModuleRtpRtcpImpl2::OnReceivedRtcpReportBlocks( + const ReportBlockList& report_blocks) { + if (rtp_sender_) { + uint32_t ssrc = SSRC(); + absl::optional<uint32_t> rtx_ssrc; + if (rtp_sender_->packet_generator.RtxStatus() != kRtxOff) { + rtx_ssrc = rtp_sender_->packet_generator.RtxSsrc(); + } + + for (const RTCPReportBlock& report_block : report_blocks) { + if (ssrc == report_block.source_ssrc) { + rtp_sender_->packet_generator.OnReceivedAckOnSsrc( + report_block.extended_highest_sequence_number); + } else if (rtx_ssrc && *rtx_ssrc == report_block.source_ssrc) { + rtp_sender_->packet_generator.OnReceivedAckOnRtxSsrc( + report_block.extended_highest_sequence_number); + } + } + } +} + +void ModuleRtpRtcpImpl2::set_rtt_ms(int64_t rtt_ms) { + RTC_DCHECK_RUN_ON(worker_queue_); + { + MutexLock lock(&mutex_rtt_); + rtt_ms_ = rtt_ms; + } + if (rtp_sender_) { + rtp_sender_->packet_history.SetRtt(TimeDelta::Millis(rtt_ms)); + } +} + +int64_t ModuleRtpRtcpImpl2::rtt_ms() const { + MutexLock lock(&mutex_rtt_); + return rtt_ms_; +} + +void ModuleRtpRtcpImpl2::SetVideoBitrateAllocation( + const VideoBitrateAllocation& bitrate) { + rtcp_sender_.SetVideoBitrateAllocation(bitrate); +} + +RTPSender* ModuleRtpRtcpImpl2::RtpSender() { + return rtp_sender_ ? &rtp_sender_->packet_generator : nullptr; +} + +const RTPSender* ModuleRtpRtcpImpl2::RtpSender() const { + return rtp_sender_ ? &rtp_sender_->packet_generator : nullptr; +} + +void ModuleRtpRtcpImpl2::PeriodicUpdate() { + RTC_DCHECK_RUN_ON(worker_queue_); + + Timestamp check_since = clock_->CurrentTime() - kRttUpdateInterval; + absl::optional<TimeDelta> rtt = + rtcp_receiver_.OnPeriodicRttUpdate(check_since, rtcp_sender_.Sending()); + if (rtt) { + if (rtt_stats_) { + rtt_stats_->OnRttUpdate(rtt->ms()); + } + set_rtt_ms(rtt->ms()); + } +} + +void ModuleRtpRtcpImpl2::MaybeSendRtcp() { + RTC_DCHECK_RUN_ON(worker_queue_); + if (rtcp_sender_.TimeToSendRTCPReport()) + rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpReport); +} + +// TODO(bugs.webrtc.org/12889): Consider removing this function when the issue +// is resolved. +void ModuleRtpRtcpImpl2::MaybeSendRtcpAtOrAfterTimestamp( + Timestamp execution_time) { + RTC_DCHECK_RUN_ON(worker_queue_); + Timestamp now = clock_->CurrentTime(); + if (now >= execution_time) { + MaybeSendRtcp(); + return; + } + + TimeDelta delta = execution_time - now; + // TaskQueue may run task 1ms earlier, so don't print warning if in this case. + if (delta > TimeDelta::Millis(1)) { + RTC_DLOG(LS_WARNING) << "BUGBUG: Task queue scheduled delayed call " + << delta << " too early."; + } + + ScheduleMaybeSendRtcpAtOrAfterTimestamp(execution_time, delta); +} + +void ModuleRtpRtcpImpl2::ScheduleRtcpSendEvaluation(TimeDelta duration) { + // We end up here under various sequences including the worker queue, and + // the RTCPSender lock is held. + // We're assuming that the fact that RTCPSender executes under other sequences + // than the worker queue on which it's created on implies that external + // synchronization is present and removes this activity before destruction. + if (duration.IsZero()) { + worker_queue_->PostTask(SafeTask(task_safety_.flag(), [this] { + RTC_DCHECK_RUN_ON(worker_queue_); + MaybeSendRtcp(); + })); + } else { + Timestamp execution_time = clock_->CurrentTime() + duration; + ScheduleMaybeSendRtcpAtOrAfterTimestamp(execution_time, duration); + } +} + +void ModuleRtpRtcpImpl2::ScheduleMaybeSendRtcpAtOrAfterTimestamp( + Timestamp execution_time, + TimeDelta duration) { + // We end up here under various sequences including the worker queue, and + // the RTCPSender lock is held. + // See note in ScheduleRtcpSendEvaluation about why `worker_queue_` can be + // accessed. + worker_queue_->PostDelayedTask( + SafeTask(task_safety_.flag(), + [this, execution_time] { + RTC_DCHECK_RUN_ON(worker_queue_); + MaybeSendRtcpAtOrAfterTimestamp(execution_time); + }), + duration.RoundUpTo(TimeDelta::Millis(1))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.h new file mode 100644 index 0000000000..c43d0c34ba --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.h @@ -0,0 +1,341 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_RTCP_IMPL2_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_RTCP_IMPL2_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/rtp_headers.h" +#include "api/sequence_checker.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/time_delta.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/include/module_fec_types.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" // RTCPPacketType +#include "modules/rtp_rtcp/source/packet_sequencer.h" +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" +#include "modules/rtp_rtcp/source/rtcp_receiver.h" +#include "modules/rtp_rtcp/source/rtcp_sender.h" +#include "modules/rtp_rtcp/source/rtp_packet_history.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_sender.h" +#include "modules/rtp_rtcp/source/rtp_sender_egress.h" +#include "rtc_base/gtest_prod_util.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class Clock; +struct PacedPacketInfo; +struct RTPVideoHeader; + +class ModuleRtpRtcpImpl2 final : public RtpRtcpInterface, + public RTCPReceiver::ModuleRtpRtcp { + public: + explicit ModuleRtpRtcpImpl2( + const RtpRtcpInterface::Configuration& configuration); + ~ModuleRtpRtcpImpl2() override; + + // This method is provided to easy with migrating away from the + // RtpRtcp::Create factory method. Since this is an internal implementation + // detail though, creating an instance of ModuleRtpRtcpImpl2 directly should + // be fine. + static std::unique_ptr<ModuleRtpRtcpImpl2> Create( + const Configuration& configuration); + + // Receiver part. + + // Called when we receive an RTCP packet. + void IncomingRtcpPacket(const uint8_t* incoming_packet, + size_t incoming_packet_length) override; + + void SetRemoteSSRC(uint32_t ssrc) override; + + void SetLocalSsrc(uint32_t local_ssrc) override; + + // Sender part. + void RegisterSendPayloadFrequency(int payload_type, + int payload_frequency) override; + + int32_t DeRegisterSendPayload(int8_t payload_type) override; + + void SetExtmapAllowMixed(bool extmap_allow_mixed) override; + + void RegisterRtpHeaderExtension(absl::string_view uri, int id) override; + void DeregisterSendRtpHeaderExtension(absl::string_view uri) override; + + bool SupportsPadding() const override; + bool SupportsRtxPayloadPadding() const override; + + // Get start timestamp. + uint32_t StartTimestamp() const override; + + // Configure start timestamp, default is a random number. + void SetStartTimestamp(uint32_t timestamp) override; + + uint16_t SequenceNumber() const override; + + // Set SequenceNumber, default is a random number. + void SetSequenceNumber(uint16_t seq) override; + + void SetRtpState(const RtpState& rtp_state) override; + void SetRtxState(const RtpState& rtp_state) override; + RtpState GetRtpState() const override; + RtpState GetRtxState() const override; + + void SetNonSenderRttMeasurement(bool enabled) override; + + uint32_t SSRC() const override { return rtcp_sender_.SSRC(); } + + // Semantically identical to `SSRC()` but must be called on the packet + // delivery thread/tq and returns the ssrc that maps to + // RtpRtcpInterface::Configuration::local_media_ssrc. + uint32_t local_media_ssrc() const; + + void SetMid(absl::string_view mid) override; + + RTCPSender::FeedbackState GetFeedbackState(); + + void SetRtxSendStatus(int mode) override; + int RtxSendStatus() const override; + absl::optional<uint32_t> RtxSsrc() const override; + + void SetRtxSendPayloadType(int payload_type, + int associated_payload_type) override; + + absl::optional<uint32_t> FlexfecSsrc() const override; + + // Sends kRtcpByeCode when going from true to false. + int32_t SetSendingStatus(bool sending) override; + + bool Sending() const override; + + // Drops or relays media packets. + void SetSendingMediaStatus(bool sending) override; + + bool SendingMedia() const override; + + bool IsAudioConfigured() const override; + + void SetAsPartOfAllocation(bool part_of_allocation) override; + + bool OnSendingRtpFrame(uint32_t timestamp, + int64_t capture_time_ms, + int payload_type, + bool force_sender_report) override; + + bool TrySendPacket(RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) override; + + void SetFecProtectionParams(const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) override; + + std::vector<std::unique_ptr<RtpPacketToSend>> FetchFecPackets() override; + + void OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers) override; + + void OnPacketsAcknowledged( + rtc::ArrayView<const uint16_t> sequence_numbers) override; + + std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding( + size_t target_size_bytes) override; + + std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const override; + + size_t ExpectedPerPacketOverhead() const override; + + void OnPacketSendingThreadSwitched() override; + + // RTCP part. + + // Get RTCP status. + RtcpMode RTCP() const override; + + // Configure RTCP status i.e on/off. + void SetRTCPStatus(RtcpMode method) override; + + // Set RTCP CName. + int32_t SetCNAME(absl::string_view c_name) override; + + // Get remote NTP. + int32_t RemoteNTP(uint32_t* received_ntp_secs, + uint32_t* received_ntp_frac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp) const override; + + // Get RoundTripTime. + int32_t RTT(uint32_t remote_ssrc, + int64_t* rtt, + int64_t* avg_rtt, + int64_t* min_rtt, + int64_t* max_rtt) const override; + + int64_t ExpectedRetransmissionTimeMs() const override; + + // Force a send of an RTCP packet. + // Normal SR and RR are triggered via the task queue that's current when this + // object is created. + int32_t SendRTCP(RTCPPacketType rtcpPacketType) override; + + void GetSendStreamDataCounters( + StreamDataCounters* rtp_counters, + StreamDataCounters* rtx_counters) const override; + + void RemoteRTCPSenderInfo(uint32_t* packet_count, + uint32_t* octet_count, + int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const override; + + // A snapshot of the most recent Report Block with additional data of + // interest to statistics. Used to implement RTCRemoteInboundRtpStreamStats. + // Within this list, the ReportBlockData::RTCPReportBlock::source_ssrc(), + // which is the SSRC of the corresponding outbound RTP stream, is unique. + std::vector<ReportBlockData> GetLatestReportBlockData() const override; + absl::optional<SenderReportStats> GetSenderReportStats() const override; + absl::optional<NonSenderRttStats> GetNonSenderRttStats() const override; + + // (REMB) Receiver Estimated Max Bitrate. + void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) override; + void UnsetRemb() override; + + void SetTmmbn(std::vector<rtcp::TmmbItem> bounding_set) override; + + size_t MaxRtpPacketSize() const override; + + void SetMaxRtpPacketSize(size_t max_packet_size) override; + + // (NACK) Negative acknowledgment part. + + // Send a Negative acknowledgment packet. + // TODO(philipel): Deprecate SendNACK and use SendNack instead. + int32_t SendNACK(const uint16_t* nack_list, uint16_t size) override; + + void SendNack(const std::vector<uint16_t>& sequence_numbers) override; + + // Store the sent packets, needed to answer to a negative acknowledgment + // requests. + void SetStorePacketsStatus(bool enable, uint16_t number_to_store) override; + + void SendCombinedRtcpPacket( + std::vector<std::unique_ptr<rtcp::RtcpPacket>> rtcp_packets) override; + + // Video part. + int32_t SendLossNotification(uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) override; + + RtpSendRates GetSendRates() const override; + + void OnReceivedNack( + const std::vector<uint16_t>& nack_sequence_numbers) override; + void OnReceivedRtcpReportBlocks( + const ReportBlockList& report_blocks) override; + void OnRequestSendReport() override; + + void SetVideoBitrateAllocation( + const VideoBitrateAllocation& bitrate) override; + + RTPSender* RtpSender() override; + const RTPSender* RtpSender() const override; + + private: + FRIEND_TEST_ALL_PREFIXES(RtpRtcpImpl2Test, Rtt); + FRIEND_TEST_ALL_PREFIXES(RtpRtcpImpl2Test, RttForReceiverOnly); + + struct RtpSenderContext { + explicit RtpSenderContext(const RtpRtcpInterface::Configuration& config); + // Storage of packets, for retransmissions and padding, if applicable. + RtpPacketHistory packet_history; + SequenceChecker sequencing_checker; + // Handles sequence number assignment and padding timestamp generation. + PacketSequencer sequencer RTC_GUARDED_BY(sequencing_checker); + // Handles final time timestamping/stats/etc and handover to Transport. + RtpSenderEgress packet_sender; + // If no paced sender configured, this class will be used to pass packets + // from `packet_generator_` to `packet_sender_`. + RtpSenderEgress::NonPacedPacketSender non_paced_sender; + // Handles creation of RTP packets to be sent. + RTPSender packet_generator; + }; + + void set_rtt_ms(int64_t rtt_ms); + int64_t rtt_ms() const; + + bool TimeToSendFullNackList(int64_t now) const; + + // Called on a timer, once a second, on the worker_queue_, to update the RTT, + // check if we need to send RTCP report, send TMMBR updates and fire events. + void PeriodicUpdate(); + + // Returns true if the module is configured to store packets. + bool StorePackets() const; + + // Used from RtcpSenderMediator to maybe send rtcp. + void MaybeSendRtcp() RTC_RUN_ON(worker_queue_); + + // Called when `rtcp_sender_` informs of the next RTCP instant. The method may + // be called on various sequences, and is called under a RTCPSenderLock. + void ScheduleRtcpSendEvaluation(TimeDelta duration); + + // Helper method combating too early delayed calls from task queues. + // TODO(bugs.webrtc.org/12889): Consider removing this function when the issue + // is resolved. + void MaybeSendRtcpAtOrAfterTimestamp(Timestamp execution_time) + RTC_RUN_ON(worker_queue_); + + // Schedules a call to MaybeSendRtcpAtOrAfterTimestamp delayed by `duration`. + void ScheduleMaybeSendRtcpAtOrAfterTimestamp(Timestamp execution_time, + TimeDelta duration); + + TaskQueueBase* const worker_queue_; + RTC_NO_UNIQUE_ADDRESS SequenceChecker rtcp_thread_checker_; + + std::unique_ptr<RtpSenderContext> rtp_sender_; + RTCPSender rtcp_sender_; + RTCPReceiver rtcp_receiver_; + + Clock* const clock_; + + uint16_t packet_overhead_; + + // Send side + int64_t nack_last_time_sent_full_ms_; + uint16_t nack_last_seq_number_sent_; + + RtcpRttStats* const rtt_stats_; + RepeatingTaskHandle rtt_update_task_ RTC_GUARDED_BY(worker_queue_); + + // The processed RTT from RtcpRttStats. + mutable Mutex mutex_rtt_; + int64_t rtt_ms_ RTC_GUARDED_BY(mutex_rtt_); + + RTC_NO_UNIQUE_ADDRESS ScopedTaskSafety task_safety_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_RTCP_IMPL2_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc new file mode 100644 index 0000000000..918e075be8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc @@ -0,0 +1,1157 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_rtcp_impl2.h" + +#include <deque> +#include <map> +#include <memory> +#include <set> +#include <utility> + +#include "absl/types/optional.h" +#include "api/field_trials_registry.h" +#include "api/units/time_delta.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/nack.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "modules/rtp_rtcp/source/rtp_sender_video.h" +#include "rtc_base/logging.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/strings/string_builder.h" +#include "test/explicit_key_value_config.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" +#include "test/run_loop.h" +#include "test/time_controller/simulated_time_controller.h" + +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Gt; +using ::testing::Not; +using ::testing::Optional; +using ::testing::SizeIs; + +using webrtc::test::ExplicitKeyValueConfig; + +namespace webrtc { +namespace { +constexpr uint32_t kSenderSsrc = 0x12345; +constexpr uint32_t kReceiverSsrc = 0x23456; +constexpr uint32_t kRtxSenderSsrc = 0x12346; +constexpr TimeDelta kOneWayNetworkDelay = TimeDelta::Millis(100); +constexpr uint8_t kBaseLayerTid = 0; +constexpr uint8_t kHigherLayerTid = 1; +constexpr uint16_t kSequenceNumber = 100; +constexpr uint8_t kPayloadType = 100; +constexpr uint8_t kRtxPayloadType = 98; +constexpr int kWidth = 320; +constexpr int kHeight = 100; +constexpr int kCaptureTimeMsToRtpTimestamp = 90; // 90 kHz clock. +constexpr TimeDelta kDefaultReportInterval = TimeDelta::Millis(1000); + +// RTP header extension ids. +enum : int { + kAbsoluteSendTimeExtensionId = 1, + kTransportSequenceNumberExtensionId, + kTransmissionOffsetExtensionId, +}; + +class RtcpRttStatsTestImpl : public RtcpRttStats { + public: + RtcpRttStatsTestImpl() : rtt_ms_(0) {} + ~RtcpRttStatsTestImpl() override = default; + + void OnRttUpdate(int64_t rtt_ms) override { rtt_ms_ = rtt_ms; } + int64_t LastProcessedRtt() const override { return rtt_ms_; } + int64_t rtt_ms_; +}; + +// TODO(bugs.webrtc.org/11581): remove inheritance once the ModuleRtpRtcpImpl2 +// Module/ProcessThread dependency is gone. +class SendTransport : public Transport, + public sim_time_impl::SimulatedSequenceRunner { + public: + SendTransport(TimeDelta delay, GlobalSimulatedTimeController* time_controller) + : receiver_(nullptr), + time_controller_(time_controller), + delay_(delay), + rtp_packets_sent_(0), + rtcp_packets_sent_(0), + last_packet_(&header_extensions_) { + time_controller_->Register(this); + } + + ~SendTransport() { time_controller_->Unregister(this); } + + void SetRtpRtcpModule(ModuleRtpRtcpImpl2* receiver) { receiver_ = receiver; } + void SimulateNetworkDelay(TimeDelta delay) { delay_ = delay; } + bool SendRtp(const uint8_t* data, + size_t len, + const PacketOptions& options) override { + EXPECT_TRUE(last_packet_.Parse(data, len)); + ++rtp_packets_sent_; + return true; + } + bool SendRtcp(const uint8_t* data, size_t len) override { + test::RtcpPacketParser parser; + parser.Parse(data, len); + last_nack_list_ = parser.nack()->packet_ids(); + Timestamp current_time = time_controller_->GetClock()->CurrentTime(); + Timestamp delivery_time = current_time + delay_; + rtcp_packets_.push_back( + Packet{delivery_time, std::vector<uint8_t>(data, data + len)}); + ++rtcp_packets_sent_; + RunReady(current_time); + return true; + } + + // sim_time_impl::SimulatedSequenceRunner + Timestamp GetNextRunTime() const override { + if (!rtcp_packets_.empty()) + return rtcp_packets_.front().send_time; + return Timestamp::PlusInfinity(); + } + void RunReady(Timestamp at_time) override { + while (!rtcp_packets_.empty() && + rtcp_packets_.front().send_time <= at_time) { + Packet packet = std::move(rtcp_packets_.front()); + rtcp_packets_.pop_front(); + EXPECT_TRUE(receiver_); + receiver_->IncomingRtcpPacket(packet.data.data(), packet.data.size()); + } + } + TaskQueueBase* GetAsTaskQueue() override { + return reinterpret_cast<TaskQueueBase*>(this); + } + + size_t NumRtcpSent() { return rtcp_packets_sent_; } + + ModuleRtpRtcpImpl2* receiver_; + GlobalSimulatedTimeController* const time_controller_; + TimeDelta delay_; + int rtp_packets_sent_; + size_t rtcp_packets_sent_; + std::vector<uint16_t> last_nack_list_; + RtpHeaderExtensionMap header_extensions_; + RtpPacketReceived last_packet_; + struct Packet { + Timestamp send_time; + std::vector<uint8_t> data; + }; + std::deque<Packet> rtcp_packets_; +}; + +class RtpRtcpModule : public RtcpPacketTypeCounterObserver, + public SendPacketObserver { + public: + struct SentPacket { + SentPacket(uint16_t packet_id, int64_t capture_time_ms, uint32_t ssrc) + : packet_id(packet_id), capture_time_ms(capture_time_ms), ssrc(ssrc) {} + uint16_t packet_id; + int64_t capture_time_ms; + uint32_t ssrc; + }; + + RtpRtcpModule(GlobalSimulatedTimeController* time_controller, + bool is_sender, + const FieldTrialsRegistry& trials) + : time_controller_(time_controller), + is_sender_(is_sender), + trials_(trials), + receive_statistics_( + ReceiveStatistics::Create(time_controller->GetClock())), + transport_(kOneWayNetworkDelay, time_controller) { + CreateModuleImpl(); + } + + TimeController* const time_controller_; + const bool is_sender_; + const FieldTrialsRegistry& trials_; + RtcpPacketTypeCounter packets_sent_; + RtcpPacketTypeCounter packets_received_; + std::unique_ptr<ReceiveStatistics> receive_statistics_; + SendTransport transport_; + RtcpRttStatsTestImpl rtt_stats_; + std::unique_ptr<ModuleRtpRtcpImpl2> impl_; + + void RtcpPacketTypesCounterUpdated( + uint32_t ssrc, + const RtcpPacketTypeCounter& packet_counter) override { + counter_map_[ssrc] = packet_counter; + } + + void OnSendPacket(uint16_t packet_id, + int64_t capture_time_ms, + uint32_t ssrc) override { + last_sent_packet_.emplace(packet_id, capture_time_ms, ssrc); + } + + absl::optional<SentPacket> last_sent_packet() const { + return last_sent_packet_; + } + + RtcpPacketTypeCounter RtcpSent() { + // RTCP counters for remote SSRC. + return counter_map_[is_sender_ ? kReceiverSsrc : kSenderSsrc]; + } + + RtcpPacketTypeCounter RtcpReceived() { + // Received RTCP stats for (own) local SSRC. + return counter_map_[impl_->SSRC()]; + } + int RtpSent() { return transport_.rtp_packets_sent_; } + uint16_t LastRtpSequenceNumber() { return last_packet().SequenceNumber(); } + std::vector<uint16_t> LastNackListSent() { + return transport_.last_nack_list_; + } + void SetRtcpReportIntervalAndReset(TimeDelta rtcp_report_interval) { + rtcp_report_interval_ = rtcp_report_interval; + CreateModuleImpl(); + } + const RtpPacketReceived& last_packet() { return transport_.last_packet_; } + void RegisterHeaderExtension(absl::string_view uri, int id) { + impl_->RegisterRtpHeaderExtension(uri, id); + transport_.header_extensions_.RegisterByUri(id, uri); + transport_.last_packet_.IdentifyExtensions(transport_.header_extensions_); + } + void ReinintWithFec(VideoFecGenerator* fec_generator) { + fec_generator_ = fec_generator; + CreateModuleImpl(); + } + + void CreateModuleImpl() { + RtpRtcpInterface::Configuration config; + config.audio = false; + config.clock = time_controller_->GetClock(); + config.outgoing_transport = &transport_; + config.receive_statistics = receive_statistics_.get(); + config.rtcp_packet_type_counter_observer = this; + config.rtt_stats = &rtt_stats_; + config.rtcp_report_interval_ms = rtcp_report_interval_.ms(); + config.local_media_ssrc = is_sender_ ? kSenderSsrc : kReceiverSsrc; + config.rtx_send_ssrc = + is_sender_ ? absl::make_optional(kRtxSenderSsrc) : absl::nullopt; + config.need_rtp_packet_infos = true; + config.non_sender_rtt_measurement = true; + config.field_trials = &trials_; + config.send_packet_observer = this; + config.fec_generator = fec_generator_; + impl_.reset(new ModuleRtpRtcpImpl2(config)); + impl_->SetRemoteSSRC(is_sender_ ? kReceiverSsrc : kSenderSsrc); + impl_->SetRTCPStatus(RtcpMode::kCompound); + } + + private: + std::map<uint32_t, RtcpPacketTypeCounter> counter_map_; + absl::optional<SentPacket> last_sent_packet_; + VideoFecGenerator* fec_generator_ = nullptr; + TimeDelta rtcp_report_interval_ = kDefaultReportInterval; +}; +} // namespace + +class RtpRtcpImpl2Test : public ::testing::Test { + protected: + RtpRtcpImpl2Test() + : time_controller_(Timestamp::Micros(133590000000000)), + field_trials_(""), + sender_(&time_controller_, + /*is_sender=*/true, + field_trials_), + receiver_(&time_controller_, + /*is_sender=*/false, + field_trials_) {} + + void SetUp() override { + // Send module. + EXPECT_EQ(0, sender_.impl_->SetSendingStatus(true)); + sender_.impl_->SetSendingMediaStatus(true); + sender_.impl_->SetSequenceNumber(kSequenceNumber); + sender_.impl_->SetStorePacketsStatus(true, 100); + + RTPSenderVideo::Config video_config; + video_config.clock = time_controller_.GetClock(); + video_config.rtp_sender = sender_.impl_->RtpSender(); + video_config.field_trials = &field_trials_; + sender_video_ = std::make_unique<RTPSenderVideo>(video_config); + + // Receive module. + EXPECT_EQ(0, receiver_.impl_->SetSendingStatus(false)); + receiver_.impl_->SetSendingMediaStatus(false); + // Transport settings. + sender_.transport_.SetRtpRtcpModule(receiver_.impl_.get()); + receiver_.transport_.SetRtpRtcpModule(sender_.impl_.get()); + } + + void AdvanceTime(TimeDelta duration) { + time_controller_.AdvanceTime(duration); + } + + void ReinitWithFec(VideoFecGenerator* fec_generator, + absl::optional<int> red_payload_type) { + sender_.ReinintWithFec(fec_generator); + EXPECT_EQ(0, sender_.impl_->SetSendingStatus(true)); + sender_.impl_->SetSendingMediaStatus(true); + sender_.impl_->SetSequenceNumber(kSequenceNumber); + sender_.impl_->SetStorePacketsStatus(true, 100); + receiver_.transport_.SetRtpRtcpModule(sender_.impl_.get()); + + RTPSenderVideo::Config video_config; + video_config.clock = time_controller_.GetClock(); + video_config.rtp_sender = sender_.impl_->RtpSender(); + video_config.field_trials = &field_trials_; + video_config.fec_overhead_bytes = fec_generator->MaxPacketOverhead(); + video_config.fec_type = fec_generator->GetFecType(); + video_config.red_payload_type = red_payload_type; + sender_video_ = std::make_unique<RTPSenderVideo>(video_config); + } + + GlobalSimulatedTimeController time_controller_; + test::ExplicitKeyValueConfig field_trials_; + RtpRtcpModule sender_; + std::unique_ptr<RTPSenderVideo> sender_video_; + RtpRtcpModule receiver_; + + bool SendFrame(const RtpRtcpModule* module, + RTPSenderVideo* sender, + uint8_t tid) { + int64_t now_ms = time_controller_.GetClock()->TimeInMilliseconds(); + return SendFrame( + module, sender, tid, + static_cast<uint32_t>(now_ms * kCaptureTimeMsToRtpTimestamp), now_ms); + } + + bool SendFrame(const RtpRtcpModule* module, + RTPSenderVideo* sender, + uint8_t tid, + uint32_t rtp_timestamp, + int64_t capture_time_ms) { + RTPVideoHeaderVP8 vp8_header = {}; + vp8_header.temporalIdx = tid; + RTPVideoHeader rtp_video_header; + rtp_video_header.frame_type = VideoFrameType::kVideoFrameKey; + rtp_video_header.width = kWidth; + rtp_video_header.height = kHeight; + rtp_video_header.rotation = kVideoRotation_0; + rtp_video_header.content_type = VideoContentType::UNSPECIFIED; + rtp_video_header.playout_delay = {-1, -1}; + rtp_video_header.is_first_packet_in_frame = true; + rtp_video_header.simulcastIdx = 0; + rtp_video_header.codec = kVideoCodecVP8; + rtp_video_header.video_type_header = vp8_header; + rtp_video_header.video_timing = {0u, 0u, 0u, 0u, 0u, 0u, false}; + + const uint8_t payload[100] = {0}; + bool success = module->impl_->OnSendingRtpFrame(0, 0, kPayloadType, true); + + success &= sender->SendVideo(kPayloadType, VideoCodecType::kVideoCodecVP8, + rtp_timestamp, capture_time_ms, payload, + rtp_video_header, 0); + return success; + } + + void IncomingRtcpNack(const RtpRtcpModule* module, uint16_t sequence_number) { + bool sender = module->impl_->SSRC() == kSenderSsrc; + rtcp::Nack nack; + uint16_t list[1]; + list[0] = sequence_number; + const uint16_t kListLength = sizeof(list) / sizeof(list[0]); + nack.SetSenderSsrc(sender ? kReceiverSsrc : kSenderSsrc); + nack.SetMediaSsrc(sender ? kSenderSsrc : kReceiverSsrc); + nack.SetPacketIds(list, kListLength); + rtc::Buffer packet = nack.Build(); + module->impl_->IncomingRtcpPacket(packet.data(), packet.size()); + } +}; + +TEST_F(RtpRtcpImpl2Test, RetransmitsAllLayers) { + // Send frames. + EXPECT_EQ(0, sender_.RtpSent()); + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), + kBaseLayerTid)); // kSequenceNumber + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), + kHigherLayerTid)); // kSequenceNumber + 1 + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), + kNoTemporalIdx)); // kSequenceNumber + 2 + EXPECT_EQ(3, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber + 2, sender_.LastRtpSequenceNumber()); + + // Min required delay until retransmit = 5 + RTT ms (RTT = 0). + AdvanceTime(TimeDelta::Millis(5)); + + // Frame with kBaseLayerTid re-sent. + IncomingRtcpNack(&sender_, kSequenceNumber); + EXPECT_EQ(4, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber, sender_.LastRtpSequenceNumber()); + // Frame with kHigherLayerTid re-sent. + IncomingRtcpNack(&sender_, kSequenceNumber + 1); + EXPECT_EQ(5, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber + 1, sender_.LastRtpSequenceNumber()); + // Frame with kNoTemporalIdx re-sent. + IncomingRtcpNack(&sender_, kSequenceNumber + 2); + EXPECT_EQ(6, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber + 2, sender_.LastRtpSequenceNumber()); +} + +TEST_F(RtpRtcpImpl2Test, Rtt) { + RtpPacketReceived packet; + packet.SetTimestamp(1); + packet.SetSequenceNumber(123); + packet.SetSsrc(kSenderSsrc); + packet.AllocatePayload(100 - 12); + receiver_.receive_statistics_->OnRtpPacket(packet); + + // Send Frame before sending an SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + // Sender module should send an SR. + EXPECT_EQ(0, sender_.impl_->SendRTCP(kRtcpReport)); + AdvanceTime(kOneWayNetworkDelay); + + // Receiver module should send a RR with a response to the last received SR. + EXPECT_EQ(0, receiver_.impl_->SendRTCP(kRtcpReport)); + AdvanceTime(kOneWayNetworkDelay); + + // Verify RTT. + int64_t rtt; + int64_t avg_rtt; + int64_t min_rtt; + int64_t max_rtt; + EXPECT_EQ( + 0, sender_.impl_->RTT(kReceiverSsrc, &rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), rtt, 1); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), avg_rtt, 1); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), min_rtt, 1); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), max_rtt, 1); + + // No RTT from other ssrc. + EXPECT_EQ(-1, sender_.impl_->RTT(kReceiverSsrc + 1, &rtt, &avg_rtt, &min_rtt, + &max_rtt)); + + // Verify RTT from rtt_stats config. + EXPECT_EQ(0, sender_.rtt_stats_.LastProcessedRtt()); + EXPECT_EQ(0, sender_.impl_->rtt_ms()); + AdvanceTime(TimeDelta::Millis(1000)); + + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), + sender_.rtt_stats_.LastProcessedRtt(), 1); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), sender_.impl_->rtt_ms(), 1); +} + +TEST_F(RtpRtcpImpl2Test, RttForReceiverOnly) { + // Receiver module should send a Receiver time reference report (RTRR). + EXPECT_EQ(0, receiver_.impl_->SendRTCP(kRtcpReport)); + + // Sender module should send a response to the last received RTRR (DLRR). + AdvanceTime(TimeDelta::Millis(1000)); + // Send Frame before sending a SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_EQ(0, sender_.impl_->SendRTCP(kRtcpReport)); + + // Verify RTT. + EXPECT_EQ(0, receiver_.rtt_stats_.LastProcessedRtt()); + EXPECT_EQ(0, receiver_.impl_->rtt_ms()); + AdvanceTime(TimeDelta::Millis(1000)); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), + receiver_.rtt_stats_.LastProcessedRtt(), 1); + EXPECT_NEAR(2 * kOneWayNetworkDelay.ms(), receiver_.impl_->rtt_ms(), 1); +} + +TEST_F(RtpRtcpImpl2Test, NoSrBeforeMedia) { + // Ignore fake transport delays in this test. + sender_.transport_.SimulateNetworkDelay(TimeDelta::Zero()); + receiver_.transport_.SimulateNetworkDelay(TimeDelta::Zero()); + + // Move ahead to the instant a rtcp is expected. + // Verify no SR is sent before media has been sent, RR should still be sent + // from the receiving module though. + AdvanceTime(kDefaultReportInterval / 2); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 0u); + EXPECT_EQ(receiver_.transport_.NumRtcpSent(), 1u); + + // RTCP should be triggered by the RTP send. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 1u); +} + +TEST_F(RtpRtcpImpl2Test, RtcpPacketTypeCounter_Nack) { + EXPECT_EQ(0U, sender_.RtcpReceived().nack_packets); + EXPECT_EQ(0U, receiver_.RtcpSent().nack_packets); + + // Receive module sends a NACK. + const uint16_t kNackLength = 1; + uint16_t nack_list[kNackLength] = {123}; + EXPECT_EQ(0, receiver_.impl_->SendNACK(nack_list, kNackLength)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_EQ(1U, receiver_.RtcpSent().nack_packets); + + // Send module receives the NACK. + EXPECT_EQ(1U, sender_.RtcpReceived().nack_packets); +} + +TEST_F(RtpRtcpImpl2Test, AddStreamDataCounters) { + StreamDataCounters rtp; + const int64_t kStartTimeMs = 1; + rtp.first_packet_time_ms = kStartTimeMs; + rtp.transmitted.packets = 1; + rtp.transmitted.payload_bytes = 1; + rtp.transmitted.header_bytes = 2; + rtp.transmitted.padding_bytes = 3; + EXPECT_EQ(rtp.transmitted.TotalBytes(), rtp.transmitted.payload_bytes + + rtp.transmitted.header_bytes + + rtp.transmitted.padding_bytes); + + StreamDataCounters rtp2; + rtp2.first_packet_time_ms = -1; + rtp2.transmitted.packets = 10; + rtp2.transmitted.payload_bytes = 10; + rtp2.retransmitted.header_bytes = 4; + rtp2.retransmitted.payload_bytes = 5; + rtp2.retransmitted.padding_bytes = 6; + rtp2.retransmitted.packets = 7; + rtp2.fec.packets = 8; + + StreamDataCounters sum = rtp; + sum.Add(rtp2); + EXPECT_EQ(kStartTimeMs, sum.first_packet_time_ms); + EXPECT_EQ(11U, sum.transmitted.packets); + EXPECT_EQ(11U, sum.transmitted.payload_bytes); + EXPECT_EQ(2U, sum.transmitted.header_bytes); + EXPECT_EQ(3U, sum.transmitted.padding_bytes); + EXPECT_EQ(4U, sum.retransmitted.header_bytes); + EXPECT_EQ(5U, sum.retransmitted.payload_bytes); + EXPECT_EQ(6U, sum.retransmitted.padding_bytes); + EXPECT_EQ(7U, sum.retransmitted.packets); + EXPECT_EQ(8U, sum.fec.packets); + EXPECT_EQ(sum.transmitted.TotalBytes(), + rtp.transmitted.TotalBytes() + rtp2.transmitted.TotalBytes()); + + StreamDataCounters rtp3; + rtp3.first_packet_time_ms = kStartTimeMs + 10; + sum.Add(rtp3); + EXPECT_EQ(kStartTimeMs, sum.first_packet_time_ms); // Holds oldest time. +} + +TEST_F(RtpRtcpImpl2Test, SendsInitialNackList) { + // Send module sends a NACK. + const uint16_t kNackLength = 1; + uint16_t nack_list[kNackLength] = {123}; + EXPECT_EQ(0U, sender_.RtcpSent().nack_packets); + // Send Frame before sending a compound RTCP that starts with SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123)); +} + +TEST_F(RtpRtcpImpl2Test, SendsExtendedNackList) { + // Send module sends a NACK. + const uint16_t kNackLength = 1; + uint16_t nack_list[kNackLength] = {123}; + EXPECT_EQ(0U, sender_.RtcpSent().nack_packets); + // Send Frame before sending a compound RTCP that starts with SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123)); + + // Same list not re-send. + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123)); + + // Only extended list sent. + const uint16_t kNackExtLength = 2; + uint16_t nack_list_ext[kNackExtLength] = {123, 124}; + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list_ext, kNackExtLength)); + EXPECT_EQ(2U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(124)); +} + +TEST_F(RtpRtcpImpl2Test, ReSendsNackListAfterRttMs) { + sender_.transport_.SimulateNetworkDelay(TimeDelta::Zero()); + // Send module sends a NACK. + const uint16_t kNackLength = 2; + uint16_t nack_list[kNackLength] = {123, 125}; + EXPECT_EQ(0U, sender_.RtcpSent().nack_packets); + // Send Frame before sending a compound RTCP that starts with SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123, 125)); + + // Same list not re-send, rtt interval has not passed. + const TimeDelta kStartupRtt = TimeDelta::Millis(100); + AdvanceTime(kStartupRtt); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + + // Rtt interval passed, full list sent. + AdvanceTime(TimeDelta::Millis(1)); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(2U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123, 125)); +} + +TEST_F(RtpRtcpImpl2Test, UniqueNackRequests) { + receiver_.transport_.SimulateNetworkDelay(TimeDelta::Zero()); + EXPECT_EQ(0U, receiver_.RtcpSent().nack_packets); + EXPECT_EQ(0U, receiver_.RtcpSent().nack_requests); + EXPECT_EQ(0U, receiver_.RtcpSent().unique_nack_requests); + EXPECT_EQ(0, receiver_.RtcpSent().UniqueNackRequestsInPercent()); + + // Receive module sends NACK request. + const uint16_t kNackLength = 4; + uint16_t nack_list[kNackLength] = {10, 11, 13, 18}; + EXPECT_EQ(0, receiver_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, receiver_.RtcpSent().nack_packets); + EXPECT_EQ(4U, receiver_.RtcpSent().nack_requests); + EXPECT_EQ(4U, receiver_.RtcpSent().unique_nack_requests); + EXPECT_THAT(receiver_.LastNackListSent(), ElementsAre(10, 11, 13, 18)); + + // Send module receives the request. + EXPECT_EQ(1U, sender_.RtcpReceived().nack_packets); + EXPECT_EQ(4U, sender_.RtcpReceived().nack_requests); + EXPECT_EQ(4U, sender_.RtcpReceived().unique_nack_requests); + EXPECT_EQ(100, sender_.RtcpReceived().UniqueNackRequestsInPercent()); + + // Receive module sends new request with duplicated packets. + const TimeDelta kStartupRtt = TimeDelta::Millis(100); + AdvanceTime(kStartupRtt + TimeDelta::Millis(1)); + const uint16_t kNackLength2 = 4; + uint16_t nack_list2[kNackLength2] = {11, 18, 20, 21}; + EXPECT_EQ(0, receiver_.impl_->SendNACK(nack_list2, kNackLength2)); + EXPECT_EQ(2U, receiver_.RtcpSent().nack_packets); + EXPECT_EQ(8U, receiver_.RtcpSent().nack_requests); + EXPECT_EQ(6U, receiver_.RtcpSent().unique_nack_requests); + EXPECT_THAT(receiver_.LastNackListSent(), ElementsAre(11, 18, 20, 21)); + + // Send module receives the request. + EXPECT_EQ(2U, sender_.RtcpReceived().nack_packets); + EXPECT_EQ(8U, sender_.RtcpReceived().nack_requests); + EXPECT_EQ(6U, sender_.RtcpReceived().unique_nack_requests); + EXPECT_EQ(75, sender_.RtcpReceived().UniqueNackRequestsInPercent()); +} + +TEST_F(RtpRtcpImpl2Test, ConfigurableRtcpReportInterval) { + const TimeDelta kVideoReportInterval = TimeDelta::Millis(3000); + + // Recreate sender impl with new configuration, and redo setup. + sender_.SetRtcpReportIntervalAndReset(kVideoReportInterval); + SetUp(); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + + // Initial state + EXPECT_EQ(0u, sender_.transport_.NumRtcpSent()); + + // Move ahead to the last ms before a rtcp is expected, no action. + AdvanceTime(kVideoReportInterval / 2 - TimeDelta::Millis(1)); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 0u); + + // Move ahead to the first rtcp. Send RTCP. + AdvanceTime(TimeDelta::Millis(1)); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 1u); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + + // Move ahead to the last possible second before second rtcp is expected. + AdvanceTime(kVideoReportInterval * 1 / 2 - TimeDelta::Millis(1)); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 1u); + + // Move ahead into the range of second rtcp, the second rtcp may be sent. + AdvanceTime(TimeDelta::Millis(1)); + EXPECT_GE(sender_.transport_.NumRtcpSent(), 1u); + + AdvanceTime(kVideoReportInterval / 2); + EXPECT_GE(sender_.transport_.NumRtcpSent(), 1u); + + // Move out the range of second rtcp, the second rtcp must have been sent. + AdvanceTime(kVideoReportInterval / 2); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 2u); +} + +TEST_F(RtpRtcpImpl2Test, RtpSenderEgressTimestampOffset) { + // RTP timestamp offset not explicitly set, default to random value. + uint16_t seqno = sender_.impl_->GetRtpState().sequence_number; + uint32_t media_rtp_ts = 1001; + uint32_t rtp_ts = media_rtp_ts + sender_.impl_->StartTimestamp(); + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid, rtp_ts, + /*capture_time_ms=*/0)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_THAT( + sender_.impl_->GetSentRtpPacketInfos(std::vector<uint16_t>{seqno}), + ElementsAre(Field(&RtpSequenceNumberMap::Info::timestamp, media_rtp_ts))); + + RtpState saved_rtp_state = sender_.impl_->GetRtpState(); + + // Change RTP timestamp offset. + sender_.impl_->SetStartTimestamp(2000); + + // Restores RtpState and make sure the old timestamp offset is in place. + sender_.impl_->SetRtpState(saved_rtp_state); + seqno = sender_.impl_->GetRtpState().sequence_number; + media_rtp_ts = 1031; + rtp_ts = media_rtp_ts + sender_.impl_->StartTimestamp(); + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid, rtp_ts, + /*capture_time_ms=*/0)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_THAT( + sender_.impl_->GetSentRtpPacketInfos(std::vector<uint16_t>{seqno}), + ElementsAre(Field(&RtpSequenceNumberMap::Info::timestamp, media_rtp_ts))); +} + +TEST_F(RtpRtcpImpl2Test, StoresPacketInfoForSentPackets) { + const uint32_t kStartTimestamp = 1u; + SetUp(); + sender_.impl_->SetStartTimestamp(kStartTimestamp); + + sender_.impl_->SetSequenceNumber(1); + + PacedPacketInfo pacing_info; + RtpPacketToSend packet(nullptr); + packet.set_packet_type(RtpPacketToSend::Type::kVideo); + packet.SetSsrc(kSenderSsrc); + + // Single-packet frame. + packet.SetTimestamp(1); + packet.set_first_packet_of_frame(true); + packet.SetMarker(true); + sender_.impl_->TrySendPacket(&packet, pacing_info); + AdvanceTime(TimeDelta::Millis(1)); + + std::vector<RtpSequenceNumberMap::Info> seqno_info = + sender_.impl_->GetSentRtpPacketInfos(std::vector<uint16_t>{1}); + + EXPECT_THAT(seqno_info, ElementsAre(RtpSequenceNumberMap::Info( + /*timestamp=*/1 - kStartTimestamp, + /*is_first=*/1, + /*is_last=*/1))); + + // Three-packet frame. + packet.SetTimestamp(2); + packet.set_first_packet_of_frame(true); + packet.SetMarker(false); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + packet.set_first_packet_of_frame(false); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + packet.SetMarker(true); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + AdvanceTime(TimeDelta::Millis(1)); + + seqno_info = + sender_.impl_->GetSentRtpPacketInfos(std::vector<uint16_t>{2, 3, 4}); + + EXPECT_THAT(seqno_info, ElementsAre(RtpSequenceNumberMap::Info( + /*timestamp=*/2 - kStartTimestamp, + /*is_first=*/1, + /*is_last=*/0), + RtpSequenceNumberMap::Info( + /*timestamp=*/2 - kStartTimestamp, + /*is_first=*/0, + /*is_last=*/0), + RtpSequenceNumberMap::Info( + /*timestamp=*/2 - kStartTimestamp, + /*is_first=*/0, + /*is_last=*/1))); +} + +// Checks that the sender report stats are not available if no RTCP SR was sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsNotAvailable) { + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks that the sender report stats are available if an RTCP SR was sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsAvailable) { + // Send a frame in order to send an SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Not(Eq(absl::nullopt))); +} + +// Checks that the sender report stats are not available if an RTCP SR with an +// unexpected SSRC is received. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsNotUpdatedWithUnexpectedSsrc) { + constexpr uint32_t kUnexpectedSenderSsrc = 0x87654321; + static_assert(kUnexpectedSenderSsrc != kSenderSsrc, ""); + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by an unexpected sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kUnexpectedSenderSsrc); + sr.SetNtp({/*seconds=*/1u, /*fractions=*/1u << 31}); + sr.SetPacketCount(123u); + sr.SetOctetCount(456u); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks the stats derived from the last received RTCP SR are set correctly. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsCheckStatsFromLastReport) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + const NtpTime ntp(/*seconds=*/1u, /*fractions=*/1u << 31); + constexpr uint32_t kPacketCount = 123u; + constexpr uint32_t kOctetCount = 456u; + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by the sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.SetNtp(ntp); + sr.SetPacketCount(kPacketCount); + sr.SetOctetCount(kOctetCount); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + + EXPECT_THAT( + receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::last_remote_timestamp, Eq(ntp)), + Field(&SenderReportStats::packets_sent, Eq(kPacketCount)), + Field(&SenderReportStats::bytes_sent, Eq(kOctetCount))))); +} + +// Checks that the sender report stats count equals the number of sent RTCP SRs. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsCount) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + // Send the first SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(1u)))); + // Send the second SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(2u)))); +} + +// Checks that the sender report stats include a valid arrival time if an RTCP +// SR was sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsArrivalTimestampSet) { + // Send a frame in order to send an SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + AdvanceTime(kOneWayNetworkDelay); + auto stats = receiver_.impl_->GetSenderReportStats(); + ASSERT_THAT(stats, Not(Eq(absl::nullopt))); + EXPECT_TRUE(stats->last_arrival_timestamp.Valid()); +} + +// Checks that the packet and byte counters from an RTCP SR are not zero once +// a frame is sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsPacketByteCounters) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Gt(0)); + // Advance time otherwise the RTCP SR report will not include any packets + // generated by `SendFrame()`. + AdvanceTime(TimeDelta::Millis(1)); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + AdvanceTime(kOneWayNetworkDelay); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::packets_sent, Gt(0u)), + Field(&SenderReportStats::bytes_sent, Gt(0u))))); +} + +TEST_F(RtpRtcpImpl2Test, SendingVideoAdvancesSequenceNumber) { + const uint16_t sequence_number = sender_.impl_->SequenceNumber(); + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Gt(0)); + EXPECT_EQ(sequence_number + 1, sender_.impl_->SequenceNumber()); +} + +TEST_F(RtpRtcpImpl2Test, SequenceNumberNotAdvancedWhenNotSending) { + const uint16_t sequence_number = sender_.impl_->SequenceNumber(); + sender_.impl_->SetSendingMediaStatus(false); + EXPECT_FALSE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Eq(0)); + EXPECT_EQ(sequence_number, sender_.impl_->SequenceNumber()); +} + +TEST_F(RtpRtcpImpl2Test, PaddingNotAllowedInMiddleOfFrame) { + constexpr size_t kPaddingSize = 100; + + // Can't send padding before media. + EXPECT_THAT(sender_.impl_->GeneratePadding(kPaddingSize), SizeIs(0u)); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + + // Padding is now ok. + EXPECT_THAT(sender_.impl_->GeneratePadding(kPaddingSize), SizeIs(Gt(0u))); + + // Send half a video frame. + PacedPacketInfo pacing_info; + std::unique_ptr<RtpPacketToSend> packet = + sender_.impl_->RtpSender()->AllocatePacket(); + packet->set_packet_type(RtpPacketToSend::Type::kVideo); + packet->set_first_packet_of_frame(true); + packet->SetMarker(false); // Marker false - not last packet of frame. + + EXPECT_TRUE(sender_.impl_->TrySendPacket(packet.get(), pacing_info)); + + // Padding not allowed in middle of frame. + EXPECT_THAT(sender_.impl_->GeneratePadding(kPaddingSize), SizeIs(0u)); + + packet = sender_.impl_->RtpSender()->AllocatePacket(); + packet->set_packet_type(RtpPacketToSend::Type::kVideo); + packet->set_first_packet_of_frame(true); + packet->SetMarker(true); + + EXPECT_TRUE(sender_.impl_->TrySendPacket(packet.get(), pacing_info)); + + // Padding is OK again. + EXPECT_THAT(sender_.impl_->GeneratePadding(kPaddingSize), SizeIs(Gt(0u))); +} + +TEST_F(RtpRtcpImpl2Test, PaddingTimestampMatchesMedia) { + constexpr size_t kPaddingSize = 100; + const uint32_t kTimestamp = 123; + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid, + kTimestamp, /*capture_time_ms=*/0)); + EXPECT_EQ(sender_.last_packet().Timestamp(), kTimestamp); + uint16_t media_seq = sender_.last_packet().SequenceNumber(); + + // Generate and send padding. + auto padding = sender_.impl_->GeneratePadding(kPaddingSize); + ASSERT_FALSE(padding.empty()); + for (auto& packet : padding) { + sender_.impl_->TrySendPacket(packet.get(), PacedPacketInfo()); + } + + // Verify we sent a new packet, but with the same timestamp. + EXPECT_NE(sender_.last_packet().SequenceNumber(), media_seq); + EXPECT_EQ(sender_.last_packet().Timestamp(), kTimestamp); +} + +TEST_F(RtpRtcpImpl2Test, AssignsTransportSequenceNumber) { + sender_.RegisterHeaderExtension(TransportSequenceNumber::Uri(), + kTransportSequenceNumberExtensionId); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + uint16_t first_transport_seq = 0; + EXPECT_TRUE(sender_.last_packet().GetExtension<TransportSequenceNumber>( + &first_transport_seq)); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + uint16_t second_transport_seq = 0; + EXPECT_TRUE(sender_.last_packet().GetExtension<TransportSequenceNumber>( + &second_transport_seq)); + + EXPECT_EQ(first_transport_seq + 1, second_transport_seq); +} + +TEST_F(RtpRtcpImpl2Test, AssignsAbsoluteSendTime) { + sender_.RegisterHeaderExtension(AbsoluteSendTime::Uri(), + kAbsoluteSendTimeExtensionId); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_NE(sender_.last_packet().GetExtension<AbsoluteSendTime>(), 0u); +} + +TEST_F(RtpRtcpImpl2Test, AssignsTransmissionTimeOffset) { + sender_.RegisterHeaderExtension(TransmissionOffset::Uri(), + kTransmissionOffsetExtensionId); + + constexpr TimeDelta kOffset = TimeDelta::Millis(100); + // Transmission offset is calculated from difference between capture time + // and send time. + int64_t capture_time_ms = time_controller_.GetClock()->TimeInMilliseconds(); + time_controller_.AdvanceTime(kOffset); + + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid, + /*timestamp=*/0, capture_time_ms)); + EXPECT_EQ(sender_.last_packet().GetExtension<TransmissionOffset>(), + kOffset.ms() * kCaptureTimeMsToRtpTimestamp); +} + +TEST_F(RtpRtcpImpl2Test, PropagatesSentPacketInfo) { + sender_.RegisterHeaderExtension(TransportSequenceNumber::Uri(), + kTransportSequenceNumberExtensionId); + int64_t now_ms = time_controller_.GetClock()->TimeInMilliseconds(); + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + EXPECT_THAT( + sender_.last_sent_packet(), + Optional( + AllOf(Field(&RtpRtcpModule::SentPacket::packet_id, + Eq(sender_.last_packet() + .GetExtension<TransportSequenceNumber>())), + Field(&RtpRtcpModule::SentPacket::capture_time_ms, Eq(now_ms)), + Field(&RtpRtcpModule::SentPacket::ssrc, Eq(kSenderSsrc))))); +} + +TEST_F(RtpRtcpImpl2Test, GeneratesFlexfec) { + constexpr int kFlexfecPayloadType = 118; + constexpr uint32_t kFlexfecSsrc = 17; + const char kNoMid[] = ""; + const std::vector<RtpExtension> kNoRtpExtensions; + const std::vector<RtpExtensionSize> kNoRtpExtensionSizes; + + // Make sure FlexFec sequence numbers start at a different point than media. + const uint16_t fec_start_seq = sender_.impl_->SequenceNumber() + 100; + RtpState start_state; + start_state.sequence_number = fec_start_seq; + FlexfecSender flexfec_sender(kFlexfecPayloadType, kFlexfecSsrc, kSenderSsrc, + kNoMid, kNoRtpExtensions, kNoRtpExtensionSizes, + &start_state, time_controller_.GetClock()); + ReinitWithFec(&flexfec_sender, /*red_payload_type=*/absl::nullopt); + + // Parameters selected to generate a single FEC packet per media packet. + FecProtectionParams params; + params.fec_rate = 15; + params.max_fec_frames = 1; + params.fec_mask_type = kFecMaskRandom; + sender_.impl_->SetFecProtectionParams(params, params); + + // Send a one packet frame, expect one media packet and one FEC packet. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Eq(2)); + + const RtpPacketReceived& fec_packet = sender_.last_packet(); + EXPECT_EQ(fec_packet.SequenceNumber(), fec_start_seq); + EXPECT_EQ(fec_packet.Ssrc(), kFlexfecSsrc); + EXPECT_EQ(fec_packet.PayloadType(), kFlexfecPayloadType); +} + +TEST_F(RtpRtcpImpl2Test, GeneratesUlpfec) { + constexpr int kUlpfecPayloadType = 118; + constexpr int kRedPayloadType = 119; + UlpfecGenerator ulpfec_sender(kRedPayloadType, kUlpfecPayloadType, + time_controller_.GetClock()); + ReinitWithFec(&ulpfec_sender, kRedPayloadType); + + // Parameters selected to generate a single FEC packet per media packet. + FecProtectionParams params; + params.fec_rate = 15; + params.max_fec_frames = 1; + params.fec_mask_type = kFecMaskRandom; + sender_.impl_->SetFecProtectionParams(params, params); + + // Send a one packet frame, expect one media packet and one FEC packet. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Eq(2)); + + // Ulpfec is sent on the media ssrc, sharing the sequene number series. + const RtpPacketReceived& fec_packet = sender_.last_packet(); + EXPECT_EQ(fec_packet.SequenceNumber(), kSequenceNumber + 1); + EXPECT_EQ(fec_packet.Ssrc(), kSenderSsrc); + // The packets are encapsulated in RED packets, check that and that the RED + // header (first byte of payload) indicates the desired FEC payload type. + EXPECT_EQ(fec_packet.PayloadType(), kRedPayloadType); + EXPECT_EQ(fec_packet.payload()[0], kUlpfecPayloadType); +} + +TEST_F(RtpRtcpImpl2Test, RtpStateReflectsCurrentState) { + // Verify that that each of the field of GetRtpState actually reflects + // the current state. + + // Current time will be used for `timestamp`, `capture_time_ms` and + // `last_timestamp_time_ms`. + const int64_t time_ms = time_controller_.GetClock()->TimeInMilliseconds(); + + // Use different than default sequence number to test `sequence_number`. + const uint16_t kSeq = kSequenceNumber + 123; + // Hard-coded value for `start_timestamp`. + const uint32_t kStartTimestamp = 3456; + const int64_t capture_time_ms = time_ms; + const uint32_t timestamp = capture_time_ms * kCaptureTimeMsToRtpTimestamp; + + sender_.impl_->SetSequenceNumber(kSeq - 1); + sender_.impl_->SetStartTimestamp(kStartTimestamp); + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + + // Simulate an RTCP receiver report in order to populate `ssrc_has_acked`. + RTCPReportBlock ack; + ack.source_ssrc = kSenderSsrc; + ack.extended_highest_sequence_number = kSeq; + sender_.impl_->OnReceivedRtcpReportBlocks({ack}); + + RtpState state = sender_.impl_->GetRtpState(); + EXPECT_EQ(state.sequence_number, kSeq); + EXPECT_EQ(state.start_timestamp, kStartTimestamp); + EXPECT_EQ(state.timestamp, timestamp); + EXPECT_EQ(state.capture_time_ms, capture_time_ms); + EXPECT_EQ(state.last_timestamp_time_ms, time_ms); + EXPECT_EQ(state.ssrc_has_acked, true); + + // Reset sender, advance time, restore state. Directly observing state + // is not feasible, so just verify returned state matches what we set. + sender_.CreateModuleImpl(); + time_controller_.AdvanceTime(TimeDelta::Millis(10)); + sender_.impl_->SetRtpState(state); + + state = sender_.impl_->GetRtpState(); + EXPECT_EQ(state.sequence_number, kSeq); + EXPECT_EQ(state.start_timestamp, kStartTimestamp); + EXPECT_EQ(state.timestamp, timestamp); + EXPECT_EQ(state.capture_time_ms, capture_time_ms); + EXPECT_EQ(state.last_timestamp_time_ms, time_ms); + EXPECT_EQ(state.ssrc_has_acked, true); +} + +TEST_F(RtpRtcpImpl2Test, RtxRtpStateReflectsCurrentState) { + // Enable RTX. + sender_.impl_->SetStorePacketsStatus(/*enable=*/true, /*number_to_store=*/10); + sender_.impl_->SetRtxSendPayloadType(kRtxPayloadType, kPayloadType); + sender_.impl_->SetRtxSendStatus(kRtxRetransmitted | kRtxRedundantPayloads); + + // `start_timestamp` is the only timestamp populate in the RTX state. + const uint32_t kStartTimestamp = 3456; + sender_.impl_->SetStartTimestamp(kStartTimestamp); + + // Send a frame and ask for a retransmit of the last packet. Capture the RTX + // packet in order to verify RTX sequence number. + EXPECT_TRUE(SendFrame(&sender_, sender_video_.get(), kBaseLayerTid)); + time_controller_.AdvanceTime(TimeDelta::Millis(5)); + sender_.impl_->OnReceivedNack( + std::vector<uint16_t>{sender_.transport_.last_packet_.SequenceNumber()}); + RtpPacketReceived& rtx_packet = sender_.transport_.last_packet_; + EXPECT_EQ(rtx_packet.Ssrc(), kRtxSenderSsrc); + + // Simulate an RTCP receiver report in order to populate `ssrc_has_acked`. + RTCPReportBlock ack; + ack.source_ssrc = kRtxSenderSsrc; + ack.extended_highest_sequence_number = rtx_packet.SequenceNumber(); + sender_.impl_->OnReceivedRtcpReportBlocks({ack}); + + RtpState rtp_state = sender_.impl_->GetRtpState(); + RtpState rtx_state = sender_.impl_->GetRtxState(); + EXPECT_EQ(rtx_state.start_timestamp, kStartTimestamp); + EXPECT_EQ(rtx_state.ssrc_has_acked, true); + EXPECT_EQ(rtx_state.sequence_number, rtx_packet.SequenceNumber() + 1); + + // Reset sender, advance time, restore state. Directly observing state + // is not feasible, so just verify returned state matches what we set. + // Needs SetRtpState() too in order to propagate start timestamp. + sender_.CreateModuleImpl(); + time_controller_.AdvanceTime(TimeDelta::Millis(10)); + sender_.impl_->SetRtpState(rtp_state); + sender_.impl_->SetRtxState(rtx_state); + + rtx_state = sender_.impl_->GetRtxState(); + EXPECT_EQ(rtx_state.start_timestamp, kStartTimestamp); + EXPECT_EQ(rtx_state.ssrc_has_acked, true); + EXPECT_EQ(rtx_state.sequence_number, rtx_packet.SequenceNumber() + 1); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc new file mode 100644 index 0000000000..96bef23a3a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc @@ -0,0 +1,705 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_rtcp_impl.h" + +#include <map> +#include <memory> +#include <set> + +#include "api/transport/field_trial_based_config.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtcp_packet.h" +#include "modules/rtp_rtcp/source/rtcp_packet/nack.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_sender_video.h" +#include "rtc_base/rate_limiter.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" + +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Gt; +using ::testing::Not; +using ::testing::Optional; + +namespace webrtc { +namespace { +const uint32_t kSenderSsrc = 0x12345; +const uint32_t kReceiverSsrc = 0x23456; +const int64_t kOneWayNetworkDelayMs = 100; +const uint8_t kBaseLayerTid = 0; +const uint8_t kHigherLayerTid = 1; +const uint16_t kSequenceNumber = 100; +const uint8_t kPayloadType = 100; +const int kWidth = 320; +const int kHeight = 100; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +class RtcpRttStatsTestImpl : public RtcpRttStats { + public: + RtcpRttStatsTestImpl() : rtt_ms_(0) {} + ~RtcpRttStatsTestImpl() override = default; + + void OnRttUpdate(int64_t rtt_ms) override { rtt_ms_ = rtt_ms; } + int64_t LastProcessedRtt() const override { return rtt_ms_; } + int64_t rtt_ms_; +}; + +class SendTransport : public Transport { + public: + SendTransport() + : receiver_(nullptr), + clock_(nullptr), + delay_ms_(0), + rtp_packets_sent_(0), + rtcp_packets_sent_(0) {} + + void SetRtpRtcpModule(ModuleRtpRtcpImpl* receiver) { receiver_ = receiver; } + void SimulateNetworkDelay(int64_t delay_ms, SimulatedClock* clock) { + clock_ = clock; + delay_ms_ = delay_ms; + } + bool SendRtp(const uint8_t* data, + size_t len, + const PacketOptions& options) override { + RtpPacket packet; + EXPECT_TRUE(packet.Parse(data, len)); + ++rtp_packets_sent_; + last_rtp_sequence_number_ = packet.SequenceNumber(); + return true; + } + bool SendRtcp(const uint8_t* data, size_t len) override { + test::RtcpPacketParser parser; + parser.Parse(data, len); + last_nack_list_ = parser.nack()->packet_ids(); + + if (clock_) { + clock_->AdvanceTimeMilliseconds(delay_ms_); + } + EXPECT_TRUE(receiver_); + receiver_->IncomingRtcpPacket(data, len); + ++rtcp_packets_sent_; + return true; + } + size_t NumRtcpSent() { return rtcp_packets_sent_; } + ModuleRtpRtcpImpl* receiver_; + SimulatedClock* clock_; + int64_t delay_ms_; + int rtp_packets_sent_; + size_t rtcp_packets_sent_; + uint16_t last_rtp_sequence_number_; + std::vector<uint16_t> last_nack_list_; +}; + +class RtpRtcpModule : public RtcpPacketTypeCounterObserver { + public: + RtpRtcpModule(SimulatedClock* clock, bool is_sender) + : is_sender_(is_sender), + receive_statistics_(ReceiveStatistics::Create(clock)), + clock_(clock) { + CreateModuleImpl(); + transport_.SimulateNetworkDelay(kOneWayNetworkDelayMs, clock); + } + + const bool is_sender_; + RtcpPacketTypeCounter packets_sent_; + RtcpPacketTypeCounter packets_received_; + std::unique_ptr<ReceiveStatistics> receive_statistics_; + SendTransport transport_; + RtcpRttStatsTestImpl rtt_stats_; + std::unique_ptr<ModuleRtpRtcpImpl> impl_; + int rtcp_report_interval_ms_ = 0; + + void RtcpPacketTypesCounterUpdated( + uint32_t ssrc, + const RtcpPacketTypeCounter& packet_counter) override { + counter_map_[ssrc] = packet_counter; + } + + RtcpPacketTypeCounter RtcpSent() { + // RTCP counters for remote SSRC. + return counter_map_[is_sender_ ? kReceiverSsrc : kSenderSsrc]; + } + + RtcpPacketTypeCounter RtcpReceived() { + // Received RTCP stats for (own) local SSRC. + return counter_map_[impl_->SSRC()]; + } + int RtpSent() { return transport_.rtp_packets_sent_; } + uint16_t LastRtpSequenceNumber() { + return transport_.last_rtp_sequence_number_; + } + std::vector<uint16_t> LastNackListSent() { + return transport_.last_nack_list_; + } + void SetRtcpReportIntervalAndReset(int rtcp_report_interval_ms) { + rtcp_report_interval_ms_ = rtcp_report_interval_ms; + CreateModuleImpl(); + } + + private: + void CreateModuleImpl() { + RtpRtcpInterface::Configuration config; + config.audio = false; + config.clock = clock_; + config.outgoing_transport = &transport_; + config.receive_statistics = receive_statistics_.get(); + config.rtcp_packet_type_counter_observer = this; + config.rtt_stats = &rtt_stats_; + config.rtcp_report_interval_ms = rtcp_report_interval_ms_; + config.local_media_ssrc = is_sender_ ? kSenderSsrc : kReceiverSsrc; + config.need_rtp_packet_infos = true; + config.non_sender_rtt_measurement = true; + + impl_.reset(new ModuleRtpRtcpImpl(config)); + impl_->SetRemoteSSRC(is_sender_ ? kReceiverSsrc : kSenderSsrc); + impl_->SetRTCPStatus(RtcpMode::kCompound); + } + + SimulatedClock* const clock_; + std::map<uint32_t, RtcpPacketTypeCounter> counter_map_; +}; +} // namespace + +class RtpRtcpImplTest : public ::testing::Test { + protected: + RtpRtcpImplTest() + : clock_(133590000000000), + sender_(&clock_, /*is_sender=*/true), + receiver_(&clock_, /*is_sender=*/false) {} + + void SetUp() override { + // Send module. + EXPECT_EQ(0, sender_.impl_->SetSendingStatus(true)); + sender_.impl_->SetSendingMediaStatus(true); + sender_.impl_->SetSequenceNumber(kSequenceNumber); + sender_.impl_->SetStorePacketsStatus(true, 100); + + FieldTrialBasedConfig field_trials; + RTPSenderVideo::Config video_config; + video_config.clock = &clock_; + video_config.rtp_sender = sender_.impl_->RtpSender(); + video_config.field_trials = &field_trials; + sender_video_ = std::make_unique<RTPSenderVideo>(video_config); + + // Receive module. + EXPECT_EQ(0, receiver_.impl_->SetSendingStatus(false)); + receiver_.impl_->SetSendingMediaStatus(false); + // Transport settings. + sender_.transport_.SetRtpRtcpModule(receiver_.impl_.get()); + receiver_.transport_.SetRtpRtcpModule(sender_.impl_.get()); + } + + SimulatedClock clock_; + RtpRtcpModule sender_; + std::unique_ptr<RTPSenderVideo> sender_video_; + RtpRtcpModule receiver_; + + void SendFrame(const RtpRtcpModule* module, + RTPSenderVideo* sender, + uint8_t tid) { + RTPVideoHeaderVP8 vp8_header = {}; + vp8_header.temporalIdx = tid; + RTPVideoHeader rtp_video_header; + rtp_video_header.frame_type = VideoFrameType::kVideoFrameKey; + rtp_video_header.width = kWidth; + rtp_video_header.height = kHeight; + rtp_video_header.rotation = kVideoRotation_0; + rtp_video_header.content_type = VideoContentType::UNSPECIFIED; + rtp_video_header.playout_delay = {-1, -1}; + rtp_video_header.is_first_packet_in_frame = true; + rtp_video_header.simulcastIdx = 0; + rtp_video_header.codec = kVideoCodecVP8; + rtp_video_header.video_type_header = vp8_header; + rtp_video_header.video_timing = {0u, 0u, 0u, 0u, 0u, 0u, false}; + + const uint8_t payload[100] = {0}; + EXPECT_TRUE(module->impl_->OnSendingRtpFrame(0, 0, kPayloadType, true)); + EXPECT_TRUE(sender->SendVideo(kPayloadType, VideoCodecType::kVideoCodecVP8, + 0, 0, payload, rtp_video_header, 0)); + } + + void IncomingRtcpNack(const RtpRtcpModule* module, uint16_t sequence_number) { + bool sender = module->impl_->SSRC() == kSenderSsrc; + rtcp::Nack nack; + uint16_t list[1]; + list[0] = sequence_number; + const uint16_t kListLength = sizeof(list) / sizeof(list[0]); + nack.SetSenderSsrc(sender ? kReceiverSsrc : kSenderSsrc); + nack.SetMediaSsrc(sender ? kSenderSsrc : kReceiverSsrc); + nack.SetPacketIds(list, kListLength); + rtc::Buffer packet = nack.Build(); + module->impl_->IncomingRtcpPacket(packet.data(), packet.size()); + } +}; + +TEST_F(RtpRtcpImplTest, RetransmitsAllLayers) { + // Send frames. + EXPECT_EQ(0, sender_.RtpSent()); + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); // kSequenceNumber + SendFrame(&sender_, sender_video_.get(), + kHigherLayerTid); // kSequenceNumber + 1 + SendFrame(&sender_, sender_video_.get(), + kNoTemporalIdx); // kSequenceNumber + 2 + EXPECT_EQ(3, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber + 2, sender_.LastRtpSequenceNumber()); + + // Min required delay until retransmit = 5 + RTT ms (RTT = 0). + clock_.AdvanceTimeMilliseconds(5); + + // Frame with kBaseLayerTid re-sent. + IncomingRtcpNack(&sender_, kSequenceNumber); + EXPECT_EQ(4, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber, sender_.LastRtpSequenceNumber()); + // Frame with kHigherLayerTid re-sent. + IncomingRtcpNack(&sender_, kSequenceNumber + 1); + EXPECT_EQ(5, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber + 1, sender_.LastRtpSequenceNumber()); + // Frame with kNoTemporalIdx re-sent. + IncomingRtcpNack(&sender_, kSequenceNumber + 2); + EXPECT_EQ(6, sender_.RtpSent()); + EXPECT_EQ(kSequenceNumber + 2, sender_.LastRtpSequenceNumber()); +} + +TEST_F(RtpRtcpImplTest, Rtt) { + RtpPacketReceived packet; + packet.SetTimestamp(1); + packet.SetSequenceNumber(123); + packet.SetSsrc(kSenderSsrc); + packet.AllocatePayload(100 - 12); + receiver_.receive_statistics_->OnRtpPacket(packet); + + // Send Frame before sending an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Sender module should send an SR. + EXPECT_EQ(0, sender_.impl_->SendRTCP(kRtcpReport)); + + // Receiver module should send a RR with a response to the last received SR. + clock_.AdvanceTimeMilliseconds(1000); + EXPECT_EQ(0, receiver_.impl_->SendRTCP(kRtcpReport)); + + // Verify RTT. + int64_t rtt; + int64_t avg_rtt; + int64_t min_rtt; + int64_t max_rtt; + EXPECT_EQ( + 0, sender_.impl_->RTT(kReceiverSsrc, &rtt, &avg_rtt, &min_rtt, &max_rtt)); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, rtt, 1); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, avg_rtt, 1); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, min_rtt, 1); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, max_rtt, 1); + + // No RTT from other ssrc. + EXPECT_EQ(-1, sender_.impl_->RTT(kReceiverSsrc + 1, &rtt, &avg_rtt, &min_rtt, + &max_rtt)); + + // Verify RTT from rtt_stats config. + EXPECT_EQ(0, sender_.rtt_stats_.LastProcessedRtt()); + EXPECT_EQ(0, sender_.impl_->rtt_ms()); + sender_.impl_->Process(); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, sender_.rtt_stats_.LastProcessedRtt(), + 1); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, sender_.impl_->rtt_ms(), 1); +} + +TEST_F(RtpRtcpImplTest, RttForReceiverOnly) { + // Receiver module should send a Receiver time reference report (RTRR). + EXPECT_EQ(0, receiver_.impl_->SendRTCP(kRtcpReport)); + + // Sender module should send a response to the last received RTRR (DLRR). + clock_.AdvanceTimeMilliseconds(1000); + // Send Frame before sending a SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + EXPECT_EQ(0, sender_.impl_->SendRTCP(kRtcpReport)); + + // Verify RTT. + EXPECT_EQ(0, receiver_.rtt_stats_.LastProcessedRtt()); + EXPECT_EQ(0, receiver_.impl_->rtt_ms()); + receiver_.impl_->Process(); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, + receiver_.rtt_stats_.LastProcessedRtt(), 1); + EXPECT_NEAR(2 * kOneWayNetworkDelayMs, receiver_.impl_->rtt_ms(), 1); +} + +TEST_F(RtpRtcpImplTest, NoSrBeforeMedia) { + // Ignore fake transport delays in this test. + sender_.transport_.SimulateNetworkDelay(0, &clock_); + receiver_.transport_.SimulateNetworkDelay(0, &clock_); + + sender_.impl_->Process(); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 0u); + + // Verify no SR is sent before media has been sent, RR should still be sent + // from the receiving module though. + clock_.AdvanceTimeMilliseconds(2000); + sender_.impl_->Process(); + receiver_.impl_->Process(); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 0u); + EXPECT_EQ(receiver_.transport_.NumRtcpSent(), 1u); + + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 1u); +} + +TEST_F(RtpRtcpImplTest, RtcpPacketTypeCounter_Nack) { + EXPECT_EQ(0U, sender_.RtcpReceived().nack_packets); + EXPECT_EQ(0U, receiver_.RtcpSent().nack_packets); + + // Receive module sends a NACK. + const uint16_t kNackLength = 1; + uint16_t nack_list[kNackLength] = {123}; + EXPECT_EQ(0, receiver_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, receiver_.RtcpSent().nack_packets); + + // Send module receives the NACK. + EXPECT_EQ(1U, sender_.RtcpReceived().nack_packets); +} + +TEST_F(RtpRtcpImplTest, AddStreamDataCounters) { + StreamDataCounters rtp; + rtp.transmitted.packets = 1; + rtp.transmitted.payload_bytes = 1; + rtp.transmitted.header_bytes = 2; + rtp.transmitted.padding_bytes = 3; + EXPECT_EQ(rtp.transmitted.TotalBytes(), rtp.transmitted.payload_bytes + + rtp.transmitted.header_bytes + + rtp.transmitted.padding_bytes); + + StreamDataCounters rtp2; + rtp2.transmitted.packets = 10; + rtp2.transmitted.payload_bytes = 10; + rtp2.retransmitted.header_bytes = 4; + rtp2.retransmitted.payload_bytes = 5; + rtp2.retransmitted.padding_bytes = 6; + rtp2.retransmitted.packets = 7; + rtp2.fec.packets = 8; + + StreamDataCounters sum = rtp; + sum.Add(rtp2); + EXPECT_EQ(11U, sum.transmitted.packets); + EXPECT_EQ(11U, sum.transmitted.payload_bytes); + EXPECT_EQ(2U, sum.transmitted.header_bytes); + EXPECT_EQ(3U, sum.transmitted.padding_bytes); + EXPECT_EQ(4U, sum.retransmitted.header_bytes); + EXPECT_EQ(5U, sum.retransmitted.payload_bytes); + EXPECT_EQ(6U, sum.retransmitted.padding_bytes); + EXPECT_EQ(7U, sum.retransmitted.packets); + EXPECT_EQ(8U, sum.fec.packets); + EXPECT_EQ(sum.transmitted.TotalBytes(), + rtp.transmitted.TotalBytes() + rtp2.transmitted.TotalBytes()); +} + +TEST_F(RtpRtcpImplTest, SendsInitialNackList) { + // Send module sends a NACK. + const uint16_t kNackLength = 1; + uint16_t nack_list[kNackLength] = {123}; + EXPECT_EQ(0U, sender_.RtcpSent().nack_packets); + // Send Frame before sending a compound RTCP that starts with SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123)); +} + +TEST_F(RtpRtcpImplTest, SendsExtendedNackList) { + // Send module sends a NACK. + const uint16_t kNackLength = 1; + uint16_t nack_list[kNackLength] = {123}; + EXPECT_EQ(0U, sender_.RtcpSent().nack_packets); + // Send Frame before sending a compound RTCP that starts with SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123)); + + // Same list not re-send. + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123)); + + // Only extended list sent. + const uint16_t kNackExtLength = 2; + uint16_t nack_list_ext[kNackExtLength] = {123, 124}; + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list_ext, kNackExtLength)); + EXPECT_EQ(2U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(124)); +} + +TEST_F(RtpRtcpImplTest, ReSendsNackListAfterRttMs) { + sender_.transport_.SimulateNetworkDelay(0, &clock_); + // Send module sends a NACK. + const uint16_t kNackLength = 2; + uint16_t nack_list[kNackLength] = {123, 125}; + EXPECT_EQ(0U, sender_.RtcpSent().nack_packets); + // Send Frame before sending a compound RTCP that starts with SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123, 125)); + + // Same list not re-send, rtt interval has not passed. + const int kStartupRttMs = 100; + clock_.AdvanceTimeMilliseconds(kStartupRttMs); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, sender_.RtcpSent().nack_packets); + + // Rtt interval passed, full list sent. + clock_.AdvanceTimeMilliseconds(1); + EXPECT_EQ(0, sender_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(2U, sender_.RtcpSent().nack_packets); + EXPECT_THAT(sender_.LastNackListSent(), ElementsAre(123, 125)); +} + +TEST_F(RtpRtcpImplTest, UniqueNackRequests) { + receiver_.transport_.SimulateNetworkDelay(0, &clock_); + EXPECT_EQ(0U, receiver_.RtcpSent().nack_packets); + EXPECT_EQ(0U, receiver_.RtcpSent().nack_requests); + EXPECT_EQ(0U, receiver_.RtcpSent().unique_nack_requests); + EXPECT_EQ(0, receiver_.RtcpSent().UniqueNackRequestsInPercent()); + + // Receive module sends NACK request. + const uint16_t kNackLength = 4; + uint16_t nack_list[kNackLength] = {10, 11, 13, 18}; + EXPECT_EQ(0, receiver_.impl_->SendNACK(nack_list, kNackLength)); + EXPECT_EQ(1U, receiver_.RtcpSent().nack_packets); + EXPECT_EQ(4U, receiver_.RtcpSent().nack_requests); + EXPECT_EQ(4U, receiver_.RtcpSent().unique_nack_requests); + EXPECT_THAT(receiver_.LastNackListSent(), ElementsAre(10, 11, 13, 18)); + + // Send module receives the request. + EXPECT_EQ(1U, sender_.RtcpReceived().nack_packets); + EXPECT_EQ(4U, sender_.RtcpReceived().nack_requests); + EXPECT_EQ(4U, sender_.RtcpReceived().unique_nack_requests); + EXPECT_EQ(100, sender_.RtcpReceived().UniqueNackRequestsInPercent()); + + // Receive module sends new request with duplicated packets. + const int kStartupRttMs = 100; + clock_.AdvanceTimeMilliseconds(kStartupRttMs + 1); + const uint16_t kNackLength2 = 4; + uint16_t nack_list2[kNackLength2] = {11, 18, 20, 21}; + EXPECT_EQ(0, receiver_.impl_->SendNACK(nack_list2, kNackLength2)); + EXPECT_EQ(2U, receiver_.RtcpSent().nack_packets); + EXPECT_EQ(8U, receiver_.RtcpSent().nack_requests); + EXPECT_EQ(6U, receiver_.RtcpSent().unique_nack_requests); + EXPECT_THAT(receiver_.LastNackListSent(), ElementsAre(11, 18, 20, 21)); + + // Send module receives the request. + EXPECT_EQ(2U, sender_.RtcpReceived().nack_packets); + EXPECT_EQ(8U, sender_.RtcpReceived().nack_requests); + EXPECT_EQ(6U, sender_.RtcpReceived().unique_nack_requests); + EXPECT_EQ(75, sender_.RtcpReceived().UniqueNackRequestsInPercent()); +} + +TEST_F(RtpRtcpImplTest, ConfigurableRtcpReportInterval) { + const int kVideoReportInterval = 3000; + + // Recreate sender impl with new configuration, and redo setup. + sender_.SetRtcpReportIntervalAndReset(kVideoReportInterval); + SetUp(); + + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + + // Initial state + sender_.impl_->Process(); + EXPECT_EQ(0u, sender_.transport_.NumRtcpSent()); + + // Move ahead to the last ms before a rtcp is expected, no action. + clock_.AdvanceTimeMilliseconds(kVideoReportInterval / 2 - 1); + sender_.impl_->Process(); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 0u); + + // Move ahead to the first rtcp. Send RTCP. + clock_.AdvanceTimeMilliseconds(1); + sender_.impl_->Process(); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 1u); + + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + + // Move ahead to the last possible second before second rtcp is expected. + clock_.AdvanceTimeMilliseconds(kVideoReportInterval * 1 / 2 - 1); + sender_.impl_->Process(); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 1u); + + // Move ahead into the range of second rtcp, the second rtcp may be sent. + clock_.AdvanceTimeMilliseconds(1); + sender_.impl_->Process(); + EXPECT_GE(sender_.transport_.NumRtcpSent(), 1u); + + clock_.AdvanceTimeMilliseconds(kVideoReportInterval / 2); + sender_.impl_->Process(); + EXPECT_GE(sender_.transport_.NumRtcpSent(), 1u); + + // Move out the range of second rtcp, the second rtcp must have been sent. + clock_.AdvanceTimeMilliseconds(kVideoReportInterval / 2); + sender_.impl_->Process(); + EXPECT_EQ(sender_.transport_.NumRtcpSent(), 2u); +} + +TEST_F(RtpRtcpImplTest, StoresPacketInfoForSentPackets) { + const uint32_t kStartTimestamp = 1u; + SetUp(); + sender_.impl_->SetStartTimestamp(kStartTimestamp); + sender_.impl_->SetSequenceNumber(1); + + PacedPacketInfo pacing_info; + RtpPacketToSend packet(nullptr); + packet.set_packet_type(RtpPacketToSend::Type::kVideo); + packet.SetSsrc(kSenderSsrc); + + // Single-packet frame. + packet.SetTimestamp(1); + packet.set_first_packet_of_frame(true); + packet.SetMarker(true); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + std::vector<RtpSequenceNumberMap::Info> seqno_info = + sender_.impl_->GetSentRtpPacketInfos(std::vector<uint16_t>{1}); + + EXPECT_THAT(seqno_info, ElementsAre(RtpSequenceNumberMap::Info( + /*timestamp=*/1 - kStartTimestamp, + /*is_first=*/1, + /*is_last=*/1))); + + // Three-packet frame. + packet.SetTimestamp(2); + packet.set_first_packet_of_frame(true); + packet.SetMarker(false); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + packet.set_first_packet_of_frame(false); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + packet.SetMarker(true); + sender_.impl_->TrySendPacket(&packet, pacing_info); + + seqno_info = + sender_.impl_->GetSentRtpPacketInfos(std::vector<uint16_t>{2, 3, 4}); + + EXPECT_THAT(seqno_info, ElementsAre(RtpSequenceNumberMap::Info( + /*timestamp=*/2 - kStartTimestamp, + /*is_first=*/1, + /*is_last=*/0), + RtpSequenceNumberMap::Info( + /*timestamp=*/2 - kStartTimestamp, + /*is_first=*/0, + /*is_last=*/0), + RtpSequenceNumberMap::Info( + /*timestamp=*/2 - kStartTimestamp, + /*is_first=*/0, + /*is_last=*/1))); +} + +// Checks that the remote sender stats are not available if no RTCP SR was sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsNotAvailable) { + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks that the remote sender stats are available if an RTCP SR was sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsAvailable) { + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Not(Eq(absl::nullopt))); +} + +// Checks that the remote sender stats are not available if an RTCP SR with an +// unexpected SSRC is received. +TEST_F(RtpRtcpImplTest, SenderReportStatsNotUpdatedWithUnexpectedSsrc) { + constexpr uint32_t kUnexpectedSenderSsrc = 0x87654321; + static_assert(kUnexpectedSenderSsrc != kSenderSsrc, ""); + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by an unexpected sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kUnexpectedSenderSsrc); + sr.SetNtp({/*seconds=*/1u, /*fractions=*/1u << 31}); + sr.SetPacketCount(123u); + sr.SetOctetCount(456u); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks the stats derived from the last received RTCP SR are set correctly. +TEST_F(RtpRtcpImplTest, SenderReportStatsCheckStatsFromLastReport) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + const NtpTime ntp(/*seconds=*/1u, /*fractions=*/1u << 31); + constexpr uint32_t kPacketCount = 123u; + constexpr uint32_t kOctetCount = 456u; + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by the sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.SetNtp(ntp); + sr.SetPacketCount(kPacketCount); + sr.SetOctetCount(kOctetCount); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + + EXPECT_THAT( + receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::last_remote_timestamp, Eq(ntp)), + Field(&SenderReportStats::packets_sent, Eq(kPacketCount)), + Field(&SenderReportStats::bytes_sent, Eq(kOctetCount))))); +} + +// Checks that the remote sender stats count equals the number of sent RTCP SRs. +TEST_F(RtpRtcpImplTest, SenderReportStatsCount) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send the first SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(1u)))); + // Send the second SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(2u)))); +} + +// Checks that the remote sender stats include a valid arrival time if an RTCP +// SR was sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsArrivalTimestampSet) { + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + auto stats = receiver_.impl_->GetSenderReportStats(); + ASSERT_THAT(stats, Not(Eq(absl::nullopt))); + EXPECT_TRUE(stats->last_arrival_timestamp.Valid()); +} + +// Checks that the packet and byte counters from an RTCP SR are not zero once +// a frame is sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsPacketByteCounters) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Gt(0)); + // Advance time otherwise the RTCP SR report will not include any packets + // generated by `SendFrame()`. + clock_.AdvanceTimeMilliseconds(1); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::packets_sent, Gt(0u)), + Field(&SenderReportStats::bytes_sent, Gt(0u))))); +} + +#pragma clang diagnostic pop + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_interface.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_interface.h new file mode 100644 index 0000000000..cb4a0a427f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_interface.h @@ -0,0 +1,468 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_RTCP_INTERFACE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_RTCP_INTERFACE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/field_trials_view.h" +#include "api/frame_transformer_interface.h" +#include "api/scoped_refptr.h" +#include "api/video/video_bitrate_allocation.h" +#include "modules/rtp_rtcp/include/receive_statistics.h" +#include "modules/rtp_rtcp/include/report_block_data.h" +#include "modules/rtp_rtcp/include/rtp_packet_sender.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_sequence_number_map.h" +#include "modules/rtp_rtcp/source/video_fec_generator.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { + +// Forward declarations. +class FrameEncryptorInterface; +class RateLimiter; +class RtcEventLog; +class RTPSender; +class Transport; +class VideoBitrateAllocationObserver; + +class RtpRtcpInterface : public RtcpFeedbackSenderInterface { + public: + struct Configuration { + Configuration() = default; + Configuration(Configuration&& rhs) = default; + + Configuration(const Configuration&) = delete; + Configuration& operator=(const Configuration&) = delete; + + // True for a audio version of the RTP/RTCP module object false will create + // a video version. + bool audio = false; + bool receiver_only = false; + + // The clock to use to read time. If nullptr then system clock will be used. + Clock* clock = nullptr; + + ReceiveStatisticsProvider* receive_statistics = nullptr; + + // Transport object that will be called when packets are ready to be sent + // out on the network. + Transport* outgoing_transport = nullptr; + + // Called when the receiver requests an intra frame. + RtcpIntraFrameObserver* intra_frame_callback = nullptr; + + // Called when the receiver sends a loss notification. + RtcpLossNotificationObserver* rtcp_loss_notification_observer = nullptr; + + // Called when we receive a changed estimate from the receiver of out + // stream. + RtcpBandwidthObserver* bandwidth_callback = nullptr; + + // Called when we receive a RTCP bye or timeout + RtcpEventObserver* rtcp_event_observer = nullptr; + + NetworkStateEstimateObserver* network_state_estimate_observer = nullptr; + TransportFeedbackObserver* transport_feedback_callback = nullptr; + VideoBitrateAllocationObserver* bitrate_allocation_observer = nullptr; + RtcpRttStats* rtt_stats = nullptr; + RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer = nullptr; + // Called on receipt of RTCP report block from remote side. + // TODO(bugs.webrtc.org/10679): Consider whether we want to use + // only getters or only callbacks. If we decide on getters, the + // ReportBlockDataObserver should also be removed in favor of + // GetLatestReportBlockData(). + RtcpCnameCallback* rtcp_cname_callback = nullptr; + ReportBlockDataObserver* report_block_data_observer = nullptr; + + // Spread any bursts of packets into smaller bursts to minimize packet loss. + RtpPacketSender* paced_sender = nullptr; + + // Generates FEC packets. + // TODO(sprang): Wire up to RtpSenderEgress. + VideoFecGenerator* fec_generator = nullptr; + + BitrateStatisticsObserver* send_bitrate_observer = nullptr; + SendSideDelayObserver* send_side_delay_observer = nullptr; + RtcEventLog* event_log = nullptr; + SendPacketObserver* send_packet_observer = nullptr; + RateLimiter* retransmission_rate_limiter = nullptr; + StreamDataCountersCallback* rtp_stats_callback = nullptr; + + int rtcp_report_interval_ms = 0; + + // Update network2 instead of pacer_exit field of video timing extension. + bool populate_network2_timestamp = false; + + rtc::scoped_refptr<FrameTransformerInterface> frame_transformer; + + // E2EE Custom Video Frame Encryption + FrameEncryptorInterface* frame_encryptor = nullptr; + // Require all outgoing frames to be encrypted with a FrameEncryptor. + bool require_frame_encryption = false; + + // Corresponds to extmap-allow-mixed in SDP negotiation. + bool extmap_allow_mixed = false; + + // If true, the RTP sender will always annotate outgoing packets with + // MID and RID header extensions, if provided and negotiated. + // If false, the RTP sender will stop sending MID and RID header extensions, + // when it knows that the receiver is ready to demux based on SSRC. This is + // done by RTCP RR acking. + bool always_send_mid_and_rid = false; + + // If set, field trials are read from `field_trials`, otherwise + // defaults to webrtc::FieldTrialBasedConfig. + const FieldTrialsView* field_trials = nullptr; + + // SSRCs for media and retransmission, respectively. + // FlexFec SSRC is fetched from `flexfec_sender`. + uint32_t local_media_ssrc = 0; + absl::optional<uint32_t> rtx_send_ssrc; + + bool need_rtp_packet_infos = false; + + // If true, the RTP packet history will select RTX packets based on + // heuristics such as send time, retransmission count etc, in order to + // make padding potentially more useful. + // If false, the last packet will always be picked. This may reduce CPU + // overhead. + bool enable_rtx_padding_prioritization = true; + + // Estimate RTT as non-sender as described in + // https://tools.ietf.org/html/rfc3611#section-4.4 and #section-4.5 + bool non_sender_rtt_measurement = false; + + // If non-empty, sets the value for sending in the RID (and Repaired) RTP + // header extension. RIDs are used to identify an RTP stream if SSRCs are + // not negotiated. If the RID and Repaired RID extensions are not + // registered, the RID will not be sent. + std::string rid; + }; + + // Stats for RTCP sender reports (SR) for a specific SSRC. + // Refer to https://tools.ietf.org/html/rfc3550#section-6.4.1. + struct SenderReportStats { + // Arrival NTP timestamp for the last received RTCP SR. + NtpTime last_arrival_timestamp; + // Received (a.k.a., remote) NTP timestamp for the last received RTCP SR. + NtpTime last_remote_timestamp; + // Total number of RTP data packets transmitted by the sender since starting + // transmission up until the time this SR packet was generated. The count + // should be reset if the sender changes its SSRC identifier. + uint32_t packets_sent; + // Total number of payload octets (i.e., not including header or padding) + // transmitted in RTP data packets by the sender since starting transmission + // up until the time this SR packet was generated. The count should be reset + // if the sender changes its SSRC identifier. + uint64_t bytes_sent; + // Total number of RTCP SR blocks received. + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-reportssent. + uint64_t reports_count; + }; + // Stats about the non-sender SSRC, based on RTCP extended reports (XR). + // Refer to https://datatracker.ietf.org/doc/html/rfc3611#section-2. + struct NonSenderRttStats { + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime + absl::optional<TimeDelta> round_trip_time; + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-totalroundtriptime + TimeDelta total_round_trip_time = TimeDelta::Zero(); + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptimemeasurements + int round_trip_time_measurements = 0; + }; + + // ************************************************************************** + // Receiver functions + // ************************************************************************** + + virtual void IncomingRtcpPacket(const uint8_t* incoming_packet, + size_t incoming_packet_length) = 0; + + virtual void SetRemoteSSRC(uint32_t ssrc) = 0; + + // Called when the local ssrc changes (post initialization) for receive + // streams to match with send. Called on the packet receive thread/tq. + virtual void SetLocalSsrc(uint32_t ssrc) = 0; + + // ************************************************************************** + // Sender + // ************************************************************************** + + // Sets the maximum size of an RTP packet, including RTP headers. + virtual void SetMaxRtpPacketSize(size_t size) = 0; + + // Returns max RTP packet size. Takes into account RTP headers and + // FEC/ULP/RED overhead (when FEC is enabled). + virtual size_t MaxRtpPacketSize() const = 0; + + virtual void RegisterSendPayloadFrequency(int payload_type, + int payload_frequency) = 0; + + // Unregisters a send payload. + // `payload_type` - payload type of codec + // Returns -1 on failure else 0. + virtual int32_t DeRegisterSendPayload(int8_t payload_type) = 0; + + virtual void SetExtmapAllowMixed(bool extmap_allow_mixed) = 0; + + // Register extension by uri, triggers CHECK on falure. + virtual void RegisterRtpHeaderExtension(absl::string_view uri, int id) = 0; + + virtual void DeregisterSendRtpHeaderExtension(absl::string_view uri) = 0; + + // Returns true if RTP module is send media, and any of the extensions + // required for bandwidth estimation is registered. + virtual bool SupportsPadding() const = 0; + // Same as SupportsPadding(), but additionally requires that + // SetRtxSendStatus() has been called with the kRtxRedundantPayloads option + // enabled. + virtual bool SupportsRtxPayloadPadding() const = 0; + + // Returns start timestamp. + virtual uint32_t StartTimestamp() const = 0; + + // Sets start timestamp. Start timestamp is set to a random value if this + // function is never called. + virtual void SetStartTimestamp(uint32_t timestamp) = 0; + + // Returns SequenceNumber. + virtual uint16_t SequenceNumber() const = 0; + + // Sets SequenceNumber, default is a random number. + virtual void SetSequenceNumber(uint16_t seq) = 0; + + virtual void SetRtpState(const RtpState& rtp_state) = 0; + virtual void SetRtxState(const RtpState& rtp_state) = 0; + virtual RtpState GetRtpState() const = 0; + virtual RtpState GetRtxState() const = 0; + + // This can be used to enable/disable receive-side RTT. + virtual void SetNonSenderRttMeasurement(bool enabled) = 0; + + // Returns SSRC. + virtual uint32_t SSRC() const = 0; + + // Sets the value for sending in the MID RTP header extension. + // The MID RTP header extension should be registered for this to do anything. + // Once set, this value can not be changed or removed. + virtual void SetMid(absl::string_view mid) = 0; + + // Turns on/off sending RTX (RFC 4588). The modes can be set as a combination + // of values of the enumerator RtxMode. + virtual void SetRtxSendStatus(int modes) = 0; + + // Returns status of sending RTX (RFC 4588). The returned value can be + // a combination of values of the enumerator RtxMode. + virtual int RtxSendStatus() const = 0; + + // Returns the SSRC used for RTX if set, otherwise a nullopt. + virtual absl::optional<uint32_t> RtxSsrc() const = 0; + + // Sets the payload type to use when sending RTX packets. Note that this + // doesn't enable RTX, only the payload type is set. + virtual void SetRtxSendPayloadType(int payload_type, + int associated_payload_type) = 0; + + // Returns the FlexFEC SSRC, if there is one. + virtual absl::optional<uint32_t> FlexfecSsrc() const = 0; + + // Sets sending status. Sends kRtcpByeCode when going from true to false. + // Returns -1 on failure else 0. + virtual int32_t SetSendingStatus(bool sending) = 0; + + // Returns current sending status. + virtual bool Sending() const = 0; + + // Starts/Stops media packets. On by default. + virtual void SetSendingMediaStatus(bool sending) = 0; + + // Returns current media sending status. + virtual bool SendingMedia() const = 0; + + // Returns whether audio is configured (i.e. Configuration::audio = true). + virtual bool IsAudioConfigured() const = 0; + + // Indicate that the packets sent by this module should be counted towards the + // bitrate estimate since the stream participates in the bitrate allocation. + virtual void SetAsPartOfAllocation(bool part_of_allocation) = 0; + + // Returns bitrate sent (post-pacing) per packet type. + virtual RtpSendRates GetSendRates() const = 0; + + virtual RTPSender* RtpSender() = 0; + virtual const RTPSender* RtpSender() const = 0; + + // Record that a frame is about to be sent. Returns true on success, and false + // if the module isn't ready to send. + virtual bool OnSendingRtpFrame(uint32_t timestamp, + int64_t capture_time_ms, + int payload_type, + bool force_sender_report) = 0; + + // Try to send the provided packet. Returns true iff packet matches any of + // the SSRCs for this module (media/rtx/fec etc) and was forwarded to the + // transport. + virtual bool TrySendPacket(RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) = 0; + + // Update the FEC protection parameters to use for delta- and key-frames. + // Only used when deferred FEC is active. + virtual void SetFecProtectionParams( + const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) = 0; + + // If deferred FEC generation is enabled, this method should be called after + // calling TrySendPacket(). Any generated FEC packets will be removed and + // returned from the FEC generator. + virtual std::vector<std::unique_ptr<RtpPacketToSend>> FetchFecPackets() = 0; + + virtual void OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers) = 0; + + virtual void OnPacketsAcknowledged( + rtc::ArrayView<const uint16_t> sequence_numbers) = 0; + + virtual std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding( + size_t target_size_bytes) = 0; + + virtual std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const = 0; + + // Returns an expected per packet overhead representing the main RTP header, + // any CSRCs, and the registered header extensions that are expected on all + // packets (i.e. disregarding things like abs capture time which is only + // populated on a subset of packets, but counting MID/RID type extensions + // when we expect to send them). + virtual size_t ExpectedPerPacketOverhead() const = 0; + + // Access to packet state (e.g. sequence numbering) must only be access by + // one thread at a time. It may be only one thread, or a construction thread + // that calls SetRtpState() - handing over to a pacer thread that calls + // TrySendPacket() - and at teardown ownership is handed to a destruciton + // thread that calls GetRtpState(). + // This method is used to signal that "ownership" of the rtp state is being + // transferred to another thread. + virtual void OnPacketSendingThreadSwitched() = 0; + + // ************************************************************************** + // RTCP + // ************************************************************************** + + // Returns RTCP status. + virtual RtcpMode RTCP() const = 0; + + // Sets RTCP status i.e on(compound or non-compound)/off. + // `method` - RTCP method to use. + virtual void SetRTCPStatus(RtcpMode method) = 0; + + // Sets RTCP CName (i.e unique identifier). + // Returns -1 on failure else 0. + virtual int32_t SetCNAME(absl::string_view cname) = 0; + + // Returns remote NTP. + // Returns -1 on failure else 0. + virtual int32_t RemoteNTP(uint32_t* received_ntp_secs, + uint32_t* received_ntp_frac, + uint32_t* rtcp_arrival_time_secs, + uint32_t* rtcp_arrival_time_frac, + uint32_t* rtcp_timestamp) const = 0; + + // Returns current RTT (round-trip time) estimate. + // Returns -1 on failure else 0. + virtual int32_t RTT(uint32_t remote_ssrc, + int64_t* rtt, + int64_t* avg_rtt, + int64_t* min_rtt, + int64_t* max_rtt) const = 0; + + // Returns the estimated RTT, with fallback to a default value. + virtual int64_t ExpectedRetransmissionTimeMs() const = 0; + + // Forces a send of a RTCP packet. Periodic SR and RR are triggered via the + // process function. + // Returns -1 on failure else 0. + virtual int32_t SendRTCP(RTCPPacketType rtcp_packet_type) = 0; + + // Returns send statistics for the RTP and RTX stream. + virtual void GetSendStreamDataCounters( + StreamDataCounters* rtp_counters, + StreamDataCounters* rtx_counters) const = 0; + + + // Returns packet count, octet count, and timestamps from RTCP sender report. + virtual void RemoteRTCPSenderInfo(uint32_t* packet_count, + uint32_t* octet_count, + int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const = 0; + // A snapshot of Report Blocks with additional data of interest to statistics. + // Within this list, the sender-source SSRC pair is unique and per-pair the + // ReportBlockData represents the latest Report Block that was received for + // that pair. + virtual std::vector<ReportBlockData> GetLatestReportBlockData() const = 0; + // Returns stats based on the received RTCP SRs. + virtual absl::optional<SenderReportStats> GetSenderReportStats() const = 0; + // Returns non-sender RTT stats, based on DLRR. + virtual absl::optional<NonSenderRttStats> GetNonSenderRttStats() const = 0; + + // (REMB) Receiver Estimated Max Bitrate. + // Schedules sending REMB on next and following sender/receiver reports. + void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) override = 0; + // Stops sending REMB on next and following sender/receiver reports. + void UnsetRemb() override = 0; + + // (NACK) + + // Sends a Negative acknowledgement packet. + // Returns -1 on failure else 0. + // TODO(philipel): Deprecate this and start using SendNack instead, mostly + // because we want a function that actually send NACK for the specified + // packets. + virtual int32_t SendNACK(const uint16_t* nack_list, uint16_t size) = 0; + + // Sends NACK for the packets specified. + // Note: This assumes the caller keeps track of timing and doesn't rely on + // the RTP module to do this. + virtual void SendNack(const std::vector<uint16_t>& sequence_numbers) = 0; + + // Store the sent packets, needed to answer to a Negative acknowledgment + // requests. + virtual void SetStorePacketsStatus(bool enable, uint16_t numberToStore) = 0; + + virtual void SetVideoBitrateAllocation( + const VideoBitrateAllocation& bitrate) = 0; + + // ************************************************************************** + // Video + // ************************************************************************** + + // Requests new key frame. + // using PLI, https://tools.ietf.org/html/rfc4585#section-6.3.1.1 + void SendPictureLossIndication() { SendRTCP(kRtcpPli); } + // using FIR, https://tools.ietf.org/html/rfc5104#section-4.3.1.2 + void SendFullIntraRequest() { SendRTCP(kRtcpFir); } + + // Sends a LossNotification RTCP message. + // Returns -1 on failure else 0. + virtual int32_t SendLossNotification(uint16_t last_decoded_seq_num, + uint16_t last_received_seq_num, + bool decodability_flag, + bool buffering_allowed) = 0; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_RTCP_INTERFACE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.cc new file mode 100644 index 0000000000..d5e8bdcccb --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.cc @@ -0,0 +1,834 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_sender.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <string> +#include <utility> + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "api/array_view.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "modules/rtp_rtcp/include/rtp_cvo.h" +#include "modules/rtp_rtcp/source/byte_io.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_to_send.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/experiments/field_trial_parser.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_minmax.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +namespace { +// Max in the RFC 3550 is 255 bytes, we limit it to be modulus 32 for SRTP. +constexpr size_t kMaxPaddingLength = 224; +constexpr size_t kMinAudioPaddingLength = 50; +constexpr size_t kRtpHeaderLength = 12; + +// Min size needed to get payload padding from packet history. +constexpr int kMinPayloadPaddingBytes = 50; + +// Determines how much larger a payload padding packet may be, compared to the +// requested padding size. +constexpr double kMaxPaddingSizeFactor = 3.0; + +template <typename Extension> +constexpr RtpExtensionSize CreateExtensionSize() { + return {Extension::kId, Extension::kValueSizeBytes}; +} + +template <typename Extension> +constexpr RtpExtensionSize CreateMaxExtensionSize() { + return {Extension::kId, Extension::kMaxValueSizeBytes}; +} + +// Size info for header extensions that might be used in padding or FEC packets. +constexpr RtpExtensionSize kFecOrPaddingExtensionSizes[] = { + CreateExtensionSize<AbsoluteSendTime>(), + CreateExtensionSize<TransmissionOffset>(), + CreateExtensionSize<TransportSequenceNumber>(), + CreateExtensionSize<PlayoutDelayLimits>(), + CreateMaxExtensionSize<RtpMid>(), + CreateExtensionSize<VideoTimingExtension>(), +}; + +// Size info for header extensions that might be used in video packets. +constexpr RtpExtensionSize kVideoExtensionSizes[] = { + CreateExtensionSize<AbsoluteSendTime>(), + CreateExtensionSize<AbsoluteCaptureTimeExtension>(), + CreateExtensionSize<TransmissionOffset>(), + CreateExtensionSize<TransportSequenceNumber>(), + CreateExtensionSize<PlayoutDelayLimits>(), + CreateExtensionSize<VideoOrientation>(), + CreateExtensionSize<VideoContentTypeExtension>(), + CreateExtensionSize<VideoTimingExtension>(), + CreateMaxExtensionSize<RtpStreamId>(), + CreateMaxExtensionSize<RepairedRtpStreamId>(), + CreateMaxExtensionSize<RtpMid>(), + {RtpGenericFrameDescriptorExtension00::kId, + RtpGenericFrameDescriptorExtension00::kMaxSizeBytes}, +}; + +// Size info for header extensions that might be used in audio packets. +constexpr RtpExtensionSize kAudioExtensionSizes[] = { + CreateExtensionSize<AbsoluteSendTime>(), + CreateExtensionSize<AbsoluteCaptureTimeExtension>(), + CreateExtensionSize<AudioLevel>(), + CreateExtensionSize<InbandComfortNoiseExtension>(), + CreateExtensionSize<TransmissionOffset>(), + CreateExtensionSize<TransportSequenceNumber>(), + CreateMaxExtensionSize<RtpStreamId>(), + CreateMaxExtensionSize<RepairedRtpStreamId>(), + CreateMaxExtensionSize<RtpMid>(), +}; + +// Non-volatile extensions can be expected on all packets, if registered. +// Volatile ones, such as VideoContentTypeExtension which is only set on +// key-frames, are removed to simplify overhead calculations at the expense of +// some accuracy. +bool IsNonVolatile(RTPExtensionType type) { + switch (type) { + case kRtpExtensionTransmissionTimeOffset: + case kRtpExtensionAudioLevel: +#if !defined(WEBRTC_MOZILLA_BUILD) + case kRtpExtensionCsrcAudioLevel: +#endif + case kRtpExtensionAbsoluteSendTime: + case kRtpExtensionTransportSequenceNumber: + case kRtpExtensionTransportSequenceNumber02: + case kRtpExtensionRtpStreamId: + case kRtpExtensionRepairedRtpStreamId: + case kRtpExtensionMid: + case kRtpExtensionGenericFrameDescriptor: + case kRtpExtensionDependencyDescriptor: + return true; + case kRtpExtensionInbandComfortNoise: + case kRtpExtensionAbsoluteCaptureTime: + case kRtpExtensionVideoRotation: + case kRtpExtensionPlayoutDelay: + case kRtpExtensionVideoContentType: + case kRtpExtensionVideoLayersAllocation: + case kRtpExtensionVideoTiming: + case kRtpExtensionColorSpace: + case kRtpExtensionVideoFrameTrackingId: + return false; + case kRtpExtensionNone: + case kRtpExtensionNumberOfExtensions: + RTC_DCHECK_NOTREACHED(); + return false; +#if defined(WEBRTC_MOZILLA_BUILD) + case kRtpExtensionCsrcAudioLevel: + // TODO: Mozilla implement for CsrcAudioLevel + RTC_CHECK(false); + return false; +#endif + } + RTC_CHECK_NOTREACHED(); +} + +bool HasBweExtension(const RtpHeaderExtensionMap& extensions_map) { + return extensions_map.IsRegistered(kRtpExtensionTransportSequenceNumber) || + extensions_map.IsRegistered(kRtpExtensionTransportSequenceNumber02) || + extensions_map.IsRegistered(kRtpExtensionAbsoluteSendTime) || + extensions_map.IsRegistered(kRtpExtensionTransmissionTimeOffset); +} + +} // namespace + +RTPSender::RTPSender(const RtpRtcpInterface::Configuration& config, + RtpPacketHistory* packet_history, + RtpPacketSender* packet_sender) + : clock_(config.clock), + random_(clock_->TimeInMicroseconds()), + audio_configured_(config.audio), + ssrc_(config.local_media_ssrc), + rtx_ssrc_(config.rtx_send_ssrc), + flexfec_ssrc_(config.fec_generator ? config.fec_generator->FecSsrc() + : absl::nullopt), + packet_history_(packet_history), + paced_sender_(packet_sender), + sending_media_(true), // Default to sending media. + max_packet_size_(IP_PACKET_SIZE - 28), // Default is IP-v4/UDP. + rtp_header_extension_map_(config.extmap_allow_mixed), + // RTP variables + rid_(config.rid), + always_send_mid_and_rid_(config.always_send_mid_and_rid), + ssrc_has_acked_(false), + rtx_ssrc_has_acked_(false), + csrcs_(), + rtx_(kRtxOff), + supports_bwe_extension_(false), + retransmission_rate_limiter_(config.retransmission_rate_limiter) { + // This random initialization is not intended to be cryptographic strong. + timestamp_offset_ = random_.Rand<uint32_t>(); + + RTC_DCHECK(paced_sender_); + RTC_DCHECK(packet_history_); + RTC_DCHECK_LE(rid_.size(), RtpStreamId::kMaxValueSizeBytes); + + UpdateHeaderSizes(); +} + +RTPSender::~RTPSender() { + // TODO(tommi): Use a thread checker to ensure the object is created and + // deleted on the same thread. At the moment this isn't possible due to + // voe::ChannelOwner in voice engine. To reproduce, run: + // voe_auto_test --automated --gtest_filter=*MixManyChannelsForStressOpus + + // TODO(tommi,holmer): We don't grab locks in the dtor before accessing member + // variables but we grab them in all other methods. (what's the design?) + // Start documenting what thread we're on in what method so that it's easier + // to understand performance attributes and possibly remove locks. +} + +rtc::ArrayView<const RtpExtensionSize> RTPSender::FecExtensionSizes() { + return rtc::MakeArrayView(kFecOrPaddingExtensionSizes, + arraysize(kFecOrPaddingExtensionSizes)); +} + +rtc::ArrayView<const RtpExtensionSize> RTPSender::VideoExtensionSizes() { + return rtc::MakeArrayView(kVideoExtensionSizes, + arraysize(kVideoExtensionSizes)); +} + +rtc::ArrayView<const RtpExtensionSize> RTPSender::AudioExtensionSizes() { + return rtc::MakeArrayView(kAudioExtensionSizes, + arraysize(kAudioExtensionSizes)); +} + +void RTPSender::SetExtmapAllowMixed(bool extmap_allow_mixed) { + MutexLock lock(&send_mutex_); + rtp_header_extension_map_.SetExtmapAllowMixed(extmap_allow_mixed); +} + +bool RTPSender::RegisterRtpHeaderExtension(absl::string_view uri, int id) { + MutexLock lock(&send_mutex_); + bool registered = rtp_header_extension_map_.RegisterByUri(id, uri); + supports_bwe_extension_ = HasBweExtension(rtp_header_extension_map_); + UpdateHeaderSizes(); + return registered; +} + +bool RTPSender::IsRtpHeaderExtensionRegistered(RTPExtensionType type) const { + MutexLock lock(&send_mutex_); + return rtp_header_extension_map_.IsRegistered(type); +} + +void RTPSender::DeregisterRtpHeaderExtension(absl::string_view uri) { + MutexLock lock(&send_mutex_); + rtp_header_extension_map_.Deregister(uri); + supports_bwe_extension_ = HasBweExtension(rtp_header_extension_map_); + UpdateHeaderSizes(); +} + +void RTPSender::SetMaxRtpPacketSize(size_t max_packet_size) { + RTC_DCHECK_GE(max_packet_size, 100); + RTC_DCHECK_LE(max_packet_size, IP_PACKET_SIZE); + MutexLock lock(&send_mutex_); + max_packet_size_ = max_packet_size; +} + +size_t RTPSender::MaxRtpPacketSize() const { + return max_packet_size_; +} + +void RTPSender::SetRtxStatus(int mode) { + MutexLock lock(&send_mutex_); + if (mode != kRtxOff && + (!rtx_ssrc_.has_value() || rtx_payload_type_map_.empty())) { + RTC_LOG(LS_ERROR) + << "Failed to enable RTX without RTX SSRC or payload types."; + return; + } + rtx_ = mode; +} + +int RTPSender::RtxStatus() const { + MutexLock lock(&send_mutex_); + return rtx_; +} + +void RTPSender::SetRtxPayloadType(int payload_type, + int associated_payload_type) { + MutexLock lock(&send_mutex_); + RTC_DCHECK_LE(payload_type, 127); + RTC_DCHECK_LE(associated_payload_type, 127); + if (payload_type < 0) { + RTC_LOG(LS_ERROR) << "Invalid RTX payload type: " << payload_type << "."; + return; + } + + rtx_payload_type_map_[associated_payload_type] = payload_type; +} + +int32_t RTPSender::ReSendPacket(uint16_t packet_id) { + int32_t packet_size = 0; + const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0; + + std::unique_ptr<RtpPacketToSend> packet = + packet_history_->GetPacketAndMarkAsPending( + packet_id, [&](const RtpPacketToSend& stored_packet) { + // Check if we're overusing retransmission bitrate. + // TODO(sprang): Add histograms for nack success or failure + // reasons. + packet_size = stored_packet.size(); + std::unique_ptr<RtpPacketToSend> retransmit_packet; + if (retransmission_rate_limiter_ && + !retransmission_rate_limiter_->TryUseRate(packet_size)) { + return retransmit_packet; + } + if (rtx) { + retransmit_packet = BuildRtxPacket(stored_packet); + } else { + retransmit_packet = + std::make_unique<RtpPacketToSend>(stored_packet); + } + if (retransmit_packet) { + retransmit_packet->set_retransmitted_sequence_number( + stored_packet.SequenceNumber()); + } + return retransmit_packet; + }); + if (packet_size == 0) { + // Packet not found or already queued for retransmission, ignore. + RTC_DCHECK(!packet); + return 0; + } + if (!packet) { + // Packet was found, but lambda helper above chose not to create + // `retransmit_packet` out of it. + return -1; + } + packet->set_packet_type(RtpPacketMediaType::kRetransmission); + packet->set_fec_protect_packet(false); + std::vector<std::unique_ptr<RtpPacketToSend>> packets; + packets.emplace_back(std::move(packet)); + paced_sender_->EnqueuePackets(std::move(packets)); + + return packet_size; +} + +void RTPSender::OnReceivedAckOnSsrc(int64_t extended_highest_sequence_number) { + MutexLock lock(&send_mutex_); + bool update_required = !ssrc_has_acked_; + ssrc_has_acked_ = true; + if (update_required) { + UpdateHeaderSizes(); + } +} + +void RTPSender::OnReceivedAckOnRtxSsrc( + int64_t extended_highest_sequence_number) { + MutexLock lock(&send_mutex_); + bool update_required = !rtx_ssrc_has_acked_; + rtx_ssrc_has_acked_ = true; + if (update_required) { + UpdateHeaderSizes(); + } +} + +void RTPSender::OnReceivedNack( + const std::vector<uint16_t>& nack_sequence_numbers, + int64_t avg_rtt) { + packet_history_->SetRtt(TimeDelta::Millis(5 + avg_rtt)); + for (uint16_t seq_no : nack_sequence_numbers) { + const int32_t bytes_sent = ReSendPacket(seq_no); + if (bytes_sent < 0) { + // Failed to send one Sequence number. Give up the rest in this nack. + RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no + << ", Discard rest of packets."; + break; + } + } +} + +bool RTPSender::SupportsPadding() const { + MutexLock lock(&send_mutex_); + return sending_media_ && supports_bwe_extension_; +} + +bool RTPSender::SupportsRtxPayloadPadding() const { + MutexLock lock(&send_mutex_); + return sending_media_ && supports_bwe_extension_ && + (rtx_ & kRtxRedundantPayloads); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> RTPSender::GeneratePadding( + size_t target_size_bytes, + bool media_has_been_sent, + bool can_send_padding_on_media_ssrc) { + // This method does not actually send packets, it just generates + // them and puts them in the pacer queue. Since this should incur + // low overhead, keep the lock for the scope of the method in order + // to make the code more readable. + + std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets; + size_t bytes_left = target_size_bytes; + if (SupportsRtxPayloadPadding()) { + while (bytes_left >= kMinPayloadPaddingBytes) { + std::unique_ptr<RtpPacketToSend> packet = + packet_history_->GetPayloadPaddingPacket( + [&](const RtpPacketToSend& packet) + -> std::unique_ptr<RtpPacketToSend> { + // Limit overshoot, generate <= `kMaxPaddingSizeFactor` * + // `target_size_bytes`. + const size_t max_overshoot_bytes = static_cast<size_t>( + ((kMaxPaddingSizeFactor - 1.0) * target_size_bytes) + 0.5); + if (packet.payload_size() + kRtxHeaderSize > + max_overshoot_bytes + bytes_left) { + return nullptr; + } + return BuildRtxPacket(packet); + }); + if (!packet) { + break; + } + + bytes_left -= std::min(bytes_left, packet->payload_size()); + packet->set_packet_type(RtpPacketMediaType::kPadding); + padding_packets.push_back(std::move(packet)); + } + } + + MutexLock lock(&send_mutex_); + if (!sending_media_) { + return {}; + } + + size_t padding_bytes_in_packet; + const size_t max_payload_size = + max_packet_size_ - max_padding_fec_packet_header_; + if (audio_configured_) { + // Allow smaller padding packets for audio. + padding_bytes_in_packet = rtc::SafeClamp<size_t>( + bytes_left, kMinAudioPaddingLength, + rtc::SafeMin(max_payload_size, kMaxPaddingLength)); + } else { + // Always send full padding packets. This is accounted for by the + // RtpPacketSender, which will make sure we don't send too much padding even + // if a single packet is larger than requested. + // We do this to avoid frequently sending small packets on higher bitrates. + padding_bytes_in_packet = rtc::SafeMin(max_payload_size, kMaxPaddingLength); + } + + while (bytes_left > 0) { + auto padding_packet = + std::make_unique<RtpPacketToSend>(&rtp_header_extension_map_); + padding_packet->set_packet_type(RtpPacketMediaType::kPadding); + padding_packet->SetMarker(false); + if (rtx_ == kRtxOff) { + if (!can_send_padding_on_media_ssrc) { + break; + } + padding_packet->SetSsrc(ssrc_); + } else { + // Without abs-send-time or transport sequence number a media packet + // must be sent before padding so that the timestamps used for + // estimation are correct. + if (!media_has_been_sent && + !(rtp_header_extension_map_.IsRegistered(AbsoluteSendTime::kId) || + rtp_header_extension_map_.IsRegistered( + TransportSequenceNumber::kId))) { + break; + } + + RTC_DCHECK(rtx_ssrc_); + RTC_DCHECK(!rtx_payload_type_map_.empty()); + padding_packet->SetSsrc(*rtx_ssrc_); + padding_packet->SetPayloadType(rtx_payload_type_map_.begin()->second); + } + + if (rtp_header_extension_map_.IsRegistered(TransportSequenceNumber::kId)) { + padding_packet->ReserveExtension<TransportSequenceNumber>(); + } + if (rtp_header_extension_map_.IsRegistered(TransmissionOffset::kId)) { + padding_packet->ReserveExtension<TransmissionOffset>(); + } + if (rtp_header_extension_map_.IsRegistered(AbsoluteSendTime::kId)) { + padding_packet->ReserveExtension<AbsoluteSendTime>(); + } + + padding_packet->SetPadding(padding_bytes_in_packet); + bytes_left -= std::min(bytes_left, padding_bytes_in_packet); + padding_packets.push_back(std::move(padding_packet)); + } + + return padding_packets; +} + +bool RTPSender::SendToNetwork(std::unique_ptr<RtpPacketToSend> packet) { + RTC_DCHECK(packet); + auto packet_type = packet->packet_type(); + RTC_CHECK(packet_type) << "Packet type must be set before sending."; + + if (packet->capture_time() <= Timestamp::Zero()) { + packet->set_capture_time(clock_->CurrentTime()); + } + + std::vector<std::unique_ptr<RtpPacketToSend>> packets; + packets.emplace_back(std::move(packet)); + paced_sender_->EnqueuePackets(std::move(packets)); + + return true; +} + +void RTPSender::EnqueuePackets( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + RTC_DCHECK(!packets.empty()); + Timestamp now = clock_->CurrentTime(); + for (auto& packet : packets) { + RTC_DCHECK(packet); + RTC_CHECK(packet->packet_type().has_value()) + << "Packet type must be set before sending."; + if (packet->capture_time() <= Timestamp::Zero()) { + packet->set_capture_time(now); + } + } + + paced_sender_->EnqueuePackets(std::move(packets)); +} + +size_t RTPSender::FecOrPaddingPacketMaxRtpHeaderLength() const { + MutexLock lock(&send_mutex_); + return max_padding_fec_packet_header_; +} + +size_t RTPSender::ExpectedPerPacketOverhead() const { + MutexLock lock(&send_mutex_); + return max_media_packet_header_; +} + +std::unique_ptr<RtpPacketToSend> RTPSender::AllocatePacket() const { + MutexLock lock(&send_mutex_); + // TODO(danilchap): Find better motivator and value for extra capacity. + // RtpPacketizer might slightly miscalulate needed size, + // SRTP may benefit from extra space in the buffer and do encryption in place + // saving reallocation. + // While sending slightly oversized packet increase chance of dropped packet, + // it is better than crash on drop packet without trying to send it. + static constexpr int kExtraCapacity = 16; + auto packet = std::make_unique<RtpPacketToSend>( + &rtp_header_extension_map_, max_packet_size_ + kExtraCapacity); + packet->SetSsrc(ssrc_); + packet->SetCsrcs(csrcs_); + // Reserve extensions, if registered, RtpSender set in SendToNetwork. + packet->ReserveExtension<AbsoluteSendTime>(); + packet->ReserveExtension<TransmissionOffset>(); + packet->ReserveExtension<TransportSequenceNumber>(); + + // BUNDLE requires that the receiver "bind" the received SSRC to the values + // in the MID and/or (R)RID header extensions if present. Therefore, the + // sender can reduce overhead by omitting these header extensions once it + // knows that the receiver has "bound" the SSRC. + // This optimization can be configured by setting + // `always_send_mid_and_rid_` appropriately. + // + // The algorithm here is fairly simple: Always attach a MID and/or RID (if + // configured) to the outgoing packets until an RTCP receiver report comes + // back for this SSRC. That feedback indicates the receiver must have + // received a packet with the SSRC and header extension(s), so the sender + // then stops attaching the MID and RID. + if (always_send_mid_and_rid_ || !ssrc_has_acked_) { + // These are no-ops if the corresponding header extension is not registered. + if (!mid_.empty()) { + packet->SetExtension<RtpMid>(mid_); + } + if (!rid_.empty()) { + packet->SetExtension<RtpStreamId>(rid_); + } + } + return packet; +} + +size_t RTPSender::RtxPacketOverhead() const { + MutexLock lock(&send_mutex_); + if (rtx_ == kRtxOff) { + return 0; + } + size_t overhead = 0; + + // Count space for the RTP header extensions that might need to be added to + // the RTX packet. + if (!always_send_mid_and_rid_ && (!rtx_ssrc_has_acked_ && ssrc_has_acked_)) { + // Prefer to reserve extra byte in case two byte header rtp header + // extensions are used. + static constexpr int kRtpExtensionHeaderSize = 2; + + // Rtx packets hasn't been acked and would need to have mid and rrsid rtp + // header extensions, while media packets no longer needs to include mid and + // rsid extensions. + if (!mid_.empty()) { + overhead += (kRtpExtensionHeaderSize + mid_.size()); + } + if (!rid_.empty()) { + overhead += (kRtpExtensionHeaderSize + rid_.size()); + } + // RTP header extensions are rounded up to 4 bytes. Depending on already + // present extensions adding mid & rrsid may add up to 3 bytes of padding. + overhead += 3; + } + + // Add two bytes for the original sequence number in the RTP payload. + overhead += kRtxHeaderSize; + return overhead; +} + +void RTPSender::SetSendingMediaStatus(bool enabled) { + MutexLock lock(&send_mutex_); + sending_media_ = enabled; +} + +bool RTPSender::SendingMedia() const { + MutexLock lock(&send_mutex_); + return sending_media_; +} + +bool RTPSender::IsAudioConfigured() const { + return audio_configured_; +} + +void RTPSender::SetTimestampOffset(uint32_t timestamp) { + MutexLock lock(&send_mutex_); + timestamp_offset_ = timestamp; +} + +uint32_t RTPSender::TimestampOffset() const { + MutexLock lock(&send_mutex_); + return timestamp_offset_; +} + +void RTPSender::SetMid(absl::string_view mid) { + // This is configured via the API. + MutexLock lock(&send_mutex_); + RTC_DCHECK_LE(mid.length(), RtpMid::kMaxValueSizeBytes); + mid_ = std::string(mid); + UpdateHeaderSizes(); +} + +void RTPSender::SetCsrcs(const std::vector<uint32_t>& csrcs) { + RTC_DCHECK_LE(csrcs.size(), kRtpCsrcSize); + MutexLock lock(&send_mutex_); + csrcs_ = csrcs; + UpdateHeaderSizes(); +} + +static void CopyHeaderAndExtensionsToRtxPacket(const RtpPacketToSend& packet, + RtpPacketToSend* rtx_packet) { + // Set the relevant fixed packet headers. The following are not set: + // * Payload type - it is replaced in rtx packets. + // * Sequence number - RTX has a separate sequence numbering. + // * SSRC - RTX stream has its own SSRC. + rtx_packet->SetMarker(packet.Marker()); + rtx_packet->SetTimestamp(packet.Timestamp()); + + // Set the variable fields in the packet header: + // * CSRCs - must be set before header extensions. + // * Header extensions - replace Rid header with RepairedRid header. + const std::vector<uint32_t> csrcs = packet.Csrcs(); + rtx_packet->SetCsrcs(csrcs); + for (int extension_num = kRtpExtensionNone + 1; + extension_num < kRtpExtensionNumberOfExtensions; ++extension_num) { + auto extension = static_cast<RTPExtensionType>(extension_num); + + // Stream ID header extensions (MID, RSID) are sent per-SSRC. Since RTX + // operates on a different SSRC, the presence and values of these header + // extensions should be determined separately and not blindly copied. + if (extension == kRtpExtensionMid || + extension == kRtpExtensionRtpStreamId) { + continue; + } + + // Empty extensions should be supported, so not checking `source.empty()`. + if (!packet.HasExtension(extension)) { + continue; + } + + rtc::ArrayView<const uint8_t> source = packet.FindExtension(extension); + + rtc::ArrayView<uint8_t> destination = + rtx_packet->AllocateExtension(extension, source.size()); + + // Could happen if any: + // 1. Extension has 0 length. + // 2. Extension is not registered in destination. + // 3. Allocating extension in destination failed. + if (destination.empty() || source.size() != destination.size()) { + continue; + } + + std::memcpy(destination.begin(), source.begin(), destination.size()); + } +} + +std::unique_ptr<RtpPacketToSend> RTPSender::BuildRtxPacket( + const RtpPacketToSend& packet) { + std::unique_ptr<RtpPacketToSend> rtx_packet; + + // Add original RTP header. + { + MutexLock lock(&send_mutex_); + if (!sending_media_) + return nullptr; + + RTC_DCHECK(rtx_ssrc_); + + // Replace payload type. + auto kv = rtx_payload_type_map_.find(packet.PayloadType()); + if (kv == rtx_payload_type_map_.end()) + return nullptr; + + rtx_packet = std::make_unique<RtpPacketToSend>(&rtp_header_extension_map_, + max_packet_size_); + + rtx_packet->SetPayloadType(kv->second); + + // Replace SSRC. + rtx_packet->SetSsrc(*rtx_ssrc_); + + CopyHeaderAndExtensionsToRtxPacket(packet, rtx_packet.get()); + + // RTX packets are sent on an SSRC different from the main media, so the + // decision to attach MID and/or RRID header extensions is completely + // separate from that of the main media SSRC. + // + // Note that RTX packets must used the RepairedRtpStreamId (RRID) header + // extension instead of the RtpStreamId (RID) header extension even though + // the payload is identical. + if (always_send_mid_and_rid_ || !rtx_ssrc_has_acked_) { + // These are no-ops if the corresponding header extension is not + // registered. + if (!mid_.empty()) { + rtx_packet->SetExtension<RtpMid>(mid_); + } + if (!rid_.empty()) { + rtx_packet->SetExtension<RepairedRtpStreamId>(rid_); + } + } + } + RTC_DCHECK(rtx_packet); + + uint8_t* rtx_payload = + rtx_packet->AllocatePayload(packet.payload_size() + kRtxHeaderSize); + if (rtx_payload == nullptr) + return nullptr; + + // Add OSN (original sequence number). + ByteWriter<uint16_t>::WriteBigEndian(rtx_payload, packet.SequenceNumber()); + + // Add original payload data. + auto payload = packet.payload(); + if (!payload.empty()) { + memcpy(rtx_payload + kRtxHeaderSize, payload.data(), payload.size()); + } + + // Add original additional data. + rtx_packet->set_additional_data(packet.additional_data()); + + // Copy capture time so e.g. TransmissionOffset is correctly set. + rtx_packet->set_capture_time(packet.capture_time()); + + return rtx_packet; +} + +void RTPSender::SetRtpState(const RtpState& rtp_state) { + MutexLock lock(&send_mutex_); + + timestamp_offset_ = rtp_state.start_timestamp; + ssrc_has_acked_ = rtp_state.ssrc_has_acked; + UpdateHeaderSizes(); +} + +RtpState RTPSender::GetRtpState() const { + MutexLock lock(&send_mutex_); + + RtpState state; + state.start_timestamp = timestamp_offset_; + state.ssrc_has_acked = ssrc_has_acked_; + return state; +} + +void RTPSender::SetRtxRtpState(const RtpState& rtp_state) { + MutexLock lock(&send_mutex_); + rtx_ssrc_has_acked_ = rtp_state.ssrc_has_acked; +} + +RtpState RTPSender::GetRtxRtpState() const { + MutexLock lock(&send_mutex_); + + RtpState state; + state.start_timestamp = timestamp_offset_; + state.ssrc_has_acked = rtx_ssrc_has_acked_; + + return state; +} + +void RTPSender::UpdateHeaderSizes() { + const size_t rtp_header_length = + kRtpHeaderLength + sizeof(uint32_t) * csrcs_.size(); + + max_padding_fec_packet_header_ = + rtp_header_length + RtpHeaderExtensionSize(kFecOrPaddingExtensionSizes, + rtp_header_extension_map_); + + // RtpStreamId, Mid and RepairedRtpStreamId are treated specially in that + // we check if they currently are being sent. RepairedRtpStreamId can be + // sent instead of RtpStreamID on RTX packets and may share the same space. + // When the primary SSRC has already been acked but the RTX SSRC has not + // yet been acked, RepairedRtpStreamId needs to be taken into account + // separately. + const bool send_mid_rid_on_rtx = + rtx_ssrc_.has_value() && + (always_send_mid_and_rid_ || !rtx_ssrc_has_acked_); + const bool send_mid_rid = always_send_mid_and_rid_ || !ssrc_has_acked_; + std::vector<RtpExtensionSize> non_volatile_extensions; + for (auto& extension : + audio_configured_ ? AudioExtensionSizes() : VideoExtensionSizes()) { + if (IsNonVolatile(extension.type)) { + switch (extension.type) { + case RTPExtensionType::kRtpExtensionMid: + if ((send_mid_rid || send_mid_rid_on_rtx) && !mid_.empty()) { + non_volatile_extensions.push_back(extension); + } + break; + case RTPExtensionType::kRtpExtensionRtpStreamId: + if (send_mid_rid && !rid_.empty()) { + non_volatile_extensions.push_back(extension); + } + break; + case RTPExtensionType::kRtpExtensionRepairedRtpStreamId: + if (send_mid_rid_on_rtx && !send_mid_rid && !rid_.empty()) { + non_volatile_extensions.push_back(extension); + } + break; + default: + non_volatile_extensions.push_back(extension); + } + } + } + max_media_packet_header_ = + rtp_header_length + RtpHeaderExtensionSize(non_volatile_extensions, + rtp_header_extension_map_); + // Reserve extra bytes if packet might be resent in an rtx packet. + if (rtx_ssrc_.has_value()) { + max_media_packet_header_ += kRtxHeaderSize; + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.h new file mode 100644 index 0000000000..b49afe0dec --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.h @@ -0,0 +1,217 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_ + +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/call/transport.h" +#include "api/field_trials_view.h" +#include "modules/rtp_rtcp/include/flexfec_sender.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_packet_sender.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_packet_history.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "rtc_base/random.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class FrameEncryptorInterface; +class RateLimiter; +class RtcEventLog; +class RtpPacketToSend; + +class RTPSender { + public: + RTPSender(const RtpRtcpInterface::Configuration& config, + RtpPacketHistory* packet_history, + RtpPacketSender* packet_sender); + RTPSender(const RTPSender&) = delete; + RTPSender& operator=(const RTPSender&) = delete; + + ~RTPSender(); + + void SetSendingMediaStatus(bool enabled) RTC_LOCKS_EXCLUDED(send_mutex_); + bool SendingMedia() const RTC_LOCKS_EXCLUDED(send_mutex_); + bool IsAudioConfigured() const RTC_LOCKS_EXCLUDED(send_mutex_); + + uint32_t TimestampOffset() const RTC_LOCKS_EXCLUDED(send_mutex_); + void SetTimestampOffset(uint32_t timestamp) RTC_LOCKS_EXCLUDED(send_mutex_); + + void SetMid(absl::string_view mid) RTC_LOCKS_EXCLUDED(send_mutex_); + + uint16_t SequenceNumber() const RTC_LOCKS_EXCLUDED(send_mutex_); + void SetSequenceNumber(uint16_t seq) RTC_LOCKS_EXCLUDED(send_mutex_); + + void SetCsrcs(const std::vector<uint32_t>& csrcs) + RTC_LOCKS_EXCLUDED(send_mutex_); + + void SetMaxRtpPacketSize(size_t max_packet_size) + RTC_LOCKS_EXCLUDED(send_mutex_); + + void SetExtmapAllowMixed(bool extmap_allow_mixed) + RTC_LOCKS_EXCLUDED(send_mutex_); + + // RTP header extension + bool RegisterRtpHeaderExtension(absl::string_view uri, int id) + RTC_LOCKS_EXCLUDED(send_mutex_); + bool IsRtpHeaderExtensionRegistered(RTPExtensionType type) const + RTC_LOCKS_EXCLUDED(send_mutex_); + void DeregisterRtpHeaderExtension(absl::string_view uri) + RTC_LOCKS_EXCLUDED(send_mutex_); + + bool SupportsPadding() const RTC_LOCKS_EXCLUDED(send_mutex_); + bool SupportsRtxPayloadPadding() const RTC_LOCKS_EXCLUDED(send_mutex_); + + std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding( + size_t target_size_bytes, + bool media_has_been_sent, + bool can_send_padding_on_media_ssrc) RTC_LOCKS_EXCLUDED(send_mutex_); + + // NACK. + void OnReceivedNack(const std::vector<uint16_t>& nack_sequence_numbers, + int64_t avg_rtt) RTC_LOCKS_EXCLUDED(send_mutex_); + + int32_t ReSendPacket(uint16_t packet_id) RTC_LOCKS_EXCLUDED(send_mutex_); + + // ACK. + void OnReceivedAckOnSsrc(int64_t extended_highest_sequence_number) + RTC_LOCKS_EXCLUDED(send_mutex_); + void OnReceivedAckOnRtxSsrc(int64_t extended_highest_sequence_number) + RTC_LOCKS_EXCLUDED(send_mutex_); + + // RTX. + void SetRtxStatus(int mode) RTC_LOCKS_EXCLUDED(send_mutex_); + int RtxStatus() const RTC_LOCKS_EXCLUDED(send_mutex_); + absl::optional<uint32_t> RtxSsrc() const RTC_LOCKS_EXCLUDED(send_mutex_) { + return rtx_ssrc_; + } + // Returns expected size difference between an RTX packet and media packet + // that RTX packet is created from. Returns 0 if RTX is disabled. + size_t RtxPacketOverhead() const; + + void SetRtxPayloadType(int payload_type, int associated_payload_type) + RTC_LOCKS_EXCLUDED(send_mutex_); + + // Size info for header extensions used by FEC packets. + static rtc::ArrayView<const RtpExtensionSize> FecExtensionSizes() + RTC_LOCKS_EXCLUDED(send_mutex_); + + // Size info for header extensions used by video packets. + static rtc::ArrayView<const RtpExtensionSize> VideoExtensionSizes() + RTC_LOCKS_EXCLUDED(send_mutex_); + + // Size info for header extensions used by audio packets. + static rtc::ArrayView<const RtpExtensionSize> AudioExtensionSizes() + RTC_LOCKS_EXCLUDED(send_mutex_); + + // Create empty packet, fills ssrc, csrcs and reserve place for header + // extensions RtpSender updates before sending. + std::unique_ptr<RtpPacketToSend> AllocatePacket() const + RTC_LOCKS_EXCLUDED(send_mutex_); + // Maximum header overhead per fec/padding packet. + size_t FecOrPaddingPacketMaxRtpHeaderLength() const + RTC_LOCKS_EXCLUDED(send_mutex_); + // Expected header overhead per media packet. + size_t ExpectedPerPacketOverhead() const RTC_LOCKS_EXCLUDED(send_mutex_); + // Including RTP headers. + size_t MaxRtpPacketSize() const RTC_LOCKS_EXCLUDED(send_mutex_); + + uint32_t SSRC() const RTC_LOCKS_EXCLUDED(send_mutex_) { return ssrc_; } + + absl::optional<uint32_t> FlexfecSsrc() const RTC_LOCKS_EXCLUDED(send_mutex_) { + return flexfec_ssrc_; + } + + // Sends packet to `transport_` or to the pacer, depending on configuration. + // TODO(bugs.webrtc.org/XXX): Remove in favor of EnqueuePackets(). + bool SendToNetwork(std::unique_ptr<RtpPacketToSend> packet) + RTC_LOCKS_EXCLUDED(send_mutex_); + + // Pass a set of packets to RtpPacketSender instance, for paced or immediate + // sending to the network. + void EnqueuePackets(std::vector<std::unique_ptr<RtpPacketToSend>> packets) + RTC_LOCKS_EXCLUDED(send_mutex_); + + void SetRtpState(const RtpState& rtp_state) RTC_LOCKS_EXCLUDED(send_mutex_); + RtpState GetRtpState() const RTC_LOCKS_EXCLUDED(send_mutex_); + void SetRtxRtpState(const RtpState& rtp_state) + RTC_LOCKS_EXCLUDED(send_mutex_); + RtpState GetRtxRtpState() const RTC_LOCKS_EXCLUDED(send_mutex_); + + private: + std::unique_ptr<RtpPacketToSend> BuildRtxPacket( + const RtpPacketToSend& packet); + + bool IsFecPacket(const RtpPacketToSend& packet) const; + + void UpdateHeaderSizes() RTC_EXCLUSIVE_LOCKS_REQUIRED(send_mutex_); + + void UpdateLastPacketState(const RtpPacketToSend& packet) + RTC_EXCLUSIVE_LOCKS_REQUIRED(send_mutex_); + + Clock* const clock_; + Random random_ RTC_GUARDED_BY(send_mutex_); + + const bool audio_configured_; + + const uint32_t ssrc_; + const absl::optional<uint32_t> rtx_ssrc_; + const absl::optional<uint32_t> flexfec_ssrc_; + + RtpPacketHistory* const packet_history_; + RtpPacketSender* const paced_sender_; + + mutable Mutex send_mutex_; + + bool sending_media_ RTC_GUARDED_BY(send_mutex_); + size_t max_packet_size_; + + RtpHeaderExtensionMap rtp_header_extension_map_ RTC_GUARDED_BY(send_mutex_); + size_t max_media_packet_header_ RTC_GUARDED_BY(send_mutex_); + size_t max_padding_fec_packet_header_ RTC_GUARDED_BY(send_mutex_); + + // RTP variables + uint32_t timestamp_offset_ RTC_GUARDED_BY(send_mutex_); + // RID value to send in the RID or RepairedRID header extension. + const std::string rid_; + // MID value to send in the MID header extension. + std::string mid_ RTC_GUARDED_BY(send_mutex_); + // Should we send MID/RID even when ACKed? (see below). + const bool always_send_mid_and_rid_; + // Track if any ACK has been received on the SSRC and RTX SSRC to indicate + // when to stop sending the MID and RID header extensions. + bool ssrc_has_acked_ RTC_GUARDED_BY(send_mutex_); + bool rtx_ssrc_has_acked_ RTC_GUARDED_BY(send_mutex_); + std::vector<uint32_t> csrcs_ RTC_GUARDED_BY(send_mutex_); + int rtx_ RTC_GUARDED_BY(send_mutex_); + // Mapping rtx_payload_type_map_[associated] = rtx. + std::map<int8_t, int8_t> rtx_payload_type_map_ RTC_GUARDED_BY(send_mutex_); + bool supports_bwe_extension_ RTC_GUARDED_BY(send_mutex_); + + RateLimiter* const retransmission_rate_limiter_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_SENDER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.cc new file mode 100644 index 0000000000..244f644bd1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.cc @@ -0,0 +1,400 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_sender_audio.h" + +#include <string.h> + +#include <memory> +#include <utility> + +#include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_format.h" +#include "api/rtp_headers.h" +#include "modules/audio_coding/include/audio_coding_module_typedefs.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/absolute_capture_time_sender.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { + +namespace { +[[maybe_unused]] const char* FrameTypeToString(AudioFrameType frame_type) { + switch (frame_type) { + case AudioFrameType::kEmptyFrame: + return "empty"; + case AudioFrameType::kAudioFrameSpeech: + return "audio_speech"; + case AudioFrameType::kAudioFrameCN: + return "audio_cn"; + } + RTC_CHECK_NOTREACHED(); +} + +constexpr char kIncludeCaptureClockOffset[] = + "WebRTC-IncludeCaptureClockOffset"; + +} // namespace + +RTPSenderAudio::RTPSenderAudio(Clock* clock, RTPSender* rtp_sender) + : clock_(clock), + rtp_sender_(rtp_sender), + absolute_capture_time_sender_(clock), + include_capture_clock_offset_( + !absl::StartsWith(field_trials_.Lookup(kIncludeCaptureClockOffset), + "Disabled")) { + RTC_DCHECK(clock_); +} + +RTPSenderAudio::~RTPSenderAudio() {} + +int32_t RTPSenderAudio::RegisterAudioPayload(absl::string_view payload_name, + const int8_t payload_type, + const uint32_t frequency, + const size_t channels, + const uint32_t rate) { + if (absl::EqualsIgnoreCase(payload_name, "cn")) { + MutexLock lock(&send_audio_mutex_); + // we can have multiple CNG payload types + switch (frequency) { + case 8000: + cngnb_payload_type_ = payload_type; + break; + case 16000: + cngwb_payload_type_ = payload_type; + break; + case 32000: + cngswb_payload_type_ = payload_type; + break; + case 48000: + cngfb_payload_type_ = payload_type; + break; + default: + return -1; + } + } else if (absl::EqualsIgnoreCase(payload_name, "telephone-event")) { + MutexLock lock(&send_audio_mutex_); + // Don't add it to the list + // we dont want to allow send with a DTMF payloadtype + dtmf_payload_type_ = payload_type; + dtmf_payload_freq_ = frequency; + return 0; + } else if (payload_name == "audio") { + MutexLock lock(&send_audio_mutex_); + encoder_rtp_timestamp_frequency_ = frequency; + return 0; + } + return 0; +} + +bool RTPSenderAudio::MarkerBit(AudioFrameType frame_type, int8_t payload_type) { + MutexLock lock(&send_audio_mutex_); + // for audio true for first packet in a speech burst + bool marker_bit = false; + if (last_payload_type_ != payload_type) { + if (payload_type != -1 && (cngnb_payload_type_ == payload_type || + cngwb_payload_type_ == payload_type || + cngswb_payload_type_ == payload_type || + cngfb_payload_type_ == payload_type)) { + // Only set a marker bit when we change payload type to a non CNG + return false; + } + + // payload_type differ + if (last_payload_type_ == -1) { + if (frame_type != AudioFrameType::kAudioFrameCN) { + // first packet and NOT CNG + return true; + } else { + // first packet and CNG + inband_vad_active_ = true; + return false; + } + } + + // not first packet AND + // not CNG AND + // payload_type changed + + // set a marker bit when we change payload type + marker_bit = true; + } + + // For G.723 G.729, AMR etc we can have inband VAD + if (frame_type == AudioFrameType::kAudioFrameCN) { + inband_vad_active_ = true; + } else if (inband_vad_active_) { + inband_vad_active_ = false; + marker_bit = true; + } + return marker_bit; +} + +bool RTPSenderAudio::SendAudio(AudioFrameType frame_type, + int8_t payload_type, + uint32_t rtp_timestamp, + const uint8_t* payload_data, + size_t payload_size) { + return SendAudio(frame_type, payload_type, rtp_timestamp, payload_data, + payload_size, + // TODO(bugs.webrtc.org/10739) replace once plumbed. + /*absolute_capture_timestamp_ms=*/-1); +} + +bool RTPSenderAudio::SendAudio(AudioFrameType frame_type, + int8_t payload_type, + uint32_t rtp_timestamp, + const uint8_t* payload_data, + size_t payload_size, + int64_t absolute_capture_timestamp_ms) { + TRACE_EVENT_ASYNC_STEP1("webrtc", "Audio", rtp_timestamp, "Send", "type", + FrameTypeToString(frame_type)); + + // From RFC 4733: + // A source has wide latitude as to how often it sends event updates. A + // natural interval is the spacing between non-event audio packets. [...] + // Alternatively, a source MAY decide to use a different spacing for event + // updates, with a value of 50 ms RECOMMENDED. + constexpr int kDtmfIntervalTimeMs = 50; + uint8_t audio_level_dbov = 0; + uint32_t dtmf_payload_freq = 0; + absl::optional<uint32_t> encoder_rtp_timestamp_frequency; + { + MutexLock lock(&send_audio_mutex_); + audio_level_dbov = audio_level_dbov_; + dtmf_payload_freq = dtmf_payload_freq_; + encoder_rtp_timestamp_frequency = encoder_rtp_timestamp_frequency_; + } + + // Check if we have pending DTMFs to send + if (!dtmf_event_is_on_ && dtmf_queue_.PendingDtmf()) { + if ((clock_->TimeInMilliseconds() - dtmf_time_last_sent_) > + kDtmfIntervalTimeMs) { + // New tone to play + dtmf_timestamp_ = rtp_timestamp; + if (dtmf_queue_.NextDtmf(&dtmf_current_event_)) { + dtmf_event_first_packet_sent_ = false; + dtmf_length_samples_ = + dtmf_current_event_.duration_ms * (dtmf_payload_freq / 1000); + dtmf_event_is_on_ = true; + } + } + } + + // A source MAY send events and coded audio packets for the same time + // but we don't support it + if (dtmf_event_is_on_) { + if (frame_type == AudioFrameType::kEmptyFrame) { + // kEmptyFrame is used to drive the DTMF when in CN mode + // it can be triggered more frequently than we want to send the + // DTMF packets. + const unsigned int dtmf_interval_time_rtp = + dtmf_payload_freq * kDtmfIntervalTimeMs / 1000; + if ((rtp_timestamp - dtmf_timestamp_last_sent_) < + dtmf_interval_time_rtp) { + // not time to send yet + return true; + } + } + dtmf_timestamp_last_sent_ = rtp_timestamp; + uint32_t dtmf_duration_samples = rtp_timestamp - dtmf_timestamp_; + bool ended = false; + bool send = true; + + if (dtmf_length_samples_ > dtmf_duration_samples) { + if (dtmf_duration_samples <= 0) { + // Skip send packet at start, since we shouldn't use duration 0 + send = false; + } + } else { + ended = true; + dtmf_event_is_on_ = false; + dtmf_time_last_sent_ = clock_->TimeInMilliseconds(); + } + if (send) { + if (dtmf_duration_samples > 0xffff) { + // RFC 4733 2.5.2.3 Long-Duration Events + SendTelephoneEventPacket(ended, dtmf_timestamp_, + static_cast<uint16_t>(0xffff), false); + + // set new timestap for this segment + dtmf_timestamp_ = rtp_timestamp; + dtmf_duration_samples -= 0xffff; + dtmf_length_samples_ -= 0xffff; + + return SendTelephoneEventPacket( + ended, dtmf_timestamp_, + static_cast<uint16_t>(dtmf_duration_samples), false); + } else { + if (!SendTelephoneEventPacket(ended, dtmf_timestamp_, + dtmf_duration_samples, + !dtmf_event_first_packet_sent_)) { + return false; + } + dtmf_event_first_packet_sent_ = true; + return true; + } + } + return true; + } + if (payload_size == 0 || payload_data == NULL) { + if (frame_type == AudioFrameType::kEmptyFrame) { + // we don't send empty audio RTP packets + // no error since we use it to either drive DTMF when we use VAD, or + // enter DTX. + return true; + } + return false; + } + + std::unique_ptr<RtpPacketToSend> packet = rtp_sender_->AllocatePacket(); + packet->SetMarker(MarkerBit(frame_type, payload_type)); + packet->SetPayloadType(payload_type); + packet->SetTimestamp(rtp_timestamp); + packet->set_capture_time(clock_->CurrentTime()); + // Update audio level extension, if included. + packet->SetExtension<AudioLevel>( + frame_type == AudioFrameType::kAudioFrameSpeech, audio_level_dbov); + + if (absolute_capture_timestamp_ms > 0) { + // Send absolute capture time periodically in order to optimize and save + // network traffic. Missing absolute capture times can be interpolated on + // the receiving end if sending intervals are small enough. + auto absolute_capture_time = absolute_capture_time_sender_.OnSendPacket( + AbsoluteCaptureTimeSender::GetSource(packet->Ssrc(), packet->Csrcs()), + packet->Timestamp(), + // Replace missing value with 0 (invalid frequency), this will trigger + // absolute capture time sending. + encoder_rtp_timestamp_frequency.value_or(0), + Int64MsToUQ32x32(clock_->ConvertTimestampToNtpTimeInMilliseconds( + absolute_capture_timestamp_ms)), + /*estimated_capture_clock_offset=*/ + include_capture_clock_offset_ ? absl::make_optional(0) : absl::nullopt); + if (absolute_capture_time) { + // It also checks that extension was registered during SDP negotiation. If + // not then setter won't do anything. + packet->SetExtension<AbsoluteCaptureTimeExtension>( + *absolute_capture_time); + } + } + + uint8_t* payload = packet->AllocatePayload(payload_size); + if (!payload) // Too large payload buffer. + return false; + memcpy(payload, payload_data, payload_size); + + { + MutexLock lock(&send_audio_mutex_); + last_payload_type_ = payload_type; + } + TRACE_EVENT_ASYNC_END2("webrtc", "Audio", rtp_timestamp, "timestamp", + packet->Timestamp(), "seqnum", + packet->SequenceNumber()); + packet->set_packet_type(RtpPacketMediaType::kAudio); + packet->set_allow_retransmission(true); + bool send_result = rtp_sender_->SendToNetwork(std::move(packet)); + if (first_packet_sent_()) { + RTC_LOG(LS_INFO) << "First audio RTP packet sent to pacer"; + } + return send_result; +} + +// Audio level magnitude and voice activity flag are set for each RTP packet +int32_t RTPSenderAudio::SetAudioLevel(uint8_t level_dbov) { + if (level_dbov > 127) { + return -1; + } + MutexLock lock(&send_audio_mutex_); + audio_level_dbov_ = level_dbov; + return 0; +} + +// Send a TelephoneEvent tone using RFC 2833 (4733) +int32_t RTPSenderAudio::SendTelephoneEvent(uint8_t key, + uint16_t time_ms, + uint8_t level) { + DtmfQueue::Event event; + { + MutexLock lock(&send_audio_mutex_); + if (dtmf_payload_type_ < 0) { + // TelephoneEvent payloadtype not configured + return -1; + } + event.payload_type = dtmf_payload_type_; + } + event.key = key; + event.duration_ms = time_ms; + event.level = level; + return dtmf_queue_.AddDtmf(event) ? 0 : -1; +} + +bool RTPSenderAudio::SendTelephoneEventPacket(bool ended, + uint32_t dtmf_timestamp, + uint16_t duration, + bool marker_bit) { + uint8_t send_count = 1; + bool result = true; + + if (ended) { + // resend last packet in an event 3 times + send_count = 3; + } + do { + // Send DTMF data. + constexpr RtpPacketToSend::ExtensionManager* kNoExtensions = nullptr; + constexpr size_t kDtmfSize = 4; + std::unique_ptr<RtpPacketToSend> packet( + new RtpPacketToSend(kNoExtensions, kRtpHeaderSize + kDtmfSize)); + packet->SetPayloadType(dtmf_current_event_.payload_type); + packet->SetMarker(marker_bit); + packet->SetSsrc(rtp_sender_->SSRC()); + packet->SetTimestamp(dtmf_timestamp); + packet->set_capture_time(clock_->CurrentTime()); + + // Create DTMF data. + uint8_t* dtmfbuffer = packet->AllocatePayload(kDtmfSize); + RTC_DCHECK(dtmfbuffer); + /* From RFC 2833: + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | event |E|R| volume | duration | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + // R bit always cleared + uint8_t R = 0x00; + uint8_t volume = dtmf_current_event_.level; + + // First packet un-ended + uint8_t E = ended ? 0x80 : 0x00; + + // First byte is Event number, equals key number + dtmfbuffer[0] = dtmf_current_event_.key; + dtmfbuffer[1] = E | R | volume; + ByteWriter<uint16_t>::WriteBigEndian(dtmfbuffer + 2, duration); + + packet->set_packet_type(RtpPacketMediaType::kAudio); + packet->set_allow_retransmission(true); + result = rtp_sender_->SendToNetwork(std::move(packet)); + send_count--; + } while (send_count > 0 && result); + + return result; +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.h new file mode 100644 index 0000000000..d40fee6386 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.h @@ -0,0 +1,122 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_SENDER_AUDIO_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_SENDER_AUDIO_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "absl/strings/string_view.h" +#include "api/transport/field_trial_based_config.h" +#include "modules/audio_coding/include/audio_coding_module_typedefs.h" +#include "modules/rtp_rtcp/source/absolute_capture_time_sender.h" +#include "modules/rtp_rtcp/source/dtmf_queue.h" +#include "modules/rtp_rtcp/source/rtp_sender.h" +#include "rtc_base/one_time_event.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +class RTPSenderAudio { + public: + RTPSenderAudio(Clock* clock, RTPSender* rtp_sender); + + RTPSenderAudio() = delete; + RTPSenderAudio(const RTPSenderAudio&) = delete; + RTPSenderAudio& operator=(const RTPSenderAudio&) = delete; + + ~RTPSenderAudio(); + + int32_t RegisterAudioPayload(absl::string_view payload_name, + int8_t payload_type, + uint32_t frequency, + size_t channels, + uint32_t rate); + + bool SendAudio(AudioFrameType frame_type, + int8_t payload_type, + uint32_t rtp_timestamp, + const uint8_t* payload_data, + size_t payload_size); + + // `absolute_capture_timestamp_ms` and `Clock::CurrentTime` + // should be using the same epoch. + bool SendAudio(AudioFrameType frame_type, + int8_t payload_type, + uint32_t rtp_timestamp, + const uint8_t* payload_data, + size_t payload_size, + int64_t absolute_capture_timestamp_ms); + + // Store the audio level in dBov for + // header-extension-for-audio-level-indication. + // Valid range is [0,127]. Actual value is negative. + int32_t SetAudioLevel(uint8_t level_dbov); + + // Send a DTMF tone using RFC 2833 (4733) + int32_t SendTelephoneEvent(uint8_t key, uint16_t time_ms, uint8_t level); + + protected: + bool SendTelephoneEventPacket( + bool ended, + uint32_t dtmf_timestamp, + uint16_t duration, + bool marker_bit); // set on first packet in talk burst + + bool MarkerBit(AudioFrameType frame_type, int8_t payload_type); + + private: + Clock* const clock_ = nullptr; + RTPSender* const rtp_sender_ = nullptr; + + Mutex send_audio_mutex_; + + // DTMF. + bool dtmf_event_is_on_ = false; + bool dtmf_event_first_packet_sent_ = false; + int8_t dtmf_payload_type_ RTC_GUARDED_BY(send_audio_mutex_) = -1; + uint32_t dtmf_payload_freq_ RTC_GUARDED_BY(send_audio_mutex_) = 8000; + uint32_t dtmf_timestamp_ = 0; + uint32_t dtmf_length_samples_ = 0; + int64_t dtmf_time_last_sent_ = 0; + uint32_t dtmf_timestamp_last_sent_ = 0; + DtmfQueue::Event dtmf_current_event_; + DtmfQueue dtmf_queue_; + + // VAD detection, used for marker bit. + bool inband_vad_active_ RTC_GUARDED_BY(send_audio_mutex_) = false; + int8_t cngnb_payload_type_ RTC_GUARDED_BY(send_audio_mutex_) = -1; + int8_t cngwb_payload_type_ RTC_GUARDED_BY(send_audio_mutex_) = -1; + int8_t cngswb_payload_type_ RTC_GUARDED_BY(send_audio_mutex_) = -1; + int8_t cngfb_payload_type_ RTC_GUARDED_BY(send_audio_mutex_) = -1; + int8_t last_payload_type_ RTC_GUARDED_BY(send_audio_mutex_) = -1; + + // Audio level indication. + // (https://datatracker.ietf.org/doc/draft-lennox-avt-rtp-audio-level-exthdr/) + uint8_t audio_level_dbov_ RTC_GUARDED_BY(send_audio_mutex_) = 127; + OneTimeEvent first_packet_sent_; + + absl::optional<uint32_t> encoder_rtp_timestamp_frequency_ + RTC_GUARDED_BY(send_audio_mutex_); + + AbsoluteCaptureTimeSender absolute_capture_time_sender_; + + const FieldTrialBasedConfig field_trials_; + const bool include_capture_clock_offset_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_SENDER_AUDIO_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio_unittest.cc new file mode 100644 index 0000000000..7397a3ac4e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio_unittest.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 "modules/rtp_rtcp/source/rtp_sender_audio.h" + +#include <memory> +#include <vector> + +#include "api/transport/field_trial_based_config.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.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_rtcp_impl2.h" +#include "rtc_base/thread.h" +#include "test/field_trial.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { +enum : int { // The first valid value is 1. + kAudioLevelExtensionId = 1, + kAbsoluteCaptureTimeExtensionId = 2, +}; + +const uint16_t kSeqNum = 33; +const uint32_t kSsrc = 725242; +const uint8_t kAudioLevel = 0x5a; +const uint64_t kStartTime = 123456789; + +using ::testing::ElementsAreArray; + +class LoopbackTransportTest : public webrtc::Transport { + public: + LoopbackTransportTest() { + receivers_extensions_.Register<AudioLevel>(kAudioLevelExtensionId); + receivers_extensions_.Register<AbsoluteCaptureTimeExtension>( + kAbsoluteCaptureTimeExtensionId); + } + + bool SendRtp(const uint8_t* data, + size_t len, + const PacketOptions& /*options*/) override { + sent_packets_.push_back(RtpPacketReceived(&receivers_extensions_)); + EXPECT_TRUE(sent_packets_.back().Parse(data, len)); + return true; + } + bool SendRtcp(const uint8_t* data, size_t len) override { return false; } + const RtpPacketReceived& last_sent_packet() { return sent_packets_.back(); } + int packets_sent() { return sent_packets_.size(); } + + private: + RtpHeaderExtensionMap receivers_extensions_; + std::vector<RtpPacketReceived> sent_packets_; +}; + +} // namespace + +class RtpSenderAudioTest : public ::testing::Test { + public: + RtpSenderAudioTest() + : fake_clock_(kStartTime), + rtp_module_(ModuleRtpRtcpImpl2::Create([&] { + RtpRtcpInterface::Configuration config; + config.audio = true; + config.clock = &fake_clock_; + config.outgoing_transport = &transport_; + config.local_media_ssrc = kSsrc; + return config; + }())), + rtp_sender_audio_( + std::make_unique<RTPSenderAudio>(&fake_clock_, + rtp_module_->RtpSender())) { + rtp_module_->SetSequenceNumber(kSeqNum); + } + + rtc::AutoThread main_thread_; + SimulatedClock fake_clock_; + LoopbackTransportTest transport_; + std::unique_ptr<ModuleRtpRtcpImpl2> rtp_module_; + std::unique_ptr<RTPSenderAudio> rtp_sender_audio_; +}; + +TEST_F(RtpSenderAudioTest, SendAudio) { + const char payload_name[] = "PAYLOAD_NAME"; + const uint8_t payload_type = 127; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + payload_name, payload_type, 48000, 0, 1500)); + uint8_t payload[] = {47, 11, 32, 93, 89}; + + ASSERT_TRUE( + rtp_sender_audio_->SendAudio(AudioFrameType::kAudioFrameCN, payload_type, + 4321, payload, sizeof(payload), + /*absolute_capture_timestamp_ms=*/0)); + + auto sent_payload = transport_.last_sent_packet().payload(); + EXPECT_THAT(sent_payload, ElementsAreArray(payload)); +} + +TEST_F(RtpSenderAudioTest, SendAudioWithAudioLevelExtension) { + EXPECT_EQ(0, rtp_sender_audio_->SetAudioLevel(kAudioLevel)); + rtp_module_->RegisterRtpHeaderExtension(AudioLevel::Uri(), + kAudioLevelExtensionId); + + const char payload_name[] = "PAYLOAD_NAME"; + const uint8_t payload_type = 127; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + payload_name, payload_type, 48000, 0, 1500)); + + uint8_t payload[] = {47, 11, 32, 93, 89}; + + ASSERT_TRUE( + rtp_sender_audio_->SendAudio(AudioFrameType::kAudioFrameCN, payload_type, + 4321, payload, sizeof(payload), + /*absolute_capture_timestamp_ms=*/0)); + + auto sent_payload = transport_.last_sent_packet().payload(); + EXPECT_THAT(sent_payload, ElementsAreArray(payload)); + // Verify AudioLevel extension. + bool voice_activity; + uint8_t audio_level; + EXPECT_TRUE(transport_.last_sent_packet().GetExtension<AudioLevel>( + &voice_activity, &audio_level)); + EXPECT_EQ(kAudioLevel, audio_level); + EXPECT_FALSE(voice_activity); +} + +TEST_F(RtpSenderAudioTest, SendAudioWithoutAbsoluteCaptureTime) { + constexpr uint32_t kAbsoluteCaptureTimestampMs = 521; + const char payload_name[] = "audio"; + const uint8_t payload_type = 127; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + payload_name, payload_type, 48000, 0, 1500)); + uint8_t payload[] = {47, 11, 32, 93, 89}; + + ASSERT_TRUE(rtp_sender_audio_->SendAudio( + AudioFrameType::kAudioFrameCN, payload_type, 4321, payload, + sizeof(payload), kAbsoluteCaptureTimestampMs)); + + EXPECT_FALSE(transport_.last_sent_packet() + .HasExtension<AbsoluteCaptureTimeExtension>()); +} + +// Essentially the same test as +// SendAudioWithAbsoluteCaptureTimeWithCaptureClockOffset but with a field +// trial. We will remove this test eventually. +TEST_F(RtpSenderAudioTest, SendAudioWithAbsoluteCaptureTime) { + // Recreate rtp_sender_audio_ with new field trial. + test::ScopedFieldTrials field_trial( + "WebRTC-IncludeCaptureClockOffset/Disabled/"); + rtp_sender_audio_ = + std::make_unique<RTPSenderAudio>(&fake_clock_, rtp_module_->RtpSender()); + + rtp_module_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + kAbsoluteCaptureTimeExtensionId); + constexpr uint32_t kAbsoluteCaptureTimestampMs = 521; + const char payload_name[] = "audio"; + const uint8_t payload_type = 127; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + payload_name, payload_type, 48000, 0, 1500)); + uint8_t payload[] = {47, 11, 32, 93, 89}; + + ASSERT_TRUE(rtp_sender_audio_->SendAudio( + AudioFrameType::kAudioFrameCN, payload_type, 4321, payload, + sizeof(payload), kAbsoluteCaptureTimestampMs)); + + auto absolute_capture_time = + transport_.last_sent_packet() + .GetExtension<AbsoluteCaptureTimeExtension>(); + EXPECT_TRUE(absolute_capture_time); + EXPECT_EQ( + absolute_capture_time->absolute_capture_timestamp, + Int64MsToUQ32x32(fake_clock_.ConvertTimestampToNtpTimeInMilliseconds( + kAbsoluteCaptureTimestampMs))); + EXPECT_FALSE( + absolute_capture_time->estimated_capture_clock_offset.has_value()); +} + +TEST_F(RtpSenderAudioTest, + SendAudioWithAbsoluteCaptureTimeWithCaptureClockOffset) { + rtp_module_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + kAbsoluteCaptureTimeExtensionId); + constexpr uint32_t kAbsoluteCaptureTimestampMs = 521; + const char payload_name[] = "audio"; + const uint8_t payload_type = 127; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + payload_name, payload_type, 48000, 0, 1500)); + uint8_t payload[] = {47, 11, 32, 93, 89}; + + ASSERT_TRUE(rtp_sender_audio_->SendAudio( + AudioFrameType::kAudioFrameCN, payload_type, 4321, payload, + sizeof(payload), kAbsoluteCaptureTimestampMs)); + + auto absolute_capture_time = + transport_.last_sent_packet() + .GetExtension<AbsoluteCaptureTimeExtension>(); + EXPECT_TRUE(absolute_capture_time); + EXPECT_EQ( + absolute_capture_time->absolute_capture_timestamp, + Int64MsToUQ32x32(fake_clock_.ConvertTimestampToNtpTimeInMilliseconds( + kAbsoluteCaptureTimestampMs))); + EXPECT_TRUE( + absolute_capture_time->estimated_capture_clock_offset.has_value()); + EXPECT_EQ(0, *absolute_capture_time->estimated_capture_clock_offset); +} + +// As RFC4733, named telephone events are carried as part of the audio stream +// and must use the same sequence number and timestamp base as the regular +// audio channel. +// This test checks the marker bit for the first packet and the consequent +// packets of the same telephone event. Since it is specifically for DTMF +// events, ignoring audio packets and sending kEmptyFrame instead of those. +TEST_F(RtpSenderAudioTest, CheckMarkerBitForTelephoneEvents) { + const char* kDtmfPayloadName = "telephone-event"; + const uint32_t kPayloadFrequency = 8000; + const uint8_t kPayloadType = 126; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + kDtmfPayloadName, kPayloadType, kPayloadFrequency, 0, 0)); + // For Telephone events, payload is not added to the registered payload list, + // it will register only the payload used for audio stream. + // Registering the payload again for audio stream with different payload name. + const char* kPayloadName = "payload_name"; + ASSERT_EQ(0, rtp_sender_audio_->RegisterAudioPayload( + kPayloadName, kPayloadType, kPayloadFrequency, 1, 0)); + // Start time is arbitrary. + uint32_t capture_timestamp = fake_clock_.TimeInMilliseconds(); + // DTMF event key=9, duration=500 and attenuationdB=10 + rtp_sender_audio_->SendTelephoneEvent(9, 500, 10); + // During start, it takes the starting timestamp as last sent timestamp. + // The duration is calculated as the difference of current and last sent + // timestamp. So for first call it will skip since the duration is zero. + ASSERT_TRUE(rtp_sender_audio_->SendAudio( + AudioFrameType::kEmptyFrame, kPayloadType, capture_timestamp, nullptr, 0, + /*absolute_capture_time_ms=0*/ 0)); + + // DTMF Sample Length is (Frequency/1000) * Duration. + // So in this case, it is (8000/1000) * 500 = 4000. + // Sending it as two packets. + ASSERT_TRUE(rtp_sender_audio_->SendAudio(AudioFrameType::kEmptyFrame, + kPayloadType, + capture_timestamp + 2000, nullptr, 0, + /*absolute_capture_time_ms=0*/ 0)); + + // Marker Bit should be set to 1 for first packet. + EXPECT_TRUE(transport_.last_sent_packet().Marker()); + + ASSERT_TRUE(rtp_sender_audio_->SendAudio(AudioFrameType::kEmptyFrame, + kPayloadType, + capture_timestamp + 4000, nullptr, 0, + /*absolute_capture_time_ms=0*/ 0)); + // Marker Bit should be set to 0 for rest of the packets. + EXPECT_FALSE(transport_.last_sent_packet().Marker()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.cc new file mode 100644 index 0000000000..e4e0993d71 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.cc @@ -0,0 +1,617 @@ +/* + * 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/rtp_sender_egress.h" + +#include <algorithm> +#include <limits> +#include <memory> +#include <utility> + +#include "absl/strings/match.h" +#include "api/transport/field_trial_based_config.h" +#include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { +constexpr uint32_t kTimestampTicksPerMs = 90; +constexpr TimeDelta kSendSideDelayWindow = TimeDelta::Seconds(1); +constexpr int kBitrateStatisticsWindowMs = 1000; +constexpr size_t kRtpSequenceNumberMapMaxEntries = 1 << 13; +constexpr TimeDelta kUpdateInterval = + TimeDelta::Millis(kBitrateStatisticsWindowMs); +} // namespace + +RtpSenderEgress::NonPacedPacketSender::NonPacedPacketSender( + RtpSenderEgress* sender, + PacketSequencer* sequencer) + : transport_sequence_number_(0), sender_(sender), sequencer_(sequencer) { + RTC_DCHECK(sequencer); +} +RtpSenderEgress::NonPacedPacketSender::~NonPacedPacketSender() = default; + +void RtpSenderEgress::NonPacedPacketSender::EnqueuePackets( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + for (auto& packet : packets) { + PrepareForSend(packet.get()); + sender_->SendPacket(packet.get(), PacedPacketInfo()); + } + auto fec_packets = sender_->FetchFecPackets(); + if (!fec_packets.empty()) { + EnqueuePackets(std::move(fec_packets)); + } +} + +void RtpSenderEgress::NonPacedPacketSender::PrepareForSend( + RtpPacketToSend* packet) { + // Assign sequence numbers, but not for flexfec which is already running on + // an internally maintained sequence number series. + if (packet->Ssrc() != sender_->FlexFecSsrc()) { + sequencer_->Sequence(*packet); + } + if (!packet->SetExtension<TransportSequenceNumber>( + ++transport_sequence_number_)) { + --transport_sequence_number_; + } + packet->ReserveExtension<TransmissionOffset>(); + packet->ReserveExtension<AbsoluteSendTime>(); +} + +RtpSenderEgress::RtpSenderEgress(const RtpRtcpInterface::Configuration& config, + RtpPacketHistory* packet_history) + : worker_queue_(TaskQueueBase::Current()), + ssrc_(config.local_media_ssrc), + rtx_ssrc_(config.rtx_send_ssrc), + flexfec_ssrc_(config.fec_generator ? config.fec_generator->FecSsrc() + : absl::nullopt), + populate_network2_timestamp_(config.populate_network2_timestamp), + clock_(config.clock), + packet_history_(packet_history), + transport_(config.outgoing_transport), + event_log_(config.event_log), +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + is_audio_(config.audio), +#endif + need_rtp_packet_infos_(config.need_rtp_packet_infos), + fec_generator_(config.fec_generator), + transport_feedback_observer_(config.transport_feedback_callback), + send_side_delay_observer_(config.send_side_delay_observer), + send_packet_observer_(config.send_packet_observer), + rtp_stats_callback_(config.rtp_stats_callback), + bitrate_callback_(config.send_bitrate_observer), + media_has_been_sent_(false), + force_part_of_allocation_(false), + timestamp_offset_(0), + max_delay_it_(send_delays_.end()), + sum_delays_(TimeDelta::Zero()), + send_rates_(kNumMediaTypes, + {kBitrateStatisticsWindowMs, RateStatistics::kBpsScale}), + rtp_sequence_number_map_(need_rtp_packet_infos_ + ? std::make_unique<RtpSequenceNumberMap>( + kRtpSequenceNumberMapMaxEntries) + : nullptr) { + RTC_DCHECK(worker_queue_); + pacer_checker_.Detach(); + if (bitrate_callback_) { + update_task_ = RepeatingTaskHandle::DelayedStart(worker_queue_, + kUpdateInterval, [this]() { + PeriodicUpdate(); + return kUpdateInterval; + }); + } +} + +RtpSenderEgress::~RtpSenderEgress() { + RTC_DCHECK_RUN_ON(worker_queue_); + update_task_.Stop(); +} + +void RtpSenderEgress::SendPacket(RtpPacketToSend* packet, + const PacedPacketInfo& pacing_info) { + RTC_DCHECK_RUN_ON(&pacer_checker_); + RTC_DCHECK(packet); + + if (packet->Ssrc() == ssrc_ && + packet->packet_type() != RtpPacketMediaType::kRetransmission) { + if (last_sent_seq_.has_value()) { + RTC_DCHECK_EQ(static_cast<uint16_t>(*last_sent_seq_ + 1), + packet->SequenceNumber()); + } + last_sent_seq_ = packet->SequenceNumber(); + } else if (packet->Ssrc() == rtx_ssrc_) { + if (last_sent_rtx_seq_.has_value()) { + RTC_DCHECK_EQ(static_cast<uint16_t>(*last_sent_rtx_seq_ + 1), + packet->SequenceNumber()); + } + last_sent_rtx_seq_ = packet->SequenceNumber(); + } + + RTC_DCHECK(packet->packet_type().has_value()); + RTC_DCHECK(HasCorrectSsrc(*packet)); + if (packet->packet_type() == RtpPacketMediaType::kRetransmission) { + RTC_DCHECK(packet->retransmitted_sequence_number().has_value()); + } + + const uint32_t packet_ssrc = packet->Ssrc(); + const Timestamp now = clock_->CurrentTime(); + +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + worker_queue_->PostTask(SafeTask( + task_safety_.flag(), + [this, now, packet_ssrc]() { BweTestLoggingPlot(now, packet_ssrc); })); +#endif + + if (need_rtp_packet_infos_ && + packet->packet_type() == RtpPacketToSend::Type::kVideo) { + worker_queue_->PostTask(SafeTask( + task_safety_.flag(), + [this, packet_timestamp = packet->Timestamp(), + is_first_packet_of_frame = packet->is_first_packet_of_frame(), + is_last_packet_of_frame = packet->Marker(), + sequence_number = packet->SequenceNumber()]() { + RTC_DCHECK_RUN_ON(worker_queue_); + // Last packet of a frame, add it to sequence number info map. + const uint32_t timestamp = packet_timestamp - timestamp_offset_; + rtp_sequence_number_map_->InsertPacket( + sequence_number, + RtpSequenceNumberMap::Info(timestamp, is_first_packet_of_frame, + is_last_packet_of_frame)); + })); + } + + if (fec_generator_ && packet->fec_protect_packet()) { + // This packet should be protected by FEC, add it to packet generator. + RTC_DCHECK(fec_generator_); + RTC_DCHECK(packet->packet_type() == RtpPacketMediaType::kVideo); + absl::optional<std::pair<FecProtectionParams, FecProtectionParams>> + new_fec_params; + { + MutexLock lock(&lock_); + new_fec_params.swap(pending_fec_params_); + } + if (new_fec_params) { + fec_generator_->SetProtectionParameters(new_fec_params->first, + new_fec_params->second); + } + if (packet->is_red()) { + RtpPacketToSend unpacked_packet(*packet); + + const rtc::CopyOnWriteBuffer buffer = packet->Buffer(); + // Grab media payload type from RED header. + const size_t headers_size = packet->headers_size(); + unpacked_packet.SetPayloadType(buffer[headers_size]); + + // Copy the media payload into the unpacked buffer. + uint8_t* payload_buffer = + unpacked_packet.SetPayloadSize(packet->payload_size() - 1); + std::copy(&packet->payload()[0] + 1, + &packet->payload()[0] + packet->payload_size(), payload_buffer); + + fec_generator_->AddPacketAndGenerateFec(unpacked_packet); + } else { + // If not RED encapsulated - we can just insert packet directly. + fec_generator_->AddPacketAndGenerateFec(*packet); + } + } + + // Bug webrtc:7859. While FEC is invoked from rtp_sender_video, and not after + // the pacer, these modifications of the header below are happening after the + // FEC protection packets are calculated. This will corrupt recovered packets + // at the same place. It's not an issue for extensions, which are present in + // all the packets (their content just may be incorrect on recovered packets). + // In case of VideoTimingExtension, since it's present not in every packet, + // data after rtp header may be corrupted if these packets are protected by + // the FEC. + if (packet->HasExtension<TransmissionOffset>() && + packet->capture_time() > Timestamp::Zero()) { + TimeDelta diff = now - packet->capture_time(); + packet->SetExtension<TransmissionOffset>(kTimestampTicksPerMs * diff.ms()); + } + if (packet->HasExtension<AbsoluteSendTime>()) { + packet->SetExtension<AbsoluteSendTime>(AbsoluteSendTime::To24Bits(now)); + } + + if (packet->HasExtension<VideoTimingExtension>()) { + if (populate_network2_timestamp_) { + packet->set_network2_time(now); + } else { + packet->set_pacer_exit_time(now); + } + } + + const bool is_media = packet->packet_type() == RtpPacketMediaType::kAudio || + packet->packet_type() == RtpPacketMediaType::kVideo; + + PacketOptions options; + { + MutexLock lock(&lock_); + options.included_in_allocation = force_part_of_allocation_; + } + + // Downstream code actually uses this flag to distinguish between media and + // everything else. + options.is_retransmit = !is_media; + if (auto packet_id = packet->GetExtension<TransportSequenceNumber>()) { + options.packet_id = *packet_id; + options.included_in_feedback = true; + options.included_in_allocation = true; + AddPacketToTransportFeedback(*packet_id, *packet, pacing_info); + } + + options.additional_data = packet->additional_data(); + + if (packet->packet_type() != RtpPacketMediaType::kPadding && + packet->packet_type() != RtpPacketMediaType::kRetransmission) { + UpdateDelayStatistics(packet->capture_time(), now, packet_ssrc); + UpdateOnSendPacket(options.packet_id, packet->capture_time(), packet_ssrc); + } + + const bool send_success = SendPacketToNetwork(*packet, options, pacing_info); + + // Put packet in retransmission history or update pending status even if + // actual sending fails. + if (is_media && packet->allow_retransmission()) { + packet_history_->PutRtpPacket(std::make_unique<RtpPacketToSend>(*packet), + now); + } else if (packet->retransmitted_sequence_number()) { + packet_history_->MarkPacketAsSent(*packet->retransmitted_sequence_number()); + } + + if (send_success) { + // `media_has_been_sent_` is used by RTPSender to figure out if it can send + // padding in the absence of transport-cc or abs-send-time. + // In those cases media must be sent first to set a reference timestamp. + media_has_been_sent_ = true; + + // TODO(sprang): Add support for FEC protecting all header extensions, add + // media packet to generator here instead. + + RTC_DCHECK(packet->packet_type().has_value()); + RtpPacketMediaType packet_type = *packet->packet_type(); + RtpPacketCounter counter(*packet); + size_t size = packet->size(); + worker_queue_->PostTask( + SafeTask(task_safety_.flag(), [this, now, packet_ssrc, packet_type, + counter = std::move(counter), size]() { + RTC_DCHECK_RUN_ON(worker_queue_); + UpdateRtpStats(now, packet_ssrc, packet_type, std::move(counter), + size); + })); + } +} + +RtpSendRates RtpSenderEgress::GetSendRates() const { + MutexLock lock(&lock_); + return GetSendRatesLocked(clock_->CurrentTime()); +} + +RtpSendRates RtpSenderEgress::GetSendRatesLocked(Timestamp now) const { + RtpSendRates current_rates; + for (size_t i = 0; i < kNumMediaTypes; ++i) { + RtpPacketMediaType type = static_cast<RtpPacketMediaType>(i); + current_rates[type] = + DataRate::BitsPerSec(send_rates_[i].Rate(now.ms()).value_or(0)); + } + return current_rates; +} + +void RtpSenderEgress::GetDataCounters(StreamDataCounters* rtp_stats, + StreamDataCounters* rtx_stats) const { + // TODO(bugs.webrtc.org/11581): make sure rtx_rtp_stats_ and rtp_stats_ are + // only touched on the worker thread. + MutexLock lock(&lock_); + *rtp_stats = rtp_stats_; + *rtx_stats = rtx_rtp_stats_; +} + +void RtpSenderEgress::ForceIncludeSendPacketsInAllocation( + bool part_of_allocation) { + MutexLock lock(&lock_); + force_part_of_allocation_ = part_of_allocation; +} + +bool RtpSenderEgress::MediaHasBeenSent() const { + RTC_DCHECK_RUN_ON(&pacer_checker_); + return media_has_been_sent_; +} + +void RtpSenderEgress::SetMediaHasBeenSent(bool media_sent) { + RTC_DCHECK_RUN_ON(&pacer_checker_); + media_has_been_sent_ = media_sent; +} + +void RtpSenderEgress::SetTimestampOffset(uint32_t timestamp) { + RTC_DCHECK_RUN_ON(worker_queue_); + timestamp_offset_ = timestamp; +} + +std::vector<RtpSequenceNumberMap::Info> RtpSenderEgress::GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const { + RTC_DCHECK_RUN_ON(worker_queue_); + RTC_DCHECK(!sequence_numbers.empty()); + if (!need_rtp_packet_infos_) { + return std::vector<RtpSequenceNumberMap::Info>(); + } + + std::vector<RtpSequenceNumberMap::Info> results; + results.reserve(sequence_numbers.size()); + + for (uint16_t sequence_number : sequence_numbers) { + const auto& info = rtp_sequence_number_map_->Get(sequence_number); + if (!info) { + // The empty vector will be returned. We can delay the clearing + // of the vector until after we exit the critical section. + return std::vector<RtpSequenceNumberMap::Info>(); + } + results.push_back(*info); + } + + return results; +} + +void RtpSenderEgress::SetFecProtectionParameters( + const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) { + // TODO(sprang): Post task to pacer queue instead, one pacer is fully + // migrated to a task queue. + MutexLock lock(&lock_); + pending_fec_params_.emplace(delta_params, key_params); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> +RtpSenderEgress::FetchFecPackets() { + RTC_DCHECK_RUN_ON(&pacer_checker_); + if (fec_generator_) { + return fec_generator_->GetFecPackets(); + } + return {}; +} + +void RtpSenderEgress::OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers) { + RTC_DCHECK_RUN_ON(&pacer_checker_); + // Mark aborted retransmissions as sent, rather than leaving them in + // a 'pending' state - otherwise they can not be requested again and + // will not be cleared until the history has reached its max size. + for (uint16_t seq_no : sequence_numbers) { + packet_history_->MarkPacketAsSent(seq_no); + } +} + +bool RtpSenderEgress::HasCorrectSsrc(const RtpPacketToSend& packet) const { + switch (*packet.packet_type()) { + case RtpPacketMediaType::kAudio: + case RtpPacketMediaType::kVideo: + return packet.Ssrc() == ssrc_; + case RtpPacketMediaType::kRetransmission: + case RtpPacketMediaType::kPadding: + // Both padding and retransmission must be on either the media or the + // RTX stream. + return packet.Ssrc() == rtx_ssrc_ || packet.Ssrc() == ssrc_; + case RtpPacketMediaType::kForwardErrorCorrection: + // FlexFEC is on separate SSRC, ULPFEC uses media SSRC. + return packet.Ssrc() == ssrc_ || packet.Ssrc() == flexfec_ssrc_; + } + return false; +} + +void RtpSenderEgress::AddPacketToTransportFeedback( + uint16_t packet_id, + const RtpPacketToSend& packet, + const PacedPacketInfo& pacing_info) { + if (transport_feedback_observer_) { + RtpPacketSendInfo packet_info; + packet_info.transport_sequence_number = packet_id; + packet_info.rtp_timestamp = packet.Timestamp(); + packet_info.length = packet.size(); + packet_info.pacing_info = pacing_info; + packet_info.packet_type = packet.packet_type(); + + switch (*packet_info.packet_type) { + case RtpPacketMediaType::kAudio: + case RtpPacketMediaType::kVideo: + packet_info.media_ssrc = ssrc_; + packet_info.rtp_sequence_number = packet.SequenceNumber(); + break; + case RtpPacketMediaType::kRetransmission: + // For retransmissions, we're want to remove the original media packet + // if the retransmit arrives - so populate that in the packet info. + packet_info.media_ssrc = ssrc_; + packet_info.rtp_sequence_number = + *packet.retransmitted_sequence_number(); + break; + case RtpPacketMediaType::kPadding: + case RtpPacketMediaType::kForwardErrorCorrection: + // We're not interested in feedback about these packets being received + // or lost. + break; + } + + transport_feedback_observer_->OnAddPacket(packet_info); + } +} + +void RtpSenderEgress::UpdateDelayStatistics(Timestamp capture_time, + Timestamp now, + uint32_t ssrc) { + if (!send_side_delay_observer_ || capture_time.IsInfinite()) + return; + + TimeDelta avg_delay = TimeDelta::Zero(); + TimeDelta max_delay = TimeDelta::Zero(); + { + MutexLock lock(&lock_); + // Compute the max and average of the recent capture-to-send delays. + // The time complexity of the current approach depends on the distribution + // of the delay values. This could be done more efficiently. + + // Remove elements older than kSendSideDelayWindowMs. + auto lower_bound = send_delays_.lower_bound(now - kSendSideDelayWindow); + for (auto it = send_delays_.begin(); it != lower_bound; ++it) { + if (max_delay_it_ == it) { + max_delay_it_ = send_delays_.end(); + } + sum_delays_ -= it->second; + } + send_delays_.erase(send_delays_.begin(), lower_bound); + if (max_delay_it_ == send_delays_.end()) { + // Removed the previous max. Need to recompute. + RecomputeMaxSendDelay(); + } + + // Add the new element. + TimeDelta new_send_delay = now - capture_time; + auto [it, inserted] = send_delays_.emplace(now, new_send_delay); + if (!inserted) { + // TODO(terelius): If we have multiple delay measurements during the same + // millisecond then we keep the most recent one. It is not clear that this + // is the right decision, but it preserves an earlier behavior. + TimeDelta previous_send_delay = it->second; + sum_delays_ -= previous_send_delay; + it->second = new_send_delay; + if (max_delay_it_ == it && new_send_delay < previous_send_delay) { + RecomputeMaxSendDelay(); + } + } + if (max_delay_it_ == send_delays_.end() || + it->second >= max_delay_it_->second) { + max_delay_it_ = it; + } + sum_delays_ += new_send_delay; + + size_t num_delays = send_delays_.size(); + RTC_DCHECK(max_delay_it_ != send_delays_.end()); + max_delay = max_delay_it_->second; + avg_delay = sum_delays_ / num_delays; + } + send_side_delay_observer_->SendSideDelayUpdated(avg_delay.ms(), + max_delay.ms(), ssrc); +} + +void RtpSenderEgress::RecomputeMaxSendDelay() { + max_delay_it_ = send_delays_.begin(); + for (auto it = send_delays_.begin(); it != send_delays_.end(); ++it) { + if (it->second >= max_delay_it_->second) { + max_delay_it_ = it; + } + } +} + +void RtpSenderEgress::UpdateOnSendPacket(int packet_id, + Timestamp capture_time, + uint32_t ssrc) { + if (!send_packet_observer_ || capture_time.IsInfinite() || packet_id == -1) { + return; + } + + send_packet_observer_->OnSendPacket(packet_id, capture_time.ms(), ssrc); +} + +bool RtpSenderEgress::SendPacketToNetwork(const RtpPacketToSend& packet, + const PacketOptions& options, + const PacedPacketInfo& pacing_info) { + int bytes_sent = -1; + if (transport_) { + bytes_sent = transport_->SendRtp(packet.data(), packet.size(), options) + ? static_cast<int>(packet.size()) + : -1; + if (event_log_ && bytes_sent > 0) { + event_log_->Log(std::make_unique<RtcEventRtpPacketOutgoing>( + packet, pacing_info.probe_cluster_id)); + } + } + + if (bytes_sent <= 0) { + RTC_LOG(LS_WARNING) << "Transport failed to send packet."; + return false; + } + return true; +} + +void RtpSenderEgress::UpdateRtpStats(Timestamp now, + uint32_t packet_ssrc, + RtpPacketMediaType packet_type, + RtpPacketCounter counter, + size_t packet_size) { + RTC_DCHECK_RUN_ON(worker_queue_); + + // TODO(bugs.webrtc.org/11581): send_rates_ should be touched only on the + // worker thread. + RtpSendRates send_rates; + { + MutexLock lock(&lock_); + + // TODO(bugs.webrtc.org/11581): make sure rtx_rtp_stats_ and rtp_stats_ are + // only touched on the worker thread. + StreamDataCounters* counters = + packet_ssrc == rtx_ssrc_ ? &rtx_rtp_stats_ : &rtp_stats_; + + if (counters->first_packet_time_ms == -1) { + counters->first_packet_time_ms = now.ms(); + } + + if (packet_type == RtpPacketMediaType::kForwardErrorCorrection) { + counters->fec.Add(counter); + } else if (packet_type == RtpPacketMediaType::kRetransmission) { + counters->retransmitted.Add(counter); + } + counters->transmitted.Add(counter); + + send_rates_[static_cast<size_t>(packet_type)].Update(packet_size, now.ms()); + if (bitrate_callback_) { + send_rates = GetSendRatesLocked(now); + } + + if (rtp_stats_callback_) { + rtp_stats_callback_->DataCountersUpdated(*counters, packet_ssrc); + } + } + + // The bitrate_callback_ and rtp_stats_callback_ pointers in practice point + // to the same object, so these callbacks could be consolidated into one. + if (bitrate_callback_) { + bitrate_callback_->Notify( + send_rates.Sum().bps(), + send_rates[RtpPacketMediaType::kRetransmission].bps(), ssrc_); + } +} + +void RtpSenderEgress::PeriodicUpdate() { + RTC_DCHECK_RUN_ON(worker_queue_); + RTC_DCHECK(bitrate_callback_); + RtpSendRates send_rates = GetSendRates(); + bitrate_callback_->Notify( + send_rates.Sum().bps(), + send_rates[RtpPacketMediaType::kRetransmission].bps(), ssrc_); +} + +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE +void RtpSenderEgress::BweTestLoggingPlot(Timestamp now, uint32_t packet_ssrc) { + RTC_DCHECK_RUN_ON(worker_queue_); + + const auto rates = GetSendRates(); + if (is_audio_) { + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "AudioTotBitrate_kbps", now.ms(), + rates.Sum().kbps(), packet_ssrc); + BWE_TEST_LOGGING_PLOT_WITH_SSRC( + 1, "AudioNackBitrate_kbps", now.ms(), + rates[RtpPacketMediaType::kRetransmission].kbps(), packet_ssrc); + } else { + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "VideoTotBitrate_kbps", now.ms(), + rates.Sum().kbps(), packet_ssrc); + BWE_TEST_LOGGING_PLOT_WITH_SSRC( + 1, "VideoNackBitrate_kbps", now.ms(), + rates[RtpPacketMediaType::kRetransmission].kbps(), packet_ssrc); + } +} +#endif // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.h new file mode 100644 index 0000000000..f76c42d4d0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.h @@ -0,0 +1,186 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_SENDER_EGRESS_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_SENDER_EGRESS_H_ + +#include <map> +#include <memory> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/call/transport.h" +#include "api/rtc_event_log/rtc_event_log.h" +#include "api/sequence_checker.h" +#include "api/task_queue/pending_task_safety_flag.h" +#include "api/task_queue/task_queue_base.h" +#include "api/units/data_rate.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/packet_sequencer.h" +#include "modules/rtp_rtcp/source/rtp_packet_history.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" +#include "modules/rtp_rtcp/source/rtp_sequence_number_map.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" +#include "rtc_base/task_utils/repeating_task.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class RtpSenderEgress { + public: + // Helper class that redirects packets directly to the send part of this class + // without passing through an actual paced sender. + class NonPacedPacketSender : public RtpPacketSender { + public: + NonPacedPacketSender(RtpSenderEgress* sender, PacketSequencer* sequencer); + virtual ~NonPacedPacketSender(); + + void EnqueuePackets( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) override; + // Since we don't pace packets, there's no pending packets to remove. + void RemovePacketsForSsrc(uint32_t ssrc) override {} + + private: + void PrepareForSend(RtpPacketToSend* packet); + uint16_t transport_sequence_number_; + RtpSenderEgress* const sender_; + PacketSequencer* sequencer_; + }; + + RtpSenderEgress(const RtpRtcpInterface::Configuration& config, + RtpPacketHistory* packet_history); + ~RtpSenderEgress(); + + void SendPacket(RtpPacketToSend* packet, const PacedPacketInfo& pacing_info) + RTC_LOCKS_EXCLUDED(lock_); + uint32_t Ssrc() const { return ssrc_; } + absl::optional<uint32_t> RtxSsrc() const { return rtx_ssrc_; } + absl::optional<uint32_t> FlexFecSsrc() const { return flexfec_ssrc_; } + + RtpSendRates GetSendRates() const RTC_LOCKS_EXCLUDED(lock_); + void GetDataCounters(StreamDataCounters* rtp_stats, + StreamDataCounters* rtx_stats) const + RTC_LOCKS_EXCLUDED(lock_); + + void ForceIncludeSendPacketsInAllocation(bool part_of_allocation) + RTC_LOCKS_EXCLUDED(lock_); + bool MediaHasBeenSent() const RTC_LOCKS_EXCLUDED(lock_); + void SetMediaHasBeenSent(bool media_sent) RTC_LOCKS_EXCLUDED(lock_); + void SetTimestampOffset(uint32_t timestamp) RTC_LOCKS_EXCLUDED(lock_); + + // For each sequence number in `sequence_number`, recall the last RTP packet + // which bore it - its timestamp and whether it was the first and/or last + // packet in that frame. If all of the given sequence numbers could be + // recalled, return a vector with all of them (in corresponding order). + // If any could not be recalled, return an empty vector. + std::vector<RtpSequenceNumberMap::Info> GetSentRtpPacketInfos( + rtc::ArrayView<const uint16_t> sequence_numbers) const + RTC_LOCKS_EXCLUDED(lock_); + + void SetFecProtectionParameters(const FecProtectionParams& delta_params, + const FecProtectionParams& key_params); + std::vector<std::unique_ptr<RtpPacketToSend>> FetchFecPackets(); + + // Clears pending status for these sequence numbers in the packet history. + void OnAbortedRetransmissions( + rtc::ArrayView<const uint16_t> sequence_numbers); + + private: + RtpSendRates GetSendRatesLocked(Timestamp now) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + bool HasCorrectSsrc(const RtpPacketToSend& packet) const; + void AddPacketToTransportFeedback(uint16_t packet_id, + const RtpPacketToSend& packet, + const PacedPacketInfo& pacing_info); + void UpdateDelayStatistics(Timestamp capture_time, + Timestamp now, + uint32_t ssrc); + void RecomputeMaxSendDelay() RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + void UpdateOnSendPacket(int packet_id, Timestamp capture_time, uint32_t ssrc); + // Sends packet on to `transport_`, leaving the RTP module. + bool SendPacketToNetwork(const RtpPacketToSend& packet, + const PacketOptions& options, + const PacedPacketInfo& pacing_info); + + void UpdateRtpStats(Timestamp now, + uint32_t packet_ssrc, + RtpPacketMediaType packet_type, + RtpPacketCounter counter, + size_t packet_size); +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + void BweTestLoggingPlot(Timestamp now, uint32_t packet_ssrc); +#endif + + // Called on a timer, once a second, on the worker_queue_. + void PeriodicUpdate(); + + TaskQueueBase* const worker_queue_; + RTC_NO_UNIQUE_ADDRESS SequenceChecker pacer_checker_; + const uint32_t ssrc_; + const absl::optional<uint32_t> rtx_ssrc_; + const absl::optional<uint32_t> flexfec_ssrc_; + const bool populate_network2_timestamp_; + Clock* const clock_; + RtpPacketHistory* const packet_history_; + Transport* const transport_; + RtcEventLog* const event_log_; +#if BWE_TEST_LOGGING_COMPILE_TIME_ENABLE + const bool is_audio_; +#endif + const bool need_rtp_packet_infos_; + VideoFecGenerator* const fec_generator_ RTC_GUARDED_BY(pacer_checker_); + absl::optional<uint16_t> last_sent_seq_ RTC_GUARDED_BY(pacer_checker_); + absl::optional<uint16_t> last_sent_rtx_seq_ RTC_GUARDED_BY(pacer_checker_); + + TransportFeedbackObserver* const transport_feedback_observer_; + SendSideDelayObserver* const send_side_delay_observer_; + SendPacketObserver* const send_packet_observer_; + StreamDataCountersCallback* const rtp_stats_callback_; + BitrateStatisticsObserver* const bitrate_callback_; + + mutable Mutex lock_; + bool media_has_been_sent_ RTC_GUARDED_BY(pacer_checker_); + bool force_part_of_allocation_ RTC_GUARDED_BY(lock_); + uint32_t timestamp_offset_ RTC_GUARDED_BY(worker_queue_); + + // Maps capture time to send-side delay. Send-side delay is the difference + // between transmission time and capture time. + std::map<Timestamp, TimeDelta> send_delays_ RTC_GUARDED_BY(lock_); + std::map<Timestamp, TimeDelta>::const_iterator max_delay_it_ + RTC_GUARDED_BY(lock_); + // The sum of delays over a kSendSideDelayWindowMs sliding window. + TimeDelta sum_delays_ RTC_GUARDED_BY(lock_); + StreamDataCounters rtp_stats_ RTC_GUARDED_BY(lock_); + StreamDataCounters rtx_rtp_stats_ RTC_GUARDED_BY(lock_); + // One element per value in RtpPacketMediaType, with index matching value. + std::vector<RateStatistics> send_rates_ RTC_GUARDED_BY(lock_); + absl::optional<std::pair<FecProtectionParams, FecProtectionParams>> + pending_fec_params_ RTC_GUARDED_BY(lock_); + + // Maps sent packets' sequence numbers to a tuple consisting of: + // 1. The timestamp, without the randomizing offset mandated by the RFC. + // 2. Whether the packet was the first in its frame. + // 3. Whether the packet was the last in its frame. + const std::unique_ptr<RtpSequenceNumberMap> rtp_sequence_number_map_ + RTC_GUARDED_BY(worker_queue_); + RepeatingTaskHandle update_task_ RTC_GUARDED_BY(worker_queue_); + ScopedTaskSafety task_safety_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_SENDER_EGRESS_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress_unittest.cc new file mode 100644 index 0000000000..bfaffa1bdb --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress_unittest.cc @@ -0,0 +1,990 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_sender_egress.h" + +#include <string> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/call/transport.h" +#include "api/field_trials_registry.h" +#include "api/units/data_size.h" +#include "api/units/timestamp.h" +#include "logging/rtc_event_log/mock/mock_rtc_event_log.h" +#include "modules/rtp_rtcp/include/flexfec_sender.h" +#include "modules/rtp_rtcp/include/rtp_rtcp.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "modules/rtp_rtcp/source/rtp_packet_history.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "test/explicit_key_value_config.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::Field; +using ::testing::NiceMock; +using ::testing::Optional; +using ::testing::StrictMock; + +constexpr Timestamp kStartTime = Timestamp::Millis(123456789); +constexpr int kDefaultPayloadType = 100; +constexpr int kFlexfectPayloadType = 110; +constexpr uint16_t kStartSequenceNumber = 33; +constexpr uint32_t kSsrc = 725242; +constexpr uint32_t kRtxSsrc = 12345; +constexpr uint32_t kFlexFecSsrc = 23456; +enum : int { + kTransportSequenceNumberExtensionId = 1, + kAbsoluteSendTimeExtensionId, + kTransmissionOffsetExtensionId, + kVideoTimingExtensionId, +}; + +class MockSendPacketObserver : public SendPacketObserver { + public: + MOCK_METHOD(void, OnSendPacket, (uint16_t, int64_t, uint32_t), (override)); +}; + +class MockTransportFeedbackObserver : public TransportFeedbackObserver { + public: + MOCK_METHOD(void, OnAddPacket, (const RtpPacketSendInfo&), (override)); + MOCK_METHOD(void, + OnTransportFeedback, + (const rtcp::TransportFeedback&), + (override)); +}; + +class MockStreamDataCountersCallback : public StreamDataCountersCallback { + public: + MOCK_METHOD(void, + DataCountersUpdated, + (const StreamDataCounters& counters, uint32_t ssrc), + (override)); +}; + +class MockSendSideDelayObserver : public SendSideDelayObserver { + public: + MOCK_METHOD(void, SendSideDelayUpdated, (int, int, uint32_t), (override)); +}; + +struct TransmittedPacket { + TransmittedPacket(rtc::ArrayView<const uint8_t> data, + const PacketOptions& packet_options, + RtpHeaderExtensionMap* extensions) + : packet(extensions), options(packet_options) { + EXPECT_TRUE(packet.Parse(data)); + } + RtpPacketReceived packet; + PacketOptions options; +}; + +class TestTransport : public Transport { + public: + explicit TestTransport(RtpHeaderExtensionMap* extensions) + : total_data_sent_(DataSize::Zero()), extensions_(extensions) {} + bool SendRtp(const uint8_t* packet, + size_t length, + const PacketOptions& options) override { + total_data_sent_ += DataSize::Bytes(length); + last_packet_.emplace(rtc::MakeArrayView(packet, length), options, + extensions_); + return true; + } + + bool SendRtcp(const uint8_t*, size_t) override { RTC_CHECK_NOTREACHED(); } + + absl::optional<TransmittedPacket> last_packet() { return last_packet_; } + + private: + DataSize total_data_sent_; + absl::optional<TransmittedPacket> last_packet_; + RtpHeaderExtensionMap* const extensions_; +}; + +} // namespace + +class RtpSenderEgressTest : public ::testing::Test { + protected: + RtpSenderEgressTest() + : time_controller_(kStartTime), + clock_(time_controller_.GetClock()), + transport_(&header_extensions_), + packet_history_(clock_, /*enable_rtx_padding_prioritization=*/true), + trials_(""), + sequence_number_(kStartSequenceNumber) {} + + std::unique_ptr<RtpSenderEgress> CreateRtpSenderEgress() { + return std::make_unique<RtpSenderEgress>(DefaultConfig(), &packet_history_); + } + + RtpRtcpInterface::Configuration DefaultConfig() { + RtpRtcpInterface::Configuration config; + config.clock = clock_; + config.outgoing_transport = &transport_; + config.local_media_ssrc = kSsrc; + config.rtx_send_ssrc = kRtxSsrc; + config.fec_generator = nullptr; + config.event_log = &mock_rtc_event_log_; + config.send_packet_observer = &send_packet_observer_; + config.rtp_stats_callback = &mock_rtp_stats_callback_; + config.transport_feedback_callback = &feedback_observer_; + config.populate_network2_timestamp = false; + config.field_trials = &trials_; + return config; + } + + std::unique_ptr<RtpPacketToSend> BuildRtpPacket(bool marker_bit, + int64_t capture_time_ms) { + auto packet = std::make_unique<RtpPacketToSend>(&header_extensions_); + packet->SetSsrc(kSsrc); + packet->ReserveExtension<AbsoluteSendTime>(); + packet->ReserveExtension<TransmissionOffset>(); + packet->ReserveExtension<TransportSequenceNumber>(); + + packet->SetPayloadType(kDefaultPayloadType); + packet->set_packet_type(RtpPacketMediaType::kVideo); + packet->SetMarker(marker_bit); + packet->SetTimestamp(capture_time_ms * 90); + packet->set_capture_time(Timestamp::Millis(capture_time_ms)); + packet->SetSequenceNumber(sequence_number_++); + return packet; + } + + std::unique_ptr<RtpPacketToSend> BuildRtpPacket() { + return BuildRtpPacket(/*marker_bit=*/true, clock_->CurrentTime().ms()); + } + + GlobalSimulatedTimeController time_controller_; + Clock* const clock_; + NiceMock<MockRtcEventLog> mock_rtc_event_log_; + NiceMock<MockStreamDataCountersCallback> mock_rtp_stats_callback_; + NiceMock<MockSendPacketObserver> send_packet_observer_; + NiceMock<MockTransportFeedbackObserver> feedback_observer_; + RtpHeaderExtensionMap header_extensions_; + TestTransport transport_; + RtpPacketHistory packet_history_; + test::ExplicitKeyValueConfig trials_; + uint16_t sequence_number_; +}; + +TEST_F(RtpSenderEgressTest, TransportFeedbackObserverGetsCorrectByteCount) { + constexpr size_t kRtpOverheadBytesPerPacket = 12 + 8; + constexpr size_t kPayloadSize = 1400; + const uint16_t kTransportSequenceNumber = 17; + + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + const size_t expected_bytes = kPayloadSize + kRtpOverheadBytesPerPacket; + + EXPECT_CALL( + feedback_observer_, + OnAddPacket(AllOf( + Field(&RtpPacketSendInfo::media_ssrc, kSsrc), + Field(&RtpPacketSendInfo::transport_sequence_number, + kTransportSequenceNumber), + Field(&RtpPacketSendInfo::rtp_sequence_number, kStartSequenceNumber), + Field(&RtpPacketSendInfo::length, expected_bytes), + Field(&RtpPacketSendInfo::pacing_info, PacedPacketInfo())))); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + packet->AllocatePayload(kPayloadSize); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + sender->SendPacket(packet.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, PacketOptionsIsRetransmitSetByPacketType) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->set_packet_type(RtpPacketMediaType::kVideo); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + EXPECT_FALSE(transport_.last_packet()->options.is_retransmit); + + std::unique_ptr<RtpPacketToSend> retransmission = BuildRtpPacket(); + retransmission->set_packet_type(RtpPacketMediaType::kRetransmission); + retransmission->set_retransmitted_sequence_number( + media_packet->SequenceNumber()); + sender->SendPacket(retransmission.get(), PacedPacketInfo()); + EXPECT_TRUE(transport_.last_packet()->options.is_retransmit); +} + +TEST_F(RtpSenderEgressTest, DoesnSetIncludedInAllocationByDefault) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + sender->SendPacket(packet.get(), PacedPacketInfo()); + EXPECT_FALSE(transport_.last_packet()->options.included_in_feedback); + EXPECT_FALSE(transport_.last_packet()->options.included_in_allocation); +} + +TEST_F(RtpSenderEgressTest, + SetsIncludedInFeedbackWhenTransportSequenceNumberExtensionIsRegistered) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + sender->SendPacket(packet.get(), PacedPacketInfo()); + EXPECT_TRUE(transport_.last_packet()->options.included_in_feedback); +} + +TEST_F( + RtpSenderEgressTest, + SetsIncludedInAllocationWhenTransportSequenceNumberExtensionIsRegistered) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + sender->SendPacket(packet.get(), PacedPacketInfo()); + EXPECT_TRUE(transport_.last_packet()->options.included_in_allocation); +} + +TEST_F(RtpSenderEgressTest, + SetsIncludedInAllocationWhenForcedAsPartOfAllocation) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + sender->ForceIncludeSendPacketsInAllocation(true); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + sender->SendPacket(packet.get(), PacedPacketInfo()); + EXPECT_FALSE(transport_.last_packet()->options.included_in_feedback); + EXPECT_TRUE(transport_.last_packet()->options.included_in_allocation); +} + +TEST_F(RtpSenderEgressTest, + DoesntWriteTransmissionOffsetOnRtxPaddingBeforeMedia) { + header_extensions_.RegisterByUri(kTransmissionOffsetExtensionId, + TransmissionOffset::Uri()); + + // Prior to sending media, timestamps are 0. + std::unique_ptr<RtpPacketToSend> padding = + BuildRtpPacket(/*marker_bit=*/true, /*capture_time_ms=*/0); + padding->set_packet_type(RtpPacketMediaType::kPadding); + padding->SetSsrc(kRtxSsrc); + EXPECT_TRUE(padding->HasExtension<TransmissionOffset>()); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + sender->SendPacket(std::move(padding), PacedPacketInfo()); + + absl::optional<int32_t> offset = + transport_.last_packet()->packet.GetExtension<TransmissionOffset>(); + EXPECT_EQ(offset, 0); +} + +TEST_F(RtpSenderEgressTest, OnSendSideDelayUpdated) { + StrictMock<MockSendSideDelayObserver> send_side_delay_observer; + RtpRtcpInterface::Configuration config = DefaultConfig(); + config.send_side_delay_observer = &send_side_delay_observer; + auto sender = std::make_unique<RtpSenderEgress>(config, &packet_history_); + + // Send packet with 10 ms send-side delay. The average, max and total should + // be 10 ms. + EXPECT_CALL(send_side_delay_observer, SendSideDelayUpdated(10, 10, kSsrc)); + int64_t capture_time_ms = clock_->TimeInMilliseconds(); + time_controller_.AdvanceTime(TimeDelta::Millis(10)); + sender->SendPacket(BuildRtpPacket(/*marker=*/true, capture_time_ms).get(), + PacedPacketInfo()); + + // Send another packet with 20 ms delay. The average, max and total should be + // 15, 20 and 30 ms respectively. + EXPECT_CALL(send_side_delay_observer, SendSideDelayUpdated(15, 20, kSsrc)); + capture_time_ms = clock_->TimeInMilliseconds(); + time_controller_.AdvanceTime(TimeDelta::Millis(20)); + sender->SendPacket(BuildRtpPacket(/*marker=*/true, capture_time_ms).get(), + PacedPacketInfo()); + + // Send another packet at the same time, which replaces the last packet. + // Since this packet has 0 ms delay, the average is now 5 ms and max is 10 ms. + // The total counter stays the same though. + // TODO(terelius): Is is not clear that this is the right behavior. + EXPECT_CALL(send_side_delay_observer, SendSideDelayUpdated(5, 10, kSsrc)); + capture_time_ms = clock_->TimeInMilliseconds(); + sender->SendPacket(BuildRtpPacket(/*marker=*/true, capture_time_ms).get(), + PacedPacketInfo()); + + // Send a packet 1 second later. The earlier packets should have timed + // out, so both max and average should be the delay of this packet. The total + // keeps increasing. + time_controller_.AdvanceTime(TimeDelta::Seconds(1)); + EXPECT_CALL(send_side_delay_observer, SendSideDelayUpdated(1, 1, kSsrc)); + capture_time_ms = clock_->TimeInMilliseconds(); + time_controller_.AdvanceTime(TimeDelta::Millis(1)); + sender->SendPacket(BuildRtpPacket(/*marker=*/true, capture_time_ms).get(), + PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, WritesPacerExitToTimingExtension) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + header_extensions_.RegisterByUri(kVideoTimingExtensionId, + VideoTimingExtension::Uri()); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->SetExtension<VideoTimingExtension>(VideoSendTiming{}); + + const int kStoredTimeInMs = 100; + time_controller_.AdvanceTime(TimeDelta::Millis(kStoredTimeInMs)); + sender->SendPacket(packet.get(), PacedPacketInfo()); + ASSERT_TRUE(transport_.last_packet().has_value()); + + VideoSendTiming video_timing; + EXPECT_TRUE( + transport_.last_packet()->packet.GetExtension<VideoTimingExtension>( + &video_timing)); + EXPECT_EQ(video_timing.pacer_exit_delta_ms, kStoredTimeInMs); +} + +TEST_F(RtpSenderEgressTest, WritesNetwork2ToTimingExtension) { + RtpRtcpInterface::Configuration rtp_config = DefaultConfig(); + rtp_config.populate_network2_timestamp = true; + auto sender = std::make_unique<RtpSenderEgress>(rtp_config, &packet_history_); + header_extensions_.RegisterByUri(kVideoTimingExtensionId, + VideoTimingExtension::Uri()); + + const uint16_t kPacerExitMs = 1234u; + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + VideoSendTiming send_timing = {}; + send_timing.pacer_exit_delta_ms = kPacerExitMs; + packet->SetExtension<VideoTimingExtension>(send_timing); + + const int kStoredTimeInMs = 100; + time_controller_.AdvanceTime(TimeDelta::Millis(kStoredTimeInMs)); + sender->SendPacket(packet.get(), PacedPacketInfo()); + ASSERT_TRUE(transport_.last_packet().has_value()); + + VideoSendTiming video_timing; + EXPECT_TRUE( + transport_.last_packet()->packet.GetExtension<VideoTimingExtension>( + &video_timing)); + EXPECT_EQ(video_timing.network2_timestamp_delta_ms, kStoredTimeInMs); + EXPECT_EQ(video_timing.pacer_exit_delta_ms, kPacerExitMs); +} + +TEST_F(RtpSenderEgressTest, OnSendPacketUpdated) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + const uint16_t kTransportSequenceNumber = 1; + EXPECT_CALL(send_packet_observer_, + OnSendPacket(kTransportSequenceNumber, + clock_->TimeInMilliseconds(), kSsrc)); + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + sender->SendPacket(packet.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, OnSendPacketNotUpdatedForRetransmits) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + const uint16_t kTransportSequenceNumber = 1; + EXPECT_CALL(send_packet_observer_, OnSendPacket).Times(0); + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + packet->set_packet_type(RtpPacketMediaType::kRetransmission); + packet->set_retransmitted_sequence_number(packet->SequenceNumber()); + sender->SendPacket(packet.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, ReportsFecRate) { + constexpr int kNumPackets = 10; + constexpr TimeDelta kTimeBetweenPackets = TimeDelta::Millis(33); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + DataSize total_fec_data_sent = DataSize::Zero(); + // Send some packets, alternating between media and FEC. + for (size_t i = 0; i < kNumPackets; ++i) { + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->set_packet_type(RtpPacketMediaType::kVideo); + media_packet->SetPayloadSize(500); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + + std::unique_ptr<RtpPacketToSend> fec_packet = BuildRtpPacket(); + fec_packet->set_packet_type(RtpPacketMediaType::kForwardErrorCorrection); + fec_packet->SetPayloadSize(123); + sender->SendPacket(fec_packet.get(), PacedPacketInfo()); + total_fec_data_sent += DataSize::Bytes(fec_packet->size()); + + time_controller_.AdvanceTime(kTimeBetweenPackets); + } + + EXPECT_NEAR( + (sender->GetSendRates()[RtpPacketMediaType::kForwardErrorCorrection]) + .bps(), + (total_fec_data_sent / (kTimeBetweenPackets * kNumPackets)).bps(), 500); +} + +TEST_F(RtpSenderEgressTest, BitrateCallbacks) { + class MockBitrateStaticsObserver : public BitrateStatisticsObserver { + public: + MOCK_METHOD(void, Notify, (uint32_t, uint32_t, uint32_t), (override)); + } observer; + + RtpRtcpInterface::Configuration config = DefaultConfig(); + config.send_bitrate_observer = &observer; + auto sender = std::make_unique<RtpSenderEgress>(config, &packet_history_); + + // Simulate kNumPackets sent with kPacketInterval intervals, with the + // number of packets selected so that we fill (but don't overflow) the one + // second averaging window. + const TimeDelta kWindowSize = TimeDelta::Seconds(1); + const TimeDelta kPacketInterval = TimeDelta::Millis(20); + const int kNumPackets = (kWindowSize - kPacketInterval) / kPacketInterval; + + DataSize total_data_sent = DataSize::Zero(); + + // Send all but on of the packets, expect a call for each packet but don't + // verify bitrate yet (noisy measurements in the beginning). + for (int i = 0; i < kNumPackets; ++i) { + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->SetPayloadSize(500); + // Mark all packets as retransmissions - will cause total and retransmission + // rates to be equal. + packet->set_packet_type(RtpPacketMediaType::kRetransmission); + packet->set_retransmitted_sequence_number(packet->SequenceNumber()); + total_data_sent += DataSize::Bytes(packet->size()); + + EXPECT_CALL(observer, Notify(_, _, kSsrc)) + .WillOnce([&](uint32_t total_bitrate_bps, + uint32_t retransmission_bitrate_bps, uint32_t /*ssrc*/) { + TimeDelta window_size = i * kPacketInterval + TimeDelta::Millis(1); + // If there is just a single data point, there is no well defined + // averaging window so a bitrate of zero will be reported. + const double expected_bitrate_bps = + i == 0 ? 0.0 : (total_data_sent / window_size).bps(); + EXPECT_NEAR(total_bitrate_bps, expected_bitrate_bps, 500); + EXPECT_NEAR(retransmission_bitrate_bps, expected_bitrate_bps, 500); + }); + + sender->SendPacket(packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(kPacketInterval); + } +} + +TEST_F(RtpSenderEgressTest, DoesNotPutNotRetransmittablePacketsInHistory) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + packet_history_.SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->set_allow_retransmission(false); + sender->SendPacket(packet.get(), PacedPacketInfo()); + EXPECT_FALSE(packet_history_.GetPacketState(packet->SequenceNumber())); +} + +TEST_F(RtpSenderEgressTest, PutsRetransmittablePacketsInHistory) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + packet_history_.SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->set_allow_retransmission(true); + sender->SendPacket(packet.get(), PacedPacketInfo()); + EXPECT_TRUE(packet_history_.GetPacketState(packet->SequenceNumber())); +} + +TEST_F(RtpSenderEgressTest, DoesNotPutNonMediaInHistory) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + packet_history_.SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + // Non-media packets, even when marked as retransmittable, are not put into + // the packet history. + std::unique_ptr<RtpPacketToSend> retransmission = BuildRtpPacket(); + retransmission->set_allow_retransmission(true); + retransmission->set_packet_type(RtpPacketMediaType::kRetransmission); + retransmission->set_retransmitted_sequence_number( + retransmission->SequenceNumber()); + sender->SendPacket(retransmission.get(), PacedPacketInfo()); + EXPECT_FALSE( + packet_history_.GetPacketState(retransmission->SequenceNumber())); + + std::unique_ptr<RtpPacketToSend> fec = BuildRtpPacket(); + fec->set_allow_retransmission(true); + fec->set_packet_type(RtpPacketMediaType::kForwardErrorCorrection); + sender->SendPacket(fec.get(), PacedPacketInfo()); + EXPECT_FALSE(packet_history_.GetPacketState(fec->SequenceNumber())); + + std::unique_ptr<RtpPacketToSend> padding = BuildRtpPacket(); + padding->set_allow_retransmission(true); + padding->set_packet_type(RtpPacketMediaType::kPadding); + sender->SendPacket(padding.get(), PacedPacketInfo()); + EXPECT_FALSE(packet_history_.GetPacketState(padding->SequenceNumber())); +} + +TEST_F(RtpSenderEgressTest, UpdatesSendStatusOfRetransmittedPackets) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + packet_history_.SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + // Send a packet, putting it in the history. + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->set_allow_retransmission(true); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + EXPECT_TRUE(packet_history_.GetPacketState(media_packet->SequenceNumber())); + + // Simulate a retransmission, marking the packet as pending. + std::unique_ptr<RtpPacketToSend> retransmission = + packet_history_.GetPacketAndMarkAsPending(media_packet->SequenceNumber()); + retransmission->set_retransmitted_sequence_number( + media_packet->SequenceNumber()); + retransmission->set_packet_type(RtpPacketMediaType::kRetransmission); + EXPECT_TRUE(packet_history_.GetPacketState(media_packet->SequenceNumber())); + + // Simulate packet leaving pacer, the packet should be marked as non-pending. + sender->SendPacket(retransmission.get(), PacedPacketInfo()); + EXPECT_TRUE(packet_history_.GetPacketState(media_packet->SequenceNumber())); +} + +TEST_F(RtpSenderEgressTest, StreamDataCountersCallbacks) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + const RtpPacketCounter kEmptyCounter; + RtpPacketCounter expected_transmitted_counter; + RtpPacketCounter expected_retransmission_counter; + + // Send a media packet. + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->SetPayloadSize(6); + media_packet->SetSequenceNumber(kStartSequenceNumber); + media_packet->set_time_in_send_queue(TimeDelta::Millis(10)); + expected_transmitted_counter.packets += 1; + expected_transmitted_counter.payload_bytes += media_packet->payload_size(); + expected_transmitted_counter.header_bytes += media_packet->headers_size(); + expected_transmitted_counter.total_packet_delay += TimeDelta::Millis(10); + + EXPECT_CALL( + mock_rtp_stats_callback_, + DataCountersUpdated(AllOf(Field(&StreamDataCounters::transmitted, + expected_transmitted_counter), + Field(&StreamDataCounters::retransmitted, + expected_retransmission_counter), + Field(&StreamDataCounters::fec, kEmptyCounter)), + kSsrc)); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); + + // Send a retransmission. Retransmissions are counted into both transmitted + // and retransmitted packet statistics. + std::unique_ptr<RtpPacketToSend> retransmission_packet = BuildRtpPacket(); + retransmission_packet->set_packet_type(RtpPacketMediaType::kRetransmission); + retransmission_packet->SetSequenceNumber(kStartSequenceNumber); + retransmission_packet->set_retransmitted_sequence_number( + kStartSequenceNumber); + retransmission_packet->set_time_in_send_queue(TimeDelta::Millis(20)); + media_packet->SetPayloadSize(7); + expected_transmitted_counter.packets += 1; + expected_transmitted_counter.payload_bytes += + retransmission_packet->payload_size(); + expected_transmitted_counter.header_bytes += + retransmission_packet->headers_size(); + expected_transmitted_counter.total_packet_delay += TimeDelta::Millis(20); + + expected_retransmission_counter.packets += 1; + expected_retransmission_counter.payload_bytes += + retransmission_packet->payload_size(); + expected_retransmission_counter.header_bytes += + retransmission_packet->headers_size(); + expected_retransmission_counter.total_packet_delay += TimeDelta::Millis(20); + + EXPECT_CALL( + mock_rtp_stats_callback_, + DataCountersUpdated(AllOf(Field(&StreamDataCounters::transmitted, + expected_transmitted_counter), + Field(&StreamDataCounters::retransmitted, + expected_retransmission_counter), + Field(&StreamDataCounters::fec, kEmptyCounter)), + kSsrc)); + sender->SendPacket(retransmission_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); + + // Send a padding packet. + std::unique_ptr<RtpPacketToSend> padding_packet = BuildRtpPacket(); + padding_packet->set_packet_type(RtpPacketMediaType::kPadding); + padding_packet->SetPadding(224); + padding_packet->SetSequenceNumber(kStartSequenceNumber + 1); + padding_packet->set_time_in_send_queue(TimeDelta::Millis(30)); + expected_transmitted_counter.packets += 1; + expected_transmitted_counter.padding_bytes += padding_packet->padding_size(); + expected_transmitted_counter.header_bytes += padding_packet->headers_size(); + expected_transmitted_counter.total_packet_delay += TimeDelta::Millis(30); + + EXPECT_CALL( + mock_rtp_stats_callback_, + DataCountersUpdated(AllOf(Field(&StreamDataCounters::transmitted, + expected_transmitted_counter), + Field(&StreamDataCounters::retransmitted, + expected_retransmission_counter), + Field(&StreamDataCounters::fec, kEmptyCounter)), + kSsrc)); + sender->SendPacket(padding_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); +} + +TEST_F(RtpSenderEgressTest, StreamDataCountersCallbacksFec) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + const RtpPacketCounter kEmptyCounter; + RtpPacketCounter expected_transmitted_counter; + RtpPacketCounter expected_fec_counter; + + // Send a media packet. + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->SetPayloadSize(6); + expected_transmitted_counter.packets += 1; + expected_transmitted_counter.payload_bytes += media_packet->payload_size(); + expected_transmitted_counter.header_bytes += media_packet->headers_size(); + + EXPECT_CALL( + mock_rtp_stats_callback_, + DataCountersUpdated( + AllOf(Field(&StreamDataCounters::transmitted, + expected_transmitted_counter), + Field(&StreamDataCounters::retransmitted, kEmptyCounter), + Field(&StreamDataCounters::fec, expected_fec_counter)), + kSsrc)); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); + + // Send and FEC packet. FEC is counted into both transmitted and FEC packet + // statistics. + std::unique_ptr<RtpPacketToSend> fec_packet = BuildRtpPacket(); + fec_packet->set_packet_type(RtpPacketMediaType::kForwardErrorCorrection); + fec_packet->SetPayloadSize(6); + expected_transmitted_counter.packets += 1; + expected_transmitted_counter.payload_bytes += fec_packet->payload_size(); + expected_transmitted_counter.header_bytes += fec_packet->headers_size(); + + expected_fec_counter.packets += 1; + expected_fec_counter.payload_bytes += fec_packet->payload_size(); + expected_fec_counter.header_bytes += fec_packet->headers_size(); + + EXPECT_CALL( + mock_rtp_stats_callback_, + DataCountersUpdated( + AllOf(Field(&StreamDataCounters::transmitted, + expected_transmitted_counter), + Field(&StreamDataCounters::retransmitted, kEmptyCounter), + Field(&StreamDataCounters::fec, expected_fec_counter)), + kSsrc)); + sender->SendPacket(fec_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); +} + +TEST_F(RtpSenderEgressTest, UpdatesDataCounters) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + const RtpPacketCounter kEmptyCounter; + + // Send a media packet. + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->SetPayloadSize(6); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); + + // Send an RTX retransmission packet. + std::unique_ptr<RtpPacketToSend> rtx_packet = BuildRtpPacket(); + rtx_packet->set_packet_type(RtpPacketMediaType::kRetransmission); + rtx_packet->SetSsrc(kRtxSsrc); + rtx_packet->SetPayloadSize(7); + rtx_packet->set_retransmitted_sequence_number(media_packet->SequenceNumber()); + sender->SendPacket(rtx_packet.get(), PacedPacketInfo()); + time_controller_.AdvanceTime(TimeDelta::Zero()); + + StreamDataCounters rtp_stats; + StreamDataCounters rtx_stats; + sender->GetDataCounters(&rtp_stats, &rtx_stats); + + EXPECT_EQ(rtp_stats.transmitted.packets, 1u); + EXPECT_EQ(rtp_stats.transmitted.payload_bytes, media_packet->payload_size()); + EXPECT_EQ(rtp_stats.transmitted.padding_bytes, media_packet->padding_size()); + EXPECT_EQ(rtp_stats.transmitted.header_bytes, media_packet->headers_size()); + EXPECT_EQ(rtp_stats.retransmitted, kEmptyCounter); + EXPECT_EQ(rtp_stats.fec, kEmptyCounter); + + // Retransmissions are counted both into transmitted and retransmitted + // packet counts. + EXPECT_EQ(rtx_stats.transmitted.packets, 1u); + EXPECT_EQ(rtx_stats.transmitted.payload_bytes, rtx_packet->payload_size()); + EXPECT_EQ(rtx_stats.transmitted.padding_bytes, rtx_packet->padding_size()); + EXPECT_EQ(rtx_stats.transmitted.header_bytes, rtx_packet->headers_size()); + EXPECT_EQ(rtx_stats.retransmitted, rtx_stats.transmitted); + EXPECT_EQ(rtx_stats.fec, kEmptyCounter); +} + +TEST_F(RtpSenderEgressTest, SendPacketUpdatesExtensions) { + header_extensions_.RegisterByUri(kVideoTimingExtensionId, + VideoTimingExtension::Uri()); + header_extensions_.RegisterByUri(kAbsoluteSendTimeExtensionId, + AbsoluteSendTime::Uri()); + header_extensions_.RegisterByUri(kTransmissionOffsetExtensionId, + TransmissionOffset::Uri()); + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->set_packetization_finish_time(clock_->CurrentTime()); + + const int32_t kDiffMs = 10; + time_controller_.AdvanceTime(TimeDelta::Millis(kDiffMs)); + + sender->SendPacket(packet.get(), PacedPacketInfo()); + + RtpPacketReceived received_packet = transport_.last_packet()->packet; + + EXPECT_EQ(received_packet.GetExtension<TransmissionOffset>(), kDiffMs * 90); + + EXPECT_EQ(received_packet.GetExtension<AbsoluteSendTime>(), + AbsoluteSendTime::To24Bits(clock_->CurrentTime())); + + VideoSendTiming timing; + EXPECT_TRUE(received_packet.GetExtension<VideoTimingExtension>(&timing)); + EXPECT_EQ(timing.pacer_exit_delta_ms, kDiffMs); +} + +TEST_F(RtpSenderEgressTest, SendPacketSetsPacketOptions) { + const uint16_t kPacketId = 42; + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + std::unique_ptr<RtpPacketToSend> packet = BuildRtpPacket(); + packet->SetExtension<TransportSequenceNumber>(kPacketId); + EXPECT_CALL(send_packet_observer_, OnSendPacket); + sender->SendPacket(packet.get(), PacedPacketInfo()); + + PacketOptions packet_options = transport_.last_packet()->options; + + EXPECT_EQ(packet_options.packet_id, kPacketId); + EXPECT_TRUE(packet_options.included_in_allocation); + EXPECT_TRUE(packet_options.included_in_feedback); + EXPECT_FALSE(packet_options.is_retransmit); + + // Send another packet as retransmission, verify options are populated. + std::unique_ptr<RtpPacketToSend> retransmission = BuildRtpPacket(); + retransmission->SetExtension<TransportSequenceNumber>(kPacketId + 1); + retransmission->set_packet_type(RtpPacketMediaType::kRetransmission); + retransmission->set_retransmitted_sequence_number(packet->SequenceNumber()); + sender->SendPacket(retransmission.get(), PacedPacketInfo()); + EXPECT_TRUE(transport_.last_packet()->options.is_retransmit); +} + +TEST_F(RtpSenderEgressTest, SendPacketUpdatesStats) { + const size_t kPayloadSize = 1000; + StrictMock<MockSendSideDelayObserver> send_side_delay_observer; + + const rtc::ArrayView<const RtpExtensionSize> kNoRtpHeaderExtensionSizes; + FlexfecSender flexfec(kFlexfectPayloadType, kFlexFecSsrc, kSsrc, /*mid=*/"", + /*header_extensions=*/{}, kNoRtpHeaderExtensionSizes, + /*rtp_state=*/nullptr, time_controller_.GetClock()); + RtpRtcpInterface::Configuration config = DefaultConfig(); + config.fec_generator = &flexfec; + config.send_side_delay_observer = &send_side_delay_observer; + auto sender = std::make_unique<RtpSenderEgress>(config, &packet_history_); + + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + const int64_t capture_time_ms = clock_->TimeInMilliseconds(); + + std::unique_ptr<RtpPacketToSend> video_packet = BuildRtpPacket(); + video_packet->set_packet_type(RtpPacketMediaType::kVideo); + video_packet->SetPayloadSize(kPayloadSize); + video_packet->SetExtension<TransportSequenceNumber>(1); + + std::unique_ptr<RtpPacketToSend> rtx_packet = BuildRtpPacket(); + rtx_packet->SetSsrc(kRtxSsrc); + rtx_packet->set_packet_type(RtpPacketMediaType::kRetransmission); + rtx_packet->set_retransmitted_sequence_number(video_packet->SequenceNumber()); + rtx_packet->SetPayloadSize(kPayloadSize); + rtx_packet->SetExtension<TransportSequenceNumber>(2); + + std::unique_ptr<RtpPacketToSend> fec_packet = BuildRtpPacket(); + fec_packet->SetSsrc(kFlexFecSsrc); + fec_packet->set_packet_type(RtpPacketMediaType::kForwardErrorCorrection); + fec_packet->SetPayloadSize(kPayloadSize); + fec_packet->SetExtension<TransportSequenceNumber>(3); + + const int64_t kDiffMs = 25; + time_controller_.AdvanceTime(TimeDelta::Millis(kDiffMs)); + + EXPECT_CALL(send_side_delay_observer, + SendSideDelayUpdated(kDiffMs, kDiffMs, kSsrc)); + EXPECT_CALL(send_side_delay_observer, + SendSideDelayUpdated(kDiffMs, kDiffMs, kFlexFecSsrc)); + + EXPECT_CALL(send_packet_observer_, OnSendPacket(1, capture_time_ms, kSsrc)); + + sender->SendPacket(video_packet.get(), PacedPacketInfo()); + + // Send packet observer not called for padding/retransmissions. + EXPECT_CALL(send_packet_observer_, OnSendPacket(2, _, _)).Times(0); + sender->SendPacket(rtx_packet.get(), PacedPacketInfo()); + + EXPECT_CALL(send_packet_observer_, + OnSendPacket(3, capture_time_ms, kFlexFecSsrc)); + sender->SendPacket(fec_packet.get(), PacedPacketInfo()); + + time_controller_.AdvanceTime(TimeDelta::Zero()); + StreamDataCounters rtp_stats; + StreamDataCounters rtx_stats; + sender->GetDataCounters(&rtp_stats, &rtx_stats); + EXPECT_EQ(rtp_stats.transmitted.packets, 2u); + EXPECT_EQ(rtp_stats.fec.packets, 1u); + EXPECT_EQ(rtx_stats.retransmitted.packets, 1u); +} + +TEST_F(RtpSenderEgressTest, TransportFeedbackObserverWithRetransmission) { + const uint16_t kTransportSequenceNumber = 17; + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + std::unique_ptr<RtpPacketToSend> retransmission = BuildRtpPacket(); + retransmission->set_packet_type(RtpPacketMediaType::kRetransmission); + retransmission->SetExtension<TransportSequenceNumber>( + kTransportSequenceNumber); + uint16_t retransmitted_seq = retransmission->SequenceNumber() - 2; + retransmission->set_retransmitted_sequence_number(retransmitted_seq); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + EXPECT_CALL( + feedback_observer_, + OnAddPacket(AllOf( + Field(&RtpPacketSendInfo::media_ssrc, kSsrc), + Field(&RtpPacketSendInfo::rtp_sequence_number, retransmitted_seq), + Field(&RtpPacketSendInfo::transport_sequence_number, + kTransportSequenceNumber)))); + sender->SendPacket(retransmission.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, TransportFeedbackObserverWithRtxRetransmission) { + const uint16_t kTransportSequenceNumber = 17; + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + std::unique_ptr<RtpPacketToSend> rtx_retransmission = BuildRtpPacket(); + rtx_retransmission->SetSsrc(kRtxSsrc); + rtx_retransmission->SetExtension<TransportSequenceNumber>( + kTransportSequenceNumber); + rtx_retransmission->set_packet_type(RtpPacketMediaType::kRetransmission); + uint16_t rtx_retransmitted_seq = rtx_retransmission->SequenceNumber() - 2; + rtx_retransmission->set_retransmitted_sequence_number(rtx_retransmitted_seq); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + EXPECT_CALL( + feedback_observer_, + OnAddPacket(AllOf( + Field(&RtpPacketSendInfo::media_ssrc, kSsrc), + Field(&RtpPacketSendInfo::rtp_sequence_number, rtx_retransmitted_seq), + Field(&RtpPacketSendInfo::transport_sequence_number, + kTransportSequenceNumber)))); + sender->SendPacket(rtx_retransmission.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, TransportFeedbackObserverPadding) { + const uint16_t kTransportSequenceNumber = 17; + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + std::unique_ptr<RtpPacketToSend> padding = BuildRtpPacket(); + padding->SetPadding(224); + padding->set_packet_type(RtpPacketMediaType::kPadding); + padding->SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + EXPECT_CALL( + feedback_observer_, + OnAddPacket(AllOf(Field(&RtpPacketSendInfo::media_ssrc, absl::nullopt), + Field(&RtpPacketSendInfo::transport_sequence_number, + kTransportSequenceNumber)))); + sender->SendPacket(padding.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, TransportFeedbackObserverRtxPadding) { + const uint16_t kTransportSequenceNumber = 17; + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + std::unique_ptr<RtpPacketToSend> rtx_padding = BuildRtpPacket(); + rtx_padding->SetPadding(224); + rtx_padding->SetSsrc(kRtxSsrc); + rtx_padding->set_packet_type(RtpPacketMediaType::kPadding); + rtx_padding->SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + EXPECT_CALL( + feedback_observer_, + OnAddPacket(AllOf(Field(&RtpPacketSendInfo::media_ssrc, absl::nullopt), + Field(&RtpPacketSendInfo::transport_sequence_number, + kTransportSequenceNumber)))); + sender->SendPacket(rtx_padding.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, TransportFeedbackObserverFec) { + const uint16_t kTransportSequenceNumber = 17; + header_extensions_.RegisterByUri(kTransportSequenceNumberExtensionId, + TransportSequenceNumber::Uri()); + + std::unique_ptr<RtpPacketToSend> fec_packet = BuildRtpPacket(); + fec_packet->SetSsrc(kFlexFecSsrc); + fec_packet->set_packet_type(RtpPacketMediaType::kForwardErrorCorrection); + fec_packet->SetExtension<TransportSequenceNumber>(kTransportSequenceNumber); + + const rtc::ArrayView<const RtpExtensionSize> kNoRtpHeaderExtensionSizes; + FlexfecSender flexfec(kFlexfectPayloadType, kFlexFecSsrc, kSsrc, /*mid=*/"", + /*header_extensions=*/{}, kNoRtpHeaderExtensionSizes, + /*rtp_state=*/nullptr, time_controller_.GetClock()); + RtpRtcpInterface::Configuration config = DefaultConfig(); + config.fec_generator = &flexfec; + auto sender = std::make_unique<RtpSenderEgress>(config, &packet_history_); + EXPECT_CALL( + feedback_observer_, + OnAddPacket(AllOf(Field(&RtpPacketSendInfo::media_ssrc, absl::nullopt), + Field(&RtpPacketSendInfo::transport_sequence_number, + kTransportSequenceNumber)))); + sender->SendPacket(fec_packet.get(), PacedPacketInfo()); +} + +TEST_F(RtpSenderEgressTest, SupportsAbortingRetransmissions) { + std::unique_ptr<RtpSenderEgress> sender = CreateRtpSenderEgress(); + packet_history_.SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + // Create a packet and send it so it is put in the history. + std::unique_ptr<RtpPacketToSend> media_packet = BuildRtpPacket(); + media_packet->set_packet_type(RtpPacketMediaType::kVideo); + media_packet->set_allow_retransmission(true); + const uint16_t media_sequence_number = media_packet->SequenceNumber(); + sender->SendPacket(media_packet.get(), PacedPacketInfo()); + + // Fetch a retranmission packet from the history, this should mark the + // media packets as pending so it is not available to grab again. + std::unique_ptr<RtpPacketToSend> retransmission_packet = + packet_history_.GetPacketAndMarkAsPending(media_sequence_number); + ASSERT_TRUE(retransmission_packet); + EXPECT_FALSE( + packet_history_.GetPacketAndMarkAsPending(media_sequence_number)); + + // Mark retransmission as aborted, fetching packet is possible again. + retransmission_packet.reset(); + uint16_t kAbortedSequenceNumbers[] = {media_sequence_number}; + sender->OnAbortedRetransmissions(kAbortedSequenceNumbers); + EXPECT_TRUE(packet_history_.GetPacketAndMarkAsPending(media_sequence_number)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc new file mode 100644 index 0000000000..432c5e72d3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc @@ -0,0 +1,1373 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_sender.h" + +#include <memory> +#include <vector> + +#include "absl/strings/string_view.h" +#include "api/rtc_event_log/rtc_event.h" +#include "api/transport/field_trial_based_config.h" +#include "api/video/video_codec_constants.h" +#include "api/video/video_timing.h" +#include "logging/rtc_event_log/mock/mock_rtc_event_log.h" +#include "modules/rtp_rtcp/include/rtp_cvo.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/include/rtp_packet_sender.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/packet_sequencer.h" +#include "modules/rtp_rtcp/source/rtp_format_video_generic.h" +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor.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_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_sender_video.h" +#include "modules/rtp_rtcp/source/video_fec_generator.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/logging.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/strings/string_builder.h" +#include "test/field_trial.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_transport.h" +#include "test/scoped_key_value_config.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +namespace { +enum : int { // The first valid value is 1. + kAbsoluteSendTimeExtensionId = 1, + kAudioLevelExtensionId, + kGenericDescriptorId, + kMidExtensionId, + kRepairedRidExtensionId, + kRidExtensionId, + kTransmissionTimeOffsetExtensionId, + kTransportSequenceNumberExtensionId, + kVideoRotationExtensionId, + kVideoTimingExtensionId, +}; + +const int kPayload = 100; +const int kRtxPayload = 98; +const uint32_t kTimestamp = 10; +const uint16_t kSeqNum = 33; +const uint32_t kSsrc = 725242; +const uint32_t kRtxSsrc = 12345; +const uint32_t kFlexFecSsrc = 45678; +const uint64_t kStartTime = 123456789; +const size_t kMaxPaddingSize = 224u; +const uint8_t kPayloadData[] = {47, 11, 32, 93, 89}; +const int64_t kDefaultExpectedRetransmissionTimeMs = 125; +const size_t kMaxPaddingLength = 224; // Value taken from rtp_sender.cc. +const uint32_t kTimestampTicksPerMs = 90; // 90kHz clock. +constexpr absl::string_view kMid = "mid"; +constexpr absl::string_view kRid = "f"; +constexpr bool kMarkerBit = true; + +using ::testing::_; +using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::Contains; +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Gt; +using ::testing::IsEmpty; +using ::testing::NiceMock; +using ::testing::Not; +using ::testing::Pointee; +using ::testing::Property; +using ::testing::Return; +using ::testing::SizeIs; + +class MockRtpPacketPacer : public RtpPacketSender { + public: + MockRtpPacketPacer() {} + virtual ~MockRtpPacketPacer() {} + + MOCK_METHOD(void, + EnqueuePackets, + (std::vector<std::unique_ptr<RtpPacketToSend>>), + (override)); + MOCK_METHOD(void, RemovePacketsForSsrc, (uint32_t), (override)); +}; + +} // namespace + +class RtpSenderTest : public ::testing::Test { + protected: + RtpSenderTest() + : time_controller_(Timestamp::Millis(kStartTime)), + clock_(time_controller_.GetClock()), + retransmission_rate_limiter_(clock_, 1000), + flexfec_sender_(0, + kFlexFecSsrc, + kSsrc, + "", + std::vector<RtpExtension>(), + std::vector<RtpExtensionSize>(), + nullptr, + clock_) {} + + void SetUp() override { SetUpRtpSender(true, false, nullptr); } + + void SetUpRtpSender(bool populate_network2, + bool always_send_mid_and_rid, + VideoFecGenerator* fec_generator) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.fec_generator = fec_generator; + config.populate_network2_timestamp = populate_network2; + config.always_send_mid_and_rid = always_send_mid_and_rid; + CreateSender(config); + } + + RtpRtcpInterface::Configuration GetDefaultConfig() { + RtpRtcpInterface::Configuration config; + config.clock = clock_; + config.local_media_ssrc = kSsrc; + config.rtx_send_ssrc = kRtxSsrc; + config.event_log = &mock_rtc_event_log_; + config.retransmission_rate_limiter = &retransmission_rate_limiter_; + config.paced_sender = &mock_paced_sender_; + config.field_trials = &field_trials_; + // Configure rid unconditionally, it has effect only if + // corresponding header extension is enabled. + config.rid = std::string(kRid); + return config; + } + + void CreateSender(const RtpRtcpInterface::Configuration& config) { + packet_history_ = std::make_unique<RtpPacketHistory>( + config.clock, config.enable_rtx_padding_prioritization); + sequencer_.emplace(kSsrc, kRtxSsrc, + /*require_marker_before_media_padding=*/!config.audio, + clock_); + rtp_sender_ = std::make_unique<RTPSender>(config, packet_history_.get(), + config.paced_sender); + sequencer_->set_media_sequence_number(kSeqNum); + rtp_sender_->SetTimestampOffset(0); + } + + GlobalSimulatedTimeController time_controller_; + Clock* const clock_; + NiceMock<MockRtcEventLog> mock_rtc_event_log_; + MockRtpPacketPacer mock_paced_sender_; + RateLimiter retransmission_rate_limiter_; + FlexfecSender flexfec_sender_; + + absl::optional<PacketSequencer> sequencer_; + std::unique_ptr<RtpPacketHistory> packet_history_; + std::unique_ptr<RTPSender> rtp_sender_; + + const test::ScopedKeyValueConfig field_trials_; + + std::unique_ptr<RtpPacketToSend> BuildRtpPacket(int payload_type, + bool marker_bit, + uint32_t timestamp, + int64_t capture_time_ms) { + auto packet = rtp_sender_->AllocatePacket(); + packet->SetPayloadType(payload_type); + packet->set_packet_type(RtpPacketMediaType::kVideo); + packet->SetMarker(marker_bit); + packet->SetTimestamp(timestamp); + packet->set_capture_time(Timestamp::Millis(capture_time_ms)); + return packet; + } + + std::unique_ptr<RtpPacketToSend> SendPacket(int64_t capture_time_ms, + int payload_length) { + uint32_t timestamp = capture_time_ms * 90; + auto packet = + BuildRtpPacket(kPayload, kMarkerBit, timestamp, capture_time_ms); + packet->AllocatePayload(payload_length); + packet->set_allow_retransmission(true); + + // Packet should be stored in a send bucket. + EXPECT_TRUE( + rtp_sender_->SendToNetwork(std::make_unique<RtpPacketToSend>(*packet))); + return packet; + } + + std::unique_ptr<RtpPacketToSend> SendGenericPacket() { + const int64_t kCaptureTimeMs = clock_->TimeInMilliseconds(); + // Use maximum allowed size to catch corner cases when packet is dropped + // because of lack of capacity for the media packet, or for an rtx packet + // containing the media packet. + return SendPacket(kCaptureTimeMs, + /*payload_length=*/rtp_sender_->MaxRtpPacketSize() - + rtp_sender_->ExpectedPerPacketOverhead()); + } + + std::vector<std::unique_ptr<RtpPacketToSend>> GeneratePadding( + size_t target_size_bytes) { + return rtp_sender_->GeneratePadding( + target_size_bytes, /*media_has_been_sent=*/true, + sequencer_->CanSendPaddingOnMediaSsrc()); + } + + std::vector<std::unique_ptr<RtpPacketToSend>> Sequence( + std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + for (auto& packet : packets) { + sequencer_->Sequence(*packet); + } + return packets; + } + + size_t GenerateAndSendPadding(size_t target_size_bytes) { + size_t generated_bytes = 0; + for (auto& packet : GeneratePadding(target_size_bytes)) { + generated_bytes += packet->payload_size() + packet->padding_size(); + rtp_sender_->SendToNetwork(std::move(packet)); + } + return generated_bytes; + } + + // The following are helpers for configuring the RTPSender. They must be + // called before sending any packets. + + // Enable the retransmission stream with sizable packet storage. + void EnableRtx() { + // RTX needs to be able to read the source packets from the packet store. + // Pick a number of packets to store big enough for any unit test. + constexpr uint16_t kNumberOfPacketsToStore = 100; + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, kNumberOfPacketsToStore); + rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); + rtp_sender_->SetRtxStatus(kRtxRetransmitted | kRtxRedundantPayloads); + } + + // Enable sending of the MID header extension for both the primary SSRC and + // the RTX SSRC. + void EnableMidSending(absl::string_view mid) { + rtp_sender_->RegisterRtpHeaderExtension(RtpMid::Uri(), kMidExtensionId); + rtp_sender_->SetMid(mid); + } + + // Enable sending of the RSID header extension for the primary SSRC and the + // RRSID header extension for the RTX SSRC. + void EnableRidSending() { + rtp_sender_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), + kRidExtensionId); + rtp_sender_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), + kRepairedRidExtensionId); + } +}; + +TEST_F(RtpSenderTest, AllocatePacketSetCsrc) { + // Configure rtp_sender with csrc. + std::vector<uint32_t> csrcs; + csrcs.push_back(0x23456789); + rtp_sender_->SetCsrcs(csrcs); + + auto packet = rtp_sender_->AllocatePacket(); + + ASSERT_TRUE(packet); + EXPECT_EQ(rtp_sender_->SSRC(), packet->Ssrc()); + EXPECT_EQ(csrcs, packet->Csrcs()); +} + +TEST_F(RtpSenderTest, AllocatePacketReserveExtensions) { + // Configure rtp_sender with extensions. + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransmissionOffset::Uri(), kTransmissionTimeOffsetExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension(AudioLevel::Uri(), + kAudioLevelExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + VideoOrientation::Uri(), kVideoRotationExtensionId)); + + auto packet = rtp_sender_->AllocatePacket(); + + ASSERT_TRUE(packet); + // Preallocate BWE extensions RtpSender set itself. + EXPECT_TRUE(packet->HasExtension<TransmissionOffset>()); + EXPECT_TRUE(packet->HasExtension<AbsoluteSendTime>()); + EXPECT_TRUE(packet->HasExtension<TransportSequenceNumber>()); + // Do not allocate media specific extensions. + EXPECT_FALSE(packet->HasExtension<AudioLevel>()); + EXPECT_FALSE(packet->HasExtension<VideoOrientation>()); +} + +TEST_F(RtpSenderTest, PaddingAlwaysAllowedOnAudio) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.audio = true; + CreateSender(config); + + std::unique_ptr<RtpPacketToSend> audio_packet = rtp_sender_->AllocatePacket(); + // Padding on audio stream allowed regardless of marker in the last packet. + audio_packet->SetMarker(false); + audio_packet->SetPayloadType(kPayload); + sequencer_->Sequence(*audio_packet); + + const size_t kPaddingSize = 59; + + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(AllOf( + Pointee(Property(&RtpPacketToSend::packet_type, + RtpPacketMediaType::kPadding)), + Pointee(Property(&RtpPacketToSend::padding_size, kPaddingSize)))))); + EXPECT_EQ(kPaddingSize, GenerateAndSendPadding(kPaddingSize)); + + // Requested padding size is too small, will send a larger one. + const size_t kMinPaddingSize = 50; + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre( + AllOf(Pointee(Property(&RtpPacketToSend::packet_type, + RtpPacketMediaType::kPadding)), + Pointee(Property(&RtpPacketToSend::padding_size, + kMinPaddingSize)))))); + EXPECT_EQ(kMinPaddingSize, GenerateAndSendPadding(kMinPaddingSize - 5)); +} + +TEST_F(RtpSenderTest, SendToNetworkForwardsPacketsToPacer) { + auto packet = BuildRtpPacket(kPayload, kMarkerBit, kTimestamp, 0); + Timestamp now = clock_->CurrentTime(); + + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(AllOf( + Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc)), + Pointee(Property(&RtpPacketToSend::capture_time, now)))))); + EXPECT_TRUE( + rtp_sender_->SendToNetwork(std::make_unique<RtpPacketToSend>(*packet))); +} + +TEST_F(RtpSenderTest, ReSendPacketForwardsPacketsToPacer) { + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + int64_t now_ms = clock_->TimeInMilliseconds(); + auto packet = BuildRtpPacket(kPayload, kMarkerBit, kTimestamp, now_ms); + packet->SetSequenceNumber(kSeqNum); + packet->set_allow_retransmission(true); + packet_history_->PutRtpPacket(std::move(packet), Timestamp::Millis(now_ms)); + + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(AllOf( + Pointee(Property(&RtpPacketToSend::Ssrc, kSsrc)), + Pointee(Property(&RtpPacketToSend::SequenceNumber, kSeqNum)), + Pointee(Property(&RtpPacketToSend::capture_time, + Timestamp::Millis(now_ms))), + Pointee(Property(&RtpPacketToSend::packet_type, + RtpPacketMediaType::kRetransmission)))))); + EXPECT_TRUE(rtp_sender_->ReSendPacket(kSeqNum)); +} + +// This test sends 1 regular video packet, then 4 padding packets, and then +// 1 more regular packet. +TEST_F(RtpSenderTest, SendPadding) { + constexpr int kNumPaddingPackets = 4; + EXPECT_CALL(mock_paced_sender_, EnqueuePackets); + std::unique_ptr<RtpPacketToSend> media_packet = + SendPacket(/*capture_time_ms=*/clock_->TimeInMilliseconds(), + /*payload_size=*/100); + sequencer_->Sequence(*media_packet); + + // Wait 50 ms before generating each padding packet. + for (int i = 0; i < kNumPaddingPackets; ++i) { + time_controller_.AdvanceTime(TimeDelta::Millis(50)); + const size_t kPaddingTargetBytes = 100; // Request 100 bytes of padding. + + // Padding should be sent on the media ssrc, with a continous sequence + // number range. Size will be forced to full pack size and the timestamp + // shall be that of the last media packet. + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::Ssrc, kSsrc), + Property(&RtpPacketToSend::padding_size, kMaxPaddingLength), + Property(&RtpPacketToSend::SequenceNumber, + media_packet->SequenceNumber() + i + 1), + Property(&RtpPacketToSend::Timestamp, + media_packet->Timestamp())))))); + std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets = + Sequence(GeneratePadding(kPaddingTargetBytes)); + ASSERT_THAT(padding_packets, SizeIs(1)); + rtp_sender_->SendToNetwork(std::move(padding_packets[0])); + } + + // Send a regular video packet again. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(Property( + &RtpPacketToSend::Timestamp, Gt(media_packet->Timestamp())))))); + + std::unique_ptr<RtpPacketToSend> next_media_packet = + SendPacket(/*capture_time_ms=*/clock_->TimeInMilliseconds(), + /*payload_size=*/100); +} + +TEST_F(RtpSenderTest, NoPaddingAsFirstPacketWithoutBweExtensions) { + EXPECT_THAT(rtp_sender_->GeneratePadding( + /*target_size_bytes=*/100, + /*media_has_been_sent=*/false, + /*can_send_padding_on_media_ssrc=*/false), + IsEmpty()); + + // Don't send padding before media even with RTX. + EnableRtx(); + EXPECT_THAT(rtp_sender_->GeneratePadding( + /*target_size_bytes=*/100, + /*media_has_been_sent=*/false, + /*can_send_padding_on_media_ssrc=*/false), + IsEmpty()); +} + +TEST_F(RtpSenderTest, RequiresRtxSsrcToEnableRtx) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = absl::nullopt; + RTPSender rtp_sender(config, packet_history_.get(), config.paced_sender); + rtp_sender.SetRtxPayloadType(kRtxPayload, kPayload); + + rtp_sender.SetRtxStatus(kRtxRetransmitted); + + EXPECT_EQ(rtp_sender.RtxStatus(), kRtxOff); +} + +TEST_F(RtpSenderTest, RequiresRtxPayloadTypesToEnableRtx) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = kRtxSsrc; + RTPSender rtp_sender(config, packet_history_.get(), config.paced_sender); + + rtp_sender.SetRtxStatus(kRtxRetransmitted); + + EXPECT_EQ(rtp_sender.RtxStatus(), kRtxOff); +} + +TEST_F(RtpSenderTest, CanEnableRtxWhenRtxSsrcAndPayloadTypeAreConfigured) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = kRtxSsrc; + RTPSender rtp_sender(config, packet_history_.get(), config.paced_sender); + rtp_sender.SetRtxPayloadType(kRtxPayload, kPayload); + + ASSERT_EQ(rtp_sender.RtxStatus(), kRtxOff); + rtp_sender.SetRtxStatus(kRtxRetransmitted); + + EXPECT_EQ(rtp_sender.RtxStatus(), kRtxRetransmitted); +} + +TEST_F(RtpSenderTest, AllowPaddingAsFirstPacketOnRtxWithTransportCc) { + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + + // Padding can't be sent as first packet on media SSRC since we don't know + // what payload type to assign. + EXPECT_THAT(rtp_sender_->GeneratePadding( + /*target_size_bytes=*/100, + /*media_has_been_sent=*/false, + /*can_send_padding_on_media_ssrc=*/false), + IsEmpty()); + + // With transportcc padding can be sent as first packet on the RTX SSRC. + EnableRtx(); + EXPECT_THAT(rtp_sender_->GeneratePadding( + /*target_size_bytes=*/100, + /*media_has_been_sent=*/false, + /*can_send_padding_on_media_ssrc=*/false), + Not(IsEmpty())); +} + +TEST_F(RtpSenderTest, AllowPaddingAsFirstPacketOnRtxWithAbsSendTime) { + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); + + // Padding can't be sent as first packet on media SSRC since we don't know + // what payload type to assign. + EXPECT_THAT(rtp_sender_->GeneratePadding( + /*target_size_bytes=*/100, + /*media_has_been_sent=*/false, + /*can_send_padding_on_media_ssrc=*/false), + IsEmpty()); + + // With abs send time, padding can be sent as first packet on the RTX SSRC. + EnableRtx(); + EXPECT_THAT(rtp_sender_->GeneratePadding( + /*target_size_bytes=*/100, + /*media_has_been_sent=*/false, + /*can_send_padding_on_media_ssrc=*/false), + Not(IsEmpty())); +} + +TEST_F(RtpSenderTest, UpdatesTimestampsOnPlainRtxPadding) { + EnableRtx(); + // Timestamps as set based on capture time in RtpSenderTest. + const int64_t start_time = clock_->TimeInMilliseconds(); + const uint32_t start_timestamp = start_time * kTimestampTicksPerMs; + + // Start by sending one media packet. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre( + AllOf(Pointee(Property(&RtpPacketToSend::padding_size, 0u)), + Pointee(Property(&RtpPacketToSend::Timestamp, start_timestamp)), + Pointee(Property(&RtpPacketToSend::capture_time, + Timestamp::Millis(start_time))))))); + std::unique_ptr<RtpPacketToSend> media_packet = + SendPacket(start_time, /*payload_size=*/600); + sequencer_->Sequence(*media_packet); + + // Advance time before sending padding. + const TimeDelta kTimeDiff = TimeDelta::Millis(17); + time_controller_.AdvanceTime(kTimeDiff); + + // Timestamps on padding should be offset from the sent media. + EXPECT_THAT( + Sequence(GeneratePadding(/*target_size_bytes=*/100)), + Each(Pointee(AllOf( + Property(&RtpPacketToSend::padding_size, kMaxPaddingLength), + Property(&RtpPacketToSend::Timestamp, + start_timestamp + (kTimestampTicksPerMs * kTimeDiff.ms())), + Property(&RtpPacketToSend::capture_time, + Timestamp::Millis(start_time) + kTimeDiff))))); +} + +TEST_F(RtpSenderTest, KeepsTimestampsOnPayloadPadding) { + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + EnableRtx(); + // Timestamps as set based on capture time in RtpSenderTest. + const int64_t start_time = clock_->TimeInMilliseconds(); + const uint32_t start_timestamp = start_time * kTimestampTicksPerMs; + const size_t kPayloadSize = 200; + const size_t kRtxHeaderSize = 2; + + // Start by sending one media packet and putting in the packet history. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre( + AllOf(Pointee(Property(&RtpPacketToSend::padding_size, 0u)), + Pointee(Property(&RtpPacketToSend::Timestamp, start_timestamp)), + Pointee(Property(&RtpPacketToSend::capture_time, + Timestamp::Millis(start_time))))))); + std::unique_ptr<RtpPacketToSend> media_packet = + SendPacket(start_time, kPayloadSize); + packet_history_->PutRtpPacket(std::move(media_packet), + Timestamp::Millis(start_time)); + + // Advance time before sending padding. + const TimeDelta kTimeDiff = TimeDelta::Millis(17); + time_controller_.AdvanceTime(kTimeDiff); + + // Timestamps on payload padding should be set to original. + EXPECT_THAT(GeneratePadding(/*target_size_bytes=*/100), + Each(AllOf(Pointee(Property(&RtpPacketToSend::padding_size, 0u)), + Pointee(Property(&RtpPacketToSend::payload_size, + kPayloadSize + kRtxHeaderSize)), + Pointee(Property(&RtpPacketToSend::Timestamp, + start_timestamp)), + Pointee(Property(&RtpPacketToSend::capture_time, + Timestamp::Millis(start_time)))))); +} + +// Test that the MID header extension is included on sent packets when +// configured. +TEST_F(RtpSenderTest, MidIncludedOnSentPackets) { + EnableMidSending(kMid); + + // Send a couple packets, expect both packets to have the MID set. + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee( + Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid))))) + .Times(2); + SendGenericPacket(); + SendGenericPacket(); +} + +TEST_F(RtpSenderTest, RidIncludedOnSentPackets) { + EnableRidSending(); + + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(Property( + &RtpPacketToSend::GetExtension<RtpStreamId>, kRid))))); + SendGenericPacket(); +} + +TEST_F(RtpSenderTest, RidIncludedOnRtxSentPackets) { + EnableRtx(); + EnableRidSending(); + + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid), + Property(&RtpPacketToSend::HasExtension<RepairedRtpStreamId>, + false)))))) + .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + sequencer_->Sequence(*packets[0]); + packet_history_->PutRtpPacket(std::move(packets[0]), + clock_->CurrentTime()); + }); + SendGenericPacket(); + + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::GetExtension<RepairedRtpStreamId>, kRid), + Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); + rtp_sender_->ReSendPacket(kSeqNum); +} + +TEST_F(RtpSenderTest, MidAndRidNotIncludedOnSentPacketsAfterAck) { + EnableMidSending(kMid); + EnableRidSending(); + + // This first packet should include both MID and RID. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), + Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid)))))); + auto first_built_packet = SendGenericPacket(); + rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); + + // The second packet should include neither since an ack was received. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::HasExtension<RtpMid>, false), + Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); + SendGenericPacket(); +} + +TEST_F(RtpSenderTest, MidAndRidAlwaysIncludedOnSentPacketsWhenConfigured) { + SetUpRtpSender(false, /*always_send_mid_and_rid=*/true, nullptr); + EnableMidSending(kMid); + EnableRidSending(); + + // Send two media packets: one before and one after the ack. + // Due to the configuration, both sent packets should contain MID and RID. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee( + AllOf(Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), + Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid)))))) + .Times(2); + auto first_built_packet = SendGenericPacket(); + rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); + SendGenericPacket(); +} + +// Test that the first RTX packet includes both MID and RRID even if the packet +// being retransmitted did not have MID or RID. The MID and RID are needed on +// the first packets for a given SSRC, and RTX packets are sent on a separate +// SSRC. +TEST_F(RtpSenderTest, MidAndRidIncludedOnFirstRtxPacket) { + EnableRtx(); + EnableMidSending(kMid); + EnableRidSending(); + + // This first packet will include both MID and RID. + EXPECT_CALL(mock_paced_sender_, EnqueuePackets); + auto first_built_packet = SendGenericPacket(); + rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); + + // The second packet will include neither since an ack was received, put + // it in the packet history for retransmission. + EXPECT_CALL(mock_paced_sender_, EnqueuePackets(SizeIs(1))) + .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + packet_history_->PutRtpPacket(std::move(packets[0]), + clock_->CurrentTime()); + }); + auto second_built_packet = SendGenericPacket(); + + // The first RTX packet should include MID and RRID. + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), + Property(&RtpPacketToSend::GetExtension<RepairedRtpStreamId>, + kRid)))))); + rtp_sender_->ReSendPacket(second_built_packet->SequenceNumber()); +} + +// Test that the RTX packets sent after receving an ACK on the RTX SSRC does +// not include either MID or RRID even if the packet being retransmitted did +// had a MID or RID. +TEST_F(RtpSenderTest, MidAndRidNotIncludedOnRtxPacketsAfterAck) { + EnableRtx(); + EnableMidSending(kMid); + EnableRidSending(); + + // This first packet will include both MID and RID. + auto first_built_packet = SendGenericPacket(); + sequencer_->Sequence(*first_built_packet); + packet_history_->PutRtpPacket( + std::make_unique<RtpPacketToSend>(*first_built_packet), + /*send_time=*/clock_->CurrentTime()); + rtp_sender_->OnReceivedAckOnSsrc(first_built_packet->SequenceNumber()); + + // The second packet will include neither since an ack was received. + auto second_built_packet = SendGenericPacket(); + sequencer_->Sequence(*second_built_packet); + packet_history_->PutRtpPacket( + std::make_unique<RtpPacketToSend>(*second_built_packet), + /*send_time=*/clock_->CurrentTime()); + + // The first RTX packet will include MID and RRID. + EXPECT_CALL(mock_paced_sender_, EnqueuePackets(SizeIs(1))) + .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + rtp_sender_->OnReceivedAckOnRtxSsrc(packets[0]->SequenceNumber()); + packet_history_->MarkPacketAsSent( + *packets[0]->retransmitted_sequence_number()); + }); + rtp_sender_->ReSendPacket(second_built_packet->SequenceNumber()); + + // The second and third RTX packets should not include MID nor RRID. + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::HasExtension<RtpMid>, false), + Property(&RtpPacketToSend::HasExtension<RepairedRtpStreamId>, + false)))))) + .Times(2); + rtp_sender_->ReSendPacket(first_built_packet->SequenceNumber()); + rtp_sender_->ReSendPacket(second_built_packet->SequenceNumber()); +} + +TEST_F(RtpSenderTest, MidAndRidAlwaysIncludedOnRtxPacketsWhenConfigured) { + SetUpRtpSender(false, /*always_send_mid_and_rid=*/true, nullptr); + EnableRtx(); + EnableMidSending(kMid); + EnableRidSending(); + + // Send two media packets: one before and one after the ack. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee( + AllOf(Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), + Property(&RtpPacketToSend::GetExtension<RtpStreamId>, kRid)))))) + .Times(2) + .WillRepeatedly( + [&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + packet_history_->PutRtpPacket(std::move(packets[0]), + clock_->CurrentTime()); + }); + auto media_packet1 = SendGenericPacket(); + rtp_sender_->OnReceivedAckOnSsrc(media_packet1->SequenceNumber()); + auto media_packet2 = SendGenericPacket(); + + // Send three RTX packets with different combinations of orders w.r.t. the + // media and RTX acks. + // Due to the configuration, all sent packets should contain MID + // and either RID (media) or RRID (RTX). + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::GetExtension<RtpMid>, kMid), + Property(&RtpPacketToSend::GetExtension<RepairedRtpStreamId>, + kRid)))))) + .Times(3) + .WillRepeatedly( + [&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + rtp_sender_->OnReceivedAckOnRtxSsrc(packets[0]->SequenceNumber()); + packet_history_->MarkPacketAsSent( + *packets[0]->retransmitted_sequence_number()); + }); + rtp_sender_->ReSendPacket(media_packet2->SequenceNumber()); + rtp_sender_->ReSendPacket(media_packet1->SequenceNumber()); + rtp_sender_->ReSendPacket(media_packet2->SequenceNumber()); +} + +// Test that if the RtpState indicates an ACK has been received on that SSRC +// then neither the MID nor RID header extensions will be sent. +TEST_F(RtpSenderTest, MidAndRidNotIncludedOnSentPacketsAfterRtpStateRestored) { + EnableMidSending(kMid); + EnableRidSending(); + + RtpState state = rtp_sender_->GetRtpState(); + EXPECT_FALSE(state.ssrc_has_acked); + state.ssrc_has_acked = true; + rtp_sender_->SetRtpState(state); + + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::HasExtension<RtpMid>, false), + Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); + SendGenericPacket(); +} + +// Test that if the RTX RtpState indicates an ACK has been received on that +// RTX SSRC then neither the MID nor RRID header extensions will be sent on +// RTX packets. +TEST_F(RtpSenderTest, MidAndRridNotIncludedOnRtxPacketsAfterRtpStateRestored) { + EnableRtx(); + EnableMidSending(kMid); + EnableRidSending(); + + RtpState rtx_state = rtp_sender_->GetRtxRtpState(); + EXPECT_FALSE(rtx_state.ssrc_has_acked); + rtx_state.ssrc_has_acked = true; + rtp_sender_->SetRtxRtpState(rtx_state); + + EXPECT_CALL(mock_paced_sender_, EnqueuePackets(SizeIs(1))) + .WillOnce([&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + packet_history_->PutRtpPacket(std::move(packets[0]), + clock_->CurrentTime()); + }); + auto built_packet = SendGenericPacket(); + + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(AllOf( + Property(&RtpPacketToSend::HasExtension<RtpMid>, false), + Property(&RtpPacketToSend::HasExtension<RtpStreamId>, false)))))); + ASSERT_LT(0, rtp_sender_->ReSendPacket(built_packet->SequenceNumber())); +} + +TEST_F(RtpSenderTest, RespectsNackBitrateLimit) { + const int32_t kPacketSize = 1400; + const int32_t kNumPackets = 30; + retransmission_rate_limiter_.SetMaxRate(kPacketSize * kNumPackets * 8); + EnableRtx(); + + std::vector<uint16_t> sequence_numbers; + for (int32_t i = 0; i < kNumPackets; ++i) { + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, /*marker_bit=*/true, /*timestamp=*/0, + /*capture_time_ms=*/clock_->TimeInMilliseconds()); + packet->set_allow_retransmission(true); + sequencer_->Sequence(*packet); + sequence_numbers.push_back(packet->SequenceNumber()); + packet_history_->PutRtpPacket(std::move(packet), + /*send_time=*/clock_->CurrentTime()); + time_controller_.AdvanceTime(TimeDelta::Millis(1)); + } + + time_controller_.AdvanceTime(TimeDelta::Millis(1000 - kNumPackets)); + + // Resending should work - brings the bandwidth up to the limit. + // NACK bitrate is capped to the same bitrate as the encoder, since the max + // protection overhead is 50% (see MediaOptimization::SetTargetRates). + EXPECT_CALL(mock_paced_sender_, EnqueuePackets(ElementsAre(Pointee(Property( + &RtpPacketToSend::packet_type, + RtpPacketMediaType::kRetransmission))))) + .Times(kNumPackets) + .WillRepeatedly( + [&](std::vector<std::unique_ptr<RtpPacketToSend>> packets) { + for (const auto& packet : packets) { + packet_history_->MarkPacketAsSent( + *packet->retransmitted_sequence_number()); + } + }); + rtp_sender_->OnReceivedNack(sequence_numbers, 0); + + // Must be at least 5ms in between retransmission attempts. + time_controller_.AdvanceTime(TimeDelta::Millis(5)); + + // Resending should not work, bandwidth exceeded. + EXPECT_CALL(mock_paced_sender_, EnqueuePackets).Times(0); + rtp_sender_->OnReceivedNack(sequence_numbers, 0); +} + +TEST_F(RtpSenderTest, UpdatingCsrcsUpdatedOverhead) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = {}; + CreateSender(config); + + // Base RTP overhead is 12B. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); + + // Adding two csrcs adds 2*4 bytes to the header. + rtp_sender_->SetCsrcs({1, 2}); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 20u); +} + +TEST_F(RtpSenderTest, OnOverheadChanged) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = {}; + CreateSender(config); + + // Base RTP overhead is 12B. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); + + rtp_sender_->RegisterRtpHeaderExtension(TransmissionOffset::Uri(), + kTransmissionTimeOffsetExtensionId); + + // TransmissionTimeOffset extension has a size of 3B, but with the addition + // of header index and rounding to 4 byte boundary we end up with 20B total. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 20u); +} + +TEST_F(RtpSenderTest, CountMidOnlyUntilAcked) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = {}; + CreateSender(config); + + // Base RTP overhead is 12B. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); + + rtp_sender_->RegisterRtpHeaderExtension(RtpMid::Uri(), kMidExtensionId); + + // Counted only if set. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); + rtp_sender_->SetMid("foo"); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 36u); + rtp_sender_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), kRidExtensionId); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 52u); + + // Ack received, mid/rid no longer sent. + rtp_sender_->OnReceivedAckOnSsrc(0); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); +} + +TEST_F(RtpSenderTest, CountMidRidRridUntilAcked) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + CreateSender(config); + + // Base RTP overhead is 12B and we use RTX which has an additional 2 bytes + // overhead. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 14u); + + rtp_sender_->RegisterRtpHeaderExtension(RtpMid::Uri(), kMidExtensionId); + + // Counted only if set. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 14u); + rtp_sender_->SetMid("foo"); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 38u); + + rtp_sender_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), kRidExtensionId); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 54u); + + // mid/rrid may be shared with mid/rid when both are active. + rtp_sender_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), + kRepairedRidExtensionId); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 54u); + + // Ack received, mid/rid no longer sent but we still need space for + // mid/rrid which can no longer be shared with mid/rid. + rtp_sender_->OnReceivedAckOnSsrc(0); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 54u); + + // Ack received for RTX, no need to send RRID anymore. + rtp_sender_->OnReceivedAckOnRtxSsrc(0); + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 14u); +} + +TEST_F(RtpSenderTest, DontCountVolatileExtensionsIntoOverhead) { + RtpRtcpInterface::Configuration config = GetDefaultConfig(); + config.rtx_send_ssrc = {}; + CreateSender(config); + + // Base RTP overhead is 12B. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); + + rtp_sender_->RegisterRtpHeaderExtension(InbandComfortNoiseExtension::Uri(), + 1); + rtp_sender_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + 2); + rtp_sender_->RegisterRtpHeaderExtension(VideoOrientation::Uri(), 3); + rtp_sender_->RegisterRtpHeaderExtension(PlayoutDelayLimits::Uri(), 4); + rtp_sender_->RegisterRtpHeaderExtension(VideoContentTypeExtension::Uri(), 5); + rtp_sender_->RegisterRtpHeaderExtension(VideoTimingExtension::Uri(), 6); + rtp_sender_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), 7); + rtp_sender_->RegisterRtpHeaderExtension(ColorSpaceExtension::Uri(), 8); + + // Still only 12B counted since can't count on above being sent. + EXPECT_EQ(rtp_sender_->ExpectedPerPacketOverhead(), 12u); +} + +TEST_F(RtpSenderTest, SendPacketHandlesRetransmissionHistory) { + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + // Ignore calls to EnqueuePackets() for this test. + EXPECT_CALL(mock_paced_sender_, EnqueuePackets).WillRepeatedly(Return()); + + // Build a media packet and put in the packet history. + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, true, 0, clock_->TimeInMilliseconds()); + const uint16_t media_sequence_number = packet->SequenceNumber(); + packet->set_allow_retransmission(true); + packet_history_->PutRtpPacket(std::move(packet), clock_->CurrentTime()); + + // Simulate successful retransmission request. + time_controller_.AdvanceTime(TimeDelta::Millis(30)); + EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Gt(0)); + + // Packet already pending, retransmission not allowed. + time_controller_.AdvanceTime(TimeDelta::Millis(30)); + EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Eq(0)); + + // Simulate packet exiting pacer, mark as not longer pending. + packet_history_->MarkPacketAsSent(media_sequence_number); + + // Retransmissions allowed again. + time_controller_.AdvanceTime(TimeDelta::Millis(30)); + EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Gt(0)); +} + +TEST_F(RtpSenderTest, MarksRetransmittedPackets) { + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + + // Build a media packet and put in the packet history. + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, true, 0, clock_->TimeInMilliseconds()); + const uint16_t media_sequence_number = packet->SequenceNumber(); + packet->set_allow_retransmission(true); + packet_history_->PutRtpPacket(std::move(packet), clock_->CurrentTime()); + + // Expect a retransmission packet marked with which packet it is a + // retransmit of. + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(AllOf( + Pointee(Property(&RtpPacketToSend::packet_type, + RtpPacketMediaType::kRetransmission)), + Pointee(Property(&RtpPacketToSend::retransmitted_sequence_number, + Eq(media_sequence_number))))))); + EXPECT_THAT(rtp_sender_->ReSendPacket(media_sequence_number), Gt(0)); +} + +TEST_F(RtpSenderTest, GeneratedPaddingHasBweExtensions) { + // Min requested size in order to use RTX payload. + const size_t kMinPaddingSize = 50; + EnableRtx(); + + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransmissionOffset::Uri(), kTransmissionTimeOffsetExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + + // Put a packet in the history, in order to facilitate payload padding. + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, true, 0, clock_->TimeInMilliseconds()); + packet->set_allow_retransmission(true); + packet->SetPayloadSize(kMinPaddingSize); + packet->set_packet_type(RtpPacketMediaType::kVideo); + packet_history_->PutRtpPacket(std::move(packet), clock_->CurrentTime()); + + // Generate a plain padding packet, check that extensions are registered. + std::vector<std::unique_ptr<RtpPacketToSend>> generated_packets = + GeneratePadding(/*target_size_bytes=*/1); + ASSERT_THAT(generated_packets, SizeIs(1)); + auto& plain_padding = generated_packets.front(); + EXPECT_GT(plain_padding->padding_size(), 0u); + EXPECT_TRUE(plain_padding->HasExtension<TransportSequenceNumber>()); + EXPECT_TRUE(plain_padding->HasExtension<AbsoluteSendTime>()); + EXPECT_TRUE(plain_padding->HasExtension<TransmissionOffset>()); + EXPECT_GT(plain_padding->padding_size(), 0u); + + // Generate a payload padding packets, check that extensions are registered. + generated_packets = GeneratePadding(kMinPaddingSize); + ASSERT_EQ(generated_packets.size(), 1u); + auto& payload_padding = generated_packets.front(); + EXPECT_EQ(payload_padding->padding_size(), 0u); + EXPECT_TRUE(payload_padding->HasExtension<TransportSequenceNumber>()); + EXPECT_TRUE(payload_padding->HasExtension<AbsoluteSendTime>()); + EXPECT_TRUE(payload_padding->HasExtension<TransmissionOffset>()); + EXPECT_GT(payload_padding->payload_size(), 0u); +} + +TEST_F(RtpSenderTest, GeneratePaddingResendsOldPacketsWithRtx) { + // Min requested size in order to use RTX payload. + const size_t kMinPaddingSize = 50; + + rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); + rtp_sender_->SetRtxStatus(kRtxRetransmitted | kRtxRedundantPayloads); + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 1); + + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + + const size_t kPayloadPacketSize = kMinPaddingSize; + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, true, 0, clock_->TimeInMilliseconds()); + packet->set_allow_retransmission(true); + packet->SetPayloadSize(kPayloadPacketSize); + packet->set_packet_type(RtpPacketMediaType::kVideo); + packet_history_->PutRtpPacket(std::move(packet), clock_->CurrentTime()); + + // Generated padding has large enough budget that the video packet should be + // retransmitted as padding. + std::vector<std::unique_ptr<RtpPacketToSend>> generated_packets = + GeneratePadding(kMinPaddingSize); + ASSERT_EQ(generated_packets.size(), 1u); + auto& padding_packet = generated_packets.front(); + EXPECT_EQ(padding_packet->packet_type(), RtpPacketMediaType::kPadding); + EXPECT_EQ(padding_packet->Ssrc(), kRtxSsrc); + EXPECT_EQ(padding_packet->payload_size(), + kPayloadPacketSize + kRtxHeaderSize); + + // Not enough budged for payload padding, use plain padding instead. + const size_t kPaddingBytesRequested = kMinPaddingSize - 1; + + size_t padding_bytes_generated = 0; + generated_packets = GeneratePadding(kPaddingBytesRequested); + EXPECT_EQ(generated_packets.size(), 1u); + for (auto& packet : generated_packets) { + EXPECT_EQ(packet->packet_type(), RtpPacketMediaType::kPadding); + EXPECT_EQ(packet->Ssrc(), kRtxSsrc); + EXPECT_EQ(packet->payload_size(), 0u); + EXPECT_GT(packet->padding_size(), 0u); + padding_bytes_generated += packet->padding_size(); + } + + EXPECT_EQ(padding_bytes_generated, kMaxPaddingSize); +} + +TEST_F(RtpSenderTest, LimitsPayloadPaddingSize) { + // RTX payload padding is limited to 3x target size. + const double kFactor = 3.0; + SetUpRtpSender(false, false, nullptr); + rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); + rtp_sender_->SetRtxStatus(kRtxRetransmitted | kRtxRedundantPayloads); + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 1); + + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + + // Send a dummy video packet so it ends up in the packet history. + const size_t kPayloadPacketSize = 1234u; + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, true, 0, clock_->TimeInMilliseconds()); + packet->set_allow_retransmission(true); + packet->SetPayloadSize(kPayloadPacketSize); + packet->set_packet_type(RtpPacketMediaType::kVideo); + packet_history_->PutRtpPacket(std::move(packet), clock_->CurrentTime()); + + // Smallest target size that will result in the sent packet being returned as + // padding. + const size_t kMinTargerSizeForPayload = + (kPayloadPacketSize + kRtxHeaderSize) / kFactor; + + // Generated padding has large enough budget that the video packet should be + // retransmitted as padding. + EXPECT_THAT( + GeneratePadding(kMinTargerSizeForPayload), + AllOf(Not(IsEmpty()), + Each(Pointee(Property(&RtpPacketToSend::padding_size, Eq(0u)))))); + + // If payload padding is > 2x requested size, plain padding is returned + // instead. + EXPECT_THAT( + GeneratePadding(kMinTargerSizeForPayload - 1), + AllOf(Not(IsEmpty()), + Each(Pointee(Property(&RtpPacketToSend::padding_size, Gt(0u)))))); +} + +TEST_F(RtpSenderTest, GeneratePaddingCreatesPurePaddingWithoutRtx) { + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 1); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransmissionOffset::Uri(), kTransmissionTimeOffsetExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + AbsoluteSendTime::Uri(), kAbsoluteSendTimeExtensionId)); + ASSERT_TRUE(rtp_sender_->RegisterRtpHeaderExtension( + TransportSequenceNumber::Uri(), kTransportSequenceNumberExtensionId)); + + const size_t kPayloadPacketSize = 1234; + // Send a dummy video packet so it ends up in the packet history. Since we + // are not using RTX, it should never be used as padding. + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, true, 0, clock_->TimeInMilliseconds()); + packet->set_allow_retransmission(true); + packet->SetPayloadSize(kPayloadPacketSize); + packet->set_packet_type(RtpPacketMediaType::kVideo); + sequencer_->Sequence(*packet); + packet_history_->PutRtpPacket(std::move(packet), clock_->CurrentTime()); + + // Payload padding not available without RTX, only generate plain padding on + // the media SSRC. + // Number of padding packets is the requested padding size divided by max + // padding packet size, rounded up. Pure padding packets are always of the + // maximum size. + const size_t kPaddingBytesRequested = kPayloadPacketSize + kRtxHeaderSize; + const size_t kExpectedNumPaddingPackets = + (kPaddingBytesRequested + kMaxPaddingSize - 1) / kMaxPaddingSize; + size_t padding_bytes_generated = 0; + std::vector<std::unique_ptr<RtpPacketToSend>> padding_packets = + GeneratePadding(kPaddingBytesRequested); + EXPECT_EQ(padding_packets.size(), kExpectedNumPaddingPackets); + for (auto& packet : padding_packets) { + EXPECT_EQ(packet->packet_type(), RtpPacketMediaType::kPadding); + EXPECT_EQ(packet->Ssrc(), kSsrc); + EXPECT_EQ(packet->payload_size(), 0u); + EXPECT_GT(packet->padding_size(), 0u); + padding_bytes_generated += packet->padding_size(); + EXPECT_TRUE(packet->HasExtension<TransportSequenceNumber>()); + EXPECT_TRUE(packet->HasExtension<AbsoluteSendTime>()); + EXPECT_TRUE(packet->HasExtension<TransmissionOffset>()); + } + + EXPECT_EQ(padding_bytes_generated, + kExpectedNumPaddingPackets * kMaxPaddingSize); +} + +TEST_F(RtpSenderTest, SupportsPadding) { + bool kSendingMediaStats[] = {true, false}; + bool kEnableRedundantPayloads[] = {true, false}; + absl::string_view kBweExtensionUris[] = { + TransportSequenceNumber::Uri(), TransportSequenceNumberV2::Uri(), + AbsoluteSendTime::Uri(), TransmissionOffset::Uri()}; + const int kExtensionsId = 7; + + for (bool sending_media : kSendingMediaStats) { + rtp_sender_->SetSendingMediaStatus(sending_media); + for (bool redundant_payloads : kEnableRedundantPayloads) { + int rtx_mode = kRtxRetransmitted; + if (redundant_payloads) { + rtx_mode |= kRtxRedundantPayloads; + } + rtp_sender_->SetRtxPayloadType(kRtxPayload, kPayload); + rtp_sender_->SetRtxStatus(rtx_mode); + + for (auto extension_uri : kBweExtensionUris) { + EXPECT_FALSE(rtp_sender_->SupportsPadding()); + rtp_sender_->RegisterRtpHeaderExtension(extension_uri, kExtensionsId); + if (!sending_media) { + EXPECT_FALSE(rtp_sender_->SupportsPadding()); + } else { + EXPECT_TRUE(rtp_sender_->SupportsPadding()); + if (redundant_payloads) { + EXPECT_TRUE(rtp_sender_->SupportsRtxPayloadPadding()); + } else { + EXPECT_FALSE(rtp_sender_->SupportsRtxPayloadPadding()); + } + } + rtp_sender_->DeregisterRtpHeaderExtension(extension_uri); + EXPECT_FALSE(rtp_sender_->SupportsPadding()); + } + } + } +} + +TEST_F(RtpSenderTest, SetsCaptureTimeOnRtxRetransmissions) { + EnableRtx(); + + // Put a packet in the packet history, with current time as capture time. + const int64_t start_time_ms = clock_->TimeInMilliseconds(); + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, kMarkerBit, start_time_ms, + /*capture_time_ms=*/start_time_ms); + packet->set_allow_retransmission(true); + sequencer_->Sequence(*packet); + packet_history_->PutRtpPacket(std::move(packet), + Timestamp::Millis(start_time_ms)); + + // Advance time, request an RTX retransmission. Capture timestamp should be + // preserved. + time_controller_.AdvanceTime(TimeDelta::Millis(10)); + + EXPECT_CALL( + mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee(Property( + &RtpPacketToSend::capture_time, Timestamp::Millis(start_time_ms)))))); + EXPECT_GT(rtp_sender_->ReSendPacket(kSeqNum), 0); +} + +TEST_F(RtpSenderTest, IgnoresNackAfterDisablingMedia) { + const TimeDelta kRtt = TimeDelta::Millis(10); + + EnableRtx(); + packet_history_->SetRtt(kRtt); + + // Put a packet in the history. + const int64_t start_time_ms = clock_->TimeInMilliseconds(); + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, kMarkerBit, start_time_ms, + /*capture_time_ms=*/start_time_ms); + packet->set_allow_retransmission(true); + sequencer_->Sequence(*packet); + packet_history_->PutRtpPacket(std::move(packet), + Timestamp::Millis(start_time_ms)); + + // Disable media sending and try to retransmit the packet, it should fail. + rtp_sender_->SetSendingMediaStatus(false); + time_controller_.AdvanceTime(kRtt); + EXPECT_LT(rtp_sender_->ReSendPacket(kSeqNum), 0); +} + +TEST_F(RtpSenderTest, DoesntFecProtectRetransmissions) { + // Set up retranmission without RTX, so that a plain copy of the old packet is + // re-sent instead. + const TimeDelta kRtt = TimeDelta::Millis(10); + rtp_sender_->SetSendingMediaStatus(true); + rtp_sender_->SetRtxStatus(kRtxOff); + packet_history_->SetStorePacketsStatus( + RtpPacketHistory::StorageMode::kStoreAndCull, 10); + packet_history_->SetRtt(kRtt); + + // Put a fec protected packet in the history. + const int64_t start_time_ms = clock_->TimeInMilliseconds(); + std::unique_ptr<RtpPacketToSend> packet = + BuildRtpPacket(kPayload, kMarkerBit, start_time_ms, + /*capture_time_ms=*/start_time_ms); + packet->set_allow_retransmission(true); + packet->set_fec_protect_packet(true); + sequencer_->Sequence(*packet); + packet_history_->PutRtpPacket(std::move(packet), + Timestamp::Millis(start_time_ms)); + + // Re-send packet, the retransmitted packet should not have the FEC protection + // flag set. + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(ElementsAre(Pointee( + Property(&RtpPacketToSend::fec_protect_packet, false))))); + + time_controller_.AdvanceTime(kRtt); + EXPECT_GT(rtp_sender_->ReSendPacket(kSeqNum), 0); +} + +TEST_F(RtpSenderTest, MarksPacketsWithKeyframeStatus) { + RTPSenderVideo::Config video_config; + video_config.clock = clock_; + video_config.rtp_sender = rtp_sender_.get(); + video_config.field_trials = &field_trials_; + RTPSenderVideo rtp_sender_video(video_config); + + const uint8_t kPayloadType = 127; + const absl::optional<VideoCodecType> kCodecType = + VideoCodecType::kVideoCodecGeneric; + + const uint32_t kCaptureTimeMsToRtpTimestamp = 90; // 90 kHz clock + + { + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(Each( + Pointee(Property(&RtpPacketToSend::is_key_frame, true))))) + .Times(AtLeast(1)); + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + int64_t capture_time_ms = clock_->TimeInMilliseconds(); + EXPECT_TRUE(rtp_sender_video.SendVideo( + kPayloadType, kCodecType, + capture_time_ms * kCaptureTimeMsToRtpTimestamp, capture_time_ms, + kPayloadData, video_header, kDefaultExpectedRetransmissionTimeMs)); + + time_controller_.AdvanceTime(TimeDelta::Millis(33)); + } + + { + EXPECT_CALL(mock_paced_sender_, + EnqueuePackets(Each( + Pointee(Property(&RtpPacketToSend::is_key_frame, false))))) + .Times(AtLeast(1)); + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameDelta; + int64_t capture_time_ms = clock_->TimeInMilliseconds(); + EXPECT_TRUE(rtp_sender_video.SendVideo( + kPayloadType, kCodecType, + capture_time_ms * kCaptureTimeMsToRtpTimestamp, capture_time_ms, + kPayloadData, video_header, kDefaultExpectedRetransmissionTimeMs)); + + time_controller_.AdvanceTime(TimeDelta::Millis(33)); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.cc new file mode 100644 index 0000000000..99a00025c1 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -0,0 +1,929 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_sender_video.h" + +#include <stdlib.h> +#include <string.h> + +#include <algorithm> +#include <limits> +#include <memory> +#include <string> +#include <utility> + +#include "absl/algorithm/container.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "api/crypto/frame_encryptor_interface.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/absolute_capture_time_sender.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_descriptor_authentication.h" +#include "modules/rtp_rtcp/source/rtp_format.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_to_send.h" +#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" +#include "modules/rtp_rtcp/source/time_util.h" +#include "rtc_base/checks.h" +#include "rtc_base/experiments/field_trial_parser.h" +#include "rtc_base/logging.h" +#include "rtc_base/trace_event.h" + +namespace webrtc { + +namespace { +constexpr size_t kRedForFecHeaderLength = 1; +constexpr int64_t kMaxUnretransmittableFrameIntervalMs = 33 * 4; +constexpr char kIncludeCaptureClockOffset[] = + "WebRTC-IncludeCaptureClockOffset"; + +void BuildRedPayload(const RtpPacketToSend& media_packet, + RtpPacketToSend* red_packet) { + uint8_t* red_payload = red_packet->AllocatePayload( + kRedForFecHeaderLength + media_packet.payload_size()); + RTC_DCHECK(red_payload); + red_payload[0] = media_packet.PayloadType(); + + auto media_payload = media_packet.payload(); + memcpy(&red_payload[kRedForFecHeaderLength], media_payload.data(), + media_payload.size()); +} + +bool MinimizeDescriptor(RTPVideoHeader* video_header) { + if (auto* vp8 = + absl::get_if<RTPVideoHeaderVP8>(&video_header->video_type_header)) { + // Set minimum fields the RtpPacketizer is using to create vp8 packets. + // nonReference is the only field that doesn't require extra space. + bool non_reference = vp8->nonReference; + vp8->InitRTPVideoHeaderVP8(); + vp8->nonReference = non_reference; + return true; + } + // TODO(danilchap): Reduce vp9 codec specific descriptor too. + return false; +} + +bool IsBaseLayer(const RTPVideoHeader& video_header) { + switch (video_header.codec) { + case kVideoCodecVP8: { + const auto& vp8 = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + return (vp8.temporalIdx == 0 || vp8.temporalIdx == kNoTemporalIdx); + } + case kVideoCodecVP9: { + const auto& vp9 = + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header); + return (vp9.temporal_idx == 0 || vp9.temporal_idx == kNoTemporalIdx); + } + case kVideoCodecH264: + // TODO(kron): Implement logic for H264 once WebRTC supports temporal + // layers for H264. + break; + default: + break; + } + return true; +} + +[[maybe_unused]] const char* FrameTypeToString(VideoFrameType frame_type) { + switch (frame_type) { + case VideoFrameType::kEmptyFrame: + return "empty"; + case VideoFrameType::kVideoFrameKey: + return "video_key"; + case VideoFrameType::kVideoFrameDelta: + return "video_delta"; + default: + RTC_DCHECK_NOTREACHED(); + return ""; + } +} + +bool IsNoopDelay(const VideoPlayoutDelay& delay) { + return delay.min_ms == -1 && delay.max_ms == -1; +} + +absl::optional<VideoPlayoutDelay> LoadVideoPlayoutDelayOverride( + const FieldTrialsView* key_value_config) { + RTC_DCHECK(key_value_config); + FieldTrialOptional<int> playout_delay_min_ms("min_ms", absl::nullopt); + FieldTrialOptional<int> playout_delay_max_ms("max_ms", absl::nullopt); + ParseFieldTrial({&playout_delay_max_ms, &playout_delay_min_ms}, + key_value_config->Lookup("WebRTC-ForceSendPlayoutDelay")); + return playout_delay_max_ms && playout_delay_min_ms + ? absl::make_optional<VideoPlayoutDelay>(*playout_delay_min_ms, + *playout_delay_max_ms) + : absl::nullopt; +} + +// Some packets can be skipped and the stream can still be decoded. Those +// packets are less likely to be retransmitted if they are lost. +bool PacketWillLikelyBeRequestedForRestransmitionIfLost( + const RTPVideoHeader& video_header) { + return IsBaseLayer(video_header) && + !(video_header.generic.has_value() + ? absl::c_linear_search( + video_header.generic->decode_target_indications, + DecodeTargetIndication::kDiscardable) + : false); +} + +} // namespace + +RTPSenderVideo::RTPSenderVideo(const Config& config) + : rtp_sender_(config.rtp_sender), + clock_(config.clock), + retransmission_settings_( + config.enable_retransmit_all_layers + ? kRetransmitAllLayers + : (kRetransmitBaseLayer | kConditionallyRetransmitHigherLayers)), + last_rotation_(kVideoRotation_0), + transmit_color_space_next_frame_(false), + send_allocation_(SendVideoLayersAllocation::kDontSend), + current_playout_delay_{-1, -1}, + playout_delay_pending_(false), + forced_playout_delay_(LoadVideoPlayoutDelayOverride(config.field_trials)), + red_payload_type_(config.red_payload_type), + fec_type_(config.fec_type), + fec_overhead_bytes_(config.fec_overhead_bytes), + packetization_overhead_bitrate_(1000, RateStatistics::kBpsScale), + frame_encryptor_(config.frame_encryptor), + require_frame_encryption_(config.require_frame_encryption), + generic_descriptor_auth_experiment_(!absl::StartsWith( + config.field_trials->Lookup("WebRTC-GenericDescriptorAuth"), + "Disabled")), + absolute_capture_time_sender_(config.clock), + frame_transformer_delegate_( + config.frame_transformer + ? rtc::make_ref_counted<RTPSenderVideoFrameTransformerDelegate>( + this, + config.frame_transformer, + rtp_sender_->SSRC(), + config.task_queue_factory) + : nullptr), + include_capture_clock_offset_(!absl::StartsWith( + config.field_trials->Lookup(kIncludeCaptureClockOffset), + "Disabled")) { + if (frame_transformer_delegate_) + frame_transformer_delegate_->Init(); +} + +RTPSenderVideo::~RTPSenderVideo() { + if (frame_transformer_delegate_) + frame_transformer_delegate_->Reset(); +} + +void RTPSenderVideo::LogAndSendToNetwork( + std::vector<std::unique_ptr<RtpPacketToSend>> packets, + size_t unpacketized_payload_size) { + { + MutexLock lock(&stats_mutex_); + size_t packetized_payload_size = 0; + for (const auto& packet : packets) { + if (*packet->packet_type() == RtpPacketMediaType::kVideo) { + packetized_payload_size += packet->payload_size(); + } + } + // AV1 and H264 packetizers may produce less packetized bytes than + // unpacketized. + if (packetized_payload_size >= unpacketized_payload_size) { + packetization_overhead_bitrate_.Update( + packetized_payload_size - unpacketized_payload_size, + clock_->TimeInMilliseconds()); + } + } + + rtp_sender_->EnqueuePackets(std::move(packets)); +} + +size_t RTPSenderVideo::FecPacketOverhead() const { + size_t overhead = fec_overhead_bytes_; + if (red_enabled()) { + // The RED overhead is due to a small header. + overhead += kRedForFecHeaderLength; + + if (fec_type_ == VideoFecGenerator::FecType::kUlpFec) { + // For ULPFEC, the overhead is the FEC headers plus RED for FEC header + // (see above) plus anything in RTP header beyond the 12 bytes base header + // (CSRC list, extensions...) + // This reason for the header extensions to be included here is that + // from an FEC viewpoint, they are part of the payload to be protected. + // (The base RTP header is already protected by the FEC header.) + overhead += + rtp_sender_->FecOrPaddingPacketMaxRtpHeaderLength() - kRtpHeaderSize; + } + } + return overhead; +} + +void RTPSenderVideo::SetVideoStructure( + const FrameDependencyStructure* video_structure) { + if (frame_transformer_delegate_) { + frame_transformer_delegate_->SetVideoStructureUnderLock(video_structure); + return; + } + SetVideoStructureInternal(video_structure); +} + +void RTPSenderVideo::SetVideoStructureAfterTransformation( + const FrameDependencyStructure* video_structure) { + SetVideoStructureInternal(video_structure); +} + +void RTPSenderVideo::SetVideoStructureInternal( + const FrameDependencyStructure* video_structure) { + RTC_DCHECK_RUNS_SERIALIZED(&send_checker_); + if (video_structure == nullptr) { + video_structure_ = nullptr; + return; + } + // Simple sanity checks video structure is set up. + RTC_DCHECK_GT(video_structure->num_decode_targets, 0); + RTC_DCHECK_GT(video_structure->templates.size(), 0); + + int structure_id = 0; + if (video_structure_) { + if (*video_structure_ == *video_structure) { + // Same structure (just a new key frame), no update required. + return; + } + // When setting different video structure make sure structure_id is updated + // so that templates from different structures do not collide. + static constexpr int kMaxTemplates = 64; + structure_id = + (video_structure_->structure_id + video_structure_->templates.size()) % + kMaxTemplates; + } + + video_structure_ = + std::make_unique<FrameDependencyStructure>(*video_structure); + video_structure_->structure_id = structure_id; +} + +void RTPSenderVideo::SetVideoLayersAllocation( + VideoLayersAllocation allocation) { + if (frame_transformer_delegate_) { + frame_transformer_delegate_->SetVideoLayersAllocationUnderLock( + std::move(allocation)); + return; + } + SetVideoLayersAllocationInternal(std::move(allocation)); +} + +void RTPSenderVideo::SetVideoLayersAllocationAfterTransformation( + VideoLayersAllocation allocation) { + SetVideoLayersAllocationInternal(std::move(allocation)); +} + +void RTPSenderVideo::SetVideoLayersAllocationInternal( + VideoLayersAllocation allocation) { + RTC_DCHECK_RUNS_SERIALIZED(&send_checker_); + if (!allocation_ || allocation.active_spatial_layers.size() != + allocation_->active_spatial_layers.size()) { + send_allocation_ = SendVideoLayersAllocation::kSendWithResolution; + } else if (send_allocation_ == SendVideoLayersAllocation::kDontSend) { + send_allocation_ = SendVideoLayersAllocation::kSendWithoutResolution; + } + if (send_allocation_ == SendVideoLayersAllocation::kSendWithoutResolution) { + // Check if frame rate changed more than 5fps since the last time the + // extension was sent with frame rate and resolution. + for (size_t i = 0; i < allocation.active_spatial_layers.size(); ++i) { + if (abs(static_cast<int>( + allocation.active_spatial_layers[i].frame_rate_fps) - + static_cast<int>( + last_full_sent_allocation_->active_spatial_layers[i] + .frame_rate_fps)) > 5) { + send_allocation_ = SendVideoLayersAllocation::kSendWithResolution; + break; + } + } + } + allocation_ = std::move(allocation); +} + +void RTPSenderVideo::AddRtpHeaderExtensions(const RTPVideoHeader& video_header, + bool first_packet, + bool last_packet, + RtpPacketToSend* packet) const { + // Send color space when changed or if the frame is a key frame. Keep + // sending color space information until the first base layer frame to + // guarantee that the information is retrieved by the receiver. + bool set_color_space = + video_header.color_space != last_color_space_ || + video_header.frame_type == VideoFrameType::kVideoFrameKey || + transmit_color_space_next_frame_; + // Color space requires two-byte header extensions if HDR metadata is + // included. Therefore, it's best to add this extension first so that the + // other extensions in the same packet are written as two-byte headers at + // once. + if (last_packet && set_color_space && video_header.color_space) + packet->SetExtension<ColorSpaceExtension>(video_header.color_space.value()); + + // According to + // http://www.etsi.org/deliver/etsi_ts/126100_126199/126114/12.07.00_60/ + // ts_126114v120700p.pdf Section 7.4.5: + // The MTSI client shall add the payload bytes as defined in this clause + // onto the last RTP packet in each group of packets which make up a key + // frame (I-frame or IDR frame in H.264 (AVC), or an IRAP picture in H.265 + // (HEVC)). The MTSI client may also add the payload bytes onto the last RTP + // packet in each group of packets which make up another type of frame + // (e.g. a P-Frame) only if the current value is different from the previous + // value sent. + // Set rotation when key frame or when changed (to follow standard). + // Or when different from 0 (to follow current receiver implementation). + bool set_video_rotation = + video_header.frame_type == VideoFrameType::kVideoFrameKey || + video_header.rotation != last_rotation_ || + video_header.rotation != kVideoRotation_0; + if (last_packet && set_video_rotation) + packet->SetExtension<VideoOrientation>(video_header.rotation); + + // Report content type only for key frames. + if (last_packet && + video_header.frame_type == VideoFrameType::kVideoFrameKey && + video_header.content_type != VideoContentType::UNSPECIFIED) + packet->SetExtension<VideoContentTypeExtension>(video_header.content_type); + + if (last_packet && + video_header.video_timing.flags != VideoSendTiming::kInvalid) + packet->SetExtension<VideoTimingExtension>(video_header.video_timing); + + // If transmitted, add to all packets; ack logic depends on this. + if (playout_delay_pending_) { + packet->SetExtension<PlayoutDelayLimits>(current_playout_delay_); + } + + if (first_packet && video_header.absolute_capture_time.has_value()) { + packet->SetExtension<AbsoluteCaptureTimeExtension>( + *video_header.absolute_capture_time); + } + + if (video_header.generic) { + bool extension_is_set = false; + if (packet->IsRegistered<RtpDependencyDescriptorExtension>() && + video_structure_ != nullptr) { + DependencyDescriptor descriptor; + descriptor.first_packet_in_frame = first_packet; + descriptor.last_packet_in_frame = last_packet; + descriptor.frame_number = video_header.generic->frame_id & 0xFFFF; + descriptor.frame_dependencies.spatial_id = + video_header.generic->spatial_index; + descriptor.frame_dependencies.temporal_id = + video_header.generic->temporal_index; + for (int64_t dep : video_header.generic->dependencies) { + descriptor.frame_dependencies.frame_diffs.push_back( + video_header.generic->frame_id - dep); + } + descriptor.frame_dependencies.chain_diffs = + video_header.generic->chain_diffs; + descriptor.frame_dependencies.decode_target_indications = + video_header.generic->decode_target_indications; + RTC_DCHECK_EQ( + descriptor.frame_dependencies.decode_target_indications.size(), + video_structure_->num_decode_targets); + + if (first_packet) { + descriptor.active_decode_targets_bitmask = + active_decode_targets_tracker_.ActiveDecodeTargetsBitmask(); + } + // VP9 mark all layer frames of the first picture as kVideoFrameKey, + // Structure should be attached to the descriptor to lowest spatial layer + // when inter layer dependency is used, i.e. L structures; or to all + // layers when inter layer dependency is not used, i.e. S structures. + // Distinguish these two cases by checking if there are any dependencies. + if (video_header.frame_type == VideoFrameType::kVideoFrameKey && + video_header.generic->dependencies.empty() && first_packet) { + // To avoid extra structure copy, temporary share ownership of the + // video_structure with the dependency descriptor. + descriptor.attached_structure = + absl::WrapUnique(video_structure_.get()); + } + extension_is_set = packet->SetExtension<RtpDependencyDescriptorExtension>( + *video_structure_, + active_decode_targets_tracker_.ActiveChainsBitmask(), descriptor); + + // Remove the temporary shared ownership. + descriptor.attached_structure.release(); + } + + // Do not use generic frame descriptor when dependency descriptor is stored. + if (packet->IsRegistered<RtpGenericFrameDescriptorExtension00>() && + !extension_is_set) { + RtpGenericFrameDescriptor generic_descriptor; + generic_descriptor.SetFirstPacketInSubFrame(first_packet); + generic_descriptor.SetLastPacketInSubFrame(last_packet); + + if (first_packet) { + generic_descriptor.SetFrameId( + static_cast<uint16_t>(video_header.generic->frame_id)); + for (int64_t dep : video_header.generic->dependencies) { + generic_descriptor.AddFrameDependencyDiff( + video_header.generic->frame_id - dep); + } + + uint8_t spatial_bimask = 1 << video_header.generic->spatial_index; + generic_descriptor.SetSpatialLayersBitmask(spatial_bimask); + + generic_descriptor.SetTemporalLayer( + video_header.generic->temporal_index); + + if (video_header.frame_type == VideoFrameType::kVideoFrameKey) { + generic_descriptor.SetResolution(video_header.width, + video_header.height); + } + } + + packet->SetExtension<RtpGenericFrameDescriptorExtension00>( + generic_descriptor); + } + } + + if (packet->IsRegistered<RtpVideoLayersAllocationExtension>() && + first_packet && + send_allocation_ != SendVideoLayersAllocation::kDontSend && + (video_header.frame_type == VideoFrameType::kVideoFrameKey || + PacketWillLikelyBeRequestedForRestransmitionIfLost(video_header))) { + VideoLayersAllocation allocation = allocation_.value(); + allocation.resolution_and_frame_rate_is_valid = + send_allocation_ == SendVideoLayersAllocation::kSendWithResolution; + packet->SetExtension<RtpVideoLayersAllocationExtension>(allocation); + } + + if (first_packet && video_header.video_frame_tracking_id) { + packet->SetExtension<VideoFrameTrackingIdExtension>( + *video_header.video_frame_tracking_id); + } +} + +bool RTPSenderVideo::SendVideo( + int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + int64_t capture_time_ms, + rtc::ArrayView<const uint8_t> payload, + RTPVideoHeader video_header, + absl::optional<int64_t> expected_retransmission_time_ms) { + TRACE_EVENT_ASYNC_STEP1("webrtc", "Video", capture_time_ms, "Send", "type", + FrameTypeToString(video_header.frame_type)); + RTC_CHECK_RUNS_SERIALIZED(&send_checker_); + + if (video_header.frame_type == VideoFrameType::kEmptyFrame) + return true; + + if (payload.empty()) + return false; + if (!rtp_sender_->SendingMedia()) { + return false; + } + + int32_t retransmission_settings = retransmission_settings_; + if (codec_type == VideoCodecType::kVideoCodecH264) { + // Backward compatibility for older receivers without temporal layer logic. + retransmission_settings = kRetransmitBaseLayer | kRetransmitHigherLayers; + } + const uint8_t temporal_id = GetTemporalId(video_header); + // TODO(bugs.webrtc.org/10714): retransmission_settings_ should generally be + // replaced by expected_retransmission_time_ms.has_value(). + const bool allow_retransmission = + expected_retransmission_time_ms.has_value() && + AllowRetransmission(temporal_id, retransmission_settings, + *expected_retransmission_time_ms); + + MaybeUpdateCurrentPlayoutDelay(video_header); + if (video_header.frame_type == VideoFrameType::kVideoFrameKey) { + if (!IsNoopDelay(current_playout_delay_)) { + // Force playout delay on key-frames, if set. + playout_delay_pending_ = true; + } + if (allocation_) { + // Send the bitrate allocation on every key frame. + send_allocation_ = SendVideoLayersAllocation::kSendWithResolution; + } + } + + if (video_structure_ != nullptr && video_header.generic) { + active_decode_targets_tracker_.OnFrame( + video_structure_->decode_target_protected_by_chain, + video_header.generic->active_decode_targets, + video_header.frame_type == VideoFrameType::kVideoFrameKey, + video_header.generic->frame_id, video_header.generic->chain_diffs); + } + + // No FEC protection for upper temporal layers, if used. + const bool use_fec = fec_type_.has_value() && + (temporal_id == 0 || temporal_id == kNoTemporalIdx); + + // Maximum size of packet including rtp headers. + // Extra space left in case packet will be resent using fec or rtx. + int packet_capacity = rtp_sender_->MaxRtpPacketSize(); + if (use_fec) { + packet_capacity -= FecPacketOverhead(); + } + if (allow_retransmission) { + packet_capacity -= rtp_sender_->RtxPacketOverhead(); + } + + absl::optional<Timestamp> capture_time; + if (capture_time_ms > 0) { + capture_time = Timestamp::Millis(capture_time_ms); + } + + std::unique_ptr<RtpPacketToSend> single_packet = + rtp_sender_->AllocatePacket(); + RTC_DCHECK_LE(packet_capacity, single_packet->capacity()); + single_packet->SetPayloadType(payload_type); + single_packet->SetTimestamp(rtp_timestamp); + if (capture_time) + single_packet->set_capture_time(*capture_time); + + // Construct the absolute capture time extension if not provided. + if (!video_header.absolute_capture_time.has_value() && + capture_time.has_value()) { + video_header.absolute_capture_time.emplace(); + video_header.absolute_capture_time->absolute_capture_timestamp = + Int64MsToUQ32x32( + clock_->ConvertTimestampToNtpTime(*capture_time).ToMs()); + if (include_capture_clock_offset_) { + video_header.absolute_capture_time->estimated_capture_clock_offset = 0; + } + } + + // Let `absolute_capture_time_sender_` decide if the extension should be sent. + if (video_header.absolute_capture_time.has_value()) { + video_header.absolute_capture_time = + absolute_capture_time_sender_.OnSendPacket( + AbsoluteCaptureTimeSender::GetSource(single_packet->Ssrc(), + single_packet->Csrcs()), + single_packet->Timestamp(), kVideoPayloadTypeFrequency, + video_header.absolute_capture_time->absolute_capture_timestamp, + video_header.absolute_capture_time->estimated_capture_clock_offset); + } + + auto first_packet = std::make_unique<RtpPacketToSend>(*single_packet); + auto middle_packet = std::make_unique<RtpPacketToSend>(*single_packet); + auto last_packet = std::make_unique<RtpPacketToSend>(*single_packet); + // Simplest way to estimate how much extensions would occupy is to set them. + AddRtpHeaderExtensions(video_header, + /*first_packet=*/true, /*last_packet=*/true, + single_packet.get()); + if (video_structure_ != nullptr && + single_packet->IsRegistered<RtpDependencyDescriptorExtension>() && + !single_packet->HasExtension<RtpDependencyDescriptorExtension>()) { + RTC_DCHECK_EQ(video_header.frame_type, VideoFrameType::kVideoFrameKey); + // Disable attaching dependency descriptor to delta packets (including + // non-first packet of a key frame) when it wasn't attached to a key frame, + // as dependency descriptor can't be usable in such case. + RTC_LOG(LS_WARNING) << "Disable dependency descriptor because failed to " + "attach it to a key frame."; + video_structure_ = nullptr; + } + + AddRtpHeaderExtensions(video_header, + /*first_packet=*/true, /*last_packet=*/false, + first_packet.get()); + AddRtpHeaderExtensions(video_header, + /*first_packet=*/false, /*last_packet=*/false, + middle_packet.get()); + AddRtpHeaderExtensions(video_header, + /*first_packet=*/false, /*last_packet=*/true, + last_packet.get()); + + RTC_DCHECK_GT(packet_capacity, single_packet->headers_size()); + RTC_DCHECK_GT(packet_capacity, first_packet->headers_size()); + RTC_DCHECK_GT(packet_capacity, middle_packet->headers_size()); + RTC_DCHECK_GT(packet_capacity, last_packet->headers_size()); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = packet_capacity - middle_packet->headers_size(); + + RTC_DCHECK_GE(single_packet->headers_size(), middle_packet->headers_size()); + limits.single_packet_reduction_len = + single_packet->headers_size() - middle_packet->headers_size(); + + RTC_DCHECK_GE(first_packet->headers_size(), middle_packet->headers_size()); + limits.first_packet_reduction_len = + first_packet->headers_size() - middle_packet->headers_size(); + + RTC_DCHECK_GE(last_packet->headers_size(), middle_packet->headers_size()); + limits.last_packet_reduction_len = + last_packet->headers_size() - middle_packet->headers_size(); + + bool has_generic_descriptor = + first_packet->HasExtension<RtpGenericFrameDescriptorExtension00>() || + first_packet->HasExtension<RtpDependencyDescriptorExtension>(); + + // Minimization of the vp8 descriptor may erase temporal_id, so use + // `temporal_id` rather than reference `video_header` beyond this point. + if (has_generic_descriptor) { + MinimizeDescriptor(&video_header); + } + + // TODO(benwright@webrtc.org) - Allocate enough to always encrypt inline. + rtc::Buffer encrypted_video_payload; + if (frame_encryptor_ != nullptr) { + const size_t max_ciphertext_size = + frame_encryptor_->GetMaxCiphertextByteSize(cricket::MEDIA_TYPE_VIDEO, + payload.size()); + encrypted_video_payload.SetSize(max_ciphertext_size); + + size_t bytes_written = 0; + + // Enable header authentication if the field trial isn't disabled. + std::vector<uint8_t> additional_data; + if (generic_descriptor_auth_experiment_) { + additional_data = RtpDescriptorAuthentication(video_header); + } + + if (frame_encryptor_->Encrypt( + cricket::MEDIA_TYPE_VIDEO, first_packet->Ssrc(), additional_data, + payload, encrypted_video_payload, &bytes_written) != 0) { + return false; + } + + encrypted_video_payload.SetSize(bytes_written); + payload = encrypted_video_payload; + } else if (require_frame_encryption_) { + RTC_LOG(LS_WARNING) + << "No FrameEncryptor is attached to this video sending stream but " + "one is required since require_frame_encryptor is set"; + } + + std::unique_ptr<RtpPacketizer> packetizer = + RtpPacketizer::Create(codec_type, payload, limits, video_header); + + const size_t num_packets = packetizer->NumPackets(); + + if (num_packets == 0) + return false; + + bool first_frame = first_frame_sent_(); + std::vector<std::unique_ptr<RtpPacketToSend>> rtp_packets; + for (size_t i = 0; i < num_packets; ++i) { + std::unique_ptr<RtpPacketToSend> packet; + int expected_payload_capacity; + // Choose right packet template: + if (num_packets == 1) { + packet = std::move(single_packet); + expected_payload_capacity = + limits.max_payload_len - limits.single_packet_reduction_len; + } else if (i == 0) { + packet = std::move(first_packet); + expected_payload_capacity = + limits.max_payload_len - limits.first_packet_reduction_len; + } else if (i == num_packets - 1) { + packet = std::move(last_packet); + expected_payload_capacity = + limits.max_payload_len - limits.last_packet_reduction_len; + } else { + packet = std::make_unique<RtpPacketToSend>(*middle_packet); + expected_payload_capacity = limits.max_payload_len; + } + + packet->set_first_packet_of_frame(i == 0); + + if (!packetizer->NextPacket(packet.get())) + return false; + RTC_DCHECK_LE(packet->payload_size(), expected_payload_capacity); + + packet->set_allow_retransmission(allow_retransmission); + packet->set_is_key_frame(video_header.frame_type == + VideoFrameType::kVideoFrameKey); + + // Put packetization finish timestamp into extension. + if (packet->HasExtension<VideoTimingExtension>()) { + packet->set_packetization_finish_time(clock_->CurrentTime()); + } + + packet->set_fec_protect_packet(use_fec); + + if (red_enabled()) { + // TODO(sprang): Consider packetizing directly into packets with the RED + // header already in place, to avoid this copy. + std::unique_ptr<RtpPacketToSend> red_packet(new RtpPacketToSend(*packet)); + BuildRedPayload(*packet, red_packet.get()); + red_packet->SetPayloadType(*red_payload_type_); + red_packet->set_is_red(true); + + // Append `red_packet` instead of `packet` to output. + red_packet->set_packet_type(RtpPacketMediaType::kVideo); + red_packet->set_allow_retransmission(packet->allow_retransmission()); + rtp_packets.emplace_back(std::move(red_packet)); + } else { + packet->set_packet_type(RtpPacketMediaType::kVideo); + rtp_packets.emplace_back(std::move(packet)); + } + + if (first_frame) { + if (i == 0) { + RTC_LOG(LS_INFO) + << "Sent first RTP packet of the first video frame (pre-pacer)"; + } + if (i == num_packets - 1) { + RTC_LOG(LS_INFO) + << "Sent last RTP packet of the first video frame (pre-pacer)"; + } + } + } + + LogAndSendToNetwork(std::move(rtp_packets), payload.size()); + + // Update details about the last sent frame. + last_rotation_ = video_header.rotation; + + if (video_header.color_space != last_color_space_) { + last_color_space_ = video_header.color_space; + transmit_color_space_next_frame_ = !IsBaseLayer(video_header); + } else { + transmit_color_space_next_frame_ = + transmit_color_space_next_frame_ ? !IsBaseLayer(video_header) : false; + } + + if (video_header.frame_type == VideoFrameType::kVideoFrameKey || + PacketWillLikelyBeRequestedForRestransmitionIfLost(video_header)) { + // This frame will likely be delivered, no need to populate playout + // delay extensions until it changes again. + playout_delay_pending_ = false; + if (send_allocation_ == SendVideoLayersAllocation::kSendWithResolution) { + last_full_sent_allocation_ = allocation_; + } + send_allocation_ = SendVideoLayersAllocation::kDontSend; + } + + TRACE_EVENT_ASYNC_END1("webrtc", "Video", capture_time_ms, "timestamp", + rtp_timestamp); + return true; +} + +bool RTPSenderVideo::SendEncodedImage( + int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + const EncodedImage& encoded_image, + RTPVideoHeader video_header, + absl::optional<int64_t> expected_retransmission_time_ms) { + if (frame_transformer_delegate_) { + // The frame will be sent async once transformed. + return frame_transformer_delegate_->TransformFrame( + payload_type, codec_type, rtp_timestamp, encoded_image, video_header, + expected_retransmission_time_ms); + } + return SendVideo(payload_type, codec_type, rtp_timestamp, + encoded_image.capture_time_ms_, encoded_image, video_header, + expected_retransmission_time_ms); +} + +uint32_t RTPSenderVideo::PacketizationOverheadBps() const { + MutexLock lock(&stats_mutex_); + return packetization_overhead_bitrate_.Rate(clock_->TimeInMilliseconds()) + .value_or(0); +} + +bool RTPSenderVideo::AllowRetransmission( + uint8_t temporal_id, + int32_t retransmission_settings, + int64_t expected_retransmission_time_ms) { + if (retransmission_settings == kRetransmitOff) + return false; + + MutexLock lock(&stats_mutex_); + // Media packet storage. + if ((retransmission_settings & kConditionallyRetransmitHigherLayers) && + UpdateConditionalRetransmit(temporal_id, + expected_retransmission_time_ms)) { + retransmission_settings |= kRetransmitHigherLayers; + } + + if (temporal_id == kNoTemporalIdx) + return true; + + if ((retransmission_settings & kRetransmitBaseLayer) && temporal_id == 0) + return true; + + if ((retransmission_settings & kRetransmitHigherLayers) && temporal_id > 0) + return true; + + return false; +} + +uint8_t RTPSenderVideo::GetTemporalId(const RTPVideoHeader& header) { + struct TemporalIdGetter { + uint8_t operator()(const RTPVideoHeaderVP8& vp8) { return vp8.temporalIdx; } + uint8_t operator()(const RTPVideoHeaderVP9& vp9) { + return vp9.temporal_idx; + } + uint8_t operator()(const RTPVideoHeaderH264&) { return kNoTemporalIdx; } + uint8_t operator()(const RTPVideoHeaderLegacyGeneric&) { + return kNoTemporalIdx; + } + uint8_t operator()(const absl::monostate&) { return kNoTemporalIdx; } + }; + return absl::visit(TemporalIdGetter(), header.video_type_header); +} + +bool RTPSenderVideo::UpdateConditionalRetransmit( + uint8_t temporal_id, + int64_t expected_retransmission_time_ms) { + int64_t now_ms = clock_->TimeInMilliseconds(); + // Update stats for any temporal layer. + TemporalLayerStats* current_layer_stats = + &frame_stats_by_temporal_layer_[temporal_id]; + current_layer_stats->frame_rate_fp1000s.Update(1, now_ms); + int64_t tl_frame_interval = now_ms - current_layer_stats->last_frame_time_ms; + current_layer_stats->last_frame_time_ms = now_ms; + + // Conditional retransmit only applies to upper layers. + if (temporal_id != kNoTemporalIdx && temporal_id > 0) { + if (tl_frame_interval >= kMaxUnretransmittableFrameIntervalMs) { + // Too long since a retransmittable frame in this layer, enable NACK + // protection. + return true; + } else { + // Estimate when the next frame of any lower layer will be sent. + const int64_t kUndefined = std::numeric_limits<int64_t>::max(); + int64_t expected_next_frame_time = kUndefined; + for (int i = temporal_id - 1; i >= 0; --i) { + TemporalLayerStats* stats = &frame_stats_by_temporal_layer_[i]; + absl::optional<uint32_t> rate = stats->frame_rate_fp1000s.Rate(now_ms); + if (rate) { + int64_t tl_next = stats->last_frame_time_ms + 1000000 / *rate; + if (tl_next - now_ms > -expected_retransmission_time_ms && + tl_next < expected_next_frame_time) { + expected_next_frame_time = tl_next; + } + } + } + + if (expected_next_frame_time == kUndefined || + expected_next_frame_time - now_ms > expected_retransmission_time_ms) { + // The next frame in a lower layer is expected at a later time (or + // unable to tell due to lack of data) than a retransmission is + // estimated to be able to arrive, so allow this packet to be nacked. + return true; + } + } + } + + return false; +} + +void RTPSenderVideo::MaybeUpdateCurrentPlayoutDelay( + const RTPVideoHeader& header) { + VideoPlayoutDelay requested_delay = + forced_playout_delay_.value_or(header.playout_delay); + + if (IsNoopDelay(requested_delay)) { + return; + } + + if (requested_delay.min_ms > PlayoutDelayLimits::kMaxMs || + requested_delay.max_ms > PlayoutDelayLimits::kMaxMs) { + RTC_DLOG(LS_ERROR) + << "Requested playout delay values out of range, ignored"; + return; + } + if (requested_delay.max_ms != -1 && + requested_delay.min_ms > requested_delay.max_ms) { + RTC_DLOG(LS_ERROR) << "Requested playout delay values out of order"; + return; + } + + if (!playout_delay_pending_) { + current_playout_delay_ = requested_delay; + playout_delay_pending_ = true; + return; + } + + if ((requested_delay.min_ms == -1 || + requested_delay.min_ms == current_playout_delay_.min_ms) && + (requested_delay.max_ms == -1 || + requested_delay.max_ms == current_playout_delay_.max_ms)) { + // No change, ignore. + return; + } + + if (requested_delay.min_ms == -1) { + RTC_DCHECK_GE(requested_delay.max_ms, 0); + requested_delay.min_ms = + std::min(current_playout_delay_.min_ms, requested_delay.max_ms); + } + if (requested_delay.max_ms == -1) { + requested_delay.max_ms = + std::max(current_playout_delay_.max_ms, requested_delay.min_ms); + } + + current_playout_delay_ = requested_delay; + playout_delay_pending_ = true; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.h new file mode 100644 index 0000000000..ecff8d42de --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.h @@ -0,0 +1,255 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_SENDER_VIDEO_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_SENDER_VIDEO_H_ + +#include <map> +#include <memory> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/frame_transformer_interface.h" +#include "api/scoped_refptr.h" +#include "api/sequence_checker.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/video/video_codec_type.h" +#include "api/video/video_frame_type.h" +#include "api/video/video_layers_allocation.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/absolute_capture_time_sender.h" +#include "modules/rtp_rtcp/source/active_decode_targets_helper.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "modules/rtp_rtcp/source/rtp_sender.h" +#include "modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_fec_generator.h" +#include "rtc_base/one_time_event.h" +#include "rtc_base/race_checker.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +namespace webrtc { + +class FrameEncryptorInterface; +class RtpPacketizer; +class RtpPacketToSend; + +// kConditionallyRetransmitHigherLayers allows retransmission of video frames +// in higher layers if either the last frame in that layer was too far back in +// time, or if we estimate that a new frame will be available in a lower layer +// in a shorter time than it would take to request and receive a retransmission. +enum RetransmissionMode : uint8_t { + kRetransmitOff = 0x0, + kRetransmitBaseLayer = 0x2, + kRetransmitHigherLayers = 0x4, + kRetransmitAllLayers = 0x6, + kConditionallyRetransmitHigherLayers = 0x8 +}; + +class RTPSenderVideo { + public: + static constexpr int64_t kTLRateWindowSizeMs = 2500; + + struct Config { + Config() = default; + Config(const Config&) = delete; + Config(Config&&) = default; + + // All members of this struct, with the exception of `field_trials`, are + // expected to outlive the RTPSenderVideo object they are passed to. + Clock* clock = nullptr; + RTPSender* rtp_sender = nullptr; + // Some FEC data is duplicated here in preparation of moving FEC to + // the egress stage. + absl::optional<VideoFecGenerator::FecType> fec_type; + size_t fec_overhead_bytes = 0; // Per packet max FEC overhead. + FrameEncryptorInterface* frame_encryptor = nullptr; + bool require_frame_encryption = false; + bool enable_retransmit_all_layers = false; + absl::optional<int> red_payload_type; + const FieldTrialsView* field_trials = nullptr; + rtc::scoped_refptr<FrameTransformerInterface> frame_transformer; + TaskQueueFactory* task_queue_factory = nullptr; + }; + + explicit RTPSenderVideo(const Config& config); + + virtual ~RTPSenderVideo(); + + // expected_retransmission_time_ms.has_value() -> retransmission allowed. + // `capture_time_ms` and `clock::CurrentTime` should be using the same epoch. + // Calls to this method are assumed to be externally serialized. + bool SendVideo(int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + int64_t capture_time_ms, + rtc::ArrayView<const uint8_t> payload, + RTPVideoHeader video_header, + absl::optional<int64_t> expected_retransmission_time_ms); + + bool SendEncodedImage( + int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + const EncodedImage& encoded_image, + RTPVideoHeader video_header, + absl::optional<int64_t> expected_retransmission_time_ms); + + // Configures video structures produced by encoder to send using the + // dependency descriptor rtp header extension. Next call to SendVideo should + // have video_header.frame_type == kVideoFrameKey. + // All calls to SendVideo after this call must use video_header compatible + // with the video_structure. + void SetVideoStructure(const FrameDependencyStructure* video_structure); + // Should only be used by a RTPSenderVideoFrameTransformerDelegate and exists + // to ensure correct syncronization. + void SetVideoStructureAfterTransformation( + const FrameDependencyStructure* video_structure); + + // Sets current active VideoLayersAllocation. The allocation will be sent + // using the rtp video layers allocation extension. The allocation will be + // sent in full on every key frame. The allocation will be sent once on a + // none discardable delta frame per call to this method and will not contain + // resolution and frame rate. + void SetVideoLayersAllocation(VideoLayersAllocation allocation); + // Should only be used by a RTPSenderVideoFrameTransformerDelegate and exists + // to ensure correct syncronization. + void SetVideoLayersAllocationAfterTransformation( + VideoLayersAllocation allocation); + + // Returns the current packetization overhead rate, in bps. Note that this is + // the payload overhead, eg the VP8 payload headers, not the RTP headers + // or extension/ + // TODO(sprang): Consider moving this to RtpSenderEgress so it's in the same + // place as the other rate stats. + uint32_t PacketizationOverheadBps() const; + + protected: + static uint8_t GetTemporalId(const RTPVideoHeader& header); + bool AllowRetransmission(uint8_t temporal_id, + int32_t retransmission_settings, + int64_t expected_retransmission_time_ms); + + private: + struct TemporalLayerStats { + TemporalLayerStats() + : frame_rate_fp1000s(kTLRateWindowSizeMs, 1000 * 1000), + last_frame_time_ms(0) {} + // Frame rate, in frames per 1000 seconds. This essentially turns the fps + // value into a fixed point value with three decimals. Improves precision at + // low frame rates. + RateStatistics frame_rate_fp1000s; + int64_t last_frame_time_ms; + }; + + enum class SendVideoLayersAllocation { + kSendWithResolution, + kSendWithoutResolution, + kDontSend + }; + + void SetVideoStructureInternal( + const FrameDependencyStructure* video_structure); + void SetVideoLayersAllocationInternal(VideoLayersAllocation allocation); + + void AddRtpHeaderExtensions(const RTPVideoHeader& video_header, + bool first_packet, + bool last_packet, + RtpPacketToSend* packet) const + RTC_EXCLUSIVE_LOCKS_REQUIRED(send_checker_); + + size_t FecPacketOverhead() const RTC_EXCLUSIVE_LOCKS_REQUIRED(send_checker_); + + void LogAndSendToNetwork( + std::vector<std::unique_ptr<RtpPacketToSend>> packets, + size_t unpacketized_payload_size); + + bool red_enabled() const { return red_payload_type_.has_value(); } + + bool UpdateConditionalRetransmit(uint8_t temporal_id, + int64_t expected_retransmission_time_ms) + RTC_EXCLUSIVE_LOCKS_REQUIRED(stats_mutex_); + + void MaybeUpdateCurrentPlayoutDelay(const RTPVideoHeader& header) + RTC_EXCLUSIVE_LOCKS_REQUIRED(send_checker_); + + RTPSender* const rtp_sender_; + Clock* const clock_; + + const int32_t retransmission_settings_; + + // These members should only be accessed from within SendVideo() to avoid + // potential race conditions. + rtc::RaceChecker send_checker_; + VideoRotation last_rotation_ RTC_GUARDED_BY(send_checker_); + absl::optional<ColorSpace> last_color_space_ RTC_GUARDED_BY(send_checker_); + bool transmit_color_space_next_frame_ RTC_GUARDED_BY(send_checker_); + std::unique_ptr<FrameDependencyStructure> video_structure_ + RTC_GUARDED_BY(send_checker_); + absl::optional<VideoLayersAllocation> allocation_ + RTC_GUARDED_BY(send_checker_); + // Flag indicating if we should send `allocation_`. + SendVideoLayersAllocation send_allocation_ RTC_GUARDED_BY(send_checker_); + absl::optional<VideoLayersAllocation> last_full_sent_allocation_ + RTC_GUARDED_BY(send_checker_); + + // Current target playout delay. + VideoPlayoutDelay current_playout_delay_ RTC_GUARDED_BY(send_checker_); + // Flag indicating if we need to send `current_playout_delay_` in order + // to guarantee it gets delivered. + bool playout_delay_pending_; + // Set by the field trial WebRTC-ForceSendPlayoutDelay to override the playout + // delay of outgoing video frames. + const absl::optional<VideoPlayoutDelay> forced_playout_delay_; + + // Should never be held when calling out of this class. + Mutex mutex_; + + const absl::optional<int> red_payload_type_; + absl::optional<VideoFecGenerator::FecType> fec_type_; + const size_t fec_overhead_bytes_; // Per packet max FEC overhead. + + mutable Mutex stats_mutex_; + RateStatistics packetization_overhead_bitrate_ RTC_GUARDED_BY(stats_mutex_); + + std::map<int, TemporalLayerStats> frame_stats_by_temporal_layer_ + RTC_GUARDED_BY(stats_mutex_); + + OneTimeEvent first_frame_sent_; + + // E2EE Custom Video Frame Encryptor (optional) + FrameEncryptorInterface* const frame_encryptor_ = nullptr; + // If set to true will require all outgoing frames to pass through an + // initialized frame_encryptor_ before being sent out of the network. + // Otherwise these payloads will be dropped. + const bool require_frame_encryption_; + // Set to true if the generic descriptor should be authenticated. + const bool generic_descriptor_auth_experiment_; + + AbsoluteCaptureTimeSender absolute_capture_time_sender_; + // Tracks updates to the active decode targets and decides when active decode + // targets bitmask should be attached to the dependency descriptor. + ActiveDecodeTargetsHelper active_decode_targets_tracker_; + + const rtc::scoped_refptr<RTPSenderVideoFrameTransformerDelegate> + frame_transformer_delegate_; + + const bool include_capture_clock_offset_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_SENDER_VIDEO_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc new file mode 100644 index 0000000000..27b6a17a20 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc @@ -0,0 +1,227 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h" + +#include <utility> +#include <vector> + +#include "api/sequence_checker.h" +#include "api/task_queue/task_queue_factory.h" +#include "modules/rtp_rtcp/source/rtp_descriptor_authentication.h" +#include "modules/rtp_rtcp/source/rtp_sender_video.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { + public: + TransformableVideoSenderFrame( + const EncodedImage& encoded_image, + const RTPVideoHeader& video_header, + int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + absl::optional<int64_t> expected_retransmission_time_ms, + uint32_t ssrc) + : encoded_data_(encoded_image.GetEncodedData()), + header_(video_header), + metadata_(header_.GetAsMetadata()), + frame_type_(encoded_image._frameType), + payload_type_(payload_type), + codec_type_(codec_type), + timestamp_(rtp_timestamp), + capture_time_ms_(encoded_image.capture_time_ms_), + expected_retransmission_time_ms_(expected_retransmission_time_ms), + ssrc_(ssrc) { + RTC_DCHECK_GE(payload_type_, 0); + RTC_DCHECK_LE(payload_type_, 127); + } + + ~TransformableVideoSenderFrame() override = default; + + // Implements TransformableVideoFrameInterface. + rtc::ArrayView<const uint8_t> GetData() const override { + return *encoded_data_; + } + + void SetData(rtc::ArrayView<const uint8_t> data) override { + encoded_data_ = EncodedImageBuffer::Create(data.data(), data.size()); + } + + uint32_t GetTimestamp() const override { return timestamp_; } + uint32_t GetSsrc() const override { return ssrc_; } + + bool IsKeyFrame() const override { + return frame_type_ == VideoFrameType::kVideoFrameKey; + } + + std::vector<uint8_t> GetAdditionalData() const override { + return RtpDescriptorAuthentication(header_); + } + + const VideoFrameMetadata& GetMetadata() const override { return metadata_; } + void SetMetadata(const VideoFrameMetadata& metadata) override { + header_.SetFromMetadata(metadata); + // We have to keep a local copy because GetMetadata() has to return a + // reference. + metadata_ = header_.GetAsMetadata(); + } + + const RTPVideoHeader& GetHeader() const { return header_; } + uint8_t GetPayloadType() const override { return payload_type_; } + absl::optional<VideoCodecType> GetCodecType() const { return codec_type_; } + int64_t GetCaptureTimeMs() const { return capture_time_ms_; } + + const absl::optional<int64_t>& GetExpectedRetransmissionTimeMs() const { + return expected_retransmission_time_ms_; + } + + Direction GetDirection() const override { return Direction::kSender; } + + private: + rtc::scoped_refptr<EncodedImageBufferInterface> encoded_data_; + RTPVideoHeader header_; + // This is a copy of `header_.GetAsMetadata()`, only needed because the + // interface says GetMetadata() must return a const ref rather than a value. + // TODO(crbug.com/webrtc/14709): Change the interface and delete this variable + // to reduce risk of it getting out-of-sync with `header_.GetAsMetadata()`. + VideoFrameMetadata metadata_; + const VideoFrameType frame_type_; + const uint8_t payload_type_; + const absl::optional<VideoCodecType> codec_type_ = absl::nullopt; + const uint32_t timestamp_; + const int64_t capture_time_ms_; + const absl::optional<int64_t> expected_retransmission_time_ms_; + const uint32_t ssrc_; +}; +} // namespace + +RTPSenderVideoFrameTransformerDelegate::RTPSenderVideoFrameTransformerDelegate( + RTPSenderVideo* sender, + rtc::scoped_refptr<FrameTransformerInterface> frame_transformer, + uint32_t ssrc, + TaskQueueFactory* task_queue_factory) + : sender_(sender), + frame_transformer_(std::move(frame_transformer)), + ssrc_(ssrc), + transformation_queue_(task_queue_factory->CreateTaskQueue( + "video_frame_transformer", + TaskQueueFactory::Priority::NORMAL)) {} + +void RTPSenderVideoFrameTransformerDelegate::Init() { + frame_transformer_->RegisterTransformedFrameSinkCallback( + rtc::scoped_refptr<TransformedFrameCallback>(this), ssrc_); +} + +bool RTPSenderVideoFrameTransformerDelegate::TransformFrame( + int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + const EncodedImage& encoded_image, + RTPVideoHeader video_header, + absl::optional<int64_t> expected_retransmission_time_ms) { + frame_transformer_->Transform(std::make_unique<TransformableVideoSenderFrame>( + encoded_image, video_header, payload_type, codec_type, rtp_timestamp, + expected_retransmission_time_ms, ssrc_)); + return true; +} + +void RTPSenderVideoFrameTransformerDelegate::OnTransformedFrame( + std::unique_ptr<TransformableFrameInterface> frame) { + MutexLock lock(&sender_lock_); + + if (!sender_) { + return; + } + rtc::scoped_refptr<RTPSenderVideoFrameTransformerDelegate> delegate(this); + transformation_queue_->PostTask( + [delegate = std::move(delegate), frame = std::move(frame)]() mutable { + RTC_DCHECK_RUN_ON(delegate->transformation_queue_.get()); + delegate->SendVideo(std::move(frame)); + }); +} + +void RTPSenderVideoFrameTransformerDelegate::SendVideo( + std::unique_ptr<TransformableFrameInterface> transformed_frame) const { + RTC_DCHECK_RUN_ON(transformation_queue_.get()); + RTC_CHECK_EQ(transformed_frame->GetDirection(), + TransformableFrameInterface::Direction::kSender); + MutexLock lock(&sender_lock_); + if (!sender_) + return; + auto* transformed_video_frame = + static_cast<TransformableVideoSenderFrame*>(transformed_frame.get()); + sender_->SendVideo( + transformed_video_frame->GetPayloadType(), + transformed_video_frame->GetCodecType(), + transformed_video_frame->GetTimestamp(), + transformed_video_frame->GetCaptureTimeMs(), + transformed_video_frame->GetData(), transformed_video_frame->GetHeader(), + transformed_video_frame->GetExpectedRetransmissionTimeMs()); +} + +void RTPSenderVideoFrameTransformerDelegate::SetVideoStructureUnderLock( + const FrameDependencyStructure* video_structure) { + MutexLock lock(&sender_lock_); + RTC_CHECK(sender_); + sender_->SetVideoStructureAfterTransformation(video_structure); +} + +void RTPSenderVideoFrameTransformerDelegate::SetVideoLayersAllocationUnderLock( + VideoLayersAllocation allocation) { + MutexLock lock(&sender_lock_); + RTC_CHECK(sender_); + sender_->SetVideoLayersAllocationAfterTransformation(std::move(allocation)); +} + +void RTPSenderVideoFrameTransformerDelegate::Reset() { + frame_transformer_->UnregisterTransformedFrameSinkCallback(ssrc_); + frame_transformer_ = nullptr; + { + MutexLock lock(&sender_lock_); + sender_ = nullptr; + } +} + +std::unique_ptr<TransformableVideoFrameInterface> CloneSenderVideoFrame( + TransformableVideoFrameInterface* original) { + auto encoded_image_buffer = EncodedImageBuffer::Create( + original->GetData().data(), original->GetData().size()); + EncodedImage encoded_image; + encoded_image.SetEncodedData(encoded_image_buffer); + RTPVideoHeader new_header; + absl::optional<VideoCodecType> new_codec_type; + // TODO(bugs.webrtc.org/14708): Figure out a way to get the header information + // without casting to TransformableVideoSenderFrame. + if (original->GetDirection() == + TransformableFrameInterface::Direction::kSender) { + // TODO(bugs.webrtc.org/14708): Figure out a way to bulletproof this cast. + auto original_as_sender = + static_cast<TransformableVideoSenderFrame*>(original); + new_header = original_as_sender->GetHeader(); + new_codec_type = original_as_sender->GetCodecType(); + } else { + // TODO(bugs.webrtc.org/14708): Make this codec dependent + new_header.video_type_header.emplace<RTPVideoHeaderVP8>(); + new_codec_type = kVideoCodecVP8; + // TODO(bugs.webrtc.org/14708): Fill in the new_header when it's not + // `Direction::kSender` + } + // TODO(bugs.webrtc.org/14708): Fill in other EncodedImage parameters + return std::make_unique<TransformableVideoSenderFrame>( + encoded_image, new_header, original->GetPayloadType(), new_codec_type, + original->GetTimestamp(), + absl::nullopt, // expected_retransmission_time_ms + original->GetSsrc()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h new file mode 100644 index 0000000000..55f7961e2d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h @@ -0,0 +1,94 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_SENDER_VIDEO_FRAME_TRANSFORMER_DELEGATE_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_SENDER_VIDEO_FRAME_TRANSFORMER_DELEGATE_H_ + +#include <memory> + +#include "api/frame_transformer_interface.h" +#include "api/scoped_refptr.h" +#include "api/sequence_checker.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/video/video_layers_allocation.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +class RTPSenderVideo; + +// Delegates calls to FrameTransformerInterface to transform frames, and to +// RTPSenderVideo to send the transformed frames. Ensures thread-safe access to +// the sender. +class RTPSenderVideoFrameTransformerDelegate : public TransformedFrameCallback { + public: + RTPSenderVideoFrameTransformerDelegate( + RTPSenderVideo* sender, + rtc::scoped_refptr<FrameTransformerInterface> frame_transformer, + uint32_t ssrc, + TaskQueueFactory* send_transport_queue); + + void Init(); + + // Delegates the call to FrameTransformerInterface::TransformFrame. + bool TransformFrame(int payload_type, + absl::optional<VideoCodecType> codec_type, + uint32_t rtp_timestamp, + const EncodedImage& encoded_image, + RTPVideoHeader video_header, + absl::optional<int64_t> expected_retransmission_time_ms); + + // Implements TransformedFrameCallback. Can be called on any thread. Posts + // the transformed frame to be sent on the `encoder_queue_`. + void OnTransformedFrame( + std::unique_ptr<TransformableFrameInterface> frame) override; + + // Delegates the call to RTPSendVideo::SendVideo on the `encoder_queue_`. + void SendVideo(std::unique_ptr<TransformableFrameInterface> frame) const + RTC_RUN_ON(transformation_queue_); + + // Delegates the call to RTPSendVideo::SetVideoStructureAfterTransformation + // under `sender_lock_`. + void SetVideoStructureUnderLock( + const FrameDependencyStructure* video_structure); + + // Delegates the call to + // RTPSendVideo::SetVideoLayersAllocationAfterTransformation under + // `sender_lock_`. + void SetVideoLayersAllocationUnderLock(VideoLayersAllocation allocation); + + // Unregisters and releases the `frame_transformer_` reference, and resets + // `sender_` under lock. Called from RTPSenderVideo destructor to prevent the + // `sender_` to dangle. + void Reset(); + + protected: + ~RTPSenderVideoFrameTransformerDelegate() override = default; + + private: + void EnsureEncoderQueueCreated(); + + mutable Mutex sender_lock_; + RTPSenderVideo* sender_ RTC_GUARDED_BY(sender_lock_); + rtc::scoped_refptr<FrameTransformerInterface> frame_transformer_; + const uint32_t ssrc_; + // Used when the encoded frames arrives without a current task queue. This can + // happen if a hardware encoder was used. + std::unique_ptr<TaskQueueBase, TaskQueueDeleter> transformation_queue_; +}; + +// Method to support cloning a Sender frame from another frame +std::unique_ptr<TransformableVideoFrameInterface> CloneSenderVideoFrame( + TransformableVideoFrameInterface* original); + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_SENDER_VIDEO_FRAME_TRANSFORMER_DELEGATE_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc new file mode 100644 index 0000000000..d6fbba7bd8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc @@ -0,0 +1,1697 @@ +/* + * 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/rtp_sender_video.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/memory/memory.h" +#include "api/field_trials_registry.h" +#include "api/frame_transformer_factory.h" +#include "api/rtp_headers.h" +#include "api/task_queue/task_queue_base.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/test/mock_frame_encryptor.h" +#include "api/transport/field_trial_based_config.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/units/timestamp.h" +#include "api/video/video_codec_constants.h" +#include "api/video/video_timing.h" +#include "modules/rtp_rtcp/include/rtp_cvo.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/source/rtcp_packet/nack.h" +#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_format_video_generic.h" +#include "modules/rtp_rtcp/source/rtp_generic_frame_descriptor.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_rtcp_impl2.h" +#include "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/rate_limiter.h" +#include "rtc_base/thread.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_frame_transformer.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { + +namespace { + +using ::testing::_; +using ::testing::ContainerEq; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::NiceMock; +using ::testing::Not; +using ::testing::Return; +using ::testing::ReturnArg; +using ::testing::SaveArg; +using ::testing::SizeIs; +using ::testing::WithArgs; + +enum : int { // The first valid value is 1. + kAbsoluteSendTimeExtensionId = 1, + kGenericDescriptorId, + kDependencyDescriptorId, + kTransmissionTimeOffsetExtensionId, + kTransportSequenceNumberExtensionId, + kVideoRotationExtensionId, + kVideoTimingExtensionId, + kAbsoluteCaptureTimeExtensionId, + kPlayoutDelayExtensionId, + kVideoLayersAllocationExtensionId, +}; + +constexpr int kPayload = 100; +constexpr VideoCodecType kType = VideoCodecType::kVideoCodecGeneric; +constexpr uint32_t kTimestamp = 10; +constexpr uint16_t kSeqNum = 33; +constexpr uint32_t kSsrc = 725242; +constexpr uint32_t kRtxSsrc = 912364; +constexpr int kMaxPacketLength = 1500; +constexpr Timestamp kStartTime = Timestamp::Millis(123456789); +constexpr int64_t kDefaultExpectedRetransmissionTimeMs = 125; + +class LoopbackTransportTest : public webrtc::Transport { + public: + LoopbackTransportTest() { + receivers_extensions_.Register<TransmissionOffset>( + kTransmissionTimeOffsetExtensionId); + receivers_extensions_.Register<AbsoluteSendTime>( + kAbsoluteSendTimeExtensionId); + receivers_extensions_.Register<TransportSequenceNumber>( + kTransportSequenceNumberExtensionId); + receivers_extensions_.Register<VideoOrientation>(kVideoRotationExtensionId); + receivers_extensions_.Register<VideoTimingExtension>( + kVideoTimingExtensionId); + receivers_extensions_.Register<RtpGenericFrameDescriptorExtension00>( + kGenericDescriptorId); + receivers_extensions_.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorId); + receivers_extensions_.Register<AbsoluteCaptureTimeExtension>( + kAbsoluteCaptureTimeExtensionId); + receivers_extensions_.Register<PlayoutDelayLimits>( + kPlayoutDelayExtensionId); + receivers_extensions_.Register<RtpVideoLayersAllocationExtension>( + kVideoLayersAllocationExtensionId); + } + + bool SendRtp(const uint8_t* data, + size_t len, + const PacketOptions& options) override { + sent_packets_.push_back(RtpPacketReceived(&receivers_extensions_)); + EXPECT_TRUE(sent_packets_.back().Parse(data, len)); + return true; + } + bool SendRtcp(const uint8_t* data, size_t len) override { return false; } + const RtpPacketReceived& last_sent_packet() { return sent_packets_.back(); } + int packets_sent() { return sent_packets_.size(); } + const std::vector<RtpPacketReceived>& sent_packets() const { + return sent_packets_; + } + + private: + RtpHeaderExtensionMap receivers_extensions_; + std::vector<RtpPacketReceived> sent_packets_; +}; + +class TestRtpSenderVideo : public RTPSenderVideo { + public: + TestRtpSenderVideo(Clock* clock, + RTPSender* rtp_sender, + const FieldTrialsView& field_trials) + : RTPSenderVideo([&] { + Config config; + config.clock = clock; + config.rtp_sender = rtp_sender; + config.field_trials = &field_trials; + return config; + }()) {} + ~TestRtpSenderVideo() override {} + + bool AllowRetransmission(const RTPVideoHeader& header, + int32_t retransmission_settings, + int64_t expected_retransmission_time_ms) { + return RTPSenderVideo::AllowRetransmission(GetTemporalId(header), + retransmission_settings, + expected_retransmission_time_ms); + } +}; + +class FieldTrials : public FieldTrialsRegistry { + public: + FieldTrials() : include_capture_clock_offset_(false) {} + + void set_include_capture_clock_offset(bool include_capture_clock_offset) { + include_capture_clock_offset_ = include_capture_clock_offset; + } + + private: + std::string GetValue(absl::string_view key) const override { + if (key == "WebRTC-IncludeCaptureClockOffset") { + return include_capture_clock_offset_ ? "" : "Disabled"; + } + return ""; + } + + bool include_capture_clock_offset_; +}; + +class RtpSenderVideoTest : public ::testing::Test { + public: + RtpSenderVideoTest() + : fake_clock_(kStartTime), + retransmission_rate_limiter_(&fake_clock_, 1000), + rtp_module_(ModuleRtpRtcpImpl2::Create([&] { + RtpRtcpInterface::Configuration config; + config.clock = &fake_clock_; + config.outgoing_transport = &transport_; + config.retransmission_rate_limiter = &retransmission_rate_limiter_; + config.field_trials = &field_trials_; + config.local_media_ssrc = kSsrc; + config.rtx_send_ssrc = kRtxSsrc; + config.rid = "rid"; + return config; + }())), + rtp_sender_video_( + std::make_unique<TestRtpSenderVideo>(&fake_clock_, + rtp_module_->RtpSender(), + field_trials_)) { + rtp_module_->SetSequenceNumber(kSeqNum); + rtp_module_->SetStartTimestamp(0); + } + + void UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed( + int version); + + protected: + rtc::AutoThread main_thread_; + const RtpRtcpInterface::Configuration config_; + FieldTrials field_trials_; + SimulatedClock fake_clock_; + LoopbackTransportTest transport_; + RateLimiter retransmission_rate_limiter_; + std::unique_ptr<ModuleRtpRtcpImpl2> rtp_module_; + std::unique_ptr<TestRtpSenderVideo> rtp_sender_video_; +}; + +TEST_F(RtpSenderVideoTest, KeyFrameHasCVO) { + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(VideoOrientation::Uri(), + kVideoRotationExtensionId); + + RTPVideoHeader hdr; + hdr.rotation = kVideoRotation_0; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoRotation rotation; + EXPECT_TRUE( + transport_.last_sent_packet().GetExtension<VideoOrientation>(&rotation)); + EXPECT_EQ(kVideoRotation_0, rotation); +} + +TEST_F(RtpSenderVideoTest, TimingFrameHasPacketizationTimstampSet) { + uint8_t kFrame[kMaxPacketLength]; + const int64_t kPacketizationTimeMs = 100; + const int64_t kEncodeStartDeltaMs = 10; + const int64_t kEncodeFinishDeltaMs = 50; + rtp_module_->RegisterRtpHeaderExtension(VideoTimingExtension::Uri(), + kVideoTimingExtensionId); + + const int64_t kCaptureTimestamp = fake_clock_.TimeInMilliseconds(); + + RTPVideoHeader hdr; + hdr.video_timing.flags = VideoSendTiming::kTriggeredByTimer; + hdr.video_timing.encode_start_delta_ms = kEncodeStartDeltaMs; + hdr.video_timing.encode_finish_delta_ms = kEncodeFinishDeltaMs; + + fake_clock_.AdvanceTimeMilliseconds(kPacketizationTimeMs); + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, kCaptureTimestamp, + kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + VideoSendTiming timing; + EXPECT_TRUE(transport_.last_sent_packet().GetExtension<VideoTimingExtension>( + &timing)); + EXPECT_EQ(kPacketizationTimeMs, timing.packetization_finish_delta_ms); + EXPECT_EQ(kEncodeStartDeltaMs, timing.encode_start_delta_ms); + EXPECT_EQ(kEncodeFinishDeltaMs, timing.encode_finish_delta_ms); +} + +TEST_F(RtpSenderVideoTest, DeltaFrameHasCVOWhenChanged) { + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(VideoOrientation::Uri(), + kVideoRotationExtensionId); + + RTPVideoHeader hdr; + hdr.rotation = kVideoRotation_90; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + EXPECT_TRUE( + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs)); + + hdr.rotation = kVideoRotation_0; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + EXPECT_TRUE( + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp + 1, 0, kFrame, + hdr, kDefaultExpectedRetransmissionTimeMs)); + + VideoRotation rotation; + EXPECT_TRUE( + transport_.last_sent_packet().GetExtension<VideoOrientation>(&rotation)); + EXPECT_EQ(kVideoRotation_0, rotation); +} + +TEST_F(RtpSenderVideoTest, DeltaFrameHasCVOWhenNonZero) { + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(VideoOrientation::Uri(), + kVideoRotationExtensionId); + + RTPVideoHeader hdr; + hdr.rotation = kVideoRotation_90; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + EXPECT_TRUE( + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs)); + + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + EXPECT_TRUE( + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp + 1, 0, kFrame, + hdr, kDefaultExpectedRetransmissionTimeMs)); + + VideoRotation rotation; + EXPECT_TRUE( + transport_.last_sent_packet().GetExtension<VideoOrientation>(&rotation)); + EXPECT_EQ(kVideoRotation_90, rotation); +} + +// Make sure rotation is parsed correctly when the Camera (C) and Flip (F) bits +// are set in the CVO byte. +TEST_F(RtpSenderVideoTest, SendVideoWithCameraAndFlipCVO) { + // Test extracting rotation when Camera (C) and Flip (F) bits are zero. + EXPECT_EQ(kVideoRotation_0, ConvertCVOByteToVideoRotation(0)); + EXPECT_EQ(kVideoRotation_90, ConvertCVOByteToVideoRotation(1)); + EXPECT_EQ(kVideoRotation_180, ConvertCVOByteToVideoRotation(2)); + EXPECT_EQ(kVideoRotation_270, ConvertCVOByteToVideoRotation(3)); + // Test extracting rotation when Camera (C) and Flip (F) bits are set. + const int flip_bit = 1 << 2; + const int camera_bit = 1 << 3; + EXPECT_EQ(kVideoRotation_0, + ConvertCVOByteToVideoRotation(flip_bit | camera_bit | 0)); + EXPECT_EQ(kVideoRotation_90, + ConvertCVOByteToVideoRotation(flip_bit | camera_bit | 1)); + EXPECT_EQ(kVideoRotation_180, + ConvertCVOByteToVideoRotation(flip_bit | camera_bit | 2)); + EXPECT_EQ(kVideoRotation_270, + ConvertCVOByteToVideoRotation(flip_bit | camera_bit | 3)); +} + +TEST_F(RtpSenderVideoTest, RetransmissionTypesGeneric) { + RTPVideoHeader header; + header.codec = kVideoCodecGeneric; + + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitOff, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitBaseLayer, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kConditionallyRetransmitHigherLayers, + kDefaultExpectedRetransmissionTimeMs)); +} + +TEST_F(RtpSenderVideoTest, RetransmissionTypesH264) { + RTPVideoHeader header; + header.video_type_header.emplace<RTPVideoHeaderH264>().packetization_mode = + H264PacketizationMode::NonInterleaved; + header.codec = kVideoCodecH264; + + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitOff, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitBaseLayer, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kConditionallyRetransmitHigherLayers, + kDefaultExpectedRetransmissionTimeMs)); +} + +TEST_F(RtpSenderVideoTest, RetransmissionTypesVP8BaseLayer) { + RTPVideoHeader header; + header.codec = kVideoCodecVP8; + auto& vp8_header = header.video_type_header.emplace<RTPVideoHeaderVP8>(); + vp8_header.temporalIdx = 0; + + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitOff, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitBaseLayer, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers | kRetransmitBaseLayer, + kDefaultExpectedRetransmissionTimeMs)); + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kConditionallyRetransmitHigherLayers, + kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitBaseLayer | kConditionallyRetransmitHigherLayers, + kDefaultExpectedRetransmissionTimeMs)); +} + +TEST_F(RtpSenderVideoTest, RetransmissionTypesVP8HigherLayers) { + RTPVideoHeader header; + header.codec = kVideoCodecVP8; + + auto& vp8_header = header.video_type_header.emplace<RTPVideoHeaderVP8>(); + for (int tid = 1; tid <= kMaxTemporalStreams; ++tid) { + vp8_header.temporalIdx = tid; + + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitOff, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitBaseLayer, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers | kRetransmitBaseLayer, + kDefaultExpectedRetransmissionTimeMs)); + } +} + +TEST_F(RtpSenderVideoTest, RetransmissionTypesVP9) { + RTPVideoHeader header; + header.codec = kVideoCodecVP9; + + auto& vp9_header = header.video_type_header.emplace<RTPVideoHeaderVP9>(); + for (int tid = 1; tid <= kMaxTemporalStreams; ++tid) { + vp9_header.temporal_idx = tid; + + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitOff, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_FALSE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitBaseLayer, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers, kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video_->AllowRetransmission( + header, kRetransmitHigherLayers | kRetransmitBaseLayer, + kDefaultExpectedRetransmissionTimeMs)); + } +} + +TEST_F(RtpSenderVideoTest, ConditionalRetransmit) { + const int64_t kFrameIntervalMs = 33; + const int64_t kRttMs = (kFrameIntervalMs * 3) / 2; + const uint8_t kSettings = + kRetransmitBaseLayer | kConditionallyRetransmitHigherLayers; + + // Insert VP8 frames for all temporal layers, but stop before the final index. + RTPVideoHeader header; + header.codec = kVideoCodecVP8; + + // Fill averaging window to prevent rounding errors. + constexpr int kNumRepetitions = + (RTPSenderVideo::kTLRateWindowSizeMs + (kFrameIntervalMs / 2)) / + kFrameIntervalMs; + constexpr int kPattern[] = {0, 2, 1, 2}; + auto& vp8_header = header.video_type_header.emplace<RTPVideoHeaderVP8>(); + for (size_t i = 0; i < arraysize(kPattern) * kNumRepetitions; ++i) { + vp8_header.temporalIdx = kPattern[i % arraysize(kPattern)]; + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + } + + // Since we're at the start of the pattern, the next expected frame in TL0 is + // right now. We will wait at most one expected retransmission time before + // acknowledging that it did not arrive, which means this frame and the next + // will not be retransmitted. + vp8_header.temporalIdx = 1; + EXPECT_FALSE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + EXPECT_FALSE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + + // The TL0 frame did not arrive. So allow retransmission. + EXPECT_TRUE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + + // Insert a frame for TL2. We just had frame in TL1, so the next one there is + // in three frames away. TL0 is still too far in the past. So, allow + // retransmission. + vp8_header.temporalIdx = 2; + EXPECT_TRUE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + + // Another TL2, next in TL1 is two frames away. Allow again. + EXPECT_TRUE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + + // Yet another TL2, next in TL1 is now only one frame away, so don't store + // for retransmission. + EXPECT_FALSE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); +} + +TEST_F(RtpSenderVideoTest, ConditionalRetransmitLimit) { + const int64_t kFrameIntervalMs = 200; + const int64_t kRttMs = (kFrameIntervalMs * 3) / 2; + const int32_t kSettings = + kRetransmitBaseLayer | kConditionallyRetransmitHigherLayers; + + // Insert VP8 frames for all temporal layers, but stop before the final index. + RTPVideoHeader header; + header.codec = kVideoCodecVP8; + + // Fill averaging window to prevent rounding errors. + constexpr int kNumRepetitions = + (RTPSenderVideo::kTLRateWindowSizeMs + (kFrameIntervalMs / 2)) / + kFrameIntervalMs; + constexpr int kPattern[] = {0, 2, 2, 2}; + auto& vp8_header = header.video_type_header.emplace<RTPVideoHeaderVP8>(); + for (size_t i = 0; i < arraysize(kPattern) * kNumRepetitions; ++i) { + vp8_header.temporalIdx = kPattern[i % arraysize(kPattern)]; + + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs); + fake_clock_.AdvanceTimeMilliseconds(kFrameIntervalMs); + } + + // Since we're at the start of the pattern, the next expected frame will be + // right now in TL0. Put it in TL1 instead. Regular rules would dictate that + // we don't store for retransmission because we expect a frame in a lower + // layer, but that last frame in TL1 was a long time ago in absolute terms, + // so allow retransmission anyway. + vp8_header.temporalIdx = 1; + EXPECT_TRUE( + rtp_sender_video_->AllowRetransmission(header, kSettings, kRttMs)); +} + +TEST_F(RtpSenderVideoTest, + ReservesEnoughSpaceForRtxPacketWhenMidAndRsidAreRegistered) { + constexpr int kMediaPayloadId = 100; + constexpr int kRtxPayloadId = 101; + constexpr size_t kMaxPacketSize = 1'000; + + rtp_module_->SetMaxRtpPacketSize(kMaxPacketSize); + rtp_module_->RegisterRtpHeaderExtension(RtpMid::Uri(), 1); + rtp_module_->RegisterRtpHeaderExtension(RtpStreamId::Uri(), 2); + rtp_module_->RegisterRtpHeaderExtension(RepairedRtpStreamId::Uri(), 3); + rtp_module_->RegisterRtpHeaderExtension(AbsoluteSendTime::Uri(), 4); + rtp_module_->SetMid("long_mid"); + rtp_module_->SetRtxSendPayloadType(kRtxPayloadId, kMediaPayloadId); + rtp_module_->SetStorePacketsStatus(/*enable=*/true, 10); + rtp_module_->SetRtxSendStatus(kRtxRetransmitted); + + RTPVideoHeader header; + header.codec = kVideoCodecVP8; + header.frame_type = VideoFrameType::kVideoFrameDelta; + auto& vp8_header = header.video_type_header.emplace<RTPVideoHeaderVP8>(); + vp8_header.temporalIdx = 0; + + uint8_t kPayload[kMaxPacketSize] = {}; + EXPECT_TRUE(rtp_sender_video_->SendVideo( + kMediaPayloadId, /*codec_type=*/kVideoCodecVP8, /*rtp_timestamp=*/0, + /*capture_time_ms=*/1'000, kPayload, header, + /*expected_retransmission_time_ms=*/absl::nullopt, /*csrcs=*/{})); + ASSERT_THAT(transport_.sent_packets(), Not(IsEmpty())); + // Ack media ssrc, but not rtx ssrc. + rtcp::ReceiverReport rr; + rtcp::ReportBlock rb; + rb.SetMediaSsrc(kSsrc); + rb.SetExtHighestSeqNum(transport_.last_sent_packet().SequenceNumber()); + rr.AddReportBlock(rb); + rtp_module_->IncomingRtcpPacket(rr.Build()); + + // Test for various frame size close to `kMaxPacketSize` to catch edge cases + // when rtx packet barely fit. + for (size_t frame_size = 800; frame_size < kMaxPacketSize; ++frame_size) { + SCOPED_TRACE(frame_size); + rtc::ArrayView<const uint8_t> payload(kPayload, frame_size); + + EXPECT_TRUE(rtp_sender_video_->SendVideo( + kMediaPayloadId, /*codec_type=*/kVideoCodecVP8, /*rtp_timestamp=*/0, + /*capture_time_ms=*/1'000, payload, header, + /*expected_retransmission_time_ms=*/1'000, /*csrcs=*/{})); + const RtpPacketReceived& media_packet = transport_.last_sent_packet(); + EXPECT_EQ(media_packet.Ssrc(), kSsrc); + + rtcp::Nack nack; + nack.SetMediaSsrc(kSsrc); + nack.SetPacketIds({media_packet.SequenceNumber()}); + rtp_module_->IncomingRtcpPacket(nack.Build()); + + const RtpPacketReceived& rtx_packet = transport_.last_sent_packet(); + EXPECT_EQ(rtx_packet.Ssrc(), kRtxSsrc); + EXPECT_LE(rtx_packet.size(), kMaxPacketSize); + } +} + +TEST_F(RtpSenderVideoTest, SendsDependencyDescriptorWhenVideoStructureIsSet) { + const int64_t kFrameId = 100000; + uint8_t kFrame[100]; + rtp_module_->RegisterRtpHeaderExtension( + RtpDependencyDescriptorExtension::Uri(), kDependencyDescriptorId); + FrameDependencyStructure video_structure; + video_structure.num_decode_targets = 2; + video_structure.templates = { + FrameDependencyTemplate().S(0).T(0).Dtis("SS"), + FrameDependencyTemplate().S(1).T(0).Dtis("-S"), + FrameDependencyTemplate().S(1).T(1).Dtis("-D"), + }; + rtp_sender_video_->SetVideoStructure(&video_structure); + + // Send key frame. + RTPVideoHeader hdr; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + generic.temporal_index = 0; + generic.spatial_index = 0; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch, + DecodeTargetIndication::kSwitch}; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + ASSERT_EQ(transport_.packets_sent(), 1); + DependencyDescriptor descriptor_key; + ASSERT_TRUE(transport_.last_sent_packet() + .GetExtension<RtpDependencyDescriptorExtension>( + nullptr, &descriptor_key)); + ASSERT_TRUE(descriptor_key.attached_structure); + EXPECT_EQ(descriptor_key.attached_structure->num_decode_targets, 2); + EXPECT_THAT(descriptor_key.attached_structure->templates, SizeIs(3)); + EXPECT_EQ(descriptor_key.frame_number, kFrameId & 0xFFFF); + EXPECT_EQ(descriptor_key.frame_dependencies.spatial_id, 0); + EXPECT_EQ(descriptor_key.frame_dependencies.temporal_id, 0); + EXPECT_EQ(descriptor_key.frame_dependencies.decode_target_indications, + generic.decode_target_indications); + EXPECT_THAT(descriptor_key.frame_dependencies.frame_diffs, IsEmpty()); + + // Send delta frame. + generic.frame_id = kFrameId + 1; + generic.temporal_index = 1; + generic.spatial_index = 1; + generic.dependencies = {kFrameId, kFrameId - 500}; + generic.decode_target_indications = {DecodeTargetIndication::kNotPresent, + DecodeTargetIndication::kRequired}; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + EXPECT_EQ(transport_.packets_sent(), 2); + DependencyDescriptor descriptor_delta; + ASSERT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpDependencyDescriptorExtension>( + descriptor_key.attached_structure.get(), &descriptor_delta)); + EXPECT_EQ(descriptor_delta.attached_structure, nullptr); + EXPECT_EQ(descriptor_delta.frame_number, (kFrameId + 1) & 0xFFFF); + EXPECT_EQ(descriptor_delta.frame_dependencies.spatial_id, 1); + EXPECT_EQ(descriptor_delta.frame_dependencies.temporal_id, 1); + EXPECT_EQ(descriptor_delta.frame_dependencies.decode_target_indications, + generic.decode_target_indications); + EXPECT_THAT(descriptor_delta.frame_dependencies.frame_diffs, + ElementsAre(1, 501)); +} + +TEST_F(RtpSenderVideoTest, + SkipsDependencyDescriptorOnDeltaFrameWhenFailedToAttachToKeyFrame) { + const int64_t kFrameId = 100000; + uint8_t kFrame[100]; + rtp_module_->RegisterRtpHeaderExtension( + RtpDependencyDescriptorExtension::Uri(), kDependencyDescriptorId); + rtp_module_->SetExtmapAllowMixed(false); + FrameDependencyStructure video_structure; + video_structure.num_decode_targets = 2; + // Use many templates so that key dependency descriptor would be too large + // to fit into 16 bytes (max size of one byte header rtp header extension) + video_structure.templates = { + FrameDependencyTemplate().S(0).T(0).Dtis("SS"), + FrameDependencyTemplate().S(1).T(0).Dtis("-S"), + FrameDependencyTemplate().S(1).T(1).Dtis("-D").FrameDiffs({1, 2, 3, 4}), + FrameDependencyTemplate().S(1).T(1).Dtis("-D").FrameDiffs({2, 3, 4, 5}), + FrameDependencyTemplate().S(1).T(1).Dtis("-D").FrameDiffs({3, 4, 5, 6}), + FrameDependencyTemplate().S(1).T(1).Dtis("-D").FrameDiffs({4, 5, 6, 7}), + }; + rtp_sender_video_->SetVideoStructure(&video_structure); + + // Send key frame. + RTPVideoHeader hdr; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + generic.temporal_index = 0; + generic.spatial_index = 0; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch, + DecodeTargetIndication::kSwitch}; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + ASSERT_EQ(transport_.packets_sent(), 1); + DependencyDescriptor descriptor_key; + ASSERT_FALSE(transport_.last_sent_packet() + .HasExtension<RtpDependencyDescriptorExtension>()); + + // Send delta frame. + generic.frame_id = kFrameId + 1; + generic.temporal_index = 1; + generic.spatial_index = 1; + generic.dependencies = {kFrameId, kFrameId - 500}; + generic.decode_target_indications = {DecodeTargetIndication::kNotPresent, + DecodeTargetIndication::kRequired}; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + EXPECT_EQ(transport_.packets_sent(), 2); + EXPECT_FALSE(transport_.last_sent_packet() + .HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST_F(RtpSenderVideoTest, PropagatesChainDiffsIntoDependencyDescriptor) { + const int64_t kFrameId = 100000; + uint8_t kFrame[100]; + rtp_module_->RegisterRtpHeaderExtension( + RtpDependencyDescriptorExtension::Uri(), kDependencyDescriptorId); + FrameDependencyStructure video_structure; + video_structure.num_decode_targets = 2; + video_structure.num_chains = 1; + video_structure.decode_target_protected_by_chain = {0, 0}; + video_structure.templates = { + FrameDependencyTemplate().S(0).T(0).Dtis("SS").ChainDiffs({1}), + }; + rtp_sender_video_->SetVideoStructure(&video_structure); + + RTPVideoHeader hdr; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch, + DecodeTargetIndication::kSwitch}; + generic.chain_diffs = {2}; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + ASSERT_EQ(transport_.packets_sent(), 1); + DependencyDescriptor descriptor_key; + ASSERT_TRUE(transport_.last_sent_packet() + .GetExtension<RtpDependencyDescriptorExtension>( + nullptr, &descriptor_key)); + EXPECT_THAT(descriptor_key.frame_dependencies.chain_diffs, + ContainerEq(generic.chain_diffs)); +} + +TEST_F(RtpSenderVideoTest, + PropagatesActiveDecodeTargetsIntoDependencyDescriptor) { + const int64_t kFrameId = 100000; + uint8_t kFrame[100]; + rtp_module_->RegisterRtpHeaderExtension( + RtpDependencyDescriptorExtension::Uri(), kDependencyDescriptorId); + FrameDependencyStructure video_structure; + video_structure.num_decode_targets = 2; + video_structure.num_chains = 1; + video_structure.decode_target_protected_by_chain = {0, 0}; + video_structure.templates = { + FrameDependencyTemplate().S(0).T(0).Dtis("SS").ChainDiffs({1}), + }; + rtp_sender_video_->SetVideoStructure(&video_structure); + + RTPVideoHeader hdr; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch, + DecodeTargetIndication::kSwitch}; + generic.active_decode_targets = 0b01; + generic.chain_diffs = {1}; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + ASSERT_EQ(transport_.packets_sent(), 1); + DependencyDescriptor descriptor_key; + ASSERT_TRUE(transport_.last_sent_packet() + .GetExtension<RtpDependencyDescriptorExtension>( + nullptr, &descriptor_key)); + EXPECT_EQ(descriptor_key.active_decode_targets_bitmask, 0b01u); +} + +TEST_F(RtpSenderVideoTest, + SetDiffentVideoStructureAvoidsCollisionWithThePreviousStructure) { + const int64_t kFrameId = 100000; + uint8_t kFrame[100]; + rtp_module_->RegisterRtpHeaderExtension( + RtpDependencyDescriptorExtension::Uri(), kDependencyDescriptorId); + FrameDependencyStructure video_structure1; + video_structure1.num_decode_targets = 2; + video_structure1.templates = { + FrameDependencyTemplate().S(0).T(0).Dtis("SS"), + FrameDependencyTemplate().S(0).T(1).Dtis("D-"), + }; + FrameDependencyStructure video_structure2; + video_structure2.num_decode_targets = 2; + video_structure2.templates = { + FrameDependencyTemplate().S(0).T(0).Dtis("SS"), + FrameDependencyTemplate().S(0).T(1).Dtis("R-"), + }; + + // Send 1st key frame. + RTPVideoHeader hdr; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch, + DecodeTargetIndication::kSwitch}; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SetVideoStructure(&video_structure1); + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + // Parse 1st extension. + ASSERT_EQ(transport_.packets_sent(), 1); + DependencyDescriptor descriptor_key1; + ASSERT_TRUE(transport_.last_sent_packet() + .GetExtension<RtpDependencyDescriptorExtension>( + nullptr, &descriptor_key1)); + ASSERT_TRUE(descriptor_key1.attached_structure); + + // Send the delta frame. + generic.frame_id = kFrameId + 1; + generic.temporal_index = 1; + generic.decode_target_indications = {DecodeTargetIndication::kDiscardable, + DecodeTargetIndication::kNotPresent}; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + ASSERT_EQ(transport_.packets_sent(), 2); + RtpPacket delta_packet = transport_.last_sent_packet(); + + // Send 2nd key frame. + generic.frame_id = kFrameId + 2; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch, + DecodeTargetIndication::kSwitch}; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SetVideoStructure(&video_structure2); + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + // Parse the 2nd key frame. + ASSERT_EQ(transport_.packets_sent(), 3); + DependencyDescriptor descriptor_key2; + ASSERT_TRUE(transport_.last_sent_packet() + .GetExtension<RtpDependencyDescriptorExtension>( + nullptr, &descriptor_key2)); + ASSERT_TRUE(descriptor_key2.attached_structure); + + // Try to parse the 1st delta frame. It should parseble using the structure + // from the 1st key frame, but not using the structure from the 2nd key frame. + DependencyDescriptor descriptor_delta; + EXPECT_TRUE(delta_packet.GetExtension<RtpDependencyDescriptorExtension>( + descriptor_key1.attached_structure.get(), &descriptor_delta)); + EXPECT_FALSE(delta_packet.GetExtension<RtpDependencyDescriptorExtension>( + descriptor_key2.attached_structure.get(), &descriptor_delta)); +} + +TEST_F(RtpSenderVideoTest, + AuthenticateVideoHeaderWhenDependencyDescriptorExtensionIsUsed) { + static constexpr size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize] = {1, 2, 3, 4}; + + rtp_module_->RegisterRtpHeaderExtension( + RtpDependencyDescriptorExtension::Uri(), kDependencyDescriptorId); + auto encryptor = rtc::make_ref_counted<NiceMock<MockFrameEncryptor>>(); + ON_CALL(*encryptor, GetMaxCiphertextByteSize).WillByDefault(ReturnArg<1>()); + ON_CALL(*encryptor, Encrypt) + .WillByDefault(WithArgs<3, 5>( + [](rtc::ArrayView<const uint8_t> frame, size_t* bytes_written) { + *bytes_written = frame.size(); + return 0; + })); + RTPSenderVideo::Config config; + config.clock = &fake_clock_; + config.rtp_sender = rtp_module_->RtpSender(); + config.field_trials = &field_trials_; + config.frame_encryptor = encryptor.get(); + RTPSenderVideo rtp_sender_video(config); + + FrameDependencyStructure video_structure; + video_structure.num_decode_targets = 1; + video_structure.templates = {FrameDependencyTemplate().Dtis("S")}; + rtp_sender_video.SetVideoStructure(&video_structure); + + // Send key frame. + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + hdr.generic.emplace().decode_target_indications = + video_structure.templates[0].decode_target_indications; + + EXPECT_CALL(*encryptor, + Encrypt(_, _, Not(IsEmpty()), ElementsAreArray(kFrame), _, _)); + rtp_sender_video.SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + // Double check packet with the dependency descriptor is sent. + ASSERT_EQ(transport_.packets_sent(), 1); + EXPECT_TRUE(transport_.last_sent_packet() + .HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST_F(RtpSenderVideoTest, PopulateGenericFrameDescriptor) { + const int64_t kFrameId = 100000; + uint8_t kFrame[100]; + rtp_module_->RegisterRtpHeaderExtension( + RtpGenericFrameDescriptorExtension00::Uri(), kGenericDescriptorId); + + RTPVideoHeader hdr; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + generic.temporal_index = 3; + generic.spatial_index = 2; + generic.dependencies.push_back(kFrameId - 1); + generic.dependencies.push_back(kFrameId - 500); + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + RtpGenericFrameDescriptor descriptor_wire; + EXPECT_EQ(1, transport_.packets_sent()); + ASSERT_TRUE(transport_.last_sent_packet() + .GetExtension<RtpGenericFrameDescriptorExtension00>( + &descriptor_wire)); + EXPECT_EQ(static_cast<uint16_t>(generic.frame_id), descriptor_wire.FrameId()); + EXPECT_EQ(generic.temporal_index, descriptor_wire.TemporalLayer()); + EXPECT_THAT(descriptor_wire.FrameDependenciesDiffs(), ElementsAre(1, 500)); + EXPECT_EQ(descriptor_wire.SpatialLayersBitmask(), 0b0000'0100); +} + +void RtpSenderVideoTest:: + UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed( + int version) { + const int64_t kFrameId = 100000; + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + + rtp_module_->RegisterRtpHeaderExtension( + RtpGenericFrameDescriptorExtension00::Uri(), kGenericDescriptorId); + + RTPVideoHeader hdr; + hdr.codec = kVideoCodecVP8; + RTPVideoHeaderVP8& vp8 = hdr.video_type_header.emplace<RTPVideoHeaderVP8>(); + vp8.pictureId = kFrameId % 0X7FFF; + vp8.tl0PicIdx = 13; + vp8.temporalIdx = 1; + vp8.keyIdx = 2; + RTPVideoHeader::GenericDescriptorInfo& generic = hdr.generic.emplace(); + generic.frame_id = kFrameId; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, VideoCodecType::kVideoCodecVP8, + kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + ASSERT_EQ(transport_.packets_sent(), 1); + // Expect only minimal 1-byte vp8 descriptor was generated. + EXPECT_EQ(transport_.last_sent_packet().payload_size(), 1 + kFrameSize); +} + +TEST_F(RtpSenderVideoTest, + UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed00) { + UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed(0); +} + +TEST_F(RtpSenderVideoTest, + UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed01) { + UsesMinimalVp8DescriptorWhenGenericFrameDescriptorExtensionIsUsed(1); +} + +TEST_F(RtpSenderVideoTest, VideoLayersAllocationWithResolutionSentOnKeyFrames) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.resolution_and_frame_rate_is_valid = true; + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoLayersAllocation sent_allocation; + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); + EXPECT_THAT(sent_allocation.active_spatial_layers, ElementsAre(layer)); + + // Next key frame also have the allocation. + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); +} + +TEST_F(RtpSenderVideoTest, + VideoLayersAllocationWithoutResolutionSentOnDeltaWhenUpdated) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + allocation.resolution_and_frame_rate_is_valid = true; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_TRUE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // No allocation sent on delta frame unless it has been updated. + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_FALSE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // Update the allocation. + rtp_sender_video_->SetVideoLayersAllocation(allocation); + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoLayersAllocation sent_allocation; + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); + ASSERT_THAT(sent_allocation.active_spatial_layers, SizeIs(1)); + EXPECT_FALSE(sent_allocation.resolution_and_frame_rate_is_valid); + EXPECT_THAT(sent_allocation.active_spatial_layers[0] + .target_bitrate_per_temporal_layer, + SizeIs(1)); +} + +TEST_F(RtpSenderVideoTest, + VideoLayersAllocationWithResolutionSentOnDeltaWhenSpatialLayerAdded) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + allocation.resolution_and_frame_rate_is_valid = true; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + layer.spatial_id = 0; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + ASSERT_TRUE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // Update the allocation. + layer.width = 640; + layer.height = 320; + layer.spatial_id = 1; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(100)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoLayersAllocation sent_allocation; + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); + EXPECT_THAT(sent_allocation.active_spatial_layers, SizeIs(2)); + EXPECT_TRUE(sent_allocation.resolution_and_frame_rate_is_valid); +} + +TEST_F(RtpSenderVideoTest, + VideoLayersAllocationWithResolutionSentOnLargeFrameRateChange) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + allocation.resolution_and_frame_rate_is_valid = true; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + layer.spatial_id = 0; + layer.frame_rate_fps = 10; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + ASSERT_TRUE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // Update frame rate only. + allocation.active_spatial_layers[0].frame_rate_fps = 20; + rtp_sender_video_->SetVideoLayersAllocation(allocation); + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoLayersAllocation sent_allocation; + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); + ASSERT_TRUE(sent_allocation.resolution_and_frame_rate_is_valid); + EXPECT_EQ(sent_allocation.active_spatial_layers[0].frame_rate_fps, 20); +} + +TEST_F(RtpSenderVideoTest, + VideoLayersAllocationWithoutResolutionSentOnSmallFrameRateChange) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + allocation.resolution_and_frame_rate_is_valid = true; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + layer.spatial_id = 0; + layer.frame_rate_fps = 10; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + ASSERT_TRUE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // Update frame rate slightly. + allocation.active_spatial_layers[0].frame_rate_fps = 9; + rtp_sender_video_->SetVideoLayersAllocation(allocation); + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoLayersAllocation sent_allocation; + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); + EXPECT_FALSE(sent_allocation.resolution_and_frame_rate_is_valid); +} + +TEST_F(RtpSenderVideoTest, VideoLayersAllocationSentOnDeltaFramesOnlyOnUpdate) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + VideoLayersAllocation sent_allocation; + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); + EXPECT_THAT(sent_allocation.active_spatial_layers, SizeIs(1)); + + // VideoLayersAllocation not sent on the next delta frame. + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_FALSE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // Update allocation. VideoLayesAllocation should be sent on the next frame. + rtp_sender_video_->SetVideoLayersAllocation(allocation); + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_TRUE( + transport_.last_sent_packet() + .GetExtension<RtpVideoLayersAllocationExtension>(&sent_allocation)); +} + +TEST_F(RtpSenderVideoTest, VideoLayersAllocationNotSentOnHigherTemporalLayers) { + const size_t kFrameSize = 100; + uint8_t kFrame[kFrameSize]; + rtp_module_->RegisterRtpHeaderExtension( + RtpVideoLayersAllocationExtension::Uri(), + kVideoLayersAllocationExtensionId); + + VideoLayersAllocation allocation; + allocation.resolution_and_frame_rate_is_valid = true; + VideoLayersAllocation::SpatialLayer layer; + layer.width = 360; + layer.height = 180; + layer.target_bitrate_per_temporal_layer.push_back( + DataRate::KilobitsPerSec(50)); + allocation.active_spatial_layers.push_back(layer); + rtp_sender_video_->SetVideoLayersAllocation(allocation); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + hdr.codec = VideoCodecType::kVideoCodecVP8; + auto& vp8_header = hdr.video_type_header.emplace<RTPVideoHeaderVP8>(); + vp8_header.temporalIdx = 1; + + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_FALSE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); + + // Send a delta frame on tl0. + vp8_header.temporalIdx = 0; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_TRUE(transport_.last_sent_packet() + .HasExtension<RtpVideoLayersAllocationExtension>()); +} + +TEST_F(RtpSenderVideoTest, AbsoluteCaptureTime) { + constexpr int64_t kAbsoluteCaptureTimestampMs = 12345678; + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + kAbsoluteCaptureTimeExtensionId); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, + kAbsoluteCaptureTimestampMs, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + absl::optional<AbsoluteCaptureTime> absolute_capture_time; + + // It is expected that one and only one of the packets sent on this video + // frame has absolute capture time header extension. + for (const RtpPacketReceived& packet : transport_.sent_packets()) { + if (absolute_capture_time.has_value()) { + EXPECT_FALSE(packet.HasExtension<AbsoluteCaptureTimeExtension>()); + } else { + absolute_capture_time = + packet.GetExtension<AbsoluteCaptureTimeExtension>(); + } + } + + // Verify the capture timestamp and that the clock offset is not set. + ASSERT_TRUE(absolute_capture_time.has_value()); + EXPECT_EQ( + absolute_capture_time->absolute_capture_timestamp, + Int64MsToUQ32x32(fake_clock_.ConvertTimestampToNtpTimeInMilliseconds( + kAbsoluteCaptureTimestampMs))); + EXPECT_FALSE( + absolute_capture_time->estimated_capture_clock_offset.has_value()); +} + +TEST_F(RtpSenderVideoTest, + AbsoluteCaptureTimeNotForwardedWhenImageHasNoCaptureTime) { + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + kAbsoluteCaptureTimeExtensionId); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, + /*capture_time_ms=*/0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + // No absolute capture time should be set as the capture_time_ms was the + // default value. + for (const RtpPacketReceived& packet : transport_.sent_packets()) { + EXPECT_FALSE(packet.HasExtension<AbsoluteCaptureTimeExtension>()); + } +} + +// Essentially the same test as AbsoluteCaptureTime but with a field trial. +// After the field trial is experimented, we will remove AbsoluteCaptureTime. +TEST_F(RtpSenderVideoTest, AbsoluteCaptureTimeWithCaptureClockOffset) { + field_trials_.set_include_capture_clock_offset(true); + rtp_sender_video_ = std::make_unique<TestRtpSenderVideo>( + &fake_clock_, rtp_module_->RtpSender(), field_trials_); + + constexpr int64_t kAbsoluteCaptureTimestampMs = 12345678; + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + kAbsoluteCaptureTimeExtensionId); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, + kAbsoluteCaptureTimestampMs, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + absl::optional<AbsoluteCaptureTime> absolute_capture_time; + + // It is expected that one and only one of the packets sent on this video + // frame has absolute capture time header extension. + for (const RtpPacketReceived& packet : transport_.sent_packets()) { + if (absolute_capture_time.has_value()) { + EXPECT_FALSE(packet.HasExtension<AbsoluteCaptureTimeExtension>()); + } else { + absolute_capture_time = + packet.GetExtension<AbsoluteCaptureTimeExtension>(); + } + } + + // Verify the capture timestamp and that the clock offset is set to zero. + ASSERT_TRUE(absolute_capture_time.has_value()); + EXPECT_EQ( + absolute_capture_time->absolute_capture_timestamp, + Int64MsToUQ32x32(fake_clock_.ConvertTimestampToNtpTimeInMilliseconds( + kAbsoluteCaptureTimestampMs))); + EXPECT_EQ(absolute_capture_time->estimated_capture_clock_offset, 0); +} + +TEST_F(RtpSenderVideoTest, AbsoluteCaptureTimeWithExtensionProvided) { + constexpr AbsoluteCaptureTime kAbsoluteCaptureTime = { + 123, + absl::optional<int64_t>(456), + }; + uint8_t kFrame[kMaxPacketLength]; + rtp_module_->RegisterRtpHeaderExtension(AbsoluteCaptureTimeExtension::Uri(), + kAbsoluteCaptureTimeExtensionId); + + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + hdr.absolute_capture_time = kAbsoluteCaptureTime; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, + /*capture_time_ms=*/789, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + + absl::optional<AbsoluteCaptureTime> absolute_capture_time; + + // It is expected that one and only one of the packets sent on this video + // frame has absolute capture time header extension. + for (const RtpPacketReceived& packet : transport_.sent_packets()) { + if (absolute_capture_time.has_value()) { + EXPECT_FALSE(packet.HasExtension<AbsoluteCaptureTimeExtension>()); + } else { + absolute_capture_time = + packet.GetExtension<AbsoluteCaptureTimeExtension>(); + } + } + + // Verify the extension. + EXPECT_EQ(absolute_capture_time, kAbsoluteCaptureTime); +} + +TEST_F(RtpSenderVideoTest, PopulatesPlayoutDelay) { + // Single packet frames. + constexpr size_t kPacketSize = 123; + uint8_t kFrame[kPacketSize]; + rtp_module_->RegisterRtpHeaderExtension(PlayoutDelayLimits::Uri(), + kPlayoutDelayExtensionId); + const VideoPlayoutDelay kExpectedDelay = {10, 20}; + + // Send initial key-frame without playout delay. + RTPVideoHeader hdr; + hdr.frame_type = VideoFrameType::kVideoFrameKey; + hdr.codec = VideoCodecType::kVideoCodecVP8; + auto& vp8_header = hdr.video_type_header.emplace<RTPVideoHeaderVP8>(); + vp8_header.temporalIdx = 0; + + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_FALSE( + transport_.last_sent_packet().HasExtension<PlayoutDelayLimits>()); + + // Set playout delay on a discardable frame. + hdr.playout_delay = kExpectedDelay; + hdr.frame_type = VideoFrameType::kVideoFrameDelta; + vp8_header.temporalIdx = 1; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + VideoPlayoutDelay received_delay = VideoPlayoutDelay(); + ASSERT_TRUE(transport_.last_sent_packet().GetExtension<PlayoutDelayLimits>( + &received_delay)); + EXPECT_EQ(received_delay, kExpectedDelay); + + // Set playout delay on a non-discardable frame, the extension should still + // be populated since dilvery wasn't guaranteed on the last one. + hdr.playout_delay = VideoPlayoutDelay(); // Indicates "no change". + vp8_header.temporalIdx = 0; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + ASSERT_TRUE(transport_.last_sent_packet().GetExtension<PlayoutDelayLimits>( + &received_delay)); + EXPECT_EQ(received_delay, kExpectedDelay); + + // The next frame does not need the extensions since it's delivery has + // already been guaranteed. + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + EXPECT_FALSE( + transport_.last_sent_packet().HasExtension<PlayoutDelayLimits>()); + + // Insert key-frame, we need to refresh the state here. + hdr.frame_type = VideoFrameType::kVideoFrameKey; + rtp_sender_video_->SendVideo(kPayload, kType, kTimestamp, 0, kFrame, hdr, + kDefaultExpectedRetransmissionTimeMs); + ASSERT_TRUE(transport_.last_sent_packet().GetExtension<PlayoutDelayLimits>( + &received_delay)); + EXPECT_EQ(received_delay, kExpectedDelay); +} + +TEST_F(RtpSenderVideoTest, SendGenericVideo) { + const uint8_t kPayloadType = 127; + const VideoCodecType kCodecType = VideoCodecType::kVideoCodecGeneric; + const uint8_t kPayload[] = {47, 11, 32, 93, 89}; + + // Send keyframe. + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + ASSERT_TRUE(rtp_sender_video_->SendVideo(kPayloadType, kCodecType, 1234, 4321, + kPayload, video_header, + absl::nullopt)); + + rtc::ArrayView<const uint8_t> sent_payload = + transport_.last_sent_packet().payload(); + uint8_t generic_header = sent_payload[0]; + EXPECT_TRUE(generic_header & RtpFormatVideoGeneric::kKeyFrameBit); + EXPECT_TRUE(generic_header & RtpFormatVideoGeneric::kFirstPacketBit); + EXPECT_THAT(sent_payload.subview(1), ElementsAreArray(kPayload)); + + // Send delta frame. + const uint8_t kDeltaPayload[] = {13, 42, 32, 93, 13}; + video_header.frame_type = VideoFrameType::kVideoFrameDelta; + ASSERT_TRUE(rtp_sender_video_->SendVideo(kPayloadType, kCodecType, 1234, 4321, + kDeltaPayload, video_header, + absl::nullopt)); + + sent_payload = sent_payload = transport_.last_sent_packet().payload(); + generic_header = sent_payload[0]; + EXPECT_FALSE(generic_header & RtpFormatVideoGeneric::kKeyFrameBit); + EXPECT_TRUE(generic_header & RtpFormatVideoGeneric::kFirstPacketBit); + EXPECT_THAT(sent_payload.subview(1), ElementsAreArray(kDeltaPayload)); +} + +TEST_F(RtpSenderVideoTest, SendRawVideo) { + const uint8_t kPayloadType = 111; + const uint8_t kPayload[] = {11, 22, 33, 44, 55}; + + // Send a frame. + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + ASSERT_TRUE(rtp_sender_video_->SendVideo(kPayloadType, absl::nullopt, 1234, + 4321, kPayload, video_header, + absl::nullopt)); + + rtc::ArrayView<const uint8_t> sent_payload = + transport_.last_sent_packet().payload(); + EXPECT_THAT(sent_payload, ElementsAreArray(kPayload)); +} + +class RtpSenderVideoWithFrameTransformerTest : public ::testing::Test { + public: + RtpSenderVideoWithFrameTransformerTest() + : time_controller_(kStartTime), + retransmission_rate_limiter_(time_controller_.GetClock(), 1000), + rtp_module_(ModuleRtpRtcpImpl2::Create([&] { + RtpRtcpInterface::Configuration config; + config.clock = time_controller_.GetClock(); + config.outgoing_transport = &transport_; + config.retransmission_rate_limiter = &retransmission_rate_limiter_; + config.field_trials = &field_trials_; + config.local_media_ssrc = kSsrc; + return config; + }())) { + rtp_module_->SetSequenceNumber(kSeqNum); + rtp_module_->SetStartTimestamp(0); + } + + std::unique_ptr<RTPSenderVideo> CreateSenderWithFrameTransformer( + rtc::scoped_refptr<FrameTransformerInterface> transformer) { + RTPSenderVideo::Config config; + config.clock = time_controller_.GetClock(); + config.rtp_sender = rtp_module_->RtpSender(); + config.field_trials = &field_trials_; + config.frame_transformer = transformer; + config.task_queue_factory = time_controller_.GetTaskQueueFactory(); + return std::make_unique<RTPSenderVideo>(config); + } + + protected: + GlobalSimulatedTimeController time_controller_; + FieldTrialBasedConfig field_trials_; + LoopbackTransportTest transport_; + RateLimiter retransmission_rate_limiter_; + std::unique_ptr<ModuleRtpRtcpImpl2> rtp_module_; +}; + +std::unique_ptr<EncodedImage> CreateDefaultEncodedImage() { + const uint8_t data[] = {1, 2, 3, 4}; + auto encoded_image = std::make_unique<EncodedImage>(); + encoded_image->SetEncodedData( + webrtc::EncodedImageBuffer::Create(data, sizeof(data))); + return encoded_image; +} + +TEST_F(RtpSenderVideoWithFrameTransformerTest, + CreateSenderRegistersFrameTransformer) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + EXPECT_CALL(*mock_frame_transformer, + RegisterTransformedFrameSinkCallback(_, kSsrc)); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); +} + +TEST_F(RtpSenderVideoWithFrameTransformerTest, + DestroySenderUnregistersFrameTransformer) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); + EXPECT_CALL(*mock_frame_transformer, + UnregisterTransformedFrameSinkCallback(kSsrc)); + rtp_sender_video = nullptr; +} + +TEST_F(RtpSenderVideoWithFrameTransformerTest, + SendEncodedImageTransformsFrame) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); + auto encoded_image = CreateDefaultEncodedImage(); + RTPVideoHeader video_header; + + EXPECT_CALL(*mock_frame_transformer, Transform); + rtp_sender_video->SendEncodedImage(kPayload, kType, kTimestamp, + *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs); +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST_F(RtpSenderVideoWithFrameTransformerTest, ValidPayloadTypes) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); + auto encoded_image = CreateDefaultEncodedImage(); + RTPVideoHeader video_header; + + EXPECT_TRUE(rtp_sender_video->SendEncodedImage( + 0, kType, kTimestamp, *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs)); + EXPECT_TRUE(rtp_sender_video->SendEncodedImage( + 127, kType, kTimestamp, *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs)); + EXPECT_DEATH(rtp_sender_video->SendEncodedImage( + -1, kType, kTimestamp, *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs), + ""); + EXPECT_DEATH(rtp_sender_video->SendEncodedImage( + 128, kType, kTimestamp, *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs), + ""); +} +#endif + +TEST_F(RtpSenderVideoWithFrameTransformerTest, OnTransformedFrameSendsVideo) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + rtc::scoped_refptr<TransformedFrameCallback> callback; + EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameSinkCallback) + .WillOnce(SaveArg<0>(&callback)); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); + ASSERT_TRUE(callback); + + auto encoded_image = CreateDefaultEncodedImage(); + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + ON_CALL(*mock_frame_transformer, Transform) + .WillByDefault( + [&callback](std::unique_ptr<TransformableFrameInterface> frame) { + callback->OnTransformedFrame(std::move(frame)); + }); + auto encoder_queue = time_controller_.GetTaskQueueFactory()->CreateTaskQueue( + "encoder_queue", TaskQueueFactory::Priority::NORMAL); + encoder_queue->PostTask([&] { + rtp_sender_video->SendEncodedImage(kPayload, kType, kTimestamp, + *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs); + }); + time_controller_.AdvanceTime(TimeDelta::Zero()); + EXPECT_EQ(transport_.packets_sent(), 1); + encoder_queue->PostTask([&] { + rtp_sender_video->SendEncodedImage(kPayload, kType, kTimestamp, + *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs); + }); + time_controller_.AdvanceTime(TimeDelta::Zero()); + EXPECT_EQ(transport_.packets_sent(), 2); +} + +TEST_F(RtpSenderVideoWithFrameTransformerTest, + TransformableFrameMetadataHasCorrectValue) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); + auto encoded_image = CreateDefaultEncodedImage(); + RTPVideoHeader video_header; + video_header.width = 1280u; + video_header.height = 720u; + RTPVideoHeader::GenericDescriptorInfo& generic = + video_header.generic.emplace(); + generic.frame_id = 10; + generic.temporal_index = 3; + generic.spatial_index = 2; + generic.decode_target_indications = {DecodeTargetIndication::kSwitch}; + generic.dependencies = {5}; + + // Check that the transformable frame passed to the frame transformer has the + // correct metadata. + EXPECT_CALL(*mock_frame_transformer, Transform) + .WillOnce( + [](std::unique_ptr<TransformableFrameInterface> transformable_frame) { + auto frame = + absl::WrapUnique(static_cast<TransformableVideoFrameInterface*>( + transformable_frame.release())); + ASSERT_TRUE(frame); + auto metadata = frame->GetMetadata(); + EXPECT_EQ(metadata.GetWidth(), 1280u); + EXPECT_EQ(metadata.GetHeight(), 720u); + EXPECT_EQ(metadata.GetFrameId(), 10); + EXPECT_EQ(metadata.GetTemporalIndex(), 3); + EXPECT_EQ(metadata.GetSpatialIndex(), 2); + EXPECT_THAT(metadata.GetFrameDependencies(), ElementsAre(5)); + EXPECT_THAT(metadata.GetDecodeTargetIndications(), + ElementsAre(DecodeTargetIndication::kSwitch)); + }); + rtp_sender_video->SendEncodedImage(kPayload, kType, kTimestamp, + *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs); +} + +TEST_F(RtpSenderVideoWithFrameTransformerTest, + OnTransformedFrameSendsVideoWhenCloned) { + auto mock_frame_transformer = + rtc::make_ref_counted<NiceMock<MockFrameTransformer>>(); + rtc::scoped_refptr<TransformedFrameCallback> callback; + EXPECT_CALL(*mock_frame_transformer, RegisterTransformedFrameSinkCallback) + .WillOnce(SaveArg<0>(&callback)); + std::unique_ptr<RTPSenderVideo> rtp_sender_video = + CreateSenderWithFrameTransformer(mock_frame_transformer); + ASSERT_TRUE(callback); + + auto encoded_image = CreateDefaultEncodedImage(); + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + ON_CALL(*mock_frame_transformer, Transform) + .WillByDefault( + [&callback](std::unique_ptr<TransformableFrameInterface> frame) { + auto clone = CloneVideoFrame( + static_cast<TransformableVideoFrameInterface*>(frame.get())); + EXPECT_TRUE(clone); + callback->OnTransformedFrame(std::move(clone)); + }); + auto encoder_queue = time_controller_.GetTaskQueueFactory()->CreateTaskQueue( + "encoder_queue", TaskQueueFactory::Priority::NORMAL); + encoder_queue->PostTask([&] { + rtp_sender_video->SendEncodedImage(kPayload, kType, kTimestamp, + *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs); + }); + time_controller_.AdvanceTime(TimeDelta::Zero()); + EXPECT_EQ(transport_.packets_sent(), 1); + encoder_queue->PostTask([&] { + rtp_sender_video->SendEncodedImage(kPayload, kType, kTimestamp, + *encoded_image, video_header, + kDefaultExpectedRetransmissionTimeMs); + }); + time_controller_.AdvanceTime(TimeDelta::Zero()); + EXPECT_EQ(transport_.packets_sent(), 2); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.cc new file mode 100644 index 0000000000..441429d442 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.cc @@ -0,0 +1,129 @@ +/* + * 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/rtp_sequence_number_map.h" + +#include <algorithm> +#include <iterator> +#include <limits> + +#include "absl/algorithm/container.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/sequence_number_util.h" + +namespace webrtc { + +RtpSequenceNumberMap::RtpSequenceNumberMap(size_t max_entries) + : max_entries_(max_entries) { + RTC_DCHECK_GT(max_entries_, 4); // See code paring down to `max_entries_`. + RTC_DCHECK_LE(max_entries_, 1 << 15); +} + +RtpSequenceNumberMap::~RtpSequenceNumberMap() = default; + +void RtpSequenceNumberMap::InsertPacket(uint16_t sequence_number, Info info) { + RTC_DCHECK(associations_.size() < 2 || + AheadOf(associations_.back().sequence_number, + associations_.front().sequence_number)); + + if (associations_.empty()) { + associations_.emplace_back(sequence_number, info); + return; + } + + if (AheadOrAt(sequence_number, associations_.front().sequence_number) && + AheadOrAt(associations_.back().sequence_number, sequence_number)) { + // The sequence number has wrapped around and is within the range + // currently held by `associations_` - we should invalidate all entries. + RTC_LOG(LS_WARNING) << "Sequence number wrapped-around unexpectedly."; + associations_.clear(); + associations_.emplace_back(sequence_number, info); + return; + } + + std::deque<Association>::iterator erase_to = associations_.begin(); + + RTC_DCHECK_LE(associations_.size(), max_entries_); + if (associations_.size() == max_entries_) { + // Pare down the container so that inserting some additional elements + // would not exceed the maximum size. + const size_t new_size = 3 * max_entries_ / 4; + erase_to = std::next(erase_to, max_entries_ - new_size); + } + + // It is guaranteed that `associations_` can be split into two partitions, + // either partition possibly empty, such that: + // * In the first partition, all elements are AheadOf the new element. + // This is the partition of the obsolete elements. + // * In the second partition, the new element is AheadOf all the elements. + // The elements of this partition may stay. + auto cmp = [](const Association& a, uint16_t sequence_number) { + return AheadOf(a.sequence_number, sequence_number); + }; + RTC_DCHECK(erase_to != associations_.end()); + erase_to = + std::lower_bound(erase_to, associations_.end(), sequence_number, cmp); + associations_.erase(associations_.begin(), erase_to); + + associations_.emplace_back(sequence_number, info); + + RTC_DCHECK(associations_.size() == 1 || + AheadOf(associations_.back().sequence_number, + associations_.front().sequence_number)); +} + +void RtpSequenceNumberMap::InsertFrame(uint16_t first_sequence_number, + size_t packet_count, + uint32_t timestamp) { + RTC_DCHECK_GT(packet_count, 0); + RTC_DCHECK_LE(packet_count, std::numeric_limits<size_t>::max()); + + for (size_t i = 0; i < packet_count; ++i) { + const bool is_first = (i == 0); + const bool is_last = (i == packet_count - 1); + InsertPacket(static_cast<uint16_t>(first_sequence_number + i), + Info(timestamp, is_first, is_last)); + } +} + +absl::optional<RtpSequenceNumberMap::Info> RtpSequenceNumberMap::Get( + uint16_t sequence_number) const { + // To make the binary search easier to understand, we use the fact that + // adding a constant offset to all elements, as well as to the searched + // element, does not change the relative ordering. This way, we can find + // an offset that would make all of the elements strictly ascending according + // to normal integer comparison. + // Finding such an offset is easy - the offset that would map the oldest + // element to 0 would serve this purpose. + + if (associations_.empty()) { + return absl::nullopt; + } + + const uint16_t offset = + static_cast<uint16_t>(0) - associations_.front().sequence_number; + + auto cmp = [offset](const Association& a, uint16_t sequence_number) { + return static_cast<uint16_t>(a.sequence_number + offset) < + static_cast<uint16_t>(sequence_number + offset); + }; + const auto elem = absl::c_lower_bound(associations_, sequence_number, cmp); + + return elem != associations_.end() && elem->sequence_number == sequence_number + ? absl::optional<Info>(elem->info) + : absl::nullopt; +} + +size_t RtpSequenceNumberMap::AssociationCountForTesting() const { + return associations_.size(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.h new file mode 100644 index 0000000000..8a036c25a4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.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 MODULES_RTP_RTCP_SOURCE_RTP_SEQUENCE_NUMBER_MAP_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_SEQUENCE_NUMBER_MAP_H_ + +#include <cstddef> +#include <cstdint> +#include <deque> + +#include "absl/types/optional.h" + +namespace webrtc { + +// Records the association of RTP sequence numbers to timestamps and to whether +// the packet was first and/or last in the frame. +// +// 1. Limits number of entries. Whenever `max_entries` is about to be exceeded, +// the size is reduced by approximately 25%. +// 2. RTP sequence numbers wrap around relatively infrequently. +// This class therefore only remembers at most the last 2^15 RTP packets, +// so that the newest packet's sequence number is still AheadOf the oldest +// packet's sequence number. +// 3. Media frames are sometimes split into several RTP packets. +// In such a case, Insert() is expected to be called once for each packet. +// The timestamp is not expected to change between those calls. +class RtpSequenceNumberMap final { + public: + struct Info final { + Info(uint32_t timestamp, bool is_first, bool is_last) + : timestamp(timestamp), is_first(is_first), is_last(is_last) {} + + friend bool operator==(const Info& lhs, const Info& rhs) { + return lhs.timestamp == rhs.timestamp && lhs.is_first == rhs.is_first && + lhs.is_last == rhs.is_last; + } + + uint32_t timestamp; + bool is_first; + bool is_last; + }; + + explicit RtpSequenceNumberMap(size_t max_entries); + RtpSequenceNumberMap(const RtpSequenceNumberMap& other) = delete; + RtpSequenceNumberMap& operator=(const RtpSequenceNumberMap& other) = delete; + ~RtpSequenceNumberMap(); + + void InsertPacket(uint16_t sequence_number, Info info); + void InsertFrame(uint16_t first_sequence_number, + size_t packet_count, + uint32_t timestamp); + + absl::optional<Info> Get(uint16_t sequence_number) const; + + size_t AssociationCountForTesting() const; + + private: + struct Association { + explicit Association(uint16_t sequence_number) + : Association(sequence_number, Info(0, false, false)) {} + + Association(uint16_t sequence_number, Info info) + : sequence_number(sequence_number), info(info) {} + + uint16_t sequence_number; + Info info; + }; + + const size_t max_entries_; + + // The non-transitivity of AheadOf() would be problematic with a map, + // so we use a deque instead. + std::deque<Association> associations_; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_SEQUENCE_NUMBER_MAP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map_unittest.cc new file mode 100644 index 0000000000..78c9e4a251 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map_unittest.cc @@ -0,0 +1,502 @@ +/* + * 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/rtp_sequence_number_map.h" + +#include <algorithm> +#include <iterator> +#include <limits> +#include <memory> +#include <tuple> +#include <vector> + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "rtc_base/checks.h" +#include "rtc_base/numerics/sequence_number_util.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +using Info = RtpSequenceNumberMap::Info; + +constexpr uint16_t kUint16Max = std::numeric_limits<uint16_t>::max(); +constexpr size_t kMaxPossibleMaxEntries = 1 << 15; + +// Just a named pair. +struct Association final { + Association(uint16_t sequence_number, Info info) + : sequence_number(sequence_number), info(info) {} + + uint16_t sequence_number; + Info info; +}; + +class RtpSequenceNumberMapTest : public ::testing::Test { + protected: + RtpSequenceNumberMapTest() : random_(1983) {} + ~RtpSequenceNumberMapTest() override = default; + + Association CreateAssociation(uint16_t sequence_number, uint32_t timestamp) { + return Association(sequence_number, + {timestamp, random_.Rand<bool>(), random_.Rand<bool>()}); + } + + void VerifyAssociations(const RtpSequenceNumberMap& uut, + const std::vector<Association>& associations) { + return VerifyAssociations(uut, associations.begin(), associations.end()); + } + + void VerifyAssociations( + const RtpSequenceNumberMap& uut, + std::vector<Association>::const_iterator associations_begin, + std::vector<Association>::const_iterator associations_end) { + RTC_DCHECK(associations_begin < associations_end); + ASSERT_EQ(static_cast<size_t>(associations_end - associations_begin), + uut.AssociationCountForTesting()); + for (auto association = associations_begin; association != associations_end; + ++association) { + EXPECT_EQ(uut.Get(association->sequence_number), association->info); + } + } + + // Allows several variations of the same test; definition next to the tests. + void GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( + bool with_wrap_around, + bool last_element_kept); + + // Allows several variations of the same test; definition next to the tests. + void RepeatedSequenceNumberInvalidatesAll(size_t index_of_repeated); + + // Allows several variations of the same test; definition next to the tests. + void MaxEntriesReachedAtSameTimeAsObsoletionOfItem(size_t max_entries, + size_t obsoleted_count); + + Random random_; +}; + +class RtpSequenceNumberMapTestWithParams + : public RtpSequenceNumberMapTest, + public ::testing::WithParamInterface<std::tuple<size_t, uint16_t>> { + protected: + RtpSequenceNumberMapTestWithParams() = default; + ~RtpSequenceNumberMapTestWithParams() override = default; + + std::vector<Association> ProduceRandomAssociationSequence( + size_t association_count, + uint16_t first_sequence_number, + bool allow_obsoletion) { + std::vector<Association> associations; + associations.reserve(association_count); + + if (association_count == 0) { + return associations; + } + + associations.emplace_back( + first_sequence_number, + Info(0, random_.Rand<bool>(), random_.Rand<bool>())); + + for (size_t i = 1; i < association_count; ++i) { + const uint16_t sequence_number = + associations[i - 1].sequence_number + random_.Rand(1, 100); + RTC_DCHECK(allow_obsoletion || + AheadOf(sequence_number, associations[0].sequence_number)); + + const uint32_t timestamp = + associations[i - 1].info.timestamp + random_.Rand(1, 10000); + + associations.emplace_back( + sequence_number, + Info(timestamp, random_.Rand<bool>(), random_.Rand<bool>())); + } + + return associations; + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + RtpSequenceNumberMapTestWithParams, + ::testing::Combine( + // Association count. + ::testing::Values(1, 2, 100), + // First sequence number. + ::testing::Values(0, + 100, + kUint16Max - 100, + kUint16Max - 1, + kUint16Max))); + +TEST_F(RtpSequenceNumberMapTest, GetBeforeAssociationsRecordedReturnsNullOpt) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + constexpr uint16_t kArbitrarySequenceNumber = 321; + EXPECT_FALSE(uut.Get(kArbitrarySequenceNumber)); +} + +// Version #1 - any old unknown sequence number. +TEST_F(RtpSequenceNumberMapTest, GetUnknownSequenceNumberReturnsNullOpt1) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + constexpr uint16_t kKnownSequenceNumber = 10; + constexpr uint32_t kArbitrary = 987; + uut.InsertPacket(kKnownSequenceNumber, {kArbitrary, false, false}); + + constexpr uint16_t kUnknownSequenceNumber = kKnownSequenceNumber + 1; + EXPECT_FALSE(uut.Get(kUnknownSequenceNumber)); +} + +// Version #2 - intentionally pick a value in the range of currently held +// values, so as to trigger lower_bound / upper_bound. +TEST_F(RtpSequenceNumberMapTest, GetUnknownSequenceNumberReturnsNullOpt2) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + const std::vector<Association> setup = {CreateAssociation(1000, 500), // + CreateAssociation(1020, 501)}; + for (const Association& association : setup) { + uut.InsertPacket(association.sequence_number, association.info); + } + + EXPECT_FALSE(uut.Get(1001)); +} + +TEST_P(RtpSequenceNumberMapTestWithParams, + GetKnownSequenceNumberReturnsCorrectValue) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + const size_t association_count = std::get<0>(GetParam()); + const uint16_t first_sequence_number = std::get<1>(GetParam()); + + const std::vector<Association> associations = + ProduceRandomAssociationSequence(association_count, first_sequence_number, + /*allow_obsoletion=*/false); + + for (const Association& association : associations) { + uut.InsertPacket(association.sequence_number, association.info); + } + + VerifyAssociations(uut, associations); +} + +TEST_F(RtpSequenceNumberMapTest, InsertFrameOnSinglePacketFrame) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + constexpr uint16_t kSequenceNumber = 888; + constexpr uint32_t kTimestamp = 98765; + uut.InsertFrame(kSequenceNumber, 1, kTimestamp); + + EXPECT_EQ(uut.Get(kSequenceNumber), Info(kTimestamp, true, true)); +} + +TEST_F(RtpSequenceNumberMapTest, InsertFrameOnMultiPacketFrameNoWrapAround) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + constexpr uint16_t kFirstSequenceNumber = 0; + constexpr uint32_t kTimestamp = 98765; + uut.InsertFrame(kFirstSequenceNumber, 3, kTimestamp); + + EXPECT_EQ(uut.Get(kFirstSequenceNumber + 0), Info(kTimestamp, true, false)); + EXPECT_EQ(uut.Get(kFirstSequenceNumber + 1), Info(kTimestamp, false, false)); + EXPECT_EQ(uut.Get(kFirstSequenceNumber + 2), Info(kTimestamp, false, true)); +} + +TEST_F(RtpSequenceNumberMapTest, InsertFrameOnMultiPacketFrameWithWrapAround) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + constexpr uint16_t kFirstSequenceNumber = kUint16Max; + constexpr uint32_t kTimestamp = 98765; + uut.InsertFrame(kFirstSequenceNumber, 3, kTimestamp); + +// Suppress "truncation of constant value" warning; wrap-around is intended. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4309) +#endif + EXPECT_EQ(uut.Get(static_cast<uint16_t>(kFirstSequenceNumber + 0u)), + Info(kTimestamp, true, false)); + EXPECT_EQ(uut.Get(static_cast<uint16_t>(kFirstSequenceNumber + 1u)), + Info(kTimestamp, false, false)); + EXPECT_EQ(uut.Get(static_cast<uint16_t>(kFirstSequenceNumber + 2u)), + Info(kTimestamp, false, true)); +#ifdef _MSC_VER +#pragma warning(pop) +#endif +} + +TEST_F(RtpSequenceNumberMapTest, + GetObsoleteSequenceNumberReturnsNullOptSingleValueObsoleted) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + const std::vector<Association> associations = { + CreateAssociation(0, 10), // + CreateAssociation(0x8000, 20), // + CreateAssociation(0x8001u, 30)}; + + uut.InsertPacket(associations[0].sequence_number, associations[0].info); + + // First association not yet obsolete, and therefore remembered. + RTC_DCHECK(AheadOf(associations[1].sequence_number, + associations[0].sequence_number)); + uut.InsertPacket(associations[1].sequence_number, associations[1].info); + VerifyAssociations(uut, {associations[0], associations[1]}); + + // Test focus - new entry obsoletes first entry. + RTC_DCHECK(!AheadOf(associations[2].sequence_number, + associations[0].sequence_number)); + uut.InsertPacket(associations[2].sequence_number, associations[2].info); + VerifyAssociations(uut, {associations[1], associations[2]}); +} + +void RtpSequenceNumberMapTest:: + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( + bool with_wrap_around, + bool last_element_kept) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + std::vector<Association> associations; + if (with_wrap_around) { + associations = {CreateAssociation(kUint16Max - 1, 10), // + CreateAssociation(kUint16Max, 20), // + CreateAssociation(0, 30), // + CreateAssociation(1, 40), // + CreateAssociation(2, 50)}; + } else { + associations = {CreateAssociation(1, 10), // + CreateAssociation(2, 20), // + CreateAssociation(3, 30), // + CreateAssociation(4, 40), // + CreateAssociation(5, 50)}; + } + + for (auto association : associations) { + uut.InsertPacket(association.sequence_number, association.info); + } + VerifyAssociations(uut, associations); + + // Define a new association that will obsolete either all previous entries, + // or all previous entries except for the last one, depending on the + // parameter instantiation of this test. + RTC_DCHECK_EQ( + static_cast<uint16_t>( + associations[associations.size() - 1].sequence_number), + static_cast<uint16_t>( + associations[associations.size() - 2].sequence_number + 1u)); + uint16_t new_sequence_number; + if (last_element_kept) { + new_sequence_number = + associations[associations.size() - 1].sequence_number + 0x8000; + RTC_DCHECK(AheadOf(new_sequence_number, + associations[associations.size() - 1].sequence_number)); + } else { + new_sequence_number = + associations[associations.size() - 1].sequence_number + 0x8001; + RTC_DCHECK(!AheadOf(new_sequence_number, + associations[associations.size() - 1].sequence_number)); + } + RTC_DCHECK(!AheadOf(new_sequence_number, + associations[associations.size() - 2].sequence_number)); + + // Record the new association. + const Association new_association = + CreateAssociation(new_sequence_number, 60); + uut.InsertPacket(new_association.sequence_number, new_association.info); + + // Make sure all obsoleted elements were removed. + const size_t obsoleted_count = + associations.size() - (last_element_kept ? 1 : 0); + for (size_t i = 0; i < obsoleted_count; ++i) { + EXPECT_FALSE(uut.Get(associations[i].sequence_number)); + } + + // Make sure the expected elements were not removed, and return the + // expected value. + if (last_element_kept) { + EXPECT_EQ(uut.Get(associations.back().sequence_number), + associations.back().info); + } + EXPECT_EQ(uut.Get(new_association.sequence_number), new_association.info); +} + +TEST_F(RtpSequenceNumberMapTest, + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted0) { + const bool with_wrap_around = false; + const bool last_element_kept = false; + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( + with_wrap_around, last_element_kept); +} + +TEST_F(RtpSequenceNumberMapTest, + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted1) { + const bool with_wrap_around = true; + const bool last_element_kept = false; + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( + with_wrap_around, last_element_kept); +} + +TEST_F(RtpSequenceNumberMapTest, + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted2) { + const bool with_wrap_around = false; + const bool last_element_kept = true; + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( + with_wrap_around, last_element_kept); +} + +TEST_F(RtpSequenceNumberMapTest, + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted3) { + const bool with_wrap_around = true; + const bool last_element_kept = true; + GetObsoleteSequenceNumberReturnsNullOptMultipleEntriesObsoleted( + with_wrap_around, last_element_kept); +} + +void RtpSequenceNumberMapTest::RepeatedSequenceNumberInvalidatesAll( + size_t index_of_repeated) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + const std::vector<Association> setup = {CreateAssociation(100, 500), // + CreateAssociation(101, 501), // + CreateAssociation(102, 502)}; + RTC_DCHECK_LT(index_of_repeated, setup.size()); + for (const Association& association : setup) { + uut.InsertPacket(association.sequence_number, association.info); + } + + const Association new_association = + CreateAssociation(setup[index_of_repeated].sequence_number, 503); + uut.InsertPacket(new_association.sequence_number, new_association.info); + + // All entries from setup invalidated. + // New entry valid and mapped to new value. + for (size_t i = 0; i < setup.size(); ++i) { + if (i == index_of_repeated) { + EXPECT_EQ(uut.Get(new_association.sequence_number), new_association.info); + } else { + EXPECT_FALSE(uut.Get(setup[i].sequence_number)); + } + } +} + +TEST_F(RtpSequenceNumberMapTest, + RepeatedSequenceNumberInvalidatesAllRepeatFirst) { + RepeatedSequenceNumberInvalidatesAll(0); +} + +TEST_F(RtpSequenceNumberMapTest, + RepeatedSequenceNumberInvalidatesAllRepeatMiddle) { + RepeatedSequenceNumberInvalidatesAll(1); +} + +TEST_F(RtpSequenceNumberMapTest, + RepeatedSequenceNumberInvalidatesAllRepeatLast) { + RepeatedSequenceNumberInvalidatesAll(2); +} + +TEST_F(RtpSequenceNumberMapTest, + SequenceNumberInsideMemorizedRangeInvalidatesAll) { + RtpSequenceNumberMap uut(kMaxPossibleMaxEntries); + + const std::vector<Association> setup = {CreateAssociation(1000, 500), // + CreateAssociation(1020, 501), // + CreateAssociation(1030, 502)}; + for (const Association& association : setup) { + uut.InsertPacket(association.sequence_number, association.info); + } + + const Association new_association = CreateAssociation(1010, 503); + uut.InsertPacket(new_association.sequence_number, new_association.info); + + // All entries from setup invalidated. + // New entry valid and mapped to new value. + for (size_t i = 0; i < setup.size(); ++i) { + EXPECT_FALSE(uut.Get(setup[i].sequence_number)); + } + EXPECT_EQ(uut.Get(new_association.sequence_number), new_association.info); +} + +TEST_F(RtpSequenceNumberMapTest, MaxEntriesObserved) { + constexpr size_t kMaxEntries = 100; + RtpSequenceNumberMap uut(kMaxEntries); + + std::vector<Association> associations; + associations.reserve(kMaxEntries); + uint32_t timestamp = 789; + for (size_t i = 0; i < kMaxEntries; ++i) { + associations.push_back(CreateAssociation(i, ++timestamp)); + uut.InsertPacket(associations[i].sequence_number, associations[i].info); + } + VerifyAssociations(uut, associations); // Sanity. + + const Association new_association = + CreateAssociation(kMaxEntries, ++timestamp); + uut.InsertPacket(new_association.sequence_number, new_association.info); + associations.push_back(new_association); + + // The +1 is for `new_association`. + const size_t kExpectedAssociationCount = 3 * kMaxEntries / 4 + 1; + const auto expected_begin = + std::prev(associations.end(), kExpectedAssociationCount); + VerifyAssociations(uut, expected_begin, associations.end()); +} + +void RtpSequenceNumberMapTest::MaxEntriesReachedAtSameTimeAsObsoletionOfItem( + size_t max_entries, + size_t obsoleted_count) { + RtpSequenceNumberMap uut(max_entries); + + std::vector<Association> associations; + associations.reserve(max_entries); + uint32_t timestamp = 789; + for (size_t i = 0; i < max_entries; ++i) { + associations.push_back(CreateAssociation(i, ++timestamp)); + uut.InsertPacket(associations[i].sequence_number, associations[i].info); + } + VerifyAssociations(uut, associations); // Sanity. + + const uint16_t new_association_sequence_number = + static_cast<uint16_t>(obsoleted_count) + (1 << 15); + const Association new_association = + CreateAssociation(new_association_sequence_number, ++timestamp); + uut.InsertPacket(new_association.sequence_number, new_association.info); + associations.push_back(new_association); + + // The +1 is for `new_association`. + const size_t kExpectedAssociationCount = + std::min(3 * max_entries / 4, max_entries - obsoleted_count) + 1; + const auto expected_begin = + std::prev(associations.end(), kExpectedAssociationCount); + VerifyAssociations(uut, expected_begin, associations.end()); +} + +// Version #1 - #(obsoleted entries) < #(entries after paring down below max). +TEST_F(RtpSequenceNumberMapTest, + MaxEntriesReachedAtSameTimeAsObsoletionOfItem1) { + constexpr size_t kMaxEntries = 100; + constexpr size_t kObsoletionTarget = (kMaxEntries / 4) - 1; + MaxEntriesReachedAtSameTimeAsObsoletionOfItem(kMaxEntries, kObsoletionTarget); +} + +// Version #2 - #(obsoleted entries) == #(entries after paring down below max). +TEST_F(RtpSequenceNumberMapTest, + MaxEntriesReachedAtSameTimeAsObsoletionOfItem2) { + constexpr size_t kMaxEntries = 100; + constexpr size_t kObsoletionTarget = kMaxEntries / 4; + MaxEntriesReachedAtSameTimeAsObsoletionOfItem(kMaxEntries, kObsoletionTarget); +} + +// Version #3 - #(obsoleted entries) > #(entries after paring down below max). +TEST_F(RtpSequenceNumberMapTest, + MaxEntriesReachedAtSameTimeAsObsoletionOfItem3) { + constexpr size_t kMaxEntries = 100; + constexpr size_t kObsoletionTarget = (kMaxEntries / 4) + 1; + MaxEntriesReachedAtSameTimeAsObsoletionOfItem(kMaxEntries, kObsoletionTarget); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.cc new file mode 100644 index 0000000000..cf1e54254a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.cc @@ -0,0 +1,63 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_util.h" + +#include <cstddef> +#include <cstdint> + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace { + +constexpr uint8_t kRtpVersion = 2; +constexpr size_t kMinRtpPacketLen = 12; +constexpr size_t kMinRtcpPacketLen = 4; + +bool HasCorrectRtpVersion(rtc::ArrayView<const uint8_t> packet) { + return packet[0] >> 6 == kRtpVersion; +} + +// For additional details, see http://tools.ietf.org/html/rfc5761#section-4 +bool PayloadTypeIsReservedForRtcp(uint8_t payload_type) { + return 64 <= payload_type && payload_type < 96; +} + +} // namespace + +bool IsRtpPacket(rtc::ArrayView<const uint8_t> packet) { + return packet.size() >= kMinRtpPacketLen && HasCorrectRtpVersion(packet) && + !PayloadTypeIsReservedForRtcp(packet[1] & 0x7F); +} + +bool IsRtcpPacket(rtc::ArrayView<const uint8_t> packet) { + return packet.size() >= kMinRtcpPacketLen && HasCorrectRtpVersion(packet) && + PayloadTypeIsReservedForRtcp(packet[1] & 0x7F); +} + +int ParseRtpPayloadType(rtc::ArrayView<const uint8_t> rtp_packet) { + RTC_DCHECK(IsRtpPacket(rtp_packet)); + return rtp_packet[1] & 0x7F; +} + +uint16_t ParseRtpSequenceNumber(rtc::ArrayView<const uint8_t> rtp_packet) { + RTC_DCHECK(IsRtpPacket(rtp_packet)); + return ByteReader<uint16_t>::ReadBigEndian(rtp_packet.data() + 2); +} + +uint32_t ParseRtpSsrc(rtc::ArrayView<const uint8_t> rtp_packet) { + RTC_DCHECK(IsRtpPacket(rtp_packet)); + return ByteReader<uint32_t>::ReadBigEndian(rtp_packet.data() + 8); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.h new file mode 100644 index 0000000000..835cfcd6c8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.h @@ -0,0 +1,31 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_UTIL_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_UTIL_H_ + +#include <cstdint> + +#include "api/array_view.h" + +namespace webrtc { + +bool IsRtcpPacket(rtc::ArrayView<const uint8_t> packet); +bool IsRtpPacket(rtc::ArrayView<const uint8_t> packet); + +// Returns base rtp header fields of the rtp packet. +// Behaviour is undefined when `!IsRtpPacket(rtp_packet)`. +int ParseRtpPayloadType(rtc::ArrayView<const uint8_t> rtp_packet); +uint16_t ParseRtpSequenceNumber(rtc::ArrayView<const uint8_t> rtp_packet); +uint32_t ParseRtpSsrc(rtc::ArrayView<const uint8_t> rtp_packet); + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_UTIL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util_unittest.cc new file mode 100644 index 0000000000..3e23416ff4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util_unittest.cc @@ -0,0 +1,86 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_util.h" + +#include "test/gmock.h" + +namespace webrtc { +namespace { + +TEST(RtpUtilTest, IsRtpPacket) { + constexpr uint8_t kMinimalisticRtpPacket[] = {0x80, 97, 0, 0, // + 0, 0, 0, 0, // + 0, 0, 0, 0}; + EXPECT_TRUE(IsRtpPacket(kMinimalisticRtpPacket)); + + constexpr uint8_t kWrongRtpVersion[] = {0xc0, 97, 0, 0, // + 0, 0, 0, 0, // + 0, 0, 0, 0}; + EXPECT_FALSE(IsRtpPacket(kWrongRtpVersion)); + + constexpr uint8_t kPacketWithPayloadForRtcp[] = {0x80, 200, 0, 0, // + 0, 0, 0, 0, // + 0, 0, 0, 0}; + EXPECT_FALSE(IsRtpPacket(kPacketWithPayloadForRtcp)); + + constexpr uint8_t kTooSmallRtpPacket[] = {0x80, 97, 0, 0, // + 0, 0, 0, 0, // + 0, 0, 0}; + EXPECT_FALSE(IsRtpPacket(kTooSmallRtpPacket)); + + EXPECT_FALSE(IsRtpPacket({})); +} + +TEST(RtpUtilTest, IsRtcpPacket) { + constexpr uint8_t kMinimalisticRtcpPacket[] = {0x80, 202, 0, 0}; + EXPECT_TRUE(IsRtcpPacket(kMinimalisticRtcpPacket)); + + constexpr uint8_t kWrongRtpVersion[] = {0xc0, 202, 0, 0}; + EXPECT_FALSE(IsRtcpPacket(kWrongRtpVersion)); + + constexpr uint8_t kPacketWithPayloadForRtp[] = {0x80, 225, 0, 0}; + EXPECT_FALSE(IsRtcpPacket(kPacketWithPayloadForRtp)); + + constexpr uint8_t kTooSmallRtcpPacket[] = {0x80, 202, 0}; + EXPECT_FALSE(IsRtcpPacket(kTooSmallRtcpPacket)); + + EXPECT_FALSE(IsRtcpPacket({})); +} + +TEST(RtpUtilTest, ParseRtpPayloadType) { + constexpr uint8_t kMinimalisticRtpPacket[] = {0x80, 97, 0, 0, // + 0, 0, 0, 0, // + 0x12, 0x34, 0x56, 0x78}; + EXPECT_EQ(ParseRtpPayloadType(kMinimalisticRtpPacket), 97); + + constexpr uint8_t kMinimalisticRtpPacketWithMarker[] = { + 0x80, 0x80 | 97, 0, 0, // + 0, 0, 0, 0, // + 0x12, 0x34, 0x56, 0x78}; + EXPECT_EQ(ParseRtpPayloadType(kMinimalisticRtpPacketWithMarker), 97); +} + +TEST(RtpUtilTest, ParseRtpSequenceNumber) { + constexpr uint8_t kMinimalisticRtpPacket[] = {0x80, 97, 0x12, 0x34, // + 0, 0, 0, 0, // + 0, 0, 0, 0}; + EXPECT_EQ(ParseRtpSequenceNumber(kMinimalisticRtpPacket), 0x1234); +} + +TEST(RtpUtilTest, ParseRtpSsrc) { + constexpr uint8_t kMinimalisticRtpPacket[] = {0x80, 97, 0, 0, // + 0, 0, 0, 0, // + 0x12, 0x34, 0x56, 0x78}; + EXPECT_EQ(ParseRtpSsrc(kMinimalisticRtpPacket), 0x12345678u); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.cc new file mode 100644 index 0000000000..b07a7beec4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.cc @@ -0,0 +1,110 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_video_header.h" + +namespace webrtc { + +RTPVideoHeader::GenericDescriptorInfo::GenericDescriptorInfo() = default; +RTPVideoHeader::GenericDescriptorInfo::GenericDescriptorInfo( + const GenericDescriptorInfo& other) = default; +RTPVideoHeader::GenericDescriptorInfo::~GenericDescriptorInfo() = default; + +// static +RTPVideoHeader RTPVideoHeader::FromMetadata( + const VideoFrameMetadata& metadata) { + RTPVideoHeader rtp_video_header; + rtp_video_header.SetFromMetadata(metadata); + return rtp_video_header; +} + +RTPVideoHeader::RTPVideoHeader() : video_timing() {} +RTPVideoHeader::RTPVideoHeader(const RTPVideoHeader& other) = default; +RTPVideoHeader::~RTPVideoHeader() = default; + +VideoFrameMetadata RTPVideoHeader::GetAsMetadata() const { + VideoFrameMetadata metadata; + metadata.SetFrameType(frame_type); + metadata.SetWidth(width); + metadata.SetHeight(height); + metadata.SetRotation(rotation); + metadata.SetContentType(content_type); + if (generic) { + metadata.SetFrameId(generic->frame_id); + metadata.SetSpatialIndex(generic->spatial_index); + metadata.SetTemporalIndex(generic->temporal_index); + metadata.SetFrameDependencies(generic->dependencies); + metadata.SetDecodeTargetIndications(generic->decode_target_indications); + } + metadata.SetIsLastFrameInPicture(is_last_frame_in_picture); + metadata.SetSimulcastIdx(simulcastIdx); + metadata.SetCodec(codec); + switch (codec) { + case VideoCodecType::kVideoCodecVP8: + metadata.SetRTPVideoHeaderCodecSpecifics( + absl::get<RTPVideoHeaderVP8>(video_type_header)); + break; + case VideoCodecType::kVideoCodecVP9: + metadata.SetRTPVideoHeaderCodecSpecifics( + absl::get<RTPVideoHeaderVP9>(video_type_header)); + break; + case VideoCodecType::kVideoCodecH264: + metadata.SetRTPVideoHeaderCodecSpecifics( + absl::get<RTPVideoHeaderH264>(video_type_header)); + break; + default: + // Codec-specifics are not supported for this codec. + break; + } + return metadata; +} + +void RTPVideoHeader::SetFromMetadata(const VideoFrameMetadata& metadata) { + frame_type = metadata.GetFrameType(); + width = metadata.GetWidth(); + height = metadata.GetHeight(); + rotation = metadata.GetRotation(); + content_type = metadata.GetContentType(); + if (!metadata.GetFrameId().has_value()) { + generic = absl::nullopt; + } else { + generic.emplace(); + generic->frame_id = metadata.GetFrameId().value(); + generic->spatial_index = metadata.GetSpatialIndex(); + generic->temporal_index = metadata.GetTemporalIndex(); + generic->dependencies.assign(metadata.GetFrameDependencies().begin(), + metadata.GetFrameDependencies().end()); + generic->decode_target_indications.assign( + metadata.GetDecodeTargetIndications().begin(), + metadata.GetDecodeTargetIndications().end()); + } + is_last_frame_in_picture = metadata.GetIsLastFrameInPicture(); + simulcastIdx = metadata.GetSimulcastIdx(); + codec = metadata.GetCodec(); + switch (codec) { + case VideoCodecType::kVideoCodecVP8: + video_type_header = absl::get<RTPVideoHeaderVP8>( + metadata.GetRTPVideoHeaderCodecSpecifics()); + break; + case VideoCodecType::kVideoCodecVP9: + video_type_header = absl::get<RTPVideoHeaderVP9>( + metadata.GetRTPVideoHeaderCodecSpecifics()); + break; + case VideoCodecType::kVideoCodecH264: + video_type_header = absl::get<RTPVideoHeaderH264>( + metadata.GetRTPVideoHeaderCodecSpecifics()); + break; + default: + // Codec-specifics are not supported for this codec. + break; + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.h new file mode 100644 index 0000000000..d185c20c83 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.h @@ -0,0 +1,101 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_RTP_VIDEO_HEADER_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_VIDEO_HEADER_H_ + +#include <bitset> +#include <cstdint> + +#include "absl/container/inlined_vector.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "api/rtp_headers.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/video/color_space.h" +#include "api/video/video_codec_type.h" +#include "api/video/video_content_type.h" +#include "api/video/video_frame_metadata.h" +#include "api/video/video_frame_type.h" +#include "api/video/video_rotation.h" +#include "api/video/video_timing.h" +#include "modules/video_coding/codecs/h264/include/h264_globals.h" +#include "modules/video_coding/codecs/vp8/include/vp8_globals.h" +#include "modules/video_coding/codecs/vp9/include/vp9_globals.h" + +namespace webrtc { +// Details passed in the rtp payload for legacy generic rtp packetizer. +// TODO(bugs.webrtc.org/9772): Deprecate in favor of passing generic video +// details in an rtp header extension. +struct RTPVideoHeaderLegacyGeneric { + uint16_t picture_id; +}; + +using RTPVideoTypeHeader = absl::variant<absl::monostate, + RTPVideoHeaderVP8, + RTPVideoHeaderVP9, + RTPVideoHeaderH264, + RTPVideoHeaderLegacyGeneric>; + +struct RTPVideoHeader { + struct GenericDescriptorInfo { + GenericDescriptorInfo(); + GenericDescriptorInfo(const GenericDescriptorInfo& other); + ~GenericDescriptorInfo(); + + int64_t frame_id = 0; + int spatial_index = 0; + int temporal_index = 0; + absl::InlinedVector<DecodeTargetIndication, 10> decode_target_indications; + absl::InlinedVector<int64_t, 5> dependencies; + absl::InlinedVector<int, 4> chain_diffs; + std::bitset<32> active_decode_targets = ~uint32_t{0}; + }; + + static RTPVideoHeader FromMetadata(const VideoFrameMetadata& metadata); + + RTPVideoHeader(); + RTPVideoHeader(const RTPVideoHeader& other); + + ~RTPVideoHeader(); + + // The subset of RTPVideoHeader that is exposed in the Insertable Streams API. + VideoFrameMetadata GetAsMetadata() const; + void SetFromMetadata(const VideoFrameMetadata& metadata); + + absl::optional<GenericDescriptorInfo> generic; + + VideoFrameType frame_type = VideoFrameType::kEmptyFrame; + uint16_t width = 0; + uint16_t height = 0; + VideoRotation rotation = VideoRotation::kVideoRotation_0; + VideoContentType content_type = VideoContentType::UNSPECIFIED; + bool is_first_packet_in_frame = false; + bool is_last_packet_in_frame = false; + bool is_last_frame_in_picture = true; + uint8_t simulcastIdx = 0; + VideoCodecType codec = VideoCodecType::kVideoCodecGeneric; + + VideoPlayoutDelay playout_delay; + VideoSendTiming video_timing; + absl::optional<ColorSpace> color_space; + // This field is meant for media quality testing purpose only. When enabled it + // carries the webrtc::VideoFrame id field from the sender to the receiver. + absl::optional<uint16_t> video_frame_tracking_id; + RTPVideoTypeHeader video_type_header; + + // When provided, is sent as is as an RTP header extension according to + // http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time. + // Otherwise, it is derived from other relevant information. + absl::optional<AbsoluteCaptureTime> absolute_capture_time; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_RTP_VIDEO_HEADER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header_unittest.cc new file mode 100644 index 0000000000..335fa1a8a0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header_unittest.cc @@ -0,0 +1,351 @@ +/* + * 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/rtp_rtcp/source/rtp_video_header.h" + +#include "api/video/video_frame_metadata.h" +#include "api/video/video_frame_type.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +TEST(RTPVideoHeaderTest, FrameType_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.frame_type = VideoFrameType::kVideoFrameKey; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetFrameType(), VideoFrameType::kVideoFrameKey); +} + +TEST(RTPVideoHeaderTest, FrameType_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetFrameType(VideoFrameType::kVideoFrameKey); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.frame_type, VideoFrameType::kVideoFrameKey); +} + +TEST(RTPVideoHeaderTest, Width_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.width = 1280u; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetWidth(), 1280u); +} + +TEST(RTPVideoHeaderTest, Width_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetWidth(1280u); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.width, 1280u); +} + +TEST(RTPVideoHeaderTest, Height_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.height = 720u; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetHeight(), 720u); +} + +TEST(RTPVideoHeaderTest, Height_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetHeight(720u); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.height, 720u); +} + +TEST(RTPVideoHeaderTest, Rotation_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.rotation = VideoRotation::kVideoRotation_90; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetRotation(), VideoRotation::kVideoRotation_90); +} + +TEST(RTPVideoHeaderTest, Rotation_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetRotation(VideoRotation::kVideoRotation_90); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.rotation, VideoRotation::kVideoRotation_90); +} + +TEST(RTPVideoHeaderTest, ContentType_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.content_type = VideoContentType::SCREENSHARE; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetContentType(), VideoContentType::SCREENSHARE); +} + +TEST(RTPVideoHeaderTest, ContentType_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetContentType(VideoContentType::SCREENSHARE); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.content_type, VideoContentType::SCREENSHARE); +} + +TEST(RTPVideoHeaderTest, FrameId_GetAsMetadata) { + RTPVideoHeader video_header; + RTPVideoHeader::GenericDescriptorInfo& generic = + video_header.generic.emplace(); + generic.frame_id = 10; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetFrameId().value(), 10); +} + +TEST(RTPVideoHeaderTest, FrameId_GetAsMetadataWhenGenericIsMissing) { + RTPVideoHeader video_header; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + ASSERT_FALSE(video_header.generic); + EXPECT_FALSE(metadata.GetFrameId().has_value()); +} + +TEST(RTPVideoHeaderTest, FrameId_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetFrameId(10); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_TRUE(video_header.generic.has_value()); + EXPECT_EQ(video_header.generic->frame_id, 10); +} + +TEST(RTPVideoHeaderTest, FrameId_FromMetadataWhenFrameIdIsMissing) { + VideoFrameMetadata metadata; + metadata.SetFrameId(absl::nullopt); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_FALSE(video_header.generic.has_value()); +} + +TEST(RTPVideoHeaderTest, SpatialIndex_GetAsMetadata) { + RTPVideoHeader video_header; + RTPVideoHeader::GenericDescriptorInfo& generic = + video_header.generic.emplace(); + generic.spatial_index = 2; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetSpatialIndex(), 2); +} + +TEST(RTPVideoHeaderTest, SpatialIndex_GetAsMetadataWhenGenericIsMissing) { + RTPVideoHeader video_header; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + ASSERT_FALSE(video_header.generic); + EXPECT_EQ(metadata.GetSpatialIndex(), 0); +} + +TEST(RTPVideoHeaderTest, SpatialIndex_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetFrameId(123); // Must have a frame ID for related properties. + metadata.SetSpatialIndex(2); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_TRUE(video_header.generic.has_value()); + EXPECT_EQ(video_header.generic->spatial_index, 2); +} + +TEST(RTPVideoHeaderTest, TemporalIndex_GetAsMetadata) { + RTPVideoHeader video_header; + RTPVideoHeader::GenericDescriptorInfo& generic = + video_header.generic.emplace(); + generic.temporal_index = 3; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetTemporalIndex(), 3); +} + +TEST(RTPVideoHeaderTest, TemporalIndex_GetAsMetadataWhenGenericIsMissing) { + RTPVideoHeader video_header; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + ASSERT_FALSE(video_header.generic); + EXPECT_EQ(metadata.GetTemporalIndex(), 0); +} + +TEST(RTPVideoHeaderTest, TemporalIndex_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetFrameId(123); // Must have a frame ID for related properties. + metadata.SetTemporalIndex(3); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_TRUE(video_header.generic.has_value()); + EXPECT_EQ(video_header.generic->temporal_index, 3); +} + +TEST(RTPVideoHeaderTest, FrameDependencies_GetAsMetadata) { + RTPVideoHeader video_header; + RTPVideoHeader::GenericDescriptorInfo& generic = + video_header.generic.emplace(); + generic.dependencies = {5, 6, 7}; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_THAT(metadata.GetFrameDependencies(), ElementsAre(5, 6, 7)); +} + +TEST(RTPVideoHeaderTest, FrameDependency_GetAsMetadataWhenGenericIsMissing) { + RTPVideoHeader video_header; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + ASSERT_FALSE(video_header.generic); + EXPECT_THAT(metadata.GetFrameDependencies(), IsEmpty()); +} + +TEST(RTPVideoHeaderTest, FrameDependencies_FromMetadata) { + VideoFrameMetadata metadata; + absl::InlinedVector<int64_t, 5> dependencies = {5, 6, 7}; + metadata.SetFrameId(123); // Must have a frame ID for related properties. + metadata.SetFrameDependencies(dependencies); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_TRUE(video_header.generic.has_value()); + EXPECT_THAT(video_header.generic->dependencies, ElementsAre(5, 6, 7)); +} + +TEST(RTPVideoHeaderTest, DecodeTargetIndications_GetAsMetadata) { + RTPVideoHeader video_header; + RTPVideoHeader::GenericDescriptorInfo& generic = + video_header.generic.emplace(); + generic.decode_target_indications = {DecodeTargetIndication::kSwitch}; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_THAT(metadata.GetDecodeTargetIndications(), + ElementsAre(DecodeTargetIndication::kSwitch)); +} + +TEST(RTPVideoHeaderTest, + DecodeTargetIndications_GetAsMetadataWhenGenericIsMissing) { + RTPVideoHeader video_header; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + ASSERT_FALSE(video_header.generic); + EXPECT_THAT(metadata.GetDecodeTargetIndications(), IsEmpty()); +} + +TEST(RTPVideoHeaderTest, DecodeTargetIndications_FromMetadata) { + VideoFrameMetadata metadata; + absl::InlinedVector<DecodeTargetIndication, 10> decode_target_indications = { + DecodeTargetIndication::kSwitch}; + metadata.SetFrameId(123); // Must have a frame ID for related properties. + metadata.SetDecodeTargetIndications(decode_target_indications); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_TRUE(video_header.generic.has_value()); + EXPECT_THAT(video_header.generic->decode_target_indications, + ElementsAre(DecodeTargetIndication::kSwitch)); +} + +TEST(RTPVideoHeaderTest, IsLastFrameInPicture_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.is_last_frame_in_picture = false; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_FALSE(metadata.GetIsLastFrameInPicture()); +} + +TEST(RTPVideoHeaderTest, IsLastFrameInPicture_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetIsLastFrameInPicture(false); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_FALSE(video_header.is_last_frame_in_picture); +} + +TEST(RTPVideoHeaderTest, SimulcastIdx_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.simulcastIdx = 123; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetSimulcastIdx(), 123); +} + +TEST(RTPVideoHeaderTest, SimulcastIdx_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetSimulcastIdx(123); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.simulcastIdx, 123); +} + +TEST(RTPVideoHeaderTest, Codec_GetAsMetadata) { + RTPVideoHeader video_header; + video_header.codec = VideoCodecType::kVideoCodecVP9; + video_header.video_type_header = RTPVideoHeaderVP9(); + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(metadata.GetCodec(), VideoCodecType::kVideoCodecVP9); +} + +TEST(RTPVideoHeaderTest, Codec_FromMetadata) { + VideoFrameMetadata metadata; + metadata.SetCodec(VideoCodecType::kVideoCodecVP9); + metadata.SetRTPVideoHeaderCodecSpecifics(RTPVideoHeaderVP9()); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(video_header.codec, VideoCodecType::kVideoCodecVP9); +} + +TEST(RTPVideoHeaderTest, RTPVideoHeaderCodecSpecifics_GetAsMetadata) { + RTPVideoHeader video_header; + { + video_header.codec = VideoCodecType::kVideoCodecVP8; + RTPVideoHeaderVP8 vp8_specifics; + vp8_specifics.InitRTPVideoHeaderVP8(); + vp8_specifics.pictureId = 42; + video_header.video_type_header = vp8_specifics; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ( + absl::get<RTPVideoHeaderVP8>(metadata.GetRTPVideoHeaderCodecSpecifics()) + .pictureId, + vp8_specifics.pictureId); + } + { + video_header.codec = VideoCodecType::kVideoCodecVP9; + RTPVideoHeaderVP9 vp9_specifics; + vp9_specifics.InitRTPVideoHeaderVP9(); + vp9_specifics.max_picture_id = 42; + video_header.video_type_header = vp9_specifics; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ( + absl::get<RTPVideoHeaderVP9>(metadata.GetRTPVideoHeaderCodecSpecifics()) + .max_picture_id, + vp9_specifics.max_picture_id); + } + { + video_header.codec = VideoCodecType::kVideoCodecH264; + RTPVideoHeaderH264 h264_specifics; + h264_specifics.nalu_type = 42; + video_header.video_type_header = h264_specifics; + VideoFrameMetadata metadata = video_header.GetAsMetadata(); + EXPECT_EQ(absl::get<RTPVideoHeaderH264>( + metadata.GetRTPVideoHeaderCodecSpecifics()) + .nalu_type, + h264_specifics.nalu_type); + } +} + +TEST(RTPVideoHeaderTest, RTPVideoHeaderCodecSpecifics_FromMetadata) { + VideoFrameMetadata metadata; + { + metadata.SetCodec(VideoCodecType::kVideoCodecVP8); + RTPVideoHeaderVP8 vp8_specifics; + vp8_specifics.InitRTPVideoHeaderVP8(); + vp8_specifics.pictureId = 42; + metadata.SetRTPVideoHeaderCodecSpecifics(vp8_specifics); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ( + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header).pictureId, + 42); + } + { + metadata.SetCodec(VideoCodecType::kVideoCodecVP9); + RTPVideoHeaderVP9 vp9_specifics; + vp9_specifics.InitRTPVideoHeaderVP9(); + vp9_specifics.max_picture_id = 42; + metadata.SetRTPVideoHeaderCodecSpecifics(vp9_specifics); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ(absl::get<RTPVideoHeaderVP9>(video_header.video_type_header) + .max_picture_id, + 42); + } + { + metadata.SetCodec(VideoCodecType::kVideoCodecH264); + RTPVideoHeaderH264 h264_specifics; + h264_specifics.nalu_type = 42; + metadata.SetRTPVideoHeaderCodecSpecifics(h264_specifics); + RTPVideoHeader video_header = RTPVideoHeader::FromMetadata(metadata); + EXPECT_EQ( + absl::get<RTPVideoHeaderH264>(video_header.video_type_header).nalu_type, + 42); + } +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc new file mode 100644 index 0000000000..78fd7c9808 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc @@ -0,0 +1,323 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" + +#include <stddef.h> +#include <stdint.h> + +#include "absl/algorithm/container.h" +#include "api/video/video_layers_allocation.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/leb128.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +constexpr RTPExtensionType RtpVideoLayersAllocationExtension::kId; + +namespace { + +constexpr int kMaxNumRtpStreams = 4; + +bool AllocationIsValid(const VideoLayersAllocation& allocation) { + // Since all multivalue fields are stored in (rtp_stream_id, spatial_id) order + // assume `allocation.active_spatial_layers` is already sorted. It is simpler + // to assemble it in the sorted way than to resort during serialization. + if (!absl::c_is_sorted( + allocation.active_spatial_layers, + [](const VideoLayersAllocation::SpatialLayer& lhs, + const VideoLayersAllocation::SpatialLayer& rhs) { + return std::make_tuple(lhs.rtp_stream_index, lhs.spatial_id) < + std::make_tuple(rhs.rtp_stream_index, rhs.spatial_id); + })) { + return false; + } + + int max_rtp_stream_idx = 0; + for (const auto& spatial_layer : allocation.active_spatial_layers) { + if (spatial_layer.rtp_stream_index < 0 || + spatial_layer.rtp_stream_index >= 4) { + return false; + } + if (spatial_layer.spatial_id < 0 || spatial_layer.spatial_id >= 4) { + return false; + } + if (spatial_layer.target_bitrate_per_temporal_layer.empty() || + spatial_layer.target_bitrate_per_temporal_layer.size() > 4) { + return false; + } + if (max_rtp_stream_idx < spatial_layer.rtp_stream_index) { + max_rtp_stream_idx = spatial_layer.rtp_stream_index; + } + if (allocation.resolution_and_frame_rate_is_valid) { + // TODO(danilchap): Add check width and height are no more than 0x10000 + // when width and height become larger type and thus would support maximum + // resolution. + if (spatial_layer.width <= 0) { + return false; + } + if (spatial_layer.height <= 0) { + return false; + } + if (spatial_layer.frame_rate_fps > 255) { + return false; + } + } + } + if (allocation.rtp_stream_index < 0 || + (!allocation.active_spatial_layers.empty() && + allocation.rtp_stream_index > max_rtp_stream_idx)) { + return false; + } + return true; +} + +struct SpatialLayersBitmasks { + int max_rtp_stream_id = 0; + uint8_t spatial_layer_bitmask[kMaxNumRtpStreams] = {}; + bool bitmasks_are_the_same = true; +}; + +SpatialLayersBitmasks SpatialLayersBitmasksPerRtpStream( + const VideoLayersAllocation& allocation) { + RTC_DCHECK(AllocationIsValid(allocation)); + SpatialLayersBitmasks result; + for (const auto& layer : allocation.active_spatial_layers) { + result.spatial_layer_bitmask[layer.rtp_stream_index] |= + (1u << layer.spatial_id); + if (result.max_rtp_stream_id < layer.rtp_stream_index) { + result.max_rtp_stream_id = layer.rtp_stream_index; + } + } + for (int i = 1; i <= result.max_rtp_stream_id; ++i) { + if (result.spatial_layer_bitmask[i] != result.spatial_layer_bitmask[0]) { + result.bitmasks_are_the_same = false; + break; + } + } + return result; +} + +} // namespace + +// See /docs/native-code/rtp-rtpext/video-layers-allocation00/README.md +// for the description of the format. + +bool RtpVideoLayersAllocationExtension::Write( + rtc::ArrayView<uint8_t> data, + const VideoLayersAllocation& allocation) { + RTC_DCHECK(AllocationIsValid(allocation)); + RTC_DCHECK_GE(data.size(), ValueSize(allocation)); + + if (allocation.active_spatial_layers.empty()) { + data[0] = 0; + return true; + } + + SpatialLayersBitmasks slb = SpatialLayersBitmasksPerRtpStream(allocation); + uint8_t* write_at = data.data(); + // First half of the header byte. + *write_at = (allocation.rtp_stream_index << 6); + // number of rtp stream - 1 is the same as the maximum rtp_stream_id. + *write_at |= slb.max_rtp_stream_id << 4; + if (slb.bitmasks_are_the_same) { + // Second half of the header byte. + *write_at |= slb.spatial_layer_bitmask[0]; + } else { + // spatial layer bitmasks when they are different for different RTP streams. + *++write_at = + (slb.spatial_layer_bitmask[0] << 4) | slb.spatial_layer_bitmask[1]; + if (slb.max_rtp_stream_id >= 2) { + *++write_at = + (slb.spatial_layer_bitmask[2] << 4) | slb.spatial_layer_bitmask[3]; + } + } + ++write_at; + + { // Number of temporal layers. + int bit_offset = 8; + *write_at = 0; + for (const auto& layer : allocation.active_spatial_layers) { + if (bit_offset == 0) { + bit_offset = 6; + *++write_at = 0; + } else { + bit_offset -= 2; + } + *write_at |= + ((layer.target_bitrate_per_temporal_layer.size() - 1) << bit_offset); + } + ++write_at; + } + + // Target bitrates. + for (const auto& spatial_layer : allocation.active_spatial_layers) { + for (const DataRate& bitrate : + spatial_layer.target_bitrate_per_temporal_layer) { + write_at += WriteLeb128(bitrate.kbps(), write_at); + } + } + + if (allocation.resolution_and_frame_rate_is_valid) { + for (const auto& spatial_layer : allocation.active_spatial_layers) { + ByteWriter<uint16_t>::WriteBigEndian(write_at, spatial_layer.width - 1); + write_at += 2; + ByteWriter<uint16_t>::WriteBigEndian(write_at, spatial_layer.height - 1); + write_at += 2; + *write_at = spatial_layer.frame_rate_fps; + ++write_at; + } + } + RTC_DCHECK_EQ(write_at - data.data(), ValueSize(allocation)); + return true; +} + +bool RtpVideoLayersAllocationExtension::Parse( + rtc::ArrayView<const uint8_t> data, + VideoLayersAllocation* allocation) { + if (data.empty() || allocation == nullptr) { + return false; + } + + allocation->active_spatial_layers.clear(); + + const uint8_t* read_at = data.data(); + const uint8_t* const end = data.data() + data.size(); + + if (data.size() == 1 && *read_at == 0) { + allocation->rtp_stream_index = 0; + allocation->resolution_and_frame_rate_is_valid = true; + return AllocationIsValid(*allocation); + } + + // Header byte. + allocation->rtp_stream_index = *read_at >> 6; + int num_rtp_streams = 1 + ((*read_at >> 4) & 0b11); + uint8_t spatial_layers_bitmasks[kMaxNumRtpStreams]; + spatial_layers_bitmasks[0] = *read_at & 0b1111; + + if (spatial_layers_bitmasks[0] != 0) { + for (int i = 1; i < num_rtp_streams; ++i) { + spatial_layers_bitmasks[i] = spatial_layers_bitmasks[0]; + } + } else { + // Spatial layer bitmasks when they are different for different RTP streams. + if (++read_at == end) { + return false; + } + spatial_layers_bitmasks[0] = *read_at >> 4; + spatial_layers_bitmasks[1] = *read_at & 0b1111; + if (num_rtp_streams > 2) { + if (++read_at == end) { + return false; + } + spatial_layers_bitmasks[2] = *read_at >> 4; + spatial_layers_bitmasks[3] = *read_at & 0b1111; + } + } + if (++read_at == end) { + return false; + } + + // Read number of temporal layers, + // Create `allocation->active_spatial_layers` while iterating though it. + int bit_offset = 8; + for (int stream_idx = 0; stream_idx < num_rtp_streams; ++stream_idx) { + for (int sid = 0; sid < VideoLayersAllocation::kMaxSpatialIds; ++sid) { + if ((spatial_layers_bitmasks[stream_idx] & (1 << sid)) == 0) { + continue; + } + + if (bit_offset == 0) { + bit_offset = 6; + if (++read_at == end) { + return false; + } + } else { + bit_offset -= 2; + } + int num_temporal_layers = 1 + ((*read_at >> bit_offset) & 0b11); + allocation->active_spatial_layers.emplace_back(); + auto& layer = allocation->active_spatial_layers.back(); + layer.rtp_stream_index = stream_idx; + layer.spatial_id = sid; + layer.target_bitrate_per_temporal_layer.resize(num_temporal_layers, + DataRate::Zero()); + } + } + if (++read_at == end) { + return false; + } + + // Target bitrates. + for (auto& layer : allocation->active_spatial_layers) { + for (DataRate& rate : layer.target_bitrate_per_temporal_layer) { + uint64_t bitrate_kbps = ReadLeb128(read_at, end); + // bitrate_kbps might represent larger values than DataRate type, + // discard unreasonably large values. + if (read_at == nullptr || bitrate_kbps > 1'000'000) { + return false; + } + rate = DataRate::KilobitsPerSec(bitrate_kbps); + } + } + + if (read_at == end) { + allocation->resolution_and_frame_rate_is_valid = false; + return AllocationIsValid(*allocation); + } + + if (read_at + 5 * allocation->active_spatial_layers.size() != end) { + // data is left, but it size is not what can be used for resolutions and + // framerates. + return false; + } + allocation->resolution_and_frame_rate_is_valid = true; + for (auto& layer : allocation->active_spatial_layers) { + layer.width = 1 + ByteReader<uint16_t, 2>::ReadBigEndian(read_at); + read_at += 2; + layer.height = 1 + ByteReader<uint16_t, 2>::ReadBigEndian(read_at); + read_at += 2; + layer.frame_rate_fps = *read_at; + ++read_at; + } + + return AllocationIsValid(*allocation); +} + +size_t RtpVideoLayersAllocationExtension::ValueSize( + const VideoLayersAllocation& allocation) { + if (allocation.active_spatial_layers.empty()) { + return 1; + } + size_t result = 1; // header + SpatialLayersBitmasks slb = SpatialLayersBitmasksPerRtpStream(allocation); + if (!slb.bitmasks_are_the_same) { + ++result; + if (slb.max_rtp_stream_id >= 2) { + ++result; + } + } + // 2 bits per active spatial layer, rounded up to full byte, i.e. + // 0.25 byte per active spatial layer. + result += (allocation.active_spatial_layers.size() + 3) / 4; + for (const auto& spatial_layer : allocation.active_spatial_layers) { + for (DataRate value : spatial_layer.target_bitrate_per_temporal_layer) { + result += Leb128Size(value.kbps()); + } + } + if (allocation.resolution_and_frame_rate_is_valid) { + result += 5 * allocation.active_spatial_layers.size(); + } + return result; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h new file mode 100644 index 0000000000..3f1603bcd8 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.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 MODULES_RTP_RTCP_SOURCE_RTP_VIDEO_LAYERS_ALLOCATION_EXTENSION_H_ +#define MODULES_RTP_RTCP_SOURCE_RTP_VIDEO_LAYERS_ALLOCATION_EXTENSION_H_ + +#include "absl/strings/string_view.h" +#include "api/rtp_parameters.h" +#include "api/video/video_layers_allocation.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" + +namespace webrtc { + +// TODO(bugs.webrtc.org/12000): Note that this extensions is being developed and +// the wire format will likely change. +class RtpVideoLayersAllocationExtension { + public: + using value_type = VideoLayersAllocation; + static constexpr RTPExtensionType kId = kRtpExtensionVideoLayersAllocation; + static constexpr absl::string_view Uri() { + return RtpExtension::kVideoLayersAllocationUri; + } + + static bool Parse(rtc::ArrayView<const uint8_t> data, + VideoLayersAllocation* allocation); + static size_t ValueSize(const VideoLayersAllocation& allocation); + static bool Write(rtc::ArrayView<uint8_t> data, + const VideoLayersAllocation& allocation); +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_RTP_VIDEO_LAYERS_ALLOCATION_EXTENSION_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc new file mode 100644 index 0000000000..db077409ee --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc @@ -0,0 +1,287 @@ +/* + * 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 "modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h" + +#include "api/video/video_layers_allocation.h" +#include "rtc_base/bit_buffer.h" +#include "rtc_base/buffer.h" + +#include "test/gmock.h" + +namespace webrtc { +namespace { + +TEST(RtpVideoLayersAllocationExtension, WriteEmptyLayersAllocationReturnsTrue) { + VideoLayersAllocation written_allocation; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseLayersAllocationWithZeroSpatialLayers) { + // We require the resolution_and_frame_rate_is_valid to be set to true in + // order to send an "empty" allocation. + VideoLayersAllocation written_allocation; + written_allocation.resolution_and_frame_rate_is_valid = true; + written_allocation.rtp_stream_index = 0; + + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParse2SpatialWith2TemporalLayers) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.active_spatial_layers = { + { + /*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ + {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0, + }, + { + /*rtp_stream_index*/ 1, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ + {DataRate::KilobitsPerSec(100), DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0, + }, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithDifferentNumerOfSpatialLayers) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.active_spatial_layers = { + {/*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 1, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(100)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 1, + /*spatial_id*/ 1, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithSkippedLowerSpatialLayer) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.active_spatial_layers = { + {/*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 1, + /*spatial_id*/ 1, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithSkippedRtpStreamIds) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 2; + written_allocation.active_spatial_layers = { + {/*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + {/*rtp_stream_index*/ 2, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(200)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0}, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithDifferentNumerOfTemporalLayers) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.active_spatial_layers = { + { + /*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ + {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0, + }, + { + /*rtp_stream_index*/ 1, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ {DataRate::KilobitsPerSec(100)}, + /*width*/ 0, + /*height*/ 0, + /*frame_rate_fps*/ 0, + }, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + CanWriteAndParseAllocationWithResolution) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + written_allocation.resolution_and_frame_rate_is_valid = true; + written_allocation.active_spatial_layers = { + { + /*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ + {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)}, + /*width*/ 320, + /*height*/ 240, + /*frame_rate_fps*/ 8, + }, + { + /*rtp_stream_index*/ 1, + /*spatial_id*/ 1, + /*target_bitrate_per_temporal_layer*/ + {DataRate::KilobitsPerSec(100), DataRate::KilobitsPerSec(200)}, + /*width*/ 640, + /*height*/ 320, + /*frame_rate_fps*/ 30, + }, + }; + + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + VideoLayersAllocation parsed_allocation; + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Parse(buffer, &parsed_allocation)); + EXPECT_EQ(written_allocation, parsed_allocation); +} + +TEST(RtpVideoLayersAllocationExtension, + WriteEmptyAllocationCanHaveAnyRtpStreamIndex) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 1; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + EXPECT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); +} + +TEST(RtpVideoLayersAllocationExtension, DiscardsOverLargeDataRate) { + constexpr uint8_t buffer[] = {0x4b, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xcb, 0x78, 0xeb, 0x8d, 0xb5, 0x31}; + VideoLayersAllocation allocation; + EXPECT_FALSE(RtpVideoLayersAllocationExtension::Parse(buffer, &allocation)); +} + +TEST(RtpVideoLayersAllocationExtension, DiscardsInvalidHeight) { + VideoLayersAllocation written_allocation; + written_allocation.rtp_stream_index = 0; + written_allocation.resolution_and_frame_rate_is_valid = true; + written_allocation.active_spatial_layers = { + { + /*rtp_stream_index*/ 0, + /*spatial_id*/ 0, + /*target_bitrate_per_temporal_layer*/ + {DataRate::KilobitsPerSec(25), DataRate::KilobitsPerSec(50)}, + /*width*/ 320, + /*height*/ 240, + /*frame_rate_fps*/ 8, + }, + }; + rtc::Buffer buffer( + RtpVideoLayersAllocationExtension::ValueSize(written_allocation)); + ASSERT_TRUE( + RtpVideoLayersAllocationExtension::Write(buffer, written_allocation)); + + // Modify the height to be invalid. + buffer[buffer.size() - 3] = 0xff; + buffer[buffer.size() - 2] = 0xff; + VideoLayersAllocation allocation; + EXPECT_FALSE(RtpVideoLayersAllocationExtension::Parse(buffer, &allocation)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.cc new file mode 100644 index 0000000000..65f21700d0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.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 "modules/rtp_rtcp/source/source_tracker.h" + +#include <algorithm> +#include <utility> + +#include "rtc_base/trace_event.h" + +namespace webrtc { + +constexpr int64_t SourceTracker::kTimeoutMs; + +SourceTracker::SourceTracker(Clock* clock) : clock_(clock) {} + +void SourceTracker::OnFrameDelivered(const RtpPacketInfos& packet_infos) { + if (packet_infos.empty()) { + return; + } + + TRACE_EVENT0("webrtc", "SourceTracker::OnFrameDelivered"); + + int64_t now_ms = clock_->TimeInMilliseconds(); + MutexLock lock_scope(&lock_); + + for (const RtpPacketInfo& packet_info : packet_infos) { + for (uint32_t csrc : packet_info.csrcs()) { + SourceKey key(RtpSourceType::CSRC, csrc); + SourceEntry& entry = UpdateEntry(key); + + const auto packet_time = packet_info.receive_time().ms(); + entry.timestamp_ms = packet_time ? packet_time : now_ms; + entry.audio_level = packet_info.audio_level(); + entry.absolute_capture_time = packet_info.absolute_capture_time(); + entry.local_capture_clock_offset = + packet_info.local_capture_clock_offset(); + entry.rtp_timestamp = packet_info.rtp_timestamp(); + } + + SourceKey key(RtpSourceType::SSRC, packet_info.ssrc()); + SourceEntry& entry = UpdateEntry(key); + + entry.timestamp_ms = now_ms; + entry.audio_level = packet_info.audio_level(); + entry.absolute_capture_time = packet_info.absolute_capture_time(); + entry.local_capture_clock_offset = packet_info.local_capture_clock_offset(); + entry.rtp_timestamp = packet_info.rtp_timestamp(); + } + + PruneEntries(now_ms); +} + +std::vector<RtpSource> SourceTracker::GetSources() const { + std::vector<RtpSource> sources; + + int64_t now_ms = clock_->TimeInMilliseconds(); + MutexLock lock_scope(&lock_); + + PruneEntries(now_ms); + + for (const auto& pair : list_) { + const SourceKey& key = pair.first; + const SourceEntry& entry = pair.second; + + sources.emplace_back( + entry.timestamp_ms, key.source, key.source_type, entry.rtp_timestamp, + RtpSource::Extensions{ + .audio_level = entry.audio_level, + .absolute_capture_time = entry.absolute_capture_time, + .local_capture_clock_offset = entry.local_capture_clock_offset}); + } + + std::sort(sources.begin(), sources.end(), [](const auto &a, const auto &b){ + return a.timestamp_ms() > b.timestamp_ms(); + }); + + return sources; +} + +SourceTracker::SourceEntry& SourceTracker::UpdateEntry(const SourceKey& key) { + // We intentionally do |find() + emplace()|, instead of checking the return + // value of `emplace()`, for performance reasons. It's much more likely for + // the key to already exist than for it not to. + auto map_it = map_.find(key); + if (map_it == map_.end()) { + // Insert a new entry at the front of the list. + list_.emplace_front(key, SourceEntry()); + map_.emplace(key, list_.begin()); + } else if (map_it->second != list_.begin()) { + // Move the old entry to the front of the list. + list_.splice(list_.begin(), list_, map_it->second); + } + + return list_.front().second; +} + +void SourceTracker::PruneEntries(int64_t now_ms) const { + int64_t prune_ms = now_ms - kTimeoutMs; + + while (!list_.empty() && list_.back().second.timestamp_ms < prune_ms) { + map_.erase(list_.back().first); + list_.pop_back(); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.h b/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.h new file mode 100644 index 0000000000..f9e8354d44 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.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 MODULES_RTP_RTCP_SOURCE_SOURCE_TRACKER_H_ +#define MODULES_RTP_RTCP_SOURCE_SOURCE_TRACKER_H_ + +#include <cstdint> +#include <list> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/rtp_packet_infos.h" +#include "api/transport/rtp/rtp_source.h" +#include "api/units/time_delta.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +// +// Tracker for `RTCRtpContributingSource` and `RTCRtpSynchronizationSource`: +// - https://w3c.github.io/webrtc-pc/#dom-rtcrtpcontributingsource +// - https://w3c.github.io/webrtc-pc/#dom-rtcrtpsynchronizationsource +// +class SourceTracker { + public: + // Amount of time before the entry associated with an update is removed. See: + // https://w3c.github.io/webrtc-pc/#dom-rtcrtpreceiver-getcontributingsources + static constexpr int64_t kTimeoutMs = 10000; // 10 seconds + + explicit SourceTracker(Clock* clock); + + SourceTracker(const SourceTracker& other) = delete; + SourceTracker(SourceTracker&& other) = delete; + SourceTracker& operator=(const SourceTracker& other) = delete; + SourceTracker& operator=(SourceTracker&& other) = delete; + + // Updates the source entries when a frame is delivered to the + // RTCRtpReceiver's MediaStreamTrack. + void OnFrameDelivered(const RtpPacketInfos& packet_infos); + + // Returns an `RtpSource` for each unique SSRC and CSRC identifier updated in + // the last `kTimeoutMs` milliseconds. Entries appear in reverse chronological + // order (i.e. with the most recently updated entries appearing first). + std::vector<RtpSource> GetSources() const; + + private: + struct SourceKey { + SourceKey(RtpSourceType source_type, uint32_t source) + : source_type(source_type), source(source) {} + + // Type of `source`. + RtpSourceType source_type; + + // CSRC or SSRC identifier of the contributing or synchronization source. + uint32_t source; + }; + + struct SourceKeyComparator { + bool operator()(const SourceKey& lhs, const SourceKey& rhs) const { + return (lhs.source_type == rhs.source_type) && (lhs.source == rhs.source); + } + }; + + struct SourceKeyHasher { + size_t operator()(const SourceKey& value) const { + return static_cast<size_t>(value.source_type) + + static_cast<size_t>(value.source) * 11076425802534262905ULL; + } + }; + + struct SourceEntry { + // Timestamp indicating the most recent time a frame from an RTP packet, + // originating from this source, was delivered to the RTCRtpReceiver's + // MediaStreamTrack. Its reference clock is the outer class's `clock_`. + int64_t timestamp_ms; + + // Audio level from an RFC 6464 or RFC 6465 header extension received with + // the most recent packet used to assemble the frame associated with + // `timestamp_ms`. May be absent. Only relevant for audio receivers. See the + // specs for `RTCRtpContributingSource` for more info. + absl::optional<uint8_t> audio_level; + + // Absolute capture time header extension received or interpolated from the + // most recent packet used to assemble the frame. For more info see + // https://webrtc.org/experiments/rtp-hdrext/abs-capture-time/ + absl::optional<AbsoluteCaptureTime> absolute_capture_time; + + // Clock offset between the local clock and the capturer's clock. + // Do not confuse with `AbsoluteCaptureTime::estimated_capture_clock_offset` + // which instead represents the clock offset between a remote sender and the + // capturer. The following holds: + // Capture's NTP Clock = Local NTP Clock + Local-Capture Clock Offset + absl::optional<TimeDelta> local_capture_clock_offset; + + // RTP timestamp of the most recent packet used to assemble the frame + // associated with `timestamp_ms`. + uint32_t rtp_timestamp; + }; + + using SourceList = std::list<std::pair<const SourceKey, SourceEntry>>; + using SourceMap = std::unordered_map<SourceKey, + SourceList::iterator, + SourceKeyHasher, + SourceKeyComparator>; + + // Updates an entry by creating it (if it didn't previously exist) and moving + // it to the front of the list. Returns a reference to the entry. + SourceEntry& UpdateEntry(const SourceKey& key) + RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + + // Removes entries that have timed out. Marked as "const" so that we can do + // pruning in getters. + void PruneEntries(int64_t now_ms) const RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_); + + Clock* const clock_; + mutable Mutex lock_; + + // Entries are stored in reverse chronological order (i.e. with the most + // recently updated entries appearing first). Mutability is needed for timeout + // pruning in const functions. + mutable SourceList list_ RTC_GUARDED_BY(lock_); + mutable SourceMap map_ RTC_GUARDED_BY(lock_); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_SOURCE_TRACKER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker_unittest.cc new file mode 100644 index 0000000000..b3e3102d7e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker_unittest.cc @@ -0,0 +1,533 @@ +/* + * 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/source_tracker.h" + +#include <algorithm> +#include <list> +#include <random> +#include <set> +#include <tuple> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "api/rtp_headers.h" +#include "api/rtp_packet_info.h" +#include "api/rtp_packet_infos.h" +#include "system_wrappers/include/ntp_time.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Combine; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::IsEmpty; +using ::testing::TestWithParam; +using ::testing::Values; + +constexpr size_t kPacketInfosCountMax = 5; + +// Simple "guaranteed to be correct" re-implementation of `SourceTracker` for +// dual-implementation testing purposes. +class ExpectedSourceTracker { + public: + explicit ExpectedSourceTracker(Clock* clock) : clock_(clock) {} + + void OnFrameDelivered(const RtpPacketInfos& packet_infos) { + const int64_t now_ms = clock_->TimeInMilliseconds(); + + for (const auto& packet_info : packet_infos) { + RtpSource::Extensions extensions = { + packet_info.audio_level(), packet_info.absolute_capture_time(), + packet_info.local_capture_clock_offset()}; + + for (const auto& csrc : packet_info.csrcs()) { + entries_.emplace_front(now_ms, csrc, RtpSourceType::CSRC, + packet_info.rtp_timestamp(), extensions); + } + + entries_.emplace_front(now_ms, packet_info.ssrc(), RtpSourceType::SSRC, + packet_info.rtp_timestamp(), extensions); + } + + PruneEntries(now_ms); + } + + std::vector<RtpSource> GetSources() const { + PruneEntries(clock_->TimeInMilliseconds()); + + return std::vector<RtpSource>(entries_.begin(), entries_.end()); + } + + private: + void PruneEntries(int64_t now_ms) const { + const int64_t prune_ms = now_ms - 10000; // 10 seconds + + std::set<std::pair<RtpSourceType, uint32_t>> seen; + + auto it = entries_.begin(); + auto end = entries_.end(); + while (it != end) { + auto next = it; + ++next; + + auto key = std::make_pair(it->source_type(), it->source_id()); + if (!seen.insert(key).second || it->timestamp_ms() < prune_ms) { + entries_.erase(it); + } + + it = next; + } + } + + Clock* const clock_; + + mutable std::list<RtpSource> entries_; +}; + +class SourceTrackerRandomTest + : public TestWithParam<std::tuple<uint32_t, uint32_t>> { + protected: + SourceTrackerRandomTest() + : ssrcs_count_(std::get<0>(GetParam())), + csrcs_count_(std::get<1>(GetParam())), + generator_(42) {} + + RtpPacketInfos GeneratePacketInfos() { + size_t count = std::uniform_int_distribution<size_t>( + 1, kPacketInfosCountMax)(generator_); + + RtpPacketInfos::vector_type packet_infos; + for (size_t i = 0; i < count; ++i) { + packet_infos + .emplace_back(GenerateSsrc(), GenerateCsrcs(), GenerateRtpTimestamp(), + GenerateReceiveTime()) + .set_audio_level(GenerateAudioLevel()) + .set_absolute_capture_time(GenerateAbsoluteCaptureTime()) + .set_local_capture_clock_offset(GenerateLocalCaptureClockOffset()); + } + + return RtpPacketInfos(std::move(packet_infos)); + } + + int64_t GenerateClockAdvanceTimeMilliseconds() { + double roll = std::uniform_real_distribution<double>(0.0, 1.0)(generator_); + + if (roll < 0.05) { + return 0; + } + + if (roll < 0.08) { + return SourceTracker::kTimeoutMs - 1; + } + + if (roll < 0.11) { + return SourceTracker::kTimeoutMs; + } + + if (roll < 0.19) { + return std::uniform_int_distribution<int64_t>( + SourceTracker::kTimeoutMs, + SourceTracker::kTimeoutMs * 1000)(generator_); + } + + return std::uniform_int_distribution<int64_t>( + 1, SourceTracker::kTimeoutMs - 1)(generator_); + } + + private: + uint32_t GenerateSsrc() { + return std::uniform_int_distribution<uint32_t>(1, ssrcs_count_)(generator_); + } + + std::vector<uint32_t> GenerateCsrcs() { + std::vector<uint32_t> csrcs; + for (size_t i = 1; i <= csrcs_count_ && csrcs.size() < kRtpCsrcSize; ++i) { + if (std::bernoulli_distribution(0.5)(generator_)) { + csrcs.push_back(i); + } + } + + return csrcs; + } + + uint32_t GenerateRtpTimestamp() { + return std::uniform_int_distribution<uint32_t>()(generator_); + } + + absl::optional<uint8_t> GenerateAudioLevel() { + if (std::bernoulli_distribution(0.25)(generator_)) { + return absl::nullopt; + } + + // Workaround for std::uniform_int_distribution<uint8_t> not being allowed. + return static_cast<uint8_t>( + std::uniform_int_distribution<uint16_t>()(generator_)); + } + + absl::optional<AbsoluteCaptureTime> GenerateAbsoluteCaptureTime() { + if (std::bernoulli_distribution(0.25)(generator_)) { + return absl::nullopt; + } + + AbsoluteCaptureTime value; + + value.absolute_capture_timestamp = + std::uniform_int_distribution<uint64_t>()(generator_); + + if (std::bernoulli_distribution(0.5)(generator_)) { + value.estimated_capture_clock_offset = absl::nullopt; + } else { + value.estimated_capture_clock_offset = + std::uniform_int_distribution<int64_t>()(generator_); + } + + return value; + } + + absl::optional<TimeDelta> GenerateLocalCaptureClockOffset() { + if (std::bernoulli_distribution(0.5)(generator_)) { + return absl::nullopt; + } + return TimeDelta::Millis( + UQ32x32ToInt64Ms(std::uniform_int_distribution<int64_t>()(generator_))); + } + + Timestamp GenerateReceiveTime() { + return Timestamp::Micros( + std::uniform_int_distribution<int64_t>()(generator_)); + } + + const uint32_t ssrcs_count_; + const uint32_t csrcs_count_; + + std::mt19937 generator_; +}; + +} // namespace + +TEST_P(SourceTrackerRandomTest, RandomOperations) { + constexpr size_t kIterationsCount = 200; + + SimulatedClock clock(1000000000000ULL); + SourceTracker actual_tracker(&clock); + ExpectedSourceTracker expected_tracker(&clock); + + ASSERT_THAT(actual_tracker.GetSources(), IsEmpty()); + ASSERT_THAT(expected_tracker.GetSources(), IsEmpty()); + + for (size_t i = 0; i < kIterationsCount; ++i) { + RtpPacketInfos packet_infos = GeneratePacketInfos(); + + actual_tracker.OnFrameDelivered(packet_infos); + expected_tracker.OnFrameDelivered(packet_infos); + + clock.AdvanceTimeMilliseconds(GenerateClockAdvanceTimeMilliseconds()); + + ASSERT_THAT(actual_tracker.GetSources(), + ElementsAreArray(expected_tracker.GetSources())); + } +} + +INSTANTIATE_TEST_SUITE_P(All, + SourceTrackerRandomTest, + Combine(/*ssrcs_count_=*/Values(1, 2, 4), + /*csrcs_count_=*/Values(0, 1, 3, 7))); + +TEST(SourceTrackerTest, StartEmpty) { + SimulatedClock clock(1000000000000ULL); + SourceTracker tracker(&clock); + + EXPECT_THAT(tracker.GetSources(), IsEmpty()); +} + +TEST(SourceTrackerTest, OnFrameDeliveredRecordsSourcesDistinctSsrcs) { + constexpr uint32_t kSsrc1 = 10; + constexpr uint32_t kSsrc2 = 11; + constexpr uint32_t kCsrcs0 = 20; + constexpr uint32_t kCsrcs1 = 21; + constexpr uint32_t kCsrcs2 = 22; + constexpr uint32_t kRtpTimestamp0 = 40; + constexpr uint32_t kRtpTimestamp1 = 50; + constexpr absl::optional<uint8_t> kAudioLevel0 = 50; + constexpr absl::optional<uint8_t> kAudioLevel1 = 20; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime = + AbsoluteCaptureTime{/*absolute_capture_timestamp=*/12, + /*estimated_capture_clock_offset=*/absl::nullopt}; + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset = absl::nullopt; + constexpr Timestamp kReceiveTime0 = Timestamp::Millis(60); + constexpr Timestamp kReceiveTime1 = Timestamp::Millis(70); + + SimulatedClock clock(1000000000000ULL); + SourceTracker tracker(&clock); + + tracker.OnFrameDelivered(RtpPacketInfos( + {RtpPacketInfo(kSsrc1, {kCsrcs0, kCsrcs1}, kRtpTimestamp0, kReceiveTime0) + .set_audio_level(kAudioLevel0) + .set_absolute_capture_time(kAbsoluteCaptureTime) + .set_local_capture_clock_offset(kLocalCaptureClockOffset), + RtpPacketInfo(kSsrc2, {kCsrcs2}, kRtpTimestamp1, kReceiveTime1) + .set_audio_level(kAudioLevel1) + .set_absolute_capture_time(kAbsoluteCaptureTime) + .set_local_capture_clock_offset(kLocalCaptureClockOffset)})); + + int64_t timestamp_ms = clock.TimeInMilliseconds(); + constexpr RtpSource::Extensions extensions0 = { + .audio_level = kAudioLevel0, + .absolute_capture_time = kAbsoluteCaptureTime, + .local_capture_clock_offset = kLocalCaptureClockOffset}; + constexpr RtpSource::Extensions extensions1 = { + .audio_level = kAudioLevel1, + .absolute_capture_time = kAbsoluteCaptureTime, + .local_capture_clock_offset = kLocalCaptureClockOffset}; + + EXPECT_THAT(tracker.GetSources(), + ElementsAre(RtpSource(timestamp_ms, kSsrc2, RtpSourceType::SSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms, kCsrcs2, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms, kSsrc1, RtpSourceType::SSRC, + kRtpTimestamp0, extensions0), + RtpSource(timestamp_ms, kCsrcs1, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0), + RtpSource(timestamp_ms, kCsrcs0, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0))); +} + +TEST(SourceTrackerTest, OnFrameDeliveredRecordsSourcesSameSsrc) { + constexpr uint32_t kSsrc = 10; + constexpr uint32_t kCsrcs0 = 20; + constexpr uint32_t kCsrcs1 = 21; + constexpr uint32_t kCsrcs2 = 22; + constexpr uint32_t kRtpTimestamp0 = 40; + constexpr uint32_t kRtpTimestamp1 = 45; + constexpr uint32_t kRtpTimestamp2 = 50; + constexpr absl::optional<uint8_t> kAudioLevel0 = 50; + constexpr absl::optional<uint8_t> kAudioLevel1 = 20; + constexpr absl::optional<uint8_t> kAudioLevel2 = 10; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime = + AbsoluteCaptureTime{/*absolute_capture_timestamp=*/12, + /*estimated_capture_clock_offset=*/absl::nullopt}; + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset = absl::nullopt; + constexpr Timestamp kReceiveTime0 = Timestamp::Millis(60); + constexpr Timestamp kReceiveTime1 = Timestamp::Millis(70); + constexpr Timestamp kReceiveTime2 = Timestamp::Millis(80); + + SimulatedClock clock(1000000000000ULL); + SourceTracker tracker(&clock); + + tracker.OnFrameDelivered(RtpPacketInfos({ + RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs1}, kRtpTimestamp0, kReceiveTime0) + .set_audio_level(kAudioLevel0) + .set_absolute_capture_time(kAbsoluteCaptureTime) + .set_local_capture_clock_offset(kLocalCaptureClockOffset), + RtpPacketInfo(kSsrc, {kCsrcs2}, kRtpTimestamp1, kReceiveTime1) + .set_audio_level(kAudioLevel1) + .set_absolute_capture_time(kAbsoluteCaptureTime) + .set_local_capture_clock_offset(kLocalCaptureClockOffset), + RtpPacketInfo(kSsrc, {kCsrcs0}, kRtpTimestamp2, kReceiveTime2) + .set_audio_level(kAudioLevel2) + .set_absolute_capture_time(kAbsoluteCaptureTime) + .set_local_capture_clock_offset(kLocalCaptureClockOffset), + })); + + int64_t timestamp_ms = clock.TimeInMilliseconds(); + constexpr RtpSource::Extensions extensions0 = { + .audio_level = kAudioLevel0, + .absolute_capture_time = kAbsoluteCaptureTime, + .local_capture_clock_offset = kLocalCaptureClockOffset}; + constexpr RtpSource::Extensions extensions1 = { + .audio_level = kAudioLevel1, + .absolute_capture_time = kAbsoluteCaptureTime, + .local_capture_clock_offset = kLocalCaptureClockOffset}; + constexpr RtpSource::Extensions extensions2 = { + .audio_level = kAudioLevel2, + .absolute_capture_time = kAbsoluteCaptureTime, + .local_capture_clock_offset = kLocalCaptureClockOffset}; + + EXPECT_THAT(tracker.GetSources(), + ElementsAre(RtpSource(timestamp_ms, kSsrc, RtpSourceType::SSRC, + kRtpTimestamp2, extensions2), + RtpSource(timestamp_ms, kCsrcs0, RtpSourceType::CSRC, + kRtpTimestamp2, extensions2), + RtpSource(timestamp_ms, kCsrcs2, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms, kCsrcs1, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0))); +} + +TEST(SourceTrackerTest, OnFrameDeliveredUpdatesSources) { + constexpr uint32_t kSsrc1 = 10; + constexpr uint32_t kSsrc2 = 11; + constexpr uint32_t kCsrcs0 = 20; + constexpr uint32_t kCsrcs1 = 21; + constexpr uint32_t kCsrcs2 = 22; + constexpr uint32_t kRtpTimestamp0 = 40; + constexpr uint32_t kRtpTimestamp1 = 41; + constexpr uint32_t kRtpTimestamp2 = 42; + constexpr absl::optional<uint8_t> kAudioLevel0 = 50; + constexpr absl::optional<uint8_t> kAudioLevel1 = absl::nullopt; + constexpr absl::optional<uint8_t> kAudioLevel2 = 10; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime0 = + AbsoluteCaptureTime{12, 34}; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime1 = + AbsoluteCaptureTime{56, 78}; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime2 = + AbsoluteCaptureTime{89, 90}; + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset0 = + TimeDelta::Millis(123); + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset1 = + TimeDelta::Millis(456); + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset2 = + TimeDelta::Millis(789); + constexpr Timestamp kReceiveTime0 = Timestamp::Millis(60); + constexpr Timestamp kReceiveTime1 = Timestamp::Millis(61); + constexpr Timestamp kReceiveTime2 = Timestamp::Millis(62); + + constexpr RtpSource::Extensions extensions0 = { + .audio_level = kAudioLevel0, + .absolute_capture_time = kAbsoluteCaptureTime0, + .local_capture_clock_offset = kLocalCaptureClockOffset0}; + constexpr RtpSource::Extensions extensions1 = { + .audio_level = kAudioLevel1, + .absolute_capture_time = kAbsoluteCaptureTime1, + .local_capture_clock_offset = kLocalCaptureClockOffset1}; + constexpr RtpSource::Extensions extensions2 = { + .audio_level = kAudioLevel2, + .absolute_capture_time = kAbsoluteCaptureTime2, + .local_capture_clock_offset = kLocalCaptureClockOffset2}; + + SimulatedClock clock(1000000000000ULL); + SourceTracker tracker(&clock); + + tracker.OnFrameDelivered(RtpPacketInfos( + {RtpPacketInfo(kSsrc1, {kCsrcs0, kCsrcs1}, kRtpTimestamp0, kReceiveTime0) + .set_audio_level(kAudioLevel0) + .set_absolute_capture_time(kAbsoluteCaptureTime0) + .set_local_capture_clock_offset(kLocalCaptureClockOffset0)})); + + int64_t timestamp_ms_0 = clock.TimeInMilliseconds(); + EXPECT_THAT( + tracker.GetSources(), + ElementsAre(RtpSource(timestamp_ms_0, kSsrc1, RtpSourceType::SSRC, + kRtpTimestamp0, extensions0), + RtpSource(timestamp_ms_0, kCsrcs1, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0), + RtpSource(timestamp_ms_0, kCsrcs0, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0))); + + // Deliver packets with updated sources. + + clock.AdvanceTimeMilliseconds(17); + tracker.OnFrameDelivered(RtpPacketInfos( + {RtpPacketInfo(kSsrc1, {kCsrcs0, kCsrcs2}, kRtpTimestamp1, kReceiveTime1) + .set_audio_level(kAudioLevel1) + .set_absolute_capture_time(kAbsoluteCaptureTime1) + .set_local_capture_clock_offset(kLocalCaptureClockOffset1)})); + + int64_t timestamp_ms_1 = clock.TimeInMilliseconds(); + + EXPECT_THAT( + tracker.GetSources(), + ElementsAre(RtpSource(timestamp_ms_1, kSsrc1, RtpSourceType::SSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_1, kCsrcs2, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_1, kCsrcs0, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_0, kCsrcs1, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0))); + + // Deliver more packets with update csrcs and a new ssrc. + clock.AdvanceTimeMilliseconds(17); + + tracker.OnFrameDelivered(RtpPacketInfos( + {RtpPacketInfo(kSsrc2, {kCsrcs0}, kRtpTimestamp2, kReceiveTime2) + .set_audio_level(kAudioLevel2) + .set_absolute_capture_time(kAbsoluteCaptureTime2) + .set_local_capture_clock_offset(kLocalCaptureClockOffset2)})); + + int64_t timestamp_ms_2 = clock.TimeInMilliseconds(); + + EXPECT_THAT( + tracker.GetSources(), + ElementsAre(RtpSource(timestamp_ms_2, kSsrc2, RtpSourceType::SSRC, + kRtpTimestamp2, extensions2), + RtpSource(timestamp_ms_2, kCsrcs0, RtpSourceType::CSRC, + kRtpTimestamp2, extensions2), + RtpSource(timestamp_ms_1, kSsrc1, RtpSourceType::SSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_1, kCsrcs2, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_0, kCsrcs1, RtpSourceType::CSRC, + kRtpTimestamp0, extensions0))); +} + +TEST(SourceTrackerTest, TimedOutSourcesAreRemoved) { + constexpr uint32_t kSsrc = 10; + constexpr uint32_t kCsrcs0 = 20; + constexpr uint32_t kCsrcs1 = 21; + constexpr uint32_t kCsrcs2 = 22; + constexpr uint32_t kRtpTimestamp0 = 40; + constexpr uint32_t kRtpTimestamp1 = 41; + constexpr absl::optional<uint8_t> kAudioLevel0 = 50; + constexpr absl::optional<uint8_t> kAudioLevel1 = absl::nullopt; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime0 = + AbsoluteCaptureTime{12, 34}; + constexpr absl::optional<AbsoluteCaptureTime> kAbsoluteCaptureTime1 = + AbsoluteCaptureTime{56, 78}; + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset0 = + TimeDelta::Millis(123); + constexpr absl::optional<TimeDelta> kLocalCaptureClockOffset1 = + TimeDelta::Millis(456); + constexpr Timestamp kReceiveTime0 = Timestamp::Millis(60); + constexpr Timestamp kReceiveTime1 = Timestamp::Millis(61); + + SimulatedClock clock(1000000000000ULL); + SourceTracker tracker(&clock); + + tracker.OnFrameDelivered(RtpPacketInfos( + {RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs1}, kRtpTimestamp0, kReceiveTime0) + .set_audio_level(kAudioLevel0) + .set_absolute_capture_time(kAbsoluteCaptureTime0) + .set_local_capture_clock_offset(kLocalCaptureClockOffset0)})); + + clock.AdvanceTimeMilliseconds(17); + + tracker.OnFrameDelivered(RtpPacketInfos( + {RtpPacketInfo(kSsrc, {kCsrcs0, kCsrcs2}, kRtpTimestamp1, kReceiveTime1) + .set_audio_level(kAudioLevel1) + .set_absolute_capture_time(kAbsoluteCaptureTime1) + .set_local_capture_clock_offset(kLocalCaptureClockOffset1)})); + + int64_t timestamp_ms_1 = clock.TimeInMilliseconds(); + + clock.AdvanceTimeMilliseconds(SourceTracker::kTimeoutMs); + + constexpr RtpSource::Extensions extensions1 = { + .audio_level = kAudioLevel1, + .absolute_capture_time = kAbsoluteCaptureTime1, + .local_capture_clock_offset = kLocalCaptureClockOffset1}; + + EXPECT_THAT( + tracker.GetSources(), + ElementsAre(RtpSource(timestamp_ms_1, kSsrc, RtpSourceType::SSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_1, kCsrcs2, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1), + RtpSource(timestamp_ms_1, kCsrcs0, RtpSourceType::CSRC, + kRtpTimestamp1, extensions1))); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.cc new file mode 100644 index 0000000000..44ca07dabe --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.cc @@ -0,0 +1,54 @@ +/* + * 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/rtp_rtcp/source/time_util.h" + +#include <algorithm> + +#include "rtc_base/checks.h" +#include "rtc_base/numerics/divide_round.h" +#include "rtc_base/time_utils.h" + +namespace webrtc { + +uint32_t SaturatedToCompactNtp(TimeDelta delta) { + constexpr uint32_t kMaxCompactNtp = 0xFFFFFFFF; + constexpr int kCompactNtpInSecond = 0x10000; + if (delta <= TimeDelta::Zero()) + return 0; + if (delta.us() >= + kMaxCompactNtp * rtc::kNumMicrosecsPerSec / kCompactNtpInSecond) + return kMaxCompactNtp; + // To convert to compact ntp need to divide by 1e6 to get seconds, + // then multiply by 0x10000 to get the final result. + // To avoid float operations, multiplication and division swapped. + return DivideRoundToNearest(delta.us() * kCompactNtpInSecond, + rtc::kNumMicrosecsPerSec); +} + +TimeDelta CompactNtpRttToTimeDelta(uint32_t compact_ntp_interval) { + static constexpr TimeDelta kMinRtt = TimeDelta::Millis(1); + // Interval to convert expected to be positive, e.g. RTT or delay. + // Because interval can be derived from non-monotonic ntp clock, + // it might become negative that is indistinguishable from very large values. + // Since very large RTT/delay is less likely than non-monotonic ntp clock, + // such value is considered negative and converted to minimum value of 1ms. + if (compact_ntp_interval > 0x80000000) + return kMinRtt; + // Convert to 64bit value to avoid multiplication overflow. + int64_t value = static_cast<int64_t>(compact_ntp_interval); + // To convert to TimeDelta need to divide by 2^16 to get seconds, + // then multiply by 1'000'000 to get microseconds. To avoid float operations, + // multiplication and division are swapped. + int64_t us = DivideRoundToNearest(value * rtc::kNumMicrosecsPerSec, 1 << 16); + // Small RTT value is considered too good to be true and increased to 1ms. + return std::max(TimeDelta::Micros(us), kMinRtt); +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h new file mode 100644 index 0000000000..9ff444b12e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h @@ -0,0 +1,56 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_TIME_UTIL_H_ +#define MODULES_RTP_RTCP_SOURCE_TIME_UTIL_H_ + +#include <stdint.h> + +#include "api/units/time_delta.h" +#include "rtc_base/numerics/safe_conversions.h" +#include "system_wrappers/include/ntp_time.h" + +namespace webrtc { + +// Helper function for compact ntp representation: +// RFC 3550, Section 4. Time Format. +// Wallclock time is represented using the timestamp format of +// the Network Time Protocol (NTP). +// ... +// In some fields where a more compact representation is +// appropriate, only the middle 32 bits are used; that is, the low 16 +// bits of the integer part and the high 16 bits of the fractional part. +inline uint32_t CompactNtp(NtpTime ntp) { + return (ntp.seconds() << 16) | (ntp.fractions() >> 16); +} + +// Converts interval to compact ntp (1/2^16 seconds) resolution. +// Negative values converted to 0, Overlarge values converted to max uint32_t. +uint32_t SaturatedToCompactNtp(TimeDelta delta); + +// Convert interval to the NTP time resolution (1/2^32 seconds ~= 0.2 ns). +// For deltas with absolute value larger than 35 minutes result is unspecified. +inline constexpr int64_t ToNtpUnits(TimeDelta delta) { + // For better precision `delta` is taken with best TimeDelta precision (us), + // then multiplaction and conversion to seconds are swapped to avoid float + // arithmetic. + // 2^31 us ~= 35.8 minutes. + return (rtc::saturated_cast<int32_t>(delta.us()) * (int64_t{1} << 32)) / + 1'000'000; +} + +// Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta. +// This interval can be up to ~9.1 hours (2^15 seconds). +// Values close to 2^16 seconds are considered negative and are converted to +// minimum value of 1ms. +TimeDelta CompactNtpRttToTimeDelta(uint32_t compact_ntp_interval); + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_TIME_UTIL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/time_util_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util_unittest.cc new file mode 100644 index 0000000000..b3d557fd83 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util_unittest.cc @@ -0,0 +1,128 @@ +/* + * 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/time_util.h" + +#include <cstdint> +#include <limits> + +#include "api/units/time_delta.h" +#include "test/gtest.h" + +namespace webrtc { + +TEST(TimeUtilTest, CompactNtp) { + const uint32_t kNtpSec = 0x12345678; + const uint32_t kNtpFrac = 0x23456789; + const NtpTime kNtp(kNtpSec, kNtpFrac); + const uint32_t kNtpMid = 0x56782345; + EXPECT_EQ(kNtpMid, CompactNtp(kNtp)); +} + +TEST(TimeUtilTest, CompactNtpRttToTimeDelta) { + const NtpTime ntp1(0x12345, 0x23456); + const NtpTime ntp2(0x12654, 0x64335); + int64_t ms_diff = ntp2.ToMs() - ntp1.ToMs(); + uint32_t ntp_diff = CompactNtp(ntp2) - CompactNtp(ntp1); + + EXPECT_NEAR(CompactNtpRttToTimeDelta(ntp_diff).ms(), ms_diff, 1); +} + +TEST(TimeUtilTest, CompactNtpRttToTimeDeltaWithWrap) { + const NtpTime ntp1(0x1ffff, 0x23456); + const NtpTime ntp2(0x20000, 0x64335); + int64_t ms_diff = ntp2.ToMs() - ntp1.ToMs(); + + // While ntp2 > ntp1, there compact ntp presentation happen to be opposite. + // That shouldn't be a problem as long as unsigned arithmetic is used. + ASSERT_GT(ntp2.ToMs(), ntp1.ToMs()); + ASSERT_LT(CompactNtp(ntp2), CompactNtp(ntp1)); + + uint32_t ntp_diff = CompactNtp(ntp2) - CompactNtp(ntp1); + EXPECT_NEAR(CompactNtpRttToTimeDelta(ntp_diff).ms(), ms_diff, 1); +} + +TEST(TimeUtilTest, CompactNtpRttToTimeDeltaLarge) { + const NtpTime ntp1(0x10000, 0x00006); + const NtpTime ntp2(0x17fff, 0xffff5); + int64_t ms_diff = ntp2.ToMs() - ntp1.ToMs(); + // Ntp difference close to 2^15 seconds should convert correctly too. + ASSERT_NEAR(ms_diff, ((1 << 15) - 1) * 1000, 1); + uint32_t ntp_diff = CompactNtp(ntp2) - CompactNtp(ntp1); + EXPECT_NEAR(CompactNtpRttToTimeDelta(ntp_diff).ms(), ms_diff, 1); +} + +TEST(TimeUtilTest, CompactNtpRttToTimeDeltaNegative) { + const NtpTime ntp1(0x20000, 0x23456); + const NtpTime ntp2(0x1ffff, 0x64335); + int64_t ms_diff = ntp2.ToMs() - ntp1.ToMs(); + ASSERT_GT(0, ms_diff); + // Ntp difference close to 2^16 seconds should be treated as negative. + uint32_t ntp_diff = CompactNtp(ntp2) - CompactNtp(ntp1); + EXPECT_EQ(CompactNtpRttToTimeDelta(ntp_diff), TimeDelta::Millis(1)); +} + +TEST(TimeUtilTest, SaturatedToCompactNtp) { + // Converts negative to zero. + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Micros(-1)), 0u); + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Zero()), 0u); + // Converts values just above and just below max uint32_t. + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Micros(65536000000)), 0xffffffff); + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Micros(65535999985)), 0xffffffff); + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Micros(65535999970)), 0xfffffffe); + // Converts half-seconds. + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Millis(500)), 0x8000u); + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Seconds(1)), 0x10000u); + EXPECT_EQ(SaturatedToCompactNtp(TimeDelta::Millis(1'500)), 0x18000u); + // Convert us -> compact_ntp -> TimeDelta. Compact ntp precision is ~15us. + EXPECT_NEAR( + CompactNtpRttToTimeDelta(SaturatedToCompactNtp(TimeDelta::Micros(1'516))) + .us(), + 1'516, 16); + EXPECT_NEAR( + CompactNtpRttToTimeDelta(SaturatedToCompactNtp(TimeDelta::Millis(15))) + .us(), + 15'000, 16); + EXPECT_NEAR( + CompactNtpRttToTimeDelta(SaturatedToCompactNtp(TimeDelta::Micros(5'485))) + .us(), + 5'485, 16); + EXPECT_NEAR( + CompactNtpRttToTimeDelta(SaturatedToCompactNtp(TimeDelta::Micros(5'515))) + .us(), + 5'515, 16); +} + +TEST(TimeUtilTest, ToNtpUnits) { + EXPECT_EQ(ToNtpUnits(TimeDelta::Zero()), 0); + EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(1)), int64_t{1} << 32); + EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(-1)), -(int64_t{1} << 32)); + + EXPECT_EQ(ToNtpUnits(TimeDelta::Millis(500)), int64_t{1} << 31); + EXPECT_EQ(ToNtpUnits(TimeDelta::Millis(-1'500)), -(int64_t{3} << 31)); + + // Smallest TimeDelta that can be converted without precision loss. + EXPECT_EQ(ToNtpUnits(TimeDelta::Micros(15'625)), int64_t{1} << 26); + + // 1 us ~= 4'294.97 NTP units. ToNtpUnits makes no rounding promises. + EXPECT_GE(ToNtpUnits(TimeDelta::Micros(1)), 4'294); + EXPECT_LE(ToNtpUnits(TimeDelta::Micros(1)), 4'295); + + // Test near maximum and minimum supported values. + static constexpr int64_t k35MinutesInNtpUnits = int64_t{35 * 60} << 32; + EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(35 * 60)), k35MinutesInNtpUnits); + EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(-35 * 60)), -k35MinutesInNtpUnits); + + // The result for too large or too small values is unspecified, but + // shouldn't cause integer overflow or other undefined behavior. + ToNtpUnits(TimeDelta::Micros(std::numeric_limits<int64_t>::max() - 1)); + ToNtpUnits(TimeDelta::Micros(std::numeric_limits<int64_t>::min() + 1)); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.cc new file mode 100644 index 0000000000..569ed4d8e0 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.cc @@ -0,0 +1,184 @@ +/* + * 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 "modules/rtp_rtcp/source/tmmbr_help.h" + +#include <stddef.h> + +#include <limits> + +#include "absl/algorithm/container.h" +#include "rtc_base/checks.h" + +namespace webrtc { +std::vector<rtcp::TmmbItem> TMMBRHelp::FindBoundingSet( + std::vector<rtcp::TmmbItem> candidates) { + // Filter out candidates with 0 bitrate. + for (auto it = candidates.begin(); it != candidates.end();) { + if (!it->bitrate_bps()) + it = candidates.erase(it); + else + ++it; + } + + if (candidates.size() <= 1) + return candidates; + + size_t num_candidates = candidates.size(); + + // 1. Sort by increasing packet overhead. + absl::c_sort(candidates, + [](const rtcp::TmmbItem& lhs, const rtcp::TmmbItem& rhs) { + return lhs.packet_overhead() < rhs.packet_overhead(); + }); + + // 2. For tuples with same overhead, keep the one with the lowest bitrate. + for (auto it = candidates.begin(); it != candidates.end();) { + RTC_DCHECK(it->bitrate_bps()); + auto current_min = it; + auto next_it = it + 1; + // Use fact candidates are sorted by overhead, so candidates with same + // overhead are adjusted. + while (next_it != candidates.end() && + next_it->packet_overhead() == current_min->packet_overhead()) { + if (next_it->bitrate_bps() < current_min->bitrate_bps()) { + current_min->set_bitrate_bps(0); + current_min = next_it; + } else { + next_it->set_bitrate_bps(0); + } + ++next_it; + --num_candidates; + } + it = next_it; + } + + // 3. Select and remove tuple with lowest bitrate. + // (If more than 1, choose the one with highest overhead). + auto min_bitrate_it = candidates.end(); + for (auto it = candidates.begin(); it != candidates.end(); ++it) { + if (it->bitrate_bps()) { + min_bitrate_it = it; + break; + } + } + + for (auto it = min_bitrate_it; it != candidates.end(); ++it) { + if (it->bitrate_bps() && + it->bitrate_bps() <= min_bitrate_it->bitrate_bps()) { + // Get min bitrate. + min_bitrate_it = it; + } + } + + std::vector<rtcp::TmmbItem> bounding_set; + bounding_set.reserve(num_candidates); + std::vector<float> intersection(num_candidates); + std::vector<float> max_packet_rate(num_candidates); + + // First member of selected list. + bounding_set.push_back(*min_bitrate_it); + intersection[0] = 0; + // Calculate its maximum packet rate (where its line crosses x-axis). + uint16_t packet_overhead = bounding_set.back().packet_overhead(); + if (packet_overhead == 0) { + // Avoid division by zero. + max_packet_rate[0] = std::numeric_limits<float>::max(); + } else { + max_packet_rate[0] = + bounding_set.back().bitrate_bps() / static_cast<float>(packet_overhead); + } + // Remove from candidate list. + min_bitrate_it->set_bitrate_bps(0); + --num_candidates; + + // 4. Discard from candidate list all tuple with lower overhead + // (next tuple must be steeper). + for (auto it = candidates.begin(); it != candidates.end(); ++it) { + if (it->bitrate_bps() && + it->packet_overhead() < bounding_set.front().packet_overhead()) { + it->set_bitrate_bps(0); + --num_candidates; + } + } + + bool get_new_candidate = true; + rtcp::TmmbItem cur_candidate; + while (num_candidates > 0) { + if (get_new_candidate) { + // 5. Remove first remaining tuple from candidate list. + for (auto it = candidates.begin(); it != candidates.end(); ++it) { + if (it->bitrate_bps()) { + cur_candidate = *it; + it->set_bitrate_bps(0); + break; + } + } + } + + // 6. Calculate packet rate and intersection of the current + // line with line of last tuple in selected list. + RTC_DCHECK_NE(cur_candidate.packet_overhead(), + bounding_set.back().packet_overhead()); + float packet_rate = static_cast<float>(cur_candidate.bitrate_bps() - + bounding_set.back().bitrate_bps()) / + (cur_candidate.packet_overhead() - + bounding_set.back().packet_overhead()); + + // 7. If the packet rate is equal or lower than intersection of + // last tuple in selected list, + // remove last tuple in selected list & go back to step 6. + if (packet_rate <= intersection[bounding_set.size() - 1]) { + // Remove last tuple and goto step 6. + bounding_set.pop_back(); + get_new_candidate = false; + } else { + // 8. If packet rate is lower than maximum packet rate of + // last tuple in selected list, add current tuple to selected + // list. + if (packet_rate < max_packet_rate[bounding_set.size() - 1]) { + bounding_set.push_back(cur_candidate); + intersection[bounding_set.size() - 1] = packet_rate; + uint16_t packet_overhead = bounding_set.back().packet_overhead(); + RTC_DCHECK_NE(packet_overhead, 0); + max_packet_rate[bounding_set.size() - 1] = + bounding_set.back().bitrate_bps() / + static_cast<float>(packet_overhead); + } + --num_candidates; + get_new_candidate = true; + } + + // 9. Go back to step 5 if any tuple remains in candidate list. + } + RTC_DCHECK(!bounding_set.empty()); + return bounding_set; +} + +bool TMMBRHelp::IsOwner(const std::vector<rtcp::TmmbItem>& bounding, + uint32_t ssrc) { + for (const rtcp::TmmbItem& item : bounding) { + if (item.ssrc() == ssrc) { + return true; + } + } + return false; +} + +uint64_t TMMBRHelp::CalcMinBitrateBps( + const std::vector<rtcp::TmmbItem>& candidates) { + RTC_DCHECK(!candidates.empty()); + uint64_t min_bitrate_bps = std::numeric_limits<uint64_t>::max(); + for (const rtcp::TmmbItem& item : candidates) + if (item.bitrate_bps() < min_bitrate_bps) + min_bitrate_bps = item.bitrate_bps(); + return min_bitrate_bps; +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.h b/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.h new file mode 100644 index 0000000000..8c26b22eb7 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.h @@ -0,0 +1,35 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_TMMBR_HELP_H_ +#define MODULES_RTP_RTCP_SOURCE_TMMBR_HELP_H_ + +#include <stdint.h> + +#include <vector> + +#include "modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h" + +namespace webrtc { + +class TMMBRHelp { + public: + static std::vector<rtcp::TmmbItem> FindBoundingSet( + std::vector<rtcp::TmmbItem> candidates); + + static bool IsOwner(const std::vector<rtcp::TmmbItem>& bounding, + uint32_t ssrc); + + static uint64_t CalcMinBitrateBps( + const std::vector<rtcp::TmmbItem>& candidates); +}; +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_TMMBR_HELP_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.cc new file mode 100644 index 0000000000..20402fc4d3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.cc @@ -0,0 +1,268 @@ +/* + * 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 "modules/rtp_rtcp/source/ulpfec_generator.h" + +#include <string.h> + +#include <cstdint> +#include <memory> +#include <utility> + +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/checks.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +namespace { + +constexpr size_t kRedForFecHeaderLength = 1; + +// This controls the maximum amount of excess overhead (actual - target) +// allowed in order to trigger EncodeFec(), before `params_.max_fec_frames` +// is reached. Overhead here is defined as relative to number of media packets. +constexpr int kMaxExcessOverhead = 50; // Q8. + +// This is the minimum number of media packets required (above some protection +// level) in order to trigger EncodeFec(), before `params_.max_fec_frames` is +// reached. +constexpr size_t kMinMediaPackets = 4; + +// Threshold on the received FEC protection level, above which we enforce at +// least `kMinMediaPackets` packets for the FEC code. Below this +// threshold `kMinMediaPackets` is set to default value of 1. +// +// The range is between 0 and 255, where 255 corresponds to 100% overhead +// (relative to the number of protected media packets). +constexpr uint8_t kHighProtectionThreshold = 80; + +// This threshold is used to adapt the `kMinMediaPackets` threshold, based +// on the average number of packets per frame seen so far. When there are few +// packets per frame (as given by this threshold), at least +// `kMinMediaPackets` + 1 packets are sent to the FEC code. +constexpr float kMinMediaPacketsAdaptationThreshold = 2.0f; + +// At construction time, we don't know the SSRC that is used for the generated +// FEC packets, but we still need to give it to the ForwardErrorCorrection ctor +// to be used in the decoding. +// TODO(brandtr): Get rid of this awkwardness by splitting +// ForwardErrorCorrection in two objects -- one encoder and one decoder. +constexpr uint32_t kUnknownSsrc = 0; + +} // namespace + +UlpfecGenerator::Params::Params() = default; +UlpfecGenerator::Params::Params(FecProtectionParams delta_params, + FecProtectionParams keyframe_params) + : delta_params(delta_params), keyframe_params(keyframe_params) {} + +UlpfecGenerator::UlpfecGenerator(int red_payload_type, + int ulpfec_payload_type, + Clock* clock) + : red_payload_type_(red_payload_type), + ulpfec_payload_type_(ulpfec_payload_type), + clock_(clock), + fec_(ForwardErrorCorrection::CreateUlpfec(kUnknownSsrc)), + num_protected_frames_(0), + min_num_media_packets_(1), + media_contains_keyframe_(false), + fec_bitrate_(/*max_window_size_ms=*/1000, RateStatistics::kBpsScale) {} + +// Used by FlexFecSender, payload types are unused. +UlpfecGenerator::UlpfecGenerator(std::unique_ptr<ForwardErrorCorrection> fec, + Clock* clock) + : red_payload_type_(0), + ulpfec_payload_type_(0), + clock_(clock), + fec_(std::move(fec)), + num_protected_frames_(0), + min_num_media_packets_(1), + media_contains_keyframe_(false), + fec_bitrate_(/*max_window_size_ms=*/1000, RateStatistics::kBpsScale) {} + +UlpfecGenerator::~UlpfecGenerator() = default; + +void UlpfecGenerator::SetProtectionParameters( + const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) { + RTC_DCHECK_GE(delta_params.fec_rate, 0); + RTC_DCHECK_LE(delta_params.fec_rate, 255); + RTC_DCHECK_GE(key_params.fec_rate, 0); + RTC_DCHECK_LE(key_params.fec_rate, 255); + // Store the new params and apply them for the next set of FEC packets being + // produced. + MutexLock lock(&mutex_); + pending_params_.emplace(delta_params, key_params); +} + +void UlpfecGenerator::AddPacketAndGenerateFec(const RtpPacketToSend& packet) { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + RTC_DCHECK(generated_fec_packets_.empty()); + + { + MutexLock lock(&mutex_); + if (pending_params_) { + current_params_ = *pending_params_; + pending_params_.reset(); + + if (CurrentParams().fec_rate > kHighProtectionThreshold) { + min_num_media_packets_ = kMinMediaPackets; + } else { + min_num_media_packets_ = 1; + } + } + } + + if (packet.is_key_frame()) { + media_contains_keyframe_ = true; + } + const bool complete_frame = packet.Marker(); + if (media_packets_.size() < kUlpfecMaxMediaPackets) { + // Our packet masks can only protect up to `kUlpfecMaxMediaPackets` packets. + auto fec_packet = std::make_unique<ForwardErrorCorrection::Packet>(); + fec_packet->data = packet.Buffer(); + media_packets_.push_back(std::move(fec_packet)); + + // Keep a copy of the last RTP packet, so we can copy the RTP header + // from it when creating newly generated ULPFEC+RED packets. + RTC_DCHECK_GE(packet.headers_size(), kRtpHeaderSize); + last_media_packet_ = packet; + } + + if (complete_frame) { + ++num_protected_frames_; + } + + auto params = CurrentParams(); + + // Produce FEC over at most `params_.max_fec_frames` frames, or as soon as: + // (1) the excess overhead (actual overhead - requested/target overhead) is + // less than `kMaxExcessOverhead`, and + // (2) at least `min_num_media_packets_` media packets is reached. + if (complete_frame && + (num_protected_frames_ >= params.max_fec_frames || + (ExcessOverheadBelowMax() && MinimumMediaPacketsReached()))) { + // We are not using Unequal Protection feature of the parity erasure code. + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + fec_->EncodeFec(media_packets_, params.fec_rate, kNumImportantPackets, + kUseUnequalProtection, params.fec_mask_type, + &generated_fec_packets_); + if (generated_fec_packets_.empty()) { + ResetState(); + } + } +} + +bool UlpfecGenerator::ExcessOverheadBelowMax() const { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + + return ((Overhead() - CurrentParams().fec_rate) < kMaxExcessOverhead); +} + +bool UlpfecGenerator::MinimumMediaPacketsReached() const { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + float average_num_packets_per_frame = + static_cast<float>(media_packets_.size()) / num_protected_frames_; + int num_media_packets = static_cast<int>(media_packets_.size()); + if (average_num_packets_per_frame < kMinMediaPacketsAdaptationThreshold) { + return num_media_packets >= min_num_media_packets_; + } else { + // For larger rates (more packets/frame), increase the threshold. + // TODO(brandtr): Investigate what impact this adaptation has. + return num_media_packets >= min_num_media_packets_ + 1; + } +} + +const FecProtectionParams& UlpfecGenerator::CurrentParams() const { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + return media_contains_keyframe_ ? current_params_.keyframe_params + : current_params_.delta_params; +} + +size_t UlpfecGenerator::MaxPacketOverhead() const { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + return fec_->MaxPacketOverhead(); +} + +std::vector<std::unique_ptr<RtpPacketToSend>> UlpfecGenerator::GetFecPackets() { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + if (generated_fec_packets_.empty()) { + return std::vector<std::unique_ptr<RtpPacketToSend>>(); + } + + // Wrap FEC packet (including FEC headers) in a RED packet. Since the + // FEC packets in `generated_fec_packets_` don't have RTP headers, we + // reuse the header from the last media packet. + RTC_CHECK(last_media_packet_.has_value()); + last_media_packet_->SetPayloadSize(0); + + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets; + fec_packets.reserve(generated_fec_packets_.size()); + + size_t total_fec_size_bytes = 0; + for (const auto* fec_packet : generated_fec_packets_) { + std::unique_ptr<RtpPacketToSend> red_packet = + std::make_unique<RtpPacketToSend>(*last_media_packet_); + red_packet->SetPayloadType(red_payload_type_); + red_packet->SetMarker(false); + uint8_t* payload_buffer = red_packet->SetPayloadSize( + kRedForFecHeaderLength + fec_packet->data.size()); + // Primary RED header with F bit unset. + // See https://tools.ietf.org/html/rfc2198#section-3 + payload_buffer[0] = ulpfec_payload_type_; // RED header. + memcpy(&payload_buffer[1], fec_packet->data.data(), + fec_packet->data.size()); + total_fec_size_bytes += red_packet->size(); + red_packet->set_packet_type(RtpPacketMediaType::kForwardErrorCorrection); + red_packet->set_allow_retransmission(false); + red_packet->set_is_red(true); + red_packet->set_fec_protect_packet(false); + fec_packets.push_back(std::move(red_packet)); + } + + ResetState(); + + MutexLock lock(&mutex_); + fec_bitrate_.Update(total_fec_size_bytes, clock_->TimeInMilliseconds()); + + return fec_packets; +} + +DataRate UlpfecGenerator::CurrentFecRate() const { + MutexLock lock(&mutex_); + return DataRate::BitsPerSec( + fec_bitrate_.Rate(clock_->TimeInMilliseconds()).value_or(0)); +} + +int UlpfecGenerator::Overhead() const { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + RTC_DCHECK(!media_packets_.empty()); + int num_fec_packets = + fec_->NumFecPackets(media_packets_.size(), CurrentParams().fec_rate); + + // Return the overhead in Q8. + return (num_fec_packets << 8) / media_packets_.size(); +} + +void UlpfecGenerator::ResetState() { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + media_packets_.clear(); + last_media_packet_.reset(); + generated_fec_packets_.clear(); + num_protected_frames_ = 0; + media_contains_keyframe_ = false; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.h b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.h new file mode 100644 index 0000000000..88a8b459e6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.h @@ -0,0 +1,123 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_ULPFEC_GENERATOR_H_ +#define MODULES_RTP_RTCP_SOURCE_ULPFEC_GENERATOR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <list> +#include <memory> +#include <vector> + +#include "modules/include/module_fec_types.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/video_fec_generator.h" +#include "rtc_base/race_checker.h" +#include "rtc_base/rate_statistics.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { + +class FlexfecSender; + +class UlpfecGenerator : public VideoFecGenerator { + friend class FlexfecSender; + + public: + UlpfecGenerator(int red_payload_type, int ulpfec_payload_type, Clock* clock); + ~UlpfecGenerator(); + + FecType GetFecType() const override { + return VideoFecGenerator::FecType::kUlpFec; + } + absl::optional<uint32_t> FecSsrc() override { return absl::nullopt; } + + void SetProtectionParameters(const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) override; + + // Adds a media packet to the internal buffer. When enough media packets + // have been added, the FEC packets are generated and stored internally. + // These FEC packets are then obtained by calling GetFecPacketsAsRed(). + void AddPacketAndGenerateFec(const RtpPacketToSend& packet) override; + + // Returns the overhead, per packet, for FEC (and possibly RED). + size_t MaxPacketOverhead() const override; + + std::vector<std::unique_ptr<RtpPacketToSend>> GetFecPackets() override; + + // Current rate of FEC packets generated, including all RTP-level headers. + DataRate CurrentFecRate() const override; + + absl::optional<RtpState> GetRtpState() override { return absl::nullopt; } + + // Currently used protection params. + const FecProtectionParams& CurrentParams() const; + + private: + struct Params { + Params(); + Params(FecProtectionParams delta_params, + FecProtectionParams keyframe_params); + + FecProtectionParams delta_params; + FecProtectionParams keyframe_params; + }; + + UlpfecGenerator(std::unique_ptr<ForwardErrorCorrection> fec, Clock* clock); + + // Overhead is defined as relative to the number of media packets, and not + // relative to total number of packets. This definition is inherited from the + // protection factor produced by video_coding module and how the FEC + // generation is implemented. + int Overhead() const; + + // Returns true if the excess overhead (actual - target) for the FEC is below + // the amount `kMaxExcessOverhead`. This effects the lower protection level + // cases and low number of media packets/frame. The target overhead is given + // by `params_.fec_rate`, and is only achievable in the limit of large number + // of media packets. + bool ExcessOverheadBelowMax() const; + + // Returns true if the number of added media packets is at least + // `min_num_media_packets_`. This condition tries to capture the effect + // that, for the same amount of protection/overhead, longer codes + // (e.g. (2k,2m) vs (k,m)) are generally more effective at recovering losses. + bool MinimumMediaPacketsReached() const; + + void ResetState(); + + const int red_payload_type_; + const int ulpfec_payload_type_; + Clock* const clock_; + + rtc::RaceChecker race_checker_; + const std::unique_ptr<ForwardErrorCorrection> fec_ + RTC_GUARDED_BY(race_checker_); + ForwardErrorCorrection::PacketList media_packets_ + RTC_GUARDED_BY(race_checker_); + absl::optional<RtpPacketToSend> last_media_packet_ + RTC_GUARDED_BY(race_checker_); + std::list<ForwardErrorCorrection::Packet*> generated_fec_packets_ + RTC_GUARDED_BY(race_checker_); + int num_protected_frames_ RTC_GUARDED_BY(race_checker_); + int min_num_media_packets_ RTC_GUARDED_BY(race_checker_); + Params current_params_ RTC_GUARDED_BY(race_checker_); + bool media_contains_keyframe_ RTC_GUARDED_BY(race_checker_); + + mutable Mutex mutex_; + absl::optional<Params> pending_params_ RTC_GUARDED_BY(mutex_); + RateStatistics fec_bitrate_ RTC_GUARDED_BY(mutex_); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_ULPFEC_GENERATOR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator_unittest.cc new file mode 100644 index 0000000000..18f5685791 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator_unittest.cc @@ -0,0 +1,273 @@ +/* + * 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 "modules/rtp_rtcp/source/ulpfec_generator.h" + +#include <list> +#include <memory> +#include <utility> +#include <vector> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/fec_test_helper.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { +using test::fec::AugmentedPacket; +using test::fec::AugmentedPacketGenerator; + +constexpr int kFecPayloadType = 96; +constexpr int kRedPayloadType = 97; +constexpr uint32_t kMediaSsrc = 835424; +} // namespace + +void VerifyHeader(uint16_t seq_num, + uint32_t timestamp, + int red_payload_type, + int fec_payload_type, + bool marker_bit, + const rtc::CopyOnWriteBuffer& data) { + // Marker bit not set. + EXPECT_EQ(marker_bit ? 0x80 : 0, data[1] & 0x80); + EXPECT_EQ(red_payload_type, data[1] & 0x7F); + EXPECT_EQ(seq_num, (data[2] << 8) + data[3]); + uint32_t parsed_timestamp = + (data[4] << 24) + (data[5] << 16) + (data[6] << 8) + data[7]; + EXPECT_EQ(timestamp, parsed_timestamp); + EXPECT_EQ(static_cast<uint8_t>(fec_payload_type), data[kRtpHeaderSize]); +} + +class UlpfecGeneratorTest : public ::testing::Test { + protected: + UlpfecGeneratorTest() + : fake_clock_(1), + ulpfec_generator_(kRedPayloadType, kFecPayloadType, &fake_clock_), + packet_generator_(kMediaSsrc) {} + + SimulatedClock fake_clock_; + UlpfecGenerator ulpfec_generator_; + AugmentedPacketGenerator packet_generator_; +}; + +// Verifies bug found via fuzzing, where a gap in the packet sequence caused us +// to move past the end of the current FEC packet mask byte without moving to +// the next byte. That likely caused us to repeatedly read from the same byte, +// and if that byte didn't protect packets we would generate empty FEC. +TEST_F(UlpfecGeneratorTest, NoEmptyFecWithSeqNumGaps) { + struct Packet { + size_t header_size; + size_t payload_size; + uint16_t seq_num; + bool marker_bit; + }; + std::vector<Packet> protected_packets; + protected_packets.push_back({15, 3, 41, 0}); + protected_packets.push_back({14, 1, 43, 0}); + protected_packets.push_back({19, 0, 48, 0}); + protected_packets.push_back({19, 0, 50, 0}); + protected_packets.push_back({14, 3, 51, 0}); + protected_packets.push_back({13, 8, 52, 0}); + protected_packets.push_back({19, 2, 53, 0}); + protected_packets.push_back({12, 3, 54, 0}); + protected_packets.push_back({21, 0, 55, 0}); + protected_packets.push_back({13, 3, 57, 1}); + FecProtectionParams params = {117, 3, kFecMaskBursty}; + ulpfec_generator_.SetProtectionParameters(params, params); + for (Packet p : protected_packets) { + RtpPacketToSend packet(nullptr); + packet.SetMarker(p.marker_bit); + packet.AllocateExtension(RTPExtensionType::kRtpExtensionMid, + p.header_size - packet.headers_size()); + packet.SetSequenceNumber(p.seq_num); + packet.AllocatePayload(p.payload_size); + ulpfec_generator_.AddPacketAndGenerateFec(packet); + + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + ulpfec_generator_.GetFecPackets(); + if (!p.marker_bit) { + EXPECT_TRUE(fec_packets.empty()); + } else { + EXPECT_FALSE(fec_packets.empty()); + } + } +} + +TEST_F(UlpfecGeneratorTest, OneFrameFec) { + // The number of media packets (`kNumPackets`), number of frames (one for + // this test), and the protection factor (|params->fec_rate|) are set to make + // sure the conditions for generating FEC are satisfied. This means: + // (1) protection factor is high enough so that actual overhead over 1 frame + // of packets is within `kMaxExcessOverhead`, and (2) the total number of + // media packets for 1 frame is at least `minimum_media_packets_fec_`. + constexpr size_t kNumPackets = 4; + FecProtectionParams params = {15, 3, kFecMaskRandom}; + packet_generator_.NewFrame(kNumPackets); + // Expecting one FEC packet. + ulpfec_generator_.SetProtectionParameters(params, params); + uint32_t last_timestamp = 0; + for (size_t i = 0; i < kNumPackets; ++i) { + std::unique_ptr<AugmentedPacket> packet = + packet_generator_.NextPacket(i, 10); + RtpPacketToSend rtp_packet(nullptr); + EXPECT_TRUE(rtp_packet.Parse(packet->data.data(), packet->data.size())); + ulpfec_generator_.AddPacketAndGenerateFec(rtp_packet); + last_timestamp = packet->header.timestamp; + } + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + ulpfec_generator_.GetFecPackets(); + EXPECT_EQ(fec_packets.size(), 1u); + uint16_t seq_num = packet_generator_.NextPacketSeqNum(); + fec_packets[0]->SetSequenceNumber(seq_num); + EXPECT_TRUE(ulpfec_generator_.GetFecPackets().empty()); + + EXPECT_EQ(fec_packets[0]->headers_size(), kRtpHeaderSize); + + VerifyHeader(seq_num, last_timestamp, kRedPayloadType, kFecPayloadType, false, + fec_packets[0]->Buffer()); +} + +TEST_F(UlpfecGeneratorTest, TwoFrameFec) { + // The number of media packets/frame (`kNumPackets`), the number of frames + // (`kNumFrames`), and the protection factor (|params->fec_rate|) are set to + // make sure the conditions for generating FEC are satisfied. This means: + // (1) protection factor is high enough so that actual overhead over + // `kNumFrames` is within `kMaxExcessOverhead`, and (2) the total number of + // media packets for `kNumFrames` frames is at least + // `minimum_media_packets_fec_`. + constexpr size_t kNumPackets = 2; + constexpr size_t kNumFrames = 2; + + FecProtectionParams params = {15, 3, kFecMaskRandom}; + // Expecting one FEC packet. + ulpfec_generator_.SetProtectionParameters(params, params); + uint32_t last_timestamp = 0; + for (size_t i = 0; i < kNumFrames; ++i) { + packet_generator_.NewFrame(kNumPackets); + for (size_t j = 0; j < kNumPackets; ++j) { + std::unique_ptr<AugmentedPacket> packet = + packet_generator_.NextPacket(i * kNumPackets + j, 10); + RtpPacketToSend rtp_packet(nullptr); + EXPECT_TRUE(rtp_packet.Parse(packet->data.data(), packet->data.size())); + ulpfec_generator_.AddPacketAndGenerateFec(rtp_packet); + last_timestamp = packet->header.timestamp; + } + } + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + ulpfec_generator_.GetFecPackets(); + EXPECT_EQ(fec_packets.size(), 1u); + const uint16_t seq_num = packet_generator_.NextPacketSeqNum(); + fec_packets[0]->SetSequenceNumber(seq_num); + VerifyHeader(seq_num, last_timestamp, kRedPayloadType, kFecPayloadType, false, + fec_packets[0]->Buffer()); +} + +TEST_F(UlpfecGeneratorTest, MixedMediaRtpHeaderLengths) { + constexpr size_t kShortRtpHeaderLength = 12; + constexpr size_t kLongRtpHeaderLength = 16; + + // Only one frame required to generate FEC. + FecProtectionParams params = {127, 1, kFecMaskRandom}; + ulpfec_generator_.SetProtectionParameters(params, params); + + // Fill up internal buffer with media packets with short RTP header length. + packet_generator_.NewFrame(kUlpfecMaxMediaPackets + 1); + for (size_t i = 0; i < kUlpfecMaxMediaPackets; ++i) { + std::unique_ptr<AugmentedPacket> packet = + packet_generator_.NextPacket(i, 10); + RtpPacketToSend rtp_packet(nullptr); + EXPECT_TRUE(rtp_packet.Parse(packet->data.data(), packet->data.size())); + EXPECT_EQ(rtp_packet.headers_size(), kShortRtpHeaderLength); + ulpfec_generator_.AddPacketAndGenerateFec(rtp_packet); + EXPECT_TRUE(ulpfec_generator_.GetFecPackets().empty()); + } + + // Kick off FEC generation with media packet with long RTP header length. + // Since the internal buffer is full, this packet will not be protected. + std::unique_ptr<AugmentedPacket> packet = + packet_generator_.NextPacket(kUlpfecMaxMediaPackets, 10); + RtpPacketToSend rtp_packet(nullptr); + EXPECT_TRUE(rtp_packet.Parse(packet->data.data(), packet->data.size())); + EXPECT_TRUE(rtp_packet.SetPayloadSize(0) != nullptr); + const uint32_t csrcs[]{1}; + rtp_packet.SetCsrcs(csrcs); + + EXPECT_EQ(rtp_packet.headers_size(), kLongRtpHeaderLength); + + ulpfec_generator_.AddPacketAndGenerateFec(rtp_packet); + std::vector<std::unique_ptr<RtpPacketToSend>> fec_packets = + ulpfec_generator_.GetFecPackets(); + EXPECT_FALSE(fec_packets.empty()); + + // Ensure that the RED header is placed correctly, i.e. the correct + // RTP header length was used in the RED packet creation. + uint16_t seq_num = packet_generator_.NextPacketSeqNum(); + for (const auto& fec_packet : fec_packets) { + fec_packet->SetSequenceNumber(seq_num++); + EXPECT_EQ(kFecPayloadType, fec_packet->data()[kShortRtpHeaderLength]); + } +} + +TEST_F(UlpfecGeneratorTest, UpdatesProtectionParameters) { + const FecProtectionParams kKeyFrameParams = {25, /*max_fec_frames=*/2, + kFecMaskRandom}; + const FecProtectionParams kDeltaFrameParams = {25, /*max_fec_frames=*/5, + kFecMaskRandom}; + + ulpfec_generator_.SetProtectionParameters(kDeltaFrameParams, kKeyFrameParams); + + // No params applied yet. + EXPECT_EQ(ulpfec_generator_.CurrentParams().max_fec_frames, 0); + + // Helper function to add a single-packet frame market as either key-frame + // or delta-frame. + auto add_frame = [&](bool is_keyframe) { + packet_generator_.NewFrame(1); + std::unique_ptr<AugmentedPacket> packet = + packet_generator_.NextPacket(0, 10); + RtpPacketToSend rtp_packet(nullptr); + EXPECT_TRUE(rtp_packet.Parse(packet->data.data(), packet->data.size())); + rtp_packet.set_is_key_frame(is_keyframe); + ulpfec_generator_.AddPacketAndGenerateFec(rtp_packet); + }; + + // Add key-frame, keyframe params should apply, no FEC generated yet. + add_frame(true); + EXPECT_EQ(ulpfec_generator_.CurrentParams().max_fec_frames, 2); + EXPECT_TRUE(ulpfec_generator_.GetFecPackets().empty()); + + // Add delta-frame, generated FEC packet. Params will not be updated until + // next added packet though. + add_frame(false); + EXPECT_EQ(ulpfec_generator_.CurrentParams().max_fec_frames, 2); + EXPECT_FALSE(ulpfec_generator_.GetFecPackets().empty()); + + // Add delta-frame, now params get updated. + add_frame(false); + EXPECT_EQ(ulpfec_generator_.CurrentParams().max_fec_frames, 5); + EXPECT_TRUE(ulpfec_generator_.GetFecPackets().empty()); + + // Add yet another delta-frame. + add_frame(false); + EXPECT_EQ(ulpfec_generator_.CurrentParams().max_fec_frames, 5); + EXPECT_TRUE(ulpfec_generator_.GetFecPackets().empty()); + + // Add key-frame, params immediately switch to key-frame ones. The two + // buffered frames plus the key-frame is protected and fec emitted, + // even though the frame count is technically over the keyframe frame count + // threshold. + add_frame(true); + EXPECT_EQ(ulpfec_generator_.CurrentParams().max_fec_frames, 2); + EXPECT_FALSE(ulpfec_generator_.GetFecPackets().empty()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc new file mode 100644 index 0000000000..8378a8f19f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc @@ -0,0 +1,141 @@ +/* + * 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/rtp_rtcp/source/ulpfec_header_reader_writer.h" + +#include <string.h> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { + +// Maximum number of media packets that can be protected in one batch. +constexpr size_t kMaxMediaPackets = 48; + +// Maximum number of media packets tracked by FEC decoder. +// Maintain a sufficiently larger tracking window than `kMaxMediaPackets` +// to account for packet reordering in pacer/ network. +constexpr size_t kMaxTrackedMediaPackets = 4 * kMaxMediaPackets; + +// Maximum number of FEC packets stored inside ForwardErrorCorrection. +constexpr size_t kMaxFecPackets = kMaxMediaPackets; + +// FEC Level 0 header size in bytes. +constexpr size_t kFecLevel0HeaderSize = 10; + +// FEC Level 1 (ULP) header size in bytes (L bit is set). +constexpr size_t kFecLevel1HeaderSizeLBitSet = 2 + kUlpfecPacketMaskSizeLBitSet; + +// FEC Level 1 (ULP) header size in bytes (L bit is cleared). +constexpr size_t kFecLevel1HeaderSizeLBitClear = + 2 + kUlpfecPacketMaskSizeLBitClear; + +constexpr size_t kPacketMaskOffset = kFecLevel0HeaderSize + 2; + +size_t UlpfecHeaderSize(size_t packet_mask_size) { + RTC_DCHECK_LE(packet_mask_size, kUlpfecPacketMaskSizeLBitSet); + if (packet_mask_size <= kUlpfecPacketMaskSizeLBitClear) { + return kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitClear; + } else { + return kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitSet; + } +} + +} // namespace + +UlpfecHeaderReader::UlpfecHeaderReader() + : FecHeaderReader(kMaxTrackedMediaPackets, kMaxFecPackets) {} + +UlpfecHeaderReader::~UlpfecHeaderReader() = default; + +bool UlpfecHeaderReader::ReadFecHeader( + ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const { + uint8_t* data = fec_packet->pkt->data.MutableData(); + if (fec_packet->pkt->data.size() < kPacketMaskOffset) { + return false; // Truncated packet. + } + bool l_bit = (data[0] & 0x40) != 0u; + size_t packet_mask_size = + l_bit ? kUlpfecPacketMaskSizeLBitSet : kUlpfecPacketMaskSizeLBitClear; + fec_packet->fec_header_size = UlpfecHeaderSize(packet_mask_size); + uint16_t seq_num_base = ByteReader<uint16_t>::ReadBigEndian(&data[2]); + fec_packet->protected_ssrc = fec_packet->ssrc; // Due to RED. + fec_packet->seq_num_base = seq_num_base; + fec_packet->packet_mask_offset = kPacketMaskOffset; + fec_packet->packet_mask_size = packet_mask_size; + fec_packet->protection_length = + ByteReader<uint16_t>::ReadBigEndian(&data[10]); + + // Store length recovery field in temporary location in header. + // This makes the header "compatible" with the corresponding + // FlexFEC location of the length recovery field, thus simplifying + // the XORing operations. + memcpy(&data[2], &data[8], 2); + + return true; +} + +UlpfecHeaderWriter::UlpfecHeaderWriter() + : FecHeaderWriter(kMaxMediaPackets, + kMaxFecPackets, + kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitSet) {} + +UlpfecHeaderWriter::~UlpfecHeaderWriter() = default; + +// TODO(brandtr): Consider updating this implementation (which actually +// returns a bound on the sequence number spread), if logic is added to +// UlpfecHeaderWriter::FinalizeFecHeader to truncate packet masks which end +// in a string of zeroes. (Similar to how it is done in the FlexFEC case.) +size_t UlpfecHeaderWriter::MinPacketMaskSize(const uint8_t* packet_mask, + size_t packet_mask_size) const { + return packet_mask_size; +} + +size_t UlpfecHeaderWriter::FecHeaderSize(size_t packet_mask_size) const { + return UlpfecHeaderSize(packet_mask_size); +} + +void UlpfecHeaderWriter::FinalizeFecHeader( + uint32_t /* media_ssrc */, + uint16_t seq_num_base, + const uint8_t* packet_mask, + size_t packet_mask_size, + ForwardErrorCorrection::Packet* fec_packet) const { + uint8_t* data = fec_packet->data.MutableData(); + // Set E bit to zero. + data[0] &= 0x7f; + // Set L bit based on packet mask size. (Note that the packet mask + // can only take on two discrete values.) + bool l_bit = (packet_mask_size == kUlpfecPacketMaskSizeLBitSet); + if (l_bit) { + data[0] |= 0x40; // Set the L bit. + } else { + RTC_DCHECK_EQ(packet_mask_size, kUlpfecPacketMaskSizeLBitClear); + data[0] &= 0xbf; // Clear the L bit. + } + // Copy length recovery field from temporary location. + memcpy(&data[8], &data[2], 2); + // Write sequence number base. + ByteWriter<uint16_t>::WriteBigEndian(&data[2], seq_num_base); + // Protection length is set to entire packet. (This is not + // required in general.) + const size_t fec_header_size = FecHeaderSize(packet_mask_size); + ByteWriter<uint16_t>::WriteBigEndian( + &data[10], fec_packet->data.size() - fec_header_size); + // Copy the packet mask. + memcpy(&data[12], packet_mask, packet_mask_size); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h new file mode 100644 index 0000000000..a8bb737dbb --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h @@ -0,0 +1,69 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_ +#define MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "modules/rtp_rtcp/source/forward_error_correction.h" + +namespace webrtc { + +// FEC Level 0 Header, 10 bytes. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |E|L|P|X| CC |M| PT recovery | SN base | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | TS recovery | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | length recovery | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// FEC Level 1 Header, 4 bytes (L = 0) or 8 bytes (L = 1). +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Protection Length | mask | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | mask cont. (present only when L = 1) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class UlpfecHeaderReader : public FecHeaderReader { + public: + UlpfecHeaderReader(); + ~UlpfecHeaderReader() override; + + bool ReadFecHeader( + ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const override; +}; + +class UlpfecHeaderWriter : public FecHeaderWriter { + public: + UlpfecHeaderWriter(); + ~UlpfecHeaderWriter() override; + + size_t MinPacketMaskSize(const uint8_t* packet_mask, + size_t packet_mask_size) const override; + + size_t FecHeaderSize(size_t packet_mask_row_size) const override; + + void FinalizeFecHeader( + uint32_t media_ssrc, // Unused by ULPFEC. + uint16_t seq_num_base, + const uint8_t* packet_mask, + size_t packet_mask_size, + ForwardErrorCorrection::Packet* fec_packet) const override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc new file mode 100644 index 0000000000..a190a548e4 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc @@ -0,0 +1,245 @@ +/* + * 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/rtp_rtcp/source/ulpfec_header_reader_writer.h" + +#include <string.h> + +#include <memory> +#include <utility> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/checks.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { + +using Packet = ForwardErrorCorrection::Packet; +using ReceivedFecPacket = ForwardErrorCorrection::ReceivedFecPacket; + +constexpr uint32_t kMediaSsrc = 1254983; +constexpr uint16_t kMediaStartSeqNum = 825; +constexpr size_t kMediaPacketLength = 1234; + +constexpr size_t kUlpfecHeaderSizeLBitClear = 14; +constexpr size_t kUlpfecHeaderSizeLBitSet = 18; +constexpr size_t kUlpfecPacketMaskOffset = 12; + +std::unique_ptr<uint8_t[]> GeneratePacketMask(size_t packet_mask_size, + uint64_t seed) { + Random random(seed); + std::unique_ptr<uint8_t[]> packet_mask(new uint8_t[packet_mask_size]); + for (size_t i = 0; i < packet_mask_size; ++i) { + packet_mask[i] = random.Rand<uint8_t>(); + } + return packet_mask; +} + +std::unique_ptr<Packet> WriteHeader(const uint8_t* packet_mask, + size_t packet_mask_size) { + UlpfecHeaderWriter writer; + std::unique_ptr<Packet> written_packet(new Packet()); + written_packet->data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet->data.MutableData(); + for (size_t i = 0; i < written_packet->data.size(); ++i) { + data[i] = i; // Actual content doesn't matter. + } + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, packet_mask, + packet_mask_size, written_packet.get()); + return written_packet; +} + +std::unique_ptr<ReceivedFecPacket> ReadHeader(const Packet& written_packet) { + UlpfecHeaderReader reader; + std::unique_ptr<ReceivedFecPacket> read_packet(new ReceivedFecPacket()); + read_packet->ssrc = kMediaSsrc; + read_packet->pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet->pkt->data = written_packet.data; + EXPECT_TRUE(reader.ReadFecHeader(read_packet.get())); + return read_packet; +} + +void VerifyHeaders(size_t expected_fec_header_size, + const uint8_t* expected_packet_mask, + size_t expected_packet_mask_size, + const Packet& written_packet, + const ReceivedFecPacket& read_packet) { + EXPECT_EQ(kMediaSsrc, read_packet.ssrc); + EXPECT_EQ(expected_fec_header_size, read_packet.fec_header_size); + EXPECT_EQ(kMediaSsrc, read_packet.protected_ssrc); + EXPECT_EQ(kMediaStartSeqNum, read_packet.seq_num_base); + EXPECT_EQ(kUlpfecPacketMaskOffset, read_packet.packet_mask_offset); + ASSERT_EQ(expected_packet_mask_size, read_packet.packet_mask_size); + EXPECT_EQ(written_packet.data.size() - expected_fec_header_size, + read_packet.protection_length); + EXPECT_EQ(0, memcmp(expected_packet_mask, + read_packet.pkt->data.MutableData() + + read_packet.packet_mask_offset, + read_packet.packet_mask_size)); + // Verify that the call to ReadFecHeader did not tamper with the payload. + EXPECT_EQ(0, memcmp(written_packet.data.data() + expected_fec_header_size, + read_packet.pkt->data.cdata() + expected_fec_header_size, + written_packet.data.size() - expected_fec_header_size)); +} + +} // namespace + +TEST(UlpfecHeaderReaderTest, ReadsSmallHeader) { + const uint8_t packet[] = { + 0x00, 0x12, 0xab, 0xcd, // L bit clear, "random" payload type and SN base + 0x12, 0x34, 0x56, 0x78, // "random" TS recovery + 0xab, 0xcd, 0x11, 0x22, // "random" length recovery and protection length + 0x33, 0x44, // "random" packet mask + 0x00, 0x00, 0x00, 0x00 // payload + }; + const size_t packet_length = sizeof(packet); + ReceivedFecPacket read_packet; + read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet.pkt->data.SetData(packet, packet_length); + + UlpfecHeaderReader reader; + EXPECT_TRUE(reader.ReadFecHeader(&read_packet)); + + EXPECT_EQ(14U, read_packet.fec_header_size); + EXPECT_EQ(0xabcdU, read_packet.seq_num_base); + EXPECT_EQ(12U, read_packet.packet_mask_offset); + EXPECT_EQ(2U, read_packet.packet_mask_size); + EXPECT_EQ(0x1122U, read_packet.protection_length); +} + +TEST(UlpfecHeaderReaderTest, ReadsLargeHeader) { + const uint8_t packet[] = { + 0x40, 0x12, 0xab, 0xcd, // L bit set, "random" payload type and SN base + 0x12, 0x34, 0x56, 0x78, // "random" TS recovery + 0xab, 0xcd, 0x11, 0x22, // "random" length recovery and protection length + 0x33, 0x44, 0x55, 0x66, // "random" packet mask + 0x77, 0x88, // + 0x00, 0x00, 0x00, 0x00 // payload + }; + const size_t packet_length = sizeof(packet); + ReceivedFecPacket read_packet; + read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet()); + read_packet.pkt->data.SetData(packet, packet_length); + + UlpfecHeaderReader reader; + EXPECT_TRUE(reader.ReadFecHeader(&read_packet)); + + EXPECT_EQ(18U, read_packet.fec_header_size); + EXPECT_EQ(0xabcdU, read_packet.seq_num_base); + EXPECT_EQ(12U, read_packet.packet_mask_offset); + EXPECT_EQ(6U, read_packet.packet_mask_size); + EXPECT_EQ(0x1122U, read_packet.protection_length); +} + +TEST(UlpfecHeaderWriterTest, FinalizesSmallHeader) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + Packet written_packet; + written_packet.data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet.data.MutableData(); + for (size_t i = 0; i < written_packet.data.size(); ++i) { + data[i] = i; + } + + UlpfecHeaderWriter writer; + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, packet_mask.get(), + packet_mask_size, &written_packet); + + const uint8_t* packet = written_packet.data.cdata(); + EXPECT_EQ(0x00, packet[0] & 0x80); // E bit. + EXPECT_EQ(0x00, packet[0] & 0x40); // L bit. + EXPECT_EQ(kMediaStartSeqNum, ByteReader<uint16_t>::ReadBigEndian(packet + 2)); + EXPECT_EQ( + static_cast<uint16_t>(kMediaPacketLength - kUlpfecHeaderSizeLBitClear), + ByteReader<uint16_t>::ReadBigEndian(packet + 10)); + EXPECT_EQ(0, memcmp(packet + kUlpfecPacketMaskOffset, packet_mask.get(), + packet_mask_size)); +} + +TEST(UlpfecHeaderWriterTest, FinalizesLargeHeader) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + Packet written_packet; + written_packet.data.SetSize(kMediaPacketLength); + uint8_t* data = written_packet.data.MutableData(); + for (size_t i = 0; i < written_packet.data.size(); ++i) { + data[i] = i; + } + + UlpfecHeaderWriter writer; + writer.FinalizeFecHeader(kMediaSsrc, kMediaStartSeqNum, packet_mask.get(), + packet_mask_size, &written_packet); + + const uint8_t* packet = written_packet.data.cdata(); + EXPECT_EQ(0x00, packet[0] & 0x80); // E bit. + EXPECT_EQ(0x40, packet[0] & 0x40); // L bit. + EXPECT_EQ(kMediaStartSeqNum, ByteReader<uint16_t>::ReadBigEndian(packet + 2)); + EXPECT_EQ( + static_cast<uint16_t>(kMediaPacketLength - kUlpfecHeaderSizeLBitSet), + ByteReader<uint16_t>::ReadBigEndian(packet + 10)); + EXPECT_EQ(0, memcmp(packet + kUlpfecPacketMaskOffset, packet_mask.get(), + packet_mask_size)); +} + +TEST(UlpfecHeaderWriterTest, CalculateSmallHeaderSize) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + + UlpfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kUlpfecPacketMaskSizeLBitClear, min_packet_mask_size); + EXPECT_EQ(kUlpfecHeaderSizeLBitClear, + writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(UlpfecHeaderWriterTest, CalculateLargeHeaderSize) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + + UlpfecHeaderWriter writer; + size_t min_packet_mask_size = + writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size); + + EXPECT_EQ(kUlpfecPacketMaskSizeLBitSet, min_packet_mask_size); + EXPECT_EQ(kUlpfecHeaderSizeLBitSet, + writer.FecHeaderSize(min_packet_mask_size)); +} + +TEST(UlpfecHeaderReaderWriterTest, WriteAndReadSmallHeader) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyHeaders(kUlpfecHeaderSizeLBitClear, packet_mask.get(), packet_mask_size, + *written_packet, *read_packet); +} + +TEST(UlpfecHeaderReaderWriterTest, WriteAndReadLargeHeader) { + const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet; + auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd); + + auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size); + auto read_packet = ReadHeader(*written_packet); + + VerifyHeaders(kUlpfecHeaderSizeLBitSet, packet_mask.get(), packet_mask_size, + *written_packet, *read_packet); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.cc new file mode 100644 index 0000000000..4452bdbba5 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.cc @@ -0,0 +1,245 @@ +/* + * 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 "modules/rtp_rtcp/source/ulpfec_receiver.h" + +#include <memory> +#include <utility> + +#include "api/scoped_refptr.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +UlpfecReceiver::UlpfecReceiver(uint32_t ssrc, + int ulpfec_payload_type, + RecoveredPacketReceiver* callback, + Clock* clock) + : ssrc_(ssrc), + ulpfec_payload_type_(ulpfec_payload_type), + clock_(clock), + recovered_packet_callback_(callback), + fec_(ForwardErrorCorrection::CreateUlpfec(ssrc_)) { + // TODO(tommi, brandtr): Once considerations for red have been split + // away from this implementation, we can require the ulpfec payload type + // to always be valid and use uint8 for storage (as is done elsewhere). + RTC_DCHECK_GE(ulpfec_payload_type_, -1); +} + +UlpfecReceiver::~UlpfecReceiver() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + if (packet_counter_.first_packet_time != Timestamp::MinusInfinity()) { + const Timestamp now = clock_->CurrentTime(); + TimeDelta elapsed = (now - packet_counter_.first_packet_time); + if (elapsed.seconds() >= metrics::kMinRunTimeInSeconds) { + if (packet_counter_.num_packets > 0) { + RTC_HISTOGRAM_PERCENTAGE( + "WebRTC.Video.ReceivedFecPacketsInPercent", + static_cast<int>(packet_counter_.num_fec_packets * 100 / + packet_counter_.num_packets)); + } + if (packet_counter_.num_fec_packets > 0) { + RTC_HISTOGRAM_PERCENTAGE( + "WebRTC.Video.RecoveredMediaPacketsInPercentOfFec", + static_cast<int>(packet_counter_.num_recovered_packets * 100 / + packet_counter_.num_fec_packets)); + } + if (ulpfec_payload_type_ != -1) { + RTC_HISTOGRAM_COUNTS_10000( + "WebRTC.Video.FecBitrateReceivedInKbps", + static_cast<int>(packet_counter_.num_bytes * 8 / elapsed.seconds() / + 1000)); + } + } + } + + received_packets_.clear(); + fec_->ResetState(&recovered_packets_); +} + +FecPacketCounter UlpfecReceiver::GetPacketCounter() const { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return packet_counter_; +} + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |F| block PT | timestamp offset | block length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// +// RFC 2198 RTP Payload for Redundant Audio Data September 1997 +// +// The bits in the header are specified as follows: +// +// F: 1 bit First bit in header indicates whether another header block +// follows. If 1 further header blocks follow, if 0 this is the +// last header block. +// If 0 there is only 1 byte RED header +// +// block PT: 7 bits RTP payload type for this block. +// +// timestamp offset: 14 bits Unsigned offset of timestamp of this block +// relative to timestamp given in RTP header. The use of an unsigned +// offset implies that redundant data must be sent after the primary +// data, and is hence a time to be subtracted from the current +// timestamp to determine the timestamp of the data for which this +// block is the redundancy. +// +// block length: 10 bits Length in bytes of the corresponding data +// block excluding header. + +bool UlpfecReceiver::AddReceivedRedPacket(const RtpPacketReceived& rtp_packet) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + // TODO(bugs.webrtc.org/11993): We get here via Call::DeliverRtp, so should be + // moved to the network thread. + + if (rtp_packet.Ssrc() != ssrc_) { + RTC_LOG(LS_WARNING) + << "Received RED packet with different SSRC than expected; dropping."; + return false; + } + if (rtp_packet.size() > IP_PACKET_SIZE) { + RTC_LOG(LS_WARNING) << "Received RED packet with length exceeds maximum IP " + "packet size; dropping."; + return false; + } + + static constexpr uint8_t kRedHeaderLength = 1; + + if (rtp_packet.payload_size() == 0) { + RTC_LOG(LS_WARNING) << "Corrupt/truncated FEC packet."; + return false; + } + + // Remove RED header of incoming packet and store as a virtual RTP packet. + auto received_packet = + std::make_unique<ForwardErrorCorrection::ReceivedPacket>(); + received_packet->pkt = new ForwardErrorCorrection::Packet(); + + // Get payload type from RED header and sequence number from RTP header. + uint8_t payload_type = rtp_packet.payload()[0] & 0x7f; + received_packet->is_fec = payload_type == ulpfec_payload_type_; + received_packet->is_recovered = rtp_packet.recovered(); + received_packet->ssrc = rtp_packet.Ssrc(); + received_packet->seq_num = rtp_packet.SequenceNumber(); + received_packet->extensions = rtp_packet.extension_manager(); + + if (rtp_packet.payload()[0] & 0x80) { + // f bit set in RED header, i.e. there are more than one RED header blocks. + // WebRTC never generates multiple blocks in a RED packet for FEC. + RTC_LOG(LS_WARNING) << "More than 1 block in RED packet is not supported."; + return false; + } + + ++packet_counter_.num_packets; + packet_counter_.num_bytes += rtp_packet.size(); + if (packet_counter_.first_packet_time == Timestamp::MinusInfinity()) { + packet_counter_.first_packet_time = clock_->CurrentTime(); + } + + if (received_packet->is_fec) { + ++packet_counter_.num_fec_packets; + // everything behind the RED header + received_packet->pkt->data = + rtp_packet.Buffer().Slice(rtp_packet.headers_size() + kRedHeaderLength, + rtp_packet.payload_size() - kRedHeaderLength); + } else { + received_packet->pkt->data.EnsureCapacity(rtp_packet.size() - + kRedHeaderLength); + // Copy RTP header. + received_packet->pkt->data.SetData(rtp_packet.data(), + rtp_packet.headers_size()); + // Set payload type. + uint8_t& payload_type_byte = received_packet->pkt->data.MutableData()[1]; + payload_type_byte &= 0x80; // Reset RED payload type. + payload_type_byte += payload_type; // Set media payload type. + // Copy payload and padding data, after the RED header. + received_packet->pkt->data.AppendData( + rtp_packet.data() + rtp_packet.headers_size() + kRedHeaderLength, + rtp_packet.size() - rtp_packet.headers_size() - kRedHeaderLength); + } + + if (received_packet->pkt->data.size() > 0) { + received_packets_.push_back(std::move(received_packet)); + } + return true; +} + +void UlpfecReceiver::ProcessReceivedFec() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + + // If we iterate over `received_packets_` and it contains a packet that cause + // us to recurse back to this function (for example a RED packet encapsulating + // a RED packet), then we will recurse forever. To avoid this we swap + // `received_packets_` with an empty vector so that the next recursive call + // wont iterate over the same packet again. This also solves the problem of + // not modifying the vector we are currently iterating over (packets are added + // in AddReceivedRedPacket). + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>> + received_packets; + received_packets.swap(received_packets_); + RtpHeaderExtensionMap* last_recovered_extension_map = nullptr; + + for (const auto& received_packet : received_packets) { + // Send received media packet to VCM. + if (!received_packet->is_fec) { + ForwardErrorCorrection::Packet* packet = received_packet->pkt.get(); + + RtpPacketReceived rtp_packet(&received_packet->extensions); + if (!rtp_packet.Parse(std::move(packet->data))) { + RTC_LOG(LS_WARNING) << "Corrupted media packet"; + continue; + } + recovered_packet_callback_->OnRecoveredPacket(rtp_packet); + // Some header extensions need to be zeroed in `received_packet` since + // they are written to the packet after FEC encoding. We try to do it + // without a copy of the underlying Copy-On-Write buffer, but if a + // reference is held by `recovered_packet_callback_->OnRecoveredPacket` a + // copy will still be made in 'rtp_packet.ZeroMutableExtensions()'. + rtp_packet.ZeroMutableExtensions(); + packet->data = rtp_packet.Buffer(); + } + if (!received_packet->is_recovered) { + // Do not pass recovered packets to FEC. Recovered packet might have + // different set of the RTP header extensions and thus different byte + // representation than the original packet, That will corrupt + // FEC calculation. + fec_->DecodeFec(*received_packet, &recovered_packets_); + last_recovered_extension_map = &received_packet->extensions; + } + } + + // Send any recovered media packets to VCM. + for (const auto& recovered_packet : recovered_packets_) { + if (recovered_packet->returned) { + // Already sent to the VCM and the jitter buffer. + continue; + } + ForwardErrorCorrection::Packet* packet = recovered_packet->pkt.get(); + ++packet_counter_.num_recovered_packets; + // Set this flag first; in case the recovered packet carries a RED + // header, OnRecoveredPacket will recurse back here. + recovered_packet->returned = true; + RtpPacketReceived parsed_packet(last_recovered_extension_map); + if (!parsed_packet.Parse(packet->data)) { + continue; + } + parsed_packet.set_recovered(true); + recovered_packet_callback_->OnRecoveredPacket(parsed_packet); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.h b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.h new file mode 100644 index 0000000000..3ad1c75e09 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.h @@ -0,0 +1,80 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_ULPFEC_RECEIVER_H_ +#define MODULES_RTP_RTCP_SOURCE_ULPFEC_RECEIVER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "api/sequence_checker.h" +#include "modules/rtp_rtcp/include/recovered_packet_receiver.h" +#include "modules/rtp_rtcp/include/rtp_header_extension_map.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "rtc_base/system/no_unique_address.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +struct FecPacketCounter { + FecPacketCounter() = default; + size_t num_packets = 0; // Number of received packets. + size_t num_bytes = 0; + size_t num_fec_packets = 0; // Number of received FEC packets. + size_t num_recovered_packets = + 0; // Number of recovered media packets using FEC. + // Time when first packet is received. + Timestamp first_packet_time = Timestamp::MinusInfinity(); +}; + +class UlpfecReceiver { + public: + UlpfecReceiver(uint32_t ssrc, + int ulpfec_payload_type, + RecoveredPacketReceiver* callback, + Clock* clock); + ~UlpfecReceiver(); + + int ulpfec_payload_type() const { return ulpfec_payload_type_; } + + bool AddReceivedRedPacket(const RtpPacketReceived& rtp_packet); + + void ProcessReceivedFec(); + + FecPacketCounter GetPacketCounter() const; + + void SetRtpExtensions(rtc::ArrayView<const RtpExtension> extensions); + + private: + const uint32_t ssrc_; + const int ulpfec_payload_type_; + Clock* const clock_; + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + RecoveredPacketReceiver* const recovered_packet_callback_; + const std::unique_ptr<ForwardErrorCorrection> fec_; + // TODO(nisse): The AddReceivedRedPacket method adds one or two packets to + // this list at a time, after which it is emptied by ProcessReceivedFec. It + // will make things simpler to merge AddReceivedRedPacket and + // ProcessReceivedFec into a single method, and we can then delete this list. + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>> + received_packets_ RTC_GUARDED_BY(&sequence_checker_); + ForwardErrorCorrection::RecoveredPacketList recovered_packets_ + RTC_GUARDED_BY(&sequence_checker_); + FecPacketCounter packet_counter_ RTC_GUARDED_BY(&sequence_checker_); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_ULPFEC_RECEIVER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver_unittest.cc new file mode 100644 index 0000000000..676e20c795 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver_unittest.cc @@ -0,0 +1,543 @@ +/* + * 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 "modules/rtp_rtcp/source/ulpfec_receiver.h" + +#include <string.h> + +#include <list> +#include <memory> +#include <utility> + +#include "modules/rtp_rtcp/mocks/mock_recovered_packet_receiver.h" +#include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/fec_test_helper.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +namespace { +using ::testing::_; +using ::testing::Eq; +using ::testing::Property; + +using test::fec::AugmentedPacket; +using Packet = ForwardErrorCorrection::Packet; +using test::fec::UlpfecPacketGenerator; + +constexpr int kFecPayloadType = 96; +constexpr uint32_t kMediaSsrc = 835424; + +class NullRecoveredPacketReceiver : public RecoveredPacketReceiver { + public: + void OnRecoveredPacket(const RtpPacketReceived& packet) override {} +}; + +} // namespace + +class UlpfecReceiverTest : public ::testing::Test { + protected: + UlpfecReceiverTest() + : fec_(ForwardErrorCorrection::CreateUlpfec(kMediaSsrc)), + receiver_fec_(kMediaSsrc, + kFecPayloadType, + &recovered_packet_receiver_, + Clock::GetRealTimeClock()), + packet_generator_(kMediaSsrc) {} + + // Generates `num_fec_packets` FEC packets, given `media_packets`. + void EncodeFec(const ForwardErrorCorrection::PacketList& media_packets, + size_t num_fec_packets, + std::list<ForwardErrorCorrection::Packet*>* fec_packets); + + // Generates `num_media_packets` corresponding to a single frame. + void PacketizeFrame(size_t num_media_packets, + size_t frame_offset, + std::list<AugmentedPacket*>* augmented_packets, + ForwardErrorCorrection::PacketList* packets); + + // Build a media packet using `packet_generator_` and add it + // to the receiver. + void BuildAndAddRedMediaPacket(AugmentedPacket* packet, + bool is_recovered = false); + + // Build a FEC packet using `packet_generator_` and add it + // to the receiver. + void BuildAndAddRedFecPacket(Packet* packet); + + // Ensure that `recovered_packet_receiver_` will be called correctly + // and that the recovered packet will be identical to the lost packet. + void VerifyReconstructedMediaPacket(const AugmentedPacket& packet, + size_t times); + + void InjectGarbagePacketLength(size_t fec_garbage_offset); + + static void SurvivesMaliciousPacket(const uint8_t* data, + size_t length, + uint8_t ulpfec_payload_type); + + MockRecoveredPacketReceiver recovered_packet_receiver_; + std::unique_ptr<ForwardErrorCorrection> fec_; + UlpfecReceiver receiver_fec_; + UlpfecPacketGenerator packet_generator_; +}; + +void UlpfecReceiverTest::EncodeFec( + const ForwardErrorCorrection::PacketList& media_packets, + size_t num_fec_packets, + std::list<ForwardErrorCorrection::Packet*>* fec_packets) { + const uint8_t protection_factor = + num_fec_packets * 255 / media_packets.size(); + // Unequal protection is turned off, and the number of important + // packets is thus irrelevant. + constexpr int kNumImportantPackets = 0; + constexpr bool kUseUnequalProtection = false; + constexpr FecMaskType kFecMaskType = kFecMaskBursty; + EXPECT_EQ( + 0, fec_->EncodeFec(media_packets, protection_factor, kNumImportantPackets, + kUseUnequalProtection, kFecMaskType, fec_packets)); + ASSERT_EQ(num_fec_packets, fec_packets->size()); +} + +void UlpfecReceiverTest::PacketizeFrame( + size_t num_media_packets, + size_t frame_offset, + std::list<AugmentedPacket*>* augmented_packets, + ForwardErrorCorrection::PacketList* packets) { + packet_generator_.NewFrame(num_media_packets); + for (size_t i = 0; i < num_media_packets; ++i) { + std::unique_ptr<AugmentedPacket> next_packet( + packet_generator_.NextPacket(frame_offset + i, kRtpHeaderSize + 10)); + augmented_packets->push_back(next_packet.get()); + packets->push_back(std::move(next_packet)); + } +} + +void UlpfecReceiverTest::BuildAndAddRedMediaPacket(AugmentedPacket* packet, + bool is_recovered) { + RtpPacketReceived red_packet = + packet_generator_.BuildMediaRedPacket(*packet, is_recovered); + EXPECT_TRUE(receiver_fec_.AddReceivedRedPacket(red_packet)); +} + +void UlpfecReceiverTest::BuildAndAddRedFecPacket(Packet* packet) { + RtpPacketReceived red_packet = + packet_generator_.BuildUlpfecRedPacket(*packet); + EXPECT_TRUE(receiver_fec_.AddReceivedRedPacket(red_packet)); +} + +void UlpfecReceiverTest::VerifyReconstructedMediaPacket( + const AugmentedPacket& packet, + size_t times) { + // Verify that the content of the reconstructed packet is equal to the + // content of `packet`, and that the same content is received `times` number + // of times in a row. + EXPECT_CALL( + recovered_packet_receiver_, + OnRecoveredPacket(Property(&RtpPacketReceived::Buffer, Eq(packet.data)))) + .Times(times); +} + +void UlpfecReceiverTest::InjectGarbagePacketLength(size_t fec_garbage_offset) { + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)); + + const size_t kNumFecPackets = 1; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(2, 0, &augmented_media_packets, &media_packets); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + ByteWriter<uint16_t>::WriteBigEndian( + fec_packets.front()->data.MutableData() + fec_garbage_offset, 0x4711); + + // Inject first media packet, then first FEC packet, skipping the second media + // packet to cause a recovery from the FEC packet. + BuildAndAddRedMediaPacket(augmented_media_packets.front()); + BuildAndAddRedFecPacket(fec_packets.front()); + receiver_fec_.ProcessReceivedFec(); + + FecPacketCounter counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(2U, counter.num_packets); + EXPECT_EQ(1U, counter.num_fec_packets); + EXPECT_EQ(0U, counter.num_recovered_packets); +} + +void UlpfecReceiverTest::SurvivesMaliciousPacket(const uint8_t* data, + size_t length, + uint8_t ulpfec_payload_type) { + NullRecoveredPacketReceiver null_callback; + UlpfecReceiver receiver_fec(kMediaSsrc, ulpfec_payload_type, &null_callback, + Clock::GetRealTimeClock()); + + RtpPacketReceived rtp_packet; + ASSERT_TRUE(rtp_packet.Parse(data, length)); + receiver_fec.AddReceivedRedPacket(rtp_packet); +} + +TEST_F(UlpfecReceiverTest, TwoMediaOneFec) { + constexpr size_t kNumFecPackets = 1u; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(2, 0, &augmented_media_packets, &media_packets); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + FecPacketCounter counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(0u, counter.num_packets); + EXPECT_EQ(Timestamp::MinusInfinity(), counter.first_packet_time); + + // Recovery + auto it = augmented_media_packets.begin(); + BuildAndAddRedMediaPacket(*it); + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(1u, counter.num_packets); + EXPECT_EQ(0u, counter.num_fec_packets); + EXPECT_EQ(0u, counter.num_recovered_packets); + const Timestamp first_packet_time = counter.first_packet_time; + EXPECT_NE(Timestamp::MinusInfinity(), first_packet_time); + + // Drop one media packet. + auto fec_it = fec_packets.begin(); + BuildAndAddRedFecPacket(*fec_it); + ++it; + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + + counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(2u, counter.num_packets); + EXPECT_EQ(1u, counter.num_fec_packets); + EXPECT_EQ(1u, counter.num_recovered_packets); + EXPECT_EQ(first_packet_time, counter.first_packet_time); +} + +TEST_F(UlpfecReceiverTest, TwoMediaOneFecNotUsesRecoveredPackets) { + constexpr size_t kNumFecPackets = 1u; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(2, 0, &augmented_media_packets, &media_packets); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + FecPacketCounter counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(0u, counter.num_packets); + EXPECT_EQ(Timestamp::MinusInfinity(), counter.first_packet_time); + + // Recovery + auto it = augmented_media_packets.begin(); + BuildAndAddRedMediaPacket(*it, /*is_recovered=*/true); + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(1u, counter.num_packets); + EXPECT_EQ(0u, counter.num_fec_packets); + EXPECT_EQ(0u, counter.num_recovered_packets); + const Timestamp first_packet_time = counter.first_packet_time; + EXPECT_NE(Timestamp::MinusInfinity(), first_packet_time); + + // Drop one media packet. + auto fec_it = fec_packets.begin(); + BuildAndAddRedFecPacket(*fec_it); + ++it; + receiver_fec_.ProcessReceivedFec(); + + counter = receiver_fec_.GetPacketCounter(); + EXPECT_EQ(2u, counter.num_packets); + EXPECT_EQ(1u, counter.num_fec_packets); + EXPECT_EQ(0u, counter.num_recovered_packets); + EXPECT_EQ(first_packet_time, counter.first_packet_time); +} + +TEST_F(UlpfecReceiverTest, InjectGarbageFecHeaderLengthRecovery) { + // Byte offset 8 is the 'length recovery' field of the FEC header. + InjectGarbagePacketLength(8); +} + +TEST_F(UlpfecReceiverTest, InjectGarbageFecLevelHeaderProtectionLength) { + // Byte offset 10 is the 'protection length' field in the first FEC level + // header. + InjectGarbagePacketLength(10); +} + +TEST_F(UlpfecReceiverTest, TwoMediaTwoFec) { + const size_t kNumFecPackets = 2; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(2, 0, &augmented_media_packets, &media_packets); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + // Recovery + // Drop both media packets. + auto it = augmented_media_packets.begin(); + auto fec_it = fec_packets.begin(); + BuildAndAddRedFecPacket(*fec_it); + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + ++fec_it; + BuildAndAddRedFecPacket(*fec_it); + ++it; + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, TwoFramesOneFec) { + const size_t kNumFecPackets = 1; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(1, 0, &augmented_media_packets, &media_packets); + PacketizeFrame(1, 1, &augmented_media_packets, &media_packets); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + // Recovery + auto it = augmented_media_packets.begin(); + BuildAndAddRedMediaPacket(augmented_media_packets.front()); + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + // Drop one media packet. + BuildAndAddRedFecPacket(fec_packets.front()); + ++it; + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, OneCompleteOneUnrecoverableFrame) { + const size_t kNumFecPackets = 1; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(1, 0, &augmented_media_packets, &media_packets); + PacketizeFrame(2, 1, &augmented_media_packets, &media_packets); + + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + // Recovery + auto it = augmented_media_packets.begin(); + BuildAndAddRedMediaPacket(*it); // First frame: one packet. + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + ++it; + BuildAndAddRedMediaPacket(*it); // First packet of second frame. + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, MaxFramesOneFec) { + const size_t kNumFecPackets = 1; + const size_t kNumMediaPackets = 48; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + for (size_t i = 0; i < kNumMediaPackets; ++i) { + PacketizeFrame(1, i, &augmented_media_packets, &media_packets); + } + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + // Recovery + auto it = augmented_media_packets.begin(); + ++it; // Drop first packet. + for (; it != augmented_media_packets.end(); ++it) { + BuildAndAddRedMediaPacket(*it); + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + } + BuildAndAddRedFecPacket(fec_packets.front()); + it = augmented_media_packets.begin(); + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, TooManyFrames) { + const size_t kNumFecPackets = 1; + const size_t kNumMediaPackets = 49; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + for (size_t i = 0; i < kNumMediaPackets; ++i) { + PacketizeFrame(1, i, &augmented_media_packets, &media_packets); + } + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EXPECT_EQ(-1, fec_->EncodeFec(media_packets, + kNumFecPackets * 255 / kNumMediaPackets, 0, + false, kFecMaskBursty, &fec_packets)); +} + +TEST_F(UlpfecReceiverTest, PacketNotDroppedTooEarly) { + // 1 frame with 2 media packets and one FEC packet. One media packet missing. + // Delay the FEC packet. + Packet* delayed_fec = nullptr; + const size_t kNumFecPacketsBatch1 = 1; + const size_t kNumMediaPacketsBatch1 = 2; + std::list<AugmentedPacket*> augmented_media_packets_batch1; + ForwardErrorCorrection::PacketList media_packets_batch1; + PacketizeFrame(kNumMediaPacketsBatch1, 0, &augmented_media_packets_batch1, + &media_packets_batch1); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets_batch1, kNumFecPacketsBatch1, &fec_packets); + + BuildAndAddRedMediaPacket(augmented_media_packets_batch1.front()); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(1); + receiver_fec_.ProcessReceivedFec(); + delayed_fec = fec_packets.front(); + + // Fill the FEC decoder. No packets should be dropped. + const size_t kNumMediaPacketsBatch2 = 191; + std::list<AugmentedPacket*> augmented_media_packets_batch2; + ForwardErrorCorrection::PacketList media_packets_batch2; + for (size_t i = 0; i < kNumMediaPacketsBatch2; ++i) { + PacketizeFrame(1, i, &augmented_media_packets_batch2, + &media_packets_batch2); + } + for (auto it = augmented_media_packets_batch2.begin(); + it != augmented_media_packets_batch2.end(); ++it) { + BuildAndAddRedMediaPacket(*it); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(1); + receiver_fec_.ProcessReceivedFec(); + } + + // Add the delayed FEC packet. One packet should be reconstructed. + BuildAndAddRedFecPacket(delayed_fec); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(1); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, PacketDroppedWhenTooOld) { + // 1 frame with 2 media packets and one FEC packet. One media packet missing. + // Delay the FEC packet. + Packet* delayed_fec = nullptr; + const size_t kNumFecPacketsBatch1 = 1; + const size_t kNumMediaPacketsBatch1 = 2; + std::list<AugmentedPacket*> augmented_media_packets_batch1; + ForwardErrorCorrection::PacketList media_packets_batch1; + PacketizeFrame(kNumMediaPacketsBatch1, 0, &augmented_media_packets_batch1, + &media_packets_batch1); + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets_batch1, kNumFecPacketsBatch1, &fec_packets); + + BuildAndAddRedMediaPacket(augmented_media_packets_batch1.front()); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(1); + receiver_fec_.ProcessReceivedFec(); + delayed_fec = fec_packets.front(); + + // Fill the FEC decoder and force the last packet to be dropped. + const size_t kNumMediaPacketsBatch2 = 192; + std::list<AugmentedPacket*> augmented_media_packets_batch2; + ForwardErrorCorrection::PacketList media_packets_batch2; + for (size_t i = 0; i < kNumMediaPacketsBatch2; ++i) { + PacketizeFrame(1, i, &augmented_media_packets_batch2, + &media_packets_batch2); + } + for (auto it = augmented_media_packets_batch2.begin(); + it != augmented_media_packets_batch2.end(); ++it) { + BuildAndAddRedMediaPacket(*it); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(1); + receiver_fec_.ProcessReceivedFec(); + } + + // Add the delayed FEC packet. No packet should be reconstructed since the + // first media packet of that frame has been dropped due to being too old. + BuildAndAddRedFecPacket(delayed_fec); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(0); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, OldFecPacketDropped) { + // 49 frames with 2 media packets and one FEC packet. All media packets + // missing. + const size_t kNumMediaPackets = 49 * 2; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + for (size_t i = 0; i < kNumMediaPackets / 2; ++i) { + std::list<AugmentedPacket*> frame_augmented_media_packets; + ForwardErrorCorrection::PacketList frame_media_packets; + std::list<ForwardErrorCorrection::Packet*> fec_packets; + PacketizeFrame(2, 0, &frame_augmented_media_packets, &frame_media_packets); + EncodeFec(frame_media_packets, 1, &fec_packets); + for (auto it = fec_packets.begin(); it != fec_packets.end(); ++it) { + // Only FEC packets inserted. No packets recoverable at this time. + BuildAndAddRedFecPacket(*it); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(0); + receiver_fec_.ProcessReceivedFec(); + } + // Move unique_ptr's to media_packets for lifetime management. + media_packets.insert(media_packets.end(), + std::make_move_iterator(frame_media_packets.begin()), + std::make_move_iterator(frame_media_packets.end())); + augmented_media_packets.insert(augmented_media_packets.end(), + frame_augmented_media_packets.begin(), + frame_augmented_media_packets.end()); + } + // Insert the oldest media packet. The corresponding FEC packet is too old + // and should have been dropped. Only the media packet we inserted will be + // returned. + BuildAndAddRedMediaPacket(augmented_media_packets.front()); + EXPECT_CALL(recovered_packet_receiver_, OnRecoveredPacket(_)).Times(1); + receiver_fec_.ProcessReceivedFec(); +} + +TEST_F(UlpfecReceiverTest, TruncatedPacketWithFBitSet) { + const uint8_t kTruncatedPacket[] = {0x80, 0x2a, 0x68, 0x71, 0x29, 0xa1, 0x27, + 0x3a, 0x29, 0x12, 0x2a, 0x98, 0xe0, 0x29}; + + SurvivesMaliciousPacket(kTruncatedPacket, sizeof(kTruncatedPacket), 100); +} + +TEST_F(UlpfecReceiverTest, + TruncatedPacketWithFBitSetEndingAfterFirstRedHeader) { + const uint8_t kPacket[] = { + 0x89, 0x27, 0x3a, 0x83, 0x27, 0x3a, 0x3a, 0xf3, 0x67, 0xbe, 0x2a, + 0xa9, 0x27, 0x54, 0x3a, 0x3a, 0x2a, 0x67, 0x3a, 0xf3, 0x67, 0xbe, + 0x2a, 0x27, 0xe6, 0xf6, 0x03, 0x3e, 0x29, 0x27, 0x21, 0x27, 0x2a, + 0x29, 0x21, 0x4b, 0x29, 0x3a, 0x28, 0x29, 0xbf, 0x29, 0x2a, 0x26, + 0x29, 0xae, 0x27, 0xa6, 0xf6, 0x00, 0x03, 0x3e}; + SurvivesMaliciousPacket(kPacket, sizeof(kPacket), 100); +} + +TEST_F(UlpfecReceiverTest, TruncatedPacketWithoutDataPastFirstBlock) { + const uint8_t kPacket[] = { + 0x82, 0x38, 0x92, 0x38, 0x92, 0x38, 0xde, 0x2a, 0x11, 0xc8, 0xa3, 0xc4, + 0x82, 0x38, 0x2a, 0x21, 0x2a, 0x28, 0x92, 0x38, 0x92, 0x00, 0x00, 0x0a, + 0x3a, 0xc8, 0xa3, 0x3a, 0x27, 0xc4, 0x2a, 0x21, 0x2a, 0x28}; + SurvivesMaliciousPacket(kPacket, sizeof(kPacket), 100); +} + +TEST_F(UlpfecReceiverTest, MediaWithPadding) { + const size_t kNumFecPackets = 1; + std::list<AugmentedPacket*> augmented_media_packets; + ForwardErrorCorrection::PacketList media_packets; + PacketizeFrame(2, 0, &augmented_media_packets, &media_packets); + + // Append four bytes of padding to the first media packet. + const uint8_t kPadding[] = {0, 0, 0, 4}; + augmented_media_packets.front()->data.AppendData(kPadding); + augmented_media_packets.front()->data.MutableData()[0] |= 1 << 5; // P bit. + augmented_media_packets.front()->header.paddingLength = 4; + + std::list<ForwardErrorCorrection::Packet*> fec_packets; + EncodeFec(media_packets, kNumFecPackets, &fec_packets); + + auto it = augmented_media_packets.begin(); + BuildAndAddRedMediaPacket(augmented_media_packets.front()); + + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); + + BuildAndAddRedFecPacket(fec_packets.front()); + ++it; + VerifyReconstructedMediaPacket(**it, 1); + receiver_fec_.ProcessReceivedFec(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_fec_generator.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_fec_generator.h new file mode 100644 index 0000000000..38e4103cb6 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_fec_generator.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 MODULES_RTP_RTCP_SOURCE_VIDEO_FEC_GENERATOR_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_FEC_GENERATOR_H_ + +#include <memory> +#include <vector> + +#include "api/units/data_rate.h" +#include "modules/include/module_fec_types.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" + +namespace webrtc { + +class VideoFecGenerator { + public: + VideoFecGenerator() = default; + virtual ~VideoFecGenerator() = default; + + enum class FecType { kFlexFec, kUlpFec }; + virtual FecType GetFecType() const = 0; + // Returns the SSRC used for FEC packets (i.e. FlexFec SSRC). + virtual absl::optional<uint32_t> FecSsrc() = 0; + // Returns the overhead, in bytes per packet, for FEC (and possibly RED). + virtual size_t MaxPacketOverhead() const = 0; + // Current rate of FEC packets generated, including all RTP-level headers. + virtual DataRate CurrentFecRate() const = 0; + // Set FEC rates, max frames before FEC is sent, and type of FEC masks. + virtual void SetProtectionParameters( + const FecProtectionParams& delta_params, + const FecProtectionParams& key_params) = 0; + // Called on new media packet to be protected. The generator may choose + // to generate FEC packets at this time, if so they will be stored in an + // internal buffer. + virtual void AddPacketAndGenerateFec(const RtpPacketToSend& packet) = 0; + // Get (and remove) and FEC packets pending in the generator. These packets + // will lack sequence numbers, that needs to be set externally. + // TODO(bugs.webrtc.org/11340): Actually FlexFec sets seq#, fix that! + virtual std::vector<std::unique_ptr<RtpPacketToSend>> GetFecPackets() = 0; + // Only called on the VideoSendStream queue, after operation has shut down, + // and only populated if there is an RtpState (e.g. FlexFec). + virtual absl::optional<RtpState> GetRtpState() = 0; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_FEC_GENERATOR_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.cc new file mode 100644 index 0000000000..bb0bf09e90 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.cc @@ -0,0 +1,42 @@ +/* + * 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 "modules/rtp_rtcp/source/video_rtp_depacketizer.h" + +#include <stddef.h> +#include <stdint.h> + +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/video/encoded_image.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +rtc::scoped_refptr<EncodedImageBuffer> VideoRtpDepacketizer::AssembleFrame( + rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads) { + size_t frame_size = 0; + for (rtc::ArrayView<const uint8_t> payload : rtp_payloads) { + frame_size += payload.size(); + } + + rtc::scoped_refptr<EncodedImageBuffer> bitstream = + EncodedImageBuffer::Create(frame_size); + + uint8_t* write_at = bitstream->data(); + for (rtc::ArrayView<const uint8_t> payload : rtp_payloads) { + memcpy(write_at, payload.data(), payload.size()); + write_at += payload.size(); + } + RTC_DCHECK_EQ(write_at - bitstream->data(), bitstream->size()); + return bitstream; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.h new file mode 100644 index 0000000000..2266120799 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H_ + +#include <stdint.h> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/video/encoded_image.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizer { + public: + struct ParsedRtpPayload { + RTPVideoHeader video_header; + rtc::CopyOnWriteBuffer video_payload; + }; + + virtual ~VideoRtpDepacketizer() = default; + virtual absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) = 0; + virtual rtc::scoped_refptr<EncodedImageBuffer> AssembleFrame( + rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads); +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.cc new file mode 100644 index 0000000000..870f788538 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.cc @@ -0,0 +1,395 @@ +/* + * 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 <utility> + +#include "modules/rtp_rtcp/source/leb128.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "rtc_base/byte_buffer.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" + +namespace webrtc { +namespace { +// AV1 format: +// +// RTP payload syntax: +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Z|Y| W |N|-|-|-| (REQUIRED) +// +=+=+=+=+=+=+=+=+ (REPEATED W-1 times, or any times if W = 0) +// |1| | +// +-+ OBU fragment| +// |1| | (REQUIRED, leb128 encoded) +// +-+ size | +// |0| | +// +-+-+-+-+-+-+-+-+ +// | OBU fragment | +// | ... | +// +=+=+=+=+=+=+=+=+ +// | ... | +// +=+=+=+=+=+=+=+=+ if W > 0, last fragment MUST NOT have size field +// | OBU fragment | +// | ... | +// +=+=+=+=+=+=+=+=+ +// +// +// OBU syntax: +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |0| type |X|S|-| (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: | TID |SID|-|-|-| (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// |1| | +// +-+ OBU payload | +// S: |1| | (OPTIONAL, variable length leb128 encoded) +// +-+ size | +// |0| | +// +-+-+-+-+-+-+-+-+ +// | OBU payload | +// | ... | +class ArrayOfArrayViews { + public: + class const_iterator; + ArrayOfArrayViews() = default; + ArrayOfArrayViews(const ArrayOfArrayViews&) = default; + ArrayOfArrayViews& operator=(const ArrayOfArrayViews&) = default; + ~ArrayOfArrayViews() = default; + + const_iterator begin() const; + const_iterator end() const; + bool empty() const { return data_.empty(); } + size_t size() const { return size_; } + void CopyTo(uint8_t* destination, const_iterator first) const; + + void Append(const uint8_t* data, size_t size) { + data_.emplace_back(data, size); + size_ += size; + } + + private: + using Storage = absl::InlinedVector<rtc::ArrayView<const uint8_t>, 2>; + + size_t size_ = 0; + Storage data_; +}; + +class ArrayOfArrayViews::const_iterator { + public: + const_iterator() = default; + const_iterator(const const_iterator&) = default; + const_iterator& operator=(const const_iterator&) = default; + + const_iterator& operator++() { + if (++inner_ == outer_->size()) { + ++outer_; + inner_ = 0; + } + return *this; + } + uint8_t operator*() const { return (*outer_)[inner_]; } + + friend bool operator==(const const_iterator& lhs, const const_iterator& rhs) { + return lhs.outer_ == rhs.outer_ && lhs.inner_ == rhs.inner_; + } + + private: + friend ArrayOfArrayViews; + const_iterator(ArrayOfArrayViews::Storage::const_iterator outer, size_t inner) + : outer_(outer), inner_(inner) {} + + Storage::const_iterator outer_; + size_t inner_; +}; + +ArrayOfArrayViews::const_iterator ArrayOfArrayViews::begin() const { + return const_iterator(data_.begin(), 0); +} + +ArrayOfArrayViews::const_iterator ArrayOfArrayViews::end() const { + return const_iterator(data_.end(), 0); +} + +void ArrayOfArrayViews::CopyTo(uint8_t* destination, + const_iterator first) const { + if (first == end()) { + // Empty OBU payload. E.g. Temporal Delimiters are always empty. + return; + } + size_t first_chunk_size = first.outer_->size() - first.inner_; + memcpy(destination, first.outer_->data() + first.inner_, first_chunk_size); + destination += first_chunk_size; + for (auto it = std::next(first.outer_); it != data_.end(); ++it) { + memcpy(destination, it->data(), it->size()); + destination += it->size(); + } +} + +struct ObuInfo { + // Size of the obu_header and obu_size fields in the ouput frame. + size_t prefix_size = 0; + // obu_header() and obu_size (leb128 encoded payload_size). + // obu_header can be up to 2 bytes, obu_size - up to 5. + std::array<uint8_t, 7> prefix; + // Size of the obu payload in the output frame, i.e. excluding header + size_t payload_size = 0; + // iterator pointing to the beginning of the obu payload. + ArrayOfArrayViews::const_iterator payload_offset; + // OBU payloads as written in the rtp packet payloads. + ArrayOfArrayViews data; +}; +// Expect that majority of the frame won't use more than 4 obus. +// In a simple stream delta frame consist of single Frame OBU, while key frame +// also has Sequence Header OBU. +using VectorObuInfo = absl::InlinedVector<ObuInfo, 4>; + +constexpr uint8_t kObuSizePresentBit = 0b0'0000'010; + +bool ObuHasExtension(uint8_t obu_header) { + return obu_header & 0b0'0000'100u; +} + +bool ObuHasSize(uint8_t obu_header) { + return obu_header & kObuSizePresentBit; +} + +bool RtpStartsWithFragment(uint8_t aggregation_header) { + return aggregation_header & 0b1000'0000u; +} +bool RtpEndsWithFragment(uint8_t aggregation_header) { + return aggregation_header & 0b0100'0000u; +} +int RtpNumObus(uint8_t aggregation_header) { // 0 for any number of obus. + return (aggregation_header & 0b0011'0000u) >> 4; +} +int RtpStartsNewCodedVideoSequence(uint8_t aggregation_header) { + return aggregation_header & 0b0000'1000u; +} + +// Reorgonizes array of rtp payloads into array of obus: +// fills ObuInfo::data field. +// Returns empty vector on error. +VectorObuInfo ParseObus( + rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads) { + VectorObuInfo obu_infos; + bool expect_continues_obu = false; + for (rtc::ArrayView<const uint8_t> rtp_payload : rtp_payloads) { + rtc::ByteBufferReader payload( + reinterpret_cast<const char*>(rtp_payload.data()), rtp_payload.size()); + uint8_t aggregation_header; + if (!payload.ReadUInt8(&aggregation_header)) { + RTC_DLOG(LS_WARNING) + << "Failed to find aggregation header in the packet."; + return {}; + } + // Z-bit: 1 if the first OBU contained in the packet is a continuation of a + // previous OBU. + bool continues_obu = RtpStartsWithFragment(aggregation_header); + if (continues_obu != expect_continues_obu) { + RTC_DLOG(LS_WARNING) << "Unexpected Z-bit " << continues_obu; + return {}; + } + int num_expected_obus = RtpNumObus(aggregation_header); + if (payload.Length() == 0) { + // rtp packet has just the aggregation header. That may be valid only when + // there is exactly one fragment in the packet of size 0. + if (num_expected_obus != 1) { + RTC_DLOG(LS_WARNING) + << "Invalid packet with just an aggregation header."; + return {}; + } + if (!continues_obu) { + // Empty packet just to notify there is a new OBU. + obu_infos.emplace_back(); + } + expect_continues_obu = RtpEndsWithFragment(aggregation_header); + continue; + } + + for (int obu_index = 1; payload.Length() > 0; ++obu_index) { + ObuInfo& obu_info = (obu_index == 1 && continues_obu) + ? obu_infos.back() + : obu_infos.emplace_back(); + uint64_t fragment_size; + // When num_expected_obus > 0, last OBU (fragment) is not preceeded by + // the size field. See W field in + // https://aomediacodec.github.io/av1-rtp-spec/#43-av1-aggregation-header + bool has_fragment_size = (obu_index != num_expected_obus); + if (has_fragment_size) { + if (!payload.ReadUVarint(&fragment_size)) { + RTC_DLOG(LS_WARNING) << "Failed to read fragment size for obu #" + << obu_index << "/" << num_expected_obus; + return {}; + } + if (fragment_size > payload.Length()) { + // Malformed input: written size is larger than remaining buffer. + RTC_DLOG(LS_WARNING) << "Malformed fragment size " << fragment_size + << " is larger than remaining size " + << payload.Length() << " while reading obu #" + << obu_index << "/" << num_expected_obus; + return {}; + } + } else { + fragment_size = payload.Length(); + } + // While it is in-practical to pass empty fragments, it is still possible. + if (fragment_size > 0) { + obu_info.data.Append(reinterpret_cast<const uint8_t*>(payload.Data()), + fragment_size); + payload.Consume(fragment_size); + } + } + // Z flag should be same as Y flag of the next packet. + expect_continues_obu = RtpEndsWithFragment(aggregation_header); + } + if (expect_continues_obu) { + RTC_DLOG(LS_WARNING) << "Last packet shouldn't have last obu fragmented."; + return {}; + } + return obu_infos; +} + +// Calculates sizes for the Obu, i.e. base on ObuInfo::data field calculates +// all other fields in the ObuInfo structure. +// Returns false if obu found to be misformed. +bool CalculateObuSizes(ObuInfo* obu_info) { + if (obu_info->data.empty()) { + RTC_DLOG(LS_WARNING) << "Invalid bitstream: empty obu provided."; + return false; + } + auto it = obu_info->data.begin(); + uint8_t obu_header = *it; + obu_info->prefix[0] = obu_header | kObuSizePresentBit; + obu_info->prefix_size = 1; + ++it; + if (ObuHasExtension(obu_header)) { + if (it == obu_info->data.end()) { + return false; + } + obu_info->prefix[1] = *it; // obu_extension_header + obu_info->prefix_size = 2; + ++it; + } + // Read, validate, and skip size, if present. + if (!ObuHasSize(obu_header)) { + obu_info->payload_size = obu_info->data.size() - obu_info->prefix_size; + } else { + // Read leb128 encoded field obu_size. + uint64_t obu_size_bytes = 0; + // Number of bytes obu_size field occupy in the bitstream. + int size_of_obu_size_bytes = 0; + uint8_t leb128_byte; + do { + if (it == obu_info->data.end() || size_of_obu_size_bytes >= 8) { + RTC_DLOG(LS_WARNING) + << "Failed to read obu_size. obu_size field is too long: " + << size_of_obu_size_bytes << " bytes processed."; + return false; + } + leb128_byte = *it; + obu_size_bytes |= uint64_t{leb128_byte & 0x7Fu} + << (size_of_obu_size_bytes * 7); + ++size_of_obu_size_bytes; + ++it; + } while ((leb128_byte & 0x80) != 0); + + obu_info->payload_size = + obu_info->data.size() - obu_info->prefix_size - size_of_obu_size_bytes; + if (obu_size_bytes != obu_info->payload_size) { + // obu_size was present in the bitstream and mismatches calculated size. + RTC_DLOG(LS_WARNING) << "Mismatch in obu_size. signaled: " + << obu_size_bytes + << ", actual: " << obu_info->payload_size; + return false; + } + } + obu_info->payload_offset = it; + obu_info->prefix_size += + WriteLeb128(rtc::dchecked_cast<uint64_t>(obu_info->payload_size), + obu_info->prefix.data() + obu_info->prefix_size); + return true; +} + +} // namespace + +rtc::scoped_refptr<EncodedImageBuffer> VideoRtpDepacketizerAv1::AssembleFrame( + rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads) { + VectorObuInfo obu_infos = ParseObus(rtp_payloads); + if (obu_infos.empty()) { + return nullptr; + } + + size_t frame_size = 0; + for (ObuInfo& obu_info : obu_infos) { + if (!CalculateObuSizes(&obu_info)) { + return nullptr; + } + frame_size += (obu_info.prefix_size + obu_info.payload_size); + } + + rtc::scoped_refptr<EncodedImageBuffer> bitstream = + EncodedImageBuffer::Create(frame_size); + uint8_t* write_at = bitstream->data(); + for (const ObuInfo& obu_info : obu_infos) { + // Copy the obu_header and obu_size fields. + memcpy(write_at, obu_info.prefix.data(), obu_info.prefix_size); + write_at += obu_info.prefix_size; + // Copy the obu payload. + obu_info.data.CopyTo(write_at, obu_info.payload_offset); + write_at += obu_info.payload_size; + } + RTC_CHECK_EQ(write_at - bitstream->data(), bitstream->size()); + return bitstream; +} + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> +VideoRtpDepacketizerAv1::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + if (rtp_payload.size() == 0) { + RTC_DLOG(LS_ERROR) << "Empty rtp payload."; + return absl::nullopt; + } + uint8_t aggregation_header = rtp_payload.cdata()[0]; + if (RtpStartsNewCodedVideoSequence(aggregation_header) && + RtpStartsWithFragment(aggregation_header)) { + // new coded video sequence can't start from an OBU fragment. + return absl::nullopt; + } + absl::optional<ParsedRtpPayload> parsed(absl::in_place); + + // To assemble frame, all of the rtp payload is required, including + // aggregation header. + parsed->video_payload = std::move(rtp_payload); + + parsed->video_header.codec = VideoCodecType::kVideoCodecAV1; + // These are not accurate since frame may consist of several packet aligned + // chunks of obus, but should be good enough for most cases. It might produce + // frame that do not map to any real frame, but av1 decoder should be able to + // handle it since it promise to handle individual obus rather than full + // frames. + parsed->video_header.is_first_packet_in_frame = + !RtpStartsWithFragment(aggregation_header); + parsed->video_header.is_last_packet_in_frame = + !RtpEndsWithFragment(aggregation_header); + + parsed->video_header.frame_type = + RtpStartsNewCodedVideoSequence(aggregation_header) + ? VideoFrameType::kVideoFrameKey + : VideoFrameType::kVideoFrameDelta; + return parsed; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h new file mode 100644 index 0000000000..ac8c7e6d11 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_AV1_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_AV1_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/video/encoded_image.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizerAv1 : public VideoRtpDepacketizer { + public: + VideoRtpDepacketizerAv1() = default; + VideoRtpDepacketizerAv1(const VideoRtpDepacketizerAv1&) = delete; + VideoRtpDepacketizerAv1& operator=(const VideoRtpDepacketizerAv1&) = delete; + ~VideoRtpDepacketizerAv1() override = default; + + rtc::scoped_refptr<EncodedImageBuffer> AssembleFrame( + rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads) + override; + + absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_AV1_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1_unittest.cc new file mode 100644 index 0000000000..e9ad1a1b8e --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1_unittest.cc @@ -0,0 +1,392 @@ +/* + * 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 "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAre; + +// Signals number of the OBU (fragments) in the packet. +constexpr uint8_t kObuCountOne = 0b00'01'0000; + +constexpr uint8_t kObuHeaderSequenceHeader = 0b0'0001'000; +constexpr uint8_t kObuHeaderFrame = 0b0'0110'000; + +constexpr uint8_t kObuHeaderHasSize = 0b0'0000'010; + +TEST(VideoRtpDepacketizerAv1Test, ParsePassFullRtpPayloadAsCodecPayload) { + const uint8_t packet[] = {(uint8_t{1} << 7) | kObuCountOne, 1, 2, 3, 4}; + rtc::CopyOnWriteBuffer rtp_payload(packet); + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + EXPECT_EQ(parsed->video_payload.size(), sizeof(packet)); + EXPECT_TRUE(parsed->video_payload.cdata() == rtp_payload.cdata()); +} + +TEST(VideoRtpDepacketizerAv1Test, + ParseTreatsContinuationFlagAsNotBeginningOfFrame) { + const uint8_t packet[] = { + (uint8_t{1} << 7) | kObuCountOne, + kObuHeaderFrame}; // Value doesn't matter since it is a + // continuation of the OBU from previous packet. + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet)); + ASSERT_TRUE(parsed); + EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame); +} + +TEST(VideoRtpDepacketizerAv1Test, + ParseTreatsNoContinuationFlagAsBeginningOfFrame) { + const uint8_t packet[] = {(uint8_t{0} << 7) | kObuCountOne, kObuHeaderFrame}; + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet)); + ASSERT_TRUE(parsed); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); +} + +TEST(VideoRtpDepacketizerAv1Test, ParseTreatsWillContinueFlagAsNotEndOfFrame) { + const uint8_t packet[] = {(uint8_t{1} << 6) | kObuCountOne, kObuHeaderFrame}; + rtc::CopyOnWriteBuffer rtp_payload(packet); + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + EXPECT_FALSE(parsed->video_header.is_last_packet_in_frame); +} + +TEST(VideoRtpDepacketizerAv1Test, ParseTreatsNoWillContinueFlagAsEndOfFrame) { + const uint8_t packet[] = {(uint8_t{0} << 6) | kObuCountOne, kObuHeaderFrame}; + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet)); + ASSERT_TRUE(parsed); + EXPECT_TRUE(parsed->video_header.is_last_packet_in_frame); +} + +TEST(VideoRtpDepacketizerAv1Test, + ParseUsesNewCodedVideoSequenceBitAsKeyFrameIndidcator) { + const uint8_t packet[] = {(uint8_t{1} << 3) | kObuCountOne, + kObuHeaderSequenceHeader}; + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet)); + ASSERT_TRUE(parsed); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + EXPECT_TRUE(parsed->video_header.frame_type == + VideoFrameType::kVideoFrameKey); +} + +TEST(VideoRtpDepacketizerAv1Test, + ParseUsesUnsetNewCodedVideoSequenceBitAsDeltaFrameIndidcator) { + const uint8_t packet[] = {(uint8_t{0} << 3) | kObuCountOne, + kObuHeaderSequenceHeader}; + VideoRtpDepacketizerAv1 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet)); + ASSERT_TRUE(parsed); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + EXPECT_TRUE(parsed->video_header.frame_type == + VideoFrameType::kVideoFrameDelta); +} + +TEST(VideoRtpDepacketizerAv1Test, + ParseRejectsPacketWithNewCVSAndContinuationFlagsBothSet) { + const uint8_t packet[] = {0b10'00'1000 | kObuCountOne, + kObuHeaderSequenceHeader}; + VideoRtpDepacketizerAv1 depacketizer; + ASSERT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(packet))); +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameSetsOBUPayloadSizeWhenAbsent) { + const uint8_t payload1[] = {0b00'01'0000, // aggregation header + 0b0'0110'000, // / Frame + 20, 30, 40}; // \ OBU + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView<const uint8_t> frame_view(*frame); + EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize); + EXPECT_EQ(frame_view[1], 3); +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameSetsOBUPayloadSizeWhenPresent) { + const uint8_t payload1[] = {0b00'01'0000, // aggregation header + 0b0'0110'010, // / Frame OBU header + 3, // obu_size + 20, + 30, + 40}; // \ obu_payload + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView<const uint8_t> frame_view(*frame); + EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize); + EXPECT_EQ(frame_view[1], 3); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameSetsOBUPayloadSizeAfterExtensionWhenAbsent) { + const uint8_t payload1[] = {0b00'01'0000, // aggregation header + 0b0'0110'100, // / Frame + 0b010'01'000, // | extension_header + 20, 30, 40}; // \ OBU + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView<const uint8_t> frame_view(*frame); + EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize); + EXPECT_EQ(frame_view[2], 3); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameSetsOBUPayloadSizeAfterExtensionWhenPresent) { + const uint8_t payload1[] = {0b00'01'0000, // aggregation header + 0b0'0110'110, // / Frame OBU header + 0b010'01'000, // | extension_header + 3, // | obu_size + 20, + 30, + 40}; // \ obu_payload + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView<const uint8_t> frame_view(*frame); + EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize); + EXPECT_EQ(frame_view[2], 3); +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameFromOnePacketWithOneObu) { + const uint8_t payload1[] = {0b00'01'0000, // aggregation header + 0b0'0110'000, // / Frame + 20}; // \ OBU + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0110'010, 1, 20)); +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameFromOnePacketWithTwoObus) { + const uint8_t payload1[] = {0b00'10'0000, // aggregation header + 2, // / Sequence + 0b0'0001'000, // | Header + 10, // \ OBU + 0b0'0110'000, // / Frame + 20}; // \ OBU + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0001'010, 1, 10, // Sequence Header OBU + 0b0'0110'010, 1, 20)); // Frame OBU +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameFromTwoPacketsWithOneObu) { + const uint8_t payload1[] = {0b01'01'0000, // aggregation header + 0b0'0110'000, 20, 30}; + const uint8_t payload2[] = {0b10'01'0000, // aggregation header + 40}; + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0110'010, 3, 20, 30, 40)); +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameFromTwoPacketsWithTwoObu) { + const uint8_t payload1[] = {0b01'10'0000, // aggregation header + 2, // / Sequence + 0b0'0001'000, // | Header + 10, // \ OBU + 0b0'0110'000, // + 20, + 30}; // + const uint8_t payload2[] = {0b10'01'0000, // aggregation header + 40}; // + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0001'010, 1, 10, // SH + 0b0'0110'010, 3, 20, 30, 40)); // Frame +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameFromTwoPacketsWithManyObusSomeWithExtensions) { + const uint8_t payload1[] = {0b01'00'0000, // aggregation header + 2, // / + 0b0'0001'000, // | Sequence Header + 10, // \ OBU + 2, // / + 0b0'0101'000, // | Metadata OBU + 20, // \ without extension + 4, // / + 0b0'0101'100, // | Metadata OBU + 0b001'10'000, // | with extension + 20, // | + 30, // \ metadata payload + 5, // / + 0b0'0110'100, // | Frame OBU + 0b001'10'000, // | with extension + 40, // | + 50, // | + 60}; // | + const uint8_t payload2[] = {0b10'01'0000, // aggregation header + 70, 80, 90}; // \ tail of the frame OBU + + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre( // Sequence header OBU + 0b0'0001'010, 1, 10, + // Metadata OBU without extension + 0b0'0101'010, 1, 20, + // Metadata OBU with extenion + 0b0'0101'110, 0b001'10'000, 2, 20, 30, + // Frame OBU with extension + 0b0'0110'110, 0b001'10'000, 6, 40, 50, 60, 70, 80, 90)); +} + +TEST(VideoRtpDepacketizerAv1Test, AssembleFrameWithOneObuFromManyPackets) { + const uint8_t payload1[] = {0b01'01'0000, // aggregation header + 0b0'0110'000, 11, 12}; + const uint8_t payload2[] = {0b11'01'0000, // aggregation header + 13, 14}; + const uint8_t payload3[] = {0b11'01'0000, // aggregation header + 15, 16, 17}; + const uint8_t payload4[] = {0b10'01'0000, // aggregation header + 18}; + + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2, payload3, + payload4}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0110'010, 8, 11, 12, 13, 14, 15, 16, 17, 18)); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameFromManyPacketsWithSomeObuBorderAligned) { + const uint8_t payload1[] = {0b01'10'0000, // aggregation header + 3, // size of the 1st fragment + 0b0'0011'000, // Frame header OBU + 11, + 12, + 0b0'0100'000, // Tile group OBU + 21, + 22, + 23}; + const uint8_t payload2[] = {0b10'01'0000, // aggregation header + 24, 25, 26, 27}; + // payload2 ends an OBU, payload3 starts a new one. + const uint8_t payload3[] = {0b01'10'0000, // aggregation header + 3, // size of the 1st fragment + 0b0'0111'000, // Redundant frame header OBU + 11, + 12, + 0b0'0100'000, // Tile group OBU + 31, + 32}; + const uint8_t payload4[] = {0b10'01'0000, // aggregation header + 33, 34, 35, 36}; + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2, payload3, + payload4}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0011'010, 2, 11, 12, // Frame header + 0b0'0100'010, 7, 21, 22, 23, 24, 25, 26, 27, // + 0b0'0111'010, 2, 11, 12, // + 0b0'0100'010, 6, 31, 32, 33, 34, 35, 36)); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameFromOnePacketsOneObuPayloadSize127Bytes) { + uint8_t payload1[4 + 127]; + memset(payload1, 0, sizeof(payload1)); + payload1[0] = 0b00'00'0000; // aggregation header + payload1[1] = 0x80; // leb128 encoded size of 128 bytes + payload1[2] = 0x01; // in two bytes + payload1[3] = 0b0'0110'000; // obu_header with size and extension bits unset. + payload1[4 + 42] = 0x42; + rtc::ArrayView<const uint8_t> payloads[] = {payload1}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_EQ(frame->size(), 2 + 127u); + rtc::ArrayView<const uint8_t> frame_view(*frame); + EXPECT_EQ(frame_view[0], 0b0'0110'010); // obu_header with size bit set. + EXPECT_EQ(frame_view[1], 127); // obu payload size, 1 byte enough to encode. + // Check 'random' byte from the payload is at the same 'random' offset. + EXPECT_EQ(frame_view[2 + 42], 0x42); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameFromTwoPacketsOneObuPayloadSize128Bytes) { + uint8_t payload1[3 + 32]; + memset(payload1, 0, sizeof(payload1)); + payload1[0] = 0b01'00'0000; // aggregation header + payload1[1] = 33; // leb128 encoded size of 33 bytes in one byte + payload1[2] = 0b0'0110'000; // obu_header with size and extension bits unset. + payload1[3 + 10] = 0x10; + uint8_t payload2[2 + 96]; + memset(payload2, 0, sizeof(payload2)); + payload2[0] = 0b10'00'0000; // aggregation header + payload2[1] = 96; // leb128 encoded size of 96 bytes in one byte + payload2[2 + 20] = 0x20; + + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2}; + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_EQ(frame->size(), 3 + 128u); + rtc::ArrayView<const uint8_t> frame_view(*frame); + EXPECT_EQ(frame_view[0], 0b0'0110'010); // obu_header with size bit set. + EXPECT_EQ(frame_view[1], 0x80); // obu payload size of 128 bytes. + EXPECT_EQ(frame_view[2], 0x01); // encoded in two byes + // Check two 'random' byte from the payload is at the same 'random' offset. + EXPECT_EQ(frame_view[3 + 10], 0x10); + EXPECT_EQ(frame_view[3 + 32 + 20], 0x20); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameFromAlmostEmptyPacketStartingAnOBU) { + const uint8_t payload1[] = {0b01'01'0000}; + const uint8_t payload2[] = {0b10'01'0000, 0b0'0110'000, 10, 20, 30}; + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2}; + + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0110'010, 3, 10, 20, 30)); +} + +TEST(VideoRtpDepacketizerAv1Test, + AssembleFrameFromAlmostEmptyPacketFinishingAnOBU) { + const uint8_t payload1[] = {0b01'01'0000, 0b0'0110'000, 10, 20, 30}; + const uint8_t payload2[] = {0b10'01'0000}; + rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2}; + + auto frame = VideoRtpDepacketizerAv1().AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame), + ElementsAre(0b0'0110'010, 3, 10, 20, 30)); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.cc new file mode 100644 index 0000000000..6010771318 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.cc @@ -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. + */ + +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h" + +#include <stddef.h> +#include <stdint.h> + +#include <utility> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { +constexpr uint8_t kKeyFrameBit = 0b0000'0001; +constexpr uint8_t kFirstPacketBit = 0b0000'0010; +// If this bit is set, there will be an extended header contained in this +// packet. This was added later so old clients will not send this. +constexpr uint8_t kExtendedHeaderBit = 0b0000'0100; + +constexpr size_t kGenericHeaderLength = 1; +constexpr size_t kExtendedHeaderLength = 2; +} // namespace + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> +VideoRtpDepacketizerGeneric::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + if (rtp_payload.size() == 0) { + RTC_LOG(LS_WARNING) << "Empty payload."; + return absl::nullopt; + } + absl::optional<ParsedRtpPayload> parsed(absl::in_place); + const uint8_t* payload_data = rtp_payload.cdata(); + + uint8_t generic_header = payload_data[0]; + size_t offset = kGenericHeaderLength; + + parsed->video_header.frame_type = (generic_header & kKeyFrameBit) + ? VideoFrameType::kVideoFrameKey + : VideoFrameType::kVideoFrameDelta; + parsed->video_header.is_first_packet_in_frame = + (generic_header & kFirstPacketBit) != 0; + parsed->video_header.codec = kVideoCodecGeneric; + parsed->video_header.width = 0; + parsed->video_header.height = 0; + + if (generic_header & kExtendedHeaderBit) { + if (rtp_payload.size() < offset + kExtendedHeaderLength) { + RTC_LOG(LS_WARNING) << "Too short payload for generic header."; + return absl::nullopt; + } + parsed->video_header.video_type_header + .emplace<RTPVideoHeaderLegacyGeneric>() + .picture_id = ((payload_data[1] & 0x7F) << 8) | payload_data[2]; + offset += kExtendedHeaderLength; + } + + parsed->video_payload = + rtp_payload.Slice(offset, rtp_payload.size() - offset); + return parsed; +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h new file mode 100644 index 0000000000..27056da481 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_GENERIC_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_GENERIC_H_ + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizerGeneric : public VideoRtpDepacketizer { + public: + ~VideoRtpDepacketizerGeneric() override = default; + + absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_GENERIC_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic_unittest.cc new file mode 100644 index 0000000000..860ddab4fd --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic_unittest.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 "modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h" + +#include <stdint.h> + +#include "absl/types/optional.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::SizeIs; + +TEST(VideoRtpDepacketizerGeneric, NonExtendedHeaderNoFrameId) { + const size_t kRtpPayloadSize = 10; + const uint8_t kPayload[kRtpPayloadSize] = {0x01}; + rtc::CopyOnWriteBuffer rtp_payload(kPayload); + + VideoRtpDepacketizerGeneric depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + + ASSERT_TRUE(parsed); + EXPECT_EQ(parsed->video_header.generic, absl::nullopt); + EXPECT_THAT(parsed->video_payload, SizeIs(kRtpPayloadSize - 1)); +} + +TEST(VideoRtpDepacketizerGeneric, ExtendedHeaderParsesFrameId) { + const size_t kRtpPayloadSize = 10; + const uint8_t kPayload[kRtpPayloadSize] = {0x05, 0x13, 0x37}; + rtc::CopyOnWriteBuffer rtp_payload(kPayload); + + VideoRtpDepacketizerGeneric depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + + ASSERT_TRUE(parsed); + const auto* generic_header = absl::get_if<RTPVideoHeaderLegacyGeneric>( + &parsed->video_header.video_type_header); + ASSERT_TRUE(generic_header); + EXPECT_EQ(generic_header->picture_id, 0x1337); + EXPECT_THAT(parsed->video_payload, SizeIs(kRtpPayloadSize - 3)); +} + +TEST(VideoRtpDepacketizerGeneric, PassRtpPayloadAsVideoPayload) { + const uint8_t kPayload[] = {0x01, 0x25, 0x52}; + rtc::CopyOnWriteBuffer rtp_payload(kPayload); + + VideoRtpDepacketizerGeneric depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + + ASSERT_TRUE(parsed); + // Check there was no memcpy involved by verifying return and original buffers + // point to the same buffer. + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata() + 1); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc new file mode 100644 index 0000000000..9978e5f5fc --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc @@ -0,0 +1,310 @@ +/* + * 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 "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" + +#include <cstddef> +#include <cstdint> +#include <utility> +#include <vector> + +#include "absl/types/optional.h" +#include "absl/types/variant.h" +#include "common_video/h264/h264_common.h" +#include "common_video/h264/pps_parser.h" +#include "common_video/h264/sps_parser.h" +#include "common_video/h264/sps_vui_rewriter.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_format_h264.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/checks.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +constexpr size_t kNalHeaderSize = 1; +constexpr size_t kFuAHeaderSize = 2; +constexpr size_t kLengthFieldSize = 2; +constexpr size_t kStapAHeaderSize = kNalHeaderSize + kLengthFieldSize; + +// TODO(pbos): Avoid parsing this here as well as inside the jitter buffer. +bool ParseStapAStartOffsets(const uint8_t* nalu_ptr, + size_t length_remaining, + std::vector<size_t>* offsets) { + size_t offset = 0; + while (length_remaining > 0) { + // Buffer doesn't contain room for additional nalu length. + if (length_remaining < sizeof(uint16_t)) + return false; + uint16_t nalu_size = ByteReader<uint16_t>::ReadBigEndian(nalu_ptr); + nalu_ptr += sizeof(uint16_t); + length_remaining -= sizeof(uint16_t); + if (nalu_size > length_remaining) + return false; + nalu_ptr += nalu_size; + length_remaining -= nalu_size; + + offsets->push_back(offset + kStapAHeaderSize); + offset += kLengthFieldSize + nalu_size; + } + return true; +} + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> ProcessStapAOrSingleNalu( + rtc::CopyOnWriteBuffer rtp_payload) { + const uint8_t* const payload_data = rtp_payload.cdata(); + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload( + absl::in_place); + bool modified_buffer = false; + parsed_payload->video_payload = rtp_payload; + parsed_payload->video_header.width = 0; + parsed_payload->video_header.height = 0; + parsed_payload->video_header.codec = kVideoCodecH264; + parsed_payload->video_header.simulcastIdx = 0; + parsed_payload->video_header.is_first_packet_in_frame = true; + auto& h264_header = parsed_payload->video_header.video_type_header + .emplace<RTPVideoHeaderH264>(); + + const uint8_t* nalu_start = payload_data + kNalHeaderSize; + const size_t nalu_length = rtp_payload.size() - kNalHeaderSize; + uint8_t nal_type = payload_data[0] & kH264TypeMask; + std::vector<size_t> nalu_start_offsets; + if (nal_type == H264::NaluType::kStapA) { + // Skip the StapA header (StapA NAL type + length). + if (rtp_payload.size() <= kStapAHeaderSize) { + RTC_LOG(LS_ERROR) << "StapA header truncated."; + return absl::nullopt; + } + + if (!ParseStapAStartOffsets(nalu_start, nalu_length, &nalu_start_offsets)) { + RTC_LOG(LS_ERROR) << "StapA packet with incorrect NALU packet lengths."; + return absl::nullopt; + } + + h264_header.packetization_type = kH264StapA; + nal_type = payload_data[kStapAHeaderSize] & kH264TypeMask; + } else { + h264_header.packetization_type = kH264SingleNalu; + nalu_start_offsets.push_back(0); + } + h264_header.nalu_type = nal_type; + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameDelta; + + nalu_start_offsets.push_back(rtp_payload.size() + + kLengthFieldSize); // End offset. + for (size_t i = 0; i < nalu_start_offsets.size() - 1; ++i) { + size_t start_offset = nalu_start_offsets[i]; + // End offset is actually start offset for next unit, excluding length field + // so remove that from this units length. + size_t end_offset = nalu_start_offsets[i + 1] - kLengthFieldSize; + if (end_offset - start_offset < H264::kNaluTypeSize) { + RTC_LOG(LS_ERROR) << "STAP-A packet too short"; + return absl::nullopt; + } + + NaluInfo nalu; + nalu.type = payload_data[start_offset] & kH264TypeMask; + nalu.sps_id = -1; + nalu.pps_id = -1; + start_offset += H264::kNaluTypeSize; + + switch (nalu.type) { + case H264::NaluType::kSps: { + // Check if VUI is present in SPS and if it needs to be modified to + // avoid + // excessive decoder latency. + + // Copy any previous data first (likely just the first header). + rtc::Buffer output_buffer; + if (start_offset) + output_buffer.AppendData(payload_data, start_offset); + + absl::optional<SpsParser::SpsState> sps; + + SpsVuiRewriter::ParseResult result = SpsVuiRewriter::ParseAndRewriteSps( + &payload_data[start_offset], end_offset - start_offset, &sps, + nullptr, &output_buffer, SpsVuiRewriter::Direction::kIncoming); + + if (result == SpsVuiRewriter::ParseResult::kVuiRewritten) { + if (modified_buffer) { + RTC_LOG(LS_WARNING) + << "More than one H264 SPS NAL units needing " + "rewriting found within a single STAP-A packet. " + "Keeping the first and rewriting the last."; + } + + // Rewrite length field to new SPS size. + if (h264_header.packetization_type == kH264StapA) { + size_t length_field_offset = + start_offset - (H264::kNaluTypeSize + kLengthFieldSize); + // Stap-A Length includes payload data and type header. + size_t rewritten_size = + output_buffer.size() - start_offset + H264::kNaluTypeSize; + ByteWriter<uint16_t>::WriteBigEndian( + &output_buffer[length_field_offset], rewritten_size); + } + + parsed_payload->video_payload.SetData(output_buffer.data(), + output_buffer.size()); + // Append rest of packet. + parsed_payload->video_payload.AppendData( + &payload_data[end_offset], + nalu_length + kNalHeaderSize - end_offset); + + modified_buffer = true; + } + + if (sps) { + parsed_payload->video_header.width = sps->width; + parsed_payload->video_header.height = sps->height; + nalu.sps_id = sps->id; + } else { + RTC_LOG(LS_WARNING) << "Failed to parse SPS id from SPS slice."; + } + parsed_payload->video_header.frame_type = + VideoFrameType::kVideoFrameKey; + break; + } + case H264::NaluType::kPps: { + uint32_t pps_id; + uint32_t sps_id; + if (PpsParser::ParsePpsIds(&payload_data[start_offset], + end_offset - start_offset, &pps_id, + &sps_id)) { + nalu.pps_id = pps_id; + nalu.sps_id = sps_id; + } else { + RTC_LOG(LS_WARNING) + << "Failed to parse PPS id and SPS id from PPS slice."; + } + break; + } + case H264::NaluType::kIdr: + parsed_payload->video_header.frame_type = + VideoFrameType::kVideoFrameKey; + [[fallthrough]]; + case H264::NaluType::kSlice: { + absl::optional<uint32_t> pps_id = PpsParser::ParsePpsIdFromSlice( + &payload_data[start_offset], end_offset - start_offset); + if (pps_id) { + nalu.pps_id = *pps_id; + } else { + RTC_LOG(LS_WARNING) << "Failed to parse PPS id from slice of type: " + << static_cast<int>(nalu.type); + } + break; + } + // Slices below don't contain SPS or PPS ids. + case H264::NaluType::kAud: + case H264::NaluType::kEndOfSequence: + case H264::NaluType::kEndOfStream: + case H264::NaluType::kFiller: + case H264::NaluType::kSei: + break; + case H264::NaluType::kStapA: + case H264::NaluType::kFuA: + RTC_LOG(LS_WARNING) << "Unexpected STAP-A or FU-A received."; + return absl::nullopt; + } + + if (h264_header.nalus_length == kMaxNalusPerPacket) { + RTC_LOG(LS_WARNING) + << "Received packet containing more than " << kMaxNalusPerPacket + << " NAL units. Will not keep track sps and pps ids for all of them."; + } else { + h264_header.nalus[h264_header.nalus_length++] = nalu; + } + } + + return parsed_payload; +} + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> ParseFuaNalu( + rtc::CopyOnWriteBuffer rtp_payload) { + if (rtp_payload.size() < kFuAHeaderSize) { + RTC_LOG(LS_ERROR) << "FU-A NAL units truncated."; + return absl::nullopt; + } + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed_payload( + absl::in_place); + uint8_t fnri = rtp_payload.cdata()[0] & (kH264FBit | kH264NriMask); + uint8_t original_nal_type = rtp_payload.cdata()[1] & kH264TypeMask; + bool first_fragment = (rtp_payload.cdata()[1] & kH264SBit) > 0; + NaluInfo nalu; + nalu.type = original_nal_type; + nalu.sps_id = -1; + nalu.pps_id = -1; + if (first_fragment) { + absl::optional<uint32_t> pps_id = + PpsParser::ParsePpsIdFromSlice(rtp_payload.cdata() + 2 * kNalHeaderSize, + rtp_payload.size() - 2 * kNalHeaderSize); + if (pps_id) { + nalu.pps_id = *pps_id; + } else { + RTC_LOG(LS_WARNING) + << "Failed to parse PPS from first fragment of FU-A NAL " + "unit with original type: " + << static_cast<int>(nalu.type); + } + uint8_t original_nal_header = fnri | original_nal_type; + rtp_payload = + rtp_payload.Slice(kNalHeaderSize, rtp_payload.size() - kNalHeaderSize); + rtp_payload.MutableData()[0] = original_nal_header; + parsed_payload->video_payload = std::move(rtp_payload); + } else { + parsed_payload->video_payload = + rtp_payload.Slice(kFuAHeaderSize, rtp_payload.size() - kFuAHeaderSize); + } + + if (original_nal_type == H264::NaluType::kIdr) { + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameKey; + } else { + parsed_payload->video_header.frame_type = VideoFrameType::kVideoFrameDelta; + } + parsed_payload->video_header.width = 0; + parsed_payload->video_header.height = 0; + parsed_payload->video_header.codec = kVideoCodecH264; + parsed_payload->video_header.simulcastIdx = 0; + parsed_payload->video_header.is_first_packet_in_frame = first_fragment; + auto& h264_header = parsed_payload->video_header.video_type_header + .emplace<RTPVideoHeaderH264>(); + h264_header.packetization_type = kH264FuA; + h264_header.nalu_type = original_nal_type; + if (first_fragment) { + h264_header.nalus[h264_header.nalus_length] = nalu; + h264_header.nalus_length = 1; + } + return parsed_payload; +} + +} // namespace + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> +VideoRtpDepacketizerH264::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + if (rtp_payload.size() == 0) { + RTC_LOG(LS_ERROR) << "Empty payload."; + return absl::nullopt; + } + + uint8_t nal_type = rtp_payload.cdata()[0] & kH264TypeMask; + + if (nal_type == H264::NaluType::kFuA) { + // Fragmented NAL units (FU-A). + return ParseFuaNalu(std::move(rtp_payload)); + } else { + // We handle STAP-A and single NALU's the same way here. The jitter buffer + // will depacketize the STAP-A into NAL units later. + return ProcessStapAOrSingleNalu(std::move(rtp_payload)); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h new file mode 100644 index 0000000000..cbea860049 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h @@ -0,0 +1,28 @@ +/* + * 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 MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H264_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H264_H_ + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { +class VideoRtpDepacketizerH264 : public VideoRtpDepacketizer { + public: + ~VideoRtpDepacketizerH264() override = default; + + absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_H264_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc new file mode 100644 index 0000000000..f569c45fd3 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc @@ -0,0 +1,425 @@ +/* + * 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 "modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h" + +#include <cstdint> +#include <vector> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "common_video/h264/h264_common.h" +#include "modules/rtp_rtcp/mocks/mock_rtp_rtcp.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtp_format_h264.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::SizeIs; + +enum Nalu { + kSlice = 1, + kIdr = 5, + kSei = 6, + kSps = 7, + kPps = 8, + kStapA = 24, + kFuA = 28 +}; + +constexpr uint8_t kOriginalSps[] = {kSps, 0x00, 0x00, 0x03, 0x03, + 0xF4, 0x05, 0x03, 0xC7, 0xC0}; +constexpr uint8_t kRewrittenSps[] = {kSps, 0x00, 0x00, 0x03, 0x03, + 0xF4, 0x05, 0x03, 0xC7, 0xE0, + 0x1B, 0x41, 0x10, 0x8D, 0x00}; +constexpr uint8_t kIdrOne[] = {kIdr, 0xFF, 0x00, 0x00, 0x04}; +constexpr uint8_t kIdrTwo[] = {kIdr, 0xFF, 0x00, 0x11}; + +TEST(VideoRtpDepacketizerH264Test, SingleNalu) { + uint8_t packet[2] = {0x05, 0xFF}; // F=0, NRI=0, Type=5 (IDR). + rtc::CopyOnWriteBuffer rtp_payload(packet); + + VideoRtpDepacketizerH264 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + const RTPVideoHeaderH264& h264 = + absl::get<RTPVideoHeaderH264>(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264SingleNalu); + EXPECT_EQ(h264.nalu_type, kIdr); +} + +TEST(VideoRtpDepacketizerH264Test, SingleNaluSpsWithResolution) { + uint8_t packet[] = {kSps, 0x7A, 0x00, 0x1F, 0xBC, 0xD9, 0x40, 0x50, + 0x05, 0xBA, 0x10, 0x00, 0x00, 0x03, 0x00, 0xC0, + 0x00, 0x00, 0x03, 0x2A, 0xE0, 0xF1, 0x83, 0x25}; + rtc::CopyOnWriteBuffer rtp_payload(packet); + + VideoRtpDepacketizerH264 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed->video_header.width, 1280u); + EXPECT_EQ(parsed->video_header.height, 720u); + const auto& h264 = + absl::get<RTPVideoHeaderH264>(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264SingleNalu); +} + +TEST(VideoRtpDepacketizerH264Test, StapAKey) { + // clang-format off + const NaluInfo kExpectedNalus[] = { {H264::kSps, 0, -1}, + {H264::kPps, 1, 2}, + {H264::kIdr, -1, 0} }; + uint8_t packet[] = {kStapA, // F=0, NRI=0, Type=24. + // Length, nal header, payload. + 0, 0x18, kExpectedNalus[0].type, + 0x7A, 0x00, 0x1F, 0xBC, 0xD9, 0x40, 0x50, 0x05, 0xBA, + 0x10, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x03, + 0x2A, 0xE0, 0xF1, 0x83, 0x25, + 0, 0xD, kExpectedNalus[1].type, + 0x69, 0xFC, 0x0, 0x0, 0x3, 0x0, 0x7, 0xFF, 0xFF, 0xFF, + 0xF6, 0x40, + 0, 0xB, kExpectedNalus[2].type, + 0x85, 0xB8, 0x0, 0x4, 0x0, 0x0, 0x13, 0x93, 0x12, 0x0}; + // clang-format on + rtc::CopyOnWriteBuffer rtp_payload(packet); + + VideoRtpDepacketizerH264 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + const auto& h264 = + absl::get<RTPVideoHeaderH264>(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264StapA); + // NALU type for aggregated packets is the type of the first packet only. + EXPECT_EQ(h264.nalu_type, kSps); + ASSERT_EQ(h264.nalus_length, 3u); + for (size_t i = 0; i < h264.nalus_length; ++i) { + EXPECT_EQ(h264.nalus[i].type, kExpectedNalus[i].type) + << "Failed parsing nalu " << i; + EXPECT_EQ(h264.nalus[i].sps_id, kExpectedNalus[i].sps_id) + << "Failed parsing nalu " << i; + EXPECT_EQ(h264.nalus[i].pps_id, kExpectedNalus[i].pps_id) + << "Failed parsing nalu " << i; + } +} + +TEST(VideoRtpDepacketizerH264Test, StapANaluSpsWithResolution) { + uint8_t packet[] = {kStapA, // F=0, NRI=0, Type=24. + // Length (2 bytes), nal header, payload. + 0x00, 0x19, kSps, 0x7A, 0x00, 0x1F, 0xBC, 0xD9, 0x40, + 0x50, 0x05, 0xBA, 0x10, 0x00, 0x00, 0x03, 0x00, 0xC0, + 0x00, 0x00, 0x03, 0x2A, 0xE0, 0xF1, 0x83, 0x25, 0x80, + 0x00, 0x03, kIdr, 0xFF, 0x00, 0x00, 0x04, kIdr, 0xFF, + 0x00, 0x11}; + rtc::CopyOnWriteBuffer rtp_payload(packet); + + VideoRtpDepacketizerH264 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload, rtp_payload); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed->video_header.width, 1280u); + EXPECT_EQ(parsed->video_header.height, 720u); + const auto& h264 = + absl::get<RTPVideoHeaderH264>(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264StapA); +} + +TEST(VideoRtpDepacketizerH264Test, EmptyStapARejected) { + uint8_t lone_empty_packet[] = {kStapA, 0x00, 0x00}; + uint8_t leading_empty_packet[] = {kStapA, 0x00, 0x00, 0x00, 0x04, + kIdr, 0xFF, 0x00, 0x11}; + uint8_t middle_empty_packet[] = {kStapA, 0x00, 0x03, kIdr, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x04, kIdr, 0xFF, 0x00, 0x11}; + uint8_t trailing_empty_packet[] = {kStapA, 0x00, 0x03, kIdr, + 0xFF, 0x00, 0x00, 0x00}; + + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(lone_empty_packet))); + EXPECT_FALSE( + depacketizer.Parse(rtc::CopyOnWriteBuffer(leading_empty_packet))); + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(middle_empty_packet))); + EXPECT_FALSE( + depacketizer.Parse(rtc::CopyOnWriteBuffer(trailing_empty_packet))); +} + +TEST(VideoRtpDepacketizerH264Test, DepacketizeWithRewriting) { + rtc::CopyOnWriteBuffer in_buffer; + rtc::Buffer out_buffer; + + uint8_t kHeader[2] = {kStapA}; + in_buffer.AppendData(kHeader, 1); + out_buffer.AppendData(kHeader, 1); + + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kOriginalSps)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kOriginalSps); + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kRewrittenSps)); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kRewrittenSps); + + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kIdrOne)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kIdrOne); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kIdrOne); + + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kIdrTwo)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kIdrTwo); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kIdrTwo); + + VideoRtpDepacketizerH264 depacketizer; + auto parsed = depacketizer.Parse(in_buffer); + ASSERT_TRUE(parsed); + EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(), + parsed->video_payload.size()), + ElementsAreArray(out_buffer)); +} + +TEST(VideoRtpDepacketizerH264Test, DepacketizeWithDoubleRewriting) { + rtc::CopyOnWriteBuffer in_buffer; + rtc::Buffer out_buffer; + + uint8_t kHeader[2] = {kStapA}; + in_buffer.AppendData(kHeader, 1); + out_buffer.AppendData(kHeader, 1); + + // First SPS will be kept... + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kOriginalSps)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kOriginalSps); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kOriginalSps); + + // ...only the second one will be rewritten. + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kOriginalSps)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kOriginalSps); + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kRewrittenSps)); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kRewrittenSps); + + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kIdrOne)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kIdrOne); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kIdrOne); + + ByteWriter<uint16_t>::WriteBigEndian(kHeader, sizeof(kIdrTwo)); + in_buffer.AppendData(kHeader, 2); + in_buffer.AppendData(kIdrTwo); + out_buffer.AppendData(kHeader, 2); + out_buffer.AppendData(kIdrTwo); + + VideoRtpDepacketizerH264 depacketizer; + auto parsed = depacketizer.Parse(in_buffer); + ASSERT_TRUE(parsed); + std::vector<uint8_t> expected_packet_payload( + out_buffer.data(), &out_buffer.data()[out_buffer.size()]); + EXPECT_THAT(rtc::MakeArrayView(parsed->video_payload.cdata(), + parsed->video_payload.size()), + ElementsAreArray(out_buffer)); +} + +TEST(VideoRtpDepacketizerH264Test, StapADelta) { + uint8_t packet[16] = {kStapA, // F=0, NRI=0, Type=24. + // Length, nal header, payload. + 0, 0x02, kSlice, 0xFF, 0, 0x03, kSlice, 0xFF, 0x00, 0, + 0x04, kSlice, 0xFF, 0x00, 0x11}; + rtc::CopyOnWriteBuffer rtp_payload(packet); + + VideoRtpDepacketizerH264 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload.size(), rtp_payload.size()); + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata()); + + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); + const RTPVideoHeaderH264& h264 = + absl::get<RTPVideoHeaderH264>(parsed->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264StapA); + // NALU type for aggregated packets is the type of the first packet only. + EXPECT_EQ(h264.nalu_type, kSlice); +} + +TEST(VideoRtpDepacketizerH264Test, FuA) { + // clang-format off + uint8_t packet1[] = { + kFuA, // F=0, NRI=0, Type=28. + kH264SBit | kIdr, // FU header. + 0x85, 0xB8, 0x0, 0x4, 0x0, 0x0, 0x13, 0x93, 0x12, 0x0 // Payload. + }; + // clang-format on + const uint8_t kExpected1[] = {kIdr, 0x85, 0xB8, 0x0, 0x4, 0x0, + 0x0, 0x13, 0x93, 0x12, 0x0}; + + uint8_t packet2[] = { + kFuA, // F=0, NRI=0, Type=28. + kIdr, // FU header. + 0x02 // Payload. + }; + const uint8_t kExpected2[] = {0x02}; + + uint8_t packet3[] = { + kFuA, // F=0, NRI=0, Type=28. + kH264EBit | kIdr, // FU header. + 0x03 // Payload. + }; + const uint8_t kExpected3[] = {0x03}; + + VideoRtpDepacketizerH264 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed1 = + depacketizer.Parse(rtc::CopyOnWriteBuffer(packet1)); + ASSERT_TRUE(parsed1); + // We expect that the first packet is one byte shorter since the FU-A header + // has been replaced by the original nal header. + EXPECT_THAT(rtc::MakeArrayView(parsed1->video_payload.cdata(), + parsed1->video_payload.size()), + ElementsAreArray(kExpected1)); + EXPECT_EQ(parsed1->video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(parsed1->video_header.codec, kVideoCodecH264); + EXPECT_TRUE(parsed1->video_header.is_first_packet_in_frame); + { + const RTPVideoHeaderH264& h264 = + absl::get<RTPVideoHeaderH264>(parsed1->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264FuA); + EXPECT_EQ(h264.nalu_type, kIdr); + ASSERT_EQ(h264.nalus_length, 1u); + EXPECT_EQ(h264.nalus[0].type, static_cast<H264::NaluType>(kIdr)); + EXPECT_EQ(h264.nalus[0].sps_id, -1); + EXPECT_EQ(h264.nalus[0].pps_id, 0); + } + + // Following packets will be 2 bytes shorter since they will only be appended + // onto the first packet. + auto parsed2 = depacketizer.Parse(rtc::CopyOnWriteBuffer(packet2)); + EXPECT_THAT(rtc::MakeArrayView(parsed2->video_payload.cdata(), + parsed2->video_payload.size()), + ElementsAreArray(kExpected2)); + EXPECT_FALSE(parsed2->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed2->video_header.codec, kVideoCodecH264); + { + const RTPVideoHeaderH264& h264 = + absl::get<RTPVideoHeaderH264>(parsed2->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264FuA); + EXPECT_EQ(h264.nalu_type, kIdr); + // NALU info is only expected for the first FU-A packet. + EXPECT_EQ(h264.nalus_length, 0u); + } + + auto parsed3 = depacketizer.Parse(rtc::CopyOnWriteBuffer(packet3)); + EXPECT_THAT(rtc::MakeArrayView(parsed3->video_payload.cdata(), + parsed3->video_payload.size()), + ElementsAreArray(kExpected3)); + EXPECT_FALSE(parsed3->video_header.is_first_packet_in_frame); + EXPECT_EQ(parsed3->video_header.codec, kVideoCodecH264); + { + const RTPVideoHeaderH264& h264 = + absl::get<RTPVideoHeaderH264>(parsed3->video_header.video_type_header); + EXPECT_EQ(h264.packetization_type, kH264FuA); + EXPECT_EQ(h264.nalu_type, kIdr); + // NALU info is only expected for the first FU-A packet. + ASSERT_EQ(h264.nalus_length, 0u); + } +} + +TEST(VideoRtpDepacketizerH264Test, EmptyPayload) { + rtc::CopyOnWriteBuffer empty; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(empty)); +} + +TEST(VideoRtpDepacketizerH264Test, TruncatedFuaNalu) { + const uint8_t kPayload[] = {0x9c}; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); +} + +TEST(VideoRtpDepacketizerH264Test, TruncatedSingleStapANalu) { + const uint8_t kPayload[] = {0xd8, 0x27}; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); +} + +TEST(VideoRtpDepacketizerH264Test, StapAPacketWithTruncatedNalUnits) { + const uint8_t kPayload[] = {0x58, 0xCB, 0xED, 0xDF}; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); +} + +TEST(VideoRtpDepacketizerH264Test, TruncationJustAfterSingleStapANalu) { + const uint8_t kPayload[] = {0x38, 0x27, 0x27}; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); +} + +TEST(VideoRtpDepacketizerH264Test, ShortSpsPacket) { + const uint8_t kPayload[] = {0x27, 0x80, 0x00}; + VideoRtpDepacketizerH264 depacketizer; + EXPECT_TRUE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); +} + +TEST(VideoRtpDepacketizerH264Test, SeiPacket) { + const uint8_t kPayload[] = { + kSei, // F=0, NRI=0, Type=6. + 0x03, 0x03, 0x03, 0x03 // Payload. + }; + VideoRtpDepacketizerH264 depacketizer; + auto parsed = depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)); + ASSERT_TRUE(parsed); + const RTPVideoHeaderH264& h264 = + absl::get<RTPVideoHeaderH264>(parsed->video_header.video_type_header); + EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_EQ(h264.packetization_type, kH264SingleNalu); + EXPECT_EQ(h264.nalu_type, kSei); + ASSERT_EQ(h264.nalus_length, 1u); + EXPECT_EQ(h264.nalus[0].type, static_cast<H264::NaluType>(kSei)); + EXPECT_EQ(h264.nalus[0].sps_id, -1); + EXPECT_EQ(h264.nalus[0].pps_id, -1); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.cc new file mode 100644 index 0000000000..81b4e4ab53 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.cc @@ -0,0 +1,28 @@ +/* + * 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_raw.h" + +#include <utility> + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> +VideoRtpDepacketizerRaw::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + absl::optional<ParsedRtpPayload> parsed(absl::in_place); + parsed->video_payload = std::move(rtp_payload); + return parsed; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.h new file mode 100644 index 0000000000..59c8695352 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_RAW_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_RAW_H_ + +#include "absl/types/optional.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizerRaw : public VideoRtpDepacketizer { + public: + ~VideoRtpDepacketizerRaw() override = default; + + absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_RAW_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw_unittest.cc new file mode 100644 index 0000000000..36c826ab84 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw_unittest.cc @@ -0,0 +1,51 @@ +/* + * 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_raw.h" + +#include <cstdint> + +#include "absl/types/optional.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +TEST(VideoRtpDepacketizerRaw, PassRtpPayloadAsVideoPayload) { + const uint8_t kPayload[] = {0x05, 0x25, 0x52}; + rtc::CopyOnWriteBuffer rtp_payload(kPayload); + + VideoRtpDepacketizerRaw depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + + ASSERT_TRUE(parsed); + EXPECT_EQ(parsed->video_payload.size(), rtp_payload.size()); + // Check there was no memcpy involved by verifying return and original buffers + // point to the same buffer. + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata()); +} + +TEST(VideoRtpDepacketizerRaw, UsesDefaultValuesForVideoHeader) { + const uint8_t kPayload[] = {0x05, 0x25, 0x52}; + rtc::CopyOnWriteBuffer rtp_payload(kPayload); + + VideoRtpDepacketizerRaw depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + + ASSERT_TRUE(parsed); + EXPECT_FALSE(parsed->video_header.generic); + EXPECT_EQ(parsed->video_header.codec, kVideoCodecGeneric); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc new file mode 100644 index 0000000000..d6bd33c24d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc @@ -0,0 +1,201 @@ +/* + * 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_vp8.h" + +#include <stddef.h> +#include <stdint.h> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +// VP8 payload descriptor +// https://datatracker.ietf.org/doc/html/rfc7741#section-4.2 +// +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|R| PID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T|K| RSV | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: |M| PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// | PictureID | +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T/K: |TID|Y| KEYIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// +// VP8 payload header. Considered part of the actual payload, sent to decoder. +// https://datatracker.ietf.org/doc/html/rfc7741#section-4.3 +// +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Size0|H| VER |P| +// +-+-+-+-+-+-+-+-+ +// : ... : +// +-+-+-+-+-+-+-+-+ + +namespace webrtc { +namespace { + +constexpr int kFailedToParse = 0; + +int ParseVP8Descriptor(RTPVideoHeaderVP8* vp8, + const uint8_t* data, + size_t data_length) { + RTC_DCHECK_GT(data_length, 0); + int parsed_bytes = 0; + // Parse mandatory first byte of payload descriptor. + bool extension = (*data & 0x80) ? true : false; // X bit + vp8->nonReference = (*data & 0x20) ? true : false; // N bit + vp8->beginningOfPartition = (*data & 0x10) ? true : false; // S bit + vp8->partitionId = (*data & 0x07); // PID field + + data++; + parsed_bytes++; + data_length--; + + if (!extension) + return parsed_bytes; + + if (data_length == 0) + return kFailedToParse; + // Optional X field is present. + bool has_picture_id = (*data & 0x80) ? true : false; // I bit + bool has_tl0_pic_idx = (*data & 0x40) ? true : false; // L bit + bool has_tid = (*data & 0x20) ? true : false; // T bit + bool has_key_idx = (*data & 0x10) ? true : false; // K bit + + // Advance data and decrease remaining payload size. + data++; + parsed_bytes++; + data_length--; + + if (has_picture_id) { + if (data_length == 0) + return kFailedToParse; + + vp8->pictureId = (*data & 0x7F); + if (*data & 0x80) { + data++; + parsed_bytes++; + if (--data_length == 0) + return kFailedToParse; + // PictureId is 15 bits + vp8->pictureId = (vp8->pictureId << 8) + *data; + } + data++; + parsed_bytes++; + data_length--; + } + + if (has_tl0_pic_idx) { + if (data_length == 0) + return kFailedToParse; + + vp8->tl0PicIdx = *data; + data++; + parsed_bytes++; + data_length--; + } + + if (has_tid || has_key_idx) { + if (data_length == 0) + return kFailedToParse; + + if (has_tid) { + vp8->temporalIdx = ((*data >> 6) & 0x03); + vp8->layerSync = (*data & 0x20) ? true : false; // Y bit + } + if (has_key_idx) { + vp8->keyIdx = *data & 0x1F; + } + data++; + parsed_bytes++; + data_length--; + } + return parsed_bytes; +} + +} // namespace + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> +VideoRtpDepacketizerVp8::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + rtc::ArrayView<const uint8_t> payload(rtp_payload.cdata(), + rtp_payload.size()); + absl::optional<ParsedRtpPayload> result(absl::in_place); + int offset = ParseRtpPayload(payload, &result->video_header); + if (offset == kFailedToParse) + return absl::nullopt; + RTC_DCHECK_LT(offset, rtp_payload.size()); + result->video_payload = + rtp_payload.Slice(offset, rtp_payload.size() - offset); + return result; +} + +int VideoRtpDepacketizerVp8::ParseRtpPayload( + rtc::ArrayView<const uint8_t> rtp_payload, + RTPVideoHeader* video_header) { + RTC_DCHECK(video_header); + if (rtp_payload.empty()) { + RTC_LOG(LS_ERROR) << "Empty rtp payload."; + return kFailedToParse; + } + + video_header->simulcastIdx = 0; + video_header->codec = kVideoCodecVP8; + auto& vp8_header = + video_header->video_type_header.emplace<RTPVideoHeaderVP8>(); + vp8_header.InitRTPVideoHeaderVP8(); + + const int descriptor_size = + ParseVP8Descriptor(&vp8_header, rtp_payload.data(), rtp_payload.size()); + if (descriptor_size == kFailedToParse) + return kFailedToParse; + + RTC_DCHECK_LT(vp8_header.partitionId, 8); + + video_header->is_first_packet_in_frame = + vp8_header.beginningOfPartition && vp8_header.partitionId == 0; + + int vp8_payload_size = rtp_payload.size() - descriptor_size; + if (vp8_payload_size == 0) { + RTC_LOG(LS_WARNING) << "Empty vp8 payload."; + return kFailedToParse; + } + const uint8_t* vp8_payload = rtp_payload.data() + descriptor_size; + + // Read P bit from payload header (only at beginning of first partition). + if (video_header->is_first_packet_in_frame && (*vp8_payload & 0x01) == 0) { + video_header->frame_type = VideoFrameType::kVideoFrameKey; + + if (vp8_payload_size < 10) { + // For an I-frame we should always have the uncompressed VP8 header + // in the beginning of the partition. + return kFailedToParse; + } + video_header->width = ((vp8_payload[7] << 8) + vp8_payload[6]) & 0x3FFF; + video_header->height = ((vp8_payload[9] << 8) + vp8_payload[8]) & 0x3FFF; + } else { + video_header->frame_type = VideoFrameType::kVideoFrameDelta; + + video_header->width = 0; + video_header->height = 0; + } + + return descriptor_size; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h new file mode 100644 index 0000000000..3d7cb3291d --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP8_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP8_H_ + +#include <cstdint> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizerVp8 : public VideoRtpDepacketizer { + public: + VideoRtpDepacketizerVp8() = default; + VideoRtpDepacketizerVp8(const VideoRtpDepacketizerVp8&) = delete; + VideoRtpDepacketizerVp8& operator=(const VideoRtpDepacketizerVp8&) = delete; + ~VideoRtpDepacketizerVp8() override = default; + + // Parses vp8 rtp payload descriptor. + // Returns zero on error or vp8 payload header offset on success. + static int ParseRtpPayload(rtc::ArrayView<const uint8_t> rtp_payload, + RTPVideoHeader* video_header); + + absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP8_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc new file mode 100644 index 0000000000..77469cf935 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc @@ -0,0 +1,244 @@ +/* + * 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_vp8.h" + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format_vp8.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "test/gmock.h" +#include "test/gtest.h" + +// VP8 payload descriptor +// https://datatracker.ietf.org/doc/html/rfc7741#section-4.2 +// +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|R| PID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T|K| RSV | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: |M| PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// | PictureID | +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T/K: |TID|Y| KEYIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// +// VP8 payload header. Considered part of the actual payload, sent to decoder. +// https://datatracker.ietf.org/doc/html/rfc7741#section-4.3 +// +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Size0|H| VER |P| +// +-+-+-+-+-+-+-+-+ +// : ... : +// +-+-+-+-+-+-+-+-+ + +namespace webrtc { +namespace { + +TEST(VideoRtpDepacketizerVp8Test, BasicHeader) { + uint8_t packet[4] = {0}; + packet[0] = 0b0001'0100; // S = 1, partition ID = 4. + packet[1] = 0x01; // P frame. + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 1); + EXPECT_EQ(video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_EQ(video_header.codec, kVideoCodecVP8); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_FALSE(vp8_header.nonReference); + EXPECT_TRUE(vp8_header.beginningOfPartition); + EXPECT_EQ(vp8_header.partitionId, 4); + EXPECT_EQ(vp8_header.pictureId, kNoPictureId); + EXPECT_EQ(vp8_header.tl0PicIdx, kNoTl0PicIdx); + EXPECT_EQ(vp8_header.temporalIdx, kNoTemporalIdx); + EXPECT_EQ(vp8_header.keyIdx, kNoKeyIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, OneBytePictureID) { + const uint8_t kPictureId = 17; + uint8_t packet[10] = {0}; + packet[0] = 0b1010'0000; + packet[1] = 0b1000'0000; + packet[2] = kPictureId; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_EQ(vp8_header.pictureId, kPictureId); +} + +TEST(VideoRtpDepacketizerVp8Test, TwoBytePictureID) { + const uint16_t kPictureId = 0x1234; + uint8_t packet[10] = {0}; + packet[0] = 0b1010'0000; + packet[1] = 0b1000'0000; + packet[2] = 0x80 | 0x12; + packet[3] = 0x34; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 4); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_EQ(vp8_header.pictureId, kPictureId); +} + +TEST(VideoRtpDepacketizerVp8Test, Tl0PicIdx) { + const uint8_t kTl0PicIdx = 17; + uint8_t packet[13] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b0100'0000; + packet[2] = kTl0PicIdx; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_EQ(vp8_header.tl0PicIdx, kTl0PicIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, TIDAndLayerSync) { + uint8_t packet[10] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b0010'0000; + packet[2] = 0b10'0'00000; // TID(2) + LayerSync(false) + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_EQ(vp8_header.temporalIdx, 2); + EXPECT_FALSE(vp8_header.layerSync); +} + +TEST(VideoRtpDepacketizerVp8Test, KeyIdx) { + const uint8_t kKeyIdx = 17; + uint8_t packet[10] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b0001'0000; + packet[2] = kKeyIdx; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_EQ(vp8_header.keyIdx, kKeyIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, MultipleExtensions) { + uint8_t packet[10] = {0}; + packet[0] = 0b1010'0110; // X and N bit set, partition ID = 6 + packet[1] = 0b1111'0000; + packet[2] = 0x80 | 0x12; // PictureID, high 7 bits. + packet[3] = 0x34; // PictureID, low 8 bits. + packet[4] = 42; // Tl0PicIdx. + packet[5] = 0b01'1'10001; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 6); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(video_header.video_type_header); + EXPECT_TRUE(vp8_header.nonReference); + EXPECT_EQ(vp8_header.partitionId, 0b0110); + EXPECT_EQ(vp8_header.pictureId, 0x1234); + EXPECT_EQ(vp8_header.tl0PicIdx, 42); + EXPECT_EQ(vp8_header.temporalIdx, 1); + EXPECT_TRUE(vp8_header.layerSync); + EXPECT_EQ(vp8_header.keyIdx, 0b10001); +} + +TEST(VideoRtpDepacketizerVp8Test, TooShortHeader) { + uint8_t packet[4] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b1111'0000; // All extensions are enabled... + packet[2] = 0x80 | 17; // ... but only 2 bytes PictureID is provided. + packet[3] = 17; // PictureID, low 8 bits. + + RTPVideoHeader unused; + EXPECT_EQ(VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &unused), 0); +} + +TEST(VideoRtpDepacketizerVp8Test, WithPacketizer) { + uint8_t data[10] = {0}; + RtpPacketToSend packet(/*extenions=*/nullptr); + RTPVideoHeaderVP8 input_header; + input_header.nonReference = true; + input_header.pictureId = 300; + input_header.temporalIdx = 1; + input_header.layerSync = false; + input_header.tl0PicIdx = kNoTl0PicIdx; // Disable. + input_header.keyIdx = 31; + RtpPacketizerVp8 packetizer(data, /*limits=*/{}, input_header); + EXPECT_EQ(packetizer.NumPackets(), 1u); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + + VideoRtpDepacketizerVp8 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(packet.PayloadBuffer()); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_header.codec, kVideoCodecVP8); + const auto& vp8_header = + absl::get<RTPVideoHeaderVP8>(parsed->video_header.video_type_header); + EXPECT_EQ(vp8_header.nonReference, input_header.nonReference); + EXPECT_EQ(vp8_header.pictureId, input_header.pictureId); + EXPECT_EQ(vp8_header.tl0PicIdx, input_header.tl0PicIdx); + EXPECT_EQ(vp8_header.temporalIdx, input_header.temporalIdx); + EXPECT_EQ(vp8_header.layerSync, input_header.layerSync); + EXPECT_EQ(vp8_header.keyIdx, input_header.keyIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, ReferencesInputCopyOnWriteBuffer) { + constexpr size_t kHeaderSize = 5; + uint8_t packet[16] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b1111'0000; // with all extensions, + packet[2] = 15; // and one-byte picture id. + + rtc::CopyOnWriteBuffer rtp_payload(packet); + VideoRtpDepacketizerVp8 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload.size(), rtp_payload.size() - kHeaderSize); + // Compare pointers to check there was no copy on write buffer unsharing. + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata() + kHeaderSize); +} + +TEST(VideoRtpDepacketizerVp8Test, FailsOnEmptyPayload) { + rtc::ArrayView<const uint8_t> empty; + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp8::ParseRtpPayload(empty, &video_header), 0); +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.cc new file mode 100644 index 0000000000..41f363d221 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.cc @@ -0,0 +1,226 @@ +/* + * 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_vp9.h" + +#include <string.h> + +#include "api/video/video_codec_constants.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/video_coding/codecs/interface/common_constants.h" +#include "rtc_base/bitstream_reader.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +void ParsePictureId(BitstreamReader& parser, RTPVideoHeaderVP9* vp9) { + if (parser.ReadBit()) { // m_bit + vp9->picture_id = parser.ReadBits(15); + vp9->max_picture_id = kMaxTwoBytePictureId; + } else { + vp9->picture_id = parser.ReadBits(7); + vp9->max_picture_id = kMaxOneBytePictureId; + } +} + +// Layer indices : +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | (non-flexible mode only) +// +-+-+-+-+-+-+-+-+ +// +void ParseLayerInfo(BitstreamReader& parser, RTPVideoHeaderVP9* vp9) { + vp9->temporal_idx = parser.ReadBits(3); + vp9->temporal_up_switch = parser.Read<bool>(); + vp9->spatial_idx = parser.ReadBits(3); + vp9->inter_layer_predicted = parser.Read<bool>(); + if (vp9->spatial_idx >= kMaxSpatialLayers) { + parser.Invalidate(); + return; + } + + if (!vp9->flexible_mode) { + vp9->tl0_pic_idx = parser.Read<uint8_t>(); + } +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +void ParseRefIndices(BitstreamReader& parser, RTPVideoHeaderVP9* vp9) { + if (vp9->picture_id == kNoPictureId) { + parser.Invalidate(); + return; + } + + vp9->num_ref_pics = 0; + bool n_bit; + do { + if (vp9->num_ref_pics == kMaxVp9RefPics) { + parser.Invalidate(); + return; + } + + uint8_t p_diff = parser.ReadBits(7); + n_bit = parser.Read<bool>(); + + vp9->pid_diff[vp9->num_ref_pics] = p_diff; + uint32_t scaled_pid = vp9->picture_id; + if (p_diff > scaled_pid) { + // TODO(asapersson): Max should correspond to the picture id of last wrap. + scaled_pid += vp9->max_picture_id + 1; + } + vp9->ref_picture_id[vp9->num_ref_pics++] = scaled_pid - p_diff; + } while (n_bit); +} + +// Scalability structure (SS). +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +void ParseSsData(BitstreamReader& parser, RTPVideoHeaderVP9* vp9) { + vp9->num_spatial_layers = parser.ReadBits(3) + 1; + vp9->spatial_layer_resolution_present = parser.Read<bool>(); + bool g_bit = parser.Read<bool>(); + parser.ConsumeBits(3); + vp9->gof.num_frames_in_gof = 0; + + if (vp9->spatial_layer_resolution_present) { + for (size_t i = 0; i < vp9->num_spatial_layers; ++i) { + vp9->width[i] = parser.Read<uint16_t>(); + vp9->height[i] = parser.Read<uint16_t>(); + } + } + if (g_bit) { + vp9->gof.num_frames_in_gof = parser.Read<uint8_t>(); + } + for (size_t i = 0; i < vp9->gof.num_frames_in_gof; ++i) { + vp9->gof.temporal_idx[i] = parser.ReadBits(3); + vp9->gof.temporal_up_switch[i] = parser.Read<bool>(); + vp9->gof.num_ref_pics[i] = parser.ReadBits(2); + parser.ConsumeBits(2); + + for (uint8_t p = 0; p < vp9->gof.num_ref_pics[i]; ++p) { + vp9->gof.pid_diff[i][p] = parser.Read<uint8_t>(); + } + } +} +} // namespace + +absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> +VideoRtpDepacketizerVp9::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + absl::optional<ParsedRtpPayload> result(absl::in_place); + int offset = ParseRtpPayload(rtp_payload, &result->video_header); + if (offset == 0) + return absl::nullopt; + RTC_DCHECK_LT(offset, rtp_payload.size()); + result->video_payload = + rtp_payload.Slice(offset, rtp_payload.size() - offset); + return result; +} + +int VideoRtpDepacketizerVp9::ParseRtpPayload( + rtc::ArrayView<const uint8_t> rtp_payload, + RTPVideoHeader* video_header) { + RTC_DCHECK(video_header); + // Parse mandatory first byte of payload descriptor. + BitstreamReader parser(rtp_payload); + uint8_t first_byte = parser.Read<uint8_t>(); + bool i_bit = first_byte & 0b1000'0000; // PictureId present . + bool p_bit = first_byte & 0b0100'0000; // Inter-picture predicted. + bool l_bit = first_byte & 0b0010'0000; // Layer indices present. + bool f_bit = first_byte & 0b0001'0000; // Flexible mode. + bool b_bit = first_byte & 0b0000'1000; // Begins frame flag. + bool e_bit = first_byte & 0b0000'0100; // Ends frame flag. + bool v_bit = first_byte & 0b0000'0010; // Scalability structure present. + bool z_bit = first_byte & 0b0000'0001; // Not used for inter-layer prediction + + // Parsed payload. + video_header->width = 0; + video_header->height = 0; + video_header->simulcastIdx = 0; + video_header->codec = kVideoCodecVP9; + + video_header->frame_type = + p_bit ? VideoFrameType::kVideoFrameDelta : VideoFrameType::kVideoFrameKey; + + auto& vp9_header = + video_header->video_type_header.emplace<RTPVideoHeaderVP9>(); + vp9_header.InitRTPVideoHeaderVP9(); + vp9_header.inter_pic_predicted = p_bit; + vp9_header.flexible_mode = f_bit; + vp9_header.beginning_of_frame = b_bit; + vp9_header.end_of_frame = e_bit; + vp9_header.ss_data_available = v_bit; + vp9_header.non_ref_for_inter_layer_pred = z_bit; + + // Parse fields that are present. + if (i_bit) { + ParsePictureId(parser, &vp9_header); + } + if (l_bit) { + ParseLayerInfo(parser, &vp9_header); + } + if (p_bit && f_bit) { + ParseRefIndices(parser, &vp9_header); + } + if (v_bit) { + ParseSsData(parser, &vp9_header); + if (vp9_header.spatial_layer_resolution_present) { + // TODO(asapersson): Add support for spatial layers. + video_header->width = vp9_header.width[0]; + video_header->height = vp9_header.height[0]; + } + } + video_header->is_first_packet_in_frame = b_bit; + video_header->is_last_packet_in_frame = e_bit; + + int num_remaining_bits = parser.RemainingBitCount(); + if (num_remaining_bits <= 0) { + // Failed to parse or empty vp9 payload data. + return 0; + } + // vp9 descriptor is byte aligned. + RTC_DCHECK_EQ(num_remaining_bits % 8, 0); + return rtp_payload.size() - num_remaining_bits / 8; +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h new file mode 100644 index 0000000000..4bb358a15f --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h @@ -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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP9_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP9_H_ + +#include <cstdint> + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizerVp9 : public VideoRtpDepacketizer { + public: + VideoRtpDepacketizerVp9() = default; + VideoRtpDepacketizerVp9(const VideoRtpDepacketizerVp9&) = delete; + VideoRtpDepacketizerVp9& operator=(const VideoRtpDepacketizerVp9&) = delete; + ~VideoRtpDepacketizerVp9() override = default; + + // Parses vp9 rtp payload descriptor. + // Returns zero on error or vp9 payload header offset on success. + static int ParseRtpPayload(rtc::ArrayView<const uint8_t> rtp_payload, + RTPVideoHeader* video_header); + + absl::optional<ParsedRtpPayload> Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP9_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9_unittest.cc new file mode 100644 index 0000000000..36af59a779 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9_unittest.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 "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" + +#include <memory> +#include <vector> + +#include "api/array_view.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +void VerifyHeader(const RTPVideoHeaderVP9& expected, + const RTPVideoHeaderVP9& actual) { + EXPECT_EQ(expected.inter_layer_predicted, actual.inter_layer_predicted); + EXPECT_EQ(expected.inter_pic_predicted, actual.inter_pic_predicted); + EXPECT_EQ(expected.flexible_mode, actual.flexible_mode); + EXPECT_EQ(expected.beginning_of_frame, actual.beginning_of_frame); + EXPECT_EQ(expected.end_of_frame, actual.end_of_frame); + EXPECT_EQ(expected.ss_data_available, actual.ss_data_available); + EXPECT_EQ(expected.non_ref_for_inter_layer_pred, + actual.non_ref_for_inter_layer_pred); + EXPECT_EQ(expected.picture_id, actual.picture_id); + EXPECT_EQ(expected.max_picture_id, actual.max_picture_id); + EXPECT_EQ(expected.temporal_idx, actual.temporal_idx); + EXPECT_EQ(expected.spatial_idx, actual.spatial_idx); + EXPECT_EQ(expected.gof_idx, actual.gof_idx); + EXPECT_EQ(expected.tl0_pic_idx, actual.tl0_pic_idx); + EXPECT_EQ(expected.temporal_up_switch, actual.temporal_up_switch); + + EXPECT_EQ(expected.num_ref_pics, actual.num_ref_pics); + for (uint8_t i = 0; i < expected.num_ref_pics; ++i) { + EXPECT_EQ(expected.pid_diff[i], actual.pid_diff[i]); + EXPECT_EQ(expected.ref_picture_id[i], actual.ref_picture_id[i]); + } + if (expected.ss_data_available) { + EXPECT_EQ(expected.spatial_layer_resolution_present, + actual.spatial_layer_resolution_present); + EXPECT_EQ(expected.num_spatial_layers, actual.num_spatial_layers); + if (expected.spatial_layer_resolution_present) { + for (size_t i = 0; i < expected.num_spatial_layers; i++) { + EXPECT_EQ(expected.width[i], actual.width[i]); + EXPECT_EQ(expected.height[i], actual.height[i]); + } + } + EXPECT_EQ(expected.gof.num_frames_in_gof, actual.gof.num_frames_in_gof); + for (size_t i = 0; i < expected.gof.num_frames_in_gof; i++) { + EXPECT_EQ(expected.gof.temporal_up_switch[i], + actual.gof.temporal_up_switch[i]); + EXPECT_EQ(expected.gof.temporal_idx[i], actual.gof.temporal_idx[i]); + EXPECT_EQ(expected.gof.num_ref_pics[i], actual.gof.num_ref_pics[i]); + for (uint8_t j = 0; j < expected.gof.num_ref_pics[i]; j++) { + EXPECT_EQ(expected.gof.pid_diff[i][j], actual.gof.pid_diff[i][j]); + } + } + } +} + +TEST(VideoRtpDepacketizerVp9Test, ParseBasicHeader) { + uint8_t packet[4] = {0}; + packet[0] = 0x0C; // I:0 P:0 L:0 F:0 B:1 E:1 V:0 Z:0 + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 1); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + expected.beginning_of_frame = true; + expected.end_of_frame = true; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseOneBytePictureId) { + uint8_t packet[10] = {0}; + packet[0] = 0x80; // I:1 P:0 L:0 F:0 B:0 E:0 V:0 Z:0 + packet[1] = kMaxOneBytePictureId; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 2); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + expected.picture_id = kMaxOneBytePictureId; + expected.max_picture_id = kMaxOneBytePictureId; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseTwoBytePictureId) { + uint8_t packet[10] = {0}; + packet[0] = 0x80; // I:1 P:0 L:0 F:0 B:0 E:0 V:0 Z:0 + packet[1] = 0x80 | ((kMaxTwoBytePictureId >> 8) & 0x7F); + packet[2] = kMaxTwoBytePictureId & 0xFF; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + expected.picture_id = kMaxTwoBytePictureId; + expected.max_picture_id = kMaxTwoBytePictureId; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseLayerInfoWithNonFlexibleMode) { + const uint8_t kTemporalIdx = 2; + const uint8_t kUbit = 1; + const uint8_t kSpatialIdx = 1; + const uint8_t kDbit = 1; + const uint8_t kTl0PicIdx = 17; + uint8_t packet[13] = {0}; + packet[0] = 0x20; // I:0 P:0 L:1 F:0 B:0 E:0 V:0 Z:0 + packet[1] = (kTemporalIdx << 5) | (kUbit << 4) | (kSpatialIdx << 1) | kDbit; + packet[2] = kTl0PicIdx; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + // T:2 U:1 S:1 D:1 + // TL0PICIDX:17 + expected.temporal_idx = kTemporalIdx; + expected.temporal_up_switch = kUbit ? true : false; + expected.spatial_idx = kSpatialIdx; + expected.inter_layer_predicted = kDbit ? true : false; + expected.tl0_pic_idx = kTl0PicIdx; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseLayerInfoWithFlexibleMode) { + const uint8_t kTemporalIdx = 2; + const uint8_t kUbit = 1; + const uint8_t kSpatialIdx = 0; + const uint8_t kDbit = 0; + uint8_t packet[13] = {0}; + packet[0] = 0x38; // I:0 P:0 L:1 F:1 B:1 E:0 V:0 Z:0 + packet[1] = (kTemporalIdx << 5) | (kUbit << 4) | (kSpatialIdx << 1) | kDbit; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 2); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + // I:0 P:0 L:1 F:1 B:1 E:0 V:0 Z:0 + // L: T:2 U:1 S:0 D:0 + expected.beginning_of_frame = true; + expected.flexible_mode = true; + expected.temporal_idx = kTemporalIdx; + expected.temporal_up_switch = kUbit ? true : false; + expected.spatial_idx = kSpatialIdx; + expected.inter_layer_predicted = kDbit ? true : false; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseRefIdx) { + const int16_t kPictureId = 17; + const uint8_t kPdiff1 = 17; + const uint8_t kPdiff2 = 18; + const uint8_t kPdiff3 = 127; + uint8_t packet[13] = {0}; + packet[0] = 0xD8; // I:1 P:1 L:0 F:1 B:1 E:0 V:0 Z:0 + packet[1] = 0x80 | ((kPictureId >> 8) & 0x7F); // Two byte pictureID. + packet[2] = kPictureId; + packet[3] = (kPdiff1 << 1) | 1; // P_DIFF N:1 + packet[4] = (kPdiff2 << 1) | 1; // P_DIFF N:1 + packet[5] = (kPdiff3 << 1) | 0; // P_DIFF N:0 + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 6); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + // I:1 P:1 L:0 F:1 B:1 E:0 V:0 Z:0 + // I: PICTURE ID:17 + // I: + // P,F: P_DIFF:17 N:1 => refPicId = 17 - 17 = 0 + // P,F: P_DIFF:18 N:1 => refPicId = (kMaxPictureId + 1) + 17 - 18 = 0x7FFF + // P,F: P_DIFF:127 N:0 => refPicId = (kMaxPictureId + 1) + 17 - 127 = 32658 + expected.beginning_of_frame = true; + expected.inter_pic_predicted = true; + expected.flexible_mode = true; + expected.picture_id = kPictureId; + expected.num_ref_pics = 3; + expected.pid_diff[0] = kPdiff1; + expected.pid_diff[1] = kPdiff2; + expected.pid_diff[2] = kPdiff3; + expected.ref_picture_id[0] = 0; + expected.ref_picture_id[1] = 0x7FFF; + expected.ref_picture_id[2] = 32658; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseRefIdxFailsWithNoPictureId) { + const uint8_t kPdiff = 3; + uint8_t packet[13] = {0}; + packet[0] = 0x58; // I:0 P:1 L:0 F:1 B:1 E:0 V:0 Z:0 + packet[1] = (kPdiff << 1); // P,F: P_DIFF:3 N:0 + + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header), 0); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseRefIdxFailsWithTooManyRefPics) { + const uint8_t kPdiff = 3; + uint8_t packet[13] = {0}; + packet[0] = 0xD8; // I:1 P:1 L:0 F:1 B:1 E:0 V:0 Z:0 + packet[1] = kMaxOneBytePictureId; // I: PICTURE ID:127 + packet[2] = (kPdiff << 1) | 1; // P,F: P_DIFF:3 N:1 + packet[3] = (kPdiff << 1) | 1; // P,F: P_DIFF:3 N:1 + packet[4] = (kPdiff << 1) | 1; // P,F: P_DIFF:3 N:1 + packet[5] = (kPdiff << 1) | 0; // P,F: P_DIFF:3 N:0 + + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header), 0); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseSsData) { + const uint8_t kYbit = 0; + const size_t kNs = 2; + const size_t kNg = 2; + uint8_t packet[23] = {0}; + packet[0] = 0x0A; // I:0 P:0 L:0 F:0 B:1 E:0 V:1 Z:0 + packet[1] = ((kNs - 1) << 5) | (kYbit << 4) | (1 << 3); // N_S Y G:1 - + packet[2] = kNg; // N_G + packet[3] = (0 << 5) | (1 << 4) | (0 << 2) | 0; // T:0 U:1 R:0 - + packet[4] = (2 << 5) | (0 << 4) | (1 << 2) | 0; // T:2 U:0 R:1 - + packet[5] = 33; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 6); + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + expected.beginning_of_frame = true; + expected.ss_data_available = true; + expected.num_spatial_layers = kNs; + expected.spatial_layer_resolution_present = kYbit ? true : false; + expected.gof.num_frames_in_gof = kNg; + expected.gof.temporal_idx[0] = 0; + expected.gof.temporal_idx[1] = 2; + expected.gof.temporal_up_switch[0] = true; + expected.gof.temporal_up_switch[1] = false; + expected.gof.num_ref_pics[0] = 0; + expected.gof.num_ref_pics[1] = 1; + expected.gof.pid_diff[1][0] = 33; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseFirstPacketInKeyFrame) { + uint8_t packet[2] = {0}; + packet[0] = 0x08; // I:0 P:0 L:0 F:0 B:1 E:0 V:0 Z:0 + + RTPVideoHeader video_header; + VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(video_header.frame_type, VideoFrameType::kVideoFrameKey); + EXPECT_TRUE(video_header.is_first_packet_in_frame); + EXPECT_FALSE(video_header.is_last_packet_in_frame); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseLastPacketInDeltaFrame) { + uint8_t packet[2] = {0}; + packet[0] = 0x44; // I:0 P:1 L:0 F:0 B:0 E:1 V:0 Z:0 + + RTPVideoHeader video_header; + VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_FALSE(video_header.is_first_packet_in_frame); + EXPECT_TRUE(video_header.is_last_packet_in_frame); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseResolution) { + const uint16_t kWidth[2] = {640, 1280}; + const uint16_t kHeight[2] = {360, 720}; + uint8_t packet[20] = {0}; + packet[0] = 0x0A; // I:0 P:0 L:0 F:0 B:1 E:0 V:1 Z:0 + packet[1] = (1 << 5) | (1 << 4) | 0; // N_S:1 Y:1 G:0 + packet[2] = kWidth[0] >> 8; + packet[3] = kWidth[0] & 0xFF; + packet[4] = kHeight[0] >> 8; + packet[5] = kHeight[0] & 0xFF; + packet[6] = kWidth[1] >> 8; + packet[7] = kWidth[1] & 0xFF; + packet[8] = kHeight[1] >> 8; + packet[9] = kHeight[1] & 0xFF; + + RTPVideoHeader video_header; + VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(video_header.width, kWidth[0]); + EXPECT_EQ(video_header.height, kHeight[0]); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseFailsForNoPayloadLength) { + rtc::ArrayView<const uint8_t> empty; + + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp9::ParseRtpPayload(empty, &video_header), 0); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseFailsForTooShortBufferToFitPayload) { + uint8_t packet[] = {0}; + + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header), 0); +} + +TEST(VideoRtpDepacketizerVp9Test, ParseNonRefForInterLayerPred) { + RTPVideoHeader video_header; + RTPVideoHeaderVP9 expected; + expected.InitRTPVideoHeaderVP9(); + uint8_t packet[2] = {0}; + + packet[0] = 0x08; // I:0 P:0 L:0 F:0 B:1 E:0 V:0 Z:0 + VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + expected.beginning_of_frame = true; + expected.non_ref_for_inter_layer_pred = false; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); + + packet[0] = 0x05; // I:0 P:0 L:0 F:0 B:0 E:1 V:0 Z:1 + VideoRtpDepacketizerVp9::ParseRtpPayload(packet, &video_header); + + expected.beginning_of_frame = false; + expected.end_of_frame = true; + expected.non_ref_for_inter_layer_pred = true; + VerifyHeader(expected, + absl::get<RTPVideoHeaderVP9>(video_header.video_type_header)); +} + +TEST(VideoRtpDepacketizerVp9Test, ReferencesInputCopyOnWriteBuffer) { + constexpr size_t kHeaderSize = 1; + uint8_t packet[4] = {0}; + packet[0] = 0x0C; // I:0 P:0 L:0 F:0 B:1 E:1 V:0 Z:0 + + rtc::CopyOnWriteBuffer rtp_payload(packet); + VideoRtpDepacketizerVp9 depacketizer; + absl::optional<VideoRtpDepacketizer::ParsedRtpPayload> parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload.size(), rtp_payload.size() - kHeaderSize); + // Compare pointers to check there was no copy on write buffer unsharing. + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata() + kHeaderSize); +} +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h b/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h new file mode 100644 index 0000000000..0bc2f56917 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h @@ -0,0 +1,57 @@ +/* + * 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 MODULES_RTP_RTCP_TEST_TESTFEC_AVERAGE_RESIDUAL_LOSS_XOR_CODES_H_ +#define MODULES_RTP_RTCP_TEST_TESTFEC_AVERAGE_RESIDUAL_LOSS_XOR_CODES_H_ + +namespace webrtc { + +// Maximum number of media packets allowed in this test. The burst mask types +// are currently defined up to (kMaxMediaPacketsTest, kMaxMediaPacketsTest). +const int kMaxMediaPacketsTest = 12; + +// Maximum number of FEC codes considered in this test. +const int kNumberCodes = kMaxMediaPacketsTest * (kMaxMediaPacketsTest + 1) / 2; + +// For the random mask type: reference level for the maximum average residual +// loss expected for each code size up to: +// (kMaxMediaPacketsTest, kMaxMediaPacketsTest). +const float kMaxResidualLossRandomMask[kNumberCodes] = { + 0.009463f, 0.022436f, 0.007376f, 0.033895f, 0.012423f, 0.004644f, 0.043438f, + 0.019937f, 0.008820f, 0.003438f, 0.051282f, 0.025795f, 0.012872f, 0.006458f, + 0.003195f, 0.057728f, 0.032146f, 0.016708f, 0.009242f, 0.005054f, 0.003078f, + 0.063050f, 0.037261f, 0.021767f, 0.012447f, 0.007099f, 0.003826f, 0.002504f, + 0.067476f, 0.042348f, 0.026169f, 0.015695f, 0.009478f, 0.005887f, 0.003568f, + 0.001689f, 0.071187f, 0.046575f, 0.031697f, 0.019797f, 0.012433f, 0.007027f, + 0.004845f, 0.002777f, 0.001753f, 0.074326f, 0.050628f, 0.034978f, 0.021955f, + 0.014821f, 0.009462f, 0.006393f, 0.004181f, 0.003105f, 0.001231f, 0.077008f, + 0.054226f, 0.038407f, 0.026251f, 0.018634f, 0.011568f, 0.008130f, 0.004957f, + 0.003334f, 0.002069f, 0.001304f, 0.079318f, 0.057180f, 0.041268f, 0.028842f, + 0.020033f, 0.014061f, 0.009636f, 0.006411f, 0.004583f, 0.002817f, 0.001770f, + 0.001258f}; + +// For the bursty mask type: reference level for the maximum average residual +// loss expected for each code size up to: +// (kMaxMediaPacketsTestf, kMaxMediaPacketsTest). +const float kMaxResidualLossBurstyMask[kNumberCodes] = { + 0.033236f, 0.053344f, 0.026616f, 0.064129f, 0.036589f, 0.021892f, 0.071055f, + 0.043890f, 0.028009f, 0.018524f, 0.075968f, 0.049828f, 0.033288f, 0.022791f, + 0.016088f, 0.079672f, 0.054586f, 0.037872f, 0.026679f, 0.019326f, 0.014293f, + 0.082582f, 0.058719f, 0.042045f, 0.030504f, 0.022391f, 0.016894f, 0.012946f, + 0.084935f, 0.062169f, 0.045620f, 0.033713f, 0.025570f, 0.019439f, 0.015121f, + 0.011920f, 0.086881f, 0.065267f, 0.048721f, 0.037613f, 0.028278f, 0.022152f, + 0.017314f, 0.013791f, 0.011130f, 0.088516f, 0.067911f, 0.051709f, 0.040819f, + 0.030777f, 0.024547f, 0.019689f, 0.015877f, 0.012773f, 0.010516f, 0.089909f, + 0.070332f, 0.054402f, 0.043210f, 0.034096f, 0.026625f, 0.021823f, 0.017648f, + 0.014649f, 0.011982f, 0.010035f, 0.091109f, 0.072428f, 0.056775f, 0.045418f, + 0.036679f, 0.028599f, 0.023693f, 0.019966f, 0.016603f, 0.013690f, 0.011359f, + 0.009657f}; + +} // namespace webrtc +#endif // MODULES_RTP_RTCP_TEST_TESTFEC_AVERAGE_RESIDUAL_LOSS_XOR_CODES_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/test_fec.cc b/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/test_fec.cc new file mode 100644 index 0000000000..5ac8feca21 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/test_fec.cc @@ -0,0 +1,474 @@ +/* + * 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. + */ + +/* + * Test application for core FEC algorithm. Calls encoding and decoding + * functions in ForwardErrorCorrection directly. + */ + +#include <string.h> +#include <time.h> + +#include <list> + +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/forward_error_correction.h" +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "rtc_base/random.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" + +// #define VERBOSE_OUTPUT + +namespace webrtc { +namespace fec_private_tables { +extern const uint8_t** kPacketMaskBurstyTbl[12]; +} +namespace test { +using fec_private_tables::kPacketMaskBurstyTbl; + +void ReceivePackets( + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>>* + to_decode_list, + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>>* + received_packet_list, + size_t num_packets_to_decode, + float reorder_rate, + float duplicate_rate, + Random* random) { + RTC_DCHECK(to_decode_list->empty()); + RTC_DCHECK_LE(num_packets_to_decode, received_packet_list->size()); + + for (size_t i = 0; i < num_packets_to_decode; i++) { + auto it = received_packet_list->begin(); + // Reorder packets. + float random_variable = random->Rand<float>(); + while (random_variable < reorder_rate) { + ++it; + if (it == received_packet_list->end()) { + --it; + break; + } + random_variable = random->Rand<float>(); + } + to_decode_list->push_back(std::move(*it)); + received_packet_list->erase(it); + + // Duplicate packets. + ForwardErrorCorrection::ReceivedPacket* received_packet = + to_decode_list->back().get(); + random_variable = random->Rand<float>(); + while (random_variable < duplicate_rate) { + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> duplicate_packet( + new ForwardErrorCorrection::ReceivedPacket()); + *duplicate_packet = *received_packet; + duplicate_packet->pkt = new ForwardErrorCorrection::Packet(); + duplicate_packet->pkt->data = received_packet->pkt->data; + + to_decode_list->push_back(std::move(duplicate_packet)); + random_variable = random->Rand<float>(); + } + } +} + +void RunTest(bool use_flexfec) { + // TODO(marpan): Split this function into subroutines/helper functions. + enum { kMaxNumberMediaPackets = 48 }; + enum { kMaxNumberFecPackets = 48 }; + + const uint32_t kNumMaskBytesL0 = 2; + const uint32_t kNumMaskBytesL1 = 6; + + // FOR UEP + const bool kUseUnequalProtection = true; + + // FEC mask types. + const FecMaskType kMaskTypes[] = {kFecMaskRandom, kFecMaskBursty}; + const int kNumFecMaskTypes = sizeof(kMaskTypes) / sizeof(*kMaskTypes); + + // Maximum number of media packets allowed for the mask type. + const uint16_t kMaxMediaPackets[] = { + kMaxNumberMediaPackets, + sizeof(kPacketMaskBurstyTbl) / sizeof(*kPacketMaskBurstyTbl)}; + + ASSERT_EQ(12, kMaxMediaPackets[1]) << "Max media packets for bursty mode not " + "equal to 12."; + + ForwardErrorCorrection::PacketList media_packet_list; + std::list<ForwardErrorCorrection::Packet*> fec_packet_list; + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>> + to_decode_list; + std::vector<std::unique_ptr<ForwardErrorCorrection::ReceivedPacket>> + received_packet_list; + ForwardErrorCorrection::RecoveredPacketList recovered_packet_list; + std::list<uint8_t*> fec_mask_list; + + // Running over only two loss rates to limit execution time. + const float loss_rate[] = {0.05f, 0.01f}; + const uint32_t loss_rate_size = sizeof(loss_rate) / sizeof(*loss_rate); + const float reorder_rate = 0.1f; + const float duplicate_rate = 0.1f; + + uint8_t media_loss_mask[kMaxNumberMediaPackets]; + uint8_t fec_loss_mask[kMaxNumberFecPackets]; + uint8_t fec_packet_masks[kMaxNumberFecPackets][kMaxNumberMediaPackets]; + + // Seed the random number generator, storing the seed to file in order to + // reproduce past results. + const unsigned int random_seed = static_cast<unsigned int>(time(nullptr)); + Random random(random_seed); + std::string filename = webrtc::test::OutputPath() + "randomSeedLog.txt"; + FILE* random_seed_file = fopen(filename.c_str(), "a"); + fprintf(random_seed_file, "%u\n", random_seed); + fclose(random_seed_file); + random_seed_file = nullptr; + + uint16_t seq_num = 0; + uint32_t timestamp = random.Rand<uint32_t>(); + const uint32_t media_ssrc = random.Rand(1u, 0xfffffffe); + uint32_t fec_ssrc; + uint16_t fec_seq_num_offset; + if (use_flexfec) { + fec_ssrc = random.Rand(1u, 0xfffffffe); + fec_seq_num_offset = random.Rand(0, 1 << 15); + } else { + fec_ssrc = media_ssrc; + fec_seq_num_offset = 0; + } + + std::unique_ptr<ForwardErrorCorrection> fec; + if (use_flexfec) { + fec = ForwardErrorCorrection::CreateFlexfec(fec_ssrc, media_ssrc); + } else { + RTC_DCHECK_EQ(media_ssrc, fec_ssrc); + fec = ForwardErrorCorrection::CreateUlpfec(fec_ssrc); + } + + // Loop over the mask types: random and bursty. + for (int mask_type_idx = 0; mask_type_idx < kNumFecMaskTypes; + ++mask_type_idx) { + for (uint32_t loss_rate_idx = 0; loss_rate_idx < loss_rate_size; + ++loss_rate_idx) { + printf("Loss rate: %.2f, Mask type %d \n", loss_rate[loss_rate_idx], + mask_type_idx); + + const uint32_t packet_mask_max = kMaxMediaPackets[mask_type_idx]; + std::unique_ptr<uint8_t[]> packet_mask( + new uint8_t[packet_mask_max * kNumMaskBytesL1]); + + FecMaskType fec_mask_type = kMaskTypes[mask_type_idx]; + + for (uint32_t num_media_packets = 1; num_media_packets <= packet_mask_max; + num_media_packets++) { + internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets); + + for (uint32_t num_fec_packets = 1; + num_fec_packets <= num_media_packets && + num_fec_packets <= packet_mask_max; + num_fec_packets++) { + // Loop over num_imp_packets: usually <= (0.3*num_media_packets). + // For this test we check up to ~ (num_media_packets / 4). + uint32_t max_num_imp_packets = num_media_packets / 4 + 1; + for (uint32_t num_imp_packets = 0; + num_imp_packets <= max_num_imp_packets && + num_imp_packets <= packet_mask_max; + num_imp_packets++) { + uint8_t protection_factor = + static_cast<uint8_t>(num_fec_packets * 255 / num_media_packets); + + const uint32_t mask_bytes_per_fec_packet = + (num_media_packets > 16) ? kNumMaskBytesL1 : kNumMaskBytesL0; + + memset(packet_mask.get(), 0, + num_media_packets * mask_bytes_per_fec_packet); + + // Transfer packet masks from bit-mask to byte-mask. + internal::GeneratePacketMasks( + num_media_packets, num_fec_packets, num_imp_packets, + kUseUnequalProtection, &mask_table, packet_mask.get()); + +#ifdef VERBOSE_OUTPUT + printf( + "%u media packets, %u FEC packets, %u num_imp_packets, " + "loss rate = %.2f \n", + num_media_packets, num_fec_packets, num_imp_packets, + loss_rate[loss_rate_idx]); + printf("Packet mask matrix \n"); +#endif + + for (uint32_t i = 0; i < num_fec_packets; i++) { + for (uint32_t j = 0; j < num_media_packets; j++) { + const uint8_t byte_mask = + packet_mask[i * mask_bytes_per_fec_packet + j / 8]; + const uint32_t bit_position = (7 - j % 8); + fec_packet_masks[i][j] = + (byte_mask & (1 << bit_position)) >> bit_position; +#ifdef VERBOSE_OUTPUT + printf("%u ", fec_packet_masks[i][j]); +#endif + } +#ifdef VERBOSE_OUTPUT + printf("\n"); +#endif + } +#ifdef VERBOSE_OUTPUT + printf("\n"); +#endif + // Check for all zero rows or columns: indicates incorrect mask. + uint32_t row_limit = num_media_packets; + for (uint32_t i = 0; i < num_fec_packets; ++i) { + uint32_t row_sum = 0; + for (uint32_t j = 0; j < row_limit; ++j) { + row_sum += fec_packet_masks[i][j]; + } + ASSERT_NE(0u, row_sum) << "Row is all zero " << i; + } + for (uint32_t j = 0; j < row_limit; ++j) { + uint32_t column_sum = 0; + for (uint32_t i = 0; i < num_fec_packets; ++i) { + column_sum += fec_packet_masks[i][j]; + } + ASSERT_NE(0u, column_sum) << "Column is all zero " << j; + } + + // Construct media packets. + // Reset the sequence number here for each FEC code/mask tested + // below, to avoid sequence number wrap-around. In actual decoding, + // old FEC packets in list are dropped if sequence number wrap + // around is detected. This case is currently not handled below. + seq_num = 0; + for (uint32_t i = 0; i < num_media_packets; ++i) { + std::unique_ptr<ForwardErrorCorrection::Packet> media_packet( + new ForwardErrorCorrection::Packet()); + const uint32_t kMinPacketSize = 12; + const uint32_t kMaxPacketSize = static_cast<uint32_t>( + IP_PACKET_SIZE - 12 - 28 - fec->MaxPacketOverhead()); + size_t packet_length = + random.Rand(kMinPacketSize, kMaxPacketSize); + media_packet->data.SetSize(packet_length); + + uint8_t* data = media_packet->data.MutableData(); + // Generate random values for the first 2 bytes. + data[0] = random.Rand<uint8_t>(); + data[1] = random.Rand<uint8_t>(); + + // The first two bits are assumed to be 10 by the + // FEC encoder. In fact the FEC decoder will set the + // two first bits to 10 regardless of what they + // actually were. Set the first two bits to 10 + // so that a memcmp can be performed for the + // whole restored packet. + data[0] |= 0x80; + data[0] &= 0xbf; + + // FEC is applied to a whole frame. + // A frame is signaled by multiple packets without + // the marker bit set followed by the last packet of + // the frame for which the marker bit is set. + // Only push one (fake) frame to the FEC. + data[1] &= 0x7f; + + ByteWriter<uint16_t>::WriteBigEndian(&data[2], seq_num); + ByteWriter<uint32_t>::WriteBigEndian(&data[4], timestamp); + ByteWriter<uint32_t>::WriteBigEndian(&data[8], media_ssrc); + // Generate random values for payload + for (size_t j = 12; j < packet_length; ++j) { + data[j] = random.Rand<uint8_t>(); + } + media_packet_list.push_back(std::move(media_packet)); + seq_num++; + } + media_packet_list.back()->data.MutableData()[1] |= 0x80; + + ASSERT_EQ(0, fec->EncodeFec(media_packet_list, protection_factor, + num_imp_packets, kUseUnequalProtection, + fec_mask_type, &fec_packet_list)) + << "EncodeFec() failed"; + + ASSERT_EQ(num_fec_packets, fec_packet_list.size()) + << "We requested " << num_fec_packets + << " FEC packets, but " + "EncodeFec() produced " + << fec_packet_list.size(); + + memset(media_loss_mask, 0, sizeof(media_loss_mask)); + uint32_t media_packet_idx = 0; + for (const auto& media_packet : media_packet_list) { + // We want a value between 0 and 1. + const float loss_random_variable = random.Rand<float>(); + + if (loss_random_variable >= loss_rate[loss_rate_idx]) { + media_loss_mask[media_packet_idx] = 1; + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> + received_packet( + new ForwardErrorCorrection::ReceivedPacket()); + received_packet->pkt = new ForwardErrorCorrection::Packet(); + received_packet->pkt->data = media_packet->data; + received_packet->ssrc = media_ssrc; + received_packet->seq_num = ByteReader<uint16_t>::ReadBigEndian( + media_packet->data.data() + 2); + received_packet->is_fec = false; + received_packet_list.push_back(std::move(received_packet)); + } + media_packet_idx++; + } + + memset(fec_loss_mask, 0, sizeof(fec_loss_mask)); + uint32_t fec_packet_idx = 0; + for (auto* fec_packet : fec_packet_list) { + const float loss_random_variable = random.Rand<float>(); + if (loss_random_variable >= loss_rate[loss_rate_idx]) { + fec_loss_mask[fec_packet_idx] = 1; + std::unique_ptr<ForwardErrorCorrection::ReceivedPacket> + received_packet( + new ForwardErrorCorrection::ReceivedPacket()); + received_packet->pkt = new ForwardErrorCorrection::Packet(); + received_packet->pkt->data = fec_packet->data; + received_packet->seq_num = fec_seq_num_offset + seq_num; + received_packet->is_fec = true; + received_packet->ssrc = fec_ssrc; + received_packet_list.push_back(std::move(received_packet)); + + fec_mask_list.push_back(fec_packet_masks[fec_packet_idx]); + } + ++fec_packet_idx; + ++seq_num; + } + +#ifdef VERBOSE_OUTPUT + printf("Media loss mask:\n"); + for (uint32_t i = 0; i < num_media_packets; i++) { + printf("%u ", media_loss_mask[i]); + } + printf("\n\n"); + + printf("FEC loss mask:\n"); + for (uint32_t i = 0; i < num_fec_packets; i++) { + printf("%u ", fec_loss_mask[i]); + } + printf("\n\n"); +#endif + + auto fec_mask_it = fec_mask_list.begin(); + while (fec_mask_it != fec_mask_list.end()) { + uint32_t hamming_dist = 0; + uint32_t recovery_position = 0; + for (uint32_t i = 0; i < num_media_packets; i++) { + if (media_loss_mask[i] == 0 && (*fec_mask_it)[i] == 1) { + recovery_position = i; + ++hamming_dist; + } + } + auto item_to_delete = fec_mask_it; + ++fec_mask_it; + + if (hamming_dist == 1) { + // Recovery possible. Restart search. + media_loss_mask[recovery_position] = 1; + fec_mask_it = fec_mask_list.begin(); + } else if (hamming_dist == 0) { + // FEC packet cannot provide further recovery. + fec_mask_list.erase(item_to_delete); + } + } +#ifdef VERBOSE_OUTPUT + printf("Recovery mask:\n"); + for (uint32_t i = 0; i < num_media_packets; ++i) { + printf("%u ", media_loss_mask[i]); + } + printf("\n\n"); +#endif + // For error-checking frame completion. + bool fec_packet_received = false; + while (!received_packet_list.empty()) { + size_t num_packets_to_decode = random.Rand( + 1u, static_cast<uint32_t>(received_packet_list.size())); + ReceivePackets(&to_decode_list, &received_packet_list, + num_packets_to_decode, reorder_rate, + duplicate_rate, &random); + + if (fec_packet_received == false) { + for (const auto& received_packet : to_decode_list) { + if (received_packet->is_fec) { + fec_packet_received = true; + } + } + } + for (const auto& received_packet : to_decode_list) { + fec->DecodeFec(*received_packet, &recovered_packet_list); + } + to_decode_list.clear(); + } + media_packet_idx = 0; + for (const auto& media_packet : media_packet_list) { + if (media_loss_mask[media_packet_idx] == 1) { + // Should have recovered this packet. + auto recovered_packet_list_it = recovered_packet_list.cbegin(); + + ASSERT_FALSE(recovered_packet_list_it == + recovered_packet_list.end()) + << "Insufficient number of recovered packets."; + ForwardErrorCorrection::RecoveredPacket* recovered_packet = + recovered_packet_list_it->get(); + + ASSERT_EQ(recovered_packet->pkt->data.size(), + media_packet->data.size()) + << "Recovered packet length not identical to original " + "media packet"; + ASSERT_EQ(0, memcmp(recovered_packet->pkt->data.cdata(), + media_packet->data.cdata(), + media_packet->data.size())) + << "Recovered packet payload not identical to original " + "media packet"; + recovered_packet_list.pop_front(); + } + ++media_packet_idx; + } + fec->ResetState(&recovered_packet_list); + ASSERT_TRUE(recovered_packet_list.empty()) + << "Excessive number of recovered packets.\t size is: " + << recovered_packet_list.size(); + // -- Teardown -- + media_packet_list.clear(); + + // Clear FEC packet list, so we don't pass in a non-empty + // list in the next call to DecodeFec(). + fec_packet_list.clear(); + + // Delete received packets we didn't pass to DecodeFec(), due to + // early frame completion. + received_packet_list.clear(); + + while (!fec_mask_list.empty()) { + fec_mask_list.pop_front(); + } + timestamp += 90000 / 30; + } // loop over num_imp_packets + } // loop over FecPackets + } // loop over num_media_packets + } // loop over loss rates + } // loop over mask types + + // Have DecodeFec clear the recovered packet list. + fec->ResetState(&recovered_packet_list); + ASSERT_TRUE(recovered_packet_list.empty()) + << "Recovered packet list is not empty"; +} + +TEST(FecTest, UlpfecTest) { + RunTest(false); +} + +TEST(FecTest, FlexfecTest) { + RunTest(true); +} + +} // namespace test +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc b/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc new file mode 100644 index 0000000000..25ceee585a --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc @@ -0,0 +1,1060 @@ +/* + * 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. + */ + +/* + * The purpose of this test is to compute metrics to characterize the properties + * and efficiency of the packets masks used in the generic XOR FEC code. + * + * The metrics measure the efficiency (recovery potential or residual loss) of + * the FEC code, under various statistical loss models for the packet/symbol + * loss events. Various constraints on the behavior of these metrics are + * verified, and compared to the reference RS (Reed-Solomon) code. This serves + * in some way as a basic check/benchmark for the packet masks. + * + * By an FEC code, we mean an erasure packet/symbol code, characterized by: + * (1) The code size parameters (k,m), where k = number of source/media packets, + * and m = number of FEC packets, + * (2) The code type: XOR or RS. + * In the case of XOR, the residual loss is determined via the set of packet + * masks (generator matrix). In the case of RS, the residual loss is determined + * directly from the MDS (maximum distance separable) property of RS. + * + * Currently two classes of packets masks are available (random type and bursty + * type), so three codes are considered below: RS, XOR-random, and XOR-bursty. + * The bursty class is defined up to k=12, so (k=12,m=12) is largest code size + * considered in this test. + * + * The XOR codes are defined via the RFC 5109 and correspond to the class of + * LDGM (low density generator matrix) codes, which is a subset of the LDPC + * (low density parity check) codes. Future implementation will consider + * extending our XOR codes to include LDPC codes, which explicitly include + * protection of FEC packets. + * + * The type of packet/symbol loss models considered in this test are: + * (1) Random loss: Bernoulli process, characterized by the average loss rate. + * (2) Bursty loss: Markov chain (Gilbert-Elliot model), characterized by two + * parameters: average loss rate and average burst length. + */ + +#include <cmath> +#include <memory> + +#include "modules/rtp_rtcp/source/forward_error_correction_internal.h" +#include "modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h" +#include "test/gtest.h" +#include "test/testsupport/file_utils.h" + +namespace webrtc { + +// Maximum number of media packets allows for XOR (RFC 5109) code. +enum { kMaxNumberMediaPackets = 48 }; + +// Maximum number of media packets allowed for each mask type. +const uint16_t kMaxMediaPackets[] = {kMaxNumberMediaPackets, 12}; + +// Maximum gap size for characterizing the consecutiveness of the loss. +const int kMaxGapSize = 2 * kMaxMediaPacketsTest; + +// Number of gap levels written to file/output. +const int kGapSizeOutput = 5; + +// Maximum number of states for characterizing the residual loss distribution. +const int kNumStatesDistribution = 2 * kMaxMediaPacketsTest * kMaxGapSize + 1; + +// The code type. +enum CodeType { + xor_random_code, // XOR with random mask type. + xor_bursty_code, // XOR with bursty mask type. + rs_code // Reed_solomon. +}; + +// The code size parameters. +struct CodeSizeParams { + int num_media_packets; + int num_fec_packets; + // Protection level: num_fec_packets / (num_media_packets + num_fec_packets). + float protection_level; + // Number of loss configurations, for a given loss number and gap number. + // The gap number refers to the maximum gap/hole of a loss configuration + // (used to measure the "consecutiveness" of the loss). + int configuration_density[kNumStatesDistribution]; +}; + +// The type of loss models. +enum LossModelType { kRandomLossModel, kBurstyLossModel }; + +struct LossModel { + LossModelType loss_type; + float average_loss_rate; + float average_burst_length; +}; + +// Average loss rates. +const float kAverageLossRate[] = {0.025f, 0.05f, 0.1f, 0.25f}; + +// Average burst lengths. The case of |kAverageBurstLength = 1.0| refers to +// the random model. Note that for the random (Bernoulli) model, the average +// burst length is determined by the average loss rate, i.e., +// AverageBurstLength = 1 / (1 - AverageLossRate) for random model. +const float kAverageBurstLength[] = {1.0f, 2.0f, 4.0f}; + +// Total number of loss models: For each burst length case, there are +// a number of models corresponding to the loss rates. +const int kNumLossModels = + (sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength)) * + (sizeof(kAverageLossRate) / sizeof(*kAverageLossRate)); + +// Thresholds on the average loss rate of the packet loss model, below which +// certain properties of the codes are expected. +float loss_rate_upper_threshold = 0.20f; +float loss_rate_lower_threshold = 0.025f; + +// Set of thresholds on the expected average recovery rate, for each code type. +// These are global thresholds for now; in future version we may condition them +// on the code length/size and protection level. +const float kRecoveryRateXorRandom[3] = {0.94f, 0.50f, 0.19f}; +const float kRecoveryRateXorBursty[3] = {0.90f, 0.54f, 0.22f}; + +// Metrics for a given FEC code; each code is defined by the code type +// (RS, XOR-random/bursty), and the code size parameters (k,m), where +// k = num_media_packets, m = num_fec_packets. +struct MetricsFecCode { + // The average and variance of the residual loss, as a function of the + // packet/symbol loss model. The average/variance is computed by averaging + // over all loss configurations wrt the loss probability given by the + // underlying loss model. + double average_residual_loss[kNumLossModels]; + double variance_residual_loss[kNumLossModels]; + // The residual loss, as a function of the loss number and the gap number of + // the loss configurations. The gap number refers to the maximum gap/hole of + // a loss configuration (used to measure the "consecutiveness" of the loss). + double residual_loss_per_loss_gap[kNumStatesDistribution]; + // The recovery rate as a function of the loss number. + double recovery_rate_per_loss[2 * kMaxMediaPacketsTest + 1]; +}; + +MetricsFecCode kMetricsXorRandom[kNumberCodes]; +MetricsFecCode kMetricsXorBursty[kNumberCodes]; +MetricsFecCode kMetricsReedSolomon[kNumberCodes]; + +class FecPacketMaskMetricsTest : public ::testing::Test { + protected: + FecPacketMaskMetricsTest() {} + + int max_num_codes_; + LossModel loss_model_[kNumLossModels]; + CodeSizeParams code_params_[kNumberCodes]; + + uint8_t fec_packet_masks_[kMaxNumberMediaPackets][kMaxNumberMediaPackets]; + FILE* fp_mask_; + + // Measure of the gap of the loss for configuration given by `state`. + // This is to measure degree of consecutiveness for the loss configuration. + // Useful if the packets are sent out in order of sequence numbers and there + // is little/no re-ordering during transmission. + int GapLoss(int tot_num_packets, uint8_t* state) { + int max_gap_loss = 0; + // Find the first loss. + int first_loss = 0; + for (int i = 0; i < tot_num_packets; i++) { + if (state[i] == 1) { + first_loss = i; + break; + } + } + int prev_loss = first_loss; + for (int i = first_loss + 1; i < tot_num_packets; i++) { + if (state[i] == 1) { // Lost state. + int gap_loss = (i - prev_loss) - 1; + if (gap_loss > max_gap_loss) { + max_gap_loss = gap_loss; + } + prev_loss = i; + } + } + return max_gap_loss; + } + + // Returns the number of recovered media packets for the XOR code, given the + // packet mask `fec_packet_masks_`, for the loss state/configuration given by + // `state`. + int RecoveredMediaPackets(int num_media_packets, + int num_fec_packets, + uint8_t* state) { + std::unique_ptr<uint8_t[]> state_tmp( + new uint8_t[num_media_packets + num_fec_packets]); + memcpy(state_tmp.get(), state, num_media_packets + num_fec_packets); + int num_recovered_packets = 0; + bool loop_again = true; + while (loop_again) { + loop_again = false; + bool recovered_new_packet = false; + // Check if we can recover anything: loop over all possible FEC packets. + for (int i = 0; i < num_fec_packets; i++) { + if (state_tmp[i + num_media_packets] == 0) { + // We have this FEC packet. + int num_packets_in_mask = 0; + int num_received_packets_in_mask = 0; + for (int j = 0; j < num_media_packets; j++) { + if (fec_packet_masks_[i][j] == 1) { + num_packets_in_mask++; + if (state_tmp[j] == 0) { + num_received_packets_in_mask++; + } + } + } + if ((num_packets_in_mask - 1) == num_received_packets_in_mask) { + // We can recover the missing media packet for this FEC packet. + num_recovered_packets++; + recovered_new_packet = true; + int jsel = -1; + int check_num_recovered = 0; + // Update the state with newly recovered media packet. + for (int j = 0; j < num_media_packets; j++) { + if (fec_packet_masks_[i][j] == 1 && state_tmp[j] == 1) { + // This is the lost media packet we will recover. + jsel = j; + check_num_recovered++; + } + } + // Check that we can only recover 1 packet. + RTC_DCHECK_EQ(check_num_recovered, 1); + // Update the state with the newly recovered media packet. + state_tmp[jsel] = 0; + } + } + } // Go to the next FEC packet in the loop. + // If we have recovered at least one new packet in this FEC loop, + // go through loop again, otherwise we leave loop. + if (recovered_new_packet) { + loop_again = true; + } + } + return num_recovered_packets; + } + + // Compute the probability of occurence of the loss state/configuration, + // given by `state`, for all the loss models considered in this test. + void ComputeProbabilityWeight(double* prob_weight, + uint8_t* state, + int tot_num_packets) { + // Loop over the loss models. + for (int k = 0; k < kNumLossModels; k++) { + double loss_rate = static_cast<double>(loss_model_[k].average_loss_rate); + double burst_length = + static_cast<double>(loss_model_[k].average_burst_length); + double result = 1.0; + if (loss_model_[k].loss_type == kRandomLossModel) { + for (int i = 0; i < tot_num_packets; i++) { + if (state[i] == 0) { + result *= (1.0 - loss_rate); + } else { + result *= loss_rate; + } + } + } else { // Gilbert-Elliot model for burst model. + RTC_DCHECK_EQ(loss_model_[k].loss_type, kBurstyLossModel); + // Transition probabilities: from previous to current state. + // Prob. of previous = lost --> current = received. + double prob10 = 1.0 / burst_length; + // Prob. of previous = lost --> currrent = lost. + double prob11 = 1.0 - prob10; + // Prob. of previous = received --> current = lost. + double prob01 = prob10 * (loss_rate / (1.0 - loss_rate)); + // Prob. of previous = received --> current = received. + double prob00 = 1.0 - prob01; + + // Use stationary probability for first state/packet. + if (state[0] == 0) { // Received + result = (1.0 - loss_rate); + } else { // Lost + result = loss_rate; + } + + // Subsequent states: use transition probabilities. + for (int i = 1; i < tot_num_packets; i++) { + // Current state is received + if (state[i] == 0) { + if (state[i - 1] == 0) { + result *= prob00; // Previous received, current received. + } else { + result *= prob10; // Previous lost, current received. + } + } else { // Current state is lost + if (state[i - 1] == 0) { + result *= prob01; // Previous received, current lost. + } else { + result *= prob11; // Previous lost, current lost. + } + } + } + } + prob_weight[k] = result; + } + } + + void CopyMetrics(MetricsFecCode* metrics_output, + MetricsFecCode metrics_input) { + memcpy(metrics_output->average_residual_loss, + metrics_input.average_residual_loss, + sizeof(double) * kNumLossModels); + memcpy(metrics_output->variance_residual_loss, + metrics_input.variance_residual_loss, + sizeof(double) * kNumLossModels); + memcpy(metrics_output->residual_loss_per_loss_gap, + metrics_input.residual_loss_per_loss_gap, + sizeof(double) * kNumStatesDistribution); + memcpy(metrics_output->recovery_rate_per_loss, + metrics_input.recovery_rate_per_loss, + sizeof(double) * 2 * kMaxMediaPacketsTest); + } + + // Compute the residual loss per gap, by summing the + // `residual_loss_per_loss_gap` over all loss configurations up to loss number + // = `num_fec_packets`. + double ComputeResidualLossPerGap(MetricsFecCode metrics, + int gap_number, + int num_fec_packets, + int code_index) { + double residual_loss_gap = 0.0; + int tot_num_configs = 0; + for (int loss = 1; loss <= num_fec_packets; loss++) { + int index = gap_number * (2 * kMaxMediaPacketsTest) + loss; + residual_loss_gap += metrics.residual_loss_per_loss_gap[index]; + tot_num_configs += code_params_[code_index].configuration_density[index]; + } + // Normalize, to compare across code sizes. + if (tot_num_configs > 0) { + residual_loss_gap = + residual_loss_gap / static_cast<double>(tot_num_configs); + } + return residual_loss_gap; + } + + // Compute the recovery rate per loss number, by summing the + // `residual_loss_per_loss_gap` over all gap configurations. + void ComputeRecoveryRatePerLoss(MetricsFecCode* metrics, + int num_media_packets, + int num_fec_packets, + int code_index) { + for (int loss = 1; loss <= num_media_packets + num_fec_packets; loss++) { + metrics->recovery_rate_per_loss[loss] = 0.0; + int tot_num_configs = 0; + double arl = 0.0; + for (int gap = 0; gap < kMaxGapSize; gap++) { + int index = gap * (2 * kMaxMediaPacketsTest) + loss; + arl += metrics->residual_loss_per_loss_gap[index]; + tot_num_configs += + code_params_[code_index].configuration_density[index]; + } + // Normalize, to compare across code sizes. + if (tot_num_configs > 0) { + arl = arl / static_cast<double>(tot_num_configs); + } + // Recovery rate for a given loss `loss` is 1 minus the scaled `arl`, + // where the scale factor is relative to code size/parameters. + double scaled_loss = + static_cast<double>(loss * num_media_packets) / + static_cast<double>(num_media_packets + num_fec_packets); + metrics->recovery_rate_per_loss[loss] = 1.0 - arl / scaled_loss; + } + } + + void SetMetricsZero(MetricsFecCode* metrics) { + memset(metrics->average_residual_loss, 0, sizeof(double) * kNumLossModels); + memset(metrics->variance_residual_loss, 0, sizeof(double) * kNumLossModels); + memset(metrics->residual_loss_per_loss_gap, 0, + sizeof(double) * kNumStatesDistribution); + memset(metrics->recovery_rate_per_loss, 0, + sizeof(double) * 2 * kMaxMediaPacketsTest + 1); + } + + // Compute the metrics for an FEC code, given by the code type `code_type` + // (XOR-random/ bursty or RS), and by the code index `code_index` + // (which containes the code size parameters/protection length). + void ComputeMetricsForCode(CodeType code_type, int code_index) { + std::unique_ptr<double[]> prob_weight(new double[kNumLossModels]); + memset(prob_weight.get(), 0, sizeof(double) * kNumLossModels); + MetricsFecCode metrics_code; + SetMetricsZero(&metrics_code); + + int num_media_packets = code_params_[code_index].num_media_packets; + int num_fec_packets = code_params_[code_index].num_fec_packets; + int tot_num_packets = num_media_packets + num_fec_packets; + std::unique_ptr<uint8_t[]> state(new uint8_t[tot_num_packets]); + memset(state.get(), 0, tot_num_packets); + + int num_loss_configurations = 1 << tot_num_packets; + // Loop over all loss configurations for the symbol sequence of length + // `tot_num_packets`. In this version we process up to (k=12, m=12) codes, + // and get exact expressions for the residual loss. + // TODO(marpan): For larger codes, loop over some random sample of loss + // configurations, sampling driven by the underlying statistical loss model + // (importance sampling). + + // The symbols/packets are arranged as a sequence of source/media packets + // followed by FEC packets. This is the sequence ordering used in the RTP. + // A configuration refers to a sequence of received/lost (0/1 bit) states + // for the string of packets/symbols. For example, for a (k=4,m=3) code + // (4 media packets, 3 FEC packets), with 2 losses (one media and one FEC), + // the loss configurations is: + // Media1 Media2 Media3 Media4 FEC1 FEC2 FEC3 + // 0 0 1 0 0 1 0 + for (int i = 1; i < num_loss_configurations; i++) { + // Counter for number of packets lost. + int num_packets_lost = 0; + // Counters for the number of media packets lost. + int num_media_packets_lost = 0; + + // Map configuration number to a loss state. + for (int j = 0; j < tot_num_packets; j++) { + state[j] = 0; // Received state. + int bit_value = i >> (tot_num_packets - j - 1) & 1; + if (bit_value == 1) { + state[j] = 1; // Lost state. + num_packets_lost++; + if (j < num_media_packets) { + num_media_packets_lost++; + } + } + } // Done with loop over total number of packets. + RTC_DCHECK_LE(num_media_packets_lost, num_media_packets); + RTC_DCHECK_LE(num_packets_lost, tot_num_packets && num_packets_lost > 0); + double residual_loss = 0.0; + // Only need to compute residual loss (number of recovered packets) for + // configurations that have at least one media packet lost. + if (num_media_packets_lost >= 1) { + // Compute the number of recovered packets. + int num_recovered_packets = 0; + if (code_type == xor_random_code || code_type == xor_bursty_code) { + num_recovered_packets = RecoveredMediaPackets( + num_media_packets, num_fec_packets, state.get()); + } else { + // For the RS code, we can either completely recover all the packets + // if the loss is less than or equal to the number of FEC packets, + // otherwise we can recover none of the missing packets. This is the + // all or nothing (MDS) property of the RS code. + if (num_packets_lost <= num_fec_packets) { + num_recovered_packets = num_media_packets_lost; + } + } + RTC_DCHECK_LE(num_recovered_packets, num_media_packets); + // Compute the residual loss. We only care about recovering media/source + // packets, so residual loss is based on lost/recovered media packets. + residual_loss = + static_cast<double>(num_media_packets_lost - num_recovered_packets); + // Compute the probability weights for this configuration. + ComputeProbabilityWeight(prob_weight.get(), state.get(), + tot_num_packets); + // Update the average and variance of the residual loss. + for (int k = 0; k < kNumLossModels; k++) { + metrics_code.average_residual_loss[k] += + residual_loss * prob_weight[k]; + metrics_code.variance_residual_loss[k] += + residual_loss * residual_loss * prob_weight[k]; + } + } // Done with processing for num_media_packets_lost >= 1. + // Update the distribution statistics. + // Compute the gap of the loss (the "consecutiveness" of the loss). + int gap_loss = GapLoss(tot_num_packets, state.get()); + RTC_DCHECK_LT(gap_loss, kMaxGapSize); + int index = gap_loss * (2 * kMaxMediaPacketsTest) + num_packets_lost; + RTC_DCHECK_LT(index, kNumStatesDistribution); + metrics_code.residual_loss_per_loss_gap[index] += residual_loss; + if (code_type == xor_random_code) { + // The configuration density is only a function of the code length and + // only needs to computed for the first `code_type` passed here. + code_params_[code_index].configuration_density[index]++; + } + } // Done with loop over configurations. + // Normalize the average residual loss and compute/normalize the variance. + for (int k = 0; k < kNumLossModels; k++) { + // Normalize the average residual loss by the total number of packets + // `tot_num_packets` (i.e., the code length). For a code with no (zero) + // recovery, the average residual loss for that code would be reduced like + // ~`average_loss_rate` * `num_media_packets` / `tot_num_packets`. This is + // the expected reduction in the average residual loss just from adding + // FEC packets to the symbol sequence. + metrics_code.average_residual_loss[k] = + metrics_code.average_residual_loss[k] / + static_cast<double>(tot_num_packets); + metrics_code.variance_residual_loss[k] = + metrics_code.variance_residual_loss[k] / + static_cast<double>(num_media_packets * num_media_packets); + metrics_code.variance_residual_loss[k] = + metrics_code.variance_residual_loss[k] - + (metrics_code.average_residual_loss[k] * + metrics_code.average_residual_loss[k]); + RTC_DCHECK_GE(metrics_code.variance_residual_loss[k], 0.0); + RTC_DCHECK_GT(metrics_code.average_residual_loss[k], 0.0); + metrics_code.variance_residual_loss[k] = + std::sqrt(metrics_code.variance_residual_loss[k]) / + metrics_code.average_residual_loss[k]; + } + + // Compute marginal distribution as a function of loss parameter. + ComputeRecoveryRatePerLoss(&metrics_code, num_media_packets, + num_fec_packets, code_index); + if (code_type == rs_code) { + CopyMetrics(&kMetricsReedSolomon[code_index], metrics_code); + } else if (code_type == xor_random_code) { + CopyMetrics(&kMetricsXorRandom[code_index], metrics_code); + } else if (code_type == xor_bursty_code) { + CopyMetrics(&kMetricsXorBursty[code_index], metrics_code); + } else { + RTC_DCHECK_NOTREACHED(); + } + } + + void WriteOutMetricsAllFecCodes() { + std::string filename = test::OutputPath() + "data_metrics_all_codes"; + FILE* fp = fopen(filename.c_str(), "wb"); + // Loop through codes up to `kMaxMediaPacketsTest`. + int code_index = 0; + for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; + num_media_packets++) { + for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; + num_fec_packets++) { + fprintf(fp, "FOR CODE: (%d, %d) \n", num_media_packets, + num_fec_packets); + for (int k = 0; k < kNumLossModels; k++) { + float loss_rate = loss_model_[k].average_loss_rate; + float burst_length = loss_model_[k].average_burst_length; + fprintf( + fp, + "Loss rate = %.2f, Burst length = %.2f: %.4f %.4f %.4f" + " **** %.4f %.4f %.4f \n", + loss_rate, burst_length, + 100 * kMetricsReedSolomon[code_index].average_residual_loss[k], + 100 * kMetricsXorRandom[code_index].average_residual_loss[k], + 100 * kMetricsXorBursty[code_index].average_residual_loss[k], + kMetricsReedSolomon[code_index].variance_residual_loss[k], + kMetricsXorRandom[code_index].variance_residual_loss[k], + kMetricsXorBursty[code_index].variance_residual_loss[k]); + } + for (int gap = 0; gap < kGapSizeOutput; gap++) { + double rs_residual_loss = + ComputeResidualLossPerGap(kMetricsReedSolomon[code_index], gap, + num_fec_packets, code_index); + double xor_random_residual_loss = ComputeResidualLossPerGap( + kMetricsXorRandom[code_index], gap, num_fec_packets, code_index); + double xor_bursty_residual_loss = ComputeResidualLossPerGap( + kMetricsXorBursty[code_index], gap, num_fec_packets, code_index); + fprintf(fp, + "Residual loss as a function of gap " + "%d: %.4f %.4f %.4f \n", + gap, rs_residual_loss, xor_random_residual_loss, + xor_bursty_residual_loss); + } + fprintf(fp, "Recovery rate as a function of loss number \n"); + for (int loss = 1; loss <= num_media_packets + num_fec_packets; + loss++) { + fprintf(fp, "For loss number %d: %.4f %.4f %.4f \n", loss, + kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss], + kMetricsXorRandom[code_index].recovery_rate_per_loss[loss], + kMetricsXorBursty[code_index].recovery_rate_per_loss[loss]); + } + fprintf(fp, "******************\n"); + fprintf(fp, "\n"); + code_index++; + } + } + fclose(fp); + } + + void SetLossModels() { + int num_loss_rates = sizeof(kAverageLossRate) / sizeof(*kAverageLossRate); + int num_burst_lengths = + sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength); + int num_loss_models = 0; + for (int k = 0; k < num_burst_lengths; k++) { + for (int k2 = 0; k2 < num_loss_rates; k2++) { + loss_model_[num_loss_models].average_loss_rate = kAverageLossRate[k2]; + loss_model_[num_loss_models].average_burst_length = + kAverageBurstLength[k]; + // First set of loss models are of random type. + if (k == 0) { + loss_model_[num_loss_models].loss_type = kRandomLossModel; + } else { + loss_model_[num_loss_models].loss_type = kBurstyLossModel; + } + num_loss_models++; + } + } + RTC_DCHECK_EQ(num_loss_models, kNumLossModels); + } + + void SetCodeParams() { + int code_index = 0; + for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; + num_media_packets++) { + for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; + num_fec_packets++) { + code_params_[code_index].num_media_packets = num_media_packets; + code_params_[code_index].num_fec_packets = num_fec_packets; + code_params_[code_index].protection_level = + static_cast<float>(num_fec_packets) / + static_cast<float>(num_media_packets + num_fec_packets); + for (int k = 0; k < kNumStatesDistribution; k++) { + code_params_[code_index].configuration_density[k] = 0; + } + code_index++; + } + } + max_num_codes_ = code_index; + } + + // Make some basic checks on the packet masks. Return -1 if any of these + // checks fail. + int RejectInvalidMasks(int num_media_packets, int num_fec_packets) { + // Make sure every FEC packet protects something. + for (int i = 0; i < num_fec_packets; i++) { + int row_degree = 0; + for (int j = 0; j < num_media_packets; j++) { + if (fec_packet_masks_[i][j] == 1) { + row_degree++; + } + } + if (row_degree == 0) { + printf( + "Invalid mask: FEC packet has empty mask (does not protect " + "anything) %d %d %d \n", + i, num_media_packets, num_fec_packets); + return -1; + } + } + // Mask sure every media packet has some protection. + for (int j = 0; j < num_media_packets; j++) { + int column_degree = 0; + for (int i = 0; i < num_fec_packets; i++) { + if (fec_packet_masks_[i][j] == 1) { + column_degree++; + } + } + if (column_degree == 0) { + printf( + "Invalid mask: Media packet has no protection at all %d %d %d " + "\n", + j, num_media_packets, num_fec_packets); + return -1; + } + } + // Make sure we do not have two identical FEC packets. + for (int i = 0; i < num_fec_packets; i++) { + for (int i2 = i + 1; i2 < num_fec_packets; i2++) { + int overlap = 0; + for (int j = 0; j < num_media_packets; j++) { + if (fec_packet_masks_[i][j] == fec_packet_masks_[i2][j]) { + overlap++; + } + } + if (overlap == num_media_packets) { + printf("Invalid mask: Two FEC packets are identical %d %d %d %d \n", + i, i2, num_media_packets, num_fec_packets); + return -1; + } + } + } + // Avoid codes that have two media packets with full protection (all 1s in + // their corresponding columns). This would mean that if we lose those + // two packets, we can never recover them even if we receive all the other + // packets. Exclude the special cases of 1 or 2 FEC packets. + if (num_fec_packets > 2) { + for (int j = 0; j < num_media_packets; j++) { + for (int j2 = j + 1; j2 < num_media_packets; j2++) { + int degree = 0; + for (int i = 0; i < num_fec_packets; i++) { + if (fec_packet_masks_[i][j] == fec_packet_masks_[i][j2] && + fec_packet_masks_[i][j] == 1) { + degree++; + } + } + if (degree == num_fec_packets) { + printf( + "Invalid mask: Two media packets are have full degree " + "%d %d %d %d \n", + j, j2, num_media_packets, num_fec_packets); + return -1; + } + } + } + } + return 0; + } + + void GetPacketMaskConvertToBitMask(uint8_t* packet_mask, + int num_media_packets, + int num_fec_packets, + int mask_bytes_fec_packet, + CodeType code_type) { + for (int i = 0; i < num_fec_packets; i++) { + for (int j = 0; j < num_media_packets; j++) { + const uint8_t byte_mask = + packet_mask[i * mask_bytes_fec_packet + j / 8]; + const int bit_position = (7 - j % 8); + fec_packet_masks_[i][j] = + (byte_mask & (1 << bit_position)) >> bit_position; + fprintf(fp_mask_, "%d ", fec_packet_masks_[i][j]); + } + fprintf(fp_mask_, "\n"); + } + fprintf(fp_mask_, "\n"); + } + + int ProcessXORPacketMasks(CodeType code_type, FecMaskType fec_mask_type) { + int code_index = 0; + // Maximum number of media packets allowed for the mask type. + const int packet_mask_max = kMaxMediaPackets[fec_mask_type]; + std::unique_ptr<uint8_t[]> packet_mask( + new uint8_t[packet_mask_max * kUlpfecMaxPacketMaskSize]); + // Loop through codes up to `kMaxMediaPacketsTest`. + for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; + ++num_media_packets) { + const int mask_bytes_fec_packet = + static_cast<int>(internal::PacketMaskSize(num_media_packets)); + internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets); + for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; + num_fec_packets++) { + memset(packet_mask.get(), 0, num_media_packets * mask_bytes_fec_packet); + rtc::ArrayView<const uint8_t> mask = + mask_table.LookUp(num_media_packets, num_fec_packets); + memcpy(packet_mask.get(), &mask[0], mask.size()); + // Convert to bit mask. + GetPacketMaskConvertToBitMask(packet_mask.get(), num_media_packets, + num_fec_packets, mask_bytes_fec_packet, + code_type); + if (RejectInvalidMasks(num_media_packets, num_fec_packets) < 0) { + return -1; + } + // Compute the metrics for this code/mask. + ComputeMetricsForCode(code_type, code_index); + code_index++; + } + } + RTC_DCHECK_EQ(code_index, kNumberCodes); + return 0; + } + + void ProcessRS(CodeType code_type) { + int code_index = 0; + for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; + num_media_packets++) { + for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; + num_fec_packets++) { + // Compute the metrics for this code type. + ComputeMetricsForCode(code_type, code_index); + code_index++; + } + } + } + + // Compute metrics for all code types and sizes. + void ComputeMetricsAllCodes() { + SetLossModels(); + SetCodeParams(); + // Get metrics for XOR code with packet masks of random type. + std::string filename = test::OutputPath() + "data_packet_masks"; + fp_mask_ = fopen(filename.c_str(), "wb"); + fprintf(fp_mask_, "MASK OF TYPE RANDOM: \n"); + EXPECT_EQ(ProcessXORPacketMasks(xor_random_code, kFecMaskRandom), 0); + // Get metrics for XOR code with packet masks of bursty type. + fprintf(fp_mask_, "MASK OF TYPE BURSTY: \n"); + EXPECT_EQ(ProcessXORPacketMasks(xor_bursty_code, kFecMaskBursty), 0); + fclose(fp_mask_); + // Get metrics for Reed-Solomon code. + ProcessRS(rs_code); + } +}; + +// Verify that the average residual loss, averaged over loss models +// appropriate to each mask type, is below some maximum acceptable level. The +// acceptable levels are read in from a file, and correspond to a current set +// of packet masks. The levels for each code may be updated over time. +TEST_F(FecPacketMaskMetricsTest, FecXorMaxResidualLoss) { + SetLossModels(); + SetCodeParams(); + ComputeMetricsAllCodes(); + WriteOutMetricsAllFecCodes(); + int num_loss_rates = sizeof(kAverageLossRate) / sizeof(*kAverageLossRate); + int num_burst_lengths = + sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength); + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + double sum_residual_loss_random_mask_random_loss = 0.0; + double sum_residual_loss_bursty_mask_bursty_loss = 0.0; + // Compute the sum residual loss across the models, for each mask type. + for (int k = 0; k < kNumLossModels; k++) { + if (loss_model_[k].loss_type == kRandomLossModel) { + sum_residual_loss_random_mask_random_loss += + kMetricsXorRandom[code_index].average_residual_loss[k]; + } else if (loss_model_[k].loss_type == kBurstyLossModel) { + sum_residual_loss_bursty_mask_bursty_loss += + kMetricsXorBursty[code_index].average_residual_loss[k]; + } + } + float average_residual_loss_random_mask_random_loss = + sum_residual_loss_random_mask_random_loss / num_loss_rates; + float average_residual_loss_bursty_mask_bursty_loss = + sum_residual_loss_bursty_mask_bursty_loss / + (num_loss_rates * (num_burst_lengths - 1)); + const float ref_random_mask = kMaxResidualLossRandomMask[code_index]; + const float ref_bursty_mask = kMaxResidualLossBurstyMask[code_index]; + EXPECT_LE(average_residual_loss_random_mask_random_loss, ref_random_mask); + EXPECT_LE(average_residual_loss_bursty_mask_bursty_loss, ref_bursty_mask); + } +} + +// Verify the behavior of the XOR codes vs the RS codes. +// For random loss model with average loss rates <= the code protection level, +// the RS code (optimal MDS code) is more efficient than XOR codes. +// However, for larger loss rates (above protection level) and/or bursty +// loss models, the RS is not always more efficient than XOR (though in most +// cases it still is). +TEST_F(FecPacketMaskMetricsTest, FecXorVsRS) { + SetLossModels(); + SetCodeParams(); + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + for (int k = 0; k < kNumLossModels; k++) { + float loss_rate = loss_model_[k].average_loss_rate; + float protection_level = code_params_[code_index].protection_level; + // Under these conditions we expect XOR to not be better than RS. + if (loss_model_[k].loss_type == kRandomLossModel && + loss_rate <= protection_level) { + EXPECT_GE(kMetricsXorRandom[code_index].average_residual_loss[k], + kMetricsReedSolomon[code_index].average_residual_loss[k]); + EXPECT_GE(kMetricsXorBursty[code_index].average_residual_loss[k], + kMetricsReedSolomon[code_index].average_residual_loss[k]); + } + // TODO(marpan): There are some cases (for high loss rates and/or + // burst loss models) where XOR is better than RS. Is there some pattern + // we can identify and enforce as a constraint? + } + } +} + +// Verify the trend (change) in the average residual loss, as a function of +// loss rate, of the XOR code relative to the RS code. +// The difference between XOR and RS should not get worse as we increase +// the average loss rate. +TEST_F(FecPacketMaskMetricsTest, FecTrendXorVsRsLossRate) { + SetLossModels(); + SetCodeParams(); + // TODO(marpan): Examine this further to see if the condition can be strictly + // satisfied (i.e., scale = 1.0) for all codes with different/better masks. + double scale = 0.90; + int num_loss_rates = sizeof(kAverageLossRate) / sizeof(*kAverageLossRate); + int num_burst_lengths = + sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength); + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + for (int i = 0; i < num_burst_lengths; i++) { + for (int j = 0; j < num_loss_rates - 1; j++) { + int k = num_loss_rates * i + j; + // For XOR random. + if (kMetricsXorRandom[code_index].average_residual_loss[k] > + kMetricsReedSolomon[code_index].average_residual_loss[k]) { + double diff_rs_xor_random_loss1 = + (kMetricsXorRandom[code_index].average_residual_loss[k] - + kMetricsReedSolomon[code_index].average_residual_loss[k]) / + kMetricsXorRandom[code_index].average_residual_loss[k]; + double diff_rs_xor_random_loss2 = + (kMetricsXorRandom[code_index].average_residual_loss[k + 1] - + kMetricsReedSolomon[code_index].average_residual_loss[k + 1]) / + kMetricsXorRandom[code_index].average_residual_loss[k + 1]; + EXPECT_GE(diff_rs_xor_random_loss1, scale * diff_rs_xor_random_loss2); + } + // TODO(marpan): Investigate the cases for the bursty mask where + // this trend is not strictly satisfied. + } + } + } +} + +// Verify the average residual loss behavior via the protection level and +// the code length. The average residual loss for a given (k1,m1) code +// should generally be higher than that of another code (k2,m2), which has +// either of the two conditions satisfied: +// 1) higher protection & code length at least as large: (k2+m2) >= (k1+m1), +// 2) equal protection and larger code length: (k2+m2) > (k1+m1). +// Currently does not hold for some cases of the XOR code with random mask. +TEST_F(FecPacketMaskMetricsTest, FecBehaviorViaProtectionLevelAndLength) { + SetLossModels(); + SetCodeParams(); + for (int code_index1 = 0; code_index1 < max_num_codes_; code_index1++) { + float protection_level1 = code_params_[code_index1].protection_level; + int length1 = code_params_[code_index1].num_media_packets + + code_params_[code_index1].num_fec_packets; + for (int code_index2 = 0; code_index2 < max_num_codes_; code_index2++) { + float protection_level2 = code_params_[code_index2].protection_level; + int length2 = code_params_[code_index2].num_media_packets + + code_params_[code_index2].num_fec_packets; + // Codes with higher protection are more efficient, conditioned on the + // length of the code (higher protection but shorter length codes are + // generally not more efficient). For two codes with equal protection, + // the longer code is generally more efficient. For high loss rate + // models, this condition may be violated for some codes with equal or + // very close protection levels. High loss rate case is excluded below. + if ((protection_level2 > protection_level1 && length2 >= length1) || + (protection_level2 == protection_level1 && length2 > length1)) { + for (int k = 0; k < kNumLossModels; k++) { + float loss_rate = loss_model_[k].average_loss_rate; + if (loss_rate < loss_rate_upper_threshold) { + EXPECT_LT( + kMetricsReedSolomon[code_index2].average_residual_loss[k], + kMetricsReedSolomon[code_index1].average_residual_loss[k]); + // TODO(marpan): There are some corner cases where this is not + // satisfied with the current packet masks. Look into updating + // these cases to see if this behavior should/can be satisfied, + // with overall lower residual loss for those XOR codes. + // EXPECT_LT( + // kMetricsXorBursty[code_index2].average_residual_loss[k], + // kMetricsXorBursty[code_index1].average_residual_loss[k]); + // EXPECT_LT( + // kMetricsXorRandom[code_index2].average_residual_loss[k], + // kMetricsXorRandom[code_index1].average_residual_loss[k]); + } + } + } + } + } +} + +// Verify the beheavior of the variance of the XOR codes. +// The partial recovery of the XOR versus the all or nothing behavior of the RS +// code means that the variance of the residual loss for XOR should generally +// not be worse than RS. +TEST_F(FecPacketMaskMetricsTest, FecVarianceBehaviorXorVsRs) { + SetLossModels(); + SetCodeParams(); + // The condition is not strictly satisfied with the current masks, + // i.e., for some codes, the variance of XOR may be slightly higher than RS. + // TODO(marpan): Examine this further to see if the condition can be strictly + // satisfied (i.e., scale = 1.0) for all codes with different/better masks. + double scale = 0.95; + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + for (int k = 0; k < kNumLossModels; k++) { + EXPECT_LE(scale * kMetricsXorRandom[code_index].variance_residual_loss[k], + kMetricsReedSolomon[code_index].variance_residual_loss[k]); + EXPECT_LE(scale * kMetricsXorBursty[code_index].variance_residual_loss[k], + kMetricsReedSolomon[code_index].variance_residual_loss[k]); + } + } +} + +// For the bursty mask type, the residual loss must be strictly zero for all +// consecutive losses (i.e, gap = 0) with number of losses <= num_fec_packets. +// This is a design property of the bursty mask type. +TEST_F(FecPacketMaskMetricsTest, FecXorBurstyPerfectRecoveryConsecutiveLoss) { + SetLossModels(); + SetCodeParams(); + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + int num_fec_packets = code_params_[code_index].num_fec_packets; + for (int loss = 1; loss <= num_fec_packets; loss++) { + int index = loss; // `gap` is zero. + EXPECT_EQ(kMetricsXorBursty[code_index].residual_loss_per_loss_gap[index], + 0.0); + } + } +} + +// The XOR codes with random mask type are generally better than the ones with +// bursty mask type, for random loss models at low loss rates. +// The XOR codes with bursty mask types are generally better than the one with +// random mask type, for bursty loss models and/or high loss rates. +// TODO(marpan): Enable this test when some of the packet masks are updated. +// Some isolated cases of the codes don't pass this currently. +/* +TEST_F(FecPacketMaskMetricsTest, FecXorRandomVsBursty) { + SetLossModels(); + SetCodeParams(); + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + double sum_residual_loss_random_mask_random_loss = 0.0; + double sum_residual_loss_bursty_mask_random_loss = 0.0; + double sum_residual_loss_random_mask_bursty_loss = 0.0; + double sum_residual_loss_bursty_mask_bursty_loss = 0.0; + // Compute the sum residual loss across the models, for each mask type. + for (int k = 0; k < kNumLossModels; k++) { + float loss_rate = loss_model_[k].average_loss_rate; + if (loss_model_[k].loss_type == kRandomLossModel && + loss_rate < loss_rate_upper_threshold) { + sum_residual_loss_random_mask_random_loss += + kMetricsXorRandom[code_index].average_residual_loss[k]; + sum_residual_loss_bursty_mask_random_loss += + kMetricsXorBursty[code_index].average_residual_loss[k]; + } else if (loss_model_[k].loss_type == kBurstyLossModel && + loss_rate > loss_rate_lower_threshold) { + sum_residual_loss_random_mask_bursty_loss += + kMetricsXorRandom[code_index].average_residual_loss[k]; + sum_residual_loss_bursty_mask_bursty_loss += + kMetricsXorBursty[code_index].average_residual_loss[k]; + } + } + EXPECT_LE(sum_residual_loss_random_mask_random_loss, + sum_residual_loss_bursty_mask_random_loss); + EXPECT_LE(sum_residual_loss_bursty_mask_bursty_loss, + sum_residual_loss_random_mask_bursty_loss); + } +} +*/ + +// Verify that the average recovery rate for each code is equal or above some +// threshold, for certain loss number conditions. +TEST_F(FecPacketMaskMetricsTest, FecRecoveryRateUnderLossConditions) { + SetLossModels(); + SetCodeParams(); + for (int code_index = 0; code_index < max_num_codes_; code_index++) { + int num_media_packets = code_params_[code_index].num_media_packets; + int num_fec_packets = code_params_[code_index].num_fec_packets; + // Perfect recovery (`recovery_rate_per_loss` == 1) is expected for + // `loss_number` = 1, for all codes. + int loss_number = 1; + EXPECT_EQ( + kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number], + 1.0); + EXPECT_EQ(kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number], + 1.0); + EXPECT_EQ(kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number], + 1.0); + // For `loss_number` = `num_fec_packets` / 2, we expect the following: + // Perfect recovery for RS, and recovery for XOR above the threshold. + loss_number = num_fec_packets / 2 > 0 ? num_fec_packets / 2 : 1; + EXPECT_EQ( + kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number], + 1.0); + EXPECT_GE(kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number], + kRecoveryRateXorRandom[0]); + EXPECT_GE(kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number], + kRecoveryRateXorBursty[0]); + // For `loss_number` = `num_fec_packets`, we expect the following: + // Perfect recovery for RS, and recovery for XOR above the threshold. + loss_number = num_fec_packets; + EXPECT_EQ( + kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number], + 1.0); + EXPECT_GE(kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number], + kRecoveryRateXorRandom[1]); + EXPECT_GE(kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number], + kRecoveryRateXorBursty[1]); + // For `loss_number` = `num_fec_packets` + 1, we expect the following: + // Zero recovery for RS, but non-zero recovery for XOR. + if (num_fec_packets > 1 && num_media_packets > 2) { + loss_number = num_fec_packets + 1; + EXPECT_EQ( + kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number], + 0.0); + EXPECT_GE( + kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number], + kRecoveryRateXorRandom[2]); + EXPECT_GE( + kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number], + kRecoveryRateXorBursty[2]); + } + } +} + +} // namespace webrtc |