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) { func ReadAuth(v base.HeaderValue) (*Auth, error) {
if len(v) == 0 { if len(v) == 0 {
return nil, fmt.Errorf("value not provided") 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) return nil, fmt.Errorf("value provided multiple times (%v)", v)
} }
ha := &Auth{} h := &Auth{}
v0 := v[0] v0 := v[0]
@@ -100,10 +100,10 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) {
switch v0[:i] { switch v0[:i] {
case "Basic": case "Basic":
ha.Method = AuthBasic h.Method = AuthBasic
case "Digest": case "Digest":
ha.Method = AuthDigest h.Method = AuthDigest
default: default:
return nil, fmt.Errorf("invalid method (%s)", v0[:i]) return nil, fmt.Errorf("invalid method (%s)", v0[:i])
@@ -127,28 +127,28 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) {
switch key { switch key {
case "username": case "username":
ha.Username = &val h.Username = &val
case "realm": case "realm":
ha.Realm = &val h.Realm = &val
case "nonce": case "nonce":
ha.Nonce = &val h.Nonce = &val
case "uri": case "uri":
ha.URI = &val h.URI = &val
case "response": case "response":
ha.Response = &val h.Response = &val
case "opaque": case "opaque":
ha.Opaque = &val h.Opaque = &val
case "stale": case "stale":
ha.Stale = &val h.Stale = &val
case "algorithm": case "algorithm":
ha.Algorithm = &val h.Algorithm = &val
// ignore non-standard keys // 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. // Write encodes an Authenticate or a WWW-Authenticate header.
func (ha Auth) Write() base.HeaderValue { func (h Auth) Write() base.HeaderValue {
ret := "" ret := ""
switch ha.Method { switch h.Method {
case AuthBasic: case AuthBasic:
ret += "Basic" ret += "Basic"
@@ -181,41 +181,41 @@ func (ha Auth) Write() base.HeaderValue {
ret += " " ret += " "
var vals []string var rets []string
if ha.Username != nil { if h.Username != nil {
vals = append(vals, "username=\""+*ha.Username+"\"") rets = append(rets, "username=\""+*h.Username+"\"")
} }
if ha.Realm != nil { if h.Realm != nil {
vals = append(vals, "realm=\""+*ha.Realm+"\"") rets = append(rets, "realm=\""+*h.Realm+"\"")
} }
if ha.Nonce != nil { if h.Nonce != nil {
vals = append(vals, "nonce=\""+*ha.Nonce+"\"") rets = append(rets, "nonce=\""+*h.Nonce+"\"")
} }
if ha.URI != nil { if h.URI != nil {
vals = append(vals, "uri=\""+*ha.URI+"\"") rets = append(rets, "uri=\""+*h.URI+"\"")
} }
if ha.Response != nil { if h.Response != nil {
vals = append(vals, "response=\""+*ha.Response+"\"") rets = append(rets, "response=\""+*h.Response+"\"")
} }
if ha.Opaque != nil { if h.Opaque != nil {
vals = append(vals, "opaque=\""+*ha.Opaque+"\"") rets = append(rets, "opaque=\""+*h.Opaque+"\"")
} }
if ha.Stale != nil { if h.Stale != nil {
vals = append(vals, "stale=\""+*ha.Stale+"\"") rets = append(rets, "stale=\""+*h.Stale+"\"")
} }
if ha.Algorithm != nil { if h.Algorithm != nil {
vals = append(vals, "algorithm=\""+*ha.Algorithm+"\"") rets = append(rets, "algorithm=\""+*h.Algorithm+"\"")
} }
ret += strings.Join(vals, ", ") ret += strings.Join(rets, ", ")
return base.HeaderValue{ret} 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 Timeout *uint
} }
// ReadSession parses a Session header. // ReadSession decodes a Session header.
func ReadSession(v base.HeaderValue) (*Session, error) { func ReadSession(v base.HeaderValue) (*Session, error) {
if len(v) == 0 { if len(v) == 0 {
return nil, fmt.Errorf("value not provided") 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) 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:] { for _, part := range parts[1:] {
// remove leading spaces // remove leading spaces
part = strings.TrimLeft(part, " ") part = strings.TrimLeft(part, " ")
keyval := strings.Split(part, "=") kv := strings.Split(part, "=")
if len(keyval) != 2 { if len(kv) != 2 {
return nil, fmt.Errorf("invalid value") return nil, fmt.Errorf("invalid value")
} }
key, strValue := keyval[0], keyval[1] key, strValue := kv[0], kv[1]
if key != "timeout" { if key != "timeout" {
return nil, fmt.Errorf("invalid key '%s'", key) return nil, fmt.Errorf("invalid key '%s'", key)
} }
@@ -56,19 +56,19 @@ func ReadSession(v base.HeaderValue) (*Session, error) {
} }
uiv := uint(iv) uiv := uint(iv)
hs.Timeout = &uiv h.Timeout = &uiv
} }
return hs, nil return h, nil
} }
// Write encodes a Session header // Write encodes a Session header.
func (hs Session) Write() base.HeaderValue { func (h Session) Write() base.HeaderValue {
val := hs.Session ret := h.Session
if hs.Timeout != nil { if h.Timeout != nil {
val += ";timeout=" + strconv.FormatUint(uint64(*hs.Timeout), 10) 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. // String implements fmt.Stringer.
func (sm TransportMode) String() string { func (tm TransportMode) String() string {
switch sm { switch tm {
case TransportModePlay: case TransportModePlay:
return "play" return "play"
@@ -89,7 +89,7 @@ func parsePorts(val string) (*[2]int, error) {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val) 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) { func ReadTransport(v base.HeaderValue) (*Transport, error) {
if len(v) == 0 { if len(v) == 0 {
return nil, fmt.Errorf("value not provided") 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) return nil, fmt.Errorf("value provided multiple times (%v)", v)
} }
ht := &Transport{} h := &Transport{}
parts := strings.Split(v[0], ";") parts := strings.Split(v[0], ";")
if len(parts) == 0 { if len(parts) == 0 {
@@ -108,10 +108,10 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
switch parts[0] { switch parts[0] {
case "RTP/AVP", "RTP/AVP/UDP": case "RTP/AVP", "RTP/AVP/UDP":
ht.Protocol = base.StreamProtocolUDP h.Protocol = base.StreamProtocolUDP
case "RTP/AVP/TCP": case "RTP/AVP/TCP":
ht.Protocol = base.StreamProtocolTCP h.Protocol = base.StreamProtocolTCP
default: default:
return nil, fmt.Errorf("invalid protocol (%v)", v) return nil, fmt.Errorf("invalid protocol (%v)", v)
@@ -121,12 +121,12 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
switch parts[0] { switch parts[0] {
case "unicast": case "unicast":
v := base.StreamDeliveryUnicast v := base.StreamDeliveryUnicast
ht.Delivery = &v h.Delivery = &v
parts = parts[1:] parts = parts[1:]
case "multicast": case "multicast":
v := base.StreamDeliveryMulticast v := base.StreamDeliveryMulticast
ht.Delivery = &v h.Delivery = &v
parts = parts[1:] parts = parts[1:]
// cast is optional, do not return any error // cast is optional, do not return any error
@@ -135,7 +135,7 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
for _, t := range parts { for _, t := range parts {
if strings.HasPrefix(t, "destination=") { if strings.HasPrefix(t, "destination=") {
v := t[len("destination="):] v := t[len("destination="):]
ht.Destination = &v h.Destination = &v
} else if strings.HasPrefix(t, "ttl=") { } else if strings.HasPrefix(t, "ttl=") {
v, err := strconv.ParseUint(t[len("ttl="):], 10, 64) v, err := strconv.ParseUint(t[len("ttl="):], 10, 64)
@@ -143,35 +143,35 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
return nil, err return nil, err
} }
vu := uint(v) vu := uint(v)
ht.TTL = &vu h.TTL = &vu
} else if strings.HasPrefix(t, "port=") { } else if strings.HasPrefix(t, "port=") {
ports, err := parsePorts(t[len("port="):]) ports, err := parsePorts(t[len("port="):])
if err != nil { if err != nil {
return nil, err return nil, err
} }
ht.Ports = ports h.Ports = ports
} else if strings.HasPrefix(t, "client_port=") { } else if strings.HasPrefix(t, "client_port=") {
ports, err := parsePorts(t[len("client_port="):]) ports, err := parsePorts(t[len("client_port="):])
if err != nil { if err != nil {
return nil, err return nil, err
} }
ht.ClientPorts = ports h.ClientPorts = ports
} else if strings.HasPrefix(t, "server_port=") { } else if strings.HasPrefix(t, "server_port=") {
ports, err := parsePorts(t[len("server_port="):]) ports, err := parsePorts(t[len("server_port="):])
if err != nil { if err != nil {
return nil, err return nil, err
} }
ht.ServerPorts = ports h.ServerPorts = ports
} else if strings.HasPrefix(t, "interleaved=") { } else if strings.HasPrefix(t, "interleaved=") {
ports, err := parsePorts(t[len("interleaved="):]) ports, err := parsePorts(t[len("interleaved="):])
if err != nil { if err != nil {
return nil, err return nil, err
} }
ht.InterleavedIds = ports h.InterleavedIds = ports
} else if strings.HasPrefix(t, "mode=") { } else if strings.HasPrefix(t, "mode=") {
str := strings.ToLower(t[len("mode="):]) str := strings.ToLower(t[len("mode="):])
@@ -181,13 +181,13 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
switch str { switch str {
case "play": case "play":
v := TransportModePlay v := TransportModePlay
ht.Mode = &v h.Mode = &v
// receive is an old alias for record, used by ffmpeg with the // receive is an old alias for record, used by ffmpeg with the
// -listen flag, and by Darwin Streaming Server // -listen flag, and by Darwin Streaming Server
case "record", "receive": case "record", "receive":
v := TransportModeRecord v := TransportModeRecord
ht.Mode = &v h.Mode = &v
default: default:
return nil, fmt.Errorf("invalid transport mode: '%s'", str) return nil, fmt.Errorf("invalid transport mode: '%s'", str)
@@ -197,49 +197,49 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) {
// ignore non-standard keys // ignore non-standard keys
} }
return ht, nil return h, nil
} }
// Write encodes a Transport header // Write encodes a Transport header
func (ht Transport) Write() base.HeaderValue { func (h Transport) Write() base.HeaderValue {
var vals []string var rets []string
if ht.Protocol == base.StreamProtocolUDP { if h.Protocol == base.StreamProtocolUDP {
vals = append(vals, "RTP/AVP") rets = append(rets, "RTP/AVP")
} else { } else {
vals = append(vals, "RTP/AVP/TCP") rets = append(rets, "RTP/AVP/TCP")
} }
if ht.Delivery != nil { if h.Delivery != nil {
if *ht.Delivery == base.StreamDeliveryUnicast { if *h.Delivery == base.StreamDeliveryUnicast {
vals = append(vals, "unicast") rets = append(rets, "unicast")
} else { } else {
vals = append(vals, "multicast") rets = append(rets, "multicast")
} }
} }
if ht.ClientPorts != nil { if h.ClientPorts != nil {
ports := *ht.ClientPorts ports := *h.ClientPorts
vals = append(vals, "client_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) rets = append(rets, "client_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
} }
if ht.ServerPorts != nil { if h.ServerPorts != nil {
ports := *ht.ServerPorts ports := *h.ServerPorts
vals = append(vals, "server_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) rets = append(rets, "server_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
} }
if ht.InterleavedIds != nil { if h.InterleavedIds != nil {
ports := *ht.InterleavedIds ports := *h.InterleavedIds
vals = append(vals, "interleaved="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) rets = append(rets, "interleaved="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10))
} }
if ht.Mode != nil { if h.Mode != nil {
if *ht.Mode == TransportModePlay { if *h.Mode == TransportModePlay {
vals = append(vals, "mode=play") rets = append(rets, "mode=play")
} else { } else {
vals = append(vals, "mode=record") rets = append(rets, "mode=record")
} }
} }
return base.HeaderValue{strings.Join(vals, ";")} return base.HeaderValue{strings.Join(rets, ";")}
} }