From b817a79dbe4eade830dd1bfbd7b97ab586451c94 Mon Sep 17 00:00:00 2001 From: Punit Date: Wed, 19 Nov 2025 16:56:32 +0530 Subject: [PATCH] Adding flag to support streaming at cutom bitrate --- cmd/lk/egress.go | 2 +- cmd/lk/perf.go | 11 +++++ pkg/loadtester/loadtest.go | 9 ++-- pkg/loadtester/loadtester.go | 79 +++++++++++++++++++++++++++++- pkg/loadtester/loadtestprovider.go | 7 ++- 5 files changed, 101 insertions(+), 7 deletions(-) diff --git a/cmd/lk/egress.go b/cmd/lk/egress.go index fd4b5d3a..7b3b87ab 100644 --- a/cmd/lk/egress.go +++ b/cmd/lk/egress.go @@ -745,7 +745,7 @@ func testEgressTemplate(ctx context.Context, cmd *cli.Command) error { } testers = append(testers, lt) - if _, err = lt.PublishSimulcastTrack("demo-video", "high", ""); err != nil { + if _, err = lt.PublishSimulcastTrack("demo-video", "high", "", 0); err != nil { return err } } diff --git a/cmd/lk/perf.go b/cmd/lk/perf.go index 35828f28..45345934 100644 --- a/cmd/lk/perf.go +++ b/cmd/lk/perf.go @@ -77,6 +77,11 @@ var ( Name: "simulate-speakers", Usage: "Fire random speaker events to simulate speaker changes", }, + &cli.IntFlag{ + Name: "video-bitrate", + Usage: "`BITRATE` in kbps for video publishing (overrides resolution-based bitrate, e.g., 10000 for 10Mbps)", + Value: 0, + }, &cli.BoolFlag{ Name: "run-all", Usage: "Runs set list of load test cases", @@ -170,6 +175,11 @@ var ( Name: "simulate-speakers", Usage: "Fire random speaker events to simulate speaker changes", }, + &cli.IntFlag{ + Name: "video-bitrate", + Usage: "`BITRATE` in kbps for video publishing (overrides resolution-based bitrate, e.g., 10000 for 10Mbps)", + Value: 0, + }, &cli.BoolFlag{ Name: "run-all", Usage: "Runs set list of load test cases", @@ -194,6 +204,7 @@ func loadTest(ctx context.Context, cmd *cli.Command) error { params := loadtester.Params{ VideoResolution: cmd.String("video-resolution"), VideoCodec: cmd.String("video-codec"), + VideoBitrate: uint32(cmd.Int("video-bitrate")), Duration: cmd.Duration("duration"), NumPerSecond: cmd.Float("num-per-second"), Simulcast: !cmd.Bool("no-simulcast"), diff --git a/pkg/loadtester/loadtest.go b/pkg/loadtester/loadtest.go index 257d8349..eb0baa15 100644 --- a/pkg/loadtester/loadtest.go +++ b/pkg/loadtester/loadtest.go @@ -44,9 +44,12 @@ type Params struct { VideoPublishers int AudioPublishers int Subscribers int + VideoResolution string VideoCodec string - Duration time.Duration + VideoBitrate uint32 // Custom bitrate in kbps, 0 means use resolution-based default + + Duration time.Duration // number of seconds to spin up per second NumPerSecond float64 Simulcast bool @@ -347,9 +350,9 @@ func (t *LoadTest) run(ctx context.Context, params Params) (map[string]*testerSt var video string var err error if params.Simulcast { - video, err = tester.PublishSimulcastTrack("video-simulcast", params.VideoResolution, params.VideoCodec) + video, err = tester.PublishSimulcastTrack("video-simulcast", params.VideoResolution, params.VideoCodec, params.VideoBitrate) } else { - video, err = tester.PublishVideoTrack("video", params.VideoResolution, params.VideoCodec) + video, err = tester.PublishVideoTrack("video", params.VideoResolution, params.VideoCodec, params.VideoBitrate) } if err != nil { errs.Store(testerParams.name, err) diff --git a/pkg/loadtester/loadtester.go b/pkg/loadtester/loadtester.go index 95fdaa06..2bbd95c6 100644 --- a/pkg/loadtester/loadtester.go +++ b/pkg/loadtester/loadtester.go @@ -174,12 +174,38 @@ func (t *LoadTester) PublishAudioTrack(name string) (string, error) { return p.SID(), nil } -func (t *LoadTester) PublishVideoTrack(name, resolution, codec string) (string, error) { +func (t *LoadTester) PublishVideoTrack(name, resolution, codec string, bitrate uint32) (string, error) { if !t.IsRunning() { return "", nil } fmt.Println("publishing video track -", t.room.LocalParticipant.Identity()) + + // Use LoadTestProvider for custom bitrate + if bitrate > 0 { + provider, err := NewLoadTestProvider(bitrate * 1000) // Convert kbps to bps + if err != nil { + return "", err + } + track, err := lksdk.NewLocalTrack(webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeH264, + }) + if err != nil { + return "", err + } + if err := track.StartWrite(provider, nil); err != nil { + return "", err + } + p, err := t.room.LocalParticipant.PublishTrack(track, &lksdk.TrackPublicationOptions{ + Name: name, + }) + if err != nil { + return "", err + } + return p.SID(), nil + } + + // Use embedded video files for default behavior loopers, err := provider2.CreateVideoLoopers(resolution, codec, false) if err != nil { return "", err @@ -201,10 +227,59 @@ func (t *LoadTester) PublishVideoTrack(name, resolution, codec string) (string, return p.SID(), nil } -func (t *LoadTester) PublishSimulcastTrack(name, resolution, codec string) (string, error) { +func (t *LoadTester) PublishSimulcastTrack(name, resolution, codec string, bitrate uint32) (string, error) { var tracks []*lksdk.LocalTrack fmt.Println("publishing simulcast video track -", t.room.LocalParticipant.Identity()) + + if bitrate > 0 { + layers := []struct { + quality livekit.VideoQuality + bitrate uint32 + width uint32 + height uint32 + }{ + {livekit.VideoQuality_HIGH, bitrate, 1280, 720}, + {livekit.VideoQuality_MEDIUM, bitrate / 2, 640, 360}, + {livekit.VideoQuality_LOW, bitrate / 4, 320, 180}, + } + + for _, layer := range layers { + provider, err := NewLoadTestProvider(layer.bitrate * 1000) + if err != nil { + return "", err + } + + videoLayer := &livekit.VideoLayer{ + Quality: layer.quality, + Width: layer.width, + Height: layer.height, + Bitrate: layer.bitrate * 1000, + } + + track, err := lksdk.NewLocalTrack(webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeH264, + }, lksdk.WithSimulcast("loadtest-video", videoLayer)) + if err != nil { + return "", err + } + if err := track.StartWrite(provider, nil); err != nil { + return "", err + } + tracks = append(tracks, track) + } + + p, err := t.room.LocalParticipant.PublishSimulcastTrack(tracks, &lksdk.TrackPublicationOptions{ + Name: name, + Source: livekit.TrackSource_CAMERA, + }) + if err != nil { + return "", err + } + return p.SID(), nil + } + + // Use embedded video files for default behavior loopers, err := provider2.CreateVideoLoopers(resolution, codec, true) if err != nil { return "", err diff --git a/pkg/loadtester/loadtestprovider.go b/pkg/loadtester/loadtestprovider.go index 8da5f7e8..a53256ad 100644 --- a/pkg/loadtester/loadtestprovider.go +++ b/pkg/loadtester/loadtestprovider.go @@ -16,6 +16,7 @@ package loadtester import ( "bytes" + "context" "encoding/binary" "errors" "time" @@ -42,7 +43,7 @@ func NewLoadTestProvider(bitrate uint32) (*LoadTestProvider, error) { }, nil } -func (p *LoadTestProvider) NextSample() (media.Sample, error) { +func (p *LoadTestProvider) NextSample(ctx context.Context) (media.Sample, error) { // sample format: // 0xfafafa + 0000... + 8 bytes for ts buf := bytes.NewBuffer(nil) @@ -68,6 +69,10 @@ func (p *LoadTestProvider) OnUnbind() error { return nil } +func (p *LoadTestProvider) Close() error { + return nil +} + type LoadTestDepacketizer struct { }