|  | /* | 
|  | *  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 <stdint.h> | 
|  |  | 
|  | #include <limits> | 
|  | #include <string> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/types/optional.h" | 
|  | #include "test/gtest.h" | 
|  |  | 
|  | namespace webrtc { | 
|  | namespace { | 
|  |  | 
|  | // The information about an RTP packet that is relevant in these tests. | 
|  | struct Packet { | 
|  | uint16_t seq_num; | 
|  | bool first_in_frame; | 
|  | bool is_keyframe; | 
|  | int64_t frame_id; | 
|  | std::vector<int64_t> frame_dependencies; | 
|  | }; | 
|  |  | 
|  | Packet CreatePacket( | 
|  | bool first_in_frame, | 
|  | bool last_in_frame, | 
|  | uint16_t seq_num, | 
|  | uint16_t frame_id, | 
|  | bool is_key_frame, | 
|  | std::vector<int64_t> ref_frame_ids = std::vector<int64_t>()) { | 
|  | Packet packet; | 
|  | packet.seq_num = seq_num; | 
|  | packet.first_in_frame = first_in_frame; | 
|  | if (first_in_frame) { | 
|  | packet.is_keyframe = is_key_frame; | 
|  | packet.frame_id = frame_id; | 
|  | RTC_DCHECK(!is_key_frame || ref_frame_ids.empty()); | 
|  | packet.frame_dependencies = std::move(ref_frame_ids); | 
|  | } | 
|  | return packet; | 
|  | } | 
|  |  | 
|  | class PacketStreamCreator final { | 
|  | public: | 
|  | PacketStreamCreator() : seq_num_(0), frame_id_(0), next_is_key_frame_(true) {} | 
|  |  | 
|  | Packet NextPacket() { | 
|  | std::vector<int64_t> ref_frame_ids; | 
|  | if (!next_is_key_frame_) { | 
|  | ref_frame_ids.push_back(frame_id_ - 1); | 
|  | } | 
|  |  | 
|  | Packet 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_; | 
|  | int64_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, | 
|  | bool buffering_allowed) override { | 
|  | EXPECT_TRUE(buffering_allowed);  // (Flag useful elsewhere.) | 
|  | EXPECT_FALSE(LastKeyFrameRequest()); | 
|  | EXPECT_FALSE(LastLossNotification()); | 
|  | last_loss_notification_.emplace(last_decoded_seq_num, last_received_seq_num, | 
|  | decodability_flag); | 
|  | } | 
|  |  | 
|  | void OnReceivedPacket(const Packet& packet) { | 
|  | EXPECT_FALSE(LastKeyFrameRequest()); | 
|  | EXPECT_FALSE(LastLossNotification()); | 
|  |  | 
|  | if (packet.first_in_frame) { | 
|  | previous_first_packet_in_frame_ = packet; | 
|  | LossNotificationController::FrameDetails frame; | 
|  | frame.is_keyframe = packet.is_keyframe; | 
|  | frame.frame_id = packet.frame_id; | 
|  | frame.frame_dependencies = packet.frame_dependencies; | 
|  | uut_.OnReceivedPacket(packet.seq_num, &frame); | 
|  | } else { | 
|  | uut_.OnReceivedPacket(packet.seq_num, nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnAssembledFrame(uint16_t first_seq_num, | 
|  | int64_t frame_id, | 
|  | bool discardable) { | 
|  | EXPECT_FALSE(LastKeyFrameRequest()); | 
|  | EXPECT_FALSE(LastLossNotification()); | 
|  |  | 
|  | ASSERT_TRUE(previous_first_packet_in_frame_); | 
|  | uut_.OnAssembledFrame(first_seq_num, frame_id, discardable, | 
|  | previous_first_packet_in_frame_->frame_dependencies); | 
|  | } | 
|  |  | 
|  | 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<Packet> 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})); | 
|  | } | 
|  |  | 
|  | 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.seq_num, key_frame_packet.frame_id, 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.seq_num, repeated_packet.seq_num, | 
|  | false); | 
|  | } | 
|  | OnReceivedPacket(repeated_packet); | 
|  | } | 
|  |  | 
|  | TEST_F(LossNotificationControllerTest, | 
|  | RecognizesDependencyAcrossIntraFrameThatIsNotAKeyframe) { | 
|  | int last_seq_num = 1; | 
|  | auto receive = [&](bool is_key_frame, int64_t frame_id, | 
|  | std::vector<int64_t> ref_frame_ids) { | 
|  | ++last_seq_num; | 
|  | OnReceivedPacket(CreatePacket( | 
|  | /*first_in_frame=*/true, /*last_in_frame=*/true, last_seq_num, frame_id, | 
|  | is_key_frame, std::move(ref_frame_ids))); | 
|  | OnAssembledFrame(last_seq_num, frame_id, /*discardable=*/false); | 
|  | }; | 
|  | //  11 -- 13 | 
|  | //   |     | | 
|  | //  10    12 | 
|  | receive(/*is_key_frame=*/true, /*frame_id=*/10, /*ref_frame_ids=*/{}); | 
|  | receive(/*is_key_frame=*/false, /*frame_id=*/11, /*ref_frame_ids=*/{10}); | 
|  | receive(/*is_key_frame=*/false, /*frame_id=*/12, /*ref_frame_ids=*/{}); | 
|  | receive(/*is_key_frame=*/false, /*frame_id=*/13, /*ref_frame_ids=*/{11, 12}); | 
|  | EXPECT_FALSE(LastLossNotification()); | 
|  | } | 
|  |  | 
|  | 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<int64_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 int64_t never_received_frame_id_ = 123; | 
|  |  | 
|  | uint16_t seq_num_; | 
|  | int64_t frame_id_; | 
|  | }; | 
|  |  | 
|  | TEST_F(LossNotificationControllerTestDecodabilityFlag, | 
|  | SinglePacketFrameWithDecodableDependencies) { | 
|  | ReceiveKeyFrame(); | 
|  | CreateGap(); | 
|  |  | 
|  | const std::vector<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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<int64_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 |