Repository: pion/interceptor
Pull request #148 (2022-07-16 16:06:25)

Feat/gcc improved rtt approximation
Uses a more accurate approximation of RTT by subtracting the time between packet arrival and feedback departure. Also removes RTT fields from each ACK because it was not used except for debugging.

mengelbart: Draft because it depends on a number of previous PRs...
codecov[bot]: # [Codecov](https://codecov.io/gh/pion/interceptor/pull/148?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) Report
> Merging [#148](https://codecov.io/gh/pion/interceptor/pull/148?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) (7b9e5cb) into [master](https://codecov.io/gh/pion/interceptor/commit/20e7ecf18e38a9f9f6a2a6447dfcc88b2170bd63?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) (20e7ecf) will **increase** coverage by `0.02%`.
> The diff coverage is `86.84%`.

> :exclamation: Current head 7b9e5cb differs from pull request most recent head 0893228. Consider uploading reports for the commit 0893228 to get more accurate results

```diff
@@            Coverage Diff             @@
##           master     #148      +/-   ##
==========================================
+ Coverage   79.52%   79.55%   +0.02%     
==========================================
  Files          64       63       -1     
  Lines        3199     3179      -20     
==========================================
- Hits         2544     2529      -15     
+ Misses        543      539       -4     
+ Partials      112      111       -1     
```

| Flag | Coverage Δ | |
|---|---|---|
| go | `79.55% <86.84%> (+0.02%)` | :arrow_up: |
| wasm | `77.25% <81.57%> (-0.05%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/pion/interceptor/pull/148?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion) | Coverage Δ | |
|---|---|---|
| [internal/cc/acknowledgment.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-aW50ZXJuYWwvY2MvYWNrbm93bGVkZ21lbnQuZ28=) | `0.00% <ø> (ø)` | |
| [pkg/gcc/arrival\_group.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9hcnJpdmFsX2dyb3VwLmdv) | `40.00% <ø> (-1.67%)` | :arrow_down: |
| [pkg/gcc/rate\_controller.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9yYXRlX2NvbnRyb2xsZXIuZ28=) | `58.55% <66.66%> (+0.15%)` | :arrow_up: |
| [pkg/gcc/send\_side\_bwe.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9zZW5kX3NpZGVfYndlLmdv) | `82.80% <82.60%> (+0.44%)` | :arrow_up: |
| [internal/cc/feedback\_adapter.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-aW50ZXJuYWwvY2MvZmVlZGJhY2tfYWRhcHRlci5nbw==) | `63.42% <100.00%> (+0.09%)` | :arrow_up: |
| [pkg/gcc/delay\_based\_bwe.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9kZWxheV9iYXNlZF9id2UuZ28=) | `100.00% <100.00%> (ø)` | |
| [pkg/gcc/overuse\_detector.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9vdmVydXNlX2RldGVjdG9yLmdv) | `100.00% <100.00%> (ø)` | |
| [pkg/gcc/slope\_estimator.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9zbG9wZV9lc3RpbWF0b3IuZ28=) | `100.00% <100.00%> (ø)` | |
| [pkg/gcc/kalman.go](https://codecov.io/gh/pion/interceptor/pull/148/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion#diff-cGtnL2djYy9rYWxtYW4uZ28=) | `100.00% <0.00%> (+5.66%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pion/interceptor/pull/148?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pion/interceptor/pull/148?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion). Last update [20e7ecf...0893228](https://codecov.io/gh/pion/interceptor/pull/148?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pion).


Patch:
diff --git a/internal/cc/acknowledgment.go b/internal/cc/acknowledgment.go
index 00ea058..f2d6091 100644
--- a/internal/cc/acknowledgment.go
+++ b/internal/cc/acknowledgment.go
@@ -15,7 +15,6 @@ type Acknowledgment struct {
 	Size           int
 	Departure      time.Time
 	Arrival        time.Time
-	RTT            time.Duration
 	ECN            rtcp.ECN
 }
 
@@ -25,6 +24,5 @@ func (a Acknowledgment) String() string {
 	s += fmt.Sprintf("\tSIZE:\t%v\n", a.Size)
 	s += fmt.Sprintf("\tDEPARTURE:\t%v\n", int64(float64(a.Departure.UnixNano())/1e+6))
 	s += fmt.Sprintf("\tARRIVAL:\t%v\n", int64(float64(a.Arrival.UnixNano())/1e+6))
-	s += fmt.Sprintf("\tRTT:\t%v\n", a.RTT)
 	return s
 }
diff --git a/internal/cc/feedback_adapter.go b/internal/cc/feedback_adapter.go
index effe2af..fe94ff0 100644
--- a/internal/cc/feedback_adapter.go
+++ b/internal/cc/feedback_adapter.go
@@ -43,7 +43,6 @@ func (f *FeedbackAdapter) onSentRFC8888(ts time.Time, header *rtp.Header, size i
 		Size:           size,
 		Departure:      ts,
 		Arrival:        time.Time{},
-		RTT:            0,
 		ECN:            0,
 	})
 	return nil
@@ -65,7 +64,6 @@ func (f *FeedbackAdapter) onSentTWCC(ts time.Time, extID uint8, header *rtp.Head
 		Size:           header.MarshalSize() + size,
 		Departure:      ts,
 		Arrival:        time.Time{},
-		RTT:            0,
 		ECN:            0,
 	})
 	return nil
