diff --git a/modules/audio_coding/BUILD.gn b/modules/audio_coding/BUILD.gn index 31c1f6f5f0..7cea425f51 100644 --- a/modules/audio_coding/BUILD.gn +++ b/modules/audio_coding/BUILD.gn @@ -1210,6 +1210,8 @@ rtc_source_set("neteq_tools") { "neteq/tools/neteq_delay_analyzer.h", "neteq/tools/neteq_replacement_input.cc", "neteq/tools/neteq_replacement_input.h", + "neteq/tools/neteq_stats_getter.cc", + "neteq/tools/neteq_stats_getter.h", ] public_configs = [ ":neteq_tools_config" ] diff --git a/modules/audio_coding/neteq/tools/neteq_rtpplay.cc b/modules/audio_coding/neteq/tools/neteq_rtpplay.cc index fb937cd707..7c071dba6f 100644 --- a/modules/audio_coding/neteq/tools/neteq_rtpplay.cc +++ b/modules/audio_coding/neteq/tools/neteq_rtpplay.cc @@ -10,22 +10,18 @@ #include #include +#include #include // For ULONG_MAX returned by strtoul. +#include #include #include // For strtoul. -#include - -#include -#include -#include -#include -#include #include #include "modules/audio_coding/neteq/include/neteq.h" #include "modules/audio_coding/neteq/tools/fake_decode_from_file.h" #include "modules/audio_coding/neteq/tools/input_audio_file.h" #include "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h" +#include "modules/audio_coding/neteq/tools/neteq_stats_getter.h" #include "modules/audio_coding/neteq/tools/neteq_packet_source_input.h" #include "modules/audio_coding/neteq/tools/neteq_replacement_input.h" #include "modules/audio_coding/neteq/tools/neteq_test.h" @@ -308,168 +304,6 @@ class SsrcSwitchDetector : public NetEqPostInsertPacket { rtc::Optional last_ssrc_; }; -class StatsGetter : public NetEqGetAudioCallback { - public: - // This struct is a replica of webrtc::NetEqNetworkStatistics, but with all - // values stored in double precision. - struct Stats { - double current_buffer_size_ms = 0.0; - double preferred_buffer_size_ms = 0.0; - double jitter_peaks_found = 0.0; - double packet_loss_rate = 0.0; - double expand_rate = 0.0; - double speech_expand_rate = 0.0; - double preemptive_rate = 0.0; - double accelerate_rate = 0.0; - double secondary_decoded_rate = 0.0; - double secondary_discarded_rate = 0.0; - double clockdrift_ppm = 0.0; - double added_zero_samples = 0.0; - double mean_waiting_time_ms = 0.0; - double median_waiting_time_ms = 0.0; - double min_waiting_time_ms = 0.0; - double max_waiting_time_ms = 0.0; - }; - - struct ConcealmentEvent { - uint64_t duration_ms; - size_t concealment_event_number; - int64_t time_from_previous_event_end_ms; - - friend std::ostream& operator<<(std::ostream& stream, - const ConcealmentEvent& concealment_event) { - stream << "ConcealmentEvent duration_ms:" << concealment_event.duration_ms - << " event_number:" << concealment_event.concealment_event_number - << " time_from_previous_event_end_ms:" - << concealment_event.time_from_previous_event_end_ms << "\n"; - return stream; - } - }; - - // Takes a pointer to another callback object, which will be invoked after - // this object finishes. This does not transfer ownership, and null is a - // valid value. - explicit StatsGetter(NetEqGetAudioCallback* other_callback) - : other_callback_(other_callback) {} - - void BeforeGetAudio(NetEq* neteq) override { - if (other_callback_) { - other_callback_->BeforeGetAudio(neteq); - } - } - - void AfterGetAudio(int64_t time_now_ms, - const AudioFrame& audio_frame, - bool muted, - NetEq* neteq) override { - if (++counter_ >= 100) { - counter_ = 0; - NetEqNetworkStatistics stats; - RTC_CHECK_EQ(neteq->NetworkStatistics(&stats), 0); - stats_.push_back(stats); - } - const auto lifetime_stat = neteq->GetLifetimeStatistics(); - if (current_concealment_event_ != lifetime_stat.concealment_events && - voice_concealed_samples_until_last_event_ < - lifetime_stat.voice_concealed_samples) { - if (last_event_end_time_ms_ > 0) { - // Do not account for the first event to avoid start of the call - // skewing. - ConcealmentEvent concealment_event; - uint64_t last_event_voice_concealed_samples = - lifetime_stat.voice_concealed_samples - - voice_concealed_samples_until_last_event_; - RTC_CHECK_GT(last_event_voice_concealed_samples, 0); - concealment_event.duration_ms = last_event_voice_concealed_samples / - (audio_frame.sample_rate_hz_ / 1000); - concealment_event.concealment_event_number = current_concealment_event_; - concealment_event.time_from_previous_event_end_ms = - time_now_ms - last_event_end_time_ms_; - concealment_events_.emplace_back(concealment_event); - voice_concealed_samples_until_last_event_ = - lifetime_stat.voice_concealed_samples; - } - last_event_end_time_ms_ = time_now_ms; - voice_concealed_samples_until_last_event_ = - lifetime_stat.voice_concealed_samples; - current_concealment_event_ = lifetime_stat.concealment_events; - } - - if (other_callback_) { - other_callback_->AfterGetAudio(time_now_ms, audio_frame, muted, neteq); - } - } - - double AverageSpeechExpandRate() const { - double sum_speech_expand = - std::accumulate(stats_.begin(), stats_.end(), double{0.0}, - [](double a, NetEqNetworkStatistics b) { - return a + static_cast(b.speech_expand_rate); - }); - return sum_speech_expand / 16384.0 / stats_.size(); - } - - const std::vector& concealment_events() { - // Do not account for the last concealment event to avoid potential end - // call skewing. - return concealment_events_; - } - - Stats AverageStats() const { - Stats sum_stats = std::accumulate( - stats_.begin(), stats_.end(), Stats(), - [](Stats a, NetEqNetworkStatistics b) { - a.current_buffer_size_ms += b.current_buffer_size_ms; - a.preferred_buffer_size_ms += b.preferred_buffer_size_ms; - a.jitter_peaks_found += b.jitter_peaks_found; - a.packet_loss_rate += b.packet_loss_rate / 16384.0; - a.expand_rate += b.expand_rate / 16384.0; - a.speech_expand_rate += b.speech_expand_rate / 16384.0; - a.preemptive_rate += b.preemptive_rate / 16384.0; - a.accelerate_rate += b.accelerate_rate / 16384.0; - a.secondary_decoded_rate += b.secondary_decoded_rate / 16384.0; - a.secondary_discarded_rate += b.secondary_discarded_rate / 16384.0; - a.clockdrift_ppm += b.clockdrift_ppm; - a.added_zero_samples += b.added_zero_samples; - a.mean_waiting_time_ms += b.mean_waiting_time_ms; - a.median_waiting_time_ms += b.median_waiting_time_ms; - a.min_waiting_time_ms = - std::min(a.min_waiting_time_ms, - static_cast(b.min_waiting_time_ms)); - a.max_waiting_time_ms = - std::max(a.max_waiting_time_ms, - static_cast(b.max_waiting_time_ms)); - return a; - }); - - sum_stats.current_buffer_size_ms /= stats_.size(); - sum_stats.preferred_buffer_size_ms /= stats_.size(); - sum_stats.jitter_peaks_found /= stats_.size(); - sum_stats.packet_loss_rate /= stats_.size(); - sum_stats.expand_rate /= stats_.size(); - sum_stats.speech_expand_rate /= stats_.size(); - sum_stats.preemptive_rate /= stats_.size(); - sum_stats.accelerate_rate /= stats_.size(); - sum_stats.secondary_decoded_rate /= stats_.size(); - sum_stats.secondary_discarded_rate /= stats_.size(); - sum_stats.clockdrift_ppm /= stats_.size(); - sum_stats.added_zero_samples /= stats_.size(); - sum_stats.mean_waiting_time_ms /= stats_.size(); - sum_stats.median_waiting_time_ms /= stats_.size(); - - return sum_stats; - } - - private: - NetEqGetAudioCallback* other_callback_; - size_t counter_ = 0; - std::vector stats_; - size_t current_concealment_event_ = 1; - uint64_t voice_concealed_samples_until_last_event_ = 0; - std::vector concealment_events_; - int64_t last_event_end_time_ms_ = 0; -}; - int RunTest(int argc, char* argv[]) { std::string program_name = argv[0]; std::string usage = "Tool for decoding an RTP dump file using NetEq.\n" @@ -675,7 +509,7 @@ int RunTest(int argc, char* argv[]) { SsrcSwitchDetector ssrc_switch_detector(delay_analyzer.get()); callbacks.post_insert_packet = &ssrc_switch_detector; - StatsGetter stats_getter(delay_analyzer.get()); + NetEqStatsGetter stats_getter(std::move(delay_analyzer)); callbacks.get_audio_callback = &stats_getter; NetEq::Config config; config.sample_rate_hz = *sample_rate_hz; @@ -721,11 +555,10 @@ int RunTest(int argc, char* argv[]) { printf(" current_buffer_size_ms: %f ms\n", stats.current_buffer_size_ms); printf(" preferred_buffer_size_ms: %f ms\n", stats.preferred_buffer_size_ms); if (FLAG_concealment_events) { - std::cout << " concealment_events_ms:" - << "\n"; + std::cout << " concealment_events_ms:" << std::endl; for (auto concealment_event : stats_getter.concealment_events()) - std::cout << concealment_event; - std::cout << " end of concealment_events_ms\n"; + std::cout << concealment_event.ToString() << std::endl; + std::cout << " end of concealment_events_ms" << std::endl; } return 0; } diff --git a/modules/audio_coding/neteq/tools/neteq_stats_getter.cc b/modules/audio_coding/neteq/tools/neteq_stats_getter.cc new file mode 100644 index 0000000000..12e10b695a --- /dev/null +++ b/modules/audio_coding/neteq/tools/neteq_stats_getter.cc @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018 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/tools/neteq_stats_getter.h" + +#include +#include +#include + +#include "rtc_base/checks.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +namespace test { + +std::string NetEqStatsGetter::ConcealmentEvent::ToString() const { + char ss_buf[256]; + rtc::SimpleStringBuilder ss(ss_buf); + ss << "ConcealmentEvent duration_ms:" << duration_ms + << " event_number:" << concealment_event_number + << " time_from_previous_event_end_ms:" + << time_from_previous_event_end_ms; + return ss.str(); +} + +NetEqStatsGetter::NetEqStatsGetter( + std::unique_ptr delay_analyzer) + : delay_analyzer_(std::move(delay_analyzer)) {} + +void NetEqStatsGetter::BeforeGetAudio(NetEq* neteq) { + if (delay_analyzer_) { + delay_analyzer_->BeforeGetAudio(neteq); + } +} + +void NetEqStatsGetter::AfterGetAudio(int64_t time_now_ms, + const AudioFrame& audio_frame, + bool muted, + NetEq* neteq) { + if (++counter_ >= 100) { + counter_ = 0; + NetEqNetworkStatistics stats; + RTC_CHECK_EQ(neteq->NetworkStatistics(&stats), 0); + stats_.push_back(stats); + } + const auto lifetime_stat = neteq->GetLifetimeStatistics(); + if (current_concealment_event_ != lifetime_stat.concealment_events && + voice_concealed_samples_until_last_event_ < + lifetime_stat.voice_concealed_samples) { + if (last_event_end_time_ms_ > 0) { + // Do not account for the first event to avoid start of the call + // skewing. + ConcealmentEvent concealment_event; + uint64_t last_event_voice_concealed_samples = + lifetime_stat.voice_concealed_samples - + voice_concealed_samples_until_last_event_; + RTC_CHECK_GT(last_event_voice_concealed_samples, 0); + concealment_event.duration_ms = last_event_voice_concealed_samples / + (audio_frame.sample_rate_hz_ / 1000); + concealment_event.concealment_event_number = current_concealment_event_; + concealment_event.time_from_previous_event_end_ms = + time_now_ms - last_event_end_time_ms_; + concealment_events_.emplace_back(concealment_event); + voice_concealed_samples_until_last_event_ = + lifetime_stat.voice_concealed_samples; + } + last_event_end_time_ms_ = time_now_ms; + voice_concealed_samples_until_last_event_ = + lifetime_stat.voice_concealed_samples; + current_concealment_event_ = lifetime_stat.concealment_events; + } + + if (delay_analyzer_) { + delay_analyzer_->AfterGetAudio(time_now_ms, audio_frame, muted, neteq); + } +} + +double NetEqStatsGetter::AverageSpeechExpandRate() const { + double sum_speech_expand = + std::accumulate(stats_.begin(), stats_.end(), double{0.0}, + [](double a, NetEqNetworkStatistics b) { + return a + static_cast(b.speech_expand_rate); + }); + return sum_speech_expand / 16384.0 / stats_.size(); +} + +NetEqStatsGetter::Stats NetEqStatsGetter::AverageStats() const { + Stats sum_stats = std::accumulate( + stats_.begin(), stats_.end(), Stats(), + [](Stats a, NetEqNetworkStatistics b) { + a.current_buffer_size_ms += b.current_buffer_size_ms; + a.preferred_buffer_size_ms += b.preferred_buffer_size_ms; + a.jitter_peaks_found += b.jitter_peaks_found; + a.packet_loss_rate += b.packet_loss_rate / 16384.0; + a.expand_rate += b.expand_rate / 16384.0; + a.speech_expand_rate += b.speech_expand_rate / 16384.0; + a.preemptive_rate += b.preemptive_rate / 16384.0; + a.accelerate_rate += b.accelerate_rate / 16384.0; + a.secondary_decoded_rate += b.secondary_decoded_rate / 16384.0; + a.secondary_discarded_rate += b.secondary_discarded_rate / 16384.0; + a.clockdrift_ppm += b.clockdrift_ppm; + a.added_zero_samples += b.added_zero_samples; + a.mean_waiting_time_ms += b.mean_waiting_time_ms; + a.median_waiting_time_ms += b.median_waiting_time_ms; + a.min_waiting_time_ms = + std::min(a.min_waiting_time_ms, + static_cast(b.min_waiting_time_ms)); + a.max_waiting_time_ms = + std::max(a.max_waiting_time_ms, + static_cast(b.max_waiting_time_ms)); + return a; + }); + + sum_stats.current_buffer_size_ms /= stats_.size(); + sum_stats.preferred_buffer_size_ms /= stats_.size(); + sum_stats.jitter_peaks_found /= stats_.size(); + sum_stats.packet_loss_rate /= stats_.size(); + sum_stats.expand_rate /= stats_.size(); + sum_stats.speech_expand_rate /= stats_.size(); + sum_stats.preemptive_rate /= stats_.size(); + sum_stats.accelerate_rate /= stats_.size(); + sum_stats.secondary_decoded_rate /= stats_.size(); + sum_stats.secondary_discarded_rate /= stats_.size(); + sum_stats.clockdrift_ppm /= stats_.size(); + sum_stats.added_zero_samples /= stats_.size(); + sum_stats.mean_waiting_time_ms /= stats_.size(); + sum_stats.median_waiting_time_ms /= stats_.size(); + + return sum_stats; +} + +} // namespace test +} // namespace webrtc diff --git a/modules/audio_coding/neteq/tools/neteq_stats_getter.h b/modules/audio_coding/neteq/tools/neteq_stats_getter.h new file mode 100644 index 0000000000..07d53c74d6 --- /dev/null +++ b/modules/audio_coding/neteq/tools/neteq_stats_getter.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 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. + */ + +#ifndef MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_STATS_GETTER_H_ +#define MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_STATS_GETTER_H_ + +#include +#include +#include + +#include "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h" +#include "modules/audio_coding/neteq/tools/neteq_test.h" + +namespace webrtc { +namespace test { + +class NetEqStatsGetter : public NetEqGetAudioCallback { + public: + // This struct is a replica of webrtc::NetEqNetworkStatistics, but with all + // values stored in double precision. + struct Stats { + double current_buffer_size_ms = 0.0; + double preferred_buffer_size_ms = 0.0; + double jitter_peaks_found = 0.0; + double packet_loss_rate = 0.0; + double expand_rate = 0.0; + double speech_expand_rate = 0.0; + double preemptive_rate = 0.0; + double accelerate_rate = 0.0; + double secondary_decoded_rate = 0.0; + double secondary_discarded_rate = 0.0; + double clockdrift_ppm = 0.0; + double added_zero_samples = 0.0; + double mean_waiting_time_ms = 0.0; + double median_waiting_time_ms = 0.0; + double min_waiting_time_ms = 0.0; + double max_waiting_time_ms = 0.0; + }; + + struct ConcealmentEvent { + uint64_t duration_ms; + size_t concealment_event_number; + int64_t time_from_previous_event_end_ms; + std::string ToString() const; + }; + + // Takes a pointer to another callback object, which will be invoked after + // this object finishes. This does not transfer ownership, and null is a + // valid value. + explicit NetEqStatsGetter(std::unique_ptr delay_analyzer); + + void BeforeGetAudio(NetEq* neteq) override; + + void AfterGetAudio(int64_t time_now_ms, + const AudioFrame& audio_frame, + bool muted, + NetEq* neteq) override; + + double AverageSpeechExpandRate() const; + + NetEqDelayAnalyzer* delay_analyzer() const { + return delay_analyzer_.get(); + } + + const std::vector& concealment_events() const { + // Do not account for the last concealment event to avoid potential end + // call skewing. + return concealment_events_; + } + + Stats AverageStats() const; + + private: + std::unique_ptr delay_analyzer_; + size_t counter_ = 0; + std::vector stats_; + size_t current_concealment_event_ = 1; + uint64_t voice_concealed_samples_until_last_event_ = 0; + std::vector concealment_events_; + int64_t last_event_end_time_ms_ = 0; +}; + +} // namespace test +} // namespace webrtc + +#endif // MODULES_AUDIO_CODING_NETEQ_TOOLS_NETEQ_STATS_GETTER_H_