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}
diff --git a/logging/BUILD.gn b/logging/BUILD.gn
index a47c81d..8f81e7a 100644
--- a/logging/BUILD.gn
+++ b/logging/BUILD.gn
@@ -253,8 +253,8 @@
 
   rtc_static_library("rtc_event_log_parser") {
     sources = [
-      "rtc_event_log/rtc_event_log_parser.cc",
-      "rtc_event_log/rtc_event_log_parser.h",
+      "rtc_event_log/rtc_event_log_parser2.cc",
+      "rtc_event_log/rtc_event_log_parser2.h",
     ]
 
     deps = [
@@ -265,6 +265,7 @@
       ":rtc_event_log_proto",
       ":rtc_stream_config",
       "..:webrtc_common",
+      "../api:libjingle_peerconnection_api",
       "../call:video_stream_api",
       "../modules/audio_coding:audio_network_adaptor",
       "../modules/remote_bitrate_estimator:remote_bitrate_estimator",
@@ -365,6 +366,7 @@
         "../rtc_base:checks",
         "../rtc_base:protobuf_utils",
         "../rtc_base:rtc_base_approved",
+        "../rtc_base:stringutils",
 
         # TODO(kwiberg): Remove this dependency.
         "../api/audio_codecs:audio_codecs_api",
diff --git a/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc b/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc
index 6d88298..0e6e183 100644
--- a/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc
+++ b/logging/rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc
@@ -32,7 +32,7 @@
 #include "logging/rtc_event_log/events/rtc_event_rtp_packet_outgoing.h"
 #include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h"
 #include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h"
-#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
 #include "modules/remote_bitrate_estimator/include/bwe_defines.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/bye.h"  // Arbitrary RTCP message.
@@ -142,11 +142,11 @@
   ASSERT_EQ(parsed_log_.GetEventType(0),
             ParsedRtcEventLog::AUDIO_NETWORK_ADAPTATION_EVENT);
 
-  AudioEncoderRuntimeConfig parsed_runtime_config;
-  parsed_log_.GetAudioNetworkAdaptation(0, &parsed_runtime_config);
+  LoggedAudioNetworkAdaptationEvent parsed_event =
+      parsed_log_.GetAudioNetworkAdaptation(0);
 
-  EXPECT_EQ(parsed_log_.GetTimestamp(0), timestamp_us);
-  EXPECT_EQ(parsed_runtime_config, original_runtime_config);
+  EXPECT_EQ(parsed_event.timestamp_us, timestamp_us);
+  EXPECT_EQ(parsed_event.config, original_runtime_config);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationBitrate) {
@@ -234,11 +234,10 @@
   ASSERT_EQ(parsed_log_.GetEventType(0),
             ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT);
 
-  uint32_t parsed_ssrc;
-  parsed_log_.GetAudioPlayout(0, &parsed_ssrc);
+  LoggedAudioPlayoutEvent playout_event = parsed_log_.GetAudioPlayout(0);
 
-  EXPECT_EQ(parsed_log_.GetTimestamp(0), timestamp_us);
-  EXPECT_EQ(parsed_ssrc, ssrc);
+  EXPECT_EQ(playout_event.timestamp_us, timestamp_us);
+  EXPECT_EQ(playout_event.ssrc, ssrc);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioReceiveStreamConfig) {
@@ -333,16 +332,12 @@
   ASSERT_EQ(parsed_log_.GetEventType(0),
             ParsedRtcEventLog::LOSS_BASED_BWE_UPDATE);
 
-  int32_t parsed_bitrate_bps;
-  uint8_t parsed_fraction_loss;
-  int32_t parsed_total_packets;
-  parsed_log_.GetLossBasedBweUpdate(
-      0, &parsed_bitrate_bps, &parsed_fraction_loss, &parsed_total_packets);
+  LoggedBweLossBasedUpdate bwe_update = parsed_log_.GetLossBasedBweUpdate(0);
 
-  EXPECT_EQ(parsed_log_.GetTimestamp(0), timestamp_us);
-  EXPECT_EQ(parsed_bitrate_bps, bitrate_bps);
-  EXPECT_EQ(parsed_fraction_loss, fraction_loss);
-  EXPECT_EQ(parsed_total_packets, total_packets);
+  EXPECT_EQ(bwe_update.timestamp_us, timestamp_us);
+  EXPECT_EQ(bwe_update.bitrate_bps, bitrate_bps);
+  EXPECT_EQ(bwe_update.fraction_lost, fraction_loss);
+  EXPECT_EQ(bwe_update.expected_packets, total_packets);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventLoggingStarted) {
diff --git a/logging/rtc_event_log/rtc_event_log.h b/logging/rtc_event_log/rtc_event_log.h
index a0714e0..0c71406 100644
--- a/logging/rtc_event_log/rtc_event_log.h
+++ b/logging/rtc_event_log/rtc_event_log.h
@@ -21,6 +21,7 @@
 
 namespace webrtc {
 
+// TODO(terelius): Move this to the parser.
 enum PacketDirection { kIncomingPacket = 0, kOutgoingPacket };
 
 class RtcEventLog {
diff --git a/logging/rtc_event_log/rtc_event_log2rtp_dump.cc b/logging/rtc_event_log/rtc_event_log2rtp_dump.cc
index c6fa129..c4d6948 100644
--- a/logging/rtc_event_log/rtc_event_log2rtp_dump.cc
+++ b/logging/rtc_event_log/rtc_event_log2rtp_dump.cc
@@ -16,7 +16,7 @@
 #include <string>
 
 #include "logging/rtc_event_log/rtc_event_log.h"
-#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "modules/rtp_rtcp/source/byte_io.h"
 #include "modules/rtp_rtcp/source/rtp_utility.h"
 #include "rtc_base/checks.h"
diff --git a/logging/rtc_event_log/rtc_event_log2text.cc b/logging/rtc_event_log/rtc_event_log2text.cc
index c71a2b8..6ceeaaf 100644
--- a/logging/rtc_event_log/rtc_event_log2text.cc
+++ b/logging/rtc_event_log/rtc_event_log2text.cc
@@ -13,13 +13,12 @@
 #include <iomanip>  // setfill, setw
 #include <iostream>
 #include <map>
-#include <sstream>
 #include <string>
 #include <utility>  // pair
 
 #include "call/video_config.h"
 #include "common_types.h"  // NOLINT(build/include)
-#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor_config.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/bye.h"
 #include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
@@ -40,6 +39,7 @@
 #include "rtc_base/checks.h"
 #include "rtc_base/flags.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/strings/string_builder.h"
 
 namespace {
 
@@ -443,7 +443,7 @@
           size_t total_length;
           uint8_t header[IP_PACKET_SIZE];
           webrtc::PacketDirection direction;
-          webrtc::RtpHeaderExtensionMap* extension_map =
+          const webrtc::RtpHeaderExtensionMap* extension_map =
               parsed_stream.GetRtpHeader(i, &direction, header, &header_length,
                                          &total_length, nullptr);
 
@@ -583,10 +583,9 @@
 
       case webrtc::ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT: {
         if (FLAG_playout) {
-          uint32_t ssrc;
-          parsed_stream.GetAudioPlayout(i, &ssrc);
-          std::cout << parsed_stream.GetTimestamp(i) << "\tAUDIO_PLAYOUT"
-                    << "\tssrc=" << ssrc << std::endl;
+          auto audio_playout = parsed_stream.GetAudioPlayout(i);
+          std::cout << audio_playout.log_time_us() << "\tAUDIO_PLAYOUT"
+                    << "\tssrc=" << audio_playout.ssrc << std::endl;
         }
         event_recognized = true;
         break;
@@ -594,15 +593,13 @@
 
       case webrtc::ParsedRtcEventLog::LOSS_BASED_BWE_UPDATE: {
         if (FLAG_bwe) {
-          int32_t bitrate_bps;
-          uint8_t fraction_loss;
-          int32_t total_packets;
-          parsed_stream.GetLossBasedBweUpdate(i, &bitrate_bps, &fraction_loss,
-                                              &total_packets);
-          std::cout << parsed_stream.GetTimestamp(i) << "\tBWE(LOSS_BASED)"
-                    << "\tbitrate_bps=" << bitrate_bps << "\tfraction_loss="
-                    << static_cast<unsigned>(fraction_loss)
-                    << "\ttotal_packets=" << total_packets << std::endl;
+          auto bwe_update = parsed_stream.GetLossBasedBweUpdate(i);
+          std::cout << bwe_update.log_time_us() << "\tBWE(LOSS_BASED)"
+                    << "\tbitrate_bps=" << bwe_update.bitrate_bps
+                    << "\tfraction_lost="
+                    << static_cast<unsigned>(bwe_update.fraction_lost)
+                    << "\texpected_packets=" << bwe_update.expected_packets
+                    << std::endl;
         }
         event_recognized = true;
         break;
@@ -611,7 +608,7 @@
       case webrtc::ParsedRtcEventLog::DELAY_BASED_BWE_UPDATE: {
         if (FLAG_bwe) {
           auto bwe_update = parsed_stream.GetDelayBasedBweUpdate(i);
-          std::cout << parsed_stream.GetTimestamp(i) << "\tBWE(DELAY_BASED)"
+          std::cout << bwe_update.log_time_us() << "\tBWE(DELAY_BASED)"
                     << "\tbitrate_bps=" << bwe_update.bitrate_bps
                     << "\tdetector_state="
                     << static_cast<int>(bwe_update.detector_state) << std::endl;
@@ -723,30 +720,31 @@
 
       case webrtc::ParsedRtcEventLog::AUDIO_NETWORK_ADAPTATION_EVENT: {
         if (FLAG_ana) {
-          webrtc::AudioEncoderRuntimeConfig ana_config;
-          parsed_stream.GetAudioNetworkAdaptation(i, &ana_config);
-          std::stringstream ss;
-          ss << parsed_stream.GetTimestamp(i) << "\tANA_UPDATE";
-          if (ana_config.bitrate_bps) {
-            ss << "\tbitrate_bps=" << *ana_config.bitrate_bps;
+          auto ana_event = parsed_stream.GetAudioNetworkAdaptation(i);
+          char buffer[300];
+          rtc::SimpleStringBuilder builder(buffer);
+          builder << parsed_stream.GetTimestamp(i) << "\tANA_UPDATE";
+          if (ana_event.config.bitrate_bps) {
+            builder << "\tbitrate_bps=" << *ana_event.config.bitrate_bps;
           }
-          if (ana_config.frame_length_ms) {
-            ss << "\tframe_length_ms=" << *ana_config.frame_length_ms;
+          if (ana_event.config.frame_length_ms) {
+            builder << "\tframe_length_ms="
+                    << *ana_event.config.frame_length_ms;
           }
-          if (ana_config.uplink_packet_loss_fraction) {
-            ss << "\tuplink_packet_loss_fraction="
-               << *ana_config.uplink_packet_loss_fraction;
+          if (ana_event.config.uplink_packet_loss_fraction) {
+            builder << "\tuplink_packet_loss_fraction="
+                    << *ana_event.config.uplink_packet_loss_fraction;
           }
-          if (ana_config.enable_fec) {
-            ss << "\tenable_fec=" << *ana_config.enable_fec;
+          if (ana_event.config.enable_fec) {
+            builder << "\tenable_fec=" << *ana_event.config.enable_fec;
           }
-          if (ana_config.enable_dtx) {
-            ss << "\tenable_dtx=" << *ana_config.enable_dtx;
+          if (ana_event.config.enable_dtx) {
+            builder << "\tenable_dtx=" << *ana_event.config.enable_dtx;
           }
-          if (ana_config.num_channels) {
-            ss << "\tnum_channels=" << *ana_config.num_channels;
+          if (ana_event.config.num_channels) {
+            builder << "\tnum_channels=" << *ana_event.config.num_channels;
           }
-          std::cout << ss.str() << std::endl;
+          std::cout << builder.str() << std::endl;
         }
         event_recognized = true;
         break;
@@ -754,8 +752,7 @@
 
       case webrtc::ParsedRtcEventLog::BWE_PROBE_CLUSTER_CREATED_EVENT: {
         if (FLAG_probe) {
-          webrtc::ParsedRtcEventLog::BweProbeClusterCreatedEvent probe_event =
-              parsed_stream.GetBweProbeClusterCreated(i);
+          auto probe_event = parsed_stream.GetBweProbeClusterCreated(i);
           std::cout << parsed_stream.GetTimestamp(i) << "\tPROBE_CREATED("
                     << probe_event.id << ")"
                     << "\tbitrate_bps=" << probe_event.bitrate_bps
@@ -768,7 +765,7 @@
 
       case webrtc::ParsedRtcEventLog::BWE_PROBE_RESULT_EVENT: {
         if (FLAG_probe) {
-          webrtc::ParsedRtcEventLog::BweProbeResultEvent probe_result =
+          webrtc::LoggedBweProbeResultEvent probe_result =
               parsed_stream.GetBweProbeResult(i);
           if (probe_result.failure_reason) {
             std::cout << parsed_stream.GetTimestamp(i) << "\tPROBE_SUCCESS("
@@ -789,8 +786,7 @@
 
       case webrtc::ParsedRtcEventLog::ALR_STATE_EVENT: {
         if (FLAG_bwe) {
-          webrtc::ParsedRtcEventLog::AlrStateEvent alr_state =
-              parsed_stream.GetAlrState(i);
+          webrtc::LoggedAlrStateEvent alr_state = parsed_stream.GetAlrState(i);
           std::cout << parsed_stream.GetTimestamp(i) << "\tALR_STATE"
                     << "\tin_alr=" << alr_state.in_alr << std::endl;
         }
@@ -800,7 +796,7 @@
 
       case webrtc::ParsedRtcEventLog::ICE_CANDIDATE_PAIR_CONFIG: {
         if (FLAG_ice) {
-          webrtc::ParsedRtcEventLog::IceCandidatePairConfig ice_cp_config =
+          webrtc::LoggedIceCandidatePairConfig ice_cp_config =
               parsed_stream.GetIceCandidatePairConfig(i);
           // TODO(qingsi): convert the numeric representation of states to text
           std::cout << parsed_stream.GetTimestamp(i)
@@ -814,7 +810,7 @@
 
       case webrtc::ParsedRtcEventLog::ICE_CANDIDATE_PAIR_EVENT: {
         if (FLAG_ice) {
-          webrtc::ParsedRtcEventLog::IceCandidatePairEvent ice_cp_event =
+          webrtc::LoggedIceCandidatePairEvent ice_cp_event =
               parsed_stream.GetIceCandidatePairEvent(i);
           // TODO(qingsi): convert the numeric representation of states to text
           std::cout << parsed_stream.GetTimestamp(i)
diff --git a/logging/rtc_event_log/rtc_event_log_parser2.cc b/logging/rtc_event_log/rtc_event_log_parser2.cc
new file mode 100644
index 0000000..9bcad0d
--- /dev/null
+++ b/logging/rtc_event_log/rtc_event_log_parser2.cc
@@ -0,0 +1,1248 @@
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <algorithm>
+#include <fstream>
+#include <istream>  // no-presubmit-check TODO(webrtc:8982)
+#include <limits>
+#include <map>
+#include <utility>
+
+#include "api/rtp_headers.h"
+#include "api/rtpparameters.h"
+#include "logging/rtc_event_log/rtc_event_log.h"
+#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
+#include "modules/remote_bitrate_estimator/include/bwe_defines.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/rtp_rtcp/source/byte_io.h"
+#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
+#include "modules/rtp_rtcp/source/rtp_utility.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/protobuf_utils.h"
+#include "rtc_base/ptr_util.h"
+
+namespace webrtc {
+
+namespace {
+RtcpMode GetRuntimeRtcpMode(rtclog::VideoReceiveConfig::RtcpMode rtcp_mode) {
+  switch (rtcp_mode) {
+    case rtclog::VideoReceiveConfig::RTCP_COMPOUND:
+      return RtcpMode::kCompound;
+    case rtclog::VideoReceiveConfig::RTCP_REDUCEDSIZE:
+      return RtcpMode::kReducedSize;
+  }
+  RTC_NOTREACHED();
+  return RtcpMode::kOff;
+}
+
+ParsedRtcEventLog::EventType GetRuntimeEventType(
+    rtclog::Event::EventType event_type) {
+  switch (event_type) {
+    case rtclog::Event::UNKNOWN_EVENT:
+      return ParsedRtcEventLog::EventType::UNKNOWN_EVENT;
+    case rtclog::Event::LOG_START:
+      return ParsedRtcEventLog::EventType::LOG_START;
+    case rtclog::Event::LOG_END:
+      return ParsedRtcEventLog::EventType::LOG_END;
+    case rtclog::Event::RTP_EVENT:
+      return ParsedRtcEventLog::EventType::RTP_EVENT;
+    case rtclog::Event::RTCP_EVENT:
+      return ParsedRtcEventLog::EventType::RTCP_EVENT;
+    case rtclog::Event::AUDIO_PLAYOUT_EVENT:
+      return ParsedRtcEventLog::EventType::AUDIO_PLAYOUT_EVENT;
+    case rtclog::Event::LOSS_BASED_BWE_UPDATE:
+      return ParsedRtcEventLog::EventType::LOSS_BASED_BWE_UPDATE;
+    case rtclog::Event::DELAY_BASED_BWE_UPDATE:
+      return ParsedRtcEventLog::EventType::DELAY_BASED_BWE_UPDATE;
+    case rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT:
+      return ParsedRtcEventLog::EventType::VIDEO_RECEIVER_CONFIG_EVENT;
+    case rtclog::Event::VIDEO_SENDER_CONFIG_EVENT:
+      return ParsedRtcEventLog::EventType::VIDEO_SENDER_CONFIG_EVENT;
+    case rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT:
+      return ParsedRtcEventLog::EventType::AUDIO_RECEIVER_CONFIG_EVENT;
+    case rtclog::Event::AUDIO_SENDER_CONFIG_EVENT:
+      return ParsedRtcEventLog::EventType::AUDIO_SENDER_CONFIG_EVENT;
+    case rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT:
+      return ParsedRtcEventLog::EventType::AUDIO_NETWORK_ADAPTATION_EVENT;
+    case rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT:
+      return ParsedRtcEventLog::EventType::BWE_PROBE_CLUSTER_CREATED_EVENT;
+    case rtclog::Event::BWE_PROBE_RESULT_EVENT:
+      return ParsedRtcEventLog::EventType::BWE_PROBE_RESULT_EVENT;
+    case rtclog::Event::ALR_STATE_EVENT:
+      return ParsedRtcEventLog::EventType::ALR_STATE_EVENT;
+    case rtclog::Event::ICE_CANDIDATE_PAIR_CONFIG:
+      return ParsedRtcEventLog::EventType::ICE_CANDIDATE_PAIR_CONFIG;
+    case rtclog::Event::ICE_CANDIDATE_PAIR_EVENT:
+      return ParsedRtcEventLog::EventType::ICE_CANDIDATE_PAIR_EVENT;
+  }
+  return ParsedRtcEventLog::EventType::UNKNOWN_EVENT;
+}
+
+BandwidthUsage GetRuntimeDetectorState(
+    rtclog::DelayBasedBweUpdate::DetectorState detector_state) {
+  switch (detector_state) {
+    case rtclog::DelayBasedBweUpdate::BWE_NORMAL:
+      return BandwidthUsage::kBwNormal;
+    case rtclog::DelayBasedBweUpdate::BWE_UNDERUSING:
+      return BandwidthUsage::kBwUnderusing;
+    case rtclog::DelayBasedBweUpdate::BWE_OVERUSING:
+      return BandwidthUsage::kBwOverusing;
+  }
+  RTC_NOTREACHED();
+  return BandwidthUsage::kBwNormal;
+}
+
+IceCandidatePairEventType GetRuntimeIceCandidatePairConfigType(
+    rtclog::IceCandidatePairConfig::IceCandidatePairConfigType type) {
+  switch (type) {
+    case rtclog::IceCandidatePairConfig::ADDED:
+      return IceCandidatePairEventType::kAdded;
+    case rtclog::IceCandidatePairConfig::UPDATED:
+      return IceCandidatePairEventType::kUpdated;
+    case rtclog::IceCandidatePairConfig::DESTROYED:
+      return IceCandidatePairEventType::kDestroyed;
+    case rtclog::IceCandidatePairConfig::SELECTED:
+      return IceCandidatePairEventType::kSelected;
+  }
+  RTC_NOTREACHED();
+  return IceCandidatePairEventType::kAdded;
+}
+
+IceCandidateType GetRuntimeIceCandidateType(
+    rtclog::IceCandidatePairConfig::IceCandidateType type) {
+  switch (type) {
+    case rtclog::IceCandidatePairConfig::LOCAL:
+      return IceCandidateType::kLocal;
+    case rtclog::IceCandidatePairConfig::STUN:
+      return IceCandidateType::kStun;
+    case rtclog::IceCandidatePairConfig::PRFLX:
+      return IceCandidateType::kPrflx;
+    case rtclog::IceCandidatePairConfig::RELAY:
+      return IceCandidateType::kRelay;
+    case rtclog::IceCandidatePairConfig::UNKNOWN_CANDIDATE_TYPE:
+      return IceCandidateType::kUnknown;
+  }
+  RTC_NOTREACHED();
+  return IceCandidateType::kUnknown;
+}
+
+IceCandidatePairProtocol GetRuntimeIceCandidatePairProtocol(
+    rtclog::IceCandidatePairConfig::Protocol protocol) {
+  switch (protocol) {
+    case rtclog::IceCandidatePairConfig::UDP:
+      return IceCandidatePairProtocol::kUdp;
+    case rtclog::IceCandidatePairConfig::TCP:
+      return IceCandidatePairProtocol::kTcp;
+    case rtclog::IceCandidatePairConfig::SSLTCP:
+      return IceCandidatePairProtocol::kSsltcp;
+    case rtclog::IceCandidatePairConfig::TLS:
+      return IceCandidatePairProtocol::kTls;
+    case rtclog::IceCandidatePairConfig::UNKNOWN_PROTOCOL:
+      return IceCandidatePairProtocol::kUnknown;
+  }
+  RTC_NOTREACHED();
+  return IceCandidatePairProtocol::kUnknown;
+}
+
+IceCandidatePairAddressFamily GetRuntimeIceCandidatePairAddressFamily(
+    rtclog::IceCandidatePairConfig::AddressFamily address_family) {
+  switch (address_family) {
+    case rtclog::IceCandidatePairConfig::IPV4:
+      return IceCandidatePairAddressFamily::kIpv4;
+    case rtclog::IceCandidatePairConfig::IPV6:
+      return IceCandidatePairAddressFamily::kIpv6;
+    case rtclog::IceCandidatePairConfig::UNKNOWN_ADDRESS_FAMILY:
+      return IceCandidatePairAddressFamily::kUnknown;
+  }
+  RTC_NOTREACHED();
+  return IceCandidatePairAddressFamily::kUnknown;
+}
+
+IceCandidateNetworkType GetRuntimeIceCandidateNetworkType(
+    rtclog::IceCandidatePairConfig::NetworkType network_type) {
+  switch (network_type) {
+    case rtclog::IceCandidatePairConfig::ETHERNET:
+      return IceCandidateNetworkType::kEthernet;
+    case rtclog::IceCandidatePairConfig::LOOPBACK:
+      return IceCandidateNetworkType::kLoopback;
+    case rtclog::IceCandidatePairConfig::WIFI:
+      return IceCandidateNetworkType::kWifi;
+    case rtclog::IceCandidatePairConfig::VPN:
+      return IceCandidateNetworkType::kVpn;
+    case rtclog::IceCandidatePairConfig::CELLULAR:
+      return IceCandidateNetworkType::kCellular;
+    case rtclog::IceCandidatePairConfig::UNKNOWN_NETWORK_TYPE:
+      return IceCandidateNetworkType::kUnknown;
+  }
+  RTC_NOTREACHED();
+  return IceCandidateNetworkType::kUnknown;
+}
+
+IceCandidatePairEventType GetRuntimeIceCandidatePairEventType(
+    rtclog::IceCandidatePairEvent::IceCandidatePairEventType type) {
+  switch (type) {
+    case rtclog::IceCandidatePairEvent::CHECK_SENT:
+      return IceCandidatePairEventType::kCheckSent;
+    case rtclog::IceCandidatePairEvent::CHECK_RECEIVED:
+      return IceCandidatePairEventType::kCheckReceived;
+    case rtclog::IceCandidatePairEvent::CHECK_RESPONSE_SENT:
+      return IceCandidatePairEventType::kCheckResponseSent;
+    case rtclog::IceCandidatePairEvent::CHECK_RESPONSE_RECEIVED:
+      return IceCandidatePairEventType::kCheckResponseReceived;
+  }
+  RTC_NOTREACHED();
+  return IceCandidatePairEventType::kCheckSent;
+}
+
+// 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;
+}
+
+std::pair<uint64_t, bool> ParseVarInt(
+    std::istream& stream) {  // no-presubmit-check TODO(webrtc:8982)
+  uint64_t varint = 0;
+  for (size_t bytes_read = 0; bytes_read < 10; ++bytes_read) {
+    // The most significant bit of each byte is 0 if it is the last byte in
+    // the varint and 1 otherwise. Thus, we take the 7 least significant bits
+    // of each byte and shift them 7 bits for each byte read previously to get
+    // the (unsigned) integer.
+    int byte = stream.get();
+    if (stream.eof()) {
+      return std::make_pair(varint, false);
+    }
+    RTC_DCHECK_GE(byte, 0);
+    RTC_DCHECK_LE(byte, 255);
+    varint |= static_cast<uint64_t>(byte & 0x7F) << (7 * bytes_read);
+    if ((byte & 0x80) == 0) {
+      return std::make_pair(varint, true);
+    }
+  }
+  return std::make_pair(varint, false);
+}
+
+void GetHeaderExtensions(std::vector<RtpExtension>* header_extensions,
+                         const RepeatedPtrField<rtclog::RtpHeaderExtension>&
+                             proto_header_extensions) {
+  header_extensions->clear();
+  for (auto& p : proto_header_extensions) {
+    RTC_CHECK(p.has_name());
+    RTC_CHECK(p.has_id());
+    const std::string& name = p.name();
+    int id = p.id();
+    header_extensions->push_back(RtpExtension(name, id));
+  }
+}
+
+}  // namespace
+
+ParsedRtcEventLog::ParsedRtcEventLog(
+    UnconfiguredHeaderExtensions parse_unconfigured_header_extensions)
+    : parse_unconfigured_header_extensions_(
+          parse_unconfigured_header_extensions) {
+  Clear();
+}
+
+void ParsedRtcEventLog::Clear() {
+  events_.clear();
+  default_extension_map_ = GetDefaultHeaderExtensionMap();
+
+  incoming_rtx_ssrcs_.clear();
+  incoming_video_ssrcs_.clear();
+  incoming_audio_ssrcs_.clear();
+  outgoing_rtx_ssrcs_.clear();
+  outgoing_video_ssrcs_.clear();
+  outgoing_audio_ssrcs_.clear();
+
+  incoming_rtp_packets_map_.clear();
+  outgoing_rtp_packets_map_.clear();
+  incoming_rtp_packets_by_ssrc_.clear();
+  outgoing_rtp_packets_by_ssrc_.clear();
+  incoming_rtp_packet_views_by_ssrc_.clear();
+  outgoing_rtp_packet_views_by_ssrc_.clear();
+
+  incoming_rtcp_packets_.clear();
+  outgoing_rtcp_packets_.clear();
+
+  incoming_rr_.clear();
+  outgoing_rr_.clear();
+  incoming_sr_.clear();
+  outgoing_sr_.clear();
+  incoming_nack_.clear();
+  outgoing_nack_.clear();
+  incoming_remb_.clear();
+  outgoing_remb_.clear();
+  incoming_transport_feedback_.clear();
+  outgoing_transport_feedback_.clear();
+
+  start_log_events_.clear();
+  stop_log_events_.clear();
+  audio_playout_events_.clear();
+  audio_network_adaptation_events_.clear();
+  bwe_probe_cluster_created_events_.clear();
+  bwe_probe_result_events_.clear();
+  bwe_delay_updates_.clear();
+  bwe_loss_updates_.clear();
+  alr_state_events_.clear();
+  ice_candidate_pair_configs_.clear();
+  ice_candidate_pair_events_.clear();
+  audio_recv_configs_.clear();
+  audio_send_configs_.clear();
+  video_recv_configs_.clear();
+  video_send_configs_.clear();
+
+  memset(last_incoming_rtcp_packet_, 0, IP_PACKET_SIZE);
+  last_incoming_rtcp_packet_length_ = 0;
+
+  first_timestamp_ = std::numeric_limits<int64_t>::max();
+  last_timestamp_ = std::numeric_limits<int64_t>::min();
+
+  incoming_rtp_extensions_maps_.clear();
+  outgoing_rtp_extensions_maps_.clear();
+}
+
+bool ParsedRtcEventLog::ParseFile(const std::string& filename) {
+  std::ifstream file(  // no-presubmit-check TODO(webrtc:8982)
+      filename, std::ios_base::in | std::ios_base::binary);
+  if (!file.good() || !file.is_open()) {
+    RTC_LOG(LS_WARNING) << "Could not open file for reading.";
+    return false;
+  }
+
+  return ParseStream(file);
+}
+
+bool ParsedRtcEventLog::ParseString(const std::string& s) {
+  std::istringstream stream(  // no-presubmit-check TODO(webrtc:8982)
+      s, std::ios_base::in | std::ios_base::binary);
+  return ParseStream(stream);
+}
+
+bool ParsedRtcEventLog::ParseStream(
+    std::istream& stream) {  // no-presubmit-check TODO(webrtc:8982)
+  Clear();
+  const size_t kMaxEventSize = (1u << 16) - 1;
+  std::vector<char> tmp_buffer(kMaxEventSize);
+  uint64_t tag;
+  uint64_t message_length;
+  bool success;
+
+  RTC_DCHECK(stream.good());
+
+  while (1) {
+    // Check whether we have reached end of file.
+    stream.peek();
+    if (stream.eof()) {
+      break;
+    }
+
+    // Read the next message tag. The tag number is defined as
+    // (fieldnumber << 3) | wire_type. In our case, the field number is
+    // supposed to be 1 and the wire type for an
+    // length-delimited field is 2.
+    const uint64_t kExpectedTag = (1 << 3) | 2;
+    std::tie(tag, success) = ParseVarInt(stream);
+    if (!success) {
+      RTC_LOG(LS_WARNING)
+          << "Missing field tag from beginning of protobuf event.";
+      return false;
+    } else if (tag != kExpectedTag) {
+      RTC_LOG(LS_WARNING)
+          << "Unexpected field tag at beginning of protobuf event.";
+      return false;
+    }
+
+    // Read the length field.
+    std::tie(message_length, success) = ParseVarInt(stream);
+    if (!success) {
+      RTC_LOG(LS_WARNING) << "Missing message length after protobuf field tag.";
+      return false;
+    } else if (message_length > kMaxEventSize) {
+      RTC_LOG(LS_WARNING) << "Protobuf message length is too large.";
+      return false;
+    }
+
+    // Read the next protobuf event to a temporary char buffer.
+    stream.read(tmp_buffer.data(), message_length);
+    if (stream.gcount() != static_cast<int>(message_length)) {
+      RTC_LOG(LS_WARNING) << "Failed to read protobuf message from file.";
+      return false;
+    }
+
+    // Parse the protobuf event from the buffer.
+    rtclog::Event event;
+    if (!event.ParseFromArray(tmp_buffer.data(), message_length)) {
+      RTC_LOG(LS_WARNING) << "Failed to parse protobuf message.";
+      return false;
+    }
+
+    StoreParsedEvent(event);
+
+    events_.push_back(event);
+  }
+
+  // Move packets_streams from map to vector.
+  incoming_rtp_packets_by_ssrc_.reserve(incoming_rtp_packets_map_.size());
+  for (const auto& kv : incoming_rtp_packets_map_) {
+    incoming_rtp_packets_by_ssrc_.emplace_back(LoggedRtpStreamIncoming());
+    incoming_rtp_packets_by_ssrc_.back().ssrc = kv.first;
+    incoming_rtp_packets_by_ssrc_.back().incoming_packets =
+        std::move(kv.second);
+  }
+  outgoing_rtp_packets_by_ssrc_.reserve(outgoing_rtp_packets_map_.size());
+  for (const auto& kv : outgoing_rtp_packets_map_) {
+    outgoing_rtp_packets_by_ssrc_.emplace_back(LoggedRtpStreamOutgoing());
+    outgoing_rtp_packets_by_ssrc_.back().ssrc = kv.first;
+    outgoing_rtp_packets_by_ssrc_.back().outgoing_packets =
+        std::move(kv.second);
+  }
+
+  // Build PacketViews for easier iteration over RTP packets
+  for (const auto& stream : incoming_rtp_packets_by_ssrc_) {
+    incoming_rtp_packet_views_by_ssrc_.emplace_back(
+        LoggedRtpStreamView(stream.ssrc, stream.incoming_packets.data(),
+                            stream.incoming_packets.size()));
+  }
+  for (const auto& stream : outgoing_rtp_packets_by_ssrc_) {
+    outgoing_rtp_packet_views_by_ssrc_.emplace_back(
+        LoggedRtpStreamView(stream.ssrc, stream.outgoing_packets.data(),
+                            stream.outgoing_packets.size()));
+  }
+
+  return true;
+}
+
+void ParsedRtcEventLog::StoreParsedEvent(const rtclog::Event& event) {
+  if (event.type() != rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT &&
+      event.type() != rtclog::Event::VIDEO_SENDER_CONFIG_EVENT &&
+      event.type() != rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT &&
+      event.type() != rtclog::Event::AUDIO_SENDER_CONFIG_EVENT &&
+      event.type() != rtclog::Event::LOG_START &&
+      event.type() != rtclog::Event::LOG_END) {
+    RTC_CHECK(event.has_timestamp_us());
+    int64_t timestamp = event.timestamp_us();
+    first_timestamp_ = std::min(first_timestamp_, timestamp);
+    last_timestamp_ = std::max(last_timestamp_, timestamp);
+  }
+
+  switch (event.type()) {
+    case rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT: {
+      rtclog::StreamConfig config = GetVideoReceiveConfig(event);
+      video_recv_configs_.emplace_back(GetTimestamp(event), config);
+      incoming_rtp_extensions_maps_[config.remote_ssrc] =
+          RtpHeaderExtensionMap(config.rtp_extensions);
+      // TODO(terelius): I don't understand the reason for configuring header
+      // extensions for the local SSRC. I think it should be removed, but for
+      // now I want to preserve the previous functionality.
+      incoming_rtp_extensions_maps_[config.local_ssrc] =
+          RtpHeaderExtensionMap(config.rtp_extensions);
+      incoming_video_ssrcs_.insert(config.remote_ssrc);
+      incoming_video_ssrcs_.insert(config.rtx_ssrc);
+      incoming_rtx_ssrcs_.insert(config.rtx_ssrc);
+      break;
+    }
+    case rtclog::Event::VIDEO_SENDER_CONFIG_EVENT: {
+      std::vector<rtclog::StreamConfig> configs = GetVideoSendConfig(event);
+      video_send_configs_.emplace_back(GetTimestamp(event), configs);
+      for (const auto& config : configs) {
+        outgoing_rtp_extensions_maps_[config.local_ssrc] =
+            RtpHeaderExtensionMap(config.rtp_extensions);
+        outgoing_rtp_extensions_maps_[config.rtx_ssrc] =
+            RtpHeaderExtensionMap(config.rtp_extensions);
+        outgoing_video_ssrcs_.insert(config.local_ssrc);
+        outgoing_video_ssrcs_.insert(config.rtx_ssrc);
+        outgoing_rtx_ssrcs_.insert(config.rtx_ssrc);
+      }
+      break;
+    }
+    case rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT: {
+      rtclog::StreamConfig config = GetAudioReceiveConfig(event);
+      audio_recv_configs_.emplace_back(GetTimestamp(event), config);
+      incoming_rtp_extensions_maps_[config.remote_ssrc] =
+          RtpHeaderExtensionMap(config.rtp_extensions);
+      incoming_rtp_extensions_maps_[config.local_ssrc] =
+          RtpHeaderExtensionMap(config.rtp_extensions);
+      incoming_audio_ssrcs_.insert(config.remote_ssrc);
+      break;
+    }
+    case rtclog::Event::AUDIO_SENDER_CONFIG_EVENT: {
+      rtclog::StreamConfig config = GetAudioSendConfig(event);
+      audio_send_configs_.emplace_back(GetTimestamp(event), config);
+      outgoing_rtp_extensions_maps_[config.local_ssrc] =
+          RtpHeaderExtensionMap(config.rtp_extensions);
+      outgoing_audio_ssrcs_.insert(config.local_ssrc);
+      break;
+    }
+    case rtclog::Event::RTP_EVENT: {
+      PacketDirection direction;
+      uint8_t header[IP_PACKET_SIZE];
+      size_t header_length;
+      size_t total_length;
+      const RtpHeaderExtensionMap* extension_map = GetRtpHeader(
+          event, &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_);
+      }
+      RTC_CHECK(event.has_timestamp_us());
+      uint64_t timestamp_us = event.timestamp_us();
+      if (direction == kIncomingPacket) {
+        incoming_rtp_packets_map_[parsed_header.ssrc].push_back(
+            LoggedRtpPacketIncoming(timestamp_us, parsed_header, header_length,
+                                    total_length));
+      } else {
+        outgoing_rtp_packets_map_[parsed_header.ssrc].push_back(
+            LoggedRtpPacketOutgoing(timestamp_us, parsed_header, header_length,
+                                    total_length));
+      }
+      break;
+    }
+    case rtclog::Event::RTCP_EVENT: {
+      PacketDirection direction;
+      uint8_t packet[IP_PACKET_SIZE];
+      size_t total_length;
+      GetRtcpPacket(event, &direction, packet, &total_length);
+      uint64_t timestamp_us = GetTimestamp(event);
+      RTC_CHECK_LE(total_length, IP_PACKET_SIZE);
+      if (direction == kIncomingPacket) {
+        // 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 (total_length == last_incoming_rtcp_packet_length_ &&
+            memcmp(last_incoming_rtcp_packet_, packet, total_length) == 0)
+          break;
+        incoming_rtcp_packets_.push_back(
+            LoggedRtcpPacketIncoming(timestamp_us, packet, total_length));
+        last_incoming_rtcp_packet_length_ = total_length;
+        memcpy(last_incoming_rtcp_packet_, packet, total_length);
+      } else {
+        outgoing_rtcp_packets_.push_back(
+            LoggedRtcpPacketOutgoing(timestamp_us, packet, 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) {
+          if (direction == kIncomingPacket) {
+            incoming_transport_feedback_.emplace_back();
+            LoggedRtcpPacketTransportFeedback& parsed_block =
+                incoming_transport_feedback_.back();
+            parsed_block.timestamp_us = GetTimestamp(event);
+            if (!parsed_block.transport_feedback.Parse(header))
+              incoming_transport_feedback_.pop_back();
+          } else {
+            outgoing_transport_feedback_.emplace_back();
+            LoggedRtcpPacketTransportFeedback& parsed_block =
+                outgoing_transport_feedback_.back();
+            parsed_block.timestamp_us = GetTimestamp(event);
+            if (!parsed_block.transport_feedback.Parse(header))
+              outgoing_transport_feedback_.pop_back();
+          }
+        } else if (header.type() == rtcp::SenderReport::kPacketType) {
+          LoggedRtcpPacketSenderReport parsed_block;
+          parsed_block.timestamp_us = GetTimestamp(event);
+          if (parsed_block.sr.Parse(header)) {
+            if (direction == kIncomingPacket)
+              incoming_sr_.push_back(std::move(parsed_block));
+            else
+              outgoing_sr_.push_back(std::move(parsed_block));
+          }
+        } else if (header.type() == rtcp::ReceiverReport::kPacketType) {
+          LoggedRtcpPacketReceiverReport parsed_block;
+          parsed_block.timestamp_us = GetTimestamp(event);
+          if (parsed_block.rr.Parse(header)) {
+            if (direction == kIncomingPacket)
+              incoming_rr_.push_back(std::move(parsed_block));
+            else
+              outgoing_rr_.push_back(std::move(parsed_block));
+          }
+        } else if (header.type() == rtcp::Remb::kPacketType &&
+                   header.fmt() == rtcp::Remb::kFeedbackMessageType) {
+          LoggedRtcpPacketRemb parsed_block;
+          parsed_block.timestamp_us = GetTimestamp(event);
+          if (parsed_block.remb.Parse(header)) {
+            if (direction == kIncomingPacket)
+              incoming_remb_.push_back(std::move(parsed_block));
+            else
+              outgoing_remb_.push_back(std::move(parsed_block));
+          }
+        } else if (header.type() == rtcp::Nack::kPacketType &&
+                   header.fmt() == rtcp::Nack::kFeedbackMessageType) {
+          LoggedRtcpPacketNack parsed_block;
+          parsed_block.timestamp_us = GetTimestamp(event);
+          if (parsed_block.nack.Parse(header)) {
+            if (direction == kIncomingPacket)
+              incoming_nack_.push_back(std::move(parsed_block));
+            else
+              outgoing_nack_.push_back(std::move(parsed_block));
+          }
+        }
+      }
+      break;
+    }
+    case ParsedRtcEventLog::LOG_START: {
+      start_log_events_.push_back(LoggedStartEvent(GetTimestamp(event)));
+      break;
+    }
+    case ParsedRtcEventLog::LOG_END: {
+      stop_log_events_.push_back(LoggedStopEvent(GetTimestamp(event)));
+      break;
+    }
+    case ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT: {
+      LoggedAudioPlayoutEvent playout_event = GetAudioPlayout(event);
+      audio_playout_events_[playout_event.ssrc].push_back(
+          playout_event.timestamp_us);
+      break;
+    }
+    case ParsedRtcEventLog::LOSS_BASED_BWE_UPDATE: {
+      bwe_loss_updates_.push_back(GetLossBasedBweUpdate(event));
+      break;
+    }
+    case ParsedRtcEventLog::DELAY_BASED_BWE_UPDATE: {
+      bwe_delay_updates_.push_back(GetDelayBasedBweUpdate(event));
+      break;
+    }
+    case ParsedRtcEventLog::AUDIO_NETWORK_ADAPTATION_EVENT: {
+      LoggedAudioNetworkAdaptationEvent ana_event =
+          GetAudioNetworkAdaptation(event);
+      audio_network_adaptation_events_.push_back(ana_event);
+      break;
+    }
+    case ParsedRtcEventLog::BWE_PROBE_CLUSTER_CREATED_EVENT: {
+      bwe_probe_cluster_created_events_.push_back(
+          GetBweProbeClusterCreated(event));
+      break;
+    }
+    case ParsedRtcEventLog::BWE_PROBE_RESULT_EVENT: {
+      bwe_probe_result_events_.push_back(GetBweProbeResult(event));
+      break;
+    }
+    case ParsedRtcEventLog::ALR_STATE_EVENT: {
+      alr_state_events_.push_back(GetAlrState(event));
+      break;
+    }
+    case ParsedRtcEventLog::ICE_CANDIDATE_PAIR_CONFIG: {
+      ice_candidate_pair_configs_.push_back(GetIceCandidatePairConfig(event));
+      break;
+    }
+    case ParsedRtcEventLog::ICE_CANDIDATE_PAIR_EVENT: {
+      ice_candidate_pair_events_.push_back(GetIceCandidatePairEvent(event));
+      break;
+    }
+    case ParsedRtcEventLog::UNKNOWN_EVENT: {
+      break;
+    }
+  }
+}
+
+size_t ParsedRtcEventLog::GetNumberOfEvents() const {
+  return events_.size();
+}
+
+int64_t ParsedRtcEventLog::GetTimestamp(size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetTimestamp(event);
+}
+
+int64_t ParsedRtcEventLog::GetTimestamp(const rtclog::Event& event) const {
+  RTC_CHECK(event.has_timestamp_us());
+  return event.timestamp_us();
+}
+
+ParsedRtcEventLog::EventType ParsedRtcEventLog::GetEventType(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  RTC_CHECK(event.has_type());
+  return GetRuntimeEventType(event.type());
+}
+
+// The header must have space for at least IP_PACKET_SIZE bytes.
+const webrtc::RtpHeaderExtensionMap* ParsedRtcEventLog::GetRtpHeader(
+    size_t index,
+    PacketDirection* incoming,
+    uint8_t* header,
+    size_t* header_length,
+    size_t* total_length,
+    int* probe_cluster_id) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetRtpHeader(event, incoming, header, header_length, total_length,
+                      probe_cluster_id);
+}
+
+const webrtc::RtpHeaderExtensionMap* ParsedRtcEventLog::GetRtpHeader(
+    const rtclog::Event& event,
+    PacketDirection* incoming,
+    uint8_t* header,
+    size_t* header_length,
+    size_t* total_length,
+    int* probe_cluster_id) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::RTP_EVENT);
+  RTC_CHECK(event.has_rtp_packet());
+  const rtclog::RtpPacket& rtp_packet = event.rtp_packet();
+  // Get direction of packet.
+  RTC_CHECK(rtp_packet.has_incoming());
+  if (incoming != nullptr) {
+    *incoming = rtp_packet.incoming() ? kIncomingPacket : kOutgoingPacket;
+  }
+  // Get packet length.
+  RTC_CHECK(rtp_packet.has_packet_length());
+  if (total_length != nullptr) {
+    *total_length = rtp_packet.packet_length();
+  }
+  // Get header length.
+  RTC_CHECK(rtp_packet.has_header());
+  if (header_length != nullptr) {
+    *header_length = rtp_packet.header().size();
+  }
+  if (probe_cluster_id != nullptr) {
+    if (rtp_packet.has_probe_cluster_id()) {
+      *probe_cluster_id = rtp_packet.probe_cluster_id();
+      RTC_CHECK_NE(*probe_cluster_id, PacedPacketInfo::kNotAProbe);
+    } else {
+      *probe_cluster_id = PacedPacketInfo::kNotAProbe;
+    }
+  }
+  // Get header contents.
+  if (header != nullptr) {
+    const size_t kMinRtpHeaderSize = 12;
+    RTC_CHECK_GE(rtp_packet.header().size(), kMinRtpHeaderSize);
+    RTC_CHECK_LE(rtp_packet.header().size(),
+                 static_cast<size_t>(IP_PACKET_SIZE));
+    memcpy(header, rtp_packet.header().data(), rtp_packet.header().size());
+    uint32_t ssrc = ByteReader<uint32_t>::ReadBigEndian(header + 8);
+    auto& extensions_maps = rtp_packet.incoming()
+                                ? incoming_rtp_extensions_maps_
+                                : outgoing_rtp_extensions_maps_;
+    auto it = extensions_maps.find(ssrc);
+    if (it != extensions_maps.end()) {
+      return &(it->second);
+    }
+    if (parse_unconfigured_header_extensions_ ==
+        UnconfiguredHeaderExtensions::kAttemptWebrtcDefaultConfig) {
+      RTC_LOG(LS_WARNING) << "Using default header extension map for SSRC "
+                          << ssrc;
+      extensions_maps.insert(std::make_pair(ssrc, default_extension_map_));
+      return &default_extension_map_;
+    }
+  }
+  return nullptr;
+}
+
+// The packet must have space for at least IP_PACKET_SIZE bytes.
+void ParsedRtcEventLog::GetRtcpPacket(size_t index,
+                                      PacketDirection* incoming,
+                                      uint8_t* packet,
+                                      size_t* length) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  GetRtcpPacket(event, incoming, packet, length);
+}
+
+void ParsedRtcEventLog::GetRtcpPacket(const rtclog::Event& event,
+                                      PacketDirection* incoming,
+                                      uint8_t* packet,
+                                      size_t* length) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::RTCP_EVENT);
+  RTC_CHECK(event.has_rtcp_packet());
+  const rtclog::RtcpPacket& rtcp_packet = event.rtcp_packet();
+  // Get direction of packet.
+  RTC_CHECK(rtcp_packet.has_incoming());
+  if (incoming != nullptr) {
+    *incoming = rtcp_packet.incoming() ? kIncomingPacket : kOutgoingPacket;
+  }
+  // Get packet length.
+  RTC_CHECK(rtcp_packet.has_packet_data());
+  if (length != nullptr) {
+    *length = rtcp_packet.packet_data().size();
+  }
+  // Get packet contents.
+  if (packet != nullptr) {
+    RTC_CHECK_LE(rtcp_packet.packet_data().size(),
+                 static_cast<unsigned>(IP_PACKET_SIZE));
+    memcpy(packet, rtcp_packet.packet_data().data(),
+           rtcp_packet.packet_data().size());
+  }
+}
+
+rtclog::StreamConfig ParsedRtcEventLog::GetVideoReceiveConfig(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  return GetVideoReceiveConfig(events_[index]);
+}
+
+rtclog::StreamConfig ParsedRtcEventLog::GetVideoReceiveConfig(
+    const rtclog::Event& event) const {
+  rtclog::StreamConfig config;
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT);
+  RTC_CHECK(event.has_video_receiver_config());
+  const rtclog::VideoReceiveConfig& receiver_config =
+      event.video_receiver_config();
+  // Get SSRCs.
+  RTC_CHECK(receiver_config.has_remote_ssrc());
+  config.remote_ssrc = receiver_config.remote_ssrc();
+  RTC_CHECK(receiver_config.has_local_ssrc());
+  config.local_ssrc = receiver_config.local_ssrc();
+  config.rtx_ssrc = 0;
+  // Get RTCP settings.
+  RTC_CHECK(receiver_config.has_rtcp_mode());
+  config.rtcp_mode = GetRuntimeRtcpMode(receiver_config.rtcp_mode());
+  RTC_CHECK(receiver_config.has_remb());
+  config.remb = receiver_config.remb();
+
+  // Get RTX map.
+  std::map<uint32_t, const rtclog::RtxConfig> rtx_map;
+  for (int i = 0; i < receiver_config.rtx_map_size(); i++) {
+    const rtclog::RtxMap& map = receiver_config.rtx_map(i);
+    RTC_CHECK(map.has_payload_type());
+    RTC_CHECK(map.has_config());
+    RTC_CHECK(map.config().has_rtx_ssrc());
+    RTC_CHECK(map.config().has_rtx_payload_type());
+    rtx_map.insert(std::make_pair(map.payload_type(), map.config()));
+  }
+
+  // Get header extensions.
+  GetHeaderExtensions(&config.rtp_extensions,
+                      receiver_config.header_extensions());
+  // Get decoders.
+  config.codecs.clear();
+  for (int i = 0; i < receiver_config.decoders_size(); i++) {
+    RTC_CHECK(receiver_config.decoders(i).has_name());
+    RTC_CHECK(receiver_config.decoders(i).has_payload_type());
+    int rtx_payload_type = 0;
+    auto rtx_it = rtx_map.find(receiver_config.decoders(i).payload_type());
+    if (rtx_it != rtx_map.end()) {
+      rtx_payload_type = rtx_it->second.rtx_payload_type();
+      if (config.rtx_ssrc != 0 &&
+          config.rtx_ssrc != rtx_it->second.rtx_ssrc()) {
+        RTC_LOG(LS_WARNING)
+            << "RtcEventLog protobuf contained different SSRCs for "
+               "different received RTX payload types. Will only use "
+               "rtx_ssrc = "
+            << config.rtx_ssrc << ".";
+      } else {
+        config.rtx_ssrc = rtx_it->second.rtx_ssrc();
+      }
+    }
+    config.codecs.emplace_back(receiver_config.decoders(i).name(),
+                               receiver_config.decoders(i).payload_type(),
+                               rtx_payload_type);
+  }
+  return config;
+}
+
+std::vector<rtclog::StreamConfig> ParsedRtcEventLog::GetVideoSendConfig(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  return GetVideoSendConfig(events_[index]);
+}
+
+std::vector<rtclog::StreamConfig> ParsedRtcEventLog::GetVideoSendConfig(
+    const rtclog::Event& event) const {
+  std::vector<rtclog::StreamConfig> configs;
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::VIDEO_SENDER_CONFIG_EVENT);
+  RTC_CHECK(event.has_video_sender_config());
+  const rtclog::VideoSendConfig& sender_config = event.video_sender_config();
+  if (sender_config.rtx_ssrcs_size() > 0 &&
+      sender_config.ssrcs_size() != sender_config.rtx_ssrcs_size()) {
+    RTC_LOG(WARNING)
+        << "VideoSendConfig is configured for RTX but the number of "
+           "SSRCs doesn't match the number of RTX SSRCs.";
+  }
+  configs.resize(sender_config.ssrcs_size());
+  for (int i = 0; i < sender_config.ssrcs_size(); i++) {
+    // Get SSRCs.
+    configs[i].local_ssrc = sender_config.ssrcs(i);
+    if (sender_config.rtx_ssrcs_size() > 0 &&
+        i < sender_config.rtx_ssrcs_size()) {
+      RTC_CHECK(sender_config.has_rtx_payload_type());
+      configs[i].rtx_ssrc = sender_config.rtx_ssrcs(i);
+    }
+    // Get header extensions.
+    GetHeaderExtensions(&configs[i].rtp_extensions,
+                        sender_config.header_extensions());
+
+    // Get the codec.
+    RTC_CHECK(sender_config.has_encoder());
+    RTC_CHECK(sender_config.encoder().has_name());
+    RTC_CHECK(sender_config.encoder().has_payload_type());
+    configs[i].codecs.emplace_back(
+        sender_config.encoder().name(), sender_config.encoder().payload_type(),
+        sender_config.has_rtx_payload_type() ? sender_config.rtx_payload_type()
+                                             : 0);
+  }
+  return configs;
+}
+
+rtclog::StreamConfig ParsedRtcEventLog::GetAudioReceiveConfig(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  return GetAudioReceiveConfig(events_[index]);
+}
+
+rtclog::StreamConfig ParsedRtcEventLog::GetAudioReceiveConfig(
+    const rtclog::Event& event) const {
+  rtclog::StreamConfig config;
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT);
+  RTC_CHECK(event.has_audio_receiver_config());
+  const rtclog::AudioReceiveConfig& receiver_config =
+      event.audio_receiver_config();
+  // Get SSRCs.
+  RTC_CHECK(receiver_config.has_remote_ssrc());
+  config.remote_ssrc = receiver_config.remote_ssrc();
+  RTC_CHECK(receiver_config.has_local_ssrc());
+  config.local_ssrc = receiver_config.local_ssrc();
+  // Get header extensions.
+  GetHeaderExtensions(&config.rtp_extensions,
+                      receiver_config.header_extensions());
+  return config;
+}
+
+rtclog::StreamConfig ParsedRtcEventLog::GetAudioSendConfig(size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  return GetAudioSendConfig(events_[index]);
+}
+
+rtclog::StreamConfig ParsedRtcEventLog::GetAudioSendConfig(
+    const rtclog::Event& event) const {
+  rtclog::StreamConfig config;
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::AUDIO_SENDER_CONFIG_EVENT);
+  RTC_CHECK(event.has_audio_sender_config());
+  const rtclog::AudioSendConfig& sender_config = event.audio_sender_config();
+  // Get SSRCs.
+  RTC_CHECK(sender_config.has_ssrc());
+  config.local_ssrc = sender_config.ssrc();
+  // Get header extensions.
+  GetHeaderExtensions(&config.rtp_extensions,
+                      sender_config.header_extensions());
+  return config;
+}
+
+LoggedAudioPlayoutEvent ParsedRtcEventLog::GetAudioPlayout(size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetAudioPlayout(event);
+}
+
+LoggedAudioPlayoutEvent ParsedRtcEventLog::GetAudioPlayout(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::AUDIO_PLAYOUT_EVENT);
+  RTC_CHECK(event.has_audio_playout_event());
+  const rtclog::AudioPlayoutEvent& playout_event = event.audio_playout_event();
+  LoggedAudioPlayoutEvent res;
+  res.timestamp_us = GetTimestamp(event);
+  RTC_CHECK(playout_event.has_local_ssrc());
+  res.ssrc = playout_event.local_ssrc();
+  return res;
+}
+
+LoggedBweLossBasedUpdate ParsedRtcEventLog::GetLossBasedBweUpdate(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetLossBasedBweUpdate(event);
+}
+
+LoggedBweLossBasedUpdate ParsedRtcEventLog::GetLossBasedBweUpdate(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::LOSS_BASED_BWE_UPDATE);
+  RTC_CHECK(event.has_loss_based_bwe_update());
+  const rtclog::LossBasedBweUpdate& loss_event = event.loss_based_bwe_update();
+
+  LoggedBweLossBasedUpdate bwe_update;
+  bwe_update.timestamp_us = GetTimestamp(event);
+  RTC_CHECK(loss_event.has_bitrate_bps());
+  bwe_update.bitrate_bps = loss_event.bitrate_bps();
+  RTC_CHECK(loss_event.has_fraction_loss());
+  bwe_update.fraction_lost = loss_event.fraction_loss();
+  RTC_CHECK(loss_event.has_total_packets());
+  bwe_update.expected_packets = loss_event.total_packets();
+  return bwe_update;
+}
+
+LoggedBweDelayBasedUpdate ParsedRtcEventLog::GetDelayBasedBweUpdate(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetDelayBasedBweUpdate(event);
+}
+
+LoggedBweDelayBasedUpdate ParsedRtcEventLog::GetDelayBasedBweUpdate(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::DELAY_BASED_BWE_UPDATE);
+  RTC_CHECK(event.has_delay_based_bwe_update());
+  const rtclog::DelayBasedBweUpdate& delay_event =
+      event.delay_based_bwe_update();
+
+  LoggedBweDelayBasedUpdate res;
+  res.timestamp_us = GetTimestamp(event);
+  RTC_CHECK(delay_event.has_bitrate_bps());
+  res.bitrate_bps = delay_event.bitrate_bps();
+  RTC_CHECK(delay_event.has_detector_state());
+  res.detector_state = GetRuntimeDetectorState(delay_event.detector_state());
+  return res;
+}
+
+LoggedAudioNetworkAdaptationEvent ParsedRtcEventLog::GetAudioNetworkAdaptation(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetAudioNetworkAdaptation(event);
+}
+
+LoggedAudioNetworkAdaptationEvent ParsedRtcEventLog::GetAudioNetworkAdaptation(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT);
+  RTC_CHECK(event.has_audio_network_adaptation());
+  const rtclog::AudioNetworkAdaptation& ana_event =
+      event.audio_network_adaptation();
+
+  LoggedAudioNetworkAdaptationEvent res;
+  res.timestamp_us = GetTimestamp(event);
+  if (ana_event.has_bitrate_bps())
+    res.config.bitrate_bps = ana_event.bitrate_bps();
+  if (ana_event.has_enable_fec())
+    res.config.enable_fec = ana_event.enable_fec();
+  if (ana_event.has_enable_dtx())
+    res.config.enable_dtx = ana_event.enable_dtx();
+  if (ana_event.has_frame_length_ms())
+    res.config.frame_length_ms = ana_event.frame_length_ms();
+  if (ana_event.has_num_channels())
+    res.config.num_channels = ana_event.num_channels();
+  if (ana_event.has_uplink_packet_loss_fraction())
+    res.config.uplink_packet_loss_fraction =
+        ana_event.uplink_packet_loss_fraction();
+  return res;
+}
+
+LoggedBweProbeClusterCreatedEvent ParsedRtcEventLog::GetBweProbeClusterCreated(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetBweProbeClusterCreated(event);
+}
+
+LoggedBweProbeClusterCreatedEvent ParsedRtcEventLog::GetBweProbeClusterCreated(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT);
+  RTC_CHECK(event.has_probe_cluster());
+  const rtclog::BweProbeCluster& pcc_event = event.probe_cluster();
+  LoggedBweProbeClusterCreatedEvent res;
+  res.timestamp_us = GetTimestamp(event);
+  RTC_CHECK(pcc_event.has_id());
+  res.id = pcc_event.id();
+  RTC_CHECK(pcc_event.has_bitrate_bps());
+  res.bitrate_bps = pcc_event.bitrate_bps();
+  RTC_CHECK(pcc_event.has_min_packets());
+  res.min_packets = pcc_event.min_packets();
+  RTC_CHECK(pcc_event.has_min_bytes());
+  res.min_bytes = pcc_event.min_bytes();
+  return res;
+}
+
+LoggedBweProbeResultEvent ParsedRtcEventLog::GetBweProbeResult(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetBweProbeResult(event);
+}
+
+LoggedBweProbeResultEvent ParsedRtcEventLog::GetBweProbeResult(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::BWE_PROBE_RESULT_EVENT);
+  RTC_CHECK(event.has_probe_result());
+  const rtclog::BweProbeResult& pr_event = event.probe_result();
+  LoggedBweProbeResultEvent res;
+  res.timestamp_us = GetTimestamp(event);
+  RTC_CHECK(pr_event.has_id());
+  res.id = pr_event.id();
+
+  RTC_CHECK(pr_event.has_result());
+  if (pr_event.result() == rtclog::BweProbeResult::SUCCESS) {
+    RTC_CHECK(pr_event.has_bitrate_bps());
+    res.bitrate_bps = pr_event.bitrate_bps();
+  } else if (pr_event.result() ==
+             rtclog::BweProbeResult::INVALID_SEND_RECEIVE_INTERVAL) {
+    res.failure_reason = ProbeFailureReason::kInvalidSendReceiveInterval;
+  } else if (pr_event.result() ==
+             rtclog::BweProbeResult::INVALID_SEND_RECEIVE_RATIO) {
+    res.failure_reason = ProbeFailureReason::kInvalidSendReceiveRatio;
+  } else if (pr_event.result() == rtclog::BweProbeResult::TIMEOUT) {
+    res.failure_reason = ProbeFailureReason::kTimeout;
+  } else {
+    RTC_NOTREACHED();
+  }
+
+  return res;
+}
+
+LoggedAlrStateEvent ParsedRtcEventLog::GetAlrState(size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& event = events_[index];
+  return GetAlrState(event);
+}
+
+LoggedAlrStateEvent ParsedRtcEventLog::GetAlrState(
+    const rtclog::Event& event) const {
+  RTC_CHECK(event.has_type());
+  RTC_CHECK_EQ(event.type(), rtclog::Event::ALR_STATE_EVENT);
+  RTC_CHECK(event.has_alr_state());
+  const rtclog::AlrState& alr_event = event.alr_state();
+  LoggedAlrStateEvent res;
+  res.timestamp_us = GetTimestamp(event);
+  RTC_CHECK(alr_event.has_in_alr());
+  res.in_alr = alr_event.in_alr();
+
+  return res;
+}
+
+LoggedIceCandidatePairConfig ParsedRtcEventLog::GetIceCandidatePairConfig(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& rtc_event = events_[index];
+  return GetIceCandidatePairConfig(rtc_event);
+}
+
+LoggedIceCandidatePairConfig ParsedRtcEventLog::GetIceCandidatePairConfig(
+    const rtclog::Event& rtc_event) const {
+  RTC_CHECK(rtc_event.has_type());
+  RTC_CHECK_EQ(rtc_event.type(), rtclog::Event::ICE_CANDIDATE_PAIR_CONFIG);
+  LoggedIceCandidatePairConfig res;
+  const rtclog::IceCandidatePairConfig& config =
+      rtc_event.ice_candidate_pair_config();
+  res.timestamp_us = GetTimestamp(rtc_event);
+  RTC_CHECK(config.has_config_type());
+  res.type = GetRuntimeIceCandidatePairConfigType(config.config_type());
+  RTC_CHECK(config.has_candidate_pair_id());
+  res.candidate_pair_id = config.candidate_pair_id();
+  RTC_CHECK(config.has_local_candidate_type());
+  res.local_candidate_type =
+      GetRuntimeIceCandidateType(config.local_candidate_type());
+  RTC_CHECK(config.has_local_relay_protocol());
+  res.local_relay_protocol =
+      GetRuntimeIceCandidatePairProtocol(config.local_relay_protocol());
+  RTC_CHECK(config.has_local_network_type());
+  res.local_network_type =
+      GetRuntimeIceCandidateNetworkType(config.local_network_type());
+  RTC_CHECK(config.has_local_address_family());
+  res.local_address_family =
+      GetRuntimeIceCandidatePairAddressFamily(config.local_address_family());
+  RTC_CHECK(config.has_remote_candidate_type());
+  res.remote_candidate_type =
+      GetRuntimeIceCandidateType(config.remote_candidate_type());
+  RTC_CHECK(config.has_remote_address_family());
+  res.remote_address_family =
+      GetRuntimeIceCandidatePairAddressFamily(config.remote_address_family());
+  RTC_CHECK(config.has_candidate_pair_protocol());
+  res.candidate_pair_protocol =
+      GetRuntimeIceCandidatePairProtocol(config.candidate_pair_protocol());
+  return res;
+}
+
+LoggedIceCandidatePairEvent ParsedRtcEventLog::GetIceCandidatePairEvent(
+    size_t index) const {
+  RTC_CHECK_LT(index, GetNumberOfEvents());
+  const rtclog::Event& rtc_event = events_[index];
+  return GetIceCandidatePairEvent(rtc_event);
+}
+
+LoggedIceCandidatePairEvent ParsedRtcEventLog::GetIceCandidatePairEvent(
+    const rtclog::Event& rtc_event) const {
+  RTC_CHECK(rtc_event.has_type());
+  RTC_CHECK_EQ(rtc_event.type(), rtclog::Event::ICE_CANDIDATE_PAIR_EVENT);
+  LoggedIceCandidatePairEvent res;
+  const rtclog::IceCandidatePairEvent& event =
+      rtc_event.ice_candidate_pair_event();
+  res.timestamp_us = GetTimestamp(rtc_event);
+  RTC_CHECK(event.has_event_type());
+  res.type = GetRuntimeIceCandidatePairEventType(event.event_type());
+  RTC_CHECK(event.has_candidate_pair_id());
+  res.candidate_pair_id = event.candidate_pair_id();
+  return res;
+}
+
+// Returns the MediaType for registered SSRCs. Search from the end to use last
+// registered types first.
+ParsedRtcEventLog::MediaType ParsedRtcEventLog::GetMediaType(
+    uint32_t ssrc,
+    PacketDirection direction) const {
+  if (direction == kIncomingPacket) {
+    if (std::find(incoming_video_ssrcs_.begin(), incoming_video_ssrcs_.end(),
+                  ssrc) != incoming_video_ssrcs_.end()) {
+      return MediaType::VIDEO;
+    }
+    if (std::find(incoming_audio_ssrcs_.begin(), incoming_audio_ssrcs_.end(),
+                  ssrc) != incoming_audio_ssrcs_.end()) {
+      return MediaType::AUDIO;
+    }
+  } else {
+    if (std::find(outgoing_video_ssrcs_.begin(), outgoing_video_ssrcs_.end(),
+                  ssrc) != outgoing_video_ssrcs_.end()) {
+      return MediaType::VIDEO;
+    }
+    if (std::find(outgoing_audio_ssrcs_.begin(), outgoing_audio_ssrcs_.end(),
+                  ssrc) != outgoing_audio_ssrcs_.end()) {
+      return MediaType::AUDIO;
+    }
+  }
+  return MediaType::ANY;
+}
+
+}  // namespace webrtc
diff --git a/logging/rtc_event_log/rtc_event_log_parser2.h b/logging/rtc_event_log/rtc_event_log_parser2.h
new file mode 100644
index 0000000..dfc607d
--- /dev/null
+++ b/logging/rtc_event_log/rtc_event_log_parser2.h
@@ -0,0 +1,920 @@
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#ifndef LOGGING_RTC_EVENT_LOG_RTC_EVENT_LOG_PARSER2_H_
+#define LOGGING_RTC_EVENT_LOG_RTC_EVENT_LOG_PARSER2_H_
+
+#include <iterator>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>  // pair
+#include <vector>
+
+#include "call/video_receive_stream.h"
+#include "call/video_send_stream.h"
+#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair.h"
+#include "logging/rtc_event_log/events/rtc_event_ice_candidate_pair_config.h"
+#include "logging/rtc_event_log/events/rtc_event_probe_result_failure.h"
+#include "logging/rtc_event_log/rtc_event_log.h"
+#include "logging/rtc_event_log/rtc_stream_config.h"
+#include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/common_header.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/nack.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/remb.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h"
+#include "rtc_base/ignore_wundef.h"
+
+// Files generated at build-time by the protobuf compiler.
+RTC_PUSH_IGNORING_WUNDEF()
+#ifdef WEBRTC_ANDROID_PLATFORM_BUILD
+#include "external/webrtc/webrtc/logging/rtc_event_log/rtc_event_log.pb.h"
+#else
+#include "logging/rtc_event_log/rtc_event_log.pb.h"
+#endif
+RTC_POP_IGNORING_WUNDEF()
+
+namespace webrtc {
+
+enum class BandwidthUsage;
+struct AudioEncoderRuntimeConfig;
+
+struct LoggedAlrStateEvent {
+  int64_t timestamp_us;
+  bool in_alr;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedAudioPlayoutEvent {
+  int64_t timestamp_us;
+  uint32_t ssrc;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedAudioNetworkAdaptationEvent {
+  int64_t timestamp_us;
+  AudioEncoderRuntimeConfig config;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedBweDelayBasedUpdate {
+  int64_t timestamp_us;
+  int32_t bitrate_bps;
+  BandwidthUsage detector_state;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedBweLossBasedUpdate {
+  int64_t timestamp_us;
+  int32_t bitrate_bps;
+  uint8_t fraction_lost;
+  int32_t expected_packets;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedBweProbeClusterCreatedEvent {
+  int64_t timestamp_us;
+  uint32_t id;
+  uint64_t bitrate_bps;
+  uint32_t min_packets;
+  uint32_t min_bytes;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedBweProbeResultEvent {
+  int64_t timestamp_us;
+  uint32_t id;
+  rtc::Optional<uint64_t> bitrate_bps;
+  rtc::Optional<ProbeFailureReason> failure_reason;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedIceCandidatePairConfig {
+  int64_t timestamp_us;
+  IceCandidatePairEventType type;
+  uint32_t candidate_pair_id;
+  IceCandidateType local_candidate_type;
+  IceCandidatePairProtocol local_relay_protocol;
+  IceCandidateNetworkType local_network_type;
+  IceCandidatePairAddressFamily local_address_family;
+  IceCandidateType remote_candidate_type;
+  IceCandidatePairAddressFamily remote_address_family;
+  IceCandidatePairProtocol candidate_pair_protocol;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedIceCandidatePairEvent {
+  int64_t timestamp_us;
+  IceCandidatePairEventType type;
+  uint32_t candidate_pair_id;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtpPacket {
+  LoggedRtpPacket(uint64_t timestamp_us,
+                  RTPHeader header,
+                  size_t header_length,
+                  size_t total_length)
+      : timestamp_us(timestamp_us),
+        header(header),
+        header_length(header_length),
+        total_length(total_length) {}
+  int64_t timestamp_us;
+  // TODO(terelius): This allocates space for 15 CSRCs even if none are used.
+  RTPHeader header;
+  size_t header_length;
+  size_t total_length;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtpPacketIncoming {
+  LoggedRtpPacketIncoming(uint64_t timestamp_us,
+                          RTPHeader header,
+                          size_t header_length,
+                          size_t total_length)
+      : rtp(timestamp_us, header, header_length, total_length) {}
+  LoggedRtpPacket rtp;
+  int64_t log_time_us() const { return rtp.timestamp_us; }
+  int64_t log_time_ms() const { return rtp.timestamp_us / 1000; }
+};
+
+struct LoggedRtpPacketOutgoing {
+  LoggedRtpPacketOutgoing(uint64_t timestamp_us,
+                          RTPHeader header,
+                          size_t header_length,
+                          size_t total_length)
+      : rtp(timestamp_us, header, header_length, total_length) {}
+  LoggedRtpPacket rtp;
+  int64_t log_time_us() const { return rtp.timestamp_us; }
+  int64_t log_time_ms() const { return rtp.timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacket {
+  LoggedRtcpPacket(uint64_t timestamp_us,
+                   const uint8_t* packet,
+                   size_t total_length)
+      : timestamp_us(timestamp_us), raw_data(packet, packet + total_length) {}
+  int64_t timestamp_us;
+  std::vector<uint8_t> raw_data;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketIncoming {
+  LoggedRtcpPacketIncoming(uint64_t timestamp_us,
+                           const uint8_t* packet,
+                           size_t total_length)
+      : rtcp(timestamp_us, packet, total_length) {}
+  LoggedRtcpPacket rtcp;
+  int64_t log_time_us() const { return rtcp.timestamp_us; }
+  int64_t log_time_ms() const { return rtcp.timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketOutgoing {
+  LoggedRtcpPacketOutgoing(uint64_t timestamp_us,
+                           const uint8_t* packet,
+                           size_t total_length)
+      : rtcp(timestamp_us, packet, total_length) {}
+  LoggedRtcpPacket rtcp;
+  int64_t log_time_us() const { return rtcp.timestamp_us; }
+  int64_t log_time_ms() const { return rtcp.timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketReceiverReport {
+  int64_t timestamp_us;
+  rtcp::ReceiverReport rr;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketSenderReport {
+  int64_t timestamp_us;
+  rtcp::SenderReport sr;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketRemb {
+  int64_t timestamp_us;
+  rtcp::Remb remb;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketNack {
+  int64_t timestamp_us;
+  rtcp::Nack nack;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedRtcpPacketTransportFeedback {
+  int64_t timestamp_us;
+  rtcp::TransportFeedback transport_feedback;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedStartEvent {
+  explicit LoggedStartEvent(uint64_t timestamp_us)
+      : timestamp_us(timestamp_us) {}
+  int64_t timestamp_us;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedStopEvent {
+  explicit LoggedStopEvent(uint64_t timestamp_us)
+      : timestamp_us(timestamp_us) {}
+  int64_t timestamp_us;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedAudioRecvConfig {
+  LoggedAudioRecvConfig(int64_t timestamp_us, const rtclog::StreamConfig config)
+      : timestamp_us(timestamp_us), config(config) {}
+  int64_t timestamp_us;
+  rtclog::StreamConfig config;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedAudioSendConfig {
+  LoggedAudioSendConfig(int64_t timestamp_us, const rtclog::StreamConfig config)
+      : timestamp_us(timestamp_us), config(config) {}
+  int64_t timestamp_us;
+  rtclog::StreamConfig config;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedVideoRecvConfig {
+  LoggedVideoRecvConfig(int64_t timestamp_us, const rtclog::StreamConfig config)
+      : timestamp_us(timestamp_us), config(config) {}
+  int64_t timestamp_us;
+  rtclog::StreamConfig config;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+struct LoggedVideoSendConfig {
+  LoggedVideoSendConfig(int64_t timestamp_us,
+                        const std::vector<rtclog::StreamConfig> configs)
+      : timestamp_us(timestamp_us), configs(configs) {}
+  int64_t timestamp_us;
+  std::vector<rtclog::StreamConfig> configs;
+  int64_t log_time_us() const { return timestamp_us; }
+  int64_t log_time_ms() const { return timestamp_us / 1000; }
+};
+
+template <typename T>
+class PacketView;
+
+template <typename T>
+class PacketIterator {
+  friend class PacketView<T>;
+
+ public:
+  // Standard iterator traits.
+  using difference_type = std::ptrdiff_t;
+  using value_type = T;
+  using pointer = T*;
+  using reference = T&;
+  using iterator_category = std::bidirectional_iterator_tag;
+
+  // The default-contructed iterator is meaningless, but is required by the
+  // ForwardIterator concept.
+  PacketIterator() : ptr_(nullptr), element_size_(0) {}
+  PacketIterator(const PacketIterator& other)
+      : ptr_(other.ptr_), element_size_(other.element_size_) {}
+  PacketIterator(const PacketIterator&& other)
+      : ptr_(other.ptr_), element_size_(other.element_size_) {}
+  ~PacketIterator() = default;
+
+  PacketIterator& operator=(const PacketIterator& other) {
+    ptr_ = other.ptr_;
+    element_size_ = other.element_size_;
+    return *this;
+  }
+  PacketIterator& operator=(const PacketIterator&& other) {
+    ptr_ = other.ptr_;
+    element_size_ = other.element_size_;
+    return *this;
+  }
+
+  bool operator==(const PacketIterator<T>& other) const {
+    RTC_DCHECK_EQ(element_size_, other.element_size_);
+    return ptr_ == other.ptr_;
+  }
+  bool operator!=(const PacketIterator<T>& other) const {
+    RTC_DCHECK_EQ(element_size_, other.element_size_);
+    return ptr_ != other.ptr_;
+  }
+
+  PacketIterator& operator++() {
+    ptr_ += element_size_;
+    return *this;
+  }
+  PacketIterator& operator--() {
+    ptr_ -= element_size_;
+    return *this;
+  }
+  PacketIterator operator++(int) {
+    PacketIterator iter_copy(ptr_, element_size_);
+    ptr_ += element_size_;
+    return iter_copy;
+  }
+  PacketIterator operator--(int) {
+    PacketIterator iter_copy(ptr_, element_size_);
+    ptr_ -= element_size_;
+    return iter_copy;
+  }
+
+  T& operator*() { return *reinterpret_cast<T*>(ptr_); }
+  const T& operator*() const { return *reinterpret_cast<const T*>(ptr_); }
+
+ private:
+  PacketIterator(typename std::conditional<std::is_const<T>::value,
+                                           const void*,
+                                           void*>::type p,
+                 size_t s)
+      : ptr_(reinterpret_cast<decltype(ptr_)>(p)), element_size_(s) {}
+
+  typename std::conditional<std::is_const<T>::value, const char*, char*>::type
+      ptr_;
+  size_t element_size_;
+};
+
+// Suppose that we have a struct S where we are only interested in a specific
+// member M. Given an array of S, PacketView can be used to treat the array
+// as an array of M, without exposing the type S to surrounding code and without
+// accessing the member through a virtual function. In this case, we want to
+// have a common view for incoming and outgoing RtpPackets, hence the PacketView
+// name.
+// Note that constructing a PacketView bypasses the typesystem, so the caller
+// has to take extra care when constructing these objects. The implementation
+// also requires that the containing struct is standard-layout (e.g. POD).
+//
+// Usage example:
+// struct A {...};
+// struct B { A a; ...};
+// struct C { A a; ...};
+// size_t len = 10;
+// B* array1 = new B[len];
+// C* array2 = new C[len];
+//
+// PacketView<A> view1 = PacketView<A>::Create<B>(array1, len, offsetof(B, a));
+// PacketView<A> view2 = PacketView<A>::Create<C>(array2, len, offsetof(C, a));
+//
+// The following code works with either view1 or view2.
+// void f(PacketView<A> view)
+// for (A& a : view) {
+//   DoSomething(a);
+// }
+template <typename T>
+class PacketView {
+ public:
+  template <typename U>
+  static PacketView Create(U* ptr, size_t num_elements, size_t offset) {
+    static_assert(std::is_standard_layout<U>::value,
+                  "PacketView can only be created for standard layout types.");
+    static_assert(std::is_standard_layout<T>::value,
+                  "PacketView can only be created for standard layout types.");
+    return PacketView(ptr, num_elements, offset, sizeof(U));
+  }
+
+  using iterator = PacketIterator<T>;
+  using const_iterator = PacketIterator<const T>;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  iterator begin() { return iterator(data_, element_size_); }
+  iterator end() {
+    auto end_ptr = data_ + num_elements_ * element_size_;
+    return iterator(end_ptr, element_size_);
+  }
+
+  const_iterator begin() const { return const_iterator(data_, element_size_); }
+  const_iterator end() const {
+    auto end_ptr = data_ + num_elements_ * element_size_;
+    return const_iterator(end_ptr, element_size_);
+  }
+
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin());
+  }
+
+  size_t size() const { return num_elements_; }
+
+  T& operator[](size_t i) {
+    auto elem_ptr = data_ + i * element_size_;
+    return *reinterpret_cast<T*>(elem_ptr);
+  }
+
+  const T& operator[](size_t i) const {
+    auto elem_ptr = data_ + i * element_size_;
+    return *reinterpret_cast<const T*>(elem_ptr);
+  }
+
+ private:
+  PacketView(typename std::conditional<std::is_const<T>::value,
+                                       const void*,
+                                       void*>::type data,
+             size_t num_elements,
+             size_t offset,
+             size_t element_size)
+      : data_(reinterpret_cast<decltype(data_)>(data) + offset),
+        num_elements_(num_elements),
+        element_size_(element_size) {}
+
+  typename std::conditional<std::is_const<T>::value, const char*, char*>::type
+      data_;
+  size_t num_elements_;
+  size_t element_size_;
+};
+
+class ParsedRtcEventLog {
+  friend class RtcEventLogTestHelper;
+
+ public:
+  enum EventType {
+    UNKNOWN_EVENT = 0,
+    LOG_START = 1,
+    LOG_END = 2,
+    RTP_EVENT = 3,
+    RTCP_EVENT = 4,
+    AUDIO_PLAYOUT_EVENT = 5,
+    LOSS_BASED_BWE_UPDATE = 6,
+    DELAY_BASED_BWE_UPDATE = 7,
+    VIDEO_RECEIVER_CONFIG_EVENT = 8,
+    VIDEO_SENDER_CONFIG_EVENT = 9,
+    AUDIO_RECEIVER_CONFIG_EVENT = 10,
+    AUDIO_SENDER_CONFIG_EVENT = 11,
+    AUDIO_NETWORK_ADAPTATION_EVENT = 16,
+    BWE_PROBE_CLUSTER_CREATED_EVENT = 17,
+    BWE_PROBE_RESULT_EVENT = 18,
+    ALR_STATE_EVENT = 19,
+    ICE_CANDIDATE_PAIR_CONFIG = 20,
+    ICE_CANDIDATE_PAIR_EVENT = 21,
+  };
+
+  enum class MediaType { ANY, AUDIO, VIDEO, DATA };
+  enum class UnconfiguredHeaderExtensions {
+    kDontParse,
+    kAttemptWebrtcDefaultConfig
+  };
+
+  explicit ParsedRtcEventLog(
+      UnconfiguredHeaderExtensions parse_unconfigured_header_extensions =
+          UnconfiguredHeaderExtensions::kDontParse);
+
+  // Clears previously parsed events and resets the ParsedRtcEventLog to an
+  // empty state.
+  void Clear();
+
+  // Reads an RtcEventLog file and returns true if parsing was successful.
+  bool ParseFile(const std::string& file_name);
+
+  // Reads an RtcEventLog from a string and returns true if successful.
+  bool ParseString(const std::string& s);
+
+  // Reads an RtcEventLog from an istream and returns true if successful.
+  bool ParseStream(
+      std::istream& stream);  // no-presubmit-check TODO(webrtc:8982)
+
+  // Returns the number of events in an EventStream.
+  size_t GetNumberOfEvents() const;
+
+  // Reads the arrival timestamp (in microseconds) from a rtclog::Event.
+  int64_t GetTimestamp(size_t index) const;
+  int64_t GetTimestamp(const rtclog::Event& event) const;
+
+  // Reads the event type of the rtclog::Event at |index|.
+  EventType GetEventType(size_t index) const;
+
+  // Reads the header, direction, header length and packet length from the RTP
+  // event at |index|, and stores the values in the corresponding output
+  // parameters. Each output parameter can be set to nullptr if that value
+  // isn't needed.
+  // NB: The header must have space for at least IP_PACKET_SIZE bytes.
+  // Returns: a pointer to a header extensions map acquired from parsing
+  // corresponding Audio/Video Sender/Receiver config events.
+  // Warning: if the same SSRC is reused by both video and audio streams during
+  // call, extensions maps may be incorrect (the last one would be returned).
+  const webrtc::RtpHeaderExtensionMap* GetRtpHeader(
+      size_t index,
+      PacketDirection* incoming,
+      uint8_t* header,
+      size_t* header_length,
+      size_t* total_length,
+      int* probe_cluster_id) const;
+  const webrtc::RtpHeaderExtensionMap* GetRtpHeader(
+      const rtclog::Event& event,
+      PacketDirection* incoming,
+      uint8_t* header,
+      size_t* header_length,
+      size_t* total_length,
+      int* probe_cluster_id) const;
+
+  // Reads packet, direction and packet length from the RTCP event at |index|,
+  // and stores the values in the corresponding output parameters.
+  // Each output parameter can be set to nullptr if that value isn't needed.
+  // NB: The packet must have space for at least IP_PACKET_SIZE bytes.
+  void GetRtcpPacket(size_t index,
+                     PacketDirection* incoming,
+                     uint8_t* packet,
+                     size_t* length) const;
+  void GetRtcpPacket(const rtclog::Event& event,
+                     PacketDirection* incoming,
+                     uint8_t* packet,
+                     size_t* length) const;
+
+  // Reads a video receive config event to a StreamConfig struct.
+  // Only the fields that are stored in the protobuf will be written.
+  rtclog::StreamConfig GetVideoReceiveConfig(size_t index) const;
+
+  // Reads a video send config event to a StreamConfig struct. If the proto
+  // contains multiple SSRCs and RTX SSRCs (this used to be the case for
+  // simulcast streams) then we return one StreamConfig per SSRC,RTX_SSRC pair.
+  // Only the fields that are stored in the protobuf will be written.
+  std::vector<rtclog::StreamConfig> GetVideoSendConfig(size_t index) const;
+
+  // Reads a audio receive config event to a StreamConfig struct.
+  // Only the fields that are stored in the protobuf will be written.
+  rtclog::StreamConfig GetAudioReceiveConfig(size_t index) const;
+
+  // Reads a config event to a StreamConfig struct.
+  // Only the fields that are stored in the protobuf will be written.
+  rtclog::StreamConfig GetAudioSendConfig(size_t index) const;
+
+  // Reads the SSRC from the audio playout event at |index|. The SSRC is stored
+  // in the output parameter ssrc. The output parameter can be set to nullptr
+  // and in that case the function only asserts that the event is well formed.
+  LoggedAudioPlayoutEvent GetAudioPlayout(size_t index) const;
+
+  // Reads bitrate, fraction loss (as defined in RFC 1889) and total number of
+  // expected packets from the loss based BWE event at |index| and stores the
+  // values in
+  // the corresponding output parameters. Each output parameter can be set to
+  // nullptr if that
+  // value isn't needed.
+  LoggedBweLossBasedUpdate GetLossBasedBweUpdate(size_t index) const;
+
+  // Reads bitrate and detector_state from the delay based BWE event at |index|
+  // and stores the values in the corresponding output parameters. Each output
+  // parameter can be set to nullptr if that
+  // value isn't needed.
+  LoggedBweDelayBasedUpdate GetDelayBasedBweUpdate(size_t index) const;
+
+  // Reads a audio network adaptation event to a (non-NULL)
+  // AudioEncoderRuntimeConfig struct. Only the fields that are
+  // stored in the protobuf will be written.
+  LoggedAudioNetworkAdaptationEvent GetAudioNetworkAdaptation(
+      size_t index) const;
+
+  LoggedBweProbeClusterCreatedEvent GetBweProbeClusterCreated(
+      size_t index) const;
+
+  LoggedBweProbeResultEvent GetBweProbeResult(size_t index) const;
+
+  MediaType GetMediaType(uint32_t ssrc, PacketDirection direction) const;
+
+  LoggedAlrStateEvent GetAlrState(size_t index) const;
+
+  LoggedIceCandidatePairConfig GetIceCandidatePairConfig(size_t index) const;
+
+  LoggedIceCandidatePairEvent GetIceCandidatePairEvent(size_t index) const;
+
+  const std::set<uint32_t>& incoming_rtx_ssrcs() const {
+    return incoming_rtx_ssrcs_;
+  }
+  const std::set<uint32_t>& incoming_video_ssrcs() const {
+    return incoming_video_ssrcs_;
+  }
+  const std::set<uint32_t>& incoming_audio_ssrcs() const {
+    return incoming_audio_ssrcs_;
+  }
+  const std::set<uint32_t>& outgoing_rtx_ssrcs() const {
+    return outgoing_rtx_ssrcs_;
+  }
+  const std::set<uint32_t>& outgoing_video_ssrcs() const {
+    return outgoing_video_ssrcs_;
+  }
+  const std::set<uint32_t>& outgoing_audio_ssrcs() const {
+    return outgoing_audio_ssrcs_;
+  }
+
+  const std::vector<LoggedStartEvent>& start_log_events() const {
+    return start_log_events_;
+  }
+  const std::vector<LoggedStopEvent>& stop_log_events() const {
+    return stop_log_events_;
+  }
+  const std::map<uint32_t, std::vector<int64_t>>& audio_playout_events() const {
+    return audio_playout_events_;
+  }
+  const std::vector<LoggedAudioNetworkAdaptationEvent>&
+  audio_network_adaptation_events() const {
+    return audio_network_adaptation_events_;
+  }
+  const std::vector<LoggedBweProbeClusterCreatedEvent>&
+  bwe_probe_cluster_created_events() const {
+    return bwe_probe_cluster_created_events_;
+  }
+  const std::vector<LoggedBweProbeResultEvent>& bwe_probe_result_events()
+      const {
+    return bwe_probe_result_events_;
+  }
+  const std::vector<LoggedBweDelayBasedUpdate>& bwe_delay_updates() const {
+    return bwe_delay_updates_;
+  }
+  const std::vector<LoggedBweLossBasedUpdate>& bwe_loss_updates() const {
+    return bwe_loss_updates_;
+  }
+  const std::vector<LoggedAlrStateEvent>& alr_state_events() const {
+    return alr_state_events_;
+  }
+  const std::vector<LoggedIceCandidatePairConfig>& ice_candidate_pair_configs()
+      const {
+    return ice_candidate_pair_configs_;
+  }
+  const std::vector<LoggedIceCandidatePairEvent>& ice_candidate_pair_events()
+      const {
+    return ice_candidate_pair_events_;
+  }
+
+  struct LoggedRtpStreamIncoming {
+    uint32_t ssrc;
+    std::vector<LoggedRtpPacketIncoming> incoming_packets;
+  };
+
+  struct LoggedRtpStreamOutgoing {
+    uint32_t ssrc;
+    std::vector<LoggedRtpPacketOutgoing> outgoing_packets;
+  };
+
+  struct LoggedRtpStreamView {
+    LoggedRtpStreamView(uint32_t ssrc,
+                        const LoggedRtpPacketIncoming* ptr,
+                        size_t num_elements)
+        : ssrc(ssrc),
+          packet_view(PacketView<const LoggedRtpPacket>::Create(
+              ptr,
+              num_elements,
+              offsetof(LoggedRtpPacketIncoming, rtp))) {}
+    LoggedRtpStreamView(uint32_t ssrc,
+                        const LoggedRtpPacketOutgoing* ptr,
+                        size_t num_elements)
+        : ssrc(ssrc),
+          packet_view(PacketView<const LoggedRtpPacket>::Create(
+              ptr,
+              num_elements,
+              offsetof(LoggedRtpPacketOutgoing, rtp))) {}
+    uint32_t ssrc;
+    PacketView<const LoggedRtpPacket> packet_view;
+  };
+
+  const std::vector<LoggedRtpStreamIncoming>& incoming_rtp_packets_by_ssrc()
+      const {
+    return incoming_rtp_packets_by_ssrc_;
+  }
+
+  const std::vector<LoggedRtpStreamOutgoing>& outgoing_rtp_packets_by_ssrc()
+      const {
+    return outgoing_rtp_packets_by_ssrc_;
+  }
+
+  const std::vector<LoggedRtcpPacketIncoming>& incoming_rtcp_packets() const {
+    return incoming_rtcp_packets_;
+  }
+
+  const std::vector<LoggedRtcpPacketOutgoing>& outgoing_rtcp_packets() const {
+    return outgoing_rtcp_packets_;
+  }
+
+  const std::vector<LoggedRtpStreamView>& rtp_packets_by_ssrc(
+      PacketDirection direction) const {
+    if (direction == kIncomingPacket)
+      return incoming_rtp_packet_views_by_ssrc_;
+    else
+      return outgoing_rtp_packet_views_by_ssrc_;
+  }
+
+  const std::vector<LoggedRtcpPacketReceiverReport>& receiver_reports(
+      PacketDirection direction) const {
+    if (direction == kIncomingPacket) {
+      return incoming_rr_;
+    } else {
+      return outgoing_rr_;
+    }
+  }
+
+  const std::vector<LoggedRtcpPacketSenderReport>& sender_reports(
+      PacketDirection direction) const {
+    if (direction == kIncomingPacket) {
+      return incoming_sr_;
+    } else {
+      return outgoing_sr_;
+    }
+  }
+
+  const std::vector<LoggedRtcpPacketNack>& nacks(
+      PacketDirection direction) const {
+    if (direction == kIncomingPacket) {
+      return incoming_nack_;
+    } else {
+      return outgoing_nack_;
+    }
+  }
+
+  const std::vector<LoggedRtcpPacketRemb>& rembs(
+      PacketDirection direction) const {
+    if (direction == kIncomingPacket) {
+      return incoming_remb_;
+    } else {
+      return outgoing_remb_;
+    }
+  }
+
+  const std::vector<LoggedRtcpPacketTransportFeedback>& transport_feedbacks(
+      PacketDirection direction) const {
+    if (direction == kIncomingPacket) {
+      return incoming_transport_feedback_;
+    } else {
+      return outgoing_transport_feedback_;
+    }
+  }
+
+  int64_t first_timestamp() const { return first_timestamp_; }
+  int64_t last_timestamp() const { return last_timestamp_; }
+
+ private:
+  void StoreParsedEvent(const rtclog::Event& event);
+
+  rtclog::StreamConfig GetVideoReceiveConfig(const rtclog::Event& event) const;
+  std::vector<rtclog::StreamConfig> GetVideoSendConfig(
+      const rtclog::Event& event) const;
+  rtclog::StreamConfig GetAudioReceiveConfig(const rtclog::Event& event) const;
+  rtclog::StreamConfig GetAudioSendConfig(const rtclog::Event& event) const;
+
+  LoggedAudioPlayoutEvent GetAudioPlayout(const rtclog::Event& event) const;
+
+  LoggedBweLossBasedUpdate GetLossBasedBweUpdate(
+      const rtclog::Event& event) const;
+  LoggedBweDelayBasedUpdate GetDelayBasedBweUpdate(
+      const rtclog::Event& event) const;
+
+  LoggedAudioNetworkAdaptationEvent GetAudioNetworkAdaptation(
+      const rtclog::Event& event) const;
+
+  LoggedBweProbeClusterCreatedEvent GetBweProbeClusterCreated(
+      const rtclog::Event& event) const;
+  LoggedBweProbeResultEvent GetBweProbeResult(const rtclog::Event& event) const;
+
+  LoggedAlrStateEvent GetAlrState(const rtclog::Event& event) const;
+
+  LoggedIceCandidatePairConfig GetIceCandidatePairConfig(
+      const rtclog::Event& event) const;
+  LoggedIceCandidatePairEvent GetIceCandidatePairEvent(
+      const rtclog::Event& event) const;
+
+  std::vector<rtclog::Event> events_;
+
+  struct Stream {
+    Stream(uint32_t ssrc,
+           MediaType media_type,
+           PacketDirection direction,
+           webrtc::RtpHeaderExtensionMap map)
+        : ssrc(ssrc),
+          media_type(media_type),
+          direction(direction),
+          rtp_extensions_map(map) {}
+    uint32_t ssrc;
+    MediaType media_type;
+    PacketDirection direction;
+    webrtc::RtpHeaderExtensionMap rtp_extensions_map;
+  };
+
+  const UnconfiguredHeaderExtensions parse_unconfigured_header_extensions_;
+
+  // 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_;
+
+  // 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<uint32_t> incoming_rtx_ssrcs_;
+  std::set<uint32_t> incoming_video_ssrcs_;
+  std::set<uint32_t> incoming_audio_ssrcs_;
+  std::set<uint32_t> outgoing_rtx_ssrcs_;
+  std::set<uint32_t> outgoing_video_ssrcs_;
+  std::set<uint32_t> outgoing_audio_ssrcs_;
+
+  // Maps an SSRC to the parsed  RTP headers in that stream. Header extensions
+  // are parsed if the stream has been configured. This is only used for
+  // grouping the events by SSRC during parsing; the events are moved to
+  // incoming_rtp_packets_by_ssrc_ once the parsing is done.
+  std::map<uint32_t, std::vector<LoggedRtpPacketIncoming>>
+      incoming_rtp_packets_map_;
+  std::map<uint32_t, std::vector<LoggedRtpPacketOutgoing>>
+      outgoing_rtp_packets_map_;
+
+  // RTP headers.
+  std::vector<LoggedRtpStreamIncoming> incoming_rtp_packets_by_ssrc_;
+  std::vector<LoggedRtpStreamOutgoing> outgoing_rtp_packets_by_ssrc_;
+  std::vector<LoggedRtpStreamView> incoming_rtp_packet_views_by_ssrc_;
+  std::vector<LoggedRtpStreamView> outgoing_rtp_packet_views_by_ssrc_;
+
+  // Raw RTCP packets.
+  std::vector<LoggedRtcpPacketIncoming> incoming_rtcp_packets_;
+  std::vector<LoggedRtcpPacketOutgoing> outgoing_rtcp_packets_;
+
+  // Parsed RTCP messages. Currently not separated based on SSRC.
+  std::vector<LoggedRtcpPacketReceiverReport> incoming_rr_;
+  std::vector<LoggedRtcpPacketReceiverReport> outgoing_rr_;
+  std::vector<LoggedRtcpPacketSenderReport> incoming_sr_;
+  std::vector<LoggedRtcpPacketSenderReport> outgoing_sr_;
+  std::vector<LoggedRtcpPacketNack> incoming_nack_;
+  std::vector<LoggedRtcpPacketNack> outgoing_nack_;
+  std::vector<LoggedRtcpPacketRemb> incoming_remb_;
+  std::vector<LoggedRtcpPacketRemb> outgoing_remb_;
+  std::vector<LoggedRtcpPacketTransportFeedback> incoming_transport_feedback_;
+  std::vector<LoggedRtcpPacketTransportFeedback> outgoing_transport_feedback_;
+
+  std::vector<LoggedStartEvent> start_log_events_;
+  std::vector<LoggedStopEvent> stop_log_events_;
+
+  // Maps an SSRC to the timestamps of parsed audio playout events.
+  std::map<uint32_t, std::vector<int64_t>> audio_playout_events_;
+
+  std::vector<LoggedAudioNetworkAdaptationEvent>
+      audio_network_adaptation_events_;
+
+  std::vector<LoggedBweProbeClusterCreatedEvent>
+      bwe_probe_cluster_created_events_;
+
+  std::vector<LoggedBweProbeResultEvent> bwe_probe_result_events_;
+
+  std::vector<LoggedBweDelayBasedUpdate> bwe_delay_updates_;
+
+  // A list of all updates from the send-side loss-based bandwidth estimator.
+  std::vector<LoggedBweLossBasedUpdate> bwe_loss_updates_;
+
+  std::vector<LoggedAlrStateEvent> alr_state_events_;
+
+  std::vector<LoggedIceCandidatePairConfig> ice_candidate_pair_configs_;
+
+  std::vector<LoggedIceCandidatePairEvent> ice_candidate_pair_events_;
+
+  std::vector<LoggedAudioRecvConfig> audio_recv_configs_;
+  std::vector<LoggedAudioSendConfig> audio_send_configs_;
+  std::vector<LoggedVideoRecvConfig> video_recv_configs_;
+  std::vector<LoggedVideoSendConfig> video_send_configs_;
+
+  uint8_t last_incoming_rtcp_packet_[IP_PACKET_SIZE];
+  uint8_t last_incoming_rtcp_packet_length_;
+
+  int64_t first_timestamp_;
+  int64_t last_timestamp_;
+
+  // The extension maps are mutable to allow us to insert the default
+  // configuration when parsing an RTP header for an unconfigured stream.
+  mutable std::map<uint32_t, webrtc::RtpHeaderExtensionMap>
+      incoming_rtp_extensions_maps_;
+  mutable std::map<uint32_t, webrtc::RtpHeaderExtensionMap>
+      outgoing_rtp_extensions_maps_;
+};
+
+}  // namespace webrtc
+
+#endif  // LOGGING_RTC_EVENT_LOG_RTC_EVENT_LOG_PARSER2_H_
diff --git a/logging/rtc_event_log/rtc_event_log_unittest.cc b/logging/rtc_event_log/rtc_event_log_unittest.cc
index 1cea313..529228b 100644
--- a/logging/rtc_event_log/rtc_event_log_unittest.cc
+++ b/logging/rtc_event_log/rtc_event_log_unittest.cc
@@ -34,7 +34,7 @@
 #include "logging/rtc_event_log/events/rtc_event_video_send_stream_config.h"
 #include "logging/rtc_event_log/output/rtc_event_log_output_file.h"
 #include "logging/rtc_event_log/rtc_event_log.h"
-#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "logging/rtc_event_log/rtc_event_log_unittest_helper.h"
 #include "logging/rtc_event_log/rtc_stream_config.h"
 #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
@@ -750,17 +750,16 @@
   for (size_t i = 1; i < parsed_log.GetNumberOfEvents() - 1; i++) {
     EXPECT_EQ(parsed_log.GetEventType(i),
               ParsedRtcEventLog::EventType::AUDIO_PLAYOUT_EVENT);
-    uint32_t ssrc;
-    parsed_log.GetAudioPlayout(i, &ssrc);
-    int64_t timestamp = parsed_log.GetTimestamp(i);
-    EXPECT_LT(ssrc, kNumEvents);
-    EXPECT_EQ(static_cast<int64_t>(kStartTime + 10000 * ssrc), timestamp);
+    LoggedAudioPlayoutEvent playout_event = parsed_log.GetAudioPlayout(i);
+    EXPECT_LT(playout_event.ssrc, kNumEvents);
+    EXPECT_EQ(static_cast<int64_t>(kStartTime + 10000 * playout_event.ssrc),
+              playout_event.timestamp_us);
     if (last_ssrc)
-      EXPECT_EQ(ssrc, *last_ssrc + 1);
+      EXPECT_EQ(playout_event.ssrc, *last_ssrc + 1);
     if (last_timestamp)
-      EXPECT_EQ(timestamp, *last_timestamp + 10000);
-    last_ssrc = ssrc;
-    last_timestamp = timestamp;
+      EXPECT_EQ(playout_event.timestamp_us, *last_timestamp + 10000);
+    last_ssrc = playout_event.ssrc;
+    last_timestamp = playout_event.timestamp_us;
   }
   RtcEventLogTestHelper::VerifyLogEndEvent(parsed_log,
                                            parsed_log.GetNumberOfEvents() - 1);
diff --git a/logging/rtc_event_log/rtc_event_log_unittest_helper.cc b/logging/rtc_event_log/rtc_event_log_unittest_helper.cc
index 23e15b5..955c3b9 100644
--- a/logging/rtc_event_log/rtc_event_log_unittest_helper.cc
+++ b/logging/rtc_event_log/rtc_event_log_unittest_helper.cc
@@ -460,9 +460,8 @@
   EXPECT_EQ(ssrc, playout_event.local_ssrc());
 
   // Check consistency of the parser.
-  uint32_t parsed_ssrc;
-  parsed_log.GetAudioPlayout(index, &parsed_ssrc);
-  EXPECT_EQ(ssrc, parsed_ssrc);
+  LoggedAudioPlayoutEvent parsed_event = parsed_log.GetAudioPlayout(index);
+  EXPECT_EQ(ssrc, parsed_event.ssrc);
 }
 
 void RtcEventLogTestHelper::VerifyBweLossEvent(
@@ -484,14 +483,10 @@
   EXPECT_EQ(total_packets, bwe_event.total_packets());
 
   // Check consistency of the parser.
-  int32_t parsed_bitrate;
-  uint8_t parsed_fraction_loss;
-  int32_t parsed_total_packets;
-  parsed_log.GetLossBasedBweUpdate(
-      index, &parsed_bitrate, &parsed_fraction_loss, &parsed_total_packets);
-  EXPECT_EQ(bitrate, parsed_bitrate);
-  EXPECT_EQ(fraction_loss, parsed_fraction_loss);
-  EXPECT_EQ(total_packets, parsed_total_packets);
+  LoggedBweLossBasedUpdate bwe_update = parsed_log.GetLossBasedBweUpdate(index);
+  EXPECT_EQ(bitrate, bwe_update.bitrate_bps);
+  EXPECT_EQ(fraction_loss, bwe_update.fraction_lost);
+  EXPECT_EQ(total_packets, bwe_update.expected_packets);
 }
 
 void RtcEventLogTestHelper::VerifyBweDelayEvent(
@@ -511,8 +506,7 @@
             GetRuntimeDetectorState(bwe_event.detector_state()));
 
   // Check consistency of the parser.
-  ParsedRtcEventLog::BweDelayBasedUpdate res =
-      parsed_log.GetDelayBasedBweUpdate(index);
+  LoggedBweDelayBasedUpdate res = parsed_log.GetDelayBasedBweUpdate(index);
   EXPECT_EQ(res.bitrate_bps, bitrate);
   EXPECT_EQ(res.detector_state, detector_state);
 }
@@ -521,15 +515,20 @@
     const ParsedRtcEventLog& parsed_log,
     size_t index,
     const AudioEncoderRuntimeConfig& config) {
-  AudioEncoderRuntimeConfig parsed_config;
-  parsed_log.GetAudioNetworkAdaptation(index, &parsed_config);
-  EXPECT_EQ(config.bitrate_bps, parsed_config.bitrate_bps);
-  EXPECT_EQ(config.enable_dtx, parsed_config.enable_dtx);
-  EXPECT_EQ(config.enable_fec, parsed_config.enable_fec);
-  EXPECT_EQ(config.frame_length_ms, parsed_config.frame_length_ms);
-  EXPECT_EQ(config.num_channels, parsed_config.num_channels);
+  ASSERT_LT(index, parsed_log.events_.size());
+  const rtclog::Event& event = parsed_log.events_[index];
+  ASSERT_TRUE(IsValidBasicEvent(event));
+  ASSERT_EQ(rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT, event.type());
+
+  LoggedAudioNetworkAdaptationEvent parsed_event =
+      parsed_log.GetAudioNetworkAdaptation(index);
+  EXPECT_EQ(config.bitrate_bps, parsed_event.config.bitrate_bps);
+  EXPECT_EQ(config.enable_dtx, parsed_event.config.enable_dtx);
+  EXPECT_EQ(config.enable_fec, parsed_event.config.enable_fec);
+  EXPECT_EQ(config.frame_length_ms, parsed_event.config.frame_length_ms);
+  EXPECT_EQ(config.num_channels, parsed_event.config.num_channels);
   EXPECT_EQ(config.uplink_packet_loss_fraction,
-            parsed_config.uplink_packet_loss_fraction);
+            parsed_event.config.uplink_packet_loss_fraction);
 }
 
 void RtcEventLogTestHelper::VerifyLogStartEvent(
diff --git a/logging/rtc_event_log/rtc_event_log_unittest_helper.h b/logging/rtc_event_log/rtc_event_log_unittest_helper.h
index 630f160..949a3db 100644
--- a/logging/rtc_event_log/rtc_event_log_unittest_helper.h
+++ b/logging/rtc_event_log/rtc_event_log_unittest_helper.h
@@ -12,7 +12,7 @@
 #define LOGGING_RTC_EVENT_LOG_RTC_EVENT_LOG_UNITTEST_HELPER_H_
 
 #include "call/call.h"
-#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "modules/rtp_rtcp/source/rtp_packet_received.h"
 #include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
 
diff --git a/modules/audio_coding/neteq/tools/rtc_event_log_source.cc b/modules/audio_coding/neteq/tools/rtc_event_log_source.cc
index d6224ff..b370e88 100644
--- a/modules/audio_coding/neteq/tools/rtc_event_log_source.cc
+++ b/modules/audio_coding/neteq/tools/rtc_event_log_source.cc
@@ -66,7 +66,7 @@
       }
 
       if (parsed_stream_.GetMediaType(packet->header().ssrc, direction) !=
-          webrtc::ParsedRtcEventLog::MediaType::AUDIO) {
+          ParsedRtcEventLog::MediaType::AUDIO) {
         continue;
       }
 
@@ -85,12 +85,10 @@
   while (audio_output_index_ < parsed_stream_.GetNumberOfEvents()) {
     if (parsed_stream_.GetEventType(audio_output_index_) ==
         ParsedRtcEventLog::AUDIO_PLAYOUT_EVENT) {
-      uint64_t timestamp_us = parsed_stream_.GetTimestamp(audio_output_index_);
-      // We call GetAudioPlayout only to check that the protobuf event is
-      // well-formed.
-      parsed_stream_.GetAudioPlayout(audio_output_index_, nullptr);
+      LoggedAudioPlayoutEvent playout_event =
+          parsed_stream_.GetAudioPlayout(audio_output_index_);
       audio_output_index_++;
-      return timestamp_us / 1000;
+      return playout_event.timestamp_us / 1000;
     }
     audio_output_index_++;
   }
diff --git a/modules/audio_coding/neteq/tools/rtc_event_log_source.h b/modules/audio_coding/neteq/tools/rtc_event_log_source.h
index df01e06..1b25e12 100644
--- a/modules/audio_coding/neteq/tools/rtc_event_log_source.h
+++ b/modules/audio_coding/neteq/tools/rtc_event_log_source.h
@@ -14,7 +14,7 @@
 #include <memory>
 #include <string>
 
-#include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "modules/audio_coding/neteq/tools/packet_source.h"
 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "rtc_base/constructormagic.h"
diff --git a/rtc_tools/BUILD.gn b/rtc_tools/BUILD.gn
index d1d2b55..73e2494 100644
--- a/rtc_tools/BUILD.gn
+++ b/rtc_tools/BUILD.gn
@@ -237,6 +237,7 @@
         "../rtc_base:checks",
         "../rtc_base:rtc_base_approved",
         "../rtc_base:rtc_numerics",
+        "../rtc_base:stringutils",
 
         # TODO(kwiberg): Remove this dependency.
         "../api/audio_codecs:audio_codecs_api",
diff --git a/rtc_tools/event_log_visualizer/analyzer.cc b/rtc_tools/event_log_visualizer/analyzer.cc
index 1a7309c..c780632 100644
--- a/rtc_tools/event_log_visualizer/analyzer.cc
+++ b/rtc_tools/event_log_visualizer/analyzer.cc
@@ -14,7 +14,6 @@
 #include <cmath>
 #include <limits>
 #include <map>
-#include <sstream>
 #include <string>
 #include <utility>
 
@@ -25,6 +24,7 @@
 #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,6 +40,7 @@
 #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"
@@ -49,6 +50,7 @@
 #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"
@@ -59,7 +61,6 @@
 #endif  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
 
 namespace webrtc {
-namespace plotting {
 
 namespace {
 
@@ -127,29 +128,45 @@
   return difference;
 }
 
-// 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;
+// 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;
 }
 
 constexpr float kLeftMargin = 0.01f;
@@ -165,7 +182,8 @@
     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.timestamp - old_packet.timestamp;
+    int64_t recv_time_diff =
+        new_packet.log_time_us() - old_packet.log_time_us();
     double delay_change_us =
         recv_time_diff - AbsSendTimeToMicroseconds(send_time_diff);
     return delay_change_us / 1000;
@@ -179,7 +197,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.timestamp - old_packet.timestamp;
+  int64_t recv_time_diff = new_packet.log_time_us() - old_packet.log_time_us();
 
   const double kVideoSampleRate = 90000;
   // TODO(terelius): We treat all streams as video for now, even though
@@ -193,9 +211,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.timestamp;
+                        << ", received time " << old_packet.log_time_us();
     RTC_LOG(LS_WARNING) << "New capture time " << new_packet.header.timestamp
-                        << ", received time " << new_packet.timestamp;
+                        << ", received time " << new_packet.log_time_us();
     RTC_LOG(LS_WARNING) << "Receive time difference " << recv_time_diff << " = "
                         << static_cast<double>(recv_time_diff) /
                                kNumMicrosecsPerSec
@@ -208,55 +226,55 @@
   return delay_change;
 }
 
-// For each element in data, use |get_y()| to extract a y-coordinate and
+// For each element in data_view, use |f()| to extract a y-coordinate and
 // store the result in a TimeSeries.
-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) /
+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) /
               kNumMicrosecsPerSec;
-    rtc::Optional<float> y = get_y(data[i]);
+    rtc::Optional<float> y = f(elem);
     if (y)
       result->points.emplace_back(x, *y);
   }
 }
 
-// For each pair of adjacent elements in |data|, use |get_y| to extract a
+// For each pair of adjacent elements in |data|, use |f()| 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>
+template <typename DataType, typename ResultType, typename IterableType>
 void ProcessPairs(
     rtc::FunctionView<rtc::Optional<ResultType>(const DataType&,
-                                                const DataType&)> get_y,
-    const std::vector<DataType>& data,
-    uint64_t begin_time,
+                                                const DataType&)> f,
+    const IterableType& data,
+    int64_t begin_time,
     TimeSeries* result) {
   for (size_t i = 1; i < data.size(); i++) {
-    float x = static_cast<float>(data[i].timestamp - begin_time) /
+    float x = static_cast<float>(data[i].log_time_us() - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<ResultType> y = get_y(data[i - 1], data[i]);
+    rtc::Optional<ResultType> y = f(data[i - 1], data[i]);
     if (y)
       result->points.emplace_back(x, static_cast<float>(*y));
   }
 }
 
-// For each element in data, use |extract()| to extract a y-coordinate and
+// For each element in data, use |f()| to extract a y-coordinate and
 // store the result in a TimeSeries.
-template <typename DataType, typename ResultType>
+template <typename DataType, typename ResultType, typename IterableType>
 void AccumulatePoints(
-    rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> extract,
-    const std::vector<DataType>& data,
-    uint64_t begin_time,
+    rtc::FunctionView<rtc::Optional<ResultType>(const DataType&)> f,
+    const IterableType& data,
+    int64_t begin_time,
     TimeSeries* result) {
   ResultType sum = 0;
   for (size_t i = 0; i < data.size(); i++) {
-    float x = static_cast<float>(data[i].timestamp - begin_time) /
+    float x = static_cast<float>(data[i].log_time_us() - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<ResultType> y = extract(data[i]);
+    rtc::Optional<ResultType> y = f(data[i]);
     if (y) {
       sum += *y;
       result->points.emplace_back(x, static_cast<float>(sum));
@@ -264,21 +282,21 @@
   }
 }
 
-// For each pair of adjacent elements in |data|, use |extract()| to extract a
+// For each pair of adjacent elements in |data|, use |f()| 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>
+template <typename DataType, typename ResultType, typename IterableType>
 void AccumulatePairs(
     rtc::FunctionView<rtc::Optional<ResultType>(const DataType&,
-                                                const DataType&)> extract,
-    const std::vector<DataType>& data,
-    uint64_t begin_time,
+                                                const DataType&)> f,
+    const IterableType& data,
+    int64_t begin_time,
     TimeSeries* result) {
   ResultType sum = 0;
   for (size_t i = 1; i < data.size(); i++) {
-    float x = static_cast<float>(data[i].timestamp - begin_time) /
+    float x = static_cast<float>(data[i].log_time_us() - begin_time) /
               kNumMicrosecsPerSec;
-    rtc::Optional<ResultType> y = extract(data[i - 1], data[i]);
+    rtc::Optional<ResultType> y = f(data[i - 1], data[i]);
     if (y)
       sum += *y;
     result->points.emplace_back(x, static_cast<float>(sum));
@@ -289,30 +307,31 @@
 // 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>
+template <typename DataType, typename ResultType, typename IterableType>
 void MovingAverage(
-    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) {
+    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) {
   size_t window_index_begin = 0;
   size_t window_index_end = 0;
   ResultType sum_in_window = 0;
 
-  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]);
+  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]);
       if (value)
         sum_in_window += *value;
       ++window_index_end;
     }
-    while (window_index_begin < data.size() &&
-           data[window_index_begin].timestamp < t - window_duration_us) {
-      rtc::Optional<ResultType> value = extract(data[window_index_begin]);
+    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]);
       if (value)
         sum_in_window -= *value;
       ++window_index_begin;
@@ -406,7 +425,7 @@
 }
 
 std::string GetCandidatePairLogDescriptionAsString(
-    const ParsedRtcEventLog::IceCandidatePairConfig& config) {
+    const LoggedIceCandidatePairConfig& 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
@@ -429,244 +448,57 @@
   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) {
-  uint64_t first_timestamp = std::numeric_limits<uint64_t>::max();
-  uint64_t last_timestamp = std::numeric_limits<uint64_t>::min();
-
-  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;
-      }
-    }
+  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;
   }
-
-  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_));
+
+  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_)));
+    }
   }
   RTC_LOG(LS_INFO) << "Found " << log_segments_.size()
                << " (LOG_START, LOG_END) segments in log.";
@@ -678,7 +510,7 @@
   BitrateObserver() : last_bitrate_bps_(0), bitrate_updated_(false) {}
 
   void OnNetworkChanged(uint32_t bitrate_bps,
-                        uint8_t fraction_loss,
+                        uint8_t fraction_lost,
                         int64_t rtt_ms,
                         int64_t probing_interval_ms) override {
     last_bitrate_bps_ = bitrate_bps;
@@ -700,187 +532,90 @@
   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 desired_direction,
+void EventLogAnalyzer::CreatePacketGraph(PacketDirection 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_)) {
+  for (const auto& stream : parsed_log_.rtp_packets_by_ssrc(direction)) {
+    // Filter on SSRC.
+    if (!MatchingSsrc(stream.ssrc, desired_ssrc_)) {
       continue;
     }
 
-    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);
+    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);
     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);
-  if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
-    plot->SetTitle("Incoming RTP packets");
-  } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
-    plot->SetTitle("Outgoing RTP packets");
-  }
+  plot->SetTitle(GetDirectionAsString(direction) + " RTP packets");
 }
 
-template <typename T>
+template <typename IterableType>
 void EventLogAnalyzer::CreateAccumulatedPacketsTimeSeries(
-    PacketDirection desired_direction,
     Plot* plot,
-    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));
+    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);
   }
+  plot->AppendTimeSeries(std::move(time_series));
 }
 
-void EventLogAnalyzer::CreateAccumulatedPacketsGraph(
-    PacketDirection desired_direction,
-    Plot* plot) {
-  CreateAccumulatedPacketsTimeSeries(desired_direction, plot, rtp_packets_,
-                                     "RTP");
-  CreateAccumulatedPacketsTimeSeries(desired_direction, plot, rtcp_packets_,
-                                     "RTCP");
+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);
+  }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Received Packets", kBottomMargin, kTopMargin);
-  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");
-  }
+  plot->SetTitle(std::string("Accumulated ") + GetDirectionAsString(direction) +
+                 " RTP/RTCP packets");
 }
 
 // For each SSRC, plot the time between the consecutive playouts.
 void EventLogAnalyzer::CreatePlayoutGraph(Plot* plot) {
-  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;
-      }
+  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);
     }