@@ -83,7 +81,7 @@ func (f *FeedbackAdapter) OnSent(ts time.Time, header *rtp.Header, size int, att
 	return f.onSentRFC8888(ts, header, size)
 }
 
-func (f *FeedbackAdapter) unpackRunLengthChunk(ts time.Time, start uint16, refTime time.Time, chunk *rtcp.RunLengthChunk, deltas []*rtcp.RecvDelta) (consumedDeltas int, nextRef time.Time, acks []Acknowledgment, err error) {
+func (f *FeedbackAdapter) unpackRunLengthChunk(start uint16, refTime time.Time, chunk *rtcp.RunLengthChunk, deltas []*rtcp.RecvDelta) (consumedDeltas int, nextRef time.Time, acks []Acknowledgment, err error) {
 	result := make([]Acknowledgment, chunk.RunLength)
 	deltaIndex := 0
 
@@ -101,7 +99,6 @@ func (f *FeedbackAdapter) unpackRunLengthChunk(ts time.Time, start uint16, refTi
 				}
 				refTime = refTime.Add(time.Duration(deltas[deltaIndex].Delta) * time.Microsecond)
 				ack.Arrival = refTime
-				ack.RTT = ts.Sub(ack.Departure)
 				deltaIndex++
 			}
 			result[resultIndex] = ack
@@ -111,7 +108,7 @@ func (f *FeedbackAdapter) unpackRunLengthChunk(ts time.Time, start uint16, refTi
 	return deltaIndex, refTime, result, nil
 }
 
-func (f *FeedbackAdapter) unpackStatusVectorChunk(ts time.Time, start uint16, refTime time.Time, chunk *rtcp.StatusVectorChunk, deltas []*rtcp.RecvDelta) (consumedDeltas int, nextRef time.Time, acks []Acknowledgment, err error) {
+func (f *FeedbackAdapter) unpackStatusVectorChunk(start uint16, refTime time.Time, chunk *rtcp.StatusVectorChunk, deltas []*rtcp.RecvDelta) (consumedDeltas int, nextRef time.Time, acks []Acknowledgment, err error) {
 	result := make([]Acknowledgment, len(chunk.SymbolList))
 	deltaIndex := 0
 	resultIndex := 0
@@ -127,7 +124,6 @@ func (f *FeedbackAdapter) unpackStatusVectorChunk(ts time.Time, start uint16, re
 				}
 				refTime = refTime.Add(time.Duration(deltas[deltaIndex].Delta) * time.Microsecond)
 				ack.Arrival = refTime
-				ack.RTT = ts.Sub(ack.Departure)
 				deltaIndex++
 			}
 			result[resultIndex] = ack
@@ -152,7 +148,7 @@ func (f *FeedbackAdapter) OnTransportCCFeedback(ts time.Time, feedback *rtcp.Tra
 	for _, chunk := range feedback.PacketChunks {
 		switch chunk := chunk.(type) {
 		case *rtcp.RunLengthChunk:
-			n, nextRefTime, acks, err := f.unpackRunLengthChunk(ts, index, refTime, chunk, recvDeltas)
+			n, nextRefTime, acks, err := f.unpackRunLengthChunk(index, refTime, chunk, recvDeltas)
 			if err != nil {
 				return nil, err
 			}
@@ -161,7 +157,7 @@ func (f *FeedbackAdapter) OnTransportCCFeedback(ts time.Time, feedback *rtcp.Tra
 			recvDeltas = recvDeltas[n:]
 			index = uint16(int(index) + len(acks))
 		case *rtcp.StatusVectorChunk:
-			n, nextRefTime, acks, err := f.unpackStatusVectorChunk(ts, index, refTime, chunk, recvDeltas)
+			n, nextRefTime, acks, err := f.unpackStatusVectorChunk(index, refTime, chunk, recvDeltas)
 			if err != nil {
 				return nil, err
 			}
@@ -196,7 +192,6 @@ func (f *FeedbackAdapter) OnRFC8888Feedback(ts time.Time, feedback *rtcp.CCFeedb
 				if mb.Received {
 					delta := time.Duration((float64(mb.ArrivalTimeOffset) / 1024.0) * float64(time.Second))
 					ack.Arrival = referenceTime.Add(-delta)
-					ack.RTT = ts.Sub(ack.Departure)
 					ack.ECN = mb.ECN
 				}
 				result = append(result, ack)
diff --git a/pkg/gcc/arrival_group.go b/pkg/gcc/arrival_group.go
index e31bb01..2b42b9a 100644
--- a/pkg/gcc/arrival_group.go
+++ b/pkg/gcc/arrival_group.go
@@ -11,21 +11,18 @@ type arrivalGroup struct {
 	packets   []cc.Acknowledgment
 	departure time.Time
 	arrival   time.Time
-	rtt       time.Duration
 }
 
 func (g *arrivalGroup) add(a cc.Acknowledgment) {
 	g.packets = append(g.packets, a)
 	g.arrival = a.Arrival
 	g.departure = a.Departure
-	g.rtt = a.RTT
 }
 
 func (g arrivalGroup) String() string {
 	s := "ARRIVALGROUP:\n"
 	s += fmt.Sprintf("\tARRIVAL:\t%v\n", int64(float64(g.arrival.UnixNano())/1e+6))
 	s += fmt.Sprintf("\tDEPARTURE:\t%v\n", int64(float64(g.departure.UnixNano())/1e+6))
-	s += fmt.Sprintf("\tRTT:\t%v\n", g.rtt)
 	s += fmt.Sprintf("\tPACKETS:\n%v\n", g.packets)
 	return s
 }
diff --git a/pkg/gcc/delay_based_bwe.go b/pkg/gcc/delay_based_bwe.go
index a3a7655..d12700c 100644
--- a/pkg/gcc/delay_based_bwe.go
+++ b/pkg/gcc/delay_based_bwe.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/pion/interceptor/internal/cc"
+	"github.com/pion/logging"
 )
 
 // DelayStats contains some internal statistics of the delay based congestion
@@ -13,12 +14,11 @@ type DelayStats struct {
 	Measurement      time.Duration
 	Estimate         time.Duration
 	Threshold        time.Duration
-	lastReceiveDelta time.Duration
+	LastReceiveDelta time.Duration
 
 	Usage         usage
 	State         state
 	TargetBitrate int
-	RTT           time.Duration
 }
 
 type now func() time.Time
@@ -26,13 +26,15 @@ type now func() time.Time
 type delayController struct {
 	ackPipe     chan<- []cc.Acknowledgment
 	ackRatePipe chan<- []cc.Acknowledgment
-	ackRTTPipe  chan<- []cc.Acknowledgment
 
 	*arrivalGroupAccumulator
+	*rateController
 
 	onUpdateCallback func(DelayStats)
 
 	wg sync.WaitGroup
+
+	log logging.LeveledLogger
 }
 
 type delayControllerConfig struct {
@@ -45,30 +47,31 @@ type delayControllerConfig struct {
 func newDelayController(c delayControllerConfig) *delayController {
 	ackPipe := make(chan []cc.Acknowledgment)
 	ackRatePipe := make(chan []cc.Acknowledgment)
-	ackRTTPipe := make(chan []cc.Acknowledgment)
 
 	delayController := &delayController{
 		ackPipe:                 ackPipe,
 		ackRatePipe:             ackRatePipe,
-		ackRTTPipe:              ackRTTPipe,
 		arrivalGroupAccumulator: nil,
+		rateController:          nil,
 		onUpdateCallback:        nil,
 		wg:                      sync.WaitGroup{},
+		log:                     logging.NewDefaultLoggerFactory().NewLogger("gcc_delay_controller"),
 	}
 
 	rateController := newRateController(c.nowFn, c.initialBitrate, c.minBitrate, c.maxBitrate, func(ds DelayStats) {
+		delayController.log.Infof("delaystats: %v", ds)
 		if delayController.onUpdateCallback != nil {
 			delayController.onUpdateCallback(ds)
 		}
 	})
+	delayController.rateController = rateController
 	overuseDetector := newOveruseDetector(newAdaptiveThreshold(), 10*time.Millisecond, rateController.onDelayStats)
 	slopeEstimator := newSlopeEstimator(newKalman(), overuseDetector.onDelayStats)
 	arrivalGroupAccumulator := newArrivalGroupAccumulator()
 
 	rc := newRateCalculator(500 * time.Millisecond)
-	re := newRTTEstimator()
 
-	delayController.wg.Add(3)
+	delayController.wg.Add(2)
 	go func() {
 		defer delayController.wg.Done()
 		arrivalGroupAccumulator.run(ackPipe, slopeEstimator.onArrivalGroup)
@@ -77,10 +80,6 @@ func newDelayController(c delayControllerConfig) *delayController {
 		defer delayController.wg.Done()
 		rc.run(ackRatePipe, rateController.onReceivedRate)
 	}()
-	go func() {
-		defer delayController.wg.Done()
-		re.run(ackRTTPipe, rateController.onRTT)
-	}()
 
 	return delayController
 }
@@ -92,14 +91,12 @@ func (d *delayController) onUpdate(f func(DelayStats)) {
 func (d *delayController) updateDelayEstimate(acks []cc.Acknowledgment) {
 	d.ackPipe <- acks
 	d.ackRatePipe <- acks
-	d.ackRTTPipe <- acks
 }
 
 func (d *delayController) Close() error {
 	defer d.wg.Wait()
 
 	close(d.ackPipe)
-	close(d.ackRTTPipe)
 	close(d.ackRatePipe)
 
 	return nil
diff --git a/pkg/gcc/overuse_detector.go b/pkg/gcc/overuse_detector.go
index 40fcdb3..12909d6 100644
--- a/pkg/gcc/overuse_detector.go
+++ b/pkg/gcc/overuse_detector.go
@@ -37,7 +37,7 @@ func (d *overuseDetector) onDelayStats(ds DelayStats) {
 	delta := now.Sub(d.lastUpdate)
 	d.lastUpdate = now
 
-	thresholdUse, estimate, currentThreshold := d.threshold.compare(ds.Estimate, ds.lastReceiveDelta)
+	thresholdUse, estimate, currentThreshold := d.threshold.compare(ds.Estimate, ds.LastReceiveDelta)
 
 	use := usageNormal
 	if thresholdUse == usageOver {
@@ -70,10 +70,9 @@ func (d *overuseDetector) onDelayStats(ds DelayStats) {
 		Measurement:      ds.Measurement,
 		Estimate:         estimate,
 		Threshold:        currentThreshold,
-		lastReceiveDelta: delta,
+		LastReceiveDelta: ds.LastReceiveDelta,
 		Usage:            use,
 		State:            0,
 		TargetBitrate:    0,
-		RTT:              0,
 	})
 }
diff --git a/pkg/gcc/rate_controller.go b/pkg/gcc/rate_controller.go
index 4d0e175..0581ec7 100644
--- a/pkg/gcc/rate_controller.go
+++ b/pkg/gcc/rate_controller.go
@@ -71,7 +71,7 @@ func (c *rateController) onReceivedRate(rate int) {
 	c.latestReceivedRate = rate
 }
 
-func (c *rateController) onRTT(rtt time.Duration) {
+func (c *rateController) updateRTT(rtt time.Duration) {
 	c.lock.Lock()
 	defer c.lock.Unlock()
 	c.latestRTT = rtt
@@ -106,11 +106,10 @@ func (c *rateController) onDelayStats(ds DelayStats) {
 			Measurement:      c.delayStats.Measurement,
 			Estimate:         c.delayStats.Estimate,
 			Threshold:        c.delayStats.Threshold,
-			lastReceiveDelta: c.delayStats.lastReceiveDelta,
+			LastReceiveDelta: c.delayStats.LastReceiveDelta,
 			Usage:            c.delayStats.Usage,
 			State:            c.delayStats.State,
 			TargetBitrate:    c.target,
-			RTT:              c.latestRTT,
 		}
 
 	case stateDecrease:
@@ -119,11 +118,10 @@ func (c *rateController) onDelayStats(ds DelayStats) {
 			Measurement:      c.delayStats.Measurement,
 			Estimate:         c.delayStats.Estimate,
 			Threshold:        c.delayStats.Threshold,
-			lastReceiveDelta: c.delayStats.lastReceiveDelta,
+			LastReceiveDelta: c.delayStats.LastReceiveDelta,
 			Usage:            c.delayStats.Usage,
 			State:            c.delayStats.State,
 			TargetBitrate:    c.target,
-			RTT:              c.latestRTT,
 		}
 	}
 
diff --git a/pkg/gcc/rtt_estimator.go b/pkg/gcc/rtt_estimator.go
deleted file mode 100644
index 4f9386f..0000000
--- a/pkg/gcc/rtt_estimator.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package gcc
-
-import (
-	"math"
-	"time"
-
-	"github.com/pion/interceptor/internal/cc"
-)
-
-type rttEstimator struct {
-	samples int
-}
-
-func newRTTEstimator() *rttEstimator {
-	return &rttEstimator{
-		samples: 100,
-	}
-}
-
-func (e *rttEstimator) run(in <-chan []cc.Acknowledgment, onRTTUpdate func(time.Duration)) {
-	history := []time.Duration{}
-	for acks := range in {
-		if len(acks) == 0 {
-			continue
-		}
-		minRTT := time.Duration(math.MaxInt64)
-		for _, ack := range acks {
-			if ack.RTT < minRTT {
-				minRTT = ack.RTT
-			}
-		}
-		history = append(history, minRTT)
-		if len(history) >= e.samples {
-			history = history[len(history)-e.samples:]
-		}
-
-		sum := time.Duration(0)
-		for _, rtt := range history {
-			sum += rtt
-		}
-		rtt := float64(sum.Milliseconds()) / float64(len(history)) * float64(time.Millisecond)
-		onRTTUpdate(time.Duration(rtt))
-	}
-}
diff --git a/pkg/gcc/send_side_bwe.go b/pkg/gcc/send_side_bwe.go
index 9138ba7..c1a28e5 100644
--- a/pkg/gcc/send_side_bwe.go
+++ b/pkg/gcc/send_side_bwe.go
@@ -2,11 +2,13 @@ package gcc
 
 import (
 	"errors"
+	"math"
 	"sync"
 	"time"
 
 	"github.com/pion/interceptor"
 	"github.com/pion/interceptor/internal/cc"
+	"github.com/pion/interceptor/internal/ntp"
 	"github.com/pion/rtcp"
 	"github.com/pion/rtp"
 )
@@ -65,6 +67,22 @@ func SendSideBWEInitialBitrate(rate int) Option {
 	}
 }
 
+// SendSideBWEMaxBitrate sets the initial bitrate of new GCC interceptors
+func SendSideBWEMaxBitrate(rate int) Option {
+	return func(e *SendSideBWE) error {
+		e.maxBitrate = rate
+		return nil
+	}
+}
+
+// SendSideBWEMinBitrate sets the initial bitrate of new GCC interceptors
+func SendSideBWEMinBitrate(rate int) Option {
+	return func(e *SendSideBWE) error {
+		e.minBitrate = rate
+		return nil
+	}
+}
+
 // SendSideBWEPacer sets the pacing algorithm to use.
 func SendSideBWEPacer(p Pacer) Option {
 	return func(e *SendSideBWE) error {
@@ -136,6 +154,7 @@ func (e *SendSideBWE) AddStream(info *interceptor.StreamInfo, writer interceptor
 
 // WriteRTCP adds some RTCP feedback to the bandwidth estimator
 func (e *SendSideBWE) WriteRTCP(pkts []rtcp.Packet, attributes interceptor.Attributes) error {
+	now := time.Now()
 	e.closeLock.RLock()
 	defer e.closeLock.RUnlock()
 
@@ -146,15 +165,42 @@ func (e *SendSideBWE) WriteRTCP(pkts []rtcp.Packet, attributes interceptor.Attri
 	for _, pkt := range pkts {
 		var acks []cc.Acknowledgment
 		var err error
+		var feedbackSentTime time.Time
 		switch fb := pkt.(type) {
 		case *rtcp.TransportLayerCC:
-			acks, err = e.feedbackAdapter.OnTransportCCFeedback(time.Now(), fb)
+			acks, err = e.feedbackAdapter.OnTransportCCFeedback(now, fb)
 			if err != nil {
 				return err
 			}
+			for i, ack := range acks {
+				if i == 0 {
+					feedbackSentTime = ack.Arrival
+					continue
+				}
+				if ack.Arrival.After(feedbackSentTime) {
+					feedbackSentTime = ack.Arrival
+				}
+			}
 		case *rtcp.CCFeedbackReport:
-			acks = e.feedbackAdapter.OnRFC8888Feedback(time.Now(), fb)
+			acks = e.feedbackAdapter.OnRFC8888Feedback(now, fb)
+			feedbackSentTime = ntp.ToTime(uint64(fb.ReportTimestamp) << 16)
+		default:
+			continue
+		}
+
+		feedbackMinRTT := time.Duration(math.MaxInt)
+		for _, ack := range acks {
+			if ack.Arrival.IsZero() {
+				continue
+			}
+			pendingTime := feedbackSentTime.Sub(ack.Arrival)
+			rtt := now.Sub(ack.Departure) - pendingTime
+			feedbackMinRTT = time.Duration(minInt(int(rtt), int(feedbackMinRTT)))
 		}
+		if feedbackMinRTT < math.MaxInt {
+			e.delayController.updateRTT(feedbackMinRTT)
+		}
+
 		e.lossController.updateLossEstimate(acks)
 		e.delayController.updateDelayEstimate(acks)
 	}
@@ -181,7 +227,6 @@ func (e *SendSideBWE) GetStats() map[string]interface{} {
 		"delayMeasurement":   float64(e.latestStats.Measurement.Microseconds()) / 1000.0,
 		"delayEstimate":      float64(e.latestStats.Estimate.Microseconds()) / 1000.0,
 		"delayThreshold":     float64(e.latestStats.Threshold.Microseconds()) / 1000.0,
-		"rtt":                float64(e.latestStats.RTT.Microseconds()) / 1000.0,
 		"usage":              e.latestStats.Usage.String(),
 		"state":              e.latestStats.State.String(),
 	}
diff --git a/pkg/gcc/slope_estimator.go b/pkg/gcc/slope_estimator.go
index cd417b8..a04e520 100644
--- a/pkg/gcc/slope_estimator.go
+++ b/pkg/gcc/slope_estimator.go
@@ -1,6 +1,8 @@
 package gcc
 
-import "time"
+import (
+	"time"
+)
 
 type estimator interface {
 	updateEstimate(measurement time.Duration) time.Duration
@@ -39,11 +41,10 @@ func (e *slopeEstimator) onArrivalGroup(ag arrivalGroup) {
 		Measurement:      measurement,
 		Estimate:         e.updateEstimate(measurement),
 		Threshold:        0,
-		lastReceiveDelta: delta,
+		LastReceiveDelta: delta,
 		Usage:            0,
 		State:            0,
 		TargetBitrate:    0,
-		RTT:              0,
 	})
 }
 


Test patch:
diff --git a/internal/cc/feedback_adapter_test.go b/internal/cc/feedback_adapter_test.go
index 07b9cfe..b75d683 100644
--- a/internal/cc/feedback_adapter_test.go
+++ b/internal/cc/feedback_adapter_test.go
@@ -60,42 +60,36 @@ func TestUnpackRunLengthChunk(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 1,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 2,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 3,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 4,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 5,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 			},
 			n:       6,
@@ -124,42 +118,36 @@ func TestUnpackRunLengthChunk(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(250 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 65535,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(500 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 0,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(750 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 1,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(1000 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 2,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(1250 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 3,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(1500 * time.Microsecond),
-					RTT:            0,
 				},
 			},
 			n:       6,
@@ -182,42 +170,36 @@ func TestUnpackRunLengthChunk(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 65535,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 0,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 1,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 2,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 3,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 			},
 			n:       0,
@@ -241,7 +223,7 @@ func TestUnpackRunLengthChunk(t *testing.T) {
 				assert.NoError(t, fa.OnSent(time.Time{}, h, 0, attributes))
 			}
 
-			n, refTime, acks, err := fa.unpackRunLengthChunk(time.Time{}, tc.start, time.Time{}, &tc.chunk, tc.deltas)
+			n, refTime, acks, err := fa.unpackRunLengthChunk(tc.start, time.Time{}, &tc.chunk, tc.deltas)
 			assert.NoError(t, err)
 			assert.Len(t, acks, len(tc.acks))
 			assert.Equal(t, tc.n, n)
@@ -309,42 +291,36 @@ func TestUnpackStatusVectorChunk(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 1,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 2,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 3,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 4,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 5,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 			},
 			n:       6,
@@ -379,42 +355,36 @@ func TestUnpackStatusVectorChunk(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(250 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 65535,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(500 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 0,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(750 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 1,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(1000 * time.Microsecond),
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 2,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				},
 				{
 					SequenceNumber: 3,
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{}.Add(1250 * time.Microsecond),
-					RTT:            0,
 				},
 			},
 			n:       5,
@@ -438,7 +408,7 @@ func TestUnpackStatusVectorChunk(t *testing.T) {
 				assert.NoError(t, fa.OnSent(time.Time{}, h, 0, attributes))
 			}
 
-			n, refTime, acks, err := fa.unpackStatusVectorChunk(time.Time{}, tc.start, time.Time{}, &tc.chunk, tc.deltas)
+			n, refTime, acks, err := fa.unpackStatusVectorChunk(tc.start, time.Time{}, &tc.chunk, tc.deltas)
 			assert.NoError(t, err)
 			assert.Len(t, acks, len(tc.acks))
 			assert.Equal(t, tc.n, n)
@@ -563,7 +533,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 			Size:           headers[0].MarshalSize() + 1200,
 			Departure:      t0,
 			Arrival:        t0.Add(4 * time.Microsecond),
-			RTT:            0,
 		})
 
 		assert.Contains(t, results, Acknowledgment{
@@ -571,7 +540,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 			Size:           headers[1].MarshalSize() + 1200,
 			Departure:      t0,
 			Arrival:        t0.Add(104 * time.Microsecond),
-			RTT:            0,
 		})
 
 		for i := uint16(2); i < 7; i++ {
@@ -580,7 +548,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 				Size:           headers[i].MarshalSize() + 1200,
 				Departure:      t0,
 				Arrival:        time.Time{},
-				RTT:            0,
 			})
 		}
 
@@ -589,7 +556,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 			Size:           headers[7].MarshalSize() + 1200,
 			Departure:      t0,
 			Arrival:        t0.Add(116 * time.Microsecond),
-			RTT:            0,
 		})
 
 		for i := uint16(8); i < 21; i++ {
@@ -598,7 +564,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 				Size:           headers[i].MarshalSize() + 1200,
 				Departure:      t0,
 				Arrival:        time.Time{},
-				RTT:            0,
 			})
 		}
 
@@ -607,7 +572,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 			Size:           headers[21].MarshalSize() + 1200,
 			Departure:      t0,
 			Arrival:        t0.Add(120 * time.Microsecond),
-			RTT:            0,
 		})
 	})
 
@@ -701,14 +665,12 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 			Size:           pkt65535.Header.MarshalSize() + 1200,
 			Departure:      t0,
 			Arrival:        t0.Add(4 * time.Microsecond),
-			RTT:            0,
 		})
 		assert.Contains(t, results, Acknowledgment{
 			SequenceNumber: 0,
 			Size:           pkt0.Header.MarshalSize() + 1200,
 			Departure:      t0,
 			Arrival:        t0.Add(8 * time.Microsecond),
-			RTT:            0,
 		})
 	})
 
@@ -769,7 +731,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 				Size:           headers[i].MarshalSize() + 1200,
 				Departure:      t0,
 				Arrival:        t0.Add(time.Duration((i + 1)) * 4 * time.Microsecond),
-				RTT:            0,
 			})
 		}
 		for i := uint16(3); i < 7; i++ {
@@ -778,7 +739,6 @@ func TestFeedbackAdapterTWCC(t *testing.T) {
 				Size:           headers[i].MarshalSize() + 1200,
 				Departure:      t0,
 				Arrival:        time.Time{},
-				RTT:            0,
 			})
 		}
 	})
