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