Skip to content

Commit f037823

Browse files
committed
Add refactored GCC implementation
1 parent 9e4f3ca commit f037823

22 files changed

+1719
-0
lines changed

bwe.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
// Package bwe implements data structures that are common to all bandwidth
5+
// estimators.
6+
package bwe

ecn.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package bwe
5+
6+
// ECN represents the ECN bits of an IP packet header.
7+
type ECN uint8
8+
9+
const (
10+
// ECNNonECT signals Non ECN-Capable Transport, Non-ECT.
11+
// nolint:misspell
12+
ECNNonECT ECN = iota // 00
13+
14+
// ECNECT1 signals ECN Capable Transport, ECT(0).
15+
// nolint:misspell
16+
ECNECT1 // 01
17+
18+
// ECNECT0 signals ECN Capable Transport, ECT(1).
19+
// nolint:misspell
20+
ECNECT0 // 10
21+
22+
// ECNCE signals ECN Congestion Encountered, CE.
23+
// nolint:misspell
24+
ECNCE // 11
25+
)

gcc/arrival_group_accumulator.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package gcc
5+
6+
import (
7+
"time"
8+
9+
"github.com/pion/bwe"
10+
)
11+
12+
type arrivalGroup []bwe.Packet
13+
14+
type arrivalGroupAccumulator struct {
15+
next arrivalGroup
16+
burstInterval time.Duration
17+
maxBurstDuration time.Duration
18+
}
19+
20+
func newArrivalGroupAccumulator() *arrivalGroupAccumulator {
21+
return &arrivalGroupAccumulator{
22+
next: make([]bwe.Packet, 0),
23+
burstInterval: 5 * time.Millisecond,
24+
maxBurstDuration: 100 * time.Millisecond,
25+
}
26+
}
27+
28+
func (a *arrivalGroupAccumulator) onPacketAcked(ack bwe.Packet) arrivalGroup {
29+
if len(a.next) == 0 {
30+
a.next = append(a.next, ack)
31+
32+
return nil
33+
}
34+
35+
sendTimeDelta := ack.Departure.Sub(a.next[0].Departure)
36+
if sendTimeDelta < a.burstInterval {
37+
a.next = append(a.next, ack)
38+
39+
return nil
40+
}
41+
42+
arrivalTimeDeltaLast := ack.Arrival.Sub(a.next[len(a.next)-1].Arrival)
43+
arrivalTimeDeltaFirst := ack.Arrival.Sub(a.next[0].Arrival)
44+
propagationDelta := arrivalTimeDeltaFirst - sendTimeDelta
45+
46+
if propagationDelta < 0 && arrivalTimeDeltaLast <= a.burstInterval && arrivalTimeDeltaFirst < a.maxBurstDuration {
47+
a.next = append(a.next, ack)
48+
49+
return nil
50+
}
51+
52+
group := make(arrivalGroup, len(a.next))
53+
copy(group, a.next)
54+
a.next = arrivalGroup{ack}
55+
56+
return group
57+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package gcc
5+
6+
import (
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestArrivalGroupAccumulator(t *testing.T) {
14+
triggerNewGroupElement := Acknowledgment{
15+
Departure: time.Time{}.Add(time.Second),
16+
Arrival: time.Time{}.Add(time.Second),
17+
}
18+
cases := []struct {
19+
name string
20+
log []Acknowledgment
21+
exp []arrivalGroup
22+
}{
23+
{
24+
name: "emptyCreatesNoGroups",
25+
log: []Acknowledgment{},
26+
exp: []arrivalGroup{},
27+
},
28+
{
29+
name: "createsSingleElementGroup",
30+
log: []Acknowledgment{
31+
{
32+
Departure: time.Time{},
33+
Arrival: time.Time{}.Add(time.Millisecond),
34+
},
35+
triggerNewGroupElement,
36+
},
37+
exp: []arrivalGroup{
38+
{
39+
{
40+
Departure: time.Time{},
41+
Arrival: time.Time{}.Add(time.Millisecond),
42+
},
43+
},
44+
},
45+
},
46+
{
47+
name: "createsTwoElementGroup",
48+
log: []Acknowledgment{
49+
{
50+
Departure: time.Time{},
51+
Arrival: time.Time{}.Add(15 * time.Millisecond),
52+
},
53+
{
54+
Departure: time.Time{}.Add(3 * time.Millisecond),
55+
Arrival: time.Time{}.Add(20 * time.Millisecond),
56+
},
57+
triggerNewGroupElement,
58+
},
59+
exp: []arrivalGroup{{
60+
{
61+
Departure: time.Time{},
62+
Arrival: time.Time{}.Add(15 * time.Millisecond),
63+
},
64+
{
65+
Departure: time.Time{}.Add(3 * time.Millisecond),
66+
Arrival: time.Time{}.Add(20 * time.Millisecond),
67+
},
68+
}},
69+
},
70+
{
71+
name: "createsTwoArrivalGroups1",
72+
log: []Acknowledgment{
73+
{
74+
Departure: time.Time{},
75+
Arrival: time.Time{}.Add(15 * time.Millisecond),
76+
},
77+
{
78+
Departure: time.Time{}.Add(3 * time.Millisecond),
79+
Arrival: time.Time{}.Add(20 * time.Millisecond),
80+
},
81+
{
82+
Departure: time.Time{}.Add(9 * time.Millisecond),
83+
Arrival: time.Time{}.Add(24 * time.Millisecond),
84+
},
85+
triggerNewGroupElement,
86+
},
87+
exp: []arrivalGroup{
88+
{
89+
{
90+
Departure: time.Time{},
91+
Arrival: time.Time{}.Add(15 * time.Millisecond),
92+
},
93+
{
94+
Departure: time.Time{}.Add(3 * time.Millisecond),
95+
Arrival: time.Time{}.Add(20 * time.Millisecond),
96+
},
97+
},
98+
{
99+
{
100+
Departure: time.Time{}.Add(9 * time.Millisecond),
101+
Arrival: time.Time{}.Add(24 * time.Millisecond),
102+
},
103+
},
104+
},
105+
},
106+
{
107+
name: "ignoresOutOfOrderPackets",
108+
log: []Acknowledgment{
109+
{
110+
Departure: time.Time{},
111+
Arrival: time.Time{}.Add(15 * time.Millisecond),
112+
},
113+
{
114+
Departure: time.Time{}.Add(6 * time.Millisecond),
115+
Arrival: time.Time{}.Add(34 * time.Millisecond),
116+
},
117+
{
118+
Departure: time.Time{}.Add(8 * time.Millisecond),
119+
Arrival: time.Time{}.Add(30 * time.Millisecond),
120+
},
121+
triggerNewGroupElement,
122+
},
123+
exp: []arrivalGroup{
124+
{
125+
{
126+
Departure: time.Time{},
127+
Arrival: time.Time{}.Add(15 * time.Millisecond),
128+
},
129+
},
130+
{
131+
{
132+
Departure: time.Time{}.Add(6 * time.Millisecond),
133+
Arrival: time.Time{}.Add(34 * time.Millisecond),
134+
},
135+
{
136+
Departure: time.Time{}.Add(8 * time.Millisecond),
137+
Arrival: time.Time{}.Add(30 * time.Millisecond),
138+
},
139+
},
140+
},
141+
},
142+
{
143+
name: "newGroupBecauseOfInterDepartureTime",
144+
log: []Acknowledgment{
145+
{
146+
SeqNr: 0,
147+
Departure: time.Time{},
148+
Arrival: time.Time{}.Add(4 * time.Millisecond),
149+
},
150+
{
151+
SeqNr: 1,
152+
Departure: time.Time{}.Add(3 * time.Millisecond),
153+
Arrival: time.Time{}.Add(4 * time.Millisecond),
154+
},
155+
{
156+
SeqNr: 2,
157+
Departure: time.Time{}.Add(6 * time.Millisecond),
158+
Arrival: time.Time{}.Add(10 * time.Millisecond),
159+
},
160+
{
161+
SeqNr: 3,
162+
Departure: time.Time{}.Add(9 * time.Millisecond),
163+
Arrival: time.Time{}.Add(10 * time.Millisecond),
164+
},
165+
triggerNewGroupElement,
166+
},
167+
exp: []arrivalGroup{
168+
{
169+
{
170+
SeqNr: 0,
171+
Departure: time.Time{},
172+
Arrival: time.Time{}.Add(4 * time.Millisecond),
173+
},
174+
{
175+
SeqNr: 1,
176+
Departure: time.Time{}.Add(3 * time.Millisecond),
177+
Arrival: time.Time{}.Add(4 * time.Millisecond),
178+
},
179+
},
180+
{
181+
{
182+
SeqNr: 2,
183+
Departure: time.Time{}.Add(6 * time.Millisecond),
184+
Arrival: time.Time{}.Add(10 * time.Millisecond),
185+
},
186+
{
187+
SeqNr: 3,
188+
Departure: time.Time{}.Add(9 * time.Millisecond),
189+
Arrival: time.Time{}.Add(10 * time.Millisecond),
190+
},
191+
},
192+
},
193+
},
194+
}
195+
196+
for _, tc := range cases {
197+
tc := tc
198+
t.Run(tc.name, func(t *testing.T) {
199+
aga := newArrivalGroupAccumulator()
200+
received := []arrivalGroup{}
201+
for _, ack := range tc.log {
202+
next := aga.onPacketAcked(ack)
203+
if next != nil {
204+
received = append(received, next)
205+
}
206+
}
207+
assert.Equal(t, tc.exp, received)
208+
})
209+
}
210+
}

gcc/delay_rate_controller.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
2+
// SPDX-License-Identifier: MIT
3+
4+
package gcc
5+
6+
import (
7+
"time"
8+
9+
"github.com/pion/bwe"
10+
"github.com/pion/logging"
11+
)
12+
13+
type delayRateController struct {
14+
log logging.LeveledLogger
15+
aga *arrivalGroupAccumulator
16+
last arrivalGroup
17+
kf *kalmanFilter
18+
od *overuseDetector
19+
rc *rateController
20+
latestUsage usage
21+
samples int
22+
}
23+
24+
func newDelayRateController(initialRate int, logger logging.LeveledLogger) *delayRateController {
25+
return &delayRateController{
26+
log: logger,
27+
aga: newArrivalGroupAccumulator(),
28+
last: []bwe.Packet{},
29+
kf: newKalmanFilter(),
30+
od: newOveruseDetector(true),
31+
rc: newRateController(initialRate),
32+
latestUsage: 0,
33+
samples: 0,
34+
}
35+
}
36+
37+
func (c *delayRateController) onPacketAcked(ack bwe.Packet) {
38+
next := c.aga.onPacketAcked(ack)
39+
if next == nil {
40+
return
41+
}
42+
if len(next) == 0 {
43+
// ignore empty groups, should never occur
44+
return
45+
}
46+
if len(c.last) == 0 {
47+
c.last = next
48+
49+
return
50+
}
51+
52+
prevSize := groupSize(c.last)
53+
nextSize := groupSize(next)
54+
sizeDelta := nextSize - prevSize
55+
56+
interArrivalTime := next[len(next)-1].Arrival.Sub(c.last[len(c.last)-1].Arrival)
57+
interDepartureTime := next[len(next)-1].Departure.Sub(c.last[len(c.last)-1].Departure)
58+
interGroupDelay := interArrivalTime - interDepartureTime
59+
estimate := c.kf.update(float64(interGroupDelay.Milliseconds()), float64(sizeDelta))
60+
c.samples++
61+
c.latestUsage = c.od.update(ack.Arrival, estimate, c.samples)
62+
c.last = next
63+
c.log.Tracef(
64+
"ts=%v.%06d, seq=%v, size=%v, interArrivalTime=%v, interDepartureTime=%v, interGroupDelay=%v, estimate=%v, threshold=%v, usage=%v, state=%v", // nolint
65+
c.last[0].Departure.UTC().Format("2006/01/02 15:04:05"),
66+
c.last[0].Departure.UTC().Nanosecond()/1e3,
67+
next[0].SequenceNumber,
68+
nextSize,
69+
interArrivalTime.Microseconds(),
70+
interDepartureTime.Microseconds(),
71+
interGroupDelay.Microseconds(),
72+
estimate,
73+
c.od.delayThreshold,
74+
int(c.latestUsage),
75+
int(c.rc.s),
76+
)
77+
}
78+
79+
func (c *delayRateController) update(ts time.Time, lastDeliveryRate int, rtt time.Duration) int {
80+
return c.rc.update(ts, c.latestUsage, lastDeliveryRate, rtt)
81+
}
82+
83+
func groupSize(group arrivalGroup) int {
84+
sum := 0
85+
for _, ack := range group {
86+
sum += int(ack.Size)
87+
}
88+
89+
return sum
90+
}

0 commit comments

Comments
 (0)