-  }
-
-  // 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->AppendTimeSeries(std::move(time_series));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
@@ -890,59 +625,51 @@
 }
 
 // For audio SSRCs, plot the audio level.
-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) {
+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) {
       if (packet.header.extension.hasAudioLevel) {
-        float x = ToCallTime(packet.timestamp);
+        float x = ToCallTime(packet.log_time_us());
         // 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[stream_id].points.emplace_back(TimeSeriesPoint(x, y));
+        time_series.points.emplace_back(TimeSeriesPoint(x, y));
       }
     }
-  }
-
-  for (auto& series : time_series) {
-    series.second.label = GetStreamName(series.first);
-    series.second.line_style = LineStyle::kLine;
-    plot->AppendTimeSeries(std::move(series.second));
+    plot->AppendTimeSeries(std::move(time_series));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetYAxis(-127, 0, "Audio level (dBov)", kBottomMargin,
                  kTopMargin);
-  plot->SetTitle("Audio level");
+  plot->SetTitle(GetDirectionAsString(direction) + " audio level");
 }
 
 // For each SSRC, plot the time between the consecutive playouts.
 void EventLogAnalyzer::CreateSequenceNumberGraph(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() != kIncomingPacket ||
-        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_)) {
+  for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
+    // Filter on SSRC.
+    if (!MatchingSsrc(stream.ssrc, desired_ssrc_)) {
       continue;
     }
 
-    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);
+    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);
     plot->AppendTimeSeries(std::move(time_series));
   }
 
