mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
add RTP-Info header
This commit is contained in:
@@ -79,7 +79,7 @@ func findValue(v0 string) (string, string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAuth parses an Authenticate or a WWW-Authenticate header.
|
||||
// ReadAuth decodes an Authenticate or a WWW-Authenticate header.
|
||||
func ReadAuth(v base.HeaderValue) (*Auth, error) {
|
||||
if len(v) == 0 {
|
||||
return nil, fmt.Errorf("value not provided")
|
||||
@@ -89,7 +89,7 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) {
|
||||
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
||||
}
|
||||
|
||||
ha := &Auth{}
|
||||
h := &Auth{}
|
||||
|
||||
v0 := v[0]
|
||||
|
||||
@@ -100,10 +100,10 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) {
|
||||
|
||||
switch v0[:i] {
|
||||
case "Basic":
|
||||
ha.Method = AuthBasic
|
||||
h.Method = AuthBasic
|
||||
|
||||
case "Digest":
|
||||
ha.Method = AuthDigest
|
||||
h.Method = AuthDigest
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid method (%s)", v0[:i])
|
||||
@@ -127,28 +127,28 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) {
|
||||
|
||||
switch key {
|
||||
case "username":
|
||||
ha.Username = &val
|
||||
h.Username = &val
|
||||
|
||||
case "realm":
|
||||
ha.Realm = &val
|
||||
h.Realm = &val
|
||||
|
||||
case "nonce":
|
||||
ha.Nonce = &val
|
||||
h.Nonce = &val
|
||||
|
||||
case "uri":
|
||||
ha.URI = &val
|
||||
h.URI = &val
|
||||
|
||||
case "response":
|
||||
ha.Response = &val
|
||||
h.Response = &val
|
||||
|
||||
case "opaque":
|
||||
ha.Opaque = &val
|
||||
h.Opaque = &val
|
||||
|
||||
case "stale":
|
||||
ha.Stale = &val
|
||||
h.Stale = &val
|
||||
|
||||
case "algorithm":
|
||||
ha.Algorithm = &val
|
||||
h.Algorithm = &val
|
||||
|
||||
// ignore non-standard keys
|
||||
}
|
||||
@@ -164,14 +164,14 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return ha, nil
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Write encodes an Authenticate or a WWW-Authenticate header.
|
||||
func (ha Auth) Write() base.HeaderValue {
|
||||
func (h Auth) Write() base.HeaderValue {
|
||||
ret := ""
|
||||
|
||||
switch ha.Method {
|
||||
switch h.Method {
|
||||
case AuthBasic:
|
||||
ret += "Basic"
|
||||
|
||||
@@ -181,41 +181,41 @@ func (ha Auth) Write() base.HeaderValue {
|
||||
|
||||
ret += " "
|
||||
|
||||
var vals []string
|
||||
var rets []string
|
||||
|
||||
if ha.Username != nil {
|
||||
vals = append(vals, "username=\""+*ha.Username+"\"")
|
||||
if h.Username != nil {
|
||||
rets = append(rets, "username=\""+*h.Username+"\"")
|
||||
}
|
||||
|
||||
if ha.Realm != nil {
|
||||
vals = append(vals, "realm=\""+*ha.Realm+"\"")
|
||||
if h.Realm != nil {
|
||||
rets = append(rets, "realm=\""+*h.Realm+"\"")
|
||||
}
|
||||
|
||||
if ha.Nonce != nil {
|
||||
vals = append(vals, "nonce=\""+*ha.Nonce+"\"")
|
||||
if h.Nonce != nil {
|
||||
rets = append(rets, "nonce=\""+*h.Nonce+"\"")
|
||||
}
|
||||
|
||||
if ha.URI != nil {
|
||||
vals = append(vals, "uri=\""+*ha.URI+"\"")
|
||||
if h.URI != nil {
|
||||
rets = append(rets, "uri=\""+*h.URI+"\"")
|
||||
}
|
||||
|
||||
if ha.Response != nil {
|
||||
vals = append(vals, "response=\""+*ha.Response+"\"")
|
||||
if h.Response != nil {
|
||||
rets = append(rets, "response=\""+*h.Response+"\"")
|
||||
}
|
||||
|
||||
if ha.Opaque != nil {
|
||||
vals = append(vals, "opaque=\""+*ha.Opaque+"\"")
|
||||
if h.Opaque != nil {
|
||||
rets = append(rets, "opaque=\""+*h.Opaque+"\"")
|
||||
}
|
||||
|
||||
if ha.Stale != nil {
|
||||
vals = append(vals, "stale=\""+*ha.Stale+"\"")
|
||||
if h.Stale != nil {
|
||||
rets = append(rets, "stale=\""+*h.Stale+"\"")
|
||||
}
|
||||
|
||||
if ha.Algorithm != nil {
|
||||
vals = append(vals, "algorithm=\""+*ha.Algorithm+"\"")
|
||||
if h.Algorithm != nil {
|
||||
rets = append(rets, "algorithm=\""+*h.Algorithm+"\"")
|
||||
}
|
||||
|
||||
ret += strings.Join(vals, ", ")
|
||||
ret += strings.Join(rets, ", ")
|
||||
|
||||
return base.HeaderValue{ret}
|
||||
}
|
||||
|
87
pkg/headers/rtpinfo.go
Normal file
87
pkg/headers/rtpinfo.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
)
|
||||
|
||||
// RTPInfoEntry is an entry of an RTP-Info header.
|
||||
type RTPInfoEntry struct {
|
||||
URL *base.URL
|
||||
SequenceNumber uint16
|
||||
RTPTime uint32
|
||||
}
|
||||
|
||||
// RTPInfo is a RTP-Info header.
|
||||
type RTPInfo []RTPInfoEntry
|
||||
|
||||
// ReadRTPInfo decodes a RTP-Info header.
|
||||
func ReadRTPInfo(v base.HeaderValue) (*RTPInfo, error) {
|
||||
if len(v) == 0 {
|
||||
return nil, fmt.Errorf("value not provided")
|
||||
}
|
||||
|
||||
if len(v) > 1 {
|
||||
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
||||
}
|
||||
|
||||
h := &RTPInfo{}
|
||||
|
||||
for _, tmp := range strings.Split(v[0], ",") {
|
||||
e := RTPInfoEntry{}
|
||||
|
||||
for _, kv := range strings.Split(tmp, ";") {
|
||||
tmp := strings.SplitN(kv, "=", 2)
|
||||
if len(tmp) != 2 {
|
||||
return nil, fmt.Errorf("unable to parse key-value (%v)", kv)
|
||||
}
|
||||
|
||||
k, v := tmp[0], tmp[1]
|
||||
switch k {
|
||||
case "url":
|
||||
vu, err := base.ParseURL(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.URL = vu
|
||||
|
||||
case "seq":
|
||||
vi, err := strconv.ParseUint(v, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.SequenceNumber = uint16(vi)
|
||||
|
||||
case "rtptime":
|
||||
vi, err := strconv.ParseUint(v, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.RTPTime = uint32(vi)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid key: %v", k)
|
||||
}
|
||||
}
|
||||
|
||||
*h = append(*h, e)
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Write encodes a RTP-Info header.
|
||||
func (h RTPInfo) Write() base.HeaderValue {
|
||||
var rets []string
|
||||
|
||||
for _, e := range h {
|
||||
rets = append(rets, "url="+e.URL.String()+
|
||||
";seq="+strconv.FormatUint(uint64(e.SequenceNumber), 10)+
|
||||
";rtptime="+strconv.FormatUint(uint64(e.RTPTime), 10))
|
||||
}
|
||||
|
||||
return base.HeaderValue{strings.Join(rets, ",")}
|
||||
}
|
65
pkg/headers/rtpinfo_test.go
Normal file
65
pkg/headers/rtpinfo_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package headers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
)
|
||||
|
||||
var casesRTPInfo = []struct {
|
||||
name string
|
||||
vin base.HeaderValue
|
||||
vout base.HeaderValue
|
||||
h *RTPInfo
|
||||
}{
|
||||
{
|
||||
"single value",
|
||||
base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556`},
|
||||
base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556`},
|
||||
&RTPInfo{
|
||||
{
|
||||
URL: base.MustParseURL("rtsp://127.0.0.1/test.mkv/track1"),
|
||||
SequenceNumber: 35243,
|
||||
RTPTime: 717574556,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple value",
|
||||
base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556,url=rtsp://127.0.0.1/test.mkv/track2;seq=13655;rtptime=2848846950`},
|
||||
base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556,url=rtsp://127.0.0.1/test.mkv/track2;seq=13655;rtptime=2848846950`},
|
||||
&RTPInfo{
|
||||
{
|
||||
URL: base.MustParseURL("rtsp://127.0.0.1/test.mkv/track1"),
|
||||
SequenceNumber: 35243,
|
||||
RTPTime: 717574556,
|
||||
},
|
||||
{
|
||||
URL: base.MustParseURL("rtsp://127.0.0.1/test.mkv/track2"),
|
||||
SequenceNumber: 13655,
|
||||
RTPTime: 2848846950,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestRTPInfoRead(t *testing.T) {
|
||||
for _, c := range casesRTPInfo {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
req, err := ReadRTPInfo(c.vin)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.h, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRTPInfoWrite(t *testing.T) {
|
||||
for _, c := range casesRTPInfo {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
req := c.h.Write()
|
||||
require.Equal(t, c.vout, req)
|
||||
})
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ type Session struct {
|
||||
Timeout *uint
|
||||
}
|
||||
|
||||
// ReadSession parses a Session header.
|
||||
// ReadSession decodes a Session header.
|
||||
func ReadSession(v base.HeaderValue) (*Session, error) {
|
||||
if len(v) == 0 {
|
||||
return nil, fmt.Errorf("value not provided")
|
||||
@@ -32,20 +32,20 @@ func ReadSession(v base.HeaderValue) (*Session, error) {
|
||||
return nil, fmt.Errorf("invalid value (%v)", v)
|
||||
}
|
||||
|
||||
hs := &Session{}
|
||||
h := &Session{}
|
||||
|
||||
hs.Session = parts[0]
|
||||
h.Session = parts[0]
|
||||
|
||||
for _, part := range parts[1:] {
|
||||
// remove leading spaces
|
||||
part = strings.TrimLeft(part, " ")
|
||||
|
||||
keyval := strings.Split(part, "=")
|
||||
if len(keyval) != 2 {
|
||||
kv := strings.Split(part, "=")
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("invalid value")
|
||||
}
|
||||
|
||||
key, strValue := keyval[0], keyval[1]
|
||||
key, strValue := kv[0], kv[1]
|
||||
if key != "timeout" {
|
||||
return nil, fmt.Errorf("invalid key '%s'", key)
|
||||
}
|
||||
@@ -56,19 +56,19 @@ func ReadSession(v base.HeaderValue) (*Session, error) {
|
||||
}
|
||||
uiv := uint(iv)
|
||||
|
||||
hs.Timeout = &uiv
|
||||
h.Timeout = &uiv
|
||||
}
|
||||
|
||||
return hs, nil
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Write encodes a Session header
|
||||
func (hs Session) Write() base.HeaderValue {
|
||||
val := hs.Session
|
||||
// Write encodes a Session header.
|
||||
func (h Session) Write() base.HeaderValue {
|
||||
ret := h.Session
|
||||
|
||||
if hs.Timeout != nil {
|
||||
val += ";timeout=" + strconv.FormatUint(uint64(*hs.Timeout), 10)
|
||||
if h.Timeout != nil {
|
||||
ret += ";timeout=" + strconv.FormatUint(uint64(*h.Timeout), 10)
|
||||
}
|
||||
|
||||
return base.HeaderValue{val}
|
||||
return base.HeaderValue{ret}
|
||||
}
|
||||
|
@@ -20,8 +20,8 @@ const (
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (sm TransportMode) String() string {
|
||||
switch sm {
|
||||
func (tm TransportMode) String() string {
|
||||
switch tm {
|
||||
case TransportModePlay:
|
||||
return "play"
|
||||
|
||||
@@ -89,7 +89,7 @@ func parsePorts(val string) (*[2]int, error) {
|
||||
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
|
||||
}
|
||||
|
||||
// ReadTransport parses a Transport header.
|
||||
// ReadTransport decodes a Transport header.
|
||||
func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
if len(v) == 0 {
|
||||
return nil, fmt.Errorf("value not provided")
|
||||
@@ -99,7 +99,7 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
||||
}
|
||||
|
||||
ht := &Transport{}
|
||||
h := &Transport{}
|
||||
|
||||
parts := strings.Split(v[0], ";")
|
||||
if len(parts) == 0 {
|
||||
@@ -108,10 +108,10 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
|
||||
switch parts[0] {
|
||||
case "RTP/AVP", "RTP/AVP/UDP":
|
||||
ht.Protocol = base.StreamProtocolUDP
|
||||
h.Protocol = base.StreamProtocolUDP
|
||||
|
||||
case "RTP/AVP/TCP":
|
||||
ht.Protocol = base.StreamProtocolTCP
|
||||
h.Protocol = base.StreamProtocolTCP
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid protocol (%v)", v)
|
||||
@@ -121,12 +121,12 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
switch parts[0] {
|
||||
case "unicast":
|
||||
v := base.StreamDeliveryUnicast
|
||||
ht.Delivery = &v
|
||||
h.Delivery = &v
|
||||
parts = parts[1:]
|
||||
|
||||
case "multicast":
|
||||
v := base.StreamDeliveryMulticast
|
||||
ht.Delivery = &v
|
||||
h.Delivery = &v
|
||||
parts = parts[1:]
|
||||
|
||||
// cast is optional, do not return any error
|
||||
@@ -135,7 +135,7 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
for _, t := range parts {
|
||||
if strings.HasPrefix(t, "destination=") {
|
||||
v := t[len("destination="):]
|
||||
ht.Destination = &v
|
||||
h.Destination = &v
|
||||
|
||||
} else if strings.HasPrefix(t, "ttl=") {
|
||||
v, err := strconv.ParseUint(t[len("ttl="):], 10, 64)
|
||||
@@ -143,35 +143,35 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
return nil, err
|
||||
}
|
||||
vu := uint(v)
|
||||
ht.TTL = &vu
|
||||
h.TTL = &vu
|
||||
|
||||
} else if strings.HasPrefix(t, "port=") {
|
||||
ports, err := parsePorts(t[len("port="):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ht.Ports = ports
|
||||
h.Ports = ports
|
||||
|
||||
} else if strings.HasPrefix(t, "client_port=") {
|
||||
ports, err := parsePorts(t[len("client_port="):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ht.ClientPorts = ports
|
||||
h.ClientPorts = ports
|
||||
|
||||
} else if strings.HasPrefix(t, "server_port=") {
|
||||
ports, err := parsePorts(t[len("server_port="):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ht.ServerPorts = ports
|
||||
h.ServerPorts = ports
|
||||
|
||||
} else if strings.HasPrefix(t, "interleaved=") {
|
||||
ports, err := parsePorts(t[len("interleaved="):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ht.InterleavedIds = ports
|
||||
h.InterleavedIds = ports
|
||||
|
||||
} else if strings.HasPrefix(t, "mode=") {
|
||||
str := strings.ToLower(t[len("mode="):])
|
||||
@@ -181,13 +181,13 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
switch str {
|
||||
case "play":
|
||||
v := TransportModePlay
|
||||
ht.Mode = &v
|
||||
h.Mode = &v
|
||||
|
||||
// receive is an old alias for record, used by ffmpeg with the
|
||||
// -listen flag, and by Darwin Streaming Server
|
||||
case "record", "receive":
|
||||
v := TransportModeRecord
|
||||
ht.Mode = &v
|
||||
h.Mode = &v
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid transport mode: '%s'", str)
|
||||
@@ -197,49 +197,49 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||
// ignore non-standard keys
|
||||
}
|
||||
|
||||
return ht, nil
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Write encodes a Transport header
|
||||
func (ht Transport) Write() base.HeaderValue {
|
||||
var vals []string
|
||||
func (h Transport) Write() base.HeaderValue {
|
||||
var rets []string
|
||||
|
||||
if ht.Protocol == base.StreamProtocolUDP {
|
||||
vals = append(vals, "RTP/AVP")
|
||||
if h.Protocol == base.StreamProtocolUDP {
|
||||
rets = append(rets, "RTP/AVP")
|
||||
} else {
|
||||
vals = append(vals, "RTP/AVP/TCP")
|
||||
rets = append(rets, "RTP/AVP/TCP")
|
||||
}
|
||||
|
||||
if ht.Delivery != nil {
|
||||
if *ht.Delivery == base.StreamDeliveryUnicast {
|
||||
vals = append(vals, "unicast")
|
||||
if h.Delivery != nil {
|
||||
if *h.Delivery == base.StreamDeliveryUnicast {
|
||||
rets = append(rets, "unicast")
|
||||
} else {
|
||||
vals = append(vals, "multicast")
|
||||
rets = append(rets, "multicast")
|
||||
}
|
||||
}
|
||||
|
||||
if ht.ClientPorts != nil {
|
||||
ports := *ht.ClientPorts
|
||||
vals = append(vals, "client_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
|
||||
if h.ClientPorts != nil {
|
||||
ports := *h.ClientPorts
|
||||
rets = append(rets, "client_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
|
||||
}
|
||||
|
||||
if ht.ServerPorts != nil {
|
||||
ports := *ht.ServerPorts
|
||||
vals = append(vals, "server_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
|
||||
if h.ServerPorts != nil {
|
||||
ports := *h.ServerPorts
|
||||
rets = append(rets, "server_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
|
||||
}
|
||||
|
||||
if ht.InterleavedIds != nil {
|
||||
ports := *ht.InterleavedIds
|
||||
vals = append(vals, "interleaved="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
|
||||
if h.InterleavedIds != nil {
|
||||
ports := *h.InterleavedIds
|
||||
rets = append(rets, "interleaved="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
|
||||
}
|
||||
|
||||
if ht.Mode != nil {
|
||||
if *ht.Mode == TransportModePlay {
|
||||
vals = append(vals, "mode=play")
|
||||
if h.Mode != nil {
|
||||
if *h.Mode == TransportModePlay {
|
||||
rets = append(rets, "mode=play")
|
||||
} else {
|
||||
vals = append(vals, "mode=record")
|
||||
rets = append(rets, "mode=record")
|
||||
}
|
||||
}
|
||||
|
||||
return base.HeaderValue{strings.Join(vals, ";")}
|
||||
return base.HeaderValue{strings.Join(rets, ";")}
|
||||
}
|
||||
|
Reference in New Issue
Block a user