509 lines
20 KiB
C++

/*
* Copyright (c) 2011 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.
*/
/**
* \file testFec.cpp
* Test application for core FEC algorithm. Calls encoding and decoding functions in
* ForwardErrorCorrection directly.
*
*/
#include "forward_error_correction.h"
#include "list_wrapper.h"
#include "rtp_utility.h"
#include <cstdio>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <windows.h>
//#include "vld.h"
#include "fec_private_tables.h"
//#define VERBOSE_OUTPUT
void ReceivePackets(ListWrapper& toDecodeList, ListWrapper& receivedPacketList,
WebRtc_UWord32 numPacketsToDecode, float reorderRate, float duplicateRate)
{
assert(toDecodeList.Empty());
assert(numPacketsToDecode <= receivedPacketList.GetSize());
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)
{
ListItem* nextItem = receivedPacketList.Next(listItem);
if (nextItem == NULL)
{
break;
}
else
{
listItem = nextItem;
}
randomVariable = static_cast<float>(rand()) / RAND_MAX;
}
assert(listItem != NULL);
ForwardErrorCorrection::ReceivedPacket* receivedPacket =
static_cast<ForwardErrorCorrection::ReceivedPacket*>(listItem->GetItem());
toDecodeList.PushBack(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.PushBack(duplicatePacket);
randomVariable = static_cast<float>(rand()) / RAND_MAX;
}
receivedPacketList.Erase(listItem);
listItem = receivedPacketList.First();
}
}
int main()
{
enum { MaxNumberMediaPackets = 48 };
enum { MaxNumberFecPackets = 48 };
WebRtc_UWord32 id = 0;
ForwardErrorCorrection fec(id);
ListWrapper mediaPacketList;
ListWrapper fecPacketList;
ListWrapper toDecodeList;
ListWrapper receivedPacketList;
ListWrapper recoveredPacketList;
ListWrapper 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[MaxNumberMediaPackets];
WebRtc_UWord8 fecLossMask[MaxNumberFecPackets];
WebRtc_UWord8 fecPacketMasks[MaxNumberFecPackets][MaxNumberMediaPackets];
// 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 <= MaxNumberMediaPackets;
numMediaPackets++)
{
for (WebRtc_UWord32 numFecPackets = 1; numFecPackets <= numMediaPackets &&
numFecPackets <= MaxNumberFecPackets; numFecPackets++)
{
#ifdef VERBOSE_OUTPUT
printf("%u media packets, %u FEC packets\n", numMediaPackets, numFecPackets);
printf("Packet mask matrix:\n");
#endif
// Transfer packet masks from bit-mask to byte-mask.
const WebRtc_UWord8* packetMask = packetMaskTbl[numMediaPackets - 1][numFecPackets - 1];
WebRtc_UWord32 maskBytesPerFecPacket = 2;
if (numMediaPackets > 16)
{
maskBytesPerFecPacket = 6;
}
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
// Construct media packets.
for (WebRtc_UWord32 i = 0; i < numMediaPackets; i++)
{
mediaPacket = new ForwardErrorCorrection::Packet;
mediaPacketList.PushBack(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;
}
// 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.
ModuleRTPUtility::AssignUWord16ToBuffer(&mediaPacket->data[2], seqNum);
ModuleRTPUtility::AssignUWord32ToBuffer(&mediaPacket->data[4], timeStamp);
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.
WebRtc_UWord8 protectionFactor = static_cast<WebRtc_UWord8>(numFecPackets * 255 / numMediaPackets);
if (fec.GenerateFEC(mediaPacketList, fecPacketList, protectionFactor) != 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));
ListItem* mediaPacketListItem = mediaPacketList.First();
ForwardErrorCorrection::ReceivedPacket* receivedPacket;
WebRtc_UWord32 mediaPacketIdx = 0;
while (mediaPacketListItem != NULL)
{
mediaPacket = static_cast<ForwardErrorCorrection::Packet*>
(mediaPacketListItem->GetItem());
const float lossRandomVariable = (static_cast<float>(rand()) /
(RAND_MAX + 1)); // +1 to get [0, 1)
if (lossRandomVariable >= lossRate[lossRateIdx])
{
mediaLossMask[mediaPacketIdx] = 1;
receivedPacket = new ForwardErrorCorrection::ReceivedPacket;
receivedPacket->pkt = new ForwardErrorCorrection::Packet;
receivedPacketList.PushBack(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 ?
true : false; // Check for marker bit.
}
mediaPacketIdx++;
mediaPacketListItem = mediaPacketList.Next(mediaPacketListItem);
}
memset(fecLossMask, 0, sizeof(fecLossMask));
ListItem* fecPacketListItem = fecPacketList.First();
ForwardErrorCorrection::Packet* fecPacket;
WebRtc_UWord32 fecPacketIdx = 0;
while (fecPacketListItem != NULL)
{
fecPacket = static_cast<ForwardErrorCorrection::Packet*>
(fecPacketListItem->GetItem());
const float lossRandomVariable = (static_cast<float>(rand()) /
(RAND_MAX + 1)); // +1 to get [0, 1)
if (lossRandomVariable >= lossRate[lossRateIdx])
{
fecLossMask[fecPacketIdx] = 1;
receivedPacket = new ForwardErrorCorrection::ReceivedPacket;
receivedPacket->pkt = new 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
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++;
}
}
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<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.
ListItem* recoveredPacketListItem = recoveredPacketList.First();
mediaPacket = static_cast<ForwardErrorCorrection::Packet*>
(mediaPacketListItem->GetItem());
if (recoveredPacketListItem == NULL)
{
printf("Error: insufficient number of recovered packets.\n");
return -1;
}
ForwardErrorCorrection::RecoveredPacket* recoveredPacket =
static_cast<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<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<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;
}
}
}
// 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");
Sleep(5000);
return 0;
}