@@ -953,47 +680,47 @@
 }
 
 void EventLogAnalyzer::CreateIncomingPacketLossGraph(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() != kIncomingPacket ||
-        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
-        packet_stream.size() == 0) {
+  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) {
       continue;
     }
 
-    TimeSeries time_series(GetStreamName(stream_id), LineStyle::kLine,
-                           PointStyle::kHighlight);
-    const uint64_t kWindowUs = 1000000;
-    const uint64_t kStep = 1000000;
+    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;
     SeqNumUnwrapper<uint16_t> unwrapper_;
     SeqNumUnwrapper<uint16_t> prior_unwrapper_;
     size_t window_index_begin = 0;
     size_t window_index_end = 0;
-    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;
+    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;
 
-    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);
+    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);
         highest_seq_number = std::max(highest_seq_number, sequence_number);
         ++window_index_end;
       }
-      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);
+      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);
         highest_prior_seq_number =
             std::max(highest_prior_seq_number, sequence_number);
         ++window_index_begin;
       }
       float x = ToCallTime(t);
-      int64_t expected_packets = highest_seq_number - highest_prior_seq_number;
+      uint64_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;
@@ -1011,28 +738,28 @@
 }
 
 void EventLogAnalyzer::CreateIncomingDelayDeltaGraph(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() != kIncomingPacket ||
-        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
-        IsAudioSsrc(stream_id) || !IsVideoSsrc(stream_id) ||
-        IsRtxSsrc(stream_id)) {
+  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)) {
       continue;
     }
 