diff --git a/pkg/gcc/arrival_group_test.go b/pkg/gcc/arrival_group_test.go
index ac75bd2..a73e5f9 100644
--- a/pkg/gcc/arrival_group_test.go
+++ b/pkg/gcc/arrival_group_test.go
@@ -21,7 +21,6 @@ func TestArrivalGroup(t *testing.T) {
 				packets:   nil,
 				arrival:   time.Time{},
 				departure: time.Time{},
-				rtt:       0,
 			},
 		},
 		{
@@ -31,7 +30,6 @@ func TestArrivalGroup(t *testing.T) {
 				Size:           0,
 				Departure:      time.Time{},
 				Arrival:        time.Time{},
-				RTT:            0,
 			}},
 			expected: arrivalGroup{
 				packets: []cc.Acknowledgment{{
@@ -39,11 +37,9 @@ func TestArrivalGroup(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				}},
 				arrival:   time.Time{},
 				departure: time.Time{},
-				rtt:       0,
 			},
 		},
 		{
@@ -53,13 +49,11 @@ func TestArrivalGroup(t *testing.T) {
 				Size:           0,
 				Departure:      time.Time{},
 				Arrival:        time.Time{},
-				RTT:            0,
 			}, {
 				SequenceNumber: 0,
 				Size:           0,
 				Departure:      time.Time{}.Add(time.Second),
 				Arrival:        time.Time{}.Add(time.Second),
-				RTT:            time.Hour,
 			}},
 			expected: arrivalGroup{
 				packets: []cc.Acknowledgment{{
@@ -67,17 +61,14 @@ func TestArrivalGroup(t *testing.T) {
 					Size:           0,
 					Departure:      time.Time{},
 					Arrival:        time.Time{},
-					RTT:            0,
 				}, {
 					SequenceNumber: 0,
 					Size:           0,
 					Departure:      time.Time{}.Add(time.Second),
 					Arrival:        time.Time{}.Add(time.Second),
-					RTT:            time.Hour,
 				}},
 				arrival:   time.Time{}.Add(time.Second),
 				departure: time.Time{}.Add(time.Second),
-				rtt:       time.Hour,
 			},
 		},
 	}
