diff --git a/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc b/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc new file mode 100644 index 000000000..e45adb16d --- /dev/null +++ b/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc @@ -0,0 +1,560 @@ +/* + * Copyright (c) 2012 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/rtp_rtcp/source/forward_error_correction.h" + +#include +#include + +#include "rtp_utility.h" + +using webrtc::ForwardErrorCorrection; + +// Minimum RTP header size in bytes. +const uint8_t kRtpHeaderSize = 12; + +// Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum. +const uint8_t kTransportOverhead = 28; + +// Maximum number of media packets used in the FEC (RFC 5109). +const uint8_t kMaxNumberMediaPackets = ForwardErrorCorrection::kMaxMediaPackets; + +template void ClearList(std::list* my_list) { + T* packet = NULL; + while (!my_list->empty()) { + packet = my_list->front(); + delete packet; + my_list->pop_front(); + } +} + +class RtpFecTest : public ::testing::Test { + protected: + RtpFecTest() + : fec_(new ForwardErrorCorrection(0)), + ssrc_(rand()), + fec_seq_num_(0) { + } + + ForwardErrorCorrection* fec_; + int ssrc_; + uint16_t fec_seq_num_; + + std::list media_packet_list_; + std::list fec_packet_list_; + std::list received_packet_list_; + std::list recovered_packet_list_; + + // Media packet "i" is lost if media_loss_mask_[i] = 1, + // received if media_loss_mask_[i] = 0. + int media_loss_mask_[kMaxNumberMediaPackets]; + + // FEC packet "i" is lost if fec_loss_mask_[i] = 1, + // received if fec_loss_mask_[i] = 0. + int fec_loss_mask_[kMaxNumberMediaPackets]; + + // Construct the media packet list, up to |num_media_packets| packets. + // Returns the next sequence number after the last media packet. + // (this will be the sequence of the first FEC packet) + int ConstructMediaPackets(int num_media_packets); + + // Construct the received packet list: a subset of the media and FEC packets. + void NetworkReceivedPackets(); + + // Add packet from |packet_list| to list of received packets, using the + // |loss_mask|. + // The |packet_list| may be a media packet list (is_fec = false), or a + // FEC packet list (is_fec = true). + void ReceivedPackets( + const std::list& packet_list, + int* loss_mask, + bool is_fec); + + // Check for complete recovery after FEC decoding. + bool IsRecoveryComplete(); + + // Delete the received packets. + void FreeRecoveredPacketList(); + + // Delete the media and FEC packets. + void TearDown(); +}; + +TEST_F(RtpFecTest, HandleIncorrectInputs) { + int num_important_packets = 0; + bool use_unequal_protection = false; + uint8_t protection_factor = 60; + + // Media packet list is empty. + EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + int num_media_packets = 10; + ConstructMediaPackets(num_media_packets); + + num_important_packets = -1; + // Number of important packets below 0. + EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + num_important_packets = 12; + // Number of important packets greater than number of media packets. + EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + num_media_packets = kMaxNumberMediaPackets + 1; + ConstructMediaPackets(num_media_packets); + + num_important_packets = 0; + // Number of media packet is above maximum allowed (kMaxNumberMediaPackets). + EXPECT_EQ(-1, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); +} + +TEST_F(RtpFecTest, FecRecoveryNoLoss) { + bool frame_complete = true; + const int num_important_packets = 0; + const bool use_unequal_protection = false; + const int num_media_packets = 4; + uint8_t protection_factor = 60; + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + EXPECT_EQ(0, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1, static_cast(fec_packet_list_.size())); + + // No packets lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // No packets lost, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryWithLoss) { + bool frame_complete = true; + const int num_important_packets = 0; + const bool use_unequal_protection = false; + const int num_media_packets = 4; + uint8_t protection_factor = 60; + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + EXPECT_EQ(0, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1, static_cast(fec_packet_list_.size())); + + // 1 media packet lost + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // 2 media packets lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[1] = 1; + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // 2 packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryWithLoss50perc) { + bool frame_complete = true; + const int num_important_packets = 0; + const bool use_unequal_protection = false; + const int num_media_packets = 4; + const uint8_t protection_factor = 255; + + // Packet Mask for (4,4,0) code: + // (num_media_packets = 4; num_fec_packets = 4, num_important_packets = 0) + + // media#0 media#1 media#2 media#3 + // fec#0: 1 1 0 0 + // fec#1: 1 0 1 0 + // fec#2: 0 1 0 1 + // fec#3: 0 0 1 1 + // + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + EXPECT_EQ(0, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 4 FEC packets. + EXPECT_EQ(4, static_cast(fec_packet_list_.size())); + + // 4 packets lost: 3 media packets and one FEC packet#2 lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + fec_loss_mask_[2] = 1; + media_loss_mask_[0] = 1; + media_loss_mask_[2] = 1; + media_loss_mask_[3] = 1; + + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // With media packet#1 and FEC packets #0, #1, #3, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // 4 packets lost: all media packets + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 1, sizeof(fec_loss_mask_)); + media_loss_mask_[0] = 1; + media_loss_mask_[1] = 1; + media_loss_mask_[2] = 1; + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // Cannot get complete recovery for this loss configuration. + EXPECT_FALSE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryNoLossUep) { + bool frame_complete = true; + const int num_important_packets = 2; + const bool use_unequal_protection = true; + const int num_media_packets = 4; + const uint8_t protection_factor = 60; + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + EXPECT_EQ(0, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1, static_cast(fec_packet_list_.size())); + + // No packets lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // No packets lost, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryWithLossUep) { + bool frame_complete = true; + const int num_important_packets = 2; + const bool use_unequal_protection = true; + const int num_media_packets = 4; + const uint8_t protection_factor = 60; + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + EXPECT_EQ(0, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 1 FEC packet. + EXPECT_EQ(1, static_cast(fec_packet_list_.size())); + + // 1 media packet lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // 2 media packets lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[1] = 1; + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // 2 packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryWithLoss50percUep) { + bool frame_complete = true; + const int num_important_packets = 1; + const bool use_unequal_protection = true; + const int num_media_packets = 4; + const uint8_t protection_factor = 255; + + // Packet Mask for (4,4,1) code: + // (num_media_packets = 4; num_fec_packets = 4, num_important_packets = 2) + + // media#0 media#1 media#2 media#3 + // fec#0: 1 0 0 0 + // fec#1: 1 1 0 0 + // fec#2: 1 0 1 1 + // fec#3: 0 1 1 0 + // + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + EXPECT_EQ(0, fec_->GenerateFEC(media_packet_list_, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 4 FEC packets. + EXPECT_EQ(4, static_cast(fec_packet_list_.size())); + + // 4 packets lost: 3 media packets and FEC packet#1 lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + fec_loss_mask_[1] = 1; + media_loss_mask_[0] = 1; + media_loss_mask_[2] = 1; + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // With media packet#1 and FEC packets #0, #2, #3, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // 4 packets lost: 3 media packets and one FEC packet#2 lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + fec_loss_mask_[2] = 1; + media_loss_mask_[0] = 1; + media_loss_mask_[2] = 1; + media_loss_mask_[3] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , &recovered_packet_list_, + fec_seq_num_, frame_complete)); + + // Cannot get complete recovery for this loss configuration. + EXPECT_FALSE(IsRecoveryComplete()); +} + +// TODO(marpan): Add more test cases. + +void RtpFecTest::TearDown() { + FreeRecoveredPacketList(); + ClearList(&media_packet_list_); + EXPECT_TRUE(media_packet_list_.empty()); + + fec_packet_list_.clear(); +} + +void RtpFecTest::FreeRecoveredPacketList() { + ClearList(&recovered_packet_list_); +} + +bool RtpFecTest::IsRecoveryComplete() { + // Check that the number of media and recovered packets are equal. + if (media_packet_list_.size() != recovered_packet_list_.size()) { + return false; + } + + ForwardErrorCorrection::Packet* media_packet; + ForwardErrorCorrection::RecoveredPacket* recovered_packet; + + bool recovery = true; + + std::list::iterator + media_packet_list_item = media_packet_list_.begin(); + std::list::iterator + recovered_packet_list_item = recovered_packet_list_.begin(); + while (media_packet_list_item != media_packet_list_.end()) { + if (recovered_packet_list_item == recovered_packet_list_.end()) { + return false; + } + media_packet = *media_packet_list_item; + recovered_packet = *recovered_packet_list_item; + if (recovered_packet->pkt->length != media_packet->length) { + return false; + } + if (memcmp(recovered_packet->pkt->data, media_packet->data, + media_packet->length) != 0) { + return false; + } + media_packet_list_item++; + recovered_packet_list_item++; + } + return recovery; +} + +void RtpFecTest::NetworkReceivedPackets() { + const bool kFecPacket = true; + ReceivedPackets(media_packet_list_, media_loss_mask_, !kFecPacket); + ReceivedPackets(fec_packet_list_, fec_loss_mask_, kFecPacket); +} + +void RtpFecTest:: ReceivedPackets( + const std::list& packet_list, + int* loss_mask, + bool is_fec) { + ForwardErrorCorrection::Packet* packet; + ForwardErrorCorrection::ReceivedPacket* received_packet; + int seq_num = fec_seq_num_; + int packet_idx = 0; + + std::list::const_iterator + packet_list_item = packet_list.begin(); + + while (packet_list_item != packet_list.end()) { + packet = *packet_list_item; + if (loss_mask[packet_idx] == 0) { + received_packet = new ForwardErrorCorrection::ReceivedPacket; + received_packet->pkt = new ForwardErrorCorrection::Packet; + received_packet_list_.push_back(received_packet); + received_packet->pkt->length = packet->length; + memcpy(received_packet->pkt->data, packet->data, + packet->length); + received_packet->isFec = is_fec; + if (!is_fec) { + // For media packets, the sequence number and marker bit is + // obtained from RTP header. These were set in ConstructMediaPackets(). + received_packet->seqNum = + webrtc::ModuleRTPUtility::BufferToUWord16(&packet->data[2]); + received_packet->lastMediaPktInFrame = (packet->data[1] & 0x80) != 0; + } + else { + // The sequence number, marker bit, and ssrc number are defined in the + // RTP header of the FEC packet, which is not constructed in this test. + // So we set these values below based on the values generated in + // ConstructMediaPackets(). + + received_packet->seqNum = seq_num; + // The marker bit (last media packet of frame) for FEC packets is + // always zero. + received_packet->lastMediaPktInFrame = false; + // The ssrc value for FEC packets is set to the one used for the + // media packets in ConstructMediaPackets(). + received_packet->ssrc = ssrc_; + } + } + packet_idx++; + packet_list_item ++; + // Sequence number of FEC packets are defined as increment by 1 from + // last media packet in frame. + if (is_fec) seq_num++; + } +} + + +int RtpFecTest::ConstructMediaPackets(int num_media_packets) { + ForwardErrorCorrection::Packet* media_packet; + int sequence_number = rand(); + int time_stamp = rand(); + + for (int i = 0; i < num_media_packets; i++) { + media_packet = new ForwardErrorCorrection::Packet; + media_packet_list_.push_back(media_packet); + media_packet->length = + static_cast((static_cast(rand()) / RAND_MAX) * + (IP_PACKET_SIZE - kRtpHeaderSize - kTransportOverhead - + ForwardErrorCorrection::PacketOverhead())); + + if (media_packet->length < kRtpHeaderSize) { + media_packet->length = kRtpHeaderSize; + } + // Generate random values for the first 2 bytes + media_packet->data[0] = static_cast(rand() % 256); + media_packet->data[1] = static_cast(rand() % 256); + + // The first two bits are assumed to be 10 by the FEC encoder. + // In fact the FEC decoder will set the two first bits to 10 regardless of + // what they actually were. Set the first two bits to 10 so that a memcmp + // can be performed for the whole restored packet. + media_packet->data[0] |= 0x80; + media_packet->data[0] &= 0xbf; + + // FEC is applied to a whole frame. + // A frame is signaled by multiple packets without the marker bit set + // followed by the last packet of the frame for which the marker bit is set. + // Only push one (fake) frame to the FEC. + media_packet->data[1] &= 0x7f; + + webrtc::ModuleRTPUtility::AssignUWord16ToBuffer(&media_packet->data[2], + sequence_number); + webrtc::ModuleRTPUtility::AssignUWord32ToBuffer(&media_packet->data[4], + time_stamp); + webrtc::ModuleRTPUtility::AssignUWord32ToBuffer(&media_packet->data[8], + ssrc_); + + // Generate random values for payload. + for (int j = 12; j < media_packet->length; j++) { + media_packet->data[j] = static_cast (rand() % 256); + } + sequence_number++; + } + // Last packet, set marker bit. + media_packet->data[1] |= 0x80; + return sequence_number; +} diff --git a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi index 53db098e4..ea705a848 100644 --- a/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi +++ b/src/modules/rtp_rtcp/source/rtp_rtcp_tests.gypi @@ -23,6 +23,7 @@ ], 'sources': [ 'receiver_fec_unittest.cc', + 'rtp_fec_unittest.cc', 'rtp_format_vp8_unittest.cc', 'rtp_format_vp8_test_helper.cc', 'rtp_format_vp8_test_helper.h',