/** * Test application for core FEC algorithm. Calls encoding and decoding functions in * ForwardErrorCorrection directly. * */ #include #include #include #include #include #include "forward_error_correction.h" #include "forward_error_correction_internal.h" #include "list_wrapper.h" #include "rtp_utility.h" #define VERBOSE_OUTPUT void ReceivePackets(webrtc::ListWrapper& toDecodeList, webrtc::ListWrapper& receivedPacketList, WebRtc_UWord32 numPacketsToDecode, float reorderRate, float duplicateRate); int main() { enum { kMaxNumberMediaPackets = 48 }; enum { kMaxNumberFecPackets = 48 }; // Use same values as set in forward_correction.cc const WebRtc_UWord8 rtpHeaderSize = 12; const bool kUEP = true; const WebRtc_UWord32 kForceFecThr = 1; WebRtc_UWord32 id = 0; webrtc::ForwardErrorCorrection fec(id); // FOR UEP test WebRtc_UWord32 numImpPackets = 0; webrtc::ListWrapper mediaPacketList; webrtc::ListWrapper fecPacketList; webrtc::ListWrapper toDecodeList; webrtc::ListWrapper receivedPacketList; webrtc::ListWrapper recoveredPacketList; webrtc::ListWrapper fecMaskList; webrtc::ForwardErrorCorrection::Packet* mediaPacket; const float lossRate[] = {0, 0.05f, 0.1f, 0.25f, 0.5f, 0.75f, 0.9f}; const WebRtc_UWord32 lossRateSize = sizeof(lossRate)/sizeof(*lossRate); const float reorderRate = 0.1f; const float duplicateRate = 0.1f; WebRtc_UWord8 mediaLossMask[kMaxNumberMediaPackets]; WebRtc_UWord8 fecLossMask[kMaxNumberFecPackets]; WebRtc_UWord8 fecPacketMasks[kMaxNumberFecPackets][kMaxNumberMediaPackets]; // Seed the random number generator, storing the seed to file in order to reproduce // past results. const unsigned int randomSeed = static_cast(time(NULL)); srand(randomSeed); FILE* randomSeedFile = fopen("randomSeedLog.txt", "a"); fprintf(randomSeedFile, "%u\n", randomSeed); fclose(randomSeedFile); randomSeedFile = NULL; WebRtc_UWord16 seqNum = static_cast(rand()); WebRtc_UWord32 timeStamp = static_cast(rand()); const WebRtc_UWord32 ssrc = static_cast(rand()); for (WebRtc_UWord32 lossRateIdx = 0; lossRateIdx < lossRateSize; lossRateIdx++) { printf("Loss rate: %.2f\n", lossRate[lossRateIdx]); for (WebRtc_UWord32 numMediaPackets = 1; numMediaPackets <= kMaxNumberMediaPackets; numMediaPackets++) { for (WebRtc_UWord32 numFecPackets = 1; numFecPackets <= numMediaPackets && numFecPackets <= kMaxNumberFecPackets; numFecPackets++) { // loop over all possible numImpPackets for (WebRtc_UWord32 numImpPackets = 0; numImpPackets <= numMediaPackets && numImpPackets <= kMaxNumberMediaPackets; numImpPackets++) { WebRtc_UWord8 protectionFactor = static_cast (numFecPackets * 255 / numMediaPackets); WebRtc_UWord32 maskBytesPerFecPacket = 2; if (numMediaPackets > 16) { maskBytesPerFecPacket = 6; } // Transfer packet masks from bit-mask to byte-mask. WebRtc_UWord8 packetMask[numFecPackets * maskBytesPerFecPacket]; memset(packetMask, 0, numFecPackets * maskBytesPerFecPacket); webrtc::internal::GeneratePacketMasks(numMediaPackets,numFecPackets, numImpPackets, packetMask); #ifdef VERBOSE_OUTPUT printf("%u media packets, %u FEC packets, %u numImpPackets, " "loss rate = %.2f \n", numMediaPackets, numFecPackets, numImpPackets, lossRate[lossRateIdx]); printf("Packet mask matrix \n"); #endif for (WebRtc_UWord32 i = 0; i < numFecPackets; i++) { for (WebRtc_UWord32 j = 0; j < numMediaPackets; j++) { const WebRtc_UWord8 byteMask = packetMask[i * maskBytesPerFecPacket + j / 8]; const WebRtc_UWord32 bitPosition = (7 - j % 8); fecPacketMasks[i][j] = (byteMask & (1 << bitPosition)) >> bitPosition; #ifdef VERBOSE_OUTPUT printf("%u ", fecPacketMasks[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 WebRtc_UWord32 rowLimit = numMediaPackets; if (numFecPackets <= numImpPackets && kUEP == true) { rowLimit = numImpPackets; } for (WebRtc_UWord32 i = 0; i < numFecPackets; i++) { WebRtc_UWord32 rowSum = 0; for (WebRtc_UWord32 j = 0; j < rowLimit; j++) { rowSum += fecPacketMasks[i][j]; } if (rowSum == 0) { printf("ERROR: row is all zero %d \n",i); return -1; } } for (WebRtc_UWord32 j = 0; j < rowLimit; j++) { WebRtc_UWord32 columnSum = 0; for (WebRtc_UWord32 i = 0; i < numFecPackets; i++) { columnSum += fecPacketMasks[i][j]; } if (columnSum == 0) { printf("ERROR: column is all zero %d \n",j); return -1; } } // Construct media packets. for (WebRtc_UWord32 i = 0; i < numMediaPackets; i++) { mediaPacket = new webrtc::ForwardErrorCorrection::Packet; mediaPacketList.PushBack(mediaPacket); mediaPacket->length = static_cast((static_cast(rand()) / RAND_MAX) * (IP_PACKET_SIZE - 12 - 28 - webrtc::ForwardErrorCorrection::PacketOverhead())); if (mediaPacket->length < 12) { mediaPacket->length = 12; } // Set the RTP version to 2. mediaPacket->data[0] |= 0x80; // Set the 1st bit. mediaPacket->data[0] &= 0xbf; // Clear the 2nd bit. mediaPacket->data[1] &= 0x7f; // Clear marker bit. webrtc::ModuleRTPUtility::AssignUWord16ToBuffer(&mediaPacket->data[2], seqNum); webrtc::ModuleRTPUtility::AssignUWord32ToBuffer(&mediaPacket->data[4], timeStamp); webrtc::ModuleRTPUtility::AssignUWord32ToBuffer(&mediaPacket->data[8], ssrc); for (WebRtc_Word32 j = 12; j < mediaPacket->length; j++) { mediaPacket->data[j] = static_cast((static_cast(rand()) / RAND_MAX) * 255); } seqNum++; } mediaPacket->data[1] |= 0x80; // Set the marker bit of the last packet. if (fec.GenerateFEC(mediaPacketList, protectionFactor, numImpPackets, fecPacketList) != 0) { printf("Error: GenerateFEC() failed\n"); return -1; } if (fecPacketList.GetSize() != numFecPackets) { printf("Error: we requested %u FEC packets, " "but GenerateFEC() produced %u\n", numFecPackets, fecPacketList.GetSize()); return -1; } memset(mediaLossMask, 0, sizeof(mediaLossMask)); webrtc::ListItem* mediaPacketListItem = mediaPacketList.First(); webrtc::ForwardErrorCorrection::ReceivedPacket* receivedPacket; WebRtc_UWord32 mediaPacketIdx = 0; while (mediaPacketListItem != NULL) { mediaPacket = static_cast (mediaPacketListItem->GetItem()); const float lossRandomVariable = (static_cast(rand()) / (RAND_MAX)); if (lossRandomVariable >= lossRate[lossRateIdx]) { mediaLossMask[mediaPacketIdx] = 1; receivedPacket = new webrtc::ForwardErrorCorrection::ReceivedPacket; receivedPacket->pkt = new webrtc::ForwardErrorCorrection::Packet; receivedPacketList.PushBack(receivedPacket); receivedPacket->pkt->length = mediaPacket->length; memcpy(receivedPacket->pkt->data, mediaPacket->data, mediaPacket->length); receivedPacket->seqNum = webrtc::ModuleRTPUtility::BufferToUWord16(&mediaPacket->data[2]); receivedPacket->isFec = false; receivedPacket->lastMediaPktInFrame = mediaPacket->data[1] & 0x80; } mediaPacketIdx++; mediaPacketListItem = mediaPacketList.Next(mediaPacketListItem); } memset(fecLossMask, 0, sizeof(fecLossMask)); webrtc::ListItem* fecPacketListItem = fecPacketList.First(); webrtc::ForwardErrorCorrection::Packet* fecPacket; WebRtc_UWord32 fecPacketIdx = 0; while (fecPacketListItem != NULL) { fecPacket = static_cast (fecPacketListItem->GetItem()); const float lossRandomVariable = (static_cast(rand()) / (RAND_MAX)); if (lossRandomVariable >= lossRate[lossRateIdx]) { fecLossMask[fecPacketIdx] = 1; receivedPacket = new webrtc::ForwardErrorCorrection::ReceivedPacket; receivedPacket->pkt = new webrtc::ForwardErrorCorrection::Packet; receivedPacketList.PushBack(receivedPacket); receivedPacket->pkt->length = fecPacket->length; memcpy(receivedPacket->pkt->data, fecPacket->data, fecPacket->length); receivedPacket->seqNum = seqNum; receivedPacket->isFec = true; receivedPacket->lastMediaPktInFrame = false; receivedPacket->ssrc = ssrc; fecMaskList.PushBack(fecPacketMasks[fecPacketIdx]); } fecPacketIdx++; seqNum++; fecPacketListItem = fecPacketList.Next(fecPacketListItem); } #ifdef VERBOSE_OUTPUT printf("Media loss mask:\n"); for (WebRtc_UWord32 i = 0; i < numMediaPackets; i++) { printf("%u ", mediaLossMask[i]); } printf("\n\n"); printf("FEC loss mask:\n"); for (WebRtc_UWord32 i = 0; i < numFecPackets; i++) { printf("%u ", fecLossMask[i]); } printf("\n\n"); #endif webrtc::ListItem* listItem = fecMaskList.First(); WebRtc_UWord8* fecMask; while (listItem != NULL) { fecMask = static_cast(listItem->GetItem()); WebRtc_UWord32 hammingDist = 0; WebRtc_UWord32 recoveryPosition = 0; for (WebRtc_UWord32 i = 0; i < numMediaPackets; i++) { if (mediaLossMask[i] == 0 && fecMask[i] == 1) { recoveryPosition = i; hammingDist++; } } webrtc::ListItem* itemToDelete = listItem; listItem = fecMaskList.Next(listItem); if (hammingDist == 1) { // Recovery possible. Restart search. mediaLossMask[recoveryPosition] = 1; listItem = fecMaskList.First(); } else if (hammingDist == 0) { // FEC packet cannot provide further recovery. fecMaskList.Erase(itemToDelete); } } #ifdef VERBOSE_OUTPUT printf("Recovery mask:\n"); for (WebRtc_UWord32 i = 0; i < numMediaPackets; i++) { printf("%u ", mediaLossMask[i]); } printf("\n\n"); #endif bool complete = true; // Marks start of new frame. bool fecPacketReceived = false; // For error-checking frame completion. while (!receivedPacketList.Empty()) { WebRtc_UWord32 numPacketsToDecode = static_cast ((static_cast(rand()) / RAND_MAX) * receivedPacketList.GetSize() + 0.5); if (numPacketsToDecode < 1) { numPacketsToDecode = 1; } ReceivePackets(toDecodeList, receivedPacketList, numPacketsToDecode, reorderRate, duplicateRate); if (fecPacketReceived == false) { listItem = toDecodeList.First(); while (listItem != NULL) { receivedPacket = static_cast(listItem->GetItem()); if (receivedPacket->isFec) { fecPacketReceived = true; } listItem = toDecodeList.Next(listItem); } } if (fec.DecodeFEC(toDecodeList, recoveredPacketList, seqNum, complete) != 0) { printf("Error: DecodeFEC() failed\n"); return -1; } if (!toDecodeList.Empty()) { printf("Error: received packet list is not empty\n"); return -1; } if (recoveredPacketList.GetSize() == numMediaPackets && fecPacketReceived == true) { if (complete == true) { #ifdef VERBOSE_OUTPUT printf("Full frame recovery correctly marked\n\n"); #endif break; } else { printf("Error: " "it should be possible to verify full frame recovery," " but complete parameter was set to false\n"); return -1; } } else { if (complete == true) { printf("Error: " "it should not be possible to verify full frame recovery," " but complete parameter was set to true\n"); return -1; } } } mediaPacketListItem = mediaPacketList.First(); mediaPacketIdx = 0; while (mediaPacketListItem != NULL) { if (mediaLossMask[mediaPacketIdx] == 1) { // Should have recovered this packet. webrtc::ListItem* recoveredPacketListItem = recoveredPacketList.First(); mediaPacket = static_cast (mediaPacketListItem->GetItem()); if (recoveredPacketListItem == NULL) { printf("Error: insufficient number of recovered packets.\n"); return -1; } webrtc::ForwardErrorCorrection::RecoveredPacket* recoveredPacket = static_cast (recoveredPacketListItem->GetItem()); if (recoveredPacket->pkt->length != mediaPacket->length) { printf("Error: recovered packet length not identical to " "original media packet\n"); return -1; } if (memcmp(recoveredPacket->pkt->data, mediaPacket->data, mediaPacket->length) != 0) { printf("Error: recovered packet payload not identical to " "original media packet\n"); return -1; } delete recoveredPacket->pkt; delete recoveredPacket; recoveredPacket = NULL; recoveredPacketList.PopFront(); } mediaPacketIdx++; mediaPacketListItem = mediaPacketList.Next(mediaPacketListItem); } if (!recoveredPacketList.Empty()) { printf("Error: excessive number of recovered packets.\n"); return -1; } // -- Teardown -- mediaPacketListItem = mediaPacketList.First(); while (mediaPacketListItem != NULL) { delete static_cast (mediaPacketListItem->GetItem()); mediaPacketListItem = mediaPacketList.Next(mediaPacketListItem); mediaPacketList.PopFront(); } assert(mediaPacketList.Empty()); fecPacketListItem = fecPacketList.First(); while (fecPacketListItem != NULL) { fecPacketListItem = fecPacketList.Next(fecPacketListItem); fecPacketList.PopFront(); } // Delete received packets we didn't pass to DecodeFEC(), due to early // frame completion. listItem = receivedPacketList.First(); while (listItem != NULL) { receivedPacket = static_cast (listItem->GetItem()); delete receivedPacket->pkt; delete receivedPacket; receivedPacket = NULL; listItem = receivedPacketList.Next(listItem); receivedPacketList.PopFront(); } assert(receivedPacketList.Empty()); while (fecMaskList.First() != NULL) { fecMaskList.PopFront(); } timeStamp += 90000 / 30; } //loop over numImpPackets } //loop over FecPackets } //loop over numMediaPackets } // loop over loss rates // Have DecodeFEC free allocated memory. bool complete = true; fec.DecodeFEC(receivedPacketList, recoveredPacketList, seqNum, complete); if (!recoveredPacketList.Empty()) { printf("Error: recovered packet list is not empty\n"); return -1; } printf("\nAll tests passed successfully\n"); return 0; } void ReceivePackets(webrtc::ListWrapper& toDecodeList, webrtc::ListWrapper& receivedPacketList, WebRtc_UWord32 numPacketsToDecode, float reorderRate, float duplicateRate) { assert(toDecodeList.Empty()); assert(numPacketsToDecode <= receivedPacketList.GetSize()); webrtc::ListItem* listItem = receivedPacketList.First(); for (WebRtc_UWord32 i = 0; i < numPacketsToDecode; i++) { // Reorder packets. float randomVariable = static_cast(rand()) / RAND_MAX; while (randomVariable < reorderRate) { webrtc::ListItem* nextItem = receivedPacketList.Next(listItem); if (nextItem == NULL) { break; } else { listItem = nextItem; } randomVariable = static_cast(rand()) / RAND_MAX; } assert(listItem != NULL); webrtc::ForwardErrorCorrection::ReceivedPacket* receivedPacket = static_cast(listItem->GetItem()); toDecodeList.PushBack(receivedPacket); // Duplicate packets. randomVariable = static_cast(rand()) / RAND_MAX; while (randomVariable < duplicateRate) { webrtc::ForwardErrorCorrection::ReceivedPacket* duplicatePacket = new webrtc::ForwardErrorCorrection::ReceivedPacket; memcpy(duplicatePacket, receivedPacket, sizeof(webrtc::ForwardErrorCorrection::ReceivedPacket)); duplicatePacket->pkt = new webrtc::ForwardErrorCorrection::Packet; memcpy(duplicatePacket->pkt->data, receivedPacket->pkt->data, receivedPacket->pkt->length); duplicatePacket->pkt->length = receivedPacket->pkt->length; toDecodeList.PushBack(duplicatePacket); randomVariable = static_cast(rand()) / RAND_MAX; } receivedPacketList.Erase(listItem); listItem = receivedPacketList.First(); } }