From 8cfba1308b1d3d673bfb8c8522a618e188496b40 Mon Sep 17 00:00:00 2001 From: cnderrauber Date: Tue, 4 Apr 2023 09:28:47 +0800 Subject: [PATCH] Add test case for munged sdp (#1574) * Add test case for munged sdp * clean code --- pkg/rtc/participant_internal_test.go | 179 +++++++++++++++++++++++++++ pkg/rtc/transport_test.go | 37 ++++++ 2 files changed, 216 insertions(+) diff --git a/pkg/rtc/participant_internal_test.go b/pkg/rtc/participant_internal_test.go index f04c277b5..a182606e8 100644 --- a/pkg/rtc/participant_internal_test.go +++ b/pkg/rtc/participant_internal_test.go @@ -1,6 +1,7 @@ package rtc import ( + "fmt" "strings" "testing" "time" @@ -425,6 +426,184 @@ func TestDisableCodecs(t *testing.T) { require.False(t, found264) } +func TestPreferVideoCodecForPublisher(t *testing.T) { + participant := newParticipantForTestWithOpts("123", &participantOpts{ + publisher: true, + }) + participant.SetMigrateState(types.MigrateStateComplete) + + pc, err := webrtc.NewPeerConnection(webrtc.Configuration{}) + require.NoError(t, err) + defer pc.Close() + + for i := 0; i < 2; i++ { + // publish h264 track without client preferred codec + trackCid := fmt.Sprintf("preferh264video%d", i) + participant.AddTrack(&livekit.AddTrackRequest{ + Type: livekit.TrackType_VIDEO, + Name: "video", + Width: 1280, + Height: 720, + Source: livekit.TrackSource_CAMERA, + SimulcastCodecs: []*livekit.SimulcastCodec{ + { + Codec: "h264", + Cid: trackCid, + }, + }, + }) + + track, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "video/vp8"}, trackCid, trackCid) + require.NoError(t, err) + transceiver, err := pc.AddTransceiverFromTrack(track, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendrecv}) + require.NoError(t, err) + sdp, err := pc.CreateOffer(nil) + require.NoError(t, err) + pc.SetLocalDescription(sdp) + codecs := transceiver.Receiver().GetParameters().Codecs + + // h264 should not be preferred + require.NotEqual(t, codecs[0].MimeType, "video/h264") + + sink := &routingfakes.FakeMessageSink{} + participant.SetResponseSink(sink) + var answer webrtc.SessionDescription + var answerReceived atomic.Bool + sink.WriteMessageStub = func(msg proto.Message) error { + if res, ok := msg.(*livekit.SignalResponse); ok { + if res.GetAnswer() != nil { + answer = FromProtoSessionDescription(res.GetAnswer()) + pc.SetRemoteDescription(answer) + answerReceived.Store(true) + } + } + return nil + } + participant.HandleOffer(sdp) + + require.Eventually(t, func() bool { return answerReceived.Load() }, 5*time.Second, 10*time.Millisecond) + + var h264Preferred bool + parsed, err := answer.Unmarshal() + require.NoError(t, err) + var videoSectionIndex int + for _, m := range parsed.MediaDescriptions { + if m.MediaName.Media == "video" { + if videoSectionIndex == i { + codecs, err := codecsFromMediaDescription(m) + require.NoError(t, err) + if strings.EqualFold(codecs[0].Name, "h264") { + h264Preferred = true + break + } + } + videoSectionIndex++ + } + } + + require.Truef(t, h264Preferred, "h264 should be preferred for video section %d, answer sdp: \n%s", i, answer.SDP) + } +} + +func TestPreferAudioCodecForRed(t *testing.T) { + participant := newParticipantForTestWithOpts("123", &participantOpts{ + publisher: true, + }) + participant.SetMigrateState(types.MigrateStateComplete) + + me := webrtc.MediaEngine{} + me.RegisterDefaultCodecs() + require.NoError(t, me.RegisterCodec(webrtc.RTPCodecParameters{ + RTPCodecCapability: redCodecCapability, + PayloadType: 63, + }, webrtc.RTPCodecTypeAudio)) + + api := webrtc.NewAPI(webrtc.WithMediaEngine(&me)) + pc, err := api.NewPeerConnection(webrtc.Configuration{}) + require.NoError(t, err) + defer pc.Close() + + for i, disableRed := range []bool{false, true} { + t.Run(fmt.Sprintf("disableRed=%v", disableRed), func(t *testing.T) { + trackCid := fmt.Sprintf("audiotrack%d", i) + participant.AddTrack(&livekit.AddTrackRequest{ + Type: livekit.TrackType_AUDIO, + DisableRed: disableRed, + Cid: trackCid, + }) + track, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, trackCid, trackCid) + require.NoError(t, err) + transceiver, err := pc.AddTransceiverFromTrack(track, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendrecv}) + require.NoError(t, err) + codecs := transceiver.Sender().GetParameters().Codecs + for i, c := range codecs { + if c.MimeType == "audio/opus" && i != 0 { + codecs[0], codecs[i] = codecs[i], codecs[0] + break + } + } + transceiver.SetCodecPreferences(codecs) + sdp, err := pc.CreateOffer(nil) + require.NoError(t, err) + pc.SetLocalDescription(sdp) + // opus should be preferred + require.Equal(t, codecs[0].MimeType, "audio/opus", sdp) + + sink := &routingfakes.FakeMessageSink{} + participant.SetResponseSink(sink) + var answer webrtc.SessionDescription + var answerReceived atomic.Bool + sink.WriteMessageStub = func(msg proto.Message) error { + if res, ok := msg.(*livekit.SignalResponse); ok { + if res.GetAnswer() != nil { + answer = FromProtoSessionDescription(res.GetAnswer()) + pc.SetRemoteDescription(answer) + answerReceived.Store(true) + } + } + return nil + } + participant.HandleOffer(sdp) + + require.Eventually(t, func() bool { return answerReceived.Load() }, 5*time.Second, 10*time.Millisecond) + + var redPreferred bool + parsed, err := answer.Unmarshal() + require.NoError(t, err) + var audioSectionIndex int + for _, m := range parsed.MediaDescriptions { + if m.MediaName.Media == "audio" { + if audioSectionIndex == i { + codecs, err := codecsFromMediaDescription(m) + require.NoError(t, err) + // nack is always enabled. if red is preferred, server will not generate nack request + var nackEnabled bool + for _, c := range codecs { + if c.Name == "opus" { + for _, fb := range c.RTCPFeedback { + if strings.Contains(fb, "nack") { + nackEnabled = true + break + } + } + } + } + require.True(t, nackEnabled, "nack should be enabled for opus") + + if strings.EqualFold(codecs[0].Name, "red") { + redPreferred = true + break + } + } + audioSectionIndex++ + } + } + require.Equalf(t, !disableRed, redPreferred, "offer : \n%s\nanswer sdp: \n%s", sdp, answer.SDP) + }) + } + +} + type participantOpts struct { permissions *livekit.ParticipantPermission protocolVersion types.ProtocolVersion diff --git a/pkg/rtc/transport_test.go b/pkg/rtc/transport_test.go index 5e388facb..e7365531e 100644 --- a/pkg/rtc/transport_test.go +++ b/pkg/rtc/transport_test.go @@ -506,3 +506,40 @@ func connectTransports(t *testing.T, offerer, answerer *PCTransport, isICERestar return answerer.pc.ICEConnectionState() == webrtc.ICEConnectionStateConnected }, 10*time.Second, time.Millisecond*10, "answerer did not become connected") } + +func TestConfigureAudioTransceiver(t *testing.T) { + pc, err := webrtc.NewPeerConnection(webrtc.Configuration{}) + require.NoError(t, err) + defer pc.Close() + + for _, testcase := range []struct { + nack bool + stereo bool + }{ + {false, false}, + {true, false}, + {false, true}, + {true, true}, + } { + t.Run(fmt.Sprintf("nack=%v,stereo=%v", testcase.nack, testcase.stereo), func(t *testing.T) { + tr, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RtpTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) + require.NoError(t, err) + + configureAudioTransceiver(tr, testcase.stereo, testcase.nack) + codecs := tr.Sender().GetParameters().Codecs + for _, codec := range codecs { + if strings.Contains(codec.MimeType, webrtc.MimeTypeOpus) { + require.Equal(t, testcase.stereo, strings.Contains(codec.SDPFmtpLine, "sprop-stereo=1")) + var nackEnabled bool + for _, fb := range codec.RTCPFeedback { + if fb.Type == webrtc.TypeRTCPFBNACK { + nackEnabled = true + break + } + } + require.Equal(t, testcase.nack, nackEnabled) + } + } + }) + } +}