diff --git a/rust/src/router/direct_transport.rs b/rust/src/router/direct_transport.rs index 9ec8d3f283..654ab1953c 100644 --- a/rust/src/router/direct_transport.rs +++ b/rust/src/router/direct_transport.rs @@ -105,6 +105,10 @@ pub struct DirectTransportStat { pub available_incoming_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub max_incoming_bitrate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_received: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_sent: Option, } #[derive(Default)] diff --git a/rust/src/router/pipe_transport.rs b/rust/src/router/pipe_transport.rs index dd8f64c73b..0547d893bf 100644 --- a/rust/src/router/pipe_transport.rs +++ b/rust/src/router/pipe_transport.rs @@ -140,6 +140,10 @@ pub struct PipeTransportStat { pub available_incoming_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub max_incoming_bitrate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_received: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_sent: Option, // PipeTransport specific. pub tuple: Option, } diff --git a/rust/src/router/plain_transport.rs b/rust/src/router/plain_transport.rs index 325d12deba..a58a784f6a 100644 --- a/rust/src/router/plain_transport.rs +++ b/rust/src/router/plain_transport.rs @@ -158,6 +158,10 @@ pub struct PlainTransportStat { pub available_incoming_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub max_incoming_bitrate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_received: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_sent: Option, // PlainTransport specific. pub rtcp_mux: bool, pub comedia: bool, diff --git a/rust/src/router/webrtc_transport.rs b/rust/src/router/webrtc_transport.rs index 87203ac1bd..5baf41bd4f 100644 --- a/rust/src/router/webrtc_transport.rs +++ b/rust/src/router/webrtc_transport.rs @@ -209,6 +209,10 @@ pub struct WebRtcTransportStat { pub available_incoming_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub max_incoming_bitrate: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_received: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rtp_packet_loss_sent: Option, // WebRtcTransport specific. pub ice_role: IceRole, pub ice_state: IceState, diff --git a/rust/tests/integration/direct_transport.rs b/rust/tests/integration/direct_transport.rs index e0e465d568..85ce47654d 100644 --- a/rust/tests/integration/direct_transport.rs +++ b/rust/tests/integration/direct_transport.rs @@ -150,6 +150,8 @@ fn get_stats_succeeds() { assert_eq!(stats[0].rtx_send_bitrate, 0); assert_eq!(stats[0].probation_bytes_sent, 0); assert_eq!(stats[0].probation_send_bitrate, 0); + assert_eq!(stats[0].rtp_packet_loss_received, None); + assert_eq!(stats[0].rtp_packet_loss_sent, None); }); } diff --git a/rust/tests/integration/plain_transport.rs b/rust/tests/integration/plain_transport.rs index f064395b96..ece61fdd74 100644 --- a/rust/tests/integration/plain_transport.rs +++ b/rust/tests/integration/plain_transport.rs @@ -411,6 +411,8 @@ fn get_stats_succeeds() { assert_eq!(stats[0].rtx_send_bitrate, 0); assert_eq!(stats[0].probation_bytes_sent, 0); assert_eq!(stats[0].probation_send_bitrate, 0); + assert_eq!(stats[0].rtp_packet_loss_received, None); + assert_eq!(stats[0].rtp_packet_loss_sent, None); assert!(matches!( stats[0].tuple, Some(TransportTuple::LocalOnly { .. }), diff --git a/rust/tests/integration/webrtc_transport.rs b/rust/tests/integration/webrtc_transport.rs index 724e0d29a6..c54cb8007a 100644 --- a/rust/tests/integration/webrtc_transport.rs +++ b/rust/tests/integration/webrtc_transport.rs @@ -360,6 +360,8 @@ fn get_stats_succeeds() { assert_eq!(stats[0].probation_send_bitrate, 0); assert_eq!(stats[0].ice_selected_tuple, None); assert_eq!(stats[0].max_incoming_bitrate, None); + assert_eq!(stats[0].rtp_packet_loss_received, None); + assert_eq!(stats[0].rtp_packet_loss_sent, None); }); } diff --git a/worker/include/RTC/TransportCongestionControlClient.hpp b/worker/include/RTC/TransportCongestionControlClient.hpp index 899f50817b..4d2ddc6b19 100644 --- a/worker/include/RTC/TransportCongestionControlClient.hpp +++ b/worker/include/RTC/TransportCongestionControlClient.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace RTC { @@ -76,10 +77,12 @@ namespace RTC return this->bitrates; } uint32_t GetAvailableBitrate() const; + double GetPacketLoss() const; void RescheduleNextAvailableBitrateEvent(); private: void MayEmitAvailableBitrateEvent(uint32_t previousAvailableBitrate); + void UpdatePacketLoss(double packetLoss); // jmillan: missing. // void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override; @@ -113,6 +116,8 @@ namespace RTC bool availableBitrateEventCalled{ false }; uint64_t lastAvailableBitrateEventAtMs{ 0u }; RTC::TrendCalculator desiredBitrateTrend; + std::deque packetLossHistory; + double packetLoss{ 0 }; }; } // namespace RTC diff --git a/worker/include/RTC/TransportCongestionControlServer.hpp b/worker/include/RTC/TransportCongestionControlServer.hpp index b8c431712a..5747d13d03 100644 --- a/worker/include/RTC/TransportCongestionControlServer.hpp +++ b/worker/include/RTC/TransportCongestionControlServer.hpp @@ -8,6 +8,7 @@ #include "RTC/RtpPacket.hpp" #include "handles/Timer.hpp" #include +#include namespace RTC { @@ -50,12 +51,14 @@ namespace RTC return 0u; } } + double GetPacketLoss() const; void IncomingPacket(uint64_t nowMs, const RTC::RtpPacket* packet); void SetMaxIncomingBitrate(uint32_t bitrate); private: void SendTransportCcFeedback(); void MaySendLimitationRembFeedback(); + void UpdatePacketLoss(double packetLoss); /* Pure virtual methods inherited from webrtc::RemoteBitrateEstimator::Listener. */ public: @@ -84,6 +87,8 @@ namespace RTC uint32_t maxIncomingBitrate{ 0u }; uint64_t limitationRembSentAtMs{ 0u }; uint8_t unlimitedRembCounter{ 0u }; + std::deque packetLossHistory; + double packetLoss{ 0 }; }; } // namespace RTC diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index 0d0cb4738c..1653085eb4 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -552,6 +552,14 @@ namespace RTC // Add maxIncomingBitrate. if (this->maxIncomingBitrate != 0u) jsonObject["maxIncomingBitrate"] = this->maxIncomingBitrate; + + // Add packetLossReceived. + if (this->tccServer) + jsonObject["rtpPacketLossReceived"] = this->tccServer->GetPacketLoss(); + + // Add packetLossSent. + if (this->tccClient) + jsonObject["rtpPacketLossSent"] = this->tccClient->GetPacketLoss(); } void Transport::HandleRequest(Channel::ChannelRequest* request) diff --git a/worker/src/RTC/TransportCongestionControlClient.cpp b/worker/src/RTC/TransportCongestionControlClient.cpp index dc97340b46..1b589139bd 100644 --- a/worker/src/RTC/TransportCongestionControlClient.cpp +++ b/worker/src/RTC/TransportCongestionControlClient.cpp @@ -16,6 +16,7 @@ namespace RTC static constexpr float MaxBitrateIncrementFactor{ 1.35f }; static constexpr float MaxPaddingBitrateFactor{ 0.85f }; static constexpr uint64_t AvailableBitrateEventInterval{ 2000u }; // In ms. + static constexpr size_t PacketLossHistogramLength{ 24 }; /* Instance methods. */ @@ -163,9 +164,56 @@ namespace RTC { MS_TRACE(); + // Update packet loss history. + size_t expected_packets = feedback->GetPacketStatusCount(); + size_t lost_packets = 0; + for (const auto& result : feedback->GetPacketResults()) + { + if (!result.received) + lost_packets += 1; + } + this->UpdatePacketLoss(static_cast(lost_packets) / expected_packets); + this->rtpTransportControllerSend->OnTransportFeedback(*feedback); } + void TransportCongestionControlClient::UpdatePacketLoss(double packetLoss) + { + // Add the score into the histogram. + if (this->packetLossHistory.size() == PacketLossHistogramLength) + this->packetLossHistory.pop_front(); + + this->packetLossHistory.push_back(packetLoss); + + /* + * Scoring mechanism is a weighted average. + * + * The more recent the score is, the more weight it has. + * The oldest score has a weight of 1 and subsequent scores weight is + * increased by one sequentially. + * + * Ie: + * - scores: [1,2,3,4] + * - this->scores = ((1) + (2+2) + (3+3+3) + (4+4+4+4)) / 10 = 2.8 => 3 + */ + + size_t weight{ 0 }; + size_t samples{ 0 }; + double totalPacketLoss{ 0 }; + + for (auto packetLossEntry : this->packetLossHistory) + { + weight++; + samples += weight; + totalPacketLoss += weight * packetLossEntry; + } + + // clang-tidy "thinks" that this can lead to division by zero but we are + // smarter. + // NOLINTNEXTLINE(clang-analyzer-core.DivideZero) + this->packetLoss = totalPacketLoss / samples; + } + void TransportCongestionControlClient::SetMaxOutgoingBitrate(uint32_t maxBitrate) { if (maxBitrate < this->initialAvailableBitrate) @@ -245,6 +293,13 @@ namespace RTC return this->bitrates.availableBitrate; } + double TransportCongestionControlClient::GetPacketLoss() const + { + MS_TRACE(); + + return this->packetLoss; + } + void TransportCongestionControlClient::RescheduleNextAvailableBitrateEvent() { MS_TRACE(); diff --git a/worker/src/RTC/TransportCongestionControlServer.cpp b/worker/src/RTC/TransportCongestionControlServer.cpp index e8e1b272d9..d171d1d99d 100644 --- a/worker/src/RTC/TransportCongestionControlServer.cpp +++ b/worker/src/RTC/TransportCongestionControlServer.cpp @@ -15,6 +15,7 @@ namespace RTC static constexpr uint64_t TransportCcFeedbackSendInterval{ 100u }; // In ms. static constexpr uint64_t LimitationRembInterval{ 1500u }; // In ms. static constexpr uint8_t UnlimitedRembNumPackets{ 4u }; + static constexpr size_t PacketLossHistogramLength{ 24 }; /* Instance methods. */ @@ -101,6 +102,13 @@ namespace RTC } } + double TransportCongestionControlServer::GetPacketLoss() const + { + MS_TRACE(); + + return this->packetLoss; + } + void TransportCongestionControlServer::IncomingPacket(uint64_t nowMs, const RTC::RtpPacket* packet) { MS_TRACE(); @@ -119,7 +127,7 @@ namespace RTC this->transportCcFeedbackMediaSsrc = packet->GetSsrc(); this->transportCcFeedbackPacket->SetSenderSsrc(0u); - this->transportCcFeedbackPacket->SetMediaSsrc(packet->GetSsrc()); + this->transportCcFeedbackPacket->SetMediaSsrc(this->transportCcFeedbackMediaSsrc); // Provide the feedback packet with the RTP packet info. If it fails, // send current feedback and add the packet info to a new one. @@ -223,6 +231,17 @@ namespace RTC this->listener->OnTransportCongestionControlServerSendRtcpPacket( this, this->transportCcFeedbackPacket.get()); + // Update packet loss history. + size_t expected_packets = this->transportCcFeedbackPacket->GetPacketStatusCount(); + size_t lost_packets = 0; + for (const auto& result : this->transportCcFeedbackPacket->GetPacketResults()) + { + if (!result.received) + lost_packets += 1; + } + + this->UpdatePacketLoss(static_cast(lost_packets) / expected_packets); + // Create a new feedback packet. this->transportCcFeedbackPacket.reset(new RTC::RTCP::FeedbackRtpTransportPacket( this->transportCcFeedbackSenderSsrc, this->transportCcFeedbackMediaSsrc)); @@ -280,6 +299,32 @@ namespace RTC } } + void TransportCongestionControlServer::UpdatePacketLoss(double packetLoss) + { + // Add the score into the histogram. + if (this->packetLossHistory.size() == PacketLossHistogramLength) + this->packetLossHistory.pop_front(); + + this->packetLossHistory.push_back(packetLoss); + + // Calculate a weighted average + size_t weight{ 0 }; + size_t samples{ 0 }; + double totalPacketLoss{ 0 }; + + for (auto packetLossEntry : this->packetLossHistory) + { + weight++; + samples += weight; + totalPacketLoss += weight * packetLossEntry; + } + + // clang-tidy "thinks" that this can lead to division by zero but we are + // smarter. + // NOLINTNEXTLINE(clang-analyzer-core.DivideZero) + this->packetLoss = totalPacketLoss / samples; + } + inline void TransportCongestionControlServer::OnRembServerAvailableBitrate( const webrtc::RemoteBitrateEstimator* /*rembServer*/, const std::vector& ssrcs,