webrtc/modules/rtp_rtcp/test/testFec/test_fec.cc
marpan@google.com ae0ad911a1 Modified the FEC to allow for option of unequal protection (UEP) across packets.
Added two files under testFec, removed old testFec.cpp, and added two
new files for generating packet masks: _internal.cc/h.
Review URL: http://webrtc-codereview.appspot.com/26003

git-svn-id: http://webrtc.googlecode.com/svn/trunk@93 4adac7df-926f-26a2-2b94-8c16560cd09d
2011-06-16 21:44:38 +00:00

570 lines
25 KiB
C++

/**
* 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 "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<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++)
{
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);
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<WebRtc_UWord16>((static_cast<float>(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<WebRtc_UWord8>((static_cast<float>(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<webrtc::ForwardErrorCorrection::Packet*>
(mediaPacketListItem->GetItem());
const float lossRandomVariable = (static_cast<float>(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<webrtc::ForwardErrorCorrection::Packet*>
(fecPacketListItem->GetItem());
const float lossRandomVariable =
(static_cast<float>(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<WebRtc_UWord8*>(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<WebRtc_UWord32>
((static_cast<float>(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<webrtc::ForwardErrorCorrection::
ReceivedPacket*>(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<webrtc::ForwardErrorCorrection::Packet*>
(mediaPacketListItem->GetItem());
if (recoveredPacketListItem == NULL)
{
printf("Error: insufficient number of recovered packets.\n");
return -1;
}
webrtc::ForwardErrorCorrection::RecoveredPacket* recoveredPacket =
static_cast<webrtc::ForwardErrorCorrection::RecoveredPacket*>
(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<webrtc::ForwardErrorCorrection::Packet*>
(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<webrtc::ForwardErrorCorrection::ReceivedPacket*>
(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<float>(rand()) / RAND_MAX;
while (randomVariable < reorderRate)
{
webrtc::ListItem* nextItem = receivedPacketList.Next(listItem);
if (nextItem == NULL)
{
break;
}
else
{
listItem = nextItem;
}
randomVariable = static_cast<float>(rand()) / RAND_MAX;
}
assert(listItem != NULL);
webrtc::ForwardErrorCorrection::ReceivedPacket* receivedPacket =
static_cast<webrtc::ForwardErrorCorrection::ReceivedPacket*>(listItem->GetItem());
toDecodeList.PushBack(receivedPacket);
// Duplicate packets.
randomVariable = static_cast<float>(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<float>(rand()) / RAND_MAX;
}
receivedPacketList.Erase(listItem);
listItem = receivedPacketList.First();
}
}