Introduce default video quality analyzer

This implementation won't support spatial layers and simulcast. It will
be added in next CLs.

Bug: webrtc:10138
Change-Id: I08baef36fb15b8d2d2fa222c761d40508de7ff61
Reviewed-on: https://webrtc-review.googlesource.com/c/121944
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Peter Slatala <psla@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26676}
diff --git a/test/pc/e2e/BUILD.gn b/test/pc/e2e/BUILD.gn
index 11293b8..27955a3 100644
--- a/test/pc/e2e/BUILD.gn
+++ b/test/pc/e2e/BUILD.gn
@@ -261,7 +261,7 @@
       "peer_connection_e2e_smoke_test.cc",
     ]
     deps = [
-      ":example_video_quality_analyzer",
+      ":default_video_quality_analyzer",
       "../../../api:callfactory_api",
       "../../../api:libjingle_peerconnection_api",
       "../../../api:scoped_refptr",
@@ -313,3 +313,34 @@
     "api:video_quality_analyzer_api",
   ]
 }
+
+rtc_source_set("default_video_quality_analyzer") {
+  visibility = [ "*" ]
+  testonly = true
+  sources = [
+    "analyzer/video/default_video_quality_analyzer.cc",
+    "analyzer/video/default_video_quality_analyzer.h",
+  ]
+
+  deps = [
+    "../..:perf_test",
+    "../../../api/units:time_delta",
+    "../../../api/units:timestamp",
+    "../../../api/video:encoded_image",
+    "../../../api/video:video_frame",
+    "../../../common_video:common_video",
+    "../../../rtc_base:criticalsection",
+    "../../../rtc_base:logging",
+    "../../../rtc_base:rtc_base_approved",
+    "../../../rtc_base:rtc_event",
+    "../../../rtc_base:rtc_numerics",
+    "../../../system_wrappers:system_wrappers",
+    "api:video_quality_analyzer_api",
+    "//third_party/abseil-cpp/absl/memory:memory",
+  ]
+
+  if (!build_with_chromium && is_clang) {
+    # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
+    suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
+  }
+}
diff --git a/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc
new file mode 100644
index 0000000..9828540
--- /dev/null
+++ b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.cc
@@ -0,0 +1,542 @@
+/*
+ *  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 "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "absl/memory/memory.h"
+#include "api/units/time_delta.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+#include "rtc_base/logging.h"
+#include "test/testsupport/perf_test.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+
+constexpr int kMaxActiveComparisons = 10;
+constexpr int kFreezeThresholdMs = 150;
+
+}  // namespace
+
+void RateCounter::AddEvent(Timestamp event_time) {
+  if (event_first_time_.IsMinusInfinity()) {
+    event_first_time_ = event_time;
+  }
+  event_last_time_ = event_time;
+  event_count_++;
+}
+
+double RateCounter::GetEventsPerSecond() const {
+  RTC_DCHECK(!IsEmpty());
+  return static_cast<double>(event_count_) /
+         (event_last_time_ - event_first_time_).seconds();
+}
+
+DefaultVideoQualityAnalyzer::DefaultVideoQualityAnalyzer(std::string test_label)
+    : test_label_(std::move(test_label)), clock_(Clock::GetRealTimeClock()) {}
+DefaultVideoQualityAnalyzer::~DefaultVideoQualityAnalyzer() {
+  Stop();
+}
+
+void DefaultVideoQualityAnalyzer::Start(int max_threads_count) {
+  for (int i = 0; i < max_threads_count; i++) {
+    auto thread = absl::make_unique<rtc::PlatformThread>(
+        &DefaultVideoQualityAnalyzer::ProcessComparisonsThread, this,
+        ("DefaultVideoQualityAnalyzerWorker-" + std::to_string(i)).data(),
+        rtc::ThreadPriority::kNormalPriority);
+    thread->Start();
+    thread_pool_.push_back(std::move(thread));
+  }
+  {
+    rtc::CritScope crit(&lock_);
+    state_ = State::kActive;
+  }
+}
+
+uint16_t DefaultVideoQualityAnalyzer::OnFrameCaptured(
+    const std::string& stream_label,
+    const webrtc::VideoFrame& frame) {
+  // |next_frame_id| is atomic, so we needn't lock here.
+  uint16_t frame_id = next_frame_id_++;
+  {
+    // Ensure stats for this stream exists.
+    rtc::CritScope crit(&comparison_lock_);
+    if (stream_stats_.find(stream_label) == stream_stats_.end()) {
+      stream_stats_.insert({stream_label, StreamStats()});
+      // Assume that the first freeze was before first stream frame captured.
+      // This way time before the first freeze would be counted as time between
+      // freezes.
+      stream_last_freeze_end_time_.insert({stream_label, Now()});
+    }
+  }
+  {
+    rtc::CritScope crit(&lock_);
+    frame_counters_.captured++;
+    stream_frame_counters_[stream_label].captured++;
+
+    StreamState* state = &stream_states_[stream_label];
+    state->frame_ids.push_back(frame_id);
+    // Update frames in flight info.
+    auto it = captured_frames_in_flight_.find(frame_id);
+    if (it != captured_frames_in_flight_.end()) {
+      // We overflow uint16_t and hit previous frame id and this frame is still
+      // in flight. It means that this stream wasn't rendered for long time and
+      // we need to process existing frame as dropped.
+      auto stats_it = frame_stats_.find(frame_id);
+      RTC_DCHECK(stats_it != frame_stats_.end());
+
+      RTC_DCHECK(frame_id == state->frame_ids.front());
+      state->frame_ids.pop_front();
+      frame_counters_.dropped++;
+      stream_frame_counters_[stream_label].dropped++;
+      AddComparison(it->second, state->last_rendered_frame, true,
+                    stats_it->second);
+
+      captured_frames_in_flight_.erase(it);
+      frame_stats_.erase(stats_it);
+    }
+    captured_frames_in_flight_.insert(
+        std::pair<uint16_t, VideoFrame>(frame_id, frame));
+    // Set frame id on local copy of the frame
+    captured_frames_in_flight_.at(frame_id).set_id(frame_id);
+    frame_stats_.insert(std::pair<uint16_t, FrameStats>(
+        frame_id, FrameStats(stream_label, /*captured_time=*/Now())));
+  }
+  return frame_id;
+}
+
+void DefaultVideoQualityAnalyzer::OnFramePreEncode(
+    const webrtc::VideoFrame& frame) {
+  rtc::CritScope crit(&lock_);
+  auto it = frame_stats_.find(frame.id());
+  RTC_DCHECK(it != frame_stats_.end());
+  frame_counters_.pre_encoded++;
+  stream_frame_counters_[it->second.stream_label].pre_encoded++;
+  it->second.pre_encode_time = Now();
+}
+
+void DefaultVideoQualityAnalyzer::OnFrameEncoded(
+    uint16_t frame_id,
+    const webrtc::EncodedImage& encoded_image) {
+  rtc::CritScope crit(&lock_);
+  // TODO(titovartem) we need to pick right spatial index here.
+  auto it = frame_stats_.find(frame_id);
+  RTC_DCHECK(it != frame_stats_.end());
+  RTC_DCHECK(it->second.encoded_time.IsInfinite())
+      << "Received multiple spatial layers for stream_label="
+      << it->second.stream_label;
+  frame_counters_.encoded++;
+  stream_frame_counters_[it->second.stream_label].encoded++;
+  it->second.encoded_time = Now();
+}
+
+void DefaultVideoQualityAnalyzer::OnFrameDropped(
+    webrtc::EncodedImageCallback::DropReason reason) {
+  // Here we do nothing, because we will see this drop on renderer side.
+}
+
+void DefaultVideoQualityAnalyzer::OnFrameReceived(
+    uint16_t frame_id,
+    const webrtc::EncodedImage& input_image) {
+  // TODO(titovartem) We should always receive only single spatial layer here.
+  rtc::CritScope crit(&lock_);
+  auto it = frame_stats_.find(frame_id);
+  RTC_DCHECK(it != frame_stats_.end());
+  RTC_DCHECK(it->second.received_time.IsInfinite())
+      << "Received multiple spatial layers for stream_label="
+      << it->second.stream_label;
+  frame_counters_.received++;
+  stream_frame_counters_[it->second.stream_label].received++;
+  it->second.received_time = Now();
+}
+
+void DefaultVideoQualityAnalyzer::OnFrameDecoded(
+    const webrtc::VideoFrame& frame,
+    absl::optional<int32_t> decode_time_ms,
+    absl::optional<uint8_t> qp) {
+  rtc::CritScope crit(&lock_);
+  auto it = frame_stats_.find(frame.id());
+  RTC_DCHECK(it != frame_stats_.end());
+  frame_counters_.decoded++;
+  stream_frame_counters_[it->second.stream_label].decoded++;
+  it->second.decoded_time = Now();
+}
+
+void DefaultVideoQualityAnalyzer::OnFrameRendered(
+    const webrtc::VideoFrame& frame) {
+  rtc::CritScope crit(&lock_);
+  auto stats_it = frame_stats_.find(frame.id());
+  RTC_DCHECK(stats_it != frame_stats_.end());
+  FrameStats* frame_stats = &stats_it->second;
+  // Update frames counters.
+  frame_counters_.rendered++;
+  stream_frame_counters_[frame_stats->stream_label].rendered++;
+
+  // Update current frame stats.
+  frame_stats->rendered_time = Now();
+  frame_stats->rendered_frame_width = frame.width();
+  frame_stats->rendered_frame_height = frame.height();
+
+  // Find corresponding captured frame.
+  auto frame_it = captured_frames_in_flight_.find(frame.id());
+  RTC_DCHECK(frame_it != captured_frames_in_flight_.end());
+  const VideoFrame& captured_frame = frame_it->second;
+
+  // After we received frame here we need to check if there are any dropped
+  // frames between this one and last one, that was rendered for this video
+  // stream.
+
+  const std::string& stream_label = frame_stats->stream_label;
+  StreamState* state = &stream_states_[stream_label];
+  int dropped_count = 0;
+  while (!state->frame_ids.empty() && state->frame_ids.front() != frame.id()) {
+    dropped_count++;
+    uint16_t dropped_frame_id = state->frame_ids.front();
+    state->frame_ids.pop_front();
+    // Frame with id |dropped_frame_id| was dropped. We need:
+    // 1. Update global and stream frame counters
+    // 2. Extract corresponding frame from |captured_frames_in_flight_|
+    // 3. Extract corresponding frame stats from |frame_stats_|
+    // 4. Send extracted frame to comparison with dropped=true
+    // 5. Cleanup dropped frame
+    frame_counters_.dropped++;
+    stream_frame_counters_[stream_label].dropped++;
+
+    auto dropped_frame_stats_it = frame_stats_.find(dropped_frame_id);
+    RTC_DCHECK(dropped_frame_stats_it != frame_stats_.end());
+    auto dropped_frame_it = captured_frames_in_flight_.find(dropped_frame_id);
+    RTC_CHECK(dropped_frame_it != captured_frames_in_flight_.end());
+
+    AddComparison(dropped_frame_it->second, state->last_rendered_frame, true,
+                  dropped_frame_stats_it->second);
+
+    frame_stats_.erase(dropped_frame_stats_it);
+    captured_frames_in_flight_.erase(dropped_frame_it);
+  }
+  RTC_DCHECK(!state->frame_ids.empty());
+  state->frame_ids.pop_front();
+
+  state->last_rendered_frame = frame;
+  if (state->last_rendered_frame_time) {
+    frame_stats->prev_frame_rendered_time =
+        state->last_rendered_frame_time.value();
+  }
+  state->last_rendered_frame_time = frame_stats->rendered_time;
+  {
+    rtc::CritScope cr(&comparison_lock_);
+    stream_stats_[stream_label].skipped_between_rendered.AddSample(
+        dropped_count);
+  }
+  AddComparison(captured_frame, frame, false, *frame_stats);
+
+  captured_frames_in_flight_.erase(frame_it);
+  frame_stats_.erase(stats_it);
+}
+
+void DefaultVideoQualityAnalyzer::OnEncoderError(
+    const webrtc::VideoFrame& frame,
+    int32_t error_code) {
+  RTC_LOG(LS_ERROR) << "Encoder error for frame.id=" << frame.id()
+                    << ", code=" << error_code;
+}
+
+void DefaultVideoQualityAnalyzer::OnDecoderError(uint16_t frame_id,
+                                                 int32_t error_code) {
+  RTC_LOG(LS_ERROR) << "Decoder error for frame_id=" << frame_id
+                    << ", code=" << error_code;
+}
+
+void DefaultVideoQualityAnalyzer::Stop() {
+  {
+    rtc::CritScope crit(&lock_);
+    if (state_ == State::kStopped) {
+      return;
+    }
+    state_ = State::kStopped;
+  }
+  comparison_available_event_.Set();
+  for (auto& thread : thread_pool_) {
+    thread->Stop();
+  }
+  // PlatformThread have to be deleted on the same thread, where it was created
+  thread_pool_.clear();
+
+  // Perform final Metrics update. On this place analyzer is stopped and no one
+  // holds any locks.
+  {
+    // Time between freezes.
+    // Count time since the last freeze to the end of the call as time
+    // between freezes.
+    rtc::CritScope crit1(&lock_);
+    rtc::CritScope crit2(&comparison_lock_);
+    for (auto& item : stream_stats_) {
+      if (item.second.freeze_time_ms.IsEmpty()) {
+        continue;
+      }
+      const StreamState& state = stream_states_[item.first];
+      if (state.last_rendered_frame_time) {
+        item.second.time_between_freezes_ms.AddSample(
+            (state.last_rendered_frame_time.value() -
+             stream_last_freeze_end_time_.at(item.first))
+                .ms());
+      }
+    }
+  }
+  ReportResults();
+}
+
+std::set<std::string> DefaultVideoQualityAnalyzer::GetKnownVideoStreams()
+    const {
+  rtc::CritScope crit2(&comparison_lock_);
+  std::set<std::string> out;
+  for (auto& item : stream_stats_) {
+    out.insert(item.first);
+  }
+  return out;
+}
+
+const FrameCounters& DefaultVideoQualityAnalyzer::GetGlobalCounters() {
+  rtc::CritScope crit(&lock_);
+  return frame_counters_;
+}
+
+const std::map<std::string, FrameCounters>&
+DefaultVideoQualityAnalyzer::GetPerStreamCounters() const {
+  rtc::CritScope crit(&lock_);
+  return stream_frame_counters_;
+}
+
+const std::map<std::string, StreamStats>&
+DefaultVideoQualityAnalyzer::GetStats() const {
+  rtc::CritScope cri(&comparison_lock_);
+  return stream_stats_;
+}
+
+const AnalyzerStats& DefaultVideoQualityAnalyzer::GetAnalyzerStats() const {
+  rtc::CritScope crit(&comparison_lock_);
+  return analyzer_stats_;
+}
+
+void DefaultVideoQualityAnalyzer::AddComparison(
+    absl::optional<VideoFrame> captured,
+    absl::optional<VideoFrame> rendered,
+    bool dropped,
+    FrameStats frame_stats) {
+  rtc::CritScope crit(&comparison_lock_);
+  analyzer_stats_.comparisons_queue_size.AddSample(comparisons_.size());
+  // If there too many computations waiting in the queue, we won't provide
+  // frames itself to make future computations lighter.
+  if (comparisons_.size() >= kMaxActiveComparisons) {
+    comparisons_.emplace_back(dropped, frame_stats);
+  } else {
+    comparisons_.emplace_back(std::move(captured), std::move(rendered), dropped,
+                              frame_stats);
+  }
+  comparison_available_event_.Set();
+}
+
+void DefaultVideoQualityAnalyzer::ProcessComparisonsThread(void* obj) {
+  static_cast<DefaultVideoQualityAnalyzer*>(obj)->ProcessComparisons();
+}
+
+void DefaultVideoQualityAnalyzer::ProcessComparisons() {
+  while (true) {
+    // Try to pick next comparison to perform from the queue.
+    absl::optional<FrameComparison> comparison = absl::nullopt;
+    {
+      rtc::CritScope crit(&comparison_lock_);
+      if (!comparisons_.empty()) {
+        comparison = comparisons_.front();
+        comparisons_.pop_front();
+        if (!comparisons_.empty()) {
+          comparison_available_event_.Set();
+        }
+      }
+    }
+    if (!comparison) {
+      bool more_frames_expected;
+      {
+        // If there are no comparisons and state is stopped =>
+        // no more frames expected.
+        rtc::CritScope crit(&lock_);
+        more_frames_expected = state_ != State::kStopped;
+      }
+      if (!more_frames_expected) {
+        comparison_available_event_.Set();
+        return;
+      }
+      comparison_available_event_.Wait(1000);
+      continue;
+    }
+
+    ProcessComparison(comparison.value());
+  }
+}
+
+void DefaultVideoQualityAnalyzer::ProcessComparison(
+    const FrameComparison& comparison) {
+  // Perform expensive psnr and ssim calculations while not holding lock.
+  double psnr = -1.0;
+  double ssim = -1.0;
+  if (comparison.captured && !comparison.dropped) {
+    psnr = I420PSNR(&*comparison.captured, &*comparison.rendered);
+    ssim = I420SSIM(&*comparison.captured, &*comparison.rendered);
+  }
+
+  const FrameStats& frame_stats = comparison.frame_stats;
+
+  rtc::CritScope crit(&comparison_lock_);
+  auto stats_it = stream_stats_.find(frame_stats.stream_label);
+  RTC_CHECK(stats_it != stream_stats_.end());
+  StreamStats* stats = &stats_it->second;
+  analyzer_stats_.comparisons_done++;
+  if (!comparison.captured) {
+    analyzer_stats_.overloaded_comparisons_done++;
+  }
+  if (psnr > 0) {
+    stats->psnr.AddSample(psnr);
+  }
+  if (ssim > 0) {
+    stats->ssim.AddSample(ssim);
+  }
+  if (frame_stats.encoded_time.IsFinite()) {
+    stats->encode_time_ms.AddSample(
+        (frame_stats.encoded_time - frame_stats.pre_encode_time).ms());
+    stats->encode_frame_rate.AddEvent(frame_stats.encoded_time);
+  } else {
+    if (frame_stats.pre_encode_time.IsFinite()) {
+      stats->dropped_by_encoder++;
+    } else {
+      stats->dropped_before_encoder++;
+    }
+  }
+  // Next stats can be calculated only if frame was received on remote side.
+  if (!comparison.dropped) {
+    stats->resolution_of_encoded_image.AddSample(
+        *comparison.frame_stats.rendered_frame_width *
+        *comparison.frame_stats.rendered_frame_height);
+    stats->transport_time_ms.AddSample(
+        (frame_stats.received_time - frame_stats.encoded_time).ms());
+    stats->total_delay_incl_transport_ms.AddSample(
+        (frame_stats.rendered_time - frame_stats.captured_time).ms());
+    stats->decode_time_ms.AddSample(
+        (frame_stats.decoded_time - frame_stats.received_time).ms());
+
+    if (frame_stats.prev_frame_rendered_time.IsFinite()) {
+      TimeDelta time_between_rendered_frames =
+          frame_stats.rendered_time - frame_stats.prev_frame_rendered_time;
+      stats->time_between_rendered_frames_ms.AddSample(
+          time_between_rendered_frames.ms());
+      double average_time_between_rendered_frames_ms =
+          stats->time_between_rendered_frames_ms.GetAverage();
+      if (time_between_rendered_frames.ms() >
+          std::max(kFreezeThresholdMs + average_time_between_rendered_frames_ms,
+                   3 * average_time_between_rendered_frames_ms)) {
+        stats->freeze_time_ms.AddSample(time_between_rendered_frames.ms());
+        auto freeze_end_it =
+            stream_last_freeze_end_time_.find(frame_stats.stream_label);
+        RTC_DCHECK(freeze_end_it != stream_last_freeze_end_time_.end());
+        stats->time_between_freezes_ms.AddSample(
+            (frame_stats.prev_frame_rendered_time - freeze_end_it->second)
+                .ms());
+        freeze_end_it->second = frame_stats.rendered_time;
+      }
+    }
+  }
+}
+
+void DefaultVideoQualityAnalyzer::ReportResults() const {
+  rtc::CritScope crit1(&lock_);
+  rtc::CritScope crit2(&comparison_lock_);
+  for (auto& item : stream_stats_) {
+    ReportResults(GetTestCaseName(item.first), item.second,
+                  stream_frame_counters_.at(item.first));
+  }
+}
+
+void DefaultVideoQualityAnalyzer::ReportResults(std::string test_case_name,
+                                                StreamStats stats,
+                                                FrameCounters frame_counters) {
+  ReportResult("psnr", test_case_name, stats.psnr, "dB");
+  ReportResult("ssim", test_case_name, stats.ssim, "unitless");
+  ReportResult("transport_time", test_case_name, stats.transport_time_ms, "ms");
+  ReportResult("total_delay_incl_transport", test_case_name,
+               stats.total_delay_incl_transport_ms, "ms");
+  ReportResult("time_between_rendered_frames", test_case_name,
+               stats.time_between_rendered_frames_ms, "ms");
+  test::PrintResult("encode_frame_rate", "", test_case_name,
+                    stats.encode_frame_rate.IsEmpty()
+                        ? 0
+                        : stats.encode_frame_rate.GetEventsPerSecond(),
+                    "fps", /*important=*/false);
+  ReportResult("encode_time", test_case_name, stats.encode_time_ms, "ms");
+  ReportResult("time_between_freezes", test_case_name,
+               stats.time_between_freezes_ms, "ms");
+  ReportResult("pixels_per_frame", test_case_name,
+               stats.resolution_of_encoded_image, "unitless");
+  test::PrintResult("min_psnr", "", test_case_name,
+                    stats.psnr.IsEmpty() ? 0 : stats.psnr.GetMin(), "dB",
+                    /*important=*/false);
+  ReportResult("decode_time", test_case_name, stats.decode_time_ms, "ms");
+  test::PrintResult("dropped_frames", "", test_case_name,
+                    frame_counters.dropped, "unitless",
+                    /*important=*/false);
+  ReportResult("max_skipped", test_case_name, stats.skipped_between_rendered,
+               "unitless");
+}
+
+void DefaultVideoQualityAnalyzer::ReportResult(
+    const std::string& metric_name,
+    const std::string& test_case_name,
+    const SamplesStatsCounter& counter,
+    const std::string& unit) {
+  test::PrintResultMeanAndError(
+      metric_name, /*modifier=*/"", test_case_name,
+      counter.IsEmpty() ? 0 : counter.GetAverage(),
+      counter.IsEmpty() ? 0 : counter.GetStandardDeviation(), unit,
+      /*important=*/false);
+}
+
+std::string DefaultVideoQualityAnalyzer::GetTestCaseName(
+    const std::string& stream_label) const {
+  return test_label_ + "/" + stream_label;
+}
+
+Timestamp DefaultVideoQualityAnalyzer::Now() {
+  return Timestamp::us(clock_->TimeInMicroseconds());
+}
+
+DefaultVideoQualityAnalyzer::FrameStats::FrameStats(std::string stream_label,
+                                                    Timestamp captured_time)
+    : stream_label(std::move(stream_label)), captured_time(captured_time) {}
+
+DefaultVideoQualityAnalyzer::FrameComparison::FrameComparison(
+    absl::optional<VideoFrame> captured,
+    absl::optional<VideoFrame> rendered,
+    bool dropped,
+    FrameStats frame_stats)
+    : captured(std::move(captured)),
+      rendered(std::move(rendered)),
+      dropped(dropped),
+      frame_stats(std::move(frame_stats)) {}
+
+DefaultVideoQualityAnalyzer::FrameComparison::FrameComparison(
+    bool dropped,
+    FrameStats frame_stats)
+    : captured(absl::nullopt),
+      rendered(absl::nullopt),
+      dropped(dropped),
+      frame_stats(std::move(frame_stats)) {}
+
+}  // namespace test
+}  // namespace webrtc
diff --git a/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h
new file mode 100644
index 0000000..2e28829
--- /dev/null
+++ b/test/pc/e2e/analyzer/video/default_video_quality_analyzer.h
@@ -0,0 +1,280 @@
+/*
+ *  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.
+ */
+
+#ifndef TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_H_
+#define TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_H_
+
+#include <atomic>
+#include <deque>
+#include <list>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "api/units/timestamp.h"
+#include "api/video/encoded_image.h"
+#include "api/video/video_frame.h"
+#include "rtc_base/critical_section.h"
+#include "rtc_base/event.h"
+#include "rtc_base/numerics/samples_stats_counter.h"
+#include "rtc_base/platform_thread.h"
+#include "system_wrappers/include/clock.h"
+#include "test/pc/e2e/api/video_quality_analyzer_interface.h"
+
+namespace webrtc {
+namespace test {
+
+class RateCounter {
+ public:
+  void AddEvent(Timestamp event_time);
+
+  bool IsEmpty() const { return event_first_time_ == event_last_time_; }
+
+  double GetEventsPerSecond() const;
+
+ private:
+  Timestamp event_first_time_ = Timestamp::MinusInfinity();
+  Timestamp event_last_time_ = Timestamp::MinusInfinity();
+  int64_t event_count_ = 0;
+};
+
+struct FrameCounters {
+  // Count of frames, that were passed into WebRTC pipeline by video stream
+  // source.
+  int64_t captured = 0;
+  // Count of frames that reached video encoder.
+  int64_t pre_encoded = 0;
+  // Count of encoded images that were produced by encoder for all requested
+  // spatial layers and simulcast streams.
+  int64_t encoded = 0;
+  // Count of encoded images received in decoder for all requested spatial
+  // layers and simulcast streams.
+  int64_t received = 0;
+  // Count of frames that were produced by decoder.
+  int64_t decoded = 0;
+  // Count of frames that went out from WebRTC pipeline to video sink.
+  int64_t rendered = 0;
+  // Count of frames that were dropped in any point between capturing and
+  // rendering.
+  int64_t dropped = 0;
+};
+
+struct StreamStats {
+ public:
+  SamplesStatsCounter psnr;
+  SamplesStatsCounter ssim;
+  // Time from frame encoded (time point on exit from encoder) to the
+  // encoded image received in decoder (time point on entrance to decoder).
+  SamplesStatsCounter transport_time_ms;
+  // Time from frame was captured on device to time frame was displayed on
+  // device.
+  SamplesStatsCounter total_delay_incl_transport_ms;
+  // Time between frames out from renderer.
+  SamplesStatsCounter time_between_rendered_frames_ms;
+  RateCounter encode_frame_rate;
+  SamplesStatsCounter encode_time_ms;
+  SamplesStatsCounter decode_time_ms;
+  // Max frames skipped between two nearest.
+  SamplesStatsCounter skipped_between_rendered;
+  // In the next 2 metrics freeze is a pause that is longer, than maximum:
+  //  1. 150ms
+  //  2. 3 * average time between two sequential frames.
+  // Item 1 will cover high fps video and is a duration, that is noticeable by
+  // human eye. Item 2 will cover low fps video like screen sharing.
+  // Freeze duration.
+  SamplesStatsCounter freeze_time_ms;
+  // Mean time between one freeze end and next freeze start.
+  SamplesStatsCounter time_between_freezes_ms;
+  SamplesStatsCounter resolution_of_encoded_image;
+
+  int64_t dropped_by_encoder = 0;
+  int64_t dropped_before_encoder = 0;
+};
+
+struct AnalyzerStats {
+ public:
+  // Size of analyzer internal comparisons queue, measured when new element
+  // id added to the queue.
+  SamplesStatsCounter comparisons_queue_size;
+  // Amount of performed comparisons of 2 video frames from captured and
+  // rendered streams.
+  int64_t comparisons_done = 0;
+  // Amount of overloaded comparisons. Comparison is overloaded if it is queued
+  // when there are too many not processed comparisons in the queue. Overloaded
+  // comparison doesn't include metrics, that require heavy computations like
+  // SSIM and PSNR.
+  int64_t overloaded_comparisons_done = 0;
+};
+
+class DefaultVideoQualityAnalyzer : public VideoQualityAnalyzerInterface {
+ public:
+  explicit DefaultVideoQualityAnalyzer(std::string test_label);
+  ~DefaultVideoQualityAnalyzer() override;
+
+  void Start(int max_threads_count) override;
+  uint16_t OnFrameCaptured(const std::string& stream_label,
+                           const VideoFrame& frame) override;
+  void OnFramePreEncode(const VideoFrame& frame) override;
+  void OnFrameEncoded(uint16_t frame_id,
+                      const EncodedImage& encoded_image) override;
+  void OnFrameDropped(EncodedImageCallback::DropReason reason) override;
+  void OnFrameReceived(uint16_t frame_id,
+                       const EncodedImage& input_image) override;
+  void OnFrameDecoded(const VideoFrame& frame,
+                      absl::optional<int32_t> decode_time_ms,
+                      absl::optional<uint8_t> qp) override;
+  void OnFrameRendered(const VideoFrame& frame) override;
+  void OnEncoderError(const VideoFrame& frame, int32_t error_code) override;
+  void OnDecoderError(uint16_t frame_id, int32_t error_code) override;
+  void Stop() override;
+
+  // Returns set of stream labels, that were met during test call.
+  std::set<std::string> GetKnownVideoStreams() const;
+  const FrameCounters& GetGlobalCounters();
+  // Returns frame counter per stream label. Valid stream labels can be obtained
+  // by calling GetKnownVideoStreams()
+  const std::map<std::string, FrameCounters>& GetPerStreamCounters() const;
+  // Returns video quality stats per stream label. Valid stream labels can be
+  // obtained by calling GetKnownVideoStreams()
+  const std::map<std::string, StreamStats>& GetStats() const;
+  const AnalyzerStats& GetAnalyzerStats() const;
+
+ private:
+  struct FrameStats {
+    FrameStats(std::string stream_label, Timestamp captured_time);
+
+    std::string stream_label;
+
+    // Frame events timestamp.
+    Timestamp captured_time;
+    Timestamp pre_encode_time = Timestamp::MinusInfinity();
+    Timestamp encoded_time = Timestamp::MinusInfinity();
+    Timestamp received_time = Timestamp::MinusInfinity();
+    Timestamp decoded_time = Timestamp::MinusInfinity();
+    Timestamp rendered_time = Timestamp::MinusInfinity();
+    Timestamp prev_frame_rendered_time = Timestamp::MinusInfinity();
+
+    absl::optional<int> rendered_frame_width = absl::nullopt;
+    absl::optional<int> rendered_frame_height = absl::nullopt;
+  };
+
+  // Represents comparison between two VideoFrames. Contains video frames itself
+  // and stats. Can be one of two types:
+  //   1. Normal - in this case |captured| is presented and either |rendered| is
+  //      presented and |dropped| is false, either |rendered| is omitted and
+  //      |dropped| is true.
+  //   2. Overloaded - in this case both |captured| and |rendered| are omitted
+  //      because there were too many comparisons in the queue. |dropped| can be
+  //      true or false showing was frame dropped or not.
+  struct FrameComparison {
+    FrameComparison(absl::optional<VideoFrame> captured,
+                    absl::optional<VideoFrame> rendered,
+                    bool dropped,
+                    FrameStats frame_stats);
+    FrameComparison(bool dropped, FrameStats frameStats);
+
+    // Frames can be omitted if there too many computations waiting in the
+    // queue.
+    absl::optional<VideoFrame> captured;
+    absl::optional<VideoFrame> rendered;
+    // If true frame was dropped somewhere from capturing to rendering and
+    // wasn't rendered on remote peer side. If |dropped| is true, |rendered|
+    // will be |absl::nullopt|.
+    bool dropped;
+    FrameStats frame_stats;
+  };
+
+  // Represents a current state of video stream.
+  struct StreamState {
+    // To correctly determine dropped frames we have to know sequence of frames
+    // in each stream so we will keep a list of frame ids inside the stream.
+    // When the frame is rendered, we will pop ids from the list for until id
+    // will match with rendered one. All ids before matched one can be
+    // considered as dropped:
+    //
+    // | frame_id1 |->| frame_id2 |->| frame_id3 |->| frame_id4 |
+    //
+    // If we received frame with id frame_id3, then we will pop frame_id1 and
+    // frame_id2 and consider that frames as dropped and then compare received
+    // frame with the one from |captured_frames_in_flight_| with id frame_id3.
+    // Also we will put it into the |last_rendered_frame|.
+    std::list<uint16_t> frame_ids;
+    absl::optional<VideoFrame> last_rendered_frame = absl::nullopt;
+    absl::optional<Timestamp> last_rendered_frame_time = absl::nullopt;
+  };
+
+  enum State { kNew, kActive, kStopped };
+
+  // Returns last rendered frame for stream if there is one or nullptr
+  // otherwise.
+  VideoFrame* GetLastRenderedFrame(const std::string& stream_label)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_);
+  void SetLastRenderedFrame(const std::string& stream_label,
+                            const VideoFrame& frame)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_);
+
+  void AddComparison(absl::optional<VideoFrame> captured,
+                     absl::optional<VideoFrame> rendered,
+                     bool dropped,
+                     FrameStats frame_stats);
+  static void ProcessComparisonsThread(void* obj);
+  void ProcessComparisons();
+  void ProcessComparison(const FrameComparison& comparison);
+  // Report results for all metrics for all streams.
+  void ReportResults() const;
+  static void ReportResults(std::string test_case_name,
+                            StreamStats stats,
+                            FrameCounters frame_counters);
+  // Report result for single metric for specified stream.
+  static void ReportResult(const std::string& metric_name,
+                           const std::string& test_case_name,
+                           const SamplesStatsCounter& counter,
+                           const std::string& unit);
+  // Returns name of current test case for reporting.
+  std::string GetTestCaseName(const std::string& stream_label) const;
+  Timestamp Now();
+
+  const std::string test_label_;
+
+  webrtc::Clock* const clock_;
+  std::atomic<uint16_t> next_frame_id_{0};
+
+  rtc::CriticalSection lock_;
+  State state_ RTC_GUARDED_BY(lock_) = State::kNew;
+  // Frames that were captured by all streams and still aren't rendered by any
+  // stream or deemed dropped.
+  std::map<uint16_t, VideoFrame> captured_frames_in_flight_
+      RTC_GUARDED_BY(lock_);
+  // Global frames count for all video streams.
+  FrameCounters frame_counters_ RTC_GUARDED_BY(lock_);
+  // Frame counters per each stream.
+  std::map<std::string, FrameCounters> stream_frame_counters_
+      RTC_GUARDED_BY(lock_);
+  std::map<uint16_t, FrameStats> frame_stats_ RTC_GUARDED_BY(lock_);
+  std::map<std::string, StreamState> stream_states_ RTC_GUARDED_BY(lock_);
+
+  rtc::CriticalSection comparison_lock_;
+  std::map<std::string, StreamStats> stream_stats_
+      RTC_GUARDED_BY(comparison_lock_);
+  std::map<std::string, Timestamp> stream_last_freeze_end_time_
+      RTC_GUARDED_BY(comparison_lock_);
+  std::deque<FrameComparison> comparisons_ RTC_GUARDED_BY(comparison_lock_);
+  AnalyzerStats analyzer_stats_ RTC_GUARDED_BY(comparison_lock_);
+
+  std::vector<std::unique_ptr<rtc::PlatformThread>> thread_pool_;
+  rtc::Event comparison_available_event_;
+};
+
+}  // namespace test
+}  // namespace webrtc
+
+#endif  // TEST_PC_E2E_ANALYZER_VIDEO_DEFAULT_VIDEO_QUALITY_ANALYZER_H_
diff --git a/test/pc/e2e/peer_connection_e2e_smoke_test.cc b/test/pc/e2e/peer_connection_e2e_smoke_test.cc
index a126580..0cfe3d9 100644
--- a/test/pc/e2e/peer_connection_e2e_smoke_test.cc
+++ b/test/pc/e2e/peer_connection_e2e_smoke_test.cc
@@ -16,7 +16,7 @@
 #include "rtc_base/async_invoker.h"
 #include "rtc_base/fake_network.h"
 #include "test/gtest.h"
