test
This commit is contained in:
@@ -3,11 +3,56 @@ package capture
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxH264Bitrate = 50_000_000
|
||||
|
||||
var h264TargetBitrate atomic.Int64
|
||||
var h264NetworkAdaptive atomic.Bool
|
||||
var lastH264NetworkAdjustment atomic.Int64
|
||||
|
||||
const h264NetworkAdjustmentInterval = 3 * time.Second
|
||||
|
||||
func SetH264NetworkAdaptive(enabled bool) {
|
||||
h264NetworkAdaptive.Store(enabled)
|
||||
if !enabled {
|
||||
lastH264NetworkAdjustment.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
func ApplyWebRTCBandwidthEstimate(bps int) int {
|
||||
if !h264NetworkAdaptive.Load() || bps <= 0 {
|
||||
return 0
|
||||
}
|
||||
target := bps * 85 / 100
|
||||
if target < 2_000_000 {
|
||||
target = 2_000_000
|
||||
}
|
||||
if target > maxH264Bitrate {
|
||||
target = maxH264Bitrate
|
||||
}
|
||||
target = ((target + 500_000) / 1_000_000) * 1_000_000
|
||||
current := configuredH264Bitrate()
|
||||
if current > 0 {
|
||||
delta := current - target
|
||||
if delta < 0 {
|
||||
delta = -delta
|
||||
}
|
||||
if delta < 1_000_000 || float64(delta)/float64(current) < 0.15 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
now := time.Now().UnixNano()
|
||||
last := lastH264NetworkAdjustment.Load()
|
||||
if last > 0 && time.Duration(now-last) < h264NetworkAdjustmentInterval {
|
||||
return 0
|
||||
}
|
||||
if !lastH264NetworkAdjustment.CompareAndSwap(last, now) {
|
||||
return 0
|
||||
}
|
||||
return SetH264TargetBitrate(target)
|
||||
}
|
||||
|
||||
func SetH264TargetBitrate(bps int) int {
|
||||
if bps < 0 {
|
||||
|
||||
@@ -36,3 +36,25 @@ func TestH264TargetCRFTracksManualRate(t *testing.T) {
|
||||
t.Fatalf("higher bitrate CRF = %.2f, want below automatic %.2f", higherRate, auto)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebRTCBandwidthEstimateOnlyAppliesInAdaptiveMode(t *testing.T) {
|
||||
original := configuredH264Bitrate()
|
||||
defer func() {
|
||||
SetH264NetworkAdaptive(false)
|
||||
SetH264TargetBitrate(original)
|
||||
}()
|
||||
|
||||
SetH264TargetBitrate(18_000_000)
|
||||
SetH264NetworkAdaptive(false)
|
||||
if got := ApplyWebRTCBandwidthEstimate(10_000_000); got != 0 {
|
||||
t.Fatalf("disabled estimate applied %d, want 0", got)
|
||||
}
|
||||
SetH264NetworkAdaptive(true)
|
||||
lastH264NetworkAdjustment.Store(0)
|
||||
if got := ApplyWebRTCBandwidthEstimate(10_000_000); got != 9_000_000 {
|
||||
t.Fatalf("adaptive estimate applied %d, want 9000000", got)
|
||||
}
|
||||
if got := ApplyWebRTCBandwidthEstimate(2_000_000); got != 0 {
|
||||
t.Fatalf("unthrottled second estimate applied %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -912,10 +912,14 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
case "desktop_set_bitrate":
|
||||
payload, _ := envelope["payload"].(map[string]interface{})
|
||||
bitrateMbps := 0
|
||||
adaptive := false
|
||||
if payload != nil {
|
||||
if v, ok := payloadInt(payload, "bitrateMbps"); ok {
|
||||
bitrateMbps = v
|
||||
}
|
||||
if v, ok := payload["adaptive"].(bool); ok {
|
||||
adaptive = v
|
||||
}
|
||||
}
|
||||
if bitrateMbps < 0 {
|
||||
bitrateMbps = 0
|
||||
@@ -923,8 +927,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
if bitrateMbps > 50 {
|
||||
bitrateMbps = 50
|
||||
}
|
||||
capture.SetH264NetworkAdaptive(adaptive)
|
||||
bps := capture.SetH264TargetBitrate(bitrateMbps * 1_000_000)
|
||||
log.Printf("desktop: set target bitrate=%d Mbps (0=auto)", bps/1_000_000)
|
||||
log.Printf("desktop: set target bitrate=%d Mbps (0=auto) adaptive=%v", bps/1_000_000, adaptive)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
case "clipboard_sync_start":
|
||||
|
||||
@@ -171,6 +171,14 @@ func handleWebrtcP2POffer(ctx context.Context, env *runtime.Env, cmdID string, p
|
||||
OnClose: func() {
|
||||
log.Printf("webrtc: P2P[%s/%s] session closed", kind, sessionID)
|
||||
},
|
||||
OnBandwidthEstimate: func(bps int) {
|
||||
if kind != webrtcpub.KindDesktop {
|
||||
return
|
||||
}
|
||||
if applied := capture.ApplyWebRTCBandwidthEstimate(bps); applied > 0 {
|
||||
log.Printf("webrtc: P2P congestion target=%d Mbps applied=%d Mbps", bps/1_000_000, applied/1_000_000)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
answerSDP, err := webrtcpub.StartP2POffer(ctx, kind, sessionID, offerSDP, callbacks, hasVideo, hasAudio)
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/interceptor/pkg/cc"
|
||||
"github.com/pion/interceptor/pkg/gcc"
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
@@ -82,7 +85,33 @@ func StartP2POffer(ctx context.Context, kind Kind, sessionID string, offerSDP st
|
||||
}
|
||||
}
|
||||
|
||||
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
|
||||
interceptorRegistry := &interceptor.Registry{}
|
||||
if hasVideo && opts.OnBandwidthEstimate != nil {
|
||||
congestionController, ccErr := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) {
|
||||
return gcc.NewSendSideBWE(
|
||||
gcc.SendSideBWEInitialBitrate(18_000_000),
|
||||
gcc.SendSideBWEMinBitrate(2_000_000),
|
||||
gcc.SendSideBWEMaxBitrate(50_000_000),
|
||||
)
|
||||
})
|
||||
if ccErr != nil {
|
||||
return "", fmt.Errorf("create congestion controller: %w", ccErr)
|
||||
}
|
||||
congestionController.OnNewPeerConnection(func(_ string, estimator cc.BandwidthEstimator) {
|
||||
estimator.OnTargetBitrateChange(opts.OnBandwidthEstimate)
|
||||
})
|
||||
interceptorRegistry.Add(congestionController)
|
||||
if err := webrtc.ConfigureTWCCHeaderExtensionSender(mediaEngine, interceptorRegistry); err != nil {
|
||||
return "", fmt.Errorf("configure TWCC sender: %w", err)
|
||||
}
|
||||
}
|
||||
if err := webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
|
||||
return "", fmt.Errorf("register WebRTC interceptors: %w", err)
|
||||
}
|
||||
api := webrtc.NewAPI(
|
||||
webrtc.WithMediaEngine(mediaEngine),
|
||||
webrtc.WithInterceptorRegistry(interceptorRegistry),
|
||||
)
|
||||
pc, err := api.NewPeerConnection(webrtc.Configuration{
|
||||
ICEServers: []webrtc.ICEServer{
|
||||
{URLs: []string{"stun:stun.l.google.com:19302"}},
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
type Kind string
|
||||
|
||||
const (
|
||||
KindDesktop Kind = "desktop"
|
||||
Kindbackstage Kind = "backstage"
|
||||
KindWebcam Kind = "webcam"
|
||||
KindAudio Kind = "audio"
|
||||
KindDesktop Kind = "desktop"
|
||||
Kindbackstage Kind = "backstage"
|
||||
KindWebcam Kind = "webcam"
|
||||
KindAudio Kind = "audio"
|
||||
)
|
||||
|
||||
type VideoWriter interface {
|
||||
@@ -225,6 +225,7 @@ type ICECandidate struct {
|
||||
}
|
||||
|
||||
type P2POfferCallbacks struct {
|
||||
OnICE func(c ICECandidate)
|
||||
OnClose func()
|
||||
OnICE func(c ICECandidate)
|
||||
OnClose func()
|
||||
OnBandwidthEstimate func(bps int)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user