snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
This commit is contained in:
224
pkg/rtc/mediaengine_test.go
Normal file
224
pkg/rtc/mediaengine_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// 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 rtc
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/livekit/protocol/codecs/mime"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
h264MainProfilePacketizationMode0Fmtp = "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f"
|
||||
h264MainProfilePacketizationMode1Fmtp = "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f"
|
||||
h264BaselineProfilePacketizationMode0Fmtp = "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f"
|
||||
h264BaselineProfilePacketizationMode1Fmtp = "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"
|
||||
)
|
||||
|
||||
func TestIsCodecEnabled(t *testing.T) {
|
||||
t.Run("empty fmtp requirement should match all", func(t *testing.T) {
|
||||
enabledCodecs := []*livekit.Codec{{Mime: "video/h264"}}
|
||||
require.True(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: mime.MimeTypeH264.String(), SDPFmtpLine: "special"}))
|
||||
require.True(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: mime.MimeTypeH264.String()}))
|
||||
require.False(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: mime.MimeTypeVP8.String()}))
|
||||
})
|
||||
|
||||
t.Run("when fmtp is provided, require match", func(t *testing.T) {
|
||||
enabledCodecs := []*livekit.Codec{{Mime: "video/h264", FmtpLine: "special"}}
|
||||
require.True(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: mime.MimeTypeH264.String(), SDPFmtpLine: "special"}))
|
||||
require.False(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: mime.MimeTypeH264.String()}))
|
||||
require.False(t, IsCodecEnabled(enabledCodecs, webrtc.RTPCodecCapability{MimeType: mime.MimeTypeVP8.String()}))
|
||||
})
|
||||
|
||||
t.Run("strict fmtp requires an exact fmtp, mime alone is not enough", func(t *testing.T) {
|
||||
cap := webrtc.RTPCodecCapability{
|
||||
MimeType: mime.MimeTypeH264.String(),
|
||||
SDPFmtpLine: h264MainProfilePacketizationMode1Fmtp,
|
||||
}
|
||||
|
||||
// mime-only config opts into every non-strict codec, but never a strict one
|
||||
mimeOnly := []*livekit.Codec{{Mime: "video/h264"}}
|
||||
require.True(t, isCodecEnabledWithFmtp(mimeOnly, cap, false))
|
||||
require.False(t, isCodecEnabledWithFmtp(mimeOnly, cap, true))
|
||||
|
||||
// spelling the fmtp out enables it
|
||||
explicit := []*livekit.Codec{{Mime: "video/h264", FmtpLine: h264MainProfilePacketizationMode1Fmtp}}
|
||||
require.True(t, isCodecEnabledWithFmtp(explicit, cap, true))
|
||||
|
||||
// ...but only for the profile that was asked for
|
||||
require.False(t, isCodecEnabledWithFmtp(explicit, webrtc.RTPCodecCapability{
|
||||
MimeType: mime.MimeTypeH264.String(),
|
||||
SDPFmtpLine: h264MainProfilePacketizationMode0Fmtp,
|
||||
}, true))
|
||||
})
|
||||
}
|
||||
|
||||
type offeredCodec struct {
|
||||
payloadType webrtc.PayloadType
|
||||
name string
|
||||
fmtp string
|
||||
}
|
||||
|
||||
func (o offeredCodec) String() string {
|
||||
return strconv.Itoa(int(o.payloadType)) + " " + o.name + " " + o.fmtp
|
||||
}
|
||||
|
||||
// offeredVideoCodecs returns the video codecs an offer built from enabledCodecs carries.
|
||||
// It goes through a real offer rather than inspecting the MediaEngine so that payload
|
||||
// type assignment, which is what collides, is covered too.
|
||||
func offeredVideoCodecs(t *testing.T, enabledCodecs []*livekit.Codec, filterOutH264HighProfile bool) []offeredCodec {
|
||||
t.Helper()
|
||||
|
||||
me, err := createMediaEngine(enabledCodecs, DirectionConfig{}, filterOutH264HighProfile)
|
||||
require.NoError(t, err)
|
||||
|
||||
pc, err := webrtc.NewAPI(webrtc.WithMediaEngine(me)).NewPeerConnection(webrtc.Configuration{})
|
||||
require.NoError(t, err)
|
||||
defer pc.Close()
|
||||
|
||||
_, err = pc.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
|
||||
require.NoError(t, err)
|
||||
|
||||
offer, err := pc.CreateOffer(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var codecs []offeredCodec
|
||||
indexByPayloadType := make(map[webrtc.PayloadType]int)
|
||||
for _, line := range strings.Split(offer.SDP, "\r\n") {
|
||||
attr, value, found := strings.Cut(line, ":")
|
||||
if !found || (attr != "a=rtpmap" && attr != "a=fmtp") {
|
||||
continue
|
||||
}
|
||||
|
||||
rawPayloadType, rest, _ := strings.Cut(value, " ")
|
||||
payloadType, err := strconv.Atoi(rawPayloadType)
|
||||
require.NoError(t, err)
|
||||
|
||||
if attr == "a=rtpmap" {
|
||||
indexByPayloadType[webrtc.PayloadType(payloadType)] = len(codecs)
|
||||
codecs = append(codecs, offeredCodec{payloadType: webrtc.PayloadType(payloadType), name: rest})
|
||||
continue
|
||||
}
|
||||
|
||||
i, ok := indexByPayloadType[webrtc.PayloadType(payloadType)]
|
||||
require.True(t, ok, "fmtp for payload type %d without an rtpmap", payloadType)
|
||||
codecs[i].fmtp = rest
|
||||
}
|
||||
|
||||
require.NotEmpty(t, codecs)
|
||||
return codecs
|
||||
}
|
||||
|
||||
func hasOfferedFmtp(codecs []offeredCodec, mimeType mime.MimeType, fmtp string) bool {
|
||||
for _, c := range codecs {
|
||||
name, _, _ := strings.Cut(c.name, "/")
|
||||
if mime.NormalizeMimeType("video/"+name) == mimeType && c.fmtp == fmtp {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// defaultEnabledCodecs mirrors what roomallocator derives from config, so these tests
|
||||
// track the shipped default codec set instead of a hand-maintained copy of it.
|
||||
func defaultEnabledCodecs() []*livekit.Codec {
|
||||
codecs := make([]*livekit.Codec, 0, len(config.DefaultConfig.Room.EnabledCodecs))
|
||||
for _, c := range config.DefaultConfig.Room.EnabledCodecs {
|
||||
codecs = append(codecs, &livekit.Codec{Mime: c.Mime, FmtpLine: c.FmtpLine})
|
||||
}
|
||||
return codecs
|
||||
}
|
||||
|
||||
func TestRegisterCodecsH264Profiles(t *testing.T) {
|
||||
t.Run("constrained baseline are offered by default", func(t *testing.T) {
|
||||
codecs := offeredVideoCodecs(t, defaultEnabledCodecs(), false)
|
||||
|
||||
for _, fmtp := range []string{
|
||||
"level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f",
|
||||
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
|
||||
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032",
|
||||
} {
|
||||
require.True(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, fmtp), "missing %q in %v", fmtp, codecs)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("main/baseline profile is not offered unless explicitly enabled", func(t *testing.T) {
|
||||
codecs := offeredVideoCodecs(t, defaultEnabledCodecs(), false)
|
||||
|
||||
require.False(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, h264MainProfilePacketizationMode0Fmtp), "%v", codecs)
|
||||
require.False(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, h264MainProfilePacketizationMode1Fmtp), "%v", codecs)
|
||||
require.False(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, h264BaselineProfilePacketizationMode0Fmtp), "%v", codecs)
|
||||
require.False(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, h264BaselineProfilePacketizationMode1Fmtp), "%v", codecs)
|
||||
})
|
||||
|
||||
t.Run("main/baseline profile is offered when explicitly enabled", func(t *testing.T) {
|
||||
for _, fmtp := range []string{
|
||||
h264MainProfilePacketizationMode0Fmtp,
|
||||
h264MainProfilePacketizationMode1Fmtp,
|
||||
h264BaselineProfilePacketizationMode0Fmtp,
|
||||
h264BaselineProfilePacketizationMode1Fmtp,
|
||||
} {
|
||||
t.Run(fmtp, func(t *testing.T) {
|
||||
enabled := append(defaultEnabledCodecs(), &livekit.Codec{
|
||||
Mime: mime.MimeTypeH264.String(),
|
||||
FmtpLine: fmtp,
|
||||
})
|
||||
|
||||
codecs := offeredVideoCodecs(t, enabled, false)
|
||||
require.True(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, fmtp), "missing %q in %v", fmtp, codecs)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("high profile is filtered out for the answerer", func(t *testing.T) {
|
||||
highProfileFmtp := "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032"
|
||||
|
||||
codecs := offeredVideoCodecs(t, defaultEnabledCodecs(), true)
|
||||
require.False(t, hasOfferedFmtp(codecs, mime.MimeTypeH264, highProfileFmtp), "%v", codecs)
|
||||
|
||||
// the other profiles survive the filter
|
||||
require.True(t, hasOfferedFmtp(codecs, mime.MimeTypeH264,
|
||||
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f"), "%v", codecs)
|
||||
})
|
||||
}
|
||||
|
||||
// Every video codec claims its own payload type plus the next one for its RTX stream,
|
||||
// so a codec whose payload type lands on another's RTX slot makes pion refuse to build
|
||||
// the MediaEngine at all.
|
||||
func TestRegisterCodecsPayloadTypesAreUnique(t *testing.T) {
|
||||
enabled := append(defaultEnabledCodecs(),
|
||||
&livekit.Codec{Mime: mime.MimeTypeH264.String(), FmtpLine: h264MainProfilePacketizationMode0Fmtp},
|
||||
&livekit.Codec{Mime: mime.MimeTypeH264.String(), FmtpLine: h264MainProfilePacketizationMode1Fmtp},
|
||||
)
|
||||
|
||||
for _, filterOutH264HighProfile := range []bool{false, true} {
|
||||
codecs := offeredVideoCodecs(t, enabled, filterOutH264HighProfile)
|
||||
|
||||
seen := make(map[webrtc.PayloadType]offeredCodec, len(codecs))
|
||||
for _, c := range codecs {
|
||||
previous, duplicate := seen[c.payloadType]
|
||||
require.False(t, duplicate, "payload type %d claimed by both %v and %v", c.payloadType, previous, c)
|
||||
seen[c.payloadType] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user