mirror of
https://github.com/livekit/livekit.git
synced 2026-08-29 03:18:47 +00:00
Implements FlexFEC-03 (RFC 8627) end to end in the SFU, in both directions: - Upstream: decode FlexFEC repair packets from publishers and recover lost media in the receive buffer before forwarding (pkg/sfu/flexfec/decoder.go, pkg/sfu/buffer). Recovered packets are surfaced via Prometheus counters. - Downstream: generate FlexFEC for subscribers off the downtrack, paced alongside media (pkg/sfu/downtrack_fec.go, pkg/sfu/pacer). - Negotiation: advertise and match flexfec-03 in the media engine and transport SDP, gated by new config knobs (pkg/rtc, pkg/config, config-sample.yaml). - Telemetry: livekit_fec_* metrics for sent/received/recovered packets (pkg/telemetry/prometheus/packets.go). Tests: unit coverage for the decoder, buffer recovery, downtrack generation, and transport negotiation, plus an end-to-end integration test and the test-client support it needs (test/flexfec_test.go, test/client).
111 lines
3.6 KiB
Go
111 lines
3.6 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 utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pion/interceptor"
|
|
"github.com/pion/rtp"
|
|
"github.com/pion/webrtc/v4"
|
|
|
|
"github.com/livekit/protocol/codecs/mime"
|
|
"github.com/livekit/protocol/livekit"
|
|
)
|
|
|
|
// Do a fuzzy find for a codec in the list of codecs
|
|
// Used for lookup up a codec in an existing list to find a match
|
|
func CodecParametersFuzzySearch(needle webrtc.RTPCodecParameters, haystack []webrtc.RTPCodecParameters) (webrtc.RTPCodecParameters, error) {
|
|
// First attempt to match on MimeType + SDPFmtpLine
|
|
for _, c := range haystack {
|
|
if mime.IsMimeTypeStringEqual(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
|
|
c.RTPCodecCapability.SDPFmtpLine == needle.RTPCodecCapability.SDPFmtpLine {
|
|
return c, nil
|
|
}
|
|
}
|
|
|
|
// Fallback to just MimeType
|
|
for _, c := range haystack {
|
|
if mime.IsMimeTypeStringEqual(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
|
|
return c, nil
|
|
}
|
|
}
|
|
|
|
return webrtc.RTPCodecParameters{}, webrtc.ErrCodecNotFound
|
|
}
|
|
|
|
// Given a CodecParameters find the RTX CodecParameters if one exists
|
|
func FindRTXPayloadType(needle webrtc.PayloadType, haystack []webrtc.RTPCodecParameters) webrtc.PayloadType {
|
|
aptStr := fmt.Sprintf("apt=%d", needle)
|
|
for _, c := range haystack {
|
|
if aptStr == c.SDPFmtpLine {
|
|
return c.PayloadType
|
|
}
|
|
}
|
|
|
|
return webrtc.PayloadType(0)
|
|
}
|
|
|
|
// FindFlexFECPayloadType returns the negotiated flexfec-03 payload type, or 0
|
|
// if flexfec was not negotiated.
|
|
func FindFlexFECPayloadType(haystack []webrtc.RTPCodecParameters) webrtc.PayloadType {
|
|
for _, c := range haystack {
|
|
if strings.EqualFold(c.MimeType, webrtc.MimeTypeFlexFEC03) {
|
|
return c.PayloadType
|
|
}
|
|
}
|
|
|
|
return webrtc.PayloadType(0)
|
|
}
|
|
|
|
// GetHeaderExtensionID returns the ID of a header extension, or 0 if not found
|
|
func GetHeaderExtensionID(extensions []interceptor.RTPHeaderExtension, extension webrtc.RTPHeaderExtensionCapability) int {
|
|
for _, h := range extensions {
|
|
if extension.URI == h.URI {
|
|
return h.ID
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
var (
|
|
ErrInvalidRTPVersion = errors.New("invalid RTP version")
|
|
ErrRTPPayloadTypeMismatch = errors.New("RTP payload type mismatch")
|
|
ErrRTPSSRCMismatch = errors.New("RTP SSRC mismatch")
|
|
)
|
|
|
|
// ValidateRTPPacket checks for a valid RTP packet and returns an error if fields are incorrect
|
|
func ValidateRTPPacket(pkt *rtp.Packet, expectedPayloadType uint8, expectedSSRC uint32) error {
|
|
if pkt.Version != 2 {
|
|
return fmt.Errorf("%w, expected: 2, actual: %d", ErrInvalidRTPVersion, pkt.Version)
|
|
}
|
|
|
|
if expectedPayloadType != 0 && pkt.PayloadType != expectedPayloadType {
|
|
return fmt.Errorf("%w, expected: %d, actual: %d", ErrRTPPayloadTypeMismatch, expectedPayloadType, pkt.PayloadType)
|
|
}
|
|
|
|
if expectedSSRC != 0 && pkt.SSRC != expectedSSRC {
|
|
return fmt.Errorf("%w, expected: %d, actual: %d", ErrRTPSSRCMismatch, expectedSSRC, pkt.SSRC)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func IsSimulcastMode(m livekit.VideoLayer_Mode) bool {
|
|
return m == livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM || m == livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR
|
|
}
|