-#include "test/pc/e2e/analyzer/video/example_video_quality_analyzer.h"
+#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h"
 #include "test/pc/e2e/api/create_peerconnection_quality_test_fixture.h"
 #include "test/pc/e2e/api/peerconnection_quality_test_fixture.h"
 #include "test/scenario/network/network_emulation.h"
@@ -101,8 +101,8 @@
   // Create analyzers.
   auto analyzers = absl::make_unique<Analyzers>();
   analyzers->video_quality_analyzer =
-      absl::make_unique<ExampleVideoQualityAnalyzer>();
-  auto* video_analyzer = static_cast<ExampleVideoQualityAnalyzer*>(
+      absl::make_unique<DefaultVideoQualityAnalyzer>("smoke_test");
+  auto* video_analyzer = static_cast<DefaultVideoQualityAnalyzer*>(
       analyzers->video_quality_analyzer.get());
 
   auto fixture =
@@ -111,17 +111,16 @@
                std::move(bob_components), absl::make_unique<Params>(),
                RunParams{TimeDelta::seconds(5)});
 
-  RTC_LOG(INFO) << "Captured: " << video_analyzer->frames_captured();
-  RTC_LOG(INFO) << "Sent    : " << video_analyzer->frames_sent();
-  RTC_LOG(INFO) << "Received: " << video_analyzer->frames_received();
-  RTC_LOG(INFO) << "Rendered: " << video_analyzer->frames_rendered();
-  RTC_LOG(INFO) << "Dropped : " << video_analyzer->frames_dropped();
-
-  // 150 = 30fps * 5s
-  EXPECT_GE(video_analyzer->frames_captured(), 150lu);
-  // EXPECT_NEAR(video_analyzer->frames_sent(), 150, 15);
-  // EXPECT_NEAR(video_analyzer->frames_received(), 150, 15);
-  // EXPECT_NEAR(video_analyzer->frames_rendered(), 150, 15);
+  // 150 = 30fps * 5s. On some devices pipeline can be too slow, so it can
+  // happen, that frames will stuck in the middle, so we actually can't force
+  // real constraints here, so lets just check, that at least 1 frame passed
+  // whole pipeline.
+  EXPECT_GE(video_analyzer->GetGlobalCounters().captured, 150);
+  EXPECT_GE(video_analyzer->GetGlobalCounters().pre_encoded, 1);
+  EXPECT_GE(video_analyzer->GetGlobalCounters().encoded, 1);
+  EXPECT_GE(video_analyzer->GetGlobalCounters().received, 1);
+  EXPECT_GE(video_analyzer->GetGlobalCounters().decoded, 1);
+  EXPECT_GE(video_analyzer->GetGlobalCounters().rendered, 1);
 }
 
 }  // namespace test