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
54namespace webrtc {
55
56// Maximum number of media packets allows for XOR (RFC 5109) code.
57enum { kMaxNumberMediaPackets = 48 };
58
59// Maximum number of media packets allowed for each mask type.
60const 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).
64const int kMaxMediaPacketsTest = 12;
65
66// Maximum number of FEC codes considered in this test.
67const int kNumberCodes = kMaxMediaPacketsTest * (kMaxMediaPacketsTest + 1) / 2;
68
69// Maximum gap size for characterizing the consecutiveness of the loss.
70const int kMaxGapSize = 2 * kMaxMediaPacketsTest;
71
72// Number of gap levels written to file/output.
73const int kGapSizeOutput = 5;
74
75// Maximum number of states for characterizing the residual loss distribution.
76const int kNumStatesDistribution = 2 * kMaxMediaPacketsTest * kMaxGapSize + 1;
77
78// The code type.
79enum 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.
86struct 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.
98enum LossModelType {
99  kRandomLossModel,
100  kBurstyLossModel
101};
102
103struct LossModel {
104  LossModelType loss_type;
105  float average_loss_rate;
106  float average_burst_length;
107};
108
109// Average loss rates.
110const 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.
116const 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.
120const 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.
126float loss_rate_upper_threshold = 0.20f;
127float 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.
132const float kRecoveryRateXorRandom[3] = { 0.94f, 0.50f, 0.19f };
133const 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.
138struct 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
153MetricsFecCode kMetricsXorRandom[kNumberCodes];
154MetricsFecCode kMetricsXorBursty[kNumberCodes];
155MetricsFecCode kMetricsReedSolomon[kNumberCodes];
156
157class 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.
808TEST_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).
848TEST_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.
874TEST_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.
915TEST_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.
961TEST_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.
984TEST_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/*
1004TEST_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.
1039TEST_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