diff options
Diffstat (limited to 'third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc')
-rw-r--r-- | third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc new file mode 100644 index 0000000000..1dc756d876 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/receive_statistics_impl.cc @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2013 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 "modules/rtp_rtcp/source/receive_statistics_impl.h" + +#include <cmath> +#include <cstdlib> +#include <memory> +#include <utility> +#include <vector> + +#include "api/units/time_delta.h" +#include "modules/remote_bitrate_estimator/test/bwe_test_logging.h" +#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_config.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace { +constexpr int64_t kStatisticsTimeoutMs = 8000; +constexpr int64_t kStatisticsProcessIntervalMs = 1000; +} // namespace + +StreamStatistician::~StreamStatistician() {} + +StreamStatisticianImpl::StreamStatisticianImpl(uint32_t ssrc, Clock* clock, + int max_reordering_threshold) + : ssrc_(ssrc), + clock_(clock), + delta_internal_unix_epoch_ms_(clock_->CurrentNtpInMilliseconds() - + clock_->TimeInMilliseconds() - + rtc::kNtpJan1970Millisecs), + incoming_bitrate_(kStatisticsProcessIntervalMs, + RateStatistics::kBpsScale), + max_reordering_threshold_(max_reordering_threshold), + enable_retransmit_detection_(false), + cumulative_loss_is_capped_(false), + jitter_q4_(0), + cumulative_loss_(0), + cumulative_loss_rtcp_offset_(0), + last_receive_time_ms_(0), + last_received_timestamp_(0), + received_seq_first_(-1), + received_seq_max_(-1), + last_report_cumulative_loss_(0), + last_report_seq_max_(-1), + last_payload_type_frequency_(0) {} + +StreamStatisticianImpl::~StreamStatisticianImpl() = default; + +bool StreamStatisticianImpl::UpdateOutOfOrder(const RtpPacketReceived& packet, + int64_t sequence_number, + int64_t now_ms) { + // Check if `packet` is second packet of a stream restart. + if (received_seq_out_of_order_) { + // Count the previous packet as a received; it was postponed below. + --cumulative_loss_; + + uint16_t expected_sequence_number = *received_seq_out_of_order_ + 1; + received_seq_out_of_order_ = absl::nullopt; + if (packet.SequenceNumber() == expected_sequence_number) { + // Ignore sequence number gap caused by stream restart for packet loss + // calculation, by setting received_seq_max_ to the sequence number just + // before the out-of-order seqno. This gives a net zero change of + // `cumulative_loss_`, for the two packets interpreted as a stream reset. + // + // Fraction loss for the next report may get a bit off, since we don't + // update last_report_seq_max_ and last_report_cumulative_loss_ in a + // consistent way. + last_report_seq_max_ = sequence_number - 2; + received_seq_max_ = sequence_number - 2; + return false; + } + } + + if (std::abs(sequence_number - received_seq_max_) > + max_reordering_threshold_) { + // Sequence number gap looks too large, wait until next packet to check + // for a stream restart. + received_seq_out_of_order_ = packet.SequenceNumber(); + // Postpone counting this as a received packet until we know how to update + // `received_seq_max_`, otherwise we temporarily decrement + // `cumulative_loss_`. The + // ReceiveStatisticsTest.StreamRestartDoesntCountAsLoss test expects + // `cumulative_loss_` to be unchanged by the reception of the first packet + // after stream reset. + ++cumulative_loss_; + return true; + } + + if (sequence_number > received_seq_max_) + return false; + + // Old out of order packet, may be retransmit. + if (enable_retransmit_detection_ && IsRetransmitOfOldPacket(packet, now_ms)) + receive_counters_.retransmitted.AddPacket(packet); + return true; +} + +void StreamStatisticianImpl::UpdateCounters(const RtpPacketReceived& packet) { + RTC_DCHECK_EQ(ssrc_, packet.Ssrc()); + int64_t now_ms = clock_->TimeInMilliseconds(); + + incoming_bitrate_.Update(packet.size(), now_ms); + receive_counters_.last_packet_received_timestamp_ms = now_ms; + receive_counters_.transmitted.AddPacket(packet); + --cumulative_loss_; + + // Use PeekUnwrap and later update the state to avoid updating the state for + // out of order packets. + int64_t sequence_number = seq_unwrapper_.PeekUnwrap(packet.SequenceNumber()); + + if (!ReceivedRtpPacket()) { + received_seq_first_ = sequence_number; + last_report_seq_max_ = sequence_number - 1; + received_seq_max_ = sequence_number - 1; + receive_counters_.first_packet_time_ms = now_ms; + } else if (UpdateOutOfOrder(packet, sequence_number, now_ms)) { + return; + } + // In order packet. + cumulative_loss_ += sequence_number - received_seq_max_; + received_seq_max_ = sequence_number; + // Update the internal state of `seq_unwrapper_`. + seq_unwrapper_.Unwrap(packet.SequenceNumber()); + + // If new time stamp and more than one in-order packet received, calculate + // new jitter statistics. + if (packet.Timestamp() != last_received_timestamp_ && + (receive_counters_.transmitted.packets - + receive_counters_.retransmitted.packets) > 1) { + UpdateJitter(packet, now_ms); + } + last_received_timestamp_ = packet.Timestamp(); + last_receive_time_ms_ = now_ms; +} + +void StreamStatisticianImpl::UpdateJitter(const RtpPacketReceived& packet, + int64_t receive_time_ms) { + int64_t receive_diff_ms = receive_time_ms - last_receive_time_ms_; + RTC_DCHECK_GE(receive_diff_ms, 0); + uint32_t receive_diff_rtp = static_cast<uint32_t>( + (receive_diff_ms * packet.payload_type_frequency()) / 1000); + int32_t time_diff_samples = + receive_diff_rtp - (packet.Timestamp() - last_received_timestamp_); + + time_diff_samples = std::abs(time_diff_samples); + + ReviseFrequencyAndJitter(packet.payload_type_frequency()); + + // lib_jingle sometimes deliver crazy jumps in TS for the same stream. + // If this happens, don't update jitter value. Use 5 secs video frequency + // as the threshold. + if (time_diff_samples < 450000) { + // Note we calculate in Q4 to avoid using float. + int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_; + jitter_q4_ += ((jitter_diff_q4 + 8) >> 4); + } +} + +void StreamStatisticianImpl::ReviseFrequencyAndJitter( + int payload_type_frequency) { + if (payload_type_frequency == last_payload_type_frequency_) { + return; + } + + if (payload_type_frequency != 0) { + if (last_payload_type_frequency_ != 0) { + // Value in "jitter_q4_" variable is a number of samples. + // I.e. jitter = timestamp (ms) * frequency (kHz). + // Since the frequency has changed we have to update the number of samples + // accordingly. The new value should rely on a new frequency. + + // If we don't do such procedure we end up with the number of samples that + // cannot be converted into milliseconds correctly + // (i.e. jitter_ms = jitter_q4_ >> 4 / (payload_type_frequency / 1000)). + // In such case, the number of samples has a "mix". + + // Doing so we pretend that everything prior and including the current + // packet were computed on packet's frequency. + jitter_q4_ = static_cast<int>(static_cast<uint64_t>(jitter_q4_) * + payload_type_frequency / + last_payload_type_frequency_); + } + // If last_payload_type_frequency_ is not present, the jitter_q4_ + // variable has its initial value. + + // Keep last_payload_type_frequency_ up to date and non-zero (set). + last_payload_type_frequency_ = payload_type_frequency; + } +} + +void StreamStatisticianImpl::SetMaxReorderingThreshold( + int max_reordering_threshold) { + max_reordering_threshold_ = max_reordering_threshold; +} + +void StreamStatisticianImpl::EnableRetransmitDetection(bool enable) { + enable_retransmit_detection_ = enable; +} + +RtpReceiveStats StreamStatisticianImpl::GetStats() const { + RtpReceiveStats stats; + stats.packets_lost = cumulative_loss_; + // Note: internal jitter value is in Q4 and needs to be scaled by 1/16. + stats.jitter = jitter_q4_ >> 4; + if (last_payload_type_frequency_ > 0) { + // Divide value in fractional seconds by frequency to get jitter in + // fractional seconds. + stats.interarrival_jitter = + webrtc::TimeDelta::Seconds(stats.jitter) / last_payload_type_frequency_; + } + if (receive_counters_.last_packet_received_timestamp_ms.has_value()) { + stats.last_packet_received_timestamp_ms = + *receive_counters_.last_packet_received_timestamp_ms + + delta_internal_unix_epoch_ms_; + } + stats.packet_counter = receive_counters_.transmitted; + return stats; +} + +void StreamStatisticianImpl::MaybeAppendReportBlockAndReset( + std::vector<rtcp::ReportBlock>& report_blocks) { + int64_t now_ms = clock_->TimeInMilliseconds(); + if (now_ms - last_receive_time_ms_ >= kStatisticsTimeoutMs) { + // Not active. + return; + } + if (!ReceivedRtpPacket()) { + return; + } + + report_blocks.emplace_back(); + rtcp::ReportBlock& stats = report_blocks.back(); + stats.SetMediaSsrc(ssrc_); + // Calculate fraction lost. + int64_t exp_since_last = received_seq_max_ - last_report_seq_max_; + RTC_DCHECK_GE(exp_since_last, 0); + + int32_t lost_since_last = cumulative_loss_ - last_report_cumulative_loss_; + if (exp_since_last > 0 && lost_since_last > 0) { + // Scale 0 to 255, where 255 is 100% loss. + stats.SetFractionLost(255 * lost_since_last / exp_since_last); + } + + int packets_lost = cumulative_loss_ + cumulative_loss_rtcp_offset_; + if (packets_lost < 0) { + // Clamp to zero. Work around to accomodate for senders that misbehave with + // negative cumulative loss. + packets_lost = 0; + cumulative_loss_rtcp_offset_ = -cumulative_loss_; + } + if (packets_lost > 0x7fffff) { + // Packets lost is a 24 bit signed field, and thus should be clamped, as + // described in https://datatracker.ietf.org/doc/html/rfc3550#appendix-A.3 + if (!cumulative_loss_is_capped_) { + cumulative_loss_is_capped_ = true; + RTC_LOG(LS_WARNING) << "Cumulative loss reached maximum value for ssrc " + << ssrc_; + } + packets_lost = 0x7fffff; + } + stats.SetCumulativeLost(packets_lost); + stats.SetExtHighestSeqNum(received_seq_max_); + // Note: internal jitter value is in Q4 and needs to be scaled by 1/16. + stats.SetJitter(jitter_q4_ >> 4); + + // Only for report blocks in RTCP SR and RR. + last_report_cumulative_loss_ = cumulative_loss_; + last_report_seq_max_ = received_seq_max_; + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "cumulative_loss_pkts", now_ms, + cumulative_loss_, ssrc_); + BWE_TEST_LOGGING_PLOT_WITH_SSRC(1, "received_seq_max_pkts", now_ms, + (received_seq_max_ - received_seq_first_), + ssrc_); +} + +absl::optional<int> StreamStatisticianImpl::GetFractionLostInPercent() const { + if (!ReceivedRtpPacket()) { + return absl::nullopt; + } + int64_t expected_packets = 1 + received_seq_max_ - received_seq_first_; + if (expected_packets <= 0) { + return absl::nullopt; + } + if (cumulative_loss_ <= 0) { + return 0; + } + return 100 * static_cast<int64_t>(cumulative_loss_) / expected_packets; +} + +StreamDataCounters StreamStatisticianImpl::GetReceiveStreamDataCounters() + const { + return receive_counters_; +} + +uint32_t StreamStatisticianImpl::BitrateReceived() const { + return incoming_bitrate_.Rate(clock_->TimeInMilliseconds()).value_or(0); +} + +bool StreamStatisticianImpl::IsRetransmitOfOldPacket( + const RtpPacketReceived& packet, + int64_t now_ms) const { + uint32_t frequency_khz = packet.payload_type_frequency() / 1000; + RTC_DCHECK_GT(frequency_khz, 0); + + int64_t time_diff_ms = now_ms - last_receive_time_ms_; + + // Diff in time stamp since last received in order. + uint32_t timestamp_diff = packet.Timestamp() - last_received_timestamp_; + uint32_t rtp_time_stamp_diff_ms = timestamp_diff / frequency_khz; + + int64_t max_delay_ms = 0; + + // Jitter standard deviation in samples. + float jitter_std = std::sqrt(static_cast<float>(jitter_q4_ >> 4)); + + // 2 times the standard deviation => 95% confidence. + // And transform to milliseconds by dividing by the frequency in kHz. + max_delay_ms = static_cast<int64_t>((2 * jitter_std) / frequency_khz); + + // Min max_delay_ms is 1. + if (max_delay_ms == 0) { + max_delay_ms = 1; + } + return time_diff_ms > rtp_time_stamp_diff_ms + max_delay_ms; +} + +std::unique_ptr<ReceiveStatistics> ReceiveStatistics::Create(Clock* clock) { + return std::make_unique<ReceiveStatisticsLocked>( + clock, [](uint32_t ssrc, Clock* clock, int max_reordering_threshold) { + return std::make_unique<StreamStatisticianLocked>( + ssrc, clock, max_reordering_threshold); + }); +} + +std::unique_ptr<ReceiveStatistics> ReceiveStatistics::CreateThreadCompatible( + Clock* clock) { + return std::make_unique<ReceiveStatisticsImpl>( + clock, [](uint32_t ssrc, Clock* clock, int max_reordering_threshold) { + return std::make_unique<StreamStatisticianImpl>( + ssrc, clock, max_reordering_threshold); + }); +} + +ReceiveStatisticsImpl::ReceiveStatisticsImpl( + Clock* clock, + std::function<std::unique_ptr<StreamStatisticianImplInterface>( + uint32_t ssrc, + Clock* clock, + int max_reordering_threshold)> stream_statistician_factory) + : clock_(clock), + stream_statistician_factory_(std::move(stream_statistician_factory)), + last_returned_ssrc_idx_(0), + max_reordering_threshold_(kDefaultMaxReorderingThreshold) {} + +void ReceiveStatisticsImpl::OnRtpPacket(const RtpPacketReceived& packet) { + // StreamStatisticianImpl instance is created once and only destroyed when + // this whole ReceiveStatisticsImpl is destroyed. StreamStatisticianImpl has + // it's own locking so don't hold receive_statistics_lock_ (potential + // deadlock). + GetOrCreateStatistician(packet.Ssrc())->UpdateCounters(packet); +} + +StreamStatistician* ReceiveStatisticsImpl::GetStatistician( + uint32_t ssrc) const { + const auto& it = statisticians_.find(ssrc); + if (it == statisticians_.end()) + return nullptr; + return it->second.get(); +} + +StreamStatisticianImplInterface* ReceiveStatisticsImpl::GetOrCreateStatistician( + uint32_t ssrc) { + std::unique_ptr<StreamStatisticianImplInterface>& impl = statisticians_[ssrc]; + if (impl == nullptr) { // new element + impl = + stream_statistician_factory_(ssrc, clock_, max_reordering_threshold_); + all_ssrcs_.push_back(ssrc); + } + return impl.get(); +} + +void ReceiveStatisticsImpl::SetMaxReorderingThreshold( + int max_reordering_threshold) { + max_reordering_threshold_ = max_reordering_threshold; + for (auto& statistician : statisticians_) { + statistician.second->SetMaxReorderingThreshold(max_reordering_threshold); + } +} + +void ReceiveStatisticsImpl::SetMaxReorderingThreshold( + uint32_t ssrc, + int max_reordering_threshold) { + GetOrCreateStatistician(ssrc)->SetMaxReorderingThreshold( + max_reordering_threshold); +} + +void ReceiveStatisticsImpl::EnableRetransmitDetection(uint32_t ssrc, + bool enable) { + GetOrCreateStatistician(ssrc)->EnableRetransmitDetection(enable); +} + +std::vector<rtcp::ReportBlock> ReceiveStatisticsImpl::RtcpReportBlocks( + size_t max_blocks) { + std::vector<rtcp::ReportBlock> result; + result.reserve(std::min(max_blocks, all_ssrcs_.size())); + + size_t ssrc_idx = 0; + for (size_t i = 0; i < all_ssrcs_.size() && result.size() < max_blocks; ++i) { + ssrc_idx = (last_returned_ssrc_idx_ + i + 1) % all_ssrcs_.size(); + const uint32_t media_ssrc = all_ssrcs_[ssrc_idx]; + auto statistician_it = statisticians_.find(media_ssrc); + RTC_DCHECK(statistician_it != statisticians_.end()); + statistician_it->second->MaybeAppendReportBlockAndReset(result); + } + last_returned_ssrc_idx_ = ssrc_idx; + return result; +} + +} // namespace webrtc |