mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +08:00
support routing ULPFEC group definitions
This commit is contained in:
@@ -2,6 +2,7 @@ package description
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
psdp "github.com/pion/sdp/v3"
|
psdp "github.com/pion/sdp/v3"
|
||||||
|
|
||||||
@@ -36,15 +37,21 @@ func hasMediaWithID(medias []*Media, id string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionFECGroup is a FEC group.
|
||||||
|
type SessionFECGroup []string
|
||||||
|
|
||||||
// Session is the description of a RTSP stream.
|
// Session is the description of a RTSP stream.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
// base URL of the stream (read only).
|
// Base URL of the stream (read only).
|
||||||
BaseURL *url.URL
|
BaseURL *url.URL
|
||||||
|
|
||||||
// title of the stream (optional).
|
// Title of the stream (optional).
|
||||||
Title string
|
Title string
|
||||||
|
|
||||||
// available media streams.
|
// FEC groups (RFC5109).
|
||||||
|
FECGroups []SessionFECGroup
|
||||||
|
|
||||||
|
// Media streams.
|
||||||
Medias []*Media
|
Medias []*Media
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +94,20 @@ func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error {
|
|||||||
return fmt.Errorf("media IDs sent partially")
|
return fmt.Errorf("media IDs sent partially")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, attr := range ssd.Attributes {
|
||||||
|
if attr.Key == "group" && strings.HasPrefix(attr.Value, "FEC ") {
|
||||||
|
group := SessionFECGroup(strings.Split(attr.Value[len("FEC "):], " "))
|
||||||
|
|
||||||
|
for _, id := range group {
|
||||||
|
if !hasMediaWithID(d.Medias, id) {
|
||||||
|
return fmt.Errorf("FEC group points to an invalid media ID: %v", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.FECGroups = append(d.FECGroups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,5 +153,12 @@ func (d Session) Marshal(multicast bool) ([]byte, error) {
|
|||||||
sout.MediaDescriptions[i] = media.Marshal()
|
sout.MediaDescriptions[i] = media.Marshal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, group := range d.FECGroups {
|
||||||
|
sout.Attributes = append(sout.Attributes, psdp.Attribute{
|
||||||
|
Key: "group",
|
||||||
|
Value: "FEC " + strings.Join(group, " "),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return sout.Marshal()
|
return sout.Marshal()
|
||||||
}
|
}
|
||||||
|
@@ -629,6 +629,89 @@ var casesSession = []struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ulpfec rfc5109",
|
||||||
|
"v=0\r\n" +
|
||||||
|
"o=adam 289083124 289083124 IN IP4 host.example.com\r\n" +
|
||||||
|
"s=ULP FEC Seminar\r\n" +
|
||||||
|
"t=0 0\r\n" +
|
||||||
|
"c=IN IP4 224.2.17.12/127\r\n" +
|
||||||
|
"a=group:FEC 1 2\r\n" +
|
||||||
|
"a=group:FEC 3 4\r\n" +
|
||||||
|
"m=audio 30000 RTP/AVP 0\r\n" +
|
||||||
|
"a=mid:1\r\n" +
|
||||||
|
"m=application 30002 RTP/AVP 100\r\n" +
|
||||||
|
"a=rtpmap:100 ulpfec/8000\r\n" +
|
||||||
|
"a=mid:2\r\n" +
|
||||||
|
"m=video 30004 RTP/AVP 31\r\n" +
|
||||||
|
"a=mid:3\r\n" +
|
||||||
|
"m=application 30004 RTP/AVP 101\r\n" +
|
||||||
|
"c=IN IP4 224.2.17.13/127\r\n" +
|
||||||
|
"a=rtpmap:101 ulpfec/8000\r\n" +
|
||||||
|
"a=mid:4\r\n",
|
||||||
|
"v=0\r\n" +
|
||||||
|
"o=- 0 0 IN IP4 127.0.0.1\r\n" +
|
||||||
|
"s=ULP FEC Seminar\r\n" +
|
||||||
|
"c=IN IP4 0.0.0.0\r\n" +
|
||||||
|
"t=0 0\r\n" +
|
||||||
|
"a=group:FEC 1 2\r\n" +
|
||||||
|
"a=group:FEC 3 4\r\n" +
|
||||||
|
"m=audio 0 RTP/AVP 0\r\n" +
|
||||||
|
"a=mid:1\r\n" +
|
||||||
|
"a=control\r\n" +
|
||||||
|
"a=rtpmap:0 PCMU/8000\r\n" +
|
||||||
|
"m=application 0 RTP/AVP 100\r\n" +
|
||||||
|
"a=mid:2\r\n" +
|
||||||
|
"a=control\r\n" +
|
||||||
|
"a=rtpmap:100 ulpfec/8000\r\n" +
|
||||||
|
"m=video 0 RTP/AVP 31\r\n" +
|
||||||
|
"a=mid:3\r\n" +
|
||||||
|
"a=control\r\n" +
|
||||||
|
"m=application 0 RTP/AVP 101\r\n" +
|
||||||
|
"a=mid:4\r\n" +
|
||||||
|
"a=control\r\n" +
|
||||||
|
"a=rtpmap:101 ulpfec/8000\r\n",
|
||||||
|
Session{
|
||||||
|
Title: "ULP FEC Seminar",
|
||||||
|
FECGroups: []SessionFECGroup{
|
||||||
|
{"1", "2"},
|
||||||
|
{"3", "4"},
|
||||||
|
},
|
||||||
|
Medias: []*Media{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
Type: MediaTypeAudio,
|
||||||
|
Formats: []format.Format{&format.G711{MULaw: true}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "2",
|
||||||
|
Type: MediaTypeApplication,
|
||||||
|
Formats: []format.Format{&format.Generic{
|
||||||
|
PayloadTyp: 100,
|
||||||
|
RTPMa: "ulpfec/8000",
|
||||||
|
ClockRat: 8000,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "3",
|
||||||
|
Type: MediaTypeVideo,
|
||||||
|
Formats: []format.Format{&format.Generic{
|
||||||
|
PayloadTyp: 31,
|
||||||
|
ClockRat: 90000,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "4",
|
||||||
|
Type: MediaTypeApplication,
|
||||||
|
Formats: []format.Format{&format.Generic{
|
||||||
|
PayloadTyp: 101,
|
||||||
|
RTPMa: "ulpfec/8000",
|
||||||
|
ClockRat: 8000,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionUnmarshal(t *testing.T) {
|
func TestSessionUnmarshal(t *testing.T) {
|
||||||
@@ -735,6 +818,25 @@ func FuzzSessionUnmarshalErrors(f *testing.F) {
|
|||||||
"m=audio 0 RTP/AVP/TCP 0\r\n" +
|
"m=audio 0 RTP/AVP/TCP 0\r\n" +
|
||||||
"a=mid:2\r\n")
|
"a=mid:2\r\n")
|
||||||
|
|
||||||
|
f.Add("v=0\r\n" +
|
||||||
|
"o=adam 289083124 289083124 IN IP4 host.example.com\r\n" +
|
||||||
|
"s=ULP FEC Seminar\r\n" +
|
||||||
|
"t=0 0\r\n" +
|
||||||
|
"c=IN IP4 224.2.17.12/127\r\n" +
|
||||||
|
"a=group:FEC 1 2\r\n" +
|
||||||
|
"a=group:FEC 3 4\r\n" +
|
||||||
|
"m=audio 30000 RTP/AVP 0\r\n" +
|
||||||
|
"a=mid:1\r\n" +
|
||||||
|
"m=application 30002 RTP/AVP 100\r\n" +
|
||||||
|
"a=rtpmap:100 ulpfec/8000\r\n" +
|
||||||
|
"a=mid:2\r\n" +
|
||||||
|
"m=video 30004 RTP/AVP 31\r\n" +
|
||||||
|
"a=mid:3\r\n" +
|
||||||
|
"m=application 30004 RTP/AVP 101\r\n" +
|
||||||
|
"c=IN IP4 224.2.17.13/127\r\n" +
|
||||||
|
"a=rtpmap:101 ulpfec/8000\r\n" +
|
||||||
|
"a=mid:4\r\n")
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, enc string) {
|
f.Fuzz(func(t *testing.T, enc string) {
|
||||||
var sd sdp.SessionDescription
|
var sd sdp.SessionDescription
|
||||||
err := sd.Unmarshal([]byte(enc))
|
err := sd.Unmarshal([]byte(enc))
|
||||||
|
2
pkg/description/testdata/fuzz/FuzzSessionUnmarshalErrors/fb7e5db5b68fa760
vendored
Normal file
2
pkg/description/testdata/fuzz/FuzzSessionUnmarshalErrors/fb7e5db5b68fa760
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
string("v=0\ns=0\na=group:FEC 0")
|
@@ -27,17 +27,19 @@ func getSessionID(header base.Header) string {
|
|||||||
func serverSideDescription(d *description.Session, contentBase *url.URL) *description.Session {
|
func serverSideDescription(d *description.Session, contentBase *url.URL) *description.Session {
|
||||||
out := &description.Session{
|
out := &description.Session{
|
||||||
Title: d.Title,
|
Title: d.Title,
|
||||||
|
FECGroups: d.FECGroups,
|
||||||
Medias: make([]*description.Media, len(d.Medias)),
|
Medias: make([]*description.Media, len(d.Medias)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, medi := range d.Medias {
|
for i, medi := range d.Medias {
|
||||||
mc := &description.Media{
|
mc := &description.Media{
|
||||||
Type: medi.Type,
|
Type: medi.Type,
|
||||||
|
ID: medi.ID,
|
||||||
// Direction: skipped for the moment
|
// Direction: skipped for the moment
|
||||||
Formats: medi.Formats,
|
|
||||||
// we have to use trackID=number in order to support clients
|
// we have to use trackID=number in order to support clients
|
||||||
// like the Grandstream GXV3500.
|
// like the Grandstream GXV3500.
|
||||||
Control: "trackID=" + strconv.FormatInt(int64(i), 10),
|
Control: "trackID=" + strconv.FormatInt(int64(i), 10),
|
||||||
|
Formats: medi.Formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
// always use the absolute URL of the track as control attribute, in order
|
// always use the absolute URL of the track as control attribute, in order
|
||||||
|
Reference in New Issue
Block a user