mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +08:00
263 lines
5.0 KiB
Go
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, ";")}
|
|
}
|