mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-17 15:47:53 +01:00

Add implementation of RTC_DCHECK_NOTREACHED equal to the RTC_NOTREACHED. The new macros will replace the old one when old one's usage will be removed. The idea of the renaming to provide a clear signal that this is debug build only macros and will be stripped in the production build. Bug: webrtc:9065 Change-Id: I4c35d8b03e74a4b3fd1ae75dba2f9c05643101db Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/237802 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Artem Titov <titovartem@webrtc.org> Cr-Commit-Position: refs/heads/main@{#35348}
1060 lines
47 KiB
C++
1060 lines
47 KiB
C++
/*
|
|
* Copyright (c) 2012 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.
|
|
*/
|
|
|
|
/*
|
|
* The purpose of this test is to compute metrics to characterize the properties
|
|
* and efficiency of the packets masks used in the generic XOR FEC code.
|
|
*
|
|
* The metrics measure the efficiency (recovery potential or residual loss) of
|
|
* the FEC code, under various statistical loss models for the packet/symbol
|
|
* loss events. Various constraints on the behavior of these metrics are
|
|
* verified, and compared to the reference RS (Reed-Solomon) code. This serves
|
|
* in some way as a basic check/benchmark for the packet masks.
|
|
*
|
|
* By an FEC code, we mean an erasure packet/symbol code, characterized by:
|
|
* (1) The code size parameters (k,m), where k = number of source/media packets,
|
|
* and m = number of FEC packets,
|
|
* (2) The code type: XOR or RS.
|
|
* In the case of XOR, the residual loss is determined via the set of packet
|
|
* masks (generator matrix). In the case of RS, the residual loss is determined
|
|
* directly from the MDS (maximum distance separable) property of RS.
|
|
*
|
|
* Currently two classes of packets masks are available (random type and bursty
|
|
* type), so three codes are considered below: RS, XOR-random, and XOR-bursty.
|
|
* The bursty class is defined up to k=12, so (k=12,m=12) is largest code size
|
|
* considered in this test.
|
|
*
|
|
* The XOR codes are defined via the RFC 5109 and correspond to the class of
|
|
* LDGM (low density generator matrix) codes, which is a subset of the LDPC
|
|
* (low density parity check) codes. Future implementation will consider
|
|
* extending our XOR codes to include LDPC codes, which explicitly include
|
|
* protection of FEC packets.
|
|
*
|
|
* The type of packet/symbol loss models considered in this test are:
|
|
* (1) Random loss: Bernoulli process, characterized by the average loss rate.
|
|
* (2) Bursty loss: Markov chain (Gilbert-Elliot model), characterized by two
|
|
* parameters: average loss rate and average burst length.
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <memory>
|
|
|
|
#include "modules/rtp_rtcp/source/forward_error_correction_internal.h"
|
|
#include "modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h"
|
|
#include "test/gtest.h"
|
|
#include "test/testsupport/file_utils.h"
|
|
|
|
namespace webrtc {
|
|
|
|
// Maximum number of media packets allows for XOR (RFC 5109) code.
|
|
enum { kMaxNumberMediaPackets = 48 };
|
|
|
|
// Maximum number of media packets allowed for each mask type.
|
|
const uint16_t kMaxMediaPackets[] = {kMaxNumberMediaPackets, 12};
|
|
|
|
// Maximum gap size for characterizing the consecutiveness of the loss.
|
|
const int kMaxGapSize = 2 * kMaxMediaPacketsTest;
|
|
|
|
// Number of gap levels written to file/output.
|
|
const int kGapSizeOutput = 5;
|
|
|
|
// Maximum number of states for characterizing the residual loss distribution.
|
|
const int kNumStatesDistribution = 2 * kMaxMediaPacketsTest * kMaxGapSize + 1;
|
|
|
|
// The code type.
|
|
enum CodeType {
|
|
xor_random_code, // XOR with random mask type.
|
|
xor_bursty_code, // XOR with bursty mask type.
|
|
rs_code // Reed_solomon.
|
|
};
|
|
|
|
// The code size parameters.
|
|
struct CodeSizeParams {
|
|
int num_media_packets;
|
|
int num_fec_packets;
|
|
// Protection level: num_fec_packets / (num_media_packets + num_fec_packets).
|
|
float protection_level;
|
|
// Number of loss configurations, for a given loss number and gap number.
|
|
// The gap number refers to the maximum gap/hole of a loss configuration
|
|
// (used to measure the "consecutiveness" of the loss).
|
|
int configuration_density[kNumStatesDistribution];
|
|
};
|
|
|
|
// The type of loss models.
|
|
enum LossModelType { kRandomLossModel, kBurstyLossModel };
|
|
|
|
struct LossModel {
|
|
LossModelType loss_type;
|
|
float average_loss_rate;
|
|
float average_burst_length;
|
|
};
|
|
|
|
// Average loss rates.
|
|
const float kAverageLossRate[] = {0.025f, 0.05f, 0.1f, 0.25f};
|
|
|
|
// Average burst lengths. The case of |kAverageBurstLength = 1.0| refers to
|
|
// the random model. Note that for the random (Bernoulli) model, the average
|
|
// burst length is determined by the average loss rate, i.e.,
|
|
// AverageBurstLength = 1 / (1 - AverageLossRate) for random model.
|
|
const float kAverageBurstLength[] = {1.0f, 2.0f, 4.0f};
|
|
|
|
// Total number of loss models: For each burst length case, there are
|
|
// a number of models corresponding to the loss rates.
|
|
const int kNumLossModels =
|
|
(sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength)) *
|
|
(sizeof(kAverageLossRate) / sizeof(*kAverageLossRate));
|
|
|
|
// Thresholds on the average loss rate of the packet loss model, below which
|
|
// certain properties of the codes are expected.
|
|
float loss_rate_upper_threshold = 0.20f;
|
|
float loss_rate_lower_threshold = 0.025f;
|
|
|
|
// Set of thresholds on the expected average recovery rate, for each code type.
|
|
// These are global thresholds for now; in future version we may condition them
|
|
// on the code length/size and protection level.
|
|
const float kRecoveryRateXorRandom[3] = {0.94f, 0.50f, 0.19f};
|
|
const float kRecoveryRateXorBursty[3] = {0.90f, 0.54f, 0.22f};
|
|
|
|
// Metrics for a given FEC code; each code is defined by the code type
|
|
// (RS, XOR-random/bursty), and the code size parameters (k,m), where
|
|
// k = num_media_packets, m = num_fec_packets.
|
|
struct MetricsFecCode {
|
|
// The average and variance of the residual loss, as a function of the
|
|
// packet/symbol loss model. The average/variance is computed by averaging
|
|
// over all loss configurations wrt the loss probability given by the
|
|
// underlying loss model.
|
|
double average_residual_loss[kNumLossModels];
|
|
double variance_residual_loss[kNumLossModels];
|
|
// The residual loss, as a function of the loss number and the gap number of
|
|
// the loss configurations. The gap number refers to the maximum gap/hole of
|
|
// a loss configuration (used to measure the "consecutiveness" of the loss).
|
|
double residual_loss_per_loss_gap[kNumStatesDistribution];
|
|
// The recovery rate as a function of the loss number.
|
|
double recovery_rate_per_loss[2 * kMaxMediaPacketsTest + 1];
|
|
};
|
|
|
|
MetricsFecCode kMetricsXorRandom[kNumberCodes];
|
|
MetricsFecCode kMetricsXorBursty[kNumberCodes];
|
|
MetricsFecCode kMetricsReedSolomon[kNumberCodes];
|
|
|
|
class FecPacketMaskMetricsTest : public ::testing::Test {
|
|
protected:
|
|
FecPacketMaskMetricsTest() {}
|
|
|
|
int max_num_codes_;
|
|
LossModel loss_model_[kNumLossModels];
|
|
CodeSizeParams code_params_[kNumberCodes];
|
|
|
|
uint8_t fec_packet_masks_[kMaxNumberMediaPackets][kMaxNumberMediaPackets];
|
|
FILE* fp_mask_;
|
|
|
|
// Measure of the gap of the loss for configuration given by `state`.
|
|
// This is to measure degree of consecutiveness for the loss configuration.
|
|
// Useful if the packets are sent out in order of sequence numbers and there
|
|
// is little/no re-ordering during transmission.
|
|
int GapLoss(int tot_num_packets, uint8_t* state) {
|
|
int max_gap_loss = 0;
|
|
// Find the first loss.
|
|
int first_loss = 0;
|
|
for (int i = 0; i < tot_num_packets; i++) {
|
|
if (state[i] == 1) {
|
|
first_loss = i;
|
|
break;
|
|
}
|
|
}
|
|
int prev_loss = first_loss;
|
|
for (int i = first_loss + 1; i < tot_num_packets; i++) {
|
|
if (state[i] == 1) { // Lost state.
|
|
int gap_loss = (i - prev_loss) - 1;
|
|
if (gap_loss > max_gap_loss) {
|
|
max_gap_loss = gap_loss;
|
|
}
|
|
prev_loss = i;
|
|
}
|
|
}
|
|
return max_gap_loss;
|
|
}
|
|
|
|
// Returns the number of recovered media packets for the XOR code, given the
|
|
// packet mask `fec_packet_masks_`, for the loss state/configuration given by
|
|
// `state`.
|
|
int RecoveredMediaPackets(int num_media_packets,
|
|
int num_fec_packets,
|
|
uint8_t* state) {
|
|
std::unique_ptr<uint8_t[]> state_tmp(
|
|
new uint8_t[num_media_packets + num_fec_packets]);
|
|
memcpy(state_tmp.get(), state, num_media_packets + num_fec_packets);
|
|
int num_recovered_packets = 0;
|
|
bool loop_again = true;
|
|
while (loop_again) {
|
|
loop_again = false;
|
|
bool recovered_new_packet = false;
|
|
// Check if we can recover anything: loop over all possible FEC packets.
|
|
for (int i = 0; i < num_fec_packets; i++) {
|
|
if (state_tmp[i + num_media_packets] == 0) {
|
|
// We have this FEC packet.
|
|
int num_packets_in_mask = 0;
|
|
int num_received_packets_in_mask = 0;
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
if (fec_packet_masks_[i][j] == 1) {
|
|
num_packets_in_mask++;
|
|
if (state_tmp[j] == 0) {
|
|
num_received_packets_in_mask++;
|
|
}
|
|
}
|
|
}
|
|
if ((num_packets_in_mask - 1) == num_received_packets_in_mask) {
|
|
// We can recover the missing media packet for this FEC packet.
|
|
num_recovered_packets++;
|
|
recovered_new_packet = true;
|
|
int jsel = -1;
|
|
int check_num_recovered = 0;
|
|
// Update the state with newly recovered media packet.
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
if (fec_packet_masks_[i][j] == 1 && state_tmp[j] == 1) {
|
|
// This is the lost media packet we will recover.
|
|
jsel = j;
|
|
check_num_recovered++;
|
|
}
|
|
}
|
|
// Check that we can only recover 1 packet.
|
|
RTC_DCHECK_EQ(check_num_recovered, 1);
|
|
// Update the state with the newly recovered media packet.
|
|
state_tmp[jsel] = 0;
|
|
}
|
|
}
|
|
} // Go to the next FEC packet in the loop.
|
|
// If we have recovered at least one new packet in this FEC loop,
|
|
// go through loop again, otherwise we leave loop.
|
|
if (recovered_new_packet) {
|
|
loop_again = true;
|
|
}
|
|
}
|
|
return num_recovered_packets;
|
|
}
|
|
|
|
// Compute the probability of occurence of the loss state/configuration,
|
|
// given by `state`, for all the loss models considered in this test.
|
|
void ComputeProbabilityWeight(double* prob_weight,
|
|
uint8_t* state,
|
|
int tot_num_packets) {
|
|
// Loop over the loss models.
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
double loss_rate = static_cast<double>(loss_model_[k].average_loss_rate);
|
|
double burst_length =
|
|
static_cast<double>(loss_model_[k].average_burst_length);
|
|
double result = 1.0;
|
|
if (loss_model_[k].loss_type == kRandomLossModel) {
|
|
for (int i = 0; i < tot_num_packets; i++) {
|
|
if (state[i] == 0) {
|
|
result *= (1.0 - loss_rate);
|
|
} else {
|
|
result *= loss_rate;
|
|
}
|
|
}
|
|
} else { // Gilbert-Elliot model for burst model.
|
|
RTC_DCHECK_EQ(loss_model_[k].loss_type, kBurstyLossModel);
|
|
// Transition probabilities: from previous to current state.
|
|
// Prob. of previous = lost --> current = received.
|
|
double prob10 = 1.0 / burst_length;
|
|
// Prob. of previous = lost --> currrent = lost.
|
|
double prob11 = 1.0 - prob10;
|
|
// Prob. of previous = received --> current = lost.
|
|
double prob01 = prob10 * (loss_rate / (1.0 - loss_rate));
|
|
// Prob. of previous = received --> current = received.
|
|
double prob00 = 1.0 - prob01;
|
|
|
|
// Use stationary probability for first state/packet.
|
|
if (state[0] == 0) { // Received
|
|
result = (1.0 - loss_rate);
|
|
} else { // Lost
|
|
result = loss_rate;
|
|
}
|
|
|
|
// Subsequent states: use transition probabilities.
|
|
for (int i = 1; i < tot_num_packets; i++) {
|
|
// Current state is received
|
|
if (state[i] == 0) {
|
|
if (state[i - 1] == 0) {
|
|
result *= prob00; // Previous received, current received.
|
|
} else {
|
|
result *= prob10; // Previous lost, current received.
|
|
}
|
|
} else { // Current state is lost
|
|
if (state[i - 1] == 0) {
|
|
result *= prob01; // Previous received, current lost.
|
|
} else {
|
|
result *= prob11; // Previous lost, current lost.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
prob_weight[k] = result;
|
|
}
|
|
}
|
|
|
|
void CopyMetrics(MetricsFecCode* metrics_output,
|
|
MetricsFecCode metrics_input) {
|
|
memcpy(metrics_output->average_residual_loss,
|
|
metrics_input.average_residual_loss,
|
|
sizeof(double) * kNumLossModels);
|
|
memcpy(metrics_output->variance_residual_loss,
|
|
metrics_input.variance_residual_loss,
|
|
sizeof(double) * kNumLossModels);
|
|
memcpy(metrics_output->residual_loss_per_loss_gap,
|
|
metrics_input.residual_loss_per_loss_gap,
|
|
sizeof(double) * kNumStatesDistribution);
|
|
memcpy(metrics_output->recovery_rate_per_loss,
|
|
metrics_input.recovery_rate_per_loss,
|
|
sizeof(double) * 2 * kMaxMediaPacketsTest);
|
|
}
|
|
|
|
// Compute the residual loss per gap, by summing the
|
|
// `residual_loss_per_loss_gap` over all loss configurations up to loss number
|
|
// = `num_fec_packets`.
|
|
double ComputeResidualLossPerGap(MetricsFecCode metrics,
|
|
int gap_number,
|
|
int num_fec_packets,
|
|
int code_index) {
|
|
double residual_loss_gap = 0.0;
|
|
int tot_num_configs = 0;
|
|
for (int loss = 1; loss <= num_fec_packets; loss++) {
|
|
int index = gap_number * (2 * kMaxMediaPacketsTest) + loss;
|
|
residual_loss_gap += metrics.residual_loss_per_loss_gap[index];
|
|
tot_num_configs += code_params_[code_index].configuration_density[index];
|
|
}
|
|
// Normalize, to compare across code sizes.
|
|
if (tot_num_configs > 0) {
|
|
residual_loss_gap =
|
|
residual_loss_gap / static_cast<double>(tot_num_configs);
|
|
}
|
|
return residual_loss_gap;
|
|
}
|
|
|
|
// Compute the recovery rate per loss number, by summing the
|
|
// `residual_loss_per_loss_gap` over all gap configurations.
|
|
void ComputeRecoveryRatePerLoss(MetricsFecCode* metrics,
|
|
int num_media_packets,
|
|
int num_fec_packets,
|
|
int code_index) {
|
|
for (int loss = 1; loss <= num_media_packets + num_fec_packets; loss++) {
|
|
metrics->recovery_rate_per_loss[loss] = 0.0;
|
|
int tot_num_configs = 0;
|
|
double arl = 0.0;
|
|
for (int gap = 0; gap < kMaxGapSize; gap++) {
|
|
int index = gap * (2 * kMaxMediaPacketsTest) + loss;
|
|
arl += metrics->residual_loss_per_loss_gap[index];
|
|
tot_num_configs +=
|
|
code_params_[code_index].configuration_density[index];
|
|
}
|
|
// Normalize, to compare across code sizes.
|
|
if (tot_num_configs > 0) {
|
|
arl = arl / static_cast<double>(tot_num_configs);
|
|
}
|
|
// Recovery rate for a given loss `loss` is 1 minus the scaled `arl`,
|
|
// where the scale factor is relative to code size/parameters.
|
|
double scaled_loss =
|
|
static_cast<double>(loss * num_media_packets) /
|
|
static_cast<double>(num_media_packets + num_fec_packets);
|
|
metrics->recovery_rate_per_loss[loss] = 1.0 - arl / scaled_loss;
|
|
}
|
|
}
|
|
|
|
void SetMetricsZero(MetricsFecCode* metrics) {
|
|
memset(metrics->average_residual_loss, 0, sizeof(double) * kNumLossModels);
|
|
memset(metrics->variance_residual_loss, 0, sizeof(double) * kNumLossModels);
|
|
memset(metrics->residual_loss_per_loss_gap, 0,
|
|
sizeof(double) * kNumStatesDistribution);
|
|
memset(metrics->recovery_rate_per_loss, 0,
|
|
sizeof(double) * 2 * kMaxMediaPacketsTest + 1);
|
|
}
|
|
|
|
// Compute the metrics for an FEC code, given by the code type `code_type`
|
|
// (XOR-random/ bursty or RS), and by the code index `code_index`
|
|
// (which containes the code size parameters/protection length).
|
|
void ComputeMetricsForCode(CodeType code_type, int code_index) {
|
|
std::unique_ptr<double[]> prob_weight(new double[kNumLossModels]);
|
|
memset(prob_weight.get(), 0, sizeof(double) * kNumLossModels);
|
|
MetricsFecCode metrics_code;
|
|
SetMetricsZero(&metrics_code);
|
|
|
|
int num_media_packets = code_params_[code_index].num_media_packets;
|
|
int num_fec_packets = code_params_[code_index].num_fec_packets;
|
|
int tot_num_packets = num_media_packets + num_fec_packets;
|
|
std::unique_ptr<uint8_t[]> state(new uint8_t[tot_num_packets]);
|
|
memset(state.get(), 0, tot_num_packets);
|
|
|
|
int num_loss_configurations = 1 << tot_num_packets;
|
|
// Loop over all loss configurations for the symbol sequence of length
|
|
// `tot_num_packets`. In this version we process up to (k=12, m=12) codes,
|
|
// and get exact expressions for the residual loss.
|
|
// TODO(marpan): For larger codes, loop over some random sample of loss
|
|
// configurations, sampling driven by the underlying statistical loss model
|
|
// (importance sampling).
|
|
|
|
// The symbols/packets are arranged as a sequence of source/media packets
|
|
// followed by FEC packets. This is the sequence ordering used in the RTP.
|
|
// A configuration refers to a sequence of received/lost (0/1 bit) states
|
|
// for the string of packets/symbols. For example, for a (k=4,m=3) code
|
|
// (4 media packets, 3 FEC packets), with 2 losses (one media and one FEC),
|
|
// the loss configurations is:
|
|
// Media1 Media2 Media3 Media4 FEC1 FEC2 FEC3
|
|
// 0 0 1 0 0 1 0
|
|
for (int i = 1; i < num_loss_configurations; i++) {
|
|
// Counter for number of packets lost.
|
|
int num_packets_lost = 0;
|
|
// Counters for the number of media packets lost.
|
|
int num_media_packets_lost = 0;
|
|
|
|
// Map configuration number to a loss state.
|
|
for (int j = 0; j < tot_num_packets; j++) {
|
|
state[j] = 0; // Received state.
|
|
int bit_value = i >> (tot_num_packets - j - 1) & 1;
|
|
if (bit_value == 1) {
|
|
state[j] = 1; // Lost state.
|
|
num_packets_lost++;
|
|
if (j < num_media_packets) {
|
|
num_media_packets_lost++;
|
|
}
|
|
}
|
|
} // Done with loop over total number of packets.
|
|
RTC_DCHECK_LE(num_media_packets_lost, num_media_packets);
|
|
RTC_DCHECK_LE(num_packets_lost, tot_num_packets && num_packets_lost > 0);
|
|
double residual_loss = 0.0;
|
|
// Only need to compute residual loss (number of recovered packets) for
|
|
// configurations that have at least one media packet lost.
|
|
if (num_media_packets_lost >= 1) {
|
|
// Compute the number of recovered packets.
|
|
int num_recovered_packets = 0;
|
|
if (code_type == xor_random_code || code_type == xor_bursty_code) {
|
|
num_recovered_packets = RecoveredMediaPackets(
|
|
num_media_packets, num_fec_packets, state.get());
|
|
} else {
|
|
// For the RS code, we can either completely recover all the packets
|
|
// if the loss is less than or equal to the number of FEC packets,
|
|
// otherwise we can recover none of the missing packets. This is the
|
|
// all or nothing (MDS) property of the RS code.
|
|
if (num_packets_lost <= num_fec_packets) {
|
|
num_recovered_packets = num_media_packets_lost;
|
|
}
|
|
}
|
|
RTC_DCHECK_LE(num_recovered_packets, num_media_packets);
|
|
// Compute the residual loss. We only care about recovering media/source
|
|
// packets, so residual loss is based on lost/recovered media packets.
|
|
residual_loss =
|
|
static_cast<double>(num_media_packets_lost - num_recovered_packets);
|
|
// Compute the probability weights for this configuration.
|
|
ComputeProbabilityWeight(prob_weight.get(), state.get(),
|
|
tot_num_packets);
|
|
// Update the average and variance of the residual loss.
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
metrics_code.average_residual_loss[k] +=
|
|
residual_loss * prob_weight[k];
|
|
metrics_code.variance_residual_loss[k] +=
|
|
residual_loss * residual_loss * prob_weight[k];
|
|
}
|
|
} // Done with processing for num_media_packets_lost >= 1.
|
|
// Update the distribution statistics.
|
|
// Compute the gap of the loss (the "consecutiveness" of the loss).
|
|
int gap_loss = GapLoss(tot_num_packets, state.get());
|
|
RTC_DCHECK_LT(gap_loss, kMaxGapSize);
|
|
int index = gap_loss * (2 * kMaxMediaPacketsTest) + num_packets_lost;
|
|
RTC_DCHECK_LT(index, kNumStatesDistribution);
|
|
metrics_code.residual_loss_per_loss_gap[index] += residual_loss;
|
|
if (code_type == xor_random_code) {
|
|
// The configuration density is only a function of the code length and
|
|
// only needs to computed for the first `code_type` passed here.
|
|
code_params_[code_index].configuration_density[index]++;
|
|
}
|
|
} // Done with loop over configurations.
|
|
// Normalize the average residual loss and compute/normalize the variance.
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
// Normalize the average residual loss by the total number of packets
|
|
// `tot_num_packets` (i.e., the code length). For a code with no (zero)
|
|
// recovery, the average residual loss for that code would be reduced like
|
|
// ~`average_loss_rate` * `num_media_packets` / `tot_num_packets`. This is
|
|
// the expected reduction in the average residual loss just from adding
|
|
// FEC packets to the symbol sequence.
|
|
metrics_code.average_residual_loss[k] =
|
|
metrics_code.average_residual_loss[k] /
|
|
static_cast<double>(tot_num_packets);
|
|
metrics_code.variance_residual_loss[k] =
|
|
metrics_code.variance_residual_loss[k] /
|
|
static_cast<double>(num_media_packets * num_media_packets);
|
|
metrics_code.variance_residual_loss[k] =
|
|
metrics_code.variance_residual_loss[k] -
|
|
(metrics_code.average_residual_loss[k] *
|
|
metrics_code.average_residual_loss[k]);
|
|
RTC_DCHECK_GE(metrics_code.variance_residual_loss[k], 0.0);
|
|
RTC_DCHECK_GT(metrics_code.average_residual_loss[k], 0.0);
|
|
metrics_code.variance_residual_loss[k] =
|
|
std::sqrt(metrics_code.variance_residual_loss[k]) /
|
|
metrics_code.average_residual_loss[k];
|
|
}
|
|
|
|
// Compute marginal distribution as a function of loss parameter.
|
|
ComputeRecoveryRatePerLoss(&metrics_code, num_media_packets,
|
|
num_fec_packets, code_index);
|
|
if (code_type == rs_code) {
|
|
CopyMetrics(&kMetricsReedSolomon[code_index], metrics_code);
|
|
} else if (code_type == xor_random_code) {
|
|
CopyMetrics(&kMetricsXorRandom[code_index], metrics_code);
|
|
} else if (code_type == xor_bursty_code) {
|
|
CopyMetrics(&kMetricsXorBursty[code_index], metrics_code);
|
|
} else {
|
|
RTC_DCHECK_NOTREACHED();
|
|
}
|
|
}
|
|
|
|
void WriteOutMetricsAllFecCodes() {
|
|
std::string filename = test::OutputPath() + "data_metrics_all_codes";
|
|
FILE* fp = fopen(filename.c_str(), "wb");
|
|
// Loop through codes up to `kMaxMediaPacketsTest`.
|
|
int code_index = 0;
|
|
for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
|
|
num_media_packets++) {
|
|
for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
|
|
num_fec_packets++) {
|
|
fprintf(fp, "FOR CODE: (%d, %d) \n", num_media_packets,
|
|
num_fec_packets);
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
float loss_rate = loss_model_[k].average_loss_rate;
|
|
float burst_length = loss_model_[k].average_burst_length;
|
|
fprintf(
|
|
fp,
|
|
"Loss rate = %.2f, Burst length = %.2f: %.4f %.4f %.4f"
|
|
" **** %.4f %.4f %.4f \n",
|
|
loss_rate, burst_length,
|
|
100 * kMetricsReedSolomon[code_index].average_residual_loss[k],
|
|
100 * kMetricsXorRandom[code_index].average_residual_loss[k],
|
|
100 * kMetricsXorBursty[code_index].average_residual_loss[k],
|
|
kMetricsReedSolomon[code_index].variance_residual_loss[k],
|
|
kMetricsXorRandom[code_index].variance_residual_loss[k],
|
|
kMetricsXorBursty[code_index].variance_residual_loss[k]);
|
|
}
|
|
for (int gap = 0; gap < kGapSizeOutput; gap++) {
|
|
double rs_residual_loss =
|
|
ComputeResidualLossPerGap(kMetricsReedSolomon[code_index], gap,
|
|
num_fec_packets, code_index);
|
|
double xor_random_residual_loss = ComputeResidualLossPerGap(
|
|
kMetricsXorRandom[code_index], gap, num_fec_packets, code_index);
|
|
double xor_bursty_residual_loss = ComputeResidualLossPerGap(
|
|
kMetricsXorBursty[code_index], gap, num_fec_packets, code_index);
|
|
fprintf(fp,
|
|
"Residual loss as a function of gap "
|
|
"%d: %.4f %.4f %.4f \n",
|
|
gap, rs_residual_loss, xor_random_residual_loss,
|
|
xor_bursty_residual_loss);
|
|
}
|
|
fprintf(fp, "Recovery rate as a function of loss number \n");
|
|
for (int loss = 1; loss <= num_media_packets + num_fec_packets;
|
|
loss++) {
|
|
fprintf(fp, "For loss number %d: %.4f %.4f %.4f \n", loss,
|
|
kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss],
|
|
kMetricsXorRandom[code_index].recovery_rate_per_loss[loss],
|
|
kMetricsXorBursty[code_index].recovery_rate_per_loss[loss]);
|
|
}
|
|
fprintf(fp, "******************\n");
|
|
fprintf(fp, "\n");
|
|
code_index++;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
void SetLossModels() {
|
|
int num_loss_rates = sizeof(kAverageLossRate) / sizeof(*kAverageLossRate);
|
|
int num_burst_lengths =
|
|
sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength);
|
|
int num_loss_models = 0;
|
|
for (int k = 0; k < num_burst_lengths; k++) {
|
|
for (int k2 = 0; k2 < num_loss_rates; k2++) {
|
|
loss_model_[num_loss_models].average_loss_rate = kAverageLossRate[k2];
|
|
loss_model_[num_loss_models].average_burst_length =
|
|
kAverageBurstLength[k];
|
|
// First set of loss models are of random type.
|
|
if (k == 0) {
|
|
loss_model_[num_loss_models].loss_type = kRandomLossModel;
|
|
} else {
|
|
loss_model_[num_loss_models].loss_type = kBurstyLossModel;
|
|
}
|
|
num_loss_models++;
|
|
}
|
|
}
|
|
RTC_DCHECK_EQ(num_loss_models, kNumLossModels);
|
|
}
|
|
|
|
void SetCodeParams() {
|
|
int code_index = 0;
|
|
for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
|
|
num_media_packets++) {
|
|
for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
|
|
num_fec_packets++) {
|
|
code_params_[code_index].num_media_packets = num_media_packets;
|
|
code_params_[code_index].num_fec_packets = num_fec_packets;
|
|
code_params_[code_index].protection_level =
|
|
static_cast<float>(num_fec_packets) /
|
|
static_cast<float>(num_media_packets + num_fec_packets);
|
|
for (int k = 0; k < kNumStatesDistribution; k++) {
|
|
code_params_[code_index].configuration_density[k] = 0;
|
|
}
|
|
code_index++;
|
|
}
|
|
}
|
|
max_num_codes_ = code_index;
|
|
}
|
|
|
|
// Make some basic checks on the packet masks. Return -1 if any of these
|
|
// checks fail.
|
|
int RejectInvalidMasks(int num_media_packets, int num_fec_packets) {
|
|
// Make sure every FEC packet protects something.
|
|
for (int i = 0; i < num_fec_packets; i++) {
|
|
int row_degree = 0;
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
if (fec_packet_masks_[i][j] == 1) {
|
|
row_degree++;
|
|
}
|
|
}
|
|
if (row_degree == 0) {
|
|
printf(
|
|
"Invalid mask: FEC packet has empty mask (does not protect "
|
|
"anything) %d %d %d \n",
|
|
i, num_media_packets, num_fec_packets);
|
|
return -1;
|
|
}
|
|
}
|
|
// Mask sure every media packet has some protection.
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
int column_degree = 0;
|
|
for (int i = 0; i < num_fec_packets; i++) {
|
|
if (fec_packet_masks_[i][j] == 1) {
|
|
column_degree++;
|
|
}
|
|
}
|
|
if (column_degree == 0) {
|
|
printf(
|
|
"Invalid mask: Media packet has no protection at all %d %d %d "
|
|
"\n",
|
|
j, num_media_packets, num_fec_packets);
|
|
return -1;
|
|
}
|
|
}
|
|
// Make sure we do not have two identical FEC packets.
|
|
for (int i = 0; i < num_fec_packets; i++) {
|
|
for (int i2 = i + 1; i2 < num_fec_packets; i2++) {
|
|
int overlap = 0;
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
if (fec_packet_masks_[i][j] == fec_packet_masks_[i2][j]) {
|
|
overlap++;
|
|
}
|
|
}
|
|
if (overlap == num_media_packets) {
|
|
printf("Invalid mask: Two FEC packets are identical %d %d %d %d \n",
|
|
i, i2, num_media_packets, num_fec_packets);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
// Avoid codes that have two media packets with full protection (all 1s in
|
|
// their corresponding columns). This would mean that if we lose those
|
|
// two packets, we can never recover them even if we receive all the other
|
|
// packets. Exclude the special cases of 1 or 2 FEC packets.
|
|
if (num_fec_packets > 2) {
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
for (int j2 = j + 1; j2 < num_media_packets; j2++) {
|
|
int degree = 0;
|
|
for (int i = 0; i < num_fec_packets; i++) {
|
|
if (fec_packet_masks_[i][j] == fec_packet_masks_[i][j2] &&
|
|
fec_packet_masks_[i][j] == 1) {
|
|
degree++;
|
|
}
|
|
}
|
|
if (degree == num_fec_packets) {
|
|
printf(
|
|
"Invalid mask: Two media packets are have full degree "
|
|
"%d %d %d %d \n",
|
|
j, j2, num_media_packets, num_fec_packets);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void GetPacketMaskConvertToBitMask(uint8_t* packet_mask,
|
|
int num_media_packets,
|
|
int num_fec_packets,
|
|
int mask_bytes_fec_packet,
|
|
CodeType code_type) {
|
|
for (int i = 0; i < num_fec_packets; i++) {
|
|
for (int j = 0; j < num_media_packets; j++) {
|
|
const uint8_t byte_mask =
|
|
packet_mask[i * mask_bytes_fec_packet + j / 8];
|
|
const int bit_position = (7 - j % 8);
|
|
fec_packet_masks_[i][j] =
|
|
(byte_mask & (1 << bit_position)) >> bit_position;
|
|
fprintf(fp_mask_, "%d ", fec_packet_masks_[i][j]);
|
|
}
|
|
fprintf(fp_mask_, "\n");
|
|
}
|
|
fprintf(fp_mask_, "\n");
|
|
}
|
|
|
|
int ProcessXORPacketMasks(CodeType code_type, FecMaskType fec_mask_type) {
|
|
int code_index = 0;
|
|
// Maximum number of media packets allowed for the mask type.
|
|
const int packet_mask_max = kMaxMediaPackets[fec_mask_type];
|
|
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 =
|
|
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.get(), 0, num_media_packets * mask_bytes_fec_packet);
|
|
rtc::ArrayView<const uint8_t> mask =
|
|
mask_table.LookUp(num_media_packets, num_fec_packets);
|
|
memcpy(packet_mask.get(), &mask[0], mask.size());
|
|
// Convert to bit mask.
|
|
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;
|
|
}
|
|
// Compute the metrics for this code/mask.
|
|
ComputeMetricsForCode(code_type, code_index);
|
|
code_index++;
|
|
}
|
|
}
|
|
RTC_DCHECK_EQ(code_index, kNumberCodes);
|
|
return 0;
|
|
}
|
|
|
|
void ProcessRS(CodeType code_type) {
|
|
int code_index = 0;
|
|
for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
|
|
num_media_packets++) {
|
|
for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
|
|
num_fec_packets++) {
|
|
// Compute the metrics for this code type.
|
|
ComputeMetricsForCode(code_type, code_index);
|
|
code_index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute metrics for all code types and sizes.
|
|
void ComputeMetricsAllCodes() {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
// Get metrics for XOR code with packet masks of random type.
|
|
std::string filename = test::OutputPath() + "data_packet_masks";
|
|
fp_mask_ = fopen(filename.c_str(), "wb");
|
|
fprintf(fp_mask_, "MASK OF TYPE RANDOM: \n");
|
|
EXPECT_EQ(ProcessXORPacketMasks(xor_random_code, kFecMaskRandom), 0);
|
|
// Get metrics for XOR code with packet masks of bursty type.
|
|
fprintf(fp_mask_, "MASK OF TYPE BURSTY: \n");
|
|
EXPECT_EQ(ProcessXORPacketMasks(xor_bursty_code, kFecMaskBursty), 0);
|
|
fclose(fp_mask_);
|
|
// Get metrics for Reed-Solomon code.
|
|
ProcessRS(rs_code);
|
|
}
|
|
};
|
|
|
|
// Verify that the average residual loss, averaged over loss models
|
|
// appropriate to each mask type, is below some maximum acceptable level. The
|
|
// acceptable levels are read in from a file, and correspond to a current set
|
|
// of packet masks. The levels for each code may be updated over time.
|
|
TEST_F(FecPacketMaskMetricsTest, FecXorMaxResidualLoss) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
ComputeMetricsAllCodes();
|
|
WriteOutMetricsAllFecCodes();
|
|
int num_loss_rates = sizeof(kAverageLossRate) / sizeof(*kAverageLossRate);
|
|
int num_burst_lengths =
|
|
sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength);
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
double sum_residual_loss_random_mask_random_loss = 0.0;
|
|
double sum_residual_loss_bursty_mask_bursty_loss = 0.0;
|
|
// Compute the sum residual loss across the models, for each mask type.
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
if (loss_model_[k].loss_type == kRandomLossModel) {
|
|
sum_residual_loss_random_mask_random_loss +=
|
|
kMetricsXorRandom[code_index].average_residual_loss[k];
|
|
} else if (loss_model_[k].loss_type == kBurstyLossModel) {
|
|
sum_residual_loss_bursty_mask_bursty_loss +=
|
|
kMetricsXorBursty[code_index].average_residual_loss[k];
|
|
}
|
|
}
|
|
float average_residual_loss_random_mask_random_loss =
|
|
sum_residual_loss_random_mask_random_loss / num_loss_rates;
|
|
float average_residual_loss_bursty_mask_bursty_loss =
|
|
sum_residual_loss_bursty_mask_bursty_loss /
|
|
(num_loss_rates * (num_burst_lengths - 1));
|
|
const float ref_random_mask = kMaxResidualLossRandomMask[code_index];
|
|
const float ref_bursty_mask = kMaxResidualLossBurstyMask[code_index];
|
|
EXPECT_LE(average_residual_loss_random_mask_random_loss, ref_random_mask);
|
|
EXPECT_LE(average_residual_loss_bursty_mask_bursty_loss, ref_bursty_mask);
|
|
}
|
|
}
|
|
|
|
// Verify the behavior of the XOR codes vs the RS codes.
|
|
// For random loss model with average loss rates <= the code protection level,
|
|
// the RS code (optimal MDS code) is more efficient than XOR codes.
|
|
// However, for larger loss rates (above protection level) and/or bursty
|
|
// loss models, the RS is not always more efficient than XOR (though in most
|
|
// cases it still is).
|
|
TEST_F(FecPacketMaskMetricsTest, FecXorVsRS) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
float loss_rate = loss_model_[k].average_loss_rate;
|
|
float protection_level = code_params_[code_index].protection_level;
|
|
// Under these conditions we expect XOR to not be better than RS.
|
|
if (loss_model_[k].loss_type == kRandomLossModel &&
|
|
loss_rate <= protection_level) {
|
|
EXPECT_GE(kMetricsXorRandom[code_index].average_residual_loss[k],
|
|
kMetricsReedSolomon[code_index].average_residual_loss[k]);
|
|
EXPECT_GE(kMetricsXorBursty[code_index].average_residual_loss[k],
|
|
kMetricsReedSolomon[code_index].average_residual_loss[k]);
|
|
}
|
|
// TODO(marpan): There are some cases (for high loss rates and/or
|
|
// burst loss models) where XOR is better than RS. Is there some pattern
|
|
// we can identify and enforce as a constraint?
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify the trend (change) in the average residual loss, as a function of
|
|
// loss rate, of the XOR code relative to the RS code.
|
|
// The difference between XOR and RS should not get worse as we increase
|
|
// the average loss rate.
|
|
TEST_F(FecPacketMaskMetricsTest, FecTrendXorVsRsLossRate) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
// TODO(marpan): Examine this further to see if the condition can be strictly
|
|
// satisfied (i.e., scale = 1.0) for all codes with different/better masks.
|
|
double scale = 0.90;
|
|
int num_loss_rates = sizeof(kAverageLossRate) / sizeof(*kAverageLossRate);
|
|
int num_burst_lengths =
|
|
sizeof(kAverageBurstLength) / sizeof(*kAverageBurstLength);
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
for (int i = 0; i < num_burst_lengths; i++) {
|
|
for (int j = 0; j < num_loss_rates - 1; j++) {
|
|
int k = num_loss_rates * i + j;
|
|
// For XOR random.
|
|
if (kMetricsXorRandom[code_index].average_residual_loss[k] >
|
|
kMetricsReedSolomon[code_index].average_residual_loss[k]) {
|
|
double diff_rs_xor_random_loss1 =
|
|
(kMetricsXorRandom[code_index].average_residual_loss[k] -
|
|
kMetricsReedSolomon[code_index].average_residual_loss[k]) /
|
|
kMetricsXorRandom[code_index].average_residual_loss[k];
|
|
double diff_rs_xor_random_loss2 =
|
|
(kMetricsXorRandom[code_index].average_residual_loss[k + 1] -
|
|
kMetricsReedSolomon[code_index].average_residual_loss[k + 1]) /
|
|
kMetricsXorRandom[code_index].average_residual_loss[k + 1];
|
|
EXPECT_GE(diff_rs_xor_random_loss1, scale * diff_rs_xor_random_loss2);
|
|
}
|
|
// TODO(marpan): Investigate the cases for the bursty mask where
|
|
// this trend is not strictly satisfied.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify the average residual loss behavior via the protection level and
|
|
// the code length. The average residual loss for a given (k1,m1) code
|
|
// should generally be higher than that of another code (k2,m2), which has
|
|
// either of the two conditions satisfied:
|
|
// 1) higher protection & code length at least as large: (k2+m2) >= (k1+m1),
|
|
// 2) equal protection and larger code length: (k2+m2) > (k1+m1).
|
|
// Currently does not hold for some cases of the XOR code with random mask.
|
|
TEST_F(FecPacketMaskMetricsTest, FecBehaviorViaProtectionLevelAndLength) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
for (int code_index1 = 0; code_index1 < max_num_codes_; code_index1++) {
|
|
float protection_level1 = code_params_[code_index1].protection_level;
|
|
int length1 = code_params_[code_index1].num_media_packets +
|
|
code_params_[code_index1].num_fec_packets;
|
|
for (int code_index2 = 0; code_index2 < max_num_codes_; code_index2++) {
|
|
float protection_level2 = code_params_[code_index2].protection_level;
|
|
int length2 = code_params_[code_index2].num_media_packets +
|
|
code_params_[code_index2].num_fec_packets;
|
|
// Codes with higher protection are more efficient, conditioned on the
|
|
// length of the code (higher protection but shorter length codes are
|
|
// generally not more efficient). For two codes with equal protection,
|
|
// the longer code is generally more efficient. For high loss rate
|
|
// models, this condition may be violated for some codes with equal or
|
|
// very close protection levels. High loss rate case is excluded below.
|
|
if ((protection_level2 > protection_level1 && length2 >= length1) ||
|
|
(protection_level2 == protection_level1 && length2 > length1)) {
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
float loss_rate = loss_model_[k].average_loss_rate;
|
|
if (loss_rate < loss_rate_upper_threshold) {
|
|
EXPECT_LT(
|
|
kMetricsReedSolomon[code_index2].average_residual_loss[k],
|
|
kMetricsReedSolomon[code_index1].average_residual_loss[k]);
|
|
// TODO(marpan): There are some corner cases where this is not
|
|
// satisfied with the current packet masks. Look into updating
|
|
// these cases to see if this behavior should/can be satisfied,
|
|
// with overall lower residual loss for those XOR codes.
|
|
// EXPECT_LT(
|
|
// kMetricsXorBursty[code_index2].average_residual_loss[k],
|
|
// kMetricsXorBursty[code_index1].average_residual_loss[k]);
|
|
// EXPECT_LT(
|
|
// kMetricsXorRandom[code_index2].average_residual_loss[k],
|
|
// kMetricsXorRandom[code_index1].average_residual_loss[k]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify the beheavior of the variance of the XOR codes.
|
|
// The partial recovery of the XOR versus the all or nothing behavior of the RS
|
|
// code means that the variance of the residual loss for XOR should generally
|
|
// not be worse than RS.
|
|
TEST_F(FecPacketMaskMetricsTest, FecVarianceBehaviorXorVsRs) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
// The condition is not strictly satisfied with the current masks,
|
|
// i.e., for some codes, the variance of XOR may be slightly higher than RS.
|
|
// TODO(marpan): Examine this further to see if the condition can be strictly
|
|
// satisfied (i.e., scale = 1.0) for all codes with different/better masks.
|
|
double scale = 0.95;
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
EXPECT_LE(scale * kMetricsXorRandom[code_index].variance_residual_loss[k],
|
|
kMetricsReedSolomon[code_index].variance_residual_loss[k]);
|
|
EXPECT_LE(scale * kMetricsXorBursty[code_index].variance_residual_loss[k],
|
|
kMetricsReedSolomon[code_index].variance_residual_loss[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For the bursty mask type, the residual loss must be strictly zero for all
|
|
// consecutive losses (i.e, gap = 0) with number of losses <= num_fec_packets.
|
|
// This is a design property of the bursty mask type.
|
|
TEST_F(FecPacketMaskMetricsTest, FecXorBurstyPerfectRecoveryConsecutiveLoss) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
int num_fec_packets = code_params_[code_index].num_fec_packets;
|
|
for (int loss = 1; loss <= num_fec_packets; loss++) {
|
|
int index = loss; // `gap` is zero.
|
|
EXPECT_EQ(kMetricsXorBursty[code_index].residual_loss_per_loss_gap[index],
|
|
0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The XOR codes with random mask type are generally better than the ones with
|
|
// bursty mask type, for random loss models at low loss rates.
|
|
// The XOR codes with bursty mask types are generally better than the one with
|
|
// random mask type, for bursty loss models and/or high loss rates.
|
|
// TODO(marpan): Enable this test when some of the packet masks are updated.
|
|
// Some isolated cases of the codes don't pass this currently.
|
|
/*
|
|
TEST_F(FecPacketMaskMetricsTest, FecXorRandomVsBursty) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
double sum_residual_loss_random_mask_random_loss = 0.0;
|
|
double sum_residual_loss_bursty_mask_random_loss = 0.0;
|
|
double sum_residual_loss_random_mask_bursty_loss = 0.0;
|
|
double sum_residual_loss_bursty_mask_bursty_loss = 0.0;
|
|
// Compute the sum residual loss across the models, for each mask type.
|
|
for (int k = 0; k < kNumLossModels; k++) {
|
|
float loss_rate = loss_model_[k].average_loss_rate;
|
|
if (loss_model_[k].loss_type == kRandomLossModel &&
|
|
loss_rate < loss_rate_upper_threshold) {
|
|
sum_residual_loss_random_mask_random_loss +=
|
|
kMetricsXorRandom[code_index].average_residual_loss[k];
|
|
sum_residual_loss_bursty_mask_random_loss +=
|
|
kMetricsXorBursty[code_index].average_residual_loss[k];
|
|
} else if (loss_model_[k].loss_type == kBurstyLossModel &&
|
|
loss_rate > loss_rate_lower_threshold) {
|
|
sum_residual_loss_random_mask_bursty_loss +=
|
|
kMetricsXorRandom[code_index].average_residual_loss[k];
|
|
sum_residual_loss_bursty_mask_bursty_loss +=
|
|
kMetricsXorBursty[code_index].average_residual_loss[k];
|
|
}
|
|
}
|
|
EXPECT_LE(sum_residual_loss_random_mask_random_loss,
|
|
sum_residual_loss_bursty_mask_random_loss);
|
|
EXPECT_LE(sum_residual_loss_bursty_mask_bursty_loss,
|
|
sum_residual_loss_random_mask_bursty_loss);
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Verify that the average recovery rate for each code is equal or above some
|
|
// threshold, for certain loss number conditions.
|
|
TEST_F(FecPacketMaskMetricsTest, FecRecoveryRateUnderLossConditions) {
|
|
SetLossModels();
|
|
SetCodeParams();
|
|
for (int code_index = 0; code_index < max_num_codes_; code_index++) {
|
|
int num_media_packets = code_params_[code_index].num_media_packets;
|
|
int num_fec_packets = code_params_[code_index].num_fec_packets;
|
|
// Perfect recovery (`recovery_rate_per_loss` == 1) is expected for
|
|
// `loss_number` = 1, for all codes.
|
|
int loss_number = 1;
|
|
EXPECT_EQ(
|
|
kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number],
|
|
1.0);
|
|
EXPECT_EQ(kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number],
|
|
1.0);
|
|
EXPECT_EQ(kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number],
|
|
1.0);
|
|
// For `loss_number` = `num_fec_packets` / 2, we expect the following:
|
|
// Perfect recovery for RS, and recovery for XOR above the threshold.
|
|
loss_number = num_fec_packets / 2 > 0 ? num_fec_packets / 2 : 1;
|
|
EXPECT_EQ(
|
|
kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number],
|
|
1.0);
|
|
EXPECT_GE(kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number],
|
|
kRecoveryRateXorRandom[0]);
|
|
EXPECT_GE(kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number],
|
|
kRecoveryRateXorBursty[0]);
|
|
// For `loss_number` = `num_fec_packets`, we expect the following:
|
|
// Perfect recovery for RS, and recovery for XOR above the threshold.
|
|
loss_number = num_fec_packets;
|
|
EXPECT_EQ(
|
|
kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number],
|
|
1.0);
|
|
EXPECT_GE(kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number],
|
|
kRecoveryRateXorRandom[1]);
|
|
EXPECT_GE(kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number],
|
|
kRecoveryRateXorBursty[1]);
|
|
// For `loss_number` = `num_fec_packets` + 1, we expect the following:
|
|
// Zero recovery for RS, but non-zero recovery for XOR.
|
|
if (num_fec_packets > 1 && num_media_packets > 2) {
|
|
loss_number = num_fec_packets + 1;
|
|
EXPECT_EQ(
|
|
kMetricsReedSolomon[code_index].recovery_rate_per_loss[loss_number],
|
|
0.0);
|
|
EXPECT_GE(
|
|
kMetricsXorRandom[code_index].recovery_rate_per_loss[loss_number],
|
|
kRecoveryRateXorRandom[2]);
|
|
EXPECT_GE(
|
|
kMetricsXorBursty[code_index].recovery_rate_per_loss[loss_number],
|
|
kRecoveryRateXorBursty[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace webrtc
|