diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc index 019f8422c8..07e8e625ed 100644 --- a/call/rtp_video_sender.cc +++ b/call/rtp_video_sender.cc @@ -16,6 +16,7 @@ #include #include "absl/memory/memory.h" +#include "api/array_view.h" #include "api/transport/field_trial_based_config.h" #include "call/rtp_transport_controller_send_interface.h" #include "modules/pacing/packet_router.h" @@ -323,18 +324,14 @@ RtpVideoSender::RtpVideoSender( fec_controller_->SetProtectionCallback(this); // Signal congestion controller this object is ready for OnPacket* callbacks. - if (fec_controller_->UseLossVectorMask()) { - transport_->RegisterPacketFeedbackObserver(this); - } + transport_->RegisterPacketFeedbackObserver(this); } RtpVideoSender::~RtpVideoSender() { for (const RtpStreamSender& stream : rtp_streams_) { transport_->packet_router()->RemoveSendRtpModule(stream.rtp_rtcp.get()); } - if (fec_controller_->UseLossVectorMask()) { - transport_->DeRegisterPacketFeedbackObserver(this); - } + transport_->DeRegisterPacketFeedbackObserver(this); } void RtpVideoSender::RegisterProcessThread( @@ -567,6 +564,7 @@ void RtpVideoSender::DeliverRtcp(const uint8_t* packet, size_t length) { void RtpVideoSender::ConfigureSsrcs(const RtpConfig& rtp_config) { // Configure regular SSRCs. + RTC_CHECK(ssrc_to_acknowledged_packets_observers_.empty()); for (size_t i = 0; i < rtp_config.ssrcs.size(); ++i) { uint32_t ssrc = rtp_config.ssrcs[i]; RtpRtcp* const rtp_rtcp = rtp_streams_[i].rtp_rtcp.get(); @@ -576,6 +574,11 @@ void RtpVideoSender::ConfigureSsrcs(const RtpConfig& rtp_config) { auto it = suspended_ssrcs_.find(ssrc); if (it != suspended_ssrcs_.end()) rtp_rtcp->SetRtpState(it->second); + + AcknowledgedPacketsObserver* receive_observer = + rtp_rtcp->GetAcknowledgedPacketsObserver(); + RTC_DCHECK(receive_observer != nullptr); + ssrc_to_acknowledged_packets_observers_[ssrc] = receive_observer; } // Set up RTX if available. @@ -791,16 +794,40 @@ void RtpVideoSender::OnPacketAdded(uint32_t ssrc, uint16_t seq_num) { void RtpVideoSender::OnPacketFeedbackVector( const std::vector& packet_feedback_vector) { - rtc::CritScope lock(&crit_); - // Lost feedbacks are not considered to be lost packets. - for (const PacketFeedback& packet : packet_feedback_vector) { - auto it = feedback_packet_seq_num_set_.find(packet.sequence_number); - if (it != feedback_packet_seq_num_set_.end()) { + if (fec_controller_->UseLossVectorMask()) { + rtc::CritScope cs(&crit_); + for (const PacketFeedback& packet : packet_feedback_vector) { const bool lost = packet.arrival_time_ms == PacketFeedback::kNotReceived; - loss_mask_vector_.push_back(lost); - feedback_packet_seq_num_set_.erase(it); + // Lost feedbacks are not considered to be lost packets. + auto it = feedback_packet_seq_num_set_.find(packet.sequence_number); + if (it != feedback_packet_seq_num_set_.end()) { + loss_mask_vector_.push_back(lost); + feedback_packet_seq_num_set_.erase(it); + } } } + + // Map from SSRC to all acked packets for that RTP module. + std::map> acked_packets_per_ssrc; + for (const PacketFeedback& packet : packet_feedback_vector) { + if (packet.ssrc && packet.arrival_time_ms != PacketFeedback::kNotReceived) { + acked_packets_per_ssrc[*packet.ssrc].push_back( + packet.rtp_sequence_number); + } + } + + for (const auto& kv : acked_packets_per_ssrc) { + const uint32_t ssrc = kv.first; + if (ssrc_to_acknowledged_packets_observers_.find(ssrc) == + ssrc_to_acknowledged_packets_observers_.end()) { + // Packets not for a media SSRC, so likely RTX or FEC. If so, ignore + // since there's no RTP history to clean up anyway. + continue; + } + rtc::ArrayView rtp_sequence_numbers(kv.second); + ssrc_to_acknowledged_packets_observers_[ssrc]->OnPacketsAcknowledged( + rtp_sequence_numbers); + } } void RtpVideoSender::SetEncodingData(size_t width, diff --git a/call/rtp_video_sender.h b/call/rtp_video_sender.h index e17bb49a36..162875b204 100644 --- a/call/rtp_video_sender.h +++ b/call/rtp_video_sender.h @@ -172,7 +172,7 @@ class RtpVideoSender : public RtpVideoSenderInterface, std::map suspended_ssrcs_; std::unique_ptr flexfec_sender_; - std::unique_ptr fec_controller_; + const std::unique_ptr fec_controller_; // Rtp modules are assumed to be sorted in simulcast index order. const std::vector rtp_streams_; @@ -197,6 +197,12 @@ class RtpVideoSender : public RtpVideoSenderInterface, std::vector frame_counts_ RTC_GUARDED_BY(crit_); FrameCountObserver* const frame_count_observer_; + // Effectively const map from ssrc to AcknowledgedPacketsObserver. This + // map is set at construction time and never changed, but it's + // non-trivial to make it properly const. + std::map + ssrc_to_acknowledged_packets_observers_; + RTC_DISALLOW_COPY_AND_ASSIGN(RtpVideoSender); }; diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc index 9416173c3c..d0fe7b311f 100644 --- a/call/rtp_video_sender_unittest.cc +++ b/call/rtp_video_sender_unittest.cc @@ -15,8 +15,13 @@ #include "api/task_queue/default_task_queue_factory.h" #include "call/rtp_transport_controller_send.h" #include "call/rtp_video_sender.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_packet.h" #include "modules/video_coding/fec_controller_default.h" #include "modules/video_coding/include/video_codec_interface.h" +#include "rtc_base/event.h" #include "rtc_base/rate_limiter.h" #include "test/field_trial.h" #include "test/gmock.h" @@ -27,6 +32,7 @@ #include "video/send_statistics_proxy.h" using ::testing::_; +using ::testing::Invoke; using ::testing::NiceMock; using ::testing::SaveArg; using ::testing::Unused; @@ -36,6 +42,8 @@ namespace { 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; @@ -60,6 +68,7 @@ RtpSenderObservers CreateObservers( RtpSenderObservers observers; observers.rtcp_rtt_stats = rtcp_rtt_stats; observers.intra_frame_callback = intra_frame_callback; + observers.rtcp_loss_notification_observer = nullptr; observers.rtcp_stats = rtcp_stats; observers.rtp_stats = rtp_stats; observers.bitrate_observer = bitrate_observer; @@ -70,16 +79,43 @@ RtpSenderObservers CreateObservers( 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& ssrcs, + const std::vector& 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; + return config; +} + class RtpVideoSenderTestFixture { public: RtpVideoSenderTestFixture( const std::vector& ssrcs, + const std::vector& rtx_ssrcs, int payload_type, const std::map& suspended_payload_states, FrameCountObserver* frame_count_observer) : clock_(1000000), - config_(&transport_), + config_(CreateVideoSendStreamConfig(&transport_, + ssrcs, + rtx_ssrcs, + payload_type)), send_delay_stats_(&clock_), + bitrate_config_(GetBitrateConfig()), task_queue_factory_(CreateDefaultTaskQueueFactory()), transport_controller_(&clock_, &event_log_, @@ -94,10 +130,6 @@ class RtpVideoSenderTestFixture { config_, VideoEncoderConfig::ContentType::kRealtimeVideo), retransmission_rate_limiter_(&clock_, kRetransmitWindowSizeMs) { - for (uint32_t ssrc : ssrcs) { - config_.rtp.ssrcs.push_back(ssrc); - } - config_.rtp.payload_type = payload_type; std::map suspended_ssrcs; router_ = absl::make_unique( &clock_, suspended_ssrcs, suspended_payload_states, config_.rtp, @@ -111,14 +143,18 @@ class RtpVideoSenderTestFixture { } RtpVideoSenderTestFixture( const std::vector& ssrcs, + const std::vector& rtx_ssrcs, int payload_type, const std::map& suspended_payload_states) : RtpVideoSenderTestFixture(ssrcs, + rtx_ssrcs, payload_type, suspended_payload_states, /*frame_count_observer=*/nullptr) {} RtpVideoSender* router() { return router_.get(); } + MockTransport& transport() { return transport_; } + SimulatedClock& clock() { return clock_; } private: NiceMock transport_; @@ -148,7 +184,7 @@ TEST(RtpVideoSenderTest, SendOnOneModule) { encoded_image.data()[0] = kPayload; encoded_image.set_size(1); - RtpVideoSenderTestFixture test({kSsrc1}, kPayloadType, {}); + RtpVideoSenderTestFixture test({kSsrc1}, {kRtxSsrc1}, kPayloadType, {}); EXPECT_NE( EncodedImageCallback::Result::OK, test.router()->OnEncodedImage(encoded_image, nullptr, nullptr).error); @@ -179,7 +215,8 @@ TEST(RtpVideoSenderTest, SendSimulcastSetActive) { encoded_image_1.data()[0] = kPayload; encoded_image_1.set_size(1); - RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, kPayloadType, {}); + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); CodecSpecificInfo codec_info; codec_info.codecType = kVideoCodecVP8; @@ -226,7 +263,8 @@ TEST(RtpVideoSenderTest, SendSimulcastSetActiveModules) { EncodedImage encoded_image_2(encoded_image_1); encoded_image_2.SetSpatialIndex(1); - RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, kPayloadType, {}); + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); CodecSpecificInfo codec_info; codec_info.codecType = kVideoCodecVP8; @@ -256,7 +294,8 @@ TEST(RtpVideoSenderTest, SendSimulcastSetActiveModules) { } TEST(RtpVideoSenderTest, CreateWithNoPreviousStates) { - RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, kPayloadType, {}); + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, {}); test.router()->SetActive(true); std::map initial_states = @@ -280,7 +319,8 @@ TEST(RtpVideoSenderTest, CreateWithPreviousStates) { std::map states = {{kSsrc1, state1}, {kSsrc2, state2}}; - RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, kPayloadType, states); + RtpVideoSenderTestFixture test({kSsrc1, kSsrc2}, {kRtxSsrc1, kRtxSsrc2}, + kPayloadType, states); test.router()->SetActive(true); std::map initial_states = @@ -301,7 +341,8 @@ TEST(RtpVideoSenderTest, FrameCountCallbacks) { void(const FrameCounts& frame_counts, uint32_t ssrc)); } callback; - RtpVideoSenderTestFixture test({kSsrc1}, kPayloadType, {}, &callback); + RtpVideoSenderTestFixture test({kSsrc1}, {kRtxSsrc1}, kPayloadType, {}, + &callback); constexpr uint8_t kPayload = 'a'; EncodedImage encoded_image; @@ -346,4 +387,123 @@ TEST(RtpVideoSenderTest, FrameCountCallbacks) { 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 retranmistted +// again. +TEST(RtpVideoSenderTest, DoesNotRetrasmitAckedPackets) { + 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); + + // Send two tiny images, mapping to two RTP packets. Capture sequence numbers. + rtc::Event event; + std::vector rtp_sequence_numbers; + std::vector transport_sequence_numbers; + EXPECT_CALL(test.transport(), SendRtp) + .Times(2) + .WillRepeatedly( + [&event, &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); + if (transport_sequence_numbers.size() == 2) { + event.Set(); + } + return true; + }); + EXPECT_EQ( + EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr, nullptr).error); + encoded_image.SetTimestamp(2); + encoded_image.capture_time_ms_ = 3; + EXPECT_EQ( + EncodedImageCallback::Result::OK, + test.router()->OnEncodedImage(encoded_image, nullptr, nullptr).error); + test.clock().AdvanceTimeMilliseconds(33); + + ASSERT_TRUE(event.Wait(kTimeoutMs)); + + // 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 retransmitted_rtp_sequence_numbers; + EXPECT_CALL(test.transport(), SendRtp) + .Times(2) + .WillRepeatedly([&event, &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 payload = rtp_packet.payload(); + retransmitted_rtp_sequence_numbers.push_back( + ByteReader::ReadBigEndian(payload.data())); + if (retransmitted_rtp_sequence_numbers.size() == 2) { + event.Set(); + } + return true; + }); + test.router()->DeliverRtcp(nack_buffer.data(), nack_buffer.size()); + ASSERT_TRUE(event.Wait(kTimeoutMs)); + + // 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. + PacketFeedback received_packet_feedback(test.clock().TimeInMilliseconds(), + transport_sequence_numbers[0]); + received_packet_feedback.rtp_sequence_number = rtp_sequence_numbers[0]; + received_packet_feedback.ssrc = kSsrc1; + + PacketFeedback lost_packet_feedback(PacketFeedback::kNotReceived, + transport_sequence_numbers[1]); + lost_packet_feedback.rtp_sequence_number = rtp_sequence_numbers[1]; + lost_packet_feedback.ssrc = kSsrc1; + std::vector feedback_vector = {received_packet_feedback, + lost_packet_feedback}; + + test.router()->OnPacketFeedbackVector(feedback_vector); + + // 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.clock().AdvanceTimeMilliseconds(33); + EXPECT_CALL(test.transport(), SendRtp) + .WillOnce([&event, &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 payload = rtp_packet.payload(); + EXPECT_EQ(lost_packet_feedback.rtp_sequence_number, + ByteReader::ReadBigEndian(payload.data())); + event.Set(); + return true; + }); + test.router()->DeliverRtcp(nack_buffer.data(), nack_buffer.size()); + ASSERT_TRUE(event.Wait(kTimeoutMs)); +} } // namespace webrtc diff --git a/modules/rtp_rtcp/include/rtp_rtcp.h b/modules/rtp_rtcp/include/rtp_rtcp.h index 1c589f4c93..9fb26cbbea 100644 --- a/modules/rtp_rtcp/include/rtp_rtcp.h +++ b/modules/rtp_rtcp/include/rtp_rtcp.h @@ -275,6 +275,11 @@ class RtpRtcp : public Module, public RtcpFeedbackSenderInterface { virtual StreamDataCountersCallback* GetSendChannelRtpStatisticsCallback() const = 0; + // Returns a pointer to an observer that handles information about packets + // that have been received by the remote end, or nullptr if not applicable. + virtual AcknowledgedPacketsObserver* GetAcknowledgedPacketsObserver() + const = 0; + // ************************************************************************** // RTCP // ************************************************************************** diff --git a/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/modules/rtp_rtcp/include/rtp_rtcp_defines.h index 4a4b82555e..2828575d01 100644 --- a/modules/rtp_rtcp/include/rtp_rtcp_defines.h +++ b/modules/rtp_rtcp/include/rtp_rtcp_defines.h @@ -16,7 +16,9 @@ #include #include "absl/strings/string_view.h" +#include "absl/types/optional.h" #include "absl/types/variant.h" +#include "api/array_view.h" #include "api/audio_codecs/audio_format.h" #include "api/rtp_headers.h" #include "api/transport/network_types.h" @@ -276,7 +278,7 @@ struct PacketFeedback { PacedPacketInfo pacing_info; // The SSRC and RTP sequence number of the packet this feedback refers to. - uint32_t ssrc; + absl::optional ssrc; uint16_t rtp_sequence_number; }; @@ -313,6 +315,17 @@ class TransportFeedbackObserver { virtual void OnTransportFeedback(const rtcp::TransportFeedback& feedback) = 0; }; +class AcknowledgedPacketsObserver { + public: + AcknowledgedPacketsObserver() = default; + virtual ~AcknowledgedPacketsObserver() = default; + + // Indicates RTP sequence numbers for packets that have been acknowledged as + // received by the remote end. + virtual void OnPacketsAcknowledged( + rtc::ArrayView sequence_numbers) = 0; +}; + // Interface for PacketRouter to send rtcp feedback on behalf of // congestion controller. // TODO(bugs.webrtc.org/8239): Remove and use RtcpTransceiver directly diff --git a/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h index 668d52707d..733ec28c47 100644 --- a/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h +++ b/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h @@ -157,7 +157,9 @@ class MockRtpRtcp : public RtpRtcp { MOCK_METHOD1(RegisterSendChannelRtpStatisticsCallback, void(StreamDataCountersCallback*)); MOCK_CONST_METHOD0(GetSendChannelRtpStatisticsCallback, - StreamDataCountersCallback*(void)); + StreamDataCountersCallback*()); + MOCK_CONST_METHOD0(GetAcknowledgedPacketsObserver, + AcknowledgedPacketsObserver*()); MOCK_METHOD1(SetVideoBitrateAllocation, void(const VideoBitrateAllocation&)); MOCK_METHOD0(RtpSender, RTPSender*()); MOCK_CONST_METHOD0(RtpSender, const RTPSender*()); diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index a15558d95a..97cc8798b2 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -867,6 +867,11 @@ ModuleRtpRtcpImpl::GetSendChannelRtpStatisticsCallback() const { return rtp_sender_->GetRtpStatisticsCallback(); } +AcknowledgedPacketsObserver* ModuleRtpRtcpImpl::GetAcknowledgedPacketsObserver() + const { + return rtp_sender_.get(); +} + void ModuleRtpRtcpImpl::SetVideoBitrateAllocation( const VideoBitrateAllocation& bitrate) { rtcp_sender_.SetVideoBitrateAllocation(bitrate); diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/modules/rtp_rtcp/source/rtp_rtcp_impl.h index cb565f8f63..de0080013d 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -276,6 +276,7 @@ class ModuleRtpRtcpImpl : public RtpRtcp, public RTCPReceiver::ModuleRtpRtcp { StreamDataCountersCallback* callback) override; StreamDataCountersCallback* GetSendChannelRtpStatisticsCallback() const override; + AcknowledgedPacketsObserver* GetAcknowledgedPacketsObserver() const override; void OnReceivedNack( const std::vector& nack_sequence_numbers) override; diff --git a/modules/rtp_rtcp/source/rtp_sender.cc b/modules/rtp_rtcp/source/rtp_sender.cc index bc672c6d07..55095d7511 100644 --- a/modules/rtp_rtcp/source/rtp_sender.cc +++ b/modules/rtp_rtcp/source/rtp_sender.cc @@ -1219,6 +1219,7 @@ void RTPSender::AddPacketToTransportFeedback( RtpPacketSendInfo packet_info; packet_info.ssrc = SSRC(); packet_info.transport_sequence_number = packet_id; + packet_info.has_rtp_sequence_number = true; packet_info.rtp_sequence_number = packet.SequenceNumber(); packet_info.length = packet_size; packet_info.pacing_info = pacing_info; @@ -1250,4 +1251,9 @@ void RTPSender::SetRtt(int64_t rtt_ms) { packet_history_.SetRtt(rtt_ms); flexfec_packet_history_.SetRtt(rtt_ms); } + +void RTPSender::OnPacketsAcknowledged( + rtc::ArrayView sequence_numbers) { + packet_history_.CullAcknowledgedPackets(sequence_numbers); +} } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_sender.h b/modules/rtp_rtcp/source/rtp_sender.h index eb0dbc40fc..11bb6cf9f0 100644 --- a/modules/rtp_rtcp/source/rtp_sender.h +++ b/modules/rtp_rtcp/source/rtp_sender.h @@ -42,7 +42,7 @@ class RateLimiter; class RtcEventLog; class RtpPacketToSend; -class RTPSender { +class RTPSender : public AcknowledgedPacketsObserver { public: RTPSender(bool audio, Clock* clock, @@ -173,6 +173,9 @@ class RTPSender { void SetRtt(int64_t rtt_ms); + void OnPacketsAcknowledged( + rtc::ArrayView sequence_numbers) override; + private: // Maps capture time in milliseconds to send-side delay in milliseconds. // Send-side delay is the difference between transmission time and capture