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"
"strconv"
"strings"
"unicode"
psdp "github.com/pion/sdp/v3"
@@ -17,9 +18,23 @@ import (
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 {
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
}
}
@@ -92,6 +107,15 @@ func sortedKeys(fmtp map[string]string) []string {
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.
type MediaDirection string
@@ -118,7 +142,10 @@ type Media struct {
// Media type.
Type MediaType
// Direction of the stream.
// Media ID (optional).
ID string
// Direction of the stream (optional).
Direction MediaDirection
// Control attribute.
@@ -131,22 +158,18 @@ type Media struct {
// Unmarshal decodes the media from the SDP format.
func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
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.Control = getControlAttribute(md.Attributes)
m.Control = getAttribute(md.Attributes, "control")
m.Formats = nil
for _, payloadType := range md.MediaName.Formats {
if payloadType == "smart/1/90000" {
for _, attr := range md.Attributes {
if attr.Key == "rtpmap" {
sm := smartRegexp.FindStringSubmatch(attr.Value)
if sm != nil {
payloadType = sm[1]
break
}
}
}
}
payloadType = replaceSmartPayloadType(payloadType, md.Attributes)
tmp, err := strconv.ParseUint(payloadType, 10, 8)
if err != nil {
@@ -179,12 +202,13 @@ func (m Media) Marshal() *psdp.MediaDescription {
Media: string(m.Type),
Protos: []string{"RTP", "AVP"},
},
Attributes: []psdp.Attribute{
{
Key: "control",
Value: m.Control,
},
},
}
if m.ID != "" {
md.Attributes = append(md.Attributes, psdp.Attribute{
Key: "mid",
Value: m.ID,
})
}
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 {
typ := strconv.FormatUint(uint64(forma.PayloadType()), 10)
md.MediaName.Formats = append(md.MediaName.Formats, typ)

View File

@@ -9,6 +9,33 @@ import (
"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.
type Session struct {
// base URL of the stream (read only).
@@ -48,9 +75,18 @@ func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error {
if err != nil {
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
}
if atLeastOneHasMID(d.Medias) && atLeastOneDoesntHaveMID(d.Medias) {
return fmt.Errorf("media IDs sent partially")
}
return nil
}

View File

@@ -51,8 +51,8 @@ var casesSession = []struct {
"a=rtpmap:97 H264/90000\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" +
"a=control:rtsp://10.0.100.50/profile5/media.smp/trackID=a\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" +
"m=application 0 RTP/AVP 107\r\n" +
"a=control\r\n",
@@ -121,8 +121,8 @@ var casesSession = []struct {
"a=rtpmap:97 H264/90000\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" +
"a=control:trackID=2\r\n" +
"a=recvonly\r\n" +
"a=control:trackID=2\r\n" +
"a=rtpmap:0 PCMU/8000\r\n" +
"m=application 0 RTP/AVP 107\r\n" +
"a=control\r\n",
@@ -264,8 +264,9 @@ var casesSession = []struct {
"c=IN IP4 0.0.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" +
"a=control\r\n" +
"a=mid:audio\r\n" +
"a=sendonly\r\n" +
"a=control\r\n" +
"a=rtpmap:111 opus/48000/2\r\n" +
"a=fmtp:111 sprop-stereo=0\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:126 telephone-event/8000\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=control\r\n" +
"a=rtpmap:96 VP8/90000\r\n" +
"a=rtpmap:97 rtx/90000\r\n" +
"a=fmtp:97 apt=96\r\n" +
@@ -301,6 +303,7 @@ var casesSession = []struct {
Title: ``,
Medias: []*Media{
{
ID: "audio",
Type: MediaTypeAudio,
Direction: MediaDirectionSendonly,
Formats: []format.Format{
@@ -368,6 +371,7 @@ var casesSession = []struct {
},
},
{
ID: "video",
Type: MediaTypeVideo,
Direction: MediaDirectionSendonly,
Formats: []format.Format{
@@ -496,16 +500,16 @@ var casesSession = []struct {
"c=IN IP4 0.0.0.0\r\n" +
"t=0 0\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=control:rtsp://192.168.0.1/video\r\n" +
"a=rtpmap:26 JPEG/90000\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=control:rtsp://192.168.0.1/audio\r\n" +
"a=rtpmap:0 PCMU/8000\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=control:rtsp://192.168.0.1/audioback\r\n" +
"a=rtpmap:0 PCMU/8000\r\n",
Session{
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=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) {
var sd sdp.SessionDescription
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: ")