store media IDs in description.Session

This commit is contained in:
aler9
2023-08-22 19:27:33 +02:00
committed by Alessandro Ros
parent 70c719a211
commit 836ffe3ff6
4 changed files with 120 additions and 28 deletions

View File

@@ -8,6 +8,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"unicode"
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
@@ -17,9 +18,23 @@ import (
var smartRegexp = regexp.MustCompile("^([0-9]+) (.*?)/90000") var smartRegexp = regexp.MustCompile("^([0-9]+) (.*?)/90000")
func getControlAttribute(attributes []psdp.Attribute) string { func replaceSmartPayloadType(payloadType string, attributes []psdp.Attribute) string {
if payloadType == "smart/1/90000" {
for _, attr := range attributes { for _, attr := range attributes {
if attr.Key == "control" { if attr.Key == "rtpmap" {
sm := smartRegexp.FindStringSubmatch(attr.Value)
if sm != nil {
return sm[1]
}
}
}
}
return payloadType
}
func getAttribute(attributes []psdp.Attribute, key string) string {
for _, attr := range attributes {
if attr.Key == key {
return attr.Value return attr.Value
} }
} }
@@ -92,6 +107,15 @@ func sortedKeys(fmtp map[string]string) []string {
return keys return keys
} }
func isAlphaNumeric(v string) bool {
for _, r := range v {
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
return false
}
}
return true
}
// MediaDirection is the direction of a media stream. // MediaDirection is the direction of a media stream.
type MediaDirection string type MediaDirection string
@@ -118,7 +142,10 @@ type Media struct {
// Media type. // Media type.
Type MediaType Type MediaType
// Direction of the stream. // Media ID (optional).
ID string
// Direction of the stream (optional).
Direction MediaDirection Direction MediaDirection
// Control attribute. // Control attribute.
@@ -131,22 +158,18 @@ type Media struct {
// Unmarshal decodes the media from the SDP format. // Unmarshal decodes the media from the SDP format.
func (m *Media) Unmarshal(md *psdp.MediaDescription) error { func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
m.Type = MediaType(md.MediaName.Media) m.Type = MediaType(md.MediaName.Media)
m.ID = getAttribute(md.Attributes, "mid")
if m.ID != "" && !isAlphaNumeric(m.ID) {
return fmt.Errorf("invalid mid: %v", m.ID)
}
m.Direction = getDirection(md.Attributes) m.Direction = getDirection(md.Attributes)
m.Control = getControlAttribute(md.Attributes) m.Control = getAttribute(md.Attributes, "control")
m.Formats = nil m.Formats = nil
for _, payloadType := range md.MediaName.Formats { for _, payloadType := range md.MediaName.Formats {
if payloadType == "smart/1/90000" { payloadType = replaceSmartPayloadType(payloadType, md.Attributes)
for _, attr := range md.Attributes {
if attr.Key == "rtpmap" {
sm := smartRegexp.FindStringSubmatch(attr.Value)
if sm != nil {
payloadType = sm[1]
break
}
}
}
}
tmp, err := strconv.ParseUint(payloadType, 10, 8) tmp, err := strconv.ParseUint(payloadType, 10, 8)
if err != nil { if err != nil {
@@ -179,12 +202,13 @@ func (m Media) Marshal() *psdp.MediaDescription {
Media: string(m.Type), Media: string(m.Type),
Protos: []string{"RTP", "AVP"}, Protos: []string{"RTP", "AVP"},
}, },
Attributes: []psdp.Attribute{ }
{
Key: "control", if m.ID != "" {
Value: m.Control, md.Attributes = append(md.Attributes, psdp.Attribute{
}, Key: "mid",
}, Value: m.ID,
})
} }
if m.Direction != "" { if m.Direction != "" {
@@ -193,6 +217,11 @@ func (m Media) Marshal() *psdp.MediaDescription {
}) })
} }
md.Attributes = append(md.Attributes, psdp.Attribute{
Key: "control",
Value: m.Control,
})
for _, forma := range m.Formats { for _, forma := range m.Formats {
typ := strconv.FormatUint(uint64(forma.PayloadType()), 10) typ := strconv.FormatUint(uint64(forma.PayloadType()), 10)
md.MediaName.Formats = append(md.MediaName.Formats, typ) md.MediaName.Formats = append(md.MediaName.Formats, typ)

View File

@@ -9,6 +9,33 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
) )
func atLeastOneHasMID(medias []*Media) bool {
for _, media := range medias {
if media.ID != "" {
return true
}
}
return false
}
func atLeastOneDoesntHaveMID(medias []*Media) bool {
for _, media := range medias {
if media.ID == "" {
return true
}
}
return false
}
func hasMediaWithID(medias []*Media, id string) bool {
for _, media := range medias {
if media.ID == id {
return true
}
}
return false
}
// 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).
@@ -48,9 +75,18 @@ func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error {
if err != nil { if err != nil {
return fmt.Errorf("media %d is invalid: %v", i+1, err) return fmt.Errorf("media %d is invalid: %v", i+1, err)
} }
if m.ID != "" && hasMediaWithID(d.Medias[:i], m.ID) {
return fmt.Errorf("duplicate media IDs")
}
d.Medias[i] = &m d.Medias[i] = &m
} }
if atLeastOneHasMID(d.Medias) && atLeastOneDoesntHaveMID(d.Medias) {
return fmt.Errorf("media IDs sent partially")
}
return nil return nil
} }

View File

