Files
gortsplib/track.go

193 lines
4.6 KiB
Go

package gortsplib
import (
"fmt"
"strconv"
"strings"
psdp "github.com/pion/sdp/v3"
"github.com/aler9/gortsplib/pkg/url"
)
// Track is a RTSP track.
type Track interface {
// ClockRate returns the track clock rate.
ClockRate() int
// GetControl returns the track control attribute.
GetControl() string
// SetControl sets the track control attribute.
SetControl(string)
// MediaDescription returns the track media description in SDP format.
MediaDescription() *psdp.MediaDescription
clone() Track
url(*url.URL) (*url.URL, error)
}
func getControlAttribute(attributes []psdp.Attribute) string {
for _, attr := range attributes {
if attr.Key == "control" {
return attr.Value
}
}
return ""
}
func getRtpmapAttribute(attributes []psdp.Attribute, payloadType uint8) string {
for _, attr := range attributes {
if attr.Key == "rtpmap" {
v := strings.TrimSpace(attr.Value)
if parts := strings.SplitN(v, " ", 2); len(parts) == 2 {
if tmp, err := strconv.ParseInt(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType {
return parts[1]
}
}
}
}
return ""
}
func getFmtpAttribute(attributes []psdp.Attribute, payloadType uint8) string {
for _, attr := range attributes {
if attr.Key == "fmtp" {
if parts := strings.SplitN(attr.Value, " ", 2); len(parts) == 2 {
if tmp, err := strconv.ParseInt(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType {
return parts[1]
}
}
}
}
return ""
}
func getCodecAndClock(attributes []psdp.Attribute, payloadType uint8) (string, string) {
rtpmap := getRtpmapAttribute(attributes, payloadType)
if rtpmap == "" {
return "", ""
}
parts2 := strings.SplitN(rtpmap, "/", 2)
if len(parts2) != 2 {
return "", ""
}
return parts2[0], parts2[1]
}
func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
if len(md.MediaName.Formats) == 0 {
return nil, fmt.Errorf("no media formats found")
}
control := getControlAttribute(md.Attributes)
if len(md.MediaName.Formats) == 1 {
tmp, err := strconv.ParseInt(md.MediaName.Formats[0], 10, 8)
if err != nil {
return nil, err
}
payloadType := uint8(tmp)
codec, clock := getCodecAndClock(md.Attributes, payloadType)
switch {
case md.MediaName.Media == "video":
switch {
case payloadType == 26:
return newTrackJPEGFromMediaDescription(control)
case payloadType == 32:
return newTrackMPEG2VideoFromMediaDescription(control)
case codec == "H264" && clock == "90000":
return newTrackH264FromMediaDescription(control, payloadType, md)
case codec == "H265" && clock == "90000":
return newTrackH265FromMediaDescription(control, payloadType, md)
case codec == "VP8" && clock == "90000":
return newTrackVP8FromMediaDescription(control, payloadType, md)
case codec == "VP9" && clock == "90000":
return newTrackVP9FromMediaDescription(control, payloadType, md)
}
case md.MediaName.Media == "audio":
switch {
case payloadType == 0:
return newTrackPCMUFromMediaDescription(control, clock)
case payloadType == 8:
return newTrackPCMAFromMediaDescription(control, clock)
case payloadType == 14:
return newTrackMPEG2AudioFromMediaDescription(control)
case strings.ToLower(codec) == "mpeg4-generic":
return newTrackMPEG4AudioFromMediaDescription(control, payloadType, md)
case codec == "opus":
return newTrackOpusFromMediaDescription(control, payloadType, clock, md)
}
}
}
return newTrackGenericFromMediaDescription(control, md)
}
type trackBase struct {
control string
}
// GetControl gets the track control attribute.
func (t *trackBase) GetControl() string {
return t.control
}
// SetControl sets the track control attribute.
func (t *trackBase) SetControl(c string) {
t.control = c
}
func (t *trackBase) url(contentBase *url.URL) (*url.URL, error) {
if contentBase == nil {
return nil, fmt.Errorf("Content-Base header not provided")
}
control := t.GetControl()
// no control attribute, use base URL
if control == "" {
return contentBase, nil
}
// control attribute contains an absolute path
if strings.HasPrefix(control, "rtsp://") {
ur, err := url.Parse(control)
if err != nil {
return nil, err
}
// copy host and credentials
ur.Host = contentBase.Host
ur.User = contentBase.User
return ur, nil
}
// control attribute contains a relative control attribute
// insert the control attribute at the end of the URL
// if there's a query, insert it after the query
// otherwise insert it after the path
strURL := contentBase.String()
if control[0] != '?' && !strings.HasSuffix(strURL, "/") {
strURL += "/"
}
ur, _ := url.Parse(strURL + control)
return ur, nil
}