aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/rtp_rtcp/test/testFec/test_packet_masks_metrics.cc
blob: 466214c740e229d17a8a50d57803c463d47d8033 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

/*
 * The purpose of this test is to compute metrics to characterize the properties
 * and efficiency of the packets masks used in the generic XOR FEC code.
 *
 * The metrics measure the efficiency (recovery potential or residual loss) of
 * the FEC code, under various statistical loss models for the packet/symbol
 * loss events. Various constraints on the behavior of these metrics are
 * verified, and compared to the reference RS (Reed-Solomon) code. This serves
 * in some way as a basic check/benchmark for the packet masks.
 *
 * By an FEC code, we mean an erasure packet/symbol code, characterized by:
 * (1) The code size parameters (k,m), where k = number of source/media packets,
 * and m = number of FEC packets,
 * (2) The code type: XOR or RS.
 * In the case of XOR, the residual loss is determined via the set of packet
 * masks (generator matrix). In the case of RS, the residual loss is determined
 * directly from the MDS (maximum distance separable) property of RS.
 *
 * Currently two classes of packets masks are available (random type and bursty
 * type), so three codes are considered below: RS, XOR-random, and XOR-bursty.
 * The bursty class is defined up to k=12, so (k=12,m=12) is largest code size
 * considered in this test.
 *
 * The XOR codes are defined via the RFC 5109 and correspond to the class of
 * LDGM (low density generator matrix) codes, which is a subset of the LDPC
 * (low density parity check) codes. Future implementation will consider
 * extending our XOR codes to include LDPC codes, which explicitly include
 * protection of FEC packets.
 *
 * The type of packet/symbol loss models considered in this test are:
 * (1) Random loss: Bernoulli process, characterized by the average loss rate.
 * (2) Bursty loss: Markov chain (Gilbert-Elliot model), characterized by two
 * parameters: average loss rate and average burst length.
*/

#include <math.h>

#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/modules/rtp_rtcp/source/forward_error_correction_internal.h"
#include "webrtc/modules/rtp_rtcp/test/testFec/average_residual_loss_xor_codes.h"
#include "webrtc/test/testsupport/fileutils.h"

namespace webrtc {

// Maximum number of media packets allows for XOR (RFC 5109) code.
enum { kMaxNumberMediaPackets = 48 };

// Maximum number of media packets allowed for each mask type.
const uint16_t kMaxMediaPackets[] = {kMaxNumberMediaPackets, 12};

// Maximum gap size for characterizing the consecutiveness of the loss.
const int kMaxGapSize = 2 * kMaxMediaPacketsTest;

// Number of gap levels written to file/output.
const int kGapSizeOutput = 5;

// Maximum number of states for characterizing the residual loss distribution.
const int kNumStatesDistribution = 2 * kMaxMediaPacketsTest * kMaxGapSize + 1;

// The code type.
enum CodeType {
  xor_random_code,    // XOR with random mask type.
  xor_bursty_code,    // XOR with bursty mask type.
  rs_code             // Reed_solomon.
};

// The code size parameters.
struct CodeSizeParams {
  int num_media_packets;
  int num_fec_packets;
  // Protection level: num_fec_packets / (num_media_packets + num_fec_packets).
  float protection_level;
  // Number of loss configurations, for a given loss number and gap number.
  // The gap number refers to the maximum gap/hole of a loss configuration
  // (used to measure the "consecutiveness" of the loss).
  int configuration_density[kNumStatesDistribution];
};

// The type of loss models.
enum LossModelType {
  kRandomLossModel,
  kBurstyLossModel
};

struct LossModel {
  LossModelType loss_type;
  float average_loss_rate;
  float average_burst_length;
};

// Average loss rates.
const float kAverageLossRate[] = { 0.025f, 0.05f, 0.1f, 0.25f };

// Average burst lengths. The case of |kAverageBurstLength = 1.0| refers to
// the random model. Note that for the random (Bernoulli) model, the average
// burst length is determined by the average loss rate, i.e.,
// AverageBurstLength = 1 / (1 - AverageLossRate) for random model.
const float kAverageBurstLength[] = { 1.0f, 2.0f, 4.0f };

// Total number of loss models: For each burst length case, there are
// a number of models corresponding to the loss rates.
const int kNumLossModels =  (sizeof(kAverageBurstLength) /
    sizeof(*kAverageBurstLength)) * (sizeof(kAverageLossRate) /
        sizeof(*kAverageLossRate));

// Thresholds on the average loss rate of the packet loss model, below which
// certain properties of the codes are expected.
float loss_rate_upper_threshold = 0.20f;
float loss_rate_lower_threshold = 0.025f;

// Set of thresholds on the expected average recovery rate, for each code type.
// These are global thresholds for now; in future version we may condition them
// on the code length/size and protection level.
const float kRecoveryRateXorRandom[3] = { 0.94f, 0.50f, 0.19f };
const float kRecoveryRateXorBursty[3] = { 0.90f, 0.54f, 0.22f };

// Metrics for a given FEC code; each code is defined by the code type
// (RS, XOR-random/bursty), and the code size parameters (k,m), where
// k = num_media_packets, m = num_fec_packets.
struct MetricsFecCode {
  // The average and variance of the residual loss, as a function of the
  // packet/symbol loss model. The average/variance is computed by averaging
  // over all loss configurations wrt the loss probability given by the
  // underlying loss model.
  double average_residual_loss[kNumLossModels];
  double variance_residual_loss[kNumLossModels];
  // The residual loss, as a function of the loss number and the gap number of
  // the loss configurations. The gap number refers to the maximum gap/hole of
  // a loss configuration (used to measure the "consecutiveness" of the loss).
  double residual_loss_per_loss_gap[kNumStatesDistribution];
  // The recovery rate as a function of the loss number.
  double recovery_rate_per_loss[2 * kMaxMediaPacketsTest + 1];
};

MetricsFecCode kMetricsXorRandom[kNumberCodes];
MetricsFecCode kMetricsXorBursty[kNumberCodes];
MetricsFecCode kMetricsReedSolomon[kNumberCodes];

class FecPacketMaskMetricsTest : public ::testing::Test {
 protected:
  FecPacketMaskMetricsTest() { }

