Create LossNotificationController

Create LossNotificationController, which produces LossNotification
RTCP feedback messages when video packets/frames are lost.

(LossNotification messages are sent when an RTP gap is detected,
as well as when frames are later received which are undecodable
because of the missing frames due to the previously dropped packets.)

Bug: webrtc:10336
Change-Id: I7b3a156ed14e5a727349acdd82dae6997462421b
Reviewed-on: https://webrtc-review.googlesource.com/c/123762
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Commit-Queue: Elad Alon <eladalon@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26812}
diff --git a/modules/include/module_common_types.h b/modules/include/module_common_types.h
index b189c43..26122b1 100644
--- a/modules/include/module_common_types.h
+++ b/modules/include/module_common_types.h
@@ -97,6 +97,17 @@
   virtual ~KeyFrameRequestSender() {}
 };
 
+// Interface used by LossNotificationController to communicate to RtpRtcp.
+// TODO(bugs.webrtc.org/10336): Hook up to RtpRtcp.
+class LossNotificationSender {
+ public:
+  virtual ~LossNotificationSender() {}
+
+  virtual void SendLossNotification(uint16_t last_decoded_seq_num,
+                                    uint16_t last_received_seq_num,
+                                    bool decodability_flag) = 0;
+};
+
 // Used to indicate if a received packet contain a complete NALU (or equivalent)
 enum VCMNaluCompleteness {
   kNaluUnset = 0,     // Packet has not been filled.
diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn
index 31d4abd..f4490cb 100644
--- a/modules/video_coding/BUILD.gn
+++ b/modules/video_coding/BUILD.gn
@@ -131,6 +131,8 @@
     "jitter_buffer_common.h",
     "jitter_estimator.cc",
     "jitter_estimator.h",
+    "loss_notification_controller.cc",
+    "loss_notification_controller.h",
     "media_opt_util.cc",
     "media_opt_util.h",
     "packet_buffer.cc",
@@ -853,6 +855,7 @@
       "include/mock/mock_vcm_callbacks.h",
       "jitter_buffer_unittest.cc",
       "jitter_estimator_tests.cc",
+      "loss_notification_controller_unittest.cc",
       "nack_module_unittest.cc",
       "receiver_unittest.cc",
       "rtp_frame_reference_finder_unittest.cc",
diff --git a/modules/video_coding/loss_notification_controller.cc b/modules/video_coding/loss_notification_controller.cc
new file mode 100644
index 0000000..6e07bd1
--- /dev/null
+++ b/modules/video_coding/loss_notification_controller.cc
@@ -0,0 +1,186 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/video_coding/loss_notification_controller.h"
+
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+namespace {
+// Keep a container's size no higher than |max_allowed_size|, by paring its size
+// down to |target_size| whenever it has more than |max_allowed_size| elements.
+template <typename Container>
+void PareDown(Container* container,
+              size_t max_allowed_size,
+              size_t target_size) {
+  if (container->size() > max_allowed_size) {
+    const size_t entries_to_delete = container->size() - target_size;
+    auto erase_to = container->begin();
+    std::advance(erase_to, entries_to_delete);
+    container->erase(container->begin(), erase_to);
+    RTC_DCHECK_EQ(container->size(), target_size);
+  }
+}
+}  // namespace
+
+LossNotificationController::LossNotificationController(
+    KeyFrameRequestSender* key_frame_request_sender,
+    LossNotificationSender* loss_notification_sender)
+    : key_frame_request_sender_(key_frame_request_sender),
+      loss_notification_sender_(loss_notification_sender),
+      current_frame_potentially_decodable_(true) {
+  RTC_DCHECK(key_frame_request_sender_);
+  RTC_DCHECK(loss_notification_sender_);
+}
+
+LossNotificationController::~LossNotificationController() = default;
+
+void LossNotificationController::OnReceivedPacket(const VCMPacket& packet) {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_task_checker_);
+
+  if (!packet.generic_descriptor) {
+    RTC_LOG(LS_WARNING) << "Generic frame descriptor missing. Buggy remote? "
+                           "Misconfigured local?";
+    return;
+  }
+
+  // Ignore repeated or reordered packets.
+  // TODO(bugs.webrtc.org/10336): Handle packet reordering.
+  if (last_received_seq_num_ &&
+      !AheadOf(packet.seqNum, *last_received_seq_num_)) {
+    return;
+  }
+
+  DiscardOldInformation();  // Prevent memory overconsumption.
+
+  const bool seq_num_gap =
+      last_received_seq_num_ &&
+      packet.seqNum != static_cast<uint16_t>(*last_received_seq_num_ + 1u);
+
+  last_received_seq_num_ = packet.seqNum;
+
+  if (packet.generic_descriptor->FirstPacketInSubFrame()) {
+    const uint16_t frame_id = packet.generic_descriptor->FrameId();
+    const uint64_t unwrapped_frame_id = frame_id_unwrapper_.Unwrap(frame_id);
+
+    // Ignore repeated or reordered frames.
+    // TODO(TODO(bugs.webrtc.org/10336): Handle frame reordering.
+    if (last_received_unwrapped_frame_id_ &&
+        unwrapped_frame_id <= *last_received_unwrapped_frame_id_) {
+      RTC_LOG(LS_WARNING) << "Repeated or reordered frame ID (" << frame_id
+                          << ").";
+      return;
+    }
+
+    last_received_unwrapped_frame_id_ = unwrapped_frame_id;
+
+    const bool intra_frame =
+        packet.generic_descriptor->FrameDependenciesDiffs().empty();
+    // Generic Frame Descriptor does not current allow us to distinguish
+    // whether an intra frame is a key frame.
+    // We therefore assume all intra frames are key frames.
+    const bool key_frame = intra_frame;
+    if (key_frame) {
+      // Subsequent frames may not rely on frames before the key frame.
+      decodable_unwrapped_frame_ids_.clear();
+      current_frame_potentially_decodable_ = true;
+    } else {
+      const bool all_dependencies_decodable = AllDependenciesDecodable(
+          unwrapped_frame_id,
+          packet.generic_descriptor->FrameDependenciesDiffs());
+      current_frame_potentially_decodable_ = all_dependencies_decodable;
+      if (seq_num_gap || !current_frame_potentially_decodable_) {
+        HandleLoss(packet.seqNum, current_frame_potentially_decodable_);
+      }
+    }
+  } else if (seq_num_gap || !current_frame_potentially_decodable_) {
+    current_frame_potentially_decodable_ = false;
+    // We allow sending multiple loss notifications for a single frame
+    // even if only one of its packets is lost. We do this because the bigger
+    // the frame, the more likely it is to be non-discardable, and therefore
+    // the more robust we wish to be to loss of the feedback messages.
+    HandleLoss(packet.seqNum, false);
+  }
+}
+
+void LossNotificationController::OnAssembledFrame(
+    uint16_t first_seq_num,
+    uint16_t frame_id,
+    bool discardable,
+    rtc::ArrayView<const uint16_t> frame_dependency_diffs) {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_task_checker_);
+
+  DiscardOldInformation();  // Prevent memory overconsumption.
+
+  if (discardable) {
+    return;
+  }
+
+  const uint64_t unwrapped_frame_id = frame_id_unwrapper_.Unwrap(frame_id);
+  if (!AllDependenciesDecodable(unwrapped_frame_id, frame_dependency_diffs)) {
+    return;
+  }
+
+  last_decodable_non_discardable_.emplace(first_seq_num);
+  const auto it = decodable_unwrapped_frame_ids_.insert(unwrapped_frame_id);
+  RTC_DCHECK(it.second);
+}
+
+void LossNotificationController::DiscardOldInformation() {
+  constexpr size_t kExpectedKeyFrameIntervalFrames = 3000;
+  constexpr size_t kMaxSize = 2 * kExpectedKeyFrameIntervalFrames;
+  constexpr size_t kTargetSize = kExpectedKeyFrameIntervalFrames;
+  PareDown(&decodable_unwrapped_frame_ids_, kMaxSize, kTargetSize);
+}
+
+bool LossNotificationController::AllDependenciesDecodable(
+    uint64_t unwrapped_frame_id,
+    rtc::ArrayView<const uint16_t> frame_dependency_diffs) const {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_task_checker_);
+
+  // Due to packet reordering, frame buffering and asynchronous decoders, it is
+  // infeasible to make reliable conclusions on the decodability of a frame
+  // immediately when it arrives. We use the following assumptions:
+  // * Intra frames are decodable.
+  // * Inter frames are decodable if all of their references were decodable.
+  // One possibility that is ignored, is that the packet may be corrupt.
+
+  for (uint16_t frame_dependency_diff : frame_dependency_diffs) {
+    RTC_DCHECK_GT(unwrapped_frame_id, frame_dependency_diff);
+    const uint64_t unwrapped_ref_frame_id =
+        unwrapped_frame_id - frame_dependency_diff;
+
+    const auto ref_frame_it =
+        decodable_unwrapped_frame_ids_.find(unwrapped_ref_frame_id);
+    if (ref_frame_it == decodable_unwrapped_frame_ids_.end()) {
+      // Reference frame not decodable.
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void LossNotificationController::HandleLoss(uint16_t last_received_seq_num,
+                                            bool decodability_flag) {
+  RTC_DCHECK_CALLED_SEQUENTIALLY(&sequenced_task_checker_);
+
+  if (last_decodable_non_discardable_) {
+    RTC_DCHECK(AheadOf(last_received_seq_num,
+                       last_decodable_non_discardable_->first_seq_num));
+    loss_notification_sender_->SendLossNotification(
+        last_decodable_non_discardable_->first_seq_num, last_received_seq_num,
+        decodability_flag);
+  } else {
+    key_frame_request_sender_->RequestKeyFrame();
+  }
+}
+}  //  namespace webrtc
diff --git a/modules/video_coding/loss_notification_controller.h b/modules/video_coding/loss_notification_controller.h
new file mode 100644
index 0000000..a2d640f
--- /dev/null
+++ b/modules/video_coding/loss_notification_controller.h
@@ -0,0 +1,108 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_VIDEO_CODING_LOSS_NOTIFICATION_CONTROLLER_H_
+#define MODULES_VIDEO_CODING_LOSS_NOTIFICATION_CONTROLLER_H_
+
+#include <set>
+
+#include "absl/types/optional.h"
+#include "modules/include/module_common_types.h"
+#include "modules/video_coding/packet.h"
+#include "rtc_base/numerics/sequence_number_util.h"
+#include "rtc_base/sequenced_task_checker.h"
+
+namespace webrtc {
+
+class LossNotificationController {
+ public:
+  LossNotificationController(KeyFrameRequestSender* key_frame_request_sender,
+                             LossNotificationSender* loss_notification_sender);
+  ~LossNotificationController();
+
+  // An RTP packet was received from the network.
+  void OnReceivedPacket(const VCMPacket& packet);
+
+  // A frame was assembled from packets previously received.
+  // (Should be called even if the frame was composed of a single packet.)
+  void OnAssembledFrame(uint16_t first_seq_num,
+                        uint16_t frame_id,
+                        bool discardable,
+                        rtc::ArrayView<const uint16_t> frame_dependency_diffs);
+
+ private:
+  void DiscardOldInformation();
+
+  bool AllDependenciesDecodable(
+      uint64_t unwrapped_frame_id,
+      rtc::ArrayView<const uint16_t> frame_dependency_diffs) const;
+
+  // When the loss of a packet or the non-decodability of a frame is detected,
+  // produces a key frame request or a loss notification.
+  // 1. |last_received_seq_num| is the last received sequence number.
+  // 2. |decodability_flag| refers to the frame associated with the last packet.
+  //    It is set to |true| if and only if all of that frame's dependencies are
+  //    known to be decodable, and the frame itself is not yet known to be
+  //    unassemblable (i.e. no earlier parts of it were lost).
+  //    Clarifications:
+  //    a. In a multi-packet frame, the first packet reveals the frame's
+  //       dependencies, but it is not yet known whether all parts of the
+  //       current frame will be received.
+  //    b. In a multi-packet frame, if the first packet is missed, the
+  //       dependencies are unknown, but it is known that the frame itself
+  //       is unassemblable.
+  void HandleLoss(uint16_t last_received_seq_num, bool decodability_flag);
+
+  KeyFrameRequestSender* const key_frame_request_sender_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  LossNotificationSender* const loss_notification_sender_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  SeqNumUnwrapper<uint16_t> frame_id_unwrapper_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  // Tracked to avoid processing repeated frames (buggy/malicious remote).
+  absl::optional<uint64_t> last_received_unwrapped_frame_id_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  // Tracked to avoid processing repeated packets.
+  absl::optional<uint16_t> last_received_seq_num_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  // Tracked in order to correctly report the potential-decodability of
+  // multi-packet frames.
+  bool current_frame_potentially_decodable_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  // Loss notifications contain the sequence number of the first packet of
+  // the last decodable-and-non-discardable frame. Since this is a bit of
+  // a mouthful, last_decodable_non_discardable_.first_seq_num is used,
+  // which hopefully is a bit easier for human beings to parse
+  // than |first_seq_num_of_last_decodable_non_discardable_|.
+  struct FrameInfo {
+    explicit FrameInfo(uint16_t first_seq_num) : first_seq_num(first_seq_num) {}
+    uint16_t first_seq_num;
+  };
+  absl::optional<FrameInfo> last_decodable_non_discardable_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  // Track which frames are decodable. Later frames are also decodable if
+  // all of their dependencies can be found in this container.
+  // (Naturally, later frames must also be assemblable to be decodable.)
+  std::set<uint64_t> decodable_unwrapped_frame_ids_
+      RTC_GUARDED_BY(sequenced_task_checker_);
+
+  rtc::SequencedTaskChecker sequenced_task_checker_;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_VIDEO_CODING_LOSS_NOTIFICATION_CONTROLLER_H_
diff --git a/modules/video_coding/loss_notification_controller_unittest.cc b/modules/video_coding/loss_notification_controller_unittest.cc
new file mode 100644
index 0000000..2ba12fe
--- /dev/null
+++ b/modules/video_coding/loss_notification_controller_unittest.cc
@@ -0,0 +1,600 @@
+/*
+ *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/video_coding/loss_notification_controller.h"
+
+#include <limits>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+VCMPacket CreatePacket(
+    bool first_in_frame,
+    bool last_in_frame,
+    uint16_t seq_num,
+    uint16_t frame_id,
+    bool is_key_frame,
+    std::vector<uint16_t> ref_frame_ids = std::vector<uint16_t>()) {
+  RtpGenericFrameDescriptor frame_descriptor;
+  frame_descriptor.SetFirstPacketInSubFrame(first_in_frame);
+  frame_descriptor.SetLastPacketInSubFrame(last_in_frame);
+  if (first_in_frame) {
+    frame_descriptor.SetFrameId(frame_id);
+    if (!is_key_frame) {
+      for (uint16_t ref_frame_id : ref_frame_ids) {
+        uint16_t fdiff = frame_id - ref_frame_id;
+        EXPECT_TRUE(frame_descriptor.AddFrameDependencyDiff(fdiff));
+      }
+    }
+  }
+
+  VCMPacket packet;
+  packet.seqNum = seq_num;
+  packet.generic_descriptor = frame_descriptor;
+  return packet;
+}
+
+class PacketStreamCreator final {
+ public:
+  PacketStreamCreator() : seq_num_(0), frame_id_(0), next_is_key_frame_(true) {}
+
+  VCMPacket NextPacket() {
+    std::vector<uint16_t> ref_frame_ids;
+    if (!next_is_key_frame_) {
+      ref_frame_ids.push_back(frame_id_ - 1);
+    }
+
+    VCMPacket packet = CreatePacket(true, true, seq_num_++, frame_id_++,
+                                    next_is_key_frame_, ref_frame_ids);
+
+    next_is_key_frame_ = false;
+
+    return packet;
+  }
+
+ private:
+  uint16_t seq_num_;
+  uint16_t frame_id_;
+  bool next_is_key_frame_;
+};
+}  // namespace
+
+// Most of the logic for the tests is here. Subclasses allow parameterizing
+// the test, or adding some more specific logic.
+class LossNotificationControllerBaseTest : public ::testing::Test,
+                                           public KeyFrameRequestSender,
+                                           public LossNotificationSender {
+ protected:
+  LossNotificationControllerBaseTest()
+      : uut_(this, this), key_frame_requested_(false) {}
+
+  ~LossNotificationControllerBaseTest() override {
+    EXPECT_FALSE(LastKeyFrameRequest());
+    EXPECT_FALSE(LastLossNotification());
+  }
+
+  // KeyFrameRequestSender implementation.
+  void RequestKeyFrame() override {
+    EXPECT_FALSE(LastKeyFrameRequest());
+    EXPECT_FALSE(LastLossNotification());
+    key_frame_requested_ = true;
+  }
+
+  // LossNotificationSender implementation.
+  void SendLossNotification(uint16_t last_decoded_seq_num,
+                            uint16_t last_received_seq_num,
+                            bool decodability_flag) override {
+    EXPECT_FALSE(LastKeyFrameRequest());
+    EXPECT_FALSE(LastLossNotification());
+    last_loss_notification_.emplace(last_decoded_seq_num, last_received_seq_num,
+                                    decodability_flag);
+  }
+
+  void OnReceivedPacket(const VCMPacket& packet) {
+    EXPECT_FALSE(LastKeyFrameRequest());
+    EXPECT_FALSE(LastLossNotification());
+
+    if (packet.generic_descriptor &&
+        packet.generic_descriptor->FirstPacketInSubFrame()) {
+      previous_first_packet_in_frame_ = packet;
+    }
+
+    uut_.OnReceivedPacket(packet);
+  }
+
+  void OnAssembledFrame(uint16_t first_seq_num,
+                        uint16_t frame_id,
+                        bool discardable) {
+    EXPECT_FALSE(LastKeyFrameRequest());
+    EXPECT_FALSE(LastLossNotification());
+
+    ASSERT_TRUE(previous_first_packet_in_frame_);
+    const RtpGenericFrameDescriptor& frame_descriptor =
+        previous_first_packet_in_frame_->generic_descriptor.value();
+
+    uut_.OnAssembledFrame(first_seq_num, frame_id, discardable,
+                          frame_descriptor.FrameDependenciesDiffs());
+  }
+
+  void ExpectKeyFrameRequest() {
+    EXPECT_EQ(LastLossNotification(), absl::nullopt);
+    EXPECT_TRUE(LastKeyFrameRequest());
+  }
+
+  void ExpectLossNotification(uint16_t last_decoded_seq_num,
+                              uint16_t last_received_seq_num,
+                              bool decodability_flag) {
+    EXPECT_FALSE(LastKeyFrameRequest());
+    const auto last_ln = LastLossNotification();
+    ASSERT_TRUE(last_ln);
+    const LossNotification expected_ln(
+        last_decoded_seq_num, last_received_seq_num, decodability_flag);
+    EXPECT_EQ(expected_ln, *last_ln)
+        << "Expected loss notification (" << expected_ln.ToString()
+        << ") != received loss notification (" << last_ln->ToString() + ")";
+  }
+
+  struct LossNotification {
+    LossNotification(uint16_t last_decoded_seq_num,
+                     uint16_t last_received_seq_num,
+                     bool decodability_flag)
+        : last_decoded_seq_num(last_decoded_seq_num),
+          last_received_seq_num(last_received_seq_num),
+          decodability_flag(decodability_flag) {}
+
+    LossNotification& operator=(const LossNotification& other) = default;
+
+    bool operator==(const LossNotification& other) const {
+      return last_decoded_seq_num == other.last_decoded_seq_num &&
+             last_received_seq_num == other.last_received_seq_num &&
+             decodability_flag == other.decodability_flag;
+    }
+
+    std::string ToString() const {
+      return std::to_string(last_decoded_seq_num) + ", " +
+             std::to_string(last_received_seq_num) + ", " +
+             std::to_string(decodability_flag);
+    }
+
+    uint16_t last_decoded_seq_num;
+    uint16_t last_received_seq_num;
+    bool decodability_flag;
+  };
+
+  bool LastKeyFrameRequest() {
+    const bool result = key_frame_requested_;
+    key_frame_requested_ = false;
+    return result;
+  }
+
+  absl::optional<LossNotification> LastLossNotification() {
+    const absl::optional<LossNotification> result = last_loss_notification_;
+    last_loss_notification_ = absl::nullopt;
+    return result;
+  }
+
+  LossNotificationController uut_;  // Unit under test.
+
+  bool key_frame_requested_;
+
+  absl::optional<LossNotification> last_loss_notification_;
+
+  // First packet of last frame. (Note that if a test skips the first packet
+  // of a subsequent frame, OnAssembledFrame is not called, and so this is
+  // note read. Therefore, it's not a problem if it is not cleared when
+  // the frame changes.)
+  absl::optional<VCMPacket> previous_first_packet_in_frame_;
+};
+
+class LossNotificationControllerTest
+    : public LossNotificationControllerBaseTest,
+      public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
+ protected:
+  // Arbitrary parameterized values, to be used by the tests whenever they
+  // wish to either check some combinations, or wish to demonstrate that
+  // a particular arbitrary value is unimportant.
+  template <size_t N>
+  bool Bool() const {
+    return std::get<N>(GetParam());
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(_,
+                         LossNotificationControllerTest,
+                         ::testing::Combine(::testing::Bool(),
+                                            ::testing::Bool(),
+                                            ::testing::Bool()));
+
+// If the first frame, which is a key frame, is lost, then a new key frame
+// is requested.
+TEST_P(LossNotificationControllerTest,
+       PacketLossBeforeFirstFrameAssembledTriggersKeyFrameRequest) {
+  OnReceivedPacket(CreatePacket(true, false, 100, 0, true));
+  OnReceivedPacket(CreatePacket(Bool<0>(), Bool<1>(), 103, 1, false, {0}));
+  ExpectKeyFrameRequest();
+}
+
+// If packet loss occurs (but not of the first packet), then a loss notification
+// is issued.
+TEST_P(LossNotificationControllerTest,
+       PacketLossAfterFirstFrameAssembledTriggersLossNotification) {
+  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
+  OnAssembledFrame(100, 0, false);
+  const bool first = Bool<0>();
+  const bool last = Bool<1>();
+  OnReceivedPacket(CreatePacket(first, last, 103, 1, false, {0}));
+  const bool expected_decodability_flag = first;
+  ExpectLossNotification(100, 103, expected_decodability_flag);
+}
+
+// No key frame or loss notifications issued due to an innocuous wrap-around
+// of the sequence number.
+TEST_P(LossNotificationControllerTest, SeqNumWrapAround) {
+  uint16_t seq_num = std::numeric_limits<uint16_t>::max();
+  OnReceivedPacket(CreatePacket(true, true, seq_num, 0, true));
+  OnAssembledFrame(seq_num, 0, false);
+  const bool first = Bool<0>();
+  const bool last = Bool<1>();
+  OnReceivedPacket(CreatePacket(first, last, ++seq_num, 1, false, {0}));
+}
+
+// No key frame or loss notifications issued due to an innocuous wrap-around
+// of the frame ID.
+TEST_P(LossNotificationControllerTest, FrameIdWrapAround) {
+  uint16_t frame_id = std::numeric_limits<uint16_t>::max();
+  OnReceivedPacket(CreatePacket(true, true, 100, frame_id, true));
+  OnAssembledFrame(100, frame_id, false);
+  ++frame_id;
+  const bool first = Bool<0>();
+  const bool last = Bool<1>();
+  OnReceivedPacket(CreatePacket(first, last, 100, frame_id, false,
+                                {static_cast<uint16_t>(frame_id - 1)}));
+}
+
+TEST_F(LossNotificationControllerTest,
+       KeyFrameAfterPacketLossProducesNoLossNotifications) {
+  OnReceivedPacket(CreatePacket(true, true, 100, 1, true));
+  OnAssembledFrame(100, 1, false);
+  OnReceivedPacket(CreatePacket(true, true, 108, 8, true));
+}
+
+TEST_P(LossNotificationControllerTest, LostReferenceProducesLossNotification) {
+  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
+  OnAssembledFrame(100, 0, false);
+  uint16_t last_decodable_non_discardable_seq_num = 100;
+
+  // RTP gap produces loss notification - not the focus of this test.
+  const bool first = Bool<0>();
+  const bool last = Bool<1>();
+  const bool discardable = Bool<2>();
+  const bool decodable = first;  // Depends on assemblability.
+  OnReceivedPacket(CreatePacket(first, last, 107, 3, false, {0}));
+  ExpectLossNotification(100, 107, decodable);
+  OnAssembledFrame(107, 3, discardable);
+  if (!discardable) {
+    last_decodable_non_discardable_seq_num = 107;
+  }
+
+  // Test focus - a loss notification is produced because of the missing
+  // dependency (frame ID 2), despite the RTP sequence number being the
+  // next expected one.
+  OnReceivedPacket(CreatePacket(true, true, 108, 4, false, {2, 0}));
+  ExpectLossNotification(last_decodable_non_discardable_seq_num, 108, false);
+}
+
+// The difference between this test and the previous one, is that in this test,
+// although the reference frame was received, it was not decodable.
+TEST_P(LossNotificationControllerTest,
+       UndecodableReferenceProducesLossNotification) {
+  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
+  OnAssembledFrame(100, 0, false);
+  uint16_t last_decodable_non_discardable_seq_num = 100;
+
+  // RTP gap produces loss notification - not the focus of this test.
+  // Also, not decodable; this is important for later in the test.
+  OnReceivedPacket(CreatePacket(true, true, 107, 3, false, {2}));
+  ExpectLossNotification(100, 107, false);
+  const bool discardable = Bool<0>();
+  OnAssembledFrame(107, 3, discardable);
+
+  // Test focus - a loss notification is produced because of the undecodable
+  // dependency (frame ID 3, which depended on the missing frame ID 2).
+  OnReceivedPacket(CreatePacket(true, true, 108, 4, false, {3, 0}));
+  ExpectLossNotification(last_decodable_non_discardable_seq_num, 108, false);
+}
+
+TEST_P(LossNotificationControllerTest, RobustnessAgainstHighInitialRefFrameId) {
+  constexpr uint16_t max_uint16_t = std::numeric_limits<uint16_t>::max();
+  OnReceivedPacket(CreatePacket(true, true, 100, 0, true));
+  OnAssembledFrame(100, 0, false);
+  OnReceivedPacket(CreatePacket(true, true, 101, 1, false, {max_uint16_t}));
+  ExpectLossNotification(100, 101, false);
+  OnAssembledFrame(101, max_uint16_t, Bool<0>());
+}
+
+TEST_P(LossNotificationControllerTest, RepeatedPacketsAreIgnored) {
+  PacketStreamCreator packet_stream;
+
+  const auto key_frame_packet = packet_stream.NextPacket();
+  OnReceivedPacket(key_frame_packet);
+  OnAssembledFrame(key_frame_packet.seqNum,
+                   key_frame_packet.generic_descriptor->FrameId(), false);
+
+  const bool gap = Bool<0>();
+
+  if (gap) {
+    // Lose one packet.
+    packet_stream.NextPacket();
+  }
+
+  auto repeated_packet = packet_stream.NextPacket();
+  OnReceivedPacket(repeated_packet);
+  if (gap) {
+    // Loss notification issued because of the gap. This is not the focus of
+    // the test.
+    ExpectLossNotification(key_frame_packet.seqNum, repeated_packet.seqNum,
+                           false);
+  }
+  OnReceivedPacket(repeated_packet);
+}
+
+// Frames without the generic frame descriptor cannot be properly handled,
+// but must not induce a crash.
+TEST_F(LossNotificationControllerTest,
+       IgnoreFramesWithoutGenericFrameDescriptor) {
+  auto packet = CreatePacket(true, true, 1, 0, true);
+  packet.generic_descriptor.reset();
+  OnReceivedPacket(packet);
+}
+
+class LossNotificationControllerTestDecodabilityFlag
+    : public LossNotificationControllerBaseTest {
+ protected:
+  LossNotificationControllerTestDecodabilityFlag()
+      : key_frame_seq_num_(100),
+        key_frame_frame_id_(0),
+        never_received_frame_id_(key_frame_frame_id_ + 1),
+        seq_num_(0),
+        frame_id_(0) {}
+
+  void ReceiveKeyFrame() {
+    RTC_DCHECK_NE(key_frame_frame_id_, never_received_frame_id_);
+    OnReceivedPacket(CreatePacket(true, true, key_frame_seq_num_,
+                                  key_frame_frame_id_, true));
+    OnAssembledFrame(key_frame_seq_num_, key_frame_frame_id_, false);
+    seq_num_ = key_frame_seq_num_;
+    frame_id_ = key_frame_frame_id_;
+  }
+
+  void ReceivePacket(bool first_packet_in_frame,
+                     bool last_packet_in_frame,
+                     const std::vector<uint16_t>& ref_frame_ids) {
+    if (first_packet_in_frame) {
+      frame_id_ += 1;
+    }
+    RTC_DCHECK_NE(frame_id_, never_received_frame_id_);
+    constexpr bool is_key_frame = false;
+    OnReceivedPacket(CreatePacket(first_packet_in_frame, last_packet_in_frame,
+                                  ++seq_num_, frame_id_, is_key_frame,
+                                  ref_frame_ids));
+  }
+
+  void CreateGap() {
+    seq_num_ += 50;
+    frame_id_ += 10;
+  }
+
+  const uint16_t key_frame_seq_num_;
+  const uint16_t key_frame_frame_id_;
+
+  // The tests intentionally never receive this, and can therefore always
+  // use this as an unsatisfied dependency.
+  const uint16_t never_received_frame_id_ = 123;
+
+  uint16_t seq_num_;
+  uint16_t frame_id_;
+};
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       SinglePacketFrameWithDecodableDependencies) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {key_frame_frame_id_};
+  ReceivePacket(true, true, ref_frame_ids);
+
+  const bool expected_decodability_flag = true;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       SinglePacketFrameWithUndecodableDependencies) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {never_received_frame_id_};
+  ReceivePacket(true, true, ref_frame_ids);
+
+  const bool expected_decodability_flag = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       FirstPacketOfMultiPacketFrameWithDecodableDependencies) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {key_frame_frame_id_};
+  ReceivePacket(true, false, ref_frame_ids);
+
+  const bool expected_decodability_flag = true;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       FirstPacketOfMultiPacketFrameWithUndecodableDependencies) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {never_received_frame_id_};
+  ReceivePacket(true, false, ref_frame_ids);
+
+  const bool expected_decodability_flag = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       MiddlePacketOfMultiPacketFrameWithDecodableDependenciesIfFirstMissed) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {key_frame_frame_id_};
+  ReceivePacket(false, false, ref_frame_ids);
+
+  const bool expected_decodability_flag = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       MiddlePacketOfMultiPacketFrameWithUndecodableDependenciesIfFirstMissed) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {never_received_frame_id_};
+  ReceivePacket(false, false, ref_frame_ids);
+
+  const bool expected_decodability_flag = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       MiddlePacketOfMultiPacketFrameWithDecodableDependenciesIfFirstReceived) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  // First packet in multi-packet frame. A loss notification is produced
+  // because of the gap in RTP sequence numbers.
+  const std::vector<uint16_t> ref_frame_ids = {key_frame_frame_id_};
+  ReceivePacket(true, false, ref_frame_ids);
+  const bool expected_decodability_flag_first = true;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag_first);
+
+  // Middle packet in multi-packet frame. No additional gap and the frame is
+  // still potentially decodable, so no additional loss indication.
+  ReceivePacket(false, false, ref_frame_ids);
+  EXPECT_FALSE(LastKeyFrameRequest());
+  EXPECT_FALSE(LastLossNotification());
+}
+
+TEST_F(
+    LossNotificationControllerTestDecodabilityFlag,
+    MiddlePacketOfMultiPacketFrameWithUndecodableDependenciesIfFirstReceived) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  // First packet in multi-packet frame. A loss notification is produced
+  // because of the gap in RTP sequence numbers. The frame is also recognized
+  // as having non-decodable dependencies.
+  const std::vector<uint16_t> ref_frame_ids = {never_received_frame_id_};
+  ReceivePacket(true, false, ref_frame_ids);
+  const bool expected_decodability_flag_first = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag_first);
+
+  // Middle packet in multi-packet frame. No additional gap, but the frame is
+  // known to be non-decodable, so we keep issuing loss indications.
+  ReceivePacket(false, false, ref_frame_ids);
+  const bool expected_decodability_flag_middle = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag_middle);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       LastPacketOfMultiPacketFrameWithDecodableDependenciesIfAllPrevMissed) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {key_frame_frame_id_};
+  ReceivePacket(false, true, ref_frame_ids);
+
+  const bool expected_decodability_flag = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       LastPacketOfMultiPacketFrameWithUndecodableDependenciesIfAllPrevMissed) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  const std::vector<uint16_t> ref_frame_ids = {never_received_frame_id_};
+  ReceivePacket(false, true, ref_frame_ids);
+
+  const bool expected_decodability_flag = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag);
+}
+
+TEST_F(LossNotificationControllerTestDecodabilityFlag,
+       LastPacketOfMultiPacketFrameWithDecodableDependenciesIfAllPrevReceived) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  // First packet in multi-packet frame. A loss notification is produced
+  // because of the gap in RTP sequence numbers.
+  const std::vector<uint16_t> ref_frame_ids = {key_frame_frame_id_};
+  ReceivePacket(true, false, ref_frame_ids);
+  const bool expected_decodability_flag_first = true;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag_first);
+
+  // Last packet in multi-packet frame. No additional gap and the frame is
+  // still potentially decodable, so no additional loss indication.
+  ReceivePacket(false, true, ref_frame_ids);
+  EXPECT_FALSE(LastKeyFrameRequest());
+  EXPECT_FALSE(LastLossNotification());
+}
+
+TEST_F(
+    LossNotificationControllerTestDecodabilityFlag,
+    LastPacketOfMultiPacketFrameWithUndecodableDependenciesIfAllPrevReceived) {
+  ReceiveKeyFrame();
+  CreateGap();
+
+  // First packet in multi-packet frame. A loss notification is produced
+  // because of the gap in RTP sequence numbers. The frame is also recognized
+  // as having non-decodable dependencies.
+  const std::vector<uint16_t> ref_frame_ids = {never_received_frame_id_};
+  ReceivePacket(true, false, ref_frame_ids);
+  const bool expected_decodability_flag_first = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag_first);
+
+  // Last packet in multi-packet frame. No additional gap, but the frame is
+  // known to be non-decodable, so we keep issuing loss indications.
+  ReceivePacket(false, true, ref_frame_ids);
+  const bool expected_decodability_flag_last = false;
+  ExpectLossNotification(key_frame_seq_num_, seq_num_,
+                         expected_decodability_flag_last);
+}
+
+}  //  namespace webrtc