This section introduces the procedures that are used by these FEC schemes.
A codec implementing the Sliding Window RLC FEC scheme relies on several parameters:
-
Maximum FEC-related latency budget, max_lat (a decimal number expressed in seconds) with real-time flows:
-
a source ADU flow can have real-time constraints, and therefore any FECFRAME related operation should take place within the validity period of each ADU (Appendix D describes an exception to this rule). When there are multiple flows with different real-time constraints, we consider the most stringent constraints (see item 6 in Section 10.2 of RFC 6363, for recommendations when several flows are globally protected). The maximum FEC-related latency budget, max_lat, accounts for all sources of latency added by FEC encoding (at a sender) and FEC decoding (at a receiver). Other sources of latency (e.g., added by network communications) are out of scope and must be considered separately (said differently, they have already been deducted from max_lat). max_lat can be regarded as the latency budget permitted for all FEC-related operations. This is an input parameter that enables a FECFRAME sender to derive other internal parameters (see Appendix C);
-
Encoding window current (resp. maximum) size, ew_size (resp. ew_max_size) (in symbols):
-
at a FECFRAME sender, during FEC encoding, a repair symbol is computed as a linear combination of the ew_size source symbols present in the encoding window. The ew_max_size is the maximum size of this window, while ew_size is the current size. For example, in the common case at session start, upon receiving new source ADUs, the ew_size progressively increases until it reaches its maximum value, ew_max_size. We have:
-
0 < ew_size <= ew_max_size
-
Decoding window maximum size, dw_max_size (in symbols):
-
at a FECFRAME receiver, dw_max_size is the maximum number of received or lost source symbols that are still within their latency budget;
-
Linear system maximum size, ls_max_size (in symbols):
-
at a FECFRAME receiver, the linear system maximum size, ls_max_size, is the maximum number of received or lost source symbols in the linear system (i.e., the variables). It SHOULD NOT be smaller than dw_max_size since it would mean that, even after receiving a sufficient number of FEC Repair Packets, a lost ADU may not be recovered just because the associated source symbols have been prematurely removed from the linear system, which is usually counter-productive. On the opposite, the linear system MAY grow beyond the dw_max_size (Appendix D);
-
Symbol size, E (in bytes):
-
the E parameter determines the source and repair symbol sizes (necessarily equal). This is an input parameter that enables a FECFRAME sender to derive other internal parameters, as explained below. An implementation at a sender MUST fix the E parameter and MUST communicate it as part of the FEC Scheme-Specific Information (Section 4.1.1.2).
-
Code rate, cr:
-
The code rate parameter determines the amount of redundancy added to the flow. More precisely the cr is the ratio between the total number of source symbols and the total number of source plus repair symbols and by definition: 0 < cr <= 1. This is an input parameter that enables a FECFRAME sender to derive other internal parameters, as explained below. However, there is no need to communicate the cr parameter per see (it's not required to process a repair symbol at a receiver). This code rate parameter can be static. However, in specific use-cases (e.g., with unicast transmissions in presence of a feedback mechanism that estimates the communication quality, out of scope of FECFRAME), the code rate may be adjusted dynamically.
Appendix C proposes non-normative techniques to derive those parameters, depending on the use-case specificities.
At a sender, an ADU coming from the application is not directly mapped to source symbols. When multiple source flows (e.g., media streams) are mapped onto the same FECFRAME instance, each flow is assigned its own Flow ID value (see below). This Flow ID is then prepended to each ADU before FEC encoding. This way, FEC decoding at a receiver also recovers this Flow ID and the recovered ADU can be assigned to the right source flow (note that the 5-tuple used to identify the right source flow of a received ADU is absent with a recovered ADU since it is not FEC protected).
Additionally, since ADUs are of variable size, padding is needed so that each ADU (with its flow identifier) contribute to an integral number of source symbols. This requires adding the original ADU length to each ADU before doing FEC encoding. Because of these requirements, an intermediate format, the ADUI, or ADU Information, is considered [
RFC 6363].
For each incoming ADU, an ADUI
MUST be created as follows. First of all, 3 bytes are prepended (
Figure 1):
-
Flow ID (F) (8-bit field):
-
this unsigned byte contains the integer identifier associated to the source ADU flow to which this ADU belongs. It is assumed that a single byte is sufficient, which implies that no more than 256 flows will be protected by a single FECFRAME session instance.
-
Length (L) (16-bit field):
-
this unsigned integer contains the length of this ADU, in network byte order (i.e., big endian). This length is for the ADU itself and does not include the F, L, or Pad fields.
Then, zero padding is added to the ADU if needed:
-
Padding (Pad) (variable size field):
-
this field contains zero padding to align the F, L, ADU and padding up to a size that is multiple of E bytes (i.e., the source and repair symbol length).
The data unit resulting from the ADU and the F, L, and Pad fields is called ADUI. Since ADUs can have different sizes, this is also the case for ADUIs. However, an ADUI always contributes to an integral number of source symbols.
symbol length, E E E
< ------------------ >< ------------------ >< ------------------ >
+-+--+---------------------------------------------+-------------+
|F| L| ADU | Pad |
+-+--+---------------------------------------------+-------------+
Note that neither the initial 3 bytes nor the optional padding are sent over the network. However, they are considered during FEC encoding, and a receiver that lost a certain FEC Source Packet (e.g., the UDP datagram containing this FEC Source Packet when UDP is used as the transport protocol) will be able to recover the ADUI if FEC decoding succeeds. Thanks to the initial 3 bytes, this receiver will get rid of the padding (if any) and identify the corresponding ADU flow.
Source symbols and the corresponding ADUs are removed from the encoding window:
-
when the sliding encoding window has reached its maximum size, ew_max_size. In that case the oldest symbol MUST be removed before adding a new symbol, so that the current encoding window size always remains inferior or equal to the maximum size: ew_size <= ew_max_size;
-
when an ADU has reached its maximum validity duration in case of a real-time flow. When this happens, all source symbols corresponding to the ADUI that expired SHOULD be removed from the encoding window;
Source symbols are added to the sliding encoding window each time a new ADU arrives, once the ADU-to-source symbols mapping has been performed (
Section 3.2). The current size of the encoding window, ew_size, is updated after adding new source symbols. This process may require to remove old source symbols so that: ew_size <= ew_max_size.
Note that a FEC codec may feature practical limits in the number of source symbols in the encoding window (e.g., for computational complexity reasons). This factor may further limit the ew_max_size value, in addition to the maximum FEC-related latency budget (
Section 3.1).
Each source symbol is identified by an Encoding Symbol ID (ESI), an unsigned integer. The ESI of source symbols
MUST start with value 0 for the first source symbol and
MUST be managed sequentially. Wrapping to zero happens after reaching the maximum value made possible by the ESI field size (this maximum value is FEC scheme dependent, for instance, 2
32-1 with FEC schemes 9 and 10).
No such consideration applies to repair symbols.
In order to compute coding coefficients (see
Section 3.6), the RLC FEC schemes rely on the TinyMT32 PRNG defined in [
RFC 8682] with two additional functions defined in this section.
This PRNG
MUST first be initialized with a 32-bit unsigned integer, used as a seed, with:
void tinymt32_init (tinymt32_t * s, uint32_t seed);
With the FEC schemes defined in this document, the seed is in practice restricted to a value between 0 and 0xFFFF inclusive (note that this PRNG accepts a seed value equal to 0), since this is the Repair_Key 16-bit field value of the Repair FEC Payload ID (
Section 4.1.3). In practice, how to manage the seed and Repair_Key values (both are equal) is left to the implementer, using a monotonically increasing counter being one possibility (
Section 6.1). In addition to the seed, this function takes as parameter a pointer to an instance of a tinymt32_t structure that is used to keep the internal state of the PRNG.
Then, each time a new pseudorandom integer between 0 and 15 inclusive (4-bit pseudorandom integer) is needed, the following function is used:
uint32_t tinymt32_rand16 (tinymt32_t * s);
This function takes as parameter a pointer to the same tinymt32_t structure (that is left unchanged between successive calls to the function).
Similarly, each time a new pseudorandom integer between 0 and 255 inclusive (8-bit pseudorandom integer) is needed, the following function is used:
uint32_t tinymt32_rand256 (tinymt32_t * s);
These two functions keep respectively the 4 or 8 less significant bits of the 32-bit pseudorandom number generated by the tinymt32_generate_uint32() function of [
RFC 8682]. This is done by computing the result of a binary AND between the tinymt32_generate_uint32() output and respectively the 0xF or 0xFF constants, using 32-bit unsigned integer operations.
Figure 2 shows a possible implementation. This is a C language implementation, written for C99 [
C99]. Test results discussed in
Appendix B show that this simple technique, applied to this PRNG, is in line with the RLC FEC schemes needs.
/**
* This function outputs a pseudorandom integer in [0 .. 15] range.
*
* @param s pointer to tinymt internal state.
* @return unsigned integer between 0 and 15 inclusive.
*/
uint32_t tinymt32_rand16(tinymt32_t *s)
{
return (tinymt32_generate_uint32(s) & 0xF);
}
/**
* This function outputs a pseudorandom integer in [0 .. 255] range.
*
* @param s pointer to tinymt internal state.
* @return unsigned integer between 0 and 255 inclusive.
*/
uint32_t tinymt32_rand256(tinymt32_t *s)
{
return (tinymt32_generate_uint32(s) & 0xFF);
}
Any implementation of this PRNG
MUST have the same output as that provided by the reference implementation of [
RFC 8682]. In order to increase the compliance confidence, three criteria are proposed: the one described in [
RFC 8682] (for the TinyMT32 32-bit unsigned integer generator), and the two others detailed in
Appendix A (for the mapping to 4-bit and 8-bit intervals). Because of the way the mapping functions work, it is unlikely that an implementation that fulfills the first criterion fails to fulfill the two others.
The coding coefficients used during the encoding process are generated at the RLC encoder by the generate_coding_coefficients() function each time a new repair symbol needs to be produced. The fraction of coefficients that are nonzero (i.e., the density) is controlled by the DT (Density Threshold) parameter. DT has values between 0 (the minimum value) and 15 (the maximum value), and the average probability of having a nonzero coefficient equals (DT + 1) / 16. In particular, when DT equals 15 the function guaranties that all coefficients are nonzero (i.e., maximum density).
These considerations apply to both the RLC over GF(2) and RLC over GF(2
8), the only difference being the value of the m parameter. With the RLC over GF(2) FEC scheme (
Section 5), m is equal to 1. With RLC over GF(2
8) FEC scheme (
Section 4), m is equal to 8.
Figure 3 shows the reference generate_coding_coefficients() implementation. This is a C language implementation, written for C99 [
C99].
#include <string.h>
/*
* Fills in the table of coding coefficients (of the right size)
* provided with the appropriate number of coding coefficients to
* use for the repair symbol key provided.
*
* (in) repair_key key associated to this repair symbol. This
* parameter is ignored (useless) if m=1 and dt=15
* (in/out) cc_tab pointer to a table of the right size to store
* coding coefficients. All coefficients are
* stored as bytes, regardless of the m parameter,
* upon return of this function.
* (in) cc_nb number of entries in the cc_tab table. This
* value is equal to the current encoding window
* size.
* (in) dt integer between 0 and 15 (inclusive) that
* controls the density. With value 15, all
* coefficients are guaranteed to be nonzero
* (i.e., equal to 1 with GF(2) and equal to a
* value in {1,... 255} with GF(2^^8)), otherwise
* a fraction of them will be 0.
* (in) m Finite Field GF(2^^m) parameter. In this
* document only values 1 and 8 are considered.
* (out) returns 0 in case of success, an error code
* different than 0 otherwise.
*/
int generate_coding_coefficients (uint16_t repair_key,
uint8_t* cc_tab,
uint16_t cc_nb,
uint8_t dt,
uint8_t m)
{
uint32_t i;
tinymt32_t s; /* PRNG internal state */
if (dt > 15) {
return -1; /* error, bad dt parameter */
}
switch (m) {
case 1:
if (dt == 15) {
/* all coefficients are 1 */
memset(cc_tab, 1, cc_nb);
} else {
/* here coefficients are either 0 or 1 */
tinymt32_init(&s, repair_key);
for (i = 0 ; i < cc_nb ; i++) {
cc_tab[i] = (tinymt32_rand16(&s) <= dt) ? 1 : 0;
}
}
break;
case 8:
tinymt32_init(&s, repair_key);
if (dt == 15) {
/* coefficient 0 is avoided here in order to include
* all the source symbols */
for (i = 0 ; i < cc_nb ; i++) {
do {
cc_tab[i] = (uint8_t) tinymt32_rand256(&s);
} while (cc_tab[i] == 0);
}
} else {
/* here a certain number of coefficients should be 0 */
for (i = 0 ; i < cc_nb ; i++) {
if (tinymt32_rand16(&s) <= dt) {
do {
cc_tab[i] = (uint8_t) tinymt32_rand256(&s);
} while (cc_tab[i] == 0);
} else {
cc_tab[i] = 0;
}
}
}
break;
default:
return -2; /* error, bad parameter m */
}
return 0; /* success */
}
The two RLC FEC schemes specified in this document reuse the Finite Fields defined in
RFC 5510,
Section 8.1. More specifically, the elements of the field GF(2
m) are represented by polynomials with binary coefficients (i.e., over GF(2)) and degree lower or equal to m-1. The addition between two elements is defined as the addition of binary polynomials in GF(2), which is equivalent to a bitwise XOR operation on the binary representation of these elements.
With GF(2
8), multiplication between two elements is the multiplication modulo a given irreducible polynomial of degree 8. The following irreducible polynomial is used for GF(2
8):
With GF(2), multiplication corresponds to a logical AND operation.
The two RLC FEC schemes require the computation of a linear combination of source symbols, using the coding coefficients produced by the generate_coding_coefficients() function and stored in the cc_tab[] array.
With the RLC over GF(2
8) FEC scheme, a linear combination of the ew_size source symbol present in the encoding window, say src_0 to src_ew_size_1, in order to generate a repair symbol, is computed as follows. For each byte of position i in each source and the repair symbol, where i belongs to [0; E-1], compute:
repair[i] = cc_tab[0] * src_0[i] XOR cc_tab[1] * src_1[i] XOR ...
XOR cc_tab[ew_size - 1] * src_ew_size_1[i]
where * is the multiplication over GF(2
8). In practice various optimizations need to be used in order to make this computation efficient (see in particular [
PGM13]).
With the RLC over GF(2) FEC scheme (binary case), a linear combination is computed as follows. The repair symbol is the XOR sum of all the source symbols corresponding to a coding coefficient cc_tab[j] equal to 1 (i.e., the source symbols corresponding to zero coding coefficients are ignored). The XOR sum of the byte of position i in each source is computed and stored in the corresponding byte of the repair symbol, where i belongs to [0; E-1]. In practice, the XOR sums will be computed several bytes at a time (e.g., on 64 bit words, or on arrays of 16 or more bytes when using SIMD CPU extensions).
With both FEC schemes, the details of how to optimize the computation of these linear combinations are of high practical importance but out of scope of this document.