Use delta-encoding in new WebRTC event logs

The new event log format makes use of delta encoding to compress
parts of the log.

Bug: webrtc:8111
Change-Id: I7bec839555323a7537dcec831d4ac1d5eb109932
Reviewed-on: https://webrtc-review.googlesource.com/c/109161
Commit-Queue: Elad Alon <eladalon@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25584}
diff --git a/logging/BUILD.gn b/logging/BUILD.gn
index 27977d3..cc19ec6 100644
--- a/logging/BUILD.gn
+++ b/logging/BUILD.gn
@@ -153,6 +153,8 @@
   ]
 }
 
+# TODO(eladalon): Break down into (1) encoder and (2) decoder; we don't need
+# the decoder code in the WebRTC library, only in unit tests and tools.
 rtc_static_library("rtc_event_log_impl_encoder") {
   visibility = [ "*" ]
   sources = [
@@ -160,6 +162,8 @@
     "rtc_event_log/encoder/blob_encoding.h",
     "rtc_event_log/encoder/delta_encoding.cc",
     "rtc_event_log/encoder/delta_encoding.h",
+    "rtc_event_log/encoder/rtc_event_log_encoder_common.cc",
+    "rtc_event_log/encoder/rtc_event_log_encoder_common.h",
     "rtc_event_log/encoder/varint.cc",
     "rtc_event_log/encoder/varint.h",
   ]
@@ -291,6 +295,7 @@
       ":rtc_event_bwe",
       ":rtc_event_log2_proto",
       ":rtc_event_log_api",
+      ":rtc_event_log_impl_encoder",
       ":rtc_event_log_proto",
       ":rtc_stream_config",
       "..:webrtc_common",
@@ -317,6 +322,7 @@
       sources = [
         "rtc_event_log/encoder/blob_encoding_unittest.cc",
         "rtc_event_log/encoder/delta_encoding_unittest.cc",
+        "rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc",
         "rtc_event_log/encoder/rtc_event_log_encoder_unittest.cc",
         "rtc_event_log/output/rtc_event_log_output_file_unittest.cc",
         "rtc_event_log/rtc_event_log_unittest.cc",
diff --git a/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.cc b/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.cc
new file mode 100644
index 0000000..7aea476
--- /dev/null
+++ b/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.cc
@@ -0,0 +1,40 @@
+/*
+ *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h"
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+namespace {
+// We use 0x3fff because that gives decent precision (compared to the underlying
+// measurement producing the packet loss fraction) on the one hand, while
+// allowing us to use no more than 2 bytes in varint form on the other hand.
+// (We might also fixed-size encode using at most 14 bits.)
+constexpr uint32_t kPacketLossFractionRange = (1 << 14) - 1;  // 0x3fff
+constexpr float kPacketLossFractionRangeFloat =
+    static_cast<float>(kPacketLossFractionRange);
+}  // namespace
+
+uint32_t ConvertPacketLossFractionToProtoFormat(float packet_loss_fraction) {
+  RTC_DCHECK_GE(packet_loss_fraction, 0);
+  RTC_DCHECK_LE(packet_loss_fraction, 1);
+  return static_cast<uint32_t>(packet_loss_fraction * kPacketLossFractionRange);
+}
+
+bool ParsePacketLossFractionFromProtoFormat(uint32_t proto_packet_loss_fraction,
+                                            float* output) {
+  if (proto_packet_loss_fraction >= kPacketLossFractionRange) {
+    return false;
+  }
+  *output = proto_packet_loss_fraction / kPacketLossFractionRangeFloat;
+  return true;
+}
+}  // namespace webrtc
diff --git a/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h b/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h
new file mode 100644
index 0000000..429f8ed
--- /dev/null
+++ b/logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h
@@ -0,0 +1,93 @@
+/*
+ *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_COMMON_H_
+#define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_COMMON_H_
+
+#include <stdint.h>
+
+#include <limits>
+#include <type_traits>
+
+namespace webrtc {
+
+// Convert between the packet fraction loss (a floating point number in
+// the range [0.0, 1.0]), and a uint32_t with up to a fixed number of bits.
+// The latter can be more efficiently stored in a protobuf and/or delta-encoded.
+uint32_t ConvertPacketLossFractionToProtoFormat(float packet_loss_fraction);
+bool ParsePacketLossFractionFromProtoFormat(uint32_t proto_packet_loss_fraction,
+                                            float* output);
+
+}  // namespace webrtc
+
+namespace webrtc_event_logging {
+
+// Produce an unsigned representation of a signed integer. On two's complement
+// machines, this is equivalent to:
+// static_cast<uint64_t>(static_cast<std::make_unsigned<T>>(y))
+template <typename T>
+uint64_t ToUnsigned(T y) {
+  static_assert(std::is_integral<T>::value, "");
+  static_assert(std::is_signed<T>::value, "");
+
+  // Note that a signed integer whose width is N bits, has N-1 digits.
+  static_assert(std::numeric_limits<T>::digits < 64, "");
+
+  constexpr T MIN_T = std::numeric_limits<T>::min();
+  constexpr T MAX_T = std::numeric_limits<T>::max();
+
+  static_assert(MAX_T + MIN_T + 1 >= 0, "MAX_T >= abs(MIN_T) - 1");
+
+  if (y >= 0) {
+    return static_cast<uint64_t>(y);
+  } else {
+    // y is in the range [MIN_T, -1], so (y - MIN_T) is in the
+    // range [0, abs(MIN_T) - 1]. This is representable in a T
+    // because MAX_T >= abs(MIN_T) - 1, as per the static_assert above.
+    return static_cast<uint64_t>(MAX_T) + 1 + static_cast<uint64_t>(y - MIN_T);
+  }
+}
+
+// Assuming x = ToUnsigned(y), return |y|.
+// Note: static_cast<T>(x) would work on most platforms and compilers, but
+// involves undefined behavior. This function is well-defined, and can be
+// optimized to a noop for 64 bit types, or a few arithmetic
+// instructions and a single conditional jump for narrower types.
+template <typename T>
+bool ToSigned(uint64_t x, T* y) {
+  static_assert(std::is_integral<T>::value, "");
+  static_assert(std::is_signed<T>::value, "");
+
+  // Note that a signed integer whose width is N bits, has N-1 digits.
+  static_assert(std::numeric_limits<T>::digits < 64, "");
+
+  constexpr T MIN_T = std::numeric_limits<T>::min();
+  constexpr T MAX_T = std::numeric_limits<T>::max();
+
+  using UNSIGNED_T = typename std::make_unsigned<T>::type;
+  constexpr auto MAX_UNSIGNED_T = std::numeric_limits<UNSIGNED_T>::max();
+  if (x > static_cast<uint64_t>(MAX_UNSIGNED_T)) {
+    return false;  // |x| cannot be represented using a T.
+  }
+
+  if (x <= static_cast<uint64_t>(MAX_T)) {
+    // The original value was positive, so it is safe to just static_cast.
+    *y = static_cast<T>(x);
+  } else {  // x > static_cast<uint64_t>(MAX_T)
+    const uint64_t neg_x = x - static_cast<uint64_t>(MAX_T) - 1;
+    *y = static_cast<T>(neg_x) + MIN_T;
+  }
+
+  return true;
+}
+
+}  // namespace webrtc_event_logging
+
+#endif  // LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_COMMON_H_
diff --git a/logging/rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc b/logging/rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc
new file mode 100644
index 0000000..a7d9440
--- /dev/null
+++ b/logging/rtc_event_log/encoder/rtc_event_log_encoder_common_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h"
+
+#include <limits>
+#include <type_traits>
+#include <vector>
+
+#include "test/gtest.h"
+
+namespace webrtc_event_logging {
+namespace {
+
+template <typename T>
+class SignednessConversionTest : public testing::Test {
+ public:
+  static_assert(std::is_integral<T>::value, "");
+  static_assert(std::is_signed<T>::value, "");
+};
+
+TYPED_TEST_CASE_P(SignednessConversionTest);
+
+TYPED_TEST_P(SignednessConversionTest, CorrectlyConvertsLegalValues) {
+  using T = TypeParam;
+  std::vector<T> legal_values = {std::numeric_limits<T>::min(),
+                                 std::numeric_limits<T>::min() + 1,
+                                 -1,
+                                 0,
+                                 1,
+                                 std::numeric_limits<T>::max() - 1,
+                                 std::numeric_limits<T>::max()};
+  for (T val : legal_values) {
+    const auto unsigned_val = ToUnsigned(val);
+    T signed_val;
+    ASSERT_TRUE(ToSigned<T>(unsigned_val, &signed_val))
+        << "Failed on " << static_cast<uint64_t>(unsigned_val) << ".";
+    EXPECT_EQ(val, signed_val)
+        << "Failed on " << static_cast<uint64_t>(unsigned_val) << ".";
+  }
+}
+
+TYPED_TEST_P(SignednessConversionTest, FailsOnConvertingIllegalValues) {
+  using T = TypeParam;
+
+  // Note that a signed integer whose width is N bits, has N-1 digits.
+  constexpr bool width_is_64 = std::numeric_limits<T>::digits == 63;
+
+  if (width_is_64) {
+    return;  // Test irrelevant; illegal values do not exist.
+  }
+
+  const uint64_t max_legal_value = ToUnsigned(static_cast<T>(-1));
+
+  const std::vector<uint64_t> illegal_values = {
+      max_legal_value + 1u, max_legal_value + 2u,
+      std::numeric_limits<uint64_t>::max() - 1u,
+      std::numeric_limits<uint64_t>::max()};
+
+  for (uint64_t unsigned_val : illegal_values) {
+    T signed_val;
+    EXPECT_FALSE(ToSigned<T>(unsigned_val, &signed_val))
+        << "Failed on " << static_cast<uint64_t>(unsigned_val) << ".";
+  }
+}
+
+REGISTER_TYPED_TEST_CASE_P(SignednessConversionTest,
+                           CorrectlyConvertsLegalValues,
+                           FailsOnConvertingIllegalValues);
+
+using Types = ::testing::Types<int8_t, int16_t, int32_t, int64_t>;
+
+INSTANTIATE_TYPED_TEST_CASE_P(_, SignednessConversionTest, Types);
+
+}  // namespace
+}  // namespace webrtc_event_logging
diff --git a/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc b/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc
index 8b2199b..46d99b9 100644
--- a/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc
+++ b/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.cc
@@ -10,9 +10,11 @@
 
 #include "logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h"
 
-#include <vector>
-
+#include "absl/types/optional.h"
 #include "api/array_view.h"
+#include "logging/rtc_event_log/encoder/blob_encoding.h"
+#include "logging/rtc_event_log/encoder/delta_encoding.h"
+#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h"
 #include "logging/rtc_event_log/events/rtc_event_alr_state.h"
 #include "logging/rtc_event_log/events/rtc_event_audio_network_adaptation.h"
 #include "logging/rtc_event_log/events/rtc_event_audio_playout.h"
@@ -61,6 +63,8 @@
 #endif
 RTC_POP_IGNORING_WUNDEF()
 
+using webrtc_event_logging::ToUnsigned;
+
 namespace webrtc {
 
 namespace {
@@ -235,6 +239,13 @@
   return rtclog2::IceCandidatePairEvent::UNKNOWN_CHECK_TYPE;
 }
 
+uint8_t ConvertAudioLevelToProtoFormat(bool voice_activity,
+                                       uint8_t audio_level) {
+  RTC_DCHECK_EQ(audio_level & static_cast<uint8_t>(0x80), 0);
+  constexpr uint8_t kVoiceActivityBit = 0x80;
+  return audio_level | (voice_activity ? kVoiceActivityBit : 0);
+}
+
 // Copies all RTCP blocks except APP, SDES and unknown from |packet| to
 // |buffer|. |buffer| must have space for |IP_PACKET_SIZE| bytes. |packet| must
 // be at most |IP_PACKET_SIZE| bytes long.
@@ -282,6 +293,311 @@
   }
   return buffer_length;
 }
+
+template <typename EventType, typename ProtoType>
+void EncodeRtcpPacket(rtc::ArrayView<const EventType*> batch,
+                      ProtoType* proto_batch) {
+  if (batch.size() == 0) {
+    return;
+  }
+
+  // Base event
+  const EventType* const base_event = batch[0];
+  proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
+  {
+    uint8_t buffer[IP_PACKET_SIZE];
+    size_t buffer_length =
+        RemoveNonWhitelistedRtcpBlocks(base_event->packet_, buffer);
+    proto_batch->set_raw_packet(buffer, buffer_length);
+  }
+
+  if (batch.size() == 1) {
+    return;
+  }
+
+  // Delta encoding
+  proto_batch->set_number_of_deltas(batch.size() - 1);
+  std::vector<absl::optional<uint64_t>> values(batch.size() - 1);
+  std::string encoded_deltas;
+
+  // timestamp_ms
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->timestamp_us_ / 1000;
+  }
+  encoded_deltas = EncodeDeltas(base_event->timestamp_us_ / 1000, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_timestamp_deltas_ms(encoded_deltas);
+  }
+
+  // raw_packet
+  std::vector<std::string> scrubed_packets(batch.size() - 1);
+  for (size_t i = 0; i < scrubed_packets.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    scrubed_packets[i].resize(event->packet_.size());
+    static_assert(sizeof(std::string::value_type) == sizeof(uint8_t), "");
+    const size_t buffer_length = RemoveNonWhitelistedRtcpBlocks(
+        event->packet_, reinterpret_cast<uint8_t*>(&scrubed_packets[i][0]));
+    scrubed_packets[i].resize(buffer_length);
+  }
+  // TODO(eladalon): s/deltas/blobs in separate CL.
+  proto_batch->set_raw_packet_deltas(EncodeBlobs(scrubed_packets));
+}
+
+template <typename EventType, typename ProtoType>
+void EncodeRtpPacket(const std::vector<const EventType*>& batch,
+                     ProtoType* proto_batch) {
+  if (batch.size() == 0) {
+    return;
+  }
+
+  // Base event
+  const EventType* const base_event = batch[0];
+  proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
+  proto_batch->set_marker(base_event->header_.Marker());
+  // TODO(terelius): Is payload type needed?
+  proto_batch->set_payload_type(base_event->header_.PayloadType());
+  proto_batch->set_sequence_number(base_event->header_.SequenceNumber());
+  proto_batch->set_rtp_timestamp(base_event->header_.Timestamp());
+  proto_batch->set_ssrc(base_event->header_.Ssrc());
+  proto_batch->set_payload_size(base_event->payload_length_);
+  proto_batch->set_header_size(base_event->header_length_);
+  proto_batch->set_padding_size(base_event->padding_length_);
+
+  // Add header extensions (base event).
+  absl::optional<uint64_t> base_transport_sequence_number;
+  {
+    uint16_t seqnum;
+    if (base_event->header_.template GetExtension<TransportSequenceNumber>(
+            &seqnum)) {
+      proto_batch->set_transport_sequence_number(seqnum);
+      base_transport_sequence_number = seqnum;
+    }
+  }
+
+  absl::optional<uint64_t> unsigned_base_transmission_time_offset;
+  {
+    int32_t offset;
+    if (base_event->header_.template GetExtension<TransmissionOffset>(
+            &offset)) {
+      proto_batch->set_transmission_time_offset(offset);
+      unsigned_base_transmission_time_offset = ToUnsigned(offset);
+    }
+  }
+
+  absl::optional<uint64_t> base_absolute_send_time;
+  {
+    uint32_t sendtime;
+    if (base_event->header_.template GetExtension<AbsoluteSendTime>(
+            &sendtime)) {
+      proto_batch->set_absolute_send_time(sendtime);
+      base_absolute_send_time = sendtime;
+    }
+  }
+
+  absl::optional<uint64_t> base_video_rotation;
+  {
+    VideoRotation video_rotation;
+    if (base_event->header_.template GetExtension<VideoOrientation>(
+            &video_rotation)) {
+      proto_batch->set_video_rotation(
+          ConvertVideoRotationToCVOByte(video_rotation));
+      base_video_rotation = ConvertVideoRotationToCVOByte(video_rotation);
+    }
+  }
+
+  // TODO(eladalon): Separate audio level from voice activity.
+  absl::optional<uint64_t> base_audio_level;
+  {
+    bool voice_activity;
+    uint8_t audio_level;
+    if (base_event->header_.template GetExtension<AudioLevel>(&voice_activity,
+                                                              &audio_level)) {
+      proto_batch->set_audio_level(
+          ConvertAudioLevelToProtoFormat(voice_activity, audio_level));
+      base_audio_level =
+          ConvertAudioLevelToProtoFormat(voice_activity, audio_level);
+    }
+  }
+
+  if (batch.size() == 1) {
+    return;
+  }
+
+  // Delta encoding
+  proto_batch->set_number_of_deltas(batch.size() - 1);
+  std::vector<absl::optional<uint64_t>> values(batch.size() - 1);
+  std::string encoded_deltas;
+
+  // timestamp_ms (event)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->timestamp_us_ / 1000;
+  }
+  encoded_deltas = EncodeDeltas(base_event->timestamp_us_ / 1000, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_timestamp_deltas_ms(encoded_deltas);
+  }
+
+  // marker (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->header_.Marker();
+  }
+  encoded_deltas = EncodeDeltas(base_event->header_.Marker(), values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_marker_deltas(encoded_deltas);
+  }
+
+  // payload_type (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->header_.PayloadType();
+  }
+  encoded_deltas = EncodeDeltas(base_event->header_.PayloadType(), values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_payload_type_deltas(encoded_deltas);
+  }
+
+  // sequence_number (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->header_.SequenceNumber();
+  }
+  encoded_deltas = EncodeDeltas(base_event->header_.SequenceNumber(), values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_sequence_number_deltas(encoded_deltas);
+  }
+
+  // rtp_timestamp (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->header_.Timestamp();
+  }
+  encoded_deltas = EncodeDeltas(base_event->header_.Timestamp(), values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_rtp_timestamp_deltas(encoded_deltas);
+  }
+
+  // ssrc (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->header_.Ssrc();
+  }
+  encoded_deltas = EncodeDeltas(base_event->header_.Ssrc(), values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_ssrc_deltas(encoded_deltas);
+  }
+
+  // payload_size (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->payload_length_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->payload_length_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_payload_size_deltas(encoded_deltas);
+  }
+
+  // header_size (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->header_length_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->header_length_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_header_size_deltas(encoded_deltas);
+  }
+
+  // padding_size (RTP base)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    values[i] = event->padding_length_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->padding_length_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_padding_size_deltas(encoded_deltas);
+  }
+
+  // transport_sequence_number (RTP extension)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    uint16_t seqnum;
+    if (event->header_.template GetExtension<TransportSequenceNumber>(
+            &seqnum)) {
+      values[i] = seqnum;
+    } else {
+      values[i].reset();
+    }
+  }
+  encoded_deltas = EncodeDeltas(base_transport_sequence_number, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_transport_sequence_number_deltas(encoded_deltas);
+  }
+
+  // transmission_time_offset (RTP extension)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    int32_t offset;
+    if (event->header_.template GetExtension<TransmissionOffset>(&offset)) {
+      values[i] = ToUnsigned(offset);
+    } else {
+      values[i].reset();
+    }
+  }
+  encoded_deltas = EncodeDeltas(unsigned_base_transmission_time_offset, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_transmission_time_offset_deltas(encoded_deltas);
+  }
+
+  // absolute_send_time (RTP extension)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    uint32_t sendtime;
+    if (event->header_.template GetExtension<AbsoluteSendTime>(&sendtime)) {
+      values[i] = sendtime;
+    } else {
+      values[i].reset();
+    }
+  }
+  encoded_deltas = EncodeDeltas(base_absolute_send_time, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_absolute_send_time_deltas(encoded_deltas);
+  }
+
+  // video_rotation (RTP extension)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    VideoRotation video_rotation;
+    if (event->header_.template GetExtension<VideoOrientation>(
+            &video_rotation)) {
+      values[i] = ConvertVideoRotationToCVOByte(video_rotation);
+    } else {
+      values[i].reset();
+    }
+  }
+  encoded_deltas = EncodeDeltas(base_video_rotation, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_video_rotation_deltas(encoded_deltas);
+  }
+
+  // audio_level (RTP extension)
+  for (size_t i = 0; i < values.size(); ++i) {
+    const EventType* event = batch[i + 1];
+    bool voice_activity;
+    uint8_t audio_level;
+    if (event->header_.template GetExtension<AudioLevel>(&voice_activity,
+                                                         &audio_level)) {
+      values[i] = ConvertAudioLevelToProtoFormat(voice_activity, audio_level);
+    } else {
+      values[i].reset();
+    }
+  }
+  encoded_deltas = EncodeDeltas(base_audio_level, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_audio_level_deltas(encoded_deltas);
+  }
+}
 }  // namespace
 
 std::string RtcEventLogEncoderNewFormat::EncodeLogStart(int64_t timestamp_us) {
@@ -320,8 +636,10 @@
     std::vector<const RtcEventProbeResultSuccess*> probe_result_success_events;
     std::vector<const RtcEventRtcpPacketIncoming*> incoming_rtcp_packets;
     std::vector<const RtcEventRtcpPacketOutgoing*> outgoing_rtcp_packets;
-    std::vector<const RtcEventRtpPacketIncoming*> incoming_rtp_packets;
-    std::vector<const RtcEventRtpPacketOutgoing*> outgoing_rtp_packets;
+    std::map<uint32_t /* SSRC */, std::vector<const RtcEventRtpPacketIncoming*>>
+        incoming_rtp_packets;
+    std::map<uint32_t /* SSRC */, std::vector<const RtcEventRtpPacketOutgoing*>>
+        outgoing_rtp_packets;
     std::vector<const RtcEventVideoReceiveStreamConfig*>
         video_recv_stream_configs;
     std::vector<const RtcEventVideoSendStreamConfig*> video_send_stream_configs;
@@ -408,13 +726,15 @@
         case RtcEvent::Type::RtpPacketIncoming: {
           auto* rtc_event =
               static_cast<const RtcEventRtpPacketIncoming* const>(it->get());
-          incoming_rtp_packets.push_back(rtc_event);
+          auto& v = incoming_rtp_packets[rtc_event->header_.Ssrc()];
+          v.emplace_back(rtc_event);
           break;
         }
         case RtcEvent::Type::RtpPacketOutgoing: {
           auto* rtc_event =
               static_cast<const RtcEventRtpPacketOutgoing* const>(it->get());
-          outgoing_rtp_packets.push_back(rtc_event);
+          auto& v = outgoing_rtp_packets[rtc_event->header_.Ssrc()];
+          v.emplace_back(rtc_event);
           break;
         }
         case RtcEvent::Type::VideoReceiveStreamConfig: {
@@ -487,28 +807,130 @@
     rtclog2::EventStream* event_stream) {
   if (batch.size() == 0)
     return;
-  for (const RtcEventAudioNetworkAdaptation* base_event : batch) {
-    rtclog2::AudioNetworkAdaptations* proto_batch =
-        event_stream->add_audio_network_adaptations();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-    if (base_event->config_->bitrate_bps.has_value())
-      proto_batch->set_bitrate_bps(base_event->config_->bitrate_bps.value());
-    if (base_event->config_->frame_length_ms.has_value()) {
-      proto_batch->set_frame_length_ms(
-          base_event->config_->frame_length_ms.value());
-    }
-    if (base_event->config_->uplink_packet_loss_fraction.has_value()) {
-      proto_batch->set_uplink_packet_loss_fraction(
-          base_event->config_->uplink_packet_loss_fraction.value());
-    }
-    if (base_event->config_->enable_fec.has_value())
-      proto_batch->set_enable_fec(base_event->config_->enable_fec.value());
-    if (base_event->config_->enable_dtx.has_value())
-      proto_batch->set_enable_dtx(base_event->config_->enable_dtx.value());
-    if (base_event->config_->num_channels.has_value())
-      proto_batch->set_num_channels(base_event->config_->num_channels.value());
+
+  // Base event
+  const RtcEventAudioNetworkAdaptation* const base_event = batch[0];
+  rtclog2::AudioNetworkAdaptations* proto_batch =
+      event_stream->add_audio_network_adaptations();
+  proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
+  if (base_event->config_->bitrate_bps.has_value())
+    proto_batch->set_bitrate_bps(base_event->config_->bitrate_bps.value());
+  if (base_event->config_->frame_length_ms.has_value()) {
+    proto_batch->set_frame_length_ms(
+        base_event->config_->frame_length_ms.value());
   }
-  // TODO(terelius): Delta-compress rest of batch.
+  absl::optional<uint64_t> base_uplink_packet_loss_fraction;
+  if (base_event->config_->uplink_packet_loss_fraction.has_value()) {
+    base_uplink_packet_loss_fraction = ConvertPacketLossFractionToProtoFormat(
+        base_event->config_->uplink_packet_loss_fraction.value());
+    proto_batch->set_uplink_packet_loss_fraction(
+        base_uplink_packet_loss_fraction.value());
+  }
+  if (base_event->config_->enable_fec.has_value())
+    proto_batch->set_enable_fec(base_event->config_->enable_fec.value());
+  if (base_event->config_->enable_dtx.has_value())
+    proto_batch->set_enable_dtx(base_event->config_->enable_dtx.value());
+  if (base_event->config_->num_channels.has_value())
+    proto_batch->set_num_channels(base_event->config_->num_channels.value());
+
+  if (batch.size() == 1)
+    return;
+
+  // Delta encoding
+  proto_batch->set_number_of_deltas(batch.size() - 1);
+  std::vector<absl::optional<uint64_t>> values(batch.size() - 1);
+  std::string encoded_deltas;
+
+  // timestamp_ms
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    values[i] = event->timestamp_us_ / 1000;
+  }
+  encoded_deltas = EncodeDeltas(base_event->timestamp_us_ / 1000, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_timestamp_deltas_ms(encoded_deltas);
+  }
+
+  // bitrate_bps
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    if (event->config_->bitrate_bps.has_value()) {
+      values[i] = ToUnsigned(event->config_->bitrate_bps.value());
+    } else {
+      values[i].reset();
+    }
+  }
+  const absl::optional<uint64_t> unsigned_base_bitrate_bps =
+      base_event->config_->bitrate_bps.has_value()
+          ? ToUnsigned(base_event->config_->bitrate_bps.value())
+          : absl::optional<uint64_t>();
+  encoded_deltas = EncodeDeltas(unsigned_base_bitrate_bps, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_bitrate_deltas_bps(encoded_deltas);
+  }
+
+  // frame_length_ms
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    if (event->config_->frame_length_ms.has_value()) {
+      values[i] = ToUnsigned(event->config_->frame_length_ms.value());
+    } else {
+      values[i].reset();
+    }
+  }
+  const absl::optional<uint64_t> unsigned_base_frame_length_ms =
+      base_event->config_->frame_length_ms.has_value()
+          ? ToUnsigned(base_event->config_->frame_length_ms.value())
+          : absl::optional<uint64_t>();
+  encoded_deltas = EncodeDeltas(unsigned_base_frame_length_ms, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_frame_length_deltas_ms(encoded_deltas);
+  }
+
+  // uplink_packet_loss_fraction
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    if (event->config_->uplink_packet_loss_fraction.has_value()) {
+      values[i] = ConvertPacketLossFractionToProtoFormat(
+          event->config_->uplink_packet_loss_fraction.value());
+    } else {
+      values[i].reset();
+    }
+  }
+  encoded_deltas = EncodeDeltas(base_uplink_packet_loss_fraction, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_uplink_packet_loss_fraction_deltas(encoded_deltas);
+  }
+
+  // enable_fec
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    values[i] = event->config_->enable_fec;
+  }
+  encoded_deltas = EncodeDeltas(base_event->config_->enable_fec, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_enable_fec_deltas(encoded_deltas);
+  }
+
+  // enable_dtx
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    values[i] = event->config_->enable_dtx;
+  }
+  encoded_deltas = EncodeDeltas(base_event->config_->enable_dtx, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_enable_dtx_deltas(encoded_deltas);
+  }
+
+  // num_channels
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioNetworkAdaptation* event = batch[i + 1];
+    values[i] = event->config_->num_channels;
+  }
+  encoded_deltas = EncodeDeltas(base_event->config_->num_channels, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_num_channels_deltas(encoded_deltas);
+  }
 }
 
 void RtcEventLogEncoderNewFormat::EncodeAudioPlayout(
@@ -516,13 +938,41 @@
     rtclog2::EventStream* event_stream) {
   if (batch.size() == 0)
     return;
-  for (const RtcEventAudioPlayout* base_event : batch) {
-    rtclog2::AudioPlayoutEvents* proto_batch =
-        event_stream->add_audio_playout_events();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-    proto_batch->set_local_ssrc(base_event->ssrc_);
+
+  // Base event
+  const RtcEventAudioPlayout* const base_event = batch[0];
+  rtclog2::AudioPlayoutEvents* proto_batch =
+      event_stream->add_audio_playout_events();
+  proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
+  proto_batch->set_local_ssrc(base_event->ssrc_);
+
+  if (batch.size() == 1)
+    return;
+
+  // Delta encoding
+  proto_batch->set_number_of_deltas(batch.size() - 1);
+  std::vector<absl::optional<uint64_t>> values(batch.size() - 1);
+  std::string encoded_deltas;
+
+  // timestamp_ms
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioPlayout* event = batch[i + 1];
+    values[i] = event->timestamp_us_ / 1000;
   }
-  // TODO(terelius): Delta-compress rest of batch.
+  encoded_deltas = EncodeDeltas(base_event->timestamp_us_ / 1000, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_timestamp_deltas_ms(encoded_deltas);
+  }
+
+  // local_ssrc
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventAudioPlayout* event = batch[i + 1];
+    values[i] = event->ssrc_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->ssrc_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_local_ssrc_deltas(encoded_deltas);
+  }
 }
 
 void RtcEventLogEncoderNewFormat::EncodeAudioRecvStreamConfig(
@@ -571,15 +1021,56 @@
     rtclog2::EventStream* event_stream) {
   if (batch.size() == 0)
     return;
-  for (const RtcEventBweUpdateDelayBased* base_event : batch) {
-    rtclog2::DelayBasedBweUpdates* proto_batch =
-        event_stream->add_delay_based_bwe_updates();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-    proto_batch->set_bitrate_bps(base_event->bitrate_bps_);
-    proto_batch->set_detector_state(
-        ConvertToProtoFormat(base_event->detector_state_));
+
+  // Base event
+  const RtcEventBweUpdateDelayBased* const base_event = batch[0];
+  rtclog2::DelayBasedBweUpdates* proto_batch =
+      event_stream->add_delay_based_bwe_updates();
+  proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
+  proto_batch->set_bitrate_bps(base_event->bitrate_bps_);
+  proto_batch->set_detector_state(
+      ConvertToProtoFormat(base_event->detector_state_));
+
+  if (batch.size() == 1)
+    return;
+
+  // Delta encoding
+  proto_batch->set_number_of_deltas(batch.size() - 1);
+  std::vector<absl::optional<uint64_t>> values(batch.size() - 1);
+  std::string encoded_deltas;
+
+  // timestamp_ms
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateDelayBased* event = batch[i + 1];
+    values[i] = event->timestamp_us_ / 1000;
   }
-  // TODO(terelius): Delta-compress rest of batch.
+  encoded_deltas = EncodeDeltas(base_event->timestamp_us_ / 1000, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_timestamp_deltas_ms(encoded_deltas);
+  }
+
+  // bitrate_bps
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateDelayBased* event = batch[i + 1];
+    values[i] = event->bitrate_bps_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->bitrate_bps_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_bitrate_deltas_bps(encoded_deltas);
+  }
+
+  // detector_state
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateDelayBased* event = batch[i + 1];
+    values[i] =
+        static_cast<uint64_t>(ConvertToProtoFormat(event->detector_state_));
+  }
+  encoded_deltas = EncodeDeltas(
+      static_cast<uint64_t>(ConvertToProtoFormat(base_event->detector_state_)),
+      values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_detector_state_deltas(encoded_deltas);
+  }
 }
 
 void RtcEventLogEncoderNewFormat::EncodeBweUpdateLossBased(
@@ -587,15 +1078,63 @@
     rtclog2::EventStream* event_stream) {
   if (batch.size() == 0)
     return;
-  for (const RtcEventBweUpdateLossBased* base_event : batch) {
-    rtclog2::LossBasedBweUpdates* proto_batch =
-        event_stream->add_loss_based_bwe_updates();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-    proto_batch->set_bitrate_bps(base_event->bitrate_bps_);
-    proto_batch->set_fraction_loss(base_event->fraction_loss_);
-    proto_batch->set_total_packets(base_event->total_packets_);
+
+  // Base event
+  const RtcEventBweUpdateLossBased* const base_event = batch[0];
+  rtclog2::LossBasedBweUpdates* proto_batch =
+      event_stream->add_loss_based_bwe_updates();
+  proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
+  proto_batch->set_bitrate_bps(base_event->bitrate_bps_);
+  proto_batch->set_fraction_loss(base_event->fraction_loss_);
+  proto_batch->set_total_packets(base_event->total_packets_);
+
+  if (batch.size() == 1)
+    return;
+
+  // Delta encoding
+  proto_batch->set_number_of_deltas(batch.size() - 1);
+  std::vector<absl::optional<uint64_t>> values(batch.size() - 1);
+  std::string encoded_deltas;
+
+  // timestamp_ms
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateLossBased* event = batch[i + 1];
+    values[i] = event->timestamp_us_ / 1000;
   }
-  // TODO(terelius): Delta-compress rest of batch.
+  encoded_deltas = EncodeDeltas(base_event->timestamp_us_ / 1000, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_timestamp_deltas_ms(encoded_deltas);
+  }
+
+  // bitrate_bps
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateLossBased* event = batch[i + 1];
+    values[i] = event->bitrate_bps_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->bitrate_bps_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_bitrate_deltas_bps(encoded_deltas);
+  }
+
+  // fraction_loss
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateLossBased* event = batch[i + 1];
+    values[i] = event->fraction_loss_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->fraction_loss_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_fraction_loss_deltas(encoded_deltas);
+  }
+
+  // total_packets
+  for (size_t i = 0; i < values.size(); ++i) {
+    const RtcEventBweUpdateLossBased* event = batch[i + 1];
+    values[i] = event->total_packets_;
+  }
+  encoded_deltas = EncodeDeltas(base_event->total_packets_, values);
+  if (!encoded_deltas.empty()) {
+    proto_batch->set_total_packets_deltas(encoded_deltas);
+  }
 }
 
 void RtcEventLogEncoderNewFormat::EncodeProbeClusterCreated(
@@ -640,149 +1179,39 @@
 void RtcEventLogEncoderNewFormat::EncodeRtcpPacketIncoming(
     rtc::ArrayView<const RtcEventRtcpPacketIncoming*> batch,
     rtclog2::EventStream* event_stream) {
-  if (batch.size() == 0)
+  if (batch.empty()) {
     return;
-  for (const RtcEventRtcpPacketIncoming* base_event : batch) {
-    rtclog2::IncomingRtcpPackets* proto_batch =
-        event_stream->add_incoming_rtcp_packets();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-
-    uint8_t buffer[IP_PACKET_SIZE];
-    size_t buffer_length =
-        RemoveNonWhitelistedRtcpBlocks(base_event->packet_, buffer);
-    proto_batch->set_raw_packet(buffer, buffer_length);
   }
-  // TODO(terelius): Delta-compress rest of batch.
+  EncodeRtcpPacket(batch, event_stream->add_incoming_rtcp_packets());
 }
 
 void RtcEventLogEncoderNewFormat::EncodeRtcpPacketOutgoing(
     rtc::ArrayView<const RtcEventRtcpPacketOutgoing*> batch,
     rtclog2::EventStream* event_stream) {
-  if (batch.size() == 0)
+  if (batch.empty()) {
     return;
-  for (const RtcEventRtcpPacketOutgoing* base_event : batch) {
-    rtclog2::OutgoingRtcpPackets* proto_batch =
-        event_stream->add_outgoing_rtcp_packets();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-
-    uint8_t buffer[IP_PACKET_SIZE];
-    size_t buffer_length =
-        RemoveNonWhitelistedRtcpBlocks(base_event->packet_, buffer);
-    proto_batch->set_raw_packet(buffer, buffer_length);
   }
-  // TODO(terelius): Delta-compress rest of batch.
+  EncodeRtcpPacket(batch, event_stream->add_outgoing_rtcp_packets());
 }
 
 void RtcEventLogEncoderNewFormat::EncodeRtpPacketIncoming(
-    rtc::ArrayView<const RtcEventRtpPacketIncoming*> batch,
+    const std::map<uint32_t, std::vector<const RtcEventRtpPacketIncoming*>>&
+        batch,
     rtclog2::EventStream* event_stream) {
-  if (batch.size() == 0)
-    return;
-  for (const RtcEventRtpPacketIncoming* base_event : batch) {
-    rtclog2::IncomingRtpPackets* proto_batch =
-        event_stream->add_incoming_rtp_packets();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-    proto_batch->set_marker(base_event->header_.Marker());
-    // TODO(terelius): Is payload type needed?
-    proto_batch->set_payload_type(base_event->header_.PayloadType());
-    proto_batch->set_sequence_number(base_event->header_.SequenceNumber());
-    proto_batch->set_rtp_timestamp(base_event->header_.Timestamp());
-    proto_batch->set_ssrc(base_event->header_.Ssrc());
-    proto_batch->set_payload_size(base_event->payload_length_);
-    proto_batch->set_header_size(base_event->header_length_);
-    proto_batch->set_padding_size(base_event->padding_length_);
-
-    // Add header extensions.
-    if (base_event->header_.HasExtension<TransmissionOffset>()) {
-      int32_t offset;
-      base_event->header_.GetExtension<TransmissionOffset>(&offset);
-      proto_batch->set_transmission_time_offset(offset);
-    }
-    if (base_event->header_.HasExtension<AbsoluteSendTime>()) {
-      uint32_t sendtime;
-      base_event->header_.GetExtension<AbsoluteSendTime>(&sendtime);
-      proto_batch->set_absolute_send_time(sendtime);
-    }
-    if (base_event->header_.HasExtension<TransportSequenceNumber>()) {
-      uint16_t seqnum;
-      base_event->header_.GetExtension<TransportSequenceNumber>(&seqnum);
-      proto_batch->set_transport_sequence_number(seqnum);
-    }
-    if (base_event->header_.HasExtension<AudioLevel>()) {
-      bool voice_activity;
-      uint8_t audio_level;
-      base_event->header_.GetExtension<AudioLevel>(&voice_activity,
-                                                   &audio_level);
-      RTC_DCHECK(audio_level < 128);
-      if (voice_activity) {
-        audio_level += 128;  // Most significant bit indicates voice activity.
-      }
-      proto_batch->set_audio_level(audio_level);
-    }
-    if (base_event->header_.HasExtension<VideoOrientation>()) {
-      VideoRotation video_rotation;
-      base_event->header_.GetExtension<VideoOrientation>(&video_rotation);
-      proto_batch->set_video_rotation(
-          ConvertVideoRotationToCVOByte(video_rotation));
-    }
+  for (auto it : batch) {
+    RTC_DCHECK(!it.second.empty());
+    EncodeRtpPacket(it.second, event_stream->add_incoming_rtp_packets());
   }
-  // TODO(terelius): Delta-compress rest of batch.
 }
 
 void RtcEventLogEncoderNewFormat::EncodeRtpPacketOutgoing(
-    rtc::ArrayView<const RtcEventRtpPacketOutgoing*> batch,
+    const std::map<uint32_t, std::vector<const RtcEventRtpPacketOutgoing*>>&
+        batch,
     rtclog2::EventStream* event_stream) {
-  if (batch.size() == 0)
-    return;
-  for (const RtcEventRtpPacketOutgoing* base_event : batch) {
-    rtclog2::OutgoingRtpPackets* proto_batch =
-        event_stream->add_outgoing_rtp_packets();
-    proto_batch->set_timestamp_ms(base_event->timestamp_us_ / 1000);
-    proto_batch->set_marker(base_event->header_.Marker());
-    // TODO(terelius): Is payload type needed?
-    proto_batch->set_payload_type(base_event->header_.PayloadType());
-    proto_batch->set_sequence_number(base_event->header_.SequenceNumber());
-    proto_batch->set_rtp_timestamp(base_event->header_.Timestamp());
-    proto_batch->set_ssrc(base_event->header_.Ssrc());
-    proto_batch->set_payload_size(base_event->payload_length_);
-    proto_batch->set_header_size(base_event->header_length_);
-    proto_batch->set_padding_size(base_event->padding_length_);
-
-    // Add header extensions.
-    if (base_event->header_.HasExtension<TransmissionOffset>()) {
-      int32_t offset;
-      base_event->header_.GetExtension<TransmissionOffset>(&offset);
-      proto_batch->set_transmission_time_offset(offset);
-    }
-    if (base_event->header_.HasExtension<AbsoluteSendTime>()) {
-      uint32_t sendtime;
-      base_event->header_.GetExtension<AbsoluteSendTime>(&sendtime);
-      proto_batch->set_absolute_send_time(sendtime);
-    }
-    if (base_event->header_.HasExtension<TransportSequenceNumber>()) {
-      uint16_t seqnum;
-      base_event->header_.GetExtension<TransportSequenceNumber>(&seqnum);
-      proto_batch->set_transport_sequence_number(seqnum);
-    }
-    if (base_event->header_.HasExtension<AudioLevel>()) {
-      bool voice_activity;
-      uint8_t audio_level;
-      base_event->header_.GetExtension<AudioLevel>(&voice_activity,
-                                                   &audio_level);
-      RTC_DCHECK(audio_level < 128);
-      if (voice_activity) {
-        audio_level += 128;  // Most significant bit indicates voice activity.
-      }
-      proto_batch->set_audio_level(audio_level);
-    }
-    if (base_event->header_.HasExtension<VideoOrientation>()) {
-      VideoRotation video_rotation;
-      base_event->header_.GetExtension<VideoOrientation>(&video_rotation);
-      proto_batch->set_video_rotation(
-          ConvertVideoRotationToCVOByte(video_rotation));
-    }
+  for (auto it : batch) {
+    RTC_DCHECK(!it.second.empty());
+    EncodeRtpPacket(it.second, event_stream->add_outgoing_rtp_packets());
   }
-  // TODO(terelius): Delta-compress rest of batch.
 }
 
 void RtcEventLogEncoderNewFormat::EncodeVideoRecvStreamConfig(
diff --git a/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h b/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h
index b49286d..d9a3f0a 100644
--- a/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h
+++ b/logging/rtc_event_log/encoder/rtc_event_log_encoder_new_format.h
@@ -12,8 +12,10 @@
 #define LOGGING_RTC_EVENT_LOG_ENCODER_RTC_EVENT_LOG_ENCODER_NEW_FORMAT_H_
 
 #include <deque>
+#include <map>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "api/array_view.h"
 #include "logging/rtc_event_log/encoder/rtc_event_log_encoder.h"
@@ -98,10 +100,12 @@
       rtc::ArrayView<const RtcEventRtcpPacketOutgoing*> batch,
       rtclog2::EventStream* event_stream);
   void EncodeRtpPacketIncoming(
-      rtc::ArrayView<const RtcEventRtpPacketIncoming*> batch,
+      const std::map<uint32_t, std::vector<const RtcEventRtpPacketIncoming*>>&
+          batch,
       rtclog2::EventStream* event_stream);
   void EncodeRtpPacketOutgoing(
-      rtc::ArrayView<const RtcEventRtpPacketOutgoing*> batch,
+      const std::map<uint32_t, std::vector<const RtcEventRtpPacketOutgoing*>>&
+          batch,
       rtclog2::EventStream* event_stream);
   void EncodeVideoRecvStreamConfig(
       rtc::ArrayView<const RtcEventVideoReceiveStreamConfig*> batch,
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 726161e..b29237e 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
@@ -41,13 +41,17 @@
 #include "test/gtest.h"
 
 namespace webrtc {
-
 class RtcEventLogEncoderTest
-    : public testing::TestWithParam<std::tuple<int, bool>> {
+    : public testing::TestWithParam<std::tuple<int, bool, size_t, bool>> {
  protected:
   RtcEventLogEncoderTest()
-      : seed_(std::get<0>(GetParam())), prng_(seed_), gen_(seed_ * 880001UL) {
-    if (std::get<1>(GetParam()))
+      : seed_(std::get<0>(GetParam())),
+        prng_(seed_),
+        gen_(seed_ * 880001UL),
+        new_encoding_(std::get<1>(GetParam())),
+        event_count_(std::get<2>(GetParam())),
+        force_repeated_fields_(std::get<3>(GetParam())) {
+    if (new_encoding_)
       encoder_ = absl::make_unique<RtcEventLogEncoderNewFormat>();
     else
       encoder_ = absl::make_unique<RtcEventLogEncoderLegacy>();
@@ -58,140 +62,345 @@
   // correct behavior both when all of the values are there, as well as when
   // only some.
   void TestRtcEventAudioNetworkAdaptation(
-      std::unique_ptr<AudioEncoderRuntimeConfig> runtime_config);
+      const std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>>&);
+
+  template <typename EventType>
+  std::unique_ptr<EventType> NewRtpPacket(
+      uint32_t ssrc,
+      const RtpHeaderExtensionMap& extension_map);
+
+  template <typename ParsedType>
+  const std::vector<ParsedType>& GetRtpPacketsBySsrc(
+      const ParsedRtcEventLogNew* parsed_log);
+
+  template <typename EventType, typename ParsedType>
+  void TestRtpPackets();
 
   std::deque<std::unique_ptr<RtcEvent>> history_;
-  // TODO(eladalon): Once we have more than one possible encoder, parameterize
-  // encoder selection.
   std::unique_ptr<RtcEventLogEncoder> encoder_;
   ParsedRtcEventLogNew parsed_log_;
   const uint64_t seed_;
   Random prng_;
   test::EventGenerator gen_;
+  const bool new_encoding_;
+  const size_t event_count_;
+  const bool force_repeated_fields_;
 };
 
-TEST_P(RtcEventLogEncoderTest, RtcEventAlrState) {
-  std::unique_ptr<RtcEventAlrState> event = gen_.NewAlrState();
-  history_.push_back(event->Copy());
-
-  std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
-  ASSERT_TRUE(parsed_log_.ParseString(encoded));
-  const auto& alr_state_events = parsed_log_.alr_state_events();
-
-  ASSERT_EQ(alr_state_events.size(), 1u);
-  test::VerifyLoggedAlrStateEvent(*event, alr_state_events[0]);
-}
-
 void RtcEventLogEncoderTest::TestRtcEventAudioNetworkAdaptation(
-    std::unique_ptr<AudioEncoderRuntimeConfig> runtime_config) {
-  // This function is called repeatedly. Clear state between calls.
-  history_.clear();
-  auto original_runtime_config = *runtime_config;
-  auto event = absl::make_unique<RtcEventAudioNetworkAdaptation>(
-      std::move(runtime_config));
-  history_.push_back(event->Copy());
+    const std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>>&
+        events) {
+  ASSERT_TRUE(history_.empty()) << "Function should be called once per test.";
+
+  for (auto& event : events) {
+    history_.push_back(event->Copy());
+  }
 
   std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
   ASSERT_TRUE(parsed_log_.ParseString(encoded));
   const auto& ana_configs = parsed_log_.audio_network_adaptation_events();
 
-  ASSERT_EQ(ana_configs.size(), 1u);
-  test::VerifyLoggedAudioNetworkAdaptationEvent(*event, ana_configs[0]);
+  ASSERT_EQ(ana_configs.size(), events.size());
+  for (size_t i = 0; i < events.size(); ++i) {
+    test::VerifyLoggedAudioNetworkAdaptationEvent(*events[i], ana_configs[i]);
+  }
+}
+
+template <>
+std::unique_ptr<RtcEventRtpPacketIncoming> RtcEventLogEncoderTest::NewRtpPacket(
+    uint32_t ssrc,
+    const RtpHeaderExtensionMap& extension_map) {
+  return gen_.NewRtpPacketIncoming(ssrc, extension_map, false);
+}
+
+template <>
+std::unique_ptr<RtcEventRtpPacketOutgoing> RtcEventLogEncoderTest::NewRtpPacket(
+    uint32_t ssrc,
+    const RtpHeaderExtensionMap& extension_map) {
+  return gen_.NewRtpPacketOutgoing(ssrc, extension_map, false);
+}
+
+template <>
+const std::vector<ParsedRtcEventLogNew::LoggedRtpStreamIncoming>&
+RtcEventLogEncoderTest::GetRtpPacketsBySsrc(
+    const ParsedRtcEventLogNew* parsed_log) {
+  return parsed_log->incoming_rtp_packets_by_ssrc();
+}
+
+template <>
+const std::vector<ParsedRtcEventLogNew::LoggedRtpStreamOutgoing>&
+RtcEventLogEncoderTest::GetRtpPacketsBySsrc(
+    const ParsedRtcEventLogNew* parsed_log) {
+  return parsed_log->outgoing_rtp_packets_by_ssrc();
+}
+
+template <typename EventType, typename ParsedType>
+void CompareRtpPacketSequences(
+    const std::vector<std::unique_ptr<EventType>>& original_events,
+    const ParsedType& parsed_events);
+
+template <>
+void CompareRtpPacketSequences(
+    const std::vector<std::unique_ptr<RtcEventRtpPacketIncoming>>& original,
+    const ParsedRtcEventLogNew::LoggedRtpStreamIncoming& parsed) {
+  ASSERT_EQ(parsed.incoming_packets.size(), original.size());
+  for (size_t i = 0; i < original.size(); ++i) {
+    test::VerifyLoggedRtpPacketIncoming(*original[i],
+                                        parsed.incoming_packets[i]);
+  }
+}
+
+template <>
+void CompareRtpPacketSequences(
+    const std::vector<std::unique_ptr<RtcEventRtpPacketOutgoing>>& original,
+    const ParsedRtcEventLogNew::LoggedRtpStreamOutgoing& parsed) {
+  ASSERT_EQ(parsed.outgoing_packets.size(), original.size());
+  for (size_t i = 0; i < original.size(); ++i) {
+    test::VerifyLoggedRtpPacketOutgoing(*original[i],
+                                        parsed.outgoing_packets[i]);
+  }
+}
+
+template <typename EventType, typename ParsedType>
+void RtcEventLogEncoderTest::TestRtpPackets() {
+  // SSRCs will be randomly assigned out of this small pool, significant only
+  // in that it also covers such edge cases as SSRC = 0 and SSRC = 0xffffffff.
+  // The pool is intentionally small, so as to produce collisions.
+  const std::vector<uint32_t> kSsrcPool = {0x00000000, 0x12345678, 0xabcdef01,
+                                           0xffffffff, 0x20171024, 0x19840730,
+                                           0x19831230};
+
+  // TODO(terelius): Test extensions for legacy encoding, too.
+  RtpHeaderExtensionMap extension_map;
+  if (new_encoding_) {
+    extension_map = gen_.NewRtpHeaderExtensionMap(true);
+  }
+
+  // Simulate |event_count_| RTP packets, with SSRCs assigned randomly
+  // out of the small pool above.
+  std::map<uint32_t, std::vector<std::unique_ptr<EventType>>> events_by_ssrc;
+  for (size_t i = 0; i < event_count_; ++i) {
+    const uint32_t ssrc = kSsrcPool[prng_.Rand(kSsrcPool.size() - 1)];
+    std::unique_ptr<EventType> event =
+        (events_by_ssrc[ssrc].empty() || !force_repeated_fields_)
+            ? NewRtpPacket<EventType>(ssrc, extension_map)
+            : events_by_ssrc[ssrc][0]->Copy();
+    history_.push_back(event->Copy());
+    events_by_ssrc[ssrc].emplace_back(std::move(event));
+  }
+
+  // Encode and parse.
+  std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
+  ASSERT_TRUE(parsed_log_.ParseString(encoded));
+
+  // Expect as many distinct SSRCs to be parsed, as were simulated.
+  const std::vector<ParsedType>& parsed_rtp_packets =
+      GetRtpPacketsBySsrc<ParsedType>(&parsed_log_);
+  ASSERT_EQ(parsed_rtp_packets.size(), events_by_ssrc.size());
+
+  // For each SSRC, make sure the RTP packets associated with it to have been
+  // correctly encoded and parsed.
+  for (auto it = events_by_ssrc.begin(); it != events_by_ssrc.end(); ++it) {
+    const uint32_t ssrc = it->first;
+    auto parsed = std::find_if(
+        parsed_rtp_packets.begin(), parsed_rtp_packets.end(),
+        [ssrc](const ParsedType& packet) { return packet.ssrc == ssrc; });
+    ASSERT_NE(parsed, parsed_rtp_packets.end());
+    CompareRtpPacketSequences(it->second, *parsed);
+  }
+}
+
+TEST_P(RtcEventLogEncoderTest, RtcEventAlrState) {
+  std::vector<std::unique_ptr<RtcEventAlrState>> events(event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    events[i] = (i == 0 || !force_repeated_fields_) ? gen_.NewAlrState()
+                                                    : events[0]->Copy();
+    history_.push_back(events[i]->Copy());
+  }
+
+  std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
+  ASSERT_TRUE(parsed_log_.ParseString(encoded));
+  const auto& alr_state_events = parsed_log_.alr_state_events();
+
+  ASSERT_EQ(alr_state_events.size(), event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    test::VerifyLoggedAlrStateEvent(*events[i], alr_state_events[i]);
+  }
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationBitrate) {
-  auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-  const int bitrate_bps = rtc::checked_cast<int>(
-      prng_.Rand(0, std::numeric_limits<int32_t>::max()));
-  runtime_config->bitrate_bps = bitrate_bps;
-  TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      const int bitrate_bps = rtc::checked_cast<int>(
+          prng_.Rand(0, std::numeric_limits<int32_t>::max()));
+      runtime_config->bitrate_bps = bitrate_bps;
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
+    }
+  }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationFrameLength) {
-  auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-  const int frame_length_ms = prng_.Rand(1, 1000);
-  runtime_config->frame_length_ms = frame_length_ms;
-  TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      const int frame_length_ms = prng_.Rand(1, 1000);
+      runtime_config->frame_length_ms = frame_length_ms;
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
+    }
+  }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationPacketLoss) {
-  // To simplify the test, we just check powers of two.
-  const float plr = std::pow(0.5f, prng_.Rand(1, 8));
-  auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-  runtime_config->uplink_packet_loss_fraction = plr;
-  TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      // To simplify the test, we just check powers of two.
+      const float plr = std::pow(0.5f, prng_.Rand(1, 8));
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      runtime_config->uplink_packet_loss_fraction = plr;
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
+    }
+  }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationFec) {
-  // The test might be trivially passing for one of the two boolean values, so
-  // for safety's sake, we test both.
-  for (bool fec_enabled : {false, true}) {
-    auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-    runtime_config->enable_fec = fec_enabled;
-    TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      runtime_config->enable_fec = prng_.Rand<bool>();
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
+    }
   }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationDtx) {
-  // The test might be trivially passing for one of the two boolean values, so
-  // for safety's sake, we test both.
-  for (bool dtx_enabled : {false, true}) {
-    auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-    runtime_config->enable_dtx = dtx_enabled;
-    TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      runtime_config->enable_dtx = prng_.Rand<bool>();
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
+    }
   }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationChannels) {
-  // The test might be trivially passing for one of the two possible values, so
-  // for safety's sake, we test both.
-  for (size_t channels : {1, 2}) {
-    auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-    runtime_config->num_channels = channels;
-    TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      runtime_config->num_channels = prng_.Rand(1, 2);
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
+    }
   }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioNetworkAdaptationAll) {
-  const int bitrate_bps = rtc::checked_cast<int>(
-      prng_.Rand(0, std::numeric_limits<int32_t>::max()));
-  const int frame_length_ms = prng_.Rand(1, 1000);
-  const float plr = std::pow(0.5f, prng_.Rand(1, 8));
-  for (bool fec_enabled : {false, true}) {
-    for (bool dtx_enabled : {false, true}) {
-      for (size_t channels : {1, 2}) {
-        auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
-        runtime_config->bitrate_bps = bitrate_bps;
-        runtime_config->frame_length_ms = frame_length_ms;
-        runtime_config->uplink_packet_loss_fraction = plr;
-        runtime_config->enable_fec = fec_enabled;
-        runtime_config->enable_dtx = dtx_enabled;
-        runtime_config->num_channels = channels;
-
-        TestRtcEventAudioNetworkAdaptation(std::move(runtime_config));
-      }
+  std::vector<std::unique_ptr<RtcEventAudioNetworkAdaptation>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    if (i == 0 || !force_repeated_fields_) {
+      auto runtime_config = absl::make_unique<AudioEncoderRuntimeConfig>();
+      runtime_config->bitrate_bps = rtc::checked_cast<int>(
+          prng_.Rand(0, std::numeric_limits<int32_t>::max()));
+      runtime_config->frame_length_ms = prng_.Rand(1, 1000);
+      runtime_config->uplink_packet_loss_fraction =
+          std::pow(0.5f, prng_.Rand(1, 8));
+      runtime_config->enable_fec = prng_.Rand<bool>();
+      runtime_config->enable_dtx = prng_.Rand<bool>();
+      runtime_config->num_channels = prng_.Rand(1, 2);
+      events[i] = absl::make_unique<RtcEventAudioNetworkAdaptation>(
+          std::move(runtime_config));
+    } else {
+      events[i] = events[0]->Copy();
     }
   }
+  TestRtcEventAudioNetworkAdaptation(events);
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioPlayout) {
-  uint32_t ssrc = prng_.Rand<uint32_t>();
-  std::unique_ptr<RtcEventAudioPlayout> event = gen_.NewAudioPlayout(ssrc);
-  history_.push_back(event->Copy());
+  // SSRCs will be randomly assigned out of this small pool, significant only
+  // in that it also covers such edge cases as SSRC = 0 and SSRC = 0xffffffff.
+  // The pool is intentionally small, so as to produce collisions.
+  const std::vector<uint32_t> kSsrcPool = {0x00000000, 0x12345678, 0xabcdef01,
+                                           0xffffffff, 0x20171024, 0x19840730,
+                                           0x19831230};
+
+  std::map<uint32_t, std::vector<std::unique_ptr<RtcEventAudioPlayout>>>
+      original_events_by_ssrc;
+  for (size_t i = 0; i < event_count_; ++i) {
+    const uint32_t ssrc = kSsrcPool[prng_.Rand(kSsrcPool.size() - 1)];
+    std::unique_ptr<RtcEventAudioPlayout> event =
+        (original_events_by_ssrc[ssrc].empty() || !force_repeated_fields_)
+            ? gen_.NewAudioPlayout(ssrc)
+            : original_events_by_ssrc[ssrc][0]->Copy();
+    history_.push_back(event->Copy());
+    original_events_by_ssrc[ssrc].push_back(std::move(event));
+  }
 
   std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
   ASSERT_TRUE(parsed_log_.ParseString(encoded));
 
-  const auto& playout_events = parsed_log_.audio_playout_events();
-  ASSERT_EQ(playout_events.size(), 1u);
-  const auto playout_stream = playout_events.find(ssrc);
-  ASSERT_TRUE(playout_stream != playout_events.end());
+  const auto& parsed_playout_events_by_ssrc =
+      parsed_log_.audio_playout_events();
 
-  ASSERT_EQ(playout_stream->second.size(), 1u);
-  LoggedAudioPlayoutEvent playout_event = playout_stream->second[0];
-  test::VerifyLoggedAudioPlayoutEvent(*event, playout_event);
+  // Same number of distinct SSRCs.
+  ASSERT_EQ(parsed_playout_events_by_ssrc.size(),
+            original_events_by_ssrc.size());
+
+  for (auto& original_event_it : original_events_by_ssrc) {
+    const uint32_t ssrc = original_event_it.first;
+    const auto& original_playout_events = original_event_it.second;
+
+    const auto& parsed_event_it = parsed_playout_events_by_ssrc.find(ssrc);
+    ASSERT_TRUE(parsed_event_it != parsed_playout_events_by_ssrc.end());
+    const auto& parsed_playout_events = parsed_event_it->second;
+
+    // Same number playout events for the SSRC under examination.
+    ASSERT_EQ(original_playout_events.size(), parsed_playout_events.size());
+
+    for (size_t i = 0; i < original_playout_events.size(); ++i) {
+      test::VerifyLoggedAudioPlayoutEvent(*original_playout_events[i],
+                                          parsed_playout_events[i]);
+    }
+  }
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioReceiveStreamConfig) {
   uint32_t ssrc = prng_.Rand<uint32_t>();
   RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap();
@@ -207,6 +416,7 @@
   test::VerifyLoggedAudioRecvConfig(*event, audio_recv_configs[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventAudioSendStreamConfig) {
   uint32_t ssrc = prng_.Rand<uint32_t>();
   RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap();
@@ -223,31 +433,47 @@
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventBweUpdateDelayBased) {
-  std::unique_ptr<RtcEventBweUpdateDelayBased> event =
-      gen_.NewBweUpdateDelayBased();
-  history_.push_back(event->Copy());
+  std::vector<std::unique_ptr<RtcEventBweUpdateDelayBased>> events(
+      event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    events[i] = (i == 0 || !force_repeated_fields_)
+                    ? gen_.NewBweUpdateDelayBased()
+                    : events[0]->Copy();
+    history_.push_back(events[i]->Copy());
+  }
 
   std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
   ASSERT_TRUE(parsed_log_.ParseString(encoded));
-  const auto& bwe_delay_updates = parsed_log_.bwe_delay_updates();
 
-  ASSERT_EQ(bwe_delay_updates.size(), 1u);
-  test::VerifyLoggedBweDelayBasedUpdate(*event, bwe_delay_updates[0]);
+  const auto& bwe_delay_updates = parsed_log_.bwe_delay_updates();
+  ASSERT_EQ(bwe_delay_updates.size(), event_count_);
+
+  for (size_t i = 0; i < event_count_; ++i) {
+    test::VerifyLoggedBweDelayBasedUpdate(*events[i], bwe_delay_updates[i]);
+  }
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventBweUpdateLossBased) {
-  std::unique_ptr<RtcEventBweUpdateLossBased> event =
-      gen_.NewBweUpdateLossBased();
-  history_.push_back(event->Copy());
+  std::vector<std::unique_ptr<RtcEventBweUpdateLossBased>> events(event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    events[i] = (i == 0 || !force_repeated_fields_)
+                    ? gen_.NewBweUpdateLossBased()
+                    : events[0]->Copy();
+    history_.push_back(events[i]->Copy());
+  }
 
   std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
   ASSERT_TRUE(parsed_log_.ParseString(encoded));
-  const auto& bwe_loss_updates = parsed_log_.bwe_loss_updates();
 
-  ASSERT_EQ(bwe_loss_updates.size(), 1u);
-  test::VerifyLoggedBweLossBasedUpdate(*event, bwe_loss_updates[0]);
+  const auto& bwe_loss_updates = parsed_log_.bwe_loss_updates();
+  ASSERT_EQ(bwe_loss_updates.size(), event_count_);
+
+  for (size_t i = 0; i < event_count_; ++i) {
+    test::VerifyLoggedBweLossBasedUpdate(*events[i], bwe_loss_updates[i]);
+  }
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventIceCandidatePairConfig) {
   std::unique_ptr<RtcEventIceCandidatePairConfig> event =
       gen_.NewIceCandidatePairConfig();
@@ -263,6 +489,7 @@
                                            ice_candidate_pair_configs[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventIceCandidatePair) {
   std::unique_ptr<RtcEventIceCandidatePair> event = gen_.NewIceCandidatePair();
   history_.push_back(event->Copy());
@@ -276,6 +503,8 @@
   test::VerifyLoggedIceCandidatePairEvent(*event, ice_candidate_pair_events[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch, or prevent
+// it from happening.
 TEST_P(RtcEventLogEncoderTest, RtcEventLoggingStarted) {
   const int64_t timestamp_us = rtc::TimeMicros();
 
@@ -286,6 +515,8 @@
   test::VerifyLoggedStartEvent(timestamp_us, start_log_events[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch, or prevent
+// it from happening.
 TEST_P(RtcEventLogEncoderTest, RtcEventLoggingStopped) {
   const int64_t timestamp_us = rtc::TimeMicros();
 
@@ -296,6 +527,7 @@
   test::VerifyLoggedStopEvent(timestamp_us, stop_log_events[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventProbeClusterCreated) {
   std::unique_ptr<RtcEventProbeClusterCreated> event =
       gen_.NewProbeClusterCreated();
@@ -311,6 +543,7 @@
       *event, bwe_probe_cluster_created_events[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventProbeResultFailure) {
   std::unique_ptr<RtcEventProbeResultFailure> event =
       gen_.NewProbeResultFailure();
@@ -324,6 +557,7 @@
   test::VerifyLoggedBweProbeFailureEvent(*event, bwe_probe_failure_events[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventProbeResultSuccess) {
   std::unique_ptr<RtcEventProbeResultSuccess> event =
       gen_.NewProbeResultSuccess();
@@ -338,73 +572,62 @@
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventRtcpPacketIncoming) {
-  std::unique_ptr<RtcEventRtcpPacketIncoming> event =
-      gen_.NewRtcpPacketIncoming();
-  history_.push_back(event->Copy());
+  if (!new_encoding_ && force_repeated_fields_) {
+    // The old encoding does not work with duplicated packets. Since the legacy
+    // encoding is being phased out, we will not fix this.
+    return;
+  }
+
+  std::vector<std::unique_ptr<RtcEventRtcpPacketIncoming>> events(event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    events[i] = (i == 0 || !force_repeated_fields_)
+                    ? gen_.NewRtcpPacketIncoming()
+                    : events[0]->Copy();
+    history_.push_back(events[i]->Copy());
+  }
 
   std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
   ASSERT_TRUE(parsed_log_.ParseString(encoded));
+
   const auto& incoming_rtcp_packets = parsed_log_.incoming_rtcp_packets();
+  ASSERT_EQ(incoming_rtcp_packets.size(), event_count_);
 
-  ASSERT_EQ(incoming_rtcp_packets.size(), 1u);
-
-  test::VerifyLoggedRtcpPacketIncoming(*event, incoming_rtcp_packets[0]);
+  for (size_t i = 0; i < event_count_; ++i) {
+    test::VerifyLoggedRtcpPacketIncoming(*events[i], incoming_rtcp_packets[i]);
+  }
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventRtcpPacketOutgoing) {
-  std::unique_ptr<RtcEventRtcpPacketOutgoing> event =
-      gen_.NewRtcpPacketOutgoing();
-  history_.push_back(event->Copy());
+  std::vector<std::unique_ptr<RtcEventRtcpPacketOutgoing>> events(event_count_);
+  for (size_t i = 0; i < event_count_; ++i) {
+    events[i] = (i == 0 || !force_repeated_fields_)
+                    ? gen_.NewRtcpPacketOutgoing()
+                    : events[0]->Copy();
+    history_.push_back(events[i]->Copy());
+  }
 
   std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
   ASSERT_TRUE(parsed_log_.ParseString(encoded));
+
   const auto& outgoing_rtcp_packets = parsed_log_.outgoing_rtcp_packets();
+  ASSERT_EQ(outgoing_rtcp_packets.size(), event_count_);
 
-  ASSERT_EQ(outgoing_rtcp_packets.size(), 1u);
-
-  test::VerifyLoggedRtcpPacketOutgoing(*event, outgoing_rtcp_packets[0]);
+  for (size_t i = 0; i < event_count_; ++i) {
+    test::VerifyLoggedRtcpPacketOutgoing(*events[i], outgoing_rtcp_packets[i]);
+  }
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventRtpPacketIncoming) {
-  uint32_t ssrc = prng_.Rand<uint32_t>();
-  RtpHeaderExtensionMap extension_map;  // TODO(terelius): Test extensions too.
-  std::unique_ptr<RtcEventRtpPacketIncoming> event =
-      gen_.NewRtpPacketIncoming(ssrc, extension_map);
-  history_.push_back(event->Copy());
-
-  std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
-  ASSERT_TRUE(parsed_log_.ParseString(encoded));
-  const auto& incoming_rtp_packets_by_ssrc =
-      parsed_log_.incoming_rtp_packets_by_ssrc();
-
-  ASSERT_EQ(incoming_rtp_packets_by_ssrc.size(), 1u);
-  const auto& stream = incoming_rtp_packets_by_ssrc[0];
-  EXPECT_EQ(stream.ssrc, ssrc);
-  ASSERT_EQ(stream.incoming_packets.size(), 1u);
-
-  test::VerifyLoggedRtpPacketIncoming(*event, stream.incoming_packets[0]);
+  TestRtpPackets<RtcEventRtpPacketIncoming,
+                 ParsedRtcEventLogNew::LoggedRtpStreamIncoming>();
 }
 
 TEST_P(RtcEventLogEncoderTest, RtcEventRtpPacketOutgoing) {
-  uint32_t ssrc = prng_.Rand<uint32_t>();
-  RtpHeaderExtensionMap extension_map;  // TODO(terelius): Test extensions too.
-  std::unique_ptr<RtcEventRtpPacketOutgoing> event =
-      gen_.NewRtpPacketOutgoing(ssrc, extension_map);
-  history_.push_back(event->Copy());
-
-  std::string encoded = encoder_->EncodeBatch(history_.begin(), history_.end());
-  ASSERT_TRUE(parsed_log_.ParseString(encoded));
-  const auto& outgoing_rtp_packets_by_ssrc =
-      parsed_log_.outgoing_rtp_packets_by_ssrc();
-
-  ASSERT_EQ(outgoing_rtp_packets_by_ssrc.size(), 1u);
-  const auto& stream = outgoing_rtp_packets_by_ssrc[0];
-  EXPECT_EQ(stream.ssrc, ssrc);
-  ASSERT_EQ(stream.outgoing_packets.size(), 1u);
-
-  test::VerifyLoggedRtpPacketOutgoing(*event, stream.outgoing_packets[0]);
+  TestRtpPackets<RtcEventRtpPacketOutgoing,
+                 ParsedRtcEventLogNew::LoggedRtpStreamOutgoing>();
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventVideoReceiveStreamConfig) {
   uint32_t ssrc = prng_.Rand<uint32_t>();
   RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap();
@@ -420,6 +643,7 @@
   test::VerifyLoggedVideoRecvConfig(*event, video_recv_configs[0]);
 }
 
+// TODO(eladalon/terelius): Test with multiple events in the batch.
 TEST_P(RtcEventLogEncoderTest, RtcEventVideoSendStreamConfig) {
   uint32_t ssrc = prng_.Rand<uint32_t>();
   RtpHeaderExtensionMap extensions = gen_.NewRtpHeaderExtensionMap();
@@ -435,9 +659,12 @@
   test::VerifyLoggedVideoSendConfig(*event, video_send_configs[0]);
 }
 
-INSTANTIATE_TEST_CASE_P(RandomSeeds,
-                        RtcEventLogEncoderTest,
-                        ::testing::Combine(::testing::Values(1, 2, 3, 4, 5),
-                                           ::testing::Bool()));
+INSTANTIATE_TEST_CASE_P(
+    RandomSeeds,
+    RtcEventLogEncoderTest,
+    ::testing::Combine(/* Random seed*: */ ::testing::Values(1, 2, 3, 4, 5),
+                       /* Encoding: */ ::testing::Bool(),
+                       /* Event count: */ ::testing::Values(1, 2, 10, 100),
+                       /* Repeated fields: */ ::testing::Bool()));
 
 }  // namespace webrtc
diff --git a/logging/rtc_event_log/rtc_event_log2.proto b/logging/rtc_event_log/rtc_event_log2.proto
index fb8f760..fb64951 100644
--- a/logging/rtc_event_log/rtc_event_log2.proto
+++ b/logging/rtc_event_log/rtc_event_log2.proto
@@ -10,6 +10,8 @@
 // single EventStream object containing the same events. Hence, it is not
 // necessary to wait for the entire log to be complete before beginning to
 // write it to a file.
+// Note: For all X_deltas fields, we rely on the default value being an
+// empty string.
 message EventStream {
   // Deprecated - Maintained for compatibility with the old event log.
   repeated Event stream = 1 [deprecated = true];
@@ -395,7 +397,12 @@
 
   // Packet loss fraction that the encoder's forward error correction (FEC) is
   // optimized for.
-  optional float uplink_packet_loss_fraction = 4;
+  // Instead of encoding a float, we encode a value between 0 and 16383, which
+  // if divided by 16383, will give a value close to the original float.
+  // The value 16383 (2^14 - 1) was chosen so that it would give good precision
+  // on the one hand, and would be encodable with two bytes in varint form
+  // on the other hand.
+  optional uint32 uplink_packet_loss_fraction = 4;
 
   // Whether forward error correction (FEC) is turned on or off.
   optional bool enable_fec = 5;
diff --git a/logging/rtc_event_log/rtc_event_log_parser_new.cc b/logging/rtc_event_log/rtc_event_log_parser_new.cc
index d352817..5b6b26a 100644
--- a/logging/rtc_event_log/rtc_event_log_parser_new.cc
+++ b/logging/rtc_event_log/rtc_event_log_parser_new.cc
@@ -24,6 +24,9 @@
 #include "absl/types/optional.h"
 #include "api/rtp_headers.h"
 #include "api/rtpparameters.h"
+#include "logging/rtc_event_log/encoder/blob_encoding.h"
+#include "logging/rtc_event_log/encoder/delta_encoding.h"
+#include "logging/rtc_event_log/encoder/rtc_event_log_encoder_common.h"
 #include "logging/rtc_event_log/rtc_event_log.h"
 #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h"
 #include "modules/congestion_controller/rtp/transport_feedback_adapter.h"
@@ -35,8 +38,12 @@
 #include "modules/rtp_rtcp/source/rtp_utility.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_conversions.h"
 #include "rtc_base/protobuf_utils.h"
 
+using webrtc_event_logging::ToSigned;
+using webrtc_event_logging::ToUnsigned;
+
 namespace webrtc {
 
 namespace {
@@ -442,6 +449,276 @@
   std::sort(vec->begin(), vec->end(), LossHandlingPacketFeedbackComparator());
 }
 
+template <typename ProtoType, typename LoggedType>
+void StoreRtpPackets(
+    const ProtoType& proto,
+    std::map<uint32_t, std::vector<LoggedType>>* rtp_packets_map) {
+  RTC_CHECK(proto.has_timestamp_ms());
+  RTC_CHECK(proto.has_marker());
+  RTC_CHECK(proto.has_payload_type());
+  RTC_CHECK(proto.has_sequence_number());
+  RTC_CHECK(proto.has_rtp_timestamp());
+  RTC_CHECK(proto.has_ssrc());
+  RTC_CHECK(proto.has_payload_size());
+  RTC_CHECK(proto.has_header_size());
+  RTC_CHECK(proto.has_padding_size());
+
+  // Base event
+  {
+    RTPHeader header;
+    header.markerBit = rtc::checked_cast<bool>(proto.marker());
+    header.payloadType = rtc::checked_cast<uint8_t>(proto.payload_type());
+    header.sequenceNumber =
+        rtc::checked_cast<uint16_t>(proto.sequence_number());
+    header.timestamp = rtc::checked_cast<uint32_t>(proto.rtp_timestamp());
+    header.ssrc = rtc::checked_cast<uint32_t>(proto.ssrc());
+    header.numCSRCs = 0;  // TODO(terelius): Implement CSRC.
+    header.paddingLength = rtc::checked_cast<size_t>(proto.padding_size());
+    header.headerLength = rtc::checked_cast<size_t>(proto.header_size());
+    // TODO(terelius): Should we implement payload_type_frequency?
+    if (proto.has_transport_sequence_number()) {
+      header.extension.hasTransportSequenceNumber = true;
+      header.extension.transportSequenceNumber =
+          rtc::checked_cast<uint16_t>(proto.transport_sequence_number());
+    }
+    if (proto.has_transmission_time_offset()) {
+      header.extension.hasTransmissionTimeOffset = true;
+      header.extension.transmissionTimeOffset =
+          rtc::checked_cast<int32_t>(proto.transmission_time_offset());
+    }
+    if (proto.has_absolute_send_time()) {
+      header.extension.hasAbsoluteSendTime = true;
+      header.extension.absoluteSendTime =
+          rtc::checked_cast<uint32_t>(proto.absolute_send_time());
+    }
+    if (proto.has_video_rotation()) {
+      header.extension.hasVideoRotation = true;
+      header.extension.videoRotation = ConvertCVOByteToVideoRotation(
+          rtc::checked_cast<uint8_t>(proto.video_rotation()));
+    }
+    if (proto.has_audio_level()) {
+      header.extension.hasAudioLevel = true;
+      const uint8_t audio_level =
+          rtc::checked_cast<uint8_t>(proto.audio_level());
+      header.extension.voiceActivity = (audio_level >> 7) != 0;
+      header.extension.audioLevel = audio_level & 0x7Fu;
+    }
+    (*rtp_packets_map)[header.ssrc].emplace_back(
+        proto.timestamp_ms() * 1000, header, proto.header_size(),
+        proto.payload_size() + header.headerLength + header.paddingLength);
+  }
+
+  const size_t number_of_deltas =
+      proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u;
+  if (number_of_deltas == 0) {
+    return;
+  }
+
+  // timestamp_ms (event)
+  std::vector<absl::optional<uint64_t>> timestamp_ms_values = DecodeDeltas(
+      proto.timestamp_deltas_ms(), proto.timestamp_ms(), number_of_deltas);
+  RTC_CHECK_EQ(timestamp_ms_values.size(), number_of_deltas);
+
+  // marker (RTP base)
+  std::vector<absl::optional<uint64_t>> marker_values =
+      DecodeDeltas(proto.marker_deltas(), proto.marker(), number_of_deltas);
+  RTC_CHECK_EQ(marker_values.size(), number_of_deltas);
+
+  // payload_type (RTP base)
+  std::vector<absl::optional<uint64_t>> payload_type_values = DecodeDeltas(
+      proto.payload_type_deltas(), proto.payload_type(), number_of_deltas);
+  RTC_CHECK_EQ(payload_type_values.size(), number_of_deltas);
+
+  // sequence_number (RTP base)
+  std::vector<absl::optional<uint64_t>> sequence_number_values =
+      DecodeDeltas(proto.sequence_number_deltas(), proto.sequence_number(),
+                   number_of_deltas);
+  RTC_CHECK_EQ(sequence_number_values.size(), number_of_deltas);
+
+  // rtp_timestamp (RTP base)
+  std::vector<absl::optional<uint64_t>> rtp_timestamp_values = DecodeDeltas(
+      proto.rtp_timestamp_deltas(), proto.rtp_timestamp(), number_of_deltas);
+  RTC_CHECK_EQ(rtp_timestamp_values.size(), number_of_deltas);
+
+  // ssrc (RTP base)
+  std::vector<absl::optional<uint64_t>> ssrc_values =
+      DecodeDeltas(proto.ssrc_deltas(), proto.ssrc(), number_of_deltas);
+  RTC_CHECK_EQ(ssrc_values.size(), number_of_deltas);
+
+  // payload_size (RTP base)
+  std::vector<absl::optional<uint64_t>> payload_size_values = DecodeDeltas(
+      proto.payload_size_deltas(), proto.payload_size(), number_of_deltas);
+  RTC_CHECK_EQ(payload_size_values.size(), number_of_deltas);
+
+  // header_size (RTP base)
+  std::vector<absl::optional<uint64_t>> header_size_values = DecodeDeltas(
+      proto.header_size_deltas(), proto.header_size(), number_of_deltas);
+  RTC_CHECK_EQ(header_size_values.size(), number_of_deltas);
+
+  // padding_size (RTP base)
+  std::vector<absl::optional<uint64_t>> padding_size_values = DecodeDeltas(
+      proto.padding_size_deltas(), proto.padding_size(), number_of_deltas);
+  RTC_CHECK_EQ(padding_size_values.size(), number_of_deltas);
+
+  // transport_sequence_number (RTP extension)
+  std::vector<absl::optional<uint64_t>> transport_sequence_number_values;
+  {
+    const absl::optional<uint64_t> base_transport_sequence_number =
+        proto.has_transport_sequence_number()
+            ? proto.transport_sequence_number()
+            : absl::optional<uint64_t>();
+    transport_sequence_number_values =
+        DecodeDeltas(proto.transport_sequence_number_deltas(),
+                     base_transport_sequence_number, number_of_deltas);
+    RTC_CHECK_EQ(transport_sequence_number_values.size(), number_of_deltas);
+  }
+
+  // transmission_time_offset (RTP extension)
+  std::vector<absl::optional<uint64_t>> transmission_time_offset_values;
+  {
+    const absl::optional<uint64_t> unsigned_base_transmission_time_offset =
+        proto.has_transmission_time_offset()
+            ? ToUnsigned(proto.transmission_time_offset())
+            : absl::optional<uint64_t>();
+    transmission_time_offset_values =
+        DecodeDeltas(proto.transmission_time_offset_deltas(),
+                     unsigned_base_transmission_time_offset, number_of_deltas);
+    RTC_CHECK_EQ(transmission_time_offset_values.size(), number_of_deltas);
+  }
+
+  // absolute_send_time (RTP extension)
+  std::vector<absl::optional<uint64_t>> absolute_send_time_values;
+  {
+    const absl::optional<uint64_t> base_absolute_send_time =
+        proto.has_absolute_send_time() ? proto.absolute_send_time()
+                                       : absl::optional<uint64_t>();
+    absolute_send_time_values =
+        DecodeDeltas(proto.absolute_send_time_deltas(), base_absolute_send_time,
+                     number_of_deltas);
+    RTC_CHECK_EQ(absolute_send_time_values.size(), number_of_deltas);
+  }
+
+  // video_rotation (RTP extension)
+  std::vector<absl::optional<uint64_t>> video_rotation_values;
+  {
+    const absl::optional<uint64_t> base_video_rotation =
+        proto.has_video_rotation() ? proto.video_rotation()
+                                   : absl::optional<uint64_t>();
+    video_rotation_values = DecodeDeltas(proto.video_rotation_deltas(),
+                                         base_video_rotation, number_of_deltas);
+    RTC_CHECK_EQ(video_rotation_values.size(), number_of_deltas);
+  }
+
+  // audio_level (RTP extension)
+  std::vector<absl::optional<uint64_t>> audio_level_values;
+  {
+    const absl::optional<uint64_t> base_audio_level =
+        proto.has_audio_level() ? proto.audio_level()
+                                : absl::optional<uint64_t>();
+    audio_level_values = DecodeDeltas(proto.audio_level_deltas(),
+                                      base_audio_level, number_of_deltas);
+    RTC_CHECK_EQ(audio_level_values.size(), number_of_deltas);
+  }
+
+  // Delta decoding
+  for (size_t i = 0; i < number_of_deltas; ++i) {
+    RTC_CHECK(timestamp_ms_values[i].has_value());
+    RTC_CHECK(marker_values[i].has_value());
+    RTC_CHECK(payload_type_values[i].has_value());
+    RTC_CHECK(sequence_number_values[i].has_value());
+    RTC_CHECK(rtp_timestamp_values[i].has_value());
+    RTC_CHECK(ssrc_values[i].has_value());
+    RTC_CHECK(payload_size_values[i].has_value());
+    RTC_CHECK(header_size_values[i].has_value());
+    RTC_CHECK(padding_size_values[i].has_value());
+
+    RTPHeader header;
+    header.markerBit = rtc::checked_cast<bool>(*marker_values[i]);
+    header.payloadType = rtc::checked_cast<uint8_t>(*payload_type_values[i]);
+    header.sequenceNumber =
+        rtc::checked_cast<uint16_t>(*sequence_number_values[i]);
+    header.timestamp = rtc::checked_cast<uint32_t>(*rtp_timestamp_values[i]);
+    header.ssrc = rtc::checked_cast<uint32_t>(*ssrc_values[i]);
+    header.numCSRCs = 0;  // TODO(terelius): Implement CSRC.
+    header.paddingLength = rtc::checked_cast<size_t>(*padding_size_values[i]);
+    header.headerLength = rtc::checked_cast<size_t>(*header_size_values[i]);
+    // TODO(terelius): Should we implement payload_type_frequency?
+    if (transport_sequence_number_values.size() > i &&
+        transport_sequence_number_values[i].has_value()) {
+      header.extension.hasTransportSequenceNumber = true;
+      header.extension.transportSequenceNumber = rtc::checked_cast<uint16_t>(
+          transport_sequence_number_values[i].value());
+    }
+    if (transmission_time_offset_values.size() > i &&
+        transmission_time_offset_values[i].has_value()) {
+      header.extension.hasTransmissionTimeOffset = true;
+      int32_t transmission_time_offset;
+      RTC_CHECK(ToSigned(transmission_time_offset_values[i].value(),
+                         &transmission_time_offset));
+      header.extension.transmissionTimeOffset = transmission_time_offset;
+    }
+    if (absolute_send_time_values.size() > i &&
+        absolute_send_time_values[i].has_value()) {
+      header.extension.hasAbsoluteSendTime = true;
+      header.extension.absoluteSendTime =
+          rtc::checked_cast<uint32_t>(absolute_send_time_values[i].value());
+    }
+    if (video_rotation_values.size() > i &&
+        video_rotation_values[i].has_value()) {
+      header.extension.hasVideoRotation = true;
+      header.extension.videoRotation = ConvertCVOByteToVideoRotation(
+          rtc::checked_cast<uint8_t>(video_rotation_values[i].value()));
+    }
+    if (audio_level_values.size() > i && audio_level_values[i].has_value()) {
+      header.extension.hasAudioLevel = true;
+      const uint8_t audio_level =
+          rtc::checked_cast<uint8_t>(audio_level_values[i].value());
+      header.extension.voiceActivity = (audio_level >> 7) != 0;
+      header.extension.audioLevel = audio_level & 0x7Fu;
+    }
+    (*rtp_packets_map)[header.ssrc].emplace_back(
+        timestamp_ms_values[i].value() * 1000, header, header.headerLength,
+        payload_size_values[i].value() + header.headerLength +
+            header.paddingLength);
+  }
+}
+
+template <typename ProtoType, typename LoggedType>
+void StoreRtcpPackets(const ProtoType& proto,
+                      std::vector<LoggedType>* rtcp_packets) {
+  RTC_CHECK(proto.has_timestamp_ms());
+  RTC_CHECK(proto.has_raw_packet());
+
+  // Base event
+  rtcp_packets->emplace_back(proto.timestamp_ms() * 1000, proto.raw_packet());
+
+  const size_t number_of_deltas =
+      proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u;
+  if (number_of_deltas == 0) {
+    return;
+  }
+
+  // timestamp_ms
+  std::vector<absl::optional<uint64_t>> timestamp_ms_values = DecodeDeltas(
+      proto.timestamp_deltas_ms(), proto.timestamp_ms(), number_of_deltas);
+  RTC_CHECK_EQ(timestamp_ms_values.size(), number_of_deltas);
+
+  // raw_packet
+  RTC_CHECK(proto.has_raw_packet_deltas());
+  std::vector<absl::string_view> raw_packet_values =
+      DecodeBlobs(proto.raw_packet_deltas(), number_of_deltas);
+  RTC_CHECK_EQ(raw_packet_values.size(), number_of_deltas);
+
+  // Delta decoding
+  for (size_t i = 0; i < number_of_deltas; ++i) {
+    RTC_CHECK(timestamp_ms_values[i].has_value());
+    rtcp_packets->emplace_back(
+        1000 * timestamp_ms_values[i].value(),
+        reinterpret_cast<const uint8_t*>(raw_packet_values[i].data()),
+        raw_packet_values[i].size());
+  }
+}
+
 }  // namespace
 
 LoggedRtcpPacket::LoggedRtcpPacket(uint64_t timestamp_us,
@@ -1675,109 +1952,71 @@
   return rtp_rtcp_matched;
 }
 
-// Helper functions for new format starts here
+// Helper functions for new format start here
 void ParsedRtcEventLogNew::StoreParsedNewFormatEvent(
     const rtclog2::EventStream& stream) {
   RTC_DCHECK_EQ(stream.stream_size(), 0);
 
-  RTC_DCHECK_LE(stream.incoming_rtp_packets_size(), 1);
+  RTC_DCHECK_EQ(
+      stream.incoming_rtp_packets_size() + stream.outgoing_rtp_packets_size() +
+          stream.incoming_rtcp_packets_size() +
+          stream.outgoing_rtcp_packets_size() +
+          stream.audio_playout_events_size() + stream.begin_log_events_size() +
+          stream.end_log_events_size() + stream.loss_based_bwe_updates_size() +
+          stream.delay_based_bwe_updates_size() +
+          stream.audio_network_adaptations_size() +
+          stream.probe_clusters_size() + stream.probe_success_size() +
+          stream.probe_failure_size() + stream.alr_states_size() +
+          stream.ice_candidate_configs_size() +
+          stream.ice_candidate_events_size() +
+          stream.audio_recv_stream_configs_size() +
+          stream.audio_send_stream_configs_size() +
+          stream.video_recv_stream_configs_size() +
+          stream.video_send_stream_configs_size(),
+      1u);
+
   if (stream.incoming_rtp_packets_size() == 1) {
     StoreIncomingRtpPackets(stream.incoming_rtp_packets(0));
-  }
-
-  RTC_DCHECK_LE(stream.outgoing_rtp_packets_size(), 1);
-  if (stream.outgoing_rtp_packets_size() == 1) {
-    StoreOutgoingRtpPacket(stream.outgoing_rtp_packets(0));
-  }
-
-  RTC_DCHECK_LE(stream.incoming_rtcp_packets_size(), 1);
-  if (stream.incoming_rtcp_packets_size() == 1) {
+  } else if (stream.outgoing_rtp_packets_size() == 1) {
+    StoreOutgoingRtpPackets(stream.outgoing_rtp_packets(0));
+  } else if (stream.incoming_rtcp_packets_size() == 1) {
     StoreIncomingRtcpPackets(stream.incoming_rtcp_packets(0));
-  }
-
-  RTC_DCHECK_LE(stream.outgoing_rtcp_packets_size(), 1);
-  if (stream.outgoing_rtcp_packets_size() == 1) {
+  } else if (stream.outgoing_rtcp_packets_size() == 1) {
     StoreOutgoingRtcpPackets(stream.outgoing_rtcp_packets(0));
-  }
-
-  RTC_DCHECK_LE(stream.audio_playout_events_size(), 1);
-  if (stream.audio_playout_events_size() == 1) {
+  } else if (stream.audio_playout_events_size() == 1) {
     StoreAudioPlayoutEvent(stream.audio_playout_events(0));
-  }
-
-  RTC_DCHECK_LE(stream.begin_log_events_size(), 1);
-  if (stream.begin_log_events_size() == 1) {
+  } else if (stream.begin_log_events_size() == 1) {
     StoreStartEvent(stream.begin_log_events(0));
-  }
-
-  RTC_DCHECK_LE(stream.end_log_events_size(), 1);
-  if (stream.end_log_events_size() == 1) {
+  } else if (stream.end_log_events_size() == 1) {
     StoreStopEvent(stream.end_log_events(0));
-  }
-
-  RTC_DCHECK_LE(stream.loss_based_bwe_updates_size(), 1);
-  if (stream.loss_based_bwe_updates_size() == 1) {
+  } else if (stream.loss_based_bwe_updates_size() == 1) {
     StoreBweLossBasedUpdate(stream.loss_based_bwe_updates(0));
-  }
-
-  RTC_DCHECK_LE(stream.delay_based_bwe_updates_size(), 1);
-  if (stream.delay_based_bwe_updates_size() == 1) {
+  } else if (stream.delay_based_bwe_updates_size() == 1) {
     StoreBweDelayBasedUpdate(stream.delay_based_bwe_updates(0));
-  }
-
-  RTC_DCHECK_LE(stream.audio_network_adaptations_size(), 1);
-  if (stream.audio_network_adaptations_size() == 1) {
+  } else if (stream.audio_network_adaptations_size() == 1) {
     StoreAudioNetworkAdaptationEvent(stream.audio_network_adaptations(0));
-  }
-
-  RTC_DCHECK_LE(stream.probe_clusters_size(), 1);
-  if (stream.probe_clusters_size() == 1) {
+  } else if (stream.probe_clusters_size() == 1) {
     StoreBweProbeClusterCreated(stream.probe_clusters(0));
-  }
-
-  RTC_DCHECK_LE(stream.probe_success_size(), 1);
-  if (stream.probe_success_size() == 1) {
+  } else if (stream.probe_success_size() == 1) {
     StoreBweProbeSuccessEvent(stream.probe_success(0));
-  }
-
-  RTC_DCHECK_LE(stream.probe_failure_size(), 1);
-  if (stream.probe_failure_size() == 1) {
+  } else if (stream.probe_failure_size() == 1) {
     StoreBweProbeFailureEvent(stream.probe_failure(0));
-  }
-
-  RTC_DCHECK_LE(stream.alr_states_size(), 1);
-  if (stream.alr_states_size() == 1) {
+  } else if (stream.alr_states_size() == 1) {
     StoreAlrStateEvent(stream.alr_states(0));
-  }
-
-  RTC_DCHECK_LE(stream.ice_candidate_configs_size(), 1);
-  if (stream.ice_candidate_configs_size() == 1) {
+  } else if (stream.ice_candidate_configs_size() == 1) {
     StoreIceCandidatePairConfig(stream.ice_candidate_configs(0));
-  }
-
-  RTC_DCHECK_LE(stream.ice_candidate_events_size(), 1);
-  if (stream.ice_candidate_events_size() == 1) {
+  } else if (stream.ice_candidate_events_size() == 1) {
     StoreIceCandidateEvent(stream.ice_candidate_events(0));
-  }
-
-  RTC_DCHECK_LE(stream.audio_recv_stream_configs_size(), 1);
-  if (stream.audio_recv_stream_configs_size() == 1) {
+  } else if (stream.audio_recv_stream_configs_size() == 1) {
     StoreAudioRecvConfig(stream.audio_recv_stream_configs(0));
-  }
-
-  RTC_DCHECK_LE(stream.audio_send_stream_configs_size(), 1);
-  if (stream.audio_send_stream_configs_size() == 1) {
+  } else if (stream.audio_send_stream_configs_size() == 1) {
     StoreAudioSendConfig(stream.audio_send_stream_configs(0));
-  }
-
-  RTC_DCHECK_LE(stream.video_recv_stream_configs_size(), 1);
-  if (stream.video_recv_stream_configs_size() == 1) {
+  } else if (stream.video_recv_stream_configs_size() == 1) {
     StoreVideoRecvConfig(stream.video_recv_stream_configs(0));
-  }
-
-  RTC_DCHECK_LE(stream.video_send_stream_configs_size(), 1);
-  if (stream.video_send_stream_configs_size() == 1) {
+  } else if (stream.video_send_stream_configs_size() == 1) {
     StoreVideoSendConfig(stream.video_send_stream_configs(0));
+  } else {
+    RTC_NOTREACHED();
   }
 }
 
@@ -1796,157 +2035,59 @@
     const rtclog2::AudioPlayoutEvents& proto) {
   RTC_CHECK(proto.has_timestamp_ms());
   RTC_CHECK(proto.has_local_ssrc());
-  LoggedAudioPlayoutEvent audio_playout_event;
-  audio_playout_event.timestamp_us = proto.timestamp_ms() * 1000;
-  audio_playout_event.ssrc = proto.local_ssrc();
 
-  audio_playout_events_[audio_playout_event.ssrc].push_back(
-      audio_playout_event);
-  // TODO(terelius): Parse deltas.
+  // Base event
+  auto map_it = audio_playout_events_[proto.local_ssrc()];
+  audio_playout_events_[proto.local_ssrc()].emplace_back(
+      1000 * proto.timestamp_ms(), proto.local_ssrc());
+
+  const size_t number_of_deltas =
+      proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u;
+  if (number_of_deltas == 0) {
+    return;
+  }
+
+  // timestamp_ms
+  std::vector<absl::optional<uint64_t>> timestamp_ms_values = DecodeDeltas(
+      proto.timestamp_deltas_ms(), proto.timestamp_ms(), number_of_deltas);
+  RTC_CHECK_EQ(timestamp_ms_values.size(), number_of_deltas);
+
+  // local_ssrc
+  std::vector<absl::optional<uint64_t>> local_ssrc_values = DecodeDeltas(
+      proto.local_ssrc_deltas(), proto.local_ssrc(), number_of_deltas);
+  RTC_CHECK_EQ(local_ssrc_values.size(), number_of_deltas);
+
+  // Delta decoding
+  for (size_t i = 0; i < number_of_deltas; ++i) {
+    RTC_CHECK(timestamp_ms_values[i].has_value());
+    RTC_CHECK(local_ssrc_values[i].has_value());
+    RTC_CHECK_LE(local_ssrc_values[i].value(),
+                 std::numeric_limits<uint32_t>::max());
+    const uint32_t local_ssrc =
+        static_cast<uint32_t>(local_ssrc_values[i].value());
+    audio_playout_events_[local_ssrc].emplace_back(
+        1000 * timestamp_ms_values[i].value(), local_ssrc);
+  }
 }
 
 void ParsedRtcEventLogNew::StoreIncomingRtpPackets(
     const rtclog2::IncomingRtpPackets& proto) {
-  RTC_CHECK(proto.has_timestamp_ms());
-  int64_t timestamp_ms = proto.timestamp_ms();
-
-  RTC_CHECK(proto.has_header_size());
-  size_t header_length = proto.header_size();
-
-  RTC_CHECK(proto.has_padding_size());
-  size_t padding_length = proto.padding_size();
-
-  RTC_CHECK(proto.has_payload_size());
-  size_t total_length = proto.payload_size() + header_length + padding_length;
-
-  RTPHeader header;
-  RTC_CHECK(proto.has_marker());
-  header.markerBit = proto.marker();
-  RTC_CHECK(proto.has_payload_type());
-  header.payloadType = proto.payload_type();
-  RTC_CHECK(proto.has_sequence_number());
-  header.sequenceNumber = proto.sequence_number();
-  RTC_CHECK(proto.has_rtp_timestamp());
-  header.timestamp = proto.rtp_timestamp();
-  RTC_CHECK(proto.has_ssrc());
-  header.ssrc = proto.ssrc();
-
-  header.numCSRCs = 0;  // TODO(terelius): Implement CSRC.
-  header.paddingLength = padding_length;
-  header.headerLength = header_length;
-  // TODO(terelius): Should we implement payload_type_frequency?
-
-  if (proto.has_transmission_time_offset()) {
-    header.extension.hasTransmissionTimeOffset = true;
-    header.extension.transmissionTimeOffset = proto.transmission_time_offset();
-  }
-  if (proto.has_absolute_send_time()) {
-    header.extension.hasAbsoluteSendTime = true;
-    header.extension.absoluteSendTime = proto.absolute_send_time();
-  }
-  if (proto.has_transport_sequence_number()) {
-    header.extension.hasTransportSequenceNumber = true;
-    header.extension.transportSequenceNumber =
-        proto.transport_sequence_number();
-  }
-  if (proto.has_audio_level()) {
-    header.extension.hasAudioLevel = true;
-    header.extension.voiceActivity = (proto.audio_level() >> 7) != 0;
-    header.extension.audioLevel = proto.audio_level() & 0x7Fu;
-  }
-  if (proto.has_video_rotation()) {
-    header.extension.hasVideoRotation = true;
-    header.extension.videoRotation =
-        ConvertCVOByteToVideoRotation(proto.video_rotation());
-  }
-
-  incoming_rtp_packets_map_[header.ssrc].push_back(LoggedRtpPacketIncoming(
-      timestamp_ms * 1000, header, header_length, total_length));
-  // TODO(terelius): Parse deltas.
+  StoreRtpPackets(proto, &incoming_rtp_packets_map_);
 }
 
-void ParsedRtcEventLogNew::StoreOutgoingRtpPacket(
+void ParsedRtcEventLogNew::StoreOutgoingRtpPackets(
     const rtclog2::OutgoingRtpPackets& proto) {
-  RTC_CHECK(proto.has_timestamp_ms());
-  int64_t timestamp_ms = proto.timestamp_ms();
-
-  RTC_CHECK(proto.has_header_size());
-  size_t header_length = proto.header_size();
-
-  RTC_CHECK(proto.has_padding_size());
-  size_t padding_length = proto.padding_size();
-
-  RTC_CHECK(proto.has_payload_size());
-  size_t total_length = proto.payload_size() + header_length + padding_length;
-
-  RTPHeader header;
-  RTC_CHECK(proto.has_marker());
-  header.markerBit = proto.marker();
-  RTC_CHECK(proto.has_payload_type());
-  header.payloadType = proto.payload_type();
-  RTC_CHECK(proto.has_sequence_number());
-  header.sequenceNumber = proto.sequence_number();
-  RTC_CHECK(proto.has_rtp_timestamp());
-  header.timestamp = proto.rtp_timestamp();
-  RTC_CHECK(proto.has_ssrc());
-  header.ssrc = proto.ssrc();
-
-  header.numCSRCs = 0;  // TODO(terelius): Implement CSRC.
-  header.paddingLength = padding_length;
-  header.headerLength = header_length;
-  // TODO(terelius): Should we implement payload_type_frequency?
-
-  if (proto.has_transmission_time_offset()) {
-    header.extension.hasTransmissionTimeOffset = true;
-    header.extension.transmissionTimeOffset = proto.transmission_time_offset();
-  }
-  if (proto.has_absolute_send_time()) {
-    header.extension.hasAbsoluteSendTime = true;
-    header.extension.absoluteSendTime = proto.absolute_send_time();
-  }
-  if (proto.has_transport_sequence_number()) {
-    header.extension.hasTransportSequenceNumber = true;
-    header.extension.transportSequenceNumber =
-        proto.transport_sequence_number();
-  }
-  if (proto.has_audio_level()) {
-    header.extension.hasAudioLevel = true;
-    header.extension.voiceActivity = (proto.audio_level() >> 7) != 0;
-    header.extension.audioLevel = proto.audio_level() & 0x7Fu;
-  }
-  if (proto.has_video_rotation()) {
-    header.extension.hasVideoRotation = true;
-    header.extension.videoRotation =
-        ConvertCVOByteToVideoRotation(proto.video_rotation());
-  }
-
-  outgoing_rtp_packets_map_[header.ssrc].push_back(LoggedRtpPacketOutgoing(
-      timestamp_ms * 1000, header, header_length, total_length));
-  // TODO(terelius): Parse deltas.
+  StoreRtpPackets(proto, &outgoing_rtp_packets_map_);
 }
 
 void ParsedRtcEventLogNew::StoreIncomingRtcpPackets(
     const rtclog2::IncomingRtcpPackets& proto) {
-  RTC_CHECK(proto.has_timestamp_ms());
-  int64_t timestamp_ms = proto.timestamp_ms();
-
-  RTC_CHECK(proto.has_raw_packet());
-  incoming_rtcp_packets_.push_back(
-      LoggedRtcpPacketIncoming(timestamp_ms * 1000, proto.raw_packet()));
-
-  // TODO(terelius): Parse deltas.
+  StoreRtcpPackets(proto, &incoming_rtcp_packets_);
 }
 
 void ParsedRtcEventLogNew::StoreOutgoingRtcpPackets(
     const rtclog2::OutgoingRtcpPackets& proto) {
-  RTC_CHECK(proto.has_timestamp_ms());
-  int64_t timestamp_ms = proto.timestamp_ms();
-
-  RTC_CHECK(proto.has_raw_packet());
-  outgoing_rtcp_packets_.push_back(
-      LoggedRtcpPacketOutgoing(timestamp_ms * 1000, proto.raw_packet()));
-
-  // TODO(terelius): Parse deltas.
+  StoreRtcpPackets(proto, &outgoing_rtcp_packets_);
 }
 
 void ParsedRtcEventLogNew::StoreStartEvent(
@@ -1971,15 +2112,62 @@
   RTC_CHECK(proto.has_fraction_loss());
   RTC_CHECK(proto.has_total_packets());
 
-  LoggedBweLossBasedUpdate loss_update;
-  loss_update.timestamp_us = proto.timestamp_ms() * 1000;
-  loss_update.bitrate_bps = proto.bitrate_bps();
-  loss_update.fraction_lost = proto.fraction_loss();
-  loss_update.expected_packets = proto.total_packets();
+  // Base event
+  bwe_loss_updates_.emplace_back(1000 * proto.timestamp_ms(),
+                                 proto.bitrate_bps(), proto.fraction_loss(),
+                                 proto.total_packets());
 
-  bwe_loss_updates_.push_back(loss_update);
+  const size_t number_of_deltas =
+      proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u;
+  if (number_of_deltas == 0) {
+    return;
+  }
 
-  // TODO(terelius): Parse deltas.
+  // timestamp_ms
+  std::vector<absl::optional<uint64_t>> timestamp_ms_values = DecodeDeltas(
+      proto.timestamp_deltas_ms(), proto.timestamp_ms(), number_of_deltas);
+  RTC_CHECK_EQ(timestamp_ms_values.size(), number_of_deltas);
+
+  // bitrate_bps
+  std::vector<absl::optional<uint64_t>> bitrate_bps_values = DecodeDeltas(
+      proto.bitrate_deltas_bps(), proto.bitrate_bps(), number_of_deltas);
+  RTC_CHECK_EQ(bitrate_bps_values.size(), number_of_deltas);
+
+  // fraction_loss
+  std::vector<absl::optional<uint64_t>> fraction_loss_values = DecodeDeltas(
+      proto.fraction_loss_deltas(), proto.fraction_loss(), number_of_deltas);
+  RTC_CHECK_EQ(fraction_loss_values.size(), number_of_deltas);
+
+  // total_packets
+  std::vector<absl::optional<uint64_t>> total_packets_values = DecodeDeltas(
+      proto.total_packets_deltas(), proto.total_packets(), number_of_deltas);
+  RTC_CHECK_EQ(total_packets_values.size(), number_of_deltas);
+
+  // Delta decoding
+  for (size_t i = 0; i < number_of_deltas; ++i) {
+    RTC_CHECK(timestamp_ms_values[i].has_value());
+
+    RTC_CHECK(bitrate_bps_values[i].has_value());
+    RTC_CHECK_LE(bitrate_bps_values[i].value(),
+                 std::numeric_limits<uint32_t>::max());
+    const uint32_t bitrate_bps =
+        static_cast<uint32_t>(bitrate_bps_values[i].value());
+
+    RTC_CHECK(fraction_loss_values[i].has_value());
+    RTC_CHECK_LE(fraction_loss_values[i].value(),
+                 std::numeric_limits<uint32_t>::max());
+    const uint32_t fraction_loss =
+        static_cast<uint32_t>(fraction_loss_values[i].value());
+
+    RTC_CHECK(total_packets_values[i].has_value());
+    RTC_CHECK_LE(total_packets_values[i].value(),
+                 std::numeric_limits<uint32_t>::max());
+    const uint32_t total_packets =
+        static_cast<uint32_t>(total_packets_values[i].value());
+
+    bwe_loss_updates_.emplace_back(1000 * timestamp_ms_values[i].value(),
+                                   bitrate_bps, fraction_loss, total_packets);
+  }
 }
 
 void ParsedRtcEventLogNew::StoreBweDelayBasedUpdate(
@@ -1988,14 +2176,53 @@
   RTC_CHECK(proto.has_bitrate_bps());
   RTC_CHECK(proto.has_detector_state());
 
-  LoggedBweDelayBasedUpdate delay_update;
-  delay_update.timestamp_us = proto.timestamp_ms() * 1000;
-  delay_update.bitrate_bps = proto.bitrate_bps();
-  delay_update.detector_state = GetRuntimeDetectorState(proto.detector_state());
+  // Base event
+  const BandwidthUsage base_detector_state =
+      GetRuntimeDetectorState(proto.detector_state());
+  bwe_delay_updates_.emplace_back(1000 * proto.timestamp_ms(),
+                                  proto.bitrate_bps(), base_detector_state);
 
-  bwe_delay_updates_.push_back(delay_update);
+  const size_t number_of_deltas =
+      proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u;
+  if (number_of_deltas == 0) {
+    return;
+  }
 
-  // TODO(terelius): Parse deltas.
+  // timestamp_ms
+  std::vector<absl::optional<uint64_t>> timestamp_ms_values = DecodeDeltas(
+      proto.timestamp_deltas_ms(), proto.timestamp_ms(), number_of_deltas);
+  RTC_CHECK_EQ(timestamp_ms_values.size(), number_of_deltas);
+
+  // bitrate_bps
+  std::vector<absl::optional<uint64_t>> bitrate_bps_values = DecodeDeltas(
+      proto.bitrate_deltas_bps(), proto.bitrate_bps(), number_of_deltas);
+  RTC_CHECK_EQ(bitrate_bps_values.size(), number_of_deltas);
+
+  // detector_state
+  std::vector<absl::optional<uint64_t>> detector_state_values = DecodeDeltas(
+      proto.detector_state_deltas(),
+      static_cast<uint64_t>(proto.detector_state()), number_of_deltas);
+  RTC_CHECK_EQ(detector_state_values.size(), number_of_deltas);
+
+  // Delta decoding
+  for (size_t i = 0; i < number_of_deltas; ++i) {
+    RTC_CHECK(timestamp_ms_values[i].has_value());
+
+    RTC_CHECK(bitrate_bps_values[i].has_value());
+    RTC_CHECK_LE(bitrate_bps_values[i].value(),
+                 std::numeric_limits<uint32_t>::max());
+    const uint32_t bitrate_bps =
+        static_cast<uint32_t>(bitrate_bps_values[i].value());
+
+    RTC_CHECK(detector_state_values[i].has_value());
+    const auto detector_state =
+        static_cast<rtclog2::DelayBasedBweUpdates::DetectorState>(
+            detector_state_values[i].value());
+
+    bwe_delay_updates_.emplace_back(1000 * timestamp_ms_values[i].value(),
+                                    bitrate_bps,
+                                    GetRuntimeDetectorState(detector_state));
+  }
 }
 
 void ParsedRtcEventLogNew::StoreBweProbeClusterCreated(
@@ -2049,33 +2276,139 @@
 
 void ParsedRtcEventLogNew::StoreAudioNetworkAdaptationEvent(
     const rtclog2::AudioNetworkAdaptations& proto) {
-  LoggedAudioNetworkAdaptationEvent ana_event;
   RTC_CHECK(proto.has_timestamp_ms());
-  ana_event.timestamp_us = proto.timestamp_ms() * 1000;
 
-  if (proto.has_bitrate_bps()) {
-    ana_event.config.bitrate_bps = proto.bitrate_bps();
-  }
-  if (proto.has_frame_length_ms()) {
-    ana_event.config.frame_length_ms = proto.frame_length_ms();
-  }
-  if (proto.has_uplink_packet_loss_fraction()) {
-    ana_event.config.uplink_packet_loss_fraction =
-        proto.uplink_packet_loss_fraction();
-  }
-  if (proto.has_enable_fec()) {
-    ana_event.config.enable_fec = proto.enable_fec();
-  }
-  if (proto.has_enable_dtx()) {
-    ana_event.config.enable_dtx = proto.enable_dtx();
-  }
-  if (proto.has_num_channels()) {
-    ana_event.config.num_channels = proto.num_channels();
+  // Base event
+  {
+    AudioEncoderRuntimeConfig runtime_config;
+    if (proto.has_bitrate_bps()) {
+      runtime_config.bitrate_bps = proto.bitrate_bps();
+    }
+    if (proto.has_frame_length_ms()) {
+      runtime_config.frame_length_ms = proto.frame_length_ms();
+    }
+    if (proto.has_uplink_packet_loss_fraction()) {
+      float uplink_packet_loss_fraction;
+      RTC_CHECK(ParsePacketLossFractionFromProtoFormat(
+          proto.uplink_packet_loss_fraction(), &uplink_packet_loss_fraction));
+      runtime_config.uplink_packet_loss_fraction = uplink_packet_loss_fraction;
+    }
+    if (proto.has_enable_fec()) {
+      runtime_config.enable_fec = proto.enable_fec();
+    }
+    if (proto.has_enable_dtx()) {
+      runtime_config.enable_dtx = proto.enable_dtx();
+    }
+    if (proto.has_num_channels()) {
+      runtime_config.num_channels = proto.num_channels();
+    }
+    audio_network_adaptation_events_.emplace_back(1000 * proto.timestamp_ms(),
+                                                  runtime_config);
   }
 
-  audio_network_adaptation_events_.push_back(ana_event);
+  const size_t number_of_deltas =
+      proto.has_number_of_deltas() ? proto.number_of_deltas() : 0u;
+  if (number_of_deltas == 0) {
+    return;
+  }
 
-  // TODO(terelius): Parse deltas.
+  // timestamp_ms
+  std::vector<absl::optional<uint64_t>> timestamp_ms_values = DecodeDeltas(
+      proto.timestamp_deltas_ms(), proto.timestamp_ms(), number_of_deltas);
+  RTC_CHECK_EQ(timestamp_ms_values.size(), number_of_deltas);
+
+  // bitrate_bps
+  const absl::optional<uint64_t> unsigned_base_bitrate_bps =
+      proto.has_bitrate_bps()
+          ? absl::optional<uint64_t>(ToUnsigned(proto.bitrate_bps()))
+          : absl::optional<uint64_t>();
+  std::vector<absl::optional<uint64_t>> bitrate_bps_values = DecodeDeltas(
+      proto.bitrate_deltas_bps(), unsigned_base_bitrate_bps, number_of_deltas);
+  RTC_CHECK_EQ(bitrate_bps_values.size(), number_of_deltas);
+
+  // frame_length_ms
+  const absl::optional<uint64_t> unsigned_base_frame_length_ms =
+      proto.has_frame_length_ms()
+          ? absl::optional<uint64_t>(ToUnsigned(proto.frame_length_ms()))
+          : absl::optional<uint64_t>();
+  std::vector<absl::optional<uint64_t>> frame_length_ms_values =
+      DecodeDeltas(proto.frame_length_deltas_ms(),
+                   unsigned_base_frame_length_ms, number_of_deltas);
+  RTC_CHECK_EQ(frame_length_ms_values.size(), number_of_deltas);
+
+  // uplink_packet_loss_fraction
+  const absl::optional<uint64_t> uplink_packet_loss_fraction =
+      proto.has_uplink_packet_loss_fraction()
+          ? absl::optional<uint64_t>(proto.uplink_packet_loss_fraction())
+          : absl::optional<uint64_t>();
+  std::vector<absl::optional<uint64_t>> uplink_packet_loss_fraction_values =
+      DecodeDeltas(proto.uplink_packet_loss_fraction_deltas(),
+                   uplink_packet_loss_fraction, number_of_deltas);
+  RTC_CHECK_EQ(uplink_packet_loss_fraction_values.size(), number_of_deltas);
+
+  // enable_fec
+  const absl::optional<uint64_t> enable_fec =
+      proto.has_enable_fec() ? absl::optional<uint64_t>(proto.enable_fec())
+                             : absl::optional<uint64_t>();
+  std::vector<absl::optional<uint64_t>> enable_fec_values =
+      DecodeDeltas(proto.enable_fec_deltas(), enable_fec, number_of_deltas);
+  RTC_CHECK_EQ(enable_fec_values.size(), number_of_deltas);
+
+  // enable_dtx
+  const absl::optional<uint64_t> enable_dtx =
+      proto.has_enable_dtx() ? absl::optional<uint64_t>(proto.enable_dtx())
+                             : absl::optional<uint64_t>();
+  std::vector<absl::optional<uint64_t>> enable_dtx_values =
+      DecodeDeltas(proto.enable_dtx_deltas(), enable_dtx, number_of_deltas);
+  RTC_CHECK_EQ(enable_dtx_values.size(), number_of_deltas);
+
+  // num_channels
+  const absl::optional<uint64_t> num_channels =
+      proto.has_num_channels() ? absl::optional<uint64_t>(proto.num_channels())
+                               : absl::optional<uint64_t>();
+  std::vector<absl::optional<uint64_t>> num_channels_values =
+      DecodeDeltas(proto.num_channels_deltas(), num_channels, number_of_deltas);
+  RTC_CHECK_EQ(num_channels_values.size(), number_of_deltas);
+
+  // Delta decoding
+  for (size_t i = 0; i < number_of_deltas; ++i) {
+    RTC_CHECK(timestamp_ms_values[i].has_value());
+
+    AudioEncoderRuntimeConfig runtime_config;
+    if (bitrate_bps_values[i].has_value()) {
+      int signed_bitrate_bps;
+      RTC_CHECK(ToSigned(bitrate_bps_values[i].value(), &signed_bitrate_bps));
+      runtime_config.bitrate_bps = signed_bitrate_bps;
+    }
+    if (frame_length_ms_values[i].has_value()) {
+      int signed_frame_length_ms;
+      RTC_CHECK(
+          ToSigned(frame_length_ms_values[i].value(), &signed_frame_length_ms));
+      runtime_config.frame_length_ms = signed_frame_length_ms;
+    }
+    if (uplink_packet_loss_fraction_values[i].has_value()) {
+      float uplink_packet_loss_fraction;
+      RTC_CHECK(ParsePacketLossFractionFromProtoFormat(
+          rtc::checked_cast<uint32_t>(
+              uplink_packet_loss_fraction_values[i].value()),
+          &uplink_packet_loss_fraction));
+      runtime_config.uplink_packet_loss_fraction = uplink_packet_loss_fraction;
+    }
+    if (enable_fec_values[i].has_value()) {
+      runtime_config.enable_fec =
+          rtc::checked_cast<bool>(enable_fec_values[i].value());
+    }
+    if (enable_dtx_values[i].has_value()) {
+      runtime_config.enable_dtx =
+          rtc::checked_cast<bool>(enable_dtx_values[i].value());
+    }
+    if (num_channels_values[i].has_value()) {
+      runtime_config.num_channels =
+          rtc::checked_cast<size_t>(num_channels_values[i].value());
+    }
+    audio_network_adaptation_events_.emplace_back(
+        1000 * timestamp_ms_values[i].value(), runtime_config);
+  }
 }
 
 void ParsedRtcEventLogNew::StoreIceCandidatePairConfig(
diff --git a/logging/rtc_event_log/rtc_event_log_parser_new.h b/logging/rtc_event_log/rtc_event_log_parser_new.h
index c4293c7..d81f5f5 100644
--- a/logging/rtc_event_log/rtc_event_log_parser_new.h
+++ b/logging/rtc_event_log/rtc_event_log_parser_new.h
@@ -1013,9 +1013,8 @@
 
   // Parsing functions for new format.
   void StoreParsedNewFormatEvent(const rtclog2::EventStream& event);
-
   void StoreIncomingRtpPackets(const rtclog2::IncomingRtpPackets& proto);
-  void StoreOutgoingRtpPacket(const rtclog2::OutgoingRtpPackets& proto);
+  void StoreOutgoingRtpPackets(const rtclog2::OutgoingRtpPackets& proto);
   void StoreIncomingRtcpPackets(const rtclog2::IncomingRtcpPackets& proto);
   void StoreOutgoingRtcpPackets(const rtclog2::OutgoingRtcpPackets& proto);
   void StoreAudioPlayoutEvent(const rtclog2::AudioPlayoutEvents& proto);
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 581e791..9782107 100644
--- a/logging/rtc_event_log/rtc_event_log_unittest_helper.cc
+++ b/logging/rtc_event_log/rtc_event_log_unittest_helper.cc
@@ -12,6 +12,7 @@
 
 #include <string.h>  // memcmp
 
+#include <algorithm>
 #include <limits>
 #include <memory>
 #include <numeric>
@@ -279,7 +280,8 @@
     size_t padding_size,
     uint32_t ssrc,
     const RtpHeaderExtensionMap& extension_map,
-    RtpPacket* rtp_packet) {
+    RtpPacket* rtp_packet,
+    bool all_configured_exts) {
   constexpr int kMaxPayloadType = 127;
   rtp_packet->SetPayloadType(prng_.Rand(kMaxPayloadType));
   rtp_packet->SetMarker(prng_.Rand<bool>());
@@ -294,16 +296,30 @@
   }
   rtp_packet->SetCsrcs(csrcs);
 
-  if (extension_map.IsRegistered(TransmissionOffset::kId))
+  if (extension_map.IsRegistered(TransmissionOffset::kId) &&
+      (all_configured_exts || prng_.Rand<bool>())) {
     rtp_packet->SetExtension<TransmissionOffset>(prng_.Rand(0x00ffffff));
-  if (extension_map.IsRegistered(AudioLevel::kId))
+  }
+
+  if (extension_map.IsRegistered(AudioLevel::kId) &&
+      (all_configured_exts || prng_.Rand<bool>())) {
     rtp_packet->SetExtension<AudioLevel>(prng_.Rand<bool>(), prng_.Rand(127));
-  if (extension_map.IsRegistered(AbsoluteSendTime::kId))
+  }
+
+  if (extension_map.IsRegistered(AbsoluteSendTime::kId) &&
+      (all_configured_exts || prng_.Rand<bool>())) {
     rtp_packet->SetExtension<AbsoluteSendTime>(prng_.Rand(0x00ffffff));
-  if (extension_map.IsRegistered(VideoOrientation::kId))
+  }
+
+  if (extension_map.IsRegistered(VideoOrientation::kId) &&
+      (all_configured_exts || prng_.Rand<bool>())) {
     rtp_packet->SetExtension<VideoOrientation>(prng_.Rand(3));
-  if (extension_map.IsRegistered(TransportSequenceNumber::kId))
+  }
+
+  if (extension_map.IsRegistered(TransportSequenceNumber::kId) &&
+      (all_configured_exts || prng_.Rand<bool>())) {
     rtp_packet->SetExtension<TransportSequenceNumber>(prng_.Rand<uint16_t>());
+  }
 
   RTC_CHECK_LE(rtp_packet->headers_size() + payload_size, IP_PACKET_SIZE);
 
@@ -317,7 +333,8 @@
 
 std::unique_ptr<RtcEventRtpPacketIncoming> EventGenerator::NewRtpPacketIncoming(
     uint32_t ssrc,
-    const RtpHeaderExtensionMap& extension_map) {
+    const RtpHeaderExtensionMap& extension_map,
+    bool all_configured_exts) {
   constexpr size_t kMaxPaddingLength = 224;
   const bool padding = prng_.Rand(0, 9) == 0;  // Let padding be 10% probable.
   const size_t padding_size = !padding ? 0u : prng_.Rand(0u, kMaxPaddingLength);
@@ -338,14 +355,15 @@
 
   RtpPacketReceived rtp_packet(&extension_map);
   RandomizeRtpPacket(payload_size, padding_size, ssrc, extension_map,
-                     &rtp_packet);
+                     &rtp_packet, all_configured_exts);
 
   return absl::make_unique<RtcEventRtpPacketIncoming>(rtp_packet);
 }
 
 std::unique_ptr<RtcEventRtpPacketOutgoing> EventGenerator::NewRtpPacketOutgoing(
     uint32_t ssrc,
-    const RtpHeaderExtensionMap& extension_map) {
+    const RtpHeaderExtensionMap& extension_map,
+    bool all_configured_exts) {
   constexpr size_t kMaxPaddingLength = 224;
   const bool padding = prng_.Rand(0, 9) == 0;  // Let padding be 10% probable.
   const size_t padding_size = !padding ? 0u : prng_.Rand(0u, kMaxPaddingLength);
@@ -367,33 +385,34 @@
   RtpPacketToSend rtp_packet(&extension_map,
                              kMaxHeaderSize + payload_size + padding_size);
   RandomizeRtpPacket(payload_size, padding_size, ssrc, extension_map,
-                     &rtp_packet);
+                     &rtp_packet, all_configured_exts);
 
   int probe_cluster_id = prng_.Rand(0, 100000);
   return absl::make_unique<RtcEventRtpPacketOutgoing>(rtp_packet,
                                                       probe_cluster_id);
 }
 
-RtpHeaderExtensionMap EventGenerator::NewRtpHeaderExtensionMap() {
+RtpHeaderExtensionMap EventGenerator::NewRtpHeaderExtensionMap(
+    bool configure_all) {
   RtpHeaderExtensionMap extension_map;
   std::vector<int> id(RtpExtension::kOneByteHeaderExtensionMaxId -
                       RtpExtension::kMinId + 1);
   std::iota(id.begin(), id.end(), RtpExtension::kMinId);
   ShuffleInPlace(&prng_, rtc::ArrayView<int>(id));
 
-  if (prng_.Rand<bool>()) {
+  if (configure_all || prng_.Rand<bool>()) {
     extension_map.Register<AudioLevel>(id[0]);
   }
-  if (prng_.Rand<bool>()) {
+  if (configure_all || prng_.Rand<bool>()) {
     extension_map.Register<TransmissionOffset>(id[1]);
   }
-  if (prng_.Rand<bool>()) {
+  if (configure_all || prng_.Rand<bool>()) {
     extension_map.Register<AbsoluteSendTime>(id[2]);
   }
-  if (prng_.Rand<bool>()) {
+  if (configure_all || prng_.Rand<bool>()) {
     extension_map.Register<VideoOrientation>(id[3]);
   }
-  if (prng_.Rand<bool>()) {
+  if (configure_all || prng_.Rand<bool>()) {
     extension_map.Register<TransportSequenceNumber>(id[4]);
   }
 
@@ -508,8 +527,18 @@
             logged_event.config.frame_length_ms);
   EXPECT_EQ(original_event.config_->num_channels,
             logged_event.config.num_channels);
-  EXPECT_EQ(original_event.config_->uplink_packet_loss_fraction,
-            logged_event.config.uplink_packet_loss_fraction);
+
+  // uplink_packet_loss_fraction
+  ASSERT_EQ(original_event.config_->uplink_packet_loss_fraction.has_value(),
+            logged_event.config.uplink_packet_loss_fraction.has_value());
+  if (original_event.config_->uplink_packet_loss_fraction.has_value()) {
+    const float original =
+        original_event.config_->uplink_packet_loss_fraction.value();
+    const float logged =
+        logged_event.config.uplink_packet_loss_fraction.value();
+    const float uplink_packet_loss_fraction_delta = std::abs(original - logged);
+    EXPECT_LE(uplink_packet_loss_fraction_delta, 0.0001f);
+  }
 }
 
 void VerifyLoggedBweDelayBasedUpdate(
@@ -603,7 +632,7 @@
             logged_header.extension.hasTransmissionTimeOffset);
   if (logged_header.extension.hasTransmissionTimeOffset) {
     int32_t offset;
-    original_header.GetExtension<TransmissionOffset>(&offset);
+    ASSERT_TRUE(original_header.GetExtension<TransmissionOffset>(&offset));
     EXPECT_EQ(offset, logged_header.extension.transmissionTimeOffset);
   }
 
@@ -612,7 +641,7 @@
             logged_header.extension.hasAbsoluteSendTime);
   if (logged_header.extension.hasAbsoluteSendTime) {
     uint32_t sendtime;
-    original_header.GetExtension<AbsoluteSendTime>(&sendtime);
+    ASSERT_TRUE(original_header.GetExtension<AbsoluteSendTime>(&sendtime));
     EXPECT_EQ(sendtime, logged_header.extension.absoluteSendTime);
   }
 
@@ -621,7 +650,7 @@
             logged_header.extension.hasTransportSequenceNumber);
   if (logged_header.extension.hasTransportSequenceNumber) {
     uint16_t seqnum;
-    original_header.GetExtension<TransportSequenceNumber>(&seqnum);
+    ASSERT_TRUE(original_header.GetExtension<TransportSequenceNumber>(&seqnum));
     EXPECT_EQ(seqnum, logged_header.extension.transportSequenceNumber);
   }
 
@@ -631,7 +660,8 @@
   if (logged_header.extension.hasAudioLevel) {
     bool voice_activity;
     uint8_t audio_level;
-    original_header.GetExtension<AudioLevel>(&voice_activity, &audio_level);
+    ASSERT_TRUE(original_header.GetExtension<AudioLevel>(&voice_activity,
+                                                         &audio_level));
     EXPECT_EQ(voice_activity, logged_header.extension.voiceActivity);
     EXPECT_EQ(audio_level, logged_header.extension.audioLevel);
   }
@@ -641,7 +671,7 @@
             logged_header.extension.hasVideoRotation);
   if (logged_header.extension.hasVideoRotation) {
     uint8_t rotation;
-    original_header.GetExtension<VideoOrientation>(&rotation);
+    ASSERT_TRUE(original_header.GetExtension<VideoOrientation>(&rotation));
     EXPECT_EQ(ConvertCVOByteToVideoRotation(rotation),
               logged_header.extension.videoRotation);
   }
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 aa75ffc..d25970c 100644
--- a/logging/rtc_event_log/rtc_event_log_unittest_helper.h
+++ b/logging/rtc_event_log/rtc_event_log_unittest_helper.h
@@ -71,21 +71,32 @@
 
   std::unique_ptr<RtcEventRtcpPacketOutgoing> NewRtcpPacketOutgoing();
 
+  // |all_configured_exts| determines whether the RTP packet exhibits all
+  // configured extensions, or a random subset thereof.
   void RandomizeRtpPacket(size_t payload_size,
                           size_t padding_size,
                           uint32_t ssrc,
                           const RtpHeaderExtensionMap& extension_map,
-                          RtpPacket* rtp_packet);
+                          RtpPacket* rtp_packet,
+                          bool all_configured_exts);
 
+  // |all_configured_exts| determines whether the RTP packet exhibits all
+  // configured extensions, or a random subset thereof.
   std::unique_ptr<RtcEventRtpPacketIncoming> NewRtpPacketIncoming(
       uint32_t ssrc,
-      const RtpHeaderExtensionMap& extension_map);
+      const RtpHeaderExtensionMap& extension_map,
+      bool all_configured_exts = true);
 
+  // |all_configured_exts| determines whether the RTP packet exhibits all
+  // configured extensions, or a random subset thereof.
   std::unique_ptr<RtcEventRtpPacketOutgoing> NewRtpPacketOutgoing(
       uint32_t ssrc,
-      const RtpHeaderExtensionMap& extension_map);
+      const RtpHeaderExtensionMap& extension_map,
+      bool all_configured_exts = true);
 
-  RtpHeaderExtensionMap NewRtpHeaderExtensionMap();
+  // |configure_all| determines whether all supported extensions are configured,
+  // or a random subset.
+  RtpHeaderExtensionMap NewRtpHeaderExtensionMap(bool configure_all = false);
 
   std::unique_ptr<RtcEventAudioReceiveStreamConfig> NewAudioReceiveStreamConfig(
       uint32_t ssrc,