/
track.go
202 lines (159 loc) · 4.94 KB
/
track.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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 {
// String returns the track codec.
String() string
// 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)
codec = strings.ToLower(codec)
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, payloadType == 8:
return newTrackG711FromMediaDescription(control, payloadType, clock)
case payloadType == 9:
return newTrackG722FromMediaDescription(control, clock)
case payloadType == 14:
return newTrackMPEG2AudioFromMediaDescription(control)
case codec == "l8", codec == "l16", codec == "l24":
return newTrackLPCMFromMediaDescription(control, payloadType, codec, clock)
case codec == "mpeg4-generic":
return newTrackMPEG4AudioFromMediaDescription(control, payloadType, md)
case codec == "vorbis":
return newTrackVorbisFromMediaDescription(control, payloadType, clock, md)
case codec == "opus":
return newTrackOpusFromMediaDescription(control, payloadType, clock)
}
}
}
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
}