1 /* 2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 /* 12 * The purpose of this test is to compute metrics to characterize the properties 13 * and efficiency of the packets masks used in the generic XOR FEC code. 14 * 15 * The metrics measure the efficiency (recovery potential or residual loss) of 16 * the FEC code, under various statistical loss models for the packet/symbol 17 * loss events. Various constraints on the behavior of these metrics are 18 * verified, and compared to the reference RS (Reed-Solomon) code. This serves 19 * in some way as a basic check/benchmark for the packet masks. 20 * 21 * By an FEC code, we mean an erasure packet/symbol code, characterized by: 22 * (1) The code size parameters (k,m), where k = number of source/media packets, 23 * and m = number of FEC packets, 24 * (2) The code type: XOR or RS. 25 * In the case of XOR, the residual loss is determined via the set of packet 26 * masks (generator matrix). In the case of RS, the residual loss is determined 27 * directly from the MDS (maximum distance separable) property of RS. 28 * 29 * Currently two classes of packets masks are available (random type and bursty 30 * type), so three codes are considered below: RS, XOR-random, and XOR-bursty. 31 * The bursty class is defined up to k=12, so (k=12,m=12) is largest code size 32 * considered in this test. 33 * 34 * The XOR codes are defined via the RFC 5109 and correspond to the class of 35 * LDGM (low density generator matrix) codes, which is a subset of the LDPC 36 * (low density parity check) codes. Future implementation will consider 37 * extending our XOR codes to include LDPC codes, which explicitly include 38 * protection of FEC packets. 39 * 40 * The type of packet/symbol loss models considered in this test are: 41 * (1) Random loss: Bernoulli process, characterized by the average loss rate. 42 * (2) Bursty loss: Markov chain (Gilbert-Elliot model), characterized by two 43 * parameters: average loss rate and average burst length. 44 */ 45 46 #include <math.h> 47 48 #include "testing/gtest/include/gtest/gtest.h" 49 #include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h" 50 #include "webrtc/modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h" 51 #include "webrtc/system_wrappers/interface/scoped_ptr.h" 52 #include "webrtc/test/testsupport/fileutils.h" 53 54 namespace webrtc { 55 56 // Maximum number of media packets allows for XOR (RFC 5109) code. 57 enum { kMaxNumberMediaPackets = 48 }; 58 59 // Maximum number of media packets allowed for each mask type. 60 const uint16_t kMaxMediaPackets[] = {kMaxNumberMediaPackets, 12}; 61 62 // Maximum number of media packets allowed in this test. The burst mask types 63 // are currently defined up to (k=12,m=12). 64 const int kMaxMediaPacketsTest = 12; 65 66 // Maximum number of FEC codes considered in this test. 67 const int kNumberCodes = kMaxMediaPacketsTest * (kMaxMediaPacketsTest + 1) / 2; 68 69 // Maximum gap size for characterizing the consecutiveness of the loss. 70 const int kMaxGapSize = 2 * kMaxMediaPacketsTest; 71 72 // Number of gap levels written to file/output. 73 const int kGapSizeOutput = 5; 74 75 // Maximum number of states for characterizing the residual loss distribution. 76 const int kNumStatesDistribution = 2 * kMaxMediaPacketsTest * kMaxGapSize + 1; 77 78 // The code type. 79 enum CodeType { 80 xor_random_code, // XOR with random mask type. 81 xor_bursty_code, // XOR with bursty mask type. 82 rs_code // Reed_solomon. 83 }; 84 85 // The code size parameters. 86 struct CodeSizeParams { 87 int num_media_packets; 88 int num_fec_packets; 89 // Protection level: num_fec_packets / (num_media_packets + num_fec_packets). 90 float protection_level; 91 // Number of loss configurations, for a given loss number and gap number. 92 // The gap number refers to the maximum gap/hole of a loss configuration 93 // (used to measure the "consecutiveness" of the loss). 94 int configuration_density[kNumStatesDistribution]; 95 }; 96 97 // The type of loss models. 98 enum LossModelType { 99 kRandomLossModel, 100 kBurstyLossModel 101 }; 102 103 struct LossModel { 104 LossModelType loss_type; 105 float average_loss_rate; 106 float average_burst_length; 107 }; 108 109 // Average loss rates. 110 const float kAverageLossRate[] = { 0.025f, 0.05f, 0.1f, 0.25f }; 111 112 // Average burst lengths. The case of |kAverageBurstLength = 1.0| refers to 113 // the random model. Note that for the random (Bernoulli) model, the average 114 // burst length is determined by the average loss rate, i.e., 115 // AverageBurstLength = 1 / (1 - AverageLossRate) for random model. 116 const float kAverageBurstLength[] = { 1.0f, 2.0f, 4.0f }; 117 118 // Total number of loss models: For each burst length case, there are 119 // a number of models corresponding to the loss rates. 120 const int kNumLossModels = (sizeof(kAverageBurstLength) / 121 sizeof(*kAverageBurstLength)) * (sizeof(kAverageLossRate) / 122 sizeof(*kAverageLossRate)); 123 124 // Thresholds on the average loss rate of the packet loss model, below which 125 // certain properties of the codes are expected. 126 float loss_rate_upper_threshold = 0.20f; 127 float loss_rate_lower_threshold = 0.025f; 128 129 // Set of thresholds on the expected average recovery rate, for each code type. 130 // These are global thresholds for now; in future version we may condition them 131 // on the code length/size and protection level. 132 const float kRecoveryRateXorRandom[3] = { 0.94f, 0.50f, 0.19f }; 133 const float kRecoveryRateXorBursty[3] = { 0.90f, 0.54f, 0.22f }; 134 135 // Metrics for a given FEC code; each code is defined by the code type 136 // (RS, XOR-random/bursty), and the code size parameters (k,m), where 137 // k = num_media_packets, m = num_fec_packets. 138 struct MetricsFecCode { 139 // The average and variance of the residual loss, as a function of the 140 // packet/symbol loss model. The average/variance is computed by averaging 141 // over all loss configurations wrt the loss probability given by the 142 // underlying loss model. 143 double average_residual_loss[kNumLossModels]; 144 double variance_residual_loss[kNumLossModels]; 145 // The residual loss, as a function of the loss number and the gap number of 146 // the loss configurations. The gap number refers to the maximum gap/hole of 147 // a loss configuration (used to measure the "consecutiveness" of the loss). 148 double residual_loss_per_loss_gap[kNumStatesDistribution]; 149 // The recovery rate as a function of the loss number. 150 double recovery_rate_per_loss[2 * kMaxMediaPacketsTest + 1]; 151 }; 152 153 MetricsFecCode kMetricsXorRandom[kNumberCodes]; 154 MetricsFecCode kMetricsXorBursty[kNumberCodes]; 155 MetricsFecCode kMetricsReedSolomon[kNumberCodes]; 156 157 class FecPacketMaskMetricsTest : public ::testing::Test { 158 protected: 159 FecPacketMaskMetricsTest() { } 160 161 int max_num_codes_; 162 LossModel loss_model_[kNumLossModels]; 163 CodeSizeParams code_params_[kNumberCodes]; 164 165 uint8_t fec_packet_masks_[kMaxNumberMediaPackets][kMaxNumberMediaPackets]; 166 FILE* fp_mask_; 167 168 // Measure of the gap of the loss for configuration given by |state|. 169 // This is to measure degree of consecutiveness for the loss configuration. 170 // Useful if the packets are sent out in order of sequence numbers and there 171 // is little/no re-ordering during transmission. 172 int GapLoss(int tot_num_packets, uint8_t* state) { 173 int max_gap_loss = 0; 174 // Find the first loss. 175 int first_loss = 0; 176 for (int i = 0; i < tot_num_packets; i++) { 177 if (state[i] == 1) { 178 first_loss = i; 179 break; 180 } 181 } 182 int prev_loss = first_loss; 183 for (int i = first_loss + 1; i < tot_num_packets; i++) { 184 if (state[i] == 1) { // Lost state. 185 int gap_loss = (i - prev_loss) - 1; 186 if (gap_loss > max_gap_loss) { 187 max_gap_loss = gap_loss; 188 } 189 prev_loss = i; 190 } 191 } 192 return max_gap_loss; 193 } 194 195 // Returns the number of recovered media packets for the XOR code, given the 196 // packet mask |fec_packet_masks_|, for the loss state/configuration given by 197 // |state|. 198 int RecoveredMediaPackets(int num_media_packets, 199 int num_fec_packets, 200 uint8_t* state) { 201 scoped_ptr<uint8_t[]> state_tmp( 202 new uint8_t[num_media_packets + num_fec_packets]); 203 memcpy(state_tmp.get(), state, num_media_packets + num_fec_packets); 204 int num_recovered_packets = 0; 205 bool loop_again = true; 206 while (loop_again) { 207 loop_again = false; 208 bool recovered_new_packet = false; 209 // Check if we can recover anything: loop over all possible FEC packets. 210 for (int i = 0; i < num_fec_packets; i++) { 211 if (state_tmp[i + num_media_packets] == 0) { 212 // We have this FEC packet. 213 int num_packets_in_mask = 0; 214 int num_received_packets_in_mask = 0; 215 for (int j = 0; j < num_media_packets; j++) { 216 if (fec_packet_masks_[i][j] == 1) { 217 num_packets_in_mask++; 218 if (state_tmp[j] == 0) { 219 num_received_packets_in_mask++; 220 } 221 } 222 } 223 if ((num_packets_in_mask - 1) == num_received_packets_in_mask) { 224 // We can recover the missing media packet for this FEC packet. 225 num_recovered_packets++; 226 recovered_new_packet = true; 227 int jsel = -1; 228 int check_num_recovered = 0; 229 // Update the state with newly recovered media packet. 230 for (int j = 0; j < num_media_packets; j++) { 231 if (fec_packet_masks_[i][j] == 1 && state_tmp[j] == 1) { 232 // This is the lost media packet we will recover. 233 jsel = j; 234 check_num_recovered++; 235 } 236 } 237 // Check that we can only recover 1 packet. 238 assert(check_num_recovered == 1); 239 // Update the state with the newly recovered media packet. 240 state_tmp[jsel] = 0; 241 } 242 } 243 } // Go to the next FEC packet in the loop. 244 // If we have recovered at least one new packet in this FEC loop, 245 // go through loop again, otherwise we leave loop. 246 if (recovered_new_packet) { 247 loop_again = true; 248 } 249 } 250 return num_recovered_packets; 251 } 252 253 // Compute the probability of occurence of the loss state/configuration, 254 // given by |state|, for all the loss models considered in this test. 255 void ComputeProbabilityWeight(double* prob_weight, 256 uint8_t* state, 257 int tot_num_packets) { 258 // Loop over the loss models. 259 for (int k = 0; k < kNumLossModels; k++) { 260 double loss_rate = static_cast<double>( 261 loss_model_[k].average_loss_rate); 262 double burst_length = static_cast<double>( 263 loss_model_[k].average_burst_length); 264 double result = 1.0; 265 if (loss_model_[k].loss_type == kRandomLossModel) { 266 for (int i = 0; i < tot_num_packets; i++) { 267 if (state[i] == 0) { 268 result *= (1.0 - loss_rate); 269 } else { 270 result *= loss_rate; 271 } 272 } 273 } else { // Gilbert-Elliot model for burst model. 274 assert(loss_model_[k].loss_type == kBurstyLossModel); 275 // Transition probabilities: from previous to current state. 276 // Prob. of previous = lost --> current = received. 277 double prob10 = 1.0 / burst_length; 278 // Prob. of previous = lost --> currrent = lost. 279 double prob11 = 1.0 - prob10; 280 // Prob. of previous = received --> current = lost. 281 double prob01 = prob10 * (loss_rate / (1.0 - loss_rate)); 282 // Prob. of previous = received --> current = received. 283 double prob00 = 1.0 - prob01; 284 285 // Use stationary probability for first state/packet. 286 if (state[0] == 0) { // Received 287 result = (1.0 - loss_rate); 288 } else { // Lost 289 result = loss_rate; 290 } 291 292 // Subsequent states: use transition probabilities. 293 for (int i = 1; i < tot_num_packets; i++) { 294 // Current state is received 295 if (state[i] == 0) { 296 if (state[i-1] == 0) { 297 result *= prob00; // Previous received, current received. 298 } else { 299 result *= prob10; // Previous lost, current received. 300 } 301 } else { // Current state is lost 302 if (state[i-1] == 0) { 303 result *= prob01; // Previous received, current lost. 304 } else { 305 result *= prob11; // Previous lost, current lost. 306 } 307 } 308 } 309 } 310 prob_weight[k] = result; 311 } 312 } 313 314 void CopyMetrics(MetricsFecCode* metrics_output, 315 MetricsFecCode metrics_input) { 316 memcpy(metrics_output->average_residual_loss, 317 metrics_input.average_residual_loss, 318 sizeof(double) * kNumLossModels); 319 memcpy(metrics_output->variance_residual_loss, 320 metrics_input.variance_residual_loss, 321 sizeof(double) * kNumLossModels); 322 memcpy(metrics_output->residual_loss_per_loss_gap, 323 metrics_input.residual_loss_per_loss_gap, 324 sizeof(double) * kNumStatesDistribution); 325 memcpy(metrics_output->recovery_rate_per_loss, 326 metrics_input.recovery_rate_per_loss, 327 sizeof(double) * 2 * kMaxMediaPacketsTest); 328 } 329 330 // Compute the residual loss per gap, by summing the 331 // |residual_loss_per_loss_gap| over all loss configurations up to loss number 332 // = |num_fec_packets|. 333 double ComputeResidualLossPerGap(MetricsFecCode metrics, 334 int gap_number, 335 int num_fec_packets, 336 int code_index) { 337 double residual_loss_gap = 0.0; 338 int tot_num_configs = 0; 339 for (int loss = 1; loss <= num_fec_packets; loss++) { 340 int index = gap_number * (2 * kMaxMediaPacketsTest) + loss; 341 residual_loss_gap += metrics.residual_loss_per_loss_gap[index]; 342 tot_num_configs += 343 code_params_[code_index].configuration_density[index]; 344 } 345 // Normalize, to compare across code sizes. 346 if (tot_num_configs > 0) { 347 residual_loss_gap = residual_loss_gap / 348 static_cast<double>(tot_num_configs); 349 } 350 return residual_loss_gap; 351 } 352 353 // Compute the recovery rate per loss number, by summing the 354 // |residual_loss_per_loss_gap| over all gap configurations. 355 void ComputeRecoveryRatePerLoss(MetricsFecCode* metrics, 356 int num_media_packets, 357 int num_fec_packets, 358 int code_index) { 359 for (int loss = 1; loss <= num_media_packets + num_fec_packets; loss++) { 360 metrics->recovery_rate_per_loss[loss] = 0.0; 361 int tot_num_configs = 0; 362 double arl = 0.0; 363 for (int gap = 0; gap < kMaxGapSize; gap ++) { 364 int index = gap * (2 * kMaxMediaPacketsTest) + loss; 365 arl += metrics->residual_loss_per_loss_gap[index]; 366 tot_num_configs += 367 code_params_[code_index].configuration_density[index]; 368 } 369 // Normalize, to compare across code sizes. 370 if (tot_num_configs > 0) { 371 arl = arl / static_cast<double>(tot_num_configs); 372 } 373 // Recovery rate for a given loss |loss| is 1 minus the scaled |arl|, 374 // where the scale factor is relative to code size/parameters. 375 double scaled_loss = static_cast<double>(loss * num_media_packets) / 376 static_cast<double>(num_media_packets + num_fec_packets); 377 metrics->recovery_rate_per_loss[loss] = 1.0 - arl / scaled_loss; 378 } 379 } 380 381 void SetMetricsZero(MetricsFecCode* metrics) { 382 memset(metrics->average_residual_loss, 0, sizeof(double) * kNumLossModels); 383 memset(metrics->variance_residual_loss, 0, sizeof(double) * kNumLossModels); 384 memset(metrics->residual_loss_per_loss_gap, 0, 385 sizeof(double) * kNumStatesDistribution); 386 memset(metrics->recovery_rate_per_loss, 0, 387 sizeof(double) * 2 * kMaxMediaPacketsTest + 1); 388 } 389 390 // Compute the metrics for an FEC code, given by the code type |code_type| 391 // (XOR-random/ bursty or RS), and by the code index |code_index| 392 // (which containes the code size parameters/protection length). 393 void ComputeMetricsForCode(CodeType code_type, 394 int code_index) { 395 scoped_ptr<double[]> prob_weight(new double[kNumLossModels]); 396 memset(prob_weight.get() , 0, sizeof(double) * kNumLossModels); 397 MetricsFecCode metrics_code; 398 SetMetricsZero(&metrics_code); 399 400 int num_media_packets = code_params_[code_index].num_media_packets; 401 int num_fec_packets = code_params_[code_index].num_fec_packets; 402 int tot_num_packets = num_media_packets + num_fec_packets; 403 scoped_ptr<uint8_t[]> state(new uint8_t[tot_num_packets]); 404 memset(state.get() , 0, tot_num_packets); 405 406 int num_loss_configurations = static_cast<int>(pow(2.0f, tot_num_packets)); 407 // Loop over all loss configurations for the symbol sequence of length 408 // |tot_num_packets|. In this version we process up to (k=12, m=12) codes, 409 // and get exact expressions for the residual loss. 410 // TODO (marpan): For larger codes, loop over some random sample of loss 411 // configurations, sampling driven by the underlying statistical loss model 412 // (importance sampling). 413 414 // The symbols/packets are arranged as a sequence of source/media packets 415 // followed by FEC packets. This is the sequence ordering used in the RTP. 416 // A configuration refers to a sequence of received/lost (0/1 bit) states 417 // for the string of packets/symbols. For example, for a (k=4,m=3) code 418 // (4 media packets, 3 FEC packets), with 2 losses (one media and one FEC), 419 // the loss configurations is: 420 // Media1 Media2 Media3 Media4 FEC1 FEC2 FEC3 421 // 0 0 1 0 0 1 0 422 for (int i = 1; i < num_loss_configurations; i++) { 423 // Counter for number of packets lost. 424 int num_packets_lost = 0; 425 // Counters for the number of media packets lost. 426 int num_media_packets_lost = 0; 427 428 // Map configuration number to a loss state. 429 for (int j = 0; j < tot_num_packets; j++) { 430 state[j]=0; // Received state. 431 int bit_value = i >> (tot_num_packets - j - 1) & 1; 432 if (bit_value == 1) { 433 state[j] = 1; // Lost state. 434 num_packets_lost++; 435 if (j < num_media_packets) { 436 num_media_packets_lost++; 437 } 438 } 439 } // Done with loop over total number of packets. 440 assert(num_media_packets_lost <= num_media_packets); 441 assert(num_packets_lost <= tot_num_packets && num_packets_lost > 0); 442 double residual_loss = 0.0; 443 // Only need to compute residual loss (number of recovered packets) for 444 // configurations that have at least one media packet lost. 445 if (num_media_packets_lost >= 1) { 446 // Compute the number of recovered packets. 447 int num_recovered_packets = 0; 448 if (code_type == xor_random_code || code_type == xor_bursty_code) { 449 num_recovered_packets = RecoveredMediaPackets(num_media_packets, 450 num_fec_packets, 451 state.get()); 452 } else { 453 // For the RS code, we can either completely recover all the packets 454 // if the loss is less than or equal to the number of FEC packets, 455 // otherwise we can recover none of the missing packets. This is the 456 // all or nothing (MDS) property of the RS code. 457 if (num_packets_lost <= num_fec_packets) { 458 num_recovered_packets = num_media_packets_lost; 459 } 460 } 461 assert(num_recovered_packets <= num_media_packets); 462 // Compute the residual loss. We only care about recovering media/source 463 // packets, so residual loss is based on lost/recovered media packets. 464 residual_loss = static_cast<double>(num_media_packets_lost - 465 num_recovered_packets); 466 // Compute the probability weights for this configuration. 467 ComputeProbabilityWeight(prob_weight.get(), 468 state.get(), 469 tot_num_packets); 470 // Update the average and variance of the residual loss. 471 for (int k = 0; k < kNumLossModels; k++) { 472 metrics_code.average_residual_loss[k] += residual_loss * 473 prob_weight[k]; 474 metrics_code.variance_residual_loss[k] += residual_loss * 475 residual_loss * prob_weight[k]; 476 } 477 } // Done with processing for num_media_packets_lost >= 1. 478 // Update the distribution statistics. 479 // Compute the gap of the loss (the "consecutiveness" of the loss). 480 int gap_loss = GapLoss(tot_num_packets, state.get()); 481 assert(gap_loss < kMaxGapSize); 482 int index = gap_loss * (2 * kMaxMediaPacketsTest) + num_packets_lost; 483 assert(index < kNumStatesDistribution); 484 metrics_code.residual_loss_per_loss_gap[index] += residual_loss; 485 if (code_type == xor_random_code) { 486 // The configuration density is only a function of the code length and 487 // only needs to computed for the first |code_type| passed here. 488 code_params_[code_index].configuration_density[index]++; 489 } 490 } // Done with loop over configurations. 491 // Normalize the average residual loss and compute/normalize the variance. 492 for (int k = 0; k < kNumLossModels; k++) { 493 // Normalize the average residual loss by the total number of packets 494 // |tot_num_packets| (i.e., the code length). For a code with no (zero) 495 // recovery, the average residual loss for that code would be reduced like 496 // ~|average_loss_rate| * |num_media_packets| / |tot_num_packets|. This is 497 // the expected reduction in the average residual loss just from adding 498 // FEC packets to the symbol sequence. 499 metrics_code.average_residual_loss[k] = 500 metrics_code.average_residual_loss[k] / 501 static_cast<double>(tot_num_packets); 502 metrics_code.variance_residual_loss[k] = 503 metrics_code.variance_residual_loss[k] / 504 static_cast<double>(num_media_packets * num_media_packets); 505 metrics_code.variance_residual_loss[k] = 506 metrics_code.variance_residual_loss[k] - 507 (metrics_code.average_residual_loss[k] * 508 metrics_code.average_residual_loss[k]); 509 assert(metrics_code.variance_residual_loss[k] >= 0.0); 510 assert(metrics_code.average_residual_loss[k] > 0.0); 511 metrics_code.variance_residual_loss[k] = 512 sqrt(metrics_code.variance_residual_loss[k]) / 513 metrics_code.average_residual_loss[k]; 514 } 515 516 // Compute marginal distribution as a function of loss parameter. 517 ComputeRecoveryRatePerLoss(&metrics_code, 518 num_media_packets, 519 num_fec_packets, 520 code_index); 521 if (code_type == rs_code) { 522 CopyMetrics(&kMetricsReedSolomon[code_index], metrics_code); 523 } else if (code_type == xor_random_code) { 524 CopyMetrics(&kMetricsXorRandom[code_index], metrics_code); 525 } else if (code_type == xor_bursty_code) { 526 CopyMetrics(&kMetricsXorBursty[code_index], metrics_code); 527 } else { 528 assert(false); 529 } 530 } 531 532 void WriteOutMetricsAllFecCodes() { 533 std::string filename = test::OutputPath() + "data_metrics_all_codes"; 534 FILE* fp = fopen(filename.c_str(), "wb"); 535 // Loop through codes up to |kMaxMediaPacketsTest|. 536 int code_index = 0; 537 for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; 538 num_media_packets++) { 539 for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; 540 num_fec_packets++) { 541 fprintf(fp, "FOR CODE: (%d, %d) \n", num_media_packets, 542 num_fec_packets); 543 for (int k = 0; k < kNumLossModels; k++) { 544 float loss_rate = loss_model_[k].average_loss_rate; 545 float burst_length = loss_model_[k].average_burst_length; 546 fprintf(fp, "Loss rate = %.2f, Burst length = %.2f: %.4f %.4f %.4f" 547 " **** %.4f %.4f %.4f \n", 548 loss_rate, 549 burst_length, 550 100 * kMetricsReedSolomon[code_index].average_residual_loss[k], 551 100 * kMetricsXorRandom[code_index].average_residual_loss[k], 552 100 * kMetricsXorBursty[code_index].average_residual_loss[k], 553 kMetricsReedSolomon[code_index].variance_residual_loss[k], 554 kMetricsXorRandom[code_index].variance_residual_loss[k], 555 kMetricsXorBursty[code_index].variance_residual_loss[k]); 556 } 557 for (int gap = 0; gap < kGapSizeOutput; gap ++) { 558 double rs_residual_loss = ComputeResidualLossPerGap( 559 kMetricsReedSolomon[code_index], 560 gap, 561 num_fec_packets, 562 code_index); 563 double xor_random_residual_loss = ComputeResidualLossPerGap( 564 kMetricsXorRandom[code_index], 565 gap, 566 num_fec_packets, 567 code_index); 568 double xor_bursty_residual_loss = ComputeResidualLossPerGap( 569 kMetricsXorBursty[code_index], 570 gap, 571 num_fec_packets, 572 code_index); 573 fprintf(fp, "Residual loss as a function of gap " 574 "%d: %.4f %.4f %.4f \n", 575 gap, 576 rs_residual_loss, 577 xor_random_residual_loss, 578 xor_bursty_residual_loss); 579 } 580 fprintf(fp, "Recovery rate as a function of loss number \n"); 581 for (int loss = 1; loss <= num_media_packets + num_fec_packets; 582 loss ++) { 583 fprintf(fp, "For loss number %d: %.4f %.4f %.4f \n", 584 loss, 585 kMetricsReedSolomon[code_index]. 586 recovery_rate_per_loss[loss], 587 kMetricsXorRandom[code_index]. 588 recovery_rate_per_loss[loss], 589 kMetricsXorBursty[code_index]. 590 recovery_rate_per_loss[loss]); 591 } 592 fprintf(fp, "******************\n"); 593 fprintf(fp, "\n"); 594 code_index++; 595 } 596 } 597 fclose(fp); 598 } 599 600 void SetLossModels() { 601 int num_loss_rates = sizeof(kAverageLossRate) / 602 sizeof(*kAverageLossRate); 603 int num_burst_lengths = sizeof(kAverageBurstLength) / 604 sizeof(*kAverageBurstLength); 605 int num_loss_models = 0; 606 for (int k = 0; k < num_burst_lengths; k++) { 607 for (int k2 = 0; k2 < num_loss_rates; k2++) { 608 loss_model_[num_loss_models].average_loss_rate = kAverageLossRate[k2]; 609 loss_model_[num_loss_models].average_burst_length = 610 kAverageBurstLength[k]; 611 // First set of loss models are of random type. 612 if (k == 0) { 613 loss_model_[num_loss_models].loss_type = kRandomLossModel; 614 } else { 615 loss_model_[num_loss_models].loss_type = kBurstyLossModel; 616 } 617 num_loss_models++; 618 } 619 } 620 assert(num_loss_models == kNumLossModels); 621 } 622 623 void SetCodeParams() { 624 int code_index = 0; 625 for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; 626 num_media_packets++) { 627 for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; 628 num_fec_packets++) { 629 code_params_[code_index].num_media_packets = num_media_packets; 630 code_params_[code_index].num_fec_packets = num_fec_packets; 631 code_params_[code_index].protection_level = 632 static_cast<float>(num_fec_packets) / 633 static_cast<float>(num_media_packets + num_fec_packets); 634 for (int k = 0; k < kNumStatesDistribution; k++) { 635 code_params_[code_index].configuration_density[k] = 0; 636 } 637 code_index++; 638 } 639 } 640 max_num_codes_ = code_index; 641 } 642 643 // Make some basic checks on the packet masks. Return -1 if any of these 644 // checks fail. 645 int RejectInvalidMasks(int num_media_packets, int num_fec_packets) { 646 // Make sure every FEC packet protects something. 647 for (int i = 0; i < num_fec_packets; i++) { 648 int row_degree = 0; 649 for (int j = 0; j < num_media_packets; j++) { 650 if (fec_packet_masks_[i][j] == 1) { 651 row_degree++; 652 } 653 } 654 if (row_degree == 0) { 655 printf("Invalid mask: FEC packet has empty mask (does not protect " 656 "anything) %d %d %d \n", i, num_media_packets, num_fec_packets); 657 return -1; 658 } 659 } 660 // Mask sure every media packet has some protection. 661 for (int j = 0; j < num_media_packets; j++) { 662 int column_degree = 0; 663 for (int i = 0; i < num_fec_packets; i++) { 664 if (fec_packet_masks_[i][j] == 1) { 665 column_degree++; 666 } 667 } 668 if (column_degree == 0) { 669 printf("Invalid mask: Media packet has no protection at all %d %d %d " 670 "\n", j, num_media_packets, num_fec_packets); 671 return -1; 672 } 673 } 674 // Make sure we do not have two identical FEC packets. 675 for (int i = 0; i < num_fec_packets; i++) { 676 for (int i2 = i + 1; i2 < num_fec_packets; i2++) { 677 int overlap = 0; 678 for (int j = 0; j < num_media_packets; j++) { 679 if (fec_packet_masks_[i][j] == fec_packet_masks_[i2][j]) { 680 overlap++; 681 } 682 } 683 if (overlap == num_media_packets) { 684 printf("Invalid mask: Two FEC packets are identical %d %d %d %d \n", 685 i, i2, num_media_packets, num_fec_packets); 686 return -1; 687 } 688 } 689 } 690 // Avoid codes that have two media packets with full protection (all 1s in 691 // their corresponding columns). This would mean that if we lose those 692 // two packets, we can never recover them even if we receive all the other 693 // packets. Exclude the special cases of 1 or 2 FEC packets. 694 if (num_fec_packets > 2) { 695 for (int j = 0; j < num_media_packets; j++) { 696 for (int j2 = j + 1; j2 < num_media_packets; j2++) { 697 int degree = 0; 698 for (int i = 0; i < num_fec_packets; i++) { 699 if (fec_packet_masks_[i][j] == fec_packet_masks_[i][j2] && 700 fec_packet_masks_[i][j] == 1) { 701 degree++; 702 } 703 } 704 if (degree == num_fec_packets) { 705 printf("Invalid mask: Two media packets are have full degree " 706 "%d %d %d %d \n", j, j2, num_media_packets, num_fec_packets); 707 return -1; 708 } 709 } 710 } 711 } 712 return 0; 713 } 714 715 void GetPacketMaskConvertToBitMask(uint8_t* packet_mask, 716 int num_media_packets, 717 int num_fec_packets, 718 int mask_bytes_fec_packet, 719 CodeType code_type) { 720 for (int i = 0; i < num_fec_packets; i++) { 721 for (int j = 0; j < num_media_packets; j++) { 722 const uint8_t byte_mask = 723 packet_mask[i * mask_bytes_fec_packet + j / 8]; 724 const int bit_position = (7 - j % 8); 725 fec_packet_masks_[i][j] = 726 (byte_mask & (1 << bit_position)) >> bit_position; 727 fprintf(fp_mask_, "%d ", fec_packet_masks_[i][j]); 728 } 729 fprintf(fp_mask_, "\n"); 730 } 731 fprintf(fp_mask_, "\n"); 732 } 733 734 int ProcessXORPacketMasks(CodeType code_type, 735 FecMaskType fec_mask_type) { 736 int code_index = 0; 737 // Maximum number of media packets allowed for the mask type. 738 const int packet_mask_max = kMaxMediaPackets[fec_mask_type]; 739 uint8_t* packet_mask = new uint8_t[packet_mask_max * kMaskSizeLBitSet]; 740 // Loop through codes up to |kMaxMediaPacketsTest|. 741 for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; 742 num_media_packets++) { 743 const int mask_bytes_fec_packet = 744 (num_media_packets > 16) ? kMaskSizeLBitSet : kMaskSizeLBitClear; 745 internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets); 746 for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; 747 num_fec_packets++) { 748 memset(packet_mask, 0, num_media_packets * mask_bytes_fec_packet); 749 memcpy(packet_mask, mask_table.fec_packet_mask_table() 750 [num_media_packets - 1][num_fec_packets - 1], 751 num_fec_packets * mask_bytes_fec_packet); 752 // Convert to bit mask. 753 GetPacketMaskConvertToBitMask(packet_mask, 754 num_media_packets, 755 num_fec_packets, 756 mask_bytes_fec_packet, 757 code_type); 758 if (RejectInvalidMasks(num_media_packets, num_fec_packets) < 0) { 759 return -1; 760 } 761 // Compute the metrics for this code/mask. 762 ComputeMetricsForCode(code_type, 763 code_index); 764 code_index++; 765 } 766 } 767 assert(code_index == kNumberCodes); 768 delete [] packet_mask; 769 return 0; 770 } 771 772 void ProcessRS(CodeType code_type) { 773 int code_index = 0; 774 for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest; 775 num_media_packets++) { 776 for (int num_fec_packets = 1; num_fec_packets <= num_media_packets; 777 num_fec_packets++) { 778 // Compute the metrics for this code type. 779 ComputeMetricsForCode(code_type, 780 code_index); 781 code_index++; 782 } 783 } 784 } 785 786 // Compute metrics for all code types and sizes. 787 void ComputeMetricsAllCodes() { 788 SetLossModels(); 789 SetCodeParams(); 790 // Get metrics for XOR code with packet masks of random type. 791 std::string filename = test::OutputPath() + "data_packet_masks"; 792 fp_mask_ = fopen(filename.c_str(), "wb"); 793 fprintf(fp_mask_, "MASK OF TYPE RANDOM: \n"); 794 EXPECT_EQ(ProcessXORPacketMasks(xor_random_code, kFecMaskRandom), 0); 795 // Get metrics for XOR code with packet masks of bursty type. 796 fprintf(fp_mask_, "MASK OF TYPE BURSTY: \n"); 797 EXPECT_EQ(ProcessXORPacketMasks(xor_bursty_code, kFecMaskBursty), 0); 798 fclose(fp_mask_); 799 // Get metrics for Reed-Solomon code. 800 ProcessRS(rs_code); 801 } 802 }; 803 804 // Verify that the average residual loss, averaged over loss models 805 // appropriate to each mask type, is below some maximum acceptable level. The 806 // acceptable levels are read in from a file, and correspond to a current set 807 // of packet masks. The levels for each code may be updated over time. 808 TEST_F(FecPacketMaskMetricsTest, FecXorMaxResidualLoss) { 809 SetLossModels(); 810 SetCodeParams(); 811 ComputeMetricsAllCodes(); 812 WriteOutMetricsAllFecCodes(); 813 int num_loss_rates = sizeof(kAverageLossRate) / 814 sizeof(*kAverageLossRate); 815 int num_burst_lengths = sizeof(kAverageBurstLength) / 816 sizeof(*kAverageBurstLength); 817 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 818 double sum_residual_loss_random_mask_random_loss = 0.0; 819 double sum_residual_loss_bursty_mask_bursty_loss = 0.0; 820 // Compute the sum residual loss across the models, for each mask type. 821 for (int k = 0; k < kNumLossModels; k++) { 822 if (loss_model_[k].loss_type == kRandomLossModel) { 823 sum_residual_loss_random_mask_random_loss += 824 kMetricsXorRandom[code_index].average_residual_loss[k]; 825 } else if (loss_model_[k].loss_type == kBurstyLossModel) { 826 sum_residual_loss_bursty_mask_bursty_loss += 827 kMetricsXorBursty[code_index].average_residual_loss[k]; 828 } 829 } 830 float average_residual_loss_random_mask_random_loss = 831 sum_residual_loss_random_mask_random_loss / num_loss_rates; 832 float average_residual_loss_bursty_mask_bursty_loss = 833 sum_residual_loss_bursty_mask_bursty_loss / 834 (num_loss_rates * (num_burst_lengths - 1)); 835 const float ref_random_mask = kMaxResidualLossRandomMask[code_index]; 836 const float ref_bursty_mask = kMaxResidualLossBurstyMask[code_index]; 837 EXPECT_LE(average_residual_loss_random_mask_random_loss, ref_random_mask); 838 EXPECT_LE(average_residual_loss_bursty_mask_bursty_loss, ref_bursty_mask); 839 } 840 } 841 842 // Verify the behavior of the XOR codes vs the RS codes. 843 // For random loss model with average loss rates <= the code protection level, 844 // the RS code (optimal MDS code) is more efficient than XOR codes. 845 // However, for larger loss rates (above protection level) and/or bursty 846 // loss models, the RS is not always more efficient than XOR (though in most 847 // cases it still is). 848 TEST_F(FecPacketMaskMetricsTest, FecXorVsRS) { 849 SetLossModels(); 850 SetCodeParams(); 851 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 852 for (int k = 0; k < kNumLossModels; k++) { 853 float loss_rate = loss_model_[k].average_loss_rate; 854 float protection_level = code_params_[code_index].protection_level; 855 // Under these conditions we expect XOR to not be better than RS. 856 if (loss_model_[k].loss_type == kRandomLossModel && 857 loss_rate <= protection_level) { 858 EXPECT_GE(kMetricsXorRandom[code_index].average_residual_loss[k], 859 kMetricsReedSolomon[code_index].average_residual_loss[k]); 860 EXPECT_GE(kMetricsXorBursty[code_index].average_residual_loss[k], 861 kMetricsReedSolomon[code_index].average_residual_loss[k]); 862 } 863 // TODO (marpan): There are some cases (for high loss rates and/or 864 // burst loss models) where XOR is better than RS. Is there some pattern 865 // we can identify and enforce as a constraint? 866 } 867 } 868 } 869 870 // Verify the trend (change) in the average residual loss, as a function of 871 // loss rate, of the XOR code relative to the RS code. 872 // The difference between XOR and RS should not get worse as we increase 873 // the average loss rate. 874 TEST_F(FecPacketMaskMetricsTest, FecTrendXorVsRsLossRate) { 875 SetLossModels(); 876 SetCodeParams(); 877 // TODO (marpan): Examine this further to see if the condition can be strictly 878 // satisfied (i.e., scale = 1.0) for all codes with different/better masks. 879 double scale = 0.90; 880 int num_loss_rates = sizeof(kAverageLossRate) / 881 sizeof(*kAverageLossRate); 882 int num_burst_lengths = sizeof(kAverageBurstLength) / 883 sizeof(*kAverageBurstLength); 884 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 885 for (int i = 0; i < num_burst_lengths; i++) { 886 for (int j = 0; j < num_loss_rates - 1; j++) { 887 int k = num_loss_rates * i + j; 888 // For XOR random. 889 if (kMetricsXorRandom[code_index].average_residual_loss[k] > 890 kMetricsReedSolomon[code_index].average_residual_loss[k]) { 891 double diff_rs_xor_random_loss1 = 892 (kMetricsXorRandom[code_index].average_residual_loss[k] - 893 kMetricsReedSolomon[code_index].average_residual_loss[k]) / 894 kMetricsXorRandom[code_index].average_residual_loss[k]; 895 double diff_rs_xor_random_loss2 = 896 (kMetricsXorRandom[code_index].average_residual_loss[k+1] - 897 kMetricsReedSolomon[code_index].average_residual_loss[k+1]) / 898 kMetricsXorRandom[code_index].average_residual_loss[k+1]; 899 EXPECT_GE(diff_rs_xor_random_loss1, scale * diff_rs_xor_random_loss2); 900 } 901 // TODO (marpan): Investigate the cases for the bursty mask where 902 // this trend is not strictly satisfied. 903 } 904 } 905 } 906 } 907 908 // Verify the average residual loss behavior via the protection level and 909 // the code length. The average residual loss for a given (k1,m1) code 910 // should generally be higher than that of another code (k2,m2), which has 911 // either of the two conditions satisfied: 912 // 1) higher protection & code length at least as large: (k2+m2) >= (k1+m1), 913 // 2) equal protection and larger code length: (k2+m2) > (k1+m1). 914 // Currently does not hold for some cases of the XOR code with random mask. 915 TEST_F(FecPacketMaskMetricsTest, FecBehaviorViaProtectionLevelAndLength) { 916 SetLossModels(); 917 SetCodeParams(); 918 for (int code_index1 = 0; code_index1 < max_num_codes_; code_index1++) { 919 float protection_level1 = code_params_[code_index1].protection_level; 920 int length1 = code_params_[code_index1].num_media_packets + 921 code_params_[code_index1].num_fec_packets; 922 for (int code_index2 = 0; code_index2 < max_num_codes_; code_index2++) { 923 float protection_level2 = code_params_[code_index2].protection_level; 924 int length2 = code_params_[code_index2].num_media_packets + 925 code_params_[code_index2].num_fec_packets; 926 // Codes with higher protection are more efficient, conditioned on the 927 // length of the code (higher protection but shorter length codes are 928 // generally not more efficient). For two codes with equal protection, 929 // the longer code is generally more efficient. For high loss rate 930 // models, this condition may be violated for some codes with equal or 931 // very close protection levels. High loss rate case is excluded below. 932 if ((protection_level2 > protection_level1 && length2 >= length1) || 933 (protection_level2 == protection_level1 && length2 > length1)) { 934 for (int k = 0; k < kNumLossModels; k++) { 935 float loss_rate = loss_model_[k].average_loss_rate; 936 if (loss_rate < loss_rate_upper_threshold) { 937 EXPECT_LT( 938 kMetricsReedSolomon[code_index2].average_residual_loss[k], 939 kMetricsReedSolomon[code_index1].average_residual_loss[k]); 940 // TODO (marpan): There are some corner cases where this is not 941 // satisfied with the current packet masks. Look into updating 942 // these cases to see if this behavior should/can be satisfied, 943 // with overall lower residual loss for those XOR codes. 944 // EXPECT_LT( 945 // kMetricsXorBursty[code_index2].average_residual_loss[k], 946 // kMetricsXorBursty[code_index1].average_residual_loss[k]); 947 // EXPECT_LT( 948 // kMetricsXorRandom[code_index2].average_residual_loss[k], 949 // kMetricsXorRandom[code_index1].average_residual_loss[k]); 950 } 951 } 952 } 953 } 954 } 955 } 956 957 // Verify the beheavior of the variance of the XOR codes. 958 // The partial recovery of the XOR versus the all or nothing behavior of the RS 959 // code means that the variance of the residual loss for XOR should generally 960 // not be worse than RS. 961 TEST_F(FecPacketMaskMetricsTest, FecVarianceBehaviorXorVsRs) { 962 SetLossModels(); 963 SetCodeParams(); 964 // The condition is not strictly satisfied with the current masks, 965 // i.e., for some codes, the variance of XOR may be slightly higher than RS. 966 // TODO (marpan): Examine this further to see if the condition can be strictly 967 // satisfied (i.e., scale = 1.0) for all codes with different/better masks. 968 double scale = 0.95; 969 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 970 for (int k = 0; k < kNumLossModels; k++) { 971 EXPECT_LE(scale * 972 kMetricsXorRandom[code_index].variance_residual_loss[k], 973 kMetricsReedSolomon[code_index].variance_residual_loss[k]); 974 EXPECT_LE(scale * 975 kMetricsXorBursty[code_index].variance_residual_loss[k], 976 kMetricsReedSolomon[code_index].variance_residual_loss[k]); 977 } 978 } 979 } 980 981 // For the bursty mask type, the residual loss must be strictly zero for all 982 // consecutive losses (i.e, gap = 0) with number of losses <= num_fec_packets. 983 // This is a design property of the bursty mask type. 984 TEST_F(FecPacketMaskMetricsTest, FecXorBurstyPerfectRecoveryConsecutiveLoss) { 985 SetLossModels(); 986 SetCodeParams(); 987 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 988 int num_fec_packets = code_params_[code_index].num_fec_packets; 989 for (int loss = 1; loss <= num_fec_packets; loss++) { 990 int index = loss; // |gap| is zero. 991 EXPECT_EQ(kMetricsXorBursty[code_index]. 992 residual_loss_per_loss_gap[index], 0.0); 993 } 994 } 995 } 996 997 // The XOR codes with random mask type are generally better than the ones with 998 // bursty mask type, for random loss models at low loss rates. 999 // The XOR codes with bursty mask types are generally better than the one with 1000 // random mask type, for bursty loss models and/or high loss rates. 1001 // TODO (marpan): Enable this test when some of the packet masks are updated. 1002 // Some isolated cases of the codes don't pass this currently. 1003 /* 1004 TEST_F(FecPacketMaskMetricsTest, FecXorRandomVsBursty) { 1005 SetLossModels(); 1006 SetCodeParams(); 1007 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 1008 double sum_residual_loss_random_mask_random_loss = 0.0; 1009 double sum_residual_loss_bursty_mask_random_loss = 0.0; 1010 double sum_residual_loss_random_mask_bursty_loss = 0.0; 1011 double sum_residual_loss_bursty_mask_bursty_loss = 0.0; 1012 // Compute the sum residual loss across the models, for each mask type. 1013 for (int k = 0; k < kNumLossModels; k++) { 1014 float loss_rate = loss_model_[k].average_loss_rate; 1015 if (loss_model_[k].loss_type == kRandomLossModel && 1016 loss_rate < loss_rate_upper_threshold) { 1017 sum_residual_loss_random_mask_random_loss += 1018 kMetricsXorRandom[code_index].average_residual_loss[k]; 1019 sum_residual_loss_bursty_mask_random_loss += 1020 kMetricsXorBursty[code_index].average_residual_loss[k]; 1021 } else if (loss_model_[k].loss_type == kBurstyLossModel && 1022 loss_rate > loss_rate_lower_threshold) { 1023 sum_residual_loss_random_mask_bursty_loss += 1024 kMetricsXorRandom[code_index].average_residual_loss[k]; 1025 sum_residual_loss_bursty_mask_bursty_loss += 1026 kMetricsXorBursty[code_index].average_residual_loss[k]; 1027 } 1028 } 1029 EXPECT_LE(sum_residual_loss_random_mask_random_loss, 1030 sum_residual_loss_bursty_mask_random_loss); 1031 EXPECT_LE(sum_residual_loss_bursty_mask_bursty_loss, 1032 sum_residual_loss_random_mask_bursty_loss); 1033 } 1034 } 1035 */ 1036 1037 // Verify that the average recovery rate for each code is equal or above some 1038 // threshold, for certain loss number conditions. 1039 TEST_F(FecPacketMaskMetricsTest, FecRecoveryRateUnderLossConditions) { 1040 SetLossModels(); 1041 SetCodeParams(); 1042 for (int code_index = 0; code_index < max_num_codes_; code_index++) { 1043 int num_media_packets = code_params_[code_index].num_media_packets; 1044 int num_fec_packets = code_params_[code_index].num_fec_packets; 1045 // Perfect recovery (|recovery_rate_per_loss| == 1) is expected for 1046 // |loss_number| = 1, for all codes. 1047 int loss_number = 1; 1048 EXPECT_EQ(kMetricsReedSolomon[code_index]. 1049 recovery_rate_per_loss[loss_number], 1.0); 1050 EXPECT_EQ(kMetricsXorRandom[code_index]. 1051 recovery_rate_per_loss[loss_number], 1.0); 1052 EXPECT_EQ(kMetricsXorBursty[code_index]. 1053 recovery_rate_per_loss[loss_number], 1.0); 1054 // For |loss_number| = |num_fec_packets| / 2, we expect the following: 1055 // Perfect recovery for RS, and recovery for XOR above the threshold. 1056 loss_number = num_fec_packets / 2 > 0 ? num_fec_packets / 2 : 1; 1057 EXPECT_EQ(kMetricsReedSolomon[code_index]. 1058 recovery_rate_per_loss[loss_number], 1.0); 1059 EXPECT_GE(kMetricsXorRandom[code_index]. 1060 recovery_rate_per_loss[loss_number], kRecoveryRateXorRandom[0]); 1061 EXPECT_GE(kMetricsXorBursty[code_index]. 1062 recovery_rate_per_loss[loss_number], kRecoveryRateXorBursty[0]); 1063 // For |loss_number| = |num_fec_packets|, we expect the following: 1064 // Perfect recovery for RS, and recovery for XOR above the threshold. 1065 loss_number = num_fec_packets; 1066 EXPECT_EQ(kMetricsReedSolomon[code_index]. 1067 recovery_rate_per_loss[loss_number], 1.0); 1068 EXPECT_GE(kMetricsXorRandom[code_index]. 1069 recovery_rate_per_loss[loss_number], kRecoveryRateXorRandom[1]); 1070 EXPECT_GE(kMetricsXorBursty[code_index]. 1071 recovery_rate_per_loss[loss_number], kRecoveryRateXorBursty[1]); 1072 // For |loss_number| = |num_fec_packets| + 1, we expect the following: 1073 // Zero recovery for RS, but non-zero recovery for XOR. 1074 if (num_fec_packets > 1 && num_media_packets > 2) { 1075 loss_number = num_fec_packets + 1; 1076 EXPECT_EQ(kMetricsReedSolomon[code_index]. 1077 recovery_rate_per_loss[loss_number], 0.0); 1078 EXPECT_GE(kMetricsXorRandom[code_index]. 1079 recovery_rate_per_loss[loss_number], 1080 kRecoveryRateXorRandom[2]); 1081 EXPECT_GE(kMetricsXorBursty[code_index]. 1082 recovery_rate_per_loss[loss_number], 1083 kRecoveryRateXorBursty[2]); 1084 } 1085 } 1086 } 1087 1088 } // namespace webrtc 1089