  int max_num_codes_;
  LossModel loss_model_[kNumLossModels];
  CodeSizeParams code_params_[kNumberCodes];

  uint8_t fec_packet_masks_[kMaxNumberMediaPackets][kMaxNumberMediaPackets];
  FILE* fp_mask_;

  // Measure of the gap of the loss for configuration given by |state|.
  // This is to measure degree of consecutiveness for the loss configuration.
  // Useful if the packets are sent out in order of sequence numbers and there
  // is little/no re-ordering during transmission.
  int GapLoss(int tot_num_packets, uint8_t* state) {
    int max_gap_loss = 0;
    // Find the first loss.
    int first_loss = 0;
    for (int i = 0; i < tot_num_packets; i++) {
      if (state[i] == 1) {
        first_loss = i;
        break;
      }
    }
    int prev_loss = first_loss;
    for (int i = first_loss + 1; i < tot_num_packets; i++) {
      if (state[i] == 1) {  // Lost state.
        int gap_loss = (i - prev_loss) - 1;
        if (gap_loss > max_gap_loss) {
          max_gap_loss = gap_loss;
        }
        prev_loss = i;
      }
    }
    return max_gap_loss;
  }

  // Returns the number of recovered media packets for the XOR code, given the
  // packet mask |fec_packet_masks_|, for the loss state/configuration given by
  // |state|.
  int RecoveredMediaPackets(int num_media_packets,
                            int num_fec_packets,
                            uint8_t* state) {
    rtc::scoped_ptr<uint8_t[]> state_tmp(
        new uint8_t[num_media_packets + num_fec_packets]);
    memcpy(state_tmp.get(), state, num_media_packets + num_fec_packets);
    int num_recovered_packets = 0;
    bool loop_again = true;
    while (loop_again) {
      loop_again = false;
      bool recovered_new_packet = false;
      // Check if we can recover anything: loop over all possible FEC packets.
      for (int i = 0; i < num_fec_packets; i++) {
        if (state_tmp[i + num_media_packets] == 0) {
          // We have this FEC packet.
          int num_packets_in_mask = 0;
          int num_received_packets_in_mask = 0;
          for (int j = 0; j < num_media_packets; j++) {
            if (fec_packet_masks_[i][j] == 1) {
              num_packets_in_mask++;
              if (state_tmp[j] == 0) {
                num_received_packets_in_mask++;
              }
            }
          }
          if ((num_packets_in_mask - 1) == num_received_packets_in_mask) {
            // We can recover the missing media packet for this FEC packet.
            num_recovered_packets++;
            recovered_new_packet = true;
            int jsel = -1;
            int check_num_recovered = 0;
            // Update the state with newly recovered media packet.
            for (int j = 0; j < num_media_packets; j++) {
              if (fec_packet_masks_[i][j] == 1 && state_tmp[j] == 1) {
                // This is the lost media packet we will recover.
                jsel = j;
                check_num_recovered++;
              }
            }
            // Check that we can only recover 1 packet.
            assert(check_num_recovered == 1);
            // Update the state with the newly recovered media packet.
            state_tmp[jsel] = 0;
          }
        }
      }  // Go to the next FEC packet in the loop.
      // If we have recovered at least one new packet in this FEC loop,
      // go through loop again, otherwise we leave loop.
      if (recovered_new_packet) {
        loop_again = true;
      }
    }
    return num_recovered_packets;
  }

  // Compute the probability of occurence of the loss state/configuration,
  // given by |state|, for all the loss models considered in this test.
  void ComputeProbabilityWeight(double* prob_weight,
                                uint8_t* state,
                                int tot_num_packets) {
    // Loop over the loss models.
    for (int k = 0; k < kNumLossModels; k++) {
      double loss_rate = static_cast<double>(
          loss_model_[k].average_loss_rate);
      double burst_length = static_cast<double>(
          loss_model_[k].average_burst_length);
      double result = 1.0;
      if (loss_model_[k].loss_type == kRandomLossModel) {
        for (int i = 0; i < tot_num_packets; i++) {
          if (state[i] == 0) {
            result *= (1.0 - loss_rate);
          } else {
            result *= loss_rate;
          }
        }
      } else {  // Gilbert-Elliot model for burst model.
        assert(loss_model_[k].loss_type == kBurstyLossModel);
        // Transition probabilities: from previous to current state.
        // Prob. of previous = lost --> current = received.
        double prob10 = 1.0 / burst_length;
        // Prob. of previous = lost --> currrent = lost.
        double prob11 = 1.0 - prob10;
        // Prob. of previous = received --> current = lost.
        double prob01 = prob10 * (loss_rate / (1.0 - loss_rate));
        // Prob. of previous = received --> current = received.
        double prob00 = 1.0 - prob01;

        // Use stationary probability for first state/packet.
        if (state[0] == 0) {  // Received
          result = (1.0 - loss_rate);
        } else {   // Lost
          result = loss_rate;
        }

        // Subsequent states: use transition probabilities.
        for (int i = 1; i < tot_num_packets; i++) {
          // Current state is received
          if (state[i] == 0) {
            if (state[i-1] == 0) {
              result *= prob00;   // Previous received, current received.
              } else {
                result *= prob10;  // Previous lost, current received.
              }
          } else {  // Current state is lost
            if (state[i-1] == 0) {
              result *= prob01;  // Previous received, current lost.
            } else {
              result *= prob11;  // Previous lost, current lost.
            }
          }
        }
      }
      prob_weight[k] = result;
    }
  }

