/* * 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. */ /* * Test application for core FEC algorithm. Calls encoding and decoding * functions in ForwardErrorCorrection directly. */ #include #include #include #include "modules/rtp_rtcp/source/byte_io.h" #include "modules/rtp_rtcp/source/forward_error_correction.h" #include "modules/rtp_rtcp/source/forward_error_correction_internal.h" #include "rtc_base/random.h" #include "test/gtest.h" #include "test/testsupport/file_utils.h" // #define VERBOSE_OUTPUT namespace webrtc { namespace fec_private_tables { extern const uint8_t** kPacketMaskBurstyTbl[12]; } namespace test { using fec_private_tables::kPacketMaskBurstyTbl; void ReceivePackets( std::vector>* to_decode_list, std::vector>* received_packet_list, size_t num_packets_to_decode, float reorder_rate, float duplicate_rate, Random* random) { RTC_DCHECK(to_decode_list->empty()); RTC_DCHECK_LE(num_packets_to_decode, received_packet_list->size()); for (size_t i = 0; i < num_packets_to_decode; i++) { auto it = received_packet_list->begin(); // Reorder packets. float random_variable = random->Rand(); while (random_variable < reorder_rate) { ++it; if (it == received_packet_list->end()) { --it; break; } random_variable = random->Rand(); } to_decode_list->push_back(std::move(*it)); received_packet_list->erase(it); // Duplicate packets. ForwardErrorCorrection::ReceivedPacket* received_packet = to_decode_list->back().get(); random_variable = random->Rand(); while (random_variable < duplicate_rate) { std::unique_ptr duplicate_packet( new ForwardErrorCorrection::ReceivedPacket()); *duplicate_packet = *received_packet; duplicate_packet->pkt = new ForwardErrorCorrection::Packet(); duplicate_packet->pkt->data = received_packet->pkt->data; to_decode_list->push_back(std::move(duplicate_packet)); random_variable = random->Rand(); } } } void RunTest(bool use_flexfec) { // TODO(marpan): Split this function into subroutines/helper functions. enum { kMaxNumberMediaPackets = 48 }; enum { kMaxNumberFecPackets = 48 }; const uint32_t kNumMaskBytesL0 = 2; const uint32_t kNumMaskBytesL1 = 6; // FOR UEP const bool kUseUnequalProtection = true; // FEC mask types. const FecMaskType kMaskTypes[] = {kFecMaskRandom, kFecMaskBursty}; const int kNumFecMaskTypes = sizeof(kMaskTypes) / sizeof(*kMaskTypes); // Maximum number of media packets allowed for the mask type. const uint16_t kMaxMediaPackets[] = { kMaxNumberMediaPackets, sizeof(kPacketMaskBurstyTbl) / sizeof(*kPacketMaskBurstyTbl)}; ASSERT_EQ(12, kMaxMediaPackets[1]) << "Max media packets for bursty mode not " "equal to 12."; ForwardErrorCorrection::PacketList media_packet_list; std::list fec_packet_list; std::vector> to_decode_list; std::vector> received_packet_list; ForwardErrorCorrection::RecoveredPacketList recovered_packet_list; std::list fec_mask_list; // Running over only two loss rates to limit execution time. const float loss_rate[] = {0.05f, 0.01f}; const uint32_t loss_rate_size = sizeof(loss_rate) / sizeof(*loss_rate); const float reorder_rate = 0.1f; const float duplicate_rate = 0.1f; uint8_t media_loss_mask[kMaxNumberMediaPackets]; uint8_t fec_loss_mask[kMaxNumberFecPackets]; uint8_t fec_packet_masks[kMaxNumberFecPackets][kMaxNumberMediaPackets]; // Seed the random number generator, storing the seed to file in order to // reproduce past results. const unsigned int random_seed = static_cast(time(nullptr)); Random random(random_seed); std::string filename = webrtc::test::OutputPath() + "randomSeedLog.txt"; FILE* random_seed_file = fopen(filename.c_str(), "a"); fprintf(random_seed_file, "%u\n", random_seed); fclose(random_seed_file); random_seed_file = nullptr; uint16_t seq_num = 0; uint32_t timestamp = random.Rand(); const uint32_t media_ssrc = random.Rand(1u, 0xfffffffe); uint32_t fec_ssrc; uint16_t fec_seq_num_offset; if (use_flexfec) { fec_ssrc = random.Rand(1u, 0xfffffffe); fec_seq_num_offset = random.Rand(0, 1 << 15); } else { fec_ssrc = media_ssrc; fec_seq_num_offset = 0; } std::unique_ptr fec; if (use_flexfec) { fec = ForwardErrorCorrection::CreateFlexfec(fec_ssrc, media_ssrc); } else { RTC_DCHECK_EQ(media_ssrc, fec_ssrc); fec = ForwardErrorCorrection::CreateUlpfec(fec_ssrc); } // Loop over the mask types: random and bursty. for (int mask_type_idx = 0; mask_type_idx < kNumFecMaskTypes; ++mask_type_idx) { for (uint32_t loss_rate_idx = 0; loss_rate_idx < loss_rate_size; ++loss_rate_idx) { printf("Loss rate: %.2f, Mask type %d \n", loss_rate[loss_rate_idx], mask_type_idx); const uint32_t packet_mask_max = kMaxMediaPackets[mask_type_idx]; std::unique_ptr packet_mask( new uint8_t[packet_mask_max * kNumMaskBytesL1]); FecMaskType fec_mask_type = kMaskTypes[mask_type_idx]; for (uint32_t num_media_packets = 1; num_media_packets <= packet_mask_max; num_media_packets++) { internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets); for (uint32_t num_fec_packets = 1; num_fec_packets <= num_media_packets && num_fec_packets <= packet_mask_max; num_fec_packets++) { // Loop over num_imp_packets: usually <= (0.3*num_media_packets). // For this test we check up to ~ (num_media_packets / 4). uint32_t max_num_imp_packets = num_media_packets / 4 + 1; for (uint32_t num_imp_packets = 0; num_imp_packets <= max_num_imp_packets && num_imp_packets <= packet_mask_max; num_imp_packets++) { uint8_t protection_factor = static_cast(num_fec_packets * 255 / num_media_packets); const uint32_t mask_bytes_per_fec_packet = (num_media_packets > 16) ? kNumMaskBytesL1 : kNumMaskBytesL0; memset(packet_mask.get(), 0, num_media_packets * mask_bytes_per_fec_packet); // Transfer packet masks from bit-mask to byte-mask. internal::GeneratePacketMasks( num_media_packets, num_fec_packets, num_imp_packets, kUseUnequalProtection, &mask_table, packet_mask.get()); #ifdef VERBOSE_OUTPUT printf( "%u media packets, %u FEC packets, %u num_imp_packets, " "loss rate = %.2f \n", num_media_packets, num_fec_packets, num_imp_packets, loss_rate[loss_rate_idx]); printf("Packet mask matrix \n"); #endif for (uint32_t i = 0; i < num_fec_packets; i++) { for (uint32_t j = 0; j < num_media_packets; j++) { const uint8_t byte_mask = packet_mask[i * mask_bytes_per_fec_packet + j / 8]; const uint32_t bit_position = (7 - j % 8); fec_packet_masks[i][j] = (byte_mask & (1 << bit_position)) >> bit_position; #ifdef VERBOSE_OUTPUT printf("%u ", fec_packet_masks[i][j]); #endif } #ifdef VERBOSE_OUTPUT printf("\n"); #endif } #ifdef VERBOSE_OUTPUT printf("\n"); #endif // Check for all zero rows or columns: indicates incorrect mask. uint32_t row_limit = num_media_packets; for (uint32_t i = 0; i < num_fec_packets; ++i) { uint32_t row_sum = 0; for (uint32_t j = 0; j < row_limit; ++j) { row_sum += fec_packet_masks[i][j]; } ASSERT_NE(0u, row_sum) << "Row is all zero " << i; } for (uint32_t j = 0; j < row_limit; ++j) { uint32_t column_sum = 0; for (uint32_t i = 0; i < num_fec_packets; ++i) { column_sum += fec_packet_masks[i][j]; } ASSERT_NE(0u, column_sum) << "Column is all zero " << j; } // Construct media packets. // Reset the sequence number here for each FEC code/mask tested // below, to avoid sequence number wrap-around. In actual decoding, // old FEC packets in list are dropped if sequence number wrap // around is detected. This case is currently not handled below. seq_num = 0; for (uint32_t i = 0; i < num_media_packets; ++i) { std::unique_ptr media_packet( new ForwardErrorCorrection::Packet()); const uint32_t kMinPacketSize = 12; const uint32_t kMaxPacketSize = static_cast( IP_PACKET_SIZE - 12 - 28 - fec->MaxPacketOverhead()); size_t packet_length = random.Rand(kMinPacketSize, kMaxPacketSize); media_packet->data.SetSize(packet_length); uint8_t* data = media_packet->data.MutableData(); // Generate random values for the first 2 bytes. data[0] = random.Rand(); data[1] = random.Rand(); // 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. data[0] |= 0x80; 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. data[1] &= 0x7f; ByteWriter::WriteBigEndian(&data[2], seq_num); ByteWriter::WriteBigEndian(&data[4], timestamp); ByteWriter::WriteBigEndian(&data[8], media_ssrc); // Generate random values for payload for (size_t j = 12; j < packet_length; ++j) { data[j] = random.Rand(); } media_packet_list.push_back(std::move(media_packet)); seq_num++; } media_packet_list.back()->data.MutableData()[1] |= 0x80; ASSERT_EQ(0, fec->EncodeFec(media_packet_list, protection_factor, num_imp_packets, kUseUnequalProtection, fec_mask_type, &fec_packet_list)) << "EncodeFec() failed"; ASSERT_EQ(num_fec_packets, fec_packet_list.size()) << "We requested " << num_fec_packets << " FEC packets, but " "EncodeFec() produced " << fec_packet_list.size(); memset(media_loss_mask, 0, sizeof(media_loss_mask)); uint32_t media_packet_idx = 0; for (const auto& media_packet : media_packet_list) { // We want a value between 0 and 1. const float loss_random_variable = random.Rand(); if (loss_random_variable >= loss_rate[loss_rate_idx]) { media_loss_mask[media_packet_idx] = 1; std::unique_ptr received_packet( new ForwardErrorCorrection::ReceivedPacket()); received_packet->pkt = new ForwardErrorCorrection::Packet(); received_packet->pkt->data = media_packet->data; received_packet->ssrc = media_ssrc; received_packet->seq_num = ByteReader::ReadBigEndian( media_packet->data.data() + 2); received_packet->is_fec = false; received_packet_list.push_back(std::move(received_packet)); } media_packet_idx++; } memset(fec_loss_mask, 0, sizeof(fec_loss_mask)); uint32_t fec_packet_idx = 0; for (auto* fec_packet : fec_packet_list) { const float loss_random_variable = random.Rand(); if (loss_random_variable >= loss_rate[loss_rate_idx]) { fec_loss_mask[fec_packet_idx] = 1; std::unique_ptr received_packet( new ForwardErrorCorrection::ReceivedPacket()); received_packet->pkt = new ForwardErrorCorrection::Packet(); received_packet->pkt->data = fec_packet->data; received_packet->seq_num = fec_seq_num_offset + seq_num; received_packet->is_fec = true; received_packet->ssrc = fec_ssrc; received_packet_list.push_back(std::move(received_packet)); fec_mask_list.push_back(fec_packet_masks[fec_packet_idx]); } ++fec_packet_idx; ++seq_num; } #ifdef VERBOSE_OUTPUT printf("Media loss mask:\n"); for (uint32_t i = 0; i < num_media_packets; i++) { printf("%u ", media_loss_mask[i]); } printf("\n\n"); printf("FEC loss mask:\n"); for (uint32_t i = 0; i < num_fec_packets; i++) { printf("%u ", fec_loss_mask[i]); } printf("\n\n"); #endif auto fec_mask_it = fec_mask_list.begin(); while (fec_mask_it != fec_mask_list.end()) { uint32_t hamming_dist = 0; uint32_t recovery_position = 0; for (uint32_t i = 0; i < num_media_packets; i++) { if (media_loss_mask[i] == 0 && (*fec_mask_it)[i] == 1) { recovery_position = i; ++hamming_dist; } } auto item_to_delete = fec_mask_it; ++fec_mask_it; if (hamming_dist == 1) { // Recovery possible. Restart search. media_loss_mask[recovery_position] = 1; fec_mask_it = fec_mask_list.begin(); } else if (hamming_dist == 0) { // FEC packet cannot provide further recovery. fec_mask_list.erase(item_to_delete); } } #ifdef VERBOSE_OUTPUT printf("Recovery mask:\n"); for (uint32_t i = 0; i < num_media_packets; ++i) { printf("%u ", media_loss_mask[i]); } printf("\n\n"); #endif // For error-checking frame completion. bool fec_packet_received = false; while (!received_packet_list.empty()) { size_t num_packets_to_decode = random.Rand( 1u, static_cast(received_packet_list.size())); ReceivePackets(&to_decode_list, &received_packet_list, num_packets_to_decode, reorder_rate, duplicate_rate, &random); if (fec_packet_received == false) { for (const auto& received_packet : to_decode_list) { if (received_packet->is_fec) { fec_packet_received = true; } } } for (const auto& received_packet : to_decode_list) { fec->DecodeFec(*received_packet, &recovered_packet_list); } to_decode_list.clear(); } media_packet_idx = 0; for (const auto& media_packet : media_packet_list) { if (media_loss_mask[media_packet_idx] == 1) { // Should have recovered this packet. auto recovered_packet_list_it = recovered_packet_list.cbegin(); ASSERT_FALSE(recovered_packet_list_it == recovered_packet_list.end()) << "Insufficient number of recovered packets."; ForwardErrorCorrection::RecoveredPacket* recovered_packet = recovered_packet_list_it->get(); ASSERT_EQ(recovered_packet->pkt->data.size(), media_packet->data.size()) << "Recovered packet length not identical to original " "media packet"; ASSERT_EQ(0, memcmp(recovered_packet->pkt->data.cdata(), media_packet->data.cdata(), media_packet->data.size())) << "Recovered packet payload not identical to original " "media packet"; recovered_packet_list.pop_front(); } ++media_packet_idx; } fec->ResetState(&recovered_packet_list); ASSERT_TRUE(recovered_packet_list.empty()) << "Excessive number of recovered packets.\t size is: " << recovered_packet_list.size(); // -- Teardown -- media_packet_list.clear(); // Clear FEC packet list, so we don't pass in a non-empty // list in the next call to DecodeFec(). fec_packet_list.clear(); // Delete received packets we didn't pass to DecodeFec(), due to // early frame completion. received_packet_list.clear(); while (!fec_mask_list.empty()) { fec_mask_list.pop_front(); } timestamp += 90000 / 30; } // loop over num_imp_packets } // loop over FecPackets } // loop over num_media_packets } // loop over loss rates } // loop over mask types // Have DecodeFec clear the recovered packet list. fec->ResetState(&recovered_packet_list); ASSERT_TRUE(recovered_packet_list.empty()) << "Recovered packet list is not empty"; } TEST(FecTest, UlpfecTest) { RunTest(false); } TEST(FecTest, FlexfecTest) { RunTest(true); } } // namespace test } // namespace webrtc