From 74eaa11d3a390d897aa84c1ef16d6999b83820fc Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Sat, 31 Jan 2026 16:21:53 +0100 Subject: [PATCH] rtsp: add rtspUDPSourcePortRange param (#5363) (#5397) --- api/openapi.yaml | 7 ++++++ docs/2-usage/02-publish.md | 2 ++ internal/conf/conf_test.go | 3 ++- internal/conf/env/env.go | 25 ++++++++++++++++++++++ internal/conf/path.go | 18 ++++++++++------ internal/staticsources/rtsp/source.go | 4 ++++ internal/staticsources/rtsp/source_test.go | 25 ++++++++++++---------- mediamtx.yml | 2 ++ 8 files changed, 67 insertions(+), 19 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 64a82f80..19dd07e4 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -416,6 +416,13 @@ components: type: string rtspRangeStart: type: string + rtspUDPSourcePortRange: + type: array + minItems: 2 + maxItems: 2 + items: + type: integer + format: uint64 # RTP source rtpSDP: diff --git a/docs/2-usage/02-publish.md b/docs/2-usage/02-publish.md index c63edf9f..7595960d 100644 --- a/docs/2-usage/02-publish.md +++ b/docs/2-usage/02-publish.md @@ -154,6 +154,8 @@ paths: # This can be increased to mitigate packet losses. # It defaults to the default value of the operating system. rtspUDPReadBufferSize: 0 + # Range of ports used as source port in outgoing UDP packets. + rtspUDPSourcePortRange: [10000, 65535] ``` All available parameters are listed in the [configuration file](/docs/references/configuration-file). diff --git a/internal/conf/conf_test.go b/internal/conf/conf_test.go index ff79dd00..975874ad 100644 --- a/internal/conf/conf_test.go +++ b/internal/conf/conf_test.go @@ -52,6 +52,7 @@ func TestConfFromFile(t *testing.T) { Source: "publisher", SourceOnDemandStartTimeout: 10 * Duration(time.Second), SourceOnDemandCloseAfter: 10 * Duration(time.Second), + OverridePublisher: true, AlwaysAvailableTracks: []AlwaysAvailableTrack{ {Codec: "H264"}, }, @@ -61,7 +62,7 @@ func TestConfFromFile(t *testing.T) { RecordMaxPartSize: 50 * 1024 * 1024, RecordSegmentDuration: 3600000000000, RecordDeleteAfter: 86400000000000, - OverridePublisher: true, + RTSPUDPSourcePortRange: []uint{10000, 65535}, RPICameraWidth: 1920, RPICameraHeight: 1080, RPICameraContrast: 1, diff --git a/internal/conf/env/env.go b/internal/conf/env/env.go index f1ac10d7..389a35e7 100644 --- a/internal/conf/env/env.go +++ b/internal/conf/env/env.go @@ -188,6 +188,31 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er } return nil + case rt.Elem() == reflect.TypeOf(uint(0)): + if ev, ok := env[prefix]; ok { + if ev == "" { + prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) + } else { + if prv.IsNil() { + prv.Set(reflect.New(rt)) + } + + raw := strings.Split(ev, ",") + vals := make([]uint, len(raw)) + + for i, v := range raw { + tmp, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return err + } + vals[i] = uint(tmp) + } + + prv.Elem().Set(reflect.ValueOf(vals)) + } + } + return nil + case rt.Elem() == reflect.TypeOf(float64(0)): if ev, ok := env[prefix]; ok { if ev == "" { diff --git a/internal/conf/path.go b/internal/conf/path.go index 9042fb9c..1de38b19 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -180,13 +180,14 @@ type Path struct { SRTPublishPassphrase string `json:"srtPublishPassphrase"` // RTSP source - 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"` - RTSPUDPReadBufferSize *uint `json:"rtspUDPReadBufferSize,omitempty"` // deprecated + 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"` + RTSPUDPReadBufferSize *uint `json:"rtspUDPReadBufferSize,omitempty"` // deprecated + RTSPUDPSourcePortRange []uint `json:"rtspUDPSourcePortRange"` // MPEG-TS source MPEGTSUDPReadBufferSize *uint `json:"mpegtsUDPReadBufferSize,omitempty"` // deprecated @@ -287,6 +288,9 @@ func (pconf *Path) setDefaults() { // Publisher source pconf.OverridePublisher = true + // RTSP source + pconf.RTSPUDPSourcePortRange = []uint{10000, 65535} + // Raspberry Pi Camera source pconf.RPICameraWidth = 1920 pconf.RPICameraHeight = 1080 diff --git a/internal/staticsources/rtsp/source.go b/internal/staticsources/rtsp/source.go index c5f75036..cd2fa1e4 100644 --- a/internal/staticsources/rtsp/source.go +++ b/internal/staticsources/rtsp/source.go @@ -163,6 +163,10 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { WriteQueueSize: s.WriteQueueSize, UDPReadBufferSize: int(udpReadBufferSize), AnyPortEnable: params.Conf.RTSPAnyPort, + UDPSourcePortRange: [2]uint16{ + uint16(params.Conf.RTSPUDPSourcePortRange[0]), + uint16(params.Conf.RTSPUDPSourcePortRange[1]), + }, OnRequest: func(req *base.Request) { s.Log(logger.Debug, "[c->s] %v", req) }, diff --git a/internal/staticsources/rtsp/source_test.go b/internal/staticsources/rtsp/source_test.go index d7242f74..e26bbf9f 100644 --- a/internal/staticsources/rtsp/source_test.go +++ b/internal/staticsources/rtsp/source_test.go @@ -141,20 +141,18 @@ func TestSource(t *testing.T) { defer strm.Close() var ur string - var cnf *conf.Path + cnf := &conf.Path{ + RTSPUDPSourcePortRange: []uint{10000, 65535}, + } if source != "tls" { ur = "rtsp://testuser:testpass@localhost:8555/teststream" var sp conf.RTSPTransport sp.UnmarshalJSON([]byte(`"` + source + `"`)) //nolint:errcheck - cnf = &conf.Path{ - RTSPTransport: sp, - } + cnf.RTSPTransport = sp } else { ur = "rtsps://testuser:testpass@localhost:8555/teststream" - cnf = &conf.Path{ - SourceFingerprint: "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739", - } + cnf.SourceFingerprint = "33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739" } p := &test.StaticSourceParent{} @@ -286,7 +284,8 @@ func TestNoPassword(t *testing.T) { Context: ctx, ResolvedSource: "rtsp://testuser:@127.0.0.1:8555/teststream", Conf: &conf.Path{ - RTSPTransport: sp, + RTSPTransport: sp, + RTSPUDPSourcePortRange: []uint{10000, 65535}, }, }) close(done) @@ -362,7 +361,9 @@ func TestRange(t *testing.T) { require.NoError(t, err) defer strm.Close() - cnf := &conf.Path{} + cnf := &conf.Path{ + RTSPUDPSourcePortRange: []uint{10000, 65535}, + } switch ca { case "clock": @@ -496,7 +497,8 @@ func TestSkipBackChannel(t *testing.T) { Context: ctx, ResolvedSource: "rtsp://127.0.0.1:8555/teststream", Conf: &conf.Path{ - RTSPTransport: conf.RTSPTransport{Protocol: ptrOf(gortsplib.ProtocolTCP)}, + RTSPTransport: conf.RTSPTransport{Protocol: ptrOf(gortsplib.ProtocolTCP)}, + RTSPUDPSourcePortRange: []uint{10000, 65535}, }, }) close(done) @@ -571,7 +573,8 @@ func TestOnlyBackChannelsError(t *testing.T) { Context: ctx, ResolvedSource: "rtsp://127.0.0.1:8555/teststream", Conf: &conf.Path{ - RTSPTransport: conf.RTSPTransport{Protocol: ptrOf(gortsplib.ProtocolTCP)}, + RTSPTransport: conf.RTSPTransport{Protocol: ptrOf(gortsplib.ProtocolTCP)}, + RTSPUDPSourcePortRange: []uint{10000, 65535}, }, }) diff --git a/mediamtx.yml b/mediamtx.yml index cdfeccc9..986ab479 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -553,6 +553,8 @@ pathDefaults: # * 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" rtspRangeStart: + # Range of ports used as source port in outgoing UDP packets. + rtspUDPSourcePortRange: [10000, 65535] ############################################### # Default path settings -> RTP source (when source is RTP)