Revert "Create new API for RtcEventLogParser."

This reverts commit 9e336ec0b8a77c3461d13677cff3563c11c88daa.

Reason for revert: Code can accidentally include the deprecated parser but link with the new one, or vice versa. Reverting to fix naming.

Original change's description:
> Create new API for RtcEventLogParser.
> 
> The new API stores events gathered by event type. For example, it is
> possible to ask fo a list of all incoming RTCP messages or all audio
> playout events.
> 
> The new API is experimental and may change over next few weeks. Once
> it has stabilized and all unit tests and existing tools have been
> ported to the new API, the old one will be removed.
> 
> This CL also updates the event_log_visualizer tool to use the new
> parser API. This is not a funcional change except for:
> - Incoming and outgoing audio level are now drawn in two separate plots.
> - Incoming and outgoing timstamps are now drawn in two separate plots.
> - RTCP count is no longer split into Video and Audio. It also counts
>   all RTCP packets rather than only specific message types.
> - Slight timing difference in sendside BWE simulation due to only
>   iterating over transport feedbacks and not over all RTCP packets.
>   This timing changes are not visible in the plots.
> 
> 
> Media type for RTCP messages might not be identified correctly by
> rtc_event_log2text anymore. On the other hand, assigning a specific
> media type to an RTCP packet was a bit hacky to begin with.
> 
> Bug: webrtc:8111
> Change-Id: I8e7168302beb69b2e163a097a2a142b86dd4a26b
> Reviewed-on: https://webrtc-review.googlesource.com/60865
> Reviewed-by: Minyue Li <minyue@webrtc.org>
> Reviewed-by: Sebastian Jansson <srte@webrtc.org>
> Commit-Queue: Björn Terelius <terelius@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#23015}

TBR=terelius@webrtc.org,srte@webrtc.org,minyue@webrtc.org

Change-Id: Ib4bbcf0563423675a3cc1dce59ebf665e0c5dae9
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: webrtc:8111
Reviewed-on: https://webrtc-review.googlesource.com/72500
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Commit-Queue: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23026}
diff --git a/rtc_tools/event_log_visualizer/analyzer.cc b/rtc_tools/event_log_visualizer/analyzer.cc
index c780632..1a7309c 100644
--- a/rtc_tools/event_log_visualizer/analyzer.cc
+++ b/rtc_tools/event_log_visualizer/analyzer.cc
@@ -14,6 +14,7 @@
 #include <cmath>
 #include <limits>
 #include <map>
+#include <sstream>
 #include <string>
 #include <utility>
 
@@ -24,7 +25,6 @@
 #include "call/video_send_stream.h"
 #include "common_types.h"  // NOLINT(build/include)
 #include "logging/rtc_event_log/rtc_stream_config.h"
-#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
 #include "modules/audio_coding/neteq/tools/audio_sink.h"
 #include "modules/audio_coding/neteq/tools/fake_decode_from_file.h"
 #include "modules/audio_coding/neteq/tools/neteq_delay_analyzer.h"
@@ -40,7 +40,6 @@
 #include "modules/pacing/packet_router.h"
 #include "modules/rtp_rtcp/include/rtp_rtcp.h"
 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
-#include "modules/rtp_rtcp/source/rtcp_packet.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/remb.h"
@@ -50,7 +49,6 @@
 #include "modules/rtp_rtcp/source/rtp_utility.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/format_macros.h"
-#include "rtc_base/function_view.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/numerics/sequence_number_util.h"
 #include "rtc_base/ptr_util.h"
