blob: 2ba12fef7465ec249880f3adc04eb5983155a20d [file] [log] [blame]
/*
* 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