| /* | |
| * 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. | |
| */ | |
| #include "modules/rtp_rtcp/source/rtp_sequence_number_map.h" | |
| #include <algorithm> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <deque> | |
| #include <iterator> | |
| #include <limits> | |
| #include <optional> | |
| #include "absl/algorithm/container.h" | |
| #include "rtc_base/checks.h" | |
| #include "rtc_base/logging.h" | |
| #include "rtc_base/numerics/sequence_number_util.h" | |
| namespace webrtc { | |
| RtpSequenceNumberMap::RtpSequenceNumberMap(size_t max_entries) | |
| : max_entries_(max_entries) { | |
| RTC_DCHECK_GT(max_entries_, 4); // See code paring down to `max_entries_`. | |
| RTC_DCHECK_LE(max_entries_, 1 << 15); | |
| } | |
| RtpSequenceNumberMap::~RtpSequenceNumberMap() = default; | |
| void RtpSequenceNumberMap::InsertPacket(uint16_t sequence_number, Info info) { | |
| RTC_DCHECK(associations_.size() < 2 || | |
| AheadOf(associations_.back().sequence_number, | |
| associations_.front().sequence_number)); | |
| if (associations_.empty()) { | |
| associations_.emplace_back(sequence_number, info); | |
| return; | |
| } | |
| if (AheadOrAt(sequence_number, associations_.front().sequence_number) && | |
| AheadOrAt(associations_.back().sequence_number, sequence_number)) { | |
| // The sequence number has wrapped around and is within the range | |
| // currently held by `associations_` - we should invalidate all entries. | |
| RTC_LOG(LS_WARNING) << "Sequence number wrapped-around unexpectedly."; | |
| associations_.clear(); | |
| associations_.emplace_back(sequence_number, info); | |
| return; | |
| } | |
| std::deque<Association>::iterator erase_to = associations_.begin(); | |
| RTC_DCHECK_LE(associations_.size(), max_entries_); | |
| if (associations_.size() == max_entries_) { | |
| // Pare down the container so that inserting some additional elements | |
| // would not exceed the maximum size. | |
| const size_t new_size = 3 * max_entries_ / 4; | |
| erase_to = std::next(erase_to, max_entries_ - new_size); | |
| } | |
| // It is guaranteed that `associations_` can be split into two partitions, | |
| // either partition possibly empty, such that: | |
| // * In the first partition, all elements are AheadOf the new element. | |
| // This is the partition of the obsolete elements. | |
| // * In the second partition, the new element is AheadOf all the elements. | |
| // The elements of this partition may stay. | |
| auto cmp = [](const Association& a, uint16_t sequence_number) { | |
| return AheadOf(a.sequence_number, sequence_number); | |
| }; | |
| RTC_DCHECK(erase_to != associations_.end()); | |
| erase_to = | |
| std::lower_bound(erase_to, associations_.end(), sequence_number, cmp); | |
| associations_.erase(associations_.begin(), erase_to); | |
| associations_.emplace_back(sequence_number, info); | |
| RTC_DCHECK(associations_.size() == 1 || | |
| AheadOf(associations_.back().sequence_number, | |
| associations_.front().sequence_number)); | |
| } | |
| void RtpSequenceNumberMap::InsertFrame(uint16_t first_sequence_number, | |
| size_t packet_count, | |
| uint32_t timestamp) { | |
| RTC_DCHECK_GT(packet_count, 0); | |
| RTC_DCHECK_LE(packet_count, std::numeric_limits<size_t>::max()); | |
| for (size_t i = 0; i < packet_count; ++i) { | |
| const bool is_first = (i == 0); | |
| const bool is_last = (i == packet_count - 1); | |
| InsertPacket(static_cast<uint16_t>(first_sequence_number + i), | |
| Info(timestamp, is_first, is_last)); | |
| } | |
| } | |
| std::optional<RtpSequenceNumberMap::Info> RtpSequenceNumberMap::Get( | |
| uint16_t sequence_number) const { | |
| // To make the binary search easier to understand, we use the fact that | |
| // adding a constant offset to all elements, as well as to the searched | |
| // element, does not change the relative ordering. This way, we can find | |
| // an offset that would make all of the elements strictly ascending according | |
| // to normal integer comparison. | |
| // Finding such an offset is easy - the offset that would map the oldest | |
| // element to 0 would serve this purpose. | |
| if (associations_.empty()) { | |
| return std::nullopt; | |
| } | |
| const uint16_t offset = | |
| static_cast<uint16_t>(0) - associations_.front().sequence_number; | |
| auto cmp = [offset](const Association& a, uint16_t sequence_number) { | |
| return static_cast<uint16_t>(a.sequence_number + offset) < | |
| static_cast<uint16_t>(sequence_number + offset); | |
| }; | |
| const auto elem = absl::c_lower_bound(associations_, sequence_number, cmp); | |
| return elem != associations_.end() && elem->sequence_number == sequence_number | |
| ? std::optional<Info>(elem->info) | |
| : std::nullopt; | |
| } | |
| size_t RtpSequenceNumberMap::AssociationCountForTesting() const { | |
| return associations_.size(); | |
| } | |
| } // namespace webrtc |