Generalize FEC header formatting.

- Split out reading/writing of FEC headers to classes separate
  from ForwardErrorCorrection. This makes ForwardErrorCorrection
  oblivious to what FEC header scheme is used, and lets it focus on
  encoding/decoding the FEC payloads.
- Add unit tests for FEC header readers/writers.
- Split ForwardErrorCorrection::XorPackets into XorHeaders and
  XorPayloads and reuse these functions for both encoding and
  decoding.
- Rename AttemptRecover -> AttemptRecovery in ForwardErrorCorrection.

BUG=webrtc:5654
R=danilchap@webrtc.org, stefan@webrtc.org

Review URL: https://codereview.webrtc.org/2260803002 .

Cr-Commit-Position: refs/heads/master@{#14316}
diff --git a/webrtc/modules/BUILD.gn b/webrtc/modules/BUILD.gn
index 4a565c1..72af579 100644
--- a/webrtc/modules/BUILD.gn
+++ b/webrtc/modules/BUILD.gn
@@ -446,6 +446,7 @@
       "rtp_rtcp/source/rtp_rtcp_impl_unittest.cc",
       "rtp_rtcp/source/rtp_sender_unittest.cc",
       "rtp_rtcp/source/time_util_unittest.cc",
+      "rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc",
       "rtp_rtcp/source/vp8_partition_aggregator_unittest.cc",
       "rtp_rtcp/test/testAPI/test_api.cc",
       "rtp_rtcp/test/testAPI/test_api.h",
diff --git a/webrtc/modules/rtp_rtcp/BUILD.gn b/webrtc/modules/rtp_rtcp/BUILD.gn
index b06b920..e448af0 100644
--- a/webrtc/modules/rtp_rtcp/BUILD.gn
+++ b/webrtc/modules/rtp_rtcp/BUILD.gn
@@ -149,6 +149,8 @@
     "source/time_util.h",
     "source/tmmbr_help.cc",
     "source/tmmbr_help.h",
+    "source/ulpfec_header_reader_writer.cc",
+    "source/ulpfec_header_reader_writer.h",
     "source/video_codec_information.h",
     "source/vp8_partition_aggregator.cc",
     "source/vp8_partition_aggregator.h",
diff --git a/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi b/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi
index e03d56c..d5028f1 100644
--- a/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi
+++ b/webrtc/modules/rtp_rtcp/rtp_rtcp.gypi
@@ -158,6 +158,8 @@
         'source/rtp_format_vp9.h',
         'source/rtp_format_video_generic.cc',
         'source/rtp_format_video_generic.h',
+        'source/ulpfec_header_reader_writer.cc',
+        'source/ulpfec_header_reader_writer.h',
         'source/vp8_partition_aggregator.cc',
         'source/vp8_partition_aggregator.h',
         # Mocks
diff --git a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc
index a4a6d29..c84ca5c 100644
--- a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc
+++ b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.cc
@@ -25,11 +25,12 @@
 }
 
 FecReceiverImpl::FecReceiverImpl(RtpData* callback)
-    : recovered_packet_callback_(callback) {}
+    : recovered_packet_callback_(callback),
+      fec_(ForwardErrorCorrection::CreateUlpfec()) {}
 
 FecReceiverImpl::~FecReceiverImpl() {
   received_packets_.clear();
-  fec_.ResetState(&recovered_packets_);
+  fec_->ResetState(&recovered_packets_);
 }
 
 FecPacketCounter FecReceiverImpl::GetPacketCounter() const {
@@ -211,7 +212,7 @@
       }
       crit_sect_.Enter();
     }
-    if (fec_.DecodeFec(&received_packets_, &recovered_packets_) != 0) {
+    if (fec_->DecodeFec(&received_packets_, &recovered_packets_) != 0) {
       crit_sect_.Leave();
       return -1;
     }
diff --git a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h
index 2a3b854..94eb3ac 100644
--- a/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h
+++ b/webrtc/modules/rtp_rtcp/source/fec_receiver_impl.h
@@ -11,6 +11,8 @@
 #ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_FEC_RECEIVER_IMPL_H_
 #define WEBRTC_MODULES_RTP_RTCP_SOURCE_FEC_RECEIVER_IMPL_H_
 
+#include <memory>
+
 #include "webrtc/base/criticalsection.h"
 #include "webrtc/modules/rtp_rtcp/include/fec_receiver.h"
 #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@@ -36,7 +38,7 @@
  private:
   rtc::CriticalSection crit_sect_;
   RtpData* recovered_packet_callback_;
-  ForwardErrorCorrection fec_;
+  std::unique_ptr<ForwardErrorCorrection> fec_;
   // TODO(holmer): In the current version |received_packets_| is never more
   // than one packet, since we process FEC every time a new packet
   // arrives. We should remove the list.
diff --git a/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc b/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc
index 110e7c0..d2e1ce7 100644
--- a/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc
+++ b/webrtc/modules/rtp_rtcp/source/fec_receiver_unittest.cc
@@ -39,14 +39,16 @@
 
 class ReceiverFecTest : public ::testing::Test {
  protected:
-  ReceiverFecTest() : receiver_fec_(FecReceiver::Create(&rtp_data_callback_)) {}
+  ReceiverFecTest()
+      : fec_(ForwardErrorCorrection::CreateUlpfec()),
+        receiver_fec_(FecReceiver::Create(&rtp_data_callback_)) {}
 
   void EncodeFec(ForwardErrorCorrection::PacketList* media_packets,
                  std::list<ForwardErrorCorrection::Packet*>* fec_packets,
                  unsigned int num_fec_packets) {
     uint8_t protection_factor = num_fec_packets * 255 / media_packets->size();
-    EXPECT_EQ(0, fec_.EncodeFec(*media_packets, protection_factor, 0, false,
-                                kFecMaskBursty, fec_packets));
+    EXPECT_EQ(0, fec_->EncodeFec(*media_packets, protection_factor, 0, false,
+                                 kFecMaskBursty, fec_packets));
     ASSERT_EQ(num_fec_packets, fec_packets->size());
   }
 
@@ -95,7 +97,7 @@
                                       uint8_t ulpfec_payload_type);
 
   MockRtpData rtp_data_callback_;
-  ForwardErrorCorrection fec_;
+  std::unique_ptr<ForwardErrorCorrection> fec_;
   std::unique_ptr<FecReceiver> receiver_fec_;
   FrameGenerator generator_;
 };
@@ -260,9 +262,9 @@
     GenerateFrame(1, i, &media_rtp_packets, &media_packets);
   }
   std::list<ForwardErrorCorrection::Packet*> fec_packets;
-  EXPECT_EQ(
-      -1, fec_.EncodeFec(media_packets, kNumFecPackets * 255 / kNumMediaPackets,
-                         0, false, kFecMaskBursty, &fec_packets));
+  EXPECT_EQ(-1, fec_->EncodeFec(media_packets,
+                                kNumFecPackets * 255 / kNumMediaPackets, 0,
+                                false, kFecMaskBursty, &fec_packets));
 }
 
 TEST_F(ReceiverFecTest, PacketNotDroppedTooEarly) {
diff --git a/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc b/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc
index a89b71b..3106d6d 100644
--- a/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc
+++ b/webrtc/modules/rtp_rtcp/source/forward_error_correction.cc
@@ -20,27 +20,17 @@
 #include "webrtc/base/logging.h"
 #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "webrtc/modules/rtp_rtcp/source/byte_io.h"
-#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
+#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
 
 namespace webrtc {
 
-// FEC header size in bytes.
-constexpr size_t kFecHeaderSize = 10;
-
-// ULP header size in bytes (L bit is set).
-constexpr size_t kUlpHeaderSizeLBitSet = (2 + kMaskSizeLBitSet);
-
-// ULP header size in bytes (L bit is cleared).
-constexpr size_t kUlpHeaderSizeLBitClear = (2 + kMaskSizeLBitClear);
-
+namespace {
 // Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum.
 constexpr size_t kTransportOverhead = 28;
+}  // namespace
 
-// Maximum number of media packets that can be protected.
-constexpr size_t ForwardErrorCorrection::kMaxMediaPackets;
-
-// Maximum number of FEC packets stored internally.
-constexpr size_t kMaxFecPackets = ForwardErrorCorrection::kMaxMediaPackets;
+ForwardErrorCorrection::Packet::Packet() : length(0), data(), ref_count_(0) {}
+ForwardErrorCorrection::Packet::~Packet() = default;
 
 int32_t ForwardErrorCorrection::Packet::AddRef() {
   return ++ref_count_;
@@ -65,35 +55,35 @@
   return IsNewerSequenceNumber(second->seq_num, first->seq_num);
 }
 
-ForwardErrorCorrection::ReceivedPacket::ReceivedPacket() {}
-ForwardErrorCorrection::ReceivedPacket::~ReceivedPacket() {}
+ForwardErrorCorrection::ReceivedPacket::ReceivedPacket() = default;
+ForwardErrorCorrection::ReceivedPacket::~ReceivedPacket() = default;
 
-ForwardErrorCorrection::RecoveredPacket::RecoveredPacket() {}
-ForwardErrorCorrection::RecoveredPacket::~RecoveredPacket() {}
+ForwardErrorCorrection::RecoveredPacket::RecoveredPacket() = default;
+ForwardErrorCorrection::RecoveredPacket::~RecoveredPacket() = default;
 
-ForwardErrorCorrection::ForwardErrorCorrection()
-    : generated_fec_packets_(kMaxMediaPackets), received_fec_packets_(),
-      packet_mask_(), tmp_packet_mask_() {}
-ForwardErrorCorrection::~ForwardErrorCorrection() {}
+ForwardErrorCorrection::ProtectedPacket::ProtectedPacket() = default;
+ForwardErrorCorrection::ProtectedPacket::~ProtectedPacket() = default;
 
-// Input packet
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |                    RTP Header (12 octets)                     |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |                         RTP Payload                           |
-//   |                                                               |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ForwardErrorCorrection::ReceivedFecPacket::ReceivedFecPacket() = default;
+ForwardErrorCorrection::ReceivedFecPacket::~ReceivedFecPacket() = default;
 
-// Output packet
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |                    FEC Header (10 octets)                     |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |                      FEC Level 0 Header                       |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |                     FEC Level 0 Payload                       |
-//   |                                                               |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//
+ForwardErrorCorrection::ForwardErrorCorrection(
+    std::unique_ptr<FecHeaderReader> fec_header_reader,
+    std::unique_ptr<FecHeaderWriter> fec_header_writer)
+    : fec_header_reader_(std::move(fec_header_reader)),
+      fec_header_writer_(std::move(fec_header_writer)),
+      generated_fec_packets_(fec_header_writer_->MaxFecPackets()),
+      packet_mask_size_(0) {}
+
+ForwardErrorCorrection::~ForwardErrorCorrection() = default;
+
+std::unique_ptr<ForwardErrorCorrection> ForwardErrorCorrection::CreateUlpfec() {
+  std::unique_ptr<FecHeaderReader> fec_header_reader(new UlpfecHeaderReader());
+  std::unique_ptr<FecHeaderWriter> fec_header_writer(new UlpfecHeaderWriter());
+  return std::unique_ptr<ForwardErrorCorrection>(new ForwardErrorCorrection(
+      std::move(fec_header_reader), std::move(fec_header_writer)));
+}
+
 int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
                                       uint8_t protection_factor,
                                       int num_important_packets,
@@ -107,17 +97,14 @@
   RTC_DCHECK_GE(num_important_packets, 0);
   RTC_DCHECK_LE(static_cast<size_t>(num_important_packets), num_media_packets);
   RTC_DCHECK(fec_packets->empty());
-
-  if (num_media_packets > kMaxMediaPackets) {
+  const size_t max_media_packets = fec_header_writer_->MaxMediaPackets();
+  if (num_media_packets > max_media_packets) {
     LOG(LS_WARNING) << "Can't protect " << num_media_packets
-                    << " media packets per frame. Max is " << kMaxMediaPackets
+                    << " media packets per frame. Max is " << max_media_packets
                     << ".";
     return -1;
   }
 
-  bool l_bit = (num_media_packets > 8 * kMaskSizeLBitClear);
-  int num_mask_bytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear;
-
   // Error check the media packets.
   for (const auto& media_packet : media_packets) {
     RTC_DCHECK(media_packet);
@@ -135,12 +122,11 @@
     }
   }
 
+  // Prepare generated FEC packets.
   int num_fec_packets = NumFecPackets(num_media_packets, protection_factor);
   if (num_fec_packets == 0) {
     return 0;
   }
-
-  // Prepare generated FEC packets by setting them to 0.
   for (int i = 0; i < num_fec_packets; ++i) {
     memset(generated_fec_packets_[i].data, 0, IP_PACKET_SIZE);
     // Use this as a marker for untouched packets.
@@ -149,26 +135,26 @@
   }
 
   const internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
-
-  // -- Generate packet masks --
-  memset(packet_mask_, 0, num_fec_packets * num_mask_bytes);
+  packet_mask_size_ = internal::PacketMaskSize(num_media_packets);
+  memset(packet_masks_, 0, num_fec_packets * packet_mask_size_);
   internal::GeneratePacketMasks(num_media_packets, num_fec_packets,
                                 num_important_packets, use_unequal_protection,
-                                mask_table, packet_mask_);
+                                mask_table, packet_masks_);
 
-  int num_mask_bits = InsertZerosInBitMasks(
-      media_packets, packet_mask_, num_mask_bytes, num_fec_packets);
-
+  // Adapt packet masks to missing media packets.
+  int num_mask_bits = InsertZerosInPacketMasks(media_packets, num_fec_packets);
   if (num_mask_bits < 0) {
     return -1;
   }
-  l_bit = (static_cast<size_t>(num_mask_bits) > 8 * kMaskSizeLBitClear);
-  if (l_bit) {
-    num_mask_bytes = kMaskSizeLBitSet;
-  }
+  packet_mask_size_ = internal::PacketMaskSize(num_mask_bits);
 
-  GenerateFecBitStrings(media_packets, packet_mask_, num_fec_packets, l_bit);
-  GenerateFecUlpHeaders(media_packets, packet_mask_, num_fec_packets, l_bit);
+  // Write FEC packets to |generated_fec_packets_|.
+  GenerateFecPayloads(media_packets, num_fec_packets);
+  // TODO(brandtr): Generalize this when multistream protection support is
+  // added.
+  const uint16_t seq_num_base =
+      ParseSequenceNumber(media_packets.front().get()->data);
+  FinalizeFecHeaders(num_fec_packets, seq_num_base);
 
   return 0;
 }
