mirror of
https://github.com/livekit/livekit.git
synced 2026-10-15 09:00:14 +00:00
* feat: acquire requested video layer directly at HIGH quality by default Two changes that together remove the visible low->high quality ramp for a new subscriber (both publisher-first and subscriber-first join orders): 1. Default a subscriber's initial video quality to HIGH on bind instead of LOW for adaptive stream, so the subscribed max layer is the top layer. Adaptive stream clients can still scale down afterwards based on viewport. 2. On initial layer acquisition the forwarder/selector latch directly onto the allocator's target (the requested top layer) instead of opportunistically latching onto the first lower key frame that arrives. A short initial-acquisition grace aims the target at the requested layer; if it does not show up in time, the target falls back to the highest layer seen so acquisition never stalls. Always on - no configuration flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: gate start-at-desired-quality behind EnableStartAtDesiredQuality flag Put the "acquire requested video layer directly at HIGH quality" behavior behind a per-subscriber EnableStartAtDesiredQuality flag (default off, so the original low->high ramp-up is restored unless enabled). Plumbed from config.RTC.EnableStartAtDesiredQuality through ParticipantParams -> SubscribedTrack/DownTrack -> Forwarder -> simulcast selector, gating all three behavior changes: the HIGH default on bind, the forwarder's initial-acquisition grace, and the selector's direct-latch-onto-target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * remove config. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
181 lines
4.4 KiB
Go
181 lines
4.4 KiB
Go
// Copyright 2023 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package videolayerselector
|
|
|
|
import (
|
|
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
|
"github.com/livekit/livekit-server/pkg/sfu/videolayerselector/temporallayerselector"
|
|
"github.com/livekit/protocol/logger"
|
|
)
|
|
|
|
type Base struct {
|
|
logger logger.Logger
|
|
|
|
tls temporallayerselector.TemporalLayerSelector
|
|
|
|
maxLayer buffer.VideoLayer
|
|
maxSeenLayer buffer.VideoLayer
|
|
|
|
targetLayer buffer.VideoLayer
|
|
previousTargetLayer buffer.VideoLayer
|
|
|
|
requestSpatial int32
|
|
|
|
currentLayer buffer.VideoLayer
|
|
previousLayer buffer.VideoLayer
|
|
|
|
// when set, on initial acquisition latch directly onto the target (requested) layer instead of
|
|
// opportunistically latching onto the first lower-layer key frame that arrives (see Simulcast.Select)
|
|
enableStartAtDesiredQuality bool
|
|
}
|
|
|
|
func NewBase(logger logger.Logger) *Base {
|
|
return &Base{
|
|
logger: logger,
|
|
maxLayer: buffer.InvalidLayer,
|
|
maxSeenLayer: buffer.InvalidLayer,
|
|
targetLayer: buffer.InvalidLayer, // start off with nothing, let streamallocator/opportunistic forwarder set the target
|
|
previousTargetLayer: buffer.InvalidLayer,
|
|
requestSpatial: buffer.InvalidLayerSpatial,
|
|
currentLayer: buffer.InvalidLayer,
|
|
previousLayer: buffer.InvalidLayer,
|
|
}
|
|
}
|
|
|
|
func (b *Base) getBase() *Base {
|
|
return b
|
|
}
|
|
|
|
func (b *Base) getLogger() logger.Logger {
|
|
return b.logger
|
|
}
|
|
|
|
func (b *Base) IsOvershootOkay() bool {
|
|
return false
|
|
}
|
|
|
|
func (b *Base) SetTemporalLayerSelector(tls temporallayerselector.TemporalLayerSelector) {
|
|
b.tls = tls
|
|
}
|
|
|
|
func (b *Base) SetEnableStartAtDesiredQuality(enable bool) {
|
|
b.enableStartAtDesiredQuality = enable
|
|
}
|
|
|
|
func (b *Base) SetMax(maxLayer buffer.VideoLayer) {
|
|
b.maxLayer = maxLayer
|
|
}
|
|
|
|
func (b *Base) SetMaxSpatial(layer int32) {
|
|
b.maxLayer.Spatial = layer
|
|
}
|
|
|
|
func (b *Base) SetMaxTemporal(layer int32) {
|
|
b.maxLayer.Temporal = layer
|
|
}
|
|
|
|
func (b *Base) GetMax() buffer.VideoLayer {
|
|
return b.maxLayer
|
|
}
|
|
|
|
func (b *Base) SetTarget(targetLayer buffer.VideoLayer) {
|
|
b.previousTargetLayer = targetLayer
|
|
b.targetLayer = targetLayer
|
|
}
|
|
|
|
func (b *Base) GetTarget() buffer.VideoLayer {
|
|
return b.targetLayer
|
|
}
|
|
|
|
func (b *Base) SetRequestSpatial(layer int32) {
|
|
b.requestSpatial = layer
|
|
}
|
|
|
|
func (b *Base) GetRequestSpatial() int32 {
|
|
return b.requestSpatial
|
|
}
|
|
|
|
func (b *Base) CheckSync() (locked bool, layer int32) {
|
|
layer = b.GetRequestSpatial()
|
|
locked = layer == b.GetCurrent().Spatial
|
|
return
|
|
}
|
|
|
|
func (b *Base) SetMaxSeen(maxSeenLayer buffer.VideoLayer) {
|
|
b.maxSeenLayer = maxSeenLayer
|
|
}
|
|
|
|
func (b *Base) SetMaxSeenSpatial(layer int32) {
|
|
b.maxSeenLayer.Spatial = layer
|
|
}
|
|
|
|
func (b *Base) SetMaxSeenTemporal(layer int32) {
|
|
b.maxSeenLayer.Temporal = layer
|
|
}
|
|
|
|
func (b *Base) GetMaxSeen() buffer.VideoLayer {
|
|
return b.maxSeenLayer
|
|
}
|
|
|
|
func (b *Base) SetCurrent(currentLayer buffer.VideoLayer) {
|
|
b.currentLayer = currentLayer
|
|
}
|
|
|
|
func (b *Base) GetCurrent() buffer.VideoLayer {
|
|
return b.currentLayer
|
|
}
|
|
|
|
func (b *Base) Select(_extPkt *buffer.ExtPacket, _layer int32) (result VideoLayerSelectorResult) {
|
|
return
|
|
}
|
|
|
|
func (b *Base) Rollback() {
|
|
b.logger.Debugw(
|
|
"rolling back",
|
|
"previous", b.previousLayer,
|
|
"current", b.currentLayer,
|
|
"previousTarget", b.previousTargetLayer,
|
|
"target", b.targetLayer,
|
|
"max", b.maxLayer,
|
|
"req", b.requestSpatial,
|
|
"maxSeen", b.maxSeenLayer,
|
|
)
|
|
b.currentLayer = b.previousLayer
|
|
b.targetLayer = b.previousTargetLayer
|
|
}
|
|
|
|
func (b *Base) SelectTemporal(extPkt *buffer.ExtPacket) int32 {
|
|
if b.tls != nil {
|
|
this, next := b.tls.Select(extPkt, b.currentLayer.Temporal, b.targetLayer.Temporal)
|
|
if next != b.currentLayer.Temporal {
|
|
previousLayer := b.currentLayer
|
|
b.currentLayer.Temporal = next
|
|
|
|
b.logger.Debugw(
|
|
"updating temporal layer",
|
|
"previous", previousLayer,
|
|
"current", b.currentLayer,
|
|
"target", b.targetLayer,
|
|
"max", b.maxLayer,
|
|
"req", b.requestSpatial,
|
|
"maxSeen", b.maxSeenLayer,
|
|
)
|
|
}
|
|
return this
|
|
}
|
|
|
|
return b.currentLayer.Temporal
|
|
}
|