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
// SetControl sets the track control.
SetControl(string)
// MediaDescription returns the media description in SDP format.
// MediaDescription returns the track media description in SDP format.
MediaDescription() *psdp.MediaDescription
clone() Track
url(*base.URL) (*base.URL, 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) {
rtpmap, ok := md.Attribute("rtpmap")
if !ok {
@@ -49,35 +58,40 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
switch {
case md.MediaName.Media == "video":
if rtpmapPart1 == "H264/90000" {
return newTrackH264FromMediaDescription(payloadType, md)
return newTrackH264FromMediaDescription(control, payloadType, md)
}
case md.MediaName.Media == "audio":
switch {
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/"):
return newTrackAACFromMediaDescription(payloadType, md)
return newTrackAACFromMediaDescription(control, payloadType, md)
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 {
for _, attr := range md.Attributes {
if attr.Key == "control" {
return attr.Value
}
}
return ""
type trackBase struct {
control string
}
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 {
return nil, fmt.Errorf("Content-Base header not provided")
}

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,6 @@ import (
"sync"
psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
)
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.
type TrackH264 struct {
control string
trackBase
payloadType uint8
sps []byte
pps []byte
@@ -80,12 +78,13 @@ func NewTrackH264(payloadType uint8, sps []byte, pps []byte, extradata []byte) (
}
func newTrackH264FromMediaDescription(
control string,
payloadType uint8,
md *psdp.MediaDescription) (*TrackH264, error) {
control := trackFindControl(md)
t := &TrackH264{
trackBase: trackBase{
control: control,
},
payloadType: payloadType,
}
@@ -105,7 +104,7 @@ func (t *TrackH264) ClockRate() int {
func (t *TrackH264) clone() Track {
return &TrackH264{
control: t.control,
trackBase: t.trackBase,
payloadType: t.payloadType,
sps: t.sps,
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.
func (t *TrackH264) SPS() []byte {
t.mutex.RLock()
@@ -160,7 +145,7 @@ func (t *TrackH264) SetPPS(v []byte) {
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 {
t.mutex.RLock()
defer t.mutex.RUnlock()

View File

@@ -6,13 +6,11 @@ import (
"strings"
psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/base"
)
// TrackOpus is a Opus track.
type TrackOpus struct {
control string
trackBase
payloadType uint8
sampleRate int
channelCount int
@@ -28,11 +26,10 @@ func NewTrackOpus(payloadType uint8, sampleRate int, channelCount int) (*TrackOp
}
func newTrackOpusFromMediaDescription(
control string,
payloadType uint8,
rtpmapPart1 string,
md *psdp.MediaDescription) (*TrackOpus, error) {
control := trackFindControl(md)
tmp := strings.SplitN(rtpmapPart1, "/", 3)
if len(tmp) != 3 {
return nil, fmt.Errorf("invalid rtpmap (%v)", rtpmapPart1)
@@ -49,7 +46,9 @@ func newTrackOpusFromMediaDescription(
}
return &TrackOpus{
trackBase: trackBase{
control: control,
},
payloadType: payloadType,
sampleRate: int(sampleRate),
channelCount: int(channelCount),
@@ -63,33 +62,19 @@ func (t *TrackOpus) ClockRate() int {
func (t *TrackOpus) clone() Track {
return &TrackOpus{
control: t.control,
trackBase: t.trackBase,
payloadType: t.payloadType,
sampleRate: t.sampleRate,
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.
func (t *TrackOpus) ChannelCount() int {
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 {
typ := strconv.FormatInt(int64(t.payloadType), 10)

View File

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

View File

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