diff --git a/modules/include/module_common_types.h b/modules/include/module_common_types.h index b189c43235..26122b16c6 100644 --- a/modules/include/module_common_types.h +++ b/modules/include/module_common_types.h @@ -97,6 +97,17 @@ class KeyFrameRequestSender { 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 31d4abd230..f4490cb9a4 100644 --- a/modules/video_coding/BUILD.gn +++ b/modules/video_coding/BUILD.gn @@ -131,6 +131,8 @@ rtc_static_library("video_coding") { "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 @@ if (rtc_include_tests) { "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 0000000000..6e07bd11be --- /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 +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(*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 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 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 0000000000..a2d640fe23 --- /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 + +#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 frame_dependency_diffs); + + private: + void DiscardOldInformation(); + + bool AllDependenciesDecodable( + uint64_t unwrapped_frame_id, + rtc::ArrayView 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 frame_id_unwrapper_ + RTC_GUARDED_BY(sequenced_task_checker_); + + // Tracked to avoid processing repeated frames (buggy/malicious remote). + absl::optional last_received_unwrapped_frame_id_ + RTC_GUARDED_BY(sequenced_task_checker_); + + // Tracked to avoid processing repeated packets. + absl::optional 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 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 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 0000000000..2ba12fef74 --- /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 +#include +#include +#include + +#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 ref_frame_ids = std::vector()) { + 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 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 LastLossNotification() { + const absl::optional result = last_loss_notification_; + last_loss_notification_ = absl::nullopt; + return result; + } + + LossNotificationController uut_; // Unit under test. + + bool key_frame_requested_; + + absl::optional 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 previous_first_packet_in_frame_; +}; + +class LossNotificationControllerTest + : public LossNotificationControllerBaseTest, + public ::testing::WithParamInterface> { + 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 + bool Bool() const { + return std::get(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::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::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(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::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& 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 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 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 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 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 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 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 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 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 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 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 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 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