@@ -61,6 +59,7 @@
 #endif  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
 
 namespace webrtc {
+namespace plotting {
 
 namespace {
 
@@ -128,45 +127,29 @@
   return difference;
 }
 
-// This is much more reliable for outgoing streams than for incoming streams.
-template <typename RtpPacketContainer>
-rtc::Optional<uint32_t> EstimateRtpClockFrequency(
-    const RtpPacketContainer& packets,
-    int64_t end_time_us) {
-  RTC_CHECK(packets.size() >= 2);
-  SeqNumUnwrapper<uint32_t> unwrapper;
-  uint64_t first_rtp_timestamp =
-      unwrapper.Unwrap(packets[0].rtp.header.timestamp);
-  int64_t first_log_timestamp = packets[0].log_time_us();
-  uint64_t last_rtp_timestamp = first_rtp_timestamp;
-  int64_t last_log_timestamp = first_log_timestamp;
-  for (size_t i = 1; i < packets.size(); i++) {
-    if (packets[i].log_time_us() > end_time_us)
-      break;
-    last_rtp_timestamp = unwrapper.Unwrap(packets[i].rtp.header.timestamp);
-    last_log_timestamp = packets[i].log_time_us();
-  }
-  if (last_log_timestamp - first_log_timestamp < kNumMicrosecsPerSec) {
-    RTC_LOG(LS_WARNING)
-        << "Failed to estimate RTP clock frequency: Stream too short. ("
-        << packets.size() << " packets, "
-        << last_log_timestamp - first_log_timestamp << " us)";
-    return rtc::nullopt;
-  }
-  double duration =
-      static_cast<double>(last_log_timestamp - first_log_timestamp) /
-      kNumMicrosecsPerSec;
-  double estimated_frequency =
-      (last_rtp_timestamp - first_rtp_timestamp) / duration;
-  for (uint32_t f : {8000, 16000, 32000, 48000, 90000}) {
-    if (std::fabs(estimated_frequency - f) < 0.05 * f) {
-      return f;
-    }
-  }
-  RTC_LOG(LS_WARNING) << "Failed to estimate RTP clock frequency: Estimate "
-                      << estimated_frequency
-                      << "not close to any stardard RTP frequency.";
-  return rtc::nullopt;
+// Return default values for header extensions, to use on streams without stored
+// mapping data. Currently this only applies to audio streams, since the mapping
+// is not stored in the event log.
+// TODO(ivoc): Remove this once this mapping is stored in the event log for
+//             audio streams. Tracking bug: webrtc:6399
+webrtc::RtpHeaderExtensionMap GetDefaultHeaderExtensionMap() {
+  webrtc::RtpHeaderExtensionMap default_map;
+  default_map.Register<AudioLevel>(webrtc::RtpExtension::kAudioLevelDefaultId);
+  default_map.Register<TransmissionOffset>(
+      webrtc::RtpExtension::kTimestampOffsetDefaultId);
+  default_map.Register<AbsoluteSendTime>(
+      webrtc::RtpExtension::kAbsSendTimeDefaultId);
+  default_map.Register<VideoOrientation>(
+      webrtc::RtpExtension::kVideoRotationDefaultId);
+  default_map.Register<VideoContentTypeExtension>(
+      webrtc::RtpExtension::kVideoContentTypeDefaultId);
+  default_map.Register<VideoTimingExtension>(
+      webrtc::RtpExtension::kVideoTimingDefaultId);
+  default_map.Register<TransportSequenceNumber>(
+      webrtc::RtpExtension::kTransportSequenceNumberDefaultId);
+  default_map.Register<PlayoutDelayLimits>(
+      webrtc::RtpExtension::kPlayoutDelayDefaultId);
+  return default_map;
 }
 
 constexpr float kLeftMargin = 0.01f;
@@ -182,8 +165,7 @@
     int64_t send_time_diff = WrappingDifference(
         new_packet.header.extension.absoluteSendTime,
         old_packet.header.extension.absoluteSendTime, 1ul << 24);
-    int64_t recv_time_diff =
-        new_packet.log_time_us() - old_packet.log_time_us();
+    int64_t recv_time_diff = new_packet.timestamp - old_packet.timestamp;
     double delay_change_us =
         recv_time_diff - AbsSendTimeToMicroseconds(send_time_diff);
     return delay_change_us / 1000;
@@ -197,7 +179,7 @@
     const LoggedRtpPacket& new_packet) {
   int64_t send_time_diff = WrappingDifference(
       new_packet.header.timestamp, old_packet.header.timestamp, 1ull << 32);
-  int64_t recv_time_diff = new_packet.log_time_us() - old_packet.log_time_us();
+  int64_t recv_time_diff = new_packet.timestamp - old_packet.timestamp;
 
   const double kVideoSampleRate = 90000;
   // TODO(terelius): We treat all streams as video for now, even though
@@ -211,9 +193,9 @@
   if (delay_change < -10000 || 10000 < delay_change) {
     RTC_LOG(LS_WARNING) << "Very large delay change. Timestamps correct?";
     RTC_LOG(LS_WARNING) << "Old capture time " << old_packet.header.timestamp
-                        << ", received time " << old_packet.log_time_us();
+                        << ", received time " << old_packet.timestamp;
     RTC_LOG(LS_WARNING) << "New capture time " << new_packet.header.timestamp
-                        << ", received time " << new_packet.log_time_us();
+                        << ", received time " << new_packet.timestamp;
     RTC_LOG(LS_WARNING) << "Receive time difference " << recv_time_diff << " = "
                         << static_cast<double>(recv_time_diff) /
                                kNumMicrosecsPerSec
@@ -226,55 +208,55 @@
   return delay_change;
 }
 
-// For each element in data_view, use |f()| to extract a y-coordinate and
+// For each element in data, use |get_y()| to extract a y-coordinate and
 // store the result in a TimeSeries.
-template <typename DataType, typename IterableType>
-void ProcessPoints(rtc::FunctionView<rtc::Optional<float>(const DataType&)> f,
-                   const IterableType& data_view,
-                   int64_t begin_time,
-                   TimeSeries* result) {
-  for (size_t i = 0; i < data_view.size(); i++) {
-    const DataType& elem = data_view[i];
-    float x = static_cast<float>(elem.log_time_us() - begin_time) /
+template <typename DataType>
+void ProcessPoints(
+    rtc::FunctionView<rtc::Optional<float>(const DataType&)> get_y,
+    const std::vector<DataType>& data,
+    uint64_t begin_time,
+    TimeSeries* result) {
+  for (size_t i = 0; i < data.size(); i++) {
+    float x = static_cast<float>(data[i].timestamp - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<float> y = f(elem);
+    rtc::Optional<float> y = get_y(data[i]);
     if (y)
       result->points.emplace_back(x, *y);
   }
 }
 
-// For each pair of adjacent elements in |data|, use |f()| to extract a
+// For each pair of adjacent elements in |data|, use |get_y| to extract a
 // y-coordinate and store the result in a TimeSeries. Note that the x-coordinate
 // will be the time of the second element in the pair.
-template <typename DataType, typename ResultType, typename IterableType>
+template <typename DataType, typename ResultType>
 void ProcessPairs(
     rtc::FunctionView<rtc::Optional<ResultType>(const DataType&,
-                                                const DataType&)> f,
-    const IterableType& data,
-    int64_t begin_time,
+                                                const DataType&)> get_y,
+    const std::vector<DataType>& data,
+    uint64_t begin_time,
     TimeSeries* result) {
   for (size_t i = 1; i < data.size(); i++) {
-    float x = static_cast<float>(data[i].log_time_us() - begin_time) /
+    float x = static_cast<float>(data[i].timestamp - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<ResultType> y = f(data[i - 1], data[i]);
+    rtc::Optional<ResultType> y = get_y(data[i - 1], data[i]);
     if (y)
       result->points.emplace_back(x, static_cast<float>(*y));
   }
 }
 
-// For each element in data, use |f()| to extract a y-coordinate and
+// For each element in data, use |extract()| to extract a y-coordinate and
 // store the result in a TimeSeries.
-template <typename DataType, typename ResultType, typename IterableType>
+template <typename DataType, typename ResultType>
 void AccumulatePoints(
-    rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> f,
-    const IterableType& data,
-    int64_t begin_time,
+    rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> extract,
+    const std::vector<DataType>& data,
+    uint64_t begin_time,
     TimeSeries* result) {
   ResultType sum = 0;
   for (size_t i = 0; i < data.size(); i++) {
-    float x = static_cast<float>(data[i].log_time_us() - begin_time) /
+    float x = static_cast<float>(data[i].timestamp - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<ResultType> y = f(data[i]);
+    rtc::Optional<ResultType> y = extract(data[i]);
     if (y) {
       sum += *y;
       result->points.emplace_back(x, static_cast<float>(sum));
@@ -282,21 +264,21 @@
   }
 }
 
-// For each pair of adjacent elements in |data|, use |f()| to extract a
+// For each pair of adjacent elements in |data|, use |extract()| to extract a
 // y-coordinate and store the result in a TimeSeries. Note that the x-coordinate
 // will be the time of the second element in the pair.
-template <typename DataType, typename ResultType, typename IterableType>
+template <typename DataType, typename ResultType>
 void AccumulatePairs(
     rtc::FunctionView<rtc::Optional<ResultType>(const DataType&,
-                                                const DataType&)> f,
-    const IterableType& data,
-    int64_t begin_time,
+                                                const DataType&)> extract,
+    const std::vector<DataType>& data,
+    uint64_t begin_time,
     TimeSeries* result) {
   ResultType sum = 0;
   for (size_t i = 1; i < data.size(); i++) {
-    float x = static_cast<float>(data[i].log_time_us() - begin_time) /
+    float x = static_cast<float>(data[i].timestamp - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<ResultType> y = f(data[i - 1], data[i]);
+    rtc::Optional<ResultType> y = extract(data[i - 1], data[i]);
     if (y)
       sum += *y;
     result->points.emplace_back(x, static_cast<float>(sum));
@@ -307,31 +289,30 @@
 // A data point is generated every |step| microseconds from |begin_time|
 // to |end_time|. The value of each data point is the average of the data
 // during the preceeding |window_duration_us| microseconds.
-template <typename DataType, typename ResultType, typename IterableType>
+template <typename DataType, typename ResultType>
 void MovingAverage(
-    rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> f,
-    const IterableType& data_view,
-    int64_t begin_time,
-    int64_t end_time,
-    int64_t window_duration_us,
-    int64_t step,
-    TimeSeries* result) {
+    rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> extract,
+    const std::vector<DataType>& data,
+    uint64_t begin_time,
+    uint64_t end_time,
+    uint64_t window_duration_us,
+    uint64_t step,
+    webrtc::plotting::TimeSeries* result) {
   size_t window_index_begin = 0;
   size_t window_index_end = 0;
   ResultType sum_in_window = 0;
 
-  for (int64_t t = begin_time; t < end_time + step; t += step) {
-    while (window_index_end < data_view.size() &&
-           data_view[window_index_end].log_time_us() < t) {
-      rtc::Optional<ResultType> value = f(data_view[window_index_end]);
+  for (uint64_t t = begin_time; t < end_time + step; t += step) {
+    while (window_index_end < data.size() &&
+           data[window_index_end].timestamp < t) {
+      rtc::Optional<ResultType> value = extract(data[window_index_end]);
       if (value)
         sum_in_window += *value;
       ++window_index_end;
     }
-    while (window_index_begin < data_view.size() &&
-           data_view[window_index_begin].log_time_us() <
-               t - window_duration_us) {
-      rtc::Optional<ResultType> value = f(data_view[window_index_begin]);
+    while (window_index_begin < data.size() &&
+           data[window_index_begin].timestamp < t - window_duration_us) {
+      rtc::Optional<ResultType> value = extract(data[window_index_begin]);
       if (value)
         sum_in_window -= *value;
       ++window_index_begin;
@@ -425,7 +406,7 @@
 }
 
 std::string GetCandidatePairLogDescriptionAsString(
-    const LoggedIceCandidatePairConfig& config) {
+    const ParsedRtcEventLog::IceCandidatePairConfig& config) {
   // Example: stun:wifi->relay(tcp):cellular@udp:ipv4
   // represents a pair of a local server-reflexive candidate on a WiFi network
   // and a remote relay candidate using TCP as the relay protocol on a cell
@@ -448,57 +429,244 @@
   return ss.str();
 }
 
-std::string GetDirectionAsString(PacketDirection direction) {
-  if (direction == kIncomingPacket) {
-    return "Incoming";
-  } else {
-    return "Outgoing";
-  }
-}
-
-std::string GetDirectionAsShortString(PacketDirection direction) {
-  if (direction == kIncomingPacket) {
-    return "In";
-  } else {
-    return "Out";
-  }
-}
-
 }  // namespace
 
 EventLogAnalyzer::EventLogAnalyzer(const ParsedRtcEventLog& log)
     : parsed_log_(log), window_duration_(250000), step_(10000) {
-  begin_time_ = parsed_log_.first_timestamp();
-  end_time_ = parsed_log_.last_timestamp();
-  if (end_time_ < begin_time_) {
-    RTC_LOG(LS_WARNING) << "No useful events in the log.";
-    begin_time_ = end_time_ = 0;
-  }
-  call_duration_s_ = ToCallTime(end_time_);
+  uint64_t first_timestamp = std::numeric_limits<uint64_t>::max();
+  uint64_t last_timestamp = std::numeric_limits<uint64_t>::min();
 
-  const auto& log_start_events = parsed_log_.start_log_events();
-  const auto& log_end_events = parsed_log_.stop_log_events();
-  auto start_iter = log_start_events.begin();
-  auto end_iter = log_end_events.begin();
-  while (start_iter != log_start_events.end()) {
-    int64_t start = start_iter->log_time_us();
-    ++start_iter;
-    rtc::Optional<int64_t> next_start;
-    if (start_iter != log_start_events.end())
-      next_start.emplace(start_iter->log_time_us());
-    if (end_iter != log_end_events.end() &&
-        end_iter->log_time_us() <=
-            next_start.value_or(std::numeric_limits<int64_t>::max())) {
-      int64_t end = end_iter->log_time_us();
-      RTC_DCHECK_LE(start, end);
-      log_segments_.push_back(std::make_pair(start, end));
-      ++end_iter;
-    } else {
-      // we're missing an end event. Assume that it occurred just before the
-      // next start.
-      log_segments_.push_back(
-          std::make_pair(start, next_start.value_or(end_time_)));
+  PacketDirection direction;
+  uint8_t header[IP_PACKET_SIZE];
+  size_t header_length;
+  size_t total_length;
+
+  uint8_t last_incoming_rtcp_packet[IP_PACKET_SIZE];
+  uint8_t last_incoming_rtcp_packet_length = 0;
+
+  // Make a default extension map for streams without configuration information.
+  // TODO(ivoc): Once configuration of audio streams is stored in the event log,
+  //             this can be removed. Tracking bug: webrtc:6399
+  RtpHeaderExtensionMap default_extension_map = GetDefaultHeaderExtensionMap();
+
+  rtc::Optional<uint64_t> last_log_start;
+
+  for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) {
+    ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i);
+    if (event_type != ParsedRtcEventLog::VIDEO_RECEIVER_CONFIG_EVENT &&
+        event_type != ParsedRtcEventLog::VIDEO_SENDER_CONFIG_EVENT &&
+        event_type != ParsedRtcEventLog::AUDIO_RECEIVER_CONFIG_EVENT &&
+        event_type != ParsedRtcEventLog::AUDIO_SENDER_CONFIG_EVENT &&
+        event_type != ParsedRtcEventLog::LOG_START &&
+        event_type != ParsedRtcEventLog::LOG_END) {
+      uint64_t timestamp = parsed_log_.GetTimestamp(i);
+      first_timestamp = std::min(first_timestamp, timestamp);
+      last_timestamp = std::max(last_timestamp, timestamp);
     }
+
+    switch (parsed_log_.GetEventType(i)) {
+      case ParsedRtcEventLog::VIDEO_RECEIVER_CONFIG_EVENT: {
+        rtclog::StreamConfig config = parsed_log_.GetVideoReceiveConfig(i);
+        StreamId stream(config.remote_ssrc, kIncomingPacket);
+        video_ssrcs_.insert(stream);
+        StreamId rtx_stream(config.rtx_ssrc, kIncomingPacket);
+        video_ssrcs_.insert(rtx_stream);
+        rtx_ssrcs_.insert(rtx_stream);
+        break;
+      }
+      case ParsedRtcEventLog::VIDEO_SENDER_CONFIG_EVENT: {
+        std::vector<rtclog::StreamConfig> configs =
+            parsed_log_.GetVideoSendConfig(i);
+        for (const auto& config : configs) {
+          StreamId stream(config.local_ssrc, kOutgoingPacket);
+          video_ssrcs_.insert(stream);
+          StreamId rtx_stream(config.rtx_ssrc, kOutgoingPacket);
+          video_ssrcs_.insert(rtx_stream);
+          rtx_ssrcs_.insert(rtx_stream);
+        }
+        break;
+      }
+      case ParsedRtcEventLog::AUDIO_RECEIVER_CONFIG_EVENT: {
+        rtclog::StreamConfig config = parsed_log_.GetAudioReceiveConfig(i);
+        StreamId stream(config.remote_ssrc, kIncomingPacket);
+        audio_ssrcs_.insert(stream);
+        break;
+      }
+      case ParsedRtcEventLog::AUDIO_SENDER_CONFIG_EVENT: {
+        rtclog::StreamConfig config = parsed_log_.GetAudioSendConfig(i);
+        StreamId stream(config.local_ssrc, kOutgoingPacket);
+        audio_ssrcs_.insert(stream);
+        break;
+      }
+      case ParsedRtcEventLog::RTP_EVENT: {
+        RtpHeaderExtensionMap* extension_map = parsed_log_.GetRtpHeader(
+            i, &direction, header, &header_length, &total_length, nullptr);
+        RtpUtility::RtpHeaderParser rtp_parser(header, header_length);
+        RTPHeader parsed_header;
+        if (extension_map != nullptr) {
+          rtp_parser.Parse(&parsed_header, extension_map);
+        } else {
+          // Use the default extension map.
+          // TODO(ivoc): Once configuration of audio streams is stored in the
+          //             event log, this can be removed.
+          //             Tracking bug: webrtc:6399
+          rtp_parser.Parse(&parsed_header, &default_extension_map);
+        }
+        uint64_t timestamp = parsed_log_.GetTimestamp(i);
+        StreamId stream(parsed_header.ssrc, direction);
+        rtp_packets_[stream].push_back(LoggedRtpPacket(
+            timestamp, parsed_header, header_length, total_length));
+        break;
+      }
+      case ParsedRtcEventLog::RTCP_EVENT: {
+        uint8_t packet[IP_PACKET_SIZE];
+        parsed_log_.GetRtcpPacket(i, &direction, packet, &total_length);
+        // Currently incoming RTCP packets are logged twice, both for audio and
+        // video. Only act on one of them. Compare against the previous parsed
+        // incoming RTCP packet.
+        if (direction == webrtc::kIncomingPacket) {
+          RTC_CHECK_LE(total_length, IP_PACKET_SIZE);
+          if (total_length == last_incoming_rtcp_packet_length &&
+              memcmp(last_incoming_rtcp_packet, packet, total_length) == 0) {
+            continue;
+          } else {
+            memcpy(last_incoming_rtcp_packet, packet, total_length);
+            last_incoming_rtcp_packet_length = total_length;
+          }
+        }
+        rtcp::CommonHeader header;
+        const uint8_t* packet_end = packet + total_length;
+        for (const uint8_t* block = packet; block < packet_end;
+             block = header.NextPacket()) {
+          RTC_CHECK(header.Parse(block, packet_end - block));
+          if (header.type() == rtcp::TransportFeedback::kPacketType &&
+              header.fmt() == rtcp::TransportFeedback::kFeedbackMessageType) {
+            std::unique_ptr<rtcp::TransportFeedback> rtcp_packet(
+                rtc::MakeUnique<rtcp::TransportFeedback>());
+            if (rtcp_packet->Parse(header)) {
+              uint32_t ssrc = rtcp_packet->sender_ssrc();
+              StreamId stream(ssrc, direction);
+              uint64_t timestamp = parsed_log_.GetTimestamp(i);
+              rtcp_packets_[stream].push_back(LoggedRtcpPacket(
+                  timestamp, kRtcpTransportFeedback, std::move(rtcp_packet)));
+            }
+          } else if (header.type() == rtcp::SenderReport::kPacketType) {
+            std::unique_ptr<rtcp::SenderReport> rtcp_packet(
+                rtc::MakeUnique<rtcp::SenderReport>());
+            if (rtcp_packet->Parse(header)) {
+              uint32_t ssrc = rtcp_packet->sender_ssrc();
+              StreamId stream(ssrc, direction);
+              uint64_t timestamp = parsed_log_.GetTimestamp(i);
+              rtcp_packets_[stream].push_back(
+                  LoggedRtcpPacket(timestamp, kRtcpSr, std::move(rtcp_packet)));
+            }
+          } else if (header.type() == rtcp::ReceiverReport::kPacketType) {
+            std::unique_ptr<rtcp::ReceiverReport> rtcp_packet(
+                rtc::MakeUnique<rtcp::ReceiverReport>());
+            if (rtcp_packet->Parse(header)) {
+              uint32_t ssrc = rtcp_packet->sender_ssrc();
+              StreamId stream(ssrc, direction);
+              uint64_t timestamp = parsed_log_.GetTimestamp(i);
+              rtcp_packets_[stream].push_back(
+                  LoggedRtcpPacket(timestamp, kRtcpRr, std::move(rtcp_packet)));
+            }
+          } else if (header.type() == rtcp::Remb::kPacketType &&
+                     header.fmt() == rtcp::Remb::kFeedbackMessageType) {
+            std::unique_ptr<rtcp::Remb> rtcp_packet(
+                rtc::MakeUnique<rtcp::Remb>());
+            if (rtcp_packet->Parse(header)) {
+              uint32_t ssrc = rtcp_packet->sender_ssrc();
+              StreamId stream(ssrc, direction);
+              uint64_t timestamp = parsed_log_.GetTimestamp(i);
+              rtcp_packets_[stream].push_back(LoggedRtcpPacket(
+                  timestamp, kRtcpRemb, std::move(rtcp_packet)));
+            }
+          }
+        }
+        break;
+      }
+      case ParsedRtcEventLog::LOG_START: {
+        if (last_log_start) {
+          // A LOG_END event was missing. Use last_timestamp.
+          RTC_DCHECK_GE(last_timestamp, *last_log_start);
+          log_segments_.push_back(
+            std::make_pair(*last_log_start, last_timestamp));
+        }
+        last_log_start = parsed_log_.GetTimestamp(i);
+        break;
+      }
+      case ParsedRtcEventLog::LOG_END: {
+        RTC_DCHECK(last_log_start);
+        log_segments_.push_back(
+            std::make_pair(*last_log_start, parsed_log_.GetTimestamp(i)));
+        last_log_start.reset();
+        break;
+      }
+      case ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT: {
+        uint32_t this_ssrc;
+        parsed_log_.GetAudioPlayout(i, &this_ssrc);
+        audio_playout_events_[this_ssrc].push_back(parsed_log_.GetTimestamp(i));
+        break;
+      }
+      case ParsedRtcEventLog::LOSS_BASED_BWE_UPDATE: {
+        LossBasedBweUpdate bwe_update;
+        bwe_update.timestamp = parsed_log_.GetTimestamp(i);
+        parsed_log_.GetLossBasedBweUpdate(i, &bwe_update.new_bitrate,
+                                          &bwe_update.fraction_loss,
+                                          &bwe_update.expected_packets);
+        bwe_loss_updates_.push_back(bwe_update);
+        break;
+      }
+      case ParsedRtcEventLog::DELAY_BASED_BWE_UPDATE: {
+        bwe_delay_updates_.push_back(parsed_log_.GetDelayBasedBweUpdate(i));
+        break;
+      }
+      case ParsedRtcEventLog::AUDIO_NETWORK_ADAPTATION_EVENT: {
+        AudioNetworkAdaptationEvent ana_event;
+        ana_event.timestamp = parsed_log_.GetTimestamp(i);
+        parsed_log_.GetAudioNetworkAdaptation(i, &ana_event.config);
+        audio_network_adaptation_events_.push_back(ana_event);
+        break;
+      }
+      case ParsedRtcEventLog::BWE_PROBE_CLUSTER_CREATED_EVENT: {
+        bwe_probe_cluster_created_events_.push_back(
+            parsed_log_.GetBweProbeClusterCreated(i));
+        break;
+      }
+      case ParsedRtcEventLog::BWE_PROBE_RESULT_EVENT: {
+        bwe_probe_result_events_.push_back(parsed_log_.GetBweProbeResult(i));
+        break;
+      }
+      case ParsedRtcEventLog::ALR_STATE_EVENT: {
+        alr_state_events_.push_back(parsed_log_.GetAlrState(i));
+        break;
+      }
+      case ParsedRtcEventLog::ICE_CANDIDATE_PAIR_CONFIG: {
+        ice_candidate_pair_configs_.push_back(
+            parsed_log_.GetIceCandidatePairConfig(i));
+        break;
+      }
+      case ParsedRtcEventLog::ICE_CANDIDATE_PAIR_EVENT: {
+        ice_candidate_pair_events_.push_back(
+            parsed_log_.GetIceCandidatePairEvent(i));
+        break;
+      }
+      case ParsedRtcEventLog::UNKNOWN_EVENT: {
+        break;
+      }
+    }
+  }
+
+  if (last_timestamp < first_timestamp) {
+    // No useful events in the log.
+    first_timestamp = last_timestamp = 0;
+  }
+  begin_time_ = first_timestamp;
+  end_time_ = last_timestamp;
+  call_duration_s_ = ToCallTime(end_time_);
+  if (last_log_start) {
+    // The log was missing the last LOG_END event. Fake it.
+    log_segments_.push_back(std::make_pair(*last_log_start, end_time_));
   }
   RTC_LOG(LS_INFO) << "Found " << log_segments_.size()
                << " (LOG_START, LOG_END) segments in log.";
@@ -510,7 +678,7 @@
   BitrateObserver() : last_bitrate_bps_(0), bitrate_updated_(false) {}
 
   void OnNetworkChanged(uint32_t bitrate_bps,
-                        uint8_t fraction_lost,
+                        uint8_t fraction_loss,
                         int64_t rtt_ms,
                         int64_t probing_interval_ms) override {
     last_bitrate_bps_ = bitrate_bps;
@@ -532,90 +700,187 @@
   bool bitrate_updated_;
 };
 
+bool EventLogAnalyzer::IsRtxSsrc(StreamId stream_id) const {
+  return rtx_ssrcs_.count(stream_id) == 1;
+}
+
+bool EventLogAnalyzer::IsVideoSsrc(StreamId stream_id) const {
+  return video_ssrcs_.count(stream_id) == 1;
+}
+
+bool EventLogAnalyzer::IsAudioSsrc(StreamId stream_id) const {
+  return audio_ssrcs_.count(stream_id) == 1;
+}
+
+std::string EventLogAnalyzer::GetStreamName(StreamId stream_id) const {
+  std::stringstream name;
+  if (IsAudioSsrc(stream_id)) {
+    name << "Audio ";
+  } else if (IsVideoSsrc(stream_id)) {
+    name << "Video ";
+  } else {
+    name << "Unknown ";
+  }
+  if (IsRtxSsrc(stream_id))
+    name << "RTX ";
+  if (stream_id.GetDirection() == kIncomingPacket) {
+    name << "(In) ";
+  } else {
+    name << "(Out) ";
+  }
+  name << SsrcToString(stream_id.GetSsrc());
+  return name.str();
+}
+
+// This is much more reliable for outgoing streams than for incoming streams.
+rtc::Optional<uint32_t> EventLogAnalyzer::EstimateRtpClockFrequency(
+    const std::vector<LoggedRtpPacket>& packets) const {
+  RTC_CHECK(packets.size() >= 2);
+  uint64_t end_time_us = log_segments_.empty()
+                             ? std::numeric_limits<uint64_t>::max()
+                             : log_segments_.front().second;
+  SeqNumUnwrapper<uint32_t> unwrapper;
+  uint64_t first_rtp_timestamp = unwrapper.Unwrap(packets[0].header.timestamp);
+  uint64_t first_log_timestamp = packets[0].timestamp;
+  uint64_t last_rtp_timestamp = first_rtp_timestamp;
+  uint64_t last_log_timestamp = first_log_timestamp;
+  for (size_t i = 1; i < packets.size(); i++) {
+    if (packets[i].timestamp > end_time_us)
+      break;
+    last_rtp_timestamp = unwrapper.Unwrap(packets[i].header.timestamp);
+    last_log_timestamp = packets[i].timestamp;
+  }
+  if (last_log_timestamp - first_log_timestamp < kNumMicrosecsPerSec) {
+    RTC_LOG(LS_WARNING)
+        << "Failed to estimate RTP clock frequency: Stream too short. ("
+        << packets.size() << " packets, "
+        << last_log_timestamp - first_log_timestamp << " us)";
+    return rtc::nullopt;
+  }
+  double duration =
+      static_cast<double>(last_log_timestamp - first_log_timestamp) /
+      kNumMicrosecsPerSec;
+  double estimated_frequency =
+      (last_rtp_timestamp - first_rtp_timestamp) / duration;
+  for (uint32_t f : {8000, 16000, 32000, 48000, 90000}) {
+    if (std::fabs(estimated_frequency - f) < 0.05 * f) {
+      return f;
+    }
+  }
+  RTC_LOG(LS_WARNING) << "Failed to estimate RTP clock frequency: Estimate "
+                      << estimated_frequency
+                      << "not close to any stardard RTP frequency.";
+  return rtc::nullopt;
+}
+
 float EventLogAnalyzer::ToCallTime(int64_t timestamp) const {
   return static_cast<float>(timestamp - begin_time_) / kNumMicrosecsPerSec;
 }
 
-void EventLogAnalyzer::CreatePacketGraph(PacketDirection direction,
+void EventLogAnalyzer::CreatePacketGraph(PacketDirection desired_direction,
                                          Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    // Filter on SSRC.
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_)) {
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != desired_direction ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
       continue;
     }
 
-    TimeSeries time_series(GetStreamName(direction, stream.ssrc),
-                           LineStyle::kBar);
-    auto GetPacketSize = [](const LoggedRtpPacket& packet) {
-      return rtc::Optional<float>(packet.total_length);
-    };
-    ProcessPoints<LoggedRtpPacket>(GetPacketSize, stream.packet_view,
-                                   begin_time_, &time_series);
+    TimeSeries time_series(GetStreamName(stream_id), LineStyle::kBar);
+    ProcessPoints<LoggedRtpPacket>(
+        [](const LoggedRtpPacket& packet) {
+          return rtc::Optional<float>(packet.total_length);
+        },
+        packet_stream, begin_time_, &time_series);
     plot->AppendTimeSeries(std::move(time_series));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Packet size (bytes)", kBottomMargin,
                           kTopMargin);
-  plot->SetTitle(GetDirectionAsString(direction) + " RTP packets");
+  if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
+    plot->SetTitle("Incoming RTP packets");
+  } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
+    plot->SetTitle("Outgoing RTP packets");
+  }
 }
 
-template <typename IterableType>
+template <typename T>
 void EventLogAnalyzer::CreateAccumulatedPacketsTimeSeries(
+    PacketDirection desired_direction,
     Plot* plot,
-    const IterableType& packets,
-    const std::string& label) {
-  TimeSeries time_series(label, LineStyle::kStep);
-  for (size_t i = 0; i < packets.size(); i++) {
-    float x = ToCallTime(packets[i].log_time_us());
-    time_series.points.emplace_back(x, i + 1);
+    const std::map<StreamId, std::vector<T>>& packets,
+    const std::string& label_prefix) {
+  for (auto& kv : packets) {
+    StreamId stream_id = kv.first;
+    const std::vector<T>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != desired_direction ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
+      continue;
+    }
+
+    std::string label = label_prefix + " " + GetStreamName(stream_id);
+    TimeSeries time_series(label, LineStyle::kStep);
+    for (size_t i = 0; i < packet_stream.size(); i++) {
+      float x = ToCallTime(packet_stream[i].timestamp);
+      time_series.points.emplace_back(x, i + 1);
+    }
+
+    plot->AppendTimeSeries(std::move(time_series));
   }
-  plot->AppendTimeSeries(std::move(time_series));
 }
 
-void EventLogAnalyzer::CreateAccumulatedPacketsGraph(PacketDirection direction,
-                                                     Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_))
-      continue;
-    std::string label =
-        std::string("RTP ") + GetStreamName(direction, stream.ssrc);
-    CreateAccumulatedPacketsTimeSeries(plot, stream.packet_view, label);
-  }
-  std::string label =
-      std::string("RTCP ") + "(" + GetDirectionAsShortString(direction) + ")";
-  if (direction == kIncomingPacket) {
-    CreateAccumulatedPacketsTimeSeries(
-        plot, parsed_log_.incoming_rtcp_packets(), label);
-  } else {
-    CreateAccumulatedPacketsTimeSeries(
-        plot, parsed_log_.outgoing_rtcp_packets(), label);
-  }
+void EventLogAnalyzer::CreateAccumulatedPacketsGraph(
+    PacketDirection desired_direction,
+    Plot* plot) {
+  CreateAccumulatedPacketsTimeSeries(desired_direction, plot, rtp_packets_,
+                                     "RTP");
+  CreateAccumulatedPacketsTimeSeries(desired_direction, plot, rtcp_packets_,
+                                     "RTCP");
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Received Packets", kBottomMargin, kTopMargin);
-  plot->SetTitle(std::string("Accumulated ") + GetDirectionAsString(direction) +
-                 " RTP/RTCP packets");
+  if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
+    plot->SetTitle("Accumulated Incoming RTP/RTCP packets");
+  } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
+    plot->SetTitle("Accumulated Outgoing RTP/RTCP packets");
+  }
 }
 
 // For each SSRC, plot the time between the consecutive playouts.
 void EventLogAnalyzer::CreatePlayoutGraph(Plot* plot) {
-  for (const auto& playout_stream : parsed_log_.audio_playout_events()) {
-    uint32_t ssrc = playout_stream.first;
-    if (!MatchingSsrc(ssrc, desired_ssrc_))
-      continue;
-    rtc::Optional<int64_t> last_playout;
-    TimeSeries time_series(SsrcToString(ssrc), LineStyle::kBar);
-    for (const auto& playout_time : playout_stream.second) {
-      float x = ToCallTime(playout_time);
-      // If there were no previous playouts, place the point on the x-axis.
-      float y = static_cast<float>(playout_time -
-                                   last_playout.value_or(playout_time)) /
-                1000;
-      time_series.points.push_back(TimeSeriesPoint(x, y));
-      last_playout.emplace(playout_time);
+  std::map<uint32_t, TimeSeries> time_series;
+  std::map<uint32_t, uint64_t> last_playout;
+
+  uint32_t ssrc;
+
+  for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) {
+    ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i);
+    if (event_type == ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT) {
+      parsed_log_.GetAudioPlayout(i, &ssrc);
+      uint64_t timestamp = parsed_log_.GetTimestamp(i);
+      if (MatchingSsrc(ssrc, desired_ssrc_)) {
+        float x = ToCallTime(timestamp);
+        float y = static_cast<float>(timestamp - last_playout[ssrc]) / 1000;
+        if (time_series[ssrc].points.size() == 0) {
+          // There were no previusly logged playout for this SSRC.
+          // Generate a point, but place it on the x-axis.
+          y = 0;
+        }
+        time_series[ssrc].points.push_back(TimeSeriesPoint(x, y));
+        last_playout[ssrc] = timestamp;
+      }
     }
-    plot->AppendTimeSeries(std::move(time_series));
+  }
+
+  // Set labels and put in graph.
+  for (auto& kv : time_series) {
+    kv.second.label = SsrcToString(kv.first);
+    kv.second.line_style = LineStyle::kBar;
+    plot->AppendTimeSeries(std::move(kv.second));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
@@ -625,51 +890,59 @@
 }
 
 // For audio SSRCs, plot the audio level.
-void EventLogAnalyzer::CreateAudioLevelGraph(PacketDirection direction,
-                                             Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    if (!IsAudioSsrc(direction, stream.ssrc))
-      continue;
-    TimeSeries time_series(GetStreamName(direction, stream.ssrc),
-                           LineStyle::kLine);
-    for (auto& packet : stream.packet_view) {
+void EventLogAnalyzer::CreateAudioLevelGraph(Plot* plot) {
+  std::map<StreamId, TimeSeries> time_series;
+
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // TODO(ivoc): When audio send/receive configs are stored in the event
+    //             log, a check should be added here to only process audio
+    //             streams. Tracking bug: webrtc:6399
+    for (auto& packet : packet_stream) {
       if (packet.header.extension.hasAudioLevel) {
-        float x = ToCallTime(packet.log_time_us());
+        float x = ToCallTime(packet.timestamp);
         // The audio level is stored in -dBov (so e.g. -10 dBov is stored as 10)
         // Here we convert it to dBov.
         float y = static_cast<float>(-packet.header.extension.audioLevel);
-        time_series.points.emplace_back(TimeSeriesPoint(x, y));
+        time_series[stream_id].points.emplace_back(TimeSeriesPoint(x, y));
       }
     }
-    plot->AppendTimeSeries(std::move(time_series));
+  }
+
+  for (auto& series : time_series) {
+    series.second.label = GetStreamName(series.first);
+    series.second.line_style = LineStyle::kLine;
+    plot->AppendTimeSeries(std::move(series.second));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetYAxis(-127, 0, "Audio level (dBov)", kBottomMargin,
                  kTopMargin);
-  plot->SetTitle(GetDirectionAsString(direction) + " audio level");
+  plot->SetTitle("Audio level");
 }
 
 // For each SSRC, plot the time between the consecutive playouts.
 void EventLogAnalyzer::CreateSequenceNumberGraph(Plot* plot) {
-  for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
-    // Filter on SSRC.
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_)) {
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != kIncomingPacket ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
       continue;
     }
 
-    TimeSeries time_series(GetStreamName(kIncomingPacket, stream.ssrc),
-                           LineStyle::kBar);
-    auto GetSequenceNumberDiff = [](const LoggedRtpPacketIncoming& old_packet,
-                                    const LoggedRtpPacketIncoming& new_packet) {
-      int64_t diff =
-          WrappingDifference(new_packet.rtp.header.sequenceNumber,
-                             old_packet.rtp.header.sequenceNumber, 1ul << 16);
-      return diff;
-    };
-    ProcessPairs<LoggedRtpPacketIncoming, float>(GetSequenceNumberDiff,
-                                                 stream.incoming_packets,
-                                                 begin_time_, &time_series);
+    TimeSeries time_series(GetStreamName(stream_id), LineStyle::kBar);
+    ProcessPairs<LoggedRtpPacket, float>(
+        [](const LoggedRtpPacket& old_packet,
+           const LoggedRtpPacket& new_packet) {
+          int64_t diff =
+              WrappingDifference(new_packet.header.sequenceNumber,
+                                 old_packet.header.sequenceNumber, 1ul << 16);
+          return diff;
+        },
+        packet_stream, begin_time_, &time_series);
     plot->AppendTimeSeries(std::move(time_series));
   }
 
@@ -680,47 +953,47 @@
 }
 
 void EventLogAnalyzer::CreateIncomingPacketLossGraph(Plot* plot) {
-  for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
-    const std::vector<LoggedRtpPacketIncoming>& packets =
-        stream.incoming_packets;
-    // Filter on SSRC.
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_) || packets.size() == 0) {
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != kIncomingPacket ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
+        packet_stream.size() == 0) {
       continue;
     }
 
-    TimeSeries time_series(GetStreamName(kIncomingPacket, stream.ssrc),
-                           LineStyle::kLine, PointStyle::kHighlight);
-    // TODO(terelius): Should the window and step size be read from the class
-    // instead?
-    const int64_t kWindowUs = 1000000;
-    const int64_t kStep = 1000000;
+    TimeSeries time_series(GetStreamName(stream_id), LineStyle::kLine,
+                           PointStyle::kHighlight);
+    const uint64_t kWindowUs = 1000000;
+    const uint64_t kStep = 1000000;
     SeqNumUnwrapper<uint16_t> unwrapper_;
     SeqNumUnwrapper<uint16_t> prior_unwrapper_;
     size_t window_index_begin = 0;
     size_t window_index_end = 0;
-    uint64_t highest_seq_number =
-        unwrapper_.Unwrap(packets[0].rtp.header.sequenceNumber) - 1;
-    uint64_t highest_prior_seq_number =
-        prior_unwrapper_.Unwrap(packets[0].rtp.header.sequenceNumber) - 1;
+    int64_t highest_seq_number =
+        unwrapper_.Unwrap(packet_stream[0].header.sequenceNumber) - 1;
+    int64_t highest_prior_seq_number =
+        prior_unwrapper_.Unwrap(packet_stream[0].header.sequenceNumber) - 1;
 
-    for (int64_t t = begin_time_; t < end_time_ + kStep; t += kStep) {
-      while (window_index_end < packets.size() &&
-             packets[window_index_end].rtp.log_time_us() < t) {
-        uint64_t sequence_number = unwrapper_.Unwrap(
-            packets[window_index_end].rtp.header.sequenceNumber);
+    for (uint64_t t = begin_time_; t < end_time_ + kStep; t += kStep) {
+      while (window_index_end < packet_stream.size() &&
+             packet_stream[window_index_end].timestamp < t) {
+        int64_t sequence_number = unwrapper_.Unwrap(
+            packet_stream[window_index_end].header.sequenceNumber);
         highest_seq_number = std::max(highest_seq_number, sequence_number);
         ++window_index_end;
       }
-      while (window_index_begin < packets.size() &&
-             packets[window_index_begin].rtp.log_time_us() < t - kWindowUs) {
-        uint64_t sequence_number = prior_unwrapper_.Unwrap(
-            packets[window_index_begin].rtp.header.sequenceNumber);
+      while (window_index_begin < packet_stream.size() &&
+             packet_stream[window_index_begin].timestamp < t - kWindowUs) {
+        int64_t sequence_number = prior_unwrapper_.Unwrap(
+            packet_stream[window_index_begin].header.sequenceNumber);
         highest_prior_seq_number =
             std::max(highest_prior_seq_number, sequence_number);
         ++window_index_begin;
       }
       float x = ToCallTime(t);
-      uint64_t expected_packets = highest_seq_number - highest_prior_seq_number;
+      int64_t expected_packets = highest_seq_number - highest_prior_seq_number;
       if (expected_packets > 0) {
         int64_t received_packets = window_index_end - window_index_begin;
         int64_t lost_packets = expected_packets - received_packets;
@@ -738,28 +1011,28 @@
 }
 
 void EventLogAnalyzer::CreateIncomingDelayDeltaGraph(Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(kIncomingPacket)) {
-    // Filter on SSRC.
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_) ||
-        IsAudioSsrc(kIncomingPacket, stream.ssrc) ||
-        !IsVideoSsrc(kIncomingPacket, stream.ssrc) ||
-        IsRtxSsrc(kIncomingPacket, stream.ssrc)) {
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != kIncomingPacket ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
+        IsAudioSsrc(stream_id) || !IsVideoSsrc(stream_id) ||
+        IsRtxSsrc(stream_id)) {
       continue;
     }
 
-    TimeSeries capture_time_data(
-        GetStreamName(kIncomingPacket, stream.ssrc) + " capture-time",
-        LineStyle::kBar);
+    TimeSeries capture_time_data(GetStreamName(stream_id) + " capture-time",
+                                 LineStyle::kBar);
     ProcessPairs<LoggedRtpPacket, double>(NetworkDelayDiff_CaptureTime,
-                                          stream.packet_view, begin_time_,
+                                          packet_stream, begin_time_,
                                           &capture_time_data);
     plot->AppendTimeSeries(std::move(capture_time_data));
 
-    TimeSeries send_time_data(
-        GetStreamName(kIncomingPacket, stream.ssrc) + " abs-send-time",
-        LineStyle::kBar);
+    TimeSeries send_time_data(GetStreamName(stream_id) + " abs-send-time",
+                              LineStyle::kBar);
     ProcessPairs<LoggedRtpPacket, double>(NetworkDelayDiff_AbsSendTime,
-                                          stream.packet_view, begin_time_,
+                                          packet_stream, begin_time_,
                                           &send_time_data);
     plot->AppendTimeSeries(std::move(send_time_data));
   }
@@ -771,28 +1044,28 @@
 }
 
 void EventLogAnalyzer::CreateIncomingDelayGraph(Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(kIncomingPacket)) {
-    // Filter on SSRC.
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_) ||
-        IsAudioSsrc(kIncomingPacket, stream.ssrc) ||
-        !IsVideoSsrc(kIncomingPacket, stream.ssrc) ||
-        IsRtxSsrc(kIncomingPacket, stream.ssrc)) {
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != kIncomingPacket ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
+        IsAudioSsrc(stream_id) || !IsVideoSsrc(stream_id) ||
+        IsRtxSsrc(stream_id)) {
       continue;
     }
 
-    TimeSeries capture_time_data(
-        GetStreamName(kIncomingPacket, stream.ssrc) + " capture-time",
-        LineStyle::kLine);
+    TimeSeries capture_time_data(GetStreamName(stream_id) + " capture-time",
+                                 LineStyle::kLine);
     AccumulatePairs<LoggedRtpPacket, double>(NetworkDelayDiff_CaptureTime,
-                                             stream.packet_view, begin_time_,
+                                             packet_stream, begin_time_,
                                              &capture_time_data);
     plot->AppendTimeSeries(std::move(capture_time_data));
 
-    TimeSeries send_time_data(
-        GetStreamName(kIncomingPacket, stream.ssrc) + " abs-send-time",
-        LineStyle::kLine);
+    TimeSeries send_time_data(GetStreamName(stream_id) + " abs-send-time",
+                              LineStyle::kLine);
     AccumulatePairs<LoggedRtpPacket, double>(NetworkDelayDiff_AbsSendTime,
-                                             stream.packet_view, begin_time_,
+                                             packet_stream, begin_time_,
                                              &send_time_data);
     plot->AppendTimeSeries(std::move(send_time_data));
   }
@@ -807,9 +1080,9 @@
 void EventLogAnalyzer::CreateFractionLossGraph(Plot* plot) {
   TimeSeries time_series("Fraction lost", LineStyle::kLine,
                          PointStyle::kHighlight);
-  for (auto& bwe_update : parsed_log_.bwe_loss_updates()) {
-    float x = ToCallTime(bwe_update.log_time_us());
-    float y = static_cast<float>(bwe_update.fraction_lost) / 255 * 100;
+  for (auto& bwe_update : bwe_loss_updates_) {
+    float x = ToCallTime(bwe_update.timestamp);
+    float y = static_cast<float>(bwe_update.fraction_loss) / 255 * 100;
     time_series.points.emplace_back(x, y);
   }
 
@@ -821,82 +1094,51 @@
 }
 
 // Plot the total bandwidth used by all RTP streams.
-void EventLogAnalyzer::CreateTotalIncomingBitrateGraph(Plot* plot) {
-  // TODO(terelius): This could be provided by the parser.
-  std::multimap<int64_t, size_t> packets_in_order;
-  for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
-    for (const LoggedRtpPacketIncoming& packet : stream.incoming_packets)
-      packets_in_order.insert(
-          std::make_pair(packet.rtp.log_time_us(), packet.rtp.total_length));
+void EventLogAnalyzer::CreateTotalBitrateGraph(
+    PacketDirection desired_direction,
+    Plot* plot,
+    bool show_detector_state,
+    bool show_alr_state) {
+  struct TimestampSize {
+    TimestampSize(uint64_t t, size_t s) : timestamp(t), size(s) {}
+    uint64_t timestamp;
+    size_t size;
+  };
+  std::vector<TimestampSize> packets;
+
+  PacketDirection direction;
+  size_t total_length;
+
+  // Extract timestamps and sizes for the relevant packets.
+  for (size_t i = 0; i < parsed_log_.GetNumberOfEvents(); i++) {
+    ParsedRtcEventLog::EventType event_type = parsed_log_.GetEventType(i);
+    if (event_type == ParsedRtcEventLog::RTP_EVENT) {
+      parsed_log_.GetRtpHeader(i, &direction, nullptr, nullptr, &total_length,
+                               nullptr);
+      if (direction == desired_direction) {
+        uint64_t timestamp = parsed_log_.GetTimestamp(i);
+        packets.push_back(TimestampSize(timestamp, total_length));
+      }
+    }
   }
 
-  auto window_begin = packets_in_order.begin();
-  auto window_end = packets_in_order.begin();
+  size_t window_index_begin = 0;
+  size_t window_index_end = 0;
   size_t bytes_in_window = 0;
 
   // Calculate a moving average of the bitrate and store in a TimeSeries.
   TimeSeries bitrate_series("Bitrate", LineStyle::kLine);
-  for (int64_t time = begin_time_; time < end_time_ + step_; time += step_) {
-    while (window_end != packets_in_order.end() && window_end->first < time) {
-      bytes_in_window += window_end->second;
-      ++window_end;
+  for (uint64_t time = begin_time_; time < end_time_ + step_; time += step_) {
+    while (window_index_end < packets.size() &&
+           packets[window_index_end].timestamp < time) {
+      bytes_in_window += packets[window_index_end].size;
+      ++window_index_end;
     }
-    while (window_begin != packets_in_order.end() &&
-           window_begin->first < time - window_duration_) {
-      RTC_DCHECK_LE(window_begin->second, bytes_in_window);
-      bytes_in_window -= window_begin->second;
-      ++window_begin;
-    }
-    float window_duration_in_seconds =
-        static_cast<float>(window_duration_) / kNumMicrosecsPerSec;
-    float x = ToCallTime(time);
-    float y = bytes_in_window * 8 / window_duration_in_seconds / 1000;
-    bitrate_series.points.emplace_back(x, y);
-  }
-  plot->AppendTimeSeries(std::move(bitrate_series));
-
-  // Overlay the outgoing REMB over incoming bitrate.
-  TimeSeries remb_series("Remb", LineStyle::kStep);
-  for (const auto& rtcp : parsed_log_.rembs(kOutgoingPacket)) {
-    float x = ToCallTime(rtcp.log_time_us());
-    float y = static_cast<float>(rtcp.remb.bitrate_bps()) / 1000;
-    remb_series.points.emplace_back(x, y);
-  }
-  plot->AppendTimeSeriesIfNotEmpty(std::move(remb_series));
-
-  plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
-  plot->SetSuggestedYAxis(0, 1, "Bitrate (kbps)", kBottomMargin, kTopMargin);
-  plot->SetTitle("Incoming RTP bitrate");
-}
-
-// Plot the total bandwidth used by all RTP streams.
-void EventLogAnalyzer::CreateTotalOutgoingBitrateGraph(Plot* plot,
-                                                       bool show_detector_state,
-                                                       bool show_alr_state) {
-  // TODO(terelius): This could be provided by the parser.
-  std::multimap<int64_t, size_t> packets_in_order;
-  for (const auto& stream : parsed_log_.outgoing_rtp_packets_by_ssrc()) {
-    for (const LoggedRtpPacketOutgoing& packet : stream.outgoing_packets)
-      packets_in_order.insert(
-          std::make_pair(packet.rtp.log_time_us(), packet.rtp.total_length));
-  }
-
-  auto window_begin = packets_in_order.begin();
-  auto window_end = packets_in_order.begin();
-  size_t bytes_in_window = 0;
-
-  // Calculate a moving average of the bitrate and store in a TimeSeries.
-  TimeSeries bitrate_series("Bitrate", LineStyle::kLine);
-  for (int64_t time = begin_time_; time < end_time_ + step_; time += step_) {
-    while (window_end != packets_in_order.end() && window_end->first < time) {
-      bytes_in_window += window_end->second;
-      ++window_end;
-    }
-    while (window_begin != packets_in_order.end() &&
-           window_begin->first < time - window_duration_) {
-      RTC_DCHECK_LE(window_begin->second, bytes_in_window);
-      bytes_in_window -= window_begin->second;
-      ++window_begin;
+    while (window_index_begin < packets.size() &&
+           packets[window_index_begin].timestamp < time - window_duration_) {
+      RTC_DCHECK_LE(packets[window_index_begin].size, bytes_in_window);
+      bytes_in_window -= packets[window_index_begin].size;
+      ++window_index_begin;
     }
     float window_duration_in_seconds =
         static_cast<float>(window_duration_) / kNumMicrosecsPerSec;
@@ -907,161 +1149,195 @@
   plot->AppendTimeSeries(std::move(bitrate_series));
 
   // Overlay the send-side bandwidth estimate over the outgoing bitrate.
-  TimeSeries loss_series("Loss-based estimate", LineStyle::kStep);
-  for (auto& loss_update : parsed_log_.bwe_loss_updates()) {
-    float x = ToCallTime(loss_update.log_time_us());
-    float y = static_cast<float>(loss_update.bitrate_bps) / 1000;
-    loss_series.points.emplace_back(x, y);
-  }
+  if (desired_direction == kOutgoingPacket) {
+    TimeSeries loss_series("Loss-based estimate", LineStyle::kStep);
+    for (auto& loss_update : bwe_loss_updates_) {
+      float x = ToCallTime(loss_update.timestamp);
+      float y = static_cast<float>(loss_update.new_bitrate) / 1000;
+      loss_series.points.emplace_back(x, y);
+    }
 
-  TimeSeries delay_series("Delay-based estimate", LineStyle::kStep);
-  IntervalSeries overusing_series("Overusing", "#ff8e82",
-                                  IntervalSeries::kHorizontal);
-  IntervalSeries underusing_series("Underusing", "#5092fc",
-                                   IntervalSeries::kHorizontal);
-  IntervalSeries normal_series("Normal", "#c4ffc4",
-                               IntervalSeries::kHorizontal);
-  IntervalSeries* last_series = &normal_series;
-  double last_detector_switch = 0.0;
+    TimeSeries delay_series("Delay-based estimate", LineStyle::kStep);
+    IntervalSeries overusing_series("Overusing", "#ff8e82",
+                                    IntervalSeries::kHorizontal);
+    IntervalSeries underusing_series("Underusing", "#5092fc",
+                                     IntervalSeries::kHorizontal);
+    IntervalSeries normal_series("Normal", "#c4ffc4",
+                                 IntervalSeries::kHorizontal);
+    IntervalSeries* last_series = &normal_series;
+    double last_detector_switch = 0.0;
 
-  BandwidthUsage last_detector_state = BandwidthUsage::kBwNormal;
+    BandwidthUsage last_detector_state = BandwidthUsage::kBwNormal;
 
-  for (auto& delay_update : parsed_log_.bwe_delay_updates()) {
-    float x = ToCallTime(delay_update.log_time_us());
-    float y = static_cast<float>(delay_update.bitrate_bps) / 1000;
+    for (auto& delay_update : bwe_delay_updates_) {
+      float x = ToCallTime(delay_update.timestamp);
+      float y = static_cast<float>(delay_update.bitrate_bps) / 1000;
 
-    if (last_detector_state != delay_update.detector_state) {
-      last_series->intervals.emplace_back(last_detector_switch, x);
-      last_detector_state = delay_update.detector_state;
-      last_detector_switch = x;
+      if (last_detector_state != delay_update.detector_state) {
+        last_series->intervals.emplace_back(last_detector_switch, x);
+        last_detector_state = delay_update.detector_state;
+        last_detector_switch = x;
 
-      switch (delay_update.detector_state) {
-        case BandwidthUsage::kBwNormal:
-          last_series = &normal_series;
-          break;
-        case BandwidthUsage::kBwUnderusing:
-          last_series = &underusing_series;
-          break;
-        case BandwidthUsage::kBwOverusing:
-          last_series = &overusing_series;
-          break;
-        case BandwidthUsage::kLast:
-          RTC_NOTREACHED();
+        switch (delay_update.detector_state) {
+          case BandwidthUsage::kBwNormal:
+            last_series = &normal_series;
+            break;
+          case BandwidthUsage::kBwUnderusing:
+            last_series = &underusing_series;
+            break;
+          case BandwidthUsage::kBwOverusing:
+            last_series = &overusing_series;
+            break;
+          case BandwidthUsage::kLast:
+            RTC_NOTREACHED();
+        }
+      }
+
+      delay_series.points.emplace_back(x, y);
+    }
+
+    RTC_CHECK(last_series);
+    last_series->intervals.emplace_back(last_detector_switch, end_time_);
+
+    TimeSeries created_series("Probe cluster created.", LineStyle::kNone,
+                              PointStyle::kHighlight);
+    for (auto& cluster : bwe_probe_cluster_created_events_) {
+      float x = ToCallTime(cluster.timestamp);
+      float y = static_cast<float>(cluster.bitrate_bps) / 1000;
+      created_series.points.emplace_back(x, y);
+    }
+
+    TimeSeries result_series("Probing results.", LineStyle::kNone,
+                             PointStyle::kHighlight);
+    for (auto& result : bwe_probe_result_events_) {
+      if (result.bitrate_bps) {
+        float x = ToCallTime(result.timestamp);
+        float y = static_cast<float>(*result.bitrate_bps) / 1000;
+        result_series.points.emplace_back(x, y);
       }
     }
 
-    delay_series.points.emplace_back(x, y);
-  }
-
-  RTC_CHECK(last_series);
-  last_series->intervals.emplace_back(last_detector_switch, end_time_);
-
-  TimeSeries created_series("Probe cluster created.", LineStyle::kNone,
-                            PointStyle::kHighlight);
-  for (auto& cluster : parsed_log_.bwe_probe_cluster_created_events()) {
-    float x = ToCallTime(cluster.log_time_us());
-    float y = static_cast<float>(cluster.bitrate_bps) / 1000;
-    created_series.points.emplace_back(x, y);
-  }
-
-  TimeSeries result_series("Probing results.", LineStyle::kNone,
-                           PointStyle::kHighlight);
-  for (auto& result : parsed_log_.bwe_probe_result_events()) {
-    if (result.bitrate_bps) {
-      float x = ToCallTime(result.log_time_us());
-      float y = static_cast<float>(*result.bitrate_bps) / 1000;
-      result_series.points.emplace_back(x, y);
+    IntervalSeries alr_state("ALR", "#555555", IntervalSeries::kHorizontal);
+    bool previously_in_alr = false;
+    int64_t alr_start = 0;
+    for (auto& alr : alr_state_events_) {
+      float y = ToCallTime(alr.timestamp);
+      if (!previously_in_alr && alr.in_alr) {
+        alr_start = alr.timestamp;
+        previously_in_alr = true;
+      } else if (previously_in_alr && !alr.in_alr) {
+        float x = ToCallTime(alr_start);
+        alr_state.intervals.emplace_back(x, y);
+        previously_in_alr = false;
+      }
     }
-  }
 
-  IntervalSeries alr_state("ALR", "#555555", IntervalSeries::kHorizontal);
-  bool previously_in_alr = false;
-  int64_t alr_start = 0;
-  for (auto& alr : parsed_log_.alr_state_events()) {
-    float y = ToCallTime(alr.log_time_us());
-    if (!previously_in_alr && alr.in_alr) {
-      alr_start = alr.log_time_us();
-      previously_in_alr = true;
-    } else if (previously_in_alr && !alr.in_alr) {
+    if (previously_in_alr) {
       float x = ToCallTime(alr_start);
+      float y = ToCallTime(end_time_);
       alr_state.intervals.emplace_back(x, y);
-      previously_in_alr = false;
+    }
+
+    if (show_detector_state) {
+      plot->AppendIntervalSeries(std::move(overusing_series));
+      plot->AppendIntervalSeries(std::move(underusing_series));
+      plot->AppendIntervalSeries(std::move(normal_series));
+    }
+
+    if (show_alr_state) {
+      plot->AppendIntervalSeries(std::move(alr_state));
+    }
+    plot->AppendTimeSeries(std::move(loss_series));
+    plot->AppendTimeSeries(std::move(delay_series));
+    plot->AppendTimeSeries(std::move(created_series));
+    plot->AppendTimeSeries(std::move(result_series));
+  }
+
+  // Overlay the incoming REMB over the outgoing bitrate
+  // and outgoing REMB over incoming bitrate.
+  PacketDirection remb_direction =
+      desired_direction == kOutgoingPacket ? kIncomingPacket : kOutgoingPacket;
+  TimeSeries remb_series("Remb", LineStyle::kStep);
+  std::multimap<uint64_t, const LoggedRtcpPacket*> remb_packets;
+  for (const auto& kv : rtcp_packets_) {
+    if (kv.first.GetDirection() == remb_direction) {
+      for (const LoggedRtcpPacket& rtcp_packet : kv.second) {
+        if (rtcp_packet.type == kRtcpRemb) {
+          remb_packets.insert(
+              std::make_pair(rtcp_packet.timestamp, &rtcp_packet));
+        }
+      }
     }
   }
 
-  if (previously_in_alr) {
-    float x = ToCallTime(alr_start);
-    float y = ToCallTime(end_time_);
-    alr_state.intervals.emplace_back(x, y);
-  }
-
-  if (show_detector_state) {
-    plot->AppendIntervalSeries(std::move(overusing_series));
-    plot->AppendIntervalSeries(std::move(underusing_series));
-    plot->AppendIntervalSeries(std::move(normal_series));
-  }
-
-  if (show_alr_state) {
-    plot->AppendIntervalSeries(std::move(alr_state));
-  }
-  plot->AppendTimeSeries(std::move(loss_series));
-  plot->AppendTimeSeries(std::move(delay_series));
-  plot->AppendTimeSeries(std::move(created_series));
-  plot->AppendTimeSeries(std::move(result_series));
-
-  // Overlay the incoming REMB over the outgoing bitrate.
-  TimeSeries remb_series("Remb", LineStyle::kStep);
-  for (const auto& rtcp : parsed_log_.rembs(kIncomingPacket)) {
-    float x = ToCallTime(rtcp.log_time_us());
-    float y = static_cast<float>(rtcp.remb.bitrate_bps()) / 1000;
+  for (const auto& kv : remb_packets) {
+    const LoggedRtcpPacket* const rtcp = kv.second;
+    const rtcp::Remb* const remb = static_cast<rtcp::Remb*>(rtcp->packet.get());
+    float x = ToCallTime(rtcp->timestamp);
+    float y = static_cast<float>(remb->bitrate_bps()) / 1000;
     remb_series.points.emplace_back(x, y);
   }
   plot->AppendTimeSeriesIfNotEmpty(std::move(remb_series));
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Bitrate (kbps)", kBottomMargin, kTopMargin);
-  plot->SetTitle("Outgoing RTP bitrate");
+  if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
+    plot->SetTitle("Incoming RTP bitrate");
+  } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
+    plot->SetTitle("Outgoing RTP bitrate");
+  }
 }
 
 // For each SSRC, plot the bandwidth used by that stream.
-void EventLogAnalyzer::CreateStreamBitrateGraph(PacketDirection direction,
-                                                Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    // Filter on SSRC.
-    if (!MatchingSsrc(stream.ssrc, desired_ssrc_)) {
+void EventLogAnalyzer::CreateStreamBitrateGraph(
+    PacketDirection desired_direction,
+    Plot* plot) {
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
+    // Filter on direction and SSRC.
+    if (stream_id.GetDirection() != desired_direction ||
+        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
       continue;
     }
 
-    TimeSeries time_series(GetStreamName(direction, stream.ssrc),
-                           LineStyle::kLine);
-    auto GetPacketSizeKilobits = [](const LoggedRtpPacket& packet) {
-      return packet.total_length * 8.0 / 1000.0;
-    };
+    TimeSeries time_series(GetStreamName(stream_id), LineStyle::kLine);
     MovingAverage<LoggedRtpPacket, double>(
-        GetPacketSizeKilobits, stream.packet_view, begin_time_, end_time_,
-        window_duration_, step_, &time_series);
+        [](const LoggedRtpPacket& packet) {
+          return packet.total_length * 8.0 / 1000.0;
+        },
+        packet_stream, begin_time_, end_time_, window_duration_, step_,
+        &time_series);
     plot->AppendTimeSeries(std::move(time_series));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Bitrate (kbps)", kBottomMargin, kTopMargin);
-  plot->SetTitle(GetDirectionAsString(direction) + " bitrate per stream");
+  if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
+    plot->SetTitle("Incoming bitrate per stream");
+  } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
+    plot->SetTitle("Outgoing bitrate per stream");
+  }
 }
 
 void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) {
-  using RtpPacketType = LoggedRtpPacketOutgoing;
-  using TransportFeedbackType = LoggedRtcpPacketTransportFeedback;
+  std::multimap<uint64_t, const LoggedRtpPacket*> outgoing_rtp;
+  std::multimap<uint64_t, const LoggedRtcpPacket*> incoming_rtcp;
 
-  // TODO(terelius): This could be provided by the parser.
-  std::multimap<int64_t, const RtpPacketType*> outgoing_rtp;
-  for (const auto& stream : parsed_log_.outgoing_rtp_packets_by_ssrc()) {
-    for (const RtpPacketType& rtp_packet : stream.outgoing_packets)
-      outgoing_rtp.insert(
-          std::make_pair(rtp_packet.rtp.log_time_us(), &rtp_packet));
+  for (const auto& kv : rtp_packets_) {
+    if (kv.first.GetDirection() == PacketDirection::kOutgoingPacket) {
+      for (const LoggedRtpPacket& rtp_packet : kv.second)
+        outgoing_rtp.insert(std::make_pair(rtp_packet.timestamp, &rtp_packet));
+    }
   }
 
-  const std::vector<TransportFeedbackType>& incoming_rtcp =
-      parsed_log_.transport_feedbacks(kIncomingPacket);
+  for (const auto& kv : rtcp_packets_) {
+    if (kv.first.GetDirection() == PacketDirection::kIncomingPacket) {
+      for (const LoggedRtcpPacket& rtcp_packet : kv.second)
+        incoming_rtcp.insert(
+            std::make_pair(rtcp_packet.timestamp, &rtcp_packet));
+    }
+  }
 
   SimulatedClock clock(0);
   BitrateObserver observer;
@@ -1091,7 +1367,7 @@
 
   auto NextRtcpTime = [&]() {
     if (rtcp_iterator != incoming_rtcp.end())
-      return static_cast<int64_t>(rtcp_iterator->log_time_us());
+      return static_cast<int64_t>(rtcp_iterator->first);
     return std::numeric_limits<int64_t>::max();
   };
 
@@ -1122,38 +1398,41 @@
     clock.AdvanceTimeMicroseconds(time_us - clock.TimeInMicroseconds());
     if (clock.TimeInMicroseconds() >= NextRtcpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtcpTime());
-      cc.OnTransportFeedback(rtcp_iterator->transport_feedback);
-      std::vector<PacketFeedback> feedback = cc.GetTransportFeedbackVector();
-      SortPacketFeedbackVector(&feedback);
-      rtc::Optional<uint32_t> bitrate_bps;
-      if (!feedback.empty()) {
+      const LoggedRtcpPacket& rtcp = *rtcp_iterator->second;
+      if (rtcp.type == kRtcpTransportFeedback) {
+        cc.OnTransportFeedback(
+            *static_cast<rtcp::TransportFeedback*>(rtcp.packet.get()));
+        std::vector<PacketFeedback> feedback = cc.GetTransportFeedbackVector();
+        SortPacketFeedbackVector(&feedback);
+        rtc::Optional<uint32_t> bitrate_bps;
+        if (!feedback.empty()) {
 #if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
-        acknowledged_bitrate_estimator.IncomingPacketFeedbackVector(feedback);
+          acknowledged_bitrate_estimator.IncomingPacketFeedbackVector(feedback);
 #endif  // !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
-        for (const PacketFeedback& packet : feedback)
-          acked_bitrate.Update(packet.payload_size, packet.arrival_time_ms);
-        bitrate_bps = acked_bitrate.Rate(feedback.back().arrival_time_ms);
+          for (const PacketFeedback& packet : feedback)
+            acked_bitrate.Update(packet.payload_size, packet.arrival_time_ms);
+          bitrate_bps = acked_bitrate.Rate(feedback.back().arrival_time_ms);
+        }
+        float x = ToCallTime(clock.TimeInMicroseconds());
+        float y = bitrate_bps.value_or(0) / 1000;
+        acked_time_series.points.emplace_back(x, y);
+#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
+        y = acknowledged_bitrate_estimator.bitrate_bps().value_or(0) / 1000;
+        acked_estimate_time_series.points.emplace_back(x, y);
+#endif  // !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
       }
-      float x = ToCallTime(clock.TimeInMicroseconds());
-      float y = bitrate_bps.value_or(0) / 1000;
-      acked_time_series.points.emplace_back(x, y);
-#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
-      y = acknowledged_bitrate_estimator.bitrate_bps().value_or(0) / 1000;
-      acked_estimate_time_series.points.emplace_back(x, y);
-#endif  // !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)
       ++rtcp_iterator;
     }
     if (clock.TimeInMicroseconds() >= NextRtpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtpTime());
-      const RtpPacketType& rtp_packet = *rtp_iterator->second;
-      if (rtp_packet.rtp.header.extension.hasTransportSequenceNumber) {
-        RTC_DCHECK(rtp_packet.rtp.header.extension.hasTransportSequenceNumber);
-        cc.AddPacket(rtp_packet.rtp.header.ssrc,
-                     rtp_packet.rtp.header.extension.transportSequenceNumber,
-                     rtp_packet.rtp.total_length, PacedPacketInfo());
+      const LoggedRtpPacket& rtp = *rtp_iterator->second;
+      if (rtp.header.extension.hasTransportSequenceNumber) {
+        RTC_DCHECK(rtp.header.extension.hasTransportSequenceNumber);
+        cc.AddPacket(rtp.header.ssrc,
+                     rtp.header.extension.transportSequenceNumber,
+                     rtp.total_length, PacedPacketInfo());
         rtc::SentPacket sent_packet(
-            rtp_packet.rtp.header.extension.transportSequenceNumber,
-            rtp_packet.rtp.log_time_us() / 1000);
+            rtp.header.extension.transportSequenceNumber, rtp.timestamp / 1000);
         cc.OnSentPacket(sent_packet);
       }
       ++rtp_iterator;
@@ -1182,7 +1461,6 @@
 }
 
 void EventLogAnalyzer::CreateReceiveSideBweSimulationGraph(Plot* plot) {
-  using RtpPacketType = LoggedRtpPacketIncoming;
   class RembInterceptingPacketRouter : public PacketRouter {
    public:
     void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
@@ -1203,19 +1481,19 @@
     bool bitrate_updated_;
   };
 
-  std::multimap<int64_t, const RtpPacketType*> incoming_rtp;
+  std::multimap<uint64_t, const LoggedRtpPacket*> incoming_rtp;
 
-  for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
-    if (IsVideoSsrc(kIncomingPacket, stream.ssrc)) {
-      for (const auto& rtp_packet : stream.incoming_packets)
-        incoming_rtp.insert(
-            std::make_pair(rtp_packet.rtp.log_time_us(), &rtp_packet));
+  for (const auto& kv : rtp_packets_) {
+    if (kv.first.GetDirection() == PacketDirection::kIncomingPacket &&
+        IsVideoSsrc(kv.first)) {
+      for (const LoggedRtpPacket& rtp_packet : kv.second)
+        incoming_rtp.insert(std::make_pair(rtp_packet.timestamp, &rtp_packet));
     }
   }
 
   SimulatedClock clock(0);
   RembInterceptingPacketRouter packet_router;
-  // TODO(terelius): The PacketRouter is used as the RemoteBitrateObserver.
+  // TODO(terelius): The PacketRrouter is the used as the RemoteBitrateObserver.
   // Is this intentional?
   ReceiveSideCongestionController rscc(&clock, &packet_router);
   // TODO(holmer): Log the call config and use that here instead.
@@ -1229,12 +1507,12 @@
   RateStatistics acked_bitrate(250, 8000);
   int64_t last_update_us = 0;
   for (const auto& kv : incoming_rtp) {
-    const RtpPacketType& packet = *kv.second;
-    int64_t arrival_time_ms = packet.rtp.log_time_us() / 1000;
-    size_t payload = packet.rtp.total_length; /*Should subtract header?*/
-    clock.AdvanceTimeMicroseconds(packet.rtp.log_time_us() -
+    const LoggedRtpPacket& packet = *kv.second;
+    int64_t arrival_time_ms = packet.timestamp / 1000;
+    size_t payload = packet.total_length; /*Should subtract header?*/
+    clock.AdvanceTimeMicroseconds(packet.timestamp -
                                   clock.TimeInMicroseconds());
-    rscc.OnReceivedPacket(arrival_time_ms, payload, packet.rtp.header);
+    rscc.OnReceivedPacket(arrival_time_ms, payload, packet.header);
     acked_bitrate.Update(payload, arrival_time_ms);
     rtc::Optional<uint32_t> bitrate_bps = acked_bitrate.Rate(arrival_time_ms);
     if (bitrate_bps) {
@@ -1260,19 +1538,23 @@
 }
 
 void EventLogAnalyzer::CreateNetworkDelayFeedbackGraph(Plot* plot) {
-  using RtpPacketType = LoggedRtpPacketOutgoing;
-  using TransportFeedbackType = LoggedRtcpPacketTransportFeedback;
+  std::multimap<uint64_t, const LoggedRtpPacket*> outgoing_rtp;
+  std::multimap<uint64_t, const LoggedRtcpPacket*> incoming_rtcp;
 
-  // TODO(terelius): This could be provided by the parser.
-  std::multimap<int64_t, const RtpPacketType*> outgoing_rtp;
-  for (const auto& stream : parsed_log_.outgoing_rtp_packets_by_ssrc()) {
-    for (const RtpPacketType& rtp_packet : stream.outgoing_packets)
-      outgoing_rtp.insert(
-          std::make_pair(rtp_packet.rtp.log_time_us(), &rtp_packet));
+  for (const auto& kv : rtp_packets_) {
+    if (kv.first.GetDirection() == PacketDirection::kOutgoingPacket) {
+      for (const LoggedRtpPacket& rtp_packet : kv.second)
+        outgoing_rtp.insert(std::make_pair(rtp_packet.timestamp, &rtp_packet));
+    }
   }
 
-  const std::vector<TransportFeedbackType>& incoming_rtcp =
-      parsed_log_.transport_feedbacks(kIncomingPacket);
+  for (const auto& kv : rtcp_packets_) {
+    if (kv.first.GetDirection() == PacketDirection::kIncomingPacket) {
+      for (const LoggedRtcpPacket& rtcp_packet : kv.second)
+        incoming_rtcp.insert(
+            std::make_pair(rtcp_packet.timestamp, &rtcp_packet));
+    }
+  }
 
   SimulatedClock clock(0);
   TransportFeedbackAdapter feedback_adapter(&clock);
@@ -1294,7 +1576,7 @@
 
   auto NextRtcpTime = [&]() {
     if (rtcp_iterator != incoming_rtcp.end())
-      return static_cast<int64_t>(rtcp_iterator->log_time_us());
+      return static_cast<int64_t>(rtcp_iterator->first);
     return std::numeric_limits<int64_t>::max();
   };
 
@@ -1304,34 +1586,37 @@
     clock.AdvanceTimeMicroseconds(time_us - clock.TimeInMicroseconds());
     if (clock.TimeInMicroseconds() >= NextRtcpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtcpTime());
-      feedback_adapter.OnTransportFeedback(rtcp_iterator->transport_feedback);
-      std::vector<PacketFeedback> feedback =
-          feedback_adapter.GetTransportFeedbackVector();
-      SortPacketFeedbackVector(&feedback);
-      for (const PacketFeedback& packet : feedback) {
-        float x = ToCallTime(clock.TimeInMicroseconds());
-        if (packet.send_time_ms == PacketFeedback::kNoSendTime) {
-          late_feedback_series.points.emplace_back(x, prev_y);
-          continue;
+      const LoggedRtcpPacket& rtcp = *rtcp_iterator->second;
+      if (rtcp.type == kRtcpTransportFeedback) {
+        feedback_adapter.OnTransportFeedback(
+            *static_cast<rtcp::TransportFeedback*>(rtcp.packet.get()));
+        std::vector<PacketFeedback> feedback =
+            feedback_adapter.GetTransportFeedbackVector();
+        SortPacketFeedbackVector(&feedback);
+        for (const PacketFeedback& packet : feedback) {
+          float x = ToCallTime(clock.TimeInMicroseconds());
+          if (packet.send_time_ms == PacketFeedback::kNoSendTime) {
+            late_feedback_series.points.emplace_back(x, prev_y);
+            continue;
+          }
+          int64_t y = packet.arrival_time_ms - packet.send_time_ms;
+          prev_y = y;
+          estimated_base_delay_ms = std::min(y, estimated_base_delay_ms);
+          time_series.points.emplace_back(x, y);
         }
-        int64_t y = packet.arrival_time_ms - packet.send_time_ms;
-        prev_y = y;
-        estimated_base_delay_ms = std::min(y, estimated_base_delay_ms);
-        time_series.points.emplace_back(x, y);
       }
       ++rtcp_iterator;
     }
     if (clock.TimeInMicroseconds() >= NextRtpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtpTime());
-      const RtpPacketType& rtp_packet = *rtp_iterator->second;
-      if (rtp_packet.rtp.header.extension.hasTransportSequenceNumber) {
-        feedback_adapter.AddPacket(
-            rtp_packet.rtp.header.ssrc,
-            rtp_packet.rtp.header.extension.transportSequenceNumber,
-            rtp_packet.rtp.total_length, PacedPacketInfo());
+      const LoggedRtpPacket& rtp = *rtp_iterator->second;
+      if (rtp.header.extension.hasTransportSequenceNumber) {
+        RTC_DCHECK(rtp.header.extension.hasTransportSequenceNumber);
+        feedback_adapter.AddPacket(rtp.header.ssrc,
+                                   rtp.header.extension.transportSequenceNumber,
+                                   rtp.total_length, PacedPacketInfo());
         feedback_adapter.OnSentPacket(
-            rtp_packet.rtp.header.extension.transportSequenceNumber,
-            rtp_packet.rtp.log_time_us() / 1000);
+            rtp.header.extension.transportSequenceNumber, rtp.timestamp / 1000);
       }
       ++rtp_iterator;
     }
@@ -1352,10 +1637,40 @@
   plot->SetTitle("Network Delay Change.");
 }
 
+std::vector<std::pair<int64_t, int64_t>> EventLogAnalyzer::GetFrameTimestamps()
+    const {
+  std::vector<std::pair<int64_t, int64_t>> timestamps;
+  size_t largest_stream_size = 0;
+  const std::vector<LoggedRtpPacket>* largest_video_stream = nullptr;
+  // Find the incoming video stream with the most number of packets that is
+  // not rtx.
+  for (const auto& kv : rtp_packets_) {
+    if (kv.first.GetDirection() == kIncomingPacket &&
+        video_ssrcs_.find(kv.first) != video_ssrcs_.end() &&
+        rtx_ssrcs_.find(kv.first) == rtx_ssrcs_.end() &&
+        kv.second.size() > largest_stream_size) {
+      largest_stream_size = kv.second.size();
+      largest_video_stream = &kv.second;
+    }
+  }
+  if (largest_video_stream == nullptr) {
+    for (auto& packet : *largest_video_stream) {
+      if (packet.header.markerBit) {
+        int64_t capture_ms = packet.header.timestamp / 90.0;
+        int64_t arrival_ms = packet.timestamp / 1000.0;
+        timestamps.push_back(std::make_pair(capture_ms, arrival_ms));
+      }
+    }
+  }
+  return timestamps;
+}
+
 void EventLogAnalyzer::CreatePacerDelayGraph(Plot* plot) {
-  for (const auto& stream : parsed_log_.outgoing_rtp_packets_by_ssrc()) {
-    const std::vector<LoggedRtpPacketOutgoing>& packets =
-        stream.outgoing_packets;
+  for (const auto& kv : rtp_packets_) {
+    const std::vector<LoggedRtpPacket>& packets = kv.second;
+    StreamId stream_id = kv.first;
+    if (stream_id.GetDirection() == kIncomingPacket)
+      continue;
 
     if (packets.size() < 2) {
       RTC_LOG(LS_WARNING)
@@ -1363,15 +1678,11 @@
              "pacer delay with less than 2 packets in the stream";
       continue;
     }
-    int64_t end_time_us = log_segments_.empty()
-                              ? std::numeric_limits<int64_t>::max()
-                              : log_segments_.front().second;
     rtc::Optional<uint32_t> estimated_frequency =
-        EstimateRtpClockFrequency(packets, end_time_us);
+        EstimateRtpClockFrequency(packets);
     if (!estimated_frequency)
       continue;
-    if (IsVideoSsrc(kOutgoingPacket, stream.ssrc) &&
-        *estimated_frequency != 90000) {
+    if (IsVideoSsrc(stream_id) && *estimated_frequency != 90000) {
       RTC_LOG(LS_WARNING)
           << "Video stream should use a 90 kHz clock but appears to use "
           << *estimated_frequency / 1000 << ". Discarding.";
@@ -1379,22 +1690,21 @@
     }
 
     TimeSeries pacer_delay_series(
-        GetStreamName(kOutgoingPacket, stream.ssrc) + "(" +
+        GetStreamName(stream_id) + "(" +
             std::to_string(*estimated_frequency / 1000) + " kHz)",
         LineStyle::kLine, PointStyle::kHighlight);
     SeqNumUnwrapper<uint32_t> timestamp_unwrapper;
     uint64_t first_capture_timestamp =
-        timestamp_unwrapper.Unwrap(packets.front().rtp.header.timestamp);
-    uint64_t first_send_timestamp = packets.front().rtp.log_time_us();
-    for (const auto& packet : packets) {
+        timestamp_unwrapper.Unwrap(packets.front().header.timestamp);
+    uint64_t first_send_timestamp = packets.front().timestamp;
+    for (LoggedRtpPacket packet : packets) {
       double capture_time_ms = (static_cast<double>(timestamp_unwrapper.Unwrap(
-                                    packet.rtp.header.timestamp)) -
+                                    packet.header.timestamp)) -
                                 first_capture_timestamp) /
                                *estimated_frequency * 1000;
       double send_time_ms =
-          static_cast<double>(packet.rtp.log_time_us() - first_send_timestamp) /
-          1000;
-      float x = ToCallTime(packet.rtp.log_time_us());
+          static_cast<double>(packet.timestamp - first_send_timestamp) / 1000;
+      float x = ToCallTime(packet.timestamp);
       float y = send_time_ms - capture_time_ms;
       pacer_delay_series.points.emplace_back(x, y);
     }
@@ -1407,52 +1717,58 @@
       "Delay from capture to send time. (First packet normalized to 0.)");
 }
 
-void EventLogAnalyzer::CreateTimestampGraph(PacketDirection direction,
-                                            Plot* plot) {
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    TimeSeries rtp_timestamps(
-        GetStreamName(direction, stream.ssrc) + " capture-time",
-        LineStyle::kLine, PointStyle::kHighlight);
-    for (const auto& packet : stream.packet_view) {
-      float x = ToCallTime(packet.log_time_us());
-      float y = packet.header.timestamp;
-      rtp_timestamps.points.emplace_back(x, y);
-    }
-    plot->AppendTimeSeries(std::move(rtp_timestamps));
+void EventLogAnalyzer::CreateTimestampGraph(Plot* plot) {
+  for (const auto& kv : rtp_packets_) {
+    const std::vector<LoggedRtpPacket>& rtp_packets = kv.second;
+    StreamId stream_id = kv.first;
 
-    TimeSeries rtcp_timestamps(
-        GetStreamName(direction, stream.ssrc) + " rtcp capture-time",
-        LineStyle::kLine, PointStyle::kHighlight);
-    // TODO(terelius): Why only sender reports?
-    const auto& sender_reports = parsed_log_.sender_reports(direction);
-    for (const auto& rtcp : sender_reports) {
-      if (rtcp.sr.sender_ssrc() != stream.ssrc)
-        continue;
-      float x = ToCallTime(rtcp.log_time_us());
-      float y = rtcp.sr.rtp_timestamp();
-      rtcp_timestamps.points.emplace_back(x, y);
+    {
+      TimeSeries timestamp_data(GetStreamName(stream_id) + " capture-time",
+                                LineStyle::kLine, PointStyle::kHighlight);
+      for (LoggedRtpPacket packet : rtp_packets) {
+        float x = ToCallTime(packet.timestamp);
+        float y = packet.header.timestamp;
+        timestamp_data.points.emplace_back(x, y);
+      }
+      plot->AppendTimeSeries(std::move(timestamp_data));
     }
-    plot->AppendTimeSeriesIfNotEmpty(std::move(rtcp_timestamps));
+
+    {
+      auto kv = rtcp_packets_.find(stream_id);
+      if (kv != rtcp_packets_.end()) {
+        const auto& packets = kv->second;
+        TimeSeries timestamp_data(
+            GetStreamName(stream_id) + " rtcp capture-time", LineStyle::kLine,
+            PointStyle::kHighlight);
+        for (const LoggedRtcpPacket& rtcp : packets) {
+          if (rtcp.type != kRtcpSr)
+            continue;
+          rtcp::SenderReport* sr;
+          sr = static_cast<rtcp::SenderReport*>(rtcp.packet.get());
+          float x = ToCallTime(rtcp.timestamp);
+          float y = sr->rtp_timestamp();
+          timestamp_data.points.emplace_back(x, y);
+        }
+        plot->AppendTimeSeries(std::move(timestamp_data));
+      }
+    }
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
-  plot->SetSuggestedYAxis(0, 1, "RTP timestamp", kBottomMargin, kTopMargin);
-  plot->SetTitle(GetDirectionAsString(direction) + " timestamps");
+  plot->SetSuggestedYAxis(0, 1, "Timestamp (90khz)", kBottomMargin, kTopMargin);
+  plot->SetTitle("Timestamps");
 }
 
 void EventLogAnalyzer::CreateAudioEncoderTargetBitrateGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder target bitrate", LineStyle::kLine,
                          PointStyle::kHighlight);
-  auto GetAnaBitrateBps = [](const LoggedAudioNetworkAdaptationEvent& ana_event)
-      -> rtc::Optional<float> {
-    if (ana_event.config.bitrate_bps)
-      return rtc::Optional<float>(
-          static_cast<float>(*ana_event.config.bitrate_bps));
-    return rtc::nullopt;
-  };
-  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
-      GetAnaBitrateBps, parsed_log_.audio_network_adaptation_events(),
-      begin_time_, &time_series);
+  ProcessPoints<AudioNetworkAdaptationEvent>(
+      [](const AudioNetworkAdaptationEvent& ana_event) -> rtc::Optional<float> {
+        if (ana_event.config.bitrate_bps)
+          return static_cast<float>(*ana_event.config.bitrate_bps);
+        return rtc::nullopt;
+      },
+      audio_network_adaptation_events_, begin_time_, &time_series);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Bitrate (bps)", kBottomMargin, kTopMargin);
@@ -1462,16 +1778,14 @@
 void EventLogAnalyzer::CreateAudioEncoderFrameLengthGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder frame length", LineStyle::kLine,
                          PointStyle::kHighlight);
-  auto GetAnaFrameLengthMs =
-      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
+  ProcessPoints<AudioNetworkAdaptationEvent>(
+      [](const AudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.frame_length_ms)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.frame_length_ms));
         return rtc::Optional<float>();
-      };
-  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
-      GetAnaFrameLengthMs, parsed_log_.audio_network_adaptation_events(),
-      begin_time_, &time_series);
+      },
+      audio_network_adaptation_events_, begin_time_, &time_series);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Frame length (ms)", kBottomMargin, kTopMargin);
@@ -1481,16 +1795,14 @@
 void EventLogAnalyzer::CreateAudioEncoderPacketLossGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder uplink packet loss fraction",
                          LineStyle::kLine, PointStyle::kHighlight);
-  auto GetAnaPacketLoss =
-      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
+  ProcessPoints<AudioNetworkAdaptationEvent>(
+      [](const AudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.uplink_packet_loss_fraction)
           return rtc::Optional<float>(static_cast<float>(
               *ana_event.config.uplink_packet_loss_fraction));
         return rtc::Optional<float>();
-      };
-  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
-      GetAnaPacketLoss, parsed_log_.audio_network_adaptation_events(),
-      begin_time_, &time_series);
+      },
+      audio_network_adaptation_events_, begin_time_, &time_series);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 10, "Percent lost packets", kBottomMargin,
@@ -1501,16 +1813,14 @@
 void EventLogAnalyzer::CreateAudioEncoderEnableFecGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder FEC", LineStyle::kLine,
                          PointStyle::kHighlight);
-  auto GetAnaFecEnabled =
-      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
+  ProcessPoints<AudioNetworkAdaptationEvent>(
+      [](const AudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.enable_fec)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.enable_fec));
         return rtc::Optional<float>();
-      };
-  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
-      GetAnaFecEnabled, parsed_log_.audio_network_adaptation_events(),
-      begin_time_, &time_series);
+      },
+      audio_network_adaptation_events_, begin_time_, &time_series);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "FEC (false/true)", kBottomMargin, kTopMargin);
@@ -1520,16 +1830,14 @@
 void EventLogAnalyzer::CreateAudioEncoderEnableDtxGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder DTX", LineStyle::kLine,
                          PointStyle::kHighlight);
-  auto GetAnaDtxEnabled =
-      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
+  ProcessPoints<AudioNetworkAdaptationEvent>(
+      [](const AudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.enable_dtx)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.enable_dtx));
         return rtc::Optional<float>();
-      };
-  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
-      GetAnaDtxEnabled, parsed_log_.audio_network_adaptation_events(),
-      begin_time_, &time_series);
+      },
+      audio_network_adaptation_events_, begin_time_, &time_series);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "DTX (false/true)", kBottomMargin, kTopMargin);
@@ -1539,16 +1847,14 @@
 void EventLogAnalyzer::CreateAudioEncoderNumChannelsGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder number of channels", LineStyle::kLine,
                          PointStyle::kHighlight);