  void CopyMetrics(MetricsFecCode* metrics_output,
                   MetricsFecCode metrics_input) {
    memcpy(metrics_output->average_residual_loss,
           metrics_input.average_residual_loss,
           sizeof(double) * kNumLossModels);
    memcpy(metrics_output->variance_residual_loss,
           metrics_input.variance_residual_loss,
           sizeof(double) * kNumLossModels);
    memcpy(metrics_output->residual_loss_per_loss_gap,
           metrics_input.residual_loss_per_loss_gap,
           sizeof(double) * kNumStatesDistribution);
    memcpy(metrics_output->recovery_rate_per_loss,
           metrics_input.recovery_rate_per_loss,
           sizeof(double) * 2 * kMaxMediaPacketsTest);
  }

  // Compute the residual loss per gap, by summing the
  // |residual_loss_per_loss_gap| over all loss configurations up to loss number
  // = |num_fec_packets|.
  double ComputeResidualLossPerGap(MetricsFecCode metrics,
                                   int gap_number,
                                   int num_fec_packets,
                                   int code_index) {
    double residual_loss_gap = 0.0;
    int tot_num_configs = 0;
    for (int loss = 1; loss <= num_fec_packets; loss++) {
      int index = gap_number * (2 * kMaxMediaPacketsTest) + loss;
      residual_loss_gap += metrics.residual_loss_per_loss_gap[index];
      tot_num_configs +=
          code_params_[code_index].configuration_density[index];
    }
    // Normalize, to compare across code sizes.
    if (tot_num_configs > 0) {
      residual_loss_gap = residual_loss_gap /
          static_cast<double>(tot_num_configs);
    }
    return residual_loss_gap;
  }

  // Compute the recovery rate per loss number, by summing the
  // |residual_loss_per_loss_gap| over all gap configurations.
  void ComputeRecoveryRatePerLoss(MetricsFecCode* metrics,
                                  int num_media_packets,
                                  int num_fec_packets,
                                  int code_index) {
    for (int loss = 1; loss <= num_media_packets + num_fec_packets; loss++) {
      metrics->recovery_rate_per_loss[loss] = 0.0;
      int tot_num_configs = 0;
      double arl = 0.0;
      for (int gap = 0; gap < kMaxGapSize; gap ++) {
        int index = gap * (2 * kMaxMediaPacketsTest) + loss;
        arl += metrics->residual_loss_per_loss_gap[index];
        tot_num_configs +=
            code_params_[code_index].configuration_density[index];
      }
      // Normalize, to compare across code sizes.
      if (tot_num_configs > 0) {
        arl = arl / static_cast<double>(tot_num_configs);
      }
      // Recovery rate for a given loss |loss| is 1 minus the scaled |arl|,
      // where the scale factor is relative to code size/parameters.
      double scaled_loss = static_cast<double>(loss * num_media_packets) /
          static_cast<double>(num_media_packets + num_fec_packets);
      metrics->recovery_rate_per_loss[loss] = 1.0 - arl / scaled_loss;
    }
  }

  void SetMetricsZero(MetricsFecCode* metrics) {
    memset(metrics->average_residual_loss, 0, sizeof(double) * kNumLossModels);
    memset(metrics->variance_residual_loss, 0, sizeof(double) * kNumLossModels);
    memset(metrics->residual_loss_per_loss_gap, 0,
           sizeof(double) * kNumStatesDistribution);
    memset(metrics->recovery_rate_per_loss, 0,
           sizeof(double) * 2 * kMaxMediaPacketsTest + 1);
  }

