route original timestamps without converting to durations (#3839)

This improves timestamp precision.
This commit is contained in:
Alessandro Ros
2024-10-07 17:59:32 +02:00
committed by GitHub
parent 54c0737074
commit 23002d9f5f
43 changed files with 475 additions and 400 deletions

View File

@@ -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
View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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