-    TimeSeries capture_time_data(GetStreamName(stream_id) + " capture-time",
-                                 LineStyle::kBar);
+    TimeSeries capture_time_data(
+        GetStreamName(kIncomingPacket, stream.ssrc) + " capture-time",
+        LineStyle::kBar);
     ProcessPairs<LoggedRtpPacket, double>(NetworkDelayDiff_CaptureTime,
-                                          packet_stream, begin_time_,
+                                          stream.packet_view, begin_time_,
                                           &capture_time_data);
     plot->AppendTimeSeries(std::move(capture_time_data));
 
-    TimeSeries send_time_data(GetStreamName(stream_id) + " abs-send-time",
-                              LineStyle::kBar);
+    TimeSeries send_time_data(
+        GetStreamName(kIncomingPacket, stream.ssrc) + " abs-send-time",
+        LineStyle::kBar);
     ProcessPairs<LoggedRtpPacket, double>(NetworkDelayDiff_AbsSendTime,
-                                          packet_stream, begin_time_,
+                                          stream.packet_view, begin_time_,
                                           &send_time_data);
     plot->AppendTimeSeries(std::move(send_time_data));
   }
@@ -1044,28 +771,28 @@
 }
 
 void EventLogAnalyzer::CreateIncomingDelayGraph(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() != kIncomingPacket ||
-        !MatchingSsrc(stream_id.GetSsrc(), desired_ssrc_) ||
-        IsAudioSsrc(stream_id) || !IsVideoSsrc(stream_id) ||
-        IsRtxSsrc(stream_id)) {
+  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)) {
       continue;
     }
 