  // Compute the metrics for an FEC code, given by the code type |code_type|
  // (XOR-random/ bursty or RS), and by the code index |code_index|
  // (which containes the code size parameters/protection length).
  void ComputeMetricsForCode(CodeType code_type,
                             int code_index) {
    rtc::scoped_ptr<double[]> prob_weight(new double[kNumLossModels]);
    memset(prob_weight.get() , 0, sizeof(double) * kNumLossModels);
    MetricsFecCode metrics_code;
    SetMetricsZero(&metrics_code);

    int num_media_packets = code_params_[code_index].num_media_packets;
    int num_fec_packets = code_params_[code_index].num_fec_packets;
    int tot_num_packets = num_media_packets + num_fec_packets;
    rtc::scoped_ptr<uint8_t[]> state(new uint8_t[tot_num_packets]);
    memset(state.get() , 0, tot_num_packets);

    int num_loss_configurations = static_cast<int>(pow(2.0f, tot_num_packets));
    // Loop over all loss configurations for the symbol sequence of length
    // |tot_num_packets|. In this version we process up to (k=12, m=12) codes,
    // and get exact expressions for the residual loss.
    // TODO(marpan): For larger codes, loop over some random sample of loss
    // configurations, sampling driven by the underlying statistical loss model
    // (importance sampling).

    // The symbols/packets are arranged as a sequence of source/media packets
    // followed by FEC packets. This is the sequence ordering used in the RTP.
    // A configuration refers to a sequence of received/lost (0/1 bit) states
    // for the string of packets/symbols. For example, for a (k=4,m=3) code
    // (4 media packets, 3 FEC packets), with 2 losses (one media and one FEC),
    // the loss configurations is:
    // Media1   Media2   Media3   Media4   FEC1   FEC2   FEC3
    //   0         0        1       0        0      1     0
    for (int i = 1; i < num_loss_configurations; i++) {
      // Counter for number of packets lost.
      int num_packets_lost = 0;
      // Counters for the number of media packets lost.
      int num_media_packets_lost = 0;

      // Map configuration number to a loss state.
      for (int j = 0; j < tot_num_packets; j++) {
        state[j] = 0;  // Received state.
        int bit_value = i >> (tot_num_packets - j - 1) & 1;
        if (bit_value == 1) {
          state[j] = 1;  // Lost state.
          num_packets_lost++;
           if (j < num_media_packets) {
             num_media_packets_lost++;
           }
        }
      }  // Done with loop over total number of packets.
      assert(num_media_packets_lost <= num_media_packets);
      assert(num_packets_lost <= tot_num_packets && num_packets_lost > 0);
      double residual_loss = 0.0;
      // Only need to compute residual loss (number of recovered packets) for
      // configurations that have at least one media packet lost.
      if (num_media_packets_lost >= 1) {
        // Compute the number of recovered packets.
        int num_recovered_packets = 0;
        if (code_type == xor_random_code || code_type == xor_bursty_code) {
          num_recovered_packets = RecoveredMediaPackets(num_media_packets,
                                                        num_fec_packets,
                                                        state.get());
        } else {
          // For the RS code, we can either completely recover all the packets
          // if the loss is less than or equal to the number of FEC packets,
          // otherwise we can recover none of the missing packets. This is the
          // all or nothing (MDS) property of the RS code.
          if (num_packets_lost <= num_fec_packets) {
            num_recovered_packets = num_media_packets_lost;
          }
        }
        assert(num_recovered_packets <= num_media_packets);
        // Compute the residual loss. We only care about recovering media/source
        // packets, so residual loss is based on lost/recovered media packets.
        residual_loss = static_cast<double>(num_media_packets_lost -
                                            num_recovered_packets);
        // Compute the probability weights for this configuration.
        ComputeProbabilityWeight(prob_weight.get(),
                                 state.get(),
                                 tot_num_packets);
        // Update the average and variance of the residual loss.
        for (int k = 0; k < kNumLossModels; k++) {
          metrics_code.average_residual_loss[k] += residual_loss *
              prob_weight[k];
          metrics_code.variance_residual_loss[k] += residual_loss *
              residual_loss * prob_weight[k];
        }
      }  // Done with processing for num_media_packets_lost >= 1.
      // Update the distribution statistics.
      // Compute the gap of the loss (the "consecutiveness" of the loss).
      int gap_loss = GapLoss(tot_num_packets, state.get());
      assert(gap_loss < kMaxGapSize);
      int index = gap_loss * (2 * kMaxMediaPacketsTest) + num_packets_lost;
      assert(index < kNumStatesDistribution);
      metrics_code.residual_loss_per_loss_gap[index] += residual_loss;
      if (code_type == xor_random_code) {
        // The configuration density is only a function of the code length and
        // only needs to computed for the first |code_type| passed here.
        code_params_[code_index].configuration_density[index]++;
      }
    }  // Done with loop over configurations.
    // Normalize the average residual loss and compute/normalize the variance.
    for (int k = 0; k < kNumLossModels; k++) {
      // Normalize the average residual loss by the total number of packets
      // |tot_num_packets| (i.e., the code length). For a code with no (zero)
      // recovery, the average residual loss for that code would be reduced like
      // ~|average_loss_rate| * |num_media_packets| / |tot_num_packets|. This is
      // the expected reduction in the average residual loss just from adding
      // FEC packets to the symbol sequence.
      metrics_code.average_residual_loss[k] =
          metrics_code.average_residual_loss[k] /
          static_cast<double>(tot_num_packets);
      metrics_code.variance_residual_loss[k] =
               metrics_code.variance_residual_loss[k] /
               static_cast<double>(num_media_packets * num_media_packets);
      metrics_code.variance_residual_loss[k] =
          metrics_code.variance_residual_loss[k] -
          (metrics_code.average_residual_loss[k] *
              metrics_code.average_residual_loss[k]);
      assert(metrics_code.variance_residual_loss[k] >= 0.0);
      assert(metrics_code.average_residual_loss[k] > 0.0);
      metrics_code.variance_residual_loss[k] =
          sqrt(metrics_code.variance_residual_loss[k]) /
          metrics_code.average_residual_loss[k];
    }

    // Compute marginal distribution as a function of loss parameter.
    ComputeRecoveryRatePerLoss(&metrics_code,
                               num_media_packets,
                               num_fec_packets,
                               code_index);
    if (code_type == rs_code) {
      CopyMetrics(&kMetricsReedSolomon[code_index], metrics_code);
    } else if (code_type == xor_random_code) {
      CopyMetrics(&kMetricsXorRandom[code_index], metrics_code);
    } else if (code_type == xor_bursty_code) {
      CopyMetrics(&kMetricsXorBursty[code_index], metrics_code);
    } else {
      assert(false);
    }
  }

