Browse Source

rename sourceProtocol into rtspTransport, sourceAnyPortEnable into rtspAnyPort (#2644)

pull/2645/head
Alessandro Ros 2 years ago committed by GitHub
parent
commit
1d1d64cb89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 4
      apidocs/openapi.yaml
  3. 2
      bench/proxy/start.sh
  4. 190
      internal/conf/path.go
  5. 10
      internal/conf/rtsp_transport.go
  6. 4
      internal/staticsources/rtsp/source.go
  7. 12
      internal/staticsources/rtsp/source_test.go
  8. 16
      mediamtx.yml
  9. 2
      scripts/run.mk

2
README.md

@ -1546,7 +1546,7 @@ In some scenarios, when publishing or reading from the server with RTSP, frames @@ -1546,7 +1546,7 @@ In some scenarios, when publishing or reading from the server with RTSP, frames
paths:
test:
source: rtsp://..
sourceProtocol: tcp
rtspTransport: tcp
```
* The stream throughput is too big to be handled by the network between server and readers. Upgrade the network or decrease the stream bitrate by re-encoding it.

4
apidocs/openapi.yaml

@ -252,9 +252,9 @@ components: @@ -252,9 +252,9 @@ components:
type: string
# RTSP source
sourceProtocol:
rtspTransport:
type: string
sourceAnyPortEnable:
rtspAnyPort:
type: boolean
rtspRangeType:
type: string

2
bench/proxy/start.sh

@ -40,7 +40,7 @@ CONF="${CONF}paths:\n" @@ -40,7 +40,7 @@ CONF="${CONF}paths:\n"
for i in $(seq 1 $PROXY_COUNT); do
CONF="${CONF} proxy$i:\n"
CONF="${CONF} source: rtsp://localhost:8555/source\n"
CONF="${CONF} sourceProtocol: $PROXY_PROTOCOL\n"
CONF="${CONF} rtspTransport: $PROXY_PROTOCOL\n"
done
echo -e "$CONF" > /proxy.conf

190
internal/conf/path.go

@ -84,8 +84,10 @@ type Path struct { @@ -84,8 +84,10 @@ type Path struct {
SRTPublishPassphrase string `json:"srtPublishPassphrase"`
// RTSP source
SourceProtocol SourceProtocol `json:"sourceProtocol"`
SourceAnyPortEnable bool `json:"sourceAnyPortEnable"`
RTSPTransport RTSPTransport `json:"rtspTransport"`
RTSPAnyPort bool `json:"rtspAnyPort"`
SourceProtocol *RTSPTransport `json:"sourceProtocol,omitempty"` // deprecated
SourceAnyPortEnable *bool `json:"sourceAnyPortEnable,omitempty"` // deprecated
RTSPRangeType RTSPRangeType `json:"rtspRangeType"`
RTSPRangeStart string `json:"rtspRangeStart"`
@ -322,14 +324,6 @@ func (pconf *Path) check(conf *Conf, name string) error { @@ -322,14 +324,6 @@ func (pconf *Path) check(conf *Conf, name string) error {
}
case pconf.Source == "redirect":
if pconf.SourceRedirect == "" {
return fmt.Errorf("source redirect must be filled")
}
_, err := url.Parse(pconf.SourceRedirect)
if err != nil {
return fmt.Errorf("'%s' is not a valid RTSP URL", pconf.SourceRedirect)
}
case pconf.Source == "rpiCamera":
if pconf.Regexp != nil {
@ -337,60 +331,9 @@ func (pconf *Path) check(conf *Conf, name string) error { @@ -337,60 +331,9 @@ func (pconf *Path) check(conf *Conf, name string) error {
"a path with a regular expression (or path 'all') cannot have 'rpiCamera' as source. use another path")
}
for otherName, otherPath := range conf.Paths {
if otherPath != pconf && otherPath != nil &&
otherPath.Source == "rpiCamera" && otherPath.RPICameraCamID == pconf.RPICameraCamID {
return fmt.Errorf("'rpiCamera' with same camera ID %d is used as source in two paths, '%s' and '%s'",
pconf.RPICameraCamID, name, otherName)
}
}
switch pconf.RPICameraExposure {
case "normal", "short", "long", "custom":
default:
return fmt.Errorf("invalid 'rpiCameraExposure' value")
}
switch pconf.RPICameraAWB {
case "auto", "incandescent", "tungsten", "fluorescent", "indoor", "daylight", "cloudy", "custom":
default:
return fmt.Errorf("invalid 'rpiCameraAWB' value")
}
switch pconf.RPICameraDenoise {
case "off", "cdn_off", "cdn_fast", "cdn_hq":
default:
return fmt.Errorf("invalid 'rpiCameraDenoise' value")
}
switch pconf.RPICameraMetering {
case "centre", "spot", "matrix", "custom":
default:
return fmt.Errorf("invalid 'rpiCameraMetering' value")
}
switch pconf.RPICameraAfMode {
case "auto", "manual", "continuous":
default:
return fmt.Errorf("invalid 'rpiCameraAfMode' value")
}
switch pconf.RPICameraAfRange {
case "normal", "macro", "full":
default:
return fmt.Errorf("invalid 'rpiCameraAfRange' value")
}
switch pconf.RPICameraAfSpeed {
case "normal", "fast":
default:
return fmt.Errorf("invalid 'rpiCameraAfSpeed' value")
}
default:
return fmt.Errorf("invalid source: '%s'", pconf.Source)
}
if pconf.SourceOnDemand {
if pconf.Source == "publisher" {
return fmt.Errorf("'sourceOnDemand' is useless when source is 'publisher'")
@ -403,7 +346,42 @@ func (pconf *Path) check(conf *Conf, name string) error { @@ -403,7 +346,42 @@ func (pconf *Path) check(conf *Conf, name string) error {
}
}
// Publisher
// Authentication
if (pconf.PublishUser != "" && pconf.PublishPass == "") ||
(pconf.PublishUser == "" && pconf.PublishPass != "") {
return fmt.Errorf("read username and password must be both filled")
}
if pconf.PublishUser != "" && pconf.Source != "publisher" {
return fmt.Errorf("'publishUser' is useless when source is not 'publisher', since " +
"the stream is not provided by a publisher, but by a fixed source")
}
if len(pconf.PublishIPs) > 0 && pconf.Source != "publisher" {
return fmt.Errorf("'publishIPs' is useless when source is not 'publisher', since " +
"the stream is not provided by a publisher, but by a fixed source")
}
if (pconf.ReadUser != "" && pconf.ReadPass == "") ||
(pconf.ReadUser == "" && pconf.ReadPass != "") {
return fmt.Errorf("read username and password must be both filled")
}
if contains(conf.AuthMethods, headers.AuthDigest) {
if strings.HasPrefix(string(pconf.PublishUser), "sha256:") ||
strings.HasPrefix(string(pconf.PublishPass), "sha256:") ||
strings.HasPrefix(string(pconf.ReadUser), "sha256:") ||
strings.HasPrefix(string(pconf.ReadPass), "sha256:") {
return fmt.Errorf("hashed credentials can't be used when the digest auth method is available")
}
}
if conf.ExternalAuthenticationURL != "" {
if pconf.PublishUser != "" ||
len(pconf.PublishIPs) > 0 ||
pconf.ReadUser != "" ||
len(pconf.ReadIPs) > 0 {
return fmt.Errorf("credentials or IPs can't be used together with 'externalAuthenticationURL'")
}
}
// Publisher source
if pconf.DisablePublisherOverride != nil {
pconf.OverridePublisher = !*pconf.DisablePublisherOverride
@ -436,40 +414,74 @@ func (pconf *Path) check(conf *Conf, name string) error { @@ -436,40 +414,74 @@ func (pconf *Path) check(conf *Conf, name string) error {
}
}
// Authentication
// RTSP source
if (pconf.PublishUser != "" && pconf.PublishPass == "") ||
(pconf.PublishUser == "" && pconf.PublishPass != "") {
return fmt.Errorf("read username and password must be both filled")
}
if pconf.PublishUser != "" && pconf.Source != "publisher" {
return fmt.Errorf("'publishUser' is useless when source is not 'publisher', since " +
"the stream is not provided by a publisher, but by a fixed source")
}
if len(pconf.PublishIPs) > 0 && pconf.Source != "publisher" {
return fmt.Errorf("'publishIPs' is useless when source is not 'publisher', since " +
"the stream is not provided by a publisher, but by a fixed source")
if pconf.SourceProtocol != nil {
pconf.RTSPTransport = *pconf.SourceProtocol
}
if (pconf.ReadUser != "" && pconf.ReadPass == "") ||
(pconf.ReadUser == "" && pconf.ReadPass != "") {
return fmt.Errorf("read username and password must be both filled")
if pconf.SourceAnyPortEnable != nil {
pconf.RTSPAnyPort = *pconf.SourceAnyPortEnable
}
if contains(conf.AuthMethods, headers.AuthDigest) {
if strings.HasPrefix(string(pconf.PublishUser), "sha256:") ||
strings.HasPrefix(string(pconf.PublishPass), "sha256:") ||
strings.HasPrefix(string(pconf.ReadUser), "sha256:") ||
strings.HasPrefix(string(pconf.ReadPass), "sha256:") {
return fmt.Errorf("hashed credentials can't be used when the digest auth method is available")
// Redirect source
if pconf.Source == "redirect" {
if pconf.SourceRedirect == "" {
return fmt.Errorf("source redirect must be filled")
}
_, err := url.Parse(pconf.SourceRedirect)
if err != nil {
return fmt.Errorf("'%s' is not a valid RTSP URL", pconf.SourceRedirect)
}
}
if conf.ExternalAuthenticationURL != "" {
if pconf.PublishUser != "" ||
len(pconf.PublishIPs) > 0 ||
pconf.ReadUser != "" ||
len(pconf.ReadIPs) > 0 {
return fmt.Errorf("credentials or IPs can't be used together with 'externalAuthenticationURL'")
// Raspberry Pi Camera source
if pconf.Source == "rpiCamera" {
for otherName, otherPath := range conf.Paths {
if otherPath != pconf && otherPath != nil &&
otherPath.Source == "rpiCamera" && otherPath.RPICameraCamID == pconf.RPICameraCamID {
return fmt.Errorf("'rpiCamera' with same camera ID %d is used as source in two paths, '%s' and '%s'",
pconf.RPICameraCamID, name, otherName)
}
}
}
switch pconf.RPICameraExposure {
case "normal", "short", "long", "custom":
default:
return fmt.Errorf("invalid 'rpiCameraExposure' value")
}
switch pconf.RPICameraAWB {
case "auto", "incandescent", "tungsten", "fluorescent", "indoor", "daylight", "cloudy", "custom":
default:
return fmt.Errorf("invalid 'rpiCameraAWB' value")
}
switch pconf.RPICameraDenoise {
case "off", "cdn_off", "cdn_fast", "cdn_hq":
default:
return fmt.Errorf("invalid 'rpiCameraDenoise' value")
}
switch pconf.RPICameraMetering {
case "centre", "spot", "matrix", "custom":
default:
return fmt.Errorf("invalid 'rpiCameraMetering' value")
}
switch pconf.RPICameraAfMode {
case "auto", "manual", "continuous":
default:
return fmt.Errorf("invalid 'rpiCameraAfMode' value")
}
switch pconf.RPICameraAfRange {
case "normal", "macro", "full":
default:
return fmt.Errorf("invalid 'rpiCameraAfRange' value")
}
switch pconf.RPICameraAfSpeed {
case "normal", "fast":
default:
return fmt.Errorf("invalid 'rpiCameraAfSpeed' value")
}
// Hooks

10
internal/conf/source_protocol.go → internal/conf/rtsp_transport.go

@ -7,13 +7,13 @@ import ( @@ -7,13 +7,13 @@ import (
"github.com/bluenviron/gortsplib/v4"
)
// SourceProtocol is the sourceProtocol parameter.
type SourceProtocol struct {
// RTSPTransport is the rtspTransport parameter.
type RTSPTransport struct {
*gortsplib.Transport
}
// MarshalJSON implements json.Marshaler.
func (d SourceProtocol) MarshalJSON() ([]byte, error) {
func (d RTSPTransport) MarshalJSON() ([]byte, error) {
var out string
if d.Transport == nil {
@ -38,7 +38,7 @@ func (d SourceProtocol) MarshalJSON() ([]byte, error) { @@ -38,7 +38,7 @@ func (d SourceProtocol) MarshalJSON() ([]byte, error) {
}
// UnmarshalJSON implements json.Unmarshaler.
func (d *SourceProtocol) UnmarshalJSON(b []byte) error {
func (d *RTSPTransport) UnmarshalJSON(b []byte) error {
var in string
if err := json.Unmarshal(b, &in); err != nil {
return err
@ -68,6 +68,6 @@ func (d *SourceProtocol) UnmarshalJSON(b []byte) error { @@ -68,6 +68,6 @@ func (d *SourceProtocol) UnmarshalJSON(b []byte) error {
}
// UnmarshalEnv implements env.Unmarshaler.
func (d *SourceProtocol) UnmarshalEnv(_ string, v string) error {
func (d *RTSPTransport) UnmarshalEnv(_ string, v string) error {
return d.UnmarshalJSON([]byte(`"` + v + `"`))
}

4
internal/staticsources/rtsp/source.go

@ -81,12 +81,12 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { @@ -81,12 +81,12 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
decodeErrLogger := logger.NewLimitedLogger(s)
c := &gortsplib.Client{
Transport: params.Conf.SourceProtocol.Transport,
Transport: params.Conf.RTSPTransport.Transport,
TLSConfig: tls.ConfigForFingerprint(params.Conf.SourceFingerprint),
ReadTimeout: time.Duration(s.ReadTimeout),
WriteTimeout: time.Duration(s.WriteTimeout),
WriteQueueSize: s.WriteQueueSize,
AnyPortEnable: params.Conf.SourceAnyPortEnable,
AnyPortEnable: params.Conf.RTSPAnyPort,
OnRequest: func(req *base.Request) {
s.Log(logger.Debug, "[c->s] %v", req)
},

12
internal/staticsources/rtsp/source_test.go

@ -210,7 +210,7 @@ func TestRTSPSource(t *testing.T) { @@ -210,7 +210,7 @@ func TestRTSPSource(t *testing.T) {
var te *tester.Tester
if source != "tls" {
var sp conf.SourceProtocol
var sp conf.RTSPTransport
sp.UnmarshalJSON([]byte(`"` + source + `"`)) //nolint:errcheck
te = tester.New(
@ -223,8 +223,8 @@ func TestRTSPSource(t *testing.T) { @@ -223,8 +223,8 @@ func TestRTSPSource(t *testing.T) {
}
},
&conf.Path{
Source: "rtsp://testuser:testpass@localhost:8555/teststream",
SourceProtocol: sp,
Source: "rtsp://testuser:testpass@localhost:8555/teststream",
RTSPTransport: sp,
},
)
} else {
@ -312,7 +312,7 @@ func TestRTSPSourceNoPassword(t *testing.T) { @@ -312,7 +312,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
defer stream.Close()
var sp conf.SourceProtocol
var sp conf.RTSPTransport
sp.UnmarshalJSON([]byte(`"tcp"`)) //nolint:errcheck
te := tester.New(
@ -325,8 +325,8 @@ func TestRTSPSourceNoPassword(t *testing.T) { @@ -325,8 +325,8 @@ func TestRTSPSourceNoPassword(t *testing.T) {
}
},
&conf.Path{
Source: "rtsp://testuser:@127.0.0.1:8555/teststream",
SourceProtocol: sp,
Source: "rtsp://testuser:@127.0.0.1:8555/teststream",
RTSPTransport: sp,
},
)
defer te.Close()

16
mediamtx.yml

@ -337,9 +337,9 @@ pathDefaults: @@ -337,9 +337,9 @@ pathDefaults:
###############################################
# Default path settings -> Publisher source (when source is "publisher")
# allow another client to disconnect the current publisher and publish in its place.
# Allow another client to disconnect the current publisher and publish in its place.
overridePublisher: yes
# if no one is publishing, redirect readers to this path.
# If no one is publishing, redirect readers to this path.
# It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
fallback:
# SRT encryption passphrase required to publish to this path
@ -348,18 +348,18 @@ pathDefaults: @@ -348,18 +348,18 @@ pathDefaults:
###############################################
# Default path settings -> RTSP source (when source is a RTSP or a RTSPS URL)
# protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
sourceProtocol: automatic
# support sources that don't provide server ports or use random server ports. This is a security issue
# Transport protocol used to pull the stream. available values are "automatic", "udp", "multicast", "tcp".
rtspTransport: automatic
# Support sources that don't provide server ports or use random server ports. This is a security issue
# and must be used only when interacting with sources that require it.
sourceAnyPortEnable: no
# range header to send to the source, in order to start streaming from the specified offset.
rtspAnyPort: no
# Range header to send to the source, in order to start streaming from the specified offset.
# available values:
# * clock: Absolute time
# * npt: Normal Play Time
# * smpte: SMPTE timestamps relative to the start of the recording
rtspRangeType:
# available values:
# Available values:
# * clock: UTC ISO 8601 combined date and time string, e.g. 20230812T120000Z
# * npt: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"
# * smpte: duration such as "300ms", "1.5m" or "2h45m", valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h"

2
scripts/run.mk

@ -28,7 +28,7 @@ paths: @@ -28,7 +28,7 @@ paths:
# proxied:
# source: rtsp://192.168.2.198:554/stream
# sourceProtocol: tcp
# rtspTransport: tcp
# sourceOnDemand: yes
# runOnDemand: ffmpeg -i rtsp://192.168.2.198:554/stream -c copy -f rtsp rtsp://localhost:$$RTSP_PORT/proxied2

Loading…
Cancel
Save