mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 07:06:58 +08:00
add base class for tracks
This commit is contained in:
42
track.go
42
track.go
@@ -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")
|
||||
}
|
||||
|
26
track_aac.go
26
track_aac.go
@@ -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)
|
||||
|
||||
|
@@ -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{
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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{
|
||||
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user