mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
store media IDs in description.Session
This commit is contained in:
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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))
|
||||
|
2
pkg/description/testdata/fuzz/FuzzSessionUnmarshalErrors/b0432aab46b15405
vendored
Normal file
2
pkg/description/testdata/fuzz/FuzzSessionUnmarshalErrors/b0432aab46b15405
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go test fuzz v1
|
||||
string("v=0\ns=0\nm=video 0 AVP 0\na=mid: ")
|
Reference in New Issue
Block a user