mirror of
https://github.com/livekit/livekit.git
synced 2026-04-27 17:15:26 +00:00
9e327b1f3c
* Make connection quality not too optimistic.
With score normalization, the quality indicator showed good
under conditions which should have normally showed some badness.
So, a few things in this PR
- Do not normalize scores
- Pick the weakest link as the representative score (moving away from
averaging)
- For down track direction, when reporting delta stats, take the number
of packets sent actually. If there are holes in the feed (upstream
packet loss), down tracks should not be penalised for that loss.
State of things in connection quality feature
- Audio uses rtcscore-go (with a change to accommodate RED codec). This
follows the E-model.
- Camera uses rtcscore-go. No change here. NOTE: THe rtscore here is
purely based on bits per pixel per frame (bpf). This has the following
existing issues (no change, these were already there)
o Does not take packet loss, jitter, rtt into account
o Expected frame rate is not available. So, measured frame rate is
used as expected frame rate also. If expected frame rate were available,
the score could be reduced for lower frame rates.
- Screen share tracks: No change. This uses the very old simple loss
based thresholding for scoring. As the bit rate varies a lot based on
content and rtcscore video algorithm used for camera relies on
bits per pixel per frame, this could produce a very low value
(large width/height encoded in a small number of bits because of static content)
and hence a low score. So, the old loss based thresholding is used.
* clean up
* update rtcscore pointer
* fix tests
* log lines reformat
* WIP commit
* WIP commit
* update mute of receiver
* WIP commit
* WIP commit
* start adding tests
* take min score if quality matches
* start adding bytes based scoring
* clean up
* more clean up
* Use Fuse
* log quality drop
* clean up debug log
* - Use number of windows for wait to make things simpler
- track no layer expected case
- always update transition
- always call updateScore
165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
package rtc
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/logger"
|
|
)
|
|
|
|
const (
|
|
initialQualityUpdateWait = 10 * time.Second
|
|
)
|
|
|
|
type DynacastQualityParams struct {
|
|
MimeType string
|
|
Logger logger.Logger
|
|
}
|
|
|
|
// DynacastQuality manages max subscribed quality of a single receiver of a media track
|
|
type DynacastQuality struct {
|
|
params DynacastQualityParams
|
|
|
|
// quality level enable/disable
|
|
lock sync.RWMutex
|
|
initialized bool
|
|
maxSubscriberQuality map[livekit.ParticipantID]livekit.VideoQuality
|
|
maxSubscriberNodeQuality map[livekit.NodeID]livekit.VideoQuality
|
|
maxSubscribedQuality livekit.VideoQuality
|
|
maxQualityTimer *time.Timer
|
|
|
|
onSubscribedMaxQualityChange func(maxSubscribedQuality livekit.VideoQuality)
|
|
}
|
|
|
|
func NewDynacastQuality(params DynacastQualityParams) *DynacastQuality {
|
|
return &DynacastQuality{
|
|
params: params,
|
|
maxSubscriberQuality: make(map[livekit.ParticipantID]livekit.VideoQuality),
|
|
maxSubscriberNodeQuality: make(map[livekit.NodeID]livekit.VideoQuality),
|
|
}
|
|
}
|
|
|
|
func (d *DynacastQuality) Start() {
|
|
d.reset()
|
|
}
|
|
|
|
func (d *DynacastQuality) Restart() {
|
|
d.reset()
|
|
}
|
|
|
|
func (d *DynacastQuality) Stop() {
|
|
d.stopMaxQualityTimer()
|
|
}
|
|
|
|
func (d *DynacastQuality) OnSubscribedMaxQualityChange(f func(maxSubscribedQuality livekit.VideoQuality)) {
|
|
d.onSubscribedMaxQualityChange = f
|
|
}
|
|
|
|
func (d *DynacastQuality) NotifySubscriberMaxQuality(subscriberID livekit.ParticipantID, quality livekit.VideoQuality) {
|
|
d.params.Logger.Debugw(
|
|
"setting subscriber max quality",
|
|
"mime", d.params.MimeType,
|
|
"subscriberID", subscriberID,
|
|
"quality", quality.String(),
|
|
)
|
|
|
|
d.lock.Lock()
|
|
if quality == livekit.VideoQuality_OFF {
|
|
delete(d.maxSubscriberQuality, subscriberID)
|
|
} else {
|
|
d.maxSubscriberQuality[subscriberID] = quality
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
d.updateQualityChange(false)
|
|
}
|
|
|
|
func (d *DynacastQuality) NotifySubscriberNodeMaxQuality(nodeID livekit.NodeID, quality livekit.VideoQuality) {
|
|
d.params.Logger.Debugw(
|
|
"setting subscriber node max quality",
|
|
"mime", d.params.MimeType,
|
|
"subscriberNodeID", nodeID,
|
|
"quality", quality.String(),
|
|
)
|
|
|
|
d.lock.Lock()
|
|
if quality == livekit.VideoQuality_OFF {
|
|
delete(d.maxSubscriberNodeQuality, nodeID)
|
|
} else {
|
|
d.maxSubscriberNodeQuality[nodeID] = quality
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
d.updateQualityChange(false)
|
|
}
|
|
|
|
func (d *DynacastQuality) reset() {
|
|
d.lock.Lock()
|
|
d.initialized = false
|
|
d.lock.Unlock()
|
|
|
|
d.startMaxQualityTimer()
|
|
}
|
|
|
|
func (d *DynacastQuality) updateQualityChange(force bool) {
|
|
d.lock.Lock()
|
|
maxSubscribedQuality := livekit.VideoQuality_OFF
|
|
for _, subQuality := range d.maxSubscriberQuality {
|
|
if maxSubscribedQuality == livekit.VideoQuality_OFF || (subQuality != livekit.VideoQuality_OFF && subQuality > maxSubscribedQuality) {
|
|
maxSubscribedQuality = subQuality
|
|
}
|
|
}
|
|
for _, nodeQuality := range d.maxSubscriberNodeQuality {
|
|
if maxSubscribedQuality == livekit.VideoQuality_OFF || (nodeQuality != livekit.VideoQuality_OFF && nodeQuality > maxSubscribedQuality) {
|
|
maxSubscribedQuality = nodeQuality
|
|
}
|
|
}
|
|
|
|
if maxSubscribedQuality == d.maxSubscribedQuality && d.initialized && !force {
|
|
d.lock.Unlock()
|
|
return
|
|
}
|
|
|
|
d.initialized = true
|
|
d.maxSubscribedQuality = maxSubscribedQuality
|
|
d.params.Logger.Debugw("notifying quality change",
|
|
"mime", d.params.MimeType,
|
|
"maxSubscriberQuality", d.maxSubscriberQuality,
|
|
"maxSubscriberNodeQuality", d.maxSubscriberNodeQuality,
|
|
"maxSubscribedQuality", d.maxSubscribedQuality,
|
|
"force", force,
|
|
)
|
|
onSubscribedMaxQualityChange := d.onSubscribedMaxQualityChange
|
|
d.lock.Unlock()
|
|
|
|
if onSubscribedMaxQualityChange != nil {
|
|
onSubscribedMaxQualityChange(maxSubscribedQuality)
|
|
}
|
|
}
|
|
|
|
func (d *DynacastQuality) startMaxQualityTimer() {
|
|
d.lock.Lock()
|
|
defer d.lock.Unlock()
|
|
|
|
if d.maxQualityTimer != nil {
|
|
d.maxQualityTimer.Stop()
|
|
d.maxQualityTimer = nil
|
|
}
|
|
|
|
d.maxQualityTimer = time.AfterFunc(initialQualityUpdateWait, func() {
|
|
d.stopMaxQualityTimer()
|
|
d.updateQualityChange(true)
|
|
})
|
|
}
|
|
|
|
func (d *DynacastQuality) stopMaxQualityTimer() {
|
|
d.lock.Lock()
|
|
defer d.lock.Unlock()
|
|
|
|
if d.maxQualityTimer != nil {
|
|
d.maxQualityTimer.Stop()
|
|
d.maxQualityTimer = nil
|
|
}
|
|
}
|