fix compatibility with Mercury cameras (#271) (#275)

This commit is contained in:
Alessandro Ros
2023-05-08 13:14:45 +02:00
committed by GitHub
parent a54a5946c7
commit 49d0d56367
5 changed files with 179 additions and 13 deletions

View File

@@ -27,6 +27,7 @@ func sha256Base64(in string) string {
} }
// Validator allows to validate credentials generated by a Sender. // Validator allows to validate credentials generated by a Sender.
//
// Deprecated: Validator{} has been replaced by Validate() // Deprecated: Validator{} has been replaced by Validate()
type Validator struct { type Validator struct {
user string user string
@@ -40,6 +41,7 @@ type Validator struct {
// NewValidator allocates a Validator. // NewValidator allocates a Validator.
// If methods is nil, the Basic and Digest methods are used. // If methods is nil, the Basic and Digest methods are used.
//
// Deprecated: Validator{} has been replaced by Validate() // Deprecated: Validator{} has been replaced by Validate()
func NewValidator(user string, pass string, methods []headers.AuthMethod) *Validator { func NewValidator(user string, pass string, methods []headers.AuthMethod) *Validator {
if methods == nil { if methods == nil {
@@ -80,6 +82,7 @@ func NewValidator(user string, pass string, methods []headers.AuthMethod) *Valid
// Header generates the WWW-Authenticate header needed by a client to // Header generates the WWW-Authenticate header needed by a client to
// authenticate. // authenticate.
//
// Deprecated: Validator{} has been replaced by Validate() // Deprecated: Validator{} has been replaced by Validate()
func (va *Validator) Header() base.HeaderValue { func (va *Validator) Header() base.HeaderValue {
var ret base.HeaderValue var ret base.HeaderValue
@@ -103,6 +106,7 @@ func (va *Validator) Header() base.HeaderValue {
} }
// ValidateRequest validates a request sent by a client. // ValidateRequest validates a request sent by a client.
//
// Deprecated: Validator{} has been replaced by Validate() // Deprecated: Validator{} has been replaced by Validate()
func (va *Validator) ValidateRequest(req *base.Request, baseURL *url.URL) error { func (va *Validator) ValidateRequest(req *base.Request, baseURL *url.URL) error {
var auth headers.Authorization var auth headers.Authorization

View File

@@ -4,6 +4,7 @@ package media
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -14,6 +15,8 @@ import (
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v3/pkg/url"
) )
var smartRegexp = regexp.MustCompile("^([0-9]+) (.*?)/90000")
func getControlAttribute(attributes []psdp.Attribute) string { func getControlAttribute(attributes []psdp.Attribute) string {
for _, attr := range attributes { for _, attr := range attributes {
if attr.Key == "control" { if attr.Key == "control" {
@@ -135,9 +138,9 @@ func (m *Media) unmarshal(md *psdp.MediaDescription) error {
if payloadType == "smart/1/90000" { if payloadType == "smart/1/90000" {
for _, attr := range md.Attributes { for _, attr := range md.Attributes {
if attr.Key == "rtpmap" { if attr.Key == "rtpmap" {
i := strings.Index(attr.Value, " TP-LINK/90000") sm := smartRegexp.FindStringSubmatch(attr.Value)
if i >= 0 { if sm != nil {
payloadType = attr.Value[:i] payloadType = sm[1]
break break
} }
} }

View File

@@ -522,19 +522,19 @@ var casesMedias = []struct {
"o=- 4158123474391860926 2 IN IP4 127.0.0.1\r\n" + "o=- 4158123474391860926 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\n" + "s=-\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=application 42504 RTP/AVP smart/1/90000\r\n" + "m=application/TP-LINK 0 RTP/AVP smart/1/90000\r\n" +
"a=rtpmap:95 TP-LINK/90000\r\n", "a=rtpmap:95 TP-LINK/90000\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Stream\r\n" +
"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=application 0 RTP/AVP 95\r\n" + "m=application/TP-LINK 0 RTP/AVP 95\r\n" +
"a=control\r\n" + "a=control\r\n" +
"a=rtpmap:95 TP-LINK/90000\r\n", "a=rtpmap:95 TP-LINK/90000\r\n",
Medias{ Medias{
{ {
Type: "application", Type: "application/TP-LINK",
Formats: []formats.Format{&formats.Generic{ Formats: []formats.Format{&formats.Generic{
PayloadTyp: 95, PayloadTyp: 95,
RTPMa: "TP-LINK/90000", RTPMa: "TP-LINK/90000",
@@ -543,6 +543,34 @@ var casesMedias = []struct {
}, },
}, },
}, },
{
"mercury",
"v=0\n" +
"o=- 14665860 31787219 1 IN IP4 192.168.0.60\n" +
"s=Session streamed by \"MERCURY RTSP Server\"\n" +
"t=0 0\n" +
"a=smart_encoder:virtualIFrame=1\n" +
"m=application/MERCURY 0 RTP/AVP smart/1/90000\n" +
"a=rtpmap:95 MERCURY/90000\n",
"v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" +
"c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" +
"m=application/MERCURY 0 RTP/AVP 95\r\n" +
"a=control\r\n" +
"a=rtpmap:95 MERCURY/90000\r\n",
Medias{
{
Type: "application/MERCURY",
Formats: []formats.Format{&formats.Generic{
PayloadTyp: 95,
RTPMa: "MERCURY/90000",
ClockRat: 90000,
}},
},
},
},
{ {
"h264 with space at end", "h264 with space at end",
"v=0\r\n" + "v=0\r\n" +

View File

@@ -406,14 +406,13 @@ func (s *SessionDescription) unmarshalMediaDescription(value string) error {
newMediaDesc := &psdp.MediaDescription{} newMediaDesc := &psdp.MediaDescription{}
if fields[0] == "application/TP-LINK" {
fields[0] = "application"
}
// <media> // <media>
// Set according to currently registered with IANA // Set according to currently registered with IANA
// https://tools.ietf.org/html/rfc4566#section-5.14 // https://tools.ietf.org/html/rfc4566#section-5.14
if i := indexOf(fields[0], []string{"audio", "video", "text", "application", "message"}); i == -1 { if fields[0] != "video" &&
fields[0] != "audio" &&
fields[0] != "application" &&
!strings.HasPrefix(fields[0], "application/") {
return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields[0]) return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields[0])
} }
newMediaDesc.MediaName.Media = fields[0] newMediaDesc.MediaName.Media = fields[0]

View File

@@ -1876,7 +1876,7 @@ var cases = []struct {
"m=audio 0 RTP/AVP 8\r\n" + "m=audio 0 RTP/AVP 8\r\n" +
"a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" +
"a=control:track2\r\n" + "a=control:track2\r\n" +
"m=application 0 RTP/AVP smart/1/90000\r\n" + "m=application/TP-LINK 0 RTP/AVP smart/1/90000\r\n" +
"a=rtpmap:95 TP-LINK/90000\r\n" + "a=rtpmap:95 TP-LINK/90000\r\n" +
"a=control:track3\r\n"), "a=control:track3\r\n"),
SessionDescription{ SessionDescription{
@@ -1945,7 +1945,7 @@ var cases = []struct {
}, },
{ {
MediaName: psdp.MediaName{ MediaName: psdp.MediaName{
Media: "application", Media: "application/TP-LINK",
Protos: []string{"RTP", "AVP"}, Protos: []string{"RTP", "AVP"},
Formats: []string{"smart/1/90000"}, Formats: []string{"smart/1/90000"},
}, },
@@ -2215,6 +2215,138 @@ var cases = []struct {
}, },
}, },
}, },
{
"mercury",
[]byte("v=0\n" +
"o=- 14665860 31787219 1 IN IP4 192.168.0.60\n" +
"s=Session streamed by \"MERCURY RTSP Server\"\n" +
"t=0 0\n" +
"a=smart_encoder:virtualIFrame=1\n" +
"m=video 0 RTP/AVP 96\n" +
"c=IN IP4 0.0.0.0\n" +
"b=AS:4096\n" +
"a=range:npt=0-\n" +
"a=control:track1\n" +
"a=rtpmap:96 H264/90000\n" +
"a=fmtp:96 packetization-mode=1; profile-level-id=4D001F;" +
" sprop-parameter-sets=J00AH+dAKALdgKUFBQXwAAADABAAAAMCi2gD6AXf//wK,KO48gA==\n" +
"m=audio 0 RTP/AVP 8\n" +
"a=rtpmap:8 PCMA/8000\n" +
"a=control:track2\n" +
"m=application/MERCURY 0 RTP/AVP smart/1/90000\n" +
"a=rtpmap:95 MERCURY/90000\n" +
"a=control:track3\n"),
[]byte("v=0\r\n" +
"o=- 14665860 31787219 1 IN IP4 192.168.0.60\r\n" +
"s=Session streamed by \"MERCURY RTSP Server\"\r\n" +
"t=0 0\r\n" +
"a=smart_encoder:virtualIFrame=1\r\n" +
"m=video 0 RTP/AVP 96\r\n" +
"c=IN IP4 0.0.0.0\r\n" +
"b=AS:4096\r\n" +
"a=range:npt=0-\r\n" +
"a=control:track1\r\n" +
"a=rtpmap:96 H264/90000\r\n" +
"a=fmtp:96 packetization-mode=1; profile-level-id=4D001F;" +
" sprop-parameter-sets=J00AH+dAKALdgKUFBQXwAAADABAAAAMCi2gD6AXf//wK,KO48gA==\r\n" +
"m=audio 0 RTP/AVP 8\r\n" +
"a=rtpmap:8 PCMA/8000\r\n" +
"a=control:track2\r\n" +
"m=application/MERCURY 0 RTP/AVP smart/1/90000\r\n" +
"a=rtpmap:95 MERCURY/90000\r\n" +
"a=control:track3\r\n"),
SessionDescription{
Origin: psdp.Origin{
Username: "- 14665860",
SessionID: 31787219,
SessionVersion: 1,
NetworkType: "IN",
AddressType: "IP4",
UnicastAddress: "192.168.0.60",
},
SessionName: "Session streamed by \"MERCURY RTSP Server\"",
TimeDescriptions: []psdp.TimeDescription{{}},
Attributes: []psdp.Attribute{
{
Key: "smart_encoder",
Value: "virtualIFrame=1",
},
},
MediaDescriptions: []*psdp.MediaDescription{
{
MediaName: psdp.MediaName{
Media: "video",
Protos: []string{"RTP", "AVP"},
Formats: []string{"96"},
},
ConnectionInformation: &psdp.ConnectionInformation{
NetworkType: "IN",
AddressType: "IP4",
Address: &psdp.Address{
Address: "0.0.0.0",
},
},
Bandwidth: []psdp.Bandwidth{{
Type: "AS",
Bandwidth: 4096,
}},
Attributes: []psdp.Attribute{
{
Key: "range",
Value: "npt=0-",
},
{
Key: "control",
Value: "track1",
},
{
Key: "rtpmap",
Value: "96 H264/90000",
},
{
Key: "fmtp",
Value: "96 packetization-mode=1; profile-level-id=4D001F;" +
" sprop-parameter-sets=J00AH+dAKALdgKUFBQXwAAADABAAAAMCi2gD6AXf//wK,KO48gA==",
},
},
},
{
MediaName: psdp.MediaName{
Media: "audio",
Protos: []string{"RTP", "AVP"},
Formats: []string{"8"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "8 PCMA/8000",
},
{
Key: "control",
Value: "track2",
},
},
},
{
MediaName: psdp.MediaName{
Media: "application/MERCURY",
Protos: []string{"RTP", "AVP"},
Formats: []string{"smart/1/90000"},
},
Attributes: []psdp.Attribute{
{
Key: "rtpmap",
Value: "95 MERCURY/90000",
},
{
Key: "control",
Value: "track3",
},
},
},
},
},
},
} }
func TestUnmarshal(t *testing.T) { func TestUnmarshal(t *testing.T) {