mirror of
https://github.com/livekit/livekit.git
synced 2026-05-19 15:25:40 +00:00
Add option to enable bitrate based scoring (#2600)
This commit is contained in:
@@ -45,14 +45,15 @@ type ConnectionStatsSenderProvider interface {
|
||||
}
|
||||
|
||||
type ConnectionStatsParams struct {
|
||||
UpdateInterval time.Duration
|
||||
MimeType string
|
||||
IsFECEnabled bool
|
||||
IncludeRTT bool
|
||||
IncludeJitter bool
|
||||
ReceiverProvider ConnectionStatsReceiverProvider
|
||||
SenderProvider ConnectionStatsSenderProvider
|
||||
Logger logger.Logger
|
||||
UpdateInterval time.Duration
|
||||
MimeType string
|
||||
IsFECEnabled bool
|
||||
IncludeRTT bool
|
||||
IncludeJitter bool
|
||||
EnableBitrateScore bool
|
||||
ReceiverProvider ConnectionStatsReceiverProvider
|
||||
SenderProvider ConnectionStatsSenderProvider
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type ConnectionStats struct {
|
||||
@@ -76,10 +77,11 @@ func NewConnectionStats(params ConnectionStatsParams) *ConnectionStats {
|
||||
return &ConnectionStats{
|
||||
params: params,
|
||||
scorer: newQualityScorer(qualityScorerParams{
|
||||
PacketLossWeight: getPacketLossWeight(params.MimeType, params.IsFECEnabled), // LK-TODO: have to notify codec change?
|
||||
IncludeRTT: params.IncludeRTT,
|
||||
IncludeJitter: params.IncludeJitter,
|
||||
Logger: params.Logger,
|
||||
PacketLossWeight: getPacketLossWeight(params.MimeType, params.IsFECEnabled), // LK-TODO: have to notify codec change?
|
||||
IncludeRTT: params.IncludeRTT,
|
||||
IncludeJitter: params.IncludeJitter,
|
||||
EnableBitrateScore: params.EnableBitrateScore,
|
||||
Logger: params.Logger,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,23 +26,6 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
)
|
||||
|
||||
func newConnectionStats(
|
||||
mimeType string,
|
||||
isFECEnabled bool,
|
||||
includeRTT bool,
|
||||
includeJitter bool,
|
||||
receiverProvider ConnectionStatsReceiverProvider,
|
||||
) *ConnectionStats {
|
||||
return NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: mimeType,
|
||||
IsFECEnabled: isFECEnabled,
|
||||
IncludeRTT: includeRTT,
|
||||
IncludeJitter: includeJitter,
|
||||
ReceiverProvider: receiverProvider,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
|
||||
type testReceiverProvider struct {
|
||||
@@ -66,7 +49,15 @@ func (trp *testReceiverProvider) GetDeltaStats() map[uint32]*buffer.StreamStatsW
|
||||
func TestConnectionQuality(t *testing.T) {
|
||||
trp := newTestReceiverProvider()
|
||||
t.Run("quality scorer operation", func(t *testing.T) {
|
||||
cs := newConnectionStats("audio/opus", false, true, true, trp)
|
||||
cs := NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: "audio/opus",
|
||||
IsFECEnabled: false,
|
||||
IncludeRTT: true,
|
||||
IncludeJitter: true,
|
||||
EnableBitrateScore: true,
|
||||
ReceiverProvider: trp,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
duration := 5 * time.Second
|
||||
now := time.Now()
|
||||
@@ -441,7 +432,14 @@ func TestConnectionQuality(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("quality scorer dependent rtt", func(t *testing.T) {
|
||||
cs := newConnectionStats("audio/opus", false, false, true, trp)
|
||||
cs := NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: "audio/opus",
|
||||
IsFECEnabled: false,
|
||||
IncludeRTT: false,
|
||||
IncludeJitter: true,
|
||||
ReceiverProvider: trp,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
duration := 5 * time.Second
|
||||
now := time.Now()
|
||||
@@ -469,7 +467,14 @@ func TestConnectionQuality(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("quality scorer dependent jitter", func(t *testing.T) {
|
||||
cs := newConnectionStats("audio/opus", false, true, false, trp)
|
||||
cs := NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: "audio/opus",
|
||||
IsFECEnabled: false,
|
||||
IncludeRTT: true,
|
||||
IncludeJitter: false,
|
||||
ReceiverProvider: trp,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
duration := 5 * time.Second
|
||||
now := time.Now()
|
||||
@@ -634,7 +639,14 @@ func TestConnectionQuality(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cs := newConnectionStats(tc.mimeType, tc.isFECEnabled, true, true, trp)
|
||||
cs := NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: tc.mimeType,
|
||||
IsFECEnabled: tc.isFECEnabled,
|
||||
IncludeRTT: true,
|
||||
IncludeJitter: true,
|
||||
ReceiverProvider: trp,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
duration := 5 * time.Second
|
||||
now := time.Now()
|
||||
@@ -727,7 +739,15 @@ func TestConnectionQuality(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cs := newConnectionStats("video/vp8", false, true, true, trp)
|
||||
cs := NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: "video/vp8",
|
||||
IsFECEnabled: false,
|
||||
IncludeRTT: true,
|
||||
IncludeJitter: true,
|
||||
EnableBitrateScore: true,
|
||||
ReceiverProvider: trp,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
duration := 5 * time.Second
|
||||
now := time.Now()
|
||||
@@ -814,7 +834,14 @@ func TestConnectionQuality(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cs := newConnectionStats("video/vp8", false, true, true, trp)
|
||||
cs := NewConnectionStats(ConnectionStatsParams{
|
||||
MimeType: "video/vp8",
|
||||
IsFECEnabled: false,
|
||||
IncludeRTT: true,
|
||||
IncludeJitter: true,
|
||||
ReceiverProvider: trp,
|
||||
Logger: logger.GetLogger(),
|
||||
})
|
||||
|
||||
duration := 5 * time.Second
|
||||
now := time.Now()
|
||||
|
||||
@@ -123,8 +123,8 @@ func (w *windowStat) calculatePacketScore(plw float64, includeRTT bool, includeJ
|
||||
return score
|
||||
}
|
||||
|
||||
func (w *windowStat) calculateBitrateScore(expectedBitrate int64) float64 {
|
||||
if expectedBitrate == 0 {
|
||||
func (w *windowStat) calculateBitrateScore(expectedBitrate int64, isEnabled bool) float64 {
|
||||
if expectedBitrate == 0 || !isEnabled {
|
||||
// unsupported mode OR all layers stopped
|
||||
return cMaxScore
|
||||
}
|
||||
@@ -180,10 +180,11 @@ func (w *windowStat) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
||||
// ------------------------------------------
|
||||
|
||||
type qualityScorerParams struct {
|
||||
PacketLossWeight float64
|
||||
IncludeRTT bool
|
||||
IncludeJitter bool
|
||||
Logger logger.Logger
|
||||
PacketLossWeight float64
|
||||
IncludeRTT bool
|
||||
IncludeJitter bool
|
||||
EnableBitrateScore bool
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type qualityScorer struct {
|
||||
@@ -396,7 +397,7 @@ func (q *qualityScorer) updateAtLocked(stat *windowStat, at time.Time) {
|
||||
score = qualityTransitionScore[livekit.ConnectionQuality_LOST]
|
||||
} else {
|
||||
packetScore := stat.calculatePacketScore(plw, q.params.IncludeRTT, q.params.IncludeJitter)
|
||||
bitrateScore := stat.calculateBitrateScore(expectedBitrate)
|
||||
bitrateScore := stat.calculateBitrateScore(expectedBitrate, q.params.EnableBitrateScore)
|
||||
layerScore := math.Max(math.Min(cMaxScore, cMaxScore-(expectedDistance*distanceWeight)), 0.0)
|
||||
|
||||
minScore := math.Min(packetScore, bitrateScore)
|
||||
|
||||
@@ -1090,7 +1090,7 @@ func (d *DownTrack) UpTrackMaxTemporalLayerSeenChange(maxTemporalLayerSeen int32
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DownTrack) maybeAddTransition(_ int64, distance float64, pauseReason VideoPauseReason) {
|
||||
func (d *DownTrack) maybeAddTransition(bitrate int64, distance float64, pauseReason VideoPauseReason) {
|
||||
if d.kind == webrtc.RTPCodecTypeAudio {
|
||||
return
|
||||
}
|
||||
@@ -1100,6 +1100,7 @@ func (d *DownTrack) maybeAddTransition(_ int64, distance float64, pauseReason Vi
|
||||
} else {
|
||||
d.connectionStats.UpdatePause(false)
|
||||
d.connectionStats.AddLayerTransition(distance)
|
||||
d.connectionStats.AddBitrateTransition(bitrate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -430,8 +430,31 @@ func (w *WebRTCReceiver) AddDownTrack(track TrackSender) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WebRTCReceiver) notifyMaxExpectedLayer(layer int32) {
|
||||
ti := w.TrackInfo()
|
||||
if ti == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if w.Kind() == webrtc.RTPCodecTypeAudio || ti.Source == livekit.TrackSource_SCREEN_SHARE {
|
||||
// screen share tracks have highly variable bitrate, do not use bit rate based quality for those
|
||||
return
|
||||
}
|
||||
|
||||
expectedBitrate := int64(0)
|
||||
for _, vl := range ti.Layers {
|
||||
l := buffer.VideoQualityToSpatialLayer(vl.Quality, ti)
|
||||
if l <= layer {
|
||||
expectedBitrate += int64(vl.Bitrate)
|
||||
}
|
||||
}
|
||||
|
||||
w.connectionStats.AddBitrateTransition(expectedBitrate)
|
||||
}
|
||||
|
||||
func (w *WebRTCReceiver) SetMaxExpectedSpatialLayer(layer int32) {
|
||||
w.streamTrackerManager.SetMaxExpectedSpatialLayer(layer)
|
||||
w.notifyMaxExpectedLayer(layer)
|
||||
|
||||
if layer == buffer.InvalidLayerSpatial {
|
||||
w.connectionStats.UpdateLayerMute(true)
|
||||
@@ -463,6 +486,7 @@ func (w *WebRTCReceiver) OnMaxPublishedLayerChanged(maxPublishedLayer int32) {
|
||||
dt.UpTrackMaxPublishedLayerChange(maxPublishedLayer)
|
||||
})
|
||||
|
||||
w.notifyMaxExpectedLayer(maxPublishedLayer)
|
||||
w.connectionStats.AddLayerTransition(w.streamTrackerManager.DistanceToDesired())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user