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.
This commit is contained in:
Alessandro Ros 2025-10-13 12:23:51 +02:00 committed by GitHub
parent 75420552b9
commit ccaccc51b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 27 additions and 9 deletions

View file

@ -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:

View file

@ -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)
}
}

View file

@ -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{

View file

@ -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
}

View file

@ -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
###############################################