From ccaccc51b46b7d3194b8637515d9acc7fe5ecfbb Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Mon, 13 Oct 2025 12:23:51 +0200 Subject: [PATCH] replace incoming absolute timestamps of any source (#5081) Absolute timestamps are used to generate recording segments, so users should not be able to change them, unless explicitly allowed. Parameter useAbsoluteTimestamp allowed to re-enable absolute timestamps with RTSP and WebRTC, now it is extended to all protocols. --- docs/2-usage/14-route-absolute-timestamps.md | 12 ++++++++++-- internal/protocols/hls/to_stream.go | 12 +++++++++--- internal/protocols/hls/to_stream_test.go | 8 ++++++-- internal/staticsources/hls/source.go | 2 +- mediamtx.yml | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/2-usage/14-route-absolute-timestamps.md b/docs/2-usage/14-route-absolute-timestamps.md index 35463602..adaca45f 100644 --- a/docs/2-usage/14-route-absolute-timestamps.md +++ b/docs/2-usage/14-route-absolute-timestamps.md @@ -3,8 +3,8 @@ Some streaming protocols allow to route absolute timestamps, associated with each frame, that are useful for synchronizing several video or data streams together. In particular, _MediaMTX_ supports receiving absolute timestamps with the following protocols and devices: - HLS -- RTSP (when the `useAbsoluteTimestamp` parameter is `true`) -- WebRTC (when the `useAbsoluteTimestamp` parameter is `true`) +- RTSP +- WebRTC - Raspberry Pi Camera and supports sending absolute timestamps with the following protocols: @@ -13,6 +13,14 @@ and supports sending absolute timestamps with the following protocols: - RTSP - WebRTC +By default, absolute timestamps of incoming frames are not used, instead they are replaced with the current timestamp. This prevents users from arbitrarily changing recording dates, and also allows to support sources that do not send absolute timestamps. It is possible to preserve original absolute timestamps by toggling the `useAbsoluteTimestamp` parameter: + +```yml +pathDefaults: + # Use absolute timestamp of frames, instead of replacing them with the current time. + useAbsoluteTimestamp: false +``` + ## Absolute timestamp in HLS In the HLS protocol, absolute timestamps are routed by adding a `EXT-X-PROGRAM-DATE-TIME` tag before each segment: diff --git a/internal/protocols/hls/to_stream.go b/internal/protocols/hls/to_stream.go index 919e6337..aa12027d 100644 --- a/internal/protocols/hls/to_stream.go +++ b/internal/protocols/hls/to_stream.go @@ -8,6 +8,7 @@ import ( "github.com/bluenviron/gohlslib/v2/pkg/codecs" "github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/format" + "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/ntpestimator" "github.com/bluenviron/mediamtx/internal/stream" @@ -20,7 +21,7 @@ const ( ntpStateInitial ntpState = iota ntpStateUnavailable ntpStateAvailable - ntpStateDegraded + ntpStateReplace ) func multiplyAndDivide(v, m, d int64) int64 { @@ -33,11 +34,16 @@ func multiplyAndDivide(v, m, d int64) int64 { func ToStream( c *gohlslib.Client, tracks []*gohlslib.Track, + pathConf *conf.Path, strm **stream.Stream, ) ([]*description.Media, error) { var ntpStat ntpState var ntpStatMutex sync.Mutex + if !pathConf.UseAbsoluteTimestamp { + ntpStat = ntpStateReplace + } + var medias []*description.Media //nolint:prealloc for _, track := range tracks { @@ -69,11 +75,11 @@ func ToStream( _, avail := c.AbsoluteTime(ctrack) if avail { (*strm).Parent.Log(logger.Warn, "absolute timestamp appeared after stream started, we are not using it") - ntpStat = ntpStateDegraded + ntpStat = ntpStateReplace } return ntpEstimator.Estimate(pts) - default: // ntpStateDegraded + default: // ntpStateReplace return ntpEstimator.Estimate(pts) } } diff --git a/internal/protocols/hls/to_stream_test.go b/internal/protocols/hls/to_stream_test.go index 73475cda..ee1b4a7d 100644 --- a/internal/protocols/hls/to_stream_test.go +++ b/internal/protocols/hls/to_stream_test.go @@ -11,6 +11,7 @@ import ( "github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/format" "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts" + "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/test" "github.com/bluenviron/mediamtx/internal/unit" @@ -18,7 +19,7 @@ import ( ) func TestToStreamNoSupportedCodecs(t *testing.T) { - _, err := ToStream(nil, []*gohlslib.Track{}, nil) + _, err := ToStream(nil, []*gohlslib.Track{}, &conf.Path{}, nil) require.Equal(t, ErrNoSupportedCodecs, err) } @@ -92,8 +93,11 @@ func TestToStream(t *testing.T) { c = &gohlslib.Client{ URI: "http://localhost:5781/stream.m3u8", OnTracks: func(tracks []*gohlslib.Track) error { - medias, err2 := ToStream(c, tracks, &strm) + medias, err2 := ToStream(c, tracks, &conf.Path{ + UseAbsoluteTimestamp: true, + }, &strm) require.NoError(t, err2) + require.Equal(t, []*description.Media{{ Type: description.MediaTypeVideo, Formats: []format.Format{&format.H264{ diff --git a/internal/staticsources/hls/source.go b/internal/staticsources/hls/source.go index 89d7f990..c9848739 100644 --- a/internal/staticsources/hls/source.go +++ b/internal/staticsources/hls/source.go @@ -94,7 +94,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { decodeErrors.Increase() }, OnTracks: func(tracks []*gohlslib.Track) error { - medias, err2 := hls.ToStream(c, tracks, &stream) + medias, err2 := hls.ToStream(c, tracks, params.Conf, &stream) if err2 != nil { return err2 } diff --git a/mediamtx.yml b/mediamtx.yml index f6275360..10917031 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -482,7 +482,7 @@ pathDefaults: # If the stream is not available, redirect readers to this path. # It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. fallback: - # Route original absolute timestamps of RTSP and WebRTC frames, instead of replacing them. + # Use absolute timestamp of frames, instead of replacing them with the current time. useAbsoluteTimestamp: false ###############################################