diff --git a/track.go b/track.go index fed82e53..6cc28ce1 100644 --- a/track.go +++ b/track.go @@ -25,37 +25,28 @@ type Track interface { } func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { - switch md.MediaName.Media { - case "video": - if rtpmap, ok := md.Attribute("rtpmap"); ok { - rtpmap = strings.TrimSpace(rtpmap) + if rtpmap, ok := md.Attribute("rtpmap"); ok { + rtpmap = strings.TrimSpace(rtpmap) - if vals := strings.Split(rtpmap, " "); len(vals) == 2 && vals[1] == "H264/90000" { - tmp, err := strconv.ParseInt(vals[0], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid payload type '%s'", vals[0]) - } + if rtpmapParts := strings.Split(rtpmap, " "); len(rtpmapParts) == 2 { + tmp, err := strconv.ParseInt(rtpmapParts[0], 10, 64) + if err == nil { payloadType := uint8(tmp) - return newTrackH264FromMediaDescription(payloadType, md) - } - } + switch { + case md.MediaName.Media == "video": + if rtpmapParts[1] == "H264/90000" { + return newTrackH264FromMediaDescription(payloadType, md) + } - 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) - if err != nil { - return nil, fmt.Errorf("invalid payload type '%s'", vals[0]) - } - payloadType := uint8(tmp) + case md.MediaName.Media == "audio": + switch { + case strings.HasPrefix(strings.ToLower(rtpmapParts[1]), "mpeg4-generic/"): + return newTrackAACFromMediaDescription(payloadType, md) - if strings.HasPrefix(strings.ToLower(vals[1]), "mpeg4-generic/") { - return newTrackAACFromMediaDescription(payloadType, md) - } - - if strings.HasPrefix(vals[1], "opus/") { - return newTrackOpusFromMediaDescription(payloadType, rtpmap, md) + case strings.HasPrefix(rtpmapParts[1], "opus/"): + return newTrackOpusFromMediaDescription(payloadType, rtpmapParts[1], md) + } } } } @@ -75,7 +66,7 @@ func trackFindControl(md *psdp.MediaDescription) string { func trackURL(t Track, contentBase *base.URL) (*base.URL, error) { if contentBase == nil { - return nil, fmt.Errorf("no Content-Base header provided") + return nil, fmt.Errorf("Content-Base header not provided") } control := t.GetControl() diff --git a/track_aac.go b/track_aac.go index 59b81506..9e9acc20 100644 --- a/track_aac.go +++ b/track_aac.go @@ -86,10 +86,7 @@ func newTrackAACFromMediaDescription( } // re-encode the conf to normalize it - enc, err = mpegConf.Encode() - if err != nil { - return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1]) - } + enc, _ = mpegConf.Encode() return &TrackAAC{ control: control, diff --git a/track_aac_test.go b/track_aac_test.go index 6ddbf195..6df37625 100644 --- a/track_aac_test.go +++ b/track_aac_test.go @@ -10,10 +10,10 @@ import ( func TestTrackAACNew(t *testing.T) { track, err := NewTrackAAC(96, 2, 48000, 4, []byte{0x01, 0x02}) require.NoError(t, err) - require.Equal(t, 2, track.typ) - require.Equal(t, 48000, track.sampleRate) - require.Equal(t, 4, track.channelCount) - require.Equal(t, []byte{0x01, 0x02}, track.aotSpecificConfig) + require.Equal(t, 2, track.Type()) + require.Equal(t, 48000, track.ClockRate()) + require.Equal(t, 4, track.ChannelCount()) + require.Equal(t, []byte{0x01, 0x02}, track.AOTSpecificConfig()) } func TestTrackAACClone(t *testing.T) { diff --git a/track_generic_test.go b/track_generic_test.go index 2d30833b..3952bb53 100644 --- a/track_generic_test.go +++ b/track_generic_test.go @@ -7,6 +7,27 @@ import ( "github.com/stretchr/testify/require" ) +func TestTrackGenericNew(t *testing.T) { + track, err := NewTrackGeneric( + "video", + []string{"100", "101"}, + "98 H265/90000", + "", + ) + require.NoError(t, err) + require.Equal(t, 90000, track.ClockRate()) +} + +func TestTrackGenericNewErrors(t *testing.T) { + _, err := NewTrackGeneric( + "video", + []string{"100", "101"}, + "98 H265/", + "", + ) + require.EqualError(t, err, "unable to get clock rate: strconv.ParseInt: parsing \"\": invalid syntax") +} + func TestTrackGenericClone(t *testing.T) { track, err := newTrackGenericFromMediaDescription( &psdp.MediaDescription{ @@ -34,3 +55,30 @@ func TestTrackGenericClone(t *testing.T) { require.NotSame(t, track, copy) require.Equal(t, track, copy) } + +func TestTrackGenericMediaDescription(t *testing.T) { + track, err := NewTrackGeneric( + "video", + []string{"100", "101"}, + "98 H265/90000", + "", + ) + require.NoError(t, err) + require.Equal(t, &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"100", "101"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "98 H265/90000", + }, + { + Key: "control", + Value: "", + }, + }, + }, track.MediaDescription()) +} diff --git a/track_h264.go b/track_h264.go index 76ae4b2c..194475da 100644 --- a/track_h264.go +++ b/track_h264.go @@ -135,6 +135,11 @@ func (t *TrackH264) PPS() []byte { return t.pps } +// ExtraData returns the track extra data. +func (t *TrackH264) ExtraData() []byte { + return t.extradata +} + // SetSPS sets the track SPS. func (t *TrackH264) SetSPS(v []byte) { t.sps = v diff --git a/track_h264_test.go b/track_h264_test.go index 06707456..8254ec06 100644 --- a/track_h264_test.go +++ b/track_h264_test.go @@ -168,9 +168,9 @@ func TestTrackH264New(t *testing.T) { track, err := NewTrackH264(96, []byte{0x01, 0x02}, []byte{0x03, 0x04}, []byte{0x05, 0x06}) require.NoError(t, err) - require.Equal(t, []byte{0x01, 0x02}, track.sps) - require.Equal(t, []byte{0x03, 0x04}, track.pps) - require.Equal(t, []byte{0x05, 0x06}, track.extradata) + require.Equal(t, []byte{0x01, 0x02}, track.SPS()) + require.Equal(t, []byte{0x03, 0x04}, track.PPS()) + require.Equal(t, []byte{0x05, 0x06}, track.ExtraData()) } func TestTrackH264Clone(t *testing.T) { diff --git a/track_opus.go b/track_opus.go index 99f7274e..eb14a4f0 100644 --- a/track_opus.go +++ b/track_opus.go @@ -29,12 +29,12 @@ func NewTrackOpus(payloadType uint8, sampleRate int, channelCount int) (*TrackOp func newTrackOpusFromMediaDescription( payloadType uint8, - rtpmap string, + rtpmapPart1 string, md *psdp.MediaDescription) (*TrackOpus, error) { control := trackFindControl(md) - tmp := strings.SplitN(rtpmap, "/", 3) + tmp := strings.SplitN(rtpmapPart1, "/", 3) if len(tmp) != 3 { - return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmap) + return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1) } sampleRate, err := strconv.ParseInt(tmp[1], 10, 64) @@ -83,6 +83,11 @@ func (t *TrackOpus) url(contentBase *base.URL) (*base.URL, error) { return trackURL(t, contentBase) } +// ChannelCount returns the channel count. +func (t *TrackOpus) ChannelCount() int { + return t.channelCount +} + // MediaDescription returns the media description in SDP format. func (t *TrackOpus) MediaDescription() *psdp.MediaDescription { typ := strconv.FormatInt(int64(t.payloadType), 10) diff --git a/track_opus_test.go b/track_opus_test.go index c13767a3..7a274b91 100644 --- a/track_opus_test.go +++ b/track_opus_test.go @@ -10,8 +10,8 @@ import ( func TestTrackOpusNew(t *testing.T) { track, err := NewTrackOpus(96, 48000, 2) require.NoError(t, err) - require.Equal(t, 48000, track.sampleRate) - require.Equal(t, 2, track.channelCount) + require.Equal(t, 48000, track.ClockRate()) + require.Equal(t, 2, track.ChannelCount()) } func TestTracOpusClone(t *testing.T) { diff --git a/track_test.go b/track_test.go index 09147136..670f0f06 100644 --- a/track_test.go +++ b/track_test.go @@ -366,7 +366,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { "unable to get clock rate: attribute 'rtpmap' not found", }, { - "generic invalid rtpmap 1", + "generic invalid clockrate 1", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", @@ -383,7 +383,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { "unable to get clock rate: invalid rtpmap (96)", }, { - "generic invalid rtpmap 2", + "generic invalid clockrate 2", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "video", @@ -399,6 +399,23 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { }, "unable to get clock rate: invalid rtpmap (96 mpeg4-generic)", }, + { + "generic invalid clockrate 3", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 mpeg4-generic/aa", + }, + }, + }, + "unable to get clock rate: strconv.ParseInt: parsing \"aa\": invalid syntax", + }, { "aac missing fmtp", &psdp.MediaDescription{ @@ -480,7 +497,7 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { "config is missing (96 profile-level-id=1)", }, { - "aac invalid config", + "aac invalid config 1", &psdp.MediaDescription{ MediaName: psdp.MediaName{ Media: "audio", @@ -500,6 +517,78 @@ func TestTrackNewFromMediaDescriptionErrors(t *testing.T) { }, "invalid AAC config (zz)", }, + { + "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)", + }, + { + "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 rtpmap (opus/48000)", + }, + { + "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", + }, + { + "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", + }, } { t.Run(ca.name, func(t *testing.T) { _, err := newTrackFromMediaDescription(ca.md) @@ -618,3 +707,10 @@ func TestTrackURL(t *testing.T) { }) } } + +func TestTrackURLError(t *testing.T) { + track, err := NewTrackH264(96, []byte{0x01, 0x02, 0x03, 0x04}, []byte{0x05, 0x06, 0x07, 0x08}, nil) + require.NoError(t, err) + _, err = track.url(nil) + require.EqualError(t, err, "Content-Base header not provided") +}