Generalize FEC header formatting.
- Split out reading/writing of FEC headers to classes separate from ForwardErrorCorrection. This makes ForwardErrorCorrection oblivious to what FEC header scheme is used, and lets it focus on encoding/decoding the FEC payloads. - Add unit tests for FEC header readers/writers. - Split ForwardErrorCorrection::XorPackets into XorHeaders and XorPayloads and reuse these functions for both encoding and decoding. - Rename AttemptRecover -> AttemptRecovery in ForwardErrorCorrection. BUG=webrtc:5654 R=danilchap@webrtc.org, stefan@webrtc.org Review URL: https://codereview.webrtc.org/2260803002 . Cr-Commit-Position: refs/heads/master@{#14316}
This commit is contained in:
parent
66ac50e58c
commit
78db1582e5
@ -446,6 +446,7 @@ if (rtc_include_tests) {
|
||||
"rtp_rtcp/source/rtp_rtcp_impl_unittest.cc",
|
||||
"rtp_rtcp/source/rtp_sender_unittest.cc",
|
||||
"rtp_rtcp/source/time_util_unittest.cc",
|
||||
"rtp_rtcp/source/ulpfec_header_reader_writer_unittest.cc",
|
||||
"rtp_rtcp/source/vp8_partition_aggregator_unittest.cc",
|
||||
"rtp_rtcp/test/testAPI/test_api.cc",
|
||||
"rtp_rtcp/test/testAPI/test_api.h",
|
||||
|
||||
@ -149,6 +149,8 @@ rtc_source_set("rtp_rtcp") {
|
||||
"source/time_util.h",
|
||||
"source/tmmbr_help.cc",
|
||||
"source/tmmbr_help.h",
|
||||
"source/ulpfec_header_reader_writer.cc",
|
||||
"source/ulpfec_header_reader_writer.h",
|
||||
"source/video_codec_information.h",
|
||||
"source/vp8_partition_aggregator.cc",
|
||||
"source/vp8_partition_aggregator.h",
|
||||
|
||||
@ -158,6 +158,8 @@
|
||||
'source/rtp_format_vp9.h',
|
||||
'source/rtp_format_video_generic.cc',
|
||||
'source/rtp_format_video_generic.h',
|
||||
'source/ulpfec_header_reader_writer.cc',
|
||||
'source/ulpfec_header_reader_writer.h',
|
||||
'source/vp8_partition_aggregator.cc',
|
||||
'source/vp8_partition_aggregator.h',
|
||||
# Mocks
|
||||
|
||||
@ -25,11 +25,12 @@ FecReceiver* FecReceiver::Create(RtpData* callback) {
|
||||
}
|
||||
|
||||
FecReceiverImpl::FecReceiverImpl(RtpData* callback)
|
||||
: recovered_packet_callback_(callback) {}
|
||||
: recovered_packet_callback_(callback),
|
||||
fec_(ForwardErrorCorrection::CreateUlpfec()) {}
|
||||
|
||||
FecReceiverImpl::~FecReceiverImpl() {
|
||||
received_packets_.clear();
|
||||
fec_.ResetState(&recovered_packets_);
|
||||
fec_->ResetState(&recovered_packets_);
|
||||
}
|
||||
|
||||
FecPacketCounter FecReceiverImpl::GetPacketCounter() const {
|
||||
@ -211,7 +212,7 @@ int32_t FecReceiverImpl::ProcessReceivedFec() {
|
||||
}
|
||||
crit_sect_.Enter();
|
||||
}
|
||||
if (fec_.DecodeFec(&received_packets_, &recovered_packets_) != 0) {
|
||||
if (fec_->DecodeFec(&received_packets_, &recovered_packets_) != 0) {
|
||||
crit_sect_.Leave();
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_FEC_RECEIVER_IMPL_H_
|
||||
#define WEBRTC_MODULES_RTP_RTCP_SOURCE_FEC_RECEIVER_IMPL_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc/base/criticalsection.h"
|
||||
#include "webrtc/modules/rtp_rtcp/include/fec_receiver.h"
|
||||
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
@ -36,7 +38,7 @@ class FecReceiverImpl : public FecReceiver {
|
||||
private:
|
||||
rtc::CriticalSection crit_sect_;
|
||||
RtpData* recovered_packet_callback_;
|
||||
ForwardErrorCorrection fec_;
|
||||
std::unique_ptr<ForwardErrorCorrection> fec_;
|
||||
// TODO(holmer): In the current version |received_packets_| is never more
|
||||
// than one packet, since we process FEC every time a new packet
|
||||
// arrives. We should remove the list.
|
||||
|
||||
@ -39,14 +39,16 @@ using ::webrtc::test::fec::RawRtpPacket;
|
||||
|
||||
class ReceiverFecTest : public ::testing::Test {
|
||||
protected:
|
||||
ReceiverFecTest() : receiver_fec_(FecReceiver::Create(&rtp_data_callback_)) {}
|
||||
ReceiverFecTest()
|
||||
: fec_(ForwardErrorCorrection::CreateUlpfec()),
|
||||
receiver_fec_(FecReceiver::Create(&rtp_data_callback_)) {}
|
||||
|
||||
void EncodeFec(ForwardErrorCorrection::PacketList* media_packets,
|
||||
std::list<ForwardErrorCorrection::Packet*>* fec_packets,
|
||||
unsigned int num_fec_packets) {
|
||||
uint8_t protection_factor = num_fec_packets * 255 / media_packets->size();
|
||||
EXPECT_EQ(0, fec_.EncodeFec(*media_packets, protection_factor, 0, false,
|
||||
kFecMaskBursty, fec_packets));
|
||||
EXPECT_EQ(0, fec_->EncodeFec(*media_packets, protection_factor, 0, false,
|
||||
kFecMaskBursty, fec_packets));
|
||||
ASSERT_EQ(num_fec_packets, fec_packets->size());
|
||||
}
|
||||
|
||||
@ -95,7 +97,7 @@ class ReceiverFecTest : public ::testing::Test {
|
||||
uint8_t ulpfec_payload_type);
|
||||
|
||||
MockRtpData rtp_data_callback_;
|
||||
ForwardErrorCorrection fec_;
|
||||
std::unique_ptr<ForwardErrorCorrection> fec_;
|
||||
std::unique_ptr<FecReceiver> receiver_fec_;
|
||||
FrameGenerator generator_;
|
||||
};
|
||||
@ -260,9 +262,9 @@ TEST_F(ReceiverFecTest, TooManyFrames) {
|
||||
GenerateFrame(1, i, &media_rtp_packets, &media_packets);
|
||||
}
|
||||
std::list<ForwardErrorCorrection::Packet*> fec_packets;
|
||||
EXPECT_EQ(
|
||||
-1, fec_.EncodeFec(media_packets, kNumFecPackets * 255 / kNumMediaPackets,
|
||||
0, false, kFecMaskBursty, &fec_packets));
|
||||
EXPECT_EQ(-1, fec_->EncodeFec(media_packets,
|
||||
kNumFecPackets * 255 / kNumMediaPackets, 0,
|
||||
false, kFecMaskBursty, &fec_packets));
|
||||
}
|
||||
|
||||
TEST_F(ReceiverFecTest, PacketNotDroppedTooEarly) {
|
||||
|
||||
@ -20,27 +20,17 @@
|
||||
#include "webrtc/base/logging.h"
|
||||
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// FEC header size in bytes.
|
||||
constexpr size_t kFecHeaderSize = 10;
|
||||
|
||||
// ULP header size in bytes (L bit is set).
|
||||
constexpr size_t kUlpHeaderSizeLBitSet = (2 + kMaskSizeLBitSet);
|
||||
|
||||
// ULP header size in bytes (L bit is cleared).
|
||||
constexpr size_t kUlpHeaderSizeLBitClear = (2 + kMaskSizeLBitClear);
|
||||
|
||||
namespace {
|
||||
// Transport header size in bytes. Assume UDP/IPv4 as a reasonable minimum.
|
||||
constexpr size_t kTransportOverhead = 28;
|
||||
} // namespace
|
||||
|
||||
// Maximum number of media packets that can be protected.
|
||||
constexpr size_t ForwardErrorCorrection::kMaxMediaPackets;
|
||||
|
||||
// Maximum number of FEC packets stored internally.
|
||||
constexpr size_t kMaxFecPackets = ForwardErrorCorrection::kMaxMediaPackets;
|
||||
ForwardErrorCorrection::Packet::Packet() : length(0), data(), ref_count_(0) {}
|
||||
ForwardErrorCorrection::Packet::~Packet() = default;
|
||||
|
||||
int32_t ForwardErrorCorrection::Packet::AddRef() {
|
||||
return ++ref_count_;
|
||||
@ -65,35 +55,35 @@ bool ForwardErrorCorrection::SortablePacket::LessThan::operator() (
|
||||
return IsNewerSequenceNumber(second->seq_num, first->seq_num);
|
||||
}
|
||||
|
||||
ForwardErrorCorrection::ReceivedPacket::ReceivedPacket() {}
|
||||
ForwardErrorCorrection::ReceivedPacket::~ReceivedPacket() {}
|
||||
ForwardErrorCorrection::ReceivedPacket::ReceivedPacket() = default;
|
||||
ForwardErrorCorrection::ReceivedPacket::~ReceivedPacket() = default;
|
||||
|
||||
ForwardErrorCorrection::RecoveredPacket::RecoveredPacket() {}
|
||||
ForwardErrorCorrection::RecoveredPacket::~RecoveredPacket() {}
|
||||
ForwardErrorCorrection::RecoveredPacket::RecoveredPacket() = default;
|
||||
ForwardErrorCorrection::RecoveredPacket::~RecoveredPacket() = default;
|
||||
|
||||
ForwardErrorCorrection::ForwardErrorCorrection()
|
||||
: generated_fec_packets_(kMaxMediaPackets), received_fec_packets_(),
|
||||
packet_mask_(), tmp_packet_mask_() {}
|
||||
ForwardErrorCorrection::~ForwardErrorCorrection() {}
|
||||
ForwardErrorCorrection::ProtectedPacket::ProtectedPacket() = default;
|
||||
ForwardErrorCorrection::ProtectedPacket::~ProtectedPacket() = default;
|
||||
|
||||
// Input packet
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | RTP Header (12 octets) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | RTP Payload |
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
ForwardErrorCorrection::ReceivedFecPacket::ReceivedFecPacket() = default;
|
||||
ForwardErrorCorrection::ReceivedFecPacket::~ReceivedFecPacket() = default;
|
||||
|
||||
ForwardErrorCorrection::ForwardErrorCorrection(
|
||||
std::unique_ptr<FecHeaderReader> fec_header_reader,
|
||||
std::unique_ptr<FecHeaderWriter> fec_header_writer)
|
||||
: fec_header_reader_(std::move(fec_header_reader)),
|
||||
fec_header_writer_(std::move(fec_header_writer)),
|
||||
generated_fec_packets_(fec_header_writer_->MaxFecPackets()),
|
||||
packet_mask_size_(0) {}
|
||||
|
||||
ForwardErrorCorrection::~ForwardErrorCorrection() = default;
|
||||
|
||||
std::unique_ptr<ForwardErrorCorrection> ForwardErrorCorrection::CreateUlpfec() {
|
||||
std::unique_ptr<FecHeaderReader> fec_header_reader(new UlpfecHeaderReader());
|
||||
std::unique_ptr<FecHeaderWriter> fec_header_writer(new UlpfecHeaderWriter());
|
||||
return std::unique_ptr<ForwardErrorCorrection>(new ForwardErrorCorrection(
|
||||
std::move(fec_header_reader), std::move(fec_header_writer)));
|
||||
}
|
||||
|
||||
// Output packet
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | FEC Header (10 octets) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | FEC Level 0 Header |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | FEC Level 0 Payload |
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
|
||||
uint8_t protection_factor,
|
||||
int num_important_packets,
|
||||
@ -107,17 +97,14 @@ int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
|
||||
RTC_DCHECK_GE(num_important_packets, 0);
|
||||
RTC_DCHECK_LE(static_cast<size_t>(num_important_packets), num_media_packets);
|
||||
RTC_DCHECK(fec_packets->empty());
|
||||
|
||||
if (num_media_packets > kMaxMediaPackets) {
|
||||
const size_t max_media_packets = fec_header_writer_->MaxMediaPackets();
|
||||
if (num_media_packets > max_media_packets) {
|
||||
LOG(LS_WARNING) << "Can't protect " << num_media_packets
|
||||
<< " media packets per frame. Max is " << kMaxMediaPackets
|
||||
<< " media packets per frame. Max is " << max_media_packets
|
||||
<< ".";
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool l_bit = (num_media_packets > 8 * kMaskSizeLBitClear);
|
||||
int num_mask_bytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
|
||||
// Error check the media packets.
|
||||
for (const auto& media_packet : media_packets) {
|
||||
RTC_DCHECK(media_packet);
|
||||
@ -135,12 +122,11 @@ int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare generated FEC packets.
|
||||
int num_fec_packets = NumFecPackets(num_media_packets, protection_factor);
|
||||
if (num_fec_packets == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Prepare generated FEC packets by setting them to 0.
|
||||
for (int i = 0; i < num_fec_packets; ++i) {
|
||||
memset(generated_fec_packets_[i].data, 0, IP_PACKET_SIZE);
|
||||
// Use this as a marker for untouched packets.
|
||||
@ -149,26 +135,26 @@ int ForwardErrorCorrection::EncodeFec(const PacketList& media_packets,
|
||||
}
|
||||
|
||||
const internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
|
||||
|
||||
// -- Generate packet masks --
|
||||
memset(packet_mask_, 0, num_fec_packets * num_mask_bytes);
|
||||
packet_mask_size_ = internal::PacketMaskSize(num_media_packets);
|
||||
memset(packet_masks_, 0, num_fec_packets * packet_mask_size_);
|
||||
internal::GeneratePacketMasks(num_media_packets, num_fec_packets,
|
||||
num_important_packets, use_unequal_protection,
|
||||
mask_table, packet_mask_);
|
||||
|
||||
int num_mask_bits = InsertZerosInBitMasks(
|
||||
media_packets, packet_mask_, num_mask_bytes, num_fec_packets);
|
||||
mask_table, packet_masks_);
|
||||
|
||||
// Adapt packet masks to missing media packets.
|
||||
int num_mask_bits = InsertZerosInPacketMasks(media_packets, num_fec_packets);
|
||||
if (num_mask_bits < 0) {
|
||||
return -1;
|
||||
}
|
||||
l_bit = (static_cast<size_t>(num_mask_bits) > 8 * kMaskSizeLBitClear);
|
||||
if (l_bit) {
|
||||
num_mask_bytes = kMaskSizeLBitSet;
|
||||
}
|
||||
packet_mask_size_ = internal::PacketMaskSize(num_mask_bits);
|
||||
|
||||
GenerateFecBitStrings(media_packets, packet_mask_, num_fec_packets, l_bit);
|
||||
GenerateFecUlpHeaders(media_packets, packet_mask_, num_fec_packets, l_bit);
|
||||
// Write FEC packets to |generated_fec_packets_|.
|
||||
GenerateFecPayloads(media_packets, num_fec_packets);
|
||||
// TODO(brandtr): Generalize this when multistream protection support is
|
||||
// added.
|
||||
const uint16_t seq_num_base =
|
||||
ParseSequenceNumber(media_packets.front().get()->data);
|
||||
FinalizeFecHeaders(num_fec_packets, seq_num_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -185,74 +171,53 @@ int ForwardErrorCorrection::NumFecPackets(int num_media_packets,
|
||||
return num_fec_packets;
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::GenerateFecBitStrings(
|
||||
void ForwardErrorCorrection::GenerateFecPayloads(
|
||||
const PacketList& media_packets,
|
||||
uint8_t* packet_mask,
|
||||
int num_fec_packets,
|
||||
bool l_bit) {
|
||||
size_t num_fec_packets) {
|
||||
RTC_DCHECK(!media_packets.empty());
|
||||
uint8_t media_payload_length[2];
|
||||
const int num_mask_bytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
const uint16_t ulp_header_size =
|
||||
l_bit ? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear;
|
||||
const uint16_t fec_rtp_offset =
|
||||
kFecHeaderSize + ulp_header_size - kRtpHeaderSize;
|
||||
|
||||
for (int i = 0; i < num_fec_packets; ++i) {
|
||||
for (size_t i = 0; i < num_fec_packets; ++i) {
|
||||
Packet* const fec_packet = &generated_fec_packets_[i];
|
||||
size_t pkt_mask_idx = i * packet_mask_size_;
|
||||
const size_t min_packet_mask_size = fec_header_writer_->MinPacketMaskSize(
|
||||
&packet_masks_[pkt_mask_idx], packet_mask_size_);
|
||||
const size_t fec_header_size =
|
||||
fec_header_writer_->FecHeaderSize(min_packet_mask_size);
|
||||
|
||||
size_t media_pkt_idx = 0;
|
||||
auto media_packets_it = media_packets.cbegin();
|
||||
uint32_t pkt_mask_idx = i * num_mask_bytes;
|
||||
uint32_t media_pkt_idx = 0;
|
||||
uint16_t fec_packet_length = 0;
|
||||
uint16_t prev_seq_num = ParseSequenceNumber((*media_packets_it)->data);
|
||||
while (media_packets_it != media_packets.end()) {
|
||||
// Each FEC packet has a multiple byte mask. Determine if this media
|
||||
// packet should be included in FEC packet i.
|
||||
if (packet_mask[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) {
|
||||
Packet* media_packet = media_packets_it->get();
|
||||
Packet* const media_packet = media_packets_it->get();
|
||||
// Should |media_packet| be protected by |fec_packet|?
|
||||
if (packet_masks_[pkt_mask_idx] & (1 << (7 - media_pkt_idx))) {
|
||||
size_t media_payload_length = media_packet->length - kRtpHeaderSize;
|
||||
|
||||
// Assign network-ordered media payload length.
|
||||
ByteWriter<uint16_t>::WriteBigEndian(
|
||||
media_payload_length, media_packet->length - kRtpHeaderSize);
|
||||
|
||||
fec_packet_length = media_packet->length + fec_rtp_offset;
|
||||
// On the first protected packet, we don't need to XOR.
|
||||
if (fec_packet->length == 0) {
|
||||
// Copy the first 2 bytes of the RTP header. Note that the E and L
|
||||
// bits are overwritten in GenerateFecUlpHeaders.
|
||||
memcpy(&fec_packet->data[0], &media_packet->data[0], 2);
|
||||
// Copy the 5th to 8th bytes of the RTP header (timestamp).
|
||||
memcpy(&fec_packet->data[4], &media_packet->data[4], 4);
|
||||
// Copy network-ordered payload size.
|
||||
memcpy(&fec_packet->data[8], media_payload_length, 2);
|
||||
|
||||
// Copy RTP payload, leaving room for the ULP header.
|
||||
memcpy(&fec_packet->data[kFecHeaderSize + ulp_header_size],
|
||||
&media_packet->data[kRtpHeaderSize],
|
||||
media_packet->length - kRtpHeaderSize);
|
||||
} else {
|
||||
// XOR with the first 2 bytes of the RTP header.
|
||||
fec_packet->data[0] ^= media_packet->data[0];
|
||||
fec_packet->data[1] ^= media_packet->data[1];
|
||||
|
||||
// XOR with the 5th to 8th bytes of the RTP header.
|
||||
for (uint32_t j = 4; j < 8; ++j) {
|
||||
fec_packet->data[j] ^= media_packet->data[j];
|
||||
}
|
||||
|
||||
// XOR with the network-ordered payload size.
|
||||
fec_packet->data[8] ^= media_payload_length[0];
|
||||
fec_packet->data[9] ^= media_payload_length[1];
|
||||
|
||||
// XOR with RTP payload, leaving room for the ULP header.
|
||||
for (int32_t j = kFecHeaderSize + ulp_header_size;
|
||||
j < fec_packet_length; j++) {
|
||||
fec_packet->data[j] ^= media_packet->data[j - fec_rtp_offset];
|
||||
}
|
||||
}
|
||||
bool first_protected_packet = (fec_packet->length == 0);
|
||||
size_t fec_packet_length = fec_header_size + media_payload_length;
|
||||
if (fec_packet_length > fec_packet->length) {
|
||||
// Recall that XORing with zero (which the FEC packets are prefilled
|
||||
// with) is the identity operator, thus all prior XORs are
|
||||
// still correct even though we expand the packet length here.
|
||||
fec_packet->length = fec_packet_length;
|
||||
}
|
||||
if (first_protected_packet) {
|
||||
// Write P, X, CC, M, and PT recovery fields.
|
||||
// Note that bits 0, 1, and 16 are overwritten in FinalizeFecHeaders.
|
||||
memcpy(&fec_packet->data[0], &media_packet->data[0], 2);
|
||||
// Write length recovery field. (This is a temporary location for
|
||||
// ULPFEC.)
|
||||
ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[2],
|
||||
media_payload_length);
|
||||
// Write timestamp recovery field.
|
||||
memcpy(&fec_packet->data[4], &media_packet->data[4], 4);
|
||||
// Write payload.
|
||||
memcpy(&fec_packet->data[fec_header_size],
|
||||
&media_packet->data[kRtpHeaderSize], media_payload_length);
|
||||
} else {
|
||||
XorHeaders(*media_packet, fec_packet);
|
||||
XorPayloads(*media_packet, media_payload_length, fec_header_size,
|
||||
fec_packet);
|
||||
}
|
||||
}
|
||||
media_packets_it++;
|
||||
if (media_packets_it != media_packets.end()) {
|
||||
@ -268,47 +233,44 @@ void ForwardErrorCorrection::GenerateFecBitStrings(
|
||||
}
|
||||
}
|
||||
|
||||
int ForwardErrorCorrection::InsertZerosInBitMasks(
|
||||
int ForwardErrorCorrection::InsertZerosInPacketMasks(
|
||||
const PacketList& media_packets,
|
||||
uint8_t* packet_mask,
|
||||
int num_mask_bytes,
|
||||
int num_fec_packets) {
|
||||
if (media_packets.size() <= 1) {
|
||||
return media_packets.size();
|
||||
size_t num_fec_packets) {
|
||||
size_t num_media_packets = media_packets.size();
|
||||
if (num_media_packets <= 1) {
|
||||
return num_media_packets;
|
||||
}
|
||||
int last_seq_num = ParseSequenceNumber(media_packets.back()->data);
|
||||
int first_seq_num = ParseSequenceNumber(media_packets.front()->data);
|
||||
int total_missing_seq_nums =
|
||||
static_cast<uint16_t>(last_seq_num - first_seq_num) -
|
||||
media_packets.size() + 1;
|
||||
uint16_t last_seq_num = ParseSequenceNumber(media_packets.back()->data);
|
||||
uint16_t first_seq_num = ParseSequenceNumber(media_packets.front()->data);
|
||||
size_t total_missing_seq_nums =
|
||||
static_cast<uint16_t>(last_seq_num - first_seq_num) - num_media_packets +
|
||||
1;
|
||||
if (total_missing_seq_nums == 0) {
|
||||
// All sequence numbers are covered by the packet mask. No zero insertion
|
||||
// required.
|
||||
return media_packets.size();
|
||||
// All sequence numbers are covered by the packet mask.
|
||||
// No zero insertion required.
|
||||
return num_media_packets;
|
||||
}
|
||||
// We can only protect 8 * kMaskSizeLBitSet packets.
|
||||
if (total_missing_seq_nums + media_packets.size() > 8 * kMaskSizeLBitSet)
|
||||
const size_t max_media_packets = fec_header_writer_->MaxMediaPackets();
|
||||
if (total_missing_seq_nums + num_media_packets > max_media_packets) {
|
||||
return -1;
|
||||
// Allocate the new mask.
|
||||
int new_mask_bytes = kMaskSizeLBitClear;
|
||||
if (media_packets.size() +
|
||||
total_missing_seq_nums > 8 * kMaskSizeLBitClear) {
|
||||
new_mask_bytes = kMaskSizeLBitSet;
|
||||
}
|
||||
memset(tmp_packet_mask_, 0, num_fec_packets * kMaskSizeLBitSet);
|
||||
// Allocate the new mask.
|
||||
size_t tmp_packet_mask_size =
|
||||
internal::PacketMaskSize(total_missing_seq_nums + num_media_packets);
|
||||
memset(tmp_packet_masks_, 0, num_fec_packets * tmp_packet_mask_size);
|
||||
|
||||
auto media_packets_it = media_packets.cbegin();
|
||||
uint16_t prev_seq_num = first_seq_num;
|
||||
++media_packets_it;
|
||||
|
||||
// Insert the first column.
|
||||
internal::CopyColumn(tmp_packet_mask_, new_mask_bytes, packet_mask_,
|
||||
num_mask_bytes, num_fec_packets, 0, 0);
|
||||
internal::CopyColumn(tmp_packet_masks_, tmp_packet_mask_size, packet_masks_,
|
||||
packet_mask_size_, num_fec_packets, 0, 0);
|
||||
size_t new_bit_index = 1;
|
||||
size_t old_bit_index = 1;
|
||||
// Insert zeros in the bit mask for every hole in the sequence.
|
||||
while (media_packets_it != media_packets.end()) {
|
||||
if (new_bit_index == 8 * kMaskSizeLBitSet) {
|
||||
if (new_bit_index == max_media_packets) {
|
||||
// We can only cover up to 48 packets.
|
||||
break;
|
||||
}
|
||||
@ -316,13 +278,13 @@ int ForwardErrorCorrection::InsertZerosInBitMasks(
|
||||
const int num_zeros_to_insert =
|
||||
static_cast<uint16_t>(seq_num - prev_seq_num - 1);
|
||||
if (num_zeros_to_insert > 0) {
|
||||
internal::InsertZeroColumns(num_zeros_to_insert, tmp_packet_mask_,
|
||||
new_mask_bytes, num_fec_packets,
|
||||
internal::InsertZeroColumns(num_zeros_to_insert, tmp_packet_masks_,
|
||||
tmp_packet_mask_size, num_fec_packets,
|
||||
new_bit_index);
|
||||
}
|
||||
new_bit_index += num_zeros_to_insert;
|
||||
internal::CopyColumn(tmp_packet_mask_, new_mask_bytes, packet_mask_,
|
||||
num_mask_bytes, num_fec_packets, new_bit_index,
|
||||
internal::CopyColumn(tmp_packet_masks_, tmp_packet_mask_size, packet_masks_,
|
||||
packet_mask_size_, num_fec_packets, new_bit_index,
|
||||
old_bit_index);
|
||||
++new_bit_index;
|
||||
++old_bit_index;
|
||||
@ -332,73 +294,22 @@ int ForwardErrorCorrection::InsertZerosInBitMasks(
|
||||
if (new_bit_index % 8 != 0) {
|
||||
// We didn't fill the last byte. Shift bits to correct position.
|
||||
for (uint16_t row = 0; row < num_fec_packets; ++row) {
|
||||
int new_byte_index = row * new_mask_bytes + new_bit_index / 8;
|
||||
tmp_packet_mask_[new_byte_index] <<= (7 - (new_bit_index % 8));
|
||||
int new_byte_index = row * tmp_packet_mask_size + new_bit_index / 8;
|
||||
tmp_packet_masks_[new_byte_index] <<= (7 - (new_bit_index % 8));
|
||||
}
|
||||
}
|
||||
// Replace the old mask with the new.
|
||||
memcpy(packet_mask, tmp_packet_mask_, kMaskSizeLBitSet * num_fec_packets);
|
||||
memcpy(packet_masks_, tmp_packet_masks_,
|
||||
num_fec_packets * tmp_packet_mask_size);
|
||||
return new_bit_index;
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::GenerateFecUlpHeaders(
|
||||
const PacketList& media_packets,
|
||||
uint8_t* packet_mask,
|
||||
int num_fec_packets,
|
||||
bool l_bit) {
|
||||
// -- Generate FEC and ULP headers --
|
||||
//
|
||||
// FEC Header, 10 bytes
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |E|L|P|X| CC |M| PT recovery | SN base |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | TS recovery |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | length recovery |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// ULP Header, 4 bytes (for L = 0)
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Protection Length | mask |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | mask cont. (present only when L = 1) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
int num_mask_bytes = l_bit ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
const uint16_t ulp_header_size =
|
||||
l_bit ? kUlpHeaderSizeLBitSet : kUlpHeaderSizeLBitClear;
|
||||
|
||||
RTC_DCHECK(!media_packets.empty());
|
||||
Packet* first_media_packet = media_packets.front().get();
|
||||
RTC_DCHECK(first_media_packet);
|
||||
uint16_t seq_num = ParseSequenceNumber(first_media_packet->data);
|
||||
for (int i = 0; i < num_fec_packets; ++i) {
|
||||
Packet* const fec_packet = &generated_fec_packets_[i];
|
||||
// -- FEC header --
|
||||
fec_packet->data[0] &= 0x7f; // Set E to zero.
|
||||
if (l_bit == 0) {
|
||||
fec_packet->data[0] &= 0xbf; // Clear the L bit.
|
||||
} else {
|
||||
fec_packet->data[0] |= 0x40; // Set the L bit.
|
||||
}
|
||||
// Sequence number from first media packet used as SN base.
|
||||
// We use the same sequence number base for every FEC packet,
|
||||
// but that's not required in general.
|
||||
ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[2], seq_num);
|
||||
|
||||
// -- ULP header --
|
||||
// Copy the payload size to the protection length field.
|
||||
// (We protect the entire packet.)
|
||||
ByteWriter<uint16_t>::WriteBigEndian(
|
||||
&fec_packet->data[10],
|
||||
fec_packet->length - kFecHeaderSize - ulp_header_size);
|
||||
|
||||
// Copy the packet mask.
|
||||
memcpy(&fec_packet->data[12], &packet_mask[i * num_mask_bytes],
|
||||
num_mask_bytes);
|
||||
void ForwardErrorCorrection::FinalizeFecHeaders(size_t num_fec_packets,
|
||||
uint16_t seq_num_base) {
|
||||
for (size_t i = 0; i < num_fec_packets; ++i) {
|
||||
fec_header_writer_->FinalizeFecHeader(
|
||||
seq_num_base, &packet_masks_[i * packet_mask_size_], packet_mask_size_,
|
||||
&generated_fec_packets_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,9 +321,8 @@ void ForwardErrorCorrection::ResetState(
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::InsertMediaPacket(
|
||||
ReceivedPacket* received_packet,
|
||||
RecoveredPacketList* recovered_packets) {
|
||||
|
||||
RecoveredPacketList* recovered_packets,
|
||||
ReceivedPacket* received_packet) {
|
||||
// Search for duplicate packets.
|
||||
for (const auto& recovered_packet : *recovered_packets) {
|
||||
if (received_packet->seq_num == recovered_packet->seq_num) {
|
||||
@ -422,7 +332,6 @@ void ForwardErrorCorrection::InsertMediaPacket(
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<RecoveredPacket> recovered_packet(new RecoveredPacket());
|
||||
// This "recovered packet" was not recovered using parity packets.
|
||||
recovered_packet->was_recovered = false;
|
||||
@ -431,33 +340,32 @@ void ForwardErrorCorrection::InsertMediaPacket(
|
||||
recovered_packet->seq_num = received_packet->seq_num;
|
||||
recovered_packet->pkt = received_packet->pkt;
|
||||
recovered_packet->pkt->length = received_packet->pkt->length;
|
||||
|
||||
RecoveredPacket* recovered_packet_ptr = recovered_packet.get();
|
||||
// TODO(holmer): Consider replacing this with a binary search for the right
|
||||
// position, and then just insert the new packet. Would get rid of the sort.
|
||||
RecoveredPacket* recovered_packet_ptr = recovered_packet.get();
|
||||
recovered_packets->push_back(std::move(recovered_packet));
|
||||
recovered_packets->sort(SortablePacket::LessThan());
|
||||
UpdateCoveringFecPackets(recovered_packet_ptr);
|
||||
UpdateCoveringFecPackets(*recovered_packet_ptr);
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::UpdateCoveringFecPackets(RecoveredPacket* packet) {
|
||||
void ForwardErrorCorrection::UpdateCoveringFecPackets(
|
||||
const RecoveredPacket& packet) {
|
||||
for (auto& fec_packet : received_fec_packets_) {
|
||||
// Is this FEC packet protecting the media packet |packet|?
|
||||
auto protected_it = std::lower_bound(fec_packet->protected_packets.begin(),
|
||||
fec_packet->protected_packets.end(),
|
||||
packet,
|
||||
SortablePacket::LessThan());
|
||||
&packet, SortablePacket::LessThan());
|
||||
if (protected_it != fec_packet->protected_packets.end() &&
|
||||
(*protected_it)->seq_num == packet->seq_num) {
|
||||
(*protected_it)->seq_num == packet.seq_num) {
|
||||
// Found an FEC packet which is protecting |packet|.
|
||||
(*protected_it)->pkt = packet->pkt;
|
||||
(*protected_it)->pkt = packet.pkt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::InsertFecPacket(
|
||||
ReceivedPacket* received_packet,
|
||||
const RecoveredPacketList* recovered_packets) {
|
||||
const RecoveredPacketList& recovered_packets,
|
||||
ReceivedPacket* received_packet) {
|
||||
// Check for duplicate.
|
||||
for (const auto& existing_fec_packet : received_fec_packets_) {
|
||||
if (received_packet->seq_num == existing_fec_packet->seq_num) {
|
||||
@ -466,31 +374,29 @@ void ForwardErrorCorrection::InsertFecPacket(
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ReceivedFecPacket> fec_packet(new ReceivedFecPacket());
|
||||
fec_packet->pkt = received_packet->pkt;
|
||||
fec_packet->seq_num = received_packet->seq_num;
|
||||
fec_packet->ssrc = received_packet->ssrc;
|
||||
|
||||
const uint16_t seq_num_base =
|
||||
ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[2]);
|
||||
const uint16_t mask_size_bytes = (fec_packet->pkt->data[0] & 0x40)
|
||||
? kMaskSizeLBitSet
|
||||
: kMaskSizeLBitClear; // L bit set?
|
||||
|
||||
// Parse erasure code mask from ULP header and represent as protected packets.
|
||||
for (uint16_t byte_idx = 0; byte_idx < mask_size_bytes; ++byte_idx) {
|
||||
uint8_t packet_mask = fec_packet->pkt->data[12 + byte_idx];
|
||||
// Parse ULPFEC/FlexFEC header specific info.
|
||||
bool ret = fec_header_reader_->ReadFecHeader(fec_packet.get());
|
||||
if (!ret) {
|
||||
LOG(LS_WARNING) << "Malformed FEC header: dropping packet.";
|
||||
return;
|
||||
}
|
||||
// Parse packet mask from header and represent as protected packets.
|
||||
for (uint16_t byte_idx = 0; byte_idx < fec_packet->packet_mask_size;
|
||||
++byte_idx) {
|
||||
uint8_t packet_mask =
|
||||
fec_packet->pkt->data[fec_packet->packet_mask_offset + byte_idx];
|
||||
for (uint16_t bit_idx = 0; bit_idx < 8; ++bit_idx) {
|
||||
if (packet_mask & (1 << (7 - bit_idx))) {
|
||||
std::unique_ptr<ProtectedPacket> protected_packet(
|
||||
new ProtectedPacket());
|
||||
// This wraps naturally with the sequence number.
|
||||
protected_packet->seq_num =
|
||||
static_cast<uint16_t>(seq_num_base + (byte_idx << 3) + bit_idx);
|
||||
protected_packet->seq_num = static_cast<uint16_t>(
|
||||
fec_packet->seq_num_base + (byte_idx << 3) + bit_idx);
|
||||
protected_packet->pkt = nullptr;
|
||||
// Note that |protected_pkt_list| is sorted (according to sequence
|
||||
// number) by construction.
|
||||
fec_packet->protected_packets.push_back(std::move(protected_packet));
|
||||
}
|
||||
}
|
||||
@ -499,26 +405,27 @@ void ForwardErrorCorrection::InsertFecPacket(
|
||||
// All-zero packet mask; we can discard this FEC packet.
|
||||
LOG(LS_WARNING) << "Received FEC packet has an all-zero packet mask.";
|
||||
} else {
|
||||
AssignRecoveredPackets(fec_packet.get(), recovered_packets);
|
||||
AssignRecoveredPackets(recovered_packets, fec_packet.get());
|
||||
// TODO(holmer): Consider replacing this with a binary search for the right
|
||||
// position, and then just insert the new packet. Would get rid of the sort.
|
||||
//
|
||||
// For correct decoding, |fec_packet_list_| does not necessarily
|
||||
// For correct decoding, |received_fec_packets_| does not necessarily
|
||||
// need to be sorted by sequence number (see decoding algorithm in
|
||||
// AttemptRecover()), but by keeping it sorted we try to recover the
|
||||
// oldest lost packets first.
|
||||
// AttemptRecover()). By keeping it sorted we try to recover the
|
||||
// oldest lost packets first, however.
|
||||
received_fec_packets_.push_back(std::move(fec_packet));
|
||||
received_fec_packets_.sort(SortablePacket::LessThan());
|
||||
if (received_fec_packets_.size() > kMaxFecPackets) {
|
||||
const size_t max_fec_packets = fec_header_reader_->MaxFecPackets();
|
||||
if (received_fec_packets_.size() > max_fec_packets) {
|
||||
received_fec_packets_.pop_front();
|
||||
}
|
||||
RTC_DCHECK_LE(received_fec_packets_.size(), kMaxFecPackets);
|
||||
RTC_DCHECK_LE(received_fec_packets_.size(), max_fec_packets);
|
||||
}
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::AssignRecoveredPackets(
|
||||
ReceivedFecPacket* fec_packet,
|
||||
const RecoveredPacketList* recovered_packets) {
|
||||
const RecoveredPacketList& recovered_packets,
|
||||
ReceivedFecPacket* fec_packet) {
|
||||
ProtectedPacketList* protected_packets = &fec_packet->protected_packets;
|
||||
std::vector<RecoveredPacket*> recovered_protected_packets;
|
||||
|
||||
@ -527,9 +434,9 @@ void ForwardErrorCorrection::AssignRecoveredPackets(
|
||||
// been recovered. Update the corresponding protected packets to point to
|
||||
// the recovered packets.
|
||||
auto it_p = protected_packets->cbegin();
|
||||
auto it_r = recovered_packets->cbegin();
|
||||
auto it_r = recovered_packets.cbegin();
|
||||
SortablePacket::LessThan less_than;
|
||||
while (it_p != protected_packets->end() && it_r != recovered_packets->end()) {
|
||||
while (it_p != protected_packets->end() && it_r != recovered_packets.end()) {
|
||||
if (less_than(*it_p, *it_r)) {
|
||||
++it_p;
|
||||
} else if (less_than(*it_r, *it_p)) {
|
||||
@ -566,9 +473,9 @@ void ForwardErrorCorrection::InsertPackets(
|
||||
}
|
||||
|
||||
if (received_packet->is_fec) {
|
||||
InsertFecPacket(received_packet, recovered_packets);
|
||||
InsertFecPacket(*recovered_packets, received_packet);
|
||||
} else {
|
||||
InsertMediaPacket(received_packet, recovered_packets);
|
||||
InsertMediaPacket(recovered_packets, received_packet);
|
||||
}
|
||||
// Delete the received packet "wrapper".
|
||||
received_packets->pop_front();
|
||||
@ -578,137 +485,143 @@ void ForwardErrorCorrection::InsertPackets(
|
||||
}
|
||||
|
||||
bool ForwardErrorCorrection::StartPacketRecovery(
|
||||
const ReceivedFecPacket* fec_packet,
|
||||
RecoveredPacket* recovered_packet) {
|
||||
// This is the first packet which we try to recover with.
|
||||
const uint16_t ulp_header_size = fec_packet->pkt->data[0] & 0x40
|
||||
? kUlpHeaderSizeLBitSet
|
||||
: kUlpHeaderSizeLBitClear; // L bit set?
|
||||
if (fec_packet->pkt->length <
|
||||
static_cast<size_t>(kFecHeaderSize + ulp_header_size)) {
|
||||
const ReceivedFecPacket& fec_packet,
|
||||
RecoveredPacket* recovered_packet) {
|
||||
// Sanity check packet length.
|
||||
if (fec_packet.pkt->length < fec_packet.fec_header_size) {
|
||||
LOG(LS_WARNING)
|
||||
<< "Truncated FEC packet doesn't contain room for ULP header.";
|
||||
<< "The FEC packet is truncated: it does not contain enough room "
|
||||
<< "for its own header.";
|
||||
return false;
|
||||
}
|
||||
// Initialize recovered packet data.
|
||||
recovered_packet->pkt = new Packet();
|
||||
memset(recovered_packet->pkt->data, 0, IP_PACKET_SIZE);
|
||||
recovered_packet->returned = false;
|
||||
recovered_packet->was_recovered = true;
|
||||
uint16_t protection_length =
|
||||
ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[10]);
|
||||
if (protection_length >
|
||||
std::min(
|
||||
sizeof(recovered_packet->pkt->data) - kRtpHeaderSize,
|
||||
sizeof(fec_packet->pkt->data) - kFecHeaderSize - ulp_header_size)) {
|
||||
LOG(LS_WARNING) << "Incorrect FEC protection length, dropping.";
|
||||
// Copy bytes corresponding to minimum RTP header size.
|
||||
// Note that the sequence number and SSRC fields will be overwritten
|
||||
// at the end of packet recovery.
|
||||
memcpy(&recovered_packet->pkt->data, fec_packet.pkt->data, kRtpHeaderSize);
|
||||
// Copy remaining FEC payload.
|
||||
if (fec_packet.protection_length >
|
||||
std::min(sizeof(recovered_packet->pkt->data) - kRtpHeaderSize,
|
||||
sizeof(fec_packet.pkt->data) - fec_packet.fec_header_size)) {
|
||||
LOG(LS_WARNING) << "Incorrect protection length, dropping FEC packet.";
|
||||
return false;
|
||||
}
|
||||
// Copy FEC payload, skipping the ULP header.
|
||||
memcpy(&recovered_packet->pkt->data[kRtpHeaderSize],
|
||||
&fec_packet->pkt->data[kFecHeaderSize + ulp_header_size],
|
||||
protection_length);
|
||||
// Copy the length recovery field.
|
||||
memcpy(recovered_packet->length_recovery, &fec_packet->pkt->data[8], 2);
|
||||
// Copy the first 2 bytes of the FEC header.
|
||||
memcpy(recovered_packet->pkt->data, fec_packet->pkt->data, 2);
|
||||
// Copy the 5th to 8th bytes of the FEC header.
|
||||
memcpy(&recovered_packet->pkt->data[4], &fec_packet->pkt->data[4], 4);
|
||||
// Set the SSRC field.
|
||||
ByteWriter<uint32_t>::WriteBigEndian(&recovered_packet->pkt->data[8],
|
||||
fec_packet->ssrc);
|
||||
&fec_packet.pkt->data[fec_packet.fec_header_size],
|
||||
fec_packet.protection_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ForwardErrorCorrection::FinishPacketRecovery(
|
||||
const ReceivedFecPacket& fec_packet,
|
||||
RecoveredPacket* recovered_packet) {
|
||||
// Set the RTP version to 2.
|
||||
recovered_packet->pkt->data[0] |= 0x80; // Set the 1st bit.
|
||||
recovered_packet->pkt->data[0] &= 0xbf; // Clear the 2nd bit.
|
||||
|
||||
// Set the SN field.
|
||||
ByteWriter<uint16_t>::WriteBigEndian(&recovered_packet->pkt->data[2],
|
||||
recovered_packet->seq_num);
|
||||
// Recover the packet length.
|
||||
// Recover the packet length, from temporary location.
|
||||
recovered_packet->pkt->length =
|
||||
ByteReader<uint16_t>::ReadBigEndian(recovered_packet->length_recovery) +
|
||||
ByteReader<uint16_t>::ReadBigEndian(&recovered_packet->pkt->data[2]) +
|
||||
kRtpHeaderSize;
|
||||
if (recovered_packet->pkt->length >
|
||||
sizeof(recovered_packet->pkt->data) - kRtpHeaderSize) {
|
||||
LOG(LS_WARNING) << "The recovered packet had a length larger than a "
|
||||
<< "typical IP packet, and is thus dropped.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the SN field.
|
||||
ByteWriter<uint16_t>::WriteBigEndian(&recovered_packet->pkt->data[2],
|
||||
recovered_packet->seq_num);
|
||||
// Set the SSRC field.
|
||||
ByteWriter<uint32_t>::WriteBigEndian(&recovered_packet->pkt->data[8],
|
||||
fec_packet.protected_ssrc);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::XorPackets(const Packet* src,
|
||||
RecoveredPacket* dst) {
|
||||
// XOR with the first 2 bytes of the RTP header.
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
dst->pkt->data[i] ^= src->data[i];
|
||||
}
|
||||
// XOR with the 5th to 8th bytes of the RTP header.
|
||||
for (uint32_t i = 4; i < 8; ++i) {
|
||||
dst->pkt->data[i] ^= src->data[i];
|
||||
}
|
||||
// XOR with the network-ordered payload size.
|
||||
uint8_t media_payload_length[2];
|
||||
ByteWriter<uint16_t>::WriteBigEndian(media_payload_length,
|
||||
src->length - kRtpHeaderSize);
|
||||
dst->length_recovery[0] ^= media_payload_length[0];
|
||||
dst->length_recovery[1] ^= media_payload_length[1];
|
||||
void ForwardErrorCorrection::XorHeaders(const Packet& src, Packet* dst) {
|
||||
// XOR the first 2 bytes of the header: V, P, X, CC, M, PT fields.
|
||||
dst->data[0] ^= src.data[0];
|
||||
dst->data[1] ^= src.data[1];
|
||||
|
||||
// XOR with RTP payload.
|
||||
// TODO(marpan/ajm): Are we doing more XORs than required here?
|
||||
for (size_t i = kRtpHeaderSize; i < src->length; ++i) {
|
||||
dst->pkt->data[i] ^= src->data[i];
|
||||
// XOR the length recovery field.
|
||||
uint8_t src_payload_length_network_order[2];
|
||||
ByteWriter<uint16_t>::WriteBigEndian(src_payload_length_network_order,
|
||||
src.length - kRtpHeaderSize);
|
||||
dst->data[2] ^= src_payload_length_network_order[0];
|
||||
dst->data[3] ^= src_payload_length_network_order[1];
|
||||
|
||||
// XOR the 5th to 8th bytes of the header: the timestamp field.
|
||||
dst->data[4] ^= src.data[4];
|
||||
dst->data[5] ^= src.data[5];
|
||||
dst->data[6] ^= src.data[6];
|
||||
dst->data[7] ^= src.data[7];
|
||||
|
||||
// Skip the 9th to 12th bytes of the header.
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::XorPayloads(const Packet& src,
|
||||
size_t payload_length,
|
||||
size_t dst_offset,
|
||||
Packet* dst) {
|
||||
// XOR the payload.
|
||||
RTC_DCHECK_LE(kRtpHeaderSize + payload_length, sizeof(src.data));
|
||||
RTC_DCHECK_LE(dst_offset + payload_length, sizeof(dst->data));
|
||||
for (size_t i = 0; i < payload_length; ++i) {
|
||||
dst->data[dst_offset + i] ^= src.data[kRtpHeaderSize + i];
|
||||
}
|
||||
}
|
||||
|
||||
bool ForwardErrorCorrection::RecoverPacket(
|
||||
const ReceivedFecPacket* fec_packet,
|
||||
RecoveredPacket* rec_packet_to_insert) {
|
||||
if (!StartPacketRecovery(fec_packet, rec_packet_to_insert))
|
||||
bool ForwardErrorCorrection::RecoverPacket(const ReceivedFecPacket& fec_packet,
|
||||
RecoveredPacket* recovered_packet) {
|
||||
if (!StartPacketRecovery(fec_packet, recovered_packet)) {
|
||||
return false;
|
||||
for (const auto& protected_packet : fec_packet->protected_packets) {
|
||||
}
|
||||
for (const auto& protected_packet : fec_packet.protected_packets) {
|
||||
if (protected_packet->pkt == nullptr) {
|
||||
// This is the packet we're recovering.
|
||||
rec_packet_to_insert->seq_num = protected_packet->seq_num;
|
||||
recovered_packet->seq_num = protected_packet->seq_num;
|
||||
} else {
|
||||
XorPackets(protected_packet->pkt, rec_packet_to_insert);
|
||||
XorHeaders(*protected_packet->pkt, recovered_packet->pkt);
|
||||
XorPayloads(*protected_packet->pkt, protected_packet->pkt->length,
|
||||
kRtpHeaderSize, recovered_packet->pkt);
|
||||
}
|
||||
}
|
||||
if (!FinishPacketRecovery(rec_packet_to_insert))
|
||||
if (!FinishPacketRecovery(fec_packet, recovered_packet)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ForwardErrorCorrection::AttemptRecover(
|
||||
void ForwardErrorCorrection::AttemptRecovery(
|
||||
RecoveredPacketList* recovered_packets) {
|
||||
auto fec_packet_it = received_fec_packets_.begin();
|
||||
while (fec_packet_it != received_fec_packets_.end()) {
|
||||
// Search for each FEC packet's protected media packets.
|
||||
int packets_missing = NumCoveredPacketsMissing(fec_packet_it->get());
|
||||
int packets_missing = NumCoveredPacketsMissing(**fec_packet_it);
|
||||
|
||||
// We can only recover one packet with an FEC packet.
|
||||
if (packets_missing == 1) {
|
||||
// Recovery possible.
|
||||
std::unique_ptr<RecoveredPacket> packet_to_insert(new RecoveredPacket());
|
||||
packet_to_insert->pkt = nullptr;
|
||||
if (!RecoverPacket(fec_packet_it->get(), packet_to_insert.get())) {
|
||||
std::unique_ptr<RecoveredPacket> recovered_packet(new RecoveredPacket());
|
||||
recovered_packet->pkt = nullptr;
|
||||
if (!RecoverPacket(**fec_packet_it, recovered_packet.get())) {
|
||||
// Can't recover using this packet, drop it.
|
||||
fec_packet_it = received_fec_packets_.erase(fec_packet_it);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto packet_to_insert_ptr = packet_to_insert.get();
|
||||
auto recovered_packet_ptr = recovered_packet.get();
|
||||
// Add recovered packet to the list of recovered packets and update any
|
||||
// FEC packets covering this packet with a pointer to the data.
|
||||
// TODO(holmer): Consider replacing this with a binary search for the
|
||||
// right position, and then just insert the new packet. Would get rid of
|
||||
// the sort.
|
||||
recovered_packets->push_back(std::move(packet_to_insert));
|
||||
recovered_packets->push_back(std::move(recovered_packet));
|
||||
recovered_packets->sort(SortablePacket::LessThan());
|
||||
UpdateCoveringFecPackets(packet_to_insert_ptr);
|
||||
UpdateCoveringFecPackets(*recovered_packet_ptr);
|
||||
DiscardOldRecoveredPackets(recovered_packets);
|
||||
fec_packet_it = received_fec_packets_.erase(fec_packet_it);
|
||||
|
||||
@ -727,9 +640,9 @@ void ForwardErrorCorrection::AttemptRecover(
|
||||
}
|
||||
|
||||
int ForwardErrorCorrection::NumCoveredPacketsMissing(
|
||||
const ReceivedFecPacket* fec_packet) {
|
||||
const ReceivedFecPacket& fec_packet) {
|
||||
int packets_missing = 0;
|
||||
for (const auto& protected_packet : fec_packet->protected_packets) {
|
||||
for (const auto& protected_packet : fec_packet.protected_packets) {
|
||||
if (protected_packet->pkt == nullptr) {
|
||||
++packets_missing;
|
||||
if (packets_missing > 1) {
|
||||
@ -742,37 +655,80 @@ int ForwardErrorCorrection::NumCoveredPacketsMissing(
|
||||
|
||||
void ForwardErrorCorrection::DiscardOldRecoveredPackets(
|
||||
RecoveredPacketList* recovered_packets) {
|
||||
while (recovered_packets->size() > kMaxMediaPackets) {
|
||||
const size_t max_media_packets = fec_header_reader_->MaxMediaPackets();
|
||||
while (recovered_packets->size() > max_media_packets) {
|
||||
recovered_packets->pop_front();
|
||||
}
|
||||
RTC_DCHECK_LE(recovered_packets->size(), kMaxMediaPackets);
|
||||
RTC_DCHECK_LE(recovered_packets->size(), max_media_packets);
|
||||
}
|
||||
|
||||
uint16_t ForwardErrorCorrection::ParseSequenceNumber(uint8_t* packet) {
|
||||
return (packet[2] << 8) + packet[3];
|
||||
}
|
||||
|
||||
uint32_t ForwardErrorCorrection::ParseSsrc(uint8_t* packet) {
|
||||
return (packet[8] << 24) + (packet[9] << 16) + (packet[10] << 8) + packet[11];
|
||||
}
|
||||
|
||||
int ForwardErrorCorrection::DecodeFec(
|
||||
ReceivedPacketList* received_packets,
|
||||
RecoveredPacketList* recovered_packets) {
|
||||
// TODO(marpan/ajm): can we check for multiple ULP headers, and return an
|
||||
// error?
|
||||
if (recovered_packets->size() == kMaxMediaPackets) {
|
||||
const size_t max_media_packets = fec_header_reader_->MaxMediaPackets();
|
||||
if (recovered_packets->size() == max_media_packets) {
|
||||
const unsigned int seq_num_diff =
|
||||
abs(static_cast<int>(received_packets->front()->seq_num) -
|
||||
static_cast<int>(recovered_packets->back()->seq_num));
|
||||
if (seq_num_diff > kMaxMediaPackets) {
|
||||
if (seq_num_diff > max_media_packets) {
|
||||
// A big gap in sequence numbers. The old recovered packets
|
||||
// are now useless, so it's safe to do a reset.
|
||||
ResetState(recovered_packets);
|
||||
}
|
||||
}
|
||||
InsertPackets(received_packets, recovered_packets);
|
||||
AttemptRecover(recovered_packets);
|
||||
AttemptRecovery(recovered_packets);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t ForwardErrorCorrection::MaxPacketOverhead() const {
|
||||
return kFecHeaderSize + kUlpHeaderSizeLBitSet;
|
||||
return fec_header_writer_->MaxPacketOverhead();
|
||||
}
|
||||
|
||||
FecHeaderReader::FecHeaderReader(size_t max_media_packets,
|
||||
size_t max_fec_packets)
|
||||
: max_media_packets_(max_media_packets),
|
||||
max_fec_packets_(max_fec_packets) {}
|
||||
|
||||
FecHeaderReader::~FecHeaderReader() = default;
|
||||
|
||||
size_t FecHeaderReader::MaxMediaPackets() const {
|
||||
return max_media_packets_;
|
||||
}
|
||||
|
||||
size_t FecHeaderReader::MaxFecPackets() const {
|
||||
return max_fec_packets_;
|
||||
}
|
||||
|
||||
FecHeaderWriter::FecHeaderWriter(size_t max_media_packets,
|
||||
size_t max_fec_packets,
|
||||
size_t max_packet_overhead)
|
||||
: max_media_packets_(max_media_packets),
|
||||
max_fec_packets_(max_fec_packets),
|
||||
max_packet_overhead_(max_packet_overhead) {}
|
||||
|
||||
FecHeaderWriter::~FecHeaderWriter() = default;
|
||||
|
||||
size_t FecHeaderWriter::MaxMediaPackets() const {
|
||||
return max_media_packets_;
|
||||
}
|
||||
|
||||
size_t FecHeaderWriter::MaxFecPackets() const {
|
||||
return max_fec_packets_;
|
||||
}
|
||||
|
||||
size_t FecHeaderWriter::MaxPacketOverhead() const {
|
||||
return max_packet_overhead_;
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -17,31 +17,32 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/base/basictypes.h"
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
#include "webrtc/base/refcount.h"
|
||||
#include "webrtc/base/scoped_ref_ptr.h"
|
||||
#include "webrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class FecHeaderReader;
|
||||
class FecHeaderWriter;
|
||||
|
||||
// Performs codec-independent forward error correction (FEC), based on RFC 5109.
|
||||
// Option exists to enable unequal protection (UEP) across packets.
|
||||
// This is not to be confused with protection within packets
|
||||
// (referred to as uneven level protection (ULP) in RFC 5109).
|
||||
class ForwardErrorCorrection {
|
||||
public:
|
||||
// Maximum number of media packets we can protect
|
||||
static constexpr size_t kMaxMediaPackets = 48u;
|
||||
|
||||
// TODO(holmer): As a next step all these struct-like packet classes should be
|
||||
// refactored into proper classes, and their members should be made private.
|
||||
// This will require parts of the functionality in forward_error_correction.cc
|
||||
// and receiver_fec.cc to be refactored into the packet classes.
|
||||
class Packet {
|
||||
public:
|
||||
Packet() : length(0), data(), ref_count_(0) {}
|
||||
virtual ~Packet() {}
|
||||
Packet();
|
||||
virtual ~Packet();
|
||||
|
||||
// Add a reference.
|
||||
virtual int32_t AddRef();
|
||||
@ -89,7 +90,7 @@ class ForwardErrorCorrection {
|
||||
rtc::scoped_refptr<Packet> pkt; // Pointer to the packet storage.
|
||||
};
|
||||
|
||||
// The recovered list parameter of #DecodeFec() references structs of
|
||||
// The recovered list parameter of DecodeFec() references structs of
|
||||
// this type.
|
||||
// TODO(holmer): Refactor into a proper class.
|
||||
class RecoveredPacket : public SortablePacket {
|
||||
@ -102,19 +103,55 @@ class ForwardErrorCorrection {
|
||||
// through the received packet list.
|
||||
bool returned; // True when the packet already has been returned to the
|
||||
// caller through the callback.
|
||||
uint8_t length_recovery[2]; // Two bytes used for recovering the packet
|
||||
// length with XOR operations.
|
||||
rtc::scoped_refptr<Packet> pkt; // Pointer to the packet storage.
|
||||
};
|
||||
|
||||
// Used to link media packets to their protecting FEC packets.
|
||||
//
|
||||
// TODO(holmer): Refactor into a proper class.
|
||||
class ProtectedPacket : public ForwardErrorCorrection::SortablePacket {
|
||||
public:
|
||||
ProtectedPacket();
|
||||
~ProtectedPacket();
|
||||
|
||||
rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
|
||||
};
|
||||
|
||||
using ProtectedPacketList = std::list<std::unique_ptr<ProtectedPacket>>;
|
||||
|
||||
// Used for internal storage of received FEC packets in a list.
|
||||
//
|
||||
// TODO(holmer): Refactor into a proper class.
|
||||
class ReceivedFecPacket : public ForwardErrorCorrection::SortablePacket {
|
||||
public:
|
||||
ReceivedFecPacket();
|
||||
~ReceivedFecPacket();
|
||||
|
||||
// List of media packets that this FEC packet protects.
|
||||
ProtectedPacketList protected_packets;
|
||||
// RTP header fields.
|
||||
uint32_t ssrc;
|
||||
// FEC header fields.
|
||||
size_t fec_header_size;
|
||||
uint32_t protected_ssrc;
|
||||
uint16_t seq_num_base;
|
||||
size_t packet_mask_offset; // Relative start of FEC header.
|
||||
size_t packet_mask_size;
|
||||
size_t protection_length;
|
||||
// Raw data.
|
||||
rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
|
||||
};
|
||||
|
||||
using PacketList = std::list<std::unique_ptr<Packet>>;
|
||||
using ReceivedPacketList = std::list<std::unique_ptr<ReceivedPacket>>;
|
||||
using RecoveredPacketList = std::list<std::unique_ptr<RecoveredPacket>>;
|
||||
using ReceivedFecPacketList = std::list<std::unique_ptr<ReceivedFecPacket>>;
|
||||
|
||||
ForwardErrorCorrection();
|
||||
virtual ~ForwardErrorCorrection();
|
||||
~ForwardErrorCorrection();
|
||||
|
||||
// Creates a ForwardErrorCorrection tailored for a specific FEC scheme.
|
||||
static std::unique_ptr<ForwardErrorCorrection> CreateUlpfec();
|
||||
|
||||
//
|
||||
// Generates a list of FEC packets from supplied media packets.
|
||||
//
|
||||
// Input: media_packets List of media packets to protect, of type
|
||||
@ -158,7 +195,6 @@ class ForwardErrorCorrection {
|
||||
FecMaskType fec_mask_type,
|
||||
std::list<Packet*>* fec_packets);
|
||||
|
||||
//
|
||||
// Decodes a list of received media and FEC packets. It will parse the
|
||||
// |received_packets|, storing FEC packets internally, and move
|
||||
// media packets to |recovered_packets|. The recovered list will be
|
||||
@ -198,47 +234,32 @@ class ForwardErrorCorrection {
|
||||
// Frees all memory allocated by this class.
|
||||
void ResetState(RecoveredPacketList* recovered_packets);
|
||||
|
||||
// TODO(brandtr): Remove these functions when the Packet classes
|
||||
// have been refactored.
|
||||
static uint16_t ParseSequenceNumber(uint8_t* packet);
|
||||
static uint32_t ParseSsrc(uint8_t* packet);
|
||||
|
||||
protected:
|
||||
ForwardErrorCorrection(std::unique_ptr<FecHeaderReader> fec_header_reader,
|
||||
std::unique_ptr<FecHeaderWriter> fec_header_writer);
|
||||
|
||||
private:
|
||||
// Used to link media packets to their protecting FEC packets.
|
||||
//
|
||||
// TODO(holmer): Refactor into a proper class.
|
||||
class ProtectedPacket : public ForwardErrorCorrection::SortablePacket {
|
||||
public:
|
||||
rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
|
||||
};
|
||||
|
||||
using ProtectedPacketList = std::list<std::unique_ptr<ProtectedPacket>>;
|
||||
|
||||
// Used for internal storage of received FEC packets in a list.
|
||||
//
|
||||
// TODO(holmer): Refactor into a proper class.
|
||||
class ReceivedFecPacket : public ForwardErrorCorrection::SortablePacket {
|
||||
public:
|
||||
ProtectedPacketList protected_packets;
|
||||
uint32_t ssrc; // SSRC of the current frame.
|
||||
rtc::scoped_refptr<ForwardErrorCorrection::Packet> pkt;
|
||||
};
|
||||
|
||||
using ReceivedFecPacketList = std::list<std::unique_ptr<ReceivedFecPacket>>;
|
||||
|
||||
// Analyzes |media_packets| for holes in the sequence and inserts zero columns
|
||||
// into the |packet_mask| where those holes are found. Zero columns means that
|
||||
// those packets will have no protection.
|
||||
// Returns the number of bits used for one row of the new packet mask.
|
||||
// Requires that |packet_mask| has at least 6 * |num_fec_packets| bytes
|
||||
// allocated.
|
||||
int InsertZerosInBitMasks(const PacketList& media_packets,
|
||||
uint8_t* packet_mask, int num_mask_bytes,
|
||||
int num_fec_packets);
|
||||
int InsertZerosInPacketMasks(const PacketList& media_packets,
|
||||
size_t num_fec_packets);
|
||||
|
||||
// Writes FEC payloads and some recovery fields in the FEC headers.
|
||||
void GenerateFecPayloads(const PacketList& media_packets,
|
||||
size_t num_fec_packets);
|
||||
|
||||
void GenerateFecUlpHeaders(const PacketList& media_packets,
|
||||
uint8_t* packet_mask, int num_fec_packets,
|
||||
bool l_bit);
|
||||
|
||||
void GenerateFecBitStrings(const PacketList& media_packets,
|
||||
uint8_t* packet_mask, int num_fec_packets,
|
||||
bool l_bit);
|
||||
// Writes the FEC header fields that are not written by GenerateFecPayloads.
|
||||
// This includes writing the packet masks.
|
||||
void FinalizeFecHeaders(size_t num_fec_packets, uint16_t seq_num_base);
|
||||
|
||||
// Inserts the |received_packets| into the internal received FEC packet list
|
||||
// or into |recovered_packets|.
|
||||
@ -246,67 +267,145 @@ class ForwardErrorCorrection {
|
||||
RecoveredPacketList* recovered_packets);
|
||||
|
||||
// Inserts the |received_packet| into |recovered_packets|. Deletes duplicates.
|
||||
void InsertMediaPacket(ReceivedPacket* received_packet,
|
||||
RecoveredPacketList* recovered_packets);
|
||||
void InsertMediaPacket(RecoveredPacketList* recovered_packets,
|
||||
ReceivedPacket* received_packet);
|
||||
|
||||
// Assigns pointers to the recovered packet from all FEC packets which cover
|
||||
// it.
|
||||
// Note: This reduces the complexity when we want to try to recover a packet
|
||||
// since we don't have to find the intersection between recovered packets and
|
||||
// packets covered by the FEC packet.
|
||||
void UpdateCoveringFecPackets(RecoveredPacket* packet);
|
||||
void UpdateCoveringFecPackets(const RecoveredPacket& packet);
|
||||
|
||||
// Insert |received_packet| into internal FEC list. Deletes duplicates.
|
||||
void InsertFecPacket(ReceivedPacket* received_packet,
|
||||
const RecoveredPacketList* recovered_packets);
|
||||
void InsertFecPacket(const RecoveredPacketList& recovered_packets,
|
||||
ReceivedPacket* received_packet);
|
||||
|
||||
// Assigns pointers to already recovered packets covered by |fec_packet|.
|
||||
static void AssignRecoveredPackets(
|
||||
ReceivedFecPacket* fec_packet,
|
||||
const RecoveredPacketList* recovered_packets);
|
||||
|
||||
// Insert |rec_packet_to_insert| into |recovered_packets| in correct position.
|
||||
void InsertRecoveredPacket(RecoveredPacket* rec_packet_to_insert,
|
||||
RecoveredPacketList* recovered_packets);
|
||||
const RecoveredPacketList& recovered_packets,
|
||||
ReceivedFecPacket* fec_packet);
|
||||
|
||||
// Attempt to recover missing packets, using the internally stored
|
||||
// received FEC packets.
|
||||
void AttemptRecover(RecoveredPacketList* recovered_packets);
|
||||
void AttemptRecovery(RecoveredPacketList* recovered_packets);
|
||||
|
||||
// Initializes packet recovery using the received |fec_packet|.
|
||||
static bool StartPacketRecovery(const ReceivedFecPacket* fec_packet,
|
||||
// Initializes headers and payload before the XOR operation
|
||||
// that recovers a packet.
|
||||
static bool StartPacketRecovery(const ReceivedFecPacket& fec_packet,
|
||||
RecoveredPacket* recovered_packet);
|
||||
|
||||
// Performs XOR between |src| and |dst| and stores the result in |dst|.
|
||||
static void XorPackets(const Packet* src, RecoveredPacket* dst);
|
||||
// Performs XOR between the first 8 bytes of |src| and |dst| and stores
|
||||
// the result in |dst|. The 3rd and 4th bytes are used for storing
|
||||
// the length recovery field.
|
||||
static void XorHeaders(const Packet& src, Packet* dst);
|
||||
|
||||
// Finish up the recovery of a packet.
|
||||
static bool FinishPacketRecovery(RecoveredPacket* recovered_packet);
|
||||
// Performs XOR between the payloads of |src| and |dst| and stores the result
|
||||
// in |dst|. The parameter |dst_offset| determines at what byte the
|
||||
// XOR operation starts in |dst|. In total, |payload_length| bytes are XORed.
|
||||
static void XorPayloads(const Packet& src,
|
||||
size_t payload_length,
|
||||
size_t dst_offset,
|
||||
Packet* dst);
|
||||
|
||||
// Finalizes recovery of packet by setting RTP header fields.
|
||||
// This is not specific to the FEC scheme used.
|
||||
static bool FinishPacketRecovery(const ReceivedFecPacket& fec_packet,
|
||||
RecoveredPacket* recovered_packet);
|
||||
|
||||
// Recover a missing packet.
|
||||
bool RecoverPacket(const ReceivedFecPacket* fec_packet,
|
||||
RecoveredPacket* rec_packet_to_insert);
|
||||
static bool RecoverPacket(const ReceivedFecPacket& fec_packet,
|
||||
RecoveredPacket* recovered_packet);
|
||||
|
||||
// Get the number of missing media packets which are covered by |fec_packet|.
|
||||
// An FEC packet can recover at most one packet, and if zero packets are
|
||||
// missing the FEC packet can be discarded. This function returns 2 when two
|
||||
// or more packets are missing.
|
||||
static int NumCoveredPacketsMissing(const ReceivedFecPacket* fec_packet);
|
||||
static int NumCoveredPacketsMissing(const ReceivedFecPacket& fec_packet);
|
||||
|
||||
// Discards old packets in |recovered_packets|, which are no longer relevant
|
||||
// for recovering lost packets.
|
||||
static void DiscardOldRecoveredPackets(
|
||||
RecoveredPacketList* recovered_packets);
|
||||
static uint16_t ParseSequenceNumber(uint8_t* packet);
|
||||
void DiscardOldRecoveredPackets(RecoveredPacketList* recovered_packets);
|
||||
|
||||
std::unique_ptr<FecHeaderReader> fec_header_reader_;
|
||||
std::unique_ptr<FecHeaderWriter> fec_header_writer_;
|
||||
|
||||
std::vector<Packet> generated_fec_packets_;
|
||||
ReceivedFecPacketList received_fec_packets_;
|
||||
|
||||
// Arrays used to avoid dynamically allocating memory when generating
|
||||
// the packet masks in the ULPFEC headers.
|
||||
// (There are never more than |kMaxMediaPackets| FEC packets generated.)
|
||||
uint8_t packet_mask_[kMaxMediaPackets * kMaskSizeLBitSet];
|
||||
uint8_t tmp_packet_mask_[kMaxMediaPackets * kMaskSizeLBitSet];
|
||||
// the packet masks.
|
||||
// (There are never more than |kUlpfecMaxMediaPackets| FEC packets generated.)
|
||||
uint8_t packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize];
|
||||
uint8_t tmp_packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize];
|
||||
size_t packet_mask_size_;
|
||||
};
|
||||
|
||||
// Classes derived from FecHeader{Reader,Writer} encapsulate the
|
||||
// specifics of reading and writing FEC header for, e.g., ULPFEC
|
||||
// and FlexFEC.
|
||||
class FecHeaderReader {
|
||||
public:
|
||||
virtual ~FecHeaderReader();
|
||||
|
||||
// The maximum number of media packets that can be covered by one FEC packet.
|
||||
size_t MaxMediaPackets() const;
|
||||
|
||||
// The maximum number of FEC packets that is supported, per call
|
||||
// to ForwardErrorCorrection::EncodeFec().
|
||||
size_t MaxFecPackets() const;
|
||||
|
||||
// Parses FEC header and stores information in ReceivedFecPacket members.
|
||||
virtual bool ReadFecHeader(
|
||||
ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const = 0;
|
||||
|
||||
protected:
|
||||
FecHeaderReader(size_t max_media_packets, size_t max_fec_packets);
|
||||
|
||||
const size_t max_media_packets_;
|
||||
const size_t max_fec_packets_;
|
||||
};
|
||||
|
||||
class FecHeaderWriter {
|
||||
public:
|
||||
virtual ~FecHeaderWriter();
|
||||
|
||||
// The maximum number of media packets that can be covered by one FEC packet.
|
||||
size_t MaxMediaPackets() const;
|
||||
|
||||
// The maximum number of FEC packets that is supported, per call
|
||||
// to ForwardErrorCorrection::EncodeFec().
|
||||
size_t MaxFecPackets() const;
|
||||
|
||||
// The maximum overhead (in bytes) per packet, due to FEC headers.
|
||||
size_t MaxPacketOverhead() const;
|
||||
|
||||
// Calculates the minimum packet mask size needed (in bytes),
|
||||
// given the discrete options of the ULPFEC masks and the bits
|
||||
// set in the current packet mask.
|
||||
virtual size_t MinPacketMaskSize(const uint8_t* packet_mask,
|
||||
size_t packet_mask_size) const = 0;
|
||||
|
||||
// The header size (in bytes), given the packet mask size.
|
||||
virtual size_t FecHeaderSize(size_t packet_mask_size) const = 0;
|
||||
|
||||
// Writes FEC header.
|
||||
virtual void FinalizeFecHeader(
|
||||
uint16_t seq_num_base,
|
||||
const uint8_t* packet_mask,
|
||||
size_t packet_mask_size,
|
||||
ForwardErrorCorrection::Packet* fec_packet) const = 0;
|
||||
|
||||
protected:
|
||||
FecHeaderWriter(size_t max_media_packets,
|
||||
size_t max_fec_packets,
|
||||
size_t max_packet_overhead);
|
||||
|
||||
const size_t max_media_packets_;
|
||||
const size_t max_fec_packets_;
|
||||
const size_t max_packet_overhead_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_FORWARD_ERROR_CORRECTION_H_
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/fec_private_tables_bursty.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/fec_private_tables_random.h"
|
||||
|
||||
@ -202,11 +203,8 @@ void RemainingPacketProtection(int num_media_packets,
|
||||
if (mode == kModeNoOverlap) {
|
||||
// sub_mask21
|
||||
|
||||
const int l_bit =
|
||||
(num_media_packets - num_fec_for_imp_packets) > 16 ? 1 : 0;
|
||||
|
||||
const int res_mask_bytes =
|
||||
(l_bit == 1) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
PacketMaskSize(num_media_packets - num_fec_for_imp_packets);
|
||||
|
||||
const uint8_t* packet_mask_sub_21 =
|
||||
mask_table.fec_packet_mask_table()[num_media_packets -
|
||||
@ -245,9 +243,7 @@ void ImportantPacketProtection(int num_fec_for_imp_packets,
|
||||
int num_mask_bytes,
|
||||
uint8_t* packet_mask,
|
||||
const PacketMaskTable& mask_table) {
|
||||
const int l_bit = num_imp_packets > 16 ? 1 : 0;
|
||||
const int num_imp_mask_bytes =
|
||||
(l_bit == 1) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
const int num_imp_mask_bytes = PacketMaskSize(num_imp_packets);
|
||||
|
||||
// Get sub_mask1 from table
|
||||
const uint8_t* packet_mask_sub_1 =
|
||||
@ -375,9 +371,7 @@ void GeneratePacketMasks(int num_media_packets,
|
||||
assert(num_fec_packets <= num_media_packets && num_fec_packets > 0);
|
||||
assert(num_imp_packets <= num_media_packets && num_imp_packets >= 0);
|
||||
|
||||
int l_bit = num_media_packets > 16 ? 1 : 0;
|
||||
const int num_mask_bytes =
|
||||
(l_bit == 1) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
const int num_mask_bytes = PacketMaskSize(num_media_packets);
|
||||
|
||||
// Equal-protection for these cases.
|
||||
if (!use_unequal_protection || num_imp_packets == 0) {
|
||||
@ -394,6 +388,14 @@ void GeneratePacketMasks(int num_media_packets,
|
||||
} // End of UEP modification
|
||||
} // End of GetPacketMasks
|
||||
|
||||
size_t PacketMaskSize(size_t num_sequence_numbers) {
|
||||
RTC_DCHECK_LE(num_sequence_numbers, 8 * kUlpfecPacketMaskSizeLBitSet);
|
||||
if (num_sequence_numbers > 8 * kUlpfecPacketMaskSizeLBitClear) {
|
||||
return kUlpfecPacketMaskSizeLBitSet;
|
||||
}
|
||||
return kUlpfecPacketMaskSizeLBitClear;
|
||||
}
|
||||
|
||||
void InsertZeroColumns(int num_zeros,
|
||||
uint8_t* new_mask,
|
||||
int new_mask_bytes,
|
||||
|
||||
@ -16,10 +16,17 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Packet mask size in bytes (L bit is set).
|
||||
constexpr size_t kMaskSizeLBitSet = 6;
|
||||
// Packet mask size in bytes (L bit is cleared).
|
||||
constexpr size_t kMaskSizeLBitClear = 2;
|
||||
// Maximum number of media packets that can be protected
|
||||
// by these packet masks.
|
||||
constexpr size_t kUlpfecMaxMediaPackets = 48;
|
||||
|
||||
// Packet mask size in bytes (given L bit).
|
||||
constexpr size_t kUlpfecPacketMaskSizeLBitClear = 2;
|
||||
constexpr size_t kUlpfecPacketMaskSizeLBitSet = 6;
|
||||
|
||||
// Convenience constants.
|
||||
constexpr size_t kUlpfecMinPacketMaskSize = kUlpfecPacketMaskSizeLBitClear;
|
||||
constexpr size_t kUlpfecMaxPacketMaskSize = kUlpfecPacketMaskSizeLBitSet;
|
||||
|
||||
namespace internal {
|
||||
|
||||
@ -65,6 +72,10 @@ void GeneratePacketMasks(int num_media_packets, int num_fec_packets,
|
||||
const PacketMaskTable& mask_table,
|
||||
uint8_t* packet_mask);
|
||||
|
||||
// Returns the required packet mask size, given the number of sequence numbers
|
||||
// that will be covered.
|
||||
size_t PacketMaskSize(size_t num_sequence_numbers);
|
||||
|
||||
// Inserts |num_zeros| zero columns into |new_mask| at position
|
||||
// |new_bit_index|. If the current byte of |new_mask| can't fit all zeros, the
|
||||
// byte will be filled with zeros from |new_bit_index|, but the next byte will
|
||||
|
||||
@ -93,9 +93,10 @@ size_t RedPacket::length() const {
|
||||
}
|
||||
|
||||
ProducerFec::ProducerFec()
|
||||
: num_protected_frames_(0),
|
||||
: fec_(ForwardErrorCorrection::CreateUlpfec()),
|
||||
num_protected_frames_(0),
|
||||
num_important_packets_(0),
|
||||
min_num_media_packets_(1) {
|
||||
min_num_media_packets_(1) {
|
||||
memset(¶ms_, 0, sizeof(params_));
|
||||
memset(&new_params_, 0, sizeof(new_params_));
|
||||
}
|
||||
@ -124,9 +125,8 @@ void ProducerFec::SetFecParameters(const FecProtectionParams* params,
|
||||
// protection in 'unequal protection mode') cannot exceed kMaxMediaPackets.
|
||||
RTC_DCHECK_GE(params->fec_rate, 0);
|
||||
RTC_DCHECK_LE(params->fec_rate, 255);
|
||||
if (num_important_packets >
|
||||
static_cast<int>(ForwardErrorCorrection::kMaxMediaPackets)) {
|
||||
num_important_packets = ForwardErrorCorrection::kMaxMediaPackets;
|
||||
if (num_important_packets > static_cast<int>(kUlpfecMaxMediaPackets)) {
|
||||
num_important_packets = kUlpfecMaxMediaPackets;
|
||||
}
|
||||
// Store the new params and apply them for the next set of FEC packets being
|
||||
// produced.
|
||||
@ -148,8 +148,8 @@ int ProducerFec::AddRtpPacketAndGenerateFec(const uint8_t* data_buffer,
|
||||
}
|
||||
bool complete_frame = false;
|
||||
const bool marker_bit = (data_buffer[1] & kRtpMarkerBitMask) ? true : false;
|
||||
if (media_packets_.size() < ForwardErrorCorrection::kMaxMediaPackets) {
|
||||
// Generic FEC can only protect up to |kMaxMediaPackets| packets.
|
||||
if (media_packets_.size() < kUlpfecMaxMediaPackets) {
|
||||
// Generic FEC can only protect up to |kUlpfecMaxMediaPackets| packets.
|
||||
std::unique_ptr<ForwardErrorCorrection::Packet> packet(
|
||||
new ForwardErrorCorrection::Packet());
|
||||
packet->length = payload_length + rtp_header_length;
|
||||
@ -168,16 +168,16 @@ int ProducerFec::AddRtpPacketAndGenerateFec(const uint8_t* data_buffer,
|
||||
(num_protected_frames_ == params_.max_fec_frames ||
|
||||
(ExcessOverheadBelowMax() && MinimumMediaPacketsReached()))) {
|
||||
RTC_DCHECK_LE(num_important_packets_,
|
||||
static_cast<int>(ForwardErrorCorrection::kMaxMediaPackets));
|
||||
static_cast<int>(kUlpfecMaxMediaPackets));
|
||||
// TODO(pbos): Consider whether unequal protection should be enabled or not,
|
||||
// it is currently always disabled.
|
||||
//
|
||||
// Since unequal protection is disabled, the value of
|
||||
// |num_important_packets_| has no importance when calling GenerateFec().
|
||||
constexpr bool kUseUnequalProtection = false;
|
||||
int ret = fec_.EncodeFec(media_packets_, params_.fec_rate,
|
||||
num_important_packets_, kUseUnequalProtection,
|
||||
params_.fec_mask_type, &generated_fec_packets_);
|
||||
int ret = fec_->EncodeFec(media_packets_, params_.fec_rate,
|
||||
num_important_packets_, kUseUnequalProtection,
|
||||
params_.fec_mask_type, &generated_fec_packets_);
|
||||
if (generated_fec_packets_.empty()) {
|
||||
num_protected_frames_ = 0;
|
||||
DeleteMediaPackets();
|
||||
@ -213,7 +213,7 @@ size_t ProducerFec::NumAvailableFecPackets() const {
|
||||
}
|
||||
|
||||
size_t ProducerFec::MaxPacketOverhead() const {
|
||||
return fec_.MaxPacketOverhead();
|
||||
return fec_->MaxPacketOverhead();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<RedPacket>> ProducerFec::GetFecPacketsAsRed(
|
||||
@ -238,15 +238,12 @@ std::vector<std::unique_ptr<RedPacket>> ProducerFec::GetFecPacketsAsRed(
|
||||
red_packet->SetSeqNum(seq_num++);
|
||||
red_packet->ClearMarkerBit();
|
||||
red_packet->AssignPayload(fec_packet->data, fec_packet->length);
|
||||
|
||||
red_packets.push_back(std::move(red_packet));
|
||||
}
|
||||
|
||||
// Reset state.
|
||||
DeleteMediaPackets();
|
||||
generated_fec_packets_.clear();
|
||||
num_protected_frames_ = 0;
|
||||
|
||||
return red_packets;
|
||||
}
|
||||
|
||||
@ -257,7 +254,7 @@ int ProducerFec::Overhead() const {
|
||||
// generation is implemented.
|
||||
RTC_DCHECK(!media_packets_.empty());
|
||||
int num_fec_packets =
|
||||
fec_.NumFecPackets(media_packets_.size(), params_.fec_rate);
|
||||
fec_->NumFecPackets(media_packets_.size(), params_.fec_rate);
|
||||
// Return the overhead in Q8.
|
||||
return (num_fec_packets << 8) / media_packets_.size();
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ class ProducerFec {
|
||||
private:
|
||||
void DeleteMediaPackets();
|
||||
int Overhead() const;
|
||||
ForwardErrorCorrection fec_;
|
||||
std::unique_ptr<ForwardErrorCorrection> fec_;
|
||||
ForwardErrorCorrection::PacketList media_packets_;
|
||||
std::list<ForwardErrorCorrection::Packet*> generated_fec_packets_;
|
||||
int num_protected_frames_;
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/fec_test_helper.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
@ -46,6 +47,15 @@ void DeepCopyEveryNthPacket(const ForwardErrorCorrection::PacketList& src,
|
||||
|
||||
using ::testing::Types;
|
||||
|
||||
// Subclass ForwardErrorCorrection to use gTest typed tests.
|
||||
class UlpfecForwardErrorCorrection : public ForwardErrorCorrection {
|
||||
public:
|
||||
UlpfecForwardErrorCorrection()
|
||||
: ForwardErrorCorrection(
|
||||
std::unique_ptr<FecHeaderReader>(new UlpfecHeaderReader()),
|
||||
std::unique_ptr<FecHeaderWriter>(new UlpfecHeaderWriter())) {}
|
||||
};
|
||||
|
||||
template <typename ForwardErrorCorrectionType>
|
||||
class RtpFecTest : public ::testing::Test {
|
||||
protected:
|
||||
@ -86,15 +96,15 @@ class RtpFecTest : public ::testing::Test {
|
||||
ForwardErrorCorrection::ReceivedPacketList received_packets_;
|
||||
ForwardErrorCorrection::RecoveredPacketList recovered_packets_;
|
||||
|
||||
int media_loss_mask_[ForwardErrorCorrection::kMaxMediaPackets];
|
||||
int fec_loss_mask_[ForwardErrorCorrection::kMaxMediaPackets];
|
||||
int media_loss_mask_[kUlpfecMaxMediaPackets];
|
||||
int fec_loss_mask_[kUlpfecMaxMediaPackets];
|
||||
};
|
||||
|
||||
// Define gTest typed test to loop over both ULPFEC and FlexFEC.
|
||||
// Since the tests now are parameterized, we need to access
|
||||
// member variables using |this|, thereby enforcing runtime
|
||||
// resolution.
|
||||
using FecTypes = Types<ForwardErrorCorrection>;
|
||||
using FecTypes = Types<UlpfecForwardErrorCorrection>;
|
||||
TYPED_TEST_CASE(RtpFecTest, FecTypes);
|
||||
|
||||
TYPED_TEST(RtpFecTest, FecRecoveryNoLoss) {
|
||||
|
||||
130
webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc
Normal file
130
webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.cc
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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 "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
// Maximum number of media packets that can be protected in one batch.
|
||||
constexpr size_t kMaxMediaPackets = 48;
|
||||
|
||||
// Maximum number of FEC packets stored inside ForwardErrorCorrection.
|
||||
constexpr size_t kMaxFecPackets = kMaxMediaPackets;
|
||||
|
||||
// FEC Level 0 header size in bytes.
|
||||
constexpr size_t kFecLevel0HeaderSize = 10;
|
||||
|
||||
// FEC Level 1 (ULP) header size in bytes (L bit is set).
|
||||
constexpr size_t kFecLevel1HeaderSizeLBitSet = 2 + kUlpfecPacketMaskSizeLBitSet;
|
||||
|
||||
// FEC Level 1 (ULP) header size in bytes (L bit is cleared).
|
||||
constexpr size_t kFecLevel1HeaderSizeLBitClear =
|
||||
2 + kUlpfecPacketMaskSizeLBitClear;
|
||||
|
||||
constexpr size_t kPacketMaskOffset = kFecLevel0HeaderSize + 2;
|
||||
|
||||
size_t UlpfecHeaderSize(size_t packet_mask_size) {
|
||||
RTC_DCHECK_LE(packet_mask_size, kUlpfecPacketMaskSizeLBitSet);
|
||||
if (packet_mask_size <= kUlpfecPacketMaskSizeLBitClear) {
|
||||
return kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitClear;
|
||||
} else {
|
||||
return kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitSet;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UlpfecHeaderReader::UlpfecHeaderReader()
|
||||
: FecHeaderReader(kMaxMediaPackets, kMaxFecPackets) {}
|
||||
|
||||
UlpfecHeaderReader::~UlpfecHeaderReader() = default;
|
||||
|
||||
bool UlpfecHeaderReader::ReadFecHeader(
|
||||
ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const {
|
||||
bool l_bit = (fec_packet->pkt->data[0] & 0x40) != 0u;
|
||||
size_t packet_mask_size =
|
||||
l_bit ? kUlpfecPacketMaskSizeLBitSet : kUlpfecPacketMaskSizeLBitClear;
|
||||
fec_packet->fec_header_size = UlpfecHeaderSize(packet_mask_size);
|
||||
uint16_t seq_num_base =
|
||||
ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[2]);
|
||||
fec_packet->protected_ssrc = fec_packet->ssrc; // Due to RED.
|
||||
fec_packet->seq_num_base = seq_num_base;
|
||||
fec_packet->packet_mask_offset = kPacketMaskOffset;
|
||||
fec_packet->packet_mask_size = packet_mask_size;
|
||||
fec_packet->protection_length =
|
||||
ByteReader<uint16_t>::ReadBigEndian(&fec_packet->pkt->data[10]);
|
||||
|
||||
// Store length recovery field in temporary location in header.
|
||||
// This makes the header "compatible" with the corresponding
|
||||
// FlexFEC location of the length recovery field, thus simplifying
|
||||
// the XORing operations.
|
||||
memcpy(&fec_packet->pkt->data[2], &fec_packet->pkt->data[8], 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UlpfecHeaderWriter::UlpfecHeaderWriter()
|
||||
: FecHeaderWriter(kMaxMediaPackets,
|
||||
kMaxFecPackets,
|
||||
kFecLevel0HeaderSize + kFecLevel1HeaderSizeLBitSet) {}
|
||||
|
||||
UlpfecHeaderWriter::~UlpfecHeaderWriter() = default;
|
||||
|
||||
// TODO(brandtr): Consider updating this implementation (which actually
|
||||
// returns a bound on the sequence number spread), if logic is added to
|
||||
// UlpfecHeaderWriter::FinalizeFecHeader to truncate packet masks which end
|
||||
// in a string of zeroes. (Similar to how it is done in the FlexFEC case.)
|
||||
size_t UlpfecHeaderWriter::MinPacketMaskSize(const uint8_t* packet_mask,
|
||||
size_t packet_mask_size) const {
|
||||
return packet_mask_size;
|
||||
}
|
||||
|
||||
size_t UlpfecHeaderWriter::FecHeaderSize(size_t packet_mask_size) const {
|
||||
return UlpfecHeaderSize(packet_mask_size);
|
||||
}
|
||||
|
||||
void UlpfecHeaderWriter::FinalizeFecHeader(
|
||||
uint16_t seq_num_base,
|
||||
const uint8_t* packet_mask,
|
||||
size_t packet_mask_size,
|
||||
ForwardErrorCorrection::Packet* fec_packet) const {
|
||||
// Set E bit to zero.
|
||||
fec_packet->data[0] &= 0x7f;
|
||||
// Set L bit based on packet mask size. (Note that the packet mask
|
||||
// can only take on two discrete values.)
|
||||
bool l_bit = (packet_mask_size == kUlpfecPacketMaskSizeLBitSet);
|
||||
if (l_bit) {
|
||||
fec_packet->data[0] |= 0x40; // Set the L bit.
|
||||
} else {
|
||||
RTC_DCHECK_EQ(packet_mask_size, kUlpfecPacketMaskSizeLBitClear);
|
||||
fec_packet->data[0] &= 0xbf; // Clear the L bit.
|
||||
}
|
||||
// Copy length recovery field from temporary location.
|
||||
memcpy(&fec_packet->data[8], &fec_packet->data[2], 2);
|
||||
// Write sequence number base.
|
||||
ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[2], seq_num_base);
|
||||
// Protection length is set to entire packet. (This is not
|
||||
// required in general.)
|
||||
const size_t fec_header_size = FecHeaderSize(packet_mask_size);
|
||||
ByteWriter<uint16_t>::WriteBigEndian(&fec_packet->data[10],
|
||||
fec_packet->length - fec_header_size);
|
||||
// Copy the packet mask.
|
||||
memcpy(&fec_packet->data[12], packet_mask, packet_mask_size);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
66
webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h
Normal file
66
webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_
|
||||
#define WEBRTC_MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_
|
||||
|
||||
#include "webrtc/base/basictypes.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// FEC Level 0 Header, 10 bytes.
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |E|L|P|X| CC |M| PT recovery | SN base |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | TS recovery |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | length recovery |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// FEC Level 1 Header, 4 bytes (L = 0) or 8 bytes (L = 1).
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Protection Length | mask |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | mask cont. (present only when L = 1) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class UlpfecHeaderReader : public FecHeaderReader {
|
||||
public:
|
||||
UlpfecHeaderReader();
|
||||
~UlpfecHeaderReader() override;
|
||||
|
||||
bool ReadFecHeader(
|
||||
ForwardErrorCorrection::ReceivedFecPacket* fec_packet) const override;
|
||||
};
|
||||
|
||||
class UlpfecHeaderWriter : public FecHeaderWriter {
|
||||
public:
|
||||
UlpfecHeaderWriter();
|
||||
~UlpfecHeaderWriter() override;
|
||||
|
||||
size_t MinPacketMaskSize(const uint8_t* packet_mask,
|
||||
size_t packet_mask_size) const override;
|
||||
|
||||
size_t FecHeaderSize(size_t packet_mask_row_size) const override;
|
||||
|
||||
void FinalizeFecHeader(
|
||||
uint16_t seq_num_base,
|
||||
const uint8_t* packet_mask,
|
||||
size_t packet_mask_size,
|
||||
ForwardErrorCorrection::Packet* fec_packet) const override;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_RTP_RTCP_SOURCE_ULPFEC_HEADER_READER_WRITER_H_
|
||||
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (c) 2016 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 <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "webrtc/base/basictypes.h"
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/base/random.h"
|
||||
#include "webrtc/base/scoped_ref_ptr.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
|
||||
#include "webrtc/modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
namespace {
|
||||
|
||||
using Packet = ::webrtc::ForwardErrorCorrection::Packet;
|
||||
using ReceivedFecPacket = ::webrtc::ForwardErrorCorrection::ReceivedFecPacket;
|
||||
|
||||
constexpr uint32_t kMediaSsrc = 1254983;
|
||||
constexpr uint16_t kMediaStartSeqNum = 825;
|
||||
constexpr size_t kMediaPacketLength = 1234;
|
||||
|
||||
constexpr size_t kUlpfecHeaderSizeLBitClear = 14;
|
||||
constexpr size_t kUlpfecHeaderSizeLBitSet = 18;
|
||||
constexpr size_t kUlpfecPacketMaskOffset = 12;
|
||||
|
||||
std::unique_ptr<uint8_t[]> GeneratePacketMask(size_t packet_mask_size,
|
||||
uint64_t seed) {
|
||||
Random random(seed);
|
||||
std::unique_ptr<uint8_t[]> packet_mask(new uint8_t[packet_mask_size]);
|
||||
for (size_t i = 0; i < packet_mask_size; ++i) {
|
||||
packet_mask[i] = random.Rand<uint8_t>();
|
||||
}
|
||||
return packet_mask;
|
||||
}
|
||||
|
||||
std::unique_ptr<Packet> WriteHeader(const uint8_t* packet_mask,
|
||||
size_t packet_mask_size) {
|
||||
UlpfecHeaderWriter writer;
|
||||
std::unique_ptr<Packet> written_packet(new Packet());
|
||||
written_packet->length = kMediaPacketLength;
|
||||
for (size_t i = 0; i < written_packet->length; ++i) {
|
||||
written_packet->data[i] = i; // Actual content doesn't matter.
|
||||
}
|
||||
writer.FinalizeFecHeader(kMediaStartSeqNum, packet_mask, packet_mask_size,
|
||||
written_packet.get());
|
||||
return written_packet;
|
||||
}
|
||||
|
||||
std::unique_ptr<ReceivedFecPacket> ReadHeader(const Packet& written_packet) {
|
||||
UlpfecHeaderReader reader;
|
||||
std::unique_ptr<ReceivedFecPacket> read_packet(new ReceivedFecPacket());
|
||||
read_packet->ssrc = kMediaSsrc;
|
||||
read_packet->pkt = rtc::scoped_refptr<Packet>(new Packet());
|
||||
memcpy(read_packet->pkt->data, written_packet.data, written_packet.length);
|
||||
read_packet->pkt->length = written_packet.length;
|
||||
EXPECT_TRUE(reader.ReadFecHeader(read_packet.get()));
|
||||
return read_packet;
|
||||
}
|
||||
|
||||
void VerifyHeaders(size_t expected_fec_header_size,
|
||||
const uint8_t* expected_packet_mask,
|
||||
size_t expected_packet_mask_size,
|
||||
const Packet& written_packet,
|
||||
const ReceivedFecPacket& read_packet) {
|
||||
EXPECT_EQ(kMediaSsrc, read_packet.ssrc);
|
||||
EXPECT_EQ(expected_fec_header_size, read_packet.fec_header_size);
|
||||
EXPECT_EQ(kMediaSsrc, read_packet.protected_ssrc);
|
||||
EXPECT_EQ(kMediaStartSeqNum, read_packet.seq_num_base);
|
||||
EXPECT_EQ(kUlpfecPacketMaskOffset, read_packet.packet_mask_offset);
|
||||
ASSERT_EQ(expected_packet_mask_size, read_packet.packet_mask_size);
|
||||
EXPECT_EQ(written_packet.length - expected_fec_header_size,
|
||||
read_packet.protection_length);
|
||||
EXPECT_EQ(0, memcmp(expected_packet_mask,
|
||||
&read_packet.pkt->data[read_packet.packet_mask_offset],
|
||||
read_packet.packet_mask_size));
|
||||
// Verify that the call to ReadFecHeader did not tamper with the payload.
|
||||
EXPECT_EQ(0, memcmp(&written_packet.data[expected_fec_header_size],
|
||||
&read_packet.pkt->data[expected_fec_header_size],
|
||||
written_packet.length - expected_fec_header_size));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UlpfecHeaderReaderTest, ReadsSmallHeader) {
|
||||
const uint8_t packet[] = {
|
||||
0x00, 0x12, 0xab, 0xcd, // L bit clear, "random" payload type and SN base
|
||||
0x12, 0x34, 0x56, 0x78, // "random" TS recovery
|
||||
0xab, 0xcd, 0x11, 0x22, // "random" length recovery and protection length
|
||||
0x33, 0x44, // "random" packet mask
|
||||
0x00, 0x00, 0x00, 0x00 // payload
|
||||
};
|
||||
const size_t packet_length = sizeof(packet);
|
||||
ReceivedFecPacket read_packet;
|
||||
read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet());
|
||||
memcpy(read_packet.pkt->data, packet, packet_length);
|
||||
read_packet.pkt->length = packet_length;
|
||||
|
||||
UlpfecHeaderReader reader;
|
||||
EXPECT_TRUE(reader.ReadFecHeader(&read_packet));
|
||||
|
||||
EXPECT_EQ(14U, read_packet.fec_header_size);
|
||||
EXPECT_EQ(0xabcdU, read_packet.seq_num_base);
|
||||
EXPECT_EQ(12U, read_packet.packet_mask_offset);
|
||||
EXPECT_EQ(2U, read_packet.packet_mask_size);
|
||||
EXPECT_EQ(0x1122U, read_packet.protection_length);
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderReaderTest, ReadsLargeHeader) {
|
||||
const uint8_t packet[] = {
|
||||
0x40, 0x12, 0xab, 0xcd, // L bit set, "random" payload type and SN base
|
||||
0x12, 0x34, 0x56, 0x78, // "random" TS recovery
|
||||
0xab, 0xcd, 0x11, 0x22, // "random" length recovery and protection length
|
||||
0x33, 0x44, 0x55, 0x66, // "random" packet mask
|
||||
0x77, 0x88, //
|
||||
0x00, 0x00, 0x00, 0x00 // payload
|
||||
};
|
||||
const size_t packet_length = sizeof(packet);
|
||||
ReceivedFecPacket read_packet;
|
||||
read_packet.pkt = rtc::scoped_refptr<Packet>(new Packet());
|
||||
memcpy(read_packet.pkt->data, packet, packet_length);
|
||||
read_packet.pkt->length = packet_length;
|
||||
|
||||
UlpfecHeaderReader reader;
|
||||
EXPECT_TRUE(reader.ReadFecHeader(&read_packet));
|
||||
|
||||
EXPECT_EQ(18U, read_packet.fec_header_size);
|
||||
EXPECT_EQ(0xabcdU, read_packet.seq_num_base);
|
||||
EXPECT_EQ(12U, read_packet.packet_mask_offset);
|
||||
EXPECT_EQ(6U, read_packet.packet_mask_size);
|
||||
EXPECT_EQ(0x1122U, read_packet.protection_length);
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderWriterTest, FinalizesSmallHeader) {
|
||||
const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear;
|
||||
auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
|
||||
Packet written_packet;
|
||||
written_packet.length = kMediaPacketLength;
|
||||
for (size_t i = 0; i < written_packet.length; ++i) {
|
||||
written_packet.data[i] = i;
|
||||
}
|
||||
|
||||
UlpfecHeaderWriter writer;
|
||||
writer.FinalizeFecHeader(kMediaStartSeqNum, packet_mask.get(),
|
||||
packet_mask_size, &written_packet);
|
||||
|
||||
const uint8_t* packet = written_packet.data;
|
||||
EXPECT_EQ(0x00, packet[0] & 0x80); // E bit.
|
||||
EXPECT_EQ(0x00, packet[0] & 0x40); // L bit.
|
||||
EXPECT_EQ(kMediaStartSeqNum, ByteReader<uint16_t>::ReadBigEndian(packet + 2));
|
||||
EXPECT_EQ(
|
||||
static_cast<uint16_t>(kMediaPacketLength - kUlpfecHeaderSizeLBitClear),
|
||||
ByteReader<uint16_t>::ReadBigEndian(packet + 10));
|
||||
EXPECT_EQ(0, memcmp(packet + kUlpfecPacketMaskOffset, packet_mask.get(),
|
||||
packet_mask_size));
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderWriterTest, FinalizesLargeHeader) {
|
||||
const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet;
|
||||
auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
|
||||
Packet written_packet;
|
||||
written_packet.length = kMediaPacketLength;
|
||||
for (size_t i = 0; i < written_packet.length; ++i) {
|
||||
written_packet.data[i] = i;
|
||||
}
|
||||
|
||||
UlpfecHeaderWriter writer;
|
||||
writer.FinalizeFecHeader(kMediaStartSeqNum, packet_mask.get(),
|
||||
packet_mask_size, &written_packet);
|
||||
|
||||
const uint8_t* packet = written_packet.data;
|
||||
EXPECT_EQ(0x00, packet[0] & 0x80); // E bit.
|
||||
EXPECT_EQ(0x40, packet[0] & 0x40); // L bit.
|
||||
EXPECT_EQ(kMediaStartSeqNum, ByteReader<uint16_t>::ReadBigEndian(packet + 2));
|
||||
EXPECT_EQ(
|
||||
static_cast<uint16_t>(kMediaPacketLength - kUlpfecHeaderSizeLBitSet),
|
||||
ByteReader<uint16_t>::ReadBigEndian(packet + 10));
|
||||
EXPECT_EQ(0, memcmp(packet + kUlpfecPacketMaskOffset, packet_mask.get(),
|
||||
packet_mask_size));
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderWriterTest, CalculateSmallHeaderSize) {
|
||||
const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear;
|
||||
auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
|
||||
|
||||
UlpfecHeaderWriter writer;
|
||||
size_t min_packet_mask_size =
|
||||
writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size);
|
||||
|
||||
EXPECT_EQ(kUlpfecPacketMaskSizeLBitClear, min_packet_mask_size);
|
||||
EXPECT_EQ(kUlpfecHeaderSizeLBitClear,
|
||||
writer.FecHeaderSize(min_packet_mask_size));
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderWriterTest, CalculateLargeHeaderSize) {
|
||||
const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet;
|
||||
auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
|
||||
|
||||
UlpfecHeaderWriter writer;
|
||||
size_t min_packet_mask_size =
|
||||
writer.MinPacketMaskSize(packet_mask.get(), packet_mask_size);
|
||||
|
||||
EXPECT_EQ(kUlpfecPacketMaskSizeLBitSet, min_packet_mask_size);
|
||||
EXPECT_EQ(kUlpfecHeaderSizeLBitSet,
|
||||
writer.FecHeaderSize(min_packet_mask_size));
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderReaderWriterTest, WriteAndReadSmallHeader) {
|
||||
const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitClear;
|
||||
auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
|
||||
|
||||
auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size);
|
||||
auto read_packet = ReadHeader(*written_packet);
|
||||
|
||||
VerifyHeaders(kUlpfecHeaderSizeLBitClear, packet_mask.get(), packet_mask_size,
|
||||
*written_packet, *read_packet);
|
||||
}
|
||||
|
||||
TEST(UlpfecHeaderReaderWriterTest, WriteAndReadLargeHeader) {
|
||||
const size_t packet_mask_size = kUlpfecPacketMaskSizeLBitSet;
|
||||
auto packet_mask = GeneratePacketMask(packet_mask_size, 0xabcd);
|
||||
|
||||
auto written_packet = WriteHeader(packet_mask.get(), packet_mask_size);
|
||||
auto read_packet = ReadHeader(*written_packet);
|
||||
|
||||
VerifyHeaders(kUlpfecHeaderSizeLBitSet, packet_mask.get(), packet_mask_size,
|
||||
*written_packet, *read_packet);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
@ -107,7 +107,8 @@ TEST(FecTest, MAYBE_FecTest) {
|
||||
ASSERT_EQ(12, kMaxMediaPackets[1]) << "Max media packets for bursty mode not "
|
||||
<< "equal to 12.";
|
||||
|
||||
ForwardErrorCorrection fec;
|
||||
std::unique_ptr<ForwardErrorCorrection> fec =
|
||||
ForwardErrorCorrection::CreateUlpfec();
|
||||
ForwardErrorCorrection::PacketList media_packet_list;
|
||||
std::list<ForwardErrorCorrection::Packet*> fec_packet_list;
|
||||
ForwardErrorCorrection::ReceivedPacketList to_decode_list;
|
||||
@ -238,7 +239,7 @@ TEST(FecTest, MAYBE_FecTest) {
|
||||
new ForwardErrorCorrection::Packet());
|
||||
const uint32_t kMinPacketSize = 12;
|
||||
const uint32_t kMaxPacketSize = static_cast<uint32_t>(
|
||||
IP_PACKET_SIZE - 12 - 28 - fec.MaxPacketOverhead());
|
||||
IP_PACKET_SIZE - 12 - 28 - fec->MaxPacketOverhead());
|
||||
media_packet->length = random.Rand(kMinPacketSize,
|
||||
kMaxPacketSize);
|
||||
|
||||
@ -277,9 +278,9 @@ TEST(FecTest, MAYBE_FecTest) {
|
||||
}
|
||||
media_packet_list.back()->data[1] |= 0x80;
|
||||
|
||||
ASSERT_EQ(0, fec.EncodeFec(media_packet_list, protection_factor,
|
||||
num_imp_packets, kUseUnequalProtection,
|
||||
fec_mask_type, &fec_packet_list))
|
||||
ASSERT_EQ(0, fec->EncodeFec(media_packet_list, protection_factor,
|
||||
num_imp_packets, kUseUnequalProtection,
|
||||
fec_mask_type, &fec_packet_list))
|
||||
<< "EncodeFec() failed";
|
||||
|
||||
ASSERT_EQ(num_fec_packets, fec_packet_list.size())
|
||||
@ -393,7 +394,7 @@ TEST(FecTest, MAYBE_FecTest) {
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(0,
|
||||
fec.DecodeFec(&to_decode_list, &recovered_packet_list))
|
||||
fec->DecodeFec(&to_decode_list, &recovered_packet_list))
|
||||
<< "DecodeFec() failed";
|
||||
ASSERT_TRUE(to_decode_list.empty())
|
||||
<< "Received packet list is not empty.";
|
||||
@ -421,7 +422,7 @@ TEST(FecTest, MAYBE_FecTest) {
|
||||
}
|
||||
++media_packet_idx;
|
||||
}
|
||||
fec.ResetState(&recovered_packet_list);
|
||||
fec->ResetState(&recovered_packet_list);
|
||||
ASSERT_TRUE(recovered_packet_list.empty())
|
||||
<< "Excessive number of recovered packets.\t size is: "
|
||||
<< recovered_packet_list.size();
|
||||
@ -447,7 +448,7 @@ TEST(FecTest, MAYBE_FecTest) {
|
||||
} // loop over mask types
|
||||
|
||||
// Have DecodeFec clear the recovered packet list.
|
||||
fec.ResetState(&recovered_packet_list);
|
||||
fec->ResetState(&recovered_packet_list);
|
||||
ASSERT_TRUE(recovered_packet_list.empty())
|
||||
<< "Recovered packet list is not empty";
|
||||
}
|
||||
|
||||
@ -730,24 +730,24 @@ class FecPacketMaskMetricsTest : public ::testing::Test {
|
||||
int code_index = 0;
|
||||
// Maximum number of media packets allowed for the mask type.
|
||||
const int packet_mask_max = kMaxMediaPackets[fec_mask_type];
|
||||
uint8_t* packet_mask = new uint8_t[packet_mask_max * kMaskSizeLBitSet];
|
||||
std::unique_ptr<uint8_t[]> packet_mask(
|
||||
new uint8_t[packet_mask_max * kUlpfecMaxPacketMaskSize]);
|
||||
// Loop through codes up to |kMaxMediaPacketsTest|.
|
||||
for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
|
||||
num_media_packets++) {
|
||||
const int mask_bytes_fec_packet =
|
||||
(num_media_packets > 16) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
|
||||
static_cast<int>(internal::PacketMaskSize(num_media_packets));
|
||||
internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
|
||||
for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
|
||||
num_fec_packets++) {
|
||||
memset(packet_mask, 0, num_media_packets * mask_bytes_fec_packet);
|
||||
memcpy(packet_mask, mask_table.fec_packet_mask_table()
|
||||
[num_media_packets - 1][num_fec_packets - 1],
|
||||
memset(packet_mask.get(), 0, num_media_packets * mask_bytes_fec_packet);
|
||||
memcpy(packet_mask.get(),
|
||||
mask_table.fec_packet_mask_table()[num_media_packets - 1]
|
||||
[num_fec_packets - 1],
|
||||
num_fec_packets * mask_bytes_fec_packet);
|
||||
// Convert to bit mask.
|
||||
GetPacketMaskConvertToBitMask(packet_mask,
|
||||
num_media_packets,
|
||||
num_fec_packets,
|
||||
mask_bytes_fec_packet,
|
||||
GetPacketMaskConvertToBitMask(packet_mask.get(), num_media_packets,
|
||||
num_fec_packets, mask_bytes_fec_packet,
|
||||
code_type);
|
||||
if (RejectInvalidMasks(num_media_packets, num_fec_packets) < 0) {
|
||||
return -1;
|
||||
@ -759,7 +759,6 @@ class FecPacketMaskMetricsTest : public ::testing::Test {
|
||||
}
|
||||
}
|
||||
assert(code_index == kNumberCodes);
|
||||
delete [] packet_mask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user