@@ -51,8 +51,8 @@ var casesSession = []struct {
"a=rtpmap:97 H264/90000\r\n" + "a=rtpmap:97 H264/90000\r\n" +
"a=fmtp:97 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" + "a=fmtp:97 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" +
"m=audio 0 RTP/AVP 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" +
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=a\r\n" +
"a=recvonly\r\n" + "a=recvonly\r\n" +
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=a\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" +
"m=application 0 RTP/AVP 107\r\n" + "m=application 0 RTP/AVP 107\r\n" +
"a=control\r\n", "a=control\r\n",
@@ -121,8 +121,8 @@ var casesSession = []struct {
"a=rtpmap:97 H264/90000\r\n" + "a=rtpmap:97 H264/90000\r\n" +
"a=fmtp:97 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" + "a=fmtp:97 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" +
"m=audio 0 RTP/AVP 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" +
"a=control:trackID=2\r\n" +
"a=recvonly\r\n" + "a=recvonly\r\n" +
"a=control:trackID=2\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" +
"m=application 0 RTP/AVP 107\r\n" + "m=application 0 RTP/AVP 107\r\n" +
"a=control\r\n", "a=control\r\n",
@@ -264,8 +264,9 @@ var casesSession = []struct {
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=audio 0 RTP/AVP 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n" + "m=audio 0 RTP/AVP 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n" +
"a=control\r\n" + "a=mid:audio\r\n" +
"a=sendonly\r\n" + "a=sendonly\r\n" +
"a=control\r\n" +
"a=rtpmap:111 opus/48000/2\r\n" + "a=rtpmap:111 opus/48000/2\r\n" +
"a=fmtp:111 sprop-stereo=0\r\n" + "a=fmtp:111 sprop-stereo=0\r\n" +
"a=rtpmap:103 ISAC/16000\r\n" + "a=rtpmap:103 ISAC/16000\r\n" +
@@ -282,8 +283,9 @@ var casesSession = []struct {
"a=rtpmap:113 telephone-event/16000\r\n" + "a=rtpmap:113 telephone-event/16000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" + "a=rtpmap:126 telephone-event/8000\r\n" +
"m=video 0 RTP/AVP 96 97 98 99 100 101 127 124 125\r\n" + "m=video 0 RTP/AVP 96 97 98 99 100 101 127 124 125\r\n" +
"a=control\r\n" + "a=mid:video\r\n" +
"a=sendonly\r\n" + "a=sendonly\r\n" +
"a=control\r\n" +
"a=rtpmap:96 VP8/90000\r\n" + "a=rtpmap:96 VP8/90000\r\n" +
"a=rtpmap:97 rtx/90000\r\n" + "a=rtpmap:97 rtx/90000\r\n" +
"a=fmtp:97 apt=96\r\n" + "a=fmtp:97 apt=96\r\n" +
@@ -301,6 +303,7 @@ var casesSession = []struct {
Title: ``, Title: ``,
Medias: []*Media{ Medias: []*Media{
{ {
ID: "audio",
Type: MediaTypeAudio, Type: MediaTypeAudio,
Direction: MediaDirectionSendonly, Direction: MediaDirectionSendonly,
Formats: []format.Format{ Formats: []format.Format{
@@ -368,6 +371,7 @@ var casesSession = []struct {
}, },
}, },
{ {
ID: "video",
Type: MediaTypeVideo, Type: MediaTypeVideo,
Direction: MediaDirectionSendonly, Direction: MediaDirectionSendonly,
Formats: []format.Format{ Formats: []format.Format{
@@ -496,16 +500,16 @@ var casesSession = []struct {
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=video 0 RTP/AVP 26\r\n" + "m=video 0 RTP/AVP 26\r\n" +
"a=control:rtsp://192.168.0.1/video\r\n" +
"a=recvonly\r\n" + "a=recvonly\r\n" +
"a=control:rtsp://192.168.0.1/video\r\n" +
"a=rtpmap:26 JPEG/90000\r\n" + "a=rtpmap:26 JPEG/90000\r\n" +
"m=audio 0 RTP/AVP 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" +
"a=control:rtsp://192.168.0.1/audio\r\n" +
"a=recvonly\r\n" + "a=recvonly\r\n" +
"a=control:rtsp://192.168.0.1/audio\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" +
"m=audio 0 RTP/AVP 0\r\n" + "m=audio 0 RTP/AVP 0\r\n" +
"a=control:rtsp://192.168.0.1/audioback\r\n" +
"a=sendonly\r\n" + "a=sendonly\r\n" +
"a=control:rtsp://192.168.0.1/audioback\r\n" +
"a=rtpmap:0 PCMU/8000\r\n", "a=rtpmap:0 PCMU/8000\r\n",
Session{ Session{
Title: `RTSP Session with audiobackchannel`, Title: `RTSP Session with audiobackchannel`,
@@ -710,6 +714,27 @@ func FuzzSessionUnmarshalErrors(f *testing.F) {
"a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=zzz1210\r\n" + "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=zzz1210\r\n" +
"a=control:streamid=1\r\n") "a=control:streamid=1\r\n")
f.Add("v=0\r\n" +
"o=- 4158123474391860926 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\n" +
"t=0 0\r\n" +
"m=video 42504 RTP/AVP 96\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=fmtp:96 packetization-mode=1\r\n" +
"m=audio 0 RTP/AVP/TCP 0\r\n" +
"a=mid:2\r\n")
f.Add("v=0\r\n" +
"o=- 4158123474391860926 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\n" +
"t=0 0\r\n" +
"m=video 42504 RTP/AVP 96\r\n" +
"a=mid:2\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=fmtp:96 packetization-mode=1\r\n" +
"m=audio 0 RTP/AVP/TCP 0\r\n" +
"a=mid:2\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))

View File

@@ -0,0 +1,2 @@
go test fuzz v1
string("v=0\ns=0\nm=video 0 AVP 0\na=mid: ")