-    TimeSeries capture_time_data(GetStreamName(stream_id) + " capture-time",
-                                 LineStyle::kLine);
+    TimeSeries capture_time_data(
+        GetStreamName(kIncomingPacket, stream.ssrc) + " capture-time",
+        LineStyle::kLine);
     AccumulatePairs<LoggedRtpPacket, double>(NetworkDelayDiff_CaptureTime,
-                                             packet_stream, begin_time_,
+                                             stream.packet_view, begin_time_,
                                              &capture_time_data);
     plot->AppendTimeSeries(std::move(capture_time_data));
 
-    TimeSeries send_time_data(GetStreamName(stream_id) + " abs-send-time",
-                              LineStyle::kLine);
+    TimeSeries send_time_data(
+        GetStreamName(kIncomingPacket, stream.ssrc) + " abs-send-time",
+        LineStyle::kLine);
     AccumulatePairs<LoggedRtpPacket, double>(NetworkDelayDiff_AbsSendTime,
-                                             packet_stream, begin_time_,
+                                             stream.packet_view, begin_time_,
                                              &send_time_data);
     plot->AppendTimeSeries(std::move(send_time_data));
   }
@@ -1080,9 +807,9 @@
 void EventLogAnalyzer::CreateFractionLossGraph(Plot* plot) {
   TimeSeries time_series("Fraction lost", LineStyle::kLine,
                          PointStyle::kHighlight);
-  for (auto& bwe_update : bwe_loss_updates_) {
-    float x = ToCallTime(bwe_update.timestamp);
-    float y = static_cast<float>(bwe_update.fraction_loss) / 255 * 100;
+  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;
     time_series.points.emplace_back(x, y);
   }
 
@@ -1094,51 +821,82 @@
 }
 
 // Plot the total bandwidth used by all RTP streams.
