| /* | |
| * 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 |