Files
livekit/pkg/sfu/testutils/data.go
T
Raja SubramanianandClaude Opus 5.5 099e03fdf8 sfu: forward the dependency descriptor for VP8 simulcast (#4987)
* sfu: forward the dependency descriptor for VP8 simulcast

libwebrtc sends VP8 with a one byte payload descriptor when it sends the
dependency descriptor, so a VP8 subscriber gets neither picture IDs nor the
descriptor and finds frame references by sequence number alone. Under loss
with temporal filtering it stalls until a key frame.

DDForwarder rewrites the publisher's descriptor for each forwarded packet:
frame numbers continue across simulcast layer switches and stream restarts,
the structure goes out with the current layer's key frame, and the active
decode targets follow the forwarded temporal layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: keep VP8 descriptor frame numbers from going back

Rebase frame numbers only when a new stream would go back past the last
number sent: on a layer switch, a stream restart (also when it starts with a
delta frame) and a new forwarder on the same downtrack, which now carries
over the last number sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: set VP8 descriptor decode targets from the packet's layer

The active decode targets use the temporal layer the packet is forwarded at,
not the selector's next layer on the last packet before a down switch. They
also go out on key frames, combined with the publisher's own mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: carry the current active decode targets on every DD packet

The parser puts the stream's current mask on every packet, so a mask change
on a dropped packet still reaches the VP8 descriptor forwarder, which no
longer tracks the publisher's mask itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* sfu: take the first DD active decode targets mask at any sequence number

A publisher whose first packet has RTP sequence number 0 never had its key
frame's mask applied, so every packet reported no active decode targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 12:08:49 +05:30

173 lines
5.0 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 testutils
import (
"time"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
"github.com/livekit/livekit-server/pkg/sfu/buffer"
dd "github.com/livekit/livekit-server/pkg/sfu/rtpextension/dependencydescriptor"
"github.com/livekit/mediatransportutil/pkg/codec"
)
// -----------------------------------------------------------
type TestExtPacketParams struct {
Marker bool
IsKeyFrame bool
PayloadType uint8
SequenceNumber uint16
SNCycles int
Timestamp uint32
TSCycles int
SSRC uint32
PayloadSize int
PaddingSize byte
ArrivalTime time.Time
VideoLayer buffer.VideoLayer
IsOutOfOrder bool
}
// -----------------------------------------------------------
func GetTestExtPacket(params *TestExtPacketParams) (*buffer.ExtPacket, error) {
packet := rtp.Packet{
Header: rtp.Header{
Version: 2,
Padding: params.PaddingSize != 0,
Marker: params.Marker,
PayloadType: params.PayloadType,
SequenceNumber: params.SequenceNumber,
Timestamp: params.Timestamp,
SSRC: params.SSRC,
PaddingSize: params.PaddingSize,
},
Payload: make([]byte, params.PayloadSize),
}
raw, err := packet.Marshal()
if err != nil {
return nil, err
}
ep := &buffer.ExtPacket{
VideoLayer: params.VideoLayer,
ExtSequenceNumber: uint64(params.SNCycles<<16) + uint64(params.SequenceNumber),
ExtTimestamp: uint64(params.TSCycles<<32) + uint64(params.Timestamp),
Arrival: params.ArrivalTime.UnixNano(),
Packet: &packet,
IsKeyFrame: params.IsKeyFrame,
RawPacket: raw,
IsOutOfOrder: params.IsOutOfOrder,
}
return ep, nil
}
// --------------------------------------
func GetTestExtPacketVP8(params *TestExtPacketParams, vp8 *codec.VP8) (*buffer.ExtPacket, error) {
ep, err := GetTestExtPacket(params)
if err != nil {
return nil, err
}
ep.IsKeyFrame = vp8.IsKeyFrame
vp8Copy := *vp8 // keep the packet independent of later changes to vp8
ep.VP8 = &vp8Copy
if ep.DependencyDescriptor == nil {
ep.Temporal = int32(vp8.TID)
}
return ep, nil
}
// --------------------------------------
type TestDDParams struct {
FirstPacketInFrame bool
ExtFrameNum uint64
TemporalID int
// decode target indications as one character per decode target: S, R, D or -
DTIs string
FrameDiffs []int
DecodeTargets []buffer.DependencyDescriptorDecodeTarget
}
// GetTestExtPacketVP8DD returns a VP8 packet with a dependency descriptor and the
// minimized VP8 payload descriptor that libwebrtc sends with it: no TID, no Y bit and
// no picture ID.
func GetTestExtPacketVP8DD(params *TestExtPacketParams, ddParams *TestDDParams) (*buffer.ExtPacket, error) {
ep, err := GetTestExtPacketVP8(params, &codec.VP8{S: ddParams.FirstPacketInFrame, IsKeyFrame: params.IsKeyFrame})
if err != nil {
return nil, err
}
dtis := make([]dd.DecodeTargetIndication, 0, len(ddParams.DTIs))
for _, c := range ddParams.DTIs {
switch c {
case 'S':
dtis = append(dtis, dd.DecodeTargetSwitch)
case 'R':
dtis = append(dtis, dd.DecodeTargetRequired)
case 'D':
dtis = append(dtis, dd.DecodeTargetDiscardable)
default:
dtis = append(dtis, dd.DecodeTargetNotPresent)
}
}
ep.Temporal = int32(ddParams.TemporalID)
ep.DependencyDescriptor = &buffer.ExtDependencyDescriptor{
Descriptor: &dd.DependencyDescriptor{
FirstPacketInFrame: ddParams.FirstPacketInFrame,
LastPacketInFrame: params.Marker,
FrameDependencies: &dd.FrameDependencyTemplate{
TemporalId: ddParams.TemporalID,
DecodeTargetIndications: dtis,
FrameDiffs: ddParams.FrameDiffs,
},
},
DecodeTargets: ddParams.DecodeTargets,
ExtFrameNum: ddParams.ExtFrameNum,
// all decode targets active, as the parser reports after a structure
ActiveDecodeTargetsMask: 1<<len(ddParams.DecodeTargets) - 1,
}
return ep, nil
}
// TestVP8DecodeTargets are the decode targets of libwebrtc's three layer VP8 structure,
// sorted from high to low.
var TestVP8DecodeTargets = []buffer.DependencyDescriptorDecodeTarget{
{Target: 2, Layer: buffer.VideoLayer{Temporal: 2}},
{Target: 1, Layer: buffer.VideoLayer{Temporal: 1}},
{Target: 0, Layer: buffer.VideoLayer{Temporal: 0}},
}
// --------------------------------------
var TestVP8Codec = webrtc.RTPCodecCapability{
MimeType: "video/vp8",
ClockRate: 90000,
}
var TestOpusCodec = webrtc.RTPCodecCapability{
MimeType: "audio/opus",
ClockRate: 48000,
}
// --------------------------------------