| /* | 
 |  *  Copyright (c) 2017 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 "audio/transport_feedback_packet_loss_tracker.h" | 
 |  | 
 | #include <iterator> | 
 | #include <limits> | 
 | #include <utility> | 
 |  | 
 | #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/numerics/mod_ops.h" | 
 |  | 
 | namespace { | 
 | constexpr uint16_t kSeqNumHalf = 0x8000u; | 
 | void UpdateCounter(size_t* counter, bool increment) { | 
 |   if (increment) { | 
 |     RTC_DCHECK_LT(*counter, std::numeric_limits<std::size_t>::max()); | 
 |     ++(*counter); | 
 |   } else { | 
 |     RTC_DCHECK_GT(*counter, 0); | 
 |     --(*counter); | 
 |   } | 
 | } | 
 | }  // namespace | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | TransportFeedbackPacketLossTracker::TransportFeedbackPacketLossTracker( | 
 |     int64_t max_window_size_ms, | 
 |     size_t plr_min_num_acked_packets, | 
 |     size_t rplr_min_num_acked_pairs) | 
 |     : max_window_size_ms_(max_window_size_ms), | 
 |       ref_packet_status_(packet_status_window_.begin()), | 
 |       plr_state_(plr_min_num_acked_packets), | 
 |       rplr_state_(rplr_min_num_acked_pairs) { | 
 |   RTC_DCHECK_GT(max_window_size_ms, 0); | 
 |   RTC_DCHECK_GT(plr_min_num_acked_packets, 0); | 
 |   RTC_DCHECK_GT(rplr_min_num_acked_pairs, 0); | 
 |   Reset(); | 
 | } | 
 |  | 
 | TransportFeedbackPacketLossTracker::~TransportFeedbackPacketLossTracker() = | 
 |     default; | 
 |  | 
 | void TransportFeedbackPacketLossTracker::Reset() { | 
 |   acked_packets_ = 0; | 
 |   plr_state_.Reset(); | 
 |   rplr_state_.Reset(); | 
 |   packet_status_window_.clear(); | 
 |   ref_packet_status_ = packet_status_window_.begin(); | 
 | } | 
 |  | 
 | uint16_t TransportFeedbackPacketLossTracker::ReferenceSequenceNumber() const { | 
 |   RTC_DCHECK(!packet_status_window_.empty()); | 
 |   return ref_packet_status_->first; | 
 | } | 
 |  | 
 | uint16_t TransportFeedbackPacketLossTracker::NewestSequenceNumber() const { | 
 |   RTC_DCHECK(!packet_status_window_.empty()); | 
 |   return PreviousPacketStatus(packet_status_window_.end())->first; | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::OnPacketAdded(uint16_t seq_num, | 
 |                                                        int64_t send_time_ms) { | 
 |   // Sanity - time can't flow backwards. | 
 |   RTC_DCHECK( | 
 |       packet_status_window_.empty() || | 
 |       PreviousPacketStatus(packet_status_window_.end())->second.send_time_ms <= | 
 |           send_time_ms); | 
 |  | 
 |   if (packet_status_window_.find(seq_num) != packet_status_window_.end() || | 
 |       (!packet_status_window_.empty() && | 
 |        ForwardDiff(seq_num, NewestSequenceNumber()) <= kSeqNumHalf)) { | 
 |     // The only way for these two to happen is when the stream lies dormant for | 
 |     // long enough for the sequence numbers to wrap. Everything in the window in | 
 |     // such a case would be too old to use. | 
 |     Reset(); | 
 |   } | 
 |  | 
 |   // Maintain a window where the newest sequence number is at most 0x7fff away | 
 |   // from the oldest, so that would could still distinguish old/new. | 
 |   while (!packet_status_window_.empty() && | 
 |          ForwardDiff(ref_packet_status_->first, seq_num) >= kSeqNumHalf) { | 
 |     RemoveOldestPacketStatus(); | 
 |   } | 
 |  | 
 |   SentPacket sent_packet(send_time_ms, PacketStatus::Unacked); | 
 |   packet_status_window_.insert(packet_status_window_.end(), | 
 |                                std::make_pair(seq_num, sent_packet)); | 
 |  | 
 |   if (packet_status_window_.size() == 1) { | 
 |     ref_packet_status_ = packet_status_window_.cbegin(); | 
 |   } | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::OnPacketFeedbackVector( | 
 |     const std::vector<PacketFeedback>& packet_feedback_vector) { | 
 |   for (const PacketFeedback& packet : packet_feedback_vector) { | 
 |     const auto& it = packet_status_window_.find(packet.sequence_number); | 
 |  | 
 |     // Packets which aren't at least marked as unacked either do not belong to | 
 |     // this media stream, or have been shifted out of window. | 
 |     if (it == packet_status_window_.end()) | 
 |       continue; | 
 |  | 
 |     const bool lost = packet.arrival_time_ms == PacketFeedback::kNotReceived; | 
 |     const PacketStatus packet_status = | 
 |         lost ? PacketStatus::Lost : PacketStatus::Received; | 
 |  | 
 |     UpdatePacketStatus(it, packet_status); | 
 |   } | 
 | } | 
 |  | 
 | absl::optional<float> TransportFeedbackPacketLossTracker::GetPacketLossRate() | 
 |     const { | 
 |   return plr_state_.GetMetric(); | 
 | } | 
 |  | 
 | absl::optional<float> | 
 | TransportFeedbackPacketLossTracker::GetRecoverablePacketLossRate() const { | 
 |   return rplr_state_.GetMetric(); | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::UpdatePacketStatus( | 
 |     SentPacketStatusMap::iterator it, | 
 |     PacketStatus new_status) { | 
 |   if (it->second.status != PacketStatus::Unacked) { | 
 |     // Normally, packets are sent (inserted into window as "unacked"), then we | 
 |     // receive one feedback for them. | 
 |     // But it is possible that a packet would receive two feedbacks. Then: | 
 |     if (it->second.status == PacketStatus::Lost && | 
 |         new_status == PacketStatus::Received) { | 
 |       // If older status said that the packet was lost but newer one says it | 
 |       // is received, we take the newer one. | 
 |       UpdateMetrics(it, false); | 
 |       it->second.status = | 
 |           PacketStatus::Unacked;  // For clarity; overwritten shortly. | 
 |     } else { | 
 |       // If the value is unchanged or if older status said that the packet was | 
 |       // received but the newer one says it is lost, we ignore it. | 
 |       // The standard allows for previously-reported packets to carry | 
 |       // no report when the reports overlap, which also looks like the | 
 |       // packet is being reported as lost. | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   // Change from UNACKED to RECEIVED/LOST. | 
 |   it->second.status = new_status; | 
 |   UpdateMetrics(it, true); | 
 |  | 
 |   // Remove packets from the beginning of the window until we only hold packets, | 
 |   // be they acked or unacked, which are not more than |max_window_size_ms| | 
 |   // older from the newest packet. (If the packet we're now inserting into the | 
 |   // window isn't the newest, it would not trigger any removals; the newest | 
 |   // already removed all relevant.) | 
 |   while (ref_packet_status_ != packet_status_window_.end() && | 
 |          (it->second.send_time_ms - ref_packet_status_->second.send_time_ms) > | 
 |              max_window_size_ms_) { | 
 |     RemoveOldestPacketStatus(); | 
 |   } | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::RemoveOldestPacketStatus() { | 
 |   UpdateMetrics(ref_packet_status_, false); | 
 |   const auto it = ref_packet_status_; | 
 |   ref_packet_status_ = NextPacketStatus(it); | 
 |   packet_status_window_.erase(it); | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::UpdateMetrics( | 
 |     ConstPacketStatusIterator it, | 
 |     bool apply /* false = undo */) { | 
 |   RTC_DCHECK(it != packet_status_window_.end()); | 
 |   // Metrics are dependent on feedbacks from the other side. We don't want | 
 |   // to update the metrics each time a packet is sent, except for the case | 
 |   // when it shifts old sent-but-unacked-packets out of window. | 
 |   RTC_DCHECK(!apply || it->second.status != PacketStatus::Unacked); | 
 |  | 
 |   if (it->second.status != PacketStatus::Unacked) { | 
 |     UpdateCounter(&acked_packets_, apply); | 
 |   } | 
 |  | 
 |   UpdatePlr(it, apply); | 
 |   UpdateRplr(it, apply); | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::UpdatePlr( | 
 |     ConstPacketStatusIterator it, | 
 |     bool apply /* false = undo */) { | 
 |   switch (it->second.status) { | 
 |     case PacketStatus::Unacked: | 
 |       return; | 
 |     case PacketStatus::Received: | 
 |       UpdateCounter(&plr_state_.num_received_packets_, apply); | 
 |       break; | 
 |     case PacketStatus::Lost: | 
 |       UpdateCounter(&plr_state_.num_lost_packets_, apply); | 
 |       break; | 
 |     default: | 
 |       RTC_NOTREACHED(); | 
 |   } | 
 | } | 
 |  | 
 | void TransportFeedbackPacketLossTracker::UpdateRplr( | 
 |     ConstPacketStatusIterator it, | 
 |     bool apply /* false = undo */) { | 
 |   if (it->second.status == PacketStatus::Unacked) { | 
 |     // Unacked packets cannot compose a pair. | 
 |     return; | 
 |   } | 
 |  | 
 |   // Previous packet and current packet might compose a pair. | 
 |   if (it != ref_packet_status_) { | 
 |     const auto& prev = PreviousPacketStatus(it); | 
 |     if (prev->second.status != PacketStatus::Unacked) { | 
 |       UpdateCounter(&rplr_state_.num_acked_pairs_, apply); | 
 |       if (prev->second.status == PacketStatus::Lost && | 
 |           it->second.status == PacketStatus::Received) { | 
 |         UpdateCounter( | 
 |             &rplr_state_.num_recoverable_losses_, apply); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Current packet and next packet might compose a pair. | 
 |   const auto& next = NextPacketStatus(it); | 
 |   if (next != packet_status_window_.end() && | 
 |       next->second.status != PacketStatus::Unacked) { | 
 |     UpdateCounter(&rplr_state_.num_acked_pairs_, apply); | 
 |     if (it->second.status == PacketStatus::Lost && | 
 |         next->second.status == PacketStatus::Received) { | 
 |       UpdateCounter(&rplr_state_.num_recoverable_losses_, apply); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | TransportFeedbackPacketLossTracker::ConstPacketStatusIterator | 
 | TransportFeedbackPacketLossTracker::PreviousPacketStatus( | 
 |     ConstPacketStatusIterator it) const { | 
 |   RTC_DCHECK(it != ref_packet_status_); | 
 |   if (it == packet_status_window_.end()) { | 
 |     // This is to make PreviousPacketStatus(packet_status_window_.end()) point | 
 |     // to the last element. | 
 |     it = ref_packet_status_; | 
 |   } | 
 |  | 
 |   if (it == packet_status_window_.begin()) { | 
 |     // Due to the circular nature of sequence numbers, we let the iterator | 
 |     // go to the end. | 
 |     it = packet_status_window_.end(); | 
 |   } | 
 |   return --it; | 
 | } | 
 |  | 
 | TransportFeedbackPacketLossTracker::ConstPacketStatusIterator | 
 | TransportFeedbackPacketLossTracker::NextPacketStatus( | 
 |     ConstPacketStatusIterator it) const { | 
 |   RTC_DCHECK(it != packet_status_window_.end()); | 
 |   ++it; | 
 |   if (it == packet_status_window_.end()) { | 
 |     // Due to the circular nature of sequence numbers, we let the iterator | 
 |     // goes back to the beginning. | 
 |     it = packet_status_window_.begin(); | 
 |   } | 
 |   if (it == ref_packet_status_) { | 
 |     // This is to make the NextPacketStatus of the last element to return the | 
 |     // beyond-the-end iterator. | 
 |     it = packet_status_window_.end(); | 
 |   } | 
 |   return it; | 
 | } | 
 |  | 
 | // TODO(minyue): This method checks the states of this class do not misbehave. | 
 | // The method is used both in unit tests and a fuzzer test. The fuzzer test | 
 | // is present to help finding potential errors. Once the fuzzer test shows no | 
 | // error after long period, we can remove the fuzzer test, and move this method | 
 | // to unit test. | 
 | void TransportFeedbackPacketLossTracker::Validate() const {  // Testing only! | 
 |   RTC_CHECK_EQ(plr_state_.num_received_packets_ + plr_state_.num_lost_packets_, | 
 |                acked_packets_); | 
 |   RTC_CHECK_LE(acked_packets_, packet_status_window_.size()); | 
 |   RTC_CHECK_LE(rplr_state_.num_recoverable_losses_, | 
 |                rplr_state_.num_acked_pairs_); | 
 |   RTC_CHECK_LE(rplr_state_.num_acked_pairs_, acked_packets_ - 1); | 
 |  | 
 |   size_t unacked_packets = 0; | 
 |   size_t received_packets = 0; | 
 |   size_t lost_packets = 0; | 
 |   size_t acked_pairs = 0; | 
 |   size_t recoverable_losses = 0; | 
 |  | 
 |   if (!packet_status_window_.empty()) { | 
 |     ConstPacketStatusIterator it = ref_packet_status_; | 
 |     do { | 
 |       switch (it->second.status) { | 
 |         case PacketStatus::Unacked: | 
 |           ++unacked_packets; | 
 |           break; | 
 |         case PacketStatus::Received: | 
 |           ++received_packets; | 
 |           break; | 
 |         case PacketStatus::Lost: | 
 |           ++lost_packets; | 
 |           break; | 
 |         default: | 
 |           RTC_NOTREACHED(); | 
 |       } | 
 |  | 
 |       auto next = std::next(it); | 
 |       if (next == packet_status_window_.end()) | 
 |         next = packet_status_window_.begin(); | 
 |  | 
 |       if (next != ref_packet_status_) {  // If we have a next packet... | 
 |         RTC_CHECK_GE(next->second.send_time_ms, it->second.send_time_ms); | 
 |  | 
 |         if (it->second.status != PacketStatus::Unacked && | 
 |             next->second.status != PacketStatus::Unacked) { | 
 |           ++acked_pairs; | 
 |           if (it->second.status == PacketStatus::Lost && | 
 |               next->second.status == PacketStatus::Received) { | 
 |             ++recoverable_losses; | 
 |           } | 
 |         } | 
 |       } | 
 |  | 
 |       RTC_CHECK_LT(ForwardDiff(ReferenceSequenceNumber(), it->first), | 
 |                    kSeqNumHalf); | 
 |  | 
 |       it = next; | 
 |     } while (it != ref_packet_status_); | 
 |   } | 
 |  | 
 |   RTC_CHECK_EQ(plr_state_.num_received_packets_, received_packets); | 
 |   RTC_CHECK_EQ(plr_state_.num_lost_packets_, lost_packets); | 
 |   RTC_CHECK_EQ(packet_status_window_.size(), | 
 |                unacked_packets + received_packets + lost_packets); | 
 |   RTC_CHECK_EQ(rplr_state_.num_acked_pairs_, acked_pairs); | 
 |   RTC_CHECK_EQ(rplr_state_.num_recoverable_losses_, recoverable_losses); | 
 | } | 
 |  | 
 | absl::optional<float> TransportFeedbackPacketLossTracker::PlrState::GetMetric() | 
 |     const { | 
 |   const size_t total = num_lost_packets_ + num_received_packets_; | 
 |   if (total < min_num_acked_packets_) { | 
 |     return absl::nullopt; | 
 |   } else { | 
 |     return static_cast<float>(num_lost_packets_) / total; | 
 |   } | 
 | } | 
 |  | 
 | absl::optional<float> TransportFeedbackPacketLossTracker::RplrState::GetMetric() | 
 |     const { | 
 |   if (num_acked_pairs_ < min_num_acked_pairs_) { | 
 |     return absl::nullopt; | 
 |   } else { | 
 |     return static_cast<float>(num_recoverable_losses_) / num_acked_pairs_; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace webrtc |