diff --git a/src/modules/rtp_rtcp/source/forward_error_correction.cc b/src/modules/rtp_rtcp/source/forward_error_correction.cc index 7ce0fe2cc..e756e7d4c 100644 --- a/src/modules/rtp_rtcp/source/forward_error_correction.cc +++ b/src/modules/rtp_rtcp/source/forward_error_correction.cc @@ -8,12 +8,13 @@ * 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 #include -#include "modules/rtp_rtcp/source/forward_error_correction.h" #include "modules/rtp_rtcp/source/forward_error_correction_internal.h" #include "modules/rtp_rtcp/source/rtp_utility.h" #include "system_wrappers/interface/trace.h" @@ -108,8 +109,8 @@ int32_t ForwardErrorCorrection::GenerateFEC( return -1; } const uint16_t numMediaPackets = mediaPacketList.size(); - const uint8_t lBit = numMediaPackets > 16 ? 1 : 0; - const uint16_t numMaskBytes = (lBit == 1)? + bool lBit = (numMediaPackets > 8 * kMaskSizeLBitClear); + uint16_t numMaskBytes = lBit ? kMaskSizeLBitSet : kMaskSizeLBitClear; if (numMediaPackets > kMaxMediaPackets) { @@ -176,15 +177,28 @@ int32_t ForwardErrorCorrection::GenerateFEC( } // -- Generate packet masks -- - uint8_t* packetMask = new uint8_t[numFecPackets * numMaskBytes]; + // Always allocate space for a large mask. + uint8_t* packetMask = new uint8_t[numFecPackets * kMaskSizeLBitSet]; memset(packetMask, 0, numFecPackets * numMaskBytes); internal::GeneratePacketMasks(numMediaPackets, numFecPackets, numImportantPackets, useUnequalProtection, packetMask); - GenerateFecBitStrings(mediaPacketList, packetMask, numFecPackets); + int numMaskBits = InsertZerosInBitMasks(mediaPacketList, packetMask, + numMaskBytes, numFecPackets); - GenerateFecUlpHeaders(mediaPacketList, packetMask, numFecPackets); + lBit = (numMaskBits > 8 * kMaskSizeLBitClear); + + if (numMaskBits < 0) { + delete [] packetMask; + return -1; + } + if (lBit) { + numMaskBytes = kMaskSizeLBitSet; + } + + GenerateFecBitStrings(mediaPacketList, packetMask, numFecPackets, lBit); + GenerateFecUlpHeaders(mediaPacketList, packetMask, lBit, numFecPackets); delete [] packetMask; return 0; @@ -193,12 +207,15 @@ int32_t ForwardErrorCorrection::GenerateFEC( void ForwardErrorCorrection::GenerateFecBitStrings( const PacketList& mediaPacketList, uint8_t* packetMask, - uint32_t numFecPackets) { + uint32_t numFecPackets, + bool lBit) { + if (mediaPacketList.empty()) { + return; + } uint8_t mediaPayloadLength[2]; - const uint8_t lBit = mediaPacketList.size() > 16 ? 1 : 0; - const uint16_t numMaskBytes = (lBit == 1) ? + const uint16_t numMaskBytes = lBit ? kMaskSizeLBitSet : kMaskSizeLBitClear; - const uint16_t ulpHeaderSize = (lBit == 1) ? + const uint16_t ulpHeaderSize = lBit ? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear; const uint16_t fecRtpOffset = kFecHeaderSize + ulpHeaderSize - kRtpHeaderSize; @@ -207,6 +224,7 @@ void ForwardErrorCorrection::GenerateFecBitStrings( uint32_t pktMaskIdx = i * numMaskBytes; uint32_t mediaPktIdx = 0; uint16_t fecPacketLength = 0; + uint16_t prevSeqNum = ParseSequenceNumber((*mediaListIt)->data); while (mediaListIt != mediaPacketList.end()) { // Each FEC packet has a multiple byte mask. if (packetMask[pktMaskIdx] & (1 << (7 - mediaPktIdx))) { @@ -257,7 +275,11 @@ void ForwardErrorCorrection::GenerateFecBitStrings( } } mediaListIt++; - mediaPktIdx++; + if (mediaListIt != mediaPacketList.end()) { + uint16_t seqNum = ParseSequenceNumber((*mediaListIt)->data); + mediaPktIdx += static_cast(seqNum - prevSeqNum); + prevSeqNum = seqNum; + } if (mediaPktIdx == 8) { // Switch to the next mask byte. mediaPktIdx = 0; @@ -269,9 +291,111 @@ void ForwardErrorCorrection::GenerateFecBitStrings( } } +int ForwardErrorCorrection::InsertZerosInBitMasks( + const PacketList& media_packets, + uint8_t* packet_mask, + uint16_t num_mask_bytes, + uint32_t num_fec_packets) { + uint8_t* new_mask = NULL; + if (media_packets.size() <= 1) { + return media_packets.size(); + } + int last_seq_num = ParseSequenceNumber(media_packets.back()->data); + int first_seq_num = ParseSequenceNumber(media_packets.front()->data); + int total_missing_seq_nums = static_cast(last_seq_num - + first_seq_num) - + media_packets.size() + 1; + if (total_missing_seq_nums == 0) { + // All sequence numbers are covered by the packet mask. No zero insertion + // required. + return media_packets.size(); + } + // Allocate the new mask. + int new_mask_bytes = kMaskSizeLBitClear; + if (media_packets.size() + total_missing_seq_nums > 8 * kMaskSizeLBitClear) { + new_mask_bytes = kMaskSizeLBitSet; + } + new_mask = new uint8_t[num_fec_packets * kMaskSizeLBitSet]; + memset(new_mask, 0, num_fec_packets * kMaskSizeLBitSet); + + PacketList::const_iterator it = media_packets.begin(); + uint16_t prev_seq_num = first_seq_num; + ++it; + + // Insert the first column. + CopyColumn(new_mask, new_mask_bytes, packet_mask, num_mask_bytes, + num_fec_packets, 0, 0); + int new_bit_index = 1; + int old_bit_index = 1; + // Insert zeros in the bit mask for every hole in the sequence. + for (; it != media_packets.end(); ++it) { + if (new_bit_index == 8 * kMaskSizeLBitSet) { + // We can only cover up to 48 packets. + break; + } + uint16_t seq_num = ParseSequenceNumber((*it)->data); + const int zeros_to_insert = + static_cast(seq_num - prev_seq_num - 1); + if (zeros_to_insert > 0) { + InsertZeroColumns(zeros_to_insert, new_mask, new_mask_bytes, + num_fec_packets, new_bit_index); + } + new_bit_index += zeros_to_insert; + CopyColumn(new_mask, new_mask_bytes, packet_mask, num_mask_bytes, + num_fec_packets, new_bit_index, old_bit_index); + ++new_bit_index; + ++old_bit_index; + prev_seq_num = seq_num; + } + if (new_bit_index % 8 != 0) { + // We didn't fill the last byte. Shift bits to correct position. + for (uint16_t row = 0; row < num_fec_packets; ++row) { + int new_byte_index = row * new_mask_bytes + new_bit_index / 8; + new_mask[new_byte_index] <<= (7 - (new_bit_index % 8)); + } + } + // Replace the old mask with the new. + memcpy(packet_mask, new_mask, kMaskSizeLBitSet * num_fec_packets); + delete [] new_mask; + return new_bit_index; +} + +void ForwardErrorCorrection::InsertZeroColumns(int num_zeros, + uint8_t* new_mask, + int new_mask_bytes, + int num_fec_packets, + int new_bit_index) { + for (uint16_t row = 0; row < num_fec_packets; ++row) { + const int new_byte_index = row * new_mask_bytes + new_bit_index / 8; + const int max_shifts = (7 - (new_bit_index % 8)); + new_mask[new_byte_index] <<= std::min(num_zeros, max_shifts); + } +} + +void ForwardErrorCorrection::CopyColumn(uint8_t* new_mask, + int new_mask_bytes, + uint8_t* old_mask, + int old_mask_bytes, + int num_fec_packets, + int new_bit_index, + int old_bit_index) { + // Copy column from the old mask to the beginning of the new mask and shift it + // out from the old mask. + for (uint16_t row = 0; row < num_fec_packets; ++row) { + int new_byte_index = row * new_mask_bytes + new_bit_index / 8; + int old_byte_index = row * old_mask_bytes + old_bit_index / 8; + new_mask[new_byte_index] |= ((old_mask[old_byte_index] & 0x80) >> 7); + if (new_bit_index % 8 != 7) { + new_mask[new_byte_index] <<= 1; + } + old_mask[old_byte_index] <<= 1; + } +} + void ForwardErrorCorrection::GenerateFecUlpHeaders( const PacketList& mediaPacketList, uint8_t* packetMask, + bool lBit, uint32_t numFecPackets) { // -- Generate FEC and ULP headers -- // @@ -297,10 +421,9 @@ void ForwardErrorCorrection::GenerateFecUlpHeaders( PacketList::const_iterator mediaListIt = mediaPacketList.begin(); Packet* mediaPacket = *mediaListIt; assert(mediaPacket != NULL); - const uint8_t lBit = mediaPacketList.size() > 16 ? 1 : 0; - const uint16_t numMaskBytes = (lBit == 1)? + const uint16_t numMaskBytes = lBit ? kMaskSizeLBitSet : kMaskSizeLBitClear; - const uint16_t ulpHeaderSize = (lBit == 1)? + const uint16_t ulpHeaderSize = lBit ? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear; for (uint32_t i = 0; i < numFecPackets; i++) { @@ -669,6 +792,10 @@ void ForwardErrorCorrection::DiscardOldPackets( assert(recoveredPacketList->size() <= kMaxMediaPackets); } +uint16_t ForwardErrorCorrection::ParseSequenceNumber(uint8_t* packet) { + return (packet[2] << 8) + packet[3]; +} + int32_t ForwardErrorCorrection::DecodeFEC( ReceivedPacketList* receivedPacketList, RecoveredPacketList* recoveredPacketList) { diff --git a/src/modules/rtp_rtcp/source/forward_error_correction.h b/src/modules/rtp_rtcp/source/forward_error_correction.h index a7588a858..1dcb5b48b 100644 --- a/src/modules/rtp_rtcp/source/forward_error_correction.h +++ b/src/modules/rtp_rtcp/source/forward_error_correction.h @@ -214,11 +214,50 @@ class ForwardErrorCorrection { void GenerateFecUlpHeaders(const PacketList& mediaPacketList, uint8_t* packetMask, + bool lBit, uint32_t numFecPackets); + // Analyzes |media_packets| for holes in the sequence and inserts zero columns + // into the |packet_mask| where those holes are found. Zero columns means that + // those packets will have no protection. + // Returns the number of bits used for one row of the new packet mask. + // Requires that |packet_mask| has at least 6 * |num_fec_packets| bytes + // allocated. + int InsertZerosInBitMasks(const PacketList& media_packets, + uint8_t* packet_mask, + uint16_t num_mask_bytes, + uint32_t num_fec_packets); + + // Inserts |num_zeros| zero columns into |new_mask| at position + // |new_bit_index|. If the current byte of |new_mask| can't fit all zeros, the + // byte will be filled with zeros from |new_bit_index|, but the next byte will + // be untouched. + static void InsertZeroColumns(int num_zeros, + uint8_t* new_mask, + int new_mask_bytes, + int num_fec_packets, + int new_bit_index); + + // Copies the left most bit column from the byte pointed to by + // |old_bit_index| in |old_mask| to the right most column of the byte pointed + // to by |new_bit_index| in |new_mask|. |old_mask_bytes| and |new_mask_bytes| + // represent the number of bytes used per row for each mask. |num_fec_packets| + // represent the number of rows of the masks. + // The copied bit is shifted out from |old_mask| and is shifted one step to + // the left in |new_mask|. |new_mask| will contain "xxxx xxn0" after this + // operation, where x are previously inserted bits and n is the new bit. + static void CopyColumn(uint8_t* new_mask, + int new_mask_bytes, + uint8_t* old_mask, + int old_mask_bytes, + int num_fec_packets, + int new_bit_index, + int old_bit_index); + void GenerateFecBitStrings(const PacketList& mediaPacketList, uint8_t* packetMask, - uint32_t numFecPackets); + uint32_t numFecPackets, + bool lBit); // Insert received packets into FEC or recovered list. void InsertPackets(ReceivedPacketList* receivedPacketList, @@ -279,6 +318,7 @@ class ForwardErrorCorrection { static void DiscardFECPacket(FecPacket* fec_packet); static void DiscardOldPackets(RecoveredPacketList* recoveredPacketList); + static uint16_t ParseSequenceNumber(uint8_t* packet); int32_t _id; std::vector _generatedFecPackets; diff --git a/src/modules/rtp_rtcp/source/producer_fec.cc b/src/modules/rtp_rtcp/source/producer_fec.cc index 20e024976..864bb5b4a 100644 --- a/src/modules/rtp_rtcp/source/producer_fec.cc +++ b/src/modules/rtp_rtcp/source/producer_fec.cc @@ -10,8 +10,6 @@ #include "modules/rtp_rtcp/source/producer_fec.h" -#include - #include "modules/rtp_rtcp/source/forward_error_correction.h" #include "modules/rtp_rtcp/source/rtp_utility.h" @@ -79,8 +77,10 @@ ProducerFec::ProducerFec(ForwardErrorCorrection* fec) num_frames_(0), incomplete_frame_(false), num_first_partition_(0), - params_() { + params_(), + new_params_() { memset(¶ms_, 0, sizeof(params_)); + memset(&new_params_, 0, sizeof(new_params_)); } ProducerFec::~ProducerFec() { @@ -96,7 +96,9 @@ void ProducerFec::SetFecParameters(const FecProtectionParams* params, num_first_partition = ForwardErrorCorrection::kMaxMediaPackets; } - params_ = *params; + // Store the new params and apply them for the next set of FEC packets being + // produced. + new_params_ = *params; num_first_partition_ = num_first_partition; } @@ -118,6 +120,9 @@ int ProducerFec::AddRtpPacketAndGenerateFec(const uint8_t* data_buffer, int payload_length, int rtp_header_length) { assert(fec_packets_.empty()); + if (media_packets_fec_.empty()) { + params_ = new_params_; + } incomplete_frame_ = true; const bool marker_bit = (data_buffer[1] & kRtpMarkerBitMask) ? true : false; if (media_packets_fec_.size() < ForwardErrorCorrection::kMaxMediaPackets) { @@ -131,6 +136,9 @@ int ProducerFec::AddRtpPacketAndGenerateFec(const uint8_t* data_buffer, ++num_frames_; incomplete_frame_ = false; } + // Produce FEC over at most |params_.max_fec_frames| frames, or as soon as + // the wasted overhead (actual overhead - requested protection) is less than + // |kMaxOverhead|. if (!incomplete_frame_ && (num_frames_ == params_.max_fec_frames || (Overhead() - params_.fec_rate) < kMaxOverhead)) { @@ -183,13 +191,17 @@ RedPacket* ProducerFec::GetFecPacket(int red_pl_type, int fec_pl_type, } int ProducerFec::Overhead() const { + // Overhead is defined as relative to the number of media packets, and not + // relative to total number of packets. This definition is inhereted from the + // protection factor produced by video_coding module and how the FEC + // generation is implemented. + assert(!media_packets_fec_.empty()); int num_fec_packets = params_.fec_rate * media_packets_fec_.size(); // Ceil. int rounding = (num_fec_packets % (1 << 8) > 0) ? (1 << 8) : 0; num_fec_packets = (num_fec_packets + rounding) >> 8; // Return the overhead in Q8. - return (num_fec_packets << 8) / - (media_packets_fec_.size() + num_fec_packets); + return (num_fec_packets << 8) / media_packets_fec_.size(); } void ProducerFec::DeletePackets() { diff --git a/src/modules/rtp_rtcp/source/producer_fec.h b/src/modules/rtp_rtcp/source/producer_fec.h index 4c0b951d4..f0fa54e15 100644 --- a/src/modules/rtp_rtcp/source/producer_fec.h +++ b/src/modules/rtp_rtcp/source/producer_fec.h @@ -69,6 +69,7 @@ class ProducerFec { bool incomplete_frame_; int num_first_partition_; FecProtectionParams params_; + FecProtectionParams new_params_; }; } // namespace webrtc diff --git a/src/modules/rtp_rtcp/source/producer_fec_unittest.cc b/src/modules/rtp_rtcp/source/producer_fec_unittest.cc index fcd8c66a8..89d2dbca2 100644 --- a/src/modules/rtp_rtcp/source/producer_fec_unittest.cc +++ b/src/modules/rtp_rtcp/source/producer_fec_unittest.cc @@ -55,7 +55,7 @@ class ProducerFecTest : public ::testing::Test { }; TEST_F(ProducerFecTest, OneFrameFec) { - const int kNumPackets = 3; + const int kNumPackets = 4; FecProtectionParams params = {5, false, 3}; std::list rtp_packets; generator_->NewFrame(kNumPackets); @@ -75,7 +75,7 @@ TEST_F(ProducerFecTest, OneFrameFec) { kFecPayloadType, seq_num); EXPECT_FALSE(producer_->FecAvailable()); - EXPECT_TRUE(packet != NULL); + ASSERT_TRUE(packet != NULL); VerifyHeader(seq_num, last_timestamp, kRedPayloadType, kFecPayloadType, packet, false); while (!rtp_packets.empty()) { diff --git a/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc b/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc index d794701e2..ec1497b37 100644 --- a/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc +++ b/src/modules/rtp_rtcp/source/rtp_fec_unittest.cc @@ -26,6 +26,10 @@ const uint8_t kTransportOverhead = 28; // Maximum number of media packets used in the FEC (RFC 5109). const uint8_t kMaxNumberMediaPackets = ForwardErrorCorrection::kMaxMediaPackets; +typedef std::list PacketList; +typedef std::list ReceivedPacketList; +typedef std::list RecoveredPacketList; + template void ClearList(std::list* my_list) { T* packet = NULL; while (!my_list->empty()) { @@ -47,10 +51,10 @@ class RtpFecTest : public ::testing::Test { 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_; + PacketList media_packet_list_; + PacketList fec_packet_list_; + ReceivedPacketList received_packet_list_; + RecoveredPacketList recovered_packet_list_; // Media packet "i" is lost if media_loss_mask_[i] = 1, // received if media_loss_mask_[i] = 0. @@ -63,6 +67,8 @@ class RtpFecTest : public ::testing::Test { // 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 ConstructMediaPacketsSeqNum(int num_media_packets, + int start_seq_num); int ConstructMediaPackets(int num_media_packets); // Construct the received packet list: a subset of the media and FEC packets. @@ -73,7 +79,7 @@ class RtpFecTest : public ::testing::Test { // 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, + const PacketList& packet_list, int* loss_mask, bool is_fec); @@ -395,6 +401,221 @@ TEST_F(RtpFecTest, FecRecoveryWithLoss50percUep) { EXPECT_FALSE(IsRecoveryComplete()); } +TEST_F(RtpFecTest, FecRecoveryNonConsecutivePackets) { + const int num_important_packets = 0; + const bool use_unequal_protection = false; + const int num_media_packets = 5; + uint8_t protection_factor = 60; + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + // Create a new temporary packet list for generating FEC packets. + // This list should have every other packet removed. + PacketList protected_media_packets; + int i = 0; + for (PacketList::iterator it = media_packet_list_.begin(); + it != media_packet_list_.end(); ++it, ++i) { + if (i % 2 == 0) + protected_media_packets.push_back(*it); + } + + EXPECT_EQ(0, fec_->GenerateFEC(protected_media_packets, + 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 protected media packet lost + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[2] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // Unprotected packet lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[1] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // Unprotected packet lost. Recovery not possible. + EXPECT_FALSE(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_[0] = 1; + media_loss_mask_[2] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // 2 protected packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryNonConsecutivePacketsExtension) { + const int num_important_packets = 0; + const bool use_unequal_protection = false; + const int num_media_packets = 21; + uint8_t protection_factor = 127; + + fec_seq_num_ = ConstructMediaPackets(num_media_packets); + + // Create a new temporary packet list for generating FEC packets. + // This list should have every other packet removed. + PacketList protected_media_packets; + int i = 0; + for (PacketList::iterator it = media_packet_list_.begin(); + it != media_packet_list_.end(); ++it, ++i) { + if (i % 2 == 0) + protected_media_packets.push_back(*it); + } + + // Zero column insertion will have to extend the size of the packet + // mask since the number of actual packets are 21, while the number + // of protected packets are 11. + EXPECT_EQ(0, fec_->GenerateFEC(protected_media_packets, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 5 FEC packet. + EXPECT_EQ(5, static_cast(fec_packet_list_.size())); + + // Last protected media packet lost + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[num_media_packets - 1] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // Last unprotected packet lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[num_media_packets - 2] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // Unprotected packet lost. Recovery not possible. + EXPECT_FALSE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // 6 media packets lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[num_media_packets - 11] = 1; + media_loss_mask_[num_media_packets - 9] = 1; + media_loss_mask_[num_media_packets - 7] = 1; + media_loss_mask_[num_media_packets - 5] = 1; + media_loss_mask_[num_media_packets - 3] = 1; + media_loss_mask_[num_media_packets - 1] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // 5 protected packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(IsRecoveryComplete()); +} + +TEST_F(RtpFecTest, FecRecoveryNonConsecutivePacketsWrap) { + const int num_important_packets = 0; + const bool use_unequal_protection = false; + const int num_media_packets = 21; + uint8_t protection_factor = 127; + + fec_seq_num_ = ConstructMediaPacketsSeqNum(num_media_packets, 0xFFFF - 5); + + // Create a new temporary packet list for generating FEC packets. + // This list should have every other packet removed. + PacketList protected_media_packets; + int i = 0; + for (PacketList::iterator it = media_packet_list_.begin(); + it != media_packet_list_.end(); ++it, ++i) { + if (i % 2 == 0) + protected_media_packets.push_back(*it); + } + + // Zero column insertion will have to extend the size of the packet + // mask since the number of actual packets are 21, while the number + // of protected packets are 11. + EXPECT_EQ(0, fec_->GenerateFEC(protected_media_packets, + protection_factor, + num_important_packets, + use_unequal_protection, + &fec_packet_list_)); + + // Expect 5 FEC packet. + EXPECT_EQ(5, static_cast(fec_packet_list_.size())); + + // Last protected media packet lost + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[num_media_packets - 1] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // One packet lost, one FEC packet, expect complete recovery. + EXPECT_TRUE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // Last unprotected packet lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[num_media_packets - 2] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // Unprotected packet lost. Recovery not possible. + EXPECT_FALSE(IsRecoveryComplete()); + FreeRecoveredPacketList(); + + // 6 media packets lost. + memset(media_loss_mask_, 0, sizeof(media_loss_mask_)); + memset(fec_loss_mask_, 0, sizeof(fec_loss_mask_)); + media_loss_mask_[num_media_packets - 11] = 1; + media_loss_mask_[num_media_packets - 9] = 1; + media_loss_mask_[num_media_packets - 7] = 1; + media_loss_mask_[num_media_packets - 5] = 1; + media_loss_mask_[num_media_packets - 3] = 1; + media_loss_mask_[num_media_packets - 1] = 1; + NetworkReceivedPackets(); + + EXPECT_EQ(0, fec_->DecodeFEC(&received_packet_list_ , + &recovered_packet_list_)); + + // 5 protected packets lost, one FEC packet, cannot get complete recovery. + EXPECT_FALSE(IsRecoveryComplete()); +} + // TODO(marpan): Add more test cases. void RtpFecTest::TearDown() { @@ -420,9 +641,9 @@ bool RtpFecTest::IsRecoveryComplete() { bool recovery = true; - std::list::iterator + PacketList::iterator media_packet_list_item = media_packet_list_.begin(); - std::list::iterator + RecoveredPacketList::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()) { @@ -450,7 +671,7 @@ void RtpFecTest::NetworkReceivedPackets() { } void RtpFecTest:: ReceivedPackets( - const std::list& packet_list, + const PacketList& packet_list, int* loss_mask, bool is_fec) { ForwardErrorCorrection::Packet* packet; @@ -458,7 +679,7 @@ void RtpFecTest:: ReceivedPackets( int seq_num = fec_seq_num_; int packet_idx = 0; - std::list::const_iterator + PacketList::const_iterator packet_list_item = packet_list.begin(); while (packet_list_item != packet_list.end()) { @@ -496,10 +717,11 @@ void RtpFecTest:: ReceivedPackets( } } -int RtpFecTest::ConstructMediaPackets(int num_media_packets) { +int RtpFecTest::ConstructMediaPacketsSeqNum(int num_media_packets, + int start_seq_num) { assert(num_media_packets > 0); ForwardErrorCorrection::Packet* media_packet = NULL; - int sequence_number = rand(); + int sequence_number = start_seq_num; int time_stamp = rand(); for (int i = 0; i < num_media_packets; i++) { @@ -548,3 +770,7 @@ int RtpFecTest::ConstructMediaPackets(int num_media_packets) { media_packet->data[1] |= 0x80; return sequence_number; } + +int RtpFecTest::ConstructMediaPackets(int num_media_packets) { + return ConstructMediaPacketsSeqNum(num_media_packets, rand()); +} diff --git a/src/modules/rtp_rtcp/source/rtp_sender_video.cc b/src/modules/rtp_rtcp/source/rtp_sender_video.cc index 2d30641b7..325de628d 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/src/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -131,7 +131,8 @@ WebRtc_Word32 RTPSenderVideo::SendVideoPacket(const WebRtc_UWord8* data_buffer, const WebRtc_UWord16 payload_length, const WebRtc_UWord16 rtp_header_length, - StorageType storage) { + StorageType storage, + bool protect) { if(_fecEnabled) { int ret = 0; int fec_overhead_sent = 0; @@ -156,11 +157,13 @@ RTPSenderVideo::SendVideoPacket(const WebRtc_UWord8* data_buffer, delete red_packet; red_packet = NULL; - ret = producer_fec_.AddRtpPacketAndGenerateFec(data_buffer, - payload_length, - rtp_header_length); - if (ret != 0) - return ret; + if (protect) { + ret = producer_fec_.AddRtpPacketAndGenerateFec(data_buffer, + payload_length, + rtp_header_length); + if (ret != 0) + return ret; + } while (producer_fec_.FecAvailable()) { red_packet = producer_fec_.GetFecPacket( @@ -283,13 +286,6 @@ RTPSenderVideo::SendVideo(const RtpVideoCodecTypes videoType, if (frameType == kVideoFrameKey) { producer_fec_.SetFecParameters(&key_fec_params_, _numberFirstPartition); - } else if (videoType == kRtpVp8Video && rtpTypeHdr->VP8.temporalIdx > 0) { - // In current version, we only apply FEC on the base layer. - FecProtectionParams params; - params.fec_rate = 0; - params.max_fec_frames = 0; - params.use_uep_protection = false; - producer_fec_.SetFecParameters(¶ms, _numberFirstPartition); } else { producer_fec_.SetFecParameters(&delta_fec_params_, _numberFirstPartition); @@ -373,7 +369,8 @@ RTPSenderVideo::SendGeneric(const WebRtc_Word8 payloadType, if(-1 == SendVideoPacket(dataBuffer, payloadBytesInPacket, rtpHeaderLength, - kAllowRetransmission)) + kAllowRetransmission, + true)) { return -1; } @@ -433,6 +430,9 @@ RTPSenderVideo::SendVP8(const FrameType frameType, bool last = false; _numberFirstPartition = 0; + // |rtpTypeHdr->VP8.temporalIdx| is zero for base layers, or -1 if the field + // isn't used. We currently only protect base layers. + bool protect = (rtpTypeHdr->VP8.temporalIdx < 1); while (!last) { // Write VP8 Payload Descriptor and VP8 payload. @@ -455,7 +455,7 @@ RTPSenderVideo::SendVP8(const FrameType frameType, _rtpSender.BuildRTPheader(dataBuffer, payloadType, last, captureTimeStamp); if (-1 == SendVideoPacket(dataBuffer, payloadBytesInPacket, - rtpHeaderLength, storage)) + rtpHeaderLength, storage, protect)) { WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "RTPSenderVideo::SendVP8 failed to send packet number" diff --git a/src/modules/rtp_rtcp/source/rtp_sender_video.h b/src/modules/rtp_rtcp/source/rtp_sender_video.h index 0765e3fb2..18f39ca9b 100644 --- a/src/modules/rtp_rtcp/source/rtp_sender_video.h +++ b/src/modules/rtp_rtcp/source/rtp_sender_video.h @@ -95,7 +95,8 @@ protected: virtual WebRtc_Word32 SendVideoPacket(const WebRtc_UWord8* dataBuffer, const WebRtc_UWord16 payloadLength, const WebRtc_UWord16 rtpHeaderLength, - StorageType storage); + StorageType storage, + bool protect); private: WebRtc_Word32 SendGeneric(const WebRtc_Word8 payloadType, diff --git a/src/modules/video_coding/main/source/media_opt_util.cc b/src/modules/video_coding/main/source/media_opt_util.cc index b520278ac..20b90c8b1 100644 --- a/src/modules/video_coding/main/source/media_opt_util.cc +++ b/src/modules/video_coding/main/source/media_opt_util.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * 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 @@ -10,6 +10,7 @@ #include "modules/video_coding/main/source/media_opt_util.h" +#include #include #include #include @@ -55,7 +56,8 @@ VCMNackFecMethod::VCMNackFecMethod(int lowRttNackThresholdMs, int highRttNackThresholdMs) : VCMFecMethod(), _lowRttNackMs(lowRttNackThresholdMs), - _highRttNackMs(highRttNackThresholdMs) { + _highRttNackMs(highRttNackThresholdMs), + _maxFramesFec(1) { assert(lowRttNackThresholdMs >= -1 && highRttNackThresholdMs >= -1); assert(highRttNackThresholdMs == -1 || lowRttNackThresholdMs <= highRttNackThresholdMs); @@ -110,6 +112,35 @@ VCMNackFecMethod::ProtectionFactor(const VCMProtectionParameters* parameters) return true; } +int VCMNackFecMethod::ComputeMaxFramesFec( + const VCMProtectionParameters* parameters) { + if (parameters->numLayers > 2) { + // For more than 2 temporal layers we will only have FEC on the base layer, + // and the base layers will be pretty far apart. Therefore we force one + // frame FEC. + return 1; + } + // We set the max number of frames to base the FEC on so that on average + // we will have complete frames in one RTT. Note that this is an upper + // bound, and that the actual number of frames used for FEC is decided by the + // RTP module based on the actual number of packets and the protection factor. + float base_layer_framerate = parameters->frameRate / + static_cast(1 << (parameters->numLayers - 1)); + int max_frames_fec = std::max(static_cast( + 2.0f * base_layer_framerate * parameters->rtt / + 1000.0f + 0.5f), 1); + // |kUpperLimitFramesFec| is the upper limit on how many frames we + // allow any FEC to be based on. + if (max_frames_fec > kUpperLimitFramesFec) { + max_frames_fec = kUpperLimitFramesFec; + } + return max_frames_fec; +} + +int VCMNackFecMethod::MaxFramesFec() const { + return _maxFramesFec; +} + bool VCMNackFecMethod::EffectivePacketLoss(const VCMProtectionParameters* parameters) { @@ -124,6 +155,7 @@ VCMNackFecMethod::UpdateParameters(const VCMProtectionParameters* parameters) { ProtectionFactor(parameters); EffectivePacketLoss(parameters); + _maxFramesFec = ComputeMaxFramesFec(parameters); // Efficiency computation is based on FEC and NACK diff --git a/src/modules/video_coding/main/source/media_opt_util.h b/src/modules/video_coding/main/source/media_opt_util.h index 1f808de9b..079b06492 100644 --- a/src/modules/video_coding/main/source/media_opt_util.h +++ b/src/modules/video_coding/main/source/media_opt_util.h @@ -144,6 +144,8 @@ public: // Return value : Required Unequal protection on/off state. virtual bool RequiredUepProtectionD() { return _useUepProtectionD; } + virtual int MaxFramesFec() const { return 1; } + // Updates content metrics void UpdateContentMetrics(const VideoContentMetrics* contentMetrics); @@ -198,6 +200,9 @@ public: void UpdateProtectionFactorK(WebRtc_UWord8 protectionFactorK); // Compute the bits per frame. Account for temporal layers when applicable. int BitsPerFrame(const VCMProtectionParameters* parameters); + +protected: + enum { kUpperLimitFramesFec = 6 }; }; @@ -212,10 +217,15 @@ public: bool EffectivePacketLoss(const VCMProtectionParameters* parameters); // Get the protection factors bool ProtectionFactor(const VCMProtectionParameters* parameters); + // Get the max number of frames the FEC is allowed to be based on. + int MaxFramesFec() const; private: + int ComputeMaxFramesFec(const VCMProtectionParameters* parameters); + int _lowRttNackMs; int _highRttNackMs; + int _maxFramesFec; }; class VCMLossProtectionLogic diff --git a/src/modules/video_coding/main/source/media_optimization.cc b/src/modules/video_coding/main/source/media_optimization.cc index a3dd89f24..cf970d470 100644 --- a/src/modules/video_coding/main/source/media_optimization.cc +++ b/src/modules/video_coding/main/source/media_optimization.cc @@ -224,8 +224,10 @@ int VCMMediaOptimization::UpdateProtectionCallback( delta_fec_params.use_uep_protection = selected_method->RequiredUepProtectionD(); - delta_fec_params.max_fec_frames = 1; - key_fec_params.max_fec_frames = 1; + // The RTP module currently requires the same |max_fec_frames| for both + // key and delta frames. + delta_fec_params.max_fec_frames = selected_method->MaxFramesFec(); + key_fec_params.max_fec_frames = selected_method->MaxFramesFec(); // TODO(Marco): Pass FEC protection values per layer. return _videoProtectionCallback->ProtectionRequest(&delta_fec_params,