// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package flexfec implements FlexFEC to recover missing RTP packets due to packet loss. // https://datatracker.ietf.org/doc/html/rfc8627
package flexfec import ( ) const ( // BaseFec03HeaderSize represents the minium FEC payload's header size including the // required first mask. BaseFec03HeaderSize = 20 ) // FlexEncoder03 implements the Fec encoding mechanism for the "Flex" variant of FlexFec. type FlexEncoder03 struct { fecBaseSn uint16 payloadType uint8 ssrc uint32 coverage *ProtectionCoverage } // FlexEncoder03Factory is a factory for FlexFEC-03 encoders. type FlexEncoder03Factory struct{} // NewEncoder creates new FlexFEC-03 encoder. func ( FlexEncoder03Factory) ( uint8, uint32) FlexEncoder { return NewFlexEncoder03(, ) } // NewFlexEncoder03 creates new FlexFEC-03 encoder. func ( uint8, uint32) *FlexEncoder03 { return &FlexEncoder03{ payloadType: , ssrc: , fecBaseSn: uint16(1000), } } // EncodeFec returns a list of generated RTP packets with FEC payloads that protect the specified mediaPackets. // This method returns nil in case of missing RTP packets in the mediaPackets array or packets passed out of order. func ( *FlexEncoder03) ( []rtp.Packet, uint32) []rtp.Packet { // Check if mediaPackets is empty if len() == 0 { return nil } // Check if RTP packets are in order by comparing sequence numbers for := 1; < len(); ++ { if [].SequenceNumber != [-1].SequenceNumber+1 { // Packets are not in order or there are missing packets return nil } } // Start by defining which FEC packets cover which media packets if .coverage == nil { .coverage = NewCoverage(, ) } else { .coverage.UpdateCoverage(, ) } if .coverage == nil { return nil } // Generate FEC payloads := make([]rtp.Packet, ) for := uint32(0); < ; ++ { [] = .encodeFlexFecPacket(, [0].SequenceNumber) } return } func ( *FlexEncoder03) ( uint32, uint16) rtp.Packet { := .coverage.GetCoveredBy() := .encodeFlexFecHeader( , .coverage.ExtractMask1(), .coverage.ExtractMask2(), .coverage.ExtractMask3_03(), , ) := .encodeFlexFecRepairPayload(.Reset()) := rtp.Packet{ Header: rtp.Header{ Version: 2, Padding: false, Extension: false, Marker: false, PayloadType: .payloadType, SequenceNumber: .fecBaseSn, Timestamp: 54243243, SSRC: .ssrc, CSRC: []uint32{}, }, Payload: append(, ...), } .fecBaseSn++ return } func ( *FlexEncoder03) ( //nolint:cyclop *util.MediaPacketIterator, uint16, uint32, uint64, uint16, ) []byte { /* 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|0| P|X| CC |M| PT recovery | length recovery | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TS recovery | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRCCount | reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC_i | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SN base_i |k| Mask [0-14] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |k| Mask [15-45] (optional) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |k| | +-+ Mask [46-108] (optional) | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ... next in SSRC_i ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Get header size - This depends on the size of the bitmask. := BaseFec03HeaderSize if > 0 || > 0 { += 4 } if > 0 { += 8 } // Allocate the FlexFec header := make([]byte, ) // We allocate a single temporary buffer to store the mediaPacket bytes. This reduces // overall allocations. := make([]byte, 0) for .HasNext() { := .Next() if .MarshalSize() > len() { // The temporary buffer is too small, we need to resize. = make([]byte, .MarshalSize()) } , := .MarshalTo() if == 0 || != nil { return nil } // XOR the first 2 bytes of the header: V, P, X, CC, M, PT fields [0] ^= [0] [1] ^= [1] // Clear the first 2 bits [0] &= 0b00111111 // XOR the length recovery field := uint16(.MarshalSize() - BaseRTPHeaderSize) //nolint:gosec // G115 [2] ^= uint8( >> 8) //nolint:gosec // G115 [3] ^= uint8() //nolint:gosec // G115 // XOR the 5th to 8th bytes of the header: the timestamp field [4] ^= [4] [5] ^= [5] [6] ^= [6] [7] ^= [7] } // Write the SSRC count [8] = 1 // Write 0s in reserved [9] = 0 [10] = 0 [11] = 0 // Write the SSRC of media packets protected by this FEC packet binary.BigEndian.PutUint32([12:16], .First().SSRC) // Write the base SN for the batch of media packets binary.BigEndian.PutUint16([16:18], ) // Write the bitmasks to the header binary.BigEndian.PutUint16([18:20], ) if == 0 && == 0 { [18] |= 0b10000000 return } binary.BigEndian.PutUint32([20:24], ) if == 0 { [20] |= 0b10000000 } else { binary.BigEndian.PutUint64([24:32], ) [24] |= 0b10000000 } return } func ( *FlexEncoder03) ( *util.MediaPacketIterator) []byte { := make([]byte, .First().MarshalSize()-BaseRTPHeaderSize) := make([]byte, 0) for .HasNext() { := .Next() if .MarshalSize() > len() { = make([]byte, .MarshalSize()) } , := .MarshalTo() if == 0 || != nil { return nil } if len() < .MarshalSize()-BaseRTPHeaderSize { // Expected FEC packet payload is bigger that what we can currently store, // we need to resize. := make([]byte, .MarshalSize()-BaseRTPHeaderSize) copy(, ) = } for := 0; < .MarshalSize()-BaseRTPHeaderSize; ++ { [] ^= [+BaseRTPHeaderSize] } } return }