-  auto GetAnaNumChannels =
-      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
+  ProcessPoints<AudioNetworkAdaptationEvent>(
+      [](const AudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.num_channels)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.num_channels));
         return rtc::Optional<float>();
-      };
-  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
-      GetAnaNumChannels, parsed_log_.audio_network_adaptation_events(),
-      begin_time_, &time_series);
+      },
+      audio_network_adaptation_events_, begin_time_, &time_series);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Number of channels (1 (mono)/2 (stereo))",
@@ -1560,9 +1866,9 @@
  public:
   // Does not take any ownership, and all pointers must refer to valid objects
   // that outlive the one constructed.
-  NetEqStreamInput(const std::vector<LoggedRtpPacketIncoming>* packet_stream,
-                   const std::vector<int64_t>* output_events_us,
-                   rtc::Optional<int64_t> end_time_us)
+  NetEqStreamInput(const std::vector<LoggedRtpPacket>* packet_stream,
+                   const std::vector<uint64_t>* output_events_us,
+                   rtc::Optional<uint64_t> end_time_us)
       : packet_stream_(*packet_stream),
         packet_stream_it_(packet_stream_.begin()),
         output_events_us_it_(output_events_us->begin()),
@@ -1576,11 +1882,11 @@
     if (packet_stream_it_ == packet_stream_.end()) {
       return rtc::nullopt;
     }