-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));
-      }
-    }
+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));
   }
 
-  size_t window_index_begin = 0;
-  size_t window_index_end = 0;
+  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 (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;
+  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_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;
+    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;
     }
     float window_duration_in_seconds =
         static_cast<float>(window_duration_) / kNumMicrosecsPerSec;
@@ -1149,195 +907,161 @@
   plot->AppendTimeSeries(std::move(bitrate_series));
 
   // Overlay the send-side bandwidth estimate over the outgoing bitrate.
-  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 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);
+  }
 
-    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 : bwe_delay_updates_) {
-      float x = ToCallTime(delay_update.timestamp);
-      float y = static_cast<float>(delay_update.bitrate_bps) / 1000;
+  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;
 
-      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();
-        }
-      }
-
-      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);
+      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();
       }
     }
 
-    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;
-      }
-    }
+    delay_series.points.emplace_back(x, y);
+  }
 
-    if (previously_in_alr) {
+  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 : 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) {
       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;
+  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);
-  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));
-        }
-      }
-    }
-  }
-
-  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;
+  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;
     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);
-  if (desired_direction == webrtc::PacketDirection::kIncomingPacket) {
-    plot->SetTitle("Incoming RTP bitrate");
-  } else if (desired_direction == webrtc::PacketDirection::kOutgoingPacket) {
-    plot->SetTitle("Outgoing RTP bitrate");
-  }
+  plot->SetTitle("Outgoing RTP bitrate");
 }
 
 // For each SSRC, plot the bandwidth used by that stream.
