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
2 changes: 1 addition & 1 deletion examples/realtime_encoder_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func main() {
// Create RTCSender with BWE capabilities
rtcSender, err := sender.NewRTCSender(
sender.DefaultInterceptors(),
sender.GCC(initialBitrate), // Initial bitrate 500 kbps
sender.GCC(initialBitrate, 0), // Initial bitrate 500 kbps, no max cap
sender.SetLoggerFactory(loggerFactory),
)
if err != nil {
Expand Down
19 changes: 16 additions & 3 deletions sender/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,24 @@ func CCLogWriter(w io.Writer) Option {
}
}

// GCC returns an Option that configures Google Congestion Control with the specified initial bitrate.
func GCC(initialBitrate int) Option {
// GCC returns an Option that configures Google Congestion Control with the
// specified initial bitrate and max bitrate (in bps). A maxBitrate of 0 means
// no cap (uses GCC default of 50 Mbps).
func GCC(initialBitrate, maxBitrate int) Option {
return func(sender ConfigurableWebRTCSender) error {
if rtcSender, ok := sender.(*RTCSender); ok {
rtcSender.gccConfigured = true

return rtcSender.setupGCC(initialBitrate, maxBitrate)
}
// Fallback for other ConfigurableWebRTCSender types.
controller, err := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) {
return gcc.NewSendSideBWE(gcc.SendSideBWEInitialBitrate(initialBitrate))
opts := []gcc.Option{gcc.SendSideBWEInitialBitrate(initialBitrate)}
if maxBitrate > 0 {
opts = append(opts, gcc.SendSideBWEMaxBitrate(maxBitrate))
}

return gcc.NewSendSideBWE(opts...)
})
if err != nil {
return err
Expand Down
36 changes: 35 additions & 1 deletion sender/option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestCCLogWriter(t *testing.T) {
func TestGCC(t *testing.T) {
initialBitrate := 1000000

option := GCC(initialBitrate)
option := GCC(initialBitrate, 0)
require.NotNil(t, option)

// Test that option is a function
Expand All @@ -107,6 +107,40 @@ func TestGCC(t *testing.T) {
// Note: Full testing would require WebRTC setup, so we just test the option creation
}

func TestGCC_WithMaxBitrate(t *testing.T) {
option := GCC(500_000, 1_500_000)
require.NotNil(t, option)
assert.IsType(t, Option(nil), option)
}

func TestGCC_AppliedToMock(t *testing.T) {
// Exercises the fallback path for non-RTCSender types.
mock := &MockConfigurableWebRTCSender{
mediaEngine: &webrtc.MediaEngine{},
registry: &interceptor.Registry{},
}
err := mock.mediaEngine.RegisterDefaultCodecs()
require.NoError(t, err)

option := GCC(500_000, 0)
err = option(mock)
require.NoError(t, err)
}

func TestGCC_AppliedToMockWithMaxBitrate(t *testing.T) {
// Exercises the fallback path with maxBitrate > 0.
mock := &MockConfigurableWebRTCSender{
mediaEngine: &webrtc.MediaEngine{},
registry: &interceptor.Registry{},
}
err := mock.mediaEngine.RegisterDefaultCodecs()
require.NoError(t, err)

option := GCC(500_000, 1_500_000)
err = option(mock)
require.NoError(t, err)
}

func TestSetLoggerFactory(t *testing.T) {
loggerFactory := plogging.NewDefaultLoggerFactory()

Expand Down
29 changes: 20 additions & 9 deletions sender/rtc_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ type RTCSender struct {
// Logging
ccLogWriter io.Writer
log logging.LeveledLogger

// gccConfigured is true when GCC was set up via the GCC option.
gccConfigured bool
}

// SetOnEncodedFrame registers a callback invoked after each VP8 frame is
Expand Down Expand Up @@ -148,31 +151,39 @@ func NewRTCSender(opts ...Option) (*RTCSender, error) {
return nil, err
}

// Set up GCC bandwidth estimation by default
if err := sender.setupGCC(1_000_000); err != nil { // Default initial bitrate: 1Mbps
return nil, err
}

// Register the stats interceptor so GetTrackStats can return RTP/RTCP
// counters (PacketsSent, RoundTripTime) per track.
if err := sender.setupStats(); err != nil {
return nil, err
}

// Apply options directly to RTCSender
// Apply options first (may include custom GCC config)
for _, opt := range opts {
if err := opt(sender); err != nil {
return nil, err
}
}

// Set up default GCC only if no GCC option was provided
if !sender.gccConfigured {
if err := sender.setupGCC(1_000_000, 0); err != nil { // Default initial bitrate: 1Mbps, no max
return nil, err
}
}

return sender, nil
}

// setupGCC sets up Google Congestion Control with the specified initial bitrate.
func (s *RTCSender) setupGCC(initialBitrate int) error {
// setupGCC sets up Google Congestion Control with the specified initial and max bitrate.
// A maxBitrate of 0 means no cap (uses GCC default of 50 Mbps).
func (s *RTCSender) setupGCC(initialBitrate, maxBitrate int) error {
controller, err := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) {
return gcc.NewSendSideBWE(gcc.SendSideBWEInitialBitrate(initialBitrate))
opts := []gcc.Option{gcc.SendSideBWEInitialBitrate(initialBitrate)}
if maxBitrate > 0 {
opts = append(opts, gcc.SendSideBWEMaxBitrate(maxBitrate))
}

return gcc.NewSendSideBWE(opts...)
})
if err != nil {
return err
Expand Down
23 changes: 23 additions & 0 deletions sender/rtc_sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ func TestNewRTCSender(t *testing.T) {
var _ ConfigurableWebRTCSender = sender
}

func TestNewRTCSender_WithGCCOption(t *testing.T) {
// When GCC option is provided, the default setupGCC should be skipped.
sender, err := NewRTCSender(GCC(500_000, 0))
require.NoError(t, err)
require.NotNil(t, sender)
assert.True(t, sender.gccConfigured)
}

func TestNewRTCSender_WithGCCMaxBitrate(t *testing.T) {
sender, err := NewRTCSender(GCC(500_000, 1_500_000))
require.NoError(t, err)
require.NotNil(t, sender)
assert.True(t, sender.gccConfigured)
}

func TestNewRTCSender_DefaultGCC(t *testing.T) {
// Without GCC option, default GCC should be set up.
sender, err := NewRTCSender()
require.NoError(t, err)
require.NotNil(t, sender)
assert.False(t, sender.gccConfigured)
}

func TestVideoTrackInfo_Validation(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion vnet/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func createWebRTCSender(
commonOpts := []sender.Option{
sender.SetVnet(leftVnet, []string{publicIPLeft}),
sender.PacketLogWriter(loggers.rtpLogger, loggers.rtcpLogger),
sender.GCC(100_000),
sender.GCC(100_000, 0),
sender.CCLogWriter(loggers.ccLogger),
sender.SetLoggerFactory(loggerFactory),
}
Expand Down
Loading