  void WriteOutMetricsAllFecCodes()  {
    std::string filename = test::OutputPath() + "data_metrics_all_codes";
    FILE* fp = fopen(filename.c_str(), "wb");
    // Loop through codes up to |kMaxMediaPacketsTest|.
    int code_index = 0;
    for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
        num_media_packets++) {
      for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
          num_fec_packets++) {
        fprintf(fp, "FOR CODE: (%d, %d) \n", num_media_packets,
                num_fec_packets);
        for (int k = 0; k < kNumLossModels; k++) {
          float loss_rate = loss_model_[k].average_loss_rate;
          float burst_length = loss_model_[k].average_burst_length;
          fprintf(fp, "Loss rate = %.2f, Burst length = %.2f:  %.4f  %.4f  %.4f"
              " **** %.4f %.4f %.4f \n",
              loss_rate,
              burst_length,
              100 * kMetricsReedSolomon[code_index].average_residual_loss[k],
              100 * kMetricsXorRandom[code_index].average_residual_loss[k],
              100 * kMetricsXorBursty[code_index].average_residual_loss[k],
              kMetricsReedSolomon[code_index].variance_residual_loss[k],
              kMetricsXorRandom[code_index].variance_residual_loss[k],
              kMetricsXorBursty[code_index].variance_residual_loss[k]);
        }
        for (int gap = 0; gap < kGapSizeOutput; gap ++) {
          double rs_residual_loss = ComputeResidualLossPerGap(
              kMetricsReedSolomon[code_index],
              gap,
              num_fec_packets,
              code_index);
          double xor_random_residual_loss = ComputeResidualLossPerGap(
              kMetricsXorRandom[code_index],
              gap,
              num_fec_packets,
              code_index);
          double xor_bursty_residual_loss = ComputeResidualLossPerGap(
              kMetricsXorBursty[code_index],
              gap,
              num_fec_packets,
              code_index);
          fprintf(fp, "Residual loss as a function of gap "
              "%d: %.4f %.4f %.4f \n",
              gap,
              rs_residual_loss,
              xor_random_residual_loss,
              xor_bursty_residual_loss);
        }
        fprintf(fp, "Recovery rate as a function of loss number \n");
        for (int loss = 1; loss <= num_media_packets + num_fec_packets;
                     loss ++) {
          fprintf(fp, "For loss number %d: %.4f %.4f %.4f \n",
                  loss,
                  kMetricsReedSolomon[code_index].
                  recovery_rate_per_loss[loss],
                  kMetricsXorRandom[code_index].
                  recovery_rate_per_loss[loss],
                  kMetricsXorBursty[code_index].
                  recovery_rate_per_loss[loss]);
        }
        fprintf(fp, "******************\n");
        fprintf(fp, "\n");
        code_index++;
      }
    }
    fclose(fp);
  }

  void SetLossModels() {
    int num_loss_rates = sizeof(kAverageLossRate) /
        sizeof(*kAverageLossRate);
    int num_burst_lengths = sizeof(kAverageBurstLength) /
        sizeof(*kAverageBurstLength);
    int num_loss_models = 0;
    for (int k = 0; k < num_burst_lengths; k++) {
      for (int k2 = 0; k2 < num_loss_rates; k2++) {
        loss_model_[num_loss_models].average_loss_rate = kAverageLossRate[k2];
        loss_model_[num_loss_models].average_burst_length =
            kAverageBurstLength[k];
        // First set of loss models are of random type.
        if (k == 0) {
          loss_model_[num_loss_models].loss_type = kRandomLossModel;
        } else {
          loss_model_[num_loss_models].loss_type = kBurstyLossModel;
        }
        num_loss_models++;
      }
    }
    assert(num_loss_models == kNumLossModels);
  }

  void SetCodeParams() {
    int code_index = 0;
    for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
        num_media_packets++) {
      for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
          num_fec_packets++) {
        code_params_[code_index].num_media_packets = num_media_packets;
        code_params_[code_index].num_fec_packets = num_fec_packets;
        code_params_[code_index].protection_level =
            static_cast<float>(num_fec_packets) /
            static_cast<float>(num_media_packets + num_fec_packets);
        for (int k = 0; k < kNumStatesDistribution; k++) {
          code_params_[code_index].configuration_density[k] = 0;
        }
        code_index++;
      }
    }
    max_num_codes_ = code_index;
  }

  // Make some basic checks on the packet masks. Return -1 if any of these
  // checks fail.
  int RejectInvalidMasks(int num_media_packets, int num_fec_packets) {
    // Make sure every FEC packet protects something.
    for (int i = 0; i < num_fec_packets; i++) {
      int row_degree = 0;
      for (int j = 0; j < num_media_packets; j++) {
        if (fec_packet_masks_[i][j] == 1) {
          row_degree++;
        }
      }
      if (row_degree == 0) {
        printf("Invalid mask: FEC packet has empty mask (does not protect "
            "anything) %d %d %d \n", i, num_media_packets, num_fec_packets);
        return -1;
      }
    }
    // Mask sure every media packet has some protection.
    for (int j = 0; j < num_media_packets; j++) {
      int column_degree = 0;
      for (int i = 0; i < num_fec_packets; i++) {
        if (fec_packet_masks_[i][j] == 1) {
          column_degree++;
        }
      }
      if (column_degree == 0) {
        printf("Invalid mask: Media packet has no protection at all %d %d %d "
            "\n", j, num_media_packets, num_fec_packets);
        return -1;
      }
    }
    // Make sure we do not have two identical FEC packets.
    for (int i = 0; i < num_fec_packets; i++) {
      for (int i2 = i + 1; i2 < num_fec_packets; i2++) {
        int overlap = 0;
        for (int j = 0; j < num_media_packets; j++) {
          if (fec_packet_masks_[i][j] == fec_packet_masks_[i2][j]) {
            overlap++;
          }
        }
        if (overlap == num_media_packets) {
          printf("Invalid mask: Two FEC packets are identical %d %d %d %d \n",
                 i, i2, num_media_packets, num_fec_packets);
          return -1;
        }
      }
    }
    // Avoid codes that have two media packets with full protection (all 1s in
    // their corresponding columns). This would mean that if we lose those
    // two packets, we can never recover them even if we receive all the other
    // packets. Exclude the special cases of 1 or 2 FEC packets.
    if (num_fec_packets > 2) {
      for (int j = 0; j < num_media_packets; j++) {
        for (int j2 = j + 1; j2 < num_media_packets; j2++) {
          int degree = 0;
          for (int i = 0; i < num_fec_packets; i++) {
            if (fec_packet_masks_[i][j] == fec_packet_masks_[i][j2] &&
                fec_packet_masks_[i][j] == 1) {
              degree++;
            }
          }
          if (degree == num_fec_packets) {
            printf("Invalid mask: Two media packets are have full degree "
                "%d %d %d %d \n", j, j2, num_media_packets, num_fec_packets);
            return -1;
          }
        }
      }
    }
    return 0;
  }

  void GetPacketMaskConvertToBitMask(uint8_t* packet_mask,
                                     int num_media_packets,
                                     int num_fec_packets,
                                     int mask_bytes_fec_packet,
                                     CodeType code_type) {
    for (int i = 0; i < num_fec_packets; i++) {
      for (int j = 0; j < num_media_packets; j++) {
        const uint8_t byte_mask =
            packet_mask[i * mask_bytes_fec_packet + j / 8];
        const int bit_position = (7 - j % 8);
        fec_packet_masks_[i][j] =
            (byte_mask & (1 << bit_position)) >> bit_position;
        fprintf(fp_mask_, "%d ", fec_packet_masks_[i][j]);
      }
      fprintf(fp_mask_, "\n");
    }
    fprintf(fp_mask_, "\n");
  }

  int ProcessXORPacketMasks(CodeType code_type,
                          FecMaskType fec_mask_type) {
    int code_index = 0;
    // Maximum number of media packets allowed for the mask type.
    const int packet_mask_max = kMaxMediaPackets[fec_mask_type];
    uint8_t* packet_mask = new uint8_t[packet_mask_max * kMaskSizeLBitSet];
    // Loop through codes up to |kMaxMediaPacketsTest|.
    for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
        num_media_packets++) {
      const int mask_bytes_fec_packet =
          (num_media_packets > 16) ? kMaskSizeLBitSet : kMaskSizeLBitClear;
      internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
      for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
          num_fec_packets++) {
        memset(packet_mask, 0, num_media_packets * mask_bytes_fec_packet);
        memcpy(packet_mask, mask_table.fec_packet_mask_table()
               [num_media_packets - 1][num_fec_packets - 1],
               num_fec_packets * mask_bytes_fec_packet);
        // Convert to bit mask.
        GetPacketMaskConvertToBitMask(packet_mask,
                                      num_media_packets,
                                      num_fec_packets,
                                      mask_bytes_fec_packet,
                                      code_type);
        if (RejectInvalidMasks(num_media_packets, num_fec_packets) < 0) {
          return -1;
        }
        // Compute the metrics for this code/mask.
        ComputeMetricsForCode(code_type,
                              code_index);
        code_index++;
      }
    }
    assert(code_index == kNumberCodes);
    delete [] packet_mask;
    return 0;
  }

  void ProcessRS(CodeType code_type) {
    int code_index = 0;
    for (int num_media_packets = 1; num_media_packets <= kMaxMediaPacketsTest;
        num_media_packets++) {
      for (int num_fec_packets = 1; num_fec_packets <= num_media_packets;
          num_fec_packets++) {
        // Compute the metrics for this code type.
        ComputeMetricsForCode(code_type,
                              code_index);
        code_index++;
      }
    }
  }

  // Compute metrics for all code types and sizes.
  void ComputeMetricsAllCodes() {
    SetLossModels();
    SetCodeParams();
    // Get metrics for XOR code with packet masks of random type.
    std::string filename = test::OutputPath() + "data_packet_masks";
    fp_mask_ = fopen(filename.c_str(), "wb");
    fprintf(fp_mask_, "MASK OF TYPE RANDOM: \n");
    EXPECT_EQ(ProcessXORPacketMasks(xor_random_code, kFecMaskRandom), 0);
    // Get metrics for XOR code with packet masks of bursty type.
    fprintf(fp_mask_, "MASK OF TYPE BURSTY: \n");
    EXPECT_EQ(ProcessXORPacketMasks(xor_bursty_code, kFecMaskBursty), 0);
    fclose(fp_mask_);
    // Get metrics for Reed-Solomon code.
    ProcessRS(rs_code);
  }
};

