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