-    if (end_time_us_ && packet_stream_it_->rtp.log_time_us() > *end_time_us_) {
+    if (end_time_us_ && packet_stream_it_->timestamp > *end_time_us_) {
       return rtc::nullopt;
     }
     // Convert from us to ms.
-    return packet_stream_it_->rtp.log_time_us() / 1000;
+    return packet_stream_it_->timestamp / 1000;
   }
 
   rtc::Optional<int64_t> NextOutputEventTime() const override {
@@ -1599,14 +1905,14 @@
       return std::unique_ptr<PacketData>();
     }
     std::unique_ptr<PacketData> packet_data(new PacketData());
-    packet_data->header = packet_stream_it_->rtp.header;
+    packet_data->header = packet_stream_it_->header;
     // Convert from us to ms.
-    packet_data->time_ms = packet_stream_it_->rtp.log_time_us() / 1000.0;
+    packet_data->time_ms = packet_stream_it_->timestamp / 1000.0;
 
     // This is a header-only "dummy" packet. Set the payload to all zeros, with
     // length according to the virtual length.
-    packet_data->payload.SetSize(packet_stream_it_->rtp.total_length -
-                                 packet_stream_it_->rtp.header_length);
+    packet_data->payload.SetSize(packet_stream_it_->total_length -
+                                 packet_stream_it_->header_length);
     std::fill_n(packet_data->payload.data(), packet_data->payload.size(), 0);
 
     ++packet_stream_it_;
