Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gcc/gcc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

// Package gcc implements a congestion controller based on
// https://datatracker.ietf.org/doc/html/draft-ietf-rmcat-gcc-02.
package gcc
63 changes: 63 additions & 0 deletions gcc/loss_rate_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package gcc

type lossRateController struct {
bitrate int
min, max float64

packetsSinceLastUpdate int
arrivedSinceLastUpdate int
lostSinceLastUpdate int
}

func newLossRateController(initialRate, minRate, maxRate int) *lossRateController {
return &lossRateController{
bitrate: initialRate,
min: float64(minRate),
max: float64(maxRate),
packetsSinceLastUpdate: 0,
arrivedSinceLastUpdate: 0,
lostSinceLastUpdate: 0,
}
}

func (l *lossRateController) onPacketAcked() {
l.packetsSinceLastUpdate++
l.arrivedSinceLastUpdate++
}

func (l *lossRateController) onPacketLost() {
l.packetsSinceLastUpdate++
l.lostSinceLastUpdate++
}

func (l *lossRateController) update(lastDeliveryRate int) int {
lossRate := float64(l.lostSinceLastUpdate) / float64(l.packetsSinceLastUpdate)
var target float64
if lossRate > 0.1 {
target = float64(l.bitrate) * (1 - 0.5*lossRate)
target = max(target, l.min)
} else if lossRate < 0.02 {
target = float64(l.bitrate) * 1.05
// Cap at 1.5 times the previously delivered rate to ensure we don't
// increase the target rate indefinitely, while being application
// limited.
target = min(target, 1.5*float64(lastDeliveryRate))
// Cap at previous target rate. In case lastDeliveryRate was much lower
// than our target, we don't want to decrease the target rate.
target = max(target, float64(l.bitrate))
// Cap at configured max bitrate.
target = min(target, l.max)
}
if target != 0 {
l.bitrate = int(target)
}

l.packetsSinceLastUpdate = 0
l.arrivedSinceLastUpdate = 0
l.lostSinceLastUpdate = 0

return l.bitrate
}
89 changes: 89 additions & 0 deletions gcc/loss_rate_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package gcc

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestLossRateController(t *testing.T) {
cases := []struct {
init, min, max int
acked int
lost int
deliveredRate int
expectedRate int
}{
{}, // all zeros
{
init: 100_000,
min: 100_000,
max: 1_000_000,
acked: 0,
lost: 0,
deliveredRate: 0,
expectedRate: 100_000,
},
{
init: 100_000,
min: 100_000,
max: 1_000_000,
acked: 99,
lost: 1,
deliveredRate: 100_000,
expectedRate: 105_000,
},
{
init: 100_000,
min: 100_000,
max: 1_000_000,
acked: 99,
lost: 1,
deliveredRate: 90_000,
expectedRate: 105_000,
},
{
init: 100_000,
min: 100_000,
max: 1_000_000,
acked: 95,
lost: 5,
deliveredRate: 99_000,
expectedRate: 100_000,
},
{
init: 100_000,
min: 50_000,
max: 1_000_000,
acked: 89,
lost: 11,
deliveredRate: 90_000,
expectedRate: 94_500,
},
{
init: 100_000,
min: 100_000,
max: 1_000_000,
acked: 89,
lost: 11,
deliveredRate: 90_000,
expectedRate: 100_000,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
lrc := newLossRateController(tc.init, tc.min, tc.max)
for i := 0; i < tc.acked; i++ {
lrc.onPacketAcked()
}
for i := 0; i < tc.lost; i++ {
lrc.onPacketLost()
}
assert.Equal(t, tc.expectedRate, lrc.update(tc.deliveredRate))
})
}
}
Loading