|  | /* | 
|  | *  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/audio_coding/neteq/statistics_calculator.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <string.h>  // memset | 
|  |  | 
|  | #include <algorithm> | 
|  |  | 
|  | #include "modules/audio_coding/neteq/delay_manager.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/numerics/safe_conversions.h" | 
|  | #include "system_wrappers/include/metrics.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | namespace { | 
|  | size_t AddIntToSizeTWithLowerCap(int a, size_t b) { | 
|  | const size_t ret = b + a; | 
|  | // If a + b is negative, resulting in a negative wrap, cap it to zero instead. | 
|  | static_assert(sizeof(size_t) >= sizeof(int), | 
|  | "int must not be wider than size_t for this to work"); | 
|  | return (a < 0 && ret > b) ? 0 : ret; | 
|  | } | 
|  |  | 
|  | constexpr int kInterruptionLenMs = 150; | 
|  | }  // namespace | 
|  |  | 
|  | // Allocating the static const so that it can be passed by reference to | 
|  | // RTC_DCHECK. | 
|  | const size_t StatisticsCalculator::kLenWaitingTimes; | 
|  |  | 
|  | StatisticsCalculator::PeriodicUmaLogger::PeriodicUmaLogger( | 
|  | const std::string& uma_name, | 
|  | int report_interval_ms, | 
|  | int max_value) | 
|  | : uma_name_(uma_name), | 
|  | report_interval_ms_(report_interval_ms), | 
|  | max_value_(max_value), | 
|  | timer_(0) {} | 
|  |  | 
|  | StatisticsCalculator::PeriodicUmaLogger::~PeriodicUmaLogger() = default; | 
|  |  | 
|  | void StatisticsCalculator::PeriodicUmaLogger::AdvanceClock(int step_ms) { | 
|  | timer_ += step_ms; | 
|  | if (timer_ < report_interval_ms_) { | 
|  | return; | 
|  | } | 
|  | LogToUma(Metric()); | 
|  | Reset(); | 
|  | timer_ -= report_interval_ms_; | 
|  | RTC_DCHECK_GE(timer_, 0); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PeriodicUmaLogger::LogToUma(int value) const { | 
|  | RTC_HISTOGRAM_COUNTS_SPARSE(uma_name_, value, 1, max_value_, 50); | 
|  | } | 
|  |  | 
|  | StatisticsCalculator::PeriodicUmaCount::PeriodicUmaCount( | 
|  | const std::string& uma_name, | 
|  | int report_interval_ms, | 
|  | int max_value) | 
|  | : PeriodicUmaLogger(uma_name, report_interval_ms, max_value) {} | 
|  |  | 
|  | StatisticsCalculator::PeriodicUmaCount::~PeriodicUmaCount() { | 
|  | // Log the count for the current (incomplete) interval. | 
|  | LogToUma(Metric()); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PeriodicUmaCount::RegisterSample() { | 
|  | ++counter_; | 
|  | } | 
|  |  | 
|  | int StatisticsCalculator::PeriodicUmaCount::Metric() const { | 
|  | return counter_; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PeriodicUmaCount::Reset() { | 
|  | counter_ = 0; | 
|  | } | 
|  |  | 
|  | StatisticsCalculator::PeriodicUmaAverage::PeriodicUmaAverage( | 
|  | const std::string& uma_name, | 
|  | int report_interval_ms, | 
|  | int max_value) | 
|  | : PeriodicUmaLogger(uma_name, report_interval_ms, max_value) {} | 
|  |  | 
|  | StatisticsCalculator::PeriodicUmaAverage::~PeriodicUmaAverage() { | 
|  | // Log the average for the current (incomplete) interval. | 
|  | LogToUma(Metric()); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PeriodicUmaAverage::RegisterSample(int value) { | 
|  | sum_ += value; | 
|  | ++counter_; | 
|  | } | 
|  |  | 
|  | int StatisticsCalculator::PeriodicUmaAverage::Metric() const { | 
|  | return counter_ == 0 ? 0 : static_cast<int>(sum_ / counter_); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PeriodicUmaAverage::Reset() { | 
|  | sum_ = 0.0; | 
|  | counter_ = 0; | 
|  | } | 
|  |  | 
|  | StatisticsCalculator::StatisticsCalculator() | 
|  | : preemptive_samples_(0), | 
|  | accelerate_samples_(0), | 
|  | added_zero_samples_(0), | 
|  | expanded_speech_samples_(0), | 
|  | expanded_noise_samples_(0), | 
|  | discarded_packets_(0), | 
|  | lost_timestamps_(0), | 
|  | timestamps_since_last_report_(0), | 
|  | secondary_decoded_samples_(0), | 
|  | discarded_secondary_packets_(0), | 
|  | delayed_packet_outage_counter_( | 
|  | "WebRTC.Audio.DelayedPacketOutageEventsPerMinute", | 
|  | 60000,  // 60 seconds report interval. | 
|  | 100), | 
|  | excess_buffer_delay_("WebRTC.Audio.AverageExcessBufferDelayMs", | 
|  | 60000,  // 60 seconds report interval. | 
|  | 1000), | 
|  | buffer_full_counter_("WebRTC.Audio.JitterBufferFullPerMinute", | 
|  | 60000,  // 60 seconds report interval. | 
|  | 100) {} | 
|  |  | 
|  | StatisticsCalculator::~StatisticsCalculator() = default; | 
|  |  | 
|  | void StatisticsCalculator::Reset() { | 
|  | preemptive_samples_ = 0; | 
|  | accelerate_samples_ = 0; | 
|  | added_zero_samples_ = 0; | 
|  | expanded_speech_samples_ = 0; | 
|  | expanded_noise_samples_ = 0; | 
|  | secondary_decoded_samples_ = 0; | 
|  | discarded_secondary_packets_ = 0; | 
|  | waiting_times_.clear(); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ResetMcu() { | 
|  | discarded_packets_ = 0; | 
|  | lost_timestamps_ = 0; | 
|  | timestamps_since_last_report_ = 0; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ExpandedVoiceSamples(size_t num_samples, | 
|  | bool is_new_concealment_event) { | 
|  | expanded_speech_samples_ += num_samples; | 
|  | ConcealedSamplesCorrection(rtc::dchecked_cast<int>(num_samples), true); | 
|  | lifetime_stats_.concealment_events += is_new_concealment_event; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ExpandedNoiseSamples(size_t num_samples, | 
|  | bool is_new_concealment_event) { | 
|  | expanded_noise_samples_ += num_samples; | 
|  | ConcealedSamplesCorrection(rtc::dchecked_cast<int>(num_samples), false); | 
|  | lifetime_stats_.concealment_events += is_new_concealment_event; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ExpandedVoiceSamplesCorrection(int num_samples) { | 
|  | expanded_speech_samples_ = | 
|  | AddIntToSizeTWithLowerCap(num_samples, expanded_speech_samples_); | 
|  | ConcealedSamplesCorrection(num_samples, true); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ExpandedNoiseSamplesCorrection(int num_samples) { | 
|  | expanded_noise_samples_ = | 
|  | AddIntToSizeTWithLowerCap(num_samples, expanded_noise_samples_); | 
|  | ConcealedSamplesCorrection(num_samples, false); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::DecodedOutputPlayed() { | 
|  | decoded_output_played_ = true; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::EndExpandEvent(int fs_hz) { | 
|  | RTC_DCHECK_GE(lifetime_stats_.concealed_samples, | 
|  | concealed_samples_at_event_end_); | 
|  | const int event_duration_ms = | 
|  | 1000 * | 
|  | (lifetime_stats_.concealed_samples - concealed_samples_at_event_end_) / | 
|  | fs_hz; | 
|  | if (event_duration_ms >= kInterruptionLenMs && decoded_output_played_) { | 
|  | lifetime_stats_.interruption_count++; | 
|  | lifetime_stats_.total_interruption_duration_ms += event_duration_ms; | 
|  | RTC_HISTOGRAM_COUNTS("WebRTC.Audio.AudioInterruptionMs", event_duration_ms, | 
|  | /*min=*/150, /*max=*/5000, /*bucket_count=*/50); | 
|  | } | 
|  | concealed_samples_at_event_end_ = lifetime_stats_.concealed_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ConcealedSamplesCorrection(int num_samples, | 
|  | bool is_voice) { | 
|  | if (num_samples < 0) { | 
|  | // Store negative correction to subtract from future positive additions. | 
|  | // See also the function comment in the header file. | 
|  | concealed_samples_correction_ -= num_samples; | 
|  | if (!is_voice) { | 
|  | silent_concealed_samples_correction_ -= num_samples; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | const size_t canceled_out = | 
|  | std::min(static_cast<size_t>(num_samples), concealed_samples_correction_); | 
|  | concealed_samples_correction_ -= canceled_out; | 
|  | lifetime_stats_.concealed_samples += num_samples - canceled_out; | 
|  |  | 
|  | if (!is_voice) { | 
|  | const size_t silent_canceled_out = std::min( | 
|  | static_cast<size_t>(num_samples), silent_concealed_samples_correction_); | 
|  | silent_concealed_samples_correction_ -= silent_canceled_out; | 
|  | lifetime_stats_.silent_concealed_samples += | 
|  | num_samples - silent_canceled_out; | 
|  | } | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PreemptiveExpandedSamples(size_t num_samples) { | 
|  | preemptive_samples_ += num_samples; | 
|  | operations_and_state_.preemptive_samples += num_samples; | 
|  | lifetime_stats_.inserted_samples_for_deceleration += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::AcceleratedSamples(size_t num_samples) { | 
|  | accelerate_samples_ += num_samples; | 
|  | operations_and_state_.accelerate_samples += num_samples; | 
|  | lifetime_stats_.removed_samples_for_acceleration += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::AddZeros(size_t num_samples) { | 
|  | added_zero_samples_ += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::PacketsDiscarded(size_t num_packets) { | 
|  | operations_and_state_.discarded_primary_packets += num_packets; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::SecondaryPacketsDiscarded(size_t num_packets) { | 
|  | discarded_secondary_packets_ += num_packets; | 
|  | lifetime_stats_.fec_packets_discarded += num_packets; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::SecondaryPacketsReceived(size_t num_packets) { | 
|  | lifetime_stats_.fec_packets_received += num_packets; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::LostSamples(size_t num_samples) { | 
|  | lost_timestamps_ += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::IncreaseCounter(size_t num_samples, int fs_hz) { | 
|  | const int time_step_ms = | 
|  | rtc::CheckedDivExact(static_cast<int>(1000 * num_samples), fs_hz); | 
|  | delayed_packet_outage_counter_.AdvanceClock(time_step_ms); | 
|  | excess_buffer_delay_.AdvanceClock(time_step_ms); | 
|  | buffer_full_counter_.AdvanceClock(time_step_ms); | 
|  | timestamps_since_last_report_ += static_cast<uint32_t>(num_samples); | 
|  | if (timestamps_since_last_report_ > | 
|  | static_cast<uint32_t>(fs_hz * kMaxReportPeriod)) { | 
|  | lost_timestamps_ = 0; | 
|  | timestamps_since_last_report_ = 0; | 
|  | discarded_packets_ = 0; | 
|  | } | 
|  | lifetime_stats_.total_samples_received += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::JitterBufferDelay(size_t num_samples, | 
|  | uint64_t waiting_time_ms, | 
|  | uint64_t target_delay_ms) { | 
|  | lifetime_stats_.jitter_buffer_delay_ms += waiting_time_ms * num_samples; | 
|  | lifetime_stats_.jitter_buffer_target_delay_ms += | 
|  | target_delay_ms * num_samples; | 
|  | lifetime_stats_.jitter_buffer_emitted_count += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::SecondaryDecodedSamples(int num_samples) { | 
|  | secondary_decoded_samples_ += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::FlushedPacketBuffer() { | 
|  | operations_and_state_.packet_buffer_flushes++; | 
|  | buffer_full_counter_.RegisterSample(); | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::ReceivedPacket() { | 
|  | ++lifetime_stats_.jitter_buffer_packets_received; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::RelativePacketArrivalDelay(size_t delay_ms) { | 
|  | lifetime_stats_.relative_packet_arrival_delay_ms += delay_ms; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::LogDelayedPacketOutageEvent(int num_samples, | 
|  | int fs_hz) { | 
|  | int outage_duration_ms = num_samples / (fs_hz / 1000); | 
|  | RTC_HISTOGRAM_COUNTS("WebRTC.Audio.DelayedPacketOutageEventMs", | 
|  | outage_duration_ms, 1 /* min */, 2000 /* max */, | 
|  | 100 /* bucket count */); | 
|  | delayed_packet_outage_counter_.RegisterSample(); | 
|  | lifetime_stats_.delayed_packet_outage_samples += num_samples; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::StoreWaitingTime(int waiting_time_ms) { | 
|  | excess_buffer_delay_.RegisterSample(waiting_time_ms); | 
|  | RTC_DCHECK_LE(waiting_times_.size(), kLenWaitingTimes); | 
|  | if (waiting_times_.size() == kLenWaitingTimes) { | 
|  | // Erase first value. | 
|  | waiting_times_.pop_front(); | 
|  | } | 
|  | waiting_times_.push_back(waiting_time_ms); | 
|  | operations_and_state_.last_waiting_time_ms = waiting_time_ms; | 
|  | } | 
|  |  | 
|  | void StatisticsCalculator::GetNetworkStatistics(int fs_hz, | 
|  | size_t num_samples_in_buffers, | 
|  | size_t samples_per_packet, | 
|  | NetEqNetworkStatistics* stats) { | 
|  | RTC_DCHECK_GT(fs_hz, 0); | 
|  | RTC_DCHECK(stats); | 
|  |  | 
|  | stats->added_zero_samples = added_zero_samples_; | 
|  | stats->current_buffer_size_ms = | 
|  | static_cast<uint16_t>(num_samples_in_buffers * 1000 / fs_hz); | 
|  |  | 
|  | stats->packet_loss_rate = | 
|  | CalculateQ14Ratio(lost_timestamps_, timestamps_since_last_report_); | 
|  |  | 
|  | stats->accelerate_rate = | 
|  | CalculateQ14Ratio(accelerate_samples_, timestamps_since_last_report_); | 
|  |  | 
|  | stats->preemptive_rate = | 
|  | CalculateQ14Ratio(preemptive_samples_, timestamps_since_last_report_); | 
|  |  | 
|  | stats->expand_rate = | 
|  | CalculateQ14Ratio(expanded_speech_samples_ + expanded_noise_samples_, | 
|  | timestamps_since_last_report_); | 
|  |  | 
|  | stats->speech_expand_rate = CalculateQ14Ratio(expanded_speech_samples_, | 
|  | timestamps_since_last_report_); | 
|  |  | 
|  | stats->secondary_decoded_rate = CalculateQ14Ratio( | 
|  | secondary_decoded_samples_, timestamps_since_last_report_); | 
|  |  | 
|  | const size_t discarded_secondary_samples = | 
|  | discarded_secondary_packets_ * samples_per_packet; | 
|  | stats->secondary_discarded_rate = | 
|  | CalculateQ14Ratio(discarded_secondary_samples, | 
|  | static_cast<uint32_t>(discarded_secondary_samples + | 
|  | secondary_decoded_samples_)); | 
|  |  | 
|  | if (waiting_times_.size() == 0) { | 
|  | stats->mean_waiting_time_ms = -1; | 
|  | stats->median_waiting_time_ms = -1; | 
|  | stats->min_waiting_time_ms = -1; | 
|  | stats->max_waiting_time_ms = -1; | 
|  | } else { | 
|  | std::sort(waiting_times_.begin(), waiting_times_.end()); | 
|  | // Find mid-point elements. If the size is odd, the two values | 
|  | // |middle_left| and |middle_right| will both be the one middle element; if | 
|  | // the size is even, they will be the the two neighboring elements at the | 
|  | // middle of the list. | 
|  | const int middle_left = waiting_times_[(waiting_times_.size() - 1) / 2]; | 
|  | const int middle_right = waiting_times_[waiting_times_.size() / 2]; | 
|  | // Calculate the average of the two. (Works also for odd sizes.) | 
|  | stats->median_waiting_time_ms = (middle_left + middle_right) / 2; | 
|  | stats->min_waiting_time_ms = waiting_times_.front(); | 
|  | stats->max_waiting_time_ms = waiting_times_.back(); | 
|  | double sum = 0; | 
|  | for (auto time : waiting_times_) { | 
|  | sum += time; | 
|  | } | 
|  | stats->mean_waiting_time_ms = static_cast<int>(sum / waiting_times_.size()); | 
|  | } | 
|  |  | 
|  | // Reset counters. | 
|  | ResetMcu(); | 
|  | Reset(); | 
|  | } | 
|  |  | 
|  | NetEqLifetimeStatistics StatisticsCalculator::GetLifetimeStatistics() const { | 
|  | return lifetime_stats_; | 
|  | } | 
|  |  | 
|  | NetEqOperationsAndState StatisticsCalculator::GetOperationsAndState() const { | 
|  | return operations_and_state_; | 
|  | } | 
|  |  | 
|  | uint16_t StatisticsCalculator::CalculateQ14Ratio(size_t numerator, | 
|  | uint32_t denominator) { | 
|  | if (numerator == 0) { | 
|  | return 0; | 
|  | } else if (numerator < denominator) { | 
|  | // Ratio must be smaller than 1 in Q14. | 
|  | assert((numerator << 14) / denominator < (1 << 14)); | 
|  | return static_cast<uint16_t>((numerator << 14) / denominator); | 
|  | } else { | 
|  | // Will not produce a ratio larger than 1, since this is probably an error. | 
|  | return 1 << 14; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |