summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/rtp_rtcp/source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/modules/rtp_rtcp/source
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/rtp_rtcp/source')
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.cc125
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator.h86
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_interpolator_unittest.cc353
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.cc124
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender.h88
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/absolute_capture_time_sender_unittest.cc374
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.cc124
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper.h63
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/active_decode_targets_helper_unittest.cc272
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/byte_io.h402
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/byte_io_unittest.cc270
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.cc33
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater.h51
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/capture_clock_offset_updater_unittest.cc58
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc43
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/create_video_rtp_depacketizer.h26
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.cc465
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/deprecated/deprecated_rtp_sender_egress.h151
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.cc51
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/dtmf_queue.h43
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.cc660
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.h37
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc82
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.cc660
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_private_tables_random.h27
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.cc230
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/fec_test_helper.h125
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.cc320
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer.h88
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_header_reader_writer_unittest.cc560
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver.cc196
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver_unittest.cc691
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender.cc204
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_sender_unittest.cc342
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.cc816
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction.h424
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc519
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h121
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/frame_transformer_factory_unittest.cc69
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/leb128.cc63
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/leb128.h31
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/leb128_unittest.cc138
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/nack_rtx_unittest.cc293
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.cc141
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats.h58
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/packet_loss_stats_unittest.cc198
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.cc156
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer.h77
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/packet_sequencer_unittest.cc250
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc431
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.h250
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_unittest.cc901
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc109
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc128
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.cc29
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats.h40
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_nack_stats_unittest.cc64
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.cc99
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet.h111
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.cc103
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app.h67
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/app_unittest.cc110
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.cc141
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye.h57
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/bye_unittest.cc147
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.cc89
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header.h52
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/common_header_unittest.cc103
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.cc50
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet.h47
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/compound_packet_unittest.cc155
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.cc94
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr.h80
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/dlrr_unittest.cc92
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.cc195
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports.h73
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc169
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.cc113
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir.h62
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/fir_unittest.cc93
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.cc133
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification.h82
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/loss_notification_unittest.cc136
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.cc176
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack.h59
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/nack_unittest.cc171
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.cc79
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli.h39
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/pli_unittest.cc58
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.cc47
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/psfb.h48
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.cc68
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request.h40
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rapid_resync_request_unittest.cc64
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.cc112
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report.h60
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/receiver_report_unittest.cc161
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.cc143
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb.h59
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remb_unittest.cc141
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.cc148
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate.h59
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/remote_estimate_unittest.cc56
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.cc100
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block.h72
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/report_block_unittest.cc110
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.cc49
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr.h59
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rrtr_unittest.cc50
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.cc45
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/rtpfb.h47
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.cc199
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h56
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sdes_unittest.cc244
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.cc141
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report.h81
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/sender_report_unittest.cc142
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.cc127
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h63
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/target_bitrate_unittest.cc96
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.cc71
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmb_item.h52
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc109
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.h55
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn_unittest.cc105
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc111
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.h54
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr_unittest.cc89
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc737
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h185
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback_unittest.cc667
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet_unittest.cc42
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.cc1292
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver.h457
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc2012
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.cc974
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.h332
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender_unittest.cc844
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.cc150
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver.h105
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.cc80
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_config.h188
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc857
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl.h170
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc1741
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_unittest.cc366
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.cc55
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h59
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_extension_unittest.cc137
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.cc239
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_reader.h69
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.cc396
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_dependency_descriptor_writer.h89
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.cc58
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.h27
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc1129
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.cc144
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format.h61
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.cc313
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264.h99
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_h264_unittest.cc496
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_unittest.cc283
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.cc100
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic.h71
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_video_generic_unittest.cc172
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc169
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.h74
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.cc174
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_test_helper.h56
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8_unittest.cc115
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.cc451
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9.h72
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp9_unittest.cc608
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.cc100
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor.h79
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.cc173
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension.h45
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_generic_frame_descriptor_extension_unittest.cc264
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map.cc169
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_map_unittest.cc115
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.cc48
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size.h32
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extension_size_unittest.cc92
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc934
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_header_extensions.h386
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.cc712
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet.h283
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.cc428
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history.h196
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_history_unittest.cc681
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.cc81
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_received.h77
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.cc31
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_to_send.h147
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packet_unittest.cc1285
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.cc402
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1.h72
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.cc57
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h51
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc342
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_config.h27
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc768
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h332
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc832
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.h341
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc1157
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc705
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_interface.h468
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.cc834
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender.h217
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.cc400
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio.h122
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_audio_unittest.cc264
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.cc617
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.h186
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress_unittest.cc990
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_unittest.cc1373
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.cc929
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.h255
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc227
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h94
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video_unittest.cc1697
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.cc129
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map.h85
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sequence_number_map_unittest.cc502
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.cc63
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.h31
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util_unittest.cc86
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.cc110
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header.h101
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_header_unittest.cc351
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.cc323
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension.h39
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtp_video_layers_allocation_extension_unittest.cc287
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.cc114
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker.h138
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/source_tracker_unittest.cc533
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/time_util.cc54
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h56
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/time_util_unittest.cc128
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.cc184
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/tmmbr_help.h35
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.cc268
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.h123
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator_unittest.cc273
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc141
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h69
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc245
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.cc245
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver.h80
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_receiver_unittest.cc543
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_fec_generator.h54
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.cc42
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer.h41
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.cc395
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h42
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_av1_unittest.cc392
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.cc72
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic.h30
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_generic_unittest.cc71
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc310
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.h28
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc425
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.cc28
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw.h30
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_raw_unittest.cc51
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc201
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h42
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc244
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.cc226
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h42
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9_unittest.cc373
272 files changed, 65005 insertions, 0 deletions
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