diff --git a/pkg/gcc/rate_calculator_test.go b/pkg/gcc/rate_calculator_test.go
index 9bd9a5e..1d1f16b 100644
--- a/pkg/gcc/rate_calculator_test.go
+++ b/pkg/gcc/rate_calculator_test.go
@@ -27,7 +27,6 @@ func TestRateCalculator(t *testing.T) {
 				Size:           0,
 				Departure:      time.Time{},
 				Arrival:        time.Time{},
-				RTT:            0,
 			}},
 			expected: []int{},
 		},
@@ -38,7 +37,6 @@ func TestRateCalculator(t *testing.T) {
 				Size:           1000,
 				Departure:      time.Time{},
 				Arrival:        t0,
-				RTT:            0,
 			}},
 			expected: []int{8000},
 		},
@@ -49,13 +47,11 @@ func TestRateCalculator(t *testing.T) {
 				Size:           125,
 				Departure:      time.Time{},
 				Arrival:        t0,
-				RTT:            0,
 			}, {
 				SequenceNumber: 0,
 				Size:           125,
 				Departure:      time.Time{},
 				Arrival:        t0.Add(100 * time.Millisecond),
-				RTT:            0,
 			}},
 			expected: []int{1000, 20_000},
 		},
diff --git a/pkg/gcc/rate_controller_test.go b/pkg/gcc/rate_controller_test.go
index b1c6d37..32e6125 100644
--- a/pkg/gcc/rate_controller_test.go
+++ b/pkg/gcc/rate_controller_test.go
@@ -30,7 +30,6 @@ func TestRateControllerRun(t *testing.T) {
 				TargetBitrate: 108_000,
 				Estimate:      0,
 				Threshold:     0,
-				RTT:           300 * time.Millisecond,
 			}},
 		},
 	}
