Add support for early loss detection using transport feedback.
Bug: webrtc:10676
Change-Id: Ifdef133e123a0c54204397fb323f4c671c40a464
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/135881
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28106}
diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc
index 9622fbc..16eb8a35 100644
--- a/call/rtp_video_sender_unittest.cc
+++ b/call/rtp_video_sender_unittest.cc
@@ -49,6 +49,7 @@
const int16_t kInitialTl0PicIdx1 = 99;
const int16_t kInitialTl0PicIdx2 = 199;
const int64_t kRetransmitWindowSizeMs = 500;
+const int kTransportsSequenceExtensionId = 7;
class MockRtcpIntraFrameObserver : public RtcpIntraFrameObserver {
public:
@@ -100,6 +101,8 @@
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);
return config;
}
@@ -391,7 +394,7 @@
}
// Integration test verifying that ack of packet via TransportFeedback means
-// that the packet is removed from RtpPacketHistory and won't be retranmistted
+// that the packet is removed from RtpPacketHistory and won't be retransmitted
// again.
TEST(RtpVideoSenderTest, DoesNotRetrasmitAckedPackets) {
const int64_t kTimeoutMs = 500;
@@ -513,4 +516,119 @@
test.router()->DeliverRtcp(nack_buffer.data(), nack_buffer.size());
ASSERT_TRUE(event.Wait(kTimeoutMs));
}
+
+// Integration test verifying that retransmissions are sent for packets which
+// can be detected as lost early, using transport wide feedback.
+TEST(RtpVideoSenderTest, EarlyRetransmits) {
+ const int64_t kTimeoutMs = 500;
+
+ RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2},
+ kPayloadType, {});
+ test.router()->SetActive(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.Allocate(1);
+ encoded_image.data()[0] = kPayload;
+ encoded_image.set_size(1);
+ encoded_image.SetSpatialIndex(0);
+
+ CodecSpecificInfo codec_specific;
+ codec_specific.codecType = VideoCodecType::kVideoCodecGeneric;
+
+ // Send two tiny images, mapping to single RTP packets. Capture sequence
+ // numbers.
+ rtc::Event event;
+ uint16_t frame1_rtp_sequence_number = 0;
+ uint16_t frame1_transport_sequence_number = 0;
+ EXPECT_CALL(test.transport(), SendRtp)
+ .WillOnce([&event, &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);
+ event.Set();
+ return true;
+ });
+ EXPECT_EQ(test.router()
+ ->OnEncodedImage(encoded_image, &codec_specific, nullptr)
+ .error,
+ EncodedImageCallback::Result::OK);
+ const int64_t send_time_ms = test.clock().TimeInMilliseconds();
+
+ test.clock().AdvanceTimeMilliseconds(33);
+ ASSERT_TRUE(event.Wait(kTimeoutMs));
+
+ uint16_t frame2_rtp_sequence_number = 0;
+ uint16_t frame2_transport_sequence_number = 0;
+ encoded_image.SetSpatialIndex(1);
+ EXPECT_CALL(test.transport(), SendRtp)
+ .WillOnce([&event, &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);
+ event.Set();
+ return true;
+ });
+ EXPECT_EQ(test.router()
+ ->OnEncodedImage(encoded_image, &codec_specific, nullptr)
+ .error,
+ EncodedImageCallback::Result::OK);
+ test.clock().AdvanceTimeMilliseconds(33);
+ ASSERT_TRUE(event.Wait(kTimeoutMs));
+
+ 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([&event, &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(), kRtxSsrc2);
+
+ // 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);
+ event.Set();
+ return true;
+ });
+
+ PacketFeedback first_packet_feedback(PacketFeedback::kNotReceived,
+ frame1_transport_sequence_number);
+ first_packet_feedback.rtp_sequence_number = frame1_rtp_sequence_number;
+ first_packet_feedback.ssrc = kSsrc1;
+ first_packet_feedback.send_time_ms = send_time_ms;
+
+ PacketFeedback second_packet_feedback(test.clock().TimeInMilliseconds(),
+ frame2_transport_sequence_number);
+ first_packet_feedback.rtp_sequence_number = frame2_rtp_sequence_number;
+ first_packet_feedback.ssrc = kSsrc2;
+ first_packet_feedback.send_time_ms = send_time_ms + 33;
+
+ std::vector<PacketFeedback> feedback_vector = {first_packet_feedback,
+ second_packet_feedback};
+
+ test.router()->OnPacketFeedbackVector(feedback_vector);
+
+ // Wait for pacer to run and send the RTX packet.
+ test.clock().AdvanceTimeMilliseconds(33);
+ ASSERT_TRUE(event.Wait(kTimeoutMs));
+}
} // namespace webrtc