Cleanup audio nack logic. Removing all code that is not used in fixed delay mode, which is now the default behavior. Bug: b/462023185 Change-Id: I8fb43f79166dd39b8d3a079bb0cae658f0f6c6b5 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/463781 Commit-Queue: Jakob Ivarsson‎ <jakobi@webrtc.org> Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47441}
diff --git a/audio/BUILD.gn b/audio/BUILD.gn index c710be0..b43ea28 100644 --- a/audio/BUILD.gn +++ b/audio/BUILD.gn
@@ -71,6 +71,7 @@ "../api/transport/rtp:rtp_source", "../api/units:data_rate", "../api/units:data_size", + "../api/units:frequency", "../api/units:time_delta", "../api/units:timestamp", "../call:audio_sender_interface",
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc index a6866fa..ed38828 100644 --- a/audio/channel_receive.cc +++ b/audio/channel_receive.cc
@@ -502,10 +502,6 @@ SafeTask(worker_safety_.flag(), [this, infos_copy, delivery_time]() { RTC_DCHECK_RUN_ON(&worker_thread_checker_); source_tracker_.OnFrameDelivered(infos_copy, delivery_time); - if (nack_tracker_) { - nack_tracker_->UpdateLastDecodedPacket( - infos_copy.back().rtp_timestamp()); - } })); } @@ -910,8 +906,7 @@ if (enable) { rtp_receive_statistics_->SetMaxReorderingThreshold(remote_ssrc_, max_packets); - nack_tracker_ = std::make_unique<NackTracker>(env_.field_trials()); - nack_tracker_->SetMaxNackListSize(max_packets); + nack_tracker_ = std::make_unique<NackTracker>(max_packets); } else { rtp_receive_statistics_->SetMaxReorderingThreshold( remote_ssrc_, kDefaultMaxReorderingThreshold);
diff --git a/audio/nack_tracker.cc b/audio/nack_tracker.cc index 9878cd0..8de3256 100644 --- a/audio/nack_tracker.cc +++ b/audio/nack_tracker.cc
@@ -13,91 +13,52 @@ #include <cstddef> #include <cstdint> #include <optional> -#include <utility> #include <vector> -#include "api/field_trials_view.h" +#include "api/units/frequency.h" #include "api/units/time_delta.h" #include "modules/include/module_common_types_public.h" #include "rtc_base/checks.h" -#include "rtc_base/experiments/struct_parameters_parser.h" -#include "rtc_base/logging.h" namespace webrtc { namespace { -const int kDefaultSampleRateKhz = 48; -const int kMaxPacketSizeMs = 120; -constexpr char kNackTrackerConfigFieldTrial[] = - "WebRTC-Audio-NetEqNackTrackerConfig"; +constexpr Frequency kDefaultSampleRate = Frequency::Hertz(48000); +constexpr TimeDelta kMaxPacketDuration = TimeDelta::Millis(120); +constexpr TimeDelta kDefaultRtt = TimeDelta::Millis(100); +constexpr TimeDelta kMaxNackDelay = TimeDelta::Seconds(1); } // namespace -NackTracker::Config::Config(const FieldTrialsView& field_trials) { - auto parser = StructParametersParser::Create( - "packet_loss_forget_factor", &packet_loss_forget_factor, - "ms_per_loss_percent", &ms_per_loss_percent, "never_nack_multiple_times", - &never_nack_multiple_times, "require_valid_rtt", &require_valid_rtt, - "max_loss_rate", &max_loss_rate, "fixed_delay", &fixed_delay); - parser->Parse(field_trials.Lookup(kNackTrackerConfigFieldTrial)); - RTC_LOG(LS_INFO) << "Nack tracker config:" - " packet_loss_forget_factor=" - << packet_loss_forget_factor - << " ms_per_loss_percent=" << ms_per_loss_percent - << " never_nack_multiple_times=" << never_nack_multiple_times - << " require_valid_rtt=" << require_valid_rtt - << " max_loss_rate=" << max_loss_rate << " fixed_delay(ms)=" - << fixed_delay.value_or(TimeDelta::Zero()).ms(); -} - -NackTracker::NackTracker(const FieldTrialsView& field_trials) - : config_(field_trials), - sequence_num_last_received_rtp_(0), - timestamp_last_received_rtp_(0), - any_rtp_received_(false), - timestamp_last_decoded_rtp_(0), - any_rtp_decoded_(false), - sample_rate_khz_(kDefaultSampleRateKhz), - max_nack_list_size_(kNackListSizeLimit) {} +NackTracker::NackTracker(size_t max_nack_list_size) + : max_nack_list_size_(max_nack_list_size), + sample_rate_(kDefaultSampleRate) {} NackTracker::~NackTracker() = default; void NackTracker::UpdateSampleRate(int sample_rate_hz) { RTC_DCHECK_GT(sample_rate_hz, 0); - int sample_rate_khz = sample_rate_hz / 1000; - if (sample_rate_khz_ != sample_rate_khz) { + Frequency sample_rate = Frequency::Hertz(sample_rate_hz); + if (sample_rate != sample_rate_) { Reset(); - sample_rate_khz_ = sample_rate_khz; + sample_rate_ = sample_rate; } } void NackTracker::UpdateLastReceivedPacket(uint16_t sequence_number, uint32_t timestamp) { - // Just record the value of sequence number and timestamp if this is the - // first packet. - if (!any_rtp_received_) { + if (!sequence_num_last_received_rtp_.has_value()) { sequence_num_last_received_rtp_ = sequence_number; timestamp_last_received_rtp_ = timestamp; - any_rtp_received_ = true; - // If no packet is decoded, to have a reasonable estimate of time-to-play - // use the given values. - if (!any_rtp_decoded_) { - timestamp_last_decoded_rtp_ = timestamp; - } return; } - if (sequence_number == sequence_num_last_received_rtp_) - return; - - // Received RTP should not be in the list. nack_list_.erase(sequence_number); - // If this is an old sequence number, no more action is required, return. - if (IsNewerSequenceNumber(sequence_num_last_received_rtp_, sequence_number)) + if (!IsNewerSequenceNumber(sequence_number, + *sequence_num_last_received_rtp_)) { return; - - UpdatePacketLossRate(sequence_number - sequence_num_last_received_rtp_ - 1); + } UpdateList(sequence_number, timestamp); @@ -110,13 +71,13 @@ uint16_t sequence_number_current_received_rtp, uint32_t timestamp_current_received_rtp) const { uint32_t timestamp_increase = - timestamp_current_received_rtp - timestamp_last_received_rtp_; + timestamp_current_received_rtp - *timestamp_last_received_rtp_; uint16_t sequence_num_increase = - sequence_number_current_received_rtp - sequence_num_last_received_rtp_; + sequence_number_current_received_rtp - *sequence_num_last_received_rtp_; int samples_per_packet = timestamp_increase / sequence_num_increase; if (samples_per_packet == 0 || - samples_per_packet > kMaxPacketSizeMs * sample_rate_khz_) { + samples_per_packet > kMaxPacketDuration * sample_rate_) { // Not a valid samples per packet. return std::nullopt; } @@ -126,7 +87,7 @@ void NackTracker::UpdateList(uint16_t sequence_number_current_received_rtp, uint32_t timestamp_current_received_rtp) { if (!IsNewerSequenceNumber(sequence_number_current_received_rtp, - sequence_num_last_received_rtp_ + 1)) { + *sequence_num_last_received_rtp_ + 1)) { return; } @@ -136,132 +97,53 @@ return; } - for (uint16_t n = sequence_num_last_received_rtp_ + 1; - IsNewerSequenceNumber(sequence_number_current_received_rtp, n); ++n) { - uint32_t timestamp = EstimateTimestamp(n, *samples_per_packet); - NackElement nack_element(TimeToPlay(timestamp), timestamp); - nack_list_.insert(nack_list_.end(), std::make_pair(n, nack_element)); + for (uint16_t sequence_number = *sequence_num_last_received_rtp_ + 1; + IsNewerSequenceNumber(sequence_number_current_received_rtp, + sequence_number); + ++sequence_number) { + nack_list_[sequence_number] = + EstimateTimestamp(sequence_number, *samples_per_packet); } } uint32_t NackTracker::EstimateTimestamp(uint16_t sequence_num, int samples_per_packet) { - uint16_t sequence_num_diff = sequence_num - sequence_num_last_received_rtp_; - return sequence_num_diff * samples_per_packet + timestamp_last_received_rtp_; -} - -void NackTracker::UpdateLastDecodedPacket(uint32_t timestamp) { - if (config_.fixed_delay) { - // Fixed delay mode does not NACK based on the decoded timestamp. - return; - } - any_rtp_decoded_ = true; - timestamp_last_decoded_rtp_ = timestamp; - // Packets in the list with timestamp less than the timestamp of the decoded - // RTP should be removed from the lists. They will be discarded by the jitter - // buffer if they arrive. - NackList::iterator it = nack_list_.begin(); - while (it != nack_list_.end() && - !IsNewerTimestamp(it->second.estimated_timestamp, timestamp)) { - it = nack_list_.erase(it); - } - // Update estimated time-to-play. - for (; it != nack_list_.end(); ++it) { - it->second.time_to_play_ms = TimeToPlay(it->second.estimated_timestamp); - } -} - -NackTracker::NackList NackTracker::GetNackList() const { - return nack_list_; + uint16_t sequence_num_diff = sequence_num - *sequence_num_last_received_rtp_; + return sequence_num_diff * samples_per_packet + *timestamp_last_received_rtp_; } void NackTracker::Reset() { nack_list_.clear(); - sequence_num_last_received_rtp_ = 0; - timestamp_last_received_rtp_ = 0; - any_rtp_received_ = false; - timestamp_last_decoded_rtp_ = 0; - any_rtp_decoded_ = false; - sample_rate_khz_ = kDefaultSampleRateKhz; -} - -void NackTracker::SetMaxNackListSize(size_t max_nack_list_size) { - RTC_CHECK_GT(max_nack_list_size, 0); - // Ugly hack to get around the problem of passing static consts by reference. - const size_t kNackListSizeLimitLocal = NackTracker::kNackListSizeLimit; - RTC_CHECK_LE(max_nack_list_size, kNackListSizeLimitLocal); - - max_nack_list_size_ = max_nack_list_size; - LimitNackListSize(); + sequence_num_last_received_rtp_.reset(); + timestamp_last_received_rtp_.reset(); + sample_rate_ = kDefaultSampleRate; } void NackTracker::LimitNackListSize() { - uint16_t limit = sequence_num_last_received_rtp_ - + uint16_t limit = *sequence_num_last_received_rtp_ - static_cast<uint16_t>(max_nack_list_size_) - 1; nack_list_.erase(nack_list_.begin(), nack_list_.upper_bound(limit)); } -int64_t NackTracker::TimeToPlay(uint32_t timestamp) const { - uint32_t timestamp_increase = timestamp - timestamp_last_decoded_rtp_; - return timestamp_increase / sample_rate_khz_; -} - -// We don't erase elements with time-to-play shorter than round-trip-time. std::vector<uint16_t> NackTracker::GetNackList( std::optional<TimeDelta> round_trip_time) { std::vector<uint16_t> sequence_numbers; if (!round_trip_time.has_value()) { - if (config_.require_valid_rtt) { - return {}; - } else { - round_trip_time = TimeDelta::Millis(config_.default_rtt_ms); - } + round_trip_time = kDefaultRtt; } - for (NackList::const_iterator it = nack_list_.begin(); it != nack_list_.end(); - ++it) { - if (Nack(it->second, round_trip_time->ms())) { - sequence_numbers.push_back(it->first); + for (const auto [sequence_number, timestamp] : nack_list_) { + if (Nack(timestamp, *round_trip_time)) { + sequence_numbers.push_back(sequence_number); } } - if (config_.never_nack_multiple_times) { - nack_list_.clear(); - } return sequence_numbers; } -void NackTracker::UpdatePacketLossRate(int packets_lost) { - const uint64_t alpha_q30 = (1 << 30) * config_.packet_loss_forget_factor; - // Exponential filter. - packet_loss_rate_ = (alpha_q30 * packet_loss_rate_) >> 30; - for (int i = 0; i < packets_lost; ++i) { - packet_loss_rate_ = - ((alpha_q30 * packet_loss_rate_) >> 30) + ((1 << 30) - alpha_q30); - } - // The estimated packet loss is between 0 and 1, so we need to multiply by 100 - // here. - max_wait_ms_ = - 100.0 * config_.ms_per_loss_percent * packet_loss_rate_ / (1 << 30); -} - -bool NackTracker::Nack(const NackElement& packet, int64_t round_trip_time_ms) { - int64_t time_since_packet_ms = - (timestamp_last_received_rtp_ - packet.estimated_timestamp) / - sample_rate_khz_; - - if (config_.fixed_delay) { - // In fixed delay mode, we only NACK based on the delay compared to the - // latest received packet. - return time_since_packet_ms + round_trip_time_ms < - config_.fixed_delay->ms(); - } - - if (packet_loss_rate_ > - static_cast<uint32_t>(config_.max_loss_rate * (1 << 30))) { - return false; - } - return packet.time_to_play_ms > round_trip_time_ms || - time_since_packet_ms + round_trip_time_ms < max_wait_ms_; +bool NackTracker::Nack(uint32_t timestamp, TimeDelta round_trip_time) { + TimeDelta time_since_packet = + (*timestamp_last_received_rtp_ - timestamp) / sample_rate_; + return time_since_packet + round_trip_time < kMaxNackDelay; } } // namespace webrtc
diff --git a/audio/nack_tracker.h b/audio/nack_tracker.h index 81772ea..08ca380 100644 --- a/audio/nack_tracker.h +++ b/audio/nack_tracker.h
@@ -18,31 +18,26 @@ #include <optional> #include <vector> -#include "api/field_trials_view.h" +#include "api/units/frequency.h" #include "api/units/time_delta.h" #include "modules/include/module_common_types_public.h" -#include "rtc_base/gtest_prod_util.h" // -// The NackTracker class keeps track of the lost packets, an estimate of -// time-to-play for each packet is also given. +// The NackTracker class keeps track of the lost packets. // -// Every time a packet is pushed into NetEq, LastReceivedPacket() has to be +// Every time a packet is received, UpdateLastReceivedPacket() has to be // called to update the NACK list. // -// Every time 10ms audio is pulled from NetEq LastDecodedPacket() should be -// called, and time-to-play is updated at that moment. -// // If packet N is received, any packet prior to N which has not arrived is // considered lost, and should be labeled as "missing" (the size of // the list might be limited and older packet eliminated from the list). // // The NackTracker class has to know about the sample rate of the packets to -// compute time-to-play. So sample rate should be set as soon as the first -// packet is received. If there is a change in the receive codec (sender changes -// codec) then NackTracker should be reset. This is because NetEQ would flush -// its buffer and re-transmission is meaning less for old packet. Therefore, in -// that case, after reset the sampling rate has to be updated. +// compute how old a packet is. So sample rate should be set as soon as the +// first packet is received. If there is a change in the receive codec (sender +// changes codec) then NackTracker should be reset. This is because NetEQ would +// flush its buffer and re-transmission is meaning less for old packet. +// Therefore, in that case, after reset the sampling rate has to be updated. // // Thread Safety // ============= @@ -53,26 +48,17 @@ class NackTracker { public: - // A limit for the size of the NACK list. - static const size_t kNackListSizeLimit = 500; // 10 seconds for 20 ms frame - // packets. - explicit NackTracker(const FieldTrialsView& field_trials); - ~NackTracker(); + // `max_nack_list_size` is the maximum size of the NACK list. If the last + // received packet has sequence number of N, then NACK list will not contain + // any element with sequence number earlier than N - `max_nack_list_size`. + explicit NackTracker(size_t max_nack_list_size); - // Set a maximum for the size of the NACK list. If the last received packet - // has sequence number of N, then NACK list will not contain any element - // with sequence number earlier than N - `max_nack_list_size`. - // - // The largest maximum size is defined by `kNackListSizeLimit` - void SetMaxNackListSize(size_t max_nack_list_size); + ~NackTracker(); // Set the sampling rate. // Resets the state if the sampling rate changes. void UpdateSampleRate(int sample_rate_hz); - // Update the the timestamp of the last decoded RTP. - void UpdateLastDecodedPacket(uint32_t timestamp); - // Update the sequence number and the timestamp of the last received RTP. This // API should be called every time a packet pushed into ACM. void UpdateLastReceivedPacket(uint16_t sequence_number, uint32_t timestamp); @@ -85,57 +71,9 @@ std::vector<uint16_t> GetNackList(std::optional<TimeDelta> round_trip_time); // Reset to default values. The NACK list is cleared. - // `max_nack_list_size_` preserves its value. void Reset(); - // Returns the estimated packet loss rate in Q30, for testing only. - uint32_t GetPacketLossRateForTest() { return packet_loss_rate_; } - private: - // This test need to access the private method GetNackList(). - FRIEND_TEST_ALL_PREFIXES(NackTrackerTest, EstimateTimestampAndTimeToPlay); - - // Options that can be configured via field trial. - struct Config { - explicit Config(const FieldTrialsView& field_trials); - - // The exponential decay factor used to estimate the packet loss rate. - double packet_loss_forget_factor = 0.996; - // How many additional ms we are willing to wait (at most) for nacked - // packets for each additional percentage of packet loss. - int ms_per_loss_percent = 20; - // If true, never nack packets more than once. - bool never_nack_multiple_times = false; - // Only nack if the RTT is valid. - bool require_valid_rtt = false; - // Default RTT to use unless `require_valid_rtt` is set. - int default_rtt_ms = 100; - // Do not nack if the loss rate is above this value. - double max_loss_rate = 1.0; - // If set, the maximum nack delay will be fixed and compared to the latest - // received packet instead of using the time to play estimate and the loss - // rate. - std::optional<TimeDelta> fixed_delay = TimeDelta::Seconds(1); - }; - - struct NackElement { - NackElement(int64_t initial_time_to_play_ms, uint32_t initial_timestamp) - : time_to_play_ms(initial_time_to_play_ms), - estimated_timestamp(initial_timestamp) {} - - // Estimated time (ms) left for this packet to be decoded. This estimate is - // updated every time jitter buffer decodes a packet. - int64_t time_to_play_ms; - - // A guess about the timestamp of the missing packet, it is used for - // estimation of `time_to_play_ms`. The estimate might be slightly wrong if - // there has been frame-size change since the last received packet and the - // missing packet. However, the risk of this is low, and in case of such - // errors, there will be a minor misestimation in time-to-play of missing - // packets. This will have a very minor effect on NACK performance. - uint32_t estimated_timestamp; - }; - class NackListCompare { public: bool operator()(uint16_t sequence_number_old, @@ -144,11 +82,8 @@ } }; - typedef std::map<uint16_t, NackElement, NackListCompare> NackList; - - // This API is used only for testing to assess whether time-to-play is - // computed correctly. - NackList GetNackList() const; + // Map between sequence number and estimated timestamp. + typedef std::map<uint16_t, uint32_t, NackListCompare> NackList; // Returns a valid number of samples per packet given the current received // sequence number and timestamp or nullopt of none could be computed. @@ -170,40 +105,22 @@ // Estimate timestamp of a missing packet given its sequence number. uint32_t EstimateTimestamp(uint16_t sequence_number, int samples_per_packet); - // Compute time-to-play given a timestamp. - int64_t TimeToPlay(uint32_t timestamp) const; + bool Nack(uint32_t timestamp, TimeDelta round_trip_time); - // Updates the estimated packet lost rate. - void UpdatePacketLossRate(int packets_lost); - - bool Nack(const NackElement& packet, int64_t round_trip_time_ms); - - const Config config_; + // NACK list will not keep track of missing packets prior to + // `sequence_num_last_received_rtp_` - `max_nack_list_size_`. + const size_t max_nack_list_size_; // Valid if a packet is received. - uint16_t sequence_num_last_received_rtp_; - uint32_t timestamp_last_received_rtp_; - bool any_rtp_received_; // If any packet received. + std::optional<uint16_t> sequence_num_last_received_rtp_; + std::optional<uint32_t> timestamp_last_received_rtp_; - // Valid if a packet is decoded. These are not used in fixed delay mode. - uint32_t timestamp_last_decoded_rtp_; - bool any_rtp_decoded_; // If any packet decoded. - - int sample_rate_khz_; // Sample rate in kHz. + Frequency sample_rate_; // A list of missing packets to be retransmitted. Components of the list // contain the sequence number of missing packets and the estimated time that // each pack is going to be played out. NackList nack_list_; - - // NACK list will not keep track of missing packets prior to - // `sequence_num_last_received_rtp_` - `max_nack_list_size_`. - size_t max_nack_list_size_; - - // Current estimate of the packet loss rate in Q30. - uint32_t packet_loss_rate_ = 0; - - int max_wait_ms_ = 0; }; } // namespace webrtc
diff --git a/audio/nack_tracker_unittest.cc b/audio/nack_tracker_unittest.cc index 1ca22f7..353559c 100644 --- a/audio/nack_tracker_unittest.cc +++ b/audio/nack_tracker_unittest.cc
@@ -13,13 +13,10 @@ #include <algorithm> #include <cstddef> #include <cstdint> -#include <memory> #include <optional> #include <vector> -#include "api/field_trials.h" #include "api/units/time_delta.h" -#include "test/create_test_field_trials.h" #include "test/gmock.h" #include "test/gtest.h" @@ -30,9 +27,9 @@ using ::testing::IsEmpty; constexpr int kSampleRateHz = 16000; -constexpr int kPacketSizeMs = 30; constexpr uint32_t kTimestampIncrement = 480; // 30 ms. constexpr TimeDelta kShortRoundTripTime = TimeDelta::Zero(); +constexpr int kDefaultNackListSize = 200; bool IsNackListCorrect(const std::vector<uint16_t>& nack_list, const uint16_t* lost_sequence_numbers, @@ -61,8 +58,7 @@ } // namespace TEST(NackTrackerTest, EmptyListWhenNoPacketLoss) { - FieldTrials field_trials = CreateTestFieldTrials(); - NackTracker nack(field_trials); + NackTracker nack(kDefaultNackListSize); nack.UpdateSampleRate(kSampleRateHz); int seq_num = 1; @@ -80,13 +76,12 @@ } TEST(NackTrackerTest, LatePacketsMovedToNackThenNackListDoesNotChange) { - FieldTrials field_trials = CreateTestFieldTrials(); const uint16_t kSequenceNumberLostPackets[] = {2, 3, 4, 5, 6, 7, 8, 9}; static const int kNumAllLostPackets = sizeof(kSequenceNumberLostPackets) / sizeof(kSequenceNumberLostPackets[0]); for (int k = 0; k < 2; k++) { // Two iteration with/without wrap around. - NackTracker nack(field_trials); + NackTracker nack(kDefaultNackListSize); nack.UpdateSampleRate(kSampleRateHz); uint16_t sequence_num_lost_packets[kNumAllLostPackets]; @@ -128,13 +123,12 @@ } TEST(NackTrackerTest, ArrivedPacketsAreRemovedFromNackList) { - FieldTrials field_trials = CreateTestFieldTrials(); const uint16_t kSequenceNumberLostPackets[] = {2, 3, 4, 5, 6, 7, 8, 9}; static const int kNumAllLostPackets = sizeof(kSequenceNumberLostPackets) / sizeof(kSequenceNumberLostPackets[0]); for (int k = 0; k < 2; ++k) { // Two iteration with/without wrap around. - NackTracker nack(field_trials); + NackTracker nack(kDefaultNackListSize); nack.UpdateSampleRate(kSampleRateHz); uint16_t sequence_num_lost_packets[kNumAllLostPackets]; @@ -187,127 +181,8 @@ } } -// Assess if estimation of timestamps and time-to-play is correct. Introduce all -// combinations that timestamps and sequence numbers might have wrap around. -TEST(NackTrackerTest, EstimateTimestampAndTimeToPlay) { - FieldTrials field_trials = CreateTestFieldTrials(); - const uint16_t kLostPackets[] = {2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15}; - static const int kNumAllLostPackets = - sizeof(kLostPackets) / sizeof(kLostPackets[0]); - - for (int k = 0; k < 4; ++k) { - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - - // Sequence number wrap around if `k` is 2 or 3; - int seq_num_offset = (k < 2) ? 0 : 65531; - - // Timestamp wrap around if `k` is 1 or 3. - uint32_t timestamp_offset = - (k & 0x1) ? static_cast<uint32_t>(0xffffffff) - 6 : 0; - - uint32_t timestamp_lost_packets[kNumAllLostPackets]; - uint16_t seq_num_lost_packets[kNumAllLostPackets]; - for (int n = 0; n < kNumAllLostPackets; ++n) { - timestamp_lost_packets[n] = - timestamp_offset + kLostPackets[n] * kTimestampIncrement; - seq_num_lost_packets[n] = seq_num_offset + kLostPackets[n]; - } - - // We and to push two packets before lost burst starts. - uint16_t seq_num = seq_num_lost_packets[0] - 2; - uint32_t timestamp = timestamp_lost_packets[0] - 2 * kTimestampIncrement; - - const uint32_t first_timestamp = timestamp; - - // Two consecutive packets to have a correct estimate of timestamp increase. - nack.UpdateLastReceivedPacket(seq_num, timestamp); - seq_num++; - timestamp += kTimestampIncrement; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - - // A packet after the last one which is supposed to be lost. - seq_num = seq_num_lost_packets[kNumAllLostPackets - 1] + 1; - timestamp = - timestamp_lost_packets[kNumAllLostPackets - 1] + kTimestampIncrement; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - - NackTracker::NackList nack_list = nack.GetNackList(); - EXPECT_EQ(static_cast<size_t>(kNumAllLostPackets), nack_list.size()); - - // Pretend the first packet is decoded. - nack.UpdateLastDecodedPacket(first_timestamp); - nack_list = nack.GetNackList(); - - NackTracker::NackList::iterator it = nack_list.begin(); - while (it != nack_list.end()) { - seq_num = it->first - seq_num_offset; - int index = seq_num - kLostPackets[0]; - EXPECT_EQ(timestamp_lost_packets[index], it->second.estimated_timestamp); - EXPECT_EQ((index + 2) * kPacketSizeMs, it->second.time_to_play_ms); - ++it; - } - } -} - -TEST(NackTrackerTest, - MissingPacketsPriorToLastDecodedRtpShouldNotBeInNackList) { - FieldTrials field_trials = CreateTestFieldTrials( - "WebRTC-Audio-NetEqNackTrackerConfig/fixed_delay:/"); - for (int m = 0; m < 2; ++m) { - uint16_t seq_num_offset = (m == 0) ? 0 : 65531; // Wrap around if `m` is 1. - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - - // Two consecutive packets to have a correct estimate of timestamp increase. - uint16_t seq_num = 0; - nack.UpdateLastReceivedPacket(seq_num_offset + seq_num, - seq_num * kTimestampIncrement); - seq_num++; - nack.UpdateLastReceivedPacket(seq_num_offset + seq_num, - seq_num * kTimestampIncrement); - - // Skip 10 packets (larger than NACK threshold). - const int kNumLostPackets = 10; - seq_num += kNumLostPackets + 1; - nack.UpdateLastReceivedPacket(seq_num_offset + seq_num, - seq_num * kTimestampIncrement); - - const size_t kExpectedListSize = kNumLostPackets; - std::vector<uint16_t> nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_EQ(kExpectedListSize, nack_list.size()); - - for (int k = 0; k < 2; ++k) { - // Decoding of the first and the second arrived packets. - for (int n = 0; n < kPacketSizeMs / 10; ++n) { - nack.UpdateLastDecodedPacket(k * kTimestampIncrement); - nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_EQ(kExpectedListSize, nack_list.size()); - } - } - - // Decoding of the last received packet. - nack.UpdateLastDecodedPacket(seq_num * kTimestampIncrement); - nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_TRUE(nack_list.empty()); - - // Make sure list of late packets is also empty. To check that, push few - // packets, if the late list is not empty its content will pop up in NACK - // list. - for (int n = 0; n < 10; ++n) { - seq_num++; - nack.UpdateLastReceivedPacket(seq_num_offset + seq_num, - seq_num * kTimestampIncrement); - nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_TRUE(nack_list.empty()); - } - } -} - TEST(NackTrackerTest, Reset) { - FieldTrials field_trials = CreateTestFieldTrials(); - NackTracker nack(field_trials); + NackTracker nack(kDefaultNackListSize); nack.UpdateSampleRate(kSampleRateHz); // Two consecutive packets to have a correct estimate of timestamp increase. @@ -331,13 +206,11 @@ } TEST(NackTrackerTest, ListSizeAppliedFromBeginning) { - FieldTrials field_trials = CreateTestFieldTrials(); const size_t kNackListSize = 10; for (int m = 0; m < 2; ++m) { uint16_t seq_num_offset = (m == 0) ? 0 : 65525; // Wrap around if `m` is 1. - NackTracker nack(field_trials); + NackTracker nack(kNackListSize); nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); uint16_t seq_num = seq_num_offset; uint32_t timestamp = 0x12345678; @@ -355,69 +228,10 @@ } } -TEST(NackTrackerTest, ChangeOfListSizeAppliedAndOldElementsRemoved) { - FieldTrials field_trials = CreateTestFieldTrials(); - const size_t kNackListSize = 10; - for (int m = 0; m < 2; ++m) { - uint16_t seq_num_offset = (m == 0) ? 0 : 65525; // Wrap around if `m` is 1. - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - - uint16_t seq_num = seq_num_offset; - uint32_t timestamp = 0x87654321; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - - // Packet lost more than NACK-list size limit. - uint16_t num_lost_packets = kNackListSize + 5; - - std::unique_ptr<uint16_t[]> seq_num_lost(new uint16_t[num_lost_packets]); - for (int n = 0; n < num_lost_packets; ++n) { - seq_num_lost[n] = ++seq_num; - } - - ++seq_num; - timestamp += (num_lost_packets + 1) * kTimestampIncrement; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - size_t expected_size = num_lost_packets; - - std::vector<uint16_t> nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_EQ(expected_size, nack_list.size()); - - nack.SetMaxNackListSize(kNackListSize); - expected_size = kNackListSize; - nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_TRUE(IsNackListCorrect( - nack_list, &seq_num_lost[num_lost_packets - kNackListSize], - expected_size)); - - // NACK list should shrink. - for (size_t n = 1; n < kNackListSize; ++n) { - ++seq_num; - timestamp += kTimestampIncrement; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - --expected_size; - nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_TRUE(IsNackListCorrect( - nack_list, &seq_num_lost[num_lost_packets - kNackListSize + n], - expected_size)); - } - - // After this packet, NACK list should be empty. - ++seq_num; - timestamp += kTimestampIncrement; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - nack_list = nack.GetNackList(kShortRoundTripTime); - EXPECT_TRUE(nack_list.empty()); - } -} - TEST(NackTrackerTest, RoudTripTimeIsApplied) { - FieldTrials field_trials = CreateTestFieldTrials( - "WebRTC-Audio-NetEqNackTrackerConfig/fixed_delay:/"); const int kNackListSize = 200; - NackTracker nack(field_trials); + NackTracker nack(kNackListSize); nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); uint16_t seq_num = 0; uint32_t timestamp = 0x87654321; @@ -436,78 +250,14 @@ // sequence number: 1, 2, 3, 4, 5 // time-to-play: 20, 50, 80, 110, 140 // - std::vector<uint16_t> nack_list = nack.GetNackList(TimeDelta::Millis(100)); - ASSERT_EQ(2u, nack_list.size()); - EXPECT_EQ(4, nack_list[0]); - EXPECT_EQ(5, nack_list[1]); -} - -// Set never_nack_multiple_times to true with a field trial and verify that -// packets are not nacked multiple times. -TEST(NackTrackerTest, DoNotNackMultipleTimes) { - FieldTrials field_trials = CreateTestFieldTrials( - "WebRTC-Audio-NetEqNackTrackerConfig/" - "packet_loss_forget_factor:0.996,ms_per_loss_percent:20," - "never_nack_multiple_times:true/"); - const int kNackListSize = 200; - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); - - uint16_t seq_num = 0; - uint32_t timestamp = 0x87654321; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - - uint16_t kNumLostPackets = 3; - - seq_num += (1 + kNumLostPackets); - timestamp += (1 + kNumLostPackets) * kTimestampIncrement; - nack.UpdateLastReceivedPacket(seq_num, timestamp); - - std::vector<uint16_t> nack_list = nack.GetNackList(kShortRoundTripTime); - ASSERT_EQ(3u, nack_list.size()); - EXPECT_EQ(1, nack_list[0]); - EXPECT_EQ(2, nack_list[1]); - EXPECT_EQ(3, nack_list[2]); - // When we get the nack list again, it should be empty. - std::vector<uint16_t> nack_list2 = nack.GetNackList(kShortRoundTripTime); - EXPECT_TRUE(nack_list2.empty()); -} - -// Test if estimated packet loss rate is correct. -TEST(NackTrackerTest, PacketLossRateCorrect) { - FieldTrials field_trials = CreateTestFieldTrials(); - const int kNackListSize = 200; - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); - uint16_t seq_num = 0; - uint32_t timestamp = 0x87654321; - auto add_packet = [&nack, &seq_num, ×tamp](bool received) { - if (received) { - nack.UpdateLastReceivedPacket(seq_num, timestamp); - } - seq_num++; - timestamp += kTimestampIncrement; - }; - // Add some packets, but every fourth packet is lost. - for (int i = 0; i < 300; i++) { - add_packet(true); - add_packet(true); - add_packet(true); - add_packet(false); - } - // 1 << 28 is 0.25 in Q30. We expect the packet loss estimate to be within - // 0.01 of that. - EXPECT_NEAR(nack.GetPacketLossRateForTest(), 1 << 28, (1 << 30) / 100); + std::vector<uint16_t> nack_list = nack.GetNackList(TimeDelta::Millis(930)); + EXPECT_THAT(nack_list, ElementsAre(4, 5)); } TEST(NackTrackerTest, DoNotNackAfterDtx) { - FieldTrials field_trials = CreateTestFieldTrials(); const int kNackListSize = 200; - NackTracker nack(field_trials); + NackTracker nack(kNackListSize); nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); uint16_t seq_num = 0; uint32_t timestamp = 0x87654321; nack.UpdateLastReceivedPacket(seq_num, timestamp); @@ -518,60 +268,10 @@ EXPECT_TRUE(nack.GetNackList(std::nullopt).empty()); } -TEST(NackTrackerTest, DoNotNackIfLossRateIsTooHigh) { - FieldTrials field_trials = CreateTestFieldTrials( - "WebRTC-Audio-NetEqNackTrackerConfig/max_loss_rate:0.4,fixed_delay:/"); - const int kNackListSize = 200; - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); - uint16_t seq_num = 0; - uint32_t timestamp = 0x87654321; - auto add_packet = [&nack, &seq_num, ×tamp](bool received) { - if (received) { - nack.UpdateLastReceivedPacket(seq_num, timestamp); - } - seq_num++; - timestamp += kTimestampIncrement; - }; - for (int i = 0; i < 500; i++) { - add_packet(true); - add_packet(false); - } - // Expect 50% loss rate which is higher that the configured maximum 40%. - EXPECT_NEAR(nack.GetPacketLossRateForTest(), 1 << 29, (1 << 30) / 100); - EXPECT_TRUE(nack.GetNackList(std::nullopt).empty()); -} - -TEST(NackTrackerTest, OnlyNackIfRttIsValid) { - FieldTrials field_trials = CreateTestFieldTrials( - "WebRTC-Audio-NetEqNackTrackerConfig/require_valid_rtt:true/"); - const int kNackListSize = 200; - NackTracker nack(field_trials); - nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); - uint16_t seq_num = 0; - uint32_t timestamp = 0x87654321; - auto add_packet = [&nack, &seq_num, ×tamp](bool received) { - if (received) { - nack.UpdateLastReceivedPacket(seq_num, timestamp); - } - seq_num++; - timestamp += kTimestampIncrement; - }; - add_packet(true); - add_packet(false); - add_packet(true); - EXPECT_TRUE(nack.GetNackList(std::nullopt).empty()); - EXPECT_FALSE(nack.GetNackList(kShortRoundTripTime).empty()); -} - TEST(NackTrackerTest, FixedDelayMode) { - FieldTrials field_trials = CreateTestFieldTrials(); const int kNackListSize = 200; - NackTracker nack(field_trials); + NackTracker nack(kNackListSize); nack.UpdateSampleRate(kSampleRateHz); - nack.SetMaxNackListSize(kNackListSize); uint16_t seq_num = 0; uint32_t timestamp = 0x87654321; nack.UpdateLastReceivedPacket(seq_num, timestamp); @@ -580,9 +280,6 @@ EXPECT_THAT(nack.GetNackList(kShortRoundTripTime), ElementsAre(seq_num + 1)); // The RTT is larger than the fixed delay, so no packets should be NACKed. EXPECT_THAT(nack.GetNackList(TimeDelta::Seconds(1)), IsEmpty()); - // Decoding a packet should not affect the NACK list in fixed delay mode. - nack.UpdateLastDecodedPacket(timestamp + 2 * kTimestampIncrement); - EXPECT_THAT(nack.GetNackList(kShortRoundTripTime), ElementsAre(seq_num + 1)); // Update the latest received packet such that the lost packet is older than // the fixed delay.