diff --git a/track.go b/track.go index d2754058..b259bf4d 100644 --- a/track.go +++ b/track.go @@ -22,7 +22,8 @@ type Track interface { } func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { - if md.MediaName.Media == "video" { + switch md.MediaName.Media { + case "video": if rtpmap, ok := md.Attribute("rtpmap"); ok { rtpmap = strings.TrimSpace(rtpmap) @@ -36,9 +37,8 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { return newTrackH264FromMediaDescription(payloadType, md) } } - } - if md.MediaName.Media == "audio" { + case "audio": if rtpmap, ok := md.Attribute("rtpmap"); ok { if vals := strings.Split(rtpmap, " "); len(vals) == 2 { tmp, err := strconv.ParseInt(vals[0], 10, 64) @@ -52,7 +52,7 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { } if strings.HasPrefix(vals[1], "opus/") { - return newTrackOpusFromMediaDescription(payloadType, md) + return newTrackOpusFromMediaDescription(payloadType, rtpmap, md) } } } diff --git a/track_aac.go b/track_aac.go index 3ce550f1..22b97b77 100644 --- a/track_aac.go +++ b/track_aac.go @@ -46,7 +46,9 @@ func NewTrackAAC(payloadType uint8, typ int, sampleRate int, }, nil } -func newTrackAACFromMediaDescription(payloadType uint8, md *psdp.MediaDescription) (*TrackAAC, error) { +func newTrackAACFromMediaDescription( + payloadType uint8, + md *psdp.MediaDescription) (*TrackAAC, error) { control := trackFindControl(md) v, ok := md.Attribute("fmtp") diff --git a/track_aac_test.go b/track_aac_test.go index 7e7925ab..bee482ca 100644 --- a/track_aac_test.go +++ b/track_aac_test.go @@ -16,190 +16,6 @@ func TestTrackAACNew(t *testing.T) { require.Equal(t, []byte{0x01, 0x02}, track.aotSpecificConfig) } -func TestTrackAACNewFromMediaDescription(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - track *TrackAAC - }{ - { - "generic", - &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", - }, - }, - }, - &TrackAAC{ - payloadType: 96, - typ: 2, - sampleRate: 48000, - channelCount: 2, - mpegConf: []byte{0x11, 0x90}, - }, - }, - { - "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;", - }, - }, - }, - &TrackAAC{ - payloadType: 96, - typ: 2, - sampleRate: 48000, - channelCount: 2, - mpegConf: []byte{0x11, 0x90}, - }, - }, - } { - t.Run(ca.name, func(t *testing.T) { - track, err := newTrackAACFromMediaDescription(96, ca.md) - require.NoError(t, err) - require.Equal(t, ca.track, track) - }) - } -} - -func TestTrackAACNewFromMediaDescriptionErrors(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - err string - }{ - { - "missing fmtp", - &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", - }, - }, - }, - "fmtp attribute is missing", - }, - { - "invalid fmtp", - &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", - }, - }, - }, - "invalid fmtp (96)", - }, - { - "fmtp without key", - &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", - }, - }, - }, - "invalid fmtp (96 profile-level-id)", - }, - { - "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 (96 profile-level-id=1)", - }, - { - "invalid 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=zz", - }, - }, - }, - "invalid AAC config (zz)", - }, - } { - t.Run(ca.name, func(t *testing.T) { - _, err := newTrackAACFromMediaDescription(96, ca.md) - require.EqualError(t, err, ca.err) - }) - } -} - func TestTrackAACClone(t *testing.T) { track, err := NewTrackAAC(96, 2, 48000, 2, []byte{0x01, 0x02}) require.NoError(t, err) diff --git a/track_generic_test.go b/track_generic_test.go index fd699e0e..2d30833b 100644 --- a/track_generic_test.go +++ b/track_generic_test.go @@ -7,148 +7,30 @@ import ( "github.com/stretchr/testify/require" ) -func TestTrackGenericNewFromMediaDescription(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - track *TrackGeneric - }{ - { - "pcma", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"8"}, +func TestTrackGenericClone(t *testing.T) { + track, err := newTrackGenericFromMediaDescription( + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Port: psdp.RangedPort{Value: 0}, + Protos: []string{"RTP", "AVP"}, + Formats: []string{"98", "96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "98 H265/90000", + }, + { + Key: "fmtp", + Value: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " + + "sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", }, }, - &TrackGeneric{ - clockRate: 8000, - media: "audio", - formats: []string{"8"}, - }, - }, - { - "pcmu", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Port: psdp.RangedPort{Value: 49170}, - Protos: []string{"RTP", "AVP"}, - Formats: []string{"0"}, - }, - }, - &TrackGeneric{ - clockRate: 8000, - media: "audio", - formats: []string{"0"}, - }, - }, - { - "multiple formats", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "video", - Port: psdp.RangedPort{Value: 0}, - Protos: []string{"RTP", "AVP"}, - Formats: []string{"98", "96"}, - }, - Attributes: []psdp.Attribute{ - { - Key: "rtpmap", - Value: "98 H265/90000", - }, - { - Key: "fmtp", - Value: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " + - "sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", - }, - }, - }, - &TrackGeneric{ - clockRate: 90000, - media: "video", - formats: []string{"98", "96"}, - rtpmap: "98 H265/90000", - fmtp: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " + - "sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", - }, - }, - } { - t.Run(ca.name, func(t *testing.T) { - track, err := newTrackGenericFromMediaDescription(ca.md) - require.NoError(t, err) - require.Equal(t, ca.track, track) }) - } -} + require.NoError(t, err) -func TestTrackGenericNewFromMediaDescriptionErrors(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - err string - }{ - { - "no formats", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Protos: []string{"RTP", "AVP"}, - Formats: []string{}, - }, - }, - "unable to get clock rate: no formats provided", - }, - { - "no rtpmap", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "video", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"90"}, - }, - }, - "unable to get clock rate: attribute 'rtpmap' not found", - }, - { - "invalid rtpmap 1", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "video", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"96"}, - }, - Attributes: []psdp.Attribute{ - { - Key: "rtpmap", - Value: "96", - }, - }, - }, - "unable to get clock rate: invalid rtpmap (96)", - }, - { - "invalid rtpmap 2", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "video", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"96"}, - }, - Attributes: []psdp.Attribute{ - { - Key: "rtpmap", - Value: "96 mpeg4-generic", - }, - }, - }, - "unable to get clock rate: invalid rtpmap (96 mpeg4-generic)", - }, - } { - t.Run(ca.name, func(t *testing.T) { - _, err := newTrackGenericFromMediaDescription(ca.md) - require.EqualError(t, err, ca.err) - }) - } + copy := track.clone() + require.NotSame(t, track, copy) + require.Equal(t, track, copy) } diff --git a/track_h264.go b/track_h264.go index bb51cddb..9477eeb9 100644 --- a/track_h264.go +++ b/track_h264.go @@ -77,7 +77,8 @@ func NewTrackH264(payloadType uint8, sps []byte, pps []byte, extradata []byte) ( }, nil } -func newTrackH264FromMediaDescription(payloadType uint8, +func newTrackH264FromMediaDescription( + payloadType uint8, md *psdp.MediaDescription) (*TrackH264, error) { control := trackFindControl(md) diff --git a/track_h264_test.go b/track_h264_test.go index 0057a96a..f64c14bb 100644 --- a/track_h264_test.go +++ b/track_h264_test.go @@ -173,119 +173,6 @@ func TestTrackH264New(t *testing.T) { require.Equal(t, []byte{0x05, 0x06}, track.extradata) } -func TestTrackH264NewFromMediaDescription(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - track *TrackH264 - }{ - { - "generic", - &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", - }, - }, - }, - &TrackH264{ - payloadType: 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, - }, - }, - }, - { - "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;", - }, - }, - }, - &TrackH264{ - payloadType: 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, - }, - }, - }, - { - "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", - }, - }, - }, - &TrackH264{ - payloadType: 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, - }, - }, - }, - } { - t.Run(ca.name, func(t *testing.T) { - track, err := newTrackH264FromMediaDescription(96, ca.md) - require.NoError(t, err) - require.Equal(t, ca.track, track) - }) - } -} - func TestTrackH264Clone(t *testing.T) { track, err := NewTrackH264(96, []byte{0x01, 0x02}, []byte{0x03, 0x04}, []byte{0x05, 0x06}) require.NoError(t, err) diff --git a/track_opus.go b/track_opus.go index b40af878..4882a4a9 100644 --- a/track_opus.go +++ b/track_opus.go @@ -27,18 +27,14 @@ func NewTrackOpus(payloadType uint8, sampleRate int, channelCount int) (*TrackOp }, nil } -func newTrackOpusFromMediaDescription(payloadType uint8, +func newTrackOpusFromMediaDescription( + payloadType uint8, + rtpmap string, md *psdp.MediaDescription) (*TrackOpus, error) { control := trackFindControl(md) - - v, ok := md.Attribute("rtpmap") - if !ok { - return nil, fmt.Errorf("rtpmap attribute is missing") - } - - tmp := strings.SplitN(v, "/", 3) + tmp := strings.SplitN(rtpmap, "/", 3) if len(tmp) != 3 { - return nil, fmt.Errorf("invalid rtpmap (%v)", v) + return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmap) } sampleRate, err := strconv.ParseInt(tmp[1], 10, 64) diff --git a/track_opus_test.go b/track_opus_test.go index dee910d0..5d22cd89 100644 --- a/track_opus_test.go +++ b/track_opus_test.go @@ -14,89 +14,6 @@ func TestTrackOpusNew(t *testing.T) { require.Equal(t, 2, track.channelCount) } -func TestTrackOpusNewFromMediaDescription(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - track *TrackOpus - }{ - { - "generic", - &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", - }, - }, - }, - &TrackOpus{ - payloadType: 96, - sampleRate: 48000, - channelCount: 2, - }, - }, - } { - t.Run(ca.name, func(t *testing.T) { - track, err := newTrackOpusFromMediaDescription(96, ca.md) - require.NoError(t, err) - require.Equal(t, ca.track, track) - }) - } -} - -func TestTrackOpusNewFromMediaDescriptionErrors(t *testing.T) { - for _, ca := range []struct { - name string - md *psdp.MediaDescription - err string - }{ - { - "missing rtpmap", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"96"}, - }, - Attributes: []psdp.Attribute{}, - }, - "rtpmap attribute is missing", - }, - { - "invalid rtpmap", - &psdp.MediaDescription{ - MediaName: psdp.MediaName{ - Media: "audio", - Protos: []string{"RTP", "AVP"}, - Formats: []string{"96"}, - }, - Attributes: []psdp.Attribute{ - { - Key: "rtpmap", - Value: "96", - }, - }, - }, - "invalid rtpmap (96)", - }, - } { - t.Run(ca.name, func(t *testing.T) { - _, err := newTrackOpusFromMediaDescription(96, ca.md) - require.EqualError(t, err, ca.err) - }) - } -} - func TestTracOpusClone(t *testing.T) { track, err := NewTrackOpus(96, 96000, 4) require.NoError(t, err) diff --git a/track_test.go b/track_test.go index 6d07945f..09147136 100644 --- a/track_test.go +++ b/track_test.go @@ -30,6 +30,22 @@ func TestTrackNewFromMediaDescription(t *testing.T) { formats: []string{"8"}, }, }, + { + "pcmu", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "audio", + Port: psdp.RangedPort{Value: 49170}, + Protos: []string{"RTP", "AVP"}, + Formats: []string{"0"}, + }, + }, + &TrackGeneric{ + clockRate: 8000, + media: "audio", + formats: []string{"0"}, + }, + }, { "aac", &psdp.MediaDescription{ @@ -84,6 +100,33 @@ func TestTrackNewFromMediaDescription(t *testing.T) { mpegConf: []byte{0x11, 0x90}, }, }, + { + "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;", + }, + }, + }, + &TrackAAC{ + payloadType: 96, + typ: 2, + sampleRate: 48000, + channelCount: 2, + mpegConf: []byte{0x11, 0x90}, + }, + }, { "opus", &psdp.MediaDescription{ @@ -160,6 +203,72 @@ func TestTrackNewFromMediaDescription(t *testing.T) { payloadType: 96, }, }, + { + "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;", + }, + }, + }, + &TrackH264{ + payloadType: 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, + }, + }, + }, + { + "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", + }, + }, + }, + &TrackH264{ + payloadType: 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, + }, + }, + }, { "h265", &psdp.MediaDescription{ @@ -175,7 +284,7 @@ func TestTrackNewFromMediaDescription(t *testing.T) { }, { Key: "fmtp", - Value: "96 96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwB4mZgJ; " + + Value: "96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwB4mZgJ; " + "sprop-sps=QgEBAWAAAAMAkAAAAwAAAwB4oAPAgBDllmZpJMrgEAAAAwAQAAADAeCA; sprop-pps=RAHBcrRiQA==", }, }, @@ -185,10 +294,40 @@ func TestTrackNewFromMediaDescription(t *testing.T) { media: "video", formats: []string{"96"}, rtpmap: "96 H265/90000", - fmtp: "96 96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwB4mZgJ; " + + fmtp: "96 sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwB4mZgJ; " + "sprop-sps=QgEBAWAAAAMAkAAAAwAAAwB4oAPAgBDllmZpJMrgEAAAAwAQAAADAeCA; sprop-pps=RAHBcrRiQA==", }, }, + { + "multiple formats", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Port: psdp.RangedPort{Value: 0}, + Protos: []string{"RTP", "AVP"}, + Formats: []string{"98", "96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "98 H265/90000", + }, + { + Key: "fmtp", + Value: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " + + "sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", + }, + }, + }, + &TrackGeneric{ + clockRate: 90000, + media: "video", + formats: []string{"98", "96"}, + rtpmap: "98 H265/90000", + fmtp: "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; " + + "sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=", + }, + }, } { t.Run(ca.name, func(t *testing.T) { track, err := newTrackFromMediaDescription(ca.md) @@ -198,6 +337,177 @@ func TestTrackNewFromMediaDescription(t *testing.T) { } } +func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { + for _, ca := range []struct { + name string + md *psdp.MediaDescription + err string + }{ + { + "generic no formats", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "audio", + Protos: []string{"RTP", "AVP"}, + Formats: []string{}, + }, + }, + "unable to get clock rate: no formats provided", + }, + { + "generic no rtpmap", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"90"}, + }, + }, + "unable to get clock rate: attribute 'rtpmap' not found", + }, + { + "generic invalid rtpmap 1", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96", + }, + }, + }, + "unable to get clock rate: invalid rtpmap (96)", + }, + { + "generic invalid rtpmap 2", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 mpeg4-generic", + }, + }, + }, + "unable to get clock rate: invalid rtpmap (96 mpeg4-generic)", + }, + { + "aac missing fmtp", + &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", + }, + }, + }, + "fmtp attribute is missing", + }, + { + "aac invalid fmtp", + &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", + }, + }, + }, + "invalid fmtp (96)", + }, + { + "aac fmtp without key", + &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", + }, + }, + }, + "invalid fmtp (96 profile-level-id)", + }, + { + "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 (96 profile-level-id=1)", + }, + { + "aac invalid 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=zz", + }, + }, + }, + "invalid AAC config (zz)", + }, + } { + t.Run(ca.name, func(t *testing.T) { + _, err := newTrackFromMediaDescription(ca.md) + require.EqualError(t, err, ca.err) + }) + } +} + func TestTrackURL(t *testing.T) { for _, ca := range []struct { name string