diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/libwebrtc/call/rtp_video_sender_unittest.cc | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/call/rtp_video_sender_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/call/rtp_video_sender_unittest.cc | 1178 |
1 files changed, 1178 insertions, 0 deletions
diff --git a/third_party/libwebrtc/call/rtp_video_sender_unittest.cc b/third_party/libwebrtc/call/rtp_video_sender_unittest.cc new file mode 100644 index 0000000000..da2bed649b --- /dev/null +++ b/third_party/libwebrtc/call/rtp_video_sender_unittest.cc @@ -0,0 +1,1178 @@ +/* + * 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 "call/rtp_video_sender.h" + +#include <atomic> +#include <memory> +#include <string> +#include <utility> + +#include "absl/functional/any_invocable.h" +#include "call/rtp_transport_controller_send.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" +#include "modules/rtp_rtcp/source/byte_io.h" +#include "modules/rtp_rtcp/source/rtcp_packet/nack.h" +#include "modules/rtp_rtcp/source/rtp_dependency_descriptor_extension.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "modules/video_coding/fec_controller_default.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "rtc_base/rate_limiter.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/mock_frame_transformer.h" +#include "test/mock_transport.h" +#include "test/scenario/scenario.h" +#include "test/scoped_key_value_config.h" +#include "test/time_controller/simulated_time_controller.h" +#include "video/send_delay_stats.h" +#include "video/send_statistics_proxy.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::SaveArg; +using ::testing::SizeIs; + +const int8_t kPayloadType = 96; +const uint32_t kSsrc1 = 12345; +const uint32_t kSsrc2 = 23456; +const uint32_t kRtxSsrc1 = 34567; +const uint32_t kRtxSsrc2 = 45678; +const int16_t kInitialPictureId1 = 222; +const int16_t kInitialPictureId2 = 44; +const int16_t kInitialTl0PicIdx1 = 99; +const int16_t kInitialTl0PicIdx2 = 199; +const int64_t kRetransmitWindowSizeMs = 500; +const int kTransportsSequenceExtensionId = 7; +const int kDependencyDescriptorExtensionId = 8; + +class MockRtcpIntraFrameObserver : public RtcpIntraFrameObserver { + public: + MOCK_METHOD(void, OnReceivedIntraFrameRequest, (uint32_t), (override)); +}; + +RtpSenderObservers CreateObservers( + RtcpRttStats* rtcp_rtt_stats, + RtcpIntraFrameObserver* intra_frame_callback, + ReportBlockDataObserver* report_block_data_observer, + StreamDataCountersCallback* rtp_stats, + BitrateStatisticsObserver* bitrate_observer, + FrameCountObserver* frame_count_observer, + RtcpPacketTypeCounterObserver* rtcp_type_observer, + SendSideDelayObserver* send_delay_observer, + SendPacketObserver* send_packet_observer) { + RtpSenderObservers observers; + observers.rtcp_rtt_stats = rtcp_rtt_stats; + observers.intra_frame_callback = intra_frame_callback; + observers.rtcp_loss_notification_observer = nullptr; + observers.report_block_data_observer = report_block_data_observer; + observers.rtp_stats = rtp_stats; + observers.bitrate_observer = bitrate_observer; + observers.frame_count_observer = frame_count_observer; + observers.rtcp_type_observer = rtcp_type_observer; + observers.send_delay_observer = send_delay_observer; + observers.send_packet_observer = send_packet_observer; + return observers; +} + +BitrateConstraints GetBitrateConfig() { + BitrateConstraints bitrate_config; + bitrate_config.min_bitrate_bps = 30000; + bitrate_config.start_bitrate_bps = 300000; + bitrate_config.max_bitrate_bps = 3000000; + return bitrate_config; +} + +VideoSendStream::Config CreateVideoSendStreamConfig( + Transport* transport, + const std::vector<uint32_t>& ssrcs, + const std::vector<uint32_t>& rtx_ssrcs, + int payload_type) { + VideoSendStream::Config config(transport); + config.rtp.ssrcs = ssrcs; + config.rtp.rtx.ssrcs = rtx_ssrcs; + config.rtp.payload_type = payload_type; + config.rtp.rtx.payload_type = payload_type + 1; + config.rtp.nack.rtp_history_ms = 1000; + config.rtp.extensions.emplace_back(RtpExtension::kTransportSequenceNumberUri, + kTransportsSequenceExtensionId); + config.rtp.extensions.emplace_back(RtpDependencyDescriptorExtension::Uri(), + kDependencyDescriptorExtensionId); + config.rtp.extmap_allow_mixed = true; + return config; +} + +class RtpVideoSenderTestFixture { + public: + RtpVideoSenderTestFixture( + const std::vector<uint32_t>& ssrcs, + const std::vector<uint32_t>& rtx_ssrcs, + int payload_type, + const std::map<uint32_t, RtpPayloadState>& suspended_payload_states, + FrameCountObserver* frame_count_observer, + rtc::scoped_refptr<FrameTransformerInterface> frame_transformer, + const FieldTrialsView* field_trials = nullptr) + : time_controller_(Timestamp::Millis(1000000)), + config_(CreateVideoSendStreamConfig(&transport_, + ssrcs, + rtx_ssrcs, + payload_type)), + send_delay_stats_(time_controller_.GetClock()), + bitrate_config_(GetBitrateConfig()), + transport_controller_( + time_controller_.GetClock(), + RtpTransportConfig{ + .bitrate_config = bitrate_config_, + .event_log = &event_log_, + .task_queue_factory = time_controller_.GetTaskQueueFactory(), + .trials = field_trials ? field_trials : &field_trials_, + }), + stats_proxy_(time_controller_.GetClock(), + config_, + VideoEncoderConfig::ContentType::kRealtimeVideo, + field_trials ? *field_trials : field_trials_), + retransmission_rate_limiter_(time_controller_.GetClock(), + kRetransmitWindowSizeMs) { + transport_controller_.EnsureStarted(); + std::map<uint32_t, RtpState> suspended_ssrcs; + router_ = std::make_unique<RtpVideoSender>( + time_controller_.GetClock(), suspended_ssrcs, suspended_payload_states, + config_.rtp, config_.rtcp_report_interval_ms, &transport_, + CreateObservers(nullptr, &encoder_feedback_, &stats_proxy_, + &stats_proxy_, &stats_proxy_, frame_count_observer, + &stats_proxy_, &stats_proxy_, &send_delay_stats_), + &transport_controller_, &event_log_, &retransmission_rate_limiter_, + std::make_unique<FecControllerDefault>(time_controller_.GetClock()), + nullptr, CryptoOptions{}, frame_transformer, + field_trials ? *field_trials : field_trials_, + time_controller_.GetTaskQueueFactory()); + } + + RtpVideoSenderTestFixture( + const std::vector<uint32_t>& ssrcs, + const std::vector<uint32_t>& rtx_ssrcs, + int payload_type, + const std::map<uint32_t, RtpPayloadState>& suspended_payload_states, + FrameCountObserver* frame_count_observer, + const FieldTrialsView* field_trials = nullptr) + : RtpVideoSenderTestFixture(ssrcs, + rtx_ssrcs, + payload_type, + suspended_payload_states, + frame_count_observer, + /*frame_transformer=*/nullptr, + field_trials) {} + + RtpVideoSenderTestFixture( + const std::vector<uint32_t>& ssrcs, + const std::vector<uint32_t>& rtx_ssrcs, + int payload_type, + const std::map<uint32_t, RtpPayloadState>& suspended_payload_states, + const FieldTrialsView* field_trials = nullptr) + : RtpVideoSenderTestFixture(ssrcs, + rtx_ssrcs, + payload_type, + suspended_payload_states, + /*frame_count_observer=*/nullptr, + /*frame_transformer=*/nullptr, + field_trials) {} + + ~RtpVideoSenderTestFixture() { Stop(); } + + RtpVideoSender* router() { return router_.get(); } + MockTransport& transport() { return transport_; } + void AdvanceTime(TimeDelta delta) { time_controller_.AdvanceTime(delta); } + + void Stop() { + RunOnTransportQueue([&]() { router_->Stop(); }); + } + + void SetActiveModules(const std::vector<bool>& active_modules) { + RunOnTransportQueue([&]() { router_->SetActiveModules(active_modules); }); + } + + // Several RtpVideoSender methods expect to be called on the task queue as + // owned by the send transport. While the SequenceChecker may pick up the + // default thread as the transport queue, explicit checks for the transport + // queue (not just using a SequenceChecker) aren't possible unless such a + // queue is actually active. So RunOnTransportQueue is a convenience function + // that allow for running a `task` on the transport queue, similar to + // SendTask(). + void RunOnTransportQueue(absl::AnyInvocable<void() &&> task) { + transport_controller_.GetWorkerQueue()->RunOrPost(std::move(task)); + AdvanceTime(TimeDelta::Zero()); + } + + private: + test::ScopedKeyValueConfig field_trials_; + NiceMock<MockTransport> transport_; + NiceMock<MockRtcpIntraFrameObserver> encoder_feedback_; + GlobalSimulatedTimeController time_controller_; + RtcEventLogNull event_log_; + VideoSendStream::Config config_; + SendDelayStats send_delay_stats_; + BitrateConstraints bitrate_config_; + RtpTransportControllerSend transport_controller_; + SendStatisticsProxy stats_proxy_; + RateLimiter retransmission_rate_limiter_; + std::unique_ptr<RtpVideoSender> router_; +}; + +BitrateAllocationUpdate CreateBitrateAllocationUpdate(int target_bitrate_bps) { + BitrateAllocationUpdate update; + update.target_bitrate = DataRate::BitsPerSec(target_bitrate_bps); + update.round_trip_time = TimeDelta::Zero(); + return update; +} + +} // namespace + +TEST(RtpVideoSenderTest, SendOnOneModule) { + constexpr uint8_t kPayload = 'a'; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image.SetEncodedData(EncodedImageBuffer::Create(&kPayload, 1)); + + RtpVideoSenderTestFixture test({kSsrc1}, {kRtxSsrc1}, kPayloadType, {}); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + + test.SetActiveModules({true}); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + + test.SetActiveModules({false}); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + + test.SetActiveModules({true}); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); +} + +TEST(RtpVideoSenderTest, SendSimulcastSetActive) { + constexpr uint8_t kPayload = 'a'; + EncodedImage encoded_image_1; + encoded_image_1.SetTimestamp(1); + encoded_image_1.capture_time_ms_ = 2; + encoded_image_1._frameType = VideoFrameType::kVideoFrameKey; + encoded_image_1.SetEncodedData(EncodedImageBuffer::Create(&kPayload, 1)); + + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); + + CodecSpecificInfo codec_info; + codec_info.codecType = kVideoCodecVP8; + + test.SetActiveModules({true, true}); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); + + EncodedImage encoded_image_2(encoded_image_1); + encoded_image_2.SetSpatialIndex(1); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_2, &codec_info).error); + + // Inactive. + test.Stop(); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_2, &codec_info).error); +} + +// Tests how setting individual rtp modules to active affects the overall +// behavior of the payload router. First sets one module to active and checks +// that outgoing data can be sent on this module, and checks that no data can +// be sent if both modules are inactive. +TEST(RtpVideoSenderTest, SendSimulcastSetActiveModules) { + constexpr uint8_t kPayload = 'a'; + EncodedImage encoded_image_1; + encoded_image_1.SetTimestamp(1); + encoded_image_1.capture_time_ms_ = 2; + encoded_image_1._frameType = VideoFrameType::kVideoFrameKey; + encoded_image_1.SetEncodedData(EncodedImageBuffer::Create(&kPayload, 1)); + + EncodedImage encoded_image_2(encoded_image_1); + encoded_image_2.SetSpatialIndex(1); + + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); + CodecSpecificInfo codec_info; + codec_info.codecType = kVideoCodecVP8; + + // Only setting one stream to active will still set the payload router to + // active and allow sending data on the active stream. + std::vector<bool> active_modules({true, false}); + test.SetActiveModules(active_modules); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); + + // Setting both streams to inactive will turn the payload router to + // inactive. + active_modules = {false, false}; + test.SetActiveModules(active_modules); + // An incoming encoded image will not ask the module to send outgoing data + // because the payload router is inactive. + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); +} + +TEST( + RtpVideoSenderTest, + DiscardsHigherSpatialVideoFramesAfterLayerDisabledInVideoLayersAllocation) { + constexpr uint8_t kPayload = 'a'; + EncodedImage encoded_image_1; + encoded_image_1.SetTimestamp(1); + encoded_image_1.capture_time_ms_ = 2; + encoded_image_1._frameType = VideoFrameType::kVideoFrameKey; + encoded_image_1.SetEncodedData(EncodedImageBuffer::Create(&kPayload, 1)); + EncodedImage encoded_image_2(encoded_image_1); + encoded_image_2.SetSpatialIndex(1); + CodecSpecificInfo codec_info; + codec_info.codecType = kVideoCodecVP8; + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); + test.SetActiveModules({true, true}); + // A layer is sent on both rtp streams. + test.router()->OnVideoLayersAllocationUpdated( + {.active_spatial_layers = {{.rtp_stream_index = 0}, + {.rtp_stream_index = 1}}}); + + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_2, &codec_info).error); + + // Only rtp stream index 0 is configured to send a stream. + test.router()->OnVideoLayersAllocationUpdated( + {.active_spatial_layers = {{.rtp_stream_index = 0}}}); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_1, &codec_info).error); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image_2, &codec_info).error); +} + +TEST(RtpVideoSenderTest, CreateWithNoPreviousStates) { + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); + test.SetActiveModules({true, true}); + + std::map<uint32_t, RtpPayloadState> initial_states = + test.router()->GetRtpPayloadStates(); + EXPECT_EQ(2u, initial_states.size()); + EXPECT_NE(initial_states.find(kSsrc1), initial_states.end()); + EXPECT_NE(initial_states.find(kSsrc2), initial_states.end()); +} + +TEST(RtpVideoSenderTest, CreateWithPreviousStates) { + const int64_t kState1SharedFrameId = 123; + const int64_t kState2SharedFrameId = 234; + RtpPayloadState state1; + state1.picture_id = kInitialPictureId1; + state1.tl0_pic_idx = kInitialTl0PicIdx1; + state1.shared_frame_id = kState1SharedFrameId; + RtpPayloadState state2; + state2.picture_id = kInitialPictureId2; + state2.tl0_pic_idx = kInitialTl0PicIdx2; + state2.shared_frame_id = kState2SharedFrameId; + std::map<uint32_t, RtpPayloadState> states = {{kSsrc1, state1}, + {kSsrc2, state2}}; + + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, states); + test.SetActiveModules({true, true}); + + std::map<uint32_t, RtpPayloadState> initial_states = + test.router()->GetRtpPayloadStates(); + EXPECT_EQ(2u, initial_states.size()); + EXPECT_EQ(kInitialPictureId1, initial_states[kSsrc1].picture_id); + EXPECT_EQ(kInitialTl0PicIdx1, initial_states[kSsrc1].tl0_pic_idx); + EXPECT_EQ(kInitialPictureId2, initial_states[kSsrc2].picture_id); + EXPECT_EQ(kInitialTl0PicIdx2, initial_states[kSsrc2].tl0_pic_idx); + EXPECT_EQ(kState2SharedFrameId, initial_states[kSsrc1].shared_frame_id); + EXPECT_EQ(kState2SharedFrameId, initial_states[kSsrc2].shared_frame_id); +} + +TEST(RtpVideoSenderTest, FrameCountCallbacks) { + class MockFrameCountObserver : public FrameCountObserver { + public: + MOCK_METHOD(void, + FrameCountUpdated, + (const FrameCounts& frame_counts, uint32_t ssrc), + (override)); + } callback; + + RtpVideoSenderTestFixture test({kSsrc1}, {kRtxSsrc1}, kPayloadType, {}, + &callback); + + constexpr uint8_t kPayload = 'a'; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image.SetEncodedData(EncodedImageBuffer::Create(&kPayload, 1)); + + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + + // No callbacks when not active. + EXPECT_CALL(callback, FrameCountUpdated).Times(0); + EXPECT_NE(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + ::testing::Mock::VerifyAndClearExpectations(&callback); + + test.SetActiveModules({true}); + + FrameCounts frame_counts; + EXPECT_CALL(callback, FrameCountUpdated(_, kSsrc1)) + .WillOnce(SaveArg<0>(&frame_counts)); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + + EXPECT_EQ(1, frame_counts.key_frames); + EXPECT_EQ(0, frame_counts.delta_frames); + + ::testing::Mock::VerifyAndClearExpectations(&callback); + + encoded_image._frameType = VideoFrameType::kVideoFrameDelta; + EXPECT_CALL(callback, FrameCountUpdated(_, kSsrc1)) + .WillOnce(SaveArg<0>(&frame_counts)); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + + EXPECT_EQ(1, frame_counts.key_frames); + EXPECT_EQ(1, frame_counts.delta_frames); +} + +// Integration test verifying that ack of packet via TransportFeedback means +// that the packet is removed from RtpPacketHistory and won't be retransmitted +// again. +TEST(RtpVideoSenderTest, DoesNotRetrasmitAckedPackets) { + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); + test.SetActiveModules({true, true}); + + constexpr uint8_t kPayload = 'a'; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image.SetEncodedData(EncodedImageBuffer::Create(&kPayload, 1)); + + // Send two tiny images, mapping to two RTP packets. Capture sequence numbers. + std::vector<uint16_t> rtp_sequence_numbers; + std::vector<uint16_t> transport_sequence_numbers; + EXPECT_CALL(test.transport(), SendRtp) + .Times(2) + .WillRepeatedly([&rtp_sequence_numbers, &transport_sequence_numbers]( + const uint8_t* packet, size_t length, + const PacketOptions& options) { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet, length)); + rtp_sequence_numbers.push_back(rtp_packet.SequenceNumber()); + transport_sequence_numbers.push_back(options.packet_id); + return true; + }); + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + encoded_image.SetTimestamp(2); + encoded_image.capture_time_ms_ = 3; + EXPECT_EQ(EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr).error); + + test.AdvanceTime(TimeDelta::Millis(33)); + + // Construct a NACK message for requesting retransmission of both packet. + rtcp::Nack nack; + nack.SetMediaSsrc(kSsrc1); + nack.SetPacketIds(rtp_sequence_numbers); + rtc::Buffer nack_buffer = nack.Build(); + + std::vector<uint16_t> retransmitted_rtp_sequence_numbers; + EXPECT_CALL(test.transport(), SendRtp) + .Times(2) + .WillRepeatedly([&retransmitted_rtp_sequence_numbers]( + const uint8_t* packet, size_t length, + const PacketOptions& options) { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet, length)); + EXPECT_EQ(rtp_packet.Ssrc(), kRtxSsrc1); + // Capture the retransmitted sequence number from the RTX header. + rtc::ArrayView<const uint8_t> payload = rtp_packet.payload(); + retransmitted_rtp_sequence_numbers.push_back( + ByteReader<uint16_t>::ReadBigEndian(payload.data())); + return true; + }); + test.router()->DeliverRtcp(nack_buffer.data(), nack_buffer.size()); + test.AdvanceTime(TimeDelta::Millis(33)); + + // Verify that both packets were retransmitted. + EXPECT_EQ(retransmitted_rtp_sequence_numbers, rtp_sequence_numbers); + + // Simulate transport feedback indicating fist packet received, next packet + // lost (not other way around as that would trigger early retransmit). + StreamFeedbackObserver::StreamPacketInfo lost_packet_feedback; + lost_packet_feedback.rtp_sequence_number = rtp_sequence_numbers[0]; + lost_packet_feedback.ssrc = kSsrc1; + lost_packet_feedback.received = false; + lost_packet_feedback.is_retransmission = false; + + StreamFeedbackObserver::StreamPacketInfo received_packet_feedback; + received_packet_feedback.rtp_sequence_number = rtp_sequence_numbers[1]; + received_packet_feedback.ssrc = kSsrc1; + received_packet_feedback.received = true; + lost_packet_feedback.is_retransmission = false; + + test.router()->OnPacketFeedbackVector( + {lost_packet_feedback, received_packet_feedback}); + + // Advance time to make sure retransmission would be allowed and try again. + // This time the retransmission should not happen for the first packet since + // the history has been notified of the ack and removed the packet. The + // second packet, included in the feedback but not marked as received, should + // still be retransmitted. + test.AdvanceTime(TimeDelta::Millis(33)); + EXPECT_CALL(test.transport(), SendRtp) + .WillOnce([&lost_packet_feedback](const uint8_t* packet, size_t length, + const PacketOptions& options) { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet, length)); + EXPECT_EQ(rtp_packet.Ssrc(), kRtxSsrc1); + // Capture the retransmitted sequence number from the RTX header. + rtc::ArrayView<const uint8_t> payload = rtp_packet.payload(); + EXPECT_EQ(lost_packet_feedback.rtp_sequence_number, + ByteReader<uint16_t>::ReadBigEndian(payload.data())); + return true; + }); + test.router()->DeliverRtcp(nack_buffer.data(), nack_buffer.size()); + test.AdvanceTime(TimeDelta::Millis(33)); +} + +// This tests that we utilize transport wide feedback to retransmit lost +// packets. This is tested by dropping all ordinary packets from a "lossy" +// stream sent along with a secondary untouched stream. The transport wide +// feedback packets from the secondary stream allows the sending side to +// detect and retreansmit the lost packets from the lossy stream. +TEST(RtpVideoSenderTest, RetransmitsOnTransportWideLossInfo) { + int rtx_packets; + test::Scenario s(test_info_); + test::CallClientConfig call_conf; + // Keeping the bitrate fixed to avoid RTX due to probing. + call_conf.transport.rates.max_rate = DataRate::KilobitsPerSec(300); + call_conf.transport.rates.start_rate = DataRate::KilobitsPerSec(300); + test::NetworkSimulationConfig net_conf; + net_conf.bandwidth = DataRate::KilobitsPerSec(300); + auto send_node = s.CreateSimulationNode(net_conf); + auto* callee = s.CreateClient("return", call_conf); + auto* route = s.CreateRoutes(s.CreateClient("send", call_conf), {send_node}, + callee, {s.CreateSimulationNode(net_conf)}); + + test::VideoStreamConfig lossy_config; + lossy_config.source.framerate = 5; + auto* lossy = s.CreateVideoStream(route->forward(), lossy_config); + // The secondary stream acts a driver for transport feedback messages, + // ensuring that lost packets on the lossy stream are retransmitted. + s.CreateVideoStream(route->forward(), test::VideoStreamConfig()); + + send_node->router()->SetFilter([&](const EmulatedIpPacket& packet) { + RtpPacket rtp; + if (rtp.Parse(packet.data)) { + // Drops all regular packets for the lossy stream and counts all RTX + // packets. Since no packets are let trough, NACKs can't be triggered + // by the receiving side. + if (lossy->send()->UsingSsrc(rtp.Ssrc())) { + return false; + } else if (lossy->send()->UsingRtxSsrc(rtp.Ssrc())) { + ++rtx_packets; + } + } + return true; + }); + + // Run for a short duration and reset counters to avoid counting RTX packets + // from initial probing. + s.RunFor(TimeDelta::Seconds(1)); + rtx_packets = 0; + int decoded_baseline = 0; + callee->SendTask([&decoded_baseline, &lossy]() { + decoded_baseline = lossy->receive()->GetStats().frames_decoded; + }); + s.RunFor(TimeDelta::Seconds(1)); + // We expect both that RTX packets were sent and that an appropriate number of + // frames were received. This is somewhat redundant but reduces the risk of + // false positives in future regressions (e.g. RTX is send due to probing). + EXPECT_GE(rtx_packets, 1); + int frames_decoded = 0; + callee->SendTask([&decoded_baseline, &frames_decoded, &lossy]() { + frames_decoded = + lossy->receive()->GetStats().frames_decoded - decoded_baseline; + }); + EXPECT_EQ(frames_decoded, 5); +} + +// Integration test verifying that retransmissions are sent for packets which +// can be detected as lost early, using transport wide feedback. +TEST(RtpVideoSenderTest, EarlyRetransmits) { + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); + test.SetActiveModules({true, true}); + + const uint8_t kPayload[1] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + encoded_image.SetSpatialIndex(0); + + CodecSpecificInfo codec_specific; + codec_specific.codecType = VideoCodecType::kVideoCodecGeneric; + + // Send two tiny images, mapping to single RTP packets. Capture sequence + // numbers. + uint16_t frame1_rtp_sequence_number = 0; + uint16_t frame1_transport_sequence_number = 0; + EXPECT_CALL(test.transport(), SendRtp) + .WillOnce( + [&frame1_rtp_sequence_number, &frame1_transport_sequence_number]( + const uint8_t* packet, size_t length, + const PacketOptions& options) { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet, length)); + frame1_rtp_sequence_number = rtp_packet.SequenceNumber(); + frame1_transport_sequence_number = options.packet_id; + EXPECT_EQ(rtp_packet.Ssrc(), kSsrc1); + return true; + }); + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + + test.AdvanceTime(TimeDelta::Millis(33)); + + uint16_t frame2_rtp_sequence_number = 0; + uint16_t frame2_transport_sequence_number = 0; + encoded_image.SetSpatialIndex(1); + EXPECT_CALL(test.transport(), SendRtp) + .WillOnce( + [&frame2_rtp_sequence_number, &frame2_transport_sequence_number]( + const uint8_t* packet, size_t length, + const PacketOptions& options) { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet, length)); + frame2_rtp_sequence_number = rtp_packet.SequenceNumber(); + frame2_transport_sequence_number = options.packet_id; + EXPECT_EQ(rtp_packet.Ssrc(), kSsrc2); + return true; + }); + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + test.AdvanceTime(TimeDelta::Millis(33)); + + EXPECT_NE(frame1_transport_sequence_number, frame2_transport_sequence_number); + + // Inject a transport feedback where the packet for the first frame is lost, + // expect a retransmission for it. + EXPECT_CALL(test.transport(), SendRtp) + .WillOnce([&frame1_rtp_sequence_number](const uint8_t* packet, + size_t length, + const PacketOptions& options) { + RtpPacket rtp_packet; + EXPECT_TRUE(rtp_packet.Parse(packet, length)); + EXPECT_EQ(rtp_packet.Ssrc(), kRtxSsrc1); + + // Retransmitted sequence number from the RTX header should match + // the lost packet. + rtc::ArrayView<const uint8_t> payload = rtp_packet.payload(); + EXPECT_EQ(ByteReader<uint16_t>::ReadBigEndian(payload.data()), + frame1_rtp_sequence_number); + return true; + }); + + StreamFeedbackObserver::StreamPacketInfo first_packet_feedback; + first_packet_feedback.rtp_sequence_number = frame1_rtp_sequence_number; + first_packet_feedback.ssrc = kSsrc1; + first_packet_feedback.received = false; + first_packet_feedback.is_retransmission = false; + + StreamFeedbackObserver::StreamPacketInfo second_packet_feedback; + second_packet_feedback.rtp_sequence_number = frame2_rtp_sequence_number; + second_packet_feedback.ssrc = kSsrc2; + second_packet_feedback.received = true; + first_packet_feedback.is_retransmission = false; + + test.router()->OnPacketFeedbackVector( + {first_packet_feedback, second_packet_feedback}); + + // Wait for pacer to run and send the RTX packet. + test.AdvanceTime(TimeDelta::Millis(33)); +} + +TEST(RtpVideoSenderTest, SupportsDependencyDescriptor) { + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}); + test.SetActiveModules({true}); + + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault([&](const uint8_t* packet, size_t length, + const PacketOptions& options) { + sent_packets.emplace_back(&extensions); + EXPECT_TRUE(sent_packets.back().Parse(packet, length)); + return true; + }); + + const uint8_t kPayload[1] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + + CodecSpecificInfo codec_specific; + codec_specific.codecType = VideoCodecType::kVideoCodecGeneric; + codec_specific.template_structure.emplace(); + codec_specific.template_structure->num_decode_targets = 1; + codec_specific.template_structure->templates = { + FrameDependencyTemplate().T(0).Dtis("S"), + FrameDependencyTemplate().T(0).Dtis("S").FrameDiffs({2}), + FrameDependencyTemplate().T(1).Dtis("D").FrameDiffs({1}), + }; + + // Send two tiny images, mapping to single RTP packets. + // Send in key frame. + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + codec_specific.generic_frame_info = + GenericFrameInfo::Builder().T(0).Dtis("S").Build(); + codec_specific.generic_frame_info->encoder_buffers = {{0, false, true}}; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(1)); + EXPECT_TRUE( + sent_packets.back().HasExtension<RtpDependencyDescriptorExtension>()); + + // Send in delta frame. + encoded_image._frameType = VideoFrameType::kVideoFrameDelta; + codec_specific.template_structure = absl::nullopt; + codec_specific.generic_frame_info = + GenericFrameInfo::Builder().T(1).Dtis("D").Build(); + codec_specific.generic_frame_info->encoder_buffers = {{0, true, false}}; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(2)); + EXPECT_TRUE( + sent_packets.back().HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST(RtpVideoSenderTest, + SupportsDependencyDescriptorForVp8NotProvidedByEncoder) { + constexpr uint8_t kPayload[1] = {'a'}; + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}); + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault( + [&](const uint8_t* packet, size_t length, const PacketOptions&) { + EXPECT_TRUE( + sent_packets.emplace_back(&extensions).Parse(packet, length)); + return true; + }); + test.SetActiveModules({true}); + + EncodedImage key_frame_image; + key_frame_image._frameType = VideoFrameType::kVideoFrameKey; + key_frame_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + CodecSpecificInfo key_frame_info; + key_frame_info.codecType = VideoCodecType::kVideoCodecVP8; + ASSERT_EQ( + test.router()->OnEncodedImage(key_frame_image, &key_frame_info).error, + EncodedImageCallback::Result::OK); + + EncodedImage delta_image; + delta_image._frameType = VideoFrameType::kVideoFrameDelta; + delta_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + CodecSpecificInfo delta_info; + delta_info.codecType = VideoCodecType::kVideoCodecVP8; + ASSERT_EQ(test.router()->OnEncodedImage(delta_image, &delta_info).error, + EncodedImageCallback::Result::OK); + + test.AdvanceTime(TimeDelta::Millis(123)); + + DependencyDescriptor key_frame_dd; + DependencyDescriptor delta_dd; + ASSERT_THAT(sent_packets, SizeIs(2)); + EXPECT_TRUE(sent_packets[0].GetExtension<RtpDependencyDescriptorExtension>( + /*structure=*/nullptr, &key_frame_dd)); + EXPECT_TRUE(sent_packets[1].GetExtension<RtpDependencyDescriptorExtension>( + key_frame_dd.attached_structure.get(), &delta_dd)); +} + +TEST(RtpVideoSenderTest, SupportsDependencyDescriptorForVp9) { + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}); + test.SetActiveModules({true}); + + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault([&](const uint8_t* packet, size_t length, + const PacketOptions& options) { + sent_packets.emplace_back(&extensions); + EXPECT_TRUE(sent_packets.back().Parse(packet, length)); + return true; + }); + + const uint8_t kPayload[1] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + + CodecSpecificInfo codec_specific; + codec_specific.codecType = VideoCodecType::kVideoCodecVP9; + codec_specific.template_structure.emplace(); + codec_specific.template_structure->num_decode_targets = 2; + codec_specific.template_structure->templates = { + FrameDependencyTemplate().S(0).Dtis("SS"), + FrameDependencyTemplate().S(1).Dtis("-S").FrameDiffs({1}), + }; + + // Send two tiny images, each mapping to single RTP packet. + // Send in key frame for the base spatial layer. + codec_specific.generic_frame_info = + GenericFrameInfo::Builder().S(0).Dtis("SS").Build(); + codec_specific.generic_frame_info->encoder_buffers = {{0, false, true}}; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + // Send in 2nd spatial layer. + codec_specific.template_structure = absl::nullopt; + codec_specific.generic_frame_info = + GenericFrameInfo::Builder().S(1).Dtis("-S").Build(); + codec_specific.generic_frame_info->encoder_buffers = {{0, true, false}, + {1, false, true}}; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(2)); + EXPECT_TRUE(sent_packets[0].HasExtension<RtpDependencyDescriptorExtension>()); + EXPECT_TRUE(sent_packets[1].HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST(RtpVideoSenderTest, + SupportsDependencyDescriptorForVp9NotProvidedByEncoder) { + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}); + test.SetActiveModules({true}); + + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault([&](const uint8_t* packet, size_t length, + const PacketOptions& options) { + sent_packets.emplace_back(&extensions); + EXPECT_TRUE(sent_packets.back().Parse(packet, length)); + return true; + }); + + const uint8_t kPayload[1] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image._encodedWidth = 320; + encoded_image._encodedHeight = 180; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + + CodecSpecificInfo codec_specific; + codec_specific.codecType = VideoCodecType::kVideoCodecVP9; + codec_specific.codecSpecific.VP9.num_spatial_layers = 1; + codec_specific.codecSpecific.VP9.temporal_idx = kNoTemporalIdx; + codec_specific.codecSpecific.VP9.first_frame_in_picture = true; + codec_specific.end_of_picture = true; + codec_specific.codecSpecific.VP9.inter_pic_predicted = false; + + // Send two tiny images, each mapping to single RTP packet. + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + + // Send in 2nd picture. + encoded_image._frameType = VideoFrameType::kVideoFrameDelta; + encoded_image.SetTimestamp(3000); + codec_specific.codecSpecific.VP9.inter_pic_predicted = true; + codec_specific.codecSpecific.VP9.num_ref_pics = 1; + codec_specific.codecSpecific.VP9.p_diff[0] = 1; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(2)); + EXPECT_TRUE(sent_packets[0].HasExtension<RtpDependencyDescriptorExtension>()); + EXPECT_TRUE(sent_packets[1].HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST(RtpVideoSenderTest, GenerateDependecyDescriptorForGenericCodecs) { + test::ScopedKeyValueConfig field_trials( + "WebRTC-GenericCodecDependencyDescriptor/Enabled/"); + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}, &field_trials); + test.SetActiveModules({true}); + + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault([&](const uint8_t* packet, size_t length, + const PacketOptions& options) { + sent_packets.emplace_back(&extensions); + EXPECT_TRUE(sent_packets.back().Parse(packet, length)); + return true; + }); + + const uint8_t kPayload[1] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image._encodedWidth = 320; + encoded_image._encodedHeight = 180; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + + CodecSpecificInfo codec_specific; + codec_specific.codecType = VideoCodecType::kVideoCodecGeneric; + codec_specific.end_of_picture = true; + + // Send two tiny images, each mapping to single RTP packet. + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + + // Send in 2nd picture. + encoded_image._frameType = VideoFrameType::kVideoFrameDelta; + encoded_image.SetTimestamp(3000); + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(2)); + EXPECT_TRUE(sent_packets[0].HasExtension<RtpDependencyDescriptorExtension>()); + EXPECT_TRUE(sent_packets[1].HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST(RtpVideoSenderTest, SupportsStoppingUsingDependencyDescriptor) { + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}); + test.SetActiveModules({true}); + + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault([&](const uint8_t* packet, size_t length, + const PacketOptions& options) { + sent_packets.emplace_back(&extensions); + EXPECT_TRUE(sent_packets.back().Parse(packet, length)); + return true; + }); + + const uint8_t kPayload[1] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + + CodecSpecificInfo codec_specific; + codec_specific.codecType = VideoCodecType::kVideoCodecGeneric; + codec_specific.template_structure.emplace(); + codec_specific.template_structure->num_decode_targets = 1; + codec_specific.template_structure->templates = { + FrameDependencyTemplate().T(0).Dtis("S"), + FrameDependencyTemplate().T(0).Dtis("S").FrameDiffs({2}), + FrameDependencyTemplate().T(1).Dtis("D").FrameDiffs({1}), + }; + + // Send two tiny images, mapping to single RTP packets. + // Send in a key frame. + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + codec_specific.generic_frame_info = + GenericFrameInfo::Builder().T(0).Dtis("S").Build(); + codec_specific.generic_frame_info->encoder_buffers = {{0, false, true}}; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(1)); + EXPECT_TRUE( + sent_packets.back().HasExtension<RtpDependencyDescriptorExtension>()); + + // Send in a new key frame without the support for the dependency descriptor. + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + codec_specific.template_structure = absl::nullopt; + EXPECT_EQ(test.router()->OnEncodedImage(encoded_image, &codec_specific).error, + EncodedImageCallback::Result::OK); + test.AdvanceTime(TimeDelta::Millis(33)); + ASSERT_THAT(sent_packets, SizeIs(2)); + EXPECT_FALSE( + sent_packets.back().HasExtension<RtpDependencyDescriptorExtension>()); +} + +TEST(RtpVideoSenderTest, CanSetZeroBitrate) { + RtpVideoSenderTestFixture test({kSsrc1}, {kRtxSsrc1}, kPayloadType, {}); + test.router()->OnBitrateUpdated(CreateBitrateAllocationUpdate(0), + /*framerate*/ 0); +} + +TEST(RtpVideoSenderTest, SimulcastSenderRegistersFrameTransformers) { + rtc::scoped_refptr<MockFrameTransformer> transformer = + rtc::make_ref_counted<MockFrameTransformer>(); + + EXPECT_CALL(*transformer, RegisterTransformedFrameSinkCallback(_, kSsrc1)); + EXPECT_CALL(*transformer, RegisterTransformedFrameSinkCallback(_, kSsrc2)); + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}, nullptr, transformer); + + EXPECT_CALL(*transformer, UnregisterTransformedFrameSinkCallback(kSsrc1)); + EXPECT_CALL(*transformer, UnregisterTransformedFrameSinkCallback(kSsrc2)); +} + +TEST(RtpVideoSenderTest, OverheadIsSubtractedFromTargetBitrate) { + test::ScopedKeyValueConfig field_trials( + "WebRTC-Video-UseFrameRateForOverhead/Enabled/"); + + // TODO(jakobi): RTP header size should not be hard coded. + constexpr uint32_t kRtpHeaderSizeBytes = 20; + constexpr uint32_t kTransportPacketOverheadBytes = 40; + constexpr uint32_t kOverheadPerPacketBytes = + kRtpHeaderSizeBytes + kTransportPacketOverheadBytes; + RtpVideoSenderTestFixture test({kSsrc1}, {}, kPayloadType, {}, &field_trials); + test.router()->OnTransportOverheadChanged(kTransportPacketOverheadBytes); + test.SetActiveModules({true}); + + { + test.router()->OnBitrateUpdated(CreateBitrateAllocationUpdate(300000), + /*framerate*/ 15); + // 1 packet per frame. + EXPECT_EQ(test.router()->GetPayloadBitrateBps(), + 300000 - kOverheadPerPacketBytes * 8 * 30); + } + { + test.router()->OnBitrateUpdated(CreateBitrateAllocationUpdate(150000), + /*framerate*/ 15); + // 1 packet per frame. + EXPECT_EQ(test.router()->GetPayloadBitrateBps(), + 150000 - kOverheadPerPacketBytes * 8 * 15); + } + { + test.router()->OnBitrateUpdated(CreateBitrateAllocationUpdate(1000000), + /*framerate*/ 30); + // 3 packets per frame. + EXPECT_EQ(test.router()->GetPayloadBitrateBps(), + 1000000 - kOverheadPerPacketBytes * 8 * 30 * 3); + } +} + +TEST(RtpVideoSenderTest, ClearsPendingPacketsOnInactivation) { + RtpVideoSenderTestFixture test({kSsrc1}, {kRtxSsrc1}, kPayloadType, {}); + test.SetActiveModules({true}); + + RtpHeaderExtensionMap extensions; + extensions.Register<RtpDependencyDescriptorExtension>( + kDependencyDescriptorExtensionId); + std::vector<RtpPacket> sent_packets; + ON_CALL(test.transport(), SendRtp) + .WillByDefault([&](const uint8_t* packet, size_t length, + const PacketOptions& options) { + sent_packets.emplace_back(&extensions); + EXPECT_TRUE(sent_packets.back().Parse(packet, length)); + return true; + }); + + // Set a very low bitrate. + test.router()->OnBitrateUpdated( + CreateBitrateAllocationUpdate(/*rate_bps=*/30'000), + /*framerate=*/30); + + // Create and send a large keyframe. + const size_t kImageSizeBytes = 10000; + constexpr uint8_t kPayload[kImageSizeBytes] = {'a'}; + EncodedImage encoded_image; + encoded_image.SetTimestamp(1); + encoded_image.capture_time_ms_ = 2; + encoded_image._frameType = VideoFrameType::kVideoFrameKey; + encoded_image.SetEncodedData( + EncodedImageBuffer::Create(kPayload, sizeof(kPayload))); + EXPECT_EQ(test.router() + ->OnEncodedImage(encoded_image, /*codec_specific=*/nullptr) + .error, + EncodedImageCallback::Result::OK); + + // Advance time a small amount, check that sent data is only part of the + // image. + test.AdvanceTime(TimeDelta::Millis(5)); + DataSize transmittedPayload = DataSize::Zero(); + for (const RtpPacket& packet : sent_packets) { + transmittedPayload += DataSize::Bytes(packet.payload_size()); + // Make sure we don't see the end of the frame. + EXPECT_FALSE(packet.Marker()); + } + EXPECT_GT(transmittedPayload, DataSize::Zero()); + EXPECT_LT(transmittedPayload, DataSize::Bytes(kImageSizeBytes / 4)); + + // Record the RTP timestamp of the first frame. + const uint32_t first_frame_timestamp = sent_packets[0].Timestamp(); + sent_packets.clear(); + + // Disable the sending module and advance time slightly. No packets should be + // sent. + test.SetActiveModules({false}); + test.AdvanceTime(TimeDelta::Millis(20)); + EXPECT_TRUE(sent_packets.empty()); + + // Reactive the send module - any packets should have been removed, so nothing + // should be transmitted. + test.SetActiveModules({true}); + test.AdvanceTime(TimeDelta::Millis(33)); + EXPECT_TRUE(sent_packets.empty()); + + // Send a new frame. + encoded_image.SetTimestamp(3); + encoded_image.capture_time_ms_ = 4; + EXPECT_EQ(test.router() + ->OnEncodedImage(encoded_image, /*codec_specific=*/nullptr) + .error, + EncodedImageCallback::Result::OK); + test.AdvanceTime(TimeDelta::Millis(33)); + + // Advance time, check we get new packets - but only for the second frame. + EXPECT_FALSE(sent_packets.empty()); + EXPECT_NE(sent_packets[0].Timestamp(), first_frame_timestamp); +} + +} // namespace webrtc |