@@ -185,74 +171,53 @@
   return num_fec_packets;
 }
 
-void ForwardErrorCorrection::GenerateFecBitStrings(
+void ForwardErrorCorrection::GenerateFecPayloads(
     const PacketList& media_packets,
-    uint8_t* packet_mask,
-    int num_fec_packets,
-    bool l_bit) {
+    size_t num_fec_packets) {
   RTC_DCHECK(!media_packets.empty());
-  uint8_t media_payload_length[2];
-  const int num_mask_bytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear;
-  const uint16_t ulp_header_size =
-      l_bit ? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear;
-  const uint16_t fec_rtp_offset =
-      kFecHeaderSize + ulp_header_size - kRtpHeaderSize;
-
-  for (int i = 0; i < num_fec_packets; ++i) {
+  for (size_t i = 0; i < num_fec_packets; ++i) {
     Packet* const fec_packet = &generated_fec_packets_[i];
+    size_t pkt_mask_idx = i * packet_mask_size_;
+    const size_t min_packet_mask_size = fec_header_writer_->MinPacketMaskSize(
+        &packet_masks_[pkt_mask_idx], packet_mask_size_);
+    const size_t fec_header_size =
+        fec_header_writer_->FecHeaderSize(min_packet_mask_size);
+
+    size_t media_pkt_idx = 0;
     auto media_packets_it = media_packets.cbegin();
-    uint32_t pkt_mask_idx = i * num_mask_bytes;
-    uint32_t media_pkt_idx = 0;
-    uint16_t fec_packet_length = 0;
     uint16_t prev_seq_num = ParseSequenceNumber((*media_packets_it)->data);
     while (media_packets_it != media_packets.end()) {
-      // Each FEC packet has a multiple byte mask. Determine if this media
-      // packet should be included in FEC packet i.
-      if (packet_mask[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) {
-        Packet* media_packet = media_packets_it->get();
+      Packet* const media_packet = media_packets_it->get();
+      // Should |media_packet| be protected by |fec_packet|?
+      if (packet_masks_[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) {
+        size_t media_payload_length = media_packet->length - kRtpHeaderSize;
 
-        // Assign network-ordered media payload length.
-        ByteWriter<uint16_t>::WriteBigEndian(
-            media_payload_length, media_packet->length - kRtpHeaderSize);
-
-        fec_packet_length = media_packet->length + fec_rtp_offset;
-        // On the first protected packet, we don't need to XOR.
-        if (fec_packet->length == 0) {
-          // Copy the first 2 bytes of the RTP header. Note that the E and L
-          // bits are overwritten in GenerateFecUlpHeaders.
-          memcpy(&fec_packet->data[0], &media_packet->data[0], 2);
-          // Copy the 5th to 8th bytes of the RTP header (timestamp).
-          memcpy(&fec_packet->data[4], &media_packet->data[4], 4);
-          // Copy network-ordered payload size.
-          memcpy(&fec_packet->data[8], media_payload_length, 2);
-
-          // Copy RTP payload, leaving room for the ULP header.
-          memcpy(&fec_packet->data[kFecHeaderSize + ulp_header_size],
-                 &media_packet->data[kRtpHeaderSize],
-                 media_packet->length - kRtpHeaderSize);
-        } else {
-          // XOR with the first 2 bytes of the RTP header.
-          fec_packet->data[0] ^= media_packet->data[0];
-          fec_packet->data[1] ^= media_packet->data[1];
-
-          // XOR with the 5th to 8th bytes of the RTP header.
-          for (uint32_t j = 4; j < 8; ++j) {
-            fec_packet->data[j] ^= media_packet->data[j];
-          }
-
-          // XOR with the network-ordered payload size.
-          fec_packet->data[8] ^= media_payload_length[0];
-          fec_packet->data[9] ^= media_payload_length[1];
-
-          // XOR with RTP payload, leaving room for the ULP header.
-          for (int32_t j = kFecHeaderSize + ulp_header_size;
-               j < fec_packet_length; j++) {
-            fec_packet->data[j] ^= media_packet->data[j - fec_rtp_offset];
-          }
-        }
+        bool first_protected_packet = (fec_packet->length == 0);
+        size_t fec_packet_length = fec_header_size + media_payload_length;
         if (fec_packet_length > fec_packet->length) {
+          // Recall that XORing with zero (which the FEC packets are prefilled
+          // with) is the identity operator, thus all prior XORs are
+          // still correct even though we expand the packet length here.
           fec_packet->length = fec_packet_length;
         }
+        if (first_protected_packet) {
+          // Write P, X, CC, M, and PT recovery fields.
+          // Note that bits 0, 1, and 16 are overwritten in FinalizeFecHeaders.
+          memcpy(&fec_packet->data[0], &media_packet->data[0], 2);
+          // Write length recovery field. (This is a temporary location for
+          // ULPFEC.)
+          ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[2],
+                                               media_payload_length);
+          // Write timestamp recovery field.
+          memcpy(&fec_packet->data[4], &media_packet->data[4], 4);
+          // Write payload.
+          memcpy(&fec_packet->data[fec_header_size],
+                 &media_packet->data[kRtpHeaderSize], media_payload_length);
+        } else {
+          XorHeaders(*media_packet, fec_packet);
+          XorPayloads(*media_packet, media_payload_length, fec_header_size,
+                      fec_packet);
+        }
       }
       media_packets_it++;
       if (media_packets_it != media_packets.end()) {
@@ -268,47 +233,44 @@
   }
 }
 
-int ForwardErrorCorrection::InsertZerosInBitMasks(
+int ForwardErrorCorrection::InsertZerosInPacketMasks(
     const PacketList& media_packets,
-    uint8_t* packet_mask,
-    int num_mask_bytes,
-    int num_fec_packets) {
-  if (media_packets.size() <= 1) {
-    return media_packets.size();
+    size_t num_fec_packets) {
+  size_t num_media_packets = media_packets.size();
+  if (num_media_packets <= 1) {
+    return num_media_packets;
   }
-  int last_seq_num = ParseSequenceNumber(media_packets.back()->data);
-  int first_seq_num = ParseSequenceNumber(media_packets.front()->data);
-  int total_missing_seq_nums =
-      static_cast<uint16_t>(last_seq_num - first_seq_num) -
-      media_packets.size() + 1;
+  uint16_t last_seq_num = ParseSequenceNumber(media_packets.back()->data);
+  uint16_t first_seq_num = ParseSequenceNumber(media_packets.front()->data);
+  size_t total_missing_seq_nums =
+      static_cast<uint16_t>(last_seq_num - first_seq_num) - num_media_packets +
+      1;
   if (total_missing_seq_nums == 0) {
-    // All sequence numbers are covered by the packet mask. No zero insertion
-    // required.
-    return media_packets.size();
+    // All sequence numbers are covered by the packet mask.
+    // No zero insertion required.
+    return num_media_packets;
   }
-  // We can only protect 8 * kMaskSizeLBitSet packets.
-  if (total_missing_seq_nums + media_packets.size() > 8 * kMaskSizeLBitSet)
+  const size_t max_media_packets = fec_header_writer_->MaxMediaPackets();
+  if (total_missing_seq_nums + num_media_packets > max_media_packets) {
     return -1;
-  // Allocate the new mask.
-  int new_mask_bytes = kMaskSizeLBitClear;
-  if (media_packets.size() +
-      total_missing_seq_nums > 8 * kMaskSizeLBitClear) {
-    new_mask_bytes = kMaskSizeLBitSet;
   }
-  memset(tmp_packet_mask_, 0, num_fec_packets * kMaskSizeLBitSet);
+  // Allocate the new mask.
+  size_t tmp_packet_mask_size =
+      internal::PacketMaskSize(total_missing_seq_nums + num_media_packets);
+  memset(tmp_packet_masks_, 0, num_fec_packets * tmp_packet_mask_size);
 
   auto media_packets_it = media_packets.cbegin();
   uint16_t prev_seq_num = first_seq_num;
   ++media_packets_it;
 
   // Insert the first column.
-  internal::CopyColumn(tmp_packet_mask_, new_mask_bytes, packet_mask_,
-                       num_mask_bytes, num_fec_packets, 0, 0);
+  internal::CopyColumn(tmp_packet_masks_, tmp_packet_mask_size, packet_masks_,
+                       packet_mask_size_, num_fec_packets, 0, 0);
   size_t new_bit_index = 1;
   size_t old_bit_index = 1;
   // Insert zeros in the bit mask for every hole in the sequence.
   while (media_packets_it != media_packets.end()) {
-    if (new_bit_index == 8 * kMaskSizeLBitSet) {
+    if (new_bit_index == max_media_packets) {
       // We can only cover up to 48 packets.
       break;
     }
@@ -316,13 +278,13 @@
     const int num_zeros_to_insert =
         static_cast<uint16_t>(seq_num - prev_seq_num - 1);
     if (num_zeros_to_insert > 0) {
-      internal::InsertZeroColumns(num_zeros_to_insert, tmp_packet_mask_,
-                                  new_mask_bytes, num_fec_packets,
+      internal::InsertZeroColumns(num_zeros_to_insert, tmp_packet_masks_,
+                                  tmp_packet_mask_size, num_fec_packets,
                                   new_bit_index);
     }
     new_bit_index += num_zeros_to_insert;
-    internal::CopyColumn(tmp_packet_mask_, new_mask_bytes, packet_mask_,
-                         num_mask_bytes, num_fec_packets, new_bit_index,
+    internal::CopyColumn(tmp_packet_masks_, tmp_packet_mask_size, packet_masks_,
+                         packet_mask_size_, num_fec_packets, new_bit_index,
                          old_bit_index);
     ++new_bit_index;
     ++old_bit_index;
@@ -332,73 +294,22 @@
   if (new_bit_index % 8 != 0) {
     // We didn't fill the last byte. Shift bits to correct position.
     for (uint16_t row = 0; row < num_fec_packets; ++row) {
-      int new_byte_index = row * new_mask_bytes + new_bit_index / 8;
-      tmp_packet_mask_[new_byte_index] <<= (7 - (new_bit_index % 8));
+      int new_byte_index = row * tmp_packet_mask_size + new_bit_index / 8;
+      tmp_packet_masks_[new_byte_index] <<= (7 - (new_bit_index % 8));
     }
   }
   // Replace the old mask with the new.
-  memcpy(packet_mask, tmp_packet_mask_, kMaskSizeLBitSet * num_fec_packets);
+  memcpy(packet_masks_, tmp_packet_masks_,
+         num_fec_packets * tmp_packet_mask_size);
   return new_bit_index;
 }
 
-void ForwardErrorCorrection::GenerateFecUlpHeaders(
-    const PacketList& media_packets,
-    uint8_t* packet_mask,
-    int num_fec_packets,
-    bool l_bit) {
-  // -- Generate FEC and ULP headers --
-  //
-  // FEC Header, 10 bytes
-  //    0                   1                   2                   3
-  //    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  //   |E|L|P|X|  CC   |M| PT recovery |            SN base            |
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  //   |                          TS recovery                          |
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  //   |        length recovery        |
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  //
-  // ULP Header, 4 bytes (for L = 0)
-  //    0                   1                   2                   3
-  //    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  //   |       Protection Length       |             mask              |
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  //   |              mask cont. (present only when L = 1)             |
-  //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-  int num_mask_bytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear;
-  const uint16_t ulp_header_size =
-      l_bit ? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear;
-
-  RTC_DCHECK(!media_packets.empty());
-  Packet* first_media_packet = media_packets.front().get();
-  RTC_DCHECK(first_media_packet);
-  uint16_t seq_num = ParseSequenceNumber(first_media_packet->data);
-  for (int i = 0; i < num_fec_packets; ++i) {
-    Packet* const fec_packet = &generated_fec_packets_[i];
-    // -- FEC header --
-    fec_packet->data[0] &= 0x7f;  // Set E to zero.
-    if (l_bit == 0) {
-      fec_packet->data[0] &= 0xbf;  // Clear the L bit.
-    } else {
-      fec_packet->data[0] |= 0x40;  // Set the L bit.
-    }
-    // Sequence number from first media packet used as SN base.
-    // We use the same sequence number base for every FEC packet,
-    // but that's not required in general.
-    ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[2], seq_num);
-
-    // -- ULP header --
-    // Copy the payload size to the protection length field.
-    // (We protect the entire packet.)
-    ByteWriter<uint16_t>::WriteBigEndian(
-        &fec_packet->data[10],
-        fec_packet->length - kFecHeaderSize - ulp_header_size);
-
-    // Copy the packet mask.
-    memcpy(&fec_packet->data[12], &packet_mask[i * num_mask_bytes],
-           num_mask_bytes);
+void ForwardErrorCorrection::FinalizeFecHeaders(size_t num_fec_packets,
+                                                uint16_t seq_num_base) {
+  for (size_t i = 0; i < num_fec_packets; ++i) {
+    fec_header_writer_->FinalizeFecHeader(
+        seq_num_base, &packet_masks_[i * packet_mask_size_], packet_mask_size_,
+        &generated_fec_packets_[i]);
   }
 }
 
@@ -410,9 +321,8 @@
 }
 
 void ForwardErrorCorrection::InsertMediaPacket(
-    ReceivedPacket* received_packet,
-    RecoveredPacketList* recovered_packets) {
-
+    RecoveredPacketList* recovered_packets,
+    ReceivedPacket* received_packet) {
   // Search for duplicate packets.
   for (const auto& recovered_packet : *recovered_packets) {
     if (received_packet->seq_num == recovered_packet->seq_num) {
@@ -422,7 +332,6 @@
       return;
     }
   }
-
   std::unique_ptr<RecoveredPacket> recovered_packet(new RecoveredPacket());
   // This "recovered packet" was not recovered using parity packets.
   recovered_packet->was_recovered = false;
@@ -431,33 +340,32 @@
   recovered_packet->seq_num = received_packet->seq_num;
   recovered_packet->pkt = received_packet->pkt;
   recovered_packet->pkt->length = received_packet->pkt->length;
-
-  RecoveredPacket* recovered_packet_ptr = recovered_packet.get();
   // TODO(holmer): Consider replacing this with a binary search for the right
   // position, and then just insert the new packet. Would get rid of the sort.
+  RecoveredPacket* recovered_packet_ptr = recovered_packet.get();
   recovered_packets->push_back(std::move(recovered_packet));
   recovered_packets->sort(SortablePacket::LessThan());
-  UpdateCoveringFecPackets(recovered_packet_ptr);
+  UpdateCoveringFecPackets(*recovered_packet_ptr);
 }
 
-void ForwardErrorCorrection::UpdateCoveringFecPackets(RecoveredPacket* packet) {
+void ForwardErrorCorrection::UpdateCoveringFecPackets(
+    const RecoveredPacket& packet) {
   for (auto& fec_packet : received_fec_packets_) {
     // Is this FEC packet protecting the media packet |packet|?
     auto protected_it = std::lower_bound(fec_packet->protected_packets.begin(),
                                          fec_packet->protected_packets.end(),
-                                         packet,
-                                         SortablePacket::LessThan());
+                                         &packet, SortablePacket::LessThan());
     if (protected_it != fec_packet->protected_packets.end() &&
-        (*protected_it)->seq_num == packet->seq_num) {
+        (*protected_it)->seq_num == packet.seq_num) {
       // Found an FEC packet which is protecting |packet|.
-      (*protected_it)->pkt = packet->pkt;
+      (*protected_it)->pkt = packet.pkt;
     }
   }
 }
 
 void ForwardErrorCorrection::InsertFecPacket(
-    ReceivedPacket* received_packet,
-    const RecoveredPacketList* recovered_packets) {
+    const RecoveredPacketList& recovered_packets,
+    ReceivedPacket* received_packet) {
   // Check for duplicate.
   for (const auto& existing_fec_packet : received_fec_packets_) {
     if (received_packet->seq_num == existing_fec_packet->seq_num) {
@@ -466,31 +374,29 @@
       return;
     }
   }
-
   std::unique_ptr<ReceivedFecPacket> fec_packet(new ReceivedFecPacket());
   fec_packet->pkt = received_packet->pkt;
   fec_packet->seq_num = received_packet->seq_num;
   fec_packet->ssrc = received_packet->ssrc;
-
-  const uint16_t seq_num_base =
-      ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[2]);
-  const uint16_t mask_size_bytes = (fec_packet->pkt->data[0] & 0x40)
-                                      ? kMaskSizeLBitSet
-                                      : kMaskSizeLBitClear;  // L bit set?
-
-  // Parse erasure code mask from ULP header and represent as protected packets.
-  for (uint16_t byte_idx = 0; byte_idx < mask_size_bytes; ++byte_idx) {
-    uint8_t packet_mask = fec_packet->pkt->data[12 + byte_idx];
+  // Parse ULPFEC/FlexFEC header specific info.
+  bool ret = fec_header_reader_->ReadFecHeader(fec_packet.get());
+  if (!ret) {
+    LOG(LS_WARNING) << "Malformed FEC header: dropping packet.";
+    return;
+  }
+  // Parse packet mask from header and represent as protected packets.
+  for (uint16_t byte_idx = 0; byte_idx < fec_packet->packet_mask_size;
+       ++byte_idx) {
+    uint8_t packet_mask =
+        fec_packet->pkt->data[fec_packet->packet_mask_offset + byte_idx];
     for (uint16_t bit_idx = 0; bit_idx < 8; ++bit_idx) {
       if (packet_mask & (1 << (7 - bit_idx))) {
         std::unique_ptr<ProtectedPacket> protected_packet(
             new ProtectedPacket());
         // This wraps naturally with the sequence number.
-        protected_packet->seq_num =
-            static_cast<uint16_t>(seq_num_base + (byte_idx << 3) + bit_idx);
+        protected_packet->seq_num = static_cast<uint16_t>(
+            fec_packet->seq_num_base + (byte_idx << 3) + bit_idx);
         protected_packet->pkt = nullptr;
-        // Note that |protected_pkt_list| is sorted (according to sequence
-        // number) by construction.
         fec_packet->protected_packets.push_back(std::move(protected_packet));
       }
     }
@@ -499,26 +405,27 @@
     // All-zero packet mask; we can discard this FEC packet.
     LOG(LS_WARNING) << "Received FEC packet has an all-zero packet mask.";
   } else {
-    AssignRecoveredPackets(fec_packet.get(), recovered_packets);
+    AssignRecoveredPackets(recovered_packets, fec_packet.get());
     // TODO(holmer): Consider replacing this with a binary search for the right
     // position, and then just insert the new packet. Would get rid of the sort.
     //
-    // For correct decoding, |fec_packet_list_| does not necessarily
+    // For correct decoding, |received_fec_packets_| does not necessarily
     // need to be sorted by sequence number (see decoding algorithm in
-    // AttemptRecover()), but by keeping it sorted we try to recover the
-    // oldest lost packets first.
+    // AttemptRecover()). By keeping it sorted we try to recover the
+    // oldest lost packets first, however.
     received_fec_packets_.push_back(std::move(fec_packet));
     received_fec_packets_.sort(SortablePacket::LessThan());
-    if (received_fec_packets_.size() > kMaxFecPackets) {
+    const size_t max_fec_packets = fec_header_reader_->MaxFecPackets();
+    if (received_fec_packets_.size() > max_fec_packets) {
       received_fec_packets_.pop_front();
     }
-    RTC_DCHECK_LE(received_fec_packets_.size(), kMaxFecPackets);
+    RTC_DCHECK_LE(received_fec_packets_.size(), max_fec_packets);
   }
 }
 
 void ForwardErrorCorrection::AssignRecoveredPackets(
-    ReceivedFecPacket* fec_packet,
-    const RecoveredPacketList* recovered_packets) {
+    const RecoveredPacketList& recovered_packets,
+    ReceivedFecPacket* fec_packet) {
   ProtectedPacketList* protected_packets = &fec_packet->protected_packets;
   std::vector<RecoveredPacket*> recovered_protected_packets;
 
@@ -527,9 +434,9 @@
   // been recovered. Update the corresponding protected packets to point to
   // the recovered packets.
   auto it_p = protected_packets->cbegin();
-  auto it_r = recovered_packets->cbegin();
+  auto it_r = recovered_packets.cbegin();
   SortablePacket::LessThan less_than;
-  while (it_p != protected_packets->end() && it_r != recovered_packets->end()) {
+  while (it_p != protected_packets->end() && it_r != recovered_packets.end()) {
     if (less_than(*it_p, *it_r)) {
       ++it_p;
     } else if (less_than(*it_r, *it_p)) {
@@ -566,9 +473,9 @@
     }
 
     if (received_packet->is_fec) {
-      InsertFecPacket(received_packet, recovered_packets);
+      InsertFecPacket(*recovered_packets, received_packet);
     } else {
-      InsertMediaPacket(received_packet, recovered_packets);
+      InsertMediaPacket(recovered_packets, received_packet);
     }
     // Delete the received packet "wrapper".
     received_packets->pop_front();
@@ -578,137 +485,143 @@
 }
 
 bool ForwardErrorCorrection::StartPacketRecovery(
-      const ReceivedFecPacket* fec_packet,
-      RecoveredPacket* recovered_packet) {
-  // This is the first packet which we try to recover with.
-  const uint16_t ulp_header_size = fec_packet->pkt->data[0] & 0x40
-                                       ? kUlpHeaderSizeLBitSet
-                                       : kUlpHeaderSizeLBitClear;  // L bit set?
-  if (fec_packet->pkt->length <
-      static_cast<size_t>(kFecHeaderSize + ulp_header_size)) {
+    const ReceivedFecPacket& fec_packet,
+    RecoveredPacket* recovered_packet) {
+  // Sanity check packet length.
+  if (fec_packet.pkt->length < fec_packet.fec_header_size) {
     LOG(LS_WARNING)
-        << "Truncated FEC packet doesn't contain room for ULP header.";
+        << "The FEC packet is truncated: it does not contain enough room "
+        << "for its own header.";
     return false;
   }
+  // Initialize recovered packet data.
   recovered_packet->pkt = new Packet();
   memset(recovered_packet->pkt->data, 0, IP_PACKET_SIZE);
   recovered_packet->returned = false;
   recovered_packet->was_recovered = true;
-  uint16_t protection_length =
-      ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[10]);
-  if (protection_length >
-      std::min(
-          sizeof(recovered_packet->pkt->data) - kRtpHeaderSize,
-          sizeof(fec_packet->pkt->data) - kFecHeaderSize - ulp_header_size)) {
-    LOG(LS_WARNING) << "Incorrect FEC protection length, dropping.";
+  // Copy bytes corresponding to minimum RTP header size.
+  // Note that the sequence number and SSRC fields will be overwritten
+  // at the end of packet recovery.
+  memcpy(&recovered_packet->pkt->data, fec_packet.pkt->data, kRtpHeaderSize);
+  // Copy remaining FEC payload.
+  if (fec_packet.protection_length >
+      std::min(sizeof(recovered_packet->pkt->data) - kRtpHeaderSize,
+               sizeof(fec_packet.pkt->data) - fec_packet.fec_header_size)) {
+    LOG(LS_WARNING) << "Incorrect protection length, dropping FEC packet.";
     return false;
   }
-  // Copy FEC payload, skipping the ULP header.
   memcpy(&recovered_packet->pkt->data[kRtpHeaderSize],
-         &fec_packet->pkt->data[kFecHeaderSize + ulp_header_size],
-         protection_length);
-  // Copy the length recovery field.
-  memcpy(recovered_packet->length_recovery, &fec_packet->pkt->data[8], 2);
-  // Copy the first 2 bytes of the FEC header.
-  memcpy(recovered_packet->pkt->data, fec_packet->pkt->data, 2);
-  // Copy the 5th to 8th bytes of the FEC header.
-  memcpy(&recovered_packet->pkt->data[4], &fec_packet->pkt->data[4], 4);
-  // Set the SSRC field.
-  ByteWriter<uint32_t>::WriteBigEndian(&recovered_packet->pkt->data[8],
-                                       fec_packet->ssrc);
+         &fec_packet.pkt->data[fec_packet.fec_header_size],
+         fec_packet.protection_length);
   return true;
 }
 
 bool ForwardErrorCorrection::FinishPacketRecovery(
+    const ReceivedFecPacket& fec_packet,
     RecoveredPacket* recovered_packet) {
   // Set the RTP version to 2.
   recovered_packet->pkt->data[0] |= 0x80;  // Set the 1st bit.
   recovered_packet->pkt->data[0] &= 0xbf;  // Clear the 2nd bit.
-
-  // Set the SN field.
-  ByteWriter<uint16_t>::WriteBigEndian(&recovered_packet->pkt->data[2],
-                                       recovered_packet->seq_num);
-  // Recover the packet length.
+  // Recover the packet length, from temporary location.
   recovered_packet->pkt->length =
-      ByteReader<uint16_t>::ReadBigEndian(recovered_packet->length_recovery) +
+      ByteReader<uint16_t>::ReadBigEndian(&recovered_packet->pkt->data[2]) +
       kRtpHeaderSize;
   if (recovered_packet->pkt->length >
       sizeof(recovered_packet->pkt->data) - kRtpHeaderSize) {
+    LOG(LS_WARNING) << "The recovered packet had a length larger than a "
+                    << "typical IP packet, and is thus dropped.";
     return false;
   }
-
+  // Set the SN field.
+  ByteWriter<uint16_t>::WriteBigEndian(&recovered_packet->pkt->data[2],
+                                       recovered_packet->seq_num);
+  // Set the SSRC field.
+  ByteWriter<uint32_t>::WriteBigEndian(&recovered_packet->pkt->data[8],
+                                       fec_packet.protected_ssrc);
   return true;
 }
 
-void ForwardErrorCorrection::XorPackets(const Packet* src,
-                                        RecoveredPacket* dst) {
-  // XOR with the first 2 bytes of the RTP header.
-  for (uint32_t i = 0; i < 2; ++i) {
-    dst->pkt->data[i] ^= src->data[i];
-  }
-  // XOR with the 5th to 8th bytes of the RTP header.
-  for (uint32_t i = 4; i < 8; ++i) {
-    dst->pkt->data[i] ^= src->data[i];
-  }
-  // XOR with the network-ordered payload size.
-  uint8_t media_payload_length[2];
-  ByteWriter<uint16_t>::WriteBigEndian(media_payload_length,
-                                       src->length - kRtpHeaderSize);
-  dst->length_recovery[0] ^= media_payload_length[0];
-  dst->length_recovery[1] ^= media_payload_length[1];
+void ForwardErrorCorrection::XorHeaders(const Packet& src, Packet* dst) {
+  // XOR the first 2 bytes of the header: V, P, X, CC, M, PT fields.
+  dst->data[0] ^= src.data[0];
+  dst->data[1] ^= src.data[1];
 
-  // XOR with RTP payload.
-  // TODO(marpan/ajm): Are we doing more XORs than required here?
-  for (size_t i = kRtpHeaderSize; i < src->length; ++i) {
-    dst->pkt->data[i] ^= src->data[i];
+  // XOR the length recovery field.
+  uint8_t src_payload_length_network_order[2];
+  ByteWriter<uint16_t>::WriteBigEndian(src_payload_length_network_order,
+                                       src.length - kRtpHeaderSize);
+  dst->data[2] ^= src_payload_length_network_order[0];
+  dst->data[3] ^= src_payload_length_network_order[1];
+
+  // XOR the 5th to 8th bytes of the header: the timestamp field.
+  dst->data[4] ^= src.data[4];
+  dst->data[5] ^= src.data[5];
+  dst->data[6] ^= src.data[6];
+  dst->data[7] ^= src.data[7];
+
+  // Skip the 9th to 12th bytes of the header.
+}
+
+void ForwardErrorCorrection::XorPayloads(const Packet& src,
+                                         size_t payload_length,
+                                         size_t dst_offset,
+                                         Packet* dst) {
+  // XOR the payload.
+  RTC_DCHECK_LE(kRtpHeaderSize + payload_length, sizeof(src.data));
+  RTC_DCHECK_LE(dst_offset + payload_length, sizeof(dst->data));
+  for (size_t i = 0; i < payload_length; ++i) {
+    dst->data[dst_offset + i] ^= src.data[kRtpHeaderSize + i];
   }
 }
 
-bool ForwardErrorCorrection::RecoverPacket(
-    const ReceivedFecPacket* fec_packet,
-    RecoveredPacket* rec_packet_to_insert) {
-  if (!StartPacketRecovery(fec_packet, rec_packet_to_insert))
+bool ForwardErrorCorrection::RecoverPacket(const ReceivedFecPacket& fec_packet,
+                                           RecoveredPacket* recovered_packet) {
+  if (!StartPacketRecovery(fec_packet, recovered_packet)) {
     return false;
-  for (const auto& protected_packet : fec_packet->protected_packets) {
+  }
+  for (const auto& protected_packet : fec_packet.protected_packets) {
     if (protected_packet->pkt == nullptr) {
       // This is the packet we're recovering.
-      rec_packet_to_insert->seq_num = protected_packet->seq_num;
+      recovered_packet->seq_num = protected_packet->seq_num;
     } else {
-      XorPackets(protected_packet->pkt, rec_packet_to_insert);
+      XorHeaders(*protected_packet->pkt, recovered_packet->pkt);
+      XorPayloads(*protected_packet->pkt, protected_packet->pkt->length,
+                  kRtpHeaderSize, recovered_packet->pkt);
     }
   }
-  if (!FinishPacketRecovery(rec_packet_to_insert))
+  if (!FinishPacketRecovery(fec_packet, recovered_packet)) {
     return false;
+  }
   return true;
 }
 
-void ForwardErrorCorrection::AttemptRecover(
+void ForwardErrorCorrection::AttemptRecovery(
     RecoveredPacketList* recovered_packets) {
   auto fec_packet_it = received_fec_packets_.begin();
   while (fec_packet_it != received_fec_packets_.end()) {
     // Search for each FEC packet's protected media packets.
-    int packets_missing = NumCoveredPacketsMissing(fec_packet_it->get());
+    int packets_missing = NumCoveredPacketsMissing(**fec_packet_it);
 
     // We can only recover one packet with an FEC packet.
     if (packets_missing == 1) {
       // Recovery possible.
-      std::unique_ptr<RecoveredPacket> packet_to_insert(new RecoveredPacket());
-      packet_to_insert->pkt = nullptr;
-      if (!RecoverPacket(fec_packet_it->get(), packet_to_insert.get())) {
+      std::unique_ptr<RecoveredPacket> recovered_packet(new RecoveredPacket());
+      recovered_packet->pkt = nullptr;
+      if (!RecoverPacket(**fec_packet_it, recovered_packet.get())) {
         // Can't recover using this packet, drop it.
         fec_packet_it = received_fec_packets_.erase(fec_packet_it);
         continue;
       }
 
-      auto packet_to_insert_ptr = packet_to_insert.get();
+      auto recovered_packet_ptr = recovered_packet.get();
       // Add recovered packet to the list of recovered packets and update any
       // FEC packets covering this packet with a pointer to the data.
       // TODO(holmer): Consider replacing this with a binary search for the
       // right position, and then just insert the new packet. Would get rid of
       // the sort.
-      recovered_packets->push_back(std::move(packet_to_insert));
+      recovered_packets->push_back(std::move(recovered_packet));
       recovered_packets->sort(SortablePacket::LessThan());
-      UpdateCoveringFecPackets(packet_to_insert_ptr);
+      UpdateCoveringFecPackets(*recovered_packet_ptr);
       DiscardOldRecoveredPackets(recovered_packets);
       fec_packet_it = received_fec_packets_.erase(fec_packet_it);
 
@@ -727,9 +640,9 @@
 }
 
 int ForwardErrorCorrection::NumCoveredPacketsMissing(
-    const ReceivedFecPacket* fec_packet) {
+    const ReceivedFecPacket& fec_packet) {
   int packets_missing = 0;
-  for (const auto& protected_packet : fec_packet->protected_packets) {
+  for (const auto& protected_packet : fec_packet.protected_packets) {
     if (protected_packet->pkt == nullptr) {
       ++packets_missing;
       if (packets_missing > 1) {
@@ -742,37 +655,80 @@
 
 void ForwardErrorCorrection::DiscardOldRecoveredPackets(
     RecoveredPacketList* recovered_packets) {
-  while (recovered_packets->size() > kMaxMediaPackets) {
+  const size_t max_media_packets = fec_header_reader_->MaxMediaPackets();
+  while (recovered_packets->size() > max_media_packets) {
     recovered_packets->pop_front();
   }
-  RTC_DCHECK_LE(recovered_packets->size(), kMaxMediaPackets);
+  RTC_DCHECK_LE(recovered_packets->size(), max_media_packets);
 }
 
 uint16_t ForwardErrorCorrection::ParseSequenceNumber(uint8_t* packet) {
   return (packet[2] << 8) + packet[3];
 }
 
+uint32_t ForwardErrorCorrection::ParseSsrc(uint8_t* packet) {
+  return (packet[8] << 24) + (packet[9] << 16) + (packet[10] << 8) + packet[11];
+}
+
 int ForwardErrorCorrection::DecodeFec(
     ReceivedPacketList* received_packets,
     RecoveredPacketList* recovered_packets) {
   // TODO(marpan/ajm): can we check for multiple ULP headers, and return an
   // error?
-  if (recovered_packets->size() == kMaxMediaPackets) {
+  const size_t max_media_packets = fec_header_reader_->MaxMediaPackets();
+  if (recovered_packets->size() == max_media_packets) {
     const unsigned int seq_num_diff =
         abs(static_cast<int>(received_packets->front()->seq_num) -
             static_cast<int>(recovered_packets->back()->seq_num));
-    if (seq_num_diff > kMaxMediaPackets) {
+    if (seq_num_diff > max_media_packets) {
       // A big gap in sequence numbers. The old recovered packets
       // are now useless, so it's safe to do a reset.
       ResetState(recovered_packets);
     }
   }
   InsertPackets(received_packets, recovered_packets);
-  AttemptRecover(recovered_packets);
+  AttemptRecovery(recovered_packets);
   return 0;
 }
 
 size_t ForwardErrorCorrection::MaxPacketOverhead() const {
-  return kFecHeaderSize + kUlpHeaderSizeLBitSet;
+  return fec_header_writer_->MaxPacketOverhead();
 }
+
+FecHeaderReader::FecHeaderReader(size_t max_media_packets,
+                                 size_t max_fec_packets)
+    : max_media_packets_(max_media_packets),
+      max_fec_packets_(max_fec_packets) {}
+
+FecHeaderReader::~FecHeaderReader() = default;
+
+size_t FecHeaderReader::MaxMediaPackets() const {
+  return max_media_packets_;
+}
+
+size_t FecHeaderReader::MaxFecPackets() const {
+  return max_fec_packets_;
+}
+
+FecHeaderWriter::FecHeaderWriter(size_t max_media_packets,
+                                 size_t max_fec_packets,
+                                 size_t max_packet_overhead)
+    : max_media_packets_(max_media_packets),
+      max_fec_packets_(max_fec_packets),
+      max_packet_overhead_(max_packet_overhead) {}
+
+FecHeaderWriter::~FecHeaderWriter() = default;
+
+size_t FecHeaderWriter::MaxMediaPackets() const {
+  return max_media_packets_;
+}
+
+size_t FecHeaderWriter::MaxFecPackets() const {
+  return max_fec_packets_;
+}
+
+size_t FecHeaderWriter::MaxPacketOverhead() const {
+  return max_packet_overhead_;
+}
+
 }  // namespace webrtc
diff --git a/webrtc/modules/rtp_rtcp/source/forward_error_correction.h b/webrtc/modules/rtp_rtcp/source/forward_error_correction.h
index 21f2afb..a0600f0 100644
--- a/webrtc/modules/rtp_rtcp/source/forward_error_correction.h
+++ b/webrtc/modules/rtp_rtcp/source/forward_error_correction.h
@@ -17,31 +17,32 @@
 #include <memory>
 #include <vector>
 
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/constructormagic.h"
 #include "webrtc/base/refcount.h"
 #include "webrtc/base/scoped_ref_ptr.h"
 #include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
-#include "webrtc/typedefs.h"
 
 namespace webrtc {
 
+class FecHeaderReader;
+class FecHeaderWriter;
+
 // Performs codec-independent forward error correction (FEC), based on RFC 5109.
 // Option exists to enable unequal protection (UEP) across packets.
 // This is not to be confused with protection within packets
 // (referred to as uneven level protection (ULP) in RFC 5109).
 class ForwardErrorCorrection {
  public:
-  // Maximum number of media packets we can protect
-  static constexpr size_t kMaxMediaPackets = 48u;
-
   // TODO(holmer): As a next step all these struct-like packet classes should be
   // refactored into proper classes, and their members should be made private.
   // This will require parts of the functionality in forward_error_correction.cc
   // and receiver_fec.cc to be refactored into the packet classes.
   class Packet {
    public:
-    Packet() : length(0), data(), ref_count_(0) {}
-    virtual ~Packet() {}
+    Packet();
+    virtual ~Packet();
 
     // Add a reference.
     virtual int32_t AddRef();
@@ -89,7 +90,7 @@
     rtc::scoped_refptr<Packet> pkt;  // Pointer to the packet storage.
   };
 
-  // The recovered list parameter of #DecodeFec() references structs of
+  // The recovered list parameter of DecodeFec() references structs of
   // this type.
   // TODO(holmer): Refactor into a proper class.
   class RecoveredPacket : public SortablePacket {
@@ -102,19 +103,55 @@
                          // through the received packet list.
     bool returned;  // True when the packet already has been returned to the
                     // caller through the callback.
-    uint8_t length_recovery[2];  // Two bytes used for recovering the packet
-                                 // length with XOR operations.
     rtc::scoped_refptr<Packet> pkt;  // Pointer to the packet storage.
   };
 
+  // Used to link media packets to their protecting FEC packets.
+  //
+  // TODO(holmer): Refactor into a proper class.
+  class ProtectedPacket : public ForwardErrorCorrection::SortablePacket {
+   public:
+    ProtectedPacket();
+    ~ProtectedPacket();
+
+    rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
+  };
+
+  using ProtectedPacketList = std::list<std::unique_ptr<ProtectedPacket>>;
+
+  // Used for internal storage of received FEC packets in a list.
+  //
+  // TODO(holmer): Refactor into a proper class.
+  class ReceivedFecPacket : public ForwardErrorCorrection::SortablePacket {
+   public:
+    ReceivedFecPacket();
+    ~ReceivedFecPacket();
+
+    // List of media packets that this FEC packet protects.
+    ProtectedPacketList protected_packets;
+    // RTP header fields.
+    uint32_t ssrc;
+    // FEC header fields.
+    size_t fec_header_size;
+    uint32_t protected_ssrc;
+    uint16_t seq_num_base;
+    size_t packet_mask_offset;  // Relative start of FEC header.
+    size_t packet_mask_size;
+    size_t protection_length;
+    // Raw data.
+    rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
+  };
+
   using PacketList = std::list<std::unique_ptr<Packet>>;
   using ReceivedPacketList = std::list<std::unique_ptr<ReceivedPacket>>;
   using RecoveredPacketList = std::list<std::unique_ptr<RecoveredPacket>>;
+  using ReceivedFecPacketList = std::list<std::unique_ptr<ReceivedFecPacket>>;
 
-  ForwardErrorCorrection();
-  virtual ~ForwardErrorCorrection();
+  ~ForwardErrorCorrection();
 
-  //
+  // Creates a ForwardErrorCorrection tailored for a specific FEC scheme.
+  static std::unique_ptr<ForwardErrorCorrection> CreateUlpfec();
+
   // Generates a list of FEC packets from supplied media packets.
   //
   // Input:  media_packets          List of media packets to protect, of type
@@ -158,7 +195,6 @@
                 FecMaskType fec_mask_type,
                 std::list<Packet*>* fec_packets);
 
-  //
   // Decodes a list of received media and FEC packets. It will parse the
   // |received_packets|, storing FEC packets internally, and move
   // media packets to |recovered_packets|. The recovered list will be
@@ -198,47 +234,32 @@
   // Frees all memory allocated by this class.
   void ResetState(RecoveredPacketList* recovered_packets);
 
+  // TODO(brandtr): Remove these functions when the Packet classes
+  // have been refactored.
+  static uint16_t ParseSequenceNumber(uint8_t* packet);
+  static uint32_t ParseSsrc(uint8_t* packet);
+
+ protected:
+  ForwardErrorCorrection(std::unique_ptr<FecHeaderReader> fec_header_reader,
+                         std::unique_ptr<FecHeaderWriter> fec_header_writer);
+
  private:
-  // Used to link media packets to their protecting FEC packets.
-  //
-  // TODO(holmer): Refactor into a proper class.
-  class ProtectedPacket : public ForwardErrorCorrection::SortablePacket {
-   public:
-    rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
-  };
-
-  using ProtectedPacketList = std::list<std::unique_ptr<ProtectedPacket>>;
-
-  // Used for internal storage of received FEC packets in a list.
-  //
-  // TODO(holmer): Refactor into a proper class.
-  class ReceivedFecPacket : public ForwardErrorCorrection::SortablePacket {
-   public:
-    ProtectedPacketList protected_packets;
-    uint32_t ssrc;  // SSRC of the current frame.
-    rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
-  };
-
-  using ReceivedFecPacketList = std::list<std::unique_ptr<ReceivedFecPacket>>;
-
   // Analyzes |media_packets| for holes in the sequence and inserts zero columns
   // into the |packet_mask| where those holes are found. Zero columns means that
   // those packets will have no protection.
   // Returns the number of bits used for one row of the new packet mask.
   // Requires that |packet_mask| has at least 6 * |num_fec_packets| bytes
   // allocated.
-  int InsertZerosInBitMasks(const PacketList& media_packets,
-                            uint8_t* packet_mask, int num_mask_bytes,
-                            int num_fec_packets);
+  int InsertZerosInPacketMasks(const PacketList& media_packets,
+                               size_t num_fec_packets);
 
+  // Writes FEC payloads and some recovery fields in the FEC headers.
+  void GenerateFecPayloads(const PacketList& media_packets,
+                           size_t num_fec_packets);
 
-  void GenerateFecUlpHeaders(const PacketList& media_packets,
-                             uint8_t* packet_mask, int num_fec_packets,
-                             bool l_bit);
-
-  void GenerateFecBitStrings(const PacketList& media_packets,
-                             uint8_t* packet_mask, int num_fec_packets,
-                             bool l_bit);
+  // Writes the FEC header fields that are not written by GenerateFecPayloads.
+  // This includes writing the packet masks.
+  void FinalizeFecHeaders(size_t num_fec_packets, uint16_t seq_num_base);
 
   // Inserts the |received_packets| into the internal received FEC packet list
   // or into |recovered_packets|.
@@ -246,67 +267,145 @@
                      RecoveredPacketList* recovered_packets);
 
   // Inserts the |received_packet| into |recovered_packets|. Deletes duplicates.
-  void InsertMediaPacket(ReceivedPacket* received_packet,
-                         RecoveredPacketList* recovered_packets);
+  void InsertMediaPacket(RecoveredPacketList* recovered_packets,
+                         ReceivedPacket* received_packet);
 
   // Assigns pointers to the recovered packet from all FEC packets which cover
   // it.
   // Note: This reduces the complexity when we want to try to recover a packet
   // since we don't have to find the intersection between recovered packets and
   // packets covered by the FEC packet.
-  void UpdateCoveringFecPackets(RecoveredPacket* packet);
+  void UpdateCoveringFecPackets(const RecoveredPacket& packet);
 
   // Insert |received_packet| into internal FEC list. Deletes duplicates.
-  void InsertFecPacket(ReceivedPacket* received_packet,
-                       const RecoveredPacketList* recovered_packets);
+  void InsertFecPacket(const RecoveredPacketList& recovered_packets,
+                       ReceivedPacket* received_packet);
 
   // Assigns pointers to already recovered packets covered by |fec_packet|.
   static void AssignRecoveredPackets(
-      ReceivedFecPacket* fec_packet,
-      const RecoveredPacketList* recovered_packets);
-
-  // Insert |rec_packet_to_insert| into |recovered_packets| in correct position.
-  void InsertRecoveredPacket(RecoveredPacket* rec_packet_to_insert,
-                             RecoveredPacketList* recovered_packets);
+      const RecoveredPacketList& recovered_packets,
+      ReceivedFecPacket* fec_packet);
 
   // Attempt to recover missing packets, using the internally stored
   // received FEC packets.
-  void AttemptRecover(RecoveredPacketList* recovered_packets);
+  void AttemptRecovery(RecoveredPacketList* recovered_packets);
 
-  // Initializes packet recovery using the received |fec_packet|.
-  static bool StartPacketRecovery(const ReceivedFecPacket* fec_packet,
+  // Initializes headers and payload before the XOR operation
+  // that recovers a packet.
+  static bool StartPacketRecovery(const ReceivedFecPacket& fec_packet,
                                   RecoveredPacket* recovered_packet);
 
-  // Performs XOR between |src| and |dst| and stores the result in |dst|.
-  static void XorPackets(const Packet* src, RecoveredPacket* dst);
+  // Performs XOR between the first 8 bytes of |src| and |dst| and stores
+  // the result in |dst|. The 3rd and 4th bytes are used for storing
+  // the length recovery field.
+  static void XorHeaders(const Packet& src, Packet* dst);
 
-  // Finish up the recovery of a packet.
-  static bool FinishPacketRecovery(RecoveredPacket* recovered_packet);
+  // Performs XOR between the payloads of |src| and |dst| and stores the result
+  // in |dst|. The parameter |dst_offset| determines at  what byte the
+  // XOR operation starts in |dst|. In total, |payload_length| bytes are XORed.
+  static void XorPayloads(const Packet& src,
+                          size_t payload_length,
+                          size_t dst_offset,
+                          Packet* dst);
+
+  // Finalizes recovery of packet by setting RTP header fields.
+  // This is not specific to the FEC scheme used.
+  static bool FinishPacketRecovery(const ReceivedFecPacket& fec_packet,
+                                   RecoveredPacket* recovered_packet);
 
   // Recover a missing packet.
-  bool RecoverPacket(const ReceivedFecPacket* fec_packet,
-                     RecoveredPacket* rec_packet_to_insert);
+  static bool RecoverPacket(const ReceivedFecPacket& fec_packet,
+                            RecoveredPacket* recovered_packet);
 
   // Get the number of missing media packets which are covered by |fec_packet|.
   // An FEC packet can recover at most one packet, and if zero packets are
   // missing the FEC packet can be discarded. This function returns 2 when two
   // or more packets are missing.
-  static int NumCoveredPacketsMissing(const ReceivedFecPacket* fec_packet);
+  static int NumCoveredPacketsMissing(const ReceivedFecPacket& fec_packet);
 
   // Discards old packets in |recovered_packets|, which are no longer relevant
   // for recovering lost packets.
-  static void DiscardOldRecoveredPackets(
-      RecoveredPacketList* recovered_packets);
-  static uint16_t ParseSequenceNumber(uint8_t* packet);
+  void DiscardOldRecoveredPackets(RecoveredPacketList* recovered_packets);
+
+  std::unique_ptr<FecHeaderReader> fec_header_reader_;
+  std::unique_ptr<FecHeaderWriter> fec_header_writer_;
 
   std::vector<Packet> generated_fec_packets_;
   ReceivedFecPacketList received_fec_packets_;
 
   // Arrays used to avoid dynamically allocating memory when generating
-  // the packet masks in the ULPFEC headers.
-  // (There are never more than |kMaxMediaPackets| FEC packets generated.)
-  uint8_t packet_mask_[kMaxMediaPackets * kMaskSizeLBitSet];
-  uint8_t tmp_packet_mask_[kMaxMediaPackets * kMaskSizeLBitSet];
+  // the packet masks.
+  // (There are never more than |kUlpfecMaxMediaPackets| FEC packets generated.)
+  uint8_t packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize];
+  uint8_t tmp_packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize];
+  size_t packet_mask_size_;
 };
+
+// Classes derived from FecHeader{Reader,Writer} encapsulate the
+// specifics of reading and writing FEC header for, e.g., ULPFEC
+// and FlexFEC.
+class FecHeaderReader {
+ public:
+  virtual ~FecHeaderReader();
+
+  // The maximum number of media packets that can be covered by one FEC packet.
+  size_t MaxMediaPackets() const;
+
+  // The maximum number of FEC packets that is supported, per call
+  // to ForwardErrorCorrection::EncodeFec().
+  size_t MaxFecPackets() const;
+
+  // Parses FEC header and stores information in ReceivedFecPacket members.
+  virtual bool ReadFecHeader(
+      ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const = 0;
+
+ protected:
+  FecHeaderReader(size_t max_media_packets, size_t max_fec_packets);
+
+  const size_t max_media_packets_;
+  const size_t max_fec_packets_;
+};
+
+class FecHeaderWriter {
+ public:
+  virtual ~FecHeaderWriter();
+
+  // The maximum number of media packets that can be covered by one FEC packet.
+  size_t MaxMediaPackets() const;
+
+  // The maximum number of FEC packets that is supported, per call
+  // to ForwardErrorCorrection::EncodeFec().
+  size_t MaxFecPackets() const;
+
+  // The maximum overhead (in bytes) per packet, due to FEC headers.
+  size_t MaxPacketOverhead() const;
+
+  // Calculates the minimum packet mask size needed (in bytes),
+  // given the discrete options of the ULPFEC masks and the bits
+  // set in the current packet mask.
+  virtual size_t MinPacketMaskSize(const uint8_t* packet_mask,
+                                   size_t packet_mask_size) const = 0;
+
+  // The header size (in bytes), given the packet mask size.
+  virtual size_t FecHeaderSize(size_t packet_mask_size) const = 0;
+
+  // Writes FEC header.
+  virtual void FinalizeFecHeader(
+      uint16_t seq_num_base,
+      const uint8_t* packet_mask,
+      size_t packet_mask_size,
+      ForwardErrorCorrection::Packet* fec_packet) const = 0;
+
+ protected:
+  FecHeaderWriter(size_t max_media_packets,
+                  size_t max_fec_packets,
+                  size_t max_packet_overhead);
+
+  const size_t max_media_packets_;
+  const size_t max_fec_packets_;
+  const size_t max_packet_overhead_;
+};
+
 }  // namespace webrtc
+
 #endif  // WEBRTC_MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_H_
diff --git a/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc b/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc
index 790e705..faef771 100644
--- a/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc
+++ b/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.cc
@@ -15,6 +15,7 @@
 
 #include <algorithm>
 
+#include "webrtc/base/checks.h"
 #include "webrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.h"
 #include "webrtc/modules/rtp_rtcp/source/fec_private_tables_random.h"
 
@@ -202,11 +203,8 @@
   if (mode == kModeNoOverlap) {
     // sub_mask21
 
-    const int l_bit =
-        (num_media_packets - num_fec_for_imp_packets) > 16 ? 1 : 0;
-
     const int res_mask_bytes =
-        (l_bit == 1) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
+        PacketMaskSize(num_media_packets - num_fec_for_imp_packets);
 
     const uint8_t* packet_mask_sub_21 =
         mask_table.fec_packet_mask_table()[num_media_packets -
@@ -245,9 +243,7 @@
                                int num_mask_bytes,
                                uint8_t* packet_mask,
                                const PacketMaskTable& mask_table) {
-  const int l_bit = num_imp_packets > 16 ? 1 : 0;
-  const int num_imp_mask_bytes =
-      (l_bit == 1) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
+  const int num_imp_mask_bytes = PacketMaskSize(num_imp_packets);
 
   // Get sub_mask1 from table
   const uint8_t* packet_mask_sub_1 =
@@ -375,9 +371,7 @@
   assert(num_fec_packets <= num_media_packets && num_fec_packets > 0);
   assert(num_imp_packets <= num_media_packets && num_imp_packets >= 0);
 
-  int l_bit = num_media_packets > 16 ? 1 : 0;
-  const int num_mask_bytes =
-      (l_bit == 1) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
+  const int num_mask_bytes = PacketMaskSize(num_media_packets);
 
   // Equal-protection for these cases.
   if (!use_unequal_protection || num_imp_packets == 0) {
@@ -394,6 +388,14 @@
   }  // End of UEP modification
 }  // End of GetPacketMasks
 
+size_t PacketMaskSize(size_t num_sequence_numbers) {
+  RTC_DCHECK_LE(num_sequence_numbers, 8 * kUlpfecPacketMaskSizeLBitSet);
+  if (num_sequence_numbers > 8 * kUlpfecPacketMaskSizeLBitClear) {
+    return kUlpfecPacketMaskSizeLBitSet;
+  }
+  return kUlpfecPacketMaskSizeLBitClear;
+}
+
 void InsertZeroColumns(int num_zeros,
                        uint8_t* new_mask,
                        int new_mask_bytes,
diff --git a/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h b/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h
index 82f02c2..c61aea8 100644
--- a/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h
+++ b/webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h
@@ -16,10 +16,17 @@
 
 namespace webrtc {
 
-// Packet mask size in bytes (L bit is set).
-constexpr size_t kMaskSizeLBitSet = 6;
-// Packet mask size in bytes (L bit is cleared).
-constexpr size_t kMaskSizeLBitClear = 2;
+// Maximum number of media packets that can be protected
+// by these packet masks.
+constexpr size_t kUlpfecMaxMediaPackets = 48;
+
+// Packet mask size in bytes (given L bit).
+constexpr size_t kUlpfecPacketMaskSizeLBitClear = 2;
+constexpr size_t kUlpfecPacketMaskSizeLBitSet = 6;
+
+// Convenience constants.
+constexpr size_t kUlpfecMinPacketMaskSize = kUlpfecPacketMaskSizeLBitClear;
+constexpr size_t kUlpfecMaxPacketMaskSize = kUlpfecPacketMaskSizeLBitSet;
 
 namespace internal {
 
@@ -65,6 +72,10 @@
                          const PacketMaskTable& mask_table,
                          uint8_t* packet_mask);
 
+// Returns the required packet mask size, given the number of sequence numbers
+// that will be covered.
+size_t PacketMaskSize(size_t num_sequence_numbers);
+
 // Inserts |num_zeros| zero columns into |new_mask| at position
 // |new_bit_index|. If the current byte of |new_mask| can't fit all zeros, the
 // byte will be filled with zeros from |new_bit_index|, but the next byte will
diff --git a/webrtc/modules/rtp_rtcp/source/producer_fec.cc b/webrtc/modules/rtp_rtcp/source/producer_fec.cc
index 06f1540..cdd898d 100644
--- a/webrtc/modules/rtp_rtcp/source/producer_fec.cc
+++ b/webrtc/modules/rtp_rtcp/source/producer_fec.cc
@@ -93,9 +93,10 @@
 }
 
 ProducerFec::ProducerFec()
-    : num_protected_frames_(0),
+    : fec_(ForwardErrorCorrection::CreateUlpfec()),
+      num_protected_frames_(0),
       num_important_packets_(0),
-      min_num_media_packets_(1)  {
+      min_num_media_packets_(1) {
   memset(&params_, 0, sizeof(params_));
   memset(&new_params_, 0, sizeof(new_params_));
 }
@@ -124,9 +125,8 @@
   // protection in 'unequal protection mode') cannot exceed kMaxMediaPackets.
   RTC_DCHECK_GE(params->fec_rate, 0);
   RTC_DCHECK_LE(params->fec_rate, 255);
-  if (num_important_packets >
-      static_cast<int>(ForwardErrorCorrection::kMaxMediaPackets)) {
-    num_important_packets = ForwardErrorCorrection::kMaxMediaPackets;
+  if (num_important_packets > static_cast<int>(kUlpfecMaxMediaPackets)) {
+    num_important_packets = kUlpfecMaxMediaPackets;
   }
   // Store the new params and apply them for the next set of FEC packets being
   // produced.
@@ -148,8 +148,8 @@
   }
   bool complete_frame = false;
   const bool marker_bit = (data_buffer[1] & kRtpMarkerBitMask) ? true : false;
-  if (media_packets_.size() < ForwardErrorCorrection::kMaxMediaPackets) {
-    // Generic FEC can only protect up to |kMaxMediaPackets| packets.
+  if (media_packets_.size() < kUlpfecMaxMediaPackets) {
+    // Generic FEC can only protect up to |kUlpfecMaxMediaPackets| packets.
     std::unique_ptr<ForwardErrorCorrection::Packet> packet(
         new ForwardErrorCorrection::Packet());
     packet->length = payload_length + rtp_header_length;
@@ -168,16 +168,16 @@
       (num_protected_frames_ == params_.max_fec_frames ||
        (ExcessOverheadBelowMax() && MinimumMediaPacketsReached()))) {
     RTC_DCHECK_LE(num_important_packets_,
-                  static_cast<int>(ForwardErrorCorrection::kMaxMediaPackets));
+                  static_cast<int>(kUlpfecMaxMediaPackets));
     // TODO(pbos): Consider whether unequal protection should be enabled or not,
     // it is currently always disabled.
     //
     // Since unequal protection is disabled, the value of
     // |num_important_packets_| has no importance when calling GenerateFec().
     constexpr bool kUseUnequalProtection = false;
-    int ret = fec_.EncodeFec(media_packets_, params_.fec_rate,
-                             num_important_packets_, kUseUnequalProtection,
-                             params_.fec_mask_type, &generated_fec_packets_);
+    int ret = fec_->EncodeFec(media_packets_, params_.fec_rate,
+                              num_important_packets_, kUseUnequalProtection,
+                              params_.fec_mask_type, &generated_fec_packets_);
     if (generated_fec_packets_.empty()) {
       num_protected_frames_ = 0;
       DeleteMediaPackets();
@@ -213,7 +213,7 @@
 }
 
 size_t ProducerFec::MaxPacketOverhead() const {
-  return fec_.MaxPacketOverhead();
+  return fec_->MaxPacketOverhead();
 }
 
 std::vector<std::unique_ptr<RedPacket>> ProducerFec::GetFecPacketsAsRed(
@@ -238,15 +238,12 @@
     red_packet->SetSeqNum(seq_num++);
     red_packet->ClearMarkerBit();
     red_packet->AssignPayload(fec_packet->data, fec_packet->length);
-
     red_packets.push_back(std::move(red_packet));
   }
-
   // Reset state.
   DeleteMediaPackets();
   generated_fec_packets_.clear();
   num_protected_frames_ = 0;
-
   return red_packets;
 }
 
@@ -257,7 +254,7 @@
   // generation is implemented.
   RTC_DCHECK(!media_packets_.empty());
   int num_fec_packets =
-      fec_.NumFecPackets(media_packets_.size(), params_.fec_rate);
+      fec_->NumFecPackets(media_packets_.size(), params_.fec_rate);
   // Return the overhead in Q8.
   return (num_fec_packets << 8) / media_packets_.size();
 }
diff --git a/webrtc/modules/rtp_rtcp/source/producer_fec.h b/webrtc/modules/rtp_rtcp/source/producer_fec.h
index f09f181..7ea0740 100644
--- a/webrtc/modules/rtp_rtcp/source/producer_fec.h
+++ b/webrtc/modules/rtp_rtcp/source/producer_fec.h
@@ -89,7 +89,7 @@
  private:
   void DeleteMediaPackets();
   int Overhead() const;
-  ForwardErrorCorrection fec_;
+  std::unique_ptr<ForwardErrorCorrection> fec_;
   ForwardErrorCorrection::PacketList media_packets_;
   std::list<ForwardErrorCorrection::Packet*> generated_fec_packets_;
   int num_protected_frames_;
diff --git a/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc b/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc
index 9f6dee2..1334cb7 100644
--- a/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc
+++ b/webrtc/modules/rtp_rtcp/source/rtp_fec_unittest.cc
@@ -18,6 +18,7 @@
 #include "webrtc/modules/rtp_rtcp/source/byte_io.h"
 #include "webrtc/modules/rtp_rtcp/source/fec_test_helper.h"
 #include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h"
+#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
 
 namespace webrtc {
 
@@ -46,6 +47,15 @@
 
 using ::testing::Types;
 
+// Subclass ForwardErrorCorrection to use gTest typed tests.
+class UlpfecForwardErrorCorrection : public ForwardErrorCorrection {
+ public:
+  UlpfecForwardErrorCorrection()
+      : ForwardErrorCorrection(
+            std::unique_ptr<FecHeaderReader>(new UlpfecHeaderReader()),
+            std::unique_ptr<FecHeaderWriter>(new UlpfecHeaderWriter())) {}
+};
+
 template <typename ForwardErrorCorrectionType>
 class RtpFecTest : public ::testing::Test {
  protected:
@@ -86,15 +96,15 @@
   ForwardErrorCorrection::ReceivedPacketList received_packets_;
   ForwardErrorCorrection::RecoveredPacketList recovered_packets_;
 
-  int media_loss_mask_[ForwardErrorCorrection::kMaxMediaPackets];
-  int fec_loss_mask_[ForwardErrorCorrection::kMaxMediaPackets];
+  int media_loss_mask_[kUlpfecMaxMediaPackets];
+  int fec_loss_mask_[kUlpfecMaxMediaPackets];
 };
 
 // Define gTest typed test to loop over both ULPFEC and FlexFEC.
 // Since the tests now are parameterized, we need to access
 // member variables using |this|, thereby enforcing runtime
 // resolution.
-using FecTypes = Types<ForwardErrorCorrection>;
+using FecTypes = Types<UlpfecForwardErrorCorrection>;
 TYPED_TEST_CASE(RtpFecTest, FecTypes);
 
 TYPED_TEST(RtpFecTest, FecRecoveryNoLoss) {
diff --git a/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc b/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc
new file mode 100644
index 0000000..2913482
--- /dev/null
+++ b/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc
@@ -0,0 +1,130 @@
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
+
+#include <utility>
+
+#include "webrtc/base/checks.h"
+#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
+#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
+
+namespace webrtc {
+
+namespace {
+
+// Maximum number of media packets that can be protected in one batch.
+constexpr size_t kMaxMediaPackets = 48;
+
+// Maximum number of FEC packets stored inside ForwardErrorCorrection.
+constexpr size_t kMaxFecPackets = kMaxMediaPackets;
+
+// FEC Level 0 header size in bytes.
+constexpr size_t kFecLevel0HeaderSize = 10;
+
+// FEC Level 1 (ULP) header size in bytes (L bit is set).
+constexpr size_t kFecLevel1HeaderSizeLBitSet = 2 + kUlpfecPacketMaskSizeLBitSet;
+
+// FEC Level 1 (ULP) header size in bytes (L bit is cleared).
+constexpr size_t kFecLevel1HeaderSizeLBitClear =
+    2 + kUlpfecPacketMaskSizeLBitClear;
+
+constexpr size_t kPacketMaskOffset = kFecLevel0HeaderSize + 2;
+
+size_t UlpfecHeaderSize(size_t packet_mask_size) {
+  RTC_DCHECK_LE(packet_mask_size, kUlpfecPacketMaskSizeLBitSet);
+  if (packet_mask_size <= kUlpfecPacketMaskSizeLBitClear) {
+    return kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitClear;
+  } else {
+    return kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitSet;
+  }
+}
+
+}  // namespace
+
+UlpfecHeaderReader::UlpfecHeaderReader()
+    : FecHeaderReader(kMaxMediaPackets, kMaxFecPackets) {}
+
+UlpfecHeaderReader::~UlpfecHeaderReader() = default;
+
+bool UlpfecHeaderReader::ReadFecHeader(
+    ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const {
+  bool l_bit = (fec_packet->pkt->data[0] & 0x40) != 0u;
+  size_t packet_mask_size =
+      l_bit ? kUlpfecPacketMaskSizeLBitSet : kUlpfecPacketMaskSizeLBitClear;
+  fec_packet->fec_header_size = UlpfecHeaderSize(packet_mask_size);
+  uint16_t seq_num_base =
+      ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[2]);
+  fec_packet->protected_ssrc = fec_packet->ssrc;  // Due to RED.
+  fec_packet->seq_num_base = seq_num_base;
+  fec_packet->packet_mask_offset = kPacketMaskOffset;
+  fec_packet->packet_mask_size = packet_mask_size;
+  fec_packet->protection_length =
+      ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[10]);
+
+  // Store length recovery field in temporary location in header.
+  // This makes the header "compatible" with the corresponding
+  // FlexFEC location of the length recovery field, thus simplifying
+  // the XORing operations.
+  memcpy(&fec_packet->pkt->data[2], &fec_packet->pkt->data[8], 2);
+
+  return true;
+}
+
+UlpfecHeaderWriter::UlpfecHeaderWriter()
+    : FecHeaderWriter(kMaxMediaPackets,
+                      kMaxFecPackets,
+                      kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitSet) {}
+
+UlpfecHeaderWriter::~UlpfecHeaderWriter() = default;
+
+// TODO(brandtr): Consider updating this implementation (which actually
+// returns a bound on the sequence number spread), if logic is added to
+// UlpfecHeaderWriter::FinalizeFecHeader to truncate packet masks which end
+// in a string of zeroes. (Similar to how it is done in the FlexFEC case.)
+size_t UlpfecHeaderWriter::MinPacketMaskSize(const uint8_t* packet_mask,
+                                             size_t packet_mask_size) const {
+  return packet_mask_size;
+}
+
+size_t UlpfecHeaderWriter::FecHeaderSize(size_t packet_mask_size) const {
+  return UlpfecHeaderSize(packet_mask_size);
+}
+
+void UlpfecHeaderWriter::FinalizeFecHeader(
+    uint16_t seq_num_base,
+    const uint8_t* packet_mask,
+    size_t packet_mask_size,
+    ForwardErrorCorrection::Packet* fec_packet) const {
+  // Set E bit to zero.
+  fec_packet->data[0] &= 0x7f;
+  // Set L bit based on packet mask size. (Note that the packet mask
+  // can only take on two discrete values.)
+  bool l_bit = (packet_mask_size == kUlpfecPacketMaskSizeLBitSet);
+  if (l_bit) {
+    fec_packet->data[0] |= 0x40;  // Set the L bit.
+  } else {
+    RTC_DCHECK_EQ(packet_mask_size, kUlpfecPacketMaskSizeLBitClear);
+    fec_packet->data[0] &= 0xbf;  // Clear the L bit.
+  }
+  // Copy length recovery field from temporary location.
+  memcpy(&fec_packet->data[8], &fec_packet->data[2], 2);
+  // Write sequence number base.
+  ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[2], seq_num_base);
+  // Protection length is set to entire packet. (This is not
+  // required in general.)
+  const size_t fec_header_size = FecHeaderSize(packet_mask_size);
+  ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[10],
+                                       fec_packet->length - fec_header_size);
+  // Copy the packet mask.
+  memcpy(&fec_packet->data[12], packet_mask, packet_mask_size);
+}
+
+}  // namespace webrtc
diff --git a/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h b/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h
new file mode 100644
index 0000000..10328ad
--- /dev/null
+++ b/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_
+#define WEBRTC_MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_
+
+#include "webrtc/base/basictypes.h"
+#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h"
+
+namespace webrtc {
+
+// FEC Level 0 Header, 10 bytes.
+//    0                   1                   2                   3
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |E|L|P|X|  CC   |M| PT recovery |            SN base            |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |                          TS recovery                          |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |        length recovery        |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// FEC Level 1 Header, 4 bytes (L = 0) or 8 bytes (L = 1).
+//    0                   1                   2                   3
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |       Protection Length       |             mask              |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |              mask cont. (present only when L = 1)             |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+class UlpfecHeaderReader : public FecHeaderReader {
+ public:
+  UlpfecHeaderReader();
+  ~UlpfecHeaderReader() override;
+
+  bool ReadFecHeader(
+      ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const override;
+};
+
+class UlpfecHeaderWriter : public FecHeaderWriter {
+ public:
+  UlpfecHeaderWriter();
+  ~UlpfecHeaderWriter() override;
+
+  size_t MinPacketMaskSize(const uint8_t* packet_mask,
+                           size_t packet_mask_size) const override;
+
+  size_t FecHeaderSize(size_t packet_mask_row_size) const override;
+
+  void FinalizeFecHeader(
+      uint16_t seq_num_base,
+      const uint8_t* packet_mask,
+      size_t packet_mask_size,
+      ForwardErrorCorrection::Packet* fec_packet) const override;
+};
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_
diff --git a/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc b/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc
new file mode 100644
index 0000000..9eb2df0
--- /dev/null
+++ b/webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc
@@ -0,0 +1,244 @@
+/*
+ *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string.h>
+
+#include <memory>
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/basictypes.h"
+#include "webrtc/base/checks.h"
+#include "webrtc/base/random.h"
+#include "webrtc/base/scoped_ref_ptr.h"
+#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
+#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h"
+#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
+#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
+
+namespace webrtc {
+
+namespace {
+
+using Packet = ::webrtc::ForwardErrorCorrection::Packet;
+using ReceivedFecPacket = ::webrtc::ForwardErrorCorrection::ReceivedFecPacket;
+
+constexpr uint32_t kMediaSsrc = 1254983;
+constexpr uint16_t kMediaStartSeqNum = 825;
+constexpr size_t kMediaPacketLength = 1234;
+
+constexpr size_t kUlpfecHeaderSizeLBitClear = 14;
+constexpr size_t kUlpfecHeaderSizeLBitSet = 18;
+constexpr size_t kUlpfecPacketMaskOffset = 12;
+
+std::unique_ptr<uint8_t[]> GeneratePacketMask(size_t packet_mask_size,
+                                              uint64_t seed) {
+  Random random(seed);
+  std::unique_ptr<uint8_t[]> packet_mask(new uint8_t[packet_mask_size]);
+  for (size_t i = 0; i < packet_mask_size; ++i) {
+    packet_mask[i] = random.Rand<uint8_t>();
+  }
+  return packet_mask;
+}
+
+std::unique_ptr<Packet> WriteHeader(const uint8_t* packet_mask,
+                                    size_t packet_mask_size) {
+  UlpfecHeaderWriter writer;
+  std::unique_ptr<Packet> written_packet(new Packet());
+  written_packet->length = kMediaPacketLength;
+  for (size_t i = 0; i < written_packet->length; ++i) {
+    written_packet->data[i] = i;  // Actual content doesn't matter.
+  }
+  writer.FinalizeFecHeader(kMediaStartSeqNum, packet_mask, packet_mask_size,
+                           written_packet.get());
+  return written_packet;
+}
+
+std::unique_ptr<ReceivedFecPacket> ReadHeader(const Packet& written_packet) {
+  UlpfecHeaderReader reader;
+  std::unique_ptr<ReceivedFecPacket> read_packet(new ReceivedFecPacket());
+  read_packet->ssrc = kMediaSsrc;
+  read_packet->pkt = rtc::scoped_refptr<Packet>(new Packet());
+  memcpy(read_packet->pkt->data, written_packet.data, written_packet.length);
+  read_packet->pkt->length = written_packet.length;
+  EXPECT_TRUE(reader.ReadFecHeader(read_packet.get()));
+  return read_packet;
+}
+
+void VerifyHeaders(size_t expected_fec_header_size,
+                   const uint8_t* expected_packet_mask,
+                   size_t expected_packet_mask_size,
+                   const Packet& written_packet,
+                   const ReceivedFecPacket& read_packet) {
+  EXPECT_EQ(kMediaSsrc, read_packet.ssrc);
+  EXPECT_EQ(expected_fec_header_size, read_packet.fec_header_size);
+  EXPECT_EQ(kMediaSsrc, read_packet.protected_ssrc);
+  EXPECT_EQ(kMediaStartSeqNum, read_packet.seq_num_base);
+  EXPECT_EQ(kUlpfecPacketMaskOffset, read_packet.packet_mask_offset);
+  ASSERT_EQ(expected_packet_mask_size, read_packet.packet_mask_size);
+  EXPECT_EQ(written_packet.length - expected_fec_header_size,
+            read_packet.protection_length);
+  EXPECT_EQ(0, memcmp(expected_packet_mask,
+                      &read_packet.pkt->data[read_packet.packet_mask_offset],
+                      read_packet.packet_mask_size));
+  // Verify that the call to ReadFecHeader did not tamper with the payload.
+  EXPECT_EQ(0, memcmp(&written_packet.data[expected_fec_header_size],
+                      &read_packet.pkt->data[expected_fec_header_size],
+                      written_packet.length - expected_fec_header_size));
+}
+
+}  // namespace
+
+TEST(UlpfecHeaderReaderTest, ReadsSmallHeader) {
+  const uint8_t packet[] = {
+      0x00, 0x12, 0xab, 0xcd,  // L bit clear, "random" payload type and SN base
+      0x12, 0x34, 0x56, 0x78,  // "random" TS recovery
+      0xab, 0xcd, 0x11, 0x22,  // "random" length recovery and protection length
+      0x33, 0x44,              // "random" packet mask
+      0x00, 0x00, 0x00, 0x00   // payload
+  };
+  const size_t packet_length = sizeof(packet);
+  ReceivedFecPacket read_packet;
+  read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet());
+  memcpy(read_packet.pkt->data, packet, packet_length);
+  read_packet.pkt->length = packet_length;
+
+  UlpfecHeaderReader reader;
+  EXPECT_TRUE(reader.ReadFecHeader(&read_packet));
+
+  EXPECT_EQ(14U, read_packet.fec_header_size);
+  EXPECT_EQ(0xabcdU, read_packet.seq_num_base);
+  EXPECT_EQ(12U, read_packet.packet_mask_offset);
+  EXPECT_EQ(2U, read_packet.packet_mask_size);
+  EXPECT_EQ(0x1122U, read_packet.protection_length);
+}
+
+TEST(UlpfecHeaderReaderTest, ReadsLargeHeader) {
+  const uint8_t packet[] = {
+      0x40, 0x12, 0xab, 0xcd,  // L bit set, "random" payload type and SN base
+      0x12, 0x34, 0x56, 0x78,  // "random" TS recovery
+      0xab, 0xcd, 0x11, 0x22,  // "random" length recovery and protection length
+      0x33, 0x44, 0x55, 0x66,  // "random" packet mask
+      0x77, 0x88,              //
+      0x00, 0x00, 0x00, 0x00   // payload
+  };
+  const size_t packet_length = sizeof(packet);
+  ReceivedFecPacket read_packet;
+  read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet());
+  memcpy(read_packet.pkt->data, packet, packet_length);
+  read_packet.pkt->length = packet_length;
+
+  UlpfecHeaderReader reader;
+  EXPECT_TRUE(reader.ReadFecHeader(&read_packet));
+
+  EXPECT_EQ(18U, read_packet.fec_header_size);
+  EXPECT_EQ(0xabcdU, read_packet.seq_num_base);
+  EXPECT_EQ(12U, read_packet.packet_mask_offset);
+  EXPECT_EQ(6U, read_packet.packet_mask_size);
+  EXPECT_EQ(0x1122U, read_packet.protection_length);
+}
+
+TEST(UlpfecHeaderWriterTest, FinalizesSmallHeader) {
+  const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear;
+  auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
+  Packet written_packet;
+  written_packet.length = kMediaPacketLength;
+  for (size_t i = 0; i < written_packet.length; ++i) {
+    written_packet.data[i] = i;
+  }
+
+  UlpfecHeaderWriter writer;
+  writer.FinalizeFecHeader(kMediaStartSeqNum, packet_mask.get(),
+                           packet_mask_size, &written_packet);
+
+  const uint8_t* packet = written_packet.data;
+  EXPECT_EQ(0x00, packet[0] & 0x80);  // E bit.
+  EXPECT_EQ(0x00, packet[0] & 0x40);  // L bit.
+  EXPECT_EQ(kMediaStartSeqNum, ByteReader<uint16_t>::ReadBigEndian(packet + 2));
+  EXPECT_EQ(
+      static_cast<uint16_t>(kMediaPacketLength - kUlpfecHeaderSizeLBitClear),
+      ByteReader<uint16_t>::ReadBigEndian(packet + 10));
+  EXPECT_EQ(0, memcmp(packet + kUlpfecPacketMaskOffset, packet_mask.get(),
+                      packet_mask_size));
+}
+
+TEST(UlpfecHeaderWriterTest, FinalizesLargeHeader) {
+  const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet;
+  auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
+  Packet written_packet;
+  written_packet.length = kMediaPacketLength;
+  for (size_t i = 0; i < written_packet.length; ++i) {
+    written_packet.data[i] = i;
+  }
+
+  UlpfecHeaderWriter writer;
+  writer.FinalizeFecHeader(kMediaStartSeqNum, packet_mask.get(),
+                           packet_mask_size, &written_packet);
+
+  const uint8_t* packet = written_packet.data;
+  EXPECT_EQ(0x00, packet[0] & 0x80);  // E bit.
+  EXPECT_EQ(0x40, packet[0] & 0x40);  // L bit.
+  EXPECT_EQ(kMediaStartSeqNum, ByteReader<uint16_t>::ReadBigEndian(packet + 2));
+  EXPECT_EQ(
+      static_cast<uint16_t>(kMediaPacketLength - kUlpfecHeaderSizeLBitSet),
+      ByteReader<uint16_t>::ReadBigEndian(packet + 10));
+  EXPECT_EQ(0, memcmp(packet + kUlpfecPacketMaskOffset, packet_mask.get(),
+                      packet_mask_size));
+}
+
+TEST(UlpfecHeaderWriterTest, CalculateSmallHeaderSize) {
+  const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear;
+  auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
+
+  UlpfecHeaderWriter writer;
+  size_t min_packet_mask_size =
+      writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size);
+
+  EXPECT_EQ(kUlpfecPacketMaskSizeLBitClear, min_packet_mask_size);
+  EXPECT_EQ(kUlpfecHeaderSizeLBitClear,
+            writer.FecHeaderSize(min_packet_mask_size));
+}
+
+TEST(UlpfecHeaderWriterTest, CalculateLargeHeaderSize) {
+  const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet;
+  auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
+
+  UlpfecHeaderWriter writer;
+  size_t min_packet_mask_size =
+      writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size);
+
+  EXPECT_EQ(kUlpfecPacketMaskSizeLBitSet, min_packet_mask_size);
+  EXPECT_EQ(kUlpfecHeaderSizeLBitSet,
+            writer.FecHeaderSize(min_packet_mask_size));
+}
+
+TEST(UlpfecHeaderReaderWriterTest, WriteAndReadSmallHeader) {
+  const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear;
+  auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
+
+  auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size);
+  auto read_packet = ReadHeader(*written_packet);
+
+  VerifyHeaders(kUlpfecHeaderSizeLBitClear, packet_mask.get(), packet_mask_size,
+                *written_packet, *read_packet);
+}
+
+TEST(UlpfecHeaderReaderWriterTest, WriteAndReadLargeHeader) {
+  const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet;
+  auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
+
+  auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size);
+  auto read_packet = ReadHeader(*written_packet);
+
+  VerifyHeaders(kUlpfecHeaderSizeLBitSet, packet_mask.get(), packet_mask_size,
+                *written_packet, *read_packet);
+}
+
+}  // namespace webrtc
diff --git a/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc b/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc
index be01038..e3368e2 100644
--- a/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc
+++ b/webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc
@@ -107,7 +107,8 @@
   ASSERT_EQ(12, kMaxMediaPackets[1]) << "Max media packets for bursty mode not "
                                      << "equal to 12.";
 
-  ForwardErrorCorrection fec;
+  std::unique_ptr<ForwardErrorCorrection> fec =
+      ForwardErrorCorrection::CreateUlpfec();
   ForwardErrorCorrection::PacketList media_packet_list;
   std::list<ForwardErrorCorrection::Packet*> fec_packet_list;
   ForwardErrorCorrection::ReceivedPacketList to_decode_list;
@@ -238,7 +239,7 @@
                   new ForwardErrorCorrection::Packet());
               const uint32_t kMinPacketSize = 12;
               const uint32_t kMaxPacketSize = static_cast<uint32_t>(
-                  IP_PACKET_SIZE - 12 - 28 - fec.MaxPacketOverhead());
+                  IP_PACKET_SIZE - 12 - 28 - fec->MaxPacketOverhead());
               media_packet->length = random.Rand(kMinPacketSize,
                                                  kMaxPacketSize);
 
@@ -277,9 +278,9 @@
             }
             media_packet_list.back()->data[1] |= 0x80;
 
-            ASSERT_EQ(0, fec.EncodeFec(media_packet_list, protection_factor,
-                                       num_imp_packets, kUseUnequalProtection,
-                                       fec_mask_type, &fec_packet_list))
+            ASSERT_EQ(0, fec->EncodeFec(media_packet_list, protection_factor,
+                                        num_imp_packets, kUseUnequalProtection,
+                                        fec_mask_type, &fec_packet_list))
                 << "EncodeFec() failed";
 
             ASSERT_EQ(num_fec_packets, fec_packet_list.size())
@@ -393,7 +394,7 @@
                 }
               }
               ASSERT_EQ(0,
-                        fec.DecodeFec(&to_decode_list, &recovered_packet_list))
+                        fec->DecodeFec(&to_decode_list, &recovered_packet_list))
                   << "DecodeFec() failed";
               ASSERT_TRUE(to_decode_list.empty())
                   << "Received packet list is not empty.";
@@ -421,7 +422,7 @@
               }
               ++media_packet_idx;
             }
-            fec.ResetState(&recovered_packet_list);
+            fec->ResetState(&recovered_packet_list);
             ASSERT_TRUE(recovered_packet_list.empty())
                 << "Excessive number of recovered packets.\t size is: "
                 << recovered_packet_list.size();
@@ -447,7 +448,7 @@
   }    // loop over mask types
 
   // Have DecodeFec clear the recovered packet list.
-  fec.ResetState(&recovered_packet_list);
+  fec->ResetState(&recovered_packet_list);
   ASSERT_TRUE(recovered_packet_list.empty())
       << "Recovered packet list is not empty";
 }
diff --git a/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc b/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc
index b7c4ef5..0b4ac25 100644
--- a/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc
+++ b/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc
@@ -730,24 +730,24 @@
     int code_index = 0;
     // Maximum number of media packets allowed for the mask type.
     const int packet_mask_max = kMaxMediaPackets[fec_mask_type];
-    uint8_t* packet_mask = new uint8_t[packet_mask_max * kMaskSizeLBitSet];
+    std::unique_ptr<uint8_t[]> packet_mask(
+        new uint8_t[packet_mask_max * kUlpfecMaxPacketMaskSize]);
     // Loop through codes up to |kMaxMediaPacketsTest|.
     for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
         num_media_packets++) {
       const int mask_bytes_fec_packet =
-          (num_media_packets > 16) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
+          static_cast<int>(internal::PacketMaskSize(num_media_packets));
       internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
       for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
           num_fec_packets++) {
-        memset(packet_mask, 0, num_media_packets * mask_bytes_fec_packet);
-        memcpy(packet_mask, mask_table.fec_packet_mask_table()
-               [num_media_packets - 1][num_fec_packets - 1],
+        memset(packet_mask.get(), 0, num_media_packets * mask_bytes_fec_packet);
+        memcpy(packet_mask.get(),
+               mask_table.fec_packet_mask_table()[num_media_packets - 1]
+                                                 [num_fec_packets - 1],
                num_fec_packets * mask_bytes_fec_packet);
         // Convert to bit mask.
-        GetPacketMaskConvertToBitMask(packet_mask,
-                                      num_media_packets,
-                                      num_fec_packets,
-                                      mask_bytes_fec_packet,
+        GetPacketMaskConvertToBitMask(packet_mask.get(), num_media_packets,
+                                      num_fec_packets, mask_bytes_fec_packet,
                                       code_type);
         if (RejectInvalidMasks(num_media_packets, num_fec_packets) < 0) {
           return -1;
@@ -759,7 +759,6 @@
       }
     }
     assert(code_index == kNumberCodes);
-    delete [] packet_mask;
     return 0;
   }