// Verify that the average residual loss, averaged over loss models
// appropriate to each mask type, is below some maximum acceptable level. The
// acceptable levels are read in from a file, and correspond to a current set
// of packet masks. The levels for each code may be updated over time.
TEST_F(FecPacketMaskMetricsTest, FecXorMaxResidualLoss) {
  SetLossModels();
  SetCodeParams();
  ComputeMetricsAllCodes();
  WriteOutMetricsAllFecCodes();
  int num_loss_rates = sizeof(kAverageLossRate) /
      sizeof(*kAverageLossRate);
  int num_burst_lengths = sizeof(kAverageBurstLength) /
      sizeof(*kAverageBurstLength);
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    double sum_residual_loss_random_mask_random_loss = 0.0;
    double sum_residual_loss_bursty_mask_bursty_loss = 0.0;
    // Compute the sum residual loss across the models, for each mask type.
    for (int k = 0; k < kNumLossModels; k++) {
      if (loss_model_[k].loss_type == kRandomLossModel) {
        sum_residual_loss_random_mask_random_loss +=
            kMetricsXorRandom[code_index].average_residual_loss[k];
      } else if (loss_model_[k].loss_type == kBurstyLossModel) {
        sum_residual_loss_bursty_mask_bursty_loss +=
            kMetricsXorBursty[code_index].average_residual_loss[k];
      }
    }
    float average_residual_loss_random_mask_random_loss =
        sum_residual_loss_random_mask_random_loss / num_loss_rates;
    float average_residual_loss_bursty_mask_bursty_loss =
        sum_residual_loss_bursty_mask_bursty_loss /
        (num_loss_rates * (num_burst_lengths  - 1));
    const float ref_random_mask = kMaxResidualLossRandomMask[code_index];
    const float ref_bursty_mask = kMaxResidualLossBurstyMask[code_index];
    EXPECT_LE(average_residual_loss_random_mask_random_loss, ref_random_mask);
    EXPECT_LE(average_residual_loss_bursty_mask_bursty_loss, ref_bursty_mask);
  }
}

// Verify the behavior of the XOR codes vs the RS codes.
// For random loss model with average loss rates <= the code protection level,
// the RS code (optimal MDS code) is more efficient than XOR codes.
// However, for larger loss rates (above protection level) and/or bursty
// loss models, the RS is not always more efficient than XOR (though in most
// cases it still is).
TEST_F(FecPacketMaskMetricsTest, FecXorVsRS) {
  SetLossModels();
  SetCodeParams();
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    for (int k = 0; k < kNumLossModels; k++) {
      float loss_rate = loss_model_[k].average_loss_rate;
      float protection_level = code_params_[code_index].protection_level;
      // Under these conditions we expect XOR to not be better than RS.
       if (loss_model_[k].loss_type == kRandomLossModel &&
           loss_rate <= protection_level) {
        EXPECT_GE(kMetricsXorRandom[code_index].average_residual_loss[k],
                  kMetricsReedSolomon[code_index].average_residual_loss[k]);
        EXPECT_GE(kMetricsXorBursty[code_index].average_residual_loss[k],
                  kMetricsReedSolomon[code_index].average_residual_loss[k]);
       }
       // TODO(marpan): There are some cases (for high loss rates and/or
       // burst loss models) where XOR is better than RS. Is there some pattern
       // we can identify and enforce as a constraint?
    }
  }
}

