diff --git a/modules/rtp_rtcp/source/forward_error_correction.cc b/modules/rtp_rtcp/source/forward_error_correction.cc index cde407c546..16f8c3368c 100644 --- a/modules/rtp_rtcp/source/forward_error_correction.cc +++ b/modules/rtp_rtcp/source/forward_error_correction.cc @@ -13,68 +13,48 @@ #include "rtp_utility.h" #include "trace.h" - #include #include -#define RtpHeaderSize 12 /**> Minimum RTP header size in bytes. */ -#define FecHeaderSize 10 /**> FEC header size in bytes. */ -#define MaskSizeLBitSet 6 /**> Packet mask size in bytes (L bit is set). */ -#define MaskSizeLBitClear 2 /**> Packet mask size in bytes (L bit is cleared). */ - -#define UlpHeaderSizeLBitSet (2+MaskSizeLBitSet) /**> ULP header size in bytes (L bit is set). */ -#define UlpHeaderSizeLBitClear (2 + MaskSizeLBitClear) /**> ULP header size in bytes (L bit is cleared). */ -#define TransportOverhead 28 /**> Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum. */ +#include "forward_error_correction_internal.h" namespace webrtc { -/** - * Used for internal storage of FEC packets in a list. - */ + +// Minimum RTP header size in bytes. +const WebRtc_UWord8 kRtpHeaderSize = 12; + +// FEC header size in bytes. +const WebRtc_UWord8 kFecHeaderSize = 10; + +// ULP header size in bytes (L bit is set). +const WebRtc_UWord8 kUlpHeaderSizeLBitSet = (2 + kMaskSizeLBitSet); + +// ULP header size in bytes (L bit is cleared). +const WebRtc_UWord8 kUlpHeaderSizeLBitClear = (2 + kMaskSizeLBitClear); + +//Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum. +const WebRtc_UWord8 kTransportOverhead = 28; + +// +// Used for internal storage of FEC packets in a list. +// struct FecPacket { - ListWrapper protectedPktList; /**> List containing #ProtectedPacket types. */ + ListWrapper protectedPktList; /**> List containing #ProtectedPacket types.*/ WebRtc_UWord16 seqNum; /**> Sequence number. */ WebRtc_UWord32 ssrc; /**> SSRC of the current frame. */ ForwardErrorCorrection::Packet* pkt; /**> Pointer to the packet storage. */ }; -/** - * Used to link media packets to their protecting FEC packets. - */ +// +// Used to link media packets to their protecting FEC packets. +// struct ProtectedPacket { WebRtc_UWord16 seqNum; /**> Sequence number. */ ForwardErrorCorrection::Packet* pkt; /**> Pointer to the packet storage. */ }; -namespace // Unnamed namespace gives internal linkage. -{ - /** - * Returns an array of packet masks. Every NumMaskBytes-bytes of the array - * corresponds to the mask of a single FEC packet. The mask indicates which - * media packets should be protected by the FEC packet. - * - * \param[out] packetMask A pointer to hold the packet mask array, of size - * numFecPackets * NumMaskBytes; - * \param[in] numMediaPackets The number of media packets to protect. - * [1, maxMediaPackets]. - * \param[in] numFecPackets The number of FEC packets which will be generated. - * [1, numMediaPackets]. - */ - void - GeneratePacketMasks(const WebRtc_UWord8*& packetMask, - const WebRtc_UWord32 numMediaPackets, - const WebRtc_UWord32 numFecPackets) - { - assert(numMediaPackets <= sizeof(packetMaskTbl)/sizeof(*packetMaskTbl) && - numMediaPackets > 0); - assert(numFecPackets <= numMediaPackets && numFecPackets > 0); - - // Retrieve corresponding mask table. - packetMask = packetMaskTbl[numMediaPackets - 1][numFecPackets - 1]; - } -} - ForwardErrorCorrection::ForwardErrorCorrection(const WebRtc_Word32 id) : _id(id), _generatedFecPackets(NULL), @@ -112,8 +92,9 @@ ForwardErrorCorrection::~ForwardErrorCorrection() // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ WebRtc_Word32 ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, - ListWrapper& fecPacketList, - WebRtc_UWord8 protectionFactor) + WebRtc_UWord8 protectionFactor, + WebRtc_UWord32 numImportantPackets, + ListWrapper& fecPacketList) { if (mediaPacketList.Empty()) { @@ -131,9 +112,12 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, const WebRtc_UWord16 numMediaPackets = mediaPacketList.GetSize(); const WebRtc_UWord8 lBit = numMediaPackets > 16 ? 1 : 0; - const WebRtc_UWord16 numMaskBytes = (lBit == 1)? MaskSizeLBitSet : MaskSizeLBitClear; - const WebRtc_UWord16 ulpHeaderSize = (lBit == 1)? UlpHeaderSizeLBitSet : UlpHeaderSizeLBitClear; - const WebRtc_UWord16 fecRtpOffset = FecHeaderSize + ulpHeaderSize - RtpHeaderSize; + const WebRtc_UWord16 numMaskBytes = + (lBit == 1)? kMaskSizeLBitSet : kMaskSizeLBitClear; + const WebRtc_UWord16 ulpHeaderSize = + (lBit == 1)? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear; + const WebRtc_UWord16 fecRtpOffset = + kFecHeaderSize + ulpHeaderSize - kRtpHeaderSize; const WebRtc_UWord16 maxMediaPackets = numMaskBytes * 8; if (numMediaPackets > maxMediaPackets) @@ -144,6 +128,23 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, return -1; } + // Error checking on the number of important packets. + // Can't have more important packets than media packets. + if (numImportantPackets > numMediaPackets) + { + WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, + "Number of Important packet greater than number of Media Packets %d %d", + numImportantPackets, numMediaPackets); + return -1; + } + if (numImportantPackets < 0) + { + WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, + "Number of Important packets less than zero %d %d", + numImportantPackets, numMediaPackets); + return -1; + } + // Do some error checking on the media packets. Packet* mediaPacket; ListItem* mediaListItem = mediaPacketList.First(); @@ -151,7 +152,7 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, { mediaPacket = static_cast(mediaListItem->GetItem()); - if (mediaPacket->length < RtpHeaderSize) + if (mediaPacket->length < kRtpHeaderSize) { WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, "%s media packet (%d bytes) is smaller than RTP header", @@ -160,7 +161,7 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, } // Ensure our FEC packets will fit in a typical MTU. - if (mediaPacket->length + PacketOverhead() + TransportOverhead > + if (mediaPacket->length + PacketOverhead() + kTransportOverhead > IP_PACKET_SIZE) { WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, @@ -197,8 +198,10 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, } // -- Generate packet masks -- - const WebRtc_UWord8* packetMask; - GeneratePacketMasks(packetMask, numMediaPackets, numFecPackets); + WebRtc_UWord8 packetMask[numFecPackets * numMaskBytes]; + memset(packetMask, 0, numFecPackets * numMaskBytes); + internal::GeneratePacketMasks(numMediaPackets, numFecPackets, + numImportantPackets, packetMask); // -- Generate FEC bit strings -- WebRtc_UWord8 mediaPayloadLength[2]; @@ -218,7 +221,7 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, // Assign network-ordered media payload length. ModuleRTPUtility::AssignUWord16ToBuffer(mediaPayloadLength, - mediaPacket->length - RtpHeaderSize); + mediaPacket->length - kRtpHeaderSize); fecPacketLength = mediaPacket->length + fecRtpOffset; // On the first protected packet, we don't need to XOR. if (_generatedFecPackets[i].length == 0) @@ -231,9 +234,9 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, memcpy(&_generatedFecPackets[i].data[8], mediaPayloadLength, 2); // Copy RTP payload, leaving room for the ULP header. - memcpy(&_generatedFecPackets[i].data[FecHeaderSize + ulpHeaderSize], - &mediaPacket->data[RtpHeaderSize], - mediaPacket->length - RtpHeaderSize); + memcpy(&_generatedFecPackets[i].data[kFecHeaderSize + ulpHeaderSize], + &mediaPacket->data[kRtpHeaderSize], + mediaPacket->length - kRtpHeaderSize); } else { @@ -252,7 +255,7 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, _generatedFecPackets[i].data[9] ^= mediaPayloadLength[1]; // XOR with RTP payload, leaving room for the ULP header. - for (WebRtc_Word32 j = FecHeaderSize + ulpHeaderSize; + for (WebRtc_Word32 j = kFecHeaderSize + ulpHeaderSize; j < fecPacketLength; j++) { _generatedFecPackets[i].data[j] ^= @@ -278,9 +281,12 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, if (_generatedFecPackets[i].length == 0) { - // This will occur in the event of an all-zero mask. - // Ensure we still mark the length of the headers. - _generatedFecPackets[i].length = FecHeaderSize + ulpHeaderSize; + //Note: This shouldn't happen: means packet mask is wrong or poorly designed + WEBRTC_TRACE(kTraceError, kTraceRtpRtcp, _id, + "Packet mask has row of zeros %d %d", + numMediaPackets, numFecPackets); + return -1; + } } @@ -330,7 +336,7 @@ ForwardErrorCorrection::GenerateFEC(const ListWrapper& mediaPacketList, // Copy the payload size to the protection length field. // (We protect the entire packet.) ModuleRTPUtility::AssignUWord16ToBuffer(&_generatedFecPackets[i].data[10], - _generatedFecPackets[i].length - FecHeaderSize - ulpHeaderSize); + _generatedFecPackets[i].length - kFecHeaderSize - ulpHeaderSize); // Copy the packet mask. memcpy(&_generatedFecPackets[i].data[12], &packetMask[i * numMaskBytes], @@ -517,7 +523,7 @@ ForwardErrorCorrection::DecodeFEC(ListWrapper& receivedPacketList, // We store this for determining frame completion later. _seqNumBase = ModuleRTPUtility::BufferToUWord16(&fecPacket->pkt->data[2]); const WebRtc_UWord16 maskSizeBytes = (fecPacket->pkt->data[0] & 0x40) ? - MaskSizeLBitSet : MaskSizeLBitClear; // L bit set? + kMaskSizeLBitSet : kMaskSizeLBitClear; // L bit set? for (WebRtc_UWord16 byteIdx = 0; byteIdx < maskSizeBytes; byteIdx++) { @@ -617,7 +623,7 @@ ForwardErrorCorrection::DecodeFEC(ListWrapper& receivedPacketList, // Recovery possible. WebRtc_UWord8 lengthRecovery[2]; const WebRtc_UWord16 ulpHeaderSize = fecPacket->pkt->data[0] & 0x40 ? - UlpHeaderSizeLBitSet : UlpHeaderSizeLBitClear; // L bit set? + kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear; // L bit set? RecoveredPacket* recPacketToInsert = new RecoveredPacket; recPacketToInsert->wasRecovered = true; @@ -641,8 +647,8 @@ ForwardErrorCorrection::DecodeFEC(ListWrapper& receivedPacketList, memcpy(&lengthRecovery, &fecPacket->pkt->data[8], 2); // Copy FEC payload, skipping the ULP header. - memcpy(&recPacketToInsert->pkt->data[RtpHeaderSize], - &fecPacket->pkt->data[FecHeaderSize + ulpHeaderSize], + memcpy(&recPacketToInsert->pkt->data[kRtpHeaderSize], + &fecPacket->pkt->data[kFecHeaderSize + ulpHeaderSize], ModuleRTPUtility::BufferToUWord16(protectionLength)); protectedPacketListItem = fecPacket->protectedPktList.First(); @@ -672,13 +678,13 @@ ForwardErrorCorrection::DecodeFEC(ListWrapper& receivedPacketList, // XOR with the network-ordered payload size. ModuleRTPUtility::AssignUWord16ToBuffer(mediaPayloadLength, - protectedPacket->pkt->length - RtpHeaderSize); + protectedPacket->pkt->length - kRtpHeaderSize); lengthRecovery[0] ^= mediaPayloadLength[0]; lengthRecovery[1] ^= mediaPayloadLength[1]; // XOR with RTP payload. // TODO: Are we doing more XORs than required here? - for (WebRtc_Word32 i = RtpHeaderSize; i < protectedPacket->pkt->length; + for (WebRtc_Word32 i = kRtpHeaderSize; i < protectedPacket->pkt->length; i++) { recPacketToInsert->pkt->data[i] ^= protectedPacket->pkt->data[i]; @@ -712,7 +718,7 @@ ForwardErrorCorrection::DecodeFEC(ListWrapper& receivedPacketList, // Recover the packet length. recPacketToInsert->pkt->length = - ModuleRTPUtility::BufferToUWord16(lengthRecovery) + RtpHeaderSize; + ModuleRTPUtility::BufferToUWord16(lengthRecovery) + kRtpHeaderSize; // Insert into recovered list in correct position. recPacketListItem = recoveredPacketList.Last(); @@ -823,6 +829,6 @@ ForwardErrorCorrection::DecodeFEC(ListWrapper& receivedPacketList, WebRtc_UWord16 ForwardErrorCorrection::PacketOverhead() { - return FecHeaderSize + UlpHeaderSizeLBitSet; + return kFecHeaderSize + kUlpHeaderSizeLBitSet; } } // namespace webrtc diff --git a/modules/rtp_rtcp/source/forward_error_correction.h b/modules/rtp_rtcp/source/forward_error_correction.h index efdec586bd..2c9ff3f246 100644 --- a/modules/rtp_rtcp/source/forward_error_correction.h +++ b/modules/rtp_rtcp/source/forward_error_correction.h @@ -84,27 +84,35 @@ public: /** * Destructor. Before freeing an instance of the class, #DecodeFEC() must be called - * in a particular fashion to free oustanding memory. Refer to #DecodeFEC(). + * in a particular fashion to free outstanding memory. Refer to #DecodeFEC(). */ virtual ~ForwardErrorCorrection(); /** * Generates a list of FEC packets from supplied media packets. * - * \param[in] mediaPacketList List of media packets to protect, of type #Packet. - * All packets must belong to the same frame and the - * list must not be empty. - * \param[out] fecPacketList List of FEC packets, of type #Packet. Must be empty - * on entry. The memory available through the list - * will be valid until the next call to GenerateFEC(). - * \param[in] protectionFactor FEC protection overhead in the [0, 255] domain. To - * obtain 100% overhead, or an equal number of FEC - * packets as media packets, use 255. + * \param[in] mediaPacketList List of media packets to protect, of type #Packet. + * All packets must belong to the same frame and the + * list must not be empty. + * \param[in] protectionFactor FEC protection overhead in the [0, 255] domain. To + * obtain 100% overhead, or an equal number of FEC + * packets as media packets, use 255. + * \param[in] numImportantPackets The number of "important" packets in the frame. + * These packets may receive greater protection than + * the remaining packets. The important packets must + * be located at the start of the media packet list. + * For codecs with data partitioning, the important + * packets may correspond to first partition packets. + * \param[out] fecPacketList List of FEC packets, of type #Packet. Must be empty + * on entry. The memory available through the list + * will be valid until the next call to GenerateFEC(). * * \return 0 on success, -1 on failure. */ - WebRtc_Word32 GenerateFEC(const ListWrapper& mediaPacketList, ListWrapper& fecPacketList, - WebRtc_UWord8 protectionFactor); + WebRtc_Word32 GenerateFEC(const ListWrapper& mediaPacketList, + WebRtc_UWord8 protectionFactor, + WebRtc_UWord32 numImportantPackets, + ListWrapper& fecPacketList); /** * Decodes a list of media and FEC packets. It will parse the input received packet diff --git a/modules/rtp_rtcp/source/forward_error_correction_internal.cc b/modules/rtp_rtcp/source/forward_error_correction_internal.cc new file mode 100644 index 0000000000..1d2e6a29ea --- /dev/null +++ b/modules/rtp_rtcp/source/forward_error_correction_internal.cc @@ -0,0 +1,340 @@ +/* + * 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. + */ + +#include "forward_error_correction_internal.h" +#include "fec_private_tables.h" + +#include +#include + +namespace { + +// This parameter enables/disables unequal protection (UEP) across packets. +// This is not to be confused with protection within packets (referred to as ULP). +// One use case of UEP across packets is for codecs with data partitioning, +// e.g., VP8, H264 XP profile, where important packets would be first partition. +// TODO (marpan): Pass this parameter from MediaOpt (VCM). +const bool kUseUnequalProtection = true; + +// Allow for two different modes of protection for residual packets. +// The residual packets are the remaining packets beyond the important ones. +enum ResidualProtectionMode +{ + kModeNoOverlap, + kModeOverlap, +}; + + +/** + * Fits an input mask (subMask) to an output mask. + * The mask is a matrix where the rows are the FEC packets, + * and the columns are the source packets the FEC is applied to. + * Each row of the mask is represented by a number of mask bytes. + * + * \param[in] numMaskBytes The number of mask bytes of output mask. + * \param[in] numSubMaskBytes The number of mask bytes of input mask. + * \param[in] numRows The number of rows of the input mask. + * \param[in] subMask A pointer to hold the input mask, of size + * [0, numRows * numSubMaskBytes] + * \param[out] packetMask A pointer to hold the output mask, of size + * [0, x * numMaskBytes], where x >= numRows. + */ +void FitSubMask(const WebRtc_UWord16 numMaskBytes, + const WebRtc_UWord16 numSubMaskBytes, + const WebRtc_UWord16 numRows, + const WebRtc_UWord8* subMask, + WebRtc_UWord8* packetMask) +{ + if (numMaskBytes == numSubMaskBytes) + { + + memcpy(packetMask,subMask, + numRows * numSubMaskBytes); + } + else + { + for (WebRtc_UWord32 i = 0; i < numRows; i++) + { + WebRtc_UWord32 pktMaskIdx = i * numMaskBytes; + WebRtc_UWord32 pktMaskIdx2 = i * numSubMaskBytes; + for (WebRtc_UWord32 j = 0; j < numSubMaskBytes; j++) + { + packetMask[pktMaskIdx] = subMask[pktMaskIdx2]; + pktMaskIdx++; + pktMaskIdx2++; + } + } + } +} + +/** + * Shifts a mask by number of columns (bits), and fits it to an output mask. + * The mask is a matrix where the rows are the FEC packets, + * and the columns are the source packets the FEC is applied to. + * Each row of the mask is represented by a number of mask bytes. + * + * \param[in] numMaskBytes The number of mask bytes of output mask. + * \param[in] numSubMaskBytes The number of mask bytes of input mask. + * \param[in] numColumnShift The number columns to be shifted, and + * the starting row for the output mask. + * \param[in] endRow The ending row for the output mask. + * \param[in] subMask A pointer to hold the input mask, of size + * [0, (endRowFEC - startRowFec) * numSubMaskBytes] + * \param[out] packetMask A pointer to hold the output mask, of size + * [0, x * numMaskBytes], where x >= endRowFEC. + */ +// TODO (marpan): This function is doing three things at the same time: +// shift within a byte, byte shift and resizing. +// Split up into subroutines. +void ShiftFitSubMask(const WebRtc_UWord16 numMaskBytes, + const WebRtc_UWord16 resMaskBytes, + const WebRtc_UWord16 numColumnShift, + const WebRtc_UWord16 endRow, + const WebRtc_UWord8* subMask, + WebRtc_UWord8* packetMask) +{ + + // Number of bit shifts within a byte + const WebRtc_UWord8 numBitShifts = (numColumnShift % 8); + const WebRtc_UWord8 numByteShifts = numColumnShift >> 3; + + // Modify new mask with sub-mask21. + + // Loop over the remaining FEC packets. + for (WebRtc_UWord32 i = numColumnShift; i < endRow; i++) + { + // Byte index of new mask, for row i and column resMaskBytes, + // offset by the number of bytes shifts + WebRtc_UWord32 pktMaskIdx = i * numMaskBytes + resMaskBytes - 1 + + numByteShifts; + // Byte index of subMask, for row i and column resMaskBytes + WebRtc_UWord32 pktMaskIdx2 = + (i - numColumnShift) * resMaskBytes + resMaskBytes - 1; + + WebRtc_UWord8 shiftRightCurrByte = 0; + WebRtc_UWord8 shiftLeftPrevByte = 0; + WebRtc_UWord8 combNewByte = 0; + + // Handle case of numMaskBytes > resMaskBytes: + // For a given row, copy the rightmost "numBitShifts" bits + // of the last byte of subMask into output mask. + if (numMaskBytes > resMaskBytes) + { + shiftLeftPrevByte = + (subMask[pktMaskIdx2] << (8 - numBitShifts)); + packetMask[pktMaskIdx + 1] = shiftLeftPrevByte; + } + + // For each row i (FEC packet), shift the bit-mask of the subMask. + // Each row of the mask contains "resMaskBytes" of bytes. + // We start from the last byte of the subMask and move to first one. + for (WebRtc_Word32 j = resMaskBytes - 1; j > 0; j--) + { + // Shift current byte of sub21 to the right by "numBitShifts". + shiftRightCurrByte = + subMask[pktMaskIdx2] >> numBitShifts; + + // Fill in shifted bits with bits from the previous (left) byte: + // First shift the previous byte to the left by "8-numBitShifts". + shiftLeftPrevByte = + (subMask[pktMaskIdx2 - 1] << (8 - numBitShifts)); + + // Then combine both shifted bytes into new mask byte. + combNewByte = shiftRightCurrByte | shiftLeftPrevByte; + + // Assign to new mask. + packetMask[pktMaskIdx] = combNewByte; + pktMaskIdx--; + pktMaskIdx2--; + } + // For the first byte in the row (j=0 case). + shiftRightCurrByte = subMask[pktMaskIdx2] >> numBitShifts; + packetMask[pktMaskIdx] = shiftRightCurrByte; + + } +} + +} //namespace + +namespace webrtc { +namespace internal { + +// Residual protection for remaining packets +void ResidualPacketProtection(const WebRtc_UWord16 numMediaPackets, + const WebRtc_UWord16 numFecPackets, + const WebRtc_UWord16 numImpPackets, + const WebRtc_UWord16 numMaskBytes, + const ResidualProtectionMode mode, + WebRtc_UWord8* packetMask) +{ + if (mode == kModeNoOverlap) + { + // subMask21 + + const WebRtc_UWord8 lBit = + (numMediaPackets - numImpPackets) > 16 ? 1 : 0; + + const WebRtc_UWord16 resMaskBytes = + (lBit == 1)? kMaskSizeLBitSet : kMaskSizeLBitClear; + + const WebRtc_UWord8* packetMaskSub21 = + packetMaskTbl[numMediaPackets - numImpPackets - 1] + [numFecPackets - numImpPackets - 1]; + + ShiftFitSubMask(numMaskBytes, resMaskBytes, + numImpPackets, numFecPackets, + packetMaskSub21, packetMask); + } + else if (mode == kModeOverlap) + { + // subMask22 + + const WebRtc_UWord16 numFecForResidual = + numFecPackets - numImpPackets; + + const WebRtc_UWord8* packetMaskSub22 = + packetMaskTbl[numMediaPackets - 1] + [numFecForResidual - 1]; + + FitSubMask(numMaskBytes, numMaskBytes, + numFecForResidual, + packetMaskSub22, + &packetMask[numImpPackets * numMaskBytes]); + } + else + { + assert(false); + } + +} + +// Higher protection for numImpPackets +void ImportantPacketProtection(const WebRtc_UWord16 numFecPackets, + const WebRtc_UWord16 numImpPackets, + const WebRtc_UWord16 numMaskBytes, + WebRtc_UWord8* packetMask) +{ + const WebRtc_UWord8 lBit = numImpPackets > 16 ? 1 : 0; + const WebRtc_UWord16 numImpMaskBytes = + (lBit == 1)? kMaskSizeLBitSet : kMaskSizeLBitClear; + + WebRtc_UWord32 numFecForImpPackets = numImpPackets; + if (numFecPackets < numImpPackets) + { + numFecForImpPackets = numFecPackets; + } + + // Get subMask1 from table + const WebRtc_UWord8* packetMaskSub1 = + packetMaskTbl[numImpPackets - 1][numFecForImpPackets - 1]; + + FitSubMask(numMaskBytes, numImpMaskBytes, + numFecForImpPackets, + packetMaskSub1, + packetMask); + +} + +// Modification for UEP: reuse the tables (designed for equal protection). +// First version is to build mask from two sub-masks. +// Longer-term, may add another set of tables for UEP cases for more +// flexibility in protection between important and residual packets. + +// UEP scheme: +// First subMask is for higher protection for important packets. +// Other subMask is the residual protection for remaining packets. + +// Mask is characterized as (#packets_to_protect, #fec_for_protection). +// Protection defined as: (#fec_for_protection / #packets_to_protect). + +// So if k = numMediaPackets, n=total#packets, (n-k)=numFecPackets, +// and m=numImpPackets, then we will have the following: + +// For important packets: +// subMask1 = (m, t): protection = m/(t), where t=min(m,n-k). + +// For the residual protection, we currently have two options: + +// Mode 0: subMask21 = (k-m,n-k-m): protection = (n-k-m)/(k-m): +// no protection overlap between the two partitions. + +// Mode 1: subMask22 = (k, n-k-m), with protection (n-k-m)/(k): +// some protection overlap between the two partitions. + +void UnequalProtectionMask(const WebRtc_UWord16 numMediaPackets, + const WebRtc_UWord16 numFecPackets, + const WebRtc_UWord16 numImpPackets, + const WebRtc_UWord16 numMaskBytes, + const ResidualProtectionMode mode, + WebRtc_UWord8* packetMask) +{ + + // + // Generate subMask1: higher protection for numImpPackets: + // + ImportantPacketProtection(numFecPackets, numImpPackets, + numMaskBytes, packetMask); + + // + // Generate subMask2: left-over protection (for remaining partition data), + // if we still have some some FEC packets + // + if (numFecPackets > numImpPackets) + { + + ResidualPacketProtection(numMediaPackets,numFecPackets, + numImpPackets, numMaskBytes, + mode, packetMask); + } + +} + +void GeneratePacketMasks(const WebRtc_UWord32 numMediaPackets, + const WebRtc_UWord32 numFecPackets, + const WebRtc_UWord32 numImpPackets, + WebRtc_UWord8* packetMask) +{ + assert(numMediaPackets <= sizeof(packetMaskTbl)/sizeof(*packetMaskTbl) && + numMediaPackets > 0); + assert(numFecPackets <= numMediaPackets && numFecPackets > 0); + assert(numImpPackets <= numMediaPackets && numImpPackets >= 0); + + WebRtc_UWord8 lBit = numMediaPackets > 16 ? 1 : 0; + const WebRtc_UWord16 numMaskBytes = + (lBit == 1)? kMaskSizeLBitSet : kMaskSizeLBitClear; + + // Default: use overlap mode for residual protection. + const ResidualProtectionMode kResidualProtectionMode = kModeOverlap; + + // Force equal-protection for these cases. + // Equal protection is also used for: (numImpPackets == 1 && numFecPackets == 1). + // UEP=off would generally be more efficient than the UEP=on for this case. + // TODO (marpan): check/test this condition. + if (!kUseUnequalProtection || numImpPackets == 0 || + (numImpPackets == 1 && numFecPackets == 1)) + { + // Retrieve corresponding mask table directly: for equal-protection case. + // Mask = (k,n-k), with protection factor = (n-k)/k, + // where k = numMediaPackets, n=total#packets, (n-k)=numFecPackets. + memcpy(packetMask, packetMaskTbl[numMediaPackets - 1][numFecPackets - 1], + numFecPackets * numMaskBytes); + } + else //UEP case + { + UnequalProtectionMask(numMediaPackets, numFecPackets, numImpPackets, + numMaskBytes, kResidualProtectionMode, + packetMask); + + } // End of UEP modification + +} //End of GetPacketMasks + +} // namespace internal +} // namespace webrtc diff --git a/modules/rtp_rtcp/source/forward_error_correction_internal.h b/modules/rtp_rtcp/source/forward_error_correction_internal.h new file mode 100644 index 0000000000..6cda8c111a --- /dev/null +++ b/modules/rtp_rtcp/source/forward_error_correction_internal.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#include "typedefs.h" + +namespace webrtc { + +// Packet mask size in bytes (L bit is set). +const WebRtc_UWord8 kMaskSizeLBitSet = 6; +// Packet mask size in bytes (L bit is cleared). +const WebRtc_UWord8 kMaskSizeLBitClear = 2; + +namespace internal { + + /** + * Returns an array of packet masks. The mask of a single FEC packet + * corresponds to a number of mask bytes. The mask indicates which + * media packets should be protected by the FEC packet. + + * \param[in] numMediaPackets The number of media packets to protect. + * [1, maxMediaPackets]. + * \param[in] numFecPackets The number of FEC packets which will be generated. + * [1, numMediaPackets]. + * \param[in] numImpPackets The number of important packets. + * [0, numMediaPackets]. + * numImpPackets = 0 is the equal protection scenario. + * \param[out] packetMask A pointer to hold the packet mask array, of size + * numFecPackets * "number of mask bytes". + */ +void GeneratePacketMasks(const WebRtc_UWord32 numMediaPackets, + const WebRtc_UWord32 numFecPackets, + const WebRtc_UWord32 numImpPackets, + WebRtc_UWord8* packetMask); + + +} // namespace internal +} // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_rtcp.gyp b/modules/rtp_rtcp/source/rtp_rtcp.gyp index e5a69c3861..fdd8ee8ab1 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp.gyp +++ b/modules/rtp_rtcp/source/rtp_rtcp.gyp @@ -65,6 +65,8 @@ 'fec_private_tables.h', 'forward_error_correction.cc', 'forward_error_correction.h', + 'forward_error_correction_internal.cc', + 'forward_error_correction_internal.h', 'overuse_detector.cc', 'overuse_detector.h', 'h263_information.cc', diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc index 4f03f85c26..2ca62b88cc 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -42,6 +42,7 @@ RTPSenderVideo::RTPSenderVideo(const WebRtc_Word32 id, RTPSenderInterface* rtpSe _codeRateKey(0), _codeRateDelta(0), _fecProtectionFactor(0), + _numberFirstPartition(0), // H263 _savedByte(0), @@ -69,6 +70,7 @@ RTPSenderVideo::Init() _codeRateKey = 0; _codeRateDelta = 0; _fecProtectionFactor = 0; + _numberFirstPartition = 0; return 0; } @@ -176,7 +178,8 @@ RTPSenderVideo::SendVideoPacket(const FrameType frameType, lastMediaRtpHeader.data[1] = _payloadTypeRED; // Replace payload and clear // marker bit. - retVal = _fec.GenerateFEC(_mediaPacketListFec, fecPacketList,_fecProtectionFactor); + retVal = _fec.GenerateFEC(_mediaPacketListFec, _fecProtectionFactor, + _numberFirstPartition, fecPacketList); while(!_rtpPacketListFec.Empty()) { WebRtc_UWord8 newDataBuffer[IP_PACKET_SIZE]; @@ -347,6 +350,10 @@ RTPSenderVideo::SendVideo(const RtpVideoCodecTypes videoType, _fecProtectionFactor = _codeRateDelta; } + // Default setting for number of first partition packets: + // Will be extracted in SendVP8 for VP8 codec; other codecs use 0 + _numberFirstPartition = 0; + WebRtc_Word32 retVal = -1; switch(videoType) { @@ -1123,6 +1130,9 @@ RTPSenderVideo::SendVP8(const FrameType frameType, _rtpSender.BuildRTPheader(dataBuffer, payloadType, last, captureTimeStamp); + // TODO (marpan): Set _numberFirstPartition here: + // Equal to the first packet that contains last fragment of first partition + if (-1 == SendVideoPacket(frameType, dataBuffer, payloadBytesInPacket, rtpHeaderLength)) { diff --git a/modules/rtp_rtcp/source/rtp_sender_video.h b/modules/rtp_rtcp/source/rtp_sender_video.h index 719ed72827..2d06302aac 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.h +++ b/modules/rtp_rtcp/source/rtp_sender_video.h @@ -152,8 +152,9 @@ private: WebRtc_UWord8 _codeRateKey; WebRtc_UWord8 _codeRateDelta; WebRtc_UWord8 _fecProtectionFactor; - ListWrapper _mediaPacketListFec; - ListWrapper _rtpPacketListFec; + WebRtc_UWord32 _numberFirstPartition; + ListWrapper _mediaPacketListFec; + ListWrapper _rtpPacketListFec; // H263 WebRtc_UWord8 _savedByte; diff --git a/modules/rtp_rtcp/test/testFec/testFec.cpp b/modules/rtp_rtcp/test/testFec/testFec.cpp deleted file mode 100644 index e49bcfa662..0000000000 --- a/modules/rtp_rtcp/test/testFec/testFec.cpp +++ /dev/null @@ -1,508 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#include - -//#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(rand()) / RAND_MAX; - while (randomVariable < reorderRate) - { - ListItem* nextItem = receivedPacketList.Next(listItem); - if (nextItem == NULL) - { - break; - } - else - { - listItem = nextItem; - } - randomVariable = static_cast(rand()) / RAND_MAX; - } - - assert(listItem != NULL); - ForwardErrorCorrection::ReceivedPacket* receivedPacket = - static_cast(listItem->GetItem()); - toDecodeList.PushBack(receivedPacket); - - // Duplicate packets. - randomVariable = static_cast(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(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(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 <= 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((static_cast(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((static_cast(rand()) / - RAND_MAX) * 255); - } - - seqNum++; - } - mediaPacket->data[1] |= 0x80; // Set the marker bit of the last packet. - - WebRtc_UWord8 protectionFactor = static_cast(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 - (mediaPacketListItem->GetItem()); - const float lossRandomVariable = (static_cast(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 - (fecPacketListItem->GetItem()); - const float lossRandomVariable = (static_cast(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(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 - ((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. - ListItem* recoveredPacketListItem = recoveredPacketList.First(); - mediaPacket = static_cast - (mediaPacketListItem->GetItem()); - - if (recoveredPacketListItem == NULL) - { - printf("Error: insufficient number of recovered packets.\n"); - return -1; - } - - 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; - } - } - } - - // 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; -} diff --git a/modules/rtp_rtcp/test/testFec/test_fec.cc b/modules/rtp_rtcp/test/testFec/test_fec.cc new file mode 100644 index 0000000000..111682950d --- /dev/null +++ b/modules/rtp_rtcp/test/testFec/test_fec.cc @@ -0,0 +1,569 @@ +/** + * 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(); + } +} diff --git a/modules/rtp_rtcp/test/testFec/test_fec.gyp b/modules/rtp_rtcp/test/testFec/test_fec.gyp new file mode 100644 index 0000000000..c5830a800f --- /dev/null +++ b/modules/rtp_rtcp/test/testFec/test_fec.gyp @@ -0,0 +1,38 @@ +# 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. + +{ + 'includes': [ + '../../../../common_settings.gypi', # Common settings + ], + 'targets': [ + { + 'target_name': 'test_fec', + 'type': 'executable', + 'dependencies': [ + '../../source/rtp_rtcp.gyp:rtp_rtcp', + ], + + 'include_dirs': [ + '../../source', + '../../../../system_wrappers/interface', + ], + + 'sources': [ + 'test_fec.cc', + ], + + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: \ No newline at end of file