// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package sdp import ( "errors" "reflect" "testing" "github.com/stretchr/testify/require" ) func getTestSessionDescription() SessionDescription { return SessionDescription{ MediaDescriptions: []*MediaDescription{ { MediaName: MediaName{ Media: "video", Port: RangedPort{ Value: 51372, }, Protos: []string{"RTP", "AVP"}, Formats: []string{"120", "121", "126", "97", "98", "111"}, }, Attributes: []Attribute{ NewAttribute("fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1", ""), NewAttribute("fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1", ""), NewAttribute("fmtp:98 profile-level-id=42e01e; packetization-mode=1", ""), NewAttribute("fmtp:120 max-fs=12288;max-fr=60", ""), NewAttribute("fmtp:121 max-fs=12288;max-fr=60", ""), NewAttribute("rtpmap:120 VP8/90000", ""), NewAttribute("rtpmap:121 VP9/90000", ""), NewAttribute("rtpmap:126 H264/90000", ""), NewAttribute("rtpmap:97 H264/90000", ""), NewAttribute("rtpmap:98 H264/90000", ""), NewAttribute("rtpmap:111 opus/48000/2", ""), NewAttribute("rtcp-fb:97 ccm fir", ""), NewAttribute("rtcp-fb:97 nack", ""), NewAttribute("rtcp-fb:97 nack pli", ""), NewAttribute("rtcp-fb:* transport-cc", ""), NewAttribute("rtcp-fb:* nack", ""), }, }, }, } } func TestGetPayloadTypeForVP8(t *testing.T) { for _, test := range []struct { Codec Codec Expected uint8 }{ { Codec: Codec{ Name: "VP8", }, Expected: 120, }, { Codec: Codec{ Name: "VP9", }, Expected: 121, }, { Codec: Codec{ Name: "H264", Fmtp: "profile-level-id=42e01f;level-asymmetry-allowed=1", }, Expected: 97, }, { Codec: Codec{ Name: "H264", Fmtp: "level-asymmetry-allowed=1;profile-level-id=42e01f", }, Expected: 97, }, { Codec: Codec{ Name: "H264", Fmtp: "profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1", }, Expected: 126, }, } { sd := getTestSessionDescription() actual, err := sd.GetPayloadTypeForCodec(test.Codec) if got, want := err, error(nil); !errors.Is(got, want) { t.Fatalf("GetPayloadTypeForCodec(): err=%v, want=%v", got, want) } if actual != test.Expected { t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", test.Expected, actual) } } } func TestGetCodecForPayloadType(t *testing.T) { for _, test := range []struct { name string SD SessionDescription PayloadType uint8 Expected Codec }{ { "vp8", getTestSessionDescription(), 120, Codec{ PayloadType: 120, Name: "VP8", ClockRate: 90000, Fmtp: "max-fs=12288;max-fr=60", RTCPFeedback: []string{"transport-cc", "nack"}, }, }, { "vp9", getTestSessionDescription(), 121, Codec{ PayloadType: 121, Name: "VP9", ClockRate: 90000, Fmtp: "max-fs=12288;max-fr=60", RTCPFeedback: []string{"transport-cc", "nack"}, }, }, { "h264 126", getTestSessionDescription(), 126, Codec{ PayloadType: 126, Name: "H264", ClockRate: 90000, Fmtp: "profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1", RTCPFeedback: []string{"transport-cc", "nack"}, }, }, { "h264 97", getTestSessionDescription(), 97, Codec{ PayloadType: 97, Name: "H264", ClockRate: 90000, Fmtp: "profile-level-id=42e01f;level-asymmetry-allowed=1", RTCPFeedback: []string{"ccm fir", "nack", "nack pli", "transport-cc"}, }, }, { "h264 98", getTestSessionDescription(), 98, Codec{ PayloadType: 98, Name: "H264", ClockRate: 90000, Fmtp: "profile-level-id=42e01e; packetization-mode=1", RTCPFeedback: []string{"transport-cc", "nack"}, }, }, { "pcmu without rtpmap", SessionDescription{ MediaDescriptions: []*MediaDescription{ { MediaName: MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"0", "8"}, }, }, }, }, 0, Codec{ PayloadType: 0, Name: "PCMU", ClockRate: 8000, }, }, { "pcma without rtpmap", SessionDescription{ MediaDescriptions: []*MediaDescription{ { MediaName: MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"0", "8"}, }, }, }, }, 8, Codec{ PayloadType: 8, Name: "PCMA", ClockRate: 8000, }, }, } { t.Run(test.name, func(t *testing.T) { actual, err := test.SD.GetCodecForPayloadType(test.PayloadType) if got, want := err, error(nil); !errors.Is(got, want) { t.Fatalf("GetCodecForPayloadType(): err=%v, want=%v", got, want) } if !reflect.DeepEqual(actual, test.Expected) { t.Errorf("error:\n\nEXPECTED:\n%v\nACTUAL:\n%v", test.Expected, actual) } }) } } func TestNewSessionID(t *testing.T) { minVal := uint64(0x7FFFFFFFFFFFFFFF) maxVal := uint64(0) for i := 0; i < 10000; i++ { r, err := newSessionID() if err != nil { t.Fatal(err) } if r > (1<<63)-1 { t.Fatalf("Session ID must be less than 2**64-1, got %d", r) } if r < minVal { minVal = r } if r > maxVal { maxVal = r } } if minVal > 0x1000000000000000 { t.Error("Value around lower boundary was not generated") } if maxVal < 0x7000000000000000 { t.Error("Value around upper boundary was not generated") } } func TestCodecMultipleValues(t *testing.T) { for _, test := range []struct { name string attributeToAdd string expectedError error }{ { "multiple name", "rtpmap:120 VP9/90000", errMultipleName, }, { "multiple clockrate", "rtpmap:120 VP8/80000", errMultipleClockRate, }, { "multiple encoding parameters", "rtpmap:111 opus/48000/3", errMultipleEncodingParameters, }, { "multiple fmtp", "fmtp:126 multiple-fmtp", errMultipleFmtp, }, } { t.Run(test.name, func(t *testing.T) { sd := getTestSessionDescription() sd.MediaDescriptions[0].Attributes = append( sd.MediaDescriptions[0].Attributes, NewPropertyAttribute(test.attributeToAdd), ) _, err := sd.GetPayloadTypeForCodec(Codec{Name: "VP8/90000"}) require.ErrorIs(t, test.expectedError, err) }) } }