// Verify the trend (change) in the average residual loss, as a function of
// loss rate, of the XOR code relative to the RS code.
// The difference between XOR and RS should not get worse as we increase
// the average loss rate.
TEST_F(FecPacketMaskMetricsTest, FecTrendXorVsRsLossRate) {
  SetLossModels();
  SetCodeParams();
  // TODO(marpan): Examine this further to see if the condition can be strictly
  // satisfied (i.e., scale = 1.0) for all codes with different/better masks.
  double scale = 0.90;
  int num_loss_rates = sizeof(kAverageLossRate) /
      sizeof(*kAverageLossRate);
  int num_burst_lengths = sizeof(kAverageBurstLength) /
      sizeof(*kAverageBurstLength);
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    for (int i = 0; i < num_burst_lengths; i++) {
      for (int j = 0; j < num_loss_rates - 1; j++) {
        int k = num_loss_rates * i + j;
        // For XOR random.
        if (kMetricsXorRandom[code_index].average_residual_loss[k] >
        kMetricsReedSolomon[code_index].average_residual_loss[k]) {
          double diff_rs_xor_random_loss1 =
              (kMetricsXorRandom[code_index].average_residual_loss[k] -
               kMetricsReedSolomon[code_index].average_residual_loss[k]) /
               kMetricsXorRandom[code_index].average_residual_loss[k];
          double diff_rs_xor_random_loss2 =
              (kMetricsXorRandom[code_index].average_residual_loss[k+1] -
               kMetricsReedSolomon[code_index].average_residual_loss[k+1]) /
               kMetricsXorRandom[code_index].average_residual_loss[k+1];
          EXPECT_GE(diff_rs_xor_random_loss1, scale * diff_rs_xor_random_loss2);
        }
        // TODO(marpan): Investigate the cases for the bursty mask where
        // this trend is not strictly satisfied.
      }
    }
  }
}

// Verify the average residual loss behavior via the protection level and
// the code length. The average residual loss for a given (k1,m1) code
// should generally be higher than that of another code (k2,m2), which has
// either of the two conditions satisfied:
// 1) higher protection & code length at least as large: (k2+m2) >= (k1+m1),
// 2) equal protection and larger code length: (k2+m2) > (k1+m1).
// Currently does not hold for some cases of the XOR code with random mask.
TEST_F(FecPacketMaskMetricsTest, FecBehaviorViaProtectionLevelAndLength) {
  SetLossModels();
  SetCodeParams();
  for (int code_index1 = 0; code_index1 < max_num_codes_; code_index1++) {
    float protection_level1 = code_params_[code_index1].protection_level;
    int length1 = code_params_[code_index1].num_media_packets +
        code_params_[code_index1].num_fec_packets;
    for (int code_index2 = 0; code_index2 < max_num_codes_; code_index2++) {
      float protection_level2 = code_params_[code_index2].protection_level;
      int length2 = code_params_[code_index2].num_media_packets +
          code_params_[code_index2].num_fec_packets;
      // Codes with higher protection are more efficient, conditioned on the
      // length of the code (higher protection but shorter length codes are
      // generally not more efficient). For two codes with equal protection,
      // the longer code is generally more efficient. For high loss rate
      // models, this condition may be violated for some codes with equal or
      // very close protection levels. High loss rate case is excluded below.
      if ((protection_level2 > protection_level1 && length2 >= length1) ||
          (protection_level2 == protection_level1 && length2 > length1)) {
        for (int k = 0; k < kNumLossModels; k++) {
          float loss_rate = loss_model_[k].average_loss_rate;
          if (loss_rate < loss_rate_upper_threshold) {
            EXPECT_LT(
                kMetricsReedSolomon[code_index2].average_residual_loss[k],
                kMetricsReedSolomon[code_index1].average_residual_loss[k]);
            // TODO(marpan): There are some corner cases where this is not
            // satisfied with the current packet masks. Look into updating
            // these cases to see if this behavior should/can be satisfied,
            // with overall lower residual loss for those XOR codes.
            // EXPECT_LT(
            //    kMetricsXorBursty[code_index2].average_residual_loss[k],
            //    kMetricsXorBursty[code_index1].average_residual_loss[k]);
            // EXPECT_LT(
            //   kMetricsXorRandom[code_index2].average_residual_loss[k],
            //   kMetricsXorRandom[code_index1].average_residual_loss[k]);
          }
        }
      }
    }
  }
}

// Verify the beheavior of the variance of the XOR codes.
// The partial recovery of the XOR versus the all or nothing behavior of the RS
// code means that the variance of the residual loss for XOR should generally
// not be worse than RS.
TEST_F(FecPacketMaskMetricsTest, FecVarianceBehaviorXorVsRs) {
  SetLossModels();
  SetCodeParams();
  // The condition is not strictly satisfied with the current masks,
  // i.e., for some codes, the variance of XOR may be slightly higher than RS.
  // TODO(marpan): Examine this further to see if the condition can be strictly
  // satisfied (i.e., scale = 1.0) for all codes with different/better masks.
  double scale = 0.95;
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    for (int k = 0; k < kNumLossModels; k++) {
      EXPECT_LE(scale *
                kMetricsXorRandom[code_index].variance_residual_loss[k],
                kMetricsReedSolomon[code_index].variance_residual_loss[k]);
      EXPECT_LE(scale *
                kMetricsXorBursty[code_index].variance_residual_loss[k],
                kMetricsReedSolomon[code_index].variance_residual_loss[k]);
    }
  }
}

