mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
client: allow to publish tracks with pre-existing control attribute (#48)
This commit is contained in:
@@ -67,13 +67,15 @@ func TestClientReadTracks(t *testing.T) {
|
||||
require.Equal(t, base.Describe, req.Method)
|
||||
require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track1, track2, track3})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track1, track2, track3}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -228,13 +230,15 @@ func TestClientRead(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{scheme + "://" + listenIP + ":8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -484,12 +488,14 @@ func TestClientReadNoContentBase(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -590,13 +596,15 @@ func TestClientReadAnyPort(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -716,13 +724,15 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -847,13 +857,15 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1072,13 +1084,15 @@ func TestClientReadRedirect(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1230,13 +1244,15 @@ func TestClientReadPause(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1414,13 +1430,15 @@ func TestClientReadRTCPReport(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1590,13 +1608,15 @@ func TestClientReadErrorTimeout(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1745,13 +1765,15 @@ func TestClientReadIgnoreTCPInvalidTrack(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1875,13 +1897,15 @@ func TestClientReadSeek(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@@ -72,13 +72,15 @@ func TestClientSession(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Session": base.HeaderValue{"123456"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
@@ -150,12 +152,14 @@ func TestClientAuth(t *testing.T) {
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
tracks := cloneAndClearTracks(Tracks{track})
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
Body: tracks.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
@@ -1090,15 +1090,18 @@ func (cc *ClientConn) doAnnounce(u *base.URL, tracks Tracks) (*base.Response, er
|
||||
// (tested with ffmpeg and gstreamer)
|
||||
baseURL := u.Clone()
|
||||
|
||||
// set id, base url and control attribute on tracks
|
||||
// set ID, base URL, control attribute of tracks
|
||||
for i, t := range tracks {
|
||||
t.ID = i
|
||||
t.BaseURL = baseURL
|
||||
|
||||
if !t.hasControlAttribute() {
|
||||
t.Media.Attributes = append(t.Media.Attributes, psdp.Attribute{
|
||||
Key: "control",
|
||||
Value: "trackID=" + strconv.FormatInt(int64(i), 10),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
res, err := cc.do(&base.Request{
|
||||
Method: base.Announce,
|
||||
|
@@ -41,11 +41,12 @@ type ServerStream struct {
|
||||
// NewServerStream allocates a ServerStream.
|
||||
func NewServerStream(tracks Tracks) *ServerStream {
|
||||
st := &ServerStream{
|
||||
tracks: tracks,
|
||||
readersUnicast: make(map[*ServerSession]struct{}),
|
||||
readers: make(map[*ServerSession]struct{}),
|
||||
}
|
||||
|
||||
st.tracks = cloneAndClearTracks(tracks)
|
||||
|
||||
st.trackInfos = make([]*trackInfo, len(tracks))
|
||||
for i := range st.trackInfos {
|
||||
st.trackInfos[i] = &trackInfo{}
|
||||
|
251
track.go
251
track.go
@@ -26,6 +26,112 @@ type Track struct {
|
||||
Media *psdp.MediaDescription
|
||||
}
|
||||
|
||||
func (t *Track) hasControlAttribute() bool {
|
||||
for _, attr := range t.Media.Attributes {
|
||||
if attr.Key == "control" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// URL returns the track url.
|
||||
func (t *Track) URL() (*base.URL, error) {
|
||||
if t.BaseURL == nil {
|
||||
return nil, fmt.Errorf("empty base url")
|
||||
}
|
||||
|
||||
controlAttr := func() string {
|
||||
for _, attr := range t.Media.Attributes {
|
||||
if attr.Key == "control" {
|
||||
return attr.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
|
||||
// no control attribute, use base URL
|
||||
if controlAttr == "" {
|
||||
return t.BaseURL, nil
|
||||
}
|
||||
|
||||
// control attribute contains an absolute path
|
||||
if strings.HasPrefix(controlAttr, "rtsp://") {
|
||||
ur, err := base.ParseURL(controlAttr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// copy host and credentials
|
||||
ur.Host = t.BaseURL.Host
|
||||
ur.User = t.BaseURL.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 := t.BaseURL.String()
|
||||
if controlAttr[0] != '?' && !strings.HasSuffix(strURL, "/") {
|
||||
strURL += "/"
|
||||
}
|
||||
ur, _ := base.ParseURL(strURL + controlAttr)
|
||||
return ur, nil
|
||||
}
|
||||
|
||||
// ClockRate returns the clock rate of the track.
|
||||
func (t *Track) ClockRate() (int, error) {
|
||||
if len(t.Media.MediaName.Formats) != 1 {
|
||||
return 0, fmt.Errorf("invalid format (%v)", t.Media.MediaName.Formats)
|
||||
}
|
||||
|
||||
// get clock rate from payload type
|
||||
switch t.Media.MediaName.Formats[0] {
|
||||
case "0", "1", "2", "3", "4", "5", "7", "8", "9", "12", "13", "15", "18":
|
||||
return 8000, nil
|
||||
|
||||
case "6":
|
||||
return 16000, nil
|
||||
|
||||
case "10", "11":
|
||||
return 44100, nil
|
||||
|
||||
case "14", "25", "26", "28", "31", "32", "33", "34":
|
||||
return 90000, nil
|
||||
|
||||
case "16":
|
||||
return 11025, nil
|
||||
|
||||
case "17":
|
||||
return 22050, nil
|
||||
}
|
||||
|
||||
// get clock rate from rtpmap
|
||||
// https://tools.ietf.org/html/rfc4566
|
||||
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
|
||||
for _, a := range t.Media.Attributes {
|
||||
if a.Key == "rtpmap" {
|
||||
tmp := strings.Split(a.Value, " ")
|
||||
if len(tmp) < 2 {
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", a.Value)
|
||||
}
|
||||
|
||||
tmp = strings.Split(tmp[1], "/")
|
||||
if len(tmp) != 2 && len(tmp) != 3 {
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", a.Value)
|
||||
}
|
||||
|
||||
v, err := strconv.ParseInt(tmp[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(v), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("attribute 'rtpmap' not found")
|
||||
}
|
||||
|
||||
// NewTrackH264 initializes an H264 track from a SPS and PPS.
|
||||
func NewTrackH264(payloadType uint8, sps []byte, pps []byte) (*Track, error) {
|
||||
spropParameterSets := base64.StdEncoding.EncodeToString(sps) +
|
||||
@@ -227,103 +333,6 @@ func (t *Track) ExtractDataAAC() ([]byte, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ClockRate returns the clock rate of the track.
|
||||
func (t *Track) ClockRate() (int, error) {
|
||||
if len(t.Media.MediaName.Formats) != 1 {
|
||||
return 0, fmt.Errorf("invalid format (%v)", t.Media.MediaName.Formats)
|
||||
}
|
||||
|
||||
// get clock rate from payload type
|
||||
switch t.Media.MediaName.Formats[0] {
|
||||
case "0", "1", "2", "3", "4", "5", "7", "8", "9", "12", "13", "15", "18":
|
||||
return 8000, nil
|
||||
|
||||
case "6":
|
||||
return 16000, nil
|
||||
|
||||
case "10", "11":
|
||||
return 44100, nil
|
||||
|
||||
case "14", "25", "26", "28", "31", "32", "33", "34":
|
||||
return 90000, nil
|
||||
|
||||
case "16":
|
||||
return 11025, nil
|
||||
|
||||
case "17":
|
||||
return 22050, nil
|
||||
}
|
||||
|
||||
// get clock rate from rtpmap
|
||||
// https://tools.ietf.org/html/rfc4566
|
||||
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
|
||||
for _, a := range t.Media.Attributes {
|
||||
if a.Key == "rtpmap" {
|
||||
tmp := strings.Split(a.Value, " ")
|
||||
if len(tmp) < 2 {
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", a.Value)
|
||||
}
|
||||
|
||||
tmp = strings.Split(tmp[1], "/")
|
||||
if len(tmp) != 2 && len(tmp) != 3 {
|
||||
return 0, fmt.Errorf("invalid rtpmap (%v)", a.Value)
|
||||
}
|
||||
|
||||
v, err := strconv.ParseInt(tmp[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(v), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("attribute 'rtpmap' not found")
|
||||
}
|
||||
|
||||
// URL returns the track url.
|
||||
func (t *Track) URL() (*base.URL, error) {
|
||||
if t.BaseURL == nil {
|
||||
return nil, fmt.Errorf("empty base url")
|
||||
}
|
||||
|
||||
controlAttr := func() string {
|
||||
for _, attr := range t.Media.Attributes {
|
||||
if attr.Key == "control" {
|
||||
return attr.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}()
|
||||
|
||||
// no control attribute, use base URL
|
||||
if controlAttr == "" {
|
||||
return t.BaseURL, nil
|
||||
}
|
||||
|
||||
// control attribute contains an absolute path
|
||||
if strings.HasPrefix(controlAttr, "rtsp://") {
|
||||
ur, err := base.ParseURL(controlAttr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// copy host and credentials
|
||||
ur.Host = t.BaseURL.Host
|
||||
ur.User = t.BaseURL.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 := t.BaseURL.String()
|
||||
if controlAttr[0] != '?' && !strings.HasSuffix(strURL, "/") {
|
||||
strURL += "/"
|
||||
}
|
||||
ur, _ := base.ParseURL(strURL + controlAttr)
|
||||
return ur, nil
|
||||
}
|
||||
|
||||
// Tracks is a list of tracks.
|
||||
type Tracks []*Track
|
||||
|
||||
@@ -357,6 +366,43 @@ func ReadTracks(byts []byte, baseURL *base.URL) (Tracks, error) {
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
func cloneAndClearTracks(ts Tracks) Tracks {
|
||||
ret := make(Tracks, len(ts))
|
||||
|
||||
for i, track := range ts {
|
||||
md := &psdp.MediaDescription{
|
||||
MediaName: psdp.MediaName{
|
||||
Media: track.Media.MediaName.Media,
|
||||
Protos: []string{"RTP", "AVP"}, // override protocol
|
||||
Formats: track.Media.MediaName.Formats,
|
||||
},
|
||||
Bandwidth: track.Media.Bandwidth,
|
||||
Attributes: func() []psdp.Attribute {
|
||||
var ret []psdp.Attribute
|
||||
|
||||
for _, attr := range track.Media.Attributes {
|
||||
if attr.Key == "rtpmap" || attr.Key == "fmtp" {
|
||||
ret = append(ret, attr)
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, psdp.Attribute{
|
||||
Key: "control",
|
||||
Value: "trackID=" + strconv.FormatInt(int64(i), 10),
|
||||
})
|
||||
|
||||
return ret
|
||||
}(),
|
||||
}
|
||||
|
||||
ret[i] = &Track{
|
||||
Media: md,
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Write encodes tracks into SDP.
|
||||
func (ts Tracks) Write() []byte {
|
||||
sout := &sdp.SessionDescription{
|
||||
@@ -378,7 +424,7 @@ func (ts Tracks) Write() []byte {
|
||||
},
|
||||
}
|
||||
|
||||
for i, track := range ts {
|
||||
for _, track := range ts {
|
||||
mout := &psdp.MediaDescription{
|
||||
MediaName: psdp.MediaName{
|
||||
Media: track.Media.MediaName.Media,
|
||||
@@ -390,18 +436,11 @@ func (ts Tracks) Write() []byte {
|
||||
var ret []psdp.Attribute
|
||||
|
||||
for _, attr := range track.Media.Attributes {
|
||||
if attr.Key == "rtpmap" || attr.Key == "fmtp" {
|
||||
if attr.Key == "rtpmap" || attr.Key == "fmtp" || attr.Key == "control" {
|
||||
ret = append(ret, attr)
|
||||
}
|
||||
}
|
||||
|
||||
// control attribute is the path that is appended
|
||||
// to the stream path in SETUP
|
||||
ret = append(ret, psdp.Attribute{
|
||||
Key: "control",
|
||||
Value: "trackID=" + strconv.FormatInt(int64(i), 10),
|
||||
})
|
||||
|
||||
return ret
|
||||
}(),
|
||||
}
|
||||
|
154
track_test.go
154
track_test.go
@@ -9,83 +9,6 @@ import (
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
)
|
||||
|
||||
func TestTrackClockRate(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
sdp []byte
|
||||
clockRate int
|
||||
}{
|
||||
{
|
||||
"empty encoding parameters",
|
||||
[]byte("v=0\r\n" +
|
||||
"o=- 38990265062388 38990265062388 IN IP4 192.168.1.142\r\n" +
|
||||
"s=RTSP Session\r\n" +
|
||||
"c=IN IP4 192.168.1.142\r\n" +
|
||||
"t=0 0\r\n" +
|
||||
"a=control:*\r\n" +
|
||||
"a=range:npt=0-\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000 \r\n" +
|
||||
"a=range:npt=0-\r\n" +
|
||||
"a=framerate:0S\r\n" +
|
||||
"a=fmtp:96 profile-level-id=64000c; packetization-mode=1; sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,aO48gA==\r\n" +
|
||||
"a=framerate:25\r\n" +
|
||||
"a=control:trackID=3\r\n"),
|
||||
90000,
|
||||
},
|
||||
{
|
||||
"static payload type 1",
|
||||
[]byte("v=0\r\n" +
|
||||
"o=- 38990265062388 38990265062388 IN IP4 192.168.1.142\r\n" +
|
||||
"s=RTSP Session\r\n" +
|
||||
"c=IN IP4 192.168.1.142\r\n" +
|
||||
"t=0 0\r\n" +
|
||||
"a=control:*\r\n" +
|
||||
"a=range:npt=0-\r\n" +
|
||||
"m=audio 0 RTP/AVP 8\r\n" +
|
||||
"a=control:trackID=4"),
|
||||
8000,
|
||||
},
|
||||
{
|
||||
"static payload type 2",
|
||||
[]byte("v=0\r\n" +
|
||||
"o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n" +
|
||||
"s=SDP Seminar\r\n" +
|
||||
"i=A Seminar on the session description protocol\r\n" +
|
||||
"u=http://www.example.com/seminars/sdp.pdf\r\n" +
|
||||
"e=j.doe@example.com (Jane Doe)\r\n" +
|
||||
"p=+1 617 555-6011\r\n" +
|
||||
"c=IN IP4 224.2.17.12/127\r\n" +
|
||||
"b=X-YZ:128\r\n" +
|
||||
"b=AS:12345\r\n" +
|
||||
"t=2873397496 2873404696\r\n" +
|
||||
"t=3034423619 3042462419\r\n" +
|
||||
"r=604800 3600 0 90000\r\n" +
|
||||
"z=2882844526 -3600 2898848070 0\r\n" +
|
||||
"k=prompt\r\n" +
|
||||
"a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n" +
|
||||
"a=recvonly\r\n" +
|
||||
"m=audio 49170 RTP/AVP 0\r\n" +
|
||||
"i=Vivamus a posuere nisl\r\n" +
|
||||
"c=IN IP4 203.0.113.1\r\n" +
|
||||
"b=X-YZ:128\r\n" +
|
||||
"k=prompt\r\n" +
|
||||
"a=sendrecv\r\n"),
|
||||
8000,
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
tracks, err := ReadTracks(ca.sdp, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
clockRate, err := tracks[0].ClockRate()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, clockRate, ca.clockRate)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrackURL(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
@@ -194,6 +117,83 @@ func TestTrackURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrackClockRate(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
sdp []byte
|
||||
clockRate int
|
||||
}{
|
||||
{
|
||||
"empty encoding parameters",
|
||||
[]byte("v=0\r\n" +
|
||||
"o=- 38990265062388 38990265062388 IN IP4 192.168.1.142\r\n" +
|
||||
"s=RTSP Session\r\n" +
|
||||
"c=IN IP4 192.168.1.142\r\n" +
|
||||
"t=0 0\r\n" +
|
||||
"a=control:*\r\n" +
|
||||
"a=range:npt=0-\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000 \r\n" +
|
||||
"a=range:npt=0-\r\n" +
|
||||
"a=framerate:0S\r\n" +
|
||||
"a=fmtp:96 profile-level-id=64000c; packetization-mode=1; sprop-parameter-sets=Z2QADKw7ULBLQgAAAwACAAADAD0I,aO48gA==\r\n" +
|
||||
"a=framerate:25\r\n" +
|
||||
"a=control:trackID=3\r\n"),
|
||||
90000,
|
||||
},
|
||||
{
|
||||
"static payload type 1",
|
||||
[]byte("v=0\r\n" +
|
||||
"o=- 38990265062388 38990265062388 IN IP4 192.168.1.142\r\n" +
|
||||
"s=RTSP Session\r\n" +
|
||||
"c=IN IP4 192.168.1.142\r\n" +
|
||||
"t=0 0\r\n" +
|
||||
"a=control:*\r\n" +
|
||||
"a=range:npt=0-\r\n" +
|
||||
"m=audio 0 RTP/AVP 8\r\n" +
|
||||
"a=control:trackID=4"),
|
||||
8000,
|
||||
},
|
||||
{
|
||||
"static payload type 2",
|
||||
[]byte("v=0\r\n" +
|
||||
"o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n" +
|
||||
"s=SDP Seminar\r\n" +
|
||||
"i=A Seminar on the session description protocol\r\n" +
|
||||
"u=http://www.example.com/seminars/sdp.pdf\r\n" +
|
||||
"e=j.doe@example.com (Jane Doe)\r\n" +
|
||||
"p=+1 617 555-6011\r\n" +
|
||||
"c=IN IP4 224.2.17.12/127\r\n" +
|
||||
"b=X-YZ:128\r\n" +
|
||||
"b=AS:12345\r\n" +
|
||||
"t=2873397496 2873404696\r\n" +
|
||||
"t=3034423619 3042462419\r\n" +
|
||||
"r=604800 3600 0 90000\r\n" +
|
||||
"z=2882844526 -3600 2898848070 0\r\n" +
|
||||
"k=prompt\r\n" +
|
||||
"a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\r\n" +
|
||||
"a=recvonly\r\n" +
|
||||
"m=audio 49170 RTP/AVP 0\r\n" +
|
||||
"i=Vivamus a posuere nisl\r\n" +
|
||||
"c=IN IP4 203.0.113.1\r\n" +
|
||||
"b=X-YZ:128\r\n" +
|
||||
"k=prompt\r\n" +
|
||||
"a=sendrecv\r\n"),
|
||||
8000,
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
tracks, err := ReadTracks(ca.sdp, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
clockRate, err := tracks[0].ClockRate()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, clockRate, ca.clockRate)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrackH264New(t *testing.T) {
|
||||
sps := []byte{
|
||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||
|
Reference in New Issue
Block a user