blob: c4b562e0a84885b5d356b60c09af8c91d86e3448 [file] [log] [blame]
/**
* Test application for core FEC algorithm. Calls encoding and decoding functions in
* ForwardErrorCorrection directly.
*
*/
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <list>
#include "forward_error_correction.h"
#include "forward_error_correction_internal.h"
#include "rtp_utility.h"
//#define VERBOSE_OUTPUT
using namespace webrtc;
void ReceivePackets(
std::list<ForwardErrorCorrection::ReceivedPacket*>* toDecodeList,
std::list<ForwardErrorCorrection::ReceivedPacket*>* receivedPacketList,
WebRtc_UWord32 numPacketsToDecode, float reorderRate, float duplicateRate);
int main() {
enum { kMaxNumberMediaPackets = 48 };
enum { kMaxNumberFecPackets = 48 };
const WebRtc_UWord32 kNumMaskBytesL0 = 2;
const WebRtc_UWord32 kNumMaskBytesL1 = 6;
// FOR UEP
const bool kUseUnequalProtection = true;
WebRtc_UWord32 id = 0;
ForwardErrorCorrection fec(id);
std::list<ForwardErrorCorrection::Packet*> mediaPacketList;
std::list<ForwardErrorCorrection::Packet*> fecPacketList;
std::list<ForwardErrorCorrection::ReceivedPacket*> toDecodeList;
std::list<ForwardErrorCorrection::ReceivedPacket*> receivedPacketList;
std::list<ForwardErrorCorrection::RecoveredPacket*> recoveredPacketList;
std::list<WebRtc_UWord8*> fecMaskList;
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<unsigned int>(time(NULL));
srand(randomSeed);
FILE* randomSeedFile = fopen("randomSeedLog.txt", "a");
fprintf(randomSeedFile, "%u\n", randomSeed);
fclose(randomSeedFile);
randomSeedFile = NULL;
WebRtc_UWord16 seqNum = static_cast<WebRtc_UWord16>(rand());
WebRtc_UWord32 timeStamp = static_cast<WebRtc_UWord32>(rand());
const WebRtc_UWord32 ssrc = static_cast<WebRtc_UWord32>(rand());
for (WebRtc_UWord32 lossRateIdx = 0; lossRateIdx < lossRateSize;
lossRateIdx++) {
WebRtc_UWord8* packetMask =
new WebRtc_UWord8[kMaxNumberMediaPackets * kNumMaskBytesL1];
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<WebRtc_UWord8>
(numFecPackets * 255 / numMediaPackets);
const WebRtc_UWord32 maskBytesPerFecPacket =
(numMediaPackets > 16) ? kNumMaskBytesL1 : kNumMaskBytesL0;
memset(packetMask, 0, numMediaPackets * maskBytesPerFecPacket);
// Transfer packet masks from bit-mask to byte-mask.
internal::GeneratePacketMasks(numMediaPackets,
numFecPackets,
numImpPackets,
kUseUnequalProtection,
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;
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 ForwardErrorCorrection::Packet;
mediaPacketList.push_back(mediaPacket);
mediaPacket->length =
static_cast<WebRtc_UWord16>((static_cast<float>(rand()) /
RAND_MAX) * (IP_PACKET_SIZE - 12 - 28 -
ForwardErrorCorrection::PacketOverhead()));
if (mediaPacket->length < 12) {
mediaPacket->length = 12;
}
// Generate random values for the first 2 bytes
mediaPacket->data[0] = static_cast<WebRtc_UWord8>(rand() % 256);
mediaPacket->data[1] = static_cast<WebRtc_UWord8>(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.
mediaPacket->data[0] |= 0x80;
mediaPacket->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.
mediaPacket->data[1] &= 0x7f;
ModuleRTPUtility::AssignUWord16ToBuffer(&mediaPacket->data[2],
seqNum);
ModuleRTPUtility::AssignUWord32ToBuffer(&mediaPacket->data[4],
timeStamp);
ModuleRTPUtility::AssignUWord32ToBuffer(&mediaPacket->data[8],
ssrc);
// Generate random values for payload
for (WebRtc_Word32 j = 12; j < mediaPacket->length; j++) {
mediaPacket->data[j] =
static_cast<WebRtc_UWord8> (rand() % 256);
}
seqNum++;
}
mediaPacket->data[1] |= 0x80;
if (fec.GenerateFEC(mediaPacketList, protectionFactor,
numImpPackets, kUseUnequalProtection,
&fecPacketList) != 0) {
printf("Error: GenerateFEC() failed\n");
return -1;
}
if (fecPacketList.size() != numFecPackets) {
printf("Error: we requested %u FEC packets, "
"but GenerateFEC() produced %u\n",
numFecPackets,
static_cast<WebRtc_UWord32>(fecPacketList.size()));
return -1;
}
memset(mediaLossMask, 0, sizeof(mediaLossMask));
std::list<ForwardErrorCorrection::Packet*>::iterator
mediaPacketListItem = mediaPacketList.begin();
ForwardErrorCorrection::ReceivedPacket* receivedPacket;
WebRtc_UWord32 mediaPacketIdx = 0;
while (mediaPacketListItem != mediaPacketList.end()) {
mediaPacket = *mediaPacketListItem;
const float lossRandomVariable = (static_cast<float>(rand()) /
(RAND_MAX));
if (lossRandomVariable >= lossRate[lossRateIdx])
{
mediaLossMask[mediaPacketIdx] = 1;
receivedPacket =
new ForwardErrorCorrection::ReceivedPacket;
receivedPacket->pkt =
new ForwardErrorCorrection::Packet;
receivedPacketList.push_back(receivedPacket);
receivedPacket->pkt->length = mediaPacket->length;
memcpy(receivedPacket->pkt->data, mediaPacket->data,
mediaPacket->length);
receivedPacket->seqNum =
ModuleRTPUtility::BufferToUWord16(&mediaPacket->data[2]);
receivedPacket->isFec = false;
receivedPacket->lastMediaPktInFrame =
(mediaPacket->data[1] & 0x80) != 0;
}
mediaPacketIdx++;
mediaPacketListItem++;
}
memset(fecLossMask, 0, sizeof(fecLossMask));
std::list<ForwardErrorCorrection::Packet*>::iterator
fecPacketListItem = fecPacketList.begin();
ForwardErrorCorrection::Packet* fecPacket;
WebRtc_UWord32 fecPacketIdx = 0;
while (fecPacketListItem != fecPacketList.end()) {
fecPacket = *fecPacketListItem;
const float lossRandomVariable =
(static_cast<float>(rand()) / (RAND_MAX));
if (lossRandomVariable >= lossRate[lossRateIdx]) {
fecLossMask[fecPacketIdx] = 1;
receivedPacket =
new ForwardErrorCorrection::ReceivedPacket;
receivedPacket->pkt =
new ForwardErrorCorrection::Packet;
receivedPacketList.push_back(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.push_back(fecPacketMasks[fecPacketIdx]);
}
fecPacketIdx++;
seqNum++;
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
std::list<WebRtc_UWord8*>::iterator fecMaskIt = fecMaskList.begin();
WebRtc_UWord8* fecMask;
while (fecMaskIt != fecMaskList.end()) {
fecMask = *fecMaskIt;
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++;
}
}
std::list<WebRtc_UWord8*>::iterator itemToDelete = fecMaskIt;
fecMaskIt++;
if (hammingDist == 1) {
// Recovery possible. Restart search.
mediaLossMask[recoveryPosition] = 1;
fecMaskIt = fecMaskList.begin();
} 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<WebRtc_UWord32>
((static_cast<float>(rand()) / RAND_MAX) *
receivedPacketList.size() + 0.5);
if (numPacketsToDecode < 1) {
numPacketsToDecode = 1;
}
ReceivePackets(&toDecodeList, &receivedPacketList,
numPacketsToDecode, reorderRate, duplicateRate);
if (fecPacketReceived == false) {
std::list<ForwardErrorCorrection::ReceivedPacket*>::iterator
toDecodeIt = toDecodeList.begin();
while (toDecodeIt != toDecodeList.end()) {
receivedPacket = *toDecodeIt;
if (receivedPacket->isFec) {
fecPacketReceived = true;
}
toDecodeIt++;
}
}
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.size() == 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) {
printf("Error: "
"it should not be possible to verify full frame recovery,"
" but complete parameter was set to true\n");
return -1;
}
}
}
mediaPacketListItem = mediaPacketList.begin();
mediaPacketIdx = 0;
while (mediaPacketListItem != mediaPacketList.end()) {
if (mediaLossMask[mediaPacketIdx] == 1) {
// Should have recovered this packet.
std::list<ForwardErrorCorrection::RecoveredPacket*>::iterator
recoveredPacketListItem = recoveredPacketList.begin();
if (recoveredPacketListItem == recoveredPacketList.end()) {
printf("Error: insufficient number of recovered packets.\n");
return -1;
}
mediaPacket = *mediaPacketListItem;
ForwardErrorCorrection::RecoveredPacket* recoveredPacket =
*recoveredPacketListItem;
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;
recoveredPacketList.pop_front();
}
mediaPacketIdx++;
mediaPacketListItem++;
}
if (!recoveredPacketList.empty()) {
printf("Error: excessive number of recovered packets.\n");
printf("\t size is:%u\n",
static_cast<WebRtc_UWord32>(recoveredPacketList.size()));
return -1;
}
// -- Teardown --
mediaPacketListItem = mediaPacketList.begin();
while (mediaPacketListItem != mediaPacketList.end()) {
delete *mediaPacketListItem;
mediaPacketListItem++;
mediaPacketList.pop_front();
}
assert(mediaPacketList.empty());
fecPacketListItem = fecPacketList.begin();
while (fecPacketListItem != fecPacketList.end()) {
fecPacketListItem++;
fecPacketList.pop_front();
}
// Delete received packets we didn't pass to DecodeFEC(), due to early
// frame completion.
std::list<ForwardErrorCorrection::ReceivedPacket*>::iterator
receivedPacketIt = receivedPacketList.begin();
while (receivedPacketIt != receivedPacketList.end()) {
receivedPacket = *receivedPacketIt;
delete receivedPacket->pkt;
delete receivedPacket;
receivedPacketIt++;
receivedPacketList.pop_front();
}
assert(receivedPacketList.empty());
while (!fecMaskList.empty()) {
fecMaskList.pop_front();
}
timeStamp += 90000 / 30;
} //loop over numImpPackets
} //loop over FecPackets
} //loop over numMediaPackets
delete [] packetMask;
} // 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(
std::list<ForwardErrorCorrection::ReceivedPacket*>* toDecodeList,
std::list<ForwardErrorCorrection::ReceivedPacket*>* receivedPacketList,
WebRtc_UWord32 numPacketsToDecode, float reorderRate, float duplicateRate) {
assert(toDecodeList->empty());
assert(numPacketsToDecode <= receivedPacketList->size());
std::list<ForwardErrorCorrection::ReceivedPacket*>::iterator it;
for (WebRtc_UWord32 i = 0; i < numPacketsToDecode; i++) {
it = receivedPacketList->begin();
// Reorder packets.
float randomVariable = static_cast<float>(rand()) / RAND_MAX;
while (randomVariable < reorderRate) {
it++;
if (it == receivedPacketList->end()) {
it++;
break;
}
randomVariable = static_cast<float>(rand()) / RAND_MAX;
}
ForwardErrorCorrection::ReceivedPacket* receivedPacket = *it;
toDecodeList->push_back(receivedPacket);
// Duplicate packets.
randomVariable = static_cast<float>(rand()) / RAND_MAX;
while (randomVariable < duplicateRate) {
ForwardErrorCorrection::ReceivedPacket* duplicatePacket =
new ForwardErrorCorrection::ReceivedPacket;
memcpy(duplicatePacket, receivedPacket,
sizeof(ForwardErrorCorrection::ReceivedPacket));
duplicatePacket->pkt = new ForwardErrorCorrection::Packet;
memcpy(duplicatePacket->pkt->data, receivedPacket->pkt->data,
receivedPacket->pkt->length);
duplicatePacket->pkt->length = receivedPacket->pkt->length;
toDecodeList->push_back(duplicatePacket);
randomVariable = static_cast<float>(rand()) / RAND_MAX;
}
receivedPacketList->erase(it);
}
}