@@ -49,7 +48,7 @@ func TestRateControllerRun(t *testing.T) {
 			})
 			in := make(chan DelayStats)
 			dc.onReceivedRate(100_000)
-			dc.onRTT(300 * time.Millisecond)
+			dc.updateRTT(300 * time.Millisecond)
 			go func() {
 				defer close(out)
 				for _, state := range tc.usage {
@@ -60,7 +59,6 @@ func TestRateControllerRun(t *testing.T) {
 						Usage:         state,
 						State:         0,
 						TargetBitrate: 0,
-						RTT:           0,
 					})
 				}
 				close(in)
diff --git a/pkg/gcc/rtt_estimator_test.go b/pkg/gcc/rtt_estimator_test.go
deleted file mode 100644
index 61bc9d9..0000000
--- a/pkg/gcc/rtt_estimator_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package gcc
-
-import (
-	"testing"
-	"time"
-
-	"github.com/pion/interceptor/internal/cc"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestRTTEstimator(t *testing.T) {
-	cases := []struct {
-		name     string
-		ackLists [][]cc.Acknowledgment
-		expected []time.Duration
-	}{
-		{
-			name:     "noACKsNoRTT",
-			ackLists: [][]cc.Acknowledgment{},
-			expected: []time.Duration{},
-		},
-		{
-			name: "staticRTT",
-			ackLists: [][]cc.Acknowledgment{
-				{{RTT: 5 * time.Millisecond}},
-				{{RTT: 5 * time.Millisecond}},
-				{{RTT: 5 * time.Millisecond}},
-				{{RTT: 5 * time.Millisecond}},
-			},
-			expected: []time.Duration{
-				5 * time.Millisecond,
-				5 * time.Millisecond,
-				5 * time.Millisecond,
-				5 * time.Millisecond,
-			},
-		},
-		{
-			name: "",
-			ackLists: [][]cc.Acknowledgment{
-				{},
-				{{RTT: 5 * time.Millisecond}},
-				{{RTT: 5 * time.Millisecond}},
-				{{RTT: 5 * time.Millisecond}},
-				{{RTT: 5 * time.Millisecond}},
-			},
-			expected: []time.Duration{
-				5 * time.Millisecond,
-				5 * time.Millisecond,
-				5 * time.Millisecond,
-				5 * time.Millisecond,
-			},
-		},
-	}
-
-	for _, tc := range cases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			re := newRTTEstimator()
-			in := make(chan []cc.Acknowledgment)
-			out := make(chan time.Duration)
-			go func() {
-				defer close(out)
-				re.run(in, func(d time.Duration) {
-					out <- d
-				})
-			}()
-			go func() {
-				for _, acks := range tc.ackLists {
-					in <- acks
-				}
-				close(in)
-			}()
-			received := []time.Duration{}
-			for rtt := range out {
-				received = append(received, rtt)
-			}
-			assert.Equal(t, tc.expected, received)
-		})
-	}
-}
diff --git a/pkg/gcc/slope_estimator_test.go b/pkg/gcc/slope_estimator_test.go
index 8130521..b6d9ec4 100644
--- a/pkg/gcc/slope_estimator_test.go
+++ b/pkg/gcc/slope_estimator_test.go
@@ -39,11 +39,10 @@ func TestSlopeEstimator(t *testing.T) {
 					Measurement:      0,
 					Estimate:         0,
 					Threshold:        0,
-					lastReceiveDelta: 5 * time.Millisecond,
+					LastReceiveDelta: 5 * time.Millisecond,
 					Usage:            0,
 					State:            0,
 					TargetBitrate:    0,
-					RTT:              0,
 				},
 			},
 		},
@@ -68,21 +67,19 @@ func TestSlopeEstimator(t *testing.T) {
 					Measurement:      0,
 					Estimate:         0,
 					Threshold:        0,
-					lastReceiveDelta: 5 * time.Millisecond,
+					LastReceiveDelta: 5 * time.Millisecond,
 					Usage:            0,
 					State:            0,
 					TargetBitrate:    0,
-					RTT:              0,
 				},
 				{
 					Measurement:      -5 * time.Millisecond,
 					Estimate:         -5 * time.Millisecond,
 					Threshold:        0,
-					lastReceiveDelta: 5 * time.Millisecond,
+					LastReceiveDelta: 5 * time.Millisecond,
 					Usage:            0,
 					State:            0,
 					TargetBitrate:    0,
-					RTT:              0,
 				},
 			},
 		},