mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 19:13:22 +08:00 
			
		
		
		
	route original timestamps without converting to durations (#3839)
This improves timestamp precision.
This commit is contained in:
		| @@ -32,8 +32,6 @@ Live streams can be published to the server with: | |||||||
| |[UDP/MPEG-TS](#udpmpeg-ts)|Unicast, broadcast, multicast|H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3| | |[UDP/MPEG-TS](#udpmpeg-ts)|Unicast, broadcast, multicast|H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3| | ||||||
| |[Raspberry Pi Cameras](#raspberry-pi-cameras)||H264|| | |[Raspberry Pi Cameras](#raspberry-pi-cameras)||H264|| | ||||||
|  |  | ||||||
| Instructions are provided for publishing through these protocols by using [FFmpeg](#ffmpeg), [GStreamer](#gstreamer), [OBS Studio](#obs-studio), [OpenCV](#opencv), [Unity](#unity), [Web browsers](#web-browsers). |  | ||||||
|  |  | ||||||
| Live streams can be read from the server with: | Live streams can be read from the server with: | ||||||
|  |  | ||||||
| |protocol|variants|video codecs|audio codecs| | |protocol|variants|video codecs|audio codecs| | ||||||
| @@ -44,8 +42,6 @@ Live streams can be read from the server with: | |||||||
| |[RTMP](#rtmp)|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)| | |[RTMP](#rtmp)|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)| | ||||||
| |[HLS](#hls)|Low-Latency HLS, MP4-based HLS, legacy HLS|AV1, VP9, H265, H264|Opus, MPEG-4 Audio (AAC)| | |[HLS](#hls)|Low-Latency HLS, MP4-based HLS, legacy HLS|AV1, VP9, H265, H264|Opus, MPEG-4 Audio (AAC)| | ||||||
|  |  | ||||||
| Instructions are provided for reading through these protocols by using [FFmpeg](#ffmpeg-1), [GStreamer](#gstreamer-1), [VLC](#vlc), [Unity](#unity-1), [Web browsers](#web-browsers-1). |  | ||||||
|  |  | ||||||
| Live streams be recorded and played back with: | Live streams be recorded and played back with: | ||||||
|  |  | ||||||
| |format|video codecs|audio codecs| | |format|video codecs|audio codecs| | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @@ -9,9 +9,9 @@ require ( | |||||||
| 	github.com/abema/go-mp4 v1.2.0 | 	github.com/abema/go-mp4 v1.2.0 | ||||||
| 	github.com/alecthomas/kong v1.2.1 | 	github.com/alecthomas/kong v1.2.1 | ||||||
| 	github.com/asticode/go-astits v1.13.0 | 	github.com/asticode/go-astits v1.13.0 | ||||||
| 	github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8 | 	github.com/bluenviron/gohlslib/v2 v2.0.0-20241007134735-ed88408cd4de | ||||||
| 	github.com/bluenviron/gortsplib/v4 v4.10.6 | 	github.com/bluenviron/gortsplib/v4 v4.10.7-0.20241007135843-2ca0bffa20a2 | ||||||
| 	github.com/bluenviron/mediacommon v1.12.4 | 	github.com/bluenviron/mediacommon v1.12.5-0.20241007134151-8883ba897cfc | ||||||
| 	github.com/datarhei/gosrt v0.7.0 | 	github.com/datarhei/gosrt v0.7.0 | ||||||
| 	github.com/fsnotify/fsnotify v1.7.0 | 	github.com/fsnotify/fsnotify v1.7.0 | ||||||
| 	github.com/gin-gonic/gin v1.10.0 | 	github.com/gin-gonic/gin v1.10.0 | ||||||
| @@ -87,7 +87,7 @@ require ( | |||||||
| 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect | 	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect | ||||||
| 	golang.org/x/arch v0.8.0 // indirect | 	golang.org/x/arch v0.8.0 // indirect | ||||||
| 	golang.org/x/mod v0.17.0 // indirect | 	golang.org/x/mod v0.17.0 // indirect | ||||||
| 	golang.org/x/net v0.29.0 // indirect | 	golang.org/x/net v0.30.0 // indirect | ||||||
| 	golang.org/x/sync v0.8.0 // indirect | 	golang.org/x/sync v0.8.0 // indirect | ||||||
| 	golang.org/x/text v0.19.0 // indirect | 	golang.org/x/text v0.19.0 // indirect | ||||||
| 	golang.org/x/time v0.5.0 // indirect | 	golang.org/x/time v0.5.0 // indirect | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								go.sum
									
									
									
									
									
								
							| @@ -31,12 +31,12 @@ github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwf | |||||||
| github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= | github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= | ||||||
| github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4= | github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4= | ||||||
| github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI= | github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI= | ||||||
| github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8 h1:OQeYfxJg5otVKa33HWJ63E+IxCJ5Ty0qwCBPD2JcIso= | github.com/bluenviron/gohlslib/v2 v2.0.0-20241007134735-ed88408cd4de h1:LPxwCQuLE89QMDfGw6qs6sfaRF1R6cmFVsh7uxcWR04= | ||||||
| github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8/go.mod h1:DVvQIj+MjYydWuYDCgP+s0/GplDgUSpDNXCA/BVLhu4= | github.com/bluenviron/gohlslib/v2 v2.0.0-20241007134735-ed88408cd4de/go.mod h1:MY9p+71KQFHNpEUzYATH9ZMVZfCkj6R+SaGFQEIEAS8= | ||||||
| github.com/bluenviron/gortsplib/v4 v4.10.6 h1:KMvVcU21xxQQu1Jqn6D/z/FoIMn+QEKE1dBDWt4aWvg= | github.com/bluenviron/gortsplib/v4 v4.10.7-0.20241007135843-2ca0bffa20a2 h1:oR8bRYadOQmb2wxBZau96zTsHocd+5vrQaM7B+sjRrk= | ||||||
| github.com/bluenviron/gortsplib/v4 v4.10.6/go.mod h1:/7C8qoGEsIQupuVw8YnXANpqBMNBpZ+51xFreLGiN2g= | github.com/bluenviron/gortsplib/v4 v4.10.7-0.20241007135843-2ca0bffa20a2/go.mod h1:QN1e+ueF5aa8LNQUaBUBbNWUEUUidl3toldYp7rZuhU= | ||||||
| github.com/bluenviron/mediacommon v1.12.4 h1:7VrA/W/iDB7VELquXqRjgjzUSJT3llZYgXjFN9WkByo= | github.com/bluenviron/mediacommon v1.12.5-0.20241007134151-8883ba897cfc h1:walYSlRh0oE5Vn+H8dHoZCAOX/XjPUhy9umlckpsn3k= | ||||||
| github.com/bluenviron/mediacommon v1.12.4/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec= | github.com/bluenviron/mediacommon v1.12.5-0.20241007134151-8883ba897cfc/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec= | ||||||
| github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= | github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= | ||||||
| github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= | ||||||
| github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= | ||||||
| @@ -292,8 +292,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= | |||||||
| golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= | golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= | ||||||
| golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= | golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= | ||||||
| golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= | ||||||
| golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= | golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= | ||||||
| golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= | golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= | ||||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
|   | |||||||
| @@ -34,7 +34,6 @@ func gitDescribeTags(repo *git.Repository) (string, error) { | |||||||
| 		} | 		} | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,24 +1,33 @@ | |||||||
| package formatprocessor | package formatprocessor | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/rand" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | func randUint32() (uint32, error) { | ||||||
|  | 	var b [4]byte | ||||||
|  | 	_, err := rand.Read(b[:]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil | ||||||
|  | } | ||||||
|  |  | ||||||
| type formatProcessorAC3 struct { | type formatProcessorAC3 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.AC3 | 	format            *format.AC3 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpac3.Encoder | 	encoder           *rtpac3.Encoder | ||||||
| 	decoder           *rtpac3.Decoder | 	decoder           *rtpac3.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newAC3( | func newAC3( | ||||||
| @@ -37,10 +46,7 @@ func newAC3( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -65,9 +71,8 @@ func (t *formatProcessorAC3) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -76,7 +81,7 @@ func (t *formatProcessorAC3) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorAC3) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorAC3) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.AC3{ | 	u := &unit.AC3{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -23,9 +22,9 @@ var ( | |||||||
| type formatProcessorAV1 struct { | type formatProcessorAV1 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.AV1 | 	format            *format.AV1 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpav1.Encoder | 	encoder           *rtpav1.Encoder | ||||||
| 	decoder           *rtpav1.Decoder | 	decoder           *rtpav1.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newAV1( | func newAV1( | ||||||
| @@ -44,10 +43,7 @@ func newAV1( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -73,9 +69,8 @@ func (t *formatProcessorAV1) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -84,7 +79,7 @@ func (t *formatProcessorAV1) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorAV1) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorAV1) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.AV1{ | 	u := &unit.AV1{ | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -15,9 +14,9 @@ import ( | |||||||
| type formatProcessorG711 struct { | type formatProcessorG711 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.G711 | 	format            *format.G711 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtplpcm.Encoder | 	encoder           *rtplpcm.Encoder | ||||||
| 	decoder           *rtplpcm.Decoder | 	decoder           *rtplpcm.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newG711( | func newG711( | ||||||
| @@ -36,10 +35,7 @@ func newG711( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -67,9 +63,8 @@ func (t *formatProcessorG711) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -78,7 +73,7 @@ func (t *formatProcessorG711) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorG711) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorG711) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.G711{ | 	u := &unit.G711{ | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ func (t *formatProcessorGeneric) ProcessUnit(_ unit.Unit) error { | |||||||
| func (t *formatProcessorGeneric) ProcessRTPPacket( | func (t *formatProcessorGeneric) ProcessRTPPacket( | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	_ bool, | 	_ bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.Generic{ | 	u := &unit.Generic{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/bluenviron/mediacommon/pkg/codecs/h264" | 	"github.com/bluenviron/mediacommon/pkg/codecs/h264" | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| @@ -85,9 +84,9 @@ func rtpH264ExtractParams(payload []byte) ([]byte, []byte) { | |||||||
| type formatProcessorH264 struct { | type formatProcessorH264 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.H264 | 	format            *format.H264 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtph264.Encoder | 	encoder           *rtph264.Encoder | ||||||
| 	decoder           *rtph264.Decoder | 	decoder           *rtph264.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newH264( | func newH264( | ||||||
| @@ -106,10 +105,7 @@ func newH264( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -246,9 +242,8 @@ func (t *formatProcessorH264) ProcessUnit(uu unit.Unit) error { | |||||||
| 		} | 		} | ||||||
| 		u.RTPPackets = pkts | 		u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 		ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 		for _, pkt := range u.RTPPackets { | 		for _, pkt := range u.RTPPackets { | ||||||
| 			pkt.Timestamp += ts | 			pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -258,7 +253,7 @@ func (t *formatProcessorH264) ProcessUnit(uu unit.Unit) error { | |||||||
| func (t *formatProcessorH264) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorH264) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.H264{ | 	u := &unit.H264{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtph265" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtph265" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/bluenviron/mediacommon/pkg/codecs/h265" | 	"github.com/bluenviron/mediacommon/pkg/codecs/h265" | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| @@ -105,9 +104,9 @@ func rtpH265ExtractParams(payload []byte) ([]byte, []byte, []byte) { | |||||||
| type formatProcessorH265 struct { | type formatProcessorH265 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.H265 | 	format            *format.H265 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtph265.Encoder | 	encoder           *rtph265.Encoder | ||||||
| 	decoder           *rtph265.Decoder | 	decoder           *rtph265.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newH265( | func newH265( | ||||||
| @@ -126,10 +125,7 @@ func newH265( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -278,9 +274,8 @@ func (t *formatProcessorH265) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 		} | 		} | ||||||
| 		u.RTPPackets = pkts | 		u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 		ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 		for _, pkt := range u.RTPPackets { | 		for _, pkt := range u.RTPPackets { | ||||||
| 			pkt.Timestamp += ts | 			pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -290,7 +285,7 @@ func (t *formatProcessorH265) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorH265) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorH265) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.H265{ | 	u := &unit.H265{ | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -15,9 +14,9 @@ import ( | |||||||
| type formatProcessorLPCM struct { | type formatProcessorLPCM struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.LPCM | 	format            *format.LPCM | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtplpcm.Encoder | 	encoder           *rtplpcm.Encoder | ||||||
| 	decoder           *rtplpcm.Decoder | 	decoder           *rtplpcm.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newLPCM( | func newLPCM( | ||||||
| @@ -36,10 +35,7 @@ func newLPCM( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -67,9 +63,8 @@ func (t *formatProcessorLPCM) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -78,7 +73,7 @@ func (t *formatProcessorLPCM) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorLPCM) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorLPCM) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.LPCM{ | 	u := &unit.LPCM{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -16,9 +15,9 @@ import ( | |||||||
| type formatProcessorMJPEG struct { | type formatProcessorMJPEG struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.MJPEG | 	format            *format.MJPEG | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpmjpeg.Encoder | 	encoder           *rtpmjpeg.Encoder | ||||||
| 	decoder           *rtpmjpeg.Decoder | 	decoder           *rtpmjpeg.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newMJPEG( | func newMJPEG( | ||||||
| @@ -37,10 +36,7 @@ func newMJPEG( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -66,9 +62,8 @@ func (t *formatProcessorMJPEG) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -77,7 +72,7 @@ func (t *formatProcessorMJPEG) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorMJPEG) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorMJPEG) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.MJPEG{ | 	u := &unit.MJPEG{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -16,9 +15,9 @@ import ( | |||||||
| type formatProcessorMPEG1Audio struct { | type formatProcessorMPEG1Audio struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.MPEG1Audio | 	format            *format.MPEG1Audio | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpmpeg1audio.Encoder | 	encoder           *rtpmpeg1audio.Encoder | ||||||
| 	decoder           *rtpmpeg1audio.Decoder | 	decoder           *rtpmpeg1audio.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newMPEG1Audio( | func newMPEG1Audio( | ||||||
| @@ -37,10 +36,7 @@ func newMPEG1Audio( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -65,9 +61,8 @@ func (t *formatProcessorMPEG1Audio) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -76,7 +71,7 @@ func (t *formatProcessorMPEG1Audio) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| func (t *formatProcessorMPEG1Audio) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorMPEG1Audio) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.MPEG1Audio{ | 	u := &unit.MPEG1Audio{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -25,9 +24,9 @@ var ( | |||||||
| type formatProcessorMPEG1Video struct { | type formatProcessorMPEG1Video struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.MPEG1Video | 	format            *format.MPEG1Video | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpmpeg1video.Encoder | 	encoder           *rtpmpeg1video.Encoder | ||||||
| 	decoder           *rtpmpeg1video.Decoder | 	decoder           *rtpmpeg1video.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newMPEG1Video( | func newMPEG1Video( | ||||||
| @@ -46,10 +45,7 @@ func newMPEG1Video( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -75,9 +71,8 @@ func (t *formatProcessorMPEG1Video) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -86,7 +81,7 @@ func (t *formatProcessorMPEG1Video) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| func (t *formatProcessorMPEG1Video) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorMPEG1Video) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.MPEG1Video{ | 	u := &unit.MPEG1Video{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -16,9 +15,9 @@ import ( | |||||||
| type formatProcessorMPEG4Audio struct { | type formatProcessorMPEG4Audio struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.MPEG4Audio | 	format            *format.MPEG4Audio | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpmpeg4audio.Encoder | 	encoder           *rtpmpeg4audio.Encoder | ||||||
| 	decoder           *rtpmpeg4audio.Decoder | 	decoder           *rtpmpeg4audio.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newMPEG4Audio( | func newMPEG4Audio( | ||||||
| @@ -37,10 +36,7 @@ func newMPEG4Audio( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -69,9 +65,8 @@ func (t *formatProcessorMPEG4Audio) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -80,7 +75,7 @@ func (t *formatProcessorMPEG4Audio) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| func (t *formatProcessorMPEG4Audio) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorMPEG4Audio) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.MPEG4Audio{ | 	u := &unit.MPEG4Audio{ | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4video" | 	"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4video" | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| @@ -30,9 +29,9 @@ var ( | |||||||
| type formatProcessorMPEG4Video struct { | type formatProcessorMPEG4Video struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.MPEG4Video | 	format            *format.MPEG4Video | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpmpeg4video.Encoder | 	encoder           *rtpmpeg4video.Encoder | ||||||
| 	decoder           *rtpmpeg4video.Decoder | 	decoder           *rtpmpeg4video.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newMPEG4Video( | func newMPEG4Video( | ||||||
| @@ -51,10 +50,7 @@ func newMPEG4Video( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -115,9 +111,8 @@ func (t *formatProcessorMPEG4Video) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 		for _, pkt := range u.RTPPackets { | 		for _, pkt := range u.RTPPackets { | ||||||
| 			pkt.Timestamp += ts | 			pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		u.RTPPackets = pkts | 		u.RTPPackets = pkts | ||||||
| @@ -129,7 +124,7 @@ func (t *formatProcessorMPEG4Video) ProcessUnit(uu unit.Unit) error { //nolint:d | |||||||
| func (t *formatProcessorMPEG4Video) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorMPEG4Video) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.MPEG4Video{ | 	u := &unit.MPEG4Video{ | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/bluenviron/mediacommon/pkg/codecs/opus" | 	"github.com/bluenviron/mediacommon/pkg/codecs/opus" | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| @@ -16,9 +15,9 @@ import ( | |||||||
| type formatProcessorOpus struct { | type formatProcessorOpus struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.Opus | 	format            *format.Opus | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpsimpleaudio.Encoder | 	encoder           *rtpsimpleaudio.Encoder | ||||||
| 	decoder           *rtpsimpleaudio.Decoder | 	decoder           *rtpsimpleaudio.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newOpus( | func newOpus( | ||||||
| @@ -37,10 +36,7 @@ func newOpus( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -69,11 +65,10 @@ func (t *formatProcessorOpus) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ts := t.timeEncoder.Encode(pts) | 		pkt.Timestamp += t.randomStart + uint32(pts) | ||||||
| 		pkt.Timestamp += ts |  | ||||||
|  |  | ||||||
| 		rtpPackets = append(rtpPackets, pkt) | 		rtpPackets = append(rtpPackets, pkt) | ||||||
| 		pts += opus.PacketDuration(packet) | 		pts += int64(opus.PacketDuration(packet)) * int64(t.format.ClockRate()) / int64(time.Second) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	u.RTPPackets = rtpPackets | 	u.RTPPackets = rtpPackets | ||||||
| @@ -84,7 +79,7 @@ func (t *formatProcessorOpus) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorOpus) ProcessRTPPacket( | func (t *formatProcessorOpus) ProcessRTPPacket( | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.Opus{ | 	u := &unit.Opus{ | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ type Processor interface { | |||||||
| 	ProcessRTPPacket( | 	ProcessRTPPacket( | ||||||
| 		pkt *rtp.Packet, | 		pkt *rtp.Packet, | ||||||
| 		ntp time.Time, | 		ntp time.Time, | ||||||
| 		pts time.Duration, | 		pts int64, | ||||||
| 		hasNonRTSPReaders bool, | 		hasNonRTSPReaders bool, | ||||||
| 	) (unit.Unit, error) | 	) (unit.Unit, error) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -16,9 +15,9 @@ import ( | |||||||
| type formatProcessorVP8 struct { | type formatProcessorVP8 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.VP8 | 	format            *format.VP8 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpvp8.Encoder | 	encoder           *rtpvp8.Encoder | ||||||
| 	decoder           *rtpvp8.Decoder | 	decoder           *rtpvp8.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newVP8( | func newVP8( | ||||||
| @@ -37,10 +36,7 @@ func newVP8( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -66,9 +62,8 @@ func (t *formatProcessorVP8) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -77,7 +72,7 @@ func (t *formatProcessorVP8) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorVP8) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorVP8) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.VP8{ | 	u := &unit.VP8{ | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtptime" |  | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| @@ -16,9 +15,9 @@ import ( | |||||||
| type formatProcessorVP9 struct { | type formatProcessorVP9 struct { | ||||||
| 	udpMaxPayloadSize int | 	udpMaxPayloadSize int | ||||||
| 	format            *format.VP9 | 	format            *format.VP9 | ||||||
| 	timeEncoder       *rtptime.Encoder |  | ||||||
| 	encoder           *rtpvp9.Encoder | 	encoder           *rtpvp9.Encoder | ||||||
| 	decoder           *rtpvp9.Decoder | 	decoder           *rtpvp9.Decoder | ||||||
|  | 	randomStart       uint32 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newVP9( | func newVP9( | ||||||
| @@ -37,10 +36,7 @@ func newVP9( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		t.timeEncoder = &rtptime.Encoder{ | 		t.randomStart, err = randUint32() | ||||||
| 			ClockRate: forma.ClockRate(), |  | ||||||
| 		} |  | ||||||
| 		err = t.timeEncoder.Initialize() |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -66,9 +62,8 @@ func (t *formatProcessorVP9) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| 	} | 	} | ||||||
| 	u.RTPPackets = pkts | 	u.RTPPackets = pkts | ||||||
|  |  | ||||||
| 	ts := t.timeEncoder.Encode(u.PTS) |  | ||||||
| 	for _, pkt := range u.RTPPackets { | 	for _, pkt := range u.RTPPackets { | ||||||
| 		pkt.Timestamp += ts | 		pkt.Timestamp += t.randomStart + uint32(u.PTS) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -77,7 +72,7 @@ func (t *formatProcessorVP9) ProcessUnit(uu unit.Unit) error { //nolint:dupl | |||||||
| func (t *formatProcessorVP9) ProcessRTPPacket( //nolint:dupl | func (t *formatProcessorVP9) ProcessRTPPacket( //nolint:dupl | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| 	hasNonRTSPReaders bool, | 	hasNonRTSPReaders bool, | ||||||
| ) (unit.Unit, error) { | ) (unit.Unit, error) { | ||||||
| 	u := &unit.VP9{ | 	u := &unit.VP9{ | ||||||
|   | |||||||
| @@ -39,7 +39,10 @@ func setupVideoTrack( | |||||||
| 	videoMedia := strea.Desc().FindFormat(&videoFormatAV1) | 	videoMedia := strea.Desc().FindFormat(&videoFormatAV1) | ||||||
|  |  | ||||||
| 	if videoFormatAV1 != nil { | 	if videoFormatAV1 != nil { | ||||||
| 		track := &gohlslib.Track{Codec: &codecs.AV1{}} | 		track := &gohlslib.Track{ | ||||||
|  | 			Codec:     &codecs.AV1{}, | ||||||
|  | 			ClockRate: videoFormatAV1.ClockRate(), | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		addTrack( | 		addTrack( | ||||||
| 			videoMedia, | 			videoMedia, | ||||||
| @@ -52,7 +55,11 @@ func setupVideoTrack( | |||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				err := muxer.WriteAV1(track, tunit.NTP, tunit.PTS, tunit.TU) | 				err := muxer.WriteAV1( | ||||||
|  | 					track, | ||||||
|  | 					tunit.NTP, | ||||||
|  | 					tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate | ||||||
|  | 					tunit.TU) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return fmt.Errorf("muxer error: %w", err) | 					return fmt.Errorf("muxer error: %w", err) | ||||||
| 				} | 				} | ||||||
| @@ -67,7 +74,10 @@ func setupVideoTrack( | |||||||
| 	videoMedia = strea.Desc().FindFormat(&videoFormatVP9) | 	videoMedia = strea.Desc().FindFormat(&videoFormatVP9) | ||||||
|  |  | ||||||
| 	if videoFormatVP9 != nil { | 	if videoFormatVP9 != nil { | ||||||
| 		track := &gohlslib.Track{Codec: &codecs.VP9{}} | 		track := &gohlslib.Track{ | ||||||
|  | 			Codec:     &codecs.VP9{}, | ||||||
|  | 			ClockRate: videoFormatVP9.ClockRate(), | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		addTrack( | 		addTrack( | ||||||
| 			videoMedia, | 			videoMedia, | ||||||
| @@ -80,7 +90,11 @@ func setupVideoTrack( | |||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				err := muxer.WriteVP9(track, tunit.NTP, tunit.PTS, tunit.Frame) | 				err := muxer.WriteVP9( | ||||||
|  | 					track, | ||||||
|  | 					tunit.NTP, | ||||||
|  | 					tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate | ||||||
|  | 					tunit.Frame) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return fmt.Errorf("muxer error: %w", err) | 					return fmt.Errorf("muxer error: %w", err) | ||||||
| 				} | 				} | ||||||
| @@ -96,11 +110,14 @@ func setupVideoTrack( | |||||||
|  |  | ||||||
| 	if videoFormatH265 != nil { | 	if videoFormatH265 != nil { | ||||||
| 		vps, sps, pps := videoFormatH265.SafeParams() | 		vps, sps, pps := videoFormatH265.SafeParams() | ||||||
| 		track := &gohlslib.Track{Codec: &codecs.H265{ | 		track := &gohlslib.Track{ | ||||||
|  | 			Codec: &codecs.H265{ | ||||||
| 				VPS: vps, | 				VPS: vps, | ||||||
| 				SPS: sps, | 				SPS: sps, | ||||||
| 				PPS: pps, | 				PPS: pps, | ||||||
| 		}} | 			}, | ||||||
|  | 			ClockRate: videoFormatH265.ClockRate(), | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		addTrack( | 		addTrack( | ||||||
| 			videoMedia, | 			videoMedia, | ||||||
| @@ -113,7 +130,11 @@ func setupVideoTrack( | |||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				err := muxer.WriteH265(track, tunit.NTP, tunit.PTS, tunit.AU) | 				err := muxer.WriteH265( | ||||||
|  | 					track, | ||||||
|  | 					tunit.NTP, | ||||||
|  | 					tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate | ||||||
|  | 					tunit.AU) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return fmt.Errorf("muxer error: %w", err) | 					return fmt.Errorf("muxer error: %w", err) | ||||||
| 				} | 				} | ||||||
| @@ -129,10 +150,13 @@ func setupVideoTrack( | |||||||
|  |  | ||||||
| 	if videoFormatH264 != nil { | 	if videoFormatH264 != nil { | ||||||
| 		sps, pps := videoFormatH264.SafeParams() | 		sps, pps := videoFormatH264.SafeParams() | ||||||
| 		track := &gohlslib.Track{Codec: &codecs.H264{ | 		track := &gohlslib.Track{ | ||||||
|  | 			Codec: &codecs.H264{ | ||||||
| 				SPS: sps, | 				SPS: sps, | ||||||
| 				PPS: pps, | 				PPS: pps, | ||||||
| 		}} | 			}, | ||||||
|  | 			ClockRate: videoFormatH264.ClockRate(), | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		addTrack( | 		addTrack( | ||||||
| 			videoMedia, | 			videoMedia, | ||||||
| @@ -145,7 +169,11 @@ func setupVideoTrack( | |||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				err := muxer.WriteH264(track, tunit.NTP, tunit.PTS, tunit.AU) | 				err := muxer.WriteH264( | ||||||
|  | 					track, | ||||||
|  | 					tunit.NTP, | ||||||
|  | 					tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate | ||||||
|  | 					tunit.AU) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return fmt.Errorf("muxer error: %w", err) | 					return fmt.Errorf("muxer error: %w", err) | ||||||
| 				} | 				} | ||||||
| @@ -178,9 +206,12 @@ func setupAudioTracks( | |||||||
| 		for _, forma := range media.Formats { | 		for _, forma := range media.Formats { | ||||||
| 			switch forma := forma.(type) { | 			switch forma := forma.(type) { | ||||||
| 			case *format.Opus: | 			case *format.Opus: | ||||||
| 				track := &gohlslib.Track{Codec: &codecs.Opus{ | 				track := &gohlslib.Track{ | ||||||
|  | 					Codec: &codecs.Opus{ | ||||||
| 						ChannelCount: forma.ChannelCount, | 						ChannelCount: forma.ChannelCount, | ||||||
| 				}} | 					}, | ||||||
|  | 					ClockRate: forma.ClockRate(), | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				addTrack( | 				addTrack( | ||||||
| 					media, | 					media, | ||||||
| @@ -192,7 +223,7 @@ func setupAudioTracks( | |||||||
| 						err := muxer.WriteOpus( | 						err := muxer.WriteOpus( | ||||||
| 							track, | 							track, | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							tunit.PTS, | 							tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate | ||||||
| 							tunit.Packets) | 							tunit.Packets) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return fmt.Errorf("muxer error: %w", err) | 							return fmt.Errorf("muxer error: %w", err) | ||||||
| @@ -204,9 +235,12 @@ func setupAudioTracks( | |||||||
| 			case *format.MPEG4Audio: | 			case *format.MPEG4Audio: | ||||||
| 				co := forma.GetConfig() | 				co := forma.GetConfig() | ||||||
| 				if co != nil { | 				if co != nil { | ||||||
| 					track := &gohlslib.Track{Codec: &codecs.MPEG4Audio{ | 					track := &gohlslib.Track{ | ||||||
|  | 						Codec: &codecs.MPEG4Audio{ | ||||||
| 							Config: *co, | 							Config: *co, | ||||||
| 					}} | 						}, | ||||||
|  | 						ClockRate: forma.ClockRate(), | ||||||
|  | 					} | ||||||
|  |  | ||||||
| 					addTrack( | 					addTrack( | ||||||
| 						media, | 						media, | ||||||
| @@ -222,7 +256,7 @@ func setupAudioTracks( | |||||||
| 							err := muxer.WriteMPEG4Audio( | 							err := muxer.WriteMPEG4Audio( | ||||||
| 								track, | 								track, | ||||||
| 								tunit.NTP, | 								tunit.NTP, | ||||||
| 								tunit.PTS, | 								tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate | ||||||
| 								tunit.AUs) | 								tunit.AUs) | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								return fmt.Errorf("muxer error: %w", err) | 								return fmt.Errorf("muxer error: %w", err) | ||||||
|   | |||||||
| @@ -11,6 +11,12 @@ import ( | |||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | func multiplyAndDivide(v, m, d int64) int64 { | ||||||
|  | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
|  | } | ||||||
|  |  | ||||||
| // ToStream maps a HLS stream to a MediaMTX stream. | // ToStream maps a HLS stream to a MediaMTX stream. | ||||||
| func ToStream( | func ToStream( | ||||||
| 	c *gohlslib.Client, | 	c *gohlslib.Client, | ||||||
| @@ -21,6 +27,7 @@ func ToStream( | |||||||
|  |  | ||||||
| 	for _, track := range tracks { | 	for _, track := range tracks { | ||||||
| 		var medi *description.Media | 		var medi *description.Media | ||||||
|  | 		clockRate := track.ClockRate | ||||||
|  |  | ||||||
| 		switch tcodec := track.Codec.(type) { | 		switch tcodec := track.Codec.(type) { | ||||||
| 		case *codecs.AV1: | 		case *codecs.AV1: | ||||||
| @@ -31,11 +38,11 @@ func ToStream( | |||||||
| 				}}, | 				}}, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) { | 			c.OnDataAV1(track, func(pts int64, tu [][]byte) { | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.AV1{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.AV1{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					TU: tu, | 					TU: tu, | ||||||
| 				}) | 				}) | ||||||
| @@ -49,37 +56,16 @@ func ToStream( | |||||||
| 				}}, | 				}}, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.OnDataVP9(track, func(pts time.Duration, frame []byte) { | 			c.OnDataVP9(track, func(pts int64, frame []byte) { | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.VP9{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.VP9{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					Frame: frame, | 					Frame: frame, | ||||||
| 				}) | 				}) | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 		case *codecs.H264: |  | ||||||
| 			medi = &description.Media{ |  | ||||||
| 				Type: description.MediaTypeVideo, |  | ||||||
| 				Formats: []format.Format{&format.H264{ |  | ||||||
| 					PayloadTyp:        96, |  | ||||||
| 					PacketizationMode: 1, |  | ||||||
| 					SPS:               tcodec.SPS, |  | ||||||
| 					PPS:               tcodec.PPS, |  | ||||||
| 				}}, |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			c.OnDataH26x(track, func(pts time.Duration, _ time.Duration, au [][]byte) { |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H264{ |  | ||||||
| 					Base: unit.Base{ |  | ||||||
| 						NTP: time.Now(), |  | ||||||
| 						PTS: pts, |  | ||||||
| 					}, |  | ||||||
| 					AU: au, |  | ||||||
| 				}) |  | ||||||
| 			}) |  | ||||||
|  |  | ||||||
| 		case *codecs.H265: | 		case *codecs.H265: | ||||||
| 			medi = &description.Media{ | 			medi = &description.Media{ | ||||||
| 				Type: description.MediaTypeVideo, | 				Type: description.MediaTypeVideo, | ||||||
| @@ -91,16 +77,56 @@ func ToStream( | |||||||
| 				}}, | 				}}, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.OnDataH26x(track, func(pts time.Duration, _ time.Duration, au [][]byte) { | 			c.OnDataH26x(track, func(pts int64, _ int64, au [][]byte) { | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H265{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H265{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					AU: au, | 					AU: au, | ||||||
| 				}) | 				}) | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
|  | 		case *codecs.H264: | ||||||
|  | 			medi = &description.Media{ | ||||||
|  | 				Type: description.MediaTypeVideo, | ||||||
|  | 				Formats: []format.Format{&format.H264{ | ||||||
|  | 					PayloadTyp:        96, | ||||||
|  | 					PacketizationMode: 1, | ||||||
|  | 					SPS:               tcodec.SPS, | ||||||
|  | 					PPS:               tcodec.PPS, | ||||||
|  | 				}}, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			c.OnDataH26x(track, func(pts int64, _ int64, au [][]byte) { | ||||||
|  | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H264{ | ||||||
|  | 					Base: unit.Base{ | ||||||
|  | 						NTP: time.Now(), | ||||||
|  | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 					}, | ||||||
|  | 					AU: au, | ||||||
|  | 				}) | ||||||
|  | 			}) | ||||||
|  |  | ||||||
|  | 		case *codecs.Opus: | ||||||
|  | 			medi = &description.Media{ | ||||||
|  | 				Type: description.MediaTypeAudio, | ||||||
|  | 				Formats: []format.Format{&format.Opus{ | ||||||
|  | 					PayloadTyp:   96, | ||||||
|  | 					ChannelCount: tcodec.ChannelCount, | ||||||
|  | 				}}, | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			c.OnDataOpus(track, func(pts int64, packets [][]byte) { | ||||||
|  | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.Opus{ | ||||||
|  | 					Base: unit.Base{ | ||||||
|  | 						NTP: time.Now(), | ||||||
|  | 						PTS: multiplyAndDivide(pts, int64(medi.Formats[0].ClockRate()), int64(clockRate)), | ||||||
|  | 					}, | ||||||
|  | 					Packets: packets, | ||||||
|  | 				}) | ||||||
|  | 			}) | ||||||
|  |  | ||||||
| 		case *codecs.MPEG4Audio: | 		case *codecs.MPEG4Audio: | ||||||
| 			medi = &description.Media{ | 			medi = &description.Media{ | ||||||
| 				Type: description.MediaTypeAudio, | 				Type: description.MediaTypeAudio, | ||||||
| @@ -113,35 +139,16 @@ func ToStream( | |||||||
| 				}}, | 				}}, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			c.OnDataMPEG4Audio(track, func(pts time.Duration, aus [][]byte) { | 			c.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) { | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4Audio{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4Audio{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: multiplyAndDivide(pts, int64(medi.Formats[0].ClockRate()), int64(clockRate)), | ||||||
| 					}, | 					}, | ||||||
| 					AUs: aus, | 					AUs: aus, | ||||||
| 				}) | 				}) | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 		case *codecs.Opus: |  | ||||||
| 			medi = &description.Media{ |  | ||||||
| 				Type: description.MediaTypeAudio, |  | ||||||
| 				Formats: []format.Format{&format.Opus{ |  | ||||||
| 					PayloadTyp:   96, |  | ||||||
| 					ChannelCount: tcodec.ChannelCount, |  | ||||||
| 				}}, |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			c.OnDataOpus(track, func(pts time.Duration, packets [][]byte) { |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.Opus{ |  | ||||||
| 					Base: unit.Base{ |  | ||||||
| 						NTP: time.Now(), |  | ||||||
| 						PTS: pts, |  | ||||||
| 					}, |  | ||||||
| 					Packets: packets, |  | ||||||
| 				}) |  | ||||||
| 			}) |  | ||||||
|  |  | ||||||
| 		default: | 		default: | ||||||
| 			panic("should not happen") | 			panic("should not happen") | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -18,8 +18,10 @@ import ( | |||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func durationGoToMPEGTS(v time.Duration) int64 { | func multiplyAndDivide(v, m, d int64) int64 { | ||||||
| 	return int64(v.Seconds() * 90000) | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
| } | } | ||||||
|  |  | ||||||
| // FromStream maps a MediaMTX stream to a MPEG-TS writer. | // FromStream maps a MediaMTX stream to a MPEG-TS writer. | ||||||
| @@ -47,11 +49,13 @@ func FromStream( | |||||||
|  |  | ||||||
| 	for _, media := range strea.Desc().Medias { | 	for _, media := range strea.Desc().Medias { | ||||||
| 		for _, forma := range media.Formats { | 		for _, forma := range media.Formats { | ||||||
|  | 			clockRate := forma.ClockRate() | ||||||
|  |  | ||||||
| 			switch forma := forma.(type) { | 			switch forma := forma.(type) { | ||||||
| 			case *format.H265: //nolint:dupl | 			case *format.H265: //nolint:dupl | ||||||
| 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecH265{}} | 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecH265{}} | ||||||
|  |  | ||||||
| 				var dtsExtractor *h265.DTSExtractor | 				var dtsExtractor *h265.DTSExtractor2 | ||||||
|  |  | ||||||
| 				addTrack( | 				addTrack( | ||||||
| 					media, | 					media, | ||||||
| @@ -69,7 +73,7 @@ func FromStream( | |||||||
| 							if !randomAccess { | 							if !randomAccess { | ||||||
| 								return nil | 								return nil | ||||||
| 							} | 							} | ||||||
| 							dtsExtractor = h265.NewDTSExtractor() | 							dtsExtractor = h265.NewDTSExtractor2() | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| @@ -78,7 +82,12 @@ func FromStream( | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 						err = (*w).WriteH265(track, durationGoToMPEGTS(tunit.PTS), durationGoToMPEGTS(dts), randomAccess, tunit.AU) | 						err = (*w).WriteH265( | ||||||
|  | 							track, | ||||||
|  | 							tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 							dts, | ||||||
|  | 							randomAccess, | ||||||
|  | 							tunit.AU) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return err | 							return err | ||||||
| 						} | 						} | ||||||
| @@ -88,7 +97,7 @@ func FromStream( | |||||||
| 			case *format.H264: //nolint:dupl | 			case *format.H264: //nolint:dupl | ||||||
| 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecH264{}} | 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecH264{}} | ||||||
|  |  | ||||||
| 				var dtsExtractor *h264.DTSExtractor | 				var dtsExtractor *h264.DTSExtractor2 | ||||||
|  |  | ||||||
| 				addTrack( | 				addTrack( | ||||||
| 					media, | 					media, | ||||||
| @@ -106,7 +115,7 @@ func FromStream( | |||||||
| 							if !idrPresent { | 							if !idrPresent { | ||||||
| 								return nil | 								return nil | ||||||
| 							} | 							} | ||||||
| 							dtsExtractor = h264.NewDTSExtractor() | 							dtsExtractor = h264.NewDTSExtractor2() | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| @@ -115,7 +124,12 @@ func FromStream( | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 						err = (*w).WriteH264(track, durationGoToMPEGTS(tunit.PTS), durationGoToMPEGTS(dts), idrPresent, tunit.AU) | 						err = (*w).WriteH264( | ||||||
|  | 							track, | ||||||
|  | 							tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 							dts, | ||||||
|  | 							idrPresent, | ||||||
|  | 							tunit.AU) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return err | 							return err | ||||||
| 						} | 						} | ||||||
| @@ -126,7 +140,7 @@ func FromStream( | |||||||
| 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG4Video{}} | 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG4Video{}} | ||||||
|  |  | ||||||
| 				firstReceived := false | 				firstReceived := false | ||||||
| 				var lastPTS time.Duration | 				var lastPTS int64 | ||||||
|  |  | ||||||
| 				addTrack( | 				addTrack( | ||||||
| 					media, | 					media, | ||||||
| @@ -146,7 +160,10 @@ func FromStream( | |||||||
| 						lastPTS = tunit.PTS | 						lastPTS = tunit.PTS | ||||||
|  |  | ||||||
| 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 						err := (*w).WriteMPEG4Video(track, durationGoToMPEGTS(tunit.PTS), tunit.Frame) | 						err := (*w).WriteMPEG4Video( | ||||||
|  | 							track, | ||||||
|  | 							tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 							tunit.Frame) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return err | 							return err | ||||||
| 						} | 						} | ||||||
| @@ -157,7 +174,7 @@ func FromStream( | |||||||
| 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG1Video{}} | 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG1Video{}} | ||||||
|  |  | ||||||
| 				firstReceived := false | 				firstReceived := false | ||||||
| 				var lastPTS time.Duration | 				var lastPTS int64 | ||||||
|  |  | ||||||
| 				addTrack( | 				addTrack( | ||||||
| 					media, | 					media, | ||||||
| @@ -177,7 +194,10 @@ func FromStream( | |||||||
| 						lastPTS = tunit.PTS | 						lastPTS = tunit.PTS | ||||||
|  |  | ||||||
| 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 						err := (*w).WriteMPEG1Video(track, durationGoToMPEGTS(tunit.PTS), tunit.Frame) | 						err := (*w).WriteMPEG1Video( | ||||||
|  | 							track, | ||||||
|  | 							tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 							tunit.Frame) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return err | 							return err | ||||||
| 						} | 						} | ||||||
| @@ -200,7 +220,10 @@ func FromStream( | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 						err := (*w).WriteOpus(track, durationGoToMPEGTS(tunit.PTS), tunit.Packets) | 						err := (*w).WriteOpus( | ||||||
|  | 							track, | ||||||
|  | 							multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)), | ||||||
|  | 							tunit.Packets) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return err | 							return err | ||||||
| 						} | 						} | ||||||
| @@ -225,7 +248,10 @@ func FromStream( | |||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 							sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 							err := (*w).WriteMPEG4Audio(track, durationGoToMPEGTS(tunit.PTS), tunit.AUs) | 							err := (*w).WriteMPEG4Audio( | ||||||
|  | 								track, | ||||||
|  | 								multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)), | ||||||
|  | 								tunit.AUs) | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								return err | 								return err | ||||||
| 							} | 							} | ||||||
| @@ -247,7 +273,10 @@ func FromStream( | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 						sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 						err := (*w).WriteMPEG1Audio(track, durationGoToMPEGTS(tunit.PTS), tunit.Frames) | 						err := (*w).WriteMPEG1Audio( | ||||||
|  | 							track, | ||||||
|  | 							tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 							tunit.Frames) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| 							return err | 							return err | ||||||
| 						} | 						} | ||||||
| @@ -257,8 +286,6 @@ func FromStream( | |||||||
| 			case *format.AC3: | 			case *format.AC3: | ||||||
| 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecAC3{}} | 				track := &mcmpegts.Track{Codec: &mcmpegts.CodecAC3{}} | ||||||
|  |  | ||||||
| 				sampleRate := time.Duration(forma.SampleRate) |  | ||||||
|  |  | ||||||
| 				addTrack( | 				addTrack( | ||||||
| 					media, | 					media, | ||||||
| 					forma, | 					forma, | ||||||
| @@ -270,11 +297,13 @@ func FromStream( | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						for i, frame := range tunit.Frames { | 						for i, frame := range tunit.Frames { | ||||||
| 							framePTS := tunit.PTS + time.Duration(i)*ac3.SamplesPerFrame* | 							framePTS := tunit.PTS + int64(i)*ac3.SamplesPerFrame | ||||||
| 								time.Second/sampleRate |  | ||||||
|  |  | ||||||
| 							sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 							sconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 							err := (*w).WriteAC3(track, durationGoToMPEGTS(framePTS), frame) | 							err := (*w).WriteAC3( | ||||||
|  | 								track, | ||||||
|  | 								multiplyAndDivide(framePTS, 90000, int64(clockRate)), | ||||||
|  | 								frame) | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								return err | 								return err | ||||||
| 							} | 							} | ||||||
|   | |||||||
| @@ -27,13 +27,7 @@ func ToStream( | |||||||
| 	var medias []*description.Media //nolint:prealloc | 	var medias []*description.Media //nolint:prealloc | ||||||
| 	var unsupportedTracks []int | 	var unsupportedTracks []int | ||||||
|  |  | ||||||
| 	var td *mpegts.TimeDecoder | 	td := mpegts.NewTimeDecoder2() | ||||||
| 	decodeTime := func(t int64) time.Duration { |  | ||||||
| 		if td == nil { |  | ||||||
| 			td = mpegts.NewTimeDecoder(t) |  | ||||||
| 		} |  | ||||||
| 		return td.Decode(t) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for i, track := range r.Tracks() { //nolint:dupl | 	for i, track := range r.Tracks() { //nolint:dupl | ||||||
| 		var medi *description.Media | 		var medi *description.Media | ||||||
| @@ -48,10 +42,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataH265(track, func(pts int64, _ int64, au [][]byte) error { | 			r.OnDataH265(track, func(pts int64, _ int64, au [][]byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H265{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H265{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					AU: au, | 					AU: au, | ||||||
| 				}) | 				}) | ||||||
| @@ -68,10 +64,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataH264(track, func(pts int64, _ int64, au [][]byte) error { | 			r.OnDataH264(track, func(pts int64, _ int64, au [][]byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H264{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.H264{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					AU: au, | 					AU: au, | ||||||
| 				}) | 				}) | ||||||
| @@ -87,10 +85,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataMPEGxVideo(track, func(pts int64, frame []byte) error { | 			r.OnDataMPEGxVideo(track, func(pts int64, frame []byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4Video{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4Video{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					Frame: frame, | 					Frame: frame, | ||||||
| 				}) | 				}) | ||||||
| @@ -104,10 +104,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataMPEGxVideo(track, func(pts int64, frame []byte) error { | 			r.OnDataMPEGxVideo(track, func(pts int64, frame []byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG1Video{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG1Video{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					Frame: frame, | 					Frame: frame, | ||||||
| 				}) | 				}) | ||||||
| @@ -124,10 +126,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataOpus(track, func(pts int64, packets [][]byte) error { | 			r.OnDataOpus(track, func(pts int64, packets [][]byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.Opus{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.Opus{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: multiplyAndDivide(pts, int64(medi.Formats[0].ClockRate()), 90000), | ||||||
| 					}, | 					}, | ||||||
| 					Packets: packets, | 					Packets: packets, | ||||||
| 				}) | 				}) | ||||||
| @@ -147,10 +151,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) error { | 			r.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4Audio{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4Audio{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: multiplyAndDivide(pts, int64(medi.Formats[0].ClockRate()), 90000), | ||||||
| 					}, | 					}, | ||||||
| 					AUs: aus, | 					AUs: aus, | ||||||
| 				}) | 				}) | ||||||
| @@ -164,10 +170,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { | 			r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: pts, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
| 					}, | 					}, | ||||||
| 					Frames: frames, | 					Frames: frames, | ||||||
| 				}) | 				}) | ||||||
| @@ -185,10 +193,12 @@ func ToStream( | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			r.OnDataAC3(track, func(pts int64, frame []byte) error { | 			r.OnDataAC3(track, func(pts int64, frame []byte) error { | ||||||
|  | 				pts = td.Decode(pts) | ||||||
|  |  | ||||||
| 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.AC3{ | 				(*stream).WriteUnit(medi, medi.Formats[0], &unit.AC3{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: decodeTime(pts), | 						PTS: multiplyAndDivide(pts, int64(medi.Formats[0].ClockRate()), 90000), | ||||||
| 					}, | 					}, | ||||||
| 					Frames: [][]byte{frame}, | 					Frames: [][]byte{frame}, | ||||||
| 				}) | 				}) | ||||||
|   | |||||||
| @@ -18,6 +18,16 @@ import ( | |||||||
| var errNoSupportedCodecsFrom = errors.New( | var errNoSupportedCodecsFrom = errors.New( | ||||||
| 	"the stream doesn't contain any supported codec, which are currently H264, MPEG-4 Audio, MPEG-1/2 Audio") | 	"the stream doesn't contain any supported codec, which are currently H264, MPEG-4 Audio, MPEG-1/2 Audio") | ||||||
|  |  | ||||||
|  | func multiplyAndDivide2(v, m, d time.Duration) time.Duration { | ||||||
|  | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func timestampToDuration(t int64, clockRate int) time.Duration { | ||||||
|  | 	return multiplyAndDivide2(time.Duration(t), time.Second, time.Duration(clockRate)) | ||||||
|  | } | ||||||
|  |  | ||||||
| func setupVideo( | func setupVideo( | ||||||
| 	strea *stream.Stream, | 	strea *stream.Stream, | ||||||
| 	reader stream.Reader, | 	reader stream.Reader, | ||||||
| @@ -29,7 +39,7 @@ func setupVideo( | |||||||
| 	videoMedia := strea.Desc().FindFormat(&videoFormatH264) | 	videoMedia := strea.Desc().FindFormat(&videoFormatH264) | ||||||
|  |  | ||||||
| 	if videoFormatH264 != nil { | 	if videoFormatH264 != nil { | ||||||
| 		var videoDTSExtractor *h264.DTSExtractor | 		var videoDTSExtractor *h264.DTSExtractor2 | ||||||
|  |  | ||||||
| 		strea.AddReader( | 		strea.AddReader( | ||||||
| 			reader, | 			reader, | ||||||
| @@ -56,35 +66,28 @@ func setupVideo( | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				var dts time.Duration |  | ||||||
|  |  | ||||||
| 				// wait until we receive an IDR | 				// wait until we receive an IDR | ||||||
| 				if videoDTSExtractor == nil { | 				if videoDTSExtractor == nil { | ||||||
| 					if !idrPresent { | 					if !idrPresent { | ||||||
| 						return nil | 						return nil | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					videoDTSExtractor = h264.NewDTSExtractor() | 					videoDTSExtractor = h264.NewDTSExtractor2() | ||||||
|  | 				} else if !idrPresent && !nonIDRPresent { | ||||||
| 					var err error |  | ||||||
| 					dts, err = videoDTSExtractor.Extract(tunit.AU, tunit.PTS) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return err |  | ||||||
| 					} |  | ||||||
| 				} else { |  | ||||||
| 					if !idrPresent && !nonIDRPresent { |  | ||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 					var err error | 				dts, err := videoDTSExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| 					dts, err = videoDTSExtractor.Extract(tunit.AU, tunit.PTS) |  | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				nconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 				nconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 				return (*w).WriteH264(tunit.PTS, dts, idrPresent, tunit.AU) | 				return (*w).WriteH264( | ||||||
|  | 					timestampToDuration(tunit.PTS, videoFormatH264.ClockRate()), | ||||||
|  | 					timestampToDuration(dts, videoFormatH264.ClockRate()), | ||||||
|  | 					idrPresent, | ||||||
|  | 					tunit.AU) | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 		return videoFormatH264 | 		return videoFormatH264 | ||||||
| @@ -116,10 +119,11 @@ func setupAudio( | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				for i, au := range tunit.AUs { | 				for i, au := range tunit.AUs { | ||||||
|  | 					pts := tunit.PTS + int64(i)*mpeg4audio.SamplesPerAccessUnit | ||||||
|  |  | ||||||
| 					nconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 					nconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 					err := (*w).WriteMPEG4Audio( | 					err := (*w).WriteMPEG4Audio( | ||||||
| 						tunit.PTS+time.Duration(i)*mpeg4audio.SamplesPerAccessUnit* | 						timestampToDuration(pts, audioFormatMPEG4Audio.ClockRate()), | ||||||
| 							time.Second/time.Duration(audioFormatMPEG4Audio.ClockRate()), |  | ||||||
| 						au, | 						au, | ||||||
| 					) | 					) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| @@ -158,13 +162,16 @@ func setupAudio( | |||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					nconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | 					nconn.SetWriteDeadline(time.Now().Add(writeTimeout)) | ||||||
| 					err = (*w).WriteMPEG1Audio(pts, &h, frame) | 					err = (*w).WriteMPEG1Audio( | ||||||
|  | 						timestampToDuration(pts, audioFormatMPEG1.ClockRate()), | ||||||
|  | 						&h, | ||||||
|  | 						frame) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return err | 						return err | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					pts += time.Duration(h.SampleCount()) * | 					pts += int64(h.SampleCount()) * | ||||||
| 						time.Second / time.Duration(h.SampleRate) | 						int64(audioFormatMPEG1.ClockRate()) / int64(h.SampleRate) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				return nil | 				return nil | ||||||
|   | |||||||
| @@ -14,6 +14,16 @@ var errNoSupportedCodecsTo = errors.New( | |||||||
| 	"the stream doesn't contain any supported codec, which are currently " + | 	"the stream doesn't contain any supported codec, which are currently " + | ||||||
| 		"AV1, VP9, H265, H264, MPEG-4 Audio, MPEG-1/2 Audio, G711, LPCM") | 		"AV1, VP9, H265, H264, MPEG-4 Audio, MPEG-1/2 Audio, G711, LPCM") | ||||||
|  |  | ||||||
|  | func multiplyAndDivide(v, m, d int64) int64 { | ||||||
|  | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func durationToTimestamp(d time.Duration, clockRate int) int64 { | ||||||
|  | 	return multiplyAndDivide(int64(d), int64(clockRate), int64(time.Second)) | ||||||
|  | } | ||||||
|  |  | ||||||
| // ToStream maps a RTMP stream to a MediaMTX stream. | // ToStream maps a RTMP stream to a MediaMTX stream. | ||||||
| func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | ||||||
| 	videoFormat, audioFormat := r.Tracks() | 	videoFormat, audioFormat := r.Tracks() | ||||||
| @@ -33,7 +43,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, videoFormat, &unit.AV1{ | 				(*stream).WriteUnit(medi, videoFormat, &unit.AV1{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, videoFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					TU: tu, | 					TU: tu, | ||||||
| 				}) | 				}) | ||||||
| @@ -44,7 +54,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, videoFormat, &unit.VP9{ | 				(*stream).WriteUnit(medi, videoFormat, &unit.VP9{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, videoFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					Frame: frame, | 					Frame: frame, | ||||||
| 				}) | 				}) | ||||||
| @@ -55,7 +65,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, videoFormat, &unit.H265{ | 				(*stream).WriteUnit(medi, videoFormat, &unit.H265{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, videoFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					AU: au, | 					AU: au, | ||||||
| 				}) | 				}) | ||||||
| @@ -66,7 +76,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, videoFormat, &unit.H264{ | 				(*stream).WriteUnit(medi, videoFormat, &unit.H264{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, videoFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					AU: au, | 					AU: au, | ||||||
| 				}) | 				}) | ||||||
| @@ -90,7 +100,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, audioFormat, &unit.MPEG4Audio{ | 				(*stream).WriteUnit(medi, audioFormat, &unit.MPEG4Audio{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, audioFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					AUs: [][]byte{au}, | 					AUs: [][]byte{au}, | ||||||
| 				}) | 				}) | ||||||
| @@ -101,7 +111,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, audioFormat, &unit.MPEG1Audio{ | 				(*stream).WriteUnit(medi, audioFormat, &unit.MPEG1Audio{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, audioFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					Frames: [][]byte{frame}, | 					Frames: [][]byte{frame}, | ||||||
| 				}) | 				}) | ||||||
| @@ -112,7 +122,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, audioFormat, &unit.G711{ | 				(*stream).WriteUnit(medi, audioFormat, &unit.G711{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, audioFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					Samples: samples, | 					Samples: samples, | ||||||
| 				}) | 				}) | ||||||
| @@ -123,7 +133,7 @@ func ToStream(r *Reader, stream **stream.Stream) ([]*description.Media, error) { | |||||||
| 				(*stream).WriteUnit(medi, audioFormat, &unit.LPCM{ | 				(*stream).WriteUnit(medi, audioFormat, &unit.LPCM{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Now(), | 						NTP: time.Now(), | ||||||
| 						PTS: pts, | 						PTS: durationToTimestamp(pts, audioFormat.ClockRate()), | ||||||
| 					}, | 					}, | ||||||
| 					Samples: samples, | 					Samples: samples, | ||||||
| 				}) | 				}) | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import ( | |||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format" | 	"github.com/bluenviron/gortsplib/v4/pkg/format" | ||||||
| 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" | 	"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" | ||||||
| @@ -213,7 +212,7 @@ func setupVideoTrack( | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		firstReceived := false | 		firstReceived := false | ||||||
| 		var lastPTS time.Duration | 		var lastPTS int64 | ||||||
|  |  | ||||||
| 		stream.AddReader( | 		stream.AddReader( | ||||||
| 			reader, | 			reader, | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ func ToStream( | |||||||
| 	stream **stream.Stream, | 	stream **stream.Stream, | ||||||
| ) ([]*description.Media, error) { | ) ([]*description.Media, error) { | ||||||
| 	var medias []*description.Media //nolint:prealloc | 	var medias []*description.Media //nolint:prealloc | ||||||
| 	timeDecoder := rtptime.NewGlobalDecoder() | 	timeDecoder := rtptime.NewGlobalDecoder2() | ||||||
|  |  | ||||||
| 	for _, track := range pc.incomingTracks { | 	for _, track := range pc.incomingTracks { | ||||||
| 		var typ description.MediaType | 		var typ description.MediaType | ||||||
|   | |||||||
| @@ -25,13 +25,6 @@ import ( | |||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func durationGoToMp4(v time.Duration, timeScale uint32) uint64 { |  | ||||||
| 	timeScale64 := uint64(timeScale) |  | ||||||
| 	secs := v / time.Second |  | ||||||
| 	dec := v % time.Second |  | ||||||
| 	return uint64(secs)*timeScale64 + uint64(dec)*timeScale64/uint64(time.Second) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func mpeg1audioChannelCount(cm mpeg1audio.ChannelMode) int { | func mpeg1audioChannelCount(cm mpeg1audio.ChannelMode) int { | ||||||
| 	switch cm { | 	switch cm { | ||||||
| 	case mpeg1audio.ChannelModeStereo, | 	case mpeg1audio.ChannelModeStereo, | ||||||
| @@ -144,6 +137,8 @@ func (f *formatFMP4) initialize() { | |||||||
|  |  | ||||||
| 	for _, media := range f.ai.agent.Stream.Desc().Medias { | 	for _, media := range f.ai.agent.Stream.Desc().Medias { | ||||||
| 		for _, forma := range media.Formats { | 		for _, forma := range media.Formats { | ||||||
|  | 			clockRate := forma.ClockRate() | ||||||
|  |  | ||||||
| 			switch forma := forma.(type) { | 			switch forma := forma.(type) { | ||||||
| 			case *rtspformat.AV1: | 			case *rtspformat.AV1: | ||||||
| 				codec := &fmp4.CodecAV1{ | 				codec := &fmp4.CodecAV1{ | ||||||
| @@ -298,7 +293,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 				} | 				} | ||||||
| 				track := addTrack(forma, codec) | 				track := addTrack(forma, codec) | ||||||
|  |  | ||||||
| 				var dtsExtractor *h265.DTSExtractor | 				var dtsExtractor *h265.DTSExtractor2 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -343,7 +338,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 							if !randomAccess { | 							if !randomAccess { | ||||||
| 								return nil | 								return nil | ||||||
| 							} | 							} | ||||||
| 							dtsExtractor = h265.NewDTSExtractor() | 							dtsExtractor = h265.NewDTSExtractor2() | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| @@ -352,7 +347,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						sampl, err := fmp4.NewPartSampleH26x( | 						sampl, err := fmp4.NewPartSampleH26x( | ||||||
| 							int32(durationGoToMp4(tunit.PTS-dts, 90000)), | 							int32(tunit.PTS-dts), | ||||||
| 							randomAccess, | 							randomAccess, | ||||||
| 							tunit.AU) | 							tunit.AU) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| @@ -380,7 +375,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 				} | 				} | ||||||
| 				track := addTrack(forma, codec) | 				track := addTrack(forma, codec) | ||||||
|  |  | ||||||
| 				var dtsExtractor *h264.DTSExtractor | 				var dtsExtractor *h264.DTSExtractor2 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -418,7 +413,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 							if !randomAccess { | 							if !randomAccess { | ||||||
| 								return nil | 								return nil | ||||||
| 							} | 							} | ||||||
| 							dtsExtractor = h264.NewDTSExtractor() | 							dtsExtractor = h264.NewDTSExtractor2() | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| @@ -427,7 +422,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						sampl, err := fmp4.NewPartSampleH26x( | 						sampl, err := fmp4.NewPartSampleH26x( | ||||||
| 							int32(durationGoToMp4(tunit.PTS-dts, 90000)), | 							int32(tunit.PTS-dts), | ||||||
| 							randomAccess, | 							randomAccess, | ||||||
| 							tunit.AU) | 							tunit.AU) | ||||||
| 						if err != nil { | 						if err != nil { | ||||||
| @@ -454,7 +449,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 				track := addTrack(forma, codec) | 				track := addTrack(forma, codec) | ||||||
|  |  | ||||||
| 				firstReceived := false | 				firstReceived := false | ||||||
| 				var lastPTS time.Duration | 				var lastPTS int64 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -507,7 +502,7 @@ func (f *formatFMP4) initialize() { | |||||||
| 				track := addTrack(forma, codec) | 				track := addTrack(forma, codec) | ||||||
|  |  | ||||||
| 				firstReceived := false | 				firstReceived := false | ||||||
| 				var lastPTS time.Duration | 				var lastPTS int64 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -608,21 +603,21 @@ func (f *formatFMP4) initialize() { | |||||||
| 							return nil | 							return nil | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						var dt time.Duration | 						pts := tunit.PTS | ||||||
|  |  | ||||||
| 						for _, packet := range tunit.Packets { | 						for _, packet := range tunit.Packets { | ||||||
| 							err := track.write(&sample{ | 							err := track.write(&sample{ | ||||||
| 								PartSample: &fmp4.PartSample{ | 								PartSample: &fmp4.PartSample{ | ||||||
| 									Payload: packet, | 									Payload: packet, | ||||||
| 								}, | 								}, | ||||||
| 								dts: tunit.PTS + dt, | 								dts: pts, | ||||||
| 								ntp: tunit.NTP.Add(dt), | 								ntp: tunit.NTP.Add(timestampToDuration(pts, clockRate)), | ||||||
| 							}) | 							}) | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								return err | 								return err | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							dt += opus.PacketDuration(packet) | 							pts += int64(opus.PacketDuration(packet)) * int64(clockRate) / int64(time.Second) | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						return nil | 						return nil | ||||||
| @@ -636,8 +631,6 @@ func (f *formatFMP4) initialize() { | |||||||
| 					} | 					} | ||||||
| 					track := addTrack(forma, codec) | 					track := addTrack(forma, codec) | ||||||
|  |  | ||||||
| 					sampleRate := time.Duration(forma.ClockRate()) |  | ||||||
|  |  | ||||||
| 					f.ai.agent.Stream.AddReader( | 					f.ai.agent.Stream.AddReader( | ||||||
| 						f.ai, | 						f.ai, | ||||||
| 						media, | 						media, | ||||||
| @@ -649,15 +642,14 @@ func (f *formatFMP4) initialize() { | |||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							for i, au := range tunit.AUs { | 							for i, au := range tunit.AUs { | ||||||
| 								dt := time.Duration(i) * mpeg4audio.SamplesPerAccessUnit * | 								pts := tunit.PTS + int64(i)*mpeg4audio.SamplesPerAccessUnit | ||||||
| 									time.Second / sampleRate |  | ||||||
|  |  | ||||||
| 								err := track.write(&sample{ | 								err := track.write(&sample{ | ||||||
| 									PartSample: &fmp4.PartSample{ | 									PartSample: &fmp4.PartSample{ | ||||||
| 										Payload: au, | 										Payload: au, | ||||||
| 									}, | 									}, | ||||||
| 									dts: tunit.PTS + dt, | 									dts: pts, | ||||||
| 									ntp: tunit.NTP.Add(dt), | 									ntp: tunit.NTP.Add(timestampToDuration(pts, clockRate)), | ||||||
| 								}) | 								}) | ||||||
| 								if err != nil { | 								if err != nil { | ||||||
| 									return err | 									return err | ||||||
| @@ -772,15 +764,14 @@ func (f *formatFMP4) initialize() { | |||||||
| 								updateCodecs() | 								updateCodecs() | ||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							dt := time.Duration(i) * time.Duration(ac3.SamplesPerFrame) * | 							pts := tunit.PTS + int64(i)*ac3.SamplesPerFrame | ||||||
| 								time.Second / time.Duration(codec.SampleRate) |  | ||||||
|  |  | ||||||
| 							err = track.write(&sample{ | 							err = track.write(&sample{ | ||||||
| 								PartSample: &fmp4.PartSample{ | 								PartSample: &fmp4.PartSample{ | ||||||
| 									Payload: frame, | 									Payload: frame, | ||||||
| 								}, | 								}, | ||||||
| 								dts: tunit.PTS + dt, | 								dts: pts, | ||||||
| 								ntp: tunit.NTP.Add(dt), | 								ntp: tunit.NTP.Add(timestampToDuration(pts, clockRate)), | ||||||
| 							}) | 							}) | ||||||
| 							if err != nil { | 							if err != nil { | ||||||
| 								return err | 								return err | ||||||
| @@ -881,8 +872,9 @@ func (f *formatFMP4) initialize() { | |||||||
| func (f *formatFMP4) close() { | func (f *formatFMP4) close() { | ||||||
| 	if f.currentSegment != nil { | 	if f.currentSegment != nil { | ||||||
| 		for _, track := range f.tracks { | 		for _, track := range f.tracks { | ||||||
| 			if track.nextSample != nil && track.nextSample.dts > f.currentSegment.lastDTS { | 			if track.nextSample != nil && | ||||||
| 				f.currentSegment.lastDTS = track.nextSample.dts | 				timestampToDuration(track.nextSample.dts, int(track.initTrack.TimeScale)) > f.currentSegment.lastDTS { | ||||||
|  | 				f.currentSegment.lastDTS = timestampToDuration(track.nextSample.dts, int(track.initTrack.TimeScale)) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -82,18 +82,19 @@ func (p *formatFMP4Part) close() error { | |||||||
| 	return writePart(p.s.fi, p.sequenceNumber, p.partTracks) | 	return writePart(p.s.fi, p.sequenceNumber, p.partTracks) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *formatFMP4Part) write(track *formatFMP4Track, sample *sample) error { | func (p *formatFMP4Part) write(track *formatFMP4Track, sample *sample, dtsDuration time.Duration) error { | ||||||
| 	partTrack, ok := p.partTracks[track] | 	partTrack, ok := p.partTracks[track] | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		partTrack = &fmp4.PartTrack{ | 		partTrack = &fmp4.PartTrack{ | ||||||
| 			ID: track.initTrack.ID, | 			ID: track.initTrack.ID, | ||||||
| 			BaseTime: durationGoToMp4(sample.dts-p.s.startDTS, track.initTrack.TimeScale), | 			BaseTime: uint64(multiplyAndDivide(int64(dtsDuration-p.s.startDTS), | ||||||
|  | 				int64(track.initTrack.TimeScale), int64(time.Second))), | ||||||
| 		} | 		} | ||||||
| 		p.partTracks[track] = partTrack | 		p.partTracks[track] = partTrack | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	partTrack.Samples = append(partTrack.Samples, sample.PartSample) | 	partTrack.Samples = append(partTrack.Samples, sample.PartSample) | ||||||
| 	p.endDTS = sample.dts | 	p.endDTS = dtsDuration | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -69,14 +69,14 @@ func (s *formatFMP4Segment) close() error { | |||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (s *formatFMP4Segment) write(track *formatFMP4Track, sample *sample) error { | func (s *formatFMP4Segment) write(track *formatFMP4Track, sample *sample, dtsDuration time.Duration) error { | ||||||
| 	s.lastDTS = sample.dts | 	s.lastDTS = dtsDuration | ||||||
|  |  | ||||||
| 	if s.curPart == nil { | 	if s.curPart == nil { | ||||||
| 		s.curPart = &formatFMP4Part{ | 		s.curPart = &formatFMP4Part{ | ||||||
| 			s:              s, | 			s:              s, | ||||||
| 			sequenceNumber: s.f.nextSequenceNumber, | 			sequenceNumber: s.f.nextSequenceNumber, | ||||||
| 			startDTS:       sample.dts, | 			startDTS:       dtsDuration, | ||||||
| 		} | 		} | ||||||
| 		s.curPart.initialize() | 		s.curPart.initialize() | ||||||
| 		s.f.nextSequenceNumber++ | 		s.f.nextSequenceNumber++ | ||||||
| @@ -91,11 +91,11 @@ func (s *formatFMP4Segment) write(track *formatFMP4Track, sample *sample) error | |||||||
| 		s.curPart = &formatFMP4Part{ | 		s.curPart = &formatFMP4Part{ | ||||||
| 			s:              s, | 			s:              s, | ||||||
| 			sequenceNumber: s.f.nextSequenceNumber, | 			sequenceNumber: s.f.nextSequenceNumber, | ||||||
| 			startDTS:       sample.dts, | 			startDTS:       dtsDuration, | ||||||
| 		} | 		} | ||||||
| 		s.curPart.initialize() | 		s.curPart.initialize() | ||||||
| 		s.f.nextSequenceNumber++ | 		s.f.nextSequenceNumber++ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return s.curPart.write(track, sample) | 	return s.curPart.write(track, sample, dtsDuration) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,29 +21,33 @@ func (t *formatFMP4Track) write(sample *sample) error { | |||||||
| 	if sample == nil { | 	if sample == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	sample.Duration = uint32(durationGoToMp4(t.nextSample.dts-sample.dts, t.initTrack.TimeScale)) | 	sample.Duration = uint32(t.nextSample.dts - sample.dts) | ||||||
|  |  | ||||||
|  | 	dtsDuration := timestampToDuration(sample.dts, int(t.initTrack.TimeScale)) | ||||||
|  |  | ||||||
| 	if t.f.currentSegment == nil { | 	if t.f.currentSegment == nil { | ||||||
| 		t.f.currentSegment = &formatFMP4Segment{ | 		t.f.currentSegment = &formatFMP4Segment{ | ||||||
| 			f:        t.f, | 			f:        t.f, | ||||||
| 			startDTS: sample.dts, | 			startDTS: dtsDuration, | ||||||
| 			startNTP: sample.ntp, | 			startNTP: sample.ntp, | ||||||
| 		} | 		} | ||||||
| 		t.f.currentSegment.initialize() | 		t.f.currentSegment.initialize() | ||||||
| 		// BaseTime is negative, this is not supported by fMP4. Reject the sample silently. | 		// BaseTime is negative, this is not supported by fMP4. Reject the sample silently. | ||||||
| 	} else if (sample.dts - t.f.currentSegment.startDTS) < 0 { | 	} else if (dtsDuration - t.f.currentSegment.startDTS) < 0 { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	err := t.f.currentSegment.write(t, sample) | 	err := t.f.currentSegment.write(t, sample, dtsDuration) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	nextDTSDuration := timestampToDuration(t.nextSample.dts, int(t.initTrack.TimeScale)) | ||||||
|  |  | ||||||
| 	if (!t.f.hasVideo || t.initTrack.Codec.IsVideo()) && | 	if (!t.f.hasVideo || t.initTrack.Codec.IsVideo()) && | ||||||
| 		!t.nextSample.IsNonSyncSample && | 		!t.nextSample.IsNonSyncSample && | ||||||
| 		(t.nextSample.dts-t.f.currentSegment.startDTS) >= t.f.ai.agent.SegmentDuration { | 		(nextDTSDuration-t.f.currentSegment.startDTS) >= t.f.ai.agent.SegmentDuration { | ||||||
| 		t.f.currentSegment.lastDTS = t.nextSample.dts | 		t.f.currentSegment.lastDTS = nextDTSDuration | ||||||
| 		err := t.f.currentSegment.close() | 		err := t.f.currentSegment.close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -51,7 +55,7 @@ func (t *formatFMP4Track) write(sample *sample) error { | |||||||
|  |  | ||||||
| 		t.f.currentSegment = &formatFMP4Segment{ | 		t.f.currentSegment = &formatFMP4Segment{ | ||||||
| 			f:        t.f, | 			f:        t.f, | ||||||
| 			startDTS: t.nextSample.dts, | 			startDTS: nextDTSDuration, | ||||||
| 			startNTP: t.nextSample.ntp, | 			startNTP: t.nextSample.ntp, | ||||||
| 		} | 		} | ||||||
| 		t.f.currentSegment.initialize() | 		t.f.currentSegment.initialize() | ||||||
|   | |||||||
| @@ -23,8 +23,20 @@ const ( | |||||||
| 	mpegtsMaxBufferSize = 64 * 1024 | 	mpegtsMaxBufferSize = 64 * 1024 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func durationGoToMPEGTS(v time.Duration) int64 { | func multiplyAndDivide(v, m, d int64) int64 { | ||||||
| 	return int64(v.Seconds() * 90000) | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func multiplyAndDivide2(v, m, d time.Duration) time.Duration { | ||||||
|  | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func timestampToDuration(t int64, clockRate int) time.Duration { | ||||||
|  | 	return multiplyAndDivide2(time.Duration(t), time.Second, time.Duration(clockRate)) | ||||||
| } | } | ||||||
|  |  | ||||||
| type dynamicWriter struct { | type dynamicWriter struct { | ||||||
| @@ -67,11 +79,13 @@ func (f *formatMPEGTS) initialize() { | |||||||
|  |  | ||||||
| 	for _, media := range f.ai.agent.Stream.Desc().Medias { | 	for _, media := range f.ai.agent.Stream.Desc().Medias { | ||||||
| 		for _, forma := range media.Formats { | 		for _, forma := range media.Formats { | ||||||
|  | 			clockRate := forma.ClockRate() | ||||||
|  |  | ||||||
| 			switch forma := forma.(type) { | 			switch forma := forma.(type) { | ||||||
| 			case *rtspformat.H265: //nolint:dupl | 			case *rtspformat.H265: //nolint:dupl | ||||||
| 				track := addTrack(forma, &mpegts.CodecH265{}) | 				track := addTrack(forma, &mpegts.CodecH265{}) | ||||||
|  |  | ||||||
| 				var dtsExtractor *h265.DTSExtractor | 				var dtsExtractor *h265.DTSExtractor2 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -89,7 +103,7 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 							if !randomAccess { | 							if !randomAccess { | ||||||
| 								return nil | 								return nil | ||||||
| 							} | 							} | ||||||
| 							dtsExtractor = h265.NewDTSExtractor() | 							dtsExtractor = h265.NewDTSExtractor2() | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| @@ -98,12 +112,17 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							dts, | 							timestampToDuration(dts, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							true, | 							true, | ||||||
| 							randomAccess, | 							randomAccess, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								return f.mw.WriteH265(track, durationGoToMPEGTS(tunit.PTS), durationGoToMPEGTS(dts), randomAccess, tunit.AU) | 								return f.mw.WriteH265( | ||||||
|  | 									track, | ||||||
|  | 									tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 									dts, | ||||||
|  | 									randomAccess, | ||||||
|  | 									tunit.AU) | ||||||
| 							}, | 							}, | ||||||
| 						) | 						) | ||||||
| 					}) | 					}) | ||||||
| @@ -111,7 +130,7 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 			case *rtspformat.H264: //nolint:dupl | 			case *rtspformat.H264: //nolint:dupl | ||||||
| 				track := addTrack(forma, &mpegts.CodecH264{}) | 				track := addTrack(forma, &mpegts.CodecH264{}) | ||||||
|  |  | ||||||
| 				var dtsExtractor *h264.DTSExtractor | 				var dtsExtractor *h264.DTSExtractor2 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -129,7 +148,7 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 							if !randomAccess { | 							if !randomAccess { | ||||||
| 								return nil | 								return nil | ||||||
| 							} | 							} | ||||||
| 							dtsExtractor = h264.NewDTSExtractor() | 							dtsExtractor = h264.NewDTSExtractor2() | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | 						dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS) | ||||||
| @@ -138,12 +157,17 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							dts, | 							timestampToDuration(dts, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							true, | 							true, | ||||||
| 							randomAccess, | 							randomAccess, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								return f.mw.WriteH264(track, durationGoToMPEGTS(tunit.PTS), durationGoToMPEGTS(dts), randomAccess, tunit.AU) | 								return f.mw.WriteH264( | ||||||
|  | 									track, | ||||||
|  | 									tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 									dts, | ||||||
|  | 									randomAccess, | ||||||
|  | 									tunit.AU) | ||||||
| 							}, | 							}, | ||||||
| 						) | 						) | ||||||
| 					}) | 					}) | ||||||
| @@ -152,7 +176,7 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 				track := addTrack(forma, &mpegts.CodecMPEG4Video{}) | 				track := addTrack(forma, &mpegts.CodecMPEG4Video{}) | ||||||
|  |  | ||||||
| 				firstReceived := false | 				firstReceived := false | ||||||
| 				var lastPTS time.Duration | 				var lastPTS int64 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -174,12 +198,15 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						randomAccess := bytes.Contains(tunit.Frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) | 						randomAccess := bytes.Contains(tunit.Frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							tunit.PTS, | 							timestampToDuration(tunit.PTS, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							true, | 							true, | ||||||
| 							randomAccess, | 							randomAccess, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								return f.mw.WriteMPEG4Video(track, durationGoToMPEGTS(tunit.PTS), tunit.Frame) | 								return f.mw.WriteMPEG4Video( | ||||||
|  | 									track, | ||||||
|  | 									tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 									tunit.Frame) | ||||||
| 							}, | 							}, | ||||||
| 						) | 						) | ||||||
| 					}) | 					}) | ||||||
| @@ -188,7 +215,7 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 				track := addTrack(forma, &mpegts.CodecMPEG1Video{}) | 				track := addTrack(forma, &mpegts.CodecMPEG1Video{}) | ||||||
|  |  | ||||||
| 				firstReceived := false | 				firstReceived := false | ||||||
| 				var lastPTS time.Duration | 				var lastPTS int64 | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| @@ -210,12 +237,15 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						randomAccess := bytes.Contains(tunit.Frame, []byte{0, 0, 1, 0xB8}) | 						randomAccess := bytes.Contains(tunit.Frame, []byte{0, 0, 1, 0xB8}) | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							tunit.PTS, | 							timestampToDuration(tunit.PTS, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							true, | 							true, | ||||||
| 							randomAccess, | 							randomAccess, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								return f.mw.WriteMPEG1Video(track, durationGoToMPEGTS(tunit.PTS), tunit.Frame) | 								return f.mw.WriteMPEG1Video( | ||||||
|  | 									track, | ||||||
|  | 									tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 									tunit.Frame) | ||||||
| 							}, | 							}, | ||||||
| 						) | 						) | ||||||
| 					}) | 					}) | ||||||
| @@ -236,12 +266,15 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							tunit.PTS, | 							timestampToDuration(tunit.PTS, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							false, | 							false, | ||||||
| 							true, | 							true, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								return f.mw.WriteOpus(track, durationGoToMPEGTS(tunit.PTS), tunit.Packets) | 								return f.mw.WriteOpus( | ||||||
|  | 									track, | ||||||
|  | 									multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)), | ||||||
|  | 									tunit.Packets) | ||||||
| 							}, | 							}, | ||||||
| 						) | 						) | ||||||
| 					}) | 					}) | ||||||
| @@ -266,12 +299,15 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 							} | 							} | ||||||
|  |  | ||||||
| 							return f.write( | 							return f.write( | ||||||
| 								tunit.PTS, | 								timestampToDuration(tunit.PTS, clockRate), | ||||||
| 								tunit.NTP, | 								tunit.NTP, | ||||||
| 								false, | 								false, | ||||||
| 								true, | 								true, | ||||||
| 								func() error { | 								func() error { | ||||||
| 									return f.mw.WriteMPEG4Audio(track, durationGoToMPEGTS(tunit.PTS), tunit.AUs) | 									return f.mw.WriteMPEG4Audio( | ||||||
|  | 										track, | ||||||
|  | 										multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)), | ||||||
|  | 										tunit.AUs) | ||||||
| 								}, | 								}, | ||||||
| 							) | 							) | ||||||
| 						}) | 						}) | ||||||
| @@ -291,12 +327,15 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							tunit.PTS, | 							timestampToDuration(tunit.PTS, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							false, | 							false, | ||||||
| 							true, | 							true, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								return f.mw.WriteMPEG1Audio(track, durationGoToMPEGTS(tunit.PTS), tunit.Frames) | 								return f.mw.WriteMPEG1Audio( | ||||||
|  | 									track, | ||||||
|  | 									tunit.PTS, // no conversion is needed since clock rate is 90khz in both MPEG-TS and RTSP | ||||||
|  | 									tunit.Frames) | ||||||
| 							}, | 							}, | ||||||
| 						) | 						) | ||||||
| 					}) | 					}) | ||||||
| @@ -304,8 +343,6 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 			case *rtspformat.AC3: | 			case *rtspformat.AC3: | ||||||
| 				track := addTrack(forma, &mpegts.CodecAC3{}) | 				track := addTrack(forma, &mpegts.CodecAC3{}) | ||||||
|  |  | ||||||
| 				sampleRate := time.Duration(forma.SampleRate) |  | ||||||
|  |  | ||||||
| 				f.ai.agent.Stream.AddReader( | 				f.ai.agent.Stream.AddReader( | ||||||
| 					f.ai, | 					f.ai, | ||||||
| 					media, | 					media, | ||||||
| @@ -317,16 +354,18 @@ func (f *formatMPEGTS) initialize() { | |||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 						return f.write( | 						return f.write( | ||||||
| 							tunit.PTS, | 							timestampToDuration(tunit.PTS, clockRate), | ||||||
| 							tunit.NTP, | 							tunit.NTP, | ||||||
| 							false, | 							false, | ||||||
| 							true, | 							true, | ||||||
| 							func() error { | 							func() error { | ||||||
| 								for i, frame := range tunit.Frames { | 								for i, frame := range tunit.Frames { | ||||||
| 									framePTS := tunit.PTS + time.Duration(i)*ac3.SamplesPerFrame* | 									framePTS := tunit.PTS + int64(i)*ac3.SamplesPerFrame | ||||||
| 										time.Second/sampleRate |  | ||||||
|  |  | ||||||
| 									err := f.mw.WriteAC3(track, durationGoToMPEGTS(framePTS), frame) | 									err := f.mw.WriteAC3( | ||||||
|  | 										track, | ||||||
|  | 										multiplyAndDivide(framePTS, 90000, int64(clockRate)), | ||||||
|  | 										frame) | ||||||
| 									if err != nil { | 									if err != nil { | ||||||
| 										return err | 										return err | ||||||
| 									} | 									} | ||||||
| @@ -370,7 +409,7 @@ func (f *formatMPEGTS) close() { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (f *formatMPEGTS) write( | func (f *formatMPEGTS) write( | ||||||
| 	dts time.Duration, | 	dtsDuration time.Duration, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	isVideo bool, | 	isVideo bool, | ||||||
| 	randomAccess bool, | 	randomAccess bool, | ||||||
| @@ -384,14 +423,14 @@ func (f *formatMPEGTS) write( | |||||||
| 	case f.currentSegment == nil: | 	case f.currentSegment == nil: | ||||||
| 		f.currentSegment = &formatMPEGTSSegment{ | 		f.currentSegment = &formatMPEGTSSegment{ | ||||||
| 			f:        f, | 			f:        f, | ||||||
| 			startDTS: dts, | 			startDTS: dtsDuration, | ||||||
| 			startNTP: ntp, | 			startNTP: ntp, | ||||||
| 		} | 		} | ||||||
| 		f.currentSegment.initialize() | 		f.currentSegment.initialize() | ||||||
| 	case (!f.hasVideo || isVideo) && | 	case (!f.hasVideo || isVideo) && | ||||||
| 		randomAccess && | 		randomAccess && | ||||||
| 		(dts-f.currentSegment.startDTS) >= f.ai.agent.SegmentDuration: | 		(dtsDuration-f.currentSegment.startDTS) >= f.ai.agent.SegmentDuration: | ||||||
| 		f.currentSegment.lastDTS = dts | 		f.currentSegment.lastDTS = dtsDuration | ||||||
| 		err := f.currentSegment.close() | 		err := f.currentSegment.close() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @@ -399,21 +438,21 @@ func (f *formatMPEGTS) write( | |||||||
|  |  | ||||||
| 		f.currentSegment = &formatMPEGTSSegment{ | 		f.currentSegment = &formatMPEGTSSegment{ | ||||||
| 			f:        f, | 			f:        f, | ||||||
| 			startDTS: dts, | 			startDTS: dtsDuration, | ||||||
| 			startNTP: ntp, | 			startNTP: ntp, | ||||||
| 		} | 		} | ||||||
| 		f.currentSegment.initialize() | 		f.currentSegment.initialize() | ||||||
|  |  | ||||||
| 	case (dts - f.currentSegment.lastFlush) >= f.ai.agent.PartDuration: | 	case (dtsDuration - f.currentSegment.lastFlush) >= f.ai.agent.PartDuration: | ||||||
| 		err := f.bw.Flush() | 		err := f.bw.Flush() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		f.currentSegment.lastFlush = dts | 		f.currentSegment.lastFlush = dtsDuration | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	f.currentSegment.lastDTS = dts | 	f.currentSegment.lastDTS = dtsDuration | ||||||
|  |  | ||||||
| 	return writeCB() | 	return writeCB() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import ( | |||||||
|  |  | ||||||
| type sample struct { | type sample struct { | ||||||
| 	*fmp4.PartSample | 	*fmp4.PartSample | ||||||
| 	dts time.Duration | 	dts int64 | ||||||
| 	ntp time.Time | 	ntp time.Time | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -70,9 +70,9 @@ func TestRecorder(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	}} | 	}} | ||||||
|  |  | ||||||
| 	writeToStream := func(stream *stream.Stream, startDTS time.Duration, startNTP time.Time) { | 	writeToStream := func(stream *stream.Stream, startDTS int64, startNTP time.Time) { | ||||||
| 		for i := 0; i < 2; i++ { | 		for i := 0; i < 2; i++ { | ||||||
| 			pts := startDTS + time.Duration(i)*100*time.Millisecond | 			pts := startDTS + int64(i)*100*90000/1000 | ||||||
| 			ntp := startNTP.Add(time.Duration(i*60) * time.Second) | 			ntp := startNTP.Add(time.Duration(i*60) * time.Second) | ||||||
|  |  | ||||||
| 			stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ | 			stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ | ||||||
| @@ -101,21 +101,21 @@ func TestRecorder(t *testing.T) { | |||||||
|  |  | ||||||
| 			stream.WriteUnit(desc.Medias[2], desc.Medias[2].Formats[0], &unit.MPEG4Audio{ | 			stream.WriteUnit(desc.Medias[2], desc.Medias[2].Formats[0], &unit.MPEG4Audio{ | ||||||
| 				Base: unit.Base{ | 				Base: unit.Base{ | ||||||
| 					PTS: pts, | 					PTS: pts * int64(desc.Medias[2].Formats[0].ClockRate()) / 90000, | ||||||
| 				}, | 				}, | ||||||
| 				AUs: [][]byte{{1, 2, 3, 4}}, | 				AUs: [][]byte{{1, 2, 3, 4}}, | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			stream.WriteUnit(desc.Medias[3], desc.Medias[3].Formats[0], &unit.G711{ | 			stream.WriteUnit(desc.Medias[3], desc.Medias[3].Formats[0], &unit.G711{ | ||||||
| 				Base: unit.Base{ | 				Base: unit.Base{ | ||||||
| 					PTS: pts, | 					PTS: pts * int64(desc.Medias[3].Formats[0].ClockRate()) / 90000, | ||||||
| 				}, | 				}, | ||||||
| 				Samples: []byte{1, 2, 3, 4}, | 				Samples: []byte{1, 2, 3, 4}, | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			stream.WriteUnit(desc.Medias[4], desc.Medias[4].Formats[0], &unit.LPCM{ | 			stream.WriteUnit(desc.Medias[4], desc.Medias[4].Formats[0], &unit.LPCM{ | ||||||
| 				Base: unit.Base{ | 				Base: unit.Base{ | ||||||
| 					PTS: pts, | 					PTS: pts * int64(desc.Medias[4].Formats[0].ClockRate()) / 90000, | ||||||
| 				}, | 				}, | ||||||
| 				Samples: []byte{1, 2, 3, 4}, | 				Samples: []byte{1, 2, 3, 4}, | ||||||
| 			}) | 			}) | ||||||
| @@ -198,11 +198,11 @@ func TestRecorder(t *testing.T) { | |||||||
| 			w.Initialize() | 			w.Initialize() | ||||||
|  |  | ||||||
| 			writeToStream(stream, | 			writeToStream(stream, | ||||||
| 				50*time.Second, | 				50*90000, | ||||||
| 				time.Date(2008, 5, 20, 22, 15, 25, 0, time.UTC)) | 				time.Date(2008, 5, 20, 22, 15, 25, 0, time.UTC)) | ||||||
|  |  | ||||||
| 			writeToStream(stream, | 			writeToStream(stream, | ||||||
| 				52*time.Second, | 				52*90000, | ||||||
| 				time.Date(2008, 5, 20, 22, 16, 25, 0, time.UTC)) | 				time.Date(2008, 5, 20, 22, 16, 25, 0, time.UTC)) | ||||||
|  |  | ||||||
| 			// simulate a write error | 			// simulate a write error | ||||||
| @@ -296,7 +296,7 @@ func TestRecorder(t *testing.T) { | |||||||
| 			time.Sleep(50 * time.Millisecond) | 			time.Sleep(50 * time.Millisecond) | ||||||
|  |  | ||||||
| 			writeToStream(stream, | 			writeToStream(stream, | ||||||
| 				300*time.Second, | 				300*90000, | ||||||
| 				time.Date(2010, 5, 20, 22, 15, 25, 0, time.UTC)) | 				time.Date(2010, 5, 20, 22, 15, 25, 0, time.UTC)) | ||||||
|  |  | ||||||
| 			time.Sleep(50 * time.Millisecond) | 			time.Sleep(50 * time.Millisecond) | ||||||
| @@ -367,7 +367,7 @@ func TestRecorderFMP4NegativeDTS(t *testing.T) { | |||||||
| 	for i := 0; i < 3; i++ { | 	for i := 0; i < 3; i++ { | ||||||
| 		stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ | 		stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ | ||||||
| 			Base: unit.Base{ | 			Base: unit.Base{ | ||||||
| 				PTS: -50*time.Millisecond + (time.Duration(i) * 200 * time.Millisecond), | 				PTS: -50*90000/1000 + (int64(i) * 200 * 90000 / 1000), | ||||||
| 				NTP: time.Date(2008, 5, 20, 22, 15, 25, 0, time.UTC), | 				NTP: time.Date(2008, 5, 20, 22, 15, 25, 0, time.UTC), | ||||||
| 			}, | 			}, | ||||||
| 			AU: [][]byte{ | 			AU: [][]byte{ | ||||||
| @@ -379,7 +379,7 @@ func TestRecorderFMP4NegativeDTS(t *testing.T) { | |||||||
|  |  | ||||||
| 		stream.WriteUnit(desc.Medias[1], desc.Medias[1].Formats[0], &unit.MPEG4Audio{ | 		stream.WriteUnit(desc.Medias[1], desc.Medias[1].Formats[0], &unit.MPEG4Audio{ | ||||||
| 			Base: unit.Base{ | 			Base: unit.Base{ | ||||||
| 				PTS: -100*time.Millisecond + (time.Duration(i) * 200 * time.Millisecond), | 				PTS: -100*44100/1000 + (int64(i) * 200 * 44100 / 1000), | ||||||
| 			}, | 			}, | ||||||
| 			AUs: [][]byte{{1, 2, 3, 4}}, | 			AUs: [][]byte{{1, 2, 3, 4}}, | ||||||
| 		}) | 		}) | ||||||
|   | |||||||
| @@ -218,11 +218,12 @@ func TestServerRead(t *testing.T) { | |||||||
| 		c.OnTracks = func(tracks []*gohlslib.Track) error { | 		c.OnTracks = func(tracks []*gohlslib.Track) error { | ||||||
| 			require.Equal(t, []*gohlslib.Track{{ | 			require.Equal(t, []*gohlslib.Track{{ | ||||||
| 				Codec:     &codecs.H264{}, | 				Codec:     &codecs.H264{}, | ||||||
|  | 				ClockRate: 90000, | ||||||
| 			}}, tracks) | 			}}, tracks) | ||||||
|  |  | ||||||
| 			c.OnDataH26x(tracks[0], func(pts, dts time.Duration, au [][]byte) { | 			c.OnDataH26x(tracks[0], func(pts, dts int64, au [][]byte) { | ||||||
| 				require.Equal(t, time.Duration(0), pts) | 				require.Equal(t, int64(0), pts) | ||||||
| 				require.Equal(t, time.Duration(0), dts) | 				require.Equal(t, int64(0), dts) | ||||||
| 				require.Equal(t, [][]byte{ | 				require.Equal(t, [][]byte{ | ||||||
| 					test.FormatH264.SPS, | 					test.FormatH264.SPS, | ||||||
| 					test.FormatH264.PPS, | 					test.FormatH264.PPS, | ||||||
| @@ -246,7 +247,7 @@ func TestServerRead(t *testing.T) { | |||||||
| 				str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ | 				str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ | ||||||
| 					Base: unit.Base{ | 					Base: unit.Base{ | ||||||
| 						NTP: time.Time{}, | 						NTP: time.Time{}, | ||||||
| 						PTS: time.Duration(i) * time.Second, | 						PTS: int64(i) * 90000, | ||||||
| 					}, | 					}, | ||||||
| 					AU: [][]byte{ | 					AU: [][]byte{ | ||||||
| 						{5, 1}, // IDR | 						{5, 1}, // IDR | ||||||
| @@ -311,7 +312,7 @@ func TestServerRead(t *testing.T) { | |||||||
| 			str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ | 			str.WriteUnit(test.MediaH264, test.FormatH264, &unit.H264{ | ||||||
| 				Base: unit.Base{ | 				Base: unit.Base{ | ||||||
| 					NTP: time.Time{}, | 					NTP: time.Time{}, | ||||||
| 					PTS: time.Duration(i) * time.Second, | 					PTS: int64(i) * 90000, | ||||||
| 				}, | 				}, | ||||||
| 				AU: [][]byte{ | 				AU: [][]byte{ | ||||||
| 					{5, 1}, // IDR | 					{5, 1}, // IDR | ||||||
| @@ -328,11 +329,12 @@ func TestServerRead(t *testing.T) { | |||||||
| 		c.OnTracks = func(tracks []*gohlslib.Track) error { | 		c.OnTracks = func(tracks []*gohlslib.Track) error { | ||||||
| 			require.Equal(t, []*gohlslib.Track{{ | 			require.Equal(t, []*gohlslib.Track{{ | ||||||
| 				Codec:     &codecs.H264{}, | 				Codec:     &codecs.H264{}, | ||||||
|  | 				ClockRate: 90000, | ||||||
| 			}}, tracks) | 			}}, tracks) | ||||||
|  |  | ||||||
| 			c.OnDataH26x(tracks[0], func(pts, dts time.Duration, au [][]byte) { | 			c.OnDataH26x(tracks[0], func(pts, dts int64, au [][]byte) { | ||||||
| 				require.Equal(t, time.Duration(0), pts) | 				require.Equal(t, int64(0), pts) | ||||||
| 				require.Equal(t, time.Duration(0), dts) | 				require.Equal(t, int64(0), dts) | ||||||
| 				require.Equal(t, [][]byte{ | 				require.Equal(t, [][]byte{ | ||||||
| 					test.FormatH264.SPS, | 					test.FormatH264.SPS, | ||||||
| 					test.FormatH264.PPS, | 					test.FormatH264.PPS, | ||||||
|   | |||||||
| @@ -292,7 +292,7 @@ func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Respons | |||||||
| 			cforma := forma | 			cforma := forma | ||||||
|  |  | ||||||
| 			s.rsession.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { | 			s.rsession.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { | ||||||
| 				pts, ok := s.rsession.PacketPTS(cmedi, pkt) | 				pts, ok := s.rsession.PacketPTS2(cmedi, pkt) | ||||||
| 				if !ok { | 				if !ok { | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|   | |||||||
| @@ -14,6 +14,12 @@ import ( | |||||||
| 	"github.com/bluenviron/mediamtx/internal/unit" | 	"github.com/bluenviron/mediamtx/internal/unit" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | func multiplyAndDivide(v, m, d int64) int64 { | ||||||
|  | 	secs := v / d | ||||||
|  | 	dec := v % d | ||||||
|  | 	return (secs*m + dec*m/d) | ||||||
|  | } | ||||||
|  |  | ||||||
| func paramsFromConf(logLevel conf.LogLevel, cnf *conf.Path) params { | func paramsFromConf(logLevel conf.LogLevel, cnf *conf.Path) params { | ||||||
| 	return params{ | 	return params{ | ||||||
| 		LogLevel: func() string { | 		LogLevel: func() string { | ||||||
| @@ -105,7 +111,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { | |||||||
| 		stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ | 		stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ | ||||||
| 			Base: unit.Base{ | 			Base: unit.Base{ | ||||||
| 				NTP: time.Now(), | 				NTP: time.Now(), | ||||||
| 				PTS: dts, | 				PTS: multiplyAndDivide(int64(dts), 90000, int64(time.Second)), | ||||||
| 			}, | 			}, | ||||||
| 			AU: au, | 			AU: au, | ||||||
| 		}) | 		}) | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { | |||||||
| 					cforma := forma | 					cforma := forma | ||||||
|  |  | ||||||
| 					c.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { | 					c.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { | ||||||
| 						pts, ok := c.PacketPTS(cmedi, pkt) | 						pts, ok := c.PacketPTS2(cmedi, pkt) | ||||||
| 						if !ok { | 						if !ok { | ||||||
| 							return | 							return | ||||||
| 						} | 						} | ||||||
|   | |||||||
| @@ -240,7 +240,7 @@ func (s *Stream) WriteRTPPacket( | |||||||
| 	forma format.Format, | 	forma format.Format, | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| ) { | ) { | ||||||
| 	sm := s.streamMedias[medi] | 	sm := s.streamMedias[medi] | ||||||
| 	sf := sm.formats[forma] | 	sf := sm.formats[forma] | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ func (sf *streamFormat) writeRTPPacket( | |||||||
| 	medi *description.Media, | 	medi *description.Media, | ||||||
| 	pkt *rtp.Packet, | 	pkt *rtp.Packet, | ||||||
| 	ntp time.Time, | 	ntp time.Time, | ||||||
| 	pts time.Duration, | 	pts int64, | ||||||
| ) { | ) { | ||||||
| 	hasNonRTSPReaders := len(sf.pausedReaders) > 0 || len(sf.runningReaders) > 0 | 	hasNonRTSPReaders := len(sf.pausedReaders) > 0 || len(sf.runningReaders) > 0 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| type Base struct { | type Base struct { | ||||||
| 	RTPPackets []*rtp.Packet | 	RTPPackets []*rtp.Packet | ||||||
| 	NTP        time.Time | 	NTP        time.Time | ||||||
| 	PTS        time.Duration | 	PTS        int64 | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetRTPPackets implements Unit. | // GetRTPPackets implements Unit. | ||||||
| @@ -24,6 +24,6 @@ func (u *Base) GetNTP() time.Time { | |||||||
| } | } | ||||||
|  |  | ||||||
| // GetPTS implements Unit. | // GetPTS implements Unit. | ||||||
| func (u *Base) GetPTS() time.Duration { | func (u *Base) GetPTS() int64 { | ||||||
| 	return u.PTS | 	return u.PTS | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,5 +16,5 @@ type Unit interface { | |||||||
| 	GetNTP() time.Time | 	GetNTP() time.Time | ||||||
|  |  | ||||||
| 	// returns the PTS of the unit. | 	// returns the PTS of the unit. | ||||||
| 	GetPTS() time.Duration | 	GetPTS() int64 | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Alessandro Ros
					Alessandro Ros