@@ -1625,15 +1931,15 @@
     if (packet_stream_it_ == packet_stream_.end()) {
       return rtc::nullopt;
     }
-    return packet_stream_it_->rtp.header;
+    return packet_stream_it_->header;
   }
 
  private:
-  const std::vector<LoggedRtpPacketIncoming>& packet_stream_;
-  std::vector<LoggedRtpPacketIncoming>::const_iterator packet_stream_it_;
-  std::vector<int64_t>::const_iterator output_events_us_it_;
-  const std::vector<int64_t>::const_iterator output_events_us_end_;
-  const rtc::Optional<int64_t> end_time_us_;
+  const std::vector<LoggedRtpPacket>& packet_stream_;
+  std::vector<LoggedRtpPacket>::const_iterator packet_stream_it_;
+  std::vector<uint64_t>::const_iterator output_events_us_it_;
+  const std::vector<uint64_t>::const_iterator output_events_us_end_;
+  const rtc::Optional<uint64_t> end_time_us_;
 };
 
 namespace {
@@ -1641,9 +1947,9 @@
 // the test and returns the NetEqDelayAnalyzer object that was used to
 // instrument the test.
 std::unique_ptr<test::NetEqDelayAnalyzer> CreateNetEqTestAndRun(
-    const std::vector<LoggedRtpPacketIncoming>* packet_stream,
-    const std::vector<int64_t>* output_events_us,
-    rtc::Optional<int64_t> end_time_us,
+    const std::vector<LoggedRtpPacket>* packet_stream,
+    const std::vector<uint64_t>* output_events_us,
+    rtc::Optional<uint64_t> end_time_us,
     const std::string& replacement_file_name,
     int file_sample_rate_hz) {
   std::unique_ptr<test::NetEqInput> input(
@@ -1698,35 +2004,34 @@
     const std::string& replacement_file_name,
     int file_sample_rate_hz,
     Plot* plot) {
-  const std::vector<LoggedRtpPacketIncoming>* audio_packets = nullptr;
-  uint32_t ssrc;
-  for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
-    if (IsAudioSsrc(kIncomingPacket, stream.ssrc)) {
-      audio_packets = &stream.incoming_packets;
-      ssrc = stream.ssrc;
-      break;
-    }
-  }
-  if (audio_packets == nullptr) {
+  const auto& incoming_audio_kv = std::find_if(
+      rtp_packets_.begin(), rtp_packets_.end(),
+      [this](std::pair<StreamId, std::vector<LoggedRtpPacket>> kv) {
+        return kv.first.GetDirection() == kIncomingPacket &&
+               this->IsAudioSsrc(kv.first);
+      });
+  if (incoming_audio_kv == rtp_packets_.end()) {
     // No incoming audio stream found.
     return;
   }
 
-  std::map<uint32_t, std::vector<int64_t>>::const_iterator output_events_it =
-      parsed_log_.audio_playout_events().find(ssrc);
-  if (output_events_it == parsed_log_.audio_playout_events().end()) {
+  const uint32_t ssrc = incoming_audio_kv->first.GetSsrc();
+
+  std::map<uint32_t, std::vector<uint64_t>>::const_iterator output_events_it =
+      audio_playout_events_.find(ssrc);
+  if (output_events_it == audio_playout_events_.end()) {
     // Could not find output events with SSRC matching the input audio stream.
     // Using the first available stream of output events.
-    output_events_it = parsed_log_.audio_playout_events().cbegin();
+    output_events_it = audio_playout_events_.cbegin();
   }
 
-  rtc::Optional<int64_t> end_time_us =
+  rtc::Optional<uint64_t> end_time_us =
       log_segments_.empty()
           ? rtc::nullopt
-          : rtc::Optional<int64_t>(log_segments_.front().second);
+          : rtc::Optional<uint64_t>(log_segments_.front().second);
 
   auto delay_cb = CreateNetEqTestAndRun(
-      audio_packets, &output_events_it->second, end_time_us,
+      &incoming_audio_kv->second, &output_events_it->second, end_time_us,
       replacement_file_name, file_sample_rate_hz);
 
   std::vector<float> send_times_s;
@@ -1742,27 +2047,28 @@
   RTC_DCHECK_EQ(send_times_s.size(), playout_delay_ms.size());
   RTC_DCHECK_EQ(send_times_s.size(), target_delay_ms.size());
 
-  std::map<uint32_t, TimeSeries> time_series_packet_arrival;
-  std::map<uint32_t, TimeSeries> time_series_relative_packet_arrival;
-  std::map<uint32_t, TimeSeries> time_series_play_time;
-  std::map<uint32_t, TimeSeries> time_series_target_time;
+  std::map<StreamId, TimeSeries> time_series_packet_arrival;
+  std::map<StreamId, TimeSeries> time_series_relative_packet_arrival;
+  std::map<StreamId, TimeSeries> time_series_play_time;
+  std::map<StreamId, TimeSeries> time_series_target_time;
   float min_y_axis = 0.f;
   float max_y_axis = 0.f;
+  const StreamId stream_id = incoming_audio_kv->first;
   for (size_t i = 0; i < send_times_s.size(); ++i) {
-    time_series_packet_arrival[ssrc].points.emplace_back(
+    time_series_packet_arrival[stream_id].points.emplace_back(
         TimeSeriesPoint(send_times_s[i], arrival_delay_ms[i]));
-    time_series_relative_packet_arrival[ssrc].points.emplace_back(
+    time_series_relative_packet_arrival[stream_id].points.emplace_back(
         TimeSeriesPoint(send_times_s[i], corrected_arrival_delay_ms[i]));
     min_y_axis = std::min(min_y_axis, corrected_arrival_delay_ms[i]);
     max_y_axis = std::max(max_y_axis, corrected_arrival_delay_ms[i]);
     if (playout_delay_ms[i]) {
-      time_series_play_time[ssrc].points.emplace_back(
+      time_series_play_time[stream_id].points.emplace_back(
           TimeSeriesPoint(send_times_s[i], *playout_delay_ms[i]));
       min_y_axis = std::min(min_y_axis, *playout_delay_ms[i]);
       max_y_axis = std::max(max_y_axis, *playout_delay_ms[i]);
     }
     if (target_delay_ms[i]) {
-      time_series_target_time[ssrc].points.emplace_back(
+      time_series_target_time[stream_id].points.emplace_back(
           TimeSeriesPoint(send_times_s[i], *target_delay_ms[i]));
       min_y_axis = std::min(min_y_axis, *target_delay_ms[i]);
       max_y_axis = std::max(max_y_axis, *target_delay_ms[i]);
@@ -1800,7 +2106,7 @@
 
 void EventLogAnalyzer::CreateIceCandidatePairConfigGraph(Plot* plot) {
   std::map<uint32_t, TimeSeries> configs_by_cp_id;
-  for (const auto& config : parsed_log_.ice_candidate_pair_configs()) {
+  for (const auto& config : ice_candidate_pair_configs_) {
     if (configs_by_cp_id.find(config.candidate_pair_id) ==
         configs_by_cp_id.end()) {
       const std::string candidate_pair_desc =
@@ -1812,7 +2118,7 @@
       candidate_pair_desc_by_id_[config.candidate_pair_id] =
           candidate_pair_desc;
     }
-    float x = ToCallTime(config.log_time_us());
+    float x = ToCallTime(config.timestamp);
     float y = static_cast<float>(config.type);
     configs_by_cp_id[config.candidate_pair_id].points.emplace_back(x, y);
   }
@@ -1836,7 +2142,7 @@
       candidate_pair_desc_by_id_.end()) {
     return candidate_pair_desc_by_id_[candidate_pair_id];
   }
-  for (const auto& config : parsed_log_.ice_candidate_pair_configs()) {
+  for (const auto& config : ice_candidate_pair_configs_) {
     // TODO(qingsi): Add the handling of the "Updated" config event after the
     // visualization of property change for candidate pairs is introduced.
     if (candidate_pair_desc_by_id_.find(config.candidate_pair_id) ==
@@ -1852,7 +2158,7 @@
 
 void EventLogAnalyzer::CreateIceConnectivityCheckGraph(Plot* plot) {
   std::map<uint32_t, TimeSeries> checks_by_cp_id;
-  for (const auto& event : parsed_log_.ice_candidate_pair_events()) {
+  for (const auto& event : ice_candidate_pair_events_) {
     if (checks_by_cp_id.find(event.candidate_pair_id) ==
         checks_by_cp_id.end()) {
       checks_by_cp_id[event.candidate_pair_id] = TimeSeries(
@@ -1860,7 +2166,7 @@
               GetCandidatePairLogDescriptionFromId(event.candidate_pair_id),
           LineStyle::kNone, PointStyle::kHighlight);
     }
-    float x = ToCallTime(event.log_time_us());
+    float x = ToCallTime(event.timestamp);
     float y = static_cast<float>(event.type);
     checks_by_cp_id[event.candidate_pair_id].points.emplace_back(x, y);
   }
@@ -1876,176 +2182,163 @@
   plot->SetTitle("[IceEventLog] ICE connectivity checks");
 }
 
+void EventLogAnalyzer::Notification(
+    std::unique_ptr<TriageNotification> notification) {
+  notifications_.push_back(std::move(notification));
+}
+
 void EventLogAnalyzer::PrintNotifications(FILE* file) {
+  if (notifications_.size() == 0)
+    return;
   fprintf(file, "========== TRIAGE NOTIFICATIONS ==========\n");
-  for (const auto& alert : incoming_rtp_recv_time_gaps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : incoming_rtcp_recv_time_gaps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : outgoing_rtp_send_time_gaps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : outgoing_rtcp_send_time_gaps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : incoming_seq_num_jumps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : incoming_capture_time_jumps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : outgoing_seq_num_jumps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : outgoing_capture_time_jumps_) {
-    fprintf(file, "%3.3lf s : %s\n", alert.Time(), alert.ToString().c_str());
-  }
-  for (const auto& alert : outgoing_high_loss_alerts_) {
-    fprintf(file, "          : %s\n", alert.ToString().c_str());
+  for (const auto& notification : notifications_) {
+    rtc::Optional<float> call_timestamp = notification->Time();
+    if (call_timestamp.has_value()) {
+      fprintf(file, "%3.3lf s : %s\n", call_timestamp.value(),
+              notification->ToString().c_str());
+    } else {
+      fprintf(file, "          : %s\n", notification->ToString().c_str());
+    }
   }
   fprintf(file, "========== END TRIAGE NOTIFICATIONS ==========\n");
 }
 
-void EventLogAnalyzer::CreateStreamGapAlerts(PacketDirection direction) {
-  // With 100 packets/s (~800kbps), false positives would require 10 s without
-  // data.
-  constexpr int64_t kMaxSeqNumJump = 1000;
-  // With a 90 kHz clock, false positives would require 10 s without data.
-  constexpr int64_t kMaxCaptureTimeJump = 900000;
-
-  int64_t end_time_us = log_segments_.empty()
-                            ? std::numeric_limits<int64_t>::max()
-                            : log_segments_.front().second;
-
-  SeqNumUnwrapper<uint16_t> seq_num_unwrapper;
-  rtc::Optional<int64_t> last_seq_num;
-  SeqNumUnwrapper<uint32_t> capture_time_unwrapper;
-  rtc::Optional<int64_t> last_capture_time;
-  // Check for gaps in sequence numbers and capture timestamps.
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    for (const auto& packet : stream.packet_view) {
-      if (packet.log_time_us() > end_time_us) {
-        // Only process the first (LOG_START, LOG_END) segment.
-        break;
-      }
-
-      int64_t seq_num = seq_num_unwrapper.Unwrap(packet.header.sequenceNumber);
-      if (last_seq_num.has_value() &&
-          std::abs(seq_num - last_seq_num.value()) > kMaxSeqNumJump) {
-        Alert_SeqNumJump(direction, ToCallTime(packet.log_time_us()),
-                         packet.header.ssrc);
-      }
-      last_seq_num.emplace(seq_num);
-
-      int64_t capture_time =
-          capture_time_unwrapper.Unwrap(packet.header.timestamp);
-      if (last_capture_time.has_value() &&
-          std::abs(capture_time - last_capture_time.value()) >
-              kMaxCaptureTimeJump) {
-        Alert_CaptureTimeJump(direction, ToCallTime(packet.log_time_us()),
-                              packet.header.ssrc);
-      }
-      last_capture_time.emplace(capture_time);
-    }
-  }
-}
-
-void EventLogAnalyzer::CreateTransmissionGapAlerts(PacketDirection direction) {
-  constexpr int64_t kMaxRtpTransmissionGap = 500000;
-  constexpr int64_t kMaxRtcpTransmissionGap = 2000000;
-  int64_t end_time_us = log_segments_.empty()
-                            ? std::numeric_limits<int64_t>::max()
-                            : log_segments_.front().second;
-
-  // TODO(terelius): The parser could provide a list of all packets, ordered
-  // by time, for each direction.
-  std::multimap<int64_t, const LoggedRtpPacket*> rtp_in_direction;
-  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
-    for (const LoggedRtpPacket& rtp_packet : stream.packet_view)
-      rtp_in_direction.emplace(rtp_packet.log_time_us(), &rtp_packet);
-  }
-  rtc::Optional<int64_t> last_rtp_time;
-  for (const auto& kv : rtp_in_direction) {
-    int64_t timestamp = kv.first;
-    if (timestamp > end_time_us) {
-      // Only process the first (LOG_START, LOG_END) segment.
-      break;
-    }
-    int64_t duration = timestamp - last_rtp_time.value_or(0);
-    if (last_rtp_time.has_value() && duration > kMaxRtpTransmissionGap) {
-      // No packet sent/received for more than 500 ms.
-      Alert_RtpLogTimeGap(direction, ToCallTime(timestamp), duration / 1000);
-    }
-    last_rtp_time.emplace(timestamp);
-  }
-
-  rtc::Optional<int64_t> last_rtcp_time;
-  if (direction == kIncomingPacket) {
-    for (const auto& rtcp : parsed_log_.incoming_rtcp_packets()) {
-      if (rtcp.log_time_us() > end_time_us) {
-        // Only process the first (LOG_START, LOG_END) segment.
-        break;
-      }
-      int64_t duration = rtcp.log_time_us() - last_rtcp_time.value_or(0);
-      if (last_rtcp_time.has_value() && duration > kMaxRtcpTransmissionGap) {
-        // No feedback sent/received for more than 2000 ms.
-        Alert_RtcpLogTimeGap(direction, ToCallTime(rtcp.log_time_us()),
-                             duration / 1000);
-      }
-      last_rtcp_time.emplace(rtcp.log_time_us());
-    }
-  } else {
-    for (const auto& rtcp : parsed_log_.outgoing_rtcp_packets()) {
-      if (rtcp.log_time_us() > end_time_us) {
-        // Only process the first (LOG_START, LOG_END) segment.
-        break;
-      }
-      int64_t duration = rtcp.log_time_us() - last_rtcp_time.value_or(0);
-      if (last_rtcp_time.has_value() && duration > kMaxRtcpTransmissionGap) {
-        // No feedback sent/received for more than 2000 ms.
-        Alert_RtcpLogTimeGap(direction, ToCallTime(rtcp.log_time_us()),
-                             duration / 1000);
-      }
-      last_rtcp_time.emplace(rtcp.log_time_us());
-    }
-  }
-}
-
 // TODO(terelius): Notifications could possibly be generated by the same code
 // that produces the graphs. There is some code duplication that could be
 // avoided, but that might be solved anyway when we move functionality from the
 // analyzer to the parser.
 void EventLogAnalyzer::CreateTriageNotifications() {
-  CreateStreamGapAlerts(kIncomingPacket);
-  CreateStreamGapAlerts(kOutgoingPacket);
-  CreateTransmissionGapAlerts(kIncomingPacket);
-  CreateTransmissionGapAlerts(kOutgoingPacket);
+  uint64_t end_time_us = log_segments_.empty()
+                             ? std::numeric_limits<uint64_t>::max()
+                             : log_segments_.front().second;
+  // Check for gaps in sequence numbers and capture timestamps.
+  for (auto& kv : rtp_packets_) {
+    StreamId stream_id = kv.first;
+    const std::vector<LoggedRtpPacket>& packet_stream = kv.second;
 
-  int64_t end_time_us = log_segments_.empty()
-                            ? std::numeric_limits<int64_t>::max()
-                            : log_segments_.front().second;
+    SeqNumUnwrapper<uint16_t> seq_no_unwrapper;
+    rtc::Optional<int64_t> last_seq_no;
+    SeqNumUnwrapper<uint32_t> timestamp_unwrapper;
+    rtc::Optional<int64_t> last_timestamp;
+    for (const auto& packet : packet_stream) {
+      if (packet.timestamp > end_time_us) {
+        // Only process the first (LOG_START, LOG_END) segment.
+        break;
+      }
+      int64_t seq_no = seq_no_unwrapper.Unwrap(packet.header.sequenceNumber);
+      if (last_seq_no.has_value() &&
+          std::abs(seq_no - last_seq_no.value()) > 1000) {
+        // With roughly 100 packets per second (~800kbps), this would require 10
+        // seconds without data to trigger incorrectly.
+        if (stream_id.GetDirection() == kIncomingPacket) {
+          Notification(rtc::MakeUnique<IncomingSeqNoJump>(
+              ToCallTime(packet.timestamp), packet.header.ssrc));
+        } else {
+          Notification(rtc::MakeUnique<OutgoingSeqNoJump>(
+              ToCallTime(packet.timestamp), packet.header.ssrc));
+        }
+      }
+      last_seq_no.emplace(seq_no);
+      int64_t timestamp = timestamp_unwrapper.Unwrap(packet.header.timestamp);
+      if (last_timestamp.has_value() &&
+          std::abs(timestamp - last_timestamp.value()) > 900000) {
+        // With a 90 kHz clock, this would require 10 seconds without data to
+        // trigger incorrectly.
+        if (stream_id.GetDirection() == kIncomingPacket) {
+          Notification(rtc::MakeUnique<IncomingCaptureTimeJump>(
+              ToCallTime(packet.timestamp), packet.header.ssrc));
+        } else {
+          Notification(rtc::MakeUnique<OutgoingCaptureTimeJump>(
+              ToCallTime(packet.timestamp), packet.header.ssrc));
+        }
+      }
+      last_timestamp.emplace(timestamp);
+    }
+  }
 
-  constexpr double kMaxLossFraction = 0.05;
+  // Check for gaps in RTP and RTCP streams
+  for (const auto direction :
+       {PacketDirection::kIncomingPacket, PacketDirection::kOutgoingPacket}) {
+    // TODO(terelius): The parser could provide a list of all packets, ordered
+    // by time, for each direction.
+    std::multimap<uint64_t, const LoggedRtpPacket*> rtp_in_direction;
+    for (const auto& kv : rtp_packets_) {
+      if (kv.first.GetDirection() == direction) {
+        for (const LoggedRtpPacket& rtp_packet : kv.second)
+          rtp_in_direction.emplace(rtp_packet.timestamp, &rtp_packet);
+      }
+    }
+    rtc::Optional<uint64_t> last_rtp_packet;
+    for (const auto& kv : rtp_in_direction) {
+      uint64_t timestamp = kv.first;
+      if (timestamp > end_time_us) {
+        // Only process the first (LOG_START, LOG_END) segment.
+        break;
+      }
+      int64_t duration = timestamp - last_rtp_packet.value_or(0);
+      if (last_rtp_packet.has_value() && duration > 500000) {
+        // No incoming packet for more than 500 ms.
+        if (direction == kIncomingPacket) {
+          Notification(rtc::MakeUnique<IncomingRtpReceiveTimeGap>(
+              ToCallTime(timestamp), duration / 1000));
+        } else {
+          Notification(rtc::MakeUnique<OutgoingRtpSendTimeGap>(
+              ToCallTime(timestamp), duration / 1000));
+        }
+      }
+      last_rtp_packet.emplace(timestamp);
+    }
+
+    // TODO(terelius): The parser could provide a list of all packets, ordered
+    // by time, for each direction.
+    std::multimap<uint64_t, const LoggedRtcpPacket*> rtcp_in_direction;
+    for (const auto& kv : rtcp_packets_) {
+      if (kv.first.GetDirection() == direction) {
+        for (const LoggedRtcpPacket& rtcp_packet : kv.second)
+          rtcp_in_direction.emplace(rtcp_packet.timestamp, &rtcp_packet);
+      }
+    }
+    rtc::Optional<uint64_t> last_incoming_rtcp_packet;
+    for (const auto& kv : rtcp_in_direction) {
+      uint64_t timestamp = kv.first;
+      if (timestamp > end_time_us) {
+        // Only process the first (LOG_START, LOG_END) segment.
+        break;
+      }
+      int64_t duration = timestamp - last_incoming_rtcp_packet.value_or(0);
+      if (last_incoming_rtcp_packet.has_value() && duration > 2000000) {
+        // No incoming feedback for more than 2000 ms.
+        if (direction == kIncomingPacket) {
+          Notification(rtc::MakeUnique<IncomingRtcpReceiveTimeGap>(
+              ToCallTime(timestamp), duration / 1000));
+        } else {
+          Notification(rtc::MakeUnique<OutgoingRtcpSendTimeGap>(
+              ToCallTime(timestamp), duration / 1000));
+        }
+      }
+      last_incoming_rtcp_packet.emplace(timestamp);
+    }
+  }
+
   // Loss feedback
   int64_t total_lost_packets = 0;
   int64_t total_expected_packets = 0;
-  for (auto& bwe_update : parsed_log_.bwe_loss_updates()) {
-    if (bwe_update.log_time_us() > end_time_us) {
+  for (auto& bwe_update : bwe_loss_updates_) {
+    if (bwe_update.timestamp > end_time_us) {
       // Only process the first (LOG_START, LOG_END) segment.
       break;
     }
-    int64_t lost_packets = static_cast<double>(bwe_update.fraction_lost) / 255 *
+    int64_t lost_packets = static_cast<double>(bwe_update.fraction_loss) / 255 *
                            bwe_update.expected_packets;
     total_lost_packets += lost_packets;
     total_expected_packets += bwe_update.expected_packets;
   }
   double avg_outgoing_loss =
       static_cast<double>(total_lost_packets) / total_expected_packets;
-  if (avg_outgoing_loss > kMaxLossFraction) {
-    Alert_OutgoingHighLoss(avg_outgoing_loss);
+  if (avg_outgoing_loss > 0.05) {
+    Notification(rtc::MakeUnique<OutgoingHighLoss>(avg_outgoing_loss));
   }
 }
 
+}  // namespace plotting
 }  // namespace webrtc
diff --git a/rtc_tools/event_log_visualizer/analyzer.h b/rtc_tools/event_log_visualizer/analyzer.h
index b37de21..a8fedb8 100644
--- a/rtc_tools/event_log_visualizer/analyzer.h
+++ b/rtc_tools/event_log_visualizer/analyzer.h
@@ -18,12 +18,54 @@
 #include <utility>
 #include <vector>
 
-#include "logging/rtc_event_log/rtc_event_log_parser2.h"
-#include "rtc_base/strings/string_builder.h"
+#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/rtcp_packet.h"
+#include "rtc_base/function_view.h"
 #include "rtc_tools/event_log_visualizer/plot_base.h"
 #include "rtc_tools/event_log_visualizer/triage_notifications.h"
 
 namespace webrtc {
+namespace plotting {
+
+struct LoggedRtpPacket {
+  LoggedRtpPacket(uint64_t timestamp,
+                  RTPHeader header,
+                  size_t header_length,
+                  size_t total_length)
+      : timestamp(timestamp),
+        header(header),
+        header_length(header_length),
+        total_length(total_length) {}
+  uint64_t timestamp;
+  // TODO(terelius): This allocates space for 15 CSRCs even if none are used.
+  RTPHeader header;
+  size_t header_length;
+  size_t total_length;
+};
+
+struct LoggedRtcpPacket {
+  LoggedRtcpPacket(uint64_t timestamp,
+                   RTCPPacketType rtcp_type,
+                   std::unique_ptr<rtcp::RtcpPacket> rtcp_packet)
+      : timestamp(timestamp), type(rtcp_type), packet(std::move(rtcp_packet)) {}
+  uint64_t timestamp;
+  RTCPPacketType type;
+  std::unique_ptr<rtcp::RtcpPacket> packet;
+};
+
+struct LossBasedBweUpdate {
+  uint64_t timestamp;
+  int32_t new_bitrate;
+  uint8_t fraction_loss;
+  int32_t expected_packets;
+};
+
+struct AudioNetworkAdaptationEvent {
+  uint64_t timestamp;
+  AudioEncoderRuntimeConfig config;
+};
 
 class EventLogAnalyzer {
  public:
@@ -32,13 +74,14 @@
   // modified while the EventLogAnalyzer is being used.
   explicit EventLogAnalyzer(const ParsedRtcEventLog& log);
 
-  void CreatePacketGraph(PacketDirection direction, Plot* plot);
+  void CreatePacketGraph(PacketDirection desired_direction, Plot* plot);
 
-  void CreateAccumulatedPacketsGraph(PacketDirection direction, Plot* plot);
+  void CreateAccumulatedPacketsGraph(PacketDirection desired_direction,
+                                     Plot* plot);
 
   void CreatePlayoutGraph(Plot* plot);
 
-  void CreateAudioLevelGraph(PacketDirection direction, Plot* plot);
+  void CreateAudioLevelGraph(Plot* plot);
 
   void CreateSequenceNumberGraph(Plot* plot);
 
@@ -49,20 +92,19 @@
 
   void CreateFractionLossGraph(Plot* plot);
 
-  void CreateTotalIncomingBitrateGraph(Plot* plot);
-  void CreateTotalOutgoingBitrateGraph(Plot* plot,
-                                       bool show_detector_state = false,
-                                       bool show_alr_state = false);
+  void CreateTotalBitrateGraph(PacketDirection desired_direction,
+                               Plot* plot,
+                               bool show_detector_state = false,
+                               bool show_alr_state = false);
 
-  void CreateStreamBitrateGraph(PacketDirection direction, Plot* plot);
+  void CreateStreamBitrateGraph(PacketDirection desired_direction, Plot* plot);
 
   void CreateSendSideBweSimulationGraph(Plot* plot);
   void CreateReceiveSideBweSimulationGraph(Plot* plot);
 
   void CreateNetworkDelayFeedbackGraph(Plot* plot);
   void CreatePacerDelayGraph(Plot* plot);
-
-  void CreateTimestampGraph(PacketDirection direction, Plot* plot);
+  void CreateTimestampGraph(Plot* plot);
 
   void CreateAudioEncoderTargetBitrateGraph(Plot* plot);
   void CreateAudioEncoderFrameLengthGraph(Plot* plot);
@@ -77,114 +119,55 @@
   void CreateIceCandidatePairConfigGraph(Plot* plot);
   void CreateIceConnectivityCheckGraph(Plot* plot);
 
+  // Returns a vector of capture and arrival timestamps for the video frames
+  // of the stream with the most number of frames.
+  std::vector<std::pair<int64_t, int64_t>> GetFrameTimestamps() const;
+
   void CreateTriageNotifications();
   void PrintNotifications(FILE* file);
 
  private:
-  bool IsRtxSsrc(PacketDirection direction, uint32_t ssrc) const {
-    if (direction == kIncomingPacket) {
-      return parsed_log_.incoming_rtx_ssrcs().find(ssrc) !=
-             parsed_log_.incoming_rtx_ssrcs().end();
-    } else {
-      return parsed_log_.outgoing_rtx_ssrcs().find(ssrc) !=
-             parsed_log_.outgoing_rtx_ssrcs().end();
+  class StreamId {
+   public:
+    StreamId(uint32_t ssrc, webrtc::PacketDirection direction)
+        : ssrc_(ssrc), direction_(direction) {}
+    bool operator<(const StreamId& other) const {
+      return std::tie(ssrc_, direction_) <
+             std::tie(other.ssrc_, other.direction_);
     }
-  }
-
-  bool IsVideoSsrc(PacketDirection direction, uint32_t ssrc) const {
-    if (direction == kIncomingPacket) {
-      return parsed_log_.incoming_video_ssrcs().find(ssrc) !=
-             parsed_log_.incoming_video_ssrcs().end();
-    } else {
-      return parsed_log_.outgoing_video_ssrcs().find(ssrc) !=
-             parsed_log_.outgoing_video_ssrcs().end();
+    bool operator==(const StreamId& other) const {
+      return std::tie(ssrc_, direction_) ==
+             std::tie(other.ssrc_, other.direction_);
     }
-  }
+    uint32_t GetSsrc() const { return ssrc_; }
+    webrtc::PacketDirection GetDirection() const { return direction_; }
 
-  bool IsAudioSsrc(PacketDirection direction, uint32_t ssrc) const {
-    if (direction == kIncomingPacket) {
-      return parsed_log_.incoming_audio_ssrcs().find(ssrc) !=
-             parsed_log_.incoming_audio_ssrcs().end();
-    } else {
-      return parsed_log_.outgoing_audio_ssrcs().find(ssrc) !=
-             parsed_log_.outgoing_audio_ssrcs().end();
-    }
-  }
+   private:
+    uint32_t ssrc_;
+    webrtc::PacketDirection direction_;
+  };
 
-  template <typename IterableType>
-  void CreateAccumulatedPacketsTimeSeries(Plot* plot,
-                                          const IterableType& packets,
-                                          const std::string& label);
+  template <typename T>
+  void CreateAccumulatedPacketsTimeSeries(
+      PacketDirection desired_direction,
+      Plot* plot,
+      const std::map<StreamId, std::vector<T>>& packets,
+      const std::string& label_prefix);
 
-  void CreateStreamGapAlerts(PacketDirection direction);
-  void CreateTransmissionGapAlerts(PacketDirection direction);
+  bool IsRtxSsrc(StreamId stream_id) const;
 
-  std::string GetStreamName(PacketDirection direction, uint32_t ssrc) const {
-    char buffer[200];
-    rtc::SimpleStringBuilder name(buffer);
-    if (IsAudioSsrc(direction, ssrc)) {
-      name << "Audio ";
-    } else if (IsVideoSsrc(direction, ssrc)) {
-      name << "Video ";
-    } else {
-      name << "Unknown ";
-    }
-    if (IsRtxSsrc(direction, ssrc)) {
-      name << "RTX ";
-    }
-    if (direction == kIncomingPacket)
-      name << "(In) ";
-    else
-      name << "(Out) ";
-    name << "SSRC " << ssrc;
-    return name.str();
-  }
+  bool IsVideoSsrc(StreamId stream_id) const;
+
+  bool IsAudioSsrc(StreamId stream_id) const;
+
+  std::string GetStreamName(StreamId stream_id) const;
+
+  rtc::Optional<uint32_t> EstimateRtpClockFrequency(
+      const std::vector<LoggedRtpPacket>& packets) const;
 
   float ToCallTime(int64_t timestamp) const;
 
-  void Alert_RtpLogTimeGap(PacketDirection direction,
-                           float time_seconds,
-                           int64_t duration) {
-    if (direction == kIncomingPacket) {
-      incoming_rtp_recv_time_gaps_.emplace_back(time_seconds, duration);
-    } else {
-      outgoing_rtp_send_time_gaps_.emplace_back(time_seconds, duration);
-    }
-  }
-
-  void Alert_RtcpLogTimeGap(PacketDirection direction,
-                            float time_seconds,
-                            int64_t duration) {
-    if (direction == kIncomingPacket) {
-      incoming_rtcp_recv_time_gaps_.emplace_back(time_seconds, duration);
-    } else {
-      outgoing_rtcp_send_time_gaps_.emplace_back(time_seconds, duration);
-    }
-  }
-
-  void Alert_SeqNumJump(PacketDirection direction,
-                        float time_seconds,
-                        uint32_t ssrc) {
-    if (direction == kIncomingPacket) {
-      incoming_seq_num_jumps_.emplace_back(time_seconds, ssrc);
-    } else {
-      outgoing_seq_num_jumps_.emplace_back(time_seconds, ssrc);
-    }
-  }
-
-  void Alert_CaptureTimeJump(PacketDirection direction,
-                             float time_seconds,
-                             uint32_t ssrc) {
-    if (direction == kIncomingPacket) {
-      incoming_capture_time_jumps_.emplace_back(time_seconds, ssrc);
-    } else {
-      outgoing_capture_time_jumps_.emplace_back(time_seconds, ssrc);
-    }
-  }
-
-  void Alert_OutgoingHighLoss(double avg_loss_fraction) {
-    outgoing_high_loss_alerts_.emplace_back(avg_loss_fraction);
-  }
+  void Notification(std::unique_ptr<TriageNotification> notification);
 
   std::string GetCandidatePairLogDescriptionFromId(uint32_t candidate_pair_id);
 
@@ -194,19 +177,50 @@
   // If left empty, all SSRCs will be considered relevant.
   std::vector<uint32_t> desired_ssrc_;
 
+  // Tracks what each stream is configured for. Note that a single SSRC can be
+  // in several sets. For example, the SSRC used for sending video over RTX
+  // will appear in both video_ssrcs_ and rtx_ssrcs_. In the unlikely case that
+  // an SSRC is reconfigured to a different media type mid-call, it will also
+  // appear in multiple sets.
+  std::set<StreamId> rtx_ssrcs_;
+  std::set<StreamId> video_ssrcs_;
+  std::set<StreamId> audio_ssrcs_;
+
+  // Maps a stream identifier consisting of ssrc and direction to the parsed
+  // RTP headers in that stream. Header extensions are parsed if the stream
+  // has been configured.
+  std::map<StreamId, std::vector<LoggedRtpPacket>> rtp_packets_;
+
+  std::map<StreamId, std::vector<LoggedRtcpPacket>> rtcp_packets_;
+
+  // Maps an SSRC to the timestamps of parsed audio playout events.
+  std::map<uint32_t, std::vector<uint64_t>> audio_playout_events_;
+
   // Stores the timestamps for all log segments, in the form of associated start
   // and end events.
-  std::vector<std::pair<int64_t, int64_t>> log_segments_;
+  std::vector<std::pair<uint64_t, uint64_t>> log_segments_;
 
-  std::vector<IncomingRtpReceiveTimeGap> incoming_rtp_recv_time_gaps_;
-  std::vector<IncomingRtcpReceiveTimeGap> incoming_rtcp_recv_time_gaps_;
-  std::vector<OutgoingRtpSendTimeGap> outgoing_rtp_send_time_gaps_;
-  std::vector<OutgoingRtcpSendTimeGap> outgoing_rtcp_send_time_gaps_;
-  std::vector<IncomingSeqNumJump> incoming_seq_num_jumps_;
-  std::vector<IncomingCaptureTimeJump> incoming_capture_time_jumps_;
-  std::vector<OutgoingSeqNoJump> outgoing_seq_num_jumps_;
-  std::vector<OutgoingCaptureTimeJump> outgoing_capture_time_jumps_;
-  std::vector<OutgoingHighLoss> outgoing_high_loss_alerts_;
+  // A list of all updates from the send-side loss-based bandwidth estimator.
+  std::vector<LossBasedBweUpdate> bwe_loss_updates_;
+
+  std::vector<AudioNetworkAdaptationEvent> audio_network_adaptation_events_;
+
+  std::vector<ParsedRtcEventLog::BweProbeClusterCreatedEvent>
+      bwe_probe_cluster_created_events_;
+
+  std::vector<ParsedRtcEventLog::BweProbeResultEvent> bwe_probe_result_events_;
+
+  std::vector<ParsedRtcEventLog::BweDelayBasedUpdate> bwe_delay_updates_;
+
+  std::vector<std::unique_ptr<TriageNotification>> notifications_;
+
+  std::vector<ParsedRtcEventLog::AlrStateEvent> alr_state_events_;
+
+  std::vector<ParsedRtcEventLog::IceCandidatePairConfig>
+      ice_candidate_pair_configs_;
+
+  std::vector<ParsedRtcEventLog::IceCandidatePairEvent>
+      ice_candidate_pair_events_;
 
   std::map<uint32_t, std::string> candidate_pair_desc_by_id_;
 
@@ -214,17 +228,18 @@
   // The generated data points will be |step_| microseconds apart.
   // Only events occuring at most |window_duration_| microseconds before the
   // current data point will be part of the average.
-  int64_t window_duration_;
-  int64_t step_;
+  uint64_t window_duration_;
+  uint64_t step_;
 
   // First and last events of the log.
-  int64_t begin_time_;
-  int64_t end_time_;
+  uint64_t begin_time_;
+  uint64_t end_time_;
 
   // Duration (in seconds) of log file.
   float call_duration_s_;
 };
 
+}  // namespace plotting
 }  // namespace webrtc
 
 #endif  // RTC_TOOLS_EVENT_LOG_VISUALIZER_ANALYZER_H_
diff --git a/rtc_tools/event_log_visualizer/main.cc b/rtc_tools/event_log_visualizer/main.cc
index 3dce290..2e7a79e 100644
--- a/rtc_tools/event_log_visualizer/main.cc
+++ b/rtc_tools/event_log_visualizer/main.cc
@@ -10,7 +10,7 @@
 
 #include <iostream>
 
-#include "logging/rtc_event_log/rtc_event_log_parser2.h"
+#include "logging/rtc_event_log/rtc_event_log_parser.h"
 #include "rtc_base/flags.h"
 #include "rtc_tools/event_log_visualizer/analyzer.h"
 #include "rtc_tools/event_log_visualizer/plot_base.h"
@@ -143,15 +143,10 @@
             false,
             "Show the state ALR state on the total bitrate graph");
 
-DEFINE_bool(parse_unconfigured_header_extensions,
-            true,
-            "Attempt to parse unconfigured header extensions using the default "
-            "WebRTC mapping. This can give very misleading results if the "
-            "application negotiates a different mapping.");
-
-DEFINE_bool(print_triage_alerts,
-            false,
-            "Print triage alerts, i.e. a list of potential problems.");
+DEFINE_bool(
+    print_triage_notifications,
+    false,
+    "Print triage notifications, i.e. a list of suspicious looking events.");
 
 void SetAllPlotFlags(bool setting);
 
@@ -214,13 +209,7 @@
 
   std::string filename = argv[1];
 
-  webrtc::ParsedRtcEventLog::UnconfiguredHeaderExtensions header_extensions =
-      webrtc::ParsedRtcEventLog::UnconfiguredHeaderExtensions::kDontParse;
-  if (FLAG_parse_unconfigured_header_extensions) {
-    header_extensions = webrtc::ParsedRtcEventLog::
-        UnconfiguredHeaderExtensions::kAttemptWebrtcDefaultConfig;
-  }
-  webrtc::ParsedRtcEventLog parsed_log(header_extensions);
+  webrtc::ParsedRtcEventLog parsed_log;
 
   if (!parsed_log.ParseFile(filename)) {
     std::cerr << "Could not parse the entire log file." << std::endl;
@@ -229,34 +218,31 @@
               << std::endl;
   }
 
-  webrtc::EventLogAnalyzer analyzer(parsed_log);
-  std::unique_ptr<webrtc::PlotCollection> collection(
-      new webrtc::PythonPlotCollection());
+  webrtc::plotting::EventLogAnalyzer analyzer(parsed_log);
+  std::unique_ptr<webrtc::plotting::PlotCollection> collection(
+      new webrtc::plotting::PythonPlotCollection());
 
   if (FLAG_plot_incoming_packet_sizes) {
-    analyzer.CreatePacketGraph(webrtc::kIncomingPacket,
+    analyzer.CreatePacketGraph(webrtc::PacketDirection::kIncomingPacket,
                                collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_packet_sizes) {
-    analyzer.CreatePacketGraph(webrtc::kOutgoingPacket,
+    analyzer.CreatePacketGraph(webrtc::PacketDirection::kOutgoingPacket,
                                collection->AppendNewPlot());
   }
   if (FLAG_plot_incoming_packet_count) {
-    analyzer.CreateAccumulatedPacketsGraph(webrtc::kIncomingPacket,
-                                           collection->AppendNewPlot());
+    analyzer.CreateAccumulatedPacketsGraph(
+        webrtc::PacketDirection::kIncomingPacket, collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_packet_count) {
-    analyzer.CreateAccumulatedPacketsGraph(webrtc::kOutgoingPacket,
-                                           collection->AppendNewPlot());
+    analyzer.CreateAccumulatedPacketsGraph(
+        webrtc::PacketDirection::kOutgoingPacket, collection->AppendNewPlot());
   }
   if (FLAG_plot_audio_playout) {
     analyzer.CreatePlayoutGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_audio_level) {
-    analyzer.CreateAudioLevelGraph(webrtc::kIncomingPacket,
-                                   collection->AppendNewPlot());
-    analyzer.CreateAudioLevelGraph(webrtc::kOutgoingPacket,
-                                   collection->AppendNewPlot());
+    analyzer.CreateAudioLevelGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_incoming_sequence_number_delta) {
     analyzer.CreateSequenceNumberGraph(collection->AppendNewPlot());
@@ -271,19 +257,23 @@
     analyzer.CreateIncomingPacketLossGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_incoming_bitrate) {
-    analyzer.CreateTotalIncomingBitrateGraph(collection->AppendNewPlot());
+    analyzer.CreateTotalBitrateGraph(webrtc::PacketDirection::kIncomingPacket,
+                                     collection->AppendNewPlot(),
+                                     FLAG_show_detector_state,
+                                     FLAG_show_alr_state);
   }
   if (FLAG_plot_outgoing_bitrate) {
-    analyzer.CreateTotalOutgoingBitrateGraph(collection->AppendNewPlot(),
-                                             FLAG_show_detector_state,
-                                             FLAG_show_alr_state);
+    analyzer.CreateTotalBitrateGraph(webrtc::PacketDirection::kOutgoingPacket,
+                                     collection->AppendNewPlot(),
+                                     FLAG_show_detector_state,
+                                     FLAG_show_alr_state);
   }
   if (FLAG_plot_incoming_stream_bitrate) {
-    analyzer.CreateStreamBitrateGraph(webrtc::kIncomingPacket,
+    analyzer.CreateStreamBitrateGraph(webrtc::PacketDirection::kIncomingPacket,
                                       collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_stream_bitrate) {
-    analyzer.CreateStreamBitrateGraph(webrtc::kOutgoingPacket,
+    analyzer.CreateStreamBitrateGraph(webrtc::PacketDirection::kOutgoingPacket,
                                       collection->AppendNewPlot());
   }
   if (FLAG_plot_simulated_receiveside_bwe) {
@@ -299,10 +289,7 @@
     analyzer.CreateFractionLossGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_timestamps) {
-    analyzer.CreateTimestampGraph(webrtc::kIncomingPacket,
-                                  collection->AppendNewPlot());
-    analyzer.CreateTimestampGraph(webrtc::kOutgoingPacket,
-                                  collection->AppendNewPlot());
+    analyzer.CreateTimestampGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_pacer_delay) {
     analyzer.CreatePacerDelayGraph(collection->AppendNewPlot());
@@ -346,7 +333,7 @@
 
   collection->Draw();
 
-  if (FLAG_print_triage_alerts) {
+  if (FLAG_print_triage_notifications) {
     analyzer.CreateTriageNotifications();
     analyzer.PrintNotifications(stderr);
   }
diff --git a/rtc_tools/event_log_visualizer/plot_base.cc b/rtc_tools/event_log_visualizer/plot_base.cc
index 9a21393..7ff4ef9 100644
--- a/rtc_tools/event_log_visualizer/plot_base.cc
+++ b/rtc_tools/event_log_visualizer/plot_base.cc
@@ -15,6 +15,7 @@
 #include "rtc_base/checks.h"
 
 namespace webrtc {
+namespace plotting {
 
 void Plot::SetXAxis(float min_value,
                     float max_value,
@@ -84,4 +85,5 @@
   }
 }
 
+}  // namespace plotting
 }  // namespace webrtc
diff --git a/rtc_tools/event_log_visualizer/plot_base.h b/rtc_tools/event_log_visualizer/plot_base.h
index e73f004..700ffbf 100644
--- a/rtc_tools/event_log_visualizer/plot_base.h
+++ b/rtc_tools/event_log_visualizer/plot_base.h
@@ -16,6 +16,7 @@
 #include <vector>
 
 namespace webrtc {
+namespace plotting {
 
 enum class LineStyle {
   kNone,  // No line connecting the points. Used to create scatter plots.
@@ -172,6 +173,7 @@
   std::vector<std::unique_ptr<Plot> > plots_;
 };
 
+}  // namespace plotting
 }  // namespace webrtc
 
 #endif  // RTC_TOOLS_EVENT_LOG_VISUALIZER_PLOT_BASE_H_
diff --git a/rtc_tools/event_log_visualizer/plot_protobuf.cc b/rtc_tools/event_log_visualizer/plot_protobuf.cc
index e986a74..e5e0a8b 100644
--- a/rtc_tools/event_log_visualizer/plot_protobuf.cc
+++ b/rtc_tools/event_log_visualizer/plot_protobuf.cc
@@ -13,6 +13,7 @@
 #include <memory>
 
 namespace webrtc {
+namespace plotting {
 
 ProtobufPlot::ProtobufPlot() {}
 
@@ -82,4 +83,5 @@
   return plot;
 }
 
+}  // namespace plotting
 }  // namespace webrtc
diff --git a/rtc_tools/event_log_visualizer/plot_protobuf.h b/rtc_tools/event_log_visualizer/plot_protobuf.h
index f59d303..5c5cce1 100644
--- a/rtc_tools/event_log_visualizer/plot_protobuf.h
+++ b/rtc_tools/event_log_visualizer/plot_protobuf.h
@@ -17,6 +17,7 @@
 #include "rtc_tools/event_log_visualizer/plot_base.h"
 
 namespace webrtc {
+namespace plotting {
 
 class ProtobufPlot final : public Plot {
  public:
@@ -35,6 +36,7 @@
   void ExportProtobuf(webrtc::analytics::ChartCollection* collection);
 };
 
+}  // namespace plotting
 }  // namespace webrtc
 
 #endif  // RTC_TOOLS_EVENT_LOG_VISUALIZER_PLOT_PROTOBUF_H_
diff --git a/rtc_tools/event_log_visualizer/plot_python.cc b/rtc_tools/event_log_visualizer/plot_python.cc
index 37c4d84..8f406e2 100644
--- a/rtc_tools/event_log_visualizer/plot_python.cc
+++ b/rtc_tools/event_log_visualizer/plot_python.cc
@@ -17,6 +17,7 @@
 #include "rtc_base/checks.h"
 
 namespace webrtc {
+namespace plotting {
 
 PythonPlot::PythonPlot() {}
 
@@ -179,4 +180,5 @@
   return plot;
 }
 
+}  // namespace plotting
 }  // namespace webrtc
diff --git a/rtc_tools/event_log_visualizer/plot_python.h b/rtc_tools/event_log_visualizer/plot_python.h
index 61d17a0..2a5a66c 100644
--- a/rtc_tools/event_log_visualizer/plot_python.h
+++ b/rtc_tools/event_log_visualizer/plot_python.h
@@ -13,6 +13,7 @@
 #include "rtc_tools/event_log_visualizer/plot_base.h"
 
 namespace webrtc {
+namespace plotting {
 
 class PythonPlot final : public Plot {
  public:
@@ -29,6 +30,7 @@
   Plot* AppendNewPlot() override;
 };
 
+}  // namespace plotting
 }  // namespace webrtc
 
 #endif  // RTC_TOOLS_EVENT_LOG_VISUALIZER_PLOT_PYTHON_H_
diff --git a/rtc_tools/event_log_visualizer/triage_notifications.h b/rtc_tools/event_log_visualizer/triage_notifications.h
index 49e0620..641e2ae 100644
--- a/rtc_tools/event_log_visualizer/triage_notifications.h
+++ b/rtc_tools/event_log_visualizer/triage_notifications.h
@@ -14,136 +14,130 @@
 #include <string>
 
 namespace webrtc {
+namespace plotting {
 
-class IncomingRtpReceiveTimeGap {
+class TriageNotification {
+ public:
+  TriageNotification() : time_seconds_() {}
+  explicit TriageNotification(float time_seconds)
+      : time_seconds_(time_seconds) {}
+  virtual ~TriageNotification() = default;
+  virtual std::string ToString() = 0;
+  rtc::Optional<float> Time() { return time_seconds_; }
+
+ private:
+  rtc::Optional<float> time_seconds_;
+};
+
+class IncomingRtpReceiveTimeGap : public TriageNotification {
  public:
   IncomingRtpReceiveTimeGap(float time_seconds, int64_t duration)
-      : time_seconds_(time_seconds), duration_(duration) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), duration_(duration) {}
+  std::string ToString() {
     return std::string("No RTP packets received for ") +
            std::to_string(duration_) + std::string(" ms");
   }
 
  private:
-  float time_seconds_;
   int64_t duration_;
 };
 
-class IncomingRtcpReceiveTimeGap {
+class IncomingRtcpReceiveTimeGap : public TriageNotification {
  public:
   IncomingRtcpReceiveTimeGap(float time_seconds, int64_t duration)
-      : time_seconds_(time_seconds), duration_(duration) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), duration_(duration) {}
+  std::string ToString() {
     return std::string("No RTCP packets received for ") +
            std::to_string(duration_) + std::string(" ms");
   }
 
  private:
-  float time_seconds_;
   int64_t duration_;
 };
 
-class OutgoingRtpSendTimeGap {
+class OutgoingRtpSendTimeGap : public TriageNotification {
  public:
   OutgoingRtpSendTimeGap(float time_seconds, int64_t duration)
-      : time_seconds_(time_seconds), duration_(duration) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), duration_(duration) {}
+  std::string ToString() {
     return std::string("No RTP packets sent for ") + std::to_string(duration_) +
            std::string(" ms");
   }
 
  private:
-  float time_seconds_;
   int64_t duration_;
 };
 
-class OutgoingRtcpSendTimeGap {
+class OutgoingRtcpSendTimeGap : public TriageNotification {
  public:
   OutgoingRtcpSendTimeGap(float time_seconds, int64_t duration)
-      : time_seconds_(time_seconds), duration_(duration) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), duration_(duration) {}
+  std::string ToString() {
     return std::string("No RTCP packets sent for ") +
            std::to_string(duration_) + std::string(" ms");
   }
 
  private:
-  float time_seconds_;
   int64_t duration_;
 };
 
-class IncomingSeqNumJump {
+class IncomingSeqNoJump : public TriageNotification {
  public:
-  IncomingSeqNumJump(float time_seconds, uint32_t ssrc)
-      : time_seconds_(time_seconds), ssrc_(ssrc) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+  IncomingSeqNoJump(float time_seconds, uint32_t ssrc)
+      : TriageNotification(time_seconds), ssrc_(ssrc) {}
+  std::string ToString() {
     return std::string("Sequence number jumps on incoming SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
-  float time_seconds_;
-
   uint32_t ssrc_;
 };
 
-class IncomingCaptureTimeJump {
+class IncomingCaptureTimeJump : public TriageNotification {
  public:
   IncomingCaptureTimeJump(float time_seconds, uint32_t ssrc)
-      : time_seconds_(time_seconds), ssrc_(ssrc) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), ssrc_(ssrc) {}
+  std::string ToString() {
     return std::string("Capture timestamp jumps on incoming SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
-  float time_seconds_;
-
   uint32_t ssrc_;
 };
 
-class OutgoingSeqNoJump {
+class OutgoingSeqNoJump : public TriageNotification {
  public:
   OutgoingSeqNoJump(float time_seconds, uint32_t ssrc)
-      : time_seconds_(time_seconds), ssrc_(ssrc) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), ssrc_(ssrc) {}
+  std::string ToString() {
     return std::string("Sequence number jumps on outgoing SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
-  float time_seconds_;
-
   uint32_t ssrc_;
 };
 
-class OutgoingCaptureTimeJump {
+class OutgoingCaptureTimeJump : public TriageNotification {
  public:
   OutgoingCaptureTimeJump(float time_seconds, uint32_t ssrc)
-      : time_seconds_(time_seconds), ssrc_(ssrc) {}
-  float Time() const { return time_seconds_; }
-  std::string ToString() const {
+      : TriageNotification(time_seconds), ssrc_(ssrc) {}
+  std::string ToString() {
     return std::string("Capture timestamp jumps on outgoing SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
-  float time_seconds_;
-
   uint32_t ssrc_;
 };
 
-class OutgoingHighLoss {
+class OutgoingHighLoss : public TriageNotification {
  public:
   explicit OutgoingHighLoss(double avg_loss_fraction)
       : avg_loss_fraction_(avg_loss_fraction) {}
-  std::string ToString() const {
+  std::string ToString() {
     return std::string("High average loss (") +
            std::to_string(avg_loss_fraction_ * 100) +
            std::string("%) across the call.");
@@ -153,6 +147,7 @@
   double avg_loss_fraction_;
 };
 
+}  // namespace plotting
 }  // namespace webrtc
 
 #endif  // RTC_TOOLS_EVENT_LOG_VISUALIZER_TRIAGE_NOTIFICATIONS_H_