Files
gortsplib/pkg/headers/transport.go
2021-04-26 19:39:20 +02:00

263 lines
5.0 KiB
Go

package headers
import (
"fmt"
"strconv"
"strings"
"github.com/aler9/gortsplib/pkg/base"
)
// TransportMode is a transport mode.
type TransportMode int
const (
// TransportModePlay is the "play" transport mode
TransportModePlay TransportMode = iota
// TransportModeRecord is the "record" transport mode
TransportModeRecord
)
// String implements fmt.Stringer.
func (tm TransportMode) String() string {
switch tm {
case TransportModePlay:
return "play"
case TransportModeRecord:
return "record"
}
return "unknown"
}
// Transport is a Transport header.
type Transport struct {
// protocol of the stream
Protocol base.StreamProtocol
// (optional) delivery method of the stream
Delivery *base.StreamDelivery
// (optional) destination
Destination *string
// (optional) TTL
TTL *uint
// (optional) ports
Ports *[2]int
// (optional) client ports
ClientPorts *[2]int
// (optional) server ports
ServerPorts *[2]int
// (optional) interleaved frame ids
InterleavedIDs *[2]int
// (optional) mode
Mode *TransportMode
}
func parsePorts(val string) (*[2]int, error) {
ports := strings.Split(val, "-")
if len(ports) == 2 {
port1, err := strconv.ParseInt(ports[0], 10, 64)
if err != nil {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
port2, err := strconv.ParseInt(ports[1], 10, 64)
if err != nil {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
return &[2]int{int(port1), int(port2)}, nil
}
if len(ports) == 1 {
port1, err := strconv.ParseInt(ports[0], 10, 64)
if err != nil {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
return &[2]int{int(port1), int(port1 + 1)}, nil
}
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
// Read decodes a Transport header.
func (h *Transport) Read(v base.HeaderValue) error {
if len(v) == 0 {
return fmt.Errorf("value not provided")
}
if len(v) > 1 {
return fmt.Errorf("value provided multiple times (%v)", v)
}
v0 := v[0]
var part string
i := strings.IndexByte(v0, ';')
if i >= 0 {
part, v0 = v0[:i], v0[i+1:]
} else {
part, v0 = v0, ""
}
switch part {
case "RTP/AVP", "RTP/AVP/UDP":
h.Protocol = base.StreamProtocolUDP
case "RTP/AVP/TCP":
h.Protocol = base.StreamProtocolTCP
default:
return fmt.Errorf("invalid protocol (%v)", v0)
}
i = strings.IndexByte(v0, ';')
if i >= 0 {
part, v0 = v0[:i], v0[i+1:]
} else {
part, v0 = v0, ""
}
switch part {
case "unicast":
v := base.StreamDeliveryUnicast
h.Delivery = &v
case "multicast":
v := base.StreamDeliveryMulticast
h.Delivery = &v
default:
// delivery is optional, go back
v0 = part + ";" + v0
}
kvs, err := keyValParse(v0, ';')
if err != nil {
return err
}
for k, rv := range kvs {
v := rv
switch k {
case "destination":
h.Destination = &v
case "ttl":
tmp, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return err
}
vu := uint(tmp)
h.TTL = &vu
case "port":
ports, err := parsePorts(v)
if err != nil {
return err
}
h.Ports = ports
case "client_port":
ports, err := parsePorts(v)
if err != nil {
return err
}
h.ClientPorts = ports
case "server_port":
ports, err := parsePorts(v)
if err != nil {
return err
}
h.ServerPorts = ports
case "interleaved":
ports, err := parsePorts(v)
if err != nil {
return err
}
h.InterleavedIDs = ports
case "mode":
str := strings.ToLower(v)
str = strings.TrimPrefix(str, "\"")
str = strings.TrimSuffix(str, "\"")
switch str {
case "play":
v := TransportModePlay
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
h.Mode = &v
default:
return fmt.Errorf("invalid transport mode: '%s'", str)
}
default:
// ignore non-standard keys
}
}
return nil
}
// Write encodes a Transport header
func (h Transport) Write() base.HeaderValue {
var rets []string
if h.Protocol == base.StreamProtocolUDP {
rets = append(rets, "RTP/AVP")
} else {
rets = append(rets, "RTP/AVP/TCP")
}
if h.Delivery != nil {
if *h.Delivery == base.StreamDeliveryUnicast {
rets = append(rets, "unicast")
} else {
rets = append(rets, "multicast")
}
}
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 h.ServerPorts != nil {
ports := *h.ServerPorts
rets = append(rets, "server_port="+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 h.Mode != nil {
if *h.Mode == TransportModePlay {
rets = append(rets, "mode=play")
} else {
rets = append(rets, "mode=record")
}
}
return base.HeaderValue{strings.Join(rets, ";")}
}