From 04d78ec414662e9ed97052a0fee0cb6438166186 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Thu, 23 Jun 2022 11:20:28 +0200 Subject: [PATCH] add TrackVP9 --- track.go | 3 + track_test.go | 19 ++++++ track_vp9.go | 171 ++++++++++++++++++++++++++++++++++++++++++++++ track_vp9_test.go | 59 ++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 track_vp9.go create mode 100644 track_vp9_test.go diff --git a/track.go b/track.go index ec796b62..efcb4178 100644 --- a/track.go +++ b/track.go @@ -74,6 +74,9 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { case rtpmapPart1 == "H265/90000": return newTrackH265FromMediaDescription(control, payloadType, md) + + case rtpmapPart1 == "VP9/90000": + return newTrackVP9FromMediaDescription(control, payloadType, md) } case md.MediaName.Media == "audio": diff --git a/track_test.go b/track_test.go index e9f41743..2fe04f58 100644 --- a/track_test.go +++ b/track_test.go @@ -381,6 +381,25 @@ func TestTrackNewFromMediaDescription(t *testing.T) { }, }, }, + { + "vp9", + &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{"96"}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: "96 VP9/90000", + }, + }, + }, + &TrackVP9{ + payloadType: 96, + }, + }, { "multiple formats", &psdp.MediaDescription{ diff --git a/track_vp9.go b/track_vp9.go new file mode 100644 index 00000000..de2e613d --- /dev/null +++ b/track_vp9.go @@ -0,0 +1,171 @@ +package gortsplib + +import ( + "fmt" + "strconv" + "strings" + + psdp "github.com/pion/sdp/v3" +) + +// TrackVP9 is a VP9 track. +type TrackVP9 struct { + trackBase + payloadType uint8 + maxFR *int + maxFS *int + profileID *int +} + +// NewTrackVP9 allocates a TrackVP9. +func NewTrackVP9(payloadType uint8, maxFR *int, maxFS *int, profileID *int) *TrackVP9 { + return &TrackVP9{ + payloadType: payloadType, + maxFR: maxFR, + maxFS: maxFS, + profileID: profileID, + } +} + +func newTrackVP9FromMediaDescription( + control string, + payloadType uint8, + md *psdp.MediaDescription, +) (*TrackVP9, error) { + t := &TrackVP9{ + trackBase: trackBase{ + control: control, + }, + payloadType: payloadType, + } + + t.fillParamsFromMediaDescription(md) + + return t, nil +} + +func (t *TrackVP9) fillParamsFromMediaDescription(md *psdp.MediaDescription) error { + v, ok := md.Attribute("fmtp") + if !ok { + return fmt.Errorf("fmtp attribute is missing") + } + + tmp := strings.SplitN(v, " ", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid fmtp attribute (%v)", v) + } + + for _, kv := range strings.Split(tmp[1], ";") { + kv = strings.Trim(kv, " ") + + if len(kv) == 0 { + continue + } + + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid fmtp attribute (%v)", v) + } + + switch tmp[0] { + case "max-fr": + val, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid max-fr (%v)", tmp[1]) + } + v2 := int(val) + t.maxFR = &v2 + + case "max-fs": + val, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid max-fs (%v)", tmp[1]) + } + v2 := int(val) + t.maxFS = &v2 + + case "profile-id": + val, err := strconv.ParseUint(tmp[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid profile-id (%v)", tmp[1]) + } + v2 := int(val) + t.profileID = &v2 + } + } + + return nil +} + +// ClockRate returns the track clock rate. +func (t *TrackVP9) ClockRate() int { + return 90000 +} + +func (t *TrackVP9) clone() Track { + return &TrackVP9{ + trackBase: t.trackBase, + payloadType: t.payloadType, + maxFR: t.maxFR, + maxFS: t.maxFS, + profileID: t.profileID, + } +} + +// MaxFR returns the track max-fr. +func (t *TrackVP9) MaxFR() *int { + return t.maxFR +} + +// MaxFS returns the track max-fs. +func (t *TrackVP9) MaxFS() *int { + return t.maxFS +} + +// ProfileID returns the track profile-id. +func (t *TrackVP9) ProfileID() *int { + return t.profileID +} + +// MediaDescription returns the track media description in SDP format. +func (t *TrackVP9) MediaDescription() *psdp.MediaDescription { + typ := strconv.FormatInt(int64(t.payloadType), 10) + + fmtp := typ + + var tmp []string + if t.maxFR != nil { + tmp = append(tmp, "max-fr="+strconv.FormatInt(int64(*t.maxFR), 10)) + } + if t.maxFS != nil { + tmp = append(tmp, "max-fs="+strconv.FormatInt(int64(*t.maxFS), 10)) + } + if t.profileID != nil { + tmp = append(tmp, "profile-id="+strconv.FormatInt(int64(*t.profileID), 10)) + } + if tmp != nil { + fmtp += " " + strings.Join(tmp, ";") + } + + return &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: "video", + Protos: []string{"RTP", "AVP"}, + Formats: []string{typ}, + }, + Attributes: []psdp.Attribute{ + { + Key: "rtpmap", + Value: typ + " VP9/90000", + }, + { + Key: "fmtp", + Value: fmtp, + }, + { + Key: "control", + Value: t.control, + }, + }, + } +} diff --git a/track_vp9_test.go b/track_vp9_test.go new file mode 100644 index 00000000..c5e85419 --- /dev/null +++ b/track_vp9_test.go @@ -0,0 +1,59 @@ +package gortsplib + +import ( + "testing" + + psdp "github.com/pion/sdp/v3" + "github.com/stretchr/testify/require" +) + +func TestTrackVP9New(t *testing.T) { + maxFR := 123 + maxFS := 456 + profileID := 789 + track := NewTrackVP9(96, &maxFR, &maxFS, &profileID) + require.Equal(t, "", track.GetControl()) + require.Equal(t, 123, *track.MaxFR()) + require.Equal(t, 456, *track.MaxFS()) + require.Equal(t, 789, *track.ProfileID()) +} + +func TestTracVP9Clone(t *testing.T) { + maxFR := 123 + maxFS := 456 + profileID := 789 + track := NewTrackVP9(96, &maxFR, &maxFS, &profileID) + + clone := track.clone() + require.NotSame(t, track, clone) + require.Equal(t, track, clone) +} + +func TestTrackVP9MediaDescription(t *testing.T) { + maxFR := 123 + maxFS := 456 + profileID := 789 + track := NewTrackVP9(96, &maxFR, &maxFS, &profileID) + + require.Equal(t, &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", + }, + { + Key: "control", + Value: "", + }, + }, + }, track.MediaDescription()) +}