-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_)) {
+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_)) {
       continue;
     }
 
-    TimeSeries time_series(GetStreamName(stream_id), LineStyle::kLine);
+    TimeSeries time_series(GetStreamName(direction, stream.ssrc),
+                           LineStyle::kLine);
+    auto GetPacketSizeKilobits = [](const LoggedRtpPacket& packet) {
+      return packet.total_length * 8.0 / 1000.0;
+    };
     MovingAverage<LoggedRtpPacket, double>(
-        [](const LoggedRtpPacket& packet) {
-          return packet.total_length * 8.0 / 1000.0;
-        },
-        packet_stream, begin_time_, end_time_, window_duration_, step_,
-        &time_series);
+        GetPacketSizeKilobits, stream.packet_view, 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);
-  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");
-  }
+  plot->SetTitle(GetDirectionAsString(direction) + " bitrate per stream");
 }
 
 void EventLogAnalyzer::CreateSendSideBweSimulationGraph(Plot* plot) {
-  std::multimap<uint64_t, const LoggedRtpPacket*> outgoing_rtp;
-  std::multimap<uint64_t, const LoggedRtcpPacket*> incoming_rtcp;
+  using RtpPacketType = LoggedRtpPacketOutgoing;
+  using TransportFeedbackType = LoggedRtcpPacketTransportFeedback;
 
-  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));
-    }
+  // 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 : 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));
-    }
-  }
+  const std::vector<TransportFeedbackType>& incoming_rtcp =
+      parsed_log_.transport_feedbacks(kIncomingPacket);
 
   SimulatedClock clock(0);
   BitrateObserver observer;
@@ -1367,7 +1091,7 @@
 
   auto NextRtcpTime = [&]() {
     if (rtcp_iterator != incoming_rtcp.end())
-      return static_cast<int64_t>(rtcp_iterator->first);
+      return static_cast<int64_t>(rtcp_iterator->log_time_us());
     return std::numeric_limits<int64_t>::max();
   };
 
@@ -1398,41 +1122,38 @@
     clock.AdvanceTimeMicroseconds(time_us - clock.TimeInMicroseconds());
     if (clock.TimeInMicroseconds() >= NextRtcpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtcpTime());
-      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()) {
+      cc.OnTransportFeedback(rtcp_iterator->transport_feedback);
+      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);
-        }
-        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)
+        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)
       ++rtcp_iterator;
     }
     if (clock.TimeInMicroseconds() >= NextRtpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtpTime());
-      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());
+      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());
         rtc::SentPacket sent_packet(
-            rtp.header.extension.transportSequenceNumber, rtp.timestamp / 1000);
+            rtp_packet.rtp.header.extension.transportSequenceNumber,
+            rtp_packet.rtp.log_time_us() / 1000);
         cc.OnSentPacket(sent_packet);
       }
       ++rtp_iterator;
@@ -1461,6 +1182,7 @@
 }
 
 void EventLogAnalyzer::CreateReceiveSideBweSimulationGraph(Plot* plot) {
+  using RtpPacketType = LoggedRtpPacketIncoming;
   class RembInterceptingPacketRouter : public PacketRouter {
    public:
     void OnReceiveBitrateChanged(const std::vector<uint32_t>& ssrcs,
@@ -1481,19 +1203,19 @@
     bool bitrate_updated_;
   };
 
-  std::multimap<uint64_t, const LoggedRtpPacket*> incoming_rtp;
+  std::multimap<int64_t, const RtpPacketType*> incoming_rtp;
 
-  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));
+  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));
     }
   }
 
   SimulatedClock clock(0);
   RembInterceptingPacketRouter packet_router;
-  // TODO(terelius): The PacketRrouter is the used as the RemoteBitrateObserver.
+  // TODO(terelius): The PacketRouter is used as the RemoteBitrateObserver.
   // Is this intentional?
   ReceiveSideCongestionController rscc(&clock, &packet_router);
   // TODO(holmer): Log the call config and use that here instead.
@@ -1507,12 +1229,12 @@
   RateStatistics acked_bitrate(250, 8000);
   int64_t last_update_us = 0;
   for (const auto& kv : incoming_rtp) {
-    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 -
+    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() -
                                   clock.TimeInMicroseconds());
-    rscc.OnReceivedPacket(arrival_time_ms, payload, packet.header);
+    rscc.OnReceivedPacket(arrival_time_ms, payload, packet.rtp.header);
     acked_bitrate.Update(payload, arrival_time_ms);
     rtc::Optional<uint32_t> bitrate_bps = acked_bitrate.Rate(arrival_time_ms);
     if (bitrate_bps) {
@@ -1538,23 +1260,19 @@
 }
 
 void EventLogAnalyzer::CreateNetworkDelayFeedbackGraph(Plot* plot) {
-  std::multimap<uint64_t, const LoggedRtpPacket*> outgoing_rtp;
-  std::multimap<uint64_t, const LoggedRtcpPacket*> incoming_rtcp;
+  using RtpPacketType = LoggedRtpPacketOutgoing;
+  using TransportFeedbackType = LoggedRtcpPacketTransportFeedback;
 
-  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));
-    }
+  // 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 : 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));
-    }
-  }
+  const std::vector<TransportFeedbackType>& incoming_rtcp =
+      parsed_log_.transport_feedbacks(kIncomingPacket);
 
   SimulatedClock clock(0);
   TransportFeedbackAdapter feedback_adapter(&clock);
@@ -1576,7 +1294,7 @@
 
   auto NextRtcpTime = [&]() {
     if (rtcp_iterator != incoming_rtcp.end())
-      return static_cast<int64_t>(rtcp_iterator->first);
+      return static_cast<int64_t>(rtcp_iterator->log_time_us());
     return std::numeric_limits<int64_t>::max();
   };
 
@@ -1586,37 +1304,34 @@
     clock.AdvanceTimeMicroseconds(time_us - clock.TimeInMicroseconds());
     if (clock.TimeInMicroseconds() >= NextRtcpTime()) {
       RTC_DCHECK_EQ(clock.TimeInMicroseconds(), NextRtcpTime());
-      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);
+      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;
         }
+        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 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());
+      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());
         feedback_adapter.OnSentPacket(
-            rtp.header.extension.transportSequenceNumber, rtp.timestamp / 1000);
+            rtp_packet.rtp.header.extension.transportSequenceNumber,
+            rtp_packet.rtp.log_time_us() / 1000);
       }
       ++rtp_iterator;
     }
@@ -1637,40 +1352,10 @@
   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& kv : rtp_packets_) {
-    const std::vector<LoggedRtpPacket>& packets = kv.second;
-    StreamId stream_id = kv.first;
-    if (stream_id.GetDirection() == kIncomingPacket)
-      continue;
+  for (const auto& stream : parsed_log_.outgoing_rtp_packets_by_ssrc()) {
+    const std::vector<LoggedRtpPacketOutgoing>& packets =
+        stream.outgoing_packets;
 
     if (packets.size() < 2) {
       RTC_LOG(LS_WARNING)
@@ -1678,11 +1363,15 @@
              "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);
+        EstimateRtpClockFrequency(packets, end_time_us);
     if (!estimated_frequency)
       continue;
-    if (IsVideoSsrc(stream_id) && *estimated_frequency != 90000) {
+    if (IsVideoSsrc(kOutgoingPacket, stream.ssrc) &&
+        *estimated_frequency != 90000) {
       RTC_LOG(LS_WARNING)
           << "Video stream should use a 90 kHz clock but appears to use "
           << *estimated_frequency / 1000 << ". Discarding.";
@@ -1690,21 +1379,22 @@
     }
 
     TimeSeries pacer_delay_series(
-        GetStreamName(stream_id) + "(" +
+        GetStreamName(kOutgoingPacket, stream.ssrc) + "(" +
             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().header.timestamp);
-    uint64_t first_send_timestamp = packets.front().timestamp;
-    for (LoggedRtpPacket packet : packets) {
+        timestamp_unwrapper.Unwrap(packets.front().rtp.header.timestamp);
+    uint64_t first_send_timestamp = packets.front().rtp.log_time_us();
+    for (const auto& packet : packets) {
       double capture_time_ms = (static_cast<double>(timestamp_unwrapper.Unwrap(
-                                    packet.header.timestamp)) -
+                                    packet.rtp.header.timestamp)) -
                                 first_capture_timestamp) /
                                *estimated_frequency * 1000;
       double send_time_ms =
-          static_cast<double>(packet.timestamp - first_send_timestamp) / 1000;
-      float x = ToCallTime(packet.timestamp);
+          static_cast<double>(packet.rtp.log_time_us() - first_send_timestamp) /
+          1000;
+      float x = ToCallTime(packet.rtp.log_time_us());
       float y = send_time_ms - capture_time_ms;
       pacer_delay_series.points.emplace_back(x, y);
     }
@@ -1717,58 +1407,52 @@
       "Delay from capture to send time. (First packet normalized to 0.)");
 }
 
-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 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));
+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));
 
-    {
-      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));
-      }
+    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);
     }
+    plot->AppendTimeSeriesIfNotEmpty(std::move(rtcp_timestamps));
   }
 
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
-  plot->SetSuggestedYAxis(0, 1, "Timestamp (90khz)", kBottomMargin, kTopMargin);
-  plot->SetTitle("Timestamps");
+  plot->SetSuggestedYAxis(0, 1, "RTP timestamp", kBottomMargin, kTopMargin);
+  plot->SetTitle(GetDirectionAsString(direction) + " timestamps");
 }
 
 void EventLogAnalyzer::CreateAudioEncoderTargetBitrateGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder target bitrate", LineStyle::kLine,
                          PointStyle::kHighlight);
-  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);
+  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);
   plot->AppendTimeSeries(std::move(time_series));
   plot->SetXAxis(0, call_duration_s_, "Time (s)", kLeftMargin, kRightMargin);
   plot->SetSuggestedYAxis(0, 1, "Bitrate (bps)", kBottomMargin, kTopMargin);
@@ -1778,14 +1462,16 @@
 void EventLogAnalyzer::CreateAudioEncoderFrameLengthGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder frame length", LineStyle::kLine,
                          PointStyle::kHighlight);
-  ProcessPoints<AudioNetworkAdaptationEvent>(
-      [](const AudioNetworkAdaptationEvent& ana_event) {
+  auto GetAnaFrameLengthMs =
+      [](const LoggedAudioNetworkAdaptationEvent& 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>();
-      },
-      audio_network_adaptation_events_, begin_time_, &time_series);
+      };
+  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
+      GetAnaFrameLengthMs, parsed_log_.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);
@@ -1795,14 +1481,16 @@
 void EventLogAnalyzer::CreateAudioEncoderPacketLossGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder uplink packet loss fraction",
                          LineStyle::kLine, PointStyle::kHighlight);
-  ProcessPoints<AudioNetworkAdaptationEvent>(
-      [](const AudioNetworkAdaptationEvent& ana_event) {
+  auto GetAnaPacketLoss =
+      [](const LoggedAudioNetworkAdaptationEvent& 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>();
-      },
-      audio_network_adaptation_events_, begin_time_, &time_series);
+      };
+  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
+      GetAnaPacketLoss, parsed_log_.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,
@@ -1813,14 +1501,16 @@
 void EventLogAnalyzer::CreateAudioEncoderEnableFecGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder FEC", LineStyle::kLine,
                          PointStyle::kHighlight);
-  ProcessPoints<AudioNetworkAdaptationEvent>(
-      [](const AudioNetworkAdaptationEvent& ana_event) {
+  auto GetAnaFecEnabled =
+      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.enable_fec)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.enable_fec));
         return rtc::Optional<float>();
-      },
-      audio_network_adaptation_events_, begin_time_, &time_series);
+      };
+  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
+      GetAnaFecEnabled, parsed_log_.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);
@@ -1830,14 +1520,16 @@
 void EventLogAnalyzer::CreateAudioEncoderEnableDtxGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder DTX", LineStyle::kLine,
                          PointStyle::kHighlight);
-  ProcessPoints<AudioNetworkAdaptationEvent>(
-      [](const AudioNetworkAdaptationEvent& ana_event) {
+  auto GetAnaDtxEnabled =
+      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.enable_dtx)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.enable_dtx));
         return rtc::Optional<float>();
-      },
-      audio_network_adaptation_events_, begin_time_, &time_series);
+      };
+  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
+      GetAnaDtxEnabled, parsed_log_.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);
@@ -1847,14 +1539,16 @@
 void EventLogAnalyzer::CreateAudioEncoderNumChannelsGraph(Plot* plot) {
   TimeSeries time_series("Audio encoder number of channels", LineStyle::kLine,
                          PointStyle::kHighlight);
-  ProcessPoints<AudioNetworkAdaptationEvent>(
-      [](const AudioNetworkAdaptationEvent& ana_event) {
+  auto GetAnaNumChannels =
+      [](const LoggedAudioNetworkAdaptationEvent& ana_event) {
         if (ana_event.config.num_channels)
           return rtc::Optional<float>(
               static_cast<float>(*ana_event.config.num_channels));
         return rtc::Optional<float>();
-      },
-      audio_network_adaptation_events_, begin_time_, &time_series);
+      };
+  ProcessPoints<LoggedAudioNetworkAdaptationEvent>(
+      GetAnaNumChannels, parsed_log_.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))",
@@ -1866,9 +1560,9 @@
  public:
   // Does not take any ownership, and all pointers must refer to valid objects
   // that outlive the one constructed.
-  NetEqStreamInput(const std::vector<LoggedRtpPacket>* packet_stream,
-                   const std::vector<uint64_t>* output_events_us,
-                   rtc::Optional<uint64_t> end_time_us)
+  NetEqStreamInput(const std::vector<LoggedRtpPacketIncoming>* packet_stream,
+                   const std::vector<int64_t>* output_events_us,
+                   rtc::Optional<int64_t> end_time_us)
       : packet_stream_(*packet_stream),
         packet_stream_it_(packet_stream_.begin()),
         output_events_us_it_(output_events_us->begin()),
@@ -1882,11 +1576,11 @@
     if (packet_stream_it_ == packet_stream_.end()) {
       return rtc::nullopt;
     }
-    if (end_time_us_ && packet_stream_it_->timestamp > *end_time_us_) {
+    if (end_time_us_ && packet_stream_it_->rtp.log_time_us() > *end_time_us_) {
       return rtc::nullopt;
     }
     // Convert from us to ms.
-    return packet_stream_it_->timestamp / 1000;
+    return packet_stream_it_->rtp.log_time_us() / 1000;
   }
 
   rtc::Optional<int64_t> NextOutputEventTime() const override {
@@ -1905,14 +1599,14 @@
       return std::unique_ptr<PacketData>();
     }
     std::unique_ptr<PacketData> packet_data(new PacketData());
-    packet_data->header = packet_stream_it_->header;
+    packet_data->header = packet_stream_it_->rtp.header;
     // Convert from us to ms.
-    packet_data->time_ms = packet_stream_it_->timestamp / 1000.0;
+    packet_data->time_ms = packet_stream_it_->rtp.log_time_us() / 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_->total_length -
-                                 packet_stream_it_->header_length);
+    packet_data->payload.SetSize(packet_stream_it_->rtp.total_length -
+                                 packet_stream_it_->rtp.header_length);
     std::fill_n(packet_data->payload.data(), packet_data->payload.size(), 0);
 
     ++packet_stream_it_;
@@ -1931,15 +1625,15 @@
     if (packet_stream_it_ == packet_stream_.end()) {
       return rtc::nullopt;
     }
-    return packet_stream_it_->header;
+    return packet_stream_it_->rtp.header;
   }
 
  private:
