package formats import ( "testing" psdp "github.com/pion/sdp/v3" "github.com/stretchr/testify/require" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" ) func intPtr(v int) *int { return &v } func boolPtr(v bool) *bool { return &v } func TestUnmarshal(t *testing.T) { for _, ca := range []struct { name string md *psdp.MediaDescription format Format }{ { "audio g711 pcma", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"8"}, }, }, &G711{}, }, { "audio g711 pcmu", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"0"}, }, }, &G711{ MULaw: true, }, }, { "audio g722", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"9"}, }, }, &G722{}, }, { "audio lpcm 8", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"97"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "97 L8/48000/2", }, }, }, &LPCM{ PayloadTyp: 97, BitDepth: 8, SampleRate: 48000, ChannelCount: 2, }, }, { "audio lpcm 16", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"97"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "97 L16/96000/2", }, }, }, &LPCM{ PayloadTyp: 97, BitDepth: 16, SampleRate: 96000, ChannelCount: 2, }, }, { "audio lpcm 16 with no explicit channel", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"97"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "97 L16/16000", }, }, }, &LPCM{ PayloadTyp: 97, BitDepth: 16, SampleRate: 16000, ChannelCount: 1, }, }, { "audio lpcm 24", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"98"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "98 L24/44100/4", }, }, }, &LPCM{ PayloadTyp: 98, BitDepth: 24, SampleRate: 44100, ChannelCount: 4, }, }, { "audio mpeg2 audio", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"14"}, }, }, &MPEG2Audio{}, }, { "audio aac", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3; config=11900810", }, { Key: "control", Value: "", }, }, }, &MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: mpeg4audio.ObjectTypeAACLC, SampleRate: 48000, ChannelCount: 2, }, SizeLength: 13, IndexLength: 3, IndexDeltaLength: 3, }, }, { "audio aac vlc rtsp server", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3; config=1190;", }, }, }, &MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: mpeg4audio.ObjectTypeAACLC, SampleRate: 48000, ChannelCount: 2, }, SizeLength: 13, IndexLength: 3, IndexDeltaLength: 3, }, }, { "audio aac without indexlength", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 streamtype=3;profile-level-id=14;mode=AAC-hbr;config=1190;sizeLength=13", }, { Key: "control", Value: "", }, }, }, &MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: mpeg4audio.ObjectTypeAACLC, SampleRate: 48000, ChannelCount: 2, }, SizeLength: 13, IndexLength: 0, IndexDeltaLength: 0, }, }, { "audio aac lc latm", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 MP4A-LATM/24000/2", }, { Key: "fmtp", Value: "96 110 profile-level-id=1; bitrate=64000; cpresent=0; " + "object=2; config=400026203fc0", }, }, }, &MPEG4AudioLATM{ PayloadTyp: 96, SampleRate: 24000, Channels: 2, ProfileLevelID: 30, Bitrate: intPtr(64000), CPresent: boolPtr(false), Object: 2, Config: []byte{0x40, 0x00, 0x26, 0x20, 0x3f, 0xc0}, }, }, { "audio aac v2 latm", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"110"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "110 MP4A-LATM/24000/1", }, { Key: "fmtp", Value: "110 profile-level-id=15; object=2; cpresent=0; " + "config=400026103fc0; SBR-enabled=1", }, }, }, &MPEG4AudioLATM{ PayloadTyp: 110, SampleRate: 24000, Channels: 1, ProfileLevelID: 15, CPresent: boolPtr(false), Object: 2, SBREnabled: boolPtr(true), Config: []byte{0x40, 0x00, 0x26, 0x10, 0x3f, 0xc0}, }, }, { "audio vorbis", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 VORBIS/44100/2", }, { Key: "fmtp", Value: "96 configuration=AQIDBA==", }, }, }, &Vorbis{ PayloadTyp: 96, SampleRate: 44100, ChannelCount: 2, Configuration: []byte{0x01, 0x02, 0x03, 0x04}, }, }, { "audio opus", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 opus/48000/2", }, { Key: "fmtp", Value: "96 sprop-stereo=1", }, }, }, &Opus{ PayloadTyp: 96, IsStereo: true, }, }, { "video jpeg", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"26"}, }, }, &MJPEG{}, }, { "video mpeg2 video", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"32"}, }, }, &MPEG2Video{}, }, { "video mpeg4 video", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 MP4V-ES/90000", }, { Key: "fmtp", Value: "96 profile-level-id=1; " + "config=000001B001000001B58913000001000000012000C48D8AEE053C04641443000001B24C61766335382E3133342E313030", }, }, }, &MPEG4Video{ PayloadTyp: 96, ProfileLevelID: 1, Config: []byte{ 0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01, 0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x8a, 0xee, 0x05, 0x3c, 0x04, 0x64, 0x14, 0x43, 0x00, 0x00, 0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38, 0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30, }, }, }, { "video h264", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 packetization-mode=1; " + "sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,aO48gA==; profile-level-id=64000C", }, }, }, &H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x3d, 0x08, }, PPS: []byte{ 0x68, 0xee, 0x3c, 0x80, }, PacketizationMode: 1, }, }, { "video h264 with a space at the end of rtpmap", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000 ", }, }, }, &H264{ PayloadTyp: 96, }, }, { "video h264 vlc rtsp server", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 packetization-mode=1;profile-level-id=64001f;" + "sprop-parameter-sets=Z2QAH6zZQFAFuwFsgAAAAwCAAAAeB4wYyw==,aOvjyyLA;", }, }, }, &H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50, 0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x1e, 0x07, 0x8c, 0x18, 0xcb, }, PPS: []byte{ 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, }, PacketizationMode: 1, }, }, { "video h264 sprop-parameter-sets with extra data", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 packetization-mode=1; " + "sprop-parameter-sets=Z2QAKawTMUB4BEfeA+oCAgPgAAADACAAAAZSgA==,aPqPLA==,aF6jzAMA; profile-level-id=640029", }, }, }, &H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x29, 0xac, 0x13, 0x31, 0x40, 0x78, 0x04, 0x47, 0xde, 0x03, 0xea, 0x02, 0x02, 0x03, 0xe0, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06, 0x52, 0x80, }, PPS: []byte{ 0x68, 0xfa, 0x8f, 0x2c, }, PacketizationMode: 1, }, }, { "video h264 empty sprop-parameter-sets", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 packetization-mode=1; sprop-parameter-sets=", }, }, }, &H264{ PayloadTyp: 96, PacketizationMode: 1, }, }, { "video h265", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H265/90000", }, { Key: "fmtp", Value: "96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwB4mZgJ; " + "sprop-sps=QgEBAWAAAAMAkAAAAwAAAwB4oAPAgBDllmZpJMrgEAAAAwAQAAADAeCA; " + "sprop-pps=RAHBcrRiQA==; sprop-max-don-diff=2", }, }, }, &H265{ PayloadTyp: 96, VPS: []byte{ 0x40, 0x1, 0xc, 0x1, 0xff, 0xff, 0x1, 0x60, 0x0, 0x0, 0x3, 0x0, 0x90, 0x0, 0x0, 0x3, 0x0, 0x0, 0x3, 0x0, 0x78, 0x99, 0x98, 0x9, }, SPS: []byte{ 0x42, 0x1, 0x1, 0x1, 0x60, 0x0, 0x0, 0x3, 0x0, 0x90, 0x0, 0x0, 0x3, 0x0, 0x0, 0x3, 0x0, 0x78, 0xa0, 0x3, 0xc0, 0x80, 0x10, 0xe5, 0x96, 0x66, 0x69, 0x24, 0xca, 0xe0, 0x10, 0x0, 0x0, 0x3, 0x0, 0x10, 0x0, 0x0, 0x3, 0x1, 0xe0, 0x80, }, PPS: []byte{ 0x44, 0x1, 0xc1, 0x72, 0xb4, 0x62, 0x40, }, MaxDONDiff: 2, }, }, { "video vp8", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 VP8/90000", }, { Key: "fmtp", Value: "96 max-fr=123;max-fs=456", }, }, }, &VP8{ PayloadTyp: 96, MaxFR: intPtr(123), MaxFS: intPtr(456), }, }, { "video vp9", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 VP9/90000", }, { Key: "fmtp", Value: "96 max-fr=123;max-fs=456;profile-id=789", }, }, }, &VP9{ PayloadTyp: 96, MaxFR: intPtr(123), MaxFS: intPtr(456), ProfileID: intPtr(789), }, }, { "application", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "application", Protos: []string{"RTP", "AVP"}, Formats: []string{"98"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "98 MetaData/80000", }, { Key: "rtcp-mux", }, }, }, &Generic{ PayloadTyp: 98, RTPMap: "MetaData/80000", ClockRat: 80000, }, }, { "application without clock rate", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "application", Protos: []string{"RTP", "AVP"}, Formats: []string{"107"}, }, }, &Generic{ PayloadTyp: 107, }, }, { "application invalid rtpmap", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "application", Protos: []string{"RTP", "AVP"}, Formats: []string{"98"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "98 custom", }, }, }, &Generic{ PayloadTyp: 98, RTPMap: "custom", }, }, { "application invalid rtpmap 2", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "application", Protos: []string{"RTP", "AVP"}, Formats: []string{"98"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "98 custom/aaa", }, }, }, &Generic{ PayloadTyp: 98, RTPMap: "custom/aaa", }, }, { "application tp-link", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "application", Protos: []string{"RTP", "AVP"}, Formats: []string{"smart/1/90000"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "95 TP-LINK/90000", }, }, }, &Generic{ PayloadTyp: 95, RTPMap: "TP-LINK/90000", ClockRat: 90000, }, }, } { t.Run(ca.name, func(t *testing.T) { format, err := Unmarshal(ca.md, ca.md.MediaName.Formats[0]) require.NoError(t, err) require.Equal(t, ca.format, format) }) } } func TestUnmarshalErrors(t *testing.T) { for _, ca := range []struct { name string md *psdp.MediaDescription err string }{ { "audio lpcm invalid clock", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"97"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "97 L8/", }, }, }, "strconv.ParseInt: parsing \"\": invalid syntax", }, { "audio lpcm invalid channels", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"97"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "97 L8/48000/", }, }, }, "strconv.ParseInt: parsing \"\": invalid syntax", }, { "audio aac missing config", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1", }, }, }, "config is missing", }, { "audio aac invalid config 1", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; config=zz", }, }, }, "invalid AAC config: zz", }, { "audio aac invalid config 2", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; config=aa", }, }, }, "invalid AAC config: aa", }, { "audio aac missing sizelength", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; config=1190", }, }, }, "sizelength is missing", }, { "audio aac invalid sizelength", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; sizelength=aaa", }, }, }, "invalid AAC SizeLength: aaa", }, { "audio aac invalid indexlength", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; indexlength=aaa", }, }, }, "invalid AAC IndexLength: aaa", }, { "audio aac invalid indexdeltalength", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 mpeg4-generic/48000/2", }, { Key: "fmtp", Value: "96 profile-level-id=1; indexdeltalength=aaa", }, }, }, "invalid AAC IndexDeltaLength: aaa", }, { "audio vorbis missing configuration", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 VORBIS/44100/2", }, { Key: "fmtp", Value: "96 aa=bb", }, }, }, "config is missing", }, { "audio opus invalid 1", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 opus/48000", }, }, }, "invalid clock (48000)", }, { "audio opus invalid 2", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 opus/aa/2", }, }, }, "strconv.ParseInt: parsing \"aa\": invalid syntax", }, { "audio opus invalid 3", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 opus/48000/aa", }, }, }, "strconv.ParseInt: parsing \"aa\": invalid syntax", }, { "video h264 invalid sps", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 sprop-parameter-sets=kkk,vvv", }, }, }, "invalid sprop-parameter-sets (kkk,vvv)", }, { "video h264 invalid pps", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,vvv", }, }, }, "invalid sprop-parameter-sets (Z2QADKw7ULBLQgAAAwACAAADAD0I,vvv)", }, { "video h264 invalid packetization-mode", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", Protos: []string{"RTP", "AVP"}, Formats: []string{"96"}, }, Attributes: []psdp.Attribute{ { Key: "rtpmap", Value: "96 H264/90000", }, { Key: "fmtp", Value: "96 packetization-mode=aaa", }, }, }, "invalid packetization-mode (aaa)", }, } { t.Run(ca.name, func(t *testing.T) { _, err := Unmarshal(ca.md, ca.md.MediaName.Formats[0]) require.EqualError(t, err, ca.err) }) } }