diff --git a/api/rtp_headers.h b/api/rtp_headers.h index 4415bd3736..44d2deeb90 100644 --- a/api/rtp_headers.h +++ b/api/rtp_headers.h @@ -101,10 +101,19 @@ struct RTPHeaderExtension { Timestamp GetAbsoluteSendTimestamp() const { RTC_DCHECK(hasAbsoluteSendTime); RTC_DCHECK(absoluteSendTime < (1ul << 24)); - return Timestamp::us((absoluteSendTime * 1000000L) / + return Timestamp::us((absoluteSendTime * 1000000ll) / (1 << kAbsSendTimeFraction)); } + TimeDelta GetAbsoluteSendTimeDelta(uint32_t previous_sendtime) const { + RTC_DCHECK(hasAbsoluteSendTime); + RTC_DCHECK(absoluteSendTime < (1ul << 24)); + RTC_DCHECK(previous_sendtime < (1ul << 24)); + int32_t delta = + static_cast((absoluteSendTime - previous_sendtime) << 8) >> 8; + return TimeDelta::us((delta * 1000000ll) / (1 << kAbsSendTimeFraction)); + } + bool hasTransmissionTimeOffset; int32_t transmissionTimeOffset; bool hasAbsoluteSendTime; diff --git a/api/transport/BUILD.gn b/api/transport/BUILD.gn index 966cb7dd19..1b485554e4 100644 --- a/api/transport/BUILD.gn +++ b/api/transport/BUILD.gn @@ -110,3 +110,16 @@ if (rtc_include_tests) { ] } } + +if (rtc_include_tests) { + rtc_source_set("mock_network_control") { + testonly = true + sources = [ + "test/mock_network_control.h", + ] + deps = [ + ":network_control", + "../../test:test_support", + ] + } +} diff --git a/api/transport/network_control.h b/api/transport/network_control.h index 8efb1fcc42..8b2958c8aa 100644 --- a/api/transport/network_control.h +++ b/api/transport/network_control.h @@ -110,7 +110,11 @@ class NetworkStateEstimator { // Gets the current best estimate according to the estimator. virtual absl::optional GetCurrentEstimate() = 0; // Called with per packet feedback regarding receive time. + // Used when the NetworkStateEstimator runs in the sending endpoint. virtual void OnTransportPacketsFeedback(const TransportPacketsFeedback&) = 0; + // Called with per packet feedback regarding receive time. + // Used when the NetworkStateEstimator runs in the receiving endpoint. + virtual void OnReceivedPacket(const PacketResult&) {} // Called when the receiving or sending endpoint changes address. virtual void OnRouteChange(const NetworkRouteChange&) = 0; virtual ~NetworkStateEstimator() = default; diff --git a/api/transport/test/mock_network_control.h b/api/transport/test/mock_network_control.h new file mode 100644 index 0000000000..54a416cb77 --- /dev/null +++ b/api/transport/test/mock_network_control.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 API_TRANSPORT_TEST_MOCK_NETWORK_CONTROL_H_ +#define API_TRANSPORT_TEST_MOCK_NETWORK_CONTROL_H_ + +#include "api/transport/network_control.h" +#include "test/gmock.h" + +namespace webrtc { + +class MockNetworkStateEstimator : public NetworkStateEstimator { + public: + MOCK_METHOD0(GetCurrentEstimate, absl::optional()); + MOCK_METHOD1(OnTransportPacketsFeedback, + void(const TransportPacketsFeedback&)); + MOCK_METHOD1(OnReceivedPacket, void(const PacketResult&)); + MOCK_METHOD1(OnRouteChange, void(const NetworkRouteChange&)); +}; + +} // namespace webrtc + +#endif // API_TRANSPORT_TEST_MOCK_NETWORK_CONTROL_H_ diff --git a/modules/congestion_controller/BUILD.gn b/modules/congestion_controller/BUILD.gn index bb13093562..3f2f5d9e2b 100644 --- a/modules/congestion_controller/BUILD.gn +++ b/modules/congestion_controller/BUILD.gn @@ -27,6 +27,7 @@ rtc_static_library("congestion_controller") { deps = [ "..:module_api", "../../api/transport:field_trial_based_config", + "../../api/transport:network_control", "../pacing", "../remote_bitrate_estimator", "../rtp_rtcp:rtp_rtcp_format", diff --git a/modules/congestion_controller/include/receive_side_congestion_controller.h b/modules/congestion_controller/include/receive_side_congestion_controller.h index dd8a0cf2a3..4f13b4d549 100644 --- a/modules/congestion_controller/include/receive_side_congestion_controller.h +++ b/modules/congestion_controller/include/receive_side_congestion_controller.h @@ -15,6 +15,7 @@ #include #include "api/transport/field_trial_based_config.h" +#include "api/transport/network_control.h" #include "modules/include/module.h" #include "modules/remote_bitrate_estimator/remote_estimator_proxy.h" #include "rtc_base/constructor_magic.h" @@ -33,6 +34,10 @@ class ReceiveSideCongestionController : public CallStatsObserver, public Module { public: ReceiveSideCongestionController(Clock* clock, PacketRouter* packet_router); + ReceiveSideCongestionController( + Clock* clock, + PacketRouter* packet_router, + NetworkStateEstimator* network_state_estimator); ~ReceiveSideCongestionController() override {} diff --git a/modules/congestion_controller/receive_side_congestion_controller.cc b/modules/congestion_controller/receive_side_congestion_controller.cc index 9f674d160d..628981f072 100644 --- a/modules/congestion_controller/receive_side_congestion_controller.cc +++ b/modules/congestion_controller/receive_side_congestion_controller.cc @@ -121,18 +121,24 @@ void ReceiveSideCongestionController::WrappingBitrateEstimator:: ReceiveSideCongestionController::ReceiveSideCongestionController( Clock* clock, PacketRouter* packet_router) + : ReceiveSideCongestionController(clock, packet_router, nullptr) {} + +ReceiveSideCongestionController::ReceiveSideCongestionController( + Clock* clock, + PacketRouter* packet_router, + NetworkStateEstimator* network_state_estimator) : remote_bitrate_estimator_(packet_router, clock), - remote_estimator_proxy_(clock, packet_router, &field_trial_config_) {} + remote_estimator_proxy_(clock, + packet_router, + &field_trial_config_, + network_state_estimator) {} void ReceiveSideCongestionController::OnReceivedPacket( int64_t arrival_time_ms, size_t payload_size, const RTPHeader& header) { - // Send-side BWE. - if (header.extension.hasTransportSequenceNumber) { - remote_estimator_proxy_.IncomingPacket(arrival_time_ms, payload_size, - header); - } else { + remote_estimator_proxy_.IncomingPacket(arrival_time_ms, payload_size, header); + if (!header.extension.hasTransportSequenceNumber) { // Receive-side BWE. remote_bitrate_estimator_.IncomingPacket(arrival_time_ms, payload_size, header); diff --git a/modules/pacing/packet_router.cc b/modules/pacing/packet_router.cc index b0069f0711..31b1ef1f3e 100644 --- a/modules/pacing/packet_router.cc +++ b/modules/pacing/packet_router.cc @@ -292,6 +292,17 @@ bool PacketRouter::SendTransportFeedback(rtcp::TransportFeedback* packet) { return false; } +void PacketRouter::SendNetworkStateEstimatePacket( + rtcp::RemoteEstimate* packet) { + rtc::CritScope cs(&modules_crit_); + for (auto* rtcp_sender : rtcp_feedback_senders_) { + packet->SetSsrc(rtcp_sender->SSRC()); + if (rtcp_sender->SendNetworkStateEstimatePacket(*packet)) { + break; + } + } +} + void PacketRouter::AddRembModuleCandidate( RtcpFeedbackSenderInterface* candidate_module, bool media_sender) { diff --git a/modules/pacing/packet_router.h b/modules/pacing/packet_router.h index 07ef4b3a23..be535fec60 100644 --- a/modules/pacing/packet_router.h +++ b/modules/pacing/packet_router.h @@ -78,6 +78,8 @@ class PacketRouter : public RemoteBitrateObserver, // Send transport feedback packet to send-side. bool SendTransportFeedback(rtcp::TransportFeedback* packet) override; + // Send RemoteEstimate packet to send-side. + void SendNetworkStateEstimatePacket(rtcp::RemoteEstimate* packet) override; private: RtpRtcp* FindRtpModule(uint32_t ssrc) diff --git a/modules/remote_bitrate_estimator/BUILD.gn b/modules/remote_bitrate_estimator/BUILD.gn index d6fe053b3c..cec58a1811 100644 --- a/modules/remote_bitrate_estimator/BUILD.gn +++ b/modules/remote_bitrate_estimator/BUILD.gn @@ -114,6 +114,7 @@ if (rtc_include_tests) { "..:module_api_public", "../..:webrtc_common", "../../api/transport:field_trial_based_config", + "../../api/transport:mock_network_control", "../../rtc_base", "../../rtc_base:checks", "../../rtc_base:rtc_base_approved", diff --git a/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h b/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h index 1546f30670..a6bba70db1 100644 --- a/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h +++ b/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h @@ -43,6 +43,7 @@ class TransportFeedbackSenderInterface { public: virtual ~TransportFeedbackSenderInterface() = default; virtual bool SendTransportFeedback(rtcp::TransportFeedback* packet) = 0; + virtual void SendNetworkStateEstimatePacket(rtcp::RemoteEstimate* packet) = 0; }; // TODO(holmer): Remove when all implementations have been updated. diff --git a/modules/remote_bitrate_estimator/remote_estimator_proxy.cc b/modules/remote_bitrate_estimator/remote_estimator_proxy.cc index 9d9be06b15..e9b61004be 100644 --- a/modules/remote_bitrate_estimator/remote_estimator_proxy.cc +++ b/modules/remote_bitrate_estimator/remote_estimator_proxy.cc @@ -32,15 +32,19 @@ static constexpr int64_t kMaxTimeMs = RemoteEstimatorProxy::RemoteEstimatorProxy( Clock* clock, TransportFeedbackSenderInterface* feedback_sender, - const WebRtcKeyValueConfig* key_value_config) + const WebRtcKeyValueConfig* key_value_config, + NetworkStateEstimator* network_state_estimator) : clock_(clock), feedback_sender_(feedback_sender), send_config_(key_value_config), last_process_time_ms_(-1), + network_state_estimator_(network_state_estimator), media_ssrc_(0), feedback_packet_count_(0), send_interval_ms_(send_config_.default_interval->ms()), - send_periodic_feedback_(true) { + send_periodic_feedback_(true), + previous_abs_send_time_(0), + abs_send_timestamp_(clock->CurrentTime()) { RTC_LOG(LS_INFO) << "Maximum interval between transport feedback RTCP messages (ms): " << send_config_.max_interval->ms(); @@ -51,60 +55,74 @@ RemoteEstimatorProxy::~RemoteEstimatorProxy() {} void RemoteEstimatorProxy::IncomingPacket(int64_t arrival_time_ms, size_t payload_size, const RTPHeader& header) { - if (!header.extension.hasTransportSequenceNumber) { - RTC_LOG(LS_WARNING) - << "RemoteEstimatorProxy: Incoming packet " - "is missing the transport sequence number extension!"; - return; - } if (arrival_time_ms < 0 || arrival_time_ms > kMaxTimeMs) { RTC_LOG(LS_WARNING) << "Arrival time out of bounds: " << arrival_time_ms; return; } rtc::CritScope cs(&lock_); media_ssrc_ = header.ssrc; + int64_t seq = 0; - int64_t seq = unwrapper_.Unwrap(header.extension.transportSequenceNumber); + if (header.extension.hasTransportSequenceNumber) { + seq = unwrapper_.Unwrap(header.extension.transportSequenceNumber); - if (send_periodic_feedback_) { - if (periodic_window_start_seq_ && - packet_arrival_times_.lower_bound(*periodic_window_start_seq_) == - packet_arrival_times_.end()) { - // Start new feedback packet, cull old packets. - for (auto it = packet_arrival_times_.begin(); - it != packet_arrival_times_.end() && it->first < seq && - arrival_time_ms - it->second >= send_config_.back_window->ms();) { - it = packet_arrival_times_.erase(it); + if (send_periodic_feedback_) { + if (periodic_window_start_seq_ && + packet_arrival_times_.lower_bound(*periodic_window_start_seq_) == + packet_arrival_times_.end()) { + // Start new feedback packet, cull old packets. + for (auto it = packet_arrival_times_.begin(); + it != packet_arrival_times_.end() && it->first < seq && + arrival_time_ms - it->second >= send_config_.back_window->ms();) { + it = packet_arrival_times_.erase(it); + } + } + if (!periodic_window_start_seq_ || seq < *periodic_window_start_seq_) { + periodic_window_start_seq_ = seq; } } - if (!periodic_window_start_seq_ || seq < *periodic_window_start_seq_) { - periodic_window_start_seq_ = seq; + + // We are only interested in the first time a packet is received. + if (packet_arrival_times_.find(seq) != packet_arrival_times_.end()) + return; + + packet_arrival_times_[seq] = arrival_time_ms; + + // Limit the range of sequence numbers to send feedback for. + auto first_arrival_time_to_keep = packet_arrival_times_.lower_bound( + packet_arrival_times_.rbegin()->first - kMaxNumberOfPackets); + if (first_arrival_time_to_keep != packet_arrival_times_.begin()) { + packet_arrival_times_.erase(packet_arrival_times_.begin(), + first_arrival_time_to_keep); + if (send_periodic_feedback_) { + // |packet_arrival_times_| cannot be empty since we just added one + // element and the last element is not deleted. + RTC_DCHECK(!packet_arrival_times_.empty()); + periodic_window_start_seq_ = packet_arrival_times_.begin()->first; + } + } + + if (header.extension.feedback_request) { + // Send feedback packet immediately. + SendFeedbackOnRequest(seq, header.extension.feedback_request.value()); } } - // We are only interested in the first time a packet is received. - if (packet_arrival_times_.find(seq) != packet_arrival_times_.end()) - return; - - packet_arrival_times_[seq] = arrival_time_ms; - - // Limit the range of sequence numbers to send feedback for. - auto first_arrival_time_to_keep = packet_arrival_times_.lower_bound( - packet_arrival_times_.rbegin()->first - kMaxNumberOfPackets); - if (first_arrival_time_to_keep != packet_arrival_times_.begin()) { - packet_arrival_times_.erase(packet_arrival_times_.begin(), - first_arrival_time_to_keep); - if (send_periodic_feedback_) { - // |packet_arrival_times_| cannot be empty since we just added one element - // and the last element is not deleted. - RTC_DCHECK(!packet_arrival_times_.empty()); - periodic_window_start_seq_ = packet_arrival_times_.begin()->first; - } - } - - if (header.extension.feedback_request) { - // Send feedback packet immediately. - SendFeedbackOnRequest(seq, header.extension.feedback_request.value()); + if (network_state_estimator_ && header.extension.hasAbsoluteSendTime) { + PacketResult packet_result; + packet_result.receive_time = Timestamp::ms(arrival_time_ms); + // Ignore reordering of packets and assume they have approximately the same + // send time. + abs_send_timestamp_ += std::max( + header.extension.GetAbsoluteSendTimeDelta(previous_abs_send_time_), + TimeDelta::ms(0)); + previous_abs_send_time_ = header.extension.absoluteSendTime; + packet_result.sent_packet.send_time = abs_send_timestamp_; + // TODO(webrtc:10742): Take IP header and transport overhead into account. + packet_result.sent_packet.size = + DataSize::bytes(header.headerLength + payload_size); + packet_result.sent_packet.sequence_number = seq; + network_state_estimator_->OnReceivedPacket(packet_result); } } @@ -169,6 +187,16 @@ void RemoteEstimatorProxy::SendPeriodicFeedbacks() { if (!periodic_window_start_seq_) return; + if (network_state_estimator_) { + absl::optional state_estimate = + network_state_estimator_->GetCurrentEstimate(); + if (state_estimate) { + rtcp::RemoteEstimate rtcp_estimate; + rtcp_estimate.SetEstimate(state_estimate.value()); + feedback_sender_->SendNetworkStateEstimatePacket(&rtcp_estimate); + } + } + for (auto begin_iterator = packet_arrival_times_.lower_bound(*periodic_window_start_seq_); begin_iterator != packet_arrival_times_.cend(); diff --git a/modules/remote_bitrate_estimator/remote_estimator_proxy.h b/modules/remote_bitrate_estimator/remote_estimator_proxy.h index d750cc3cf7..a772b58dc8 100644 --- a/modules/remote_bitrate_estimator/remote_estimator_proxy.h +++ b/modules/remote_bitrate_estimator/remote_estimator_proxy.h @@ -14,6 +14,7 @@ #include #include +#include "api/transport/network_control.h" #include "api/transport/webrtc_key_value_config.h" #include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "rtc_base/critical_section.h" @@ -36,7 +37,8 @@ class RemoteEstimatorProxy : public RemoteBitrateEstimator { public: RemoteEstimatorProxy(Clock* clock, TransportFeedbackSenderInterface* feedback_sender, - const WebRtcKeyValueConfig* key_value_config); + const WebRtcKeyValueConfig* key_value_config, + NetworkStateEstimator* network_state_estimator); ~RemoteEstimatorProxy() override; void IncomingPacket(int64_t arrival_time_ms, @@ -90,7 +92,9 @@ class RemoteEstimatorProxy : public RemoteBitrateEstimator { int64_t last_process_time_ms_; rtc::CriticalSection lock_; - + // |network_state_estimator_| may be null. + NetworkStateEstimator* const network_state_estimator_ + RTC_PT_GUARDED_BY(&lock_); uint32_t media_ssrc_ RTC_GUARDED_BY(&lock_); uint8_t feedback_packet_count_ RTC_GUARDED_BY(&lock_); SeqNumUnwrapper unwrapper_ RTC_GUARDED_BY(&lock_); @@ -99,6 +103,10 @@ class RemoteEstimatorProxy : public RemoteBitrateEstimator { std::map packet_arrival_times_ RTC_GUARDED_BY(&lock_); int64_t send_interval_ms_ RTC_GUARDED_BY(&lock_); bool send_periodic_feedback_ RTC_GUARDED_BY(&lock_); + + // Unwraps absolute send times. + uint32_t previous_abs_send_time_ RTC_GUARDED_BY(&lock_); + Timestamp abs_send_timestamp_ RTC_GUARDED_BY(&lock_); }; } // namespace webrtc diff --git a/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc b/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc index 6962723a2a..30e6ef4102 100644 --- a/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc +++ b/modules/remote_bitrate_estimator/remote_estimator_proxy_unittest.cc @@ -11,6 +11,7 @@ #include "modules/remote_bitrate_estimator/remote_estimator_proxy.h" #include "api/transport/field_trial_based_config.h" +#include "api/transport/test/mock_network_control.h" #include "modules/pacing/packet_router.h" #include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" #include "system_wrappers/include/clock.h" @@ -61,27 +62,43 @@ class MockTransportFeedbackSender : public TransportFeedbackSenderInterface { public: MOCK_METHOD1(SendTransportFeedback, bool(rtcp::TransportFeedback* feedback_packet)); + MOCK_METHOD1(SendNetworkStateEstimatePacket, + void(rtcp::RemoteEstimate* packet)); }; class RemoteEstimatorProxyTest : public ::testing::Test { public: RemoteEstimatorProxyTest() - : clock_(0), proxy_(&clock_, &router_, &field_trial_config_) {} + : clock_(0), + proxy_(&clock_, + &router_, + &field_trial_config_, + &network_state_estimator_) {} protected: - void IncomingPacket(uint16_t seq, - int64_t time_ms, - absl::optional feedback_request) { - RTPHeader header; - header.extension.hasTransportSequenceNumber = true; - header.extension.transportSequenceNumber = seq; - header.extension.feedback_request = feedback_request; - header.ssrc = kMediaSsrc; - proxy_.IncomingPacket(time_ms, kDefaultPacketSize, header); + void IncomingPacket( + uint16_t seq, + int64_t time_ms, + absl::optional feedback_request = absl::nullopt) { + proxy_.IncomingPacket(time_ms, kDefaultPacketSize, + CreateHeader(seq, feedback_request, absl::nullopt)); } - void IncomingPacket(uint16_t seq, int64_t time_ms) { - IncomingPacket(seq, time_ms, absl::nullopt); + RTPHeader CreateHeader(absl::optional transport_sequence, + absl::optional feedback_request, + absl::optional absolute_send_time) { + RTPHeader header; + if (transport_sequence) { + header.extension.hasTransportSequenceNumber = true; + header.extension.transportSequenceNumber = transport_sequence.value(); + } + header.extension.feedback_request = feedback_request; + if (absolute_send_time) { + header.extension.hasAbsoluteSendTime = true; + header.extension.absoluteSendTime = absolute_send_time.value(); + } + header.ssrc = kMediaSsrc; + return header; } void Process() { @@ -92,6 +109,7 @@ class RemoteEstimatorProxyTest : public ::testing::Test { FieldTrialBasedConfig field_trial_config_; SimulatedClock clock_; ::testing::StrictMock router_; + ::testing::NiceMock network_state_estimator_; RemoteEstimatorProxy proxy_; }; @@ -499,5 +517,74 @@ TEST_F(RemoteEstimatorProxyOnRequestTest, kFivePacketsFeedbackRequest); } +TEST_F(RemoteEstimatorProxyTest, ReportsIncomingPacketToNetworkStateEstimator) { + Timestamp first_send_timestamp = Timestamp::ms(0); + EXPECT_CALL(network_state_estimator_, OnReceivedPacket(_)) + .WillOnce(Invoke([&first_send_timestamp](const PacketResult& packet) { + EXPECT_EQ(packet.receive_time, Timestamp::ms(kBaseTimeMs)); + first_send_timestamp = packet.sent_packet.send_time; + })); + // Incoming packet with abs sendtime but without transport sequence number. + proxy_.IncomingPacket( + kBaseTimeMs, kDefaultPacketSize, + CreateHeader(absl::nullopt, absl::nullopt, + AbsoluteSendTime::MsTo24Bits(kBaseTimeMs))); + + // Expect packet with older abs send time to be treated as sent at the same + // time as the previous packet due to reordering. + EXPECT_CALL(network_state_estimator_, OnReceivedPacket(_)) + .WillOnce(Invoke([&first_send_timestamp](const PacketResult& packet) { + EXPECT_EQ(packet.receive_time, Timestamp::ms(kBaseTimeMs)); + EXPECT_EQ(packet.sent_packet.send_time, first_send_timestamp); + })); + proxy_.IncomingPacket( + kBaseTimeMs, kDefaultPacketSize, + CreateHeader(absl::nullopt, absl::nullopt, + AbsoluteSendTime::MsTo24Bits(kBaseTimeMs - 12))); +} + +TEST_F(RemoteEstimatorProxyTest, IncomingPacketHandlesWrapInAbsSendTime) { + // abs send time use 24bit precision. + const uint32_t kFirstAbsSendTime = + AbsoluteSendTime::MsTo24Bits((1 << 24) - 30); + // Second abs send time has wrapped. + const uint32_t kSecondAbsSendTime = AbsoluteSendTime::MsTo24Bits((1 << 24)); + const TimeDelta kExpectedAbsSendTimeDelta = TimeDelta::ms(30); + + Timestamp first_send_timestamp = Timestamp::ms(0); + EXPECT_CALL(network_state_estimator_, OnReceivedPacket(_)) + .WillOnce(Invoke([&first_send_timestamp](const PacketResult& packet) { + EXPECT_EQ(packet.receive_time, Timestamp::ms(kBaseTimeMs)); + first_send_timestamp = packet.sent_packet.send_time; + })); + proxy_.IncomingPacket( + kBaseTimeMs, kDefaultPacketSize, + CreateHeader(kBaseSeq, absl::nullopt, kFirstAbsSendTime)); + + EXPECT_CALL(network_state_estimator_, OnReceivedPacket(_)) + .WillOnce(Invoke([first_send_timestamp, + kExpectedAbsSendTimeDelta](const PacketResult& packet) { + EXPECT_EQ(packet.receive_time, Timestamp::ms(kBaseTimeMs + 123)); + EXPECT_EQ(packet.sent_packet.send_time.ms(), + (first_send_timestamp + kExpectedAbsSendTimeDelta).ms()); + })); + proxy_.IncomingPacket( + kBaseTimeMs + 123, kDefaultPacketSize, + CreateHeader(kBaseSeq + 1, absl::nullopt, kSecondAbsSendTime)); +} + +TEST_F(RemoteEstimatorProxyTest, SendTransportFeedbackAndNetworkStateUpdate) { + proxy_.IncomingPacket( + kBaseTimeMs, kDefaultPacketSize, + CreateHeader(kBaseSeq, absl::nullopt, + AbsoluteSendTime::MsTo24Bits(kBaseTimeMs - 1))); + + EXPECT_CALL(router_, SendTransportFeedback(_)); + EXPECT_CALL(network_state_estimator_, GetCurrentEstimate()) + .WillOnce(Return(NetworkStateEstimate())); + EXPECT_CALL(router_, SendNetworkStateEstimatePacket(_)); + Process(); +} + } // namespace } // namespace webrtc