add RTP-Info header

This commit is contained in:
aler9
2021-03-14 12:09:18 +01:00
parent 32c10cfb66
commit f6c26b5369
5 changed files with 239 additions and 87 deletions

View File

@@ -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
View 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, ",")}
}

View 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)
})
}
}

View File

@@ -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}
}

View File

@@ -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, ";")}
}