// 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 ( // BaseRTPHeaderSize represents the minium RTP packet header size in bytes. BaseRTPHeaderSize = 12 // BaseFecHeaderSize represents the minium FEC payload's header size including the // required first mask. BaseFecHeaderSize = 12 ) // EncoderFactory is an interface for generic FEC encoders. type EncoderFactory interface { NewEncoder(payloadType uint8, ssrc uint32) FlexEncoder } // FlexEncoder is the interface that FecInterceptor uses to encode Fec packets. type FlexEncoder interface { EncodeFec(mediaPackets []rtp.Packet, numFecPackets uint32) []rtp.Packet } // FlexEncoder20 implementation is WIP, contains bugs and no tests. Check out FlexEncoder03. type FlexEncoder20 struct { fecBaseSn uint16 payloadType uint8 ssrc uint32 coverage *ProtectionCoverage } // NewFlexEncoder returns a new FlexEncoder20. // FlexEncoder20 implementation is WIP, contains bugs and no tests. Check out FlexEncoder03. func ( uint8, uint32) *FlexEncoder20 { return &FlexEncoder20{ payloadType: , ssrc: , fecBaseSn: uint16(1000), } } // EncodeFec returns a list of generated RTP packets with FEC payloads that protect the specified mediaPackets. // This method does not account for missing RTP packets in the mediaPackets array nor does it account for // them being passed out of order. func ( *FlexEncoder20) ( []rtp.Packet, uint32) []rtp.Packet { // 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 ( *FlexEncoder20) ( uint32, uint16) rtp.Packet { := .coverage.GetCoveredBy() := .encodeFlexFecHeader( , .coverage.ExtractMask1(), .coverage.ExtractMask2(), .coverage.ExtractMask3(), , ) := .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 ( *FlexEncoder20) ( *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 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SN base_i |k| Mask [0-14] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |k| Mask [15-45] (optional) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Mask [46-109] (optional) | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ... next SN base and Mask for CSRC_i in CSRC list ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ : Repair "Payload" follows FEC Header : : : */ // Get header size - This depends on the size of the bitmask. := BaseFecHeaderSize if > 0 { += 4 } if > 0 { += 8 } // Allocate the FlexFec header := make([]byte, ) // XOR the relevant fields for the header // TO DO - CHECK TO SEE IF THE MARSHALTO() call works with this. := make([]byte, ) for .HasNext() { := .Next() , := .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] // 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 base SN for the batch of media packets binary.BigEndian.PutUint16([8:10], ) // Write the bitmasks to the header binary.BigEndian.PutUint16([10:12], ) if > 0 { binary.BigEndian.PutUint32([12:16], ) [10] |= 0b10000000 } if > 0 { binary.BigEndian.PutUint64([16:24], ) [12] |= 0b10000000 } return } func ( *FlexEncoder20) ( *util.MediaPacketIterator) []byte { := make([]byte, len(.First().Payload)) for .HasNext() { := .Next().Payload if len() < len() { // Expected FEC packet payload is bigger that what we can currently store, // we need to resize. := make([]byte, len()) copy(, ) = } for := 0; < len(); ++ { [] ^= [] } } return }