// For the bursty mask type, the residual loss must be strictly zero for all
// consecutive losses (i.e, gap = 0) with number of losses <= num_fec_packets.
// This is a design property of the bursty mask type.
TEST_F(FecPacketMaskMetricsTest, FecXorBurstyPerfectRecoveryConsecutiveLoss) {
  SetLossModels();
  SetCodeParams();
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    int num_fec_packets = code_params_[code_index].num_fec_packets;
    for (int loss = 1; loss <= num_fec_packets; loss++) {
      int index = loss;  // |gap| is zero.
      EXPECT_EQ(kMetricsXorBursty[code_index].
                residual_loss_per_loss_gap[index], 0.0);
    }
  }
}

// The XOR codes with random mask type are generally better than the ones with
// bursty mask type, for random loss models at low loss rates.
// The XOR codes with bursty mask types are generally better than the one with
// random mask type, for bursty loss models and/or high loss rates.
// TODO(marpan): Enable this test when some of the packet masks are updated.
// Some isolated cases of the codes don't pass this currently.
/*
TEST_F(FecPacketMaskMetricsTest, FecXorRandomVsBursty) {
  SetLossModels();
  SetCodeParams();
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    double sum_residual_loss_random_mask_random_loss = 0.0;
    double sum_residual_loss_bursty_mask_random_loss = 0.0;
    double sum_residual_loss_random_mask_bursty_loss = 0.0;
    double sum_residual_loss_bursty_mask_bursty_loss = 0.0;
    // Compute the sum residual loss across the models, for each mask type.
    for (int k = 0; k < kNumLossModels; k++) {
      float loss_rate = loss_model_[k].average_loss_rate;
      if (loss_model_[k].loss_type == kRandomLossModel &&
          loss_rate < loss_rate_upper_threshold) {
        sum_residual_loss_random_mask_random_loss +=
            kMetricsXorRandom[code_index].average_residual_loss[k];
        sum_residual_loss_bursty_mask_random_loss +=
            kMetricsXorBursty[code_index].average_residual_loss[k];
      } else if (loss_model_[k].loss_type == kBurstyLossModel &&
          loss_rate > loss_rate_lower_threshold) {
        sum_residual_loss_random_mask_bursty_loss +=
            kMetricsXorRandom[code_index].average_residual_loss[k];
        sum_residual_loss_bursty_mask_bursty_loss +=
            kMetricsXorBursty[code_index].average_residual_loss[k];
      }
    }
    EXPECT_LE(sum_residual_loss_random_mask_random_loss,
              sum_residual_loss_bursty_mask_random_loss);
    EXPECT_LE(sum_residual_loss_bursty_mask_bursty_loss,
              sum_residual_loss_random_mask_bursty_loss);
  }
}
*/

// Verify that the average recovery rate for each code is equal or above some
// threshold, for certain loss number conditions.
TEST_F(FecPacketMaskMetricsTest, FecRecoveryRateUnderLossConditions) {
  SetLossModels();
  SetCodeParams();
  for (int code_index = 0; code_index < max_num_codes_; code_index++) {
    int num_media_packets = code_params_[code_index].num_media_packets;
    int num_fec_packets = code_params_[code_index].num_fec_packets;
    // Perfect recovery (|recovery_rate_per_loss| == 1) is expected for
    // |loss_number| = 1, for all codes.
    int loss_number = 1;
    EXPECT_EQ(kMetricsReedSolomon[code_index].
              recovery_rate_per_loss[loss_number], 1.0);
    EXPECT_EQ(kMetricsXorRandom[code_index].
              recovery_rate_per_loss[loss_number], 1.0);
    EXPECT_EQ(kMetricsXorBursty[code_index].
              recovery_rate_per_loss[loss_number], 1.0);
    // For |loss_number| = |num_fec_packets| / 2, we expect the following:
    // Perfect recovery for RS, and recovery for XOR above the threshold.
    loss_number = num_fec_packets / 2 > 0 ? num_fec_packets / 2 : 1;
    EXPECT_EQ(kMetricsReedSolomon[code_index].
              recovery_rate_per_loss[loss_number], 1.0);
    EXPECT_GE(kMetricsXorRandom[code_index].
              recovery_rate_per_loss[loss_number], kRecoveryRateXorRandom[0]);
    EXPECT_GE(kMetricsXorBursty[code_index].
              recovery_rate_per_loss[loss_number], kRecoveryRateXorBursty[0]);
    // For |loss_number| = |num_fec_packets|, we expect the following:
    // Perfect recovery for RS, and recovery for XOR above the threshold.
    loss_number = num_fec_packets;
    EXPECT_EQ(kMetricsReedSolomon[code_index].
              recovery_rate_per_loss[loss_number], 1.0);
    EXPECT_GE(kMetricsXorRandom[code_index].
              recovery_rate_per_loss[loss_number], kRecoveryRateXorRandom[1]);
    EXPECT_GE(kMetricsXorBursty[code_index].
              recovery_rate_per_loss[loss_number], kRecoveryRateXorBursty[1]);
    // For |loss_number| = |num_fec_packets| + 1, we expect the following:
    // Zero recovery for RS, but non-zero recovery for XOR.
    if (num_fec_packets > 1 && num_media_packets > 2) {
      loss_number =  num_fec_packets + 1;
      EXPECT_EQ(kMetricsReedSolomon[code_index].
                recovery_rate_per_loss[loss_number], 0.0);
      EXPECT_GE(kMetricsXorRandom[code_index].
                recovery_rate_per_loss[loss_number],
                kRecoveryRateXorRandom[2]);
      EXPECT_GE(kMetricsXorBursty[code_index].
                recovery_rate_per_loss[loss_number],
                kRecoveryRateXorBursty[2]);
    }
  }
}

}  // namespace webrtc