Skip to content

Commit

Permalink
Add naive REMB bitrate handling.
Browse files Browse the repository at this point in the history
Assumes a track can consume
all available bandwidth regardless
of whether other tracks or datachannels
are transmitting. Needs improvement.
  • Loading branch information
KW-M committed Jan 20, 2023
1 parent 5478710 commit 44a76f5
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 14 deletions.
6 changes: 3 additions & 3 deletions pkg/codec/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func NewRTPH264Codec(clockrate uint32) *RTPCodec {
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
RTCPFeedback: nil,
RTCPFeedback: []webrtc.RTCPFeedback{{Type: "nack", Parameter: "pli"}, {Type: "ccm", Parameter: "fir"}, {Type: "goog-remb", Parameter: ""}},
},
PayloadType: 125,
},
Expand All @@ -46,7 +46,7 @@ func NewRTPVP8Codec(clockrate uint32) *RTPCodec {
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
RTCPFeedback: []webrtc.RTCPFeedback{{Type: "nack", Parameter: "pli"}, {Type: "ccm", Parameter: "fir"}, {Type: "goog-remb", Parameter: ""}},
},
PayloadType: 96,
},
Expand All @@ -63,7 +63,7 @@ func NewRTPVP9Codec(clockrate uint32) *RTPCodec {
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
RTCPFeedback: []webrtc.RTCPFeedback{{Type: "nack", Parameter: "pli"}, {Type: "ccm", Parameter: "fir"}, {Type: "goog-remb", Parameter: ""}},
},
PayloadType: 98,
},
Expand Down
29 changes: 21 additions & 8 deletions track.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,16 @@ func (track *baseTrack) bind(ctx webrtc.TrackLocalContext, specializedTrack Trac
}
}()

keyFrameController, ok := encodedReader.Controller().(codec.KeyFrameController)
if ok {
go track.rtcpReadLoop(ctx.RTCPReader(), keyFrameController, stopRead)
keyFrameController, keyCtlOk := encodedReader.Controller().(codec.KeyFrameController)
bitRateController, bitCtlOk := encodedReader.Controller().(codec.BitRateController)
if keyCtlOk || bitCtlOk {
go track.rtcpReadLoop(ctx.RTCPReader(), keyFrameController, bitRateController, stopRead)
}

return selectedCodec, nil
}

func (track *baseTrack) rtcpReadLoop(reader interceptor.RTCPReader, keyFrameController codec.KeyFrameController, stopRead chan struct{}) {
func (track *baseTrack) rtcpReadLoop(reader interceptor.RTCPReader, keyFrameController codec.KeyFrameController, bitRateController codec.BitRateController, stopRead chan struct{}) {
readerBuffer := make([]byte, rtcpInboundMTU)

readLoop:
Expand All @@ -256,11 +257,23 @@ readLoop:
}

for _, pkt := range pkts {
switch pkt.(type) {
switch pkt := pkt.(type) {
case *rtcp.PictureLossIndication, *rtcp.FullIntraRequest:
if err := keyFrameController.ForceKeyFrame(); err != nil {
logger.Warnf("failed to force key frame: %s", err)
continue readLoop
if keyFrameController != nil {
if err := keyFrameController.ForceKeyFrame(); err != nil {
logger.Warnf("failed to force key frame: %s", err)
continue readLoop
}
}
case *rtcp.ReceiverEstimatedMaximumBitrate:
if bitRateController != nil {
var available_bitrate int = int(pkt.Bitrate * 0.93) // Scale what we consider "available" bitrate to 93% of total estimated bitrate gives some breathing room for IP/UDP/RTP overhead.
// TODO: Adjust this to account for extra tracks or datachannels that may take up bandwidth. The bitrate from a REMB packet is the TOTAL available bitrate between us and the receiver peer.
// Here we naively set the bitrate to the "available_bitrate", ignoring whether other tracks and/or datachannels are sending at the same time and need a share of that bandwidth.
if err := bitRateController.SetBitRate(available_bitrate); err != nil {
logger.Warnf("failed to set bitrate: %s", err)
continue readLoop
}
}
}
}
Expand Down
53 changes: 50 additions & 3 deletions track_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package mediadevices

import (
"errors"
"github.com/pion/interceptor"
"io"
"testing"
"time"

"github.com/pion/interceptor"
)

func TestOnEnded(t *testing.T) {
Expand Down Expand Up @@ -83,14 +84,23 @@ func (mock *fakeKeyFrameController) ForceKeyFrame() error {
return nil
}

type fakeBitRateController struct {
rateUpdate chan int
}

func (mock *fakeBitRateController) SetBitRate(bitRate int) error {
mock.rateUpdate <- bitRate
return nil
}

func TestRtcpHandler(t *testing.T) {

t.Run("ShouldStopReading", func(t *testing.T) {
tr := &baseTrack{}
stop := make(chan struct{}, 1)
stopped := make(chan struct{})
go func() {
tr.rtcpReadLoop(&fakeRTCPReader{end: stop}, &fakeKeyFrameController{}, stop)
tr.rtcpReadLoop(&fakeRTCPReader{end: stop}, &fakeKeyFrameController{}, &fakeBitRateController{}, stop)
stopped <- struct{}{}
}()

Expand Down Expand Up @@ -140,7 +150,7 @@ func TestRtcpHandler(t *testing.T) {
mockKeyFrameController := &fakeKeyFrameController{called: make(chan struct{}, 1)}
mockRTCPReader := &fakeRTCPReader{end: stop, mockReturn: make(chan []byte, 1)}

go tr.rtcpReadLoop(mockRTCPReader, mockKeyFrameController, stop)
go tr.rtcpReadLoop(mockRTCPReader, mockKeyFrameController, nil, stop)

mockRTCPReader.mockReturn <- packet

Expand All @@ -152,4 +162,41 @@ func TestRtcpHandler(t *testing.T) {
})
}
})

t.Run("ShouldChangeBitrate", func(t *testing.T) {
for packetType, packet := range map[string][]byte{
"REMB": {
// source: https://github.com/pion/rtcp/blob/master/receiver_estimated_maximum_bitrate_test.go#L21
143, 206, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 82, 69, 77, 66, 1, 26, 32, 223, 72, 116, 237, 22,
},
} {
t.Run(packetType, func(t *testing.T) {
tr := &baseTrack{}
tr.OnEnded(func(err error) {
if err != io.EOF {
t.Error(err)
}
})
stop := make(chan struct{}, 1)
defer func() {
stop <- struct{}{}
}()
mockBitRateController := &fakeBitRateController{rateUpdate: make(chan int, 1)}
mockRTCPReader := &fakeRTCPReader{end: stop, mockReturn: make(chan []byte, 1)}

go tr.rtcpReadLoop(mockRTCPReader, nil, mockBitRateController, stop)

mockRTCPReader.mockReturn <- packet

select {
case <-time.After(1000 * time.Millisecond):
t.Error("Timeout")
case bitRate := <-mockBitRateController.rateUpdate:
if bitRate != 8302266 { // 8302266 = 93% of 8927168 (what the bitrate in the REMB packet was)
t.Errorf("Got Unexpected bitrate: %d", bitRate)
}
}
})
}
})
}

0 comments on commit 44a76f5

Please sign in to comment.