-  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_;
+  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_;
 };
 
 namespace {
@@ -1947,9 +1641,9 @@
 // the test and returns the NetEqDelayAnalyzer object that was used to
 // instrument the test.
 std::unique_ptr<test::NetEqDelayAnalyzer> CreateNetEqTestAndRun(
-    const std::vector<LoggedRtpPacket>* packet_stream,
-    const std::vector<uint64_t>* output_events_us,
-    rtc::Optional<uint64_t> end_time_us,
+    const std::vector<LoggedRtpPacketIncoming>* packet_stream,
+    const std::vector<int64_t>* output_events_us,
+    rtc::Optional<int64_t> end_time_us,
     const std::string& replacement_file_name,
     int file_sample_rate_hz) {
   std::unique_ptr<test::NetEqInput> input(
@@ -2004,34 +1698,35 @@
     const std::string& replacement_file_name,
     int file_sample_rate_hz,
     Plot* plot) {
-  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()) {
+  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) {
     // No incoming audio stream found.
     return;
   }
 
-  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()) {
+  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()) {
     // Could not find output events with SSRC matching the input audio stream.
     // Using the first available stream of output events.
-    output_events_it = audio_playout_events_.cbegin();
+    output_events_it = parsed_log_.audio_playout_events().cbegin();
   }
 
-  rtc::Optional<uint64_t> end_time_us =
+  rtc::Optional<int64_t> end_time_us =
       log_segments_.empty()
           ? rtc::nullopt
-          : rtc::Optional<uint64_t>(log_segments_.front().second);
+          : rtc::Optional<int64_t>(log_segments_.front().second);
 
   auto delay_cb = CreateNetEqTestAndRun(
-      &incoming_audio_kv->second, &output_events_it->second, end_time_us,
+      audio_packets, &output_events_it->second, end_time_us,
       replacement_file_name, file_sample_rate_hz);
 
   std::vector<float> send_times_s;
@@ -2047,28 +1742,27 @@
   RTC_DCHECK_EQ(send_times_s.size(), playout_delay_ms.size());
   RTC_DCHECK_EQ(send_times_s.size(), target_delay_ms.size());
 
-  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;
+  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;
   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[stream_id].points.emplace_back(
+    time_series_packet_arrival[ssrc].points.emplace_back(
         TimeSeriesPoint(send_times_s[i], arrival_delay_ms[i]));
-    time_series_relative_packet_arrival[stream_id].points.emplace_back(
+    time_series_relative_packet_arrival[ssrc].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[stream_id].points.emplace_back(
+      time_series_play_time[ssrc].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[stream_id].points.emplace_back(
+      time_series_target_time[ssrc].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]);
@@ -2106,7 +1800,7 @@
 
 void EventLogAnalyzer::CreateIceCandidatePairConfigGraph(Plot* plot) {
   std::map<uint32_t, TimeSeries> configs_by_cp_id;
-  for (const auto& config : ice_candidate_pair_configs_) {
+  for (const auto& config : parsed_log_.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 =
@@ -2118,7 +1812,7 @@
       candidate_pair_desc_by_id_[config.candidate_pair_id] =
           candidate_pair_desc;
     }
-    float x = ToCallTime(config.timestamp);
+    float x = ToCallTime(config.log_time_us());
     float y = static_cast<float>(config.type);
     configs_by_cp_id[config.candidate_pair_id].points.emplace_back(x, y);
   }
@@ -2142,7 +1836,7 @@
       candidate_pair_desc_by_id_.end()) {
     return candidate_pair_desc_by_id_[candidate_pair_id];
   }
-  for (const auto& config : ice_candidate_pair_configs_) {
+  for (const auto& config : parsed_log_.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) ==
@@ -2158,7 +1852,7 @@
 
 void EventLogAnalyzer::CreateIceConnectivityCheckGraph(Plot* plot) {
   std::map<uint32_t, TimeSeries> checks_by_cp_id;
-  for (const auto& event : ice_candidate_pair_events_) {
+  for (const auto& event : parsed_log_.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(
@@ -2166,7 +1860,7 @@
               GetCandidatePairLogDescriptionFromId(event.candidate_pair_id),
           LineStyle::kNone, PointStyle::kHighlight);
     }
-    float x = ToCallTime(event.timestamp);
+    float x = ToCallTime(event.log_time_us());
     float y = static_cast<float>(event.type);
     checks_by_cp_id[event.candidate_pair_id].points.emplace_back(x, y);
   }
@@ -2182,163 +1876,176 @@
   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& 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());
-    }
+  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());
   }
   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() {
-  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;
+  CreateStreamGapAlerts(kIncomingPacket);
+  CreateStreamGapAlerts(kOutgoingPacket);
+  CreateTransmissionGapAlerts(kIncomingPacket);
+  CreateTransmissionGapAlerts(kOutgoingPacket);
 
-    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);
-    }
-  }
+  int64_t end_time_us = log_segments_.empty()
+                            ? std::numeric_limits<int64_t>::max()
+                            : log_segments_.front().second;
 
-  // 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);
-    }
-  }
-
+  constexpr double kMaxLossFraction = 0.05;
   // Loss feedback
   int64_t total_lost_packets = 0;
   int64_t total_expected_packets = 0;
-  for (auto& bwe_update : bwe_loss_updates_) {
-    if (bwe_update.timestamp > end_time_us) {
+  for (auto& bwe_update : parsed_log_.bwe_loss_updates()) {
+    if (bwe_update.log_time_us() > end_time_us) {
       // Only process the first (LOG_START, LOG_END) segment.
       break;
     }
-    int64_t lost_packets = static_cast<double>(bwe_update.fraction_loss) / 255 *
+    int64_t lost_packets = static_cast<double>(bwe_update.fraction_lost) / 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 > 0.05) {
-    Notification(rtc::MakeUnique<OutgoingHighLoss>(avg_outgoing_loss));
+  if (avg_outgoing_loss > kMaxLossFraction) {
+    Alert_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 a8fedb8..b37de21 100644
--- a/rtc_tools/event_log_visualizer/analyzer.h
+++ b/rtc_tools/event_log_visualizer/analyzer.h
@@ -18,54 +18,12 @@
 #include <utility>
 #include <vector>
 
-#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 "logging/rtc_event_log/rtc_event_log_parser2.h"
+#include "rtc_base/strings/string_builder.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:
@@ -74,14 +32,13 @@
   // modified while the EventLogAnalyzer is being used.
   explicit EventLogAnalyzer(const ParsedRtcEventLog& log);
 
-  void CreatePacketGraph(PacketDirection desired_direction, Plot* plot);
+  void CreatePacketGraph(PacketDirection direction, Plot* plot);
 
-  void CreateAccumulatedPacketsGraph(PacketDirection desired_direction,
-                                     Plot* plot);
+  void CreateAccumulatedPacketsGraph(PacketDirection direction, Plot* plot);
 
   void CreatePlayoutGraph(Plot* plot);
 
-  void CreateAudioLevelGraph(Plot* plot);
+  void CreateAudioLevelGraph(PacketDirection direction, Plot* plot);
 
   void CreateSequenceNumberGraph(Plot* plot);
 
@@ -92,19 +49,20 @@
 
   void CreateFractionLossGraph(Plot* plot);
 
-  void CreateTotalBitrateGraph(PacketDirection desired_direction,
-                               Plot* plot,
-                               bool show_detector_state = false,
-                               bool show_alr_state = false);
+  void CreateTotalIncomingBitrateGraph(Plot* plot);
+  void CreateTotalOutgoingBitrateGraph(Plot* plot,
+                                       bool show_detector_state = false,
+                                       bool show_alr_state = false);
 
-  void CreateStreamBitrateGraph(PacketDirection desired_direction, Plot* plot);
+  void CreateStreamBitrateGraph(PacketDirection direction, Plot* plot);
 
   void CreateSendSideBweSimulationGraph(Plot* plot);
   void CreateReceiveSideBweSimulationGraph(Plot* plot);
 
   void CreateNetworkDelayFeedbackGraph(Plot* plot);
   void CreatePacerDelayGraph(Plot* plot);
-  void CreateTimestampGraph(Plot* plot);
+
+  void CreateTimestampGraph(PacketDirection direction, Plot* plot);
 
   void CreateAudioEncoderTargetBitrateGraph(Plot* plot);
   void CreateAudioEncoderFrameLengthGraph(Plot* plot);
@@ -119,55 +77,114 @@
   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:
-  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 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();
     }
-    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();
     }
-    uint32_t GetSsrc() const { return ssrc_; }
-    webrtc::PacketDirection GetDirection() const { return direction_; }
+  }
 
-   private:
-    uint32_t ssrc_;
-    webrtc::PacketDirection 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();
+    }
+  }
 
-  template <typename T>
-  void CreateAccumulatedPacketsTimeSeries(
-      PacketDirection desired_direction,
-      Plot* plot,
-      const std::map<StreamId, std::vector<T>>& packets,
-      const std::string& label_prefix);
+  template <typename IterableType>
+  void CreateAccumulatedPacketsTimeSeries(Plot* plot,
+                                          const IterableType& packets,
+                                          const std::string& label);
 
-  bool IsRtxSsrc(StreamId stream_id) const;
+  void CreateStreamGapAlerts(PacketDirection direction);
+  void CreateTransmissionGapAlerts(PacketDirection direction);
 
-  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;
+  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();
+  }
 
   float ToCallTime(int64_t timestamp) const;
 
-  void Notification(std::unique_ptr<TriageNotification> notification);
+  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);
+  }
 
   std::string GetCandidatePairLogDescriptionFromId(uint32_t candidate_pair_id);
 
@@ -177,50 +194,19 @@
   // 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<uint64_t, uint64_t>> log_segments_;
+  std::vector<std::pair<int64_t, int64_t>> log_segments_;
 
-  // 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::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_;
 
   std::map<uint32_t, std::string> candidate_pair_desc_by_id_;
 
@@ -228,18 +214,17 @@
   // 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.
-  uint64_t window_duration_;
-  uint64_t step_;
+  int64_t window_duration_;
+  int64_t step_;
 
   // First and last events of the log.
-  uint64_t begin_time_;
-  uint64_t end_time_;
+  int64_t begin_time_;
+  int64_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 2e7a79e..3dce290 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_parser.h"
+#include "logging/rtc_event_log/rtc_event_log_parser2.h"
 #include "rtc_base/flags.h"
 #include "rtc_tools/event_log_visualizer/analyzer.h"
 #include "rtc_tools/event_log_visualizer/plot_base.h"
@@ -143,10 +143,15 @@
             false,
             "Show the state ALR state on the total bitrate graph");
 
-DEFINE_bool(
-    print_triage_notifications,
-    false,
-    "Print triage notifications, i.e. a list of suspicious looking events.");
+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.");
 
 void SetAllPlotFlags(bool setting);
 
@@ -209,7 +214,13 @@
 
   std::string filename = argv[1];
 
-  webrtc::ParsedRtcEventLog parsed_log;
+  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);
 
   if (!parsed_log.ParseFile(filename)) {
     std::cerr << "Could not parse the entire log file." << std::endl;
@@ -218,31 +229,34 @@
               << std::endl;
   }
 
-  webrtc::plotting::EventLogAnalyzer analyzer(parsed_log);
-  std::unique_ptr<webrtc::plotting::PlotCollection> collection(
-      new webrtc::plotting::PythonPlotCollection());
+  webrtc::EventLogAnalyzer analyzer(parsed_log);
+  std::unique_ptr<webrtc::PlotCollection> collection(
+      new webrtc::PythonPlotCollection());
 
   if (FLAG_plot_incoming_packet_sizes) {
-    analyzer.CreatePacketGraph(webrtc::PacketDirection::kIncomingPacket,
+    analyzer.CreatePacketGraph(webrtc::kIncomingPacket,
                                collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_packet_sizes) {
-    analyzer.CreatePacketGraph(webrtc::PacketDirection::kOutgoingPacket,
+    analyzer.CreatePacketGraph(webrtc::kOutgoingPacket,
                                collection->AppendNewPlot());
   }
   if (FLAG_plot_incoming_packet_count) {
-    analyzer.CreateAccumulatedPacketsGraph(
-        webrtc::PacketDirection::kIncomingPacket, collection->AppendNewPlot());
+    analyzer.CreateAccumulatedPacketsGraph(webrtc::kIncomingPacket,
+                                           collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_packet_count) {
-    analyzer.CreateAccumulatedPacketsGraph(
-        webrtc::PacketDirection::kOutgoingPacket, collection->AppendNewPlot());
+    analyzer.CreateAccumulatedPacketsGraph(webrtc::kOutgoingPacket,
+                                           collection->AppendNewPlot());
   }
   if (FLAG_plot_audio_playout) {
     analyzer.CreatePlayoutGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_audio_level) {
-    analyzer.CreateAudioLevelGraph(collection->AppendNewPlot());
+    analyzer.CreateAudioLevelGraph(webrtc::kIncomingPacket,
+                                   collection->AppendNewPlot());
+    analyzer.CreateAudioLevelGraph(webrtc::kOutgoingPacket,
+                                   collection->AppendNewPlot());
   }
   if (FLAG_plot_incoming_sequence_number_delta) {
     analyzer.CreateSequenceNumberGraph(collection->AppendNewPlot());
@@ -257,23 +271,19 @@
     analyzer.CreateIncomingPacketLossGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_incoming_bitrate) {
-    analyzer.CreateTotalBitrateGraph(webrtc::PacketDirection::kIncomingPacket,
-                                     collection->AppendNewPlot(),
-                                     FLAG_show_detector_state,
-                                     FLAG_show_alr_state);
+    analyzer.CreateTotalIncomingBitrateGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_bitrate) {
-    analyzer.CreateTotalBitrateGraph(webrtc::PacketDirection::kOutgoingPacket,
-                                     collection->AppendNewPlot(),
-                                     FLAG_show_detector_state,
-                                     FLAG_show_alr_state);
+    analyzer.CreateTotalOutgoingBitrateGraph(collection->AppendNewPlot(),
+                                             FLAG_show_detector_state,
+                                             FLAG_show_alr_state);
   }
   if (FLAG_plot_incoming_stream_bitrate) {
-    analyzer.CreateStreamBitrateGraph(webrtc::PacketDirection::kIncomingPacket,
+    analyzer.CreateStreamBitrateGraph(webrtc::kIncomingPacket,
                                       collection->AppendNewPlot());
   }
   if (FLAG_plot_outgoing_stream_bitrate) {
-    analyzer.CreateStreamBitrateGraph(webrtc::PacketDirection::kOutgoingPacket,
+    analyzer.CreateStreamBitrateGraph(webrtc::kOutgoingPacket,
                                       collection->AppendNewPlot());
   }
   if (FLAG_plot_simulated_receiveside_bwe) {
@@ -289,7 +299,10 @@
     analyzer.CreateFractionLossGraph(collection->AppendNewPlot());
   }
   if (FLAG_plot_timestamps) {
-    analyzer.CreateTimestampGraph(collection->AppendNewPlot());
+    analyzer.CreateTimestampGraph(webrtc::kIncomingPacket,
+                                  collection->AppendNewPlot());
+    analyzer.CreateTimestampGraph(webrtc::kOutgoingPacket,
+                                  collection->AppendNewPlot());
   }
   if (FLAG_plot_pacer_delay) {
     analyzer.CreatePacerDelayGraph(collection->AppendNewPlot());
@@ -333,7 +346,7 @@
 
   collection->Draw();
 
-  if (FLAG_print_triage_notifications) {
+  if (FLAG_print_triage_alerts) {
     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 7ff4ef9..9a21393 100644
--- a/rtc_tools/event_log_visualizer/plot_base.cc
+++ b/rtc_tools/event_log_visualizer/plot_base.cc
@@ -15,7 +15,6 @@
 #include "rtc_base/checks.h"
 
 namespace webrtc {
-namespace plotting {
 
 void Plot::SetXAxis(float min_value,
                     float max_value,
@@ -85,5 +84,4 @@
   }
 }
 
-}  // 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 700ffbf..e73f004 100644
--- a/rtc_tools/event_log_visualizer/plot_base.h
+++ b/rtc_tools/event_log_visualizer/plot_base.h
@@ -16,7 +16,6 @@
 #include <vector>
 
 namespace webrtc {
-namespace plotting {
 
 enum class LineStyle {
   kNone,  // No line connecting the points. Used to create scatter plots.
@@ -173,7 +172,6 @@
   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 e5e0a8b..e986a74 100644
--- a/rtc_tools/event_log_visualizer/plot_protobuf.cc
+++ b/rtc_tools/event_log_visualizer/plot_protobuf.cc
@@ -13,7 +13,6 @@
 #include <memory>
 
 namespace webrtc {
-namespace plotting {
 
 ProtobufPlot::ProtobufPlot() {}
 
@@ -83,5 +82,4 @@
   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 5c5cce1..f59d303 100644
--- a/rtc_tools/event_log_visualizer/plot_protobuf.h
+++ b/rtc_tools/event_log_visualizer/plot_protobuf.h
@@ -17,7 +17,6 @@
 #include "rtc_tools/event_log_visualizer/plot_base.h"
 
 namespace webrtc {
-namespace plotting {
 
 class ProtobufPlot final : public Plot {
  public:
@@ -36,7 +35,6 @@
   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 8f406e2..37c4d84 100644
--- a/rtc_tools/event_log_visualizer/plot_python.cc
+++ b/rtc_tools/event_log_visualizer/plot_python.cc
@@ -17,7 +17,6 @@
 #include "rtc_base/checks.h"
 
 namespace webrtc {
-namespace plotting {
 
 PythonPlot::PythonPlot() {}
 
@@ -180,5 +179,4 @@
   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 2a5a66c..61d17a0 100644
--- a/rtc_tools/event_log_visualizer/plot_python.h
+++ b/rtc_tools/event_log_visualizer/plot_python.h
@@ -13,7 +13,6 @@
 #include "rtc_tools/event_log_visualizer/plot_base.h"
 
 namespace webrtc {
-namespace plotting {
 
 class PythonPlot final : public Plot {
  public:
@@ -30,7 +29,6 @@
   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 641e2ae..49e0620 100644
--- a/rtc_tools/event_log_visualizer/triage_notifications.h
+++ b/rtc_tools/event_log_visualizer/triage_notifications.h
@@ -14,130 +14,136 @@
 #include <string>
 
 namespace webrtc {
-namespace plotting {
 
-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 {
+class IncomingRtpReceiveTimeGap {
  public:
   IncomingRtpReceiveTimeGap(float time_seconds, int64_t duration)
-      : TriageNotification(time_seconds), duration_(duration) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), duration_(duration) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("No RTP packets received for ") +
            std::to_string(duration_) + std::string(" ms");
   }
 
  private:
+  float time_seconds_;
   int64_t duration_;
 };
 
-class IncomingRtcpReceiveTimeGap : public TriageNotification {
+class IncomingRtcpReceiveTimeGap {
  public:
   IncomingRtcpReceiveTimeGap(float time_seconds, int64_t duration)
-      : TriageNotification(time_seconds), duration_(duration) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), duration_(duration) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("No RTCP packets received for ") +
            std::to_string(duration_) + std::string(" ms");
   }
 
  private:
+  float time_seconds_;
   int64_t duration_;
 };
 
-class OutgoingRtpSendTimeGap : public TriageNotification {
+class OutgoingRtpSendTimeGap {
  public:
   OutgoingRtpSendTimeGap(float time_seconds, int64_t duration)
-      : TriageNotification(time_seconds), duration_(duration) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), duration_(duration) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("No RTP packets sent for ") + std::to_string(duration_) +
            std::string(" ms");
   }
 
  private:
+  float time_seconds_;
   int64_t duration_;
 };
 
-class OutgoingRtcpSendTimeGap : public TriageNotification {
+class OutgoingRtcpSendTimeGap {
  public:
   OutgoingRtcpSendTimeGap(float time_seconds, int64_t duration)
-      : TriageNotification(time_seconds), duration_(duration) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), duration_(duration) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("No RTCP packets sent for ") +
            std::to_string(duration_) + std::string(" ms");
   }
 
  private:
+  float time_seconds_;
   int64_t duration_;
 };
 
-class IncomingSeqNoJump : public TriageNotification {
+class IncomingSeqNumJump {
  public:
-  IncomingSeqNoJump(float time_seconds, uint32_t ssrc)
-      : TriageNotification(time_seconds), ssrc_(ssrc) {}
-  std::string ToString() {
+  IncomingSeqNumJump(float time_seconds, uint32_t ssrc)
+      : time_seconds_(time_seconds), ssrc_(ssrc) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("Sequence number jumps on incoming SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
+  float time_seconds_;
+
   uint32_t ssrc_;
 };
 
-class IncomingCaptureTimeJump : public TriageNotification {
+class IncomingCaptureTimeJump {
  public:
   IncomingCaptureTimeJump(float time_seconds, uint32_t ssrc)
-      : TriageNotification(time_seconds), ssrc_(ssrc) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), ssrc_(ssrc) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("Capture timestamp jumps on incoming SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
+  float time_seconds_;
+
   uint32_t ssrc_;
 };
 
-class OutgoingSeqNoJump : public TriageNotification {
+class OutgoingSeqNoJump {
  public:
   OutgoingSeqNoJump(float time_seconds, uint32_t ssrc)
-      : TriageNotification(time_seconds), ssrc_(ssrc) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), ssrc_(ssrc) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("Sequence number jumps on outgoing SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
+  float time_seconds_;
+
   uint32_t ssrc_;
 };
 
-class OutgoingCaptureTimeJump : public TriageNotification {
+class OutgoingCaptureTimeJump {
  public:
   OutgoingCaptureTimeJump(float time_seconds, uint32_t ssrc)
-      : TriageNotification(time_seconds), ssrc_(ssrc) {}
-  std::string ToString() {
+      : time_seconds_(time_seconds), ssrc_(ssrc) {}
+  float Time() const { return time_seconds_; }
+  std::string ToString() const {
     return std::string("Capture timestamp jumps on outgoing SSRC ") +
            std::to_string(ssrc_);
   }
 
  private:
+  float time_seconds_;
+
   uint32_t ssrc_;
 };
 
-class OutgoingHighLoss : public TriageNotification {
+class OutgoingHighLoss {
  public:
   explicit OutgoingHighLoss(double avg_loss_fraction)
       : avg_loss_fraction_(avg_loss_fraction) {}
-  std::string ToString() {
+  std::string ToString() const {
     return std::string("High average loss (") +
            std::to_string(avg_loss_fraction_ * 100) +
            std::string("%) across the call.");
@@ -147,7 +153,6 @@
   double avg_loss_fraction_;
 };
 
-}  // namespace plotting
 }  // namespace webrtc
 
 #endif  // RTC_TOOLS_EVENT_LOG_VISUALIZER_TRIAGE_NOTIFICATIONS_H_