Skip to content

Commit a621baf

Browse files
committed
Flex fec encoder for RFC version 03
1 parent 7d6c986 commit a621baf

File tree

5 files changed

+412
-95
lines changed

5 files changed

+412
-95
lines changed

pkg/flexfec/encoder_interceptor.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package flexfec
5+
6+
import (
7+
"github.com/pion/interceptor"
8+
"github.com/pion/rtp"
9+
)
10+
11+
// FecInterceptor implements FlexFec.
12+
type FecInterceptor struct {
13+
interceptor.NoOp
14+
flexFecEncoder FlexEncoder
15+
packetBuffer []rtp.Packet
16+
minNumMediaPackets uint32
17+
}
18+
19+
// FecOption can be used to set initial options on Fec encoder interceptors.
20+
type FecOption func(d *FecInterceptor) error
21+
22+
// FecInterceptorFactory creates new FecInterceptors.
23+
type FecInterceptorFactory struct {
24+
opts []FecOption
25+
}
26+
27+
// NewFecInterceptor returns a new Fec interceptor factory.
28+
func NewFecInterceptor(opts ...FecOption) (*FecInterceptorFactory, error) {
29+
return &FecInterceptorFactory{opts: opts}, nil
30+
}
31+
32+
// NewInterceptor constructs a new FecInterceptor.
33+
func (r *FecInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
34+
// Hardcoded for now:
35+
// Min num media packets to encode FEC -> 5
36+
// Min num fec packets -> 1
37+
38+
interceptor := &FecInterceptor{
39+
packetBuffer: make([]rtp.Packet, 0),
40+
minNumMediaPackets: 5,
41+
}
42+
return interceptor, nil
43+
}
44+
45+
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
46+
// will be called once per rtp packet.
47+
func (r *FecInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
48+
// Chromium supports version flexfec-03 of existing draft, this is the one we will configure by default
49+
// although we should support configuring the latest (flexfec-20) as well.
50+
r.flexFecEncoder = NewFlexEncoder03(info.PayloadType, info.SSRC)
51+
52+
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
53+
r.packetBuffer = append(r.packetBuffer, rtp.Packet{
54+
Header: *header,
55+
Payload: payload,
56+
})
57+
58+
// Send the media RTP packet
59+
result, err := writer.Write(header, payload, attributes)
60+
61+
// Send the FEC packets
62+
var fecPackets []rtp.Packet
63+
if len(r.packetBuffer) == int(r.minNumMediaPackets) {
64+
fecPackets = r.flexFecEncoder.EncodeFec(r.packetBuffer, 2)
65+
66+
for _, fecPacket := range fecPackets {
67+
fecResult, fecErr := writer.Write(&fecPacket.Header, fecPacket.Payload, attributes)
68+
69+
if fecErr != nil && fecResult == 0 {
70+
break
71+
}
72+
}
73+
// Reset the packet buffer now that we've sent the corresponding FEC packets.
74+
r.packetBuffer = nil
75+
}
76+
77+
return result, err
78+
})
79+
}

pkg/flexfec/flexfec_coverage.go

Lines changed: 72 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,36 +42,64 @@ func NewCoverage(mediaPackets []rtp.Packet, numFecPackets uint32) *ProtectionCov
4242
// We allocate the biggest array of bitmasks that respects the max constraints.
4343
var packetMasks [MaxFecPackets]util.BitArray
4444
for i := 0; i < int(MaxFecPackets); i++ {
45-
packetMasks[i] = util.NewBitArray(MaxMediaPackets)
45+
packetMasks[i] = util.BitArray{}
4646
}
4747

48+
coverage := &ProtectionCoverage{
49+
packetMasks: packetMasks,
50+
numFecPackets: 0,
51+
numMediaPackets: 0,
52+
mediaPackets: nil,
53+
}
54+
55+
coverage.UpdateCoverage(mediaPackets, numFecPackets)
56+
return coverage
57+
}
58+
59+
// UpdateCoverage updates the ProtectionCoverage object with new bitmasks accounting for the numFecPackets
60+
// we want to use to protect the batch media packets.
61+
func (p *ProtectionCoverage) UpdateCoverage(mediaPackets []rtp.Packet, numFecPackets uint32) {
62+
numMediaPackets := uint32(len(mediaPackets))
63+
64+
// Basic sanity checks
65+
if numMediaPackets <= 0 || numMediaPackets > MaxMediaPackets {
66+
return
67+
}
68+
69+
p.mediaPackets = mediaPackets
70+
71+
if numFecPackets == p.numFecPackets && numMediaPackets == p.numMediaPackets {
72+
// We have the same number of FEC packets covering the same number of media packets, we can simply
73+
// reuse the previous coverage map with the updated media packets.
74+
return
75+
}
76+
77+
p.numFecPackets = numFecPackets
78+
p.numMediaPackets = numMediaPackets
79+
80+
// The number of FEC packets and/or the number of packets has changed, we need to update the coverage map
81+
// to reflect these new values.
82+
p.resetCoverage()
83+
4884
// Generate FEC bit mask where numFecPackets FEC packets are covering numMediaPackets Media packets.
4985
// In the packetMasks array, each FEC packet is represented by a single BitArray, each bit in a given BitArray
5086
// corresponds to a specific Media packet.
5187
// Ex: Row I, Col J is set to 1 -> FEC packet I will protect media packet J.
5288
for fecPacketIndex := uint32(0); fecPacketIndex < numFecPackets; fecPacketIndex++ {
5389
// We use an interleaved method to determine coverage. Given N FEC packets, Media packet X will be
5490
// covered by FEC packet X % N.
55-
for mediaPacketIndex := uint32(0); mediaPacketIndex < numMediaPackets; mediaPacketIndex++ {
56-
coveringFecPktIndex := mediaPacketIndex % numFecPackets
57-
packetMasks[coveringFecPktIndex].SetBit(mediaPacketIndex, 1)
91+
coveredMediaPacketIndex := fecPacketIndex
92+
for coveredMediaPacketIndex < numMediaPackets {
93+
p.packetMasks[fecPacketIndex].SetBit(coveredMediaPacketIndex)
94+
coveredMediaPacketIndex += numFecPackets
5895
}
5996
}
60-
61-
return &ProtectionCoverage{
62-
packetMasks: packetMasks,
63-
numFecPackets: numFecPackets,
64-
numMediaPackets: numMediaPackets,
65-
mediaPackets: mediaPackets,
66-
}
6797
}
6898

6999
// ResetCoverage clears the underlying map so that we can reuse it for new batches of RTP packets.
70-
func (p *ProtectionCoverage) ResetCoverage() {
100+
func (p *ProtectionCoverage) resetCoverage() {
71101
for i := uint32(0); i < MaxFecPackets; i++ {
72-
for j := uint32(0); j < MaxMediaPackets; j++ {
73-
p.packetMasks[i].SetBit(j, 0)
74-
}
102+
p.packetMasks[i].Reset()
75103
}
76104
}
77105

@@ -86,26 +114,46 @@ func (p *ProtectionCoverage) GetCoveredBy(fecPacketIndex uint32) *util.MediaPack
86114
return util.NewMediaPacketIterator(p.mediaPackets, coverage)
87115
}
88116

89-
// MarshalBitmasks returns the underlying bitmask that defines which media packets are protected by the
90-
// specified fecPacketIndex.
91-
func (p *ProtectionCoverage) MarshalBitmasks(fecPacketIndex uint32) []byte {
92-
return p.packetMasks[fecPacketIndex].Marshal()
93-
}
94-
95117
// ExtractMask1 returns the first section of the bitmask as defined by the FEC header.
96118
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
97119
func (p *ProtectionCoverage) ExtractMask1(fecPacketIndex uint32) uint16 {
98-
return uint16(p.packetMasks[fecPacketIndex].GetBitValue(0, 14))
120+
mask := p.packetMasks[fecPacketIndex]
121+
// We get the first 16 bits (64 - 16 -> shift by 48) and we shift once more for K field
122+
mask1 := mask.Lo >> 49
123+
return uint16(mask1)
99124
}
100125

101126
// ExtractMask2 returns the second section of the bitmask as defined by the FEC header.
102127
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
103128
func (p *ProtectionCoverage) ExtractMask2(fecPacketIndex uint32) uint32 {
104-
return uint32(p.packetMasks[fecPacketIndex].GetBitValue(15, 45))
129+
mask := p.packetMasks[fecPacketIndex]
130+
// We remove the first 15 bits
131+
mask2 := mask.Lo << 15
132+
// We get the first 31 bits (64 - 31 -> shift by 33) and we shift once more for K field
133+
mask2 >>= 34
134+
return uint32(mask2)
105135
}
106136

107137
// ExtractMask3 returns the third section of the bitmask as defined by the FEC header.
108138
// https://datatracker.ietf.org/doc/html/rfc8627#section-4.2.2.1
109139
func (p *ProtectionCoverage) ExtractMask3(fecPacketIndex uint32) uint64 {
110-
return p.packetMasks[fecPacketIndex].GetBitValue(46, 109)
140+
mask := p.packetMasks[fecPacketIndex]
141+
// We remove the first 46 bits
142+
maskLo := mask.Lo << 46
143+
maskHi := mask.Hi >> 18
144+
mask3 := maskLo | maskHi
145+
return mask3
146+
}
147+
148+
// ExtractMask3_03 returns the third section of the bitmask as defined by the FEC header.
149+
// https://datatracker.ietf.org/doc/html/draft-ietf-payload-flexible-fec-scheme-03#section-4.2
150+
func (p *ProtectionCoverage) ExtractMask3_03(fecPacketIndex uint32) uint64 {
151+
mask := p.packetMasks[fecPacketIndex]
152+
// We remove the first 46 bits
153+
maskLo := mask.Lo << 46
154+
maskHi := mask.Hi >> 18
155+
mask3 := maskLo | maskHi
156+
// We shift once for the K bit.
157+
mask3 >>= 1
158+
return mask3
111159
}

pkg/flexfec/flexfec_encoder.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,37 @@ const (
2020
BaseFecHeaderSize = 12
2121
)
2222

23-
// FlexEncoder implements the Fec encoding mechanism for the "Flex" variant of FlexFec.
24-
type FlexEncoder struct {
25-
baseSN uint16
23+
// FlexEncoder is the interface that FecInterceptor uses to encode Fec packets.
24+
type FlexEncoder interface {
25+
EncodeFec(mediaPackets []rtp.Packet, numFecPackets uint32) []rtp.Packet
26+
}
27+
28+
// FlexEncoder20 implements the Fec encoding mechanism for the "Flex" variant of FlexFec.
29+
type FlexEncoder20 struct {
30+
fecBaseSn uint16
2631
payloadType uint8
2732
ssrc uint32
2833
coverage *ProtectionCoverage
2934
}
3035

3136
// NewFlexEncoder returns a new FlexFecEncer.
32-
func NewFlexEncoder(baseSN uint16, payloadType uint8, ssrc uint32) *FlexEncoder {
33-
return &FlexEncoder{
34-
baseSN: baseSN,
37+
func NewFlexEncoder(payloadType uint8, ssrc uint32) *FlexEncoder20 {
38+
return &FlexEncoder20{
3539
payloadType: payloadType,
3640
ssrc: ssrc,
37-
coverage: nil,
41+
fecBaseSn: uint16(1000),
3842
}
3943
}
4044

4145
// EncodeFec returns a list of generated RTP packets with FEC payloads that protect the specified mediaPackets.
4246
// This method does not account for missing RTP packets in the mediaPackets array nor does it account for
4347
// them being passed out of order.
44-
func (flex *FlexEncoder) EncodeFec(mediaPackets []rtp.Packet, numFecPackets uint32) []rtp.Packet {
48+
func (flex *FlexEncoder20) EncodeFec(mediaPackets []rtp.Packet, numFecPackets uint32) []rtp.Packet {
4549
// Start by defining which FEC packets cover which media packets
4650
if flex.coverage == nil {
4751
flex.coverage = NewCoverage(mediaPackets, numFecPackets)
4852
} else {
49-
flex.coverage.ResetCoverage()
53+
flex.coverage.UpdateCoverage(mediaPackets, numFecPackets)
5054
}
5155

5256
if flex.coverage == nil {
@@ -56,39 +60,42 @@ func (flex *FlexEncoder) EncodeFec(mediaPackets []rtp.Packet, numFecPackets uint
5660
// Generate FEC payloads
5761
fecPackets := make([]rtp.Packet, numFecPackets)
5862
for fecPacketIndex := uint32(0); fecPacketIndex < numFecPackets; fecPacketIndex++ {
59-
fecPackets[fecPacketIndex] = flex.encodeFlexFecPacket(fecPacketIndex)
63+
fecPackets[fecPacketIndex] = flex.encodeFlexFecPacket(fecPacketIndex, mediaPackets[0].SequenceNumber)
6064
}
6165

6266
return fecPackets
6367
}
6468

65-
func (flex *FlexEncoder) encodeFlexFecPacket(fecPacketIndex uint32) rtp.Packet {
69+
func (flex *FlexEncoder20) encodeFlexFecPacket(fecPacketIndex uint32, mediaBaseSn uint16) rtp.Packet {
6670
mediaPacketsIt := flex.coverage.GetCoveredBy(fecPacketIndex)
6771
flexFecHeader := flex.encodeFlexFecHeader(
6872
mediaPacketsIt,
6973
flex.coverage.ExtractMask1(fecPacketIndex),
7074
flex.coverage.ExtractMask2(fecPacketIndex),
7175
flex.coverage.ExtractMask3(fecPacketIndex),
76+
mediaBaseSn,
7277
)
7378
flexFecRepairPayload := flex.encodeFlexFecRepairPayload(mediaPacketsIt.Reset())
7479

75-
return rtp.Packet{
80+
packet := rtp.Packet{
7681
Header: rtp.Header{
7782
Version: 2,
7883
Padding: false,
7984
Extension: false,
8085
Marker: false,
8186
PayloadType: flex.payloadType,
82-
SequenceNumber: flex.baseSN,
87+
SequenceNumber: flex.fecBaseSn,
8388
Timestamp: 54243243,
8489
SSRC: flex.ssrc,
8590
CSRC: []uint32{},
8691
},
8792
Payload: append(flexFecHeader, flexFecRepairPayload...),
8893
}
94+
flex.fecBaseSn++
95+
return packet
8996
}
9097

91-
func (flex *FlexEncoder) encodeFlexFecHeader(mediaPackets *util.MediaPacketIterator, mask1 uint16, optionalMask2 uint32, optionalMask3 uint64) []byte {
98+
func (flex *FlexEncoder20) encodeFlexFecHeader(mediaPackets *util.MediaPacketIterator, mask1 uint16, optionalMask2 uint32, optionalMask3 uint64, mediaBaseSn uint16) []byte {
9299
/*
93100
0 1 2 3
94101
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
@@ -119,7 +126,7 @@ func (flex *FlexEncoder) encodeFlexFecHeader(mediaPackets *util.MediaPacketItera
119126
headerSize += 8
120127
}
121128

122-
// Allocate a the FlexFec header
129+
// Allocate the FlexFec header
123130
flexFecHeader := make([]byte, headerSize)
124131

125132
// XOR the relevant fields for the header
@@ -149,6 +156,9 @@ func (flex *FlexEncoder) encodeFlexFecHeader(mediaPackets *util.MediaPacketItera
149156
flexFecHeader[7] ^= flexFecHeader[7]
150157
}
151158

159+
// Write the base SN for the batch of media packets
160+
binary.BigEndian.PutUint16(flexFecHeader[8:10], mediaBaseSn)
161+
152162
// Write the bitmasks to the header
153163
binary.BigEndian.PutUint16(flexFecHeader[10:12], mask1)
154164

@@ -163,7 +173,7 @@ func (flex *FlexEncoder) encodeFlexFecHeader(mediaPackets *util.MediaPacketItera
163173
return flexFecHeader
164174
}
165175

166-
func (flex *FlexEncoder) encodeFlexFecRepairPayload(mediaPackets *util.MediaPacketIterator) []byte {
176+
func (flex *FlexEncoder20) encodeFlexFecRepairPayload(mediaPackets *util.MediaPacketIterator) []byte {
167177
flexFecPayload := make([]byte, len(mediaPackets.First().Payload))
168178

169179
for mediaPackets.HasNext() {

0 commit comments

Comments
 (0)