add base class for tracks

This commit is contained in:
aler9
2022-03-15 12:03:13 +01:00
parent 0ba73bacab
commit f4c783bc85
8 changed files with 82 additions and 147 deletions

View File

@@ -18,13 +18,22 @@ type Track interface {
GetControl() string GetControl() string
// SetControl sets the track control. // SetControl sets the track control.
SetControl(string) SetControl(string)
// MediaDescription returns the media description in SDP format. // MediaDescription returns the track media description in SDP format.
MediaDescription() *psdp.MediaDescription MediaDescription() *psdp.MediaDescription
clone() Track clone() Track
url(*base.URL) (*base.URL, error) url(*base.URL) (*base.URL, error)
} }
func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) { func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
control := func() string {
for _, attr := range md.Attributes {
if attr.Key == "control" {
return attr.Value
}
}
return ""
}()
rtpmapPart1, payloadType := func() (string, uint8) { rtpmapPart1, payloadType := func() (string, uint8) {
rtpmap, ok := md.Attribute("rtpmap") rtpmap, ok := md.Attribute("rtpmap")
if !ok { if !ok {
@@ -49,35 +58,40 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
switch { switch {
case md.MediaName.Media == "video": case md.MediaName.Media == "video":
if rtpmapPart1 == "H264/90000" { if rtpmapPart1 == "H264/90000" {
return newTrackH264FromMediaDescription(payloadType, md) return newTrackH264FromMediaDescription(control, payloadType, md)
} }
case md.MediaName.Media == "audio": case md.MediaName.Media == "audio":
switch { switch {
case len(md.MediaName.Formats) == 1 && md.MediaName.Formats[0] == "0": case len(md.MediaName.Formats) == 1 && md.MediaName.Formats[0] == "0":
return newTrackPCMUFromMediaDescription(rtpmapPart1, md) return newTrackPCMUFromMediaDescription(control, rtpmapPart1, md)
case strings.HasPrefix(strings.ToLower(rtpmapPart1), "mpeg4-generic/"): case strings.HasPrefix(strings.ToLower(rtpmapPart1), "mpeg4-generic/"):
return newTrackAACFromMediaDescription(payloadType, md) return newTrackAACFromMediaDescription(control, payloadType, md)
case strings.HasPrefix(rtpmapPart1, "opus/"): case strings.HasPrefix(rtpmapPart1, "opus/"):
return newTrackOpusFromMediaDescription(payloadType, rtpmapPart1, md) return newTrackOpusFromMediaDescription(control, payloadType, rtpmapPart1, md)
} }
} }
return newTrackGenericFromMediaDescription(md) return newTrackGenericFromMediaDescription(control, md)
} }
func trackFindControl(md *psdp.MediaDescription) string { type trackBase struct {
for _, attr := range md.Attributes { control string
if attr.Key == "control" {
return attr.Value
}
}
return ""
} }
func trackURL(t Track, contentBase *base.URL) (*base.URL, error) { // GetControl gets the track control.
func (t *trackBase) GetControl() string {
return t.control
}
// SetControl sets the track control.
func (t *trackBase) SetControl(c string) {
t.control = c
}
func (t *trackBase) url(contentBase *base.URL) (*base.URL, error) {
if contentBase == nil { if contentBase == nil {
return nil, fmt.Errorf("Content-Base header not provided") return nil, fmt.Errorf("Content-Base header not provided")
} }

View File

@@ -9,12 +9,11 @@ import (
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/aac" "github.com/aler9/gortsplib/pkg/aac"
"github.com/aler9/gortsplib/pkg/base"
) )
// TrackAAC is an AAC track. // TrackAAC is an AAC track.
type TrackAAC struct { type TrackAAC struct {
control string trackBase
payloadType uint8 payloadType uint8
typ int typ int
sampleRate int sampleRate int
@@ -47,10 +46,9 @@ func NewTrackAAC(payloadType uint8, typ int, sampleRate int,
} }
func newTrackAACFromMediaDescription( func newTrackAACFromMediaDescription(
control string,
payloadType uint8, payloadType uint8,
md *psdp.MediaDescription) (*TrackAAC, error) { md *psdp.MediaDescription) (*TrackAAC, error) {
control := trackFindControl(md)
v, ok := md.Attribute("fmtp") v, ok := md.Attribute("fmtp")
if !ok { if !ok {
return nil, fmt.Errorf("fmtp attribute is missing") return nil, fmt.Errorf("fmtp attribute is missing")
@@ -89,7 +87,9 @@ func newTrackAACFromMediaDescription(
enc, _ = mpegConf.Encode() enc, _ = mpegConf.Encode()
return &TrackAAC{ return &TrackAAC{
control: control, trackBase: trackBase{
control: control,
},
payloadType: payloadType, payloadType: payloadType,
typ: int(mpegConf.Type), typ: int(mpegConf.Type),
sampleRate: mpegConf.SampleRate, sampleRate: mpegConf.SampleRate,
@@ -125,7 +125,7 @@ func (t *TrackAAC) AOTSpecificConfig() []byte {
func (t *TrackAAC) clone() Track { func (t *TrackAAC) clone() Track {
return &TrackAAC{ return &TrackAAC{
control: t.control, trackBase: t.trackBase,
payloadType: t.payloadType, payloadType: t.payloadType,
typ: t.typ, typ: t.typ,
sampleRate: t.sampleRate, sampleRate: t.sampleRate,
@@ -135,21 +135,7 @@ func (t *TrackAAC) clone() Track {
} }
} }
// GetControl gets the track control. // MediaDescription returns the track media description in SDP format.
func (t *TrackAAC) GetControl() string {
return t.control
}
// SetControl sets the track control.
func (t *TrackAAC) SetControl(c string) {
t.control = c
}
func (t *TrackAAC) url(contentBase *base.URL) (*base.URL, error) {
return trackURL(t, contentBase)
}
// MediaDescription returns the media description in SDP format.
func (t *TrackAAC) MediaDescription() *psdp.MediaDescription { func (t *TrackAAC) MediaDescription() *psdp.MediaDescription {
typ := strconv.FormatInt(int64(t.payloadType), 10) typ := strconv.FormatInt(int64(t.payloadType), 10)

View File

@@ -6,8 +6,6 @@ import (
"strings" "strings"
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
) )
func trackGenericGetClockRate(formats []string, rtpmap string) (int, error) { func trackGenericGetClockRate(formats []string, rtpmap string) (int, error) {
@@ -63,7 +61,7 @@ func trackGenericGetClockRate(formats []string, rtpmap string) (int, error) {
// TrackGeneric is a generic track. // TrackGeneric is a generic track.
type TrackGeneric struct { type TrackGeneric struct {
control string trackBase
clockRate int clockRate int
media string media string
formats []string formats []string
@@ -87,9 +85,9 @@ func NewTrackGeneric(media string, formats []string, rtpmap string, fmtp string)
}, nil }, nil
} }
func newTrackGenericFromMediaDescription(md *psdp.MediaDescription) (*TrackGeneric, error) { func newTrackGenericFromMediaDescription(
control := trackFindControl(md) control string,
md *psdp.MediaDescription) (*TrackGeneric, error) {
rtpmap := func() string { rtpmap := func() string {
for _, attr := range md.Attributes { for _, attr := range md.Attributes {
if attr.Key == "rtpmap" { if attr.Key == "rtpmap" {
@@ -114,7 +112,9 @@ func newTrackGenericFromMediaDescription(md *psdp.MediaDescription) (*TrackGener
}() }()
return &TrackGeneric{ return &TrackGeneric{
control: control, trackBase: trackBase{
control: control,
},
clockRate: clockRate, clockRate: clockRate,
media: md.MediaName.Media, media: md.MediaName.Media,
formats: md.MediaName.Formats, formats: md.MediaName.Formats,
@@ -130,7 +130,7 @@ func (t *TrackGeneric) ClockRate() int {
func (t *TrackGeneric) clone() Track { func (t *TrackGeneric) clone() Track {
return &TrackGeneric{ return &TrackGeneric{
control: t.control, trackBase: t.trackBase,
clockRate: t.clockRate, clockRate: t.clockRate,
media: t.media, media: t.media,
formats: t.formats, formats: t.formats,
@@ -139,21 +139,7 @@ func (t *TrackGeneric) clone() Track {
} }
} }
// GetControl returns the track control. // MediaDescription returns the track media description in SDP format.
func (t *TrackGeneric) GetControl() string {
return t.control
}
// SetControl set the track control.
func (t *TrackGeneric) SetControl(c string) {
t.control = c
}
func (t *TrackGeneric) url(contentBase *base.URL) (*base.URL, error) {
return trackURL(t, contentBase)
}
// MediaDescription returns the media description in SDP format.
func (t *TrackGeneric) MediaDescription() *psdp.MediaDescription { func (t *TrackGeneric) MediaDescription() *psdp.MediaDescription {
return &psdp.MediaDescription{ return &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{

View File

@@ -31,26 +31,13 @@ func TestTrackGenericNewErrors(t *testing.T) {
} }
func TestTrackGenericClone(t *testing.T) { func TestTrackGenericClone(t *testing.T) {
track, err := newTrackGenericFromMediaDescription( track, err := NewTrackGeneric(
&psdp.MediaDescription{ "video",
MediaName: psdp.MediaName{ []string{"100", "101"},
Media: "video", "98 H265/90000",
Port: psdp.RangedPort{Value: 0}, "98 profile-id=1; sprop-vps=QAEMAf//AWAAAAMAAAMAAAMAAAMAlqwJ; "+
Protos: []string{"RTP", "AVP"}, "sprop-sps=QgEBAWAAAAMAAAMAAAMAAAMAlqADwIAQ5Za5JMmuWcBSSgAAB9AAAHUwgkA=; sprop-pps=RAHgdrAwxmQ=",
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=",
},
},
})
require.NoError(t, err) require.NoError(t, err)
clone := track.clone() clone := track.clone()

View File

@@ -9,8 +9,6 @@ import (
"sync" "sync"
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
) )
func trackH264GetSPSPPS(md *psdp.MediaDescription) ([]byte, []byte, error) { func trackH264GetSPSPPS(md *psdp.MediaDescription) ([]byte, []byte, error) {
@@ -61,7 +59,7 @@ func trackH264GetSPSPPS(md *psdp.MediaDescription) ([]byte, []byte, error) {
// TrackH264 is a H264 track. // TrackH264 is a H264 track.
type TrackH264 struct { type TrackH264 struct {
control string trackBase
payloadType uint8 payloadType uint8
sps []byte sps []byte
pps []byte pps []byte
@@ -80,12 +78,13 @@ func NewTrackH264(payloadType uint8, sps []byte, pps []byte, extradata []byte) (
} }
func newTrackH264FromMediaDescription( func newTrackH264FromMediaDescription(
control string,
payloadType uint8, payloadType uint8,
md *psdp.MediaDescription) (*TrackH264, error) { md *psdp.MediaDescription) (*TrackH264, error) {
control := trackFindControl(md)
t := &TrackH264{ t := &TrackH264{
control: control, trackBase: trackBase{
control: control,
},
payloadType: payloadType, payloadType: payloadType,
} }
@@ -105,7 +104,7 @@ func (t *TrackH264) ClockRate() int {
func (t *TrackH264) clone() Track { func (t *TrackH264) clone() Track {
return &TrackH264{ return &TrackH264{
control: t.control, trackBase: t.trackBase,
payloadType: t.payloadType, payloadType: t.payloadType,
sps: t.sps, sps: t.sps,
pps: t.pps, pps: t.pps,
@@ -113,20 +112,6 @@ func (t *TrackH264) clone() Track {
} }
} }
// GetControl gets the track control.
func (t *TrackH264) GetControl() string {
return t.control
}
// SetControl sets the track control.
func (t *TrackH264) SetControl(c string) {
t.control = c
}
func (t *TrackH264) url(contentBase *base.URL) (*base.URL, error) {
return trackURL(t, contentBase)
}
// SPS returns the track SPS. // SPS returns the track SPS.
func (t *TrackH264) SPS() []byte { func (t *TrackH264) SPS() []byte {
t.mutex.RLock() t.mutex.RLock()
@@ -160,7 +145,7 @@ func (t *TrackH264) SetPPS(v []byte) {
t.pps = v t.pps = v
} }
// MediaDescription returns the media description in SDP format. // MediaDescription returns the track media description in SDP format.
func (t *TrackH264) MediaDescription() *psdp.MediaDescription { func (t *TrackH264) MediaDescription() *psdp.MediaDescription {
t.mutex.RLock() t.mutex.RLock()
defer t.mutex.RUnlock() defer t.mutex.RUnlock()

View File

@@ -6,13 +6,11 @@ import (
"strings" "strings"
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
) )
// TrackOpus is a Opus track. // TrackOpus is a Opus track.
type TrackOpus struct { type TrackOpus struct {
control string trackBase
payloadType uint8 payloadType uint8
sampleRate int sampleRate int
channelCount int channelCount int
@@ -28,11 +26,10 @@ func NewTrackOpus(payloadType uint8, sampleRate int, channelCount int) (*TrackOp
} }
func newTrackOpusFromMediaDescription( func newTrackOpusFromMediaDescription(
control string,
payloadType uint8, payloadType uint8,
rtpmapPart1 string, rtpmapPart1 string,
md *psdp.MediaDescription) (*TrackOpus, error) { md *psdp.MediaDescription) (*TrackOpus, error) {
control := trackFindControl(md)
tmp := strings.SplitN(rtpmapPart1, "/", 3) tmp := strings.SplitN(rtpmapPart1, "/", 3)
if len(tmp) != 3 { if len(tmp) != 3 {
return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1) return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1)
@@ -49,7 +46,9 @@ func newTrackOpusFromMediaDescription(
} }
return &TrackOpus{ return &TrackOpus{
control: control, trackBase: trackBase{
control: control,
},
payloadType: payloadType, payloadType: payloadType,
sampleRate: int(sampleRate), sampleRate: int(sampleRate),
channelCount: int(channelCount), channelCount: int(channelCount),
@@ -63,33 +62,19 @@ func (t *TrackOpus) ClockRate() int {
func (t *TrackOpus) clone() Track { func (t *TrackOpus) clone() Track {
return &TrackOpus{ return &TrackOpus{
control: t.control, trackBase: t.trackBase,
payloadType: t.payloadType, payloadType: t.payloadType,
sampleRate: t.sampleRate, sampleRate: t.sampleRate,
channelCount: t.channelCount, channelCount: t.channelCount,
} }
} }
// GetControl returns the track control.
func (t *TrackOpus) GetControl() string {
return t.control
}
// SetControl sets the track control.
func (t *TrackOpus) SetControl(c string) {
t.control = c
}
func (t *TrackOpus) url(contentBase *base.URL) (*base.URL, error) {
return trackURL(t, contentBase)
}
// ChannelCount returns the channel count. // ChannelCount returns the channel count.
func (t *TrackOpus) ChannelCount() int { func (t *TrackOpus) ChannelCount() int {
return t.channelCount return t.channelCount
} }
// MediaDescription returns the media description in SDP format. // MediaDescription returns the track media description in SDP format.
func (t *TrackOpus) MediaDescription() *psdp.MediaDescription { func (t *TrackOpus) MediaDescription() *psdp.MediaDescription {
typ := strconv.FormatInt(int64(t.payloadType), 10) typ := strconv.FormatInt(int64(t.payloadType), 10)

View File

@@ -5,13 +5,11 @@ import (
"strings" "strings"
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
) )
// TrackPCMU is a PCMU track. // TrackPCMU is a PCMU track.
type TrackPCMU struct { type TrackPCMU struct {
control string trackBase
} }
// NewTrackPCMU allocates a TrackPCMU. // NewTrackPCMU allocates a TrackPCMU.
@@ -19,18 +17,20 @@ func NewTrackPCMU() *TrackPCMU {
return &TrackPCMU{} return &TrackPCMU{}
} }
func newTrackPCMUFromMediaDescription(rtpmapPart1 string, func newTrackPCMUFromMediaDescription(
control string,
rtpmapPart1 string,
md *psdp.MediaDescription) (*TrackPCMU, error, md *psdp.MediaDescription) (*TrackPCMU, error,
) { ) {
control := trackFindControl(md)
tmp := strings.Split(rtpmapPart1, "/") tmp := strings.Split(rtpmapPart1, "/")
if len(tmp) >= 3 && tmp[2] != "1" { if len(tmp) >= 3 && tmp[2] != "1" {
return nil, fmt.Errorf("PCMU tracks must have only one channel") return nil, fmt.Errorf("PCMU tracks must have only one channel")
} }
return &TrackPCMU{ return &TrackPCMU{
control: control, trackBase: trackBase{
control: control,
},
}, nil }, nil
} }
@@ -40,24 +40,12 @@ func (t *TrackPCMU) ClockRate() int {
} }
func (t *TrackPCMU) clone() Track { func (t *TrackPCMU) clone() Track {
return &TrackPCMU{} return &TrackPCMU{
trackBase: t.trackBase,
}
} }
// GetControl returns the track control. // MediaDescription returns the track media description in SDP format.
func (t *TrackPCMU) GetControl() string {
return t.control
}
// SetControl sets the track control.
func (t *TrackPCMU) SetControl(c string) {
t.control = c
}
func (t *TrackPCMU) url(contentBase *base.URL) (*base.URL, error) {
return trackURL(t, contentBase)
}
// MediaDescription returns the media description in SDP format.
func (t *TrackPCMU) MediaDescription() *psdp.MediaDescription { func (t *TrackPCMU) MediaDescription() *psdp.MediaDescription {
return &psdp.MediaDescription{ return &psdp.MediaDescription{
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{

View File

@@ -71,13 +71,17 @@ func TestTracksReadSkipGenericTracksWithoutClockRate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, Tracks{ require.Equal(t, Tracks{
&TrackH264{ &TrackH264{
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=v", trackBase: trackBase{
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=v",
},
payloadType: 97, payloadType: 97,
sps: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a}, sps: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
pps: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c}, pps: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
}, },
&TrackPCMU{ &TrackPCMU{
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a", trackBase: trackBase{
control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
},
}, },
}, tracks) }, tracks)
} }