mirror of
https://github.com/livekit/livekit.git
synced 2026-04-15 02:05:47 +00:00
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
package connectionquality
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/rtcscore-go/pkg/rtcmos"
|
|
)
|
|
|
|
const (
|
|
MinScore = float32(1)
|
|
MaxScore = float32(5)
|
|
)
|
|
|
|
func Score2Rating(score float32) livekit.ConnectionQuality {
|
|
if score > 4.0 {
|
|
return livekit.ConnectionQuality_EXCELLENT
|
|
}
|
|
|
|
if score > 3.0 {
|
|
return livekit.ConnectionQuality_GOOD
|
|
}
|
|
return livekit.ConnectionQuality_POOR
|
|
}
|
|
|
|
func clamp(value, min, max float64) float64 {
|
|
return math.Max(min, math.Min(value, max))
|
|
}
|
|
|
|
func getBitRate(interval float64, bytes uint64) float64 {
|
|
return float64(bytes*8) / interval
|
|
}
|
|
|
|
func getFrameRate(interval float64, frames uint32) float64 {
|
|
return float64(frames) / interval
|
|
}
|
|
|
|
func getLossPercentage(expected uint32, lost uint32) float32 {
|
|
if expected == 0 {
|
|
return 0.0
|
|
}
|
|
|
|
return float32(lost) * 100.0 / float32(expected)
|
|
}
|
|
|
|
func int32Ptr(x int32) *int32 {
|
|
return &x
|
|
}
|
|
|
|
func float32Ptr(x float32) *float32 {
|
|
return &x
|
|
}
|
|
|
|
type TrackScoreParams struct {
|
|
Duration time.Duration
|
|
Codec string
|
|
PacketsExpected uint32
|
|
PacketsLost uint32
|
|
Bytes uint64
|
|
Frames uint32
|
|
FrameRateExpected uint32
|
|
Jitter float64
|
|
Rtt uint32
|
|
DtxEnabled bool
|
|
Width uint32
|
|
Height uint32
|
|
IsReducedQuality bool
|
|
}
|
|
|
|
func getRtcMosStat(params TrackScoreParams) rtcmos.Stat {
|
|
return rtcmos.Stat{
|
|
Bitrate: float32(getBitRate(params.Duration.Seconds(), params.Bytes)),
|
|
PacketLoss: getLossPercentage(params.PacketsExpected, params.PacketsLost),
|
|
RoundTripTime: int32Ptr(int32(params.Rtt)),
|
|
BufferDelay: int32Ptr(int32(params.Jitter / 1000.0)),
|
|
}
|
|
}
|
|
|
|
func AudioTrackScore(params TrackScoreParams, normFactor float32) float32 {
|
|
stat := getRtcMosStat(params)
|
|
stat.AudioConfig = &rtcmos.AudioConfig{}
|
|
stat.AudioConfig.Dtx = ¶ms.DtxEnabled
|
|
|
|
scores := rtcmos.Score([]rtcmos.Stat{stat})
|
|
if len(scores) == 1 {
|
|
return float32(clamp(float64(float32(scores[0].AudioScore)*normFactor), float64(MinScore), float64(MaxScore)))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func VideoTrackScore(params TrackScoreParams, normFactor float32) float32 {
|
|
stat := getRtcMosStat(params)
|
|
stat.VideoConfig = &rtcmos.VideoConfig{
|
|
FrameRate: float32Ptr(float32(getFrameRate(params.Duration.Seconds(), params.Frames))),
|
|
Codec: params.Codec,
|
|
Width: int32Ptr(int32(params.Width)),
|
|
Height: int32Ptr(int32(params.Height)),
|
|
}
|
|
if params.FrameRateExpected == 0 {
|
|
stat.VideoConfig.ExpectedFrameRate = stat.VideoConfig.FrameRate
|
|
}
|
|
|
|
scores := rtcmos.Score([]rtcmos.Stat{stat})
|
|
if len(scores) == 1 {
|
|
return float32(clamp(float64(float32(scores[0].VideoScore)*normFactor), float64(MinScore), float64(MaxScore)))
|
|
}
|
|
return 0
|
|
}
|
|
|
|
//
|
|
// rtcmos gives lower score when screen share content is static.
|
|
// That is due to use of bits / pixel / frame in the model.
|
|
// Even though the frame rate is low, the bit rate is also low and
|
|
// the resolution is high. Till rtcmos model can be adapted to that
|
|
// scenario, use loss based scoring.
|
|
//
|
|
func LossBasedTrackScore(params TrackScoreParams) float32 {
|
|
pctLoss := getLossPercentage(params.PacketsExpected, params.PacketsLost)
|
|
// No Loss, excellent
|
|
if pctLoss == 0.0 && !params.IsReducedQuality {
|
|
return MaxScore
|
|
}
|
|
// default when loss is minimal, but reducedQuality
|
|
score := float32(3.5)
|
|
// loss is bad
|
|
if pctLoss >= 4.0 {
|
|
score = 2.0
|
|
} else if pctLoss <= 2.0 && !params.IsReducedQuality {
|
|
// loss is acceptable and not at reduced quality
|
|
score = 4.5
|
|
}
|
|
return score
|
|
}
|