diff --git a/.golangci.yml b/.golangci.yml index b9fd5c65..593d6b20 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -52,11 +52,11 @@ linters-settings: - (github.com/datarhei/gosrt.Conn).Close - (github.com/datarhei/gosrt.Conn).SetReadDeadline - (github.com/datarhei/gosrt.Conn).SetWriteDeadline - - (*github.com/bluenviron/gortsplib/v3.Client).Close - - (*github.com/bluenviron/gortsplib/v3.Server).Close - - (*github.com/bluenviron/gortsplib/v3.ServerSession).Close - - (*github.com/bluenviron/gortsplib/v3.ServerStream).Close - - (*github.com/bluenviron/gortsplib/v3.ServerConn).Close + - (*github.com/bluenviron/gortsplib/v4.Client).Close + - (*github.com/bluenviron/gortsplib/v4.Server).Close + - (*github.com/bluenviron/gortsplib/v4.ServerSession).Close + - (*github.com/bluenviron/gortsplib/v4.ServerStream).Close + - (*github.com/bluenviron/gortsplib/v4.ServerConn).Close govet: enable-all: true diff --git a/go.mod b/go.mod index b67e92ec..d77d31dc 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/abema/go-mp4 v0.13.0 github.com/alecthomas/kong v0.8.0 github.com/bluenviron/gohlslib v1.0.0 - github.com/bluenviron/gortsplib/v3 v3.10.0 + github.com/bluenviron/gortsplib/v4 v4.0.0-20230826160945-3bdae4ed4630 github.com/bluenviron/mediacommon v1.0.0 github.com/datarhei/gosrt v0.5.3 github.com/fsnotify/fsnotify v1.6.0 diff --git a/go.sum b/go.sum index 7ce340ed..2949a26f 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYh github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI= github.com/bluenviron/gohlslib v1.0.0 h1:UOI7wW7EdXPnnoflPL+WRiUB+bDSyrR9AXtu029n5EY= github.com/bluenviron/gohlslib v1.0.0/go.mod h1:fwqXogd2G/CJ/0kD6TTALmWI3KAm66nZoI+06O02YKI= -github.com/bluenviron/gortsplib/v3 v3.10.0 h1:E2ytPD1/b6JgzHYVSsyaG2xtXsvaGw9sxTdZ0Wnwsd4= -github.com/bluenviron/gortsplib/v3 v3.10.0/go.mod h1:prNU1aMVBmgmmKwlvLiEdjBbTEpTw4BRsqVcqEARgMY= +github.com/bluenviron/gortsplib/v4 v4.0.0-20230826160945-3bdae4ed4630 h1:Xe2em0Hv8ftQ1G3YgKVIcnor63h+OuXBHL7sNDMpfZA= +github.com/bluenviron/gortsplib/v4 v4.0.0-20230826160945-3bdae4ed4630/go.mod h1:UU9EkvmpvyXdRIDBBUK7zdFt2LsdnV4iz9cHP3JaB8s= github.com/bluenviron/mediacommon v1.0.0 h1:hKelTQKfetasCmXaXMiL1ihID0GRmItyWZt1/pqiKKk= github.com/bluenviron/mediacommon v1.0.0/go.mod h1:nt5oKCO0WcZ+AH1oc12gs2ldp67xW2vl88c2StNmPlI= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= diff --git a/internal/conf/auth_method.go b/internal/conf/auth_method.go index 73f1757c..353764d0 100644 --- a/internal/conf/auth_method.go +++ b/internal/conf/auth_method.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/bluenviron/gortsplib/v3/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/headers" ) // AuthMethods is the authMethods parameter. diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 01ae0503..bfbaf2b4 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -12,8 +12,8 @@ import ( "time" "github.com/bluenviron/gohlslib" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/headers" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/mediamtx/internal/conf/decrypt" "github.com/bluenviron/mediamtx/internal/conf/env" diff --git a/internal/conf/conf_test.go b/internal/conf/conf_test.go index ca939724..1b241a5f 100644 --- a/internal/conf/conf_test.go +++ b/internal/conf/conf_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" + "github.com/bluenviron/gortsplib/v4" "github.com/stretchr/testify/require" "golang.org/x/crypto/nacl/secretbox" diff --git a/internal/conf/path.go b/internal/conf/path.go index bc53a74e..61611894 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/bluenviron/gortsplib/v3/pkg/headers" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/url" ) var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/\.~]+$`) diff --git a/internal/conf/protocol.go b/internal/conf/protocol.go index 54b8f816..5c1713d5 100644 --- a/internal/conf/protocol.go +++ b/internal/conf/protocol.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/bluenviron/gortsplib/v3" + "github.com/bluenviron/gortsplib/v4" ) // Protocol is a RTSP transport. diff --git a/internal/conf/source_protocol.go b/internal/conf/source_protocol.go index 24420ba6..c681f552 100644 --- a/internal/conf/source_protocol.go +++ b/internal/conf/source_protocol.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/bluenviron/gortsplib/v3" + "github.com/bluenviron/gortsplib/v4" ) // SourceProtocol is the sourceProtocol parameter. diff --git a/internal/core/api_test.go b/internal/core/api_test.go index ac967ea0..a42ca0ea 100644 --- a/internal/core/api_test.go +++ b/internal/core/api_test.go @@ -14,9 +14,9 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/datarhei/gosrt" @@ -27,7 +27,7 @@ import ( "github.com/bluenviron/mediamtx/internal/rtmp" ) -var testFormatH264 = &formats.H264{ +var testFormatH264 = &format.H264{ PayloadTyp: 96, SPS: []byte{ // 1920x1080 baseline 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, @@ -38,9 +38,9 @@ var testFormatH264 = &formats.H264{ PacketizationMode: 1, } -var testMediaH264 = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{testFormatH264}, +var testMediaH264 = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{testFormatH264}, } func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) { @@ -245,11 +245,11 @@ func TestAPIPathsList(t *testing.T) { source := gortsplib.Client{} err := source.StartRecording( "rtsp://localhost:8554/mypath", - media.Medias{ + &description.Session{Medias: []*description.Media{ media0, { - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG4Audio{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -261,7 +261,7 @@ func TestAPIPathsList(t *testing.T) { IndexDeltaLength: 3, }}, }, - }) + }}) require.NoError(t, err) defer source.Close() @@ -270,7 +270,7 @@ func TestAPIPathsList(t *testing.T) { Version: 2, PayloadType: 96, }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + Payload: []byte{5, 1, 2, 3, 4}, }) require.NoError(t, err) @@ -286,7 +286,7 @@ func TestAPIPathsList(t *testing.T) { }, Ready: true, Tracks: []string{"H264", "MPEG-4 Audio"}, - BytesReceived: 16, + BytesReceived: 17, }}, }, out) }) @@ -311,29 +311,28 @@ func TestAPIPathsList(t *testing.T) { hc := &http.Client{Transport: &http.Transport{}} - medias := media.Medias{ - { - Type: media.TypeVideo, - Formats: []formats.Format{testFormatH264}, - }, - { - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG4Audio{ - PayloadTyp: 97, - Config: &mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }, - SizeLength: 13, - IndexLength: 3, - IndexDeltaLength: 3, - }}, - }, - } - source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}} - err = source.StartRecording("rtsps://localhost:8322/mypath", medias) + err = source.StartRecording("rtsps://localhost:8322/mypath", + &description.Session{Medias: []*description.Media{ + { + Type: description.MediaTypeVideo, + Formats: []format.Format{testFormatH264}, + }, + { + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG4Audio{ + PayloadTyp: 97, + Config: &mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + }}, + }, + }}) require.NoError(t, err) defer source.Close() @@ -471,7 +470,8 @@ func TestAPIPathsGet(t *testing.T) { if ca == "ok" || ca == "ok-nested" { source := gortsplib.Client{} - err := source.StartRecording("rtsp://localhost:8554/"+pathName, media.Medias{testMediaH264}) + err := source.StartRecording("rtsp://localhost:8554/"+pathName, + &description.Session{Medias: []*description.Media{testMediaH264}}) require.NoError(t, err) defer source.Close() @@ -546,7 +546,8 @@ func TestAPIProtocolList(t *testing.T) { case "rtsp conns", "rtsp sessions": source := gortsplib.Client{} - err := source.StartRecording("rtsp://localhost:8554/mypath", media.Medias{medi}) + err := source.StartRecording("rtsp://localhost:8554/mypath", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -555,7 +556,8 @@ func TestAPIProtocolList(t *testing.T) { TLSConfig: &tls.Config{InsecureSkipVerify: true}, } - err := source.StartRecording("rtsps://localhost:8322/mypath", media.Medias{medi}) + err := source.StartRecording("rtsps://localhost:8322/mypath", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -590,7 +592,7 @@ func TestAPIProtocolList(t *testing.T) { case "hls": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -647,7 +649,7 @@ func TestAPIProtocolList(t *testing.T) { case "webrtc": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -665,7 +667,7 @@ func TestAPIProtocolList(t *testing.T) { Timestamp: 45343, SSRC: 563423, }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + Payload: []byte{5, 1, 2, 3, 4}, }) require.NoError(t, err) @@ -830,7 +832,8 @@ func TestAPIProtocolGet(t *testing.T) { case "rtsp conns", "rtsp sessions": source := gortsplib.Client{} - err := source.StartRecording("rtsp://localhost:8554/mypath", media.Medias{medi}) + err := source.StartRecording("rtsp://localhost:8554/mypath", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -839,7 +842,8 @@ func TestAPIProtocolGet(t *testing.T) { TLSConfig: &tls.Config{InsecureSkipVerify: true}, } - err := source.StartRecording("rtsps://localhost:8322/mypath", media.Medias{medi}) + err := source.StartRecording("rtsps://localhost:8322/mypath", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -874,7 +878,7 @@ func TestAPIProtocolGet(t *testing.T) { case "hls": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -931,7 +935,7 @@ func TestAPIProtocolGet(t *testing.T) { case "webrtc": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -949,7 +953,7 @@ func TestAPIProtocolGet(t *testing.T) { Timestamp: 45343, SSRC: 563423, }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + Payload: []byte{5, 1, 2, 3, 4}, }) require.NoError(t, err) @@ -1196,7 +1200,7 @@ func TestAPIProtocolKick(t *testing.T) { source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -1206,7 +1210,7 @@ func TestAPIProtocolKick(t *testing.T) { } err := source.StartRecording("rtsps://localhost:8322/mypath", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() diff --git a/internal/core/authentication.go b/internal/core/authentication.go index 3ce28385..61aaf25b 100644 --- a/internal/core/authentication.go +++ b/internal/core/authentication.go @@ -11,10 +11,10 @@ import ( "net/http" "strings" - "github.com/bluenviron/gortsplib/v3/pkg/auth" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/headers" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/google/uuid" "github.com/bluenviron/mediamtx/internal/conf" diff --git a/internal/core/core.go b/internal/core/core.go index 175c9ada..a57dd418 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -9,7 +9,7 @@ import ( "reflect" "github.com/alecthomas/kong" - "github.com/bluenviron/gortsplib/v3" + "github.com/bluenviron/gortsplib/v4" "github.com/gin-gonic/gin" "github.com/bluenviron/mediamtx/internal/conf" @@ -213,29 +213,27 @@ func (p *Core) createResources(initial bool) error { p.externalCmdPool = externalcmd.NewPool() } - if p.conf.Metrics { - if p.metrics == nil { - p.metrics, err = newMetrics( - p.conf.MetricsAddress, - p.conf.ReadTimeout, - p, - ) - if err != nil { - return err - } + if p.conf.Metrics && + p.metrics == nil { + p.metrics, err = newMetrics( + p.conf.MetricsAddress, + p.conf.ReadTimeout, + p, + ) + if err != nil { + return err } } - if p.conf.PPROF { - if p.pprof == nil { - p.pprof, err = newPPROF( - p.conf.PPROFAddress, - p.conf.ReadTimeout, - p, - ) - if err != nil { - return err - } + if p.conf.PPROF && + p.pprof == nil { + p.pprof, err = newPPROF( + p.conf.PPROFAddress, + p.conf.ReadTimeout, + p, + ) + if err != nil { + return err } } @@ -257,219 +255,212 @@ func (p *Core) createResources(initial bool) error { if p.conf.RTSP && (p.conf.Encryption == conf.EncryptionNo || - p.conf.Encryption == conf.EncryptionOptional) { - if p.rtspServer == nil { - _, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)] - _, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)] - p.rtspServer, err = newRTSPServer( - p.conf.RTSPAddress, - p.conf.AuthMethods, - p.conf.ReadTimeout, - p.conf.WriteTimeout, - p.conf.WriteQueueSize, - useUDP, - useMulticast, - p.conf.RTPAddress, - p.conf.RTCPAddress, - p.conf.MulticastIPRange, - p.conf.MulticastRTPPort, - p.conf.MulticastRTCPPort, - false, - "", - "", - p.conf.RTSPAddress, - p.conf.Protocols, - p.conf.RunOnConnect, - p.conf.RunOnConnectRestart, - p.externalCmdPool, - p.metrics, - p.pathManager, - p, - ) - if err != nil { - return err - } + p.conf.Encryption == conf.EncryptionOptional) && + p.rtspServer == nil { + _, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)] + _, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)] + + p.rtspServer, err = newRTSPServer( + p.conf.RTSPAddress, + p.conf.AuthMethods, + p.conf.ReadTimeout, + p.conf.WriteTimeout, + p.conf.WriteQueueSize, + useUDP, + useMulticast, + p.conf.RTPAddress, + p.conf.RTCPAddress, + p.conf.MulticastIPRange, + p.conf.MulticastRTPPort, + p.conf.MulticastRTCPPort, + false, + "", + "", + p.conf.RTSPAddress, + p.conf.Protocols, + p.conf.RunOnConnect, + p.conf.RunOnConnectRestart, + p.externalCmdPool, + p.metrics, + p.pathManager, + p, + ) + if err != nil { + return err } } if p.conf.RTSP && (p.conf.Encryption == conf.EncryptionStrict || - p.conf.Encryption == conf.EncryptionOptional) { - if p.rtspsServer == nil { - p.rtspsServer, err = newRTSPServer( - p.conf.RTSPSAddress, - p.conf.AuthMethods, - p.conf.ReadTimeout, - p.conf.WriteTimeout, - p.conf.WriteQueueSize, - false, - false, - "", - "", - "", - 0, - 0, - true, - p.conf.ServerCert, - p.conf.ServerKey, - p.conf.RTSPAddress, - p.conf.Protocols, - p.conf.RunOnConnect, - p.conf.RunOnConnectRestart, - p.externalCmdPool, - p.metrics, - p.pathManager, - p, - ) - if err != nil { - return err - } + p.conf.Encryption == conf.EncryptionOptional) && + p.rtspsServer == nil { + p.rtspsServer, err = newRTSPServer( + p.conf.RTSPSAddress, + p.conf.AuthMethods, + p.conf.ReadTimeout, + p.conf.WriteTimeout, + p.conf.WriteQueueSize, + false, + false, + "", + "", + "", + 0, + 0, + true, + p.conf.ServerCert, + p.conf.ServerKey, + p.conf.RTSPAddress, + p.conf.Protocols, + p.conf.RunOnConnect, + p.conf.RunOnConnectRestart, + p.externalCmdPool, + p.metrics, + p.pathManager, + p, + ) + if err != nil { + return err } } if p.conf.RTMP && (p.conf.RTMPEncryption == conf.EncryptionNo || - p.conf.RTMPEncryption == conf.EncryptionOptional) { - if p.rtmpServer == nil { - p.rtmpServer, err = newRTMPServer( - p.conf.RTMPAddress, - p.conf.ReadTimeout, - p.conf.WriteTimeout, - p.conf.WriteQueueSize, - false, - "", - "", - p.conf.RTSPAddress, - p.conf.RunOnConnect, - p.conf.RunOnConnectRestart, - p.externalCmdPool, - p.metrics, - p.pathManager, - p, - ) - if err != nil { - return err - } + p.conf.RTMPEncryption == conf.EncryptionOptional) && + p.rtmpServer == nil { + p.rtmpServer, err = newRTMPServer( + p.conf.RTMPAddress, + p.conf.ReadTimeout, + p.conf.WriteTimeout, + p.conf.WriteQueueSize, + false, + "", + "", + p.conf.RTSPAddress, + p.conf.RunOnConnect, + p.conf.RunOnConnectRestart, + p.externalCmdPool, + p.metrics, + p.pathManager, + p, + ) + if err != nil { + return err } } if p.conf.RTMP && (p.conf.RTMPEncryption == conf.EncryptionStrict || - p.conf.RTMPEncryption == conf.EncryptionOptional) { - if p.rtmpsServer == nil { - p.rtmpsServer, err = newRTMPServer( - p.conf.RTMPSAddress, - p.conf.ReadTimeout, - p.conf.WriteTimeout, - p.conf.WriteQueueSize, - true, - p.conf.RTMPServerCert, - p.conf.RTMPServerKey, - p.conf.RTSPAddress, - p.conf.RunOnConnect, - p.conf.RunOnConnectRestart, - p.externalCmdPool, - p.metrics, - p.pathManager, - p, - ) - if err != nil { - return err - } + p.conf.RTMPEncryption == conf.EncryptionOptional) && + p.rtmpsServer == nil { + p.rtmpsServer, err = newRTMPServer( + p.conf.RTMPSAddress, + p.conf.ReadTimeout, + p.conf.WriteTimeout, + p.conf.WriteQueueSize, + true, + p.conf.RTMPServerCert, + p.conf.RTMPServerKey, + p.conf.RTSPAddress, + p.conf.RunOnConnect, + p.conf.RunOnConnectRestart, + p.externalCmdPool, + p.metrics, + p.pathManager, + p, + ) + if err != nil { + return err } } - if p.conf.HLS { - if p.hlsManager == nil { - p.hlsManager, err = newHLSManager( - p.conf.HLSAddress, - p.conf.HLSEncryption, - p.conf.HLSServerKey, - p.conf.HLSServerCert, - p.conf.ExternalAuthenticationURL, - p.conf.HLSAlwaysRemux, - p.conf.HLSVariant, - p.conf.HLSSegmentCount, - p.conf.HLSSegmentDuration, - p.conf.HLSPartDuration, - p.conf.HLSSegmentMaxSize, - p.conf.HLSAllowOrigin, - p.conf.HLSTrustedProxies, - p.conf.HLSDirectory, - p.conf.ReadTimeout, - p.conf.WriteQueueSize, - p.pathManager, - p.metrics, - p, - ) - if err != nil { - return err - } + if p.conf.HLS && + p.hlsManager == nil { + p.hlsManager, err = newHLSManager( + p.conf.HLSAddress, + p.conf.HLSEncryption, + p.conf.HLSServerKey, + p.conf.HLSServerCert, + p.conf.ExternalAuthenticationURL, + p.conf.HLSAlwaysRemux, + p.conf.HLSVariant, + p.conf.HLSSegmentCount, + p.conf.HLSSegmentDuration, + p.conf.HLSPartDuration, + p.conf.HLSSegmentMaxSize, + p.conf.HLSAllowOrigin, + p.conf.HLSTrustedProxies, + p.conf.HLSDirectory, + p.conf.ReadTimeout, + p.conf.WriteQueueSize, + p.pathManager, + p.metrics, + p, + ) + if err != nil { + return err } } - if p.conf.WebRTC { - if p.webRTCManager == nil { - p.webRTCManager, err = newWebRTCManager( - p.conf.WebRTCAddress, - p.conf.WebRTCEncryption, - p.conf.WebRTCServerKey, - p.conf.WebRTCServerCert, - p.conf.WebRTCAllowOrigin, - p.conf.WebRTCTrustedProxies, - p.conf.WebRTCICEServers2, - p.conf.ReadTimeout, - p.conf.WriteQueueSize, - p.conf.WebRTCICEHostNAT1To1IPs, - p.conf.WebRTCICEUDPMuxAddress, - p.conf.WebRTCICETCPMuxAddress, - p.pathManager, - p.metrics, - p, - ) - if err != nil { - return err - } + if p.conf.WebRTC && + p.webRTCManager == nil { + p.webRTCManager, err = newWebRTCManager( + p.conf.WebRTCAddress, + p.conf.WebRTCEncryption, + p.conf.WebRTCServerKey, + p.conf.WebRTCServerCert, + p.conf.WebRTCAllowOrigin, + p.conf.WebRTCTrustedProxies, + p.conf.WebRTCICEServers2, + p.conf.ReadTimeout, + p.conf.WriteQueueSize, + p.conf.WebRTCICEHostNAT1To1IPs, + p.conf.WebRTCICEUDPMuxAddress, + p.conf.WebRTCICETCPMuxAddress, + p.pathManager, + p.metrics, + p, + ) + if err != nil { + return err } } - if p.conf.SRT { - if p.srtServer == nil { - p.srtServer, err = newSRTServer( - p.conf.SRTAddress, - p.conf.ReadTimeout, - p.conf.WriteTimeout, - p.conf.WriteQueueSize, - p.conf.UDPMaxPayloadSize, - p.externalCmdPool, - p.pathManager, - p, - ) - if err != nil { - return err - } + if p.conf.SRT && + p.srtServer == nil { + p.srtServer, err = newSRTServer( + p.conf.SRTAddress, + p.conf.ReadTimeout, + p.conf.WriteTimeout, + p.conf.WriteQueueSize, + p.conf.UDPMaxPayloadSize, + p.externalCmdPool, + p.pathManager, + p, + ) + if err != nil { + return err } } - if p.conf.API { - if p.api == nil { - p.api, err = newAPI( - p.conf.APIAddress, - p.conf.ReadTimeout, - p.conf, - p.pathManager, - p.rtspServer, - p.rtspsServer, - p.rtmpServer, - p.rtmpsServer, - p.hlsManager, - p.webRTCManager, - p.srtServer, - p, - ) - if err != nil { - return err - } + if p.conf.API && + p.api == nil { + p.api, err = newAPI( + p.conf.APIAddress, + p.conf.ReadTimeout, + p.conf, + p.pathManager, + p.rtspServer, + p.rtspsServer, + p.rtmpServer, + p.rtmpsServer, + p.hlsManager, + p.webRTCManager, + p.srtServer, + p, + ) + if err != nil { + return err } } diff --git a/internal/core/core_test.go b/internal/core/core_test.go index 634e31eb..8bf20d63 100644 --- a/internal/core/core_test.go +++ b/internal/core/core_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/stretchr/testify/require" ) @@ -111,7 +111,8 @@ func TestCoreHotReloading(t *testing.T) { medi := testMediaH264 c := gortsplib.Client{} - err = c.StartRecording("rtsp://localhost:8554/test1", media.Medias{medi}) + err = c.StartRecording("rtsp://localhost:8554/test1", + &description.Session{Medias: []*description.Media{medi}}) require.EqualError(t, err, "bad status code: 401 (Unauthorized)") }() @@ -126,7 +127,8 @@ func TestCoreHotReloading(t *testing.T) { medi := testMediaH264 conn := gortsplib.Client{} - err = conn.StartRecording("rtsp://localhost:8554/test1", media.Medias{medi}) + err = conn.StartRecording("rtsp://localhost:8554/test1", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer conn.Close() }() diff --git a/internal/core/hls_manager_test.go b/internal/core/hls_manager_test.go index 91724a96..332f5953 100644 --- a/internal/core/hls_manager_test.go +++ b/internal/core/hls_manager_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/pion/rtp" "github.com/stretchr/testify/require" ) @@ -118,9 +118,9 @@ func TestHLSRead(t *testing.T) { require.Equal(t, true, ok) defer p.Close() - medi := &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi := &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, SPS: []byte{ // 1920x1080 baseline @@ -136,7 +136,8 @@ func TestHLSRead(t *testing.T) { source := gortsplib.Client{ Transport: &v, } - err := source.StartRecording("rtsp://localhost:8554/stream", media.Medias{medi}) + err := source.StartRecording("rtsp://localhost:8554/stream", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() diff --git a/internal/core/hls_muxer.go b/internal/core/hls_muxer.go index f1233b77..cf495d1d 100644 --- a/internal/core/hls_muxer.go +++ b/internal/core/hls_muxer.go @@ -13,9 +13,9 @@ import ( "github.com/bluenviron/gohlslib" "github.com/bluenviron/gohlslib/pkg/codecs" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer" "github.com/gin-gonic/gin" "github.com/bluenviron/mediamtx/internal/conf" @@ -256,7 +256,7 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{}) m.ringBuffer, _ = ringbuffer.New(uint64(m.writeQueueSize)) - var medias media.Medias + var medias []*description.Media videoMedia, videoTrack := m.createVideoTrack(res.stream) if videoMedia != nil { @@ -335,14 +335,11 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{}) } } -func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohlslib.Track) { - var videoFormatAV1 *formats.AV1 - videoMedia := stream.Medias().FindFormat(&videoFormatAV1) +func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) { + var videoFormatAV1 *format.AV1 + videoMedia := stream.Desc().FindFormat(&videoFormatAV1) if videoFormatAV1 != nil { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(m, videoMedia, videoFormatAV1, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.AV1) @@ -351,12 +348,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - - pts := tunit.PTS - startPTS + pts := tunit.PTS err := m.muxer.WriteAV1(tunit.NTP, pts, tunit.TU) if err != nil { return fmt.Errorf("muxer error: %v", err) @@ -371,13 +363,10 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls } } - var videoFormatVP9 *formats.VP9 - videoMedia = stream.Medias().FindFormat(&videoFormatVP9) + var videoFormatVP9 *format.VP9 + videoMedia = stream.Desc().FindFormat(&videoFormatVP9) if videoFormatVP9 != nil { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(m, videoMedia, videoFormatVP9, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.VP9) @@ -386,12 +375,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - - pts := tunit.PTS - startPTS + pts := tunit.PTS err := m.muxer.WriteVP9(tunit.NTP, pts, tunit.Frame) if err != nil { return fmt.Errorf("muxer error: %v", err) @@ -406,13 +390,10 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls } } - var videoFormatH265 *formats.H265 - videoMedia = stream.Medias().FindFormat(&videoFormatH265) + var videoFormatH265 *format.H265 + videoMedia = stream.Desc().FindFormat(&videoFormatH265) if videoFormatH265 != nil { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(m, videoMedia, videoFormatH265, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.H265) @@ -421,12 +402,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - - pts := tunit.PTS - startPTS + pts := tunit.PTS err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU) if err != nil { return fmt.Errorf("muxer error: %v", err) @@ -447,13 +423,10 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls } } - var videoFormatH264 *formats.H264 - videoMedia = stream.Medias().FindFormat(&videoFormatH264) + var videoFormatH264 *format.H264 + videoMedia = stream.Desc().FindFormat(&videoFormatH264) if videoFormatH264 != nil { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(m, videoMedia, videoFormatH264, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.H264) @@ -462,12 +435,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - - pts := tunit.PTS - startPTS + pts := tunit.PTS err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU) if err != nil { return fmt.Errorf("muxer error: %v", err) @@ -490,24 +458,16 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls return nil, nil } -func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohlslib.Track) { - var audioFormatOpus *formats.Opus - audioMedia := stream.Medias().FindFormat(&audioFormatOpus) +func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) { + var audioFormatOpus *format.Opus + audioMedia := stream.Desc().FindFormat(&audioFormatOpus) if audioMedia != nil { - audioStartPTSFilled := false - var audioStartPTS time.Duration - stream.AddReader(m, audioMedia, audioFormatOpus, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.Opus) - if !audioStartPTSFilled { - audioStartPTSFilled = true - audioStartPTS = tunit.PTS - } - - pts := tunit.PTS - audioStartPTS + pts := tunit.PTS err := m.muxer.WriteOpus( tunit.NTP, pts, @@ -532,13 +492,10 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls } } - var audioFormatMPEG4AudioGeneric *formats.MPEG4AudioGeneric - audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioGeneric) + var audioFormatMPEG4AudioGeneric *format.MPEG4AudioGeneric + audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioGeneric) if audioMedia != nil { - audioStartPTSFilled := false - var audioStartPTS time.Duration - stream.AddReader(m, audioMedia, audioFormatMPEG4AudioGeneric, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.MPEG4AudioGeneric) @@ -547,12 +504,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls return nil } - if !audioStartPTSFilled { - audioStartPTSFilled = true - audioStartPTS = tunit.PTS - } - - pts := tunit.PTS - audioStartPTS + pts := tunit.PTS err := m.muxer.WriteMPEG4Audio( tunit.NTP, pts, @@ -572,16 +524,13 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls } } - var audioFormatMPEG4AudioLATM *formats.MPEG4AudioLATM - audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioLATM) + var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM + audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioLATM) if audioMedia != nil && audioFormatMPEG4AudioLATM.Config != nil && len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 && len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 { - audioStartPTSFilled := false - var audioStartPTS time.Duration - stream.AddReader(m, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) { m.ringBuffer.Push(func() error { tunit := u.(*unit.MPEG4AudioLATM) @@ -590,12 +539,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls return nil } - if !audioStartPTSFilled { - audioStartPTSFilled = true - audioStartPTS = tunit.PTS - } - - pts := tunit.PTS - audioStartPTS + pts := tunit.PTS err := m.muxer.WriteMPEG4Audio( tunit.NTP, pts, diff --git a/internal/core/hls_source.go b/internal/core/hls_source.go index 0029eb5c..40d05efb 100644 --- a/internal/core/hls_source.go +++ b/internal/core/hls_source.go @@ -7,8 +7,8 @@ import ( "github.com/bluenviron/gohlslib" "github.com/bluenviron/gohlslib/pkg/codecs" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" @@ -69,48 +69,48 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan s.Log(logger.Warn, err.Error()) }, OnTracks: func(tracks []*gohlslib.Track) error { - var medias media.Medias + var medias []*description.Media for _, track := range tracks { - var medi *media.Media + var medi *description.Media switch tcodec := track.Codec.(type) { case *codecs.AV1: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.AV1{}}, + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.AV1{}}, } c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) { stream.WriteUnit(medi, medi.Formats[0], &unit.AV1{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - TU: tu, + TU: tu, }) }) case *codecs.VP9: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.VP9{}}, + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.VP9{}}, } c.OnDataVP9(track, func(pts time.Duration, frame []byte) { stream.WriteUnit(medi, medi.Formats[0], &unit.VP9{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, Frame: frame, }) }) case *codecs.H264: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, SPS: tcodec.SPS, @@ -122,16 +122,16 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - AU: au, + AU: au, }) }) case *codecs.H265: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H265{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H265{ PayloadTyp: 96, VPS: tcodec.VPS, SPS: tcodec.SPS, @@ -143,16 +143,16 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - AU: au, + AU: au, }) }) case *codecs.MPEG4Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG4Audio{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG4Audio{ PayloadTyp: 96, SizeLength: 13, IndexLength: 3, @@ -165,16 +165,16 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, AUs: aus, }) }) case *codecs.Opus: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.Opus{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.Opus{ PayloadTyp: 96, IsStereo: (tcodec.ChannelCount == 2), }}, @@ -184,8 +184,8 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, Packets: packets, }) }) @@ -195,7 +195,7 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan } res := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if res.err != nil { diff --git a/internal/core/hls_source_test.go b/internal/core/hls_source_test.go index 5bc3aaef..44e4cb62 100644 --- a/internal/core/hls_source_test.go +++ b/internal/core/hls_source_test.go @@ -8,10 +8,10 @@ import ( "net/http" "testing" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/gin-gonic/gin" @@ -135,25 +135,25 @@ func TestHLSSource(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - require.Equal(t, media.Medias{ + require.Equal(t, []*description.Media{ { - Type: media.TypeVideo, - Control: medias[0].Control, - Formats: []formats.Format{ - &formats.H264{ + Type: description.MediaTypeVideo, + Control: desc.Medias[0].Control, + Formats: []format.Format{ + &format.H264{ PayloadTyp: 96, PacketizationMode: 1, }, }, }, { - Type: media.TypeAudio, - Control: medias[1].Control, - Formats: []formats.Format{ - &formats.MPEG4Audio{ + Type: description.MediaTypeAudio, + Control: desc.Medias[1].Control, + Formats: []format.Format{ + &format.MPEG4Audio{ PayloadTyp: 96, ProfileLevelID: 1, Config: &mpeg4audio.Config{ @@ -167,12 +167,12 @@ func TestHLSSource(t *testing.T) { }, }, }, - }, medias) + }, desc.Medias) - var forma *formats.H264 - medi := medias.FindFormat(&forma) + var forma *format.H264 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { diff --git a/internal/core/metrics.go b/internal/core/metrics.go index 84290b4b..a05fe218 100644 --- a/internal/core/metrics.go +++ b/internal/core/metrics.go @@ -232,15 +232,15 @@ func (m *metrics) setHLSManager(s apiHLSManager) { m.hlsManager = s } -// rtspServerSet is called by rtspServer (plain). -func (m *metrics) rtspServerSet(s apiRTSPServer) { +// setRTSPServer is called by rtspServer (plain). +func (m *metrics) setRTSPServer(s apiRTSPServer) { m.mutex.Lock() defer m.mutex.Unlock() m.rtspServer = s } -// rtspsServerSet is called by rtspServer (tls). -func (m *metrics) rtspsServerSet(s apiRTSPServer) { +// setRTSPSServer is called by rtspServer (tls). +func (m *metrics) setRTSPSServer(s apiRTSPServer) { m.mutex.Lock() defer m.mutex.Unlock() m.rtspsServer = s diff --git a/internal/core/metrics_test.go b/internal/core/metrics_test.go index 709f96d3..dc335bf6 100644 --- a/internal/core/metrics_test.go +++ b/internal/core/metrics_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/stretchr/testify/require" "github.com/bluenviron/mediamtx/internal/rtmp" @@ -69,13 +69,13 @@ webrtc_sessions_bytes_sent 0 source := gortsplib.Client{} err = source.StartRecording("rtsp://localhost:8554/rtsp_path", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}} err = source2.StartRecording("rtsps://localhost:8322/rtsps_path", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source2.Close() @@ -89,7 +89,7 @@ webrtc_sessions_bytes_sent 0 conn, err := rtmp.NewClientConn(nconn, u, true) require.NoError(t, err) - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ // 1920x1080 baseline 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, diff --git a/internal/core/path.go b/internal/core/path.go index 3f12b453..054dcbe8 100644 --- a/internal/core/path.go +++ b/internal/core/path.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "time" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/externalcmd" @@ -56,7 +56,7 @@ type pathSourceStaticSetReadyRes struct { } type pathSourceStaticSetReadyReq struct { - medias media.Medias + desc *description.Session generateRTPPackets bool res chan pathSourceStaticSetReadyRes } @@ -135,7 +135,7 @@ type pathStartPublisherRes struct { type pathStartPublisherReq struct { author publisher - medias media.Medias + desc *description.Session generateRTPPackets bool res chan pathStartPublisherRes } @@ -589,10 +589,10 @@ func (pa *path) onDemandStopPublisher() { } } -func (pa *path) setReady(medias media.Medias, allocateEncoder bool) error { +func (pa *path) setReady(desc *description.Session, allocateEncoder bool) error { stream, err := stream.New( pa.udpMaxPayloadSize, - medias, + desc, allocateEncoder, pa.bytesReceived, pa.source, @@ -654,7 +654,7 @@ func (pa *path) doPublisherRemove() { } func (pa *path) handleSourceStaticSetReady(req pathSourceStaticSetReadyReq) { - err := pa.setReady(req.medias, req.generateRTPPackets) + err := pa.setReady(req.desc, req.generateRTPPackets) if err != nil { req.res <- pathSourceStaticSetReadyRes{err: err} return @@ -782,7 +782,7 @@ func (pa *path) handleStartPublisher(req pathStartPublisherReq) { return } - err := pa.setReady(req.medias, req.generateRTPPackets) + err := pa.setReady(req.desc, req.generateRTPPackets) if err != nil { req.res <- pathStartPublisherRes{err: err} return @@ -790,7 +790,7 @@ func (pa *path) handleStartPublisher(req pathStartPublisherReq) { req.author.Log(logger.Info, "is publishing to path '%s', %s", pa.name, - sourceMediaInfo(req.medias)) + sourceMediaInfo(req.desc.Medias)) if pa.conf.HasOnDemandPublisher() { pa.onDemandPublisherReadyTimer.Stop() @@ -920,7 +920,7 @@ func (pa *path) handleAPIPathsGet(req pathAPIPathsGetReq) { if pa.stream == nil { return []string{} } - return mediasDescription(pa.stream.Medias()) + return mediasDescription(pa.stream.Desc().Medias) }(), BytesReceived: atomic.LoadUint64(pa.bytesReceived), Readers: func() []interface{} { diff --git a/internal/core/path_manager_test.go b/internal/core/path_manager_test.go index 75464233..ecb81393 100644 --- a/internal/core/path_manager_test.go +++ b/internal/core/path_manager_test.go @@ -10,12 +10,12 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/headers" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/sdp" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/sdp" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/stretchr/testify/require" ) @@ -103,9 +103,9 @@ import ( "os" "os/signal" "syscall" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" ) func main() { @@ -113,9 +113,9 @@ func main() { panic("environment not set") } - medi := &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi := &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, SPS: []byte{0x01, 0x02, 0x03, 0x04}, PPS: []byte{0x01, 0x02, 0x03, 0x04}, @@ -127,7 +127,7 @@ func main() { err := source.StartRecording( "rtsp://localhost:" + os.Getenv("RTSP_PORT") + "/" + os.Getenv("MTX_PATH"), - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) if err != nil { panic(err) } @@ -264,7 +264,7 @@ func TestPathRunOnReady(t *testing.T) { err := c.StartRecording( "rtsp://localhost:8554/test", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer c.Close() @@ -284,7 +284,7 @@ func TestPathMaxReaders(t *testing.T) { source := gortsplib.Client{} err := source.StartRecording( "rtsp://localhost:8554/mystream", - media.Medias{testMediaH264}) + &description.Session{Medias: []*description.Media{testMediaH264}}) require.NoError(t, err) defer source.Close() @@ -298,10 +298,10 @@ func TestPathMaxReaders(t *testing.T) { require.NoError(t, err) defer reader.Close() - medias, baseURL, _, err := reader.Describe(u) + desc, _, err := reader.Describe(u) require.NoError(t, err) - err = reader.SetupAll(medias, baseURL) + err = reader.SetupAll(desc.BaseURL, desc.Medias) if i != 1 { require.NoError(t, err) } else { diff --git a/internal/core/rpicamera_source.go b/internal/core/rpicamera_source.go index 7c754d77..de437276 100644 --- a/internal/core/rpicamera_source.go +++ b/internal/core/rpicamera_source.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" @@ -75,20 +75,20 @@ func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interfa // run implements sourceStaticImpl. func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error { - medi := &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi := &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, }}, } - medias := media.Medias{medi} + medias := []*description.Media{medi} var stream *stream.Stream onData := func(dts time.Duration, au [][]byte) { if stream == nil { res := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if res.err != nil { @@ -101,9 +101,9 @@ func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadCon stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: dts, }, - PTS: dts, - AU: au, + AU: au, }) } diff --git a/internal/core/rtmp_conn.go b/internal/core/rtmp_conn.go index c214421b..9c9fd3fe 100644 --- a/internal/core/rtmp_conn.go +++ b/internal/core/rtmp_conn.go @@ -10,9 +10,9 @@ import ( "sync" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" @@ -247,17 +247,13 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error { ringBuffer.Close() }() - var medias media.Medias - videoFirstIDRFound := false - var videoStartDTS time.Duration + var medias []*description.Media var w *rtmp.Writer videoMedia, videoFormat := c.setupVideo( &w, res.stream, - ringBuffer, - &videoFirstIDRFound, - &videoStartDTS) + ringBuffer) if videoMedia != nil { medias = append(medias, videoMedia) } @@ -265,10 +261,7 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error { audioMedia, audioFormat := c.setupAudio( &w, res.stream, - ringBuffer, - videoFormat, - &videoFirstIDRFound, - &videoStartDTS) + ringBuffer) if audioFormat != nil { medias = append(medias, audioMedia) } @@ -327,15 +320,11 @@ func (c *rtmpConn) setupVideo( w **rtmp.Writer, stream *stream.Stream, ringBuffer *ringbuffer.RingBuffer, - videoFirstIDRFound *bool, - videoStartDTS *time.Duration, -) (*media.Media, formats.Format) { - var videoFormatH264 *formats.H264 - videoMedia := stream.Medias().FindFormat(&videoFormatH264) +) (*description.Media, format.Format) { + var videoFormatH264 *format.H264 + videoMedia := stream.Desc().FindFormat(&videoFormatH264) if videoFormatH264 != nil { - startPTSFilled := false - var startPTS time.Duration var videoDTSExtractor *h264.DTSExtractor stream.AddReader(c, videoMedia, videoFormatH264, func(u unit.Unit) { @@ -346,11 +335,7 @@ func (c *rtmpConn) setupVideo( return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - pts := tunit.PTS - startPTS + pts := tunit.PTS idrPresent := false nonIDRPresent := false @@ -369,12 +354,11 @@ func (c *rtmpConn) setupVideo( var dts time.Duration // wait until we receive an IDR - if !*videoFirstIDRFound { + if videoDTSExtractor == nil { if !idrPresent { return nil } - *videoFirstIDRFound = true videoDTSExtractor = h264.NewDTSExtractor() var err error @@ -382,10 +366,6 @@ func (c *rtmpConn) setupVideo( if err != nil { return err } - - *videoStartDTS = dts - dts = 0 - pts -= *videoStartDTS } else { if !idrPresent && !nonIDRPresent { return nil @@ -396,9 +376,6 @@ func (c *rtmpConn) setupVideo( if err != nil { return err } - - dts -= *videoStartDTS - pts -= *videoStartDTS } c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) @@ -416,17 +393,11 @@ func (c *rtmpConn) setupAudio( w **rtmp.Writer, stream *stream.Stream, ringBuffer *ringbuffer.RingBuffer, - videoFormat formats.Format, - videoFirstIDRFound *bool, - videoStartDTS *time.Duration, -) (*media.Media, formats.Format) { - var audioFormatMPEG4Generic *formats.MPEG4AudioGeneric - audioMedia := stream.Medias().FindFormat(&audioFormatMPEG4Generic) +) (*description.Media, format.Format) { + var audioFormatMPEG4Generic *format.MPEG4AudioGeneric + audioMedia := stream.Desc().FindFormat(&audioFormatMPEG4Generic) if audioMedia != nil { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(c, audioMedia, audioFormatMPEG4Generic, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.MPEG4AudioGeneric) @@ -435,22 +406,7 @@ func (c *rtmpConn) setupAudio( return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - pts := tunit.PTS - startPTS - - if videoFormat != nil { - if !*videoFirstIDRFound { - return nil - } - - pts -= *videoStartDTS - if pts < 0 { - return nil - } - } + pts := tunit.PTS for i, au := range tunit.AUs { c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) @@ -471,16 +427,13 @@ func (c *rtmpConn) setupAudio( return audioMedia, audioFormatMPEG4Generic } - var audioFormatMPEG4AudioLATM *formats.MPEG4AudioLATM - audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioLATM) + var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM + audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioLATM) if audioMedia != nil && audioFormatMPEG4AudioLATM.Config != nil && len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 && len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(c, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.MPEG4AudioLATM) @@ -489,22 +442,7 @@ func (c *rtmpConn) setupAudio( return nil } - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - pts := tunit.PTS - startPTS - - if videoFormat != nil { - if !*videoFirstIDRFound { - return nil - } - - pts -= *videoStartDTS - if pts < 0 { - return nil - } - } + pts := tunit.PTS c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) return (*w).WriteMPEG4Audio(pts, tunit.AU) @@ -514,33 +452,15 @@ func (c *rtmpConn) setupAudio( return audioMedia, audioFormatMPEG4AudioLATM } - var audioFormatMPEG1 *formats.MPEG1Audio - audioMedia = stream.Medias().FindFormat(&audioFormatMPEG1) + var audioFormatMPEG1 *format.MPEG1Audio + audioMedia = stream.Desc().FindFormat(&audioFormatMPEG1) if audioMedia != nil { - startPTSFilled := false - var startPTS time.Duration - stream.AddReader(c, audioMedia, audioFormatMPEG1, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.MPEG1Audio) - if !startPTSFilled { - startPTSFilled = true - startPTS = tunit.PTS - } - pts := tunit.PTS - startPTS - - if videoFormat != nil { - if !*videoFirstIDRFound { - return nil - } - - pts -= *videoStartDTS - if pts < 0 { - return nil - } - } + pts := tunit.PTS for _, frame := range tunit.Frames { var h mpeg1audio.FrameHeader @@ -611,58 +531,58 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error { } videoFormat, audioFormat := r.Tracks() - var medias media.Medias + var medias []*description.Media var stream *stream.Stream if videoFormat != nil { - videoMedia := &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{videoFormat}, + videoMedia := &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{videoFormat}, } medias = append(medias, videoMedia) switch videoFormat.(type) { - case *formats.AV1: + case *format.AV1: r.OnDataAV1(func(pts time.Duration, tu [][]byte) { stream.WriteUnit(videoMedia, videoFormat, &unit.AV1{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - TU: tu, + TU: tu, }) }) - case *formats.VP9: + case *format.VP9: r.OnDataVP9(func(pts time.Duration, frame []byte) { stream.WriteUnit(videoMedia, videoFormat, &unit.VP9{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, Frame: frame, }) }) - case *formats.H265: + case *format.H265: r.OnDataH265(func(pts time.Duration, au [][]byte) { stream.WriteUnit(videoMedia, videoFormat, &unit.H265{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - AU: au, + AU: au, }) }) - case *formats.H264: + case *format.H264: r.OnDataH264(func(pts time.Duration, au [][]byte) { stream.WriteUnit(videoMedia, videoFormat, &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - AU: au, + AU: au, }) }) @@ -672,31 +592,31 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error { } if audioFormat != nil { //nolint:dupl - audioMedia := &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{audioFormat}, + audioMedia := &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{audioFormat}, } medias = append(medias, audioMedia) switch audioFormat.(type) { - case *formats.MPEG4AudioGeneric: + case *format.MPEG4AudioGeneric: r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) { stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, AUs: [][]byte{au}, }) }) - case *formats.MPEG1Audio: + case *format.MPEG1Audio: r.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) { stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, Frames: [][]byte{frame}, }) }) @@ -708,7 +628,7 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error { rres := res.path.startPublisher(pathStartPublisherReq{ author: c, - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if rres.err != nil { diff --git a/internal/core/rtmp_server_test.go b/internal/core/rtmp_server_test.go index 1af97005..42849488 100644 --- a/internal/core/rtmp_server_test.go +++ b/internal/core/rtmp_server_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/stretchr/testify/require" @@ -128,7 +128,7 @@ func TestRTMPServer(t *testing.T) { conn1, err := rtmp.NewClientConn(nconn1, u1, true) require.NoError(t, err) - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ // 1920x1080 baseline 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, @@ -139,7 +139,7 @@ func TestRTMPServer(t *testing.T) { PacketizationMode: 1, } - audioTrack := &formats.MPEG4Audio{ + audioTrack := &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -238,7 +238,7 @@ func TestRTMPServerAuthFail(t *testing.T) { conn1, err := rtmp.NewClientConn(nconn1, u1, true) require.NoError(t, err) - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, @@ -290,7 +290,7 @@ func TestRTMPServerAuthFail(t *testing.T) { conn1, err := rtmp.NewClientConn(nconn1, u1, true) require.NoError(t, err) - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, @@ -343,7 +343,7 @@ func TestRTMPServerAuthFail(t *testing.T) { conn1, err := rtmp.NewClientConn(nconn1, u1, true) require.NoError(t, err) - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, diff --git a/internal/core/rtmp_source.go b/internal/core/rtmp_source.go index 0f0cf382..fb6fcd0d 100644 --- a/internal/core/rtmp_source.go +++ b/internal/core/rtmp_source.go @@ -8,8 +8,8 @@ import ( "net/url" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" @@ -113,25 +113,25 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error { videoFormat, audioFormat := mc.Tracks() - var medias media.Medias + var medias []*description.Media var stream *stream.Stream if videoFormat != nil { - videoMedia := &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{videoFormat}, + videoMedia := &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{videoFormat}, } medias = append(medias, videoMedia) switch videoFormat.(type) { - case *formats.H264: + case *format.H264: mc.OnDataH264(func(pts time.Duration, au [][]byte) { stream.WriteUnit(videoMedia, videoFormat, &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, - AU: au, + AU: au, }) }) @@ -141,31 +141,31 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error { } if audioFormat != nil { //nolint:dupl - audioMedia := &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{audioFormat}, + audioMedia := &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{audioFormat}, } medias = append(medias, audioMedia) switch audioFormat.(type) { - case *formats.MPEG4AudioGeneric: + case *format.MPEG4AudioGeneric: mc.OnDataMPEG4Audio(func(pts time.Duration, au []byte) { stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, AUs: [][]byte{au}, }) }) - case *formats.MPEG1Audio: + case *format.MPEG1Audio: mc.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) { stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{ Base: unit.Base{ NTP: time.Now(), + PTS: pts, }, - PTS: pts, Frames: [][]byte{frame}, }) }) @@ -176,7 +176,7 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error { } res := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if res.err != nil { diff --git a/internal/core/rtmp_source_test.go b/internal/core/rtmp_source_test.go index 561319e5..480eaf8e 100644 --- a/internal/core/rtmp_source_test.go +++ b/internal/core/rtmp_source_test.go @@ -6,9 +6,9 @@ import ( "os" "testing" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -56,7 +56,7 @@ func TestRTMPSource(t *testing.T) { conn, _, _, err := rtmp.NewServerConn(nconn) require.NoError(t, err) - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ // 1920x1080 baseline 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, @@ -67,7 +67,7 @@ func TestRTMPSource(t *testing.T) { PacketizationMode: 1, } - audioTrack := &formats.MPEG4Audio{ + audioTrack := &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -116,13 +116,13 @@ func TestRTMPSource(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - var forma *formats.H264 - medi := medias.FindFormat(&forma) + var forma *format.H264 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { diff --git a/internal/core/rtsp_conn.go b/internal/core/rtsp_conn.go index 0f309314..f8f96e64 100644 --- a/internal/core/rtsp_conn.go +++ b/internal/core/rtsp_conn.go @@ -5,10 +5,10 @@ import ( "net" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/auth" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/headers" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/google/uuid" "github.com/bluenviron/mediamtx/internal/conf" @@ -22,6 +22,8 @@ const ( type rtspConnParent interface { logger.Writer + getISTLS() bool + getServer() *gortsplib.Server } type rtspConn struct { @@ -138,7 +140,7 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx, if c.authNonce == "" { var err error - c.authNonce, err = auth.GenerateNonce2() + c.authNonce, err = auth.GenerateNonce() if err != nil { return &base.Response{ StatusCode: base.StatusInternalServerError, @@ -186,9 +188,16 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx, }, nil, nil } + var stream *gortsplib.ServerStream + if !c.parent.getISTLS() { + stream = res.stream.RTSPStream(c.parent.getServer()) + } else { + stream = res.stream.RTSPSStream(c.parent.getServer()) + } + return &base.Response{ StatusCode: base.StatusOK, - }, res.stream.RTSPStream(), nil + }, stream, nil } func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) { diff --git a/internal/core/rtsp_server.go b/internal/core/rtsp_server.go index f90f4494..51155855 100644 --- a/internal/core/rtsp_server.go +++ b/internal/core/rtsp_server.go @@ -9,10 +9,10 @@ import ( "sync" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/headers" - "github.com/bluenviron/gortsplib/v3/pkg/liberrors" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" "github.com/google/uuid" "github.com/bluenviron/mediamtx/internal/conf" @@ -108,12 +108,11 @@ func newRTSPServer( } s.srv = &gortsplib.Server{ - Handler: s, - ReadTimeout: time.Duration(readTimeout), - WriteTimeout: time.Duration(writeTimeout), - ReadBufferCount: writeQueueSize, - WriteBufferCount: writeQueueSize, - RTSPAddress: address, + Handler: s, + ReadTimeout: time.Duration(readTimeout), + WriteTimeout: time.Duration(writeTimeout), + WriteQueueSize: writeQueueSize, + RTSPAddress: address, } if useUDP { @@ -145,9 +144,9 @@ func newRTSPServer( if metrics != nil { if !isTLS { - metrics.rtspServerSet(s) + metrics.setRTSPServer(s) } else { - metrics.rtspsServerSet(s) + metrics.setRTSPSServer(s) } } @@ -167,6 +166,14 @@ func (s *rtspServer) Log(level logger.Level, format string, args ...interface{}) s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...) } +func (s *rtspServer) getISTLS() bool { + return s.isTLS +} + +func (s *rtspServer) getServer() *gortsplib.Server { + return s.srv +} + func (s *rtspServer) close() { s.Log(logger.Info, "listener is closing") s.ctxCancel() @@ -197,9 +204,9 @@ outer: if s.metrics != nil { if !s.isTLS { - s.metrics.rtspServerSet(nil) + s.metrics.setRTSPServer(nil) } else { - s.metrics.rtspsServerSet(nil) + s.metrics.setRTSPSServer(nil) } } } diff --git a/internal/core/rtsp_server_test.go b/internal/core/rtsp_server_test.go index 1651198e..05df9c06 100644 --- a/internal/core/rtsp_server_test.go +++ b/internal/core/rtsp_server_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/pion/rtp" "github.com/stretchr/testify/require" ) @@ -29,7 +29,7 @@ func TestRTSPServerRunOnConnect(t *testing.T) { err = source.StartRecording( "rtsp://127.0.0.1:8554/mypath", - media.Medias{testMediaH264}) + &description.Session{Medias: []*description.Media{testMediaH264}}) require.NoError(t, err) defer source.Close() @@ -88,7 +88,7 @@ func TestRTSPServer(t *testing.T) { err := source.StartRecording( "rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -107,10 +107,10 @@ func TestRTSPServer(t *testing.T) { require.NoError(t, err) defer reader.Close() - medias, baseURL, _, err := reader.Describe(u) + desc, _, err := reader.Describe(u) require.NoError(t, err) - err = reader.SetupAll(medias, baseURL) + err = reader.SetupAll(desc.BaseURL, desc.Medias) require.NoError(t, err) _, err = reader.Play(nil) @@ -137,7 +137,7 @@ func TestRTSPServerAuthHashed(t *testing.T) { err := source.StartRecording( "rtsp://testuser:testpass@127.0.0.1:8554/test/stream", - media.Medias{medi}) + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() } @@ -181,7 +181,7 @@ func TestRTSPServerAuthFail(t *testing.T) { err := c.StartRecording( "rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream", - media.Medias{medi}, + &description.Session{Medias: []*description.Media{medi}}, ) require.EqualError(t, err, "bad status code: 401 (Unauthorized)") }) @@ -228,7 +228,7 @@ func TestRTSPServerAuthFail(t *testing.T) { require.NoError(t, err) defer c.Close() - _, _, _, err = c.Describe(u) + _, _, err = c.Describe(u) require.EqualError(t, err, "bad status code: 401 (Unauthorized)") }) } @@ -249,7 +249,7 @@ func TestRTSPServerAuthFail(t *testing.T) { err := c.StartRecording( "rtsp://localhost:8554/test/stream", - media.Medias{medi}, + &description.Session{Medias: []*description.Media{medi}}, ) require.EqualError(t, err, "bad status code: 401 (Unauthorized)") }) @@ -270,7 +270,7 @@ func TestRTSPServerAuthFail(t *testing.T) { err := c.StartRecording( "rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value", - media.Medias{medi}, + &description.Session{Medias: []*description.Media{medi}}, ) require.EqualError(t, err, "bad status code: 401 (Unauthorized)") }) @@ -298,13 +298,15 @@ func TestRTSPServerPublisherOverride(t *testing.T) { s1 := gortsplib.Client{} - err := s1.StartRecording("rtsp://localhost:8554/teststream", media.Medias{medi}) + err := s1.StartRecording("rtsp://localhost:8554/teststream", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer s1.Close() s2 := gortsplib.Client{} - err = s2.StartRecording("rtsp://localhost:8554/teststream", media.Medias{medi}) + err = s2.StartRecording("rtsp://localhost:8554/teststream", + &description.Session{Medias: []*description.Media{medi}}) if ca == "enabled" { require.NoError(t, err) defer s2.Close() @@ -323,17 +325,17 @@ func TestRTSPServerPublisherOverride(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - err = c.SetupAll(medias, baseURL) + err = c.SetupAll(desc.BaseURL, desc.Medias) require.NoError(t, err) - c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) { + c.OnPacketRTP(desc.Medias[0], desc.Medias[0].Formats[0], func(pkt *rtp.Packet) { if ca == "enabled" { - require.Equal(t, []byte{0x05, 0x06, 0x07, 0x08}, pkt.Payload) + require.Equal(t, []byte{5, 15, 16, 17, 18}, pkt.Payload) } else { - require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload) + require.Equal(t, []byte{5, 11, 12, 13, 14}, pkt.Payload) } close(frameRecv) }) @@ -354,7 +356,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) { SSRC: 978651231, Marker: true, }, - Payload: []byte{0x05, 0x06, 0x07, 0x08}, + Payload: []byte{5, 15, 16, 17, 18}, }) require.NoError(t, err) } else { @@ -367,7 +369,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) { SSRC: 978651231, Marker: true, }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + Payload: []byte{5, 11, 12, 13, 14}, }) require.NoError(t, err) } @@ -402,7 +404,7 @@ func TestRTSPServerFallback(t *testing.T) { source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/path2", - media.Medias{testMediaH264}) + &description.Session{Medias: []*description.Media{testMediaH264}}) require.NoError(t, err) defer source.Close() @@ -414,9 +416,9 @@ func TestRTSPServerFallback(t *testing.T) { require.NoError(t, err) defer dest.Close() - medias, _, _, err := dest.Describe(u) + desc, _, err := dest.Describe(u) require.NoError(t, err) - require.Equal(t, 1, len(medias)) + require.Equal(t, 1, len(desc.Medias)) }) } } diff --git a/internal/core/rtsp_session.go b/internal/core/rtsp_session.go index 6612a6b3..e2cfcf0c 100644 --- a/internal/core/rtsp_session.go +++ b/internal/core/rtsp_session.go @@ -7,10 +7,10 @@ import ( "sync" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/auth" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/google/uuid" "github.com/pion/rtp" @@ -27,6 +27,8 @@ type rtspSessionPathManager interface { type rtspSessionParent interface { logger.Writer + getISTLS() bool + getServer() *gortsplib.Server } type rtspSession struct { @@ -124,7 +126,7 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno if c.authNonce == "" { var err error - c.authNonce, err = auth.GenerateNonce2() + c.authNonce, err = auth.GenerateNonce() if err != nil { return &base.Response{ StatusCode: base.StatusInternalServerError, @@ -209,7 +211,7 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt if c.authNonce == "" { var err error - c.authNonce, err = auth.GenerateNonce2() + c.authNonce, err = auth.GenerateNonce() if err != nil { return &base.Response{ StatusCode: base.StatusInternalServerError, @@ -257,9 +259,16 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt s.pathName = ctx.Path s.mutex.Unlock() + var stream *gortsplib.ServerStream + if !s.parent.getISTLS() { + stream = res.stream.RTSPStream(s.parent.getServer()) + } else { + stream = res.stream.RTSPSStream(s.parent.getServer()) + } + return &base.Response{ StatusCode: base.StatusOK, - }, res.stream.RTSPStream(), nil + }, stream, nil default: // record return &base.Response{ @@ -308,7 +317,7 @@ func (s *rtspSession) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Respons func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { res := s.path.startPublisher(pathStartPublisherReq{ author: s, - medias: s.session.AnnouncedMedias(), + desc: s.session.AnnouncedDescription(), generateRTPPackets: false, }) if res.err != nil { @@ -319,13 +328,18 @@ func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Res s.stream = res.stream - for _, medi := range s.session.AnnouncedMedias() { + for _, medi := range s.session.AnnouncedDescription().Medias { for _, forma := range medi.Formats { cmedi := medi cforma := forma s.session.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { - res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now()) + pts, ok := s.session.PacketPTS(cmedi, pkt) + if !ok { + return + } + + res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now(), pts) }) } } diff --git a/internal/core/rtsp_source.go b/internal/core/rtsp_source.go index bf625500..6d536ec0 100644 --- a/internal/core/rtsp_source.go +++ b/internal/core/rtsp_source.go @@ -4,12 +4,12 @@ import ( "context" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/headers" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/pion/rtp" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" ) @@ -95,13 +95,12 @@ func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf cha s.Log(logger.Debug, "connecting") c := &gortsplib.Client{ - Transport: cnf.SourceProtocol.Transport, - TLSConfig: tlsConfigForFingerprint(cnf.SourceFingerprint), - ReadTimeout: time.Duration(s.readTimeout), - WriteTimeout: time.Duration(s.writeTimeout), - ReadBufferCount: s.writeQueueSize, - WriteBufferCount: s.writeQueueSize, - AnyPortEnable: cnf.SourceAnyPortEnable, + Transport: cnf.SourceProtocol.Transport, + TLSConfig: tlsConfigForFingerprint(cnf.SourceFingerprint), + ReadTimeout: time.Duration(s.readTimeout), + WriteTimeout: time.Duration(s.writeTimeout), + WriteQueueSize: s.writeQueueSize, + AnyPortEnable: cnf.SourceAnyPortEnable, OnRequest: func(req *base.Request) { s.Log(logger.Debug, "c->s %v", req) }, @@ -133,18 +132,18 @@ func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf cha readErr := make(chan error) go func() { readErr <- func() error { - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) if err != nil { return err } - err = c.SetupAll(medias, baseURL) + err = c.SetupAll(desc.BaseURL, desc.Medias) if err != nil { return err } res := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: desc, generateRTPPackets: false, }) if res.err != nil { @@ -153,13 +152,18 @@ func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf cha defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{}) - for _, medi := range medias { + for _, medi := range desc.Medias { for _, forma := range medi.Formats { cmedi := medi cforma := forma c.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { - res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now()) + pts, ok := c.PacketPTS(cmedi, pkt) + if !ok { + return + } + + res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now(), pts) }) } } diff --git a/internal/core/rtsp_source_test.go b/internal/core/rtsp_source_test.go index db0d9568..6e203cfb 100644 --- a/internal/core/rtsp_source_test.go +++ b/internal/core/rtsp_source_test.go @@ -6,12 +6,12 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/auth" - "github.com/bluenviron/gortsplib/v3/pkg/base" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/pion/rtp" "github.com/stretchr/testify/require" ) @@ -43,9 +43,9 @@ func TestRTSPSource(t *testing.T) { } { t.Run(source, func(t *testing.T) { serverMedia := testMediaH264 - stream := gortsplib.NewServerStream(media.Medias{serverMedia}) + var stream *gortsplib.ServerStream - nonce, err := auth.GenerateNonce2() + nonce, err := auth.GenerateNonce() require.NoError(t, err) s := gortsplib.Server{ @@ -74,7 +74,7 @@ func TestRTSPSource(t *testing.T) { onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { go func() { time.Sleep(1 * time.Second) - stream.WritePacketRTP(serverMedia, &rtp.Packet{ + err := stream.WritePacketRTP(serverMedia, &rtp.Packet{ Header: rtp.Header{ Version: 0x02, PayloadType: 96, @@ -83,8 +83,9 @@ func TestRTSPSource(t *testing.T) { SSRC: 978651231, Marker: true, }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + Payload: []byte{5, 1, 2, 3, 4}, }) + require.NoError(t, err) }() return &base.Response{ @@ -120,6 +121,9 @@ func TestRTSPSource(t *testing.T) { defer s.Wait() //nolint:errcheck defer s.Close() + stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{serverMedia}}) + defer stream.Close() + if source == "udp" || source == "tcp" { p, ok := newInstance("paths:\n" + " proxied:\n" + @@ -149,17 +153,17 @@ func TestRTSPSource(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - var forma *formats.H264 - medi := medias.FindFormat(&forma) + var forma *format.H264 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { - require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload) + require.Equal(t, []byte{5, 1, 2, 3, 4}, pkt.Payload) close(received) }) @@ -172,9 +176,9 @@ func TestRTSPSource(t *testing.T) { } func TestRTSPSourceNoPassword(t *testing.T) { - stream := gortsplib.NewServerStream(media.Medias{testMediaH264}) + var stream *gortsplib.ServerStream - nonce, err := auth.GenerateNonce2() + nonce, err := auth.GenerateNonce() require.NoError(t, err) done := make(chan struct{}) @@ -210,11 +214,15 @@ func TestRTSPSourceNoPassword(t *testing.T) { }, RTSPAddress: "127.0.0.1:8555", } + err = s.Start() require.NoError(t, err) defer s.Wait() //nolint:errcheck defer s.Close() + stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}}) + defer stream.Close() + p, ok := newInstance("rtmp: no\n" + "hls: no\n" + "webrtc: no\n" + @@ -231,7 +239,7 @@ func TestRTSPSourceNoPassword(t *testing.T) { func TestRTSPSourceRange(t *testing.T) { for _, ca := range []string{"clock", "npt", "smpte"} { t.Run(ca, func(t *testing.T) { - stream := gortsplib.NewServerStream(media.Medias{testMediaH264}) + var stream *gortsplib.ServerStream done := make(chan struct{}) s := gortsplib.Server{ @@ -266,11 +274,15 @@ func TestRTSPSourceRange(t *testing.T) { }, RTSPAddress: "127.0.0.1:8555", } + err := s.Start() require.NoError(t, err) defer s.Wait() //nolint:errcheck defer s.Close() + stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}}) + defer stream.Close() + var addConf string switch ca { case "clock": diff --git a/internal/core/source.go b/internal/core/source.go index cf115fa7..d4b76ef3 100644 --- a/internal/core/source.go +++ b/internal/core/source.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/mediamtx/internal/logger" ) @@ -19,7 +19,7 @@ type source interface { apiSourceDescribe() pathAPISourceOrReader } -func mediaDescription(media *media.Media) string { +func mediaDescription(media *description.Media) string { ret := make([]string, len(media.Formats)) for i, forma := range media.Formats { ret[i] = forma.Codec() @@ -27,7 +27,7 @@ func mediaDescription(media *media.Media) string { return strings.Join(ret, "/") } -func mediasDescription(medias media.Medias) []string { +func mediasDescription(medias []*description.Media) []string { ret := make([]string, len(medias)) for i, media := range medias { ret[i] = mediaDescription(media) @@ -35,7 +35,7 @@ func mediasDescription(medias media.Medias) []string { return ret } -func sourceMediaInfo(medias media.Medias) string { +func sourceMediaInfo(medias []*description.Media) string { return fmt.Sprintf("%d %s (%s)", len(medias), func() string { diff --git a/internal/core/source_static.go b/internal/core/source_static.go index 560350d7..0079c242 100644 --- a/internal/core/source_static.go +++ b/internal/core/source_static.go @@ -224,7 +224,7 @@ func (s *sourceStatic) setReady(req pathSourceStaticSetReadyReq) pathSourceStati res := <-req.res if res.err == nil { - s.impl.Log(logger.Info, "ready: %s", sourceMediaInfo(req.medias)) + s.impl.Log(logger.Info, "ready: %s", sourceMediaInfo(req.desc.Medias)) } return res diff --git a/internal/core/srt_conn.go b/internal/core/srt_conn.go index 9dcd54eb..3d18a6b7 100644 --- a/internal/core/srt_conn.go +++ b/internal/core/srt_conn.go @@ -10,9 +10,9 @@ import ( "sync" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" @@ -235,7 +235,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { return err } - var medias media.Medias + var medias []*description.Media //nolint:prealloc var stream *stream.Stream var td *mpegts.TimeDecoder @@ -247,13 +247,13 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { } for _, track := range r.Tracks() { //nolint:dupl - var medi *media.Media + var medi *description.Media switch tcodec := track.Codec.(type) { case *mpegts.CodecH264: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, }}, @@ -263,17 +263,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), - AU: au, + AU: au, }) return nil }) case *mpegts.CodecH265: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H265{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H265{ PayloadTyp: 96, }}, } @@ -282,17 +282,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), - AU: au, + AU: au, }) return nil }) case *mpegts.CodecMPEG4Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG4Audio{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG4Audio{ PayloadTyp: 96, SizeLength: 13, IndexLength: 3, @@ -305,17 +305,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), AUs: aus, }) return nil }) case *mpegts.CodecOpus: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.Opus{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.Opus{ PayloadTyp: 96, IsStereo: (tcodec.ChannelCount == 2), }}, @@ -325,25 +325,25 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), Packets: packets, }) return nil }) case *mpegts.CodecMPEG1Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG1Audio{}}, + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG1Audio{}}, } r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), Frames: frames, }) return nil @@ -362,7 +362,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error { rres := path.startPublisher(pathStartPublisherReq{ author: c, - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if rres.err != nil { @@ -424,14 +424,10 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass var w *mpegts.Writer var tracks []*mpegts.Track - var medias media.Medias + var medias []*description.Media bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize)) - leadingTrackChosen := false - leadingTrackInitialized := false - var leadingTrackStartDTS time.Duration - - addTrack := func(medi *media.Media, codec mpegts.Codec) *mpegts.Track { + addTrack := func(medi *description.Media, codec mpegts.Codec) *mpegts.Track { track := &mpegts.Track{ Codec: codec, } @@ -440,37 +436,22 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass return track } - for _, medi := range res.stream.Medias() { - for _, format := range medi.Formats { - switch format := format.(type) { - case *formats.H265: //nolint:dupl + for _, medi := range res.stream.Desc().Medias { + for _, forma := range medi.Formats { + switch forma := forma.(type) { + case *format.H265: //nolint:dupl track := addTrack(medi, &mpegts.CodecH265{}) - var startPTS time.Duration - startPTSFilled := false - - var isLeadingTrack bool - if !leadingTrackChosen { - isLeadingTrack = true - } else { - isLeadingTrack = false - } - randomAccessReceived := false dtsExtractor := h265.NewDTSExtractor() - res.stream.AddReader(c, medi, format, func(u unit.Unit) { + res.stream.AddReader(c, medi, forma, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.H265) if tunit.AU == nil { return nil } - if !startPTSFilled { - startPTS = tunit.PTS - startPTSFilled = true - } - randomAccess := h265.IsRandomAccess(tunit.AU) if !randomAccessReceived { @@ -480,24 +461,12 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass randomAccessReceived = true } - pts := tunit.PTS - startPTS + pts := tunit.PTS dts, err := dtsExtractor.Extract(tunit.AU, pts) if err != nil { return err } - if !leadingTrackInitialized { - if isLeadingTrack { - leadingTrackStartDTS = dts - leadingTrackInitialized = true - } else { - return nil - } - } - - dts -= leadingTrackStartDTS - pts -= leadingTrackStartDTS - sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), randomAccess, tunit.AU) if err != nil { @@ -507,34 +476,19 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass }) }) - case *formats.H264: //nolint:dupl + case *format.H264: //nolint:dupl track := addTrack(medi, &mpegts.CodecH264{}) - var startPTS time.Duration - startPTSFilled := false - - var isLeadingTrack bool - if !leadingTrackChosen { - isLeadingTrack = true - } else { - isLeadingTrack = false - } - firstIDRReceived := false dtsExtractor := h264.NewDTSExtractor() - res.stream.AddReader(c, medi, format, func(u unit.Unit) { + res.stream.AddReader(c, medi, forma, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.H264) if tunit.AU == nil { return nil } - if !startPTSFilled { - startPTS = tunit.PTS - startPTSFilled = true - } - idrPresent := h264.IDRPresent(tunit.AU) if !firstIDRReceived { @@ -544,24 +498,12 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass firstIDRReceived = true } - pts := tunit.PTS - startPTS + pts := tunit.PTS dts, err := dtsExtractor.Extract(tunit.AU, pts) if err != nil { return err } - if !leadingTrackInitialized { - if isLeadingTrack { - leadingTrackStartDTS = dts - leadingTrackInitialized = true - } else { - return nil - } - } - - dts -= leadingTrackStartDTS - pts -= leadingTrackStartDTS - sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), idrPresent, tunit.AU) if err != nil { @@ -571,33 +513,19 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass }) }) - case *formats.MPEG4AudioGeneric: + case *format.MPEG4AudioGeneric: track := addTrack(medi, &mpegts.CodecMPEG4Audio{ - Config: *format.Config, + Config: *forma.Config, }) - var startPTS time.Duration - startPTSFilled := false - - res.stream.AddReader(c, medi, format, func(u unit.Unit) { + res.stream.AddReader(c, medi, forma, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.MPEG4AudioGeneric) if tunit.AUs == nil { return nil } - if !startPTSFilled { - startPTS = tunit.PTS - startPTSFilled = true - } - - if leadingTrackChosen && !leadingTrackInitialized { - return nil - } - pts := tunit.PTS - pts -= startPTS - pts -= leadingTrackStartDTS sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), tunit.AUs) @@ -608,36 +536,22 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass }) }) - case *formats.MPEG4AudioLATM: - if format.Config != nil && - len(format.Config.Programs) == 1 && - len(format.Config.Programs[0].Layers) == 1 { + case *format.MPEG4AudioLATM: + if forma.Config != nil && + len(forma.Config.Programs) == 1 && + len(forma.Config.Programs[0].Layers) == 1 { track := addTrack(medi, &mpegts.CodecMPEG4Audio{ - Config: *format.Config.Programs[0].Layers[0].AudioSpecificConfig, + Config: *forma.Config.Programs[0].Layers[0].AudioSpecificConfig, }) - var startPTS time.Duration - startPTSFilled := false - - res.stream.AddReader(c, medi, format, func(u unit.Unit) { + res.stream.AddReader(c, medi, forma, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.MPEG4AudioLATM) if tunit.AU == nil { return nil } - if !startPTSFilled { - startPTS = tunit.PTS - startPTSFilled = true - } - - if leadingTrackChosen && !leadingTrackInitialized { - return nil - } - pts := tunit.PTS - pts -= startPTS - pts -= leadingTrackStartDTS sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), [][]byte{tunit.AU}) @@ -649,38 +563,24 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass }) } - case *formats.Opus: + case *format.Opus: track := addTrack(medi, &mpegts.CodecOpus{ ChannelCount: func() int { - if format.IsStereo { + if forma.IsStereo { return 2 } return 1 }(), }) - var startPTS time.Duration - startPTSFilled := false - - res.stream.AddReader(c, medi, format, func(u unit.Unit) { + res.stream.AddReader(c, medi, forma, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.Opus) if tunit.Packets == nil { return nil } - if !startPTSFilled { - startPTS = tunit.PTS - startPTSFilled = true - } - - if leadingTrackChosen && !leadingTrackInitialized { - return nil - } - pts := tunit.PTS - pts -= startPTS - pts -= leadingTrackStartDTS sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) err = w.WriteOpus(track, durationGoToMPEGTS(pts), tunit.Packets) @@ -691,31 +591,17 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass }) }) - case *formats.MPEG1Audio: + case *format.MPEG1Audio: track := addTrack(medi, &mpegts.CodecMPEG1Audio{}) - var startPTS time.Duration - startPTSFilled := false - - res.stream.AddReader(c, medi, format, func(u unit.Unit) { + res.stream.AddReader(c, medi, forma, func(u unit.Unit) { ringBuffer.Push(func() error { tunit := u.(*unit.MPEG1Audio) if tunit.Frames == nil { return nil } - if !startPTSFilled { - startPTS = tunit.PTS - startPTSFilled = true - } - - if leadingTrackChosen && !leadingTrackInitialized { - return nil - } - pts := tunit.PTS - pts -= startPTS - pts -= leadingTrackStartDTS sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(pts), tunit.Frames) diff --git a/internal/core/srt_source.go b/internal/core/srt_source.go index 7d67b080..39c8d151 100644 --- a/internal/core/srt_source.go +++ b/internal/core/srt_source.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/datarhei/gosrt" @@ -91,7 +91,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error { return err } - var medias media.Medias + var medias []*description.Media //nolint:prealloc var stream *stream.Stream var td *mpegts.TimeDecoder @@ -103,13 +103,13 @@ func (s *srtSource) runReader(sconn srt.Conn) error { } for _, track := range r.Tracks() { //nolint:dupl - var medi *media.Media + var medi *description.Media switch tcodec := track.Codec.(type) { case *mpegts.CodecH264: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, }}, @@ -119,17 +119,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), - AU: au, + AU: au, }) return nil }) case *mpegts.CodecH265: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H265{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H265{ PayloadTyp: 96, }}, } @@ -138,17 +138,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), - AU: au, + AU: au, }) return nil }) case *mpegts.CodecMPEG4Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG4Audio{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG4Audio{ PayloadTyp: 96, SizeLength: 13, IndexLength: 3, @@ -161,17 +161,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), AUs: aus, }) return nil }) case *mpegts.CodecOpus: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.Opus{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.Opus{ PayloadTyp: 96, IsStereo: (tcodec.ChannelCount == 2), }}, @@ -181,25 +181,25 @@ func (s *srtSource) runReader(sconn srt.Conn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), Packets: packets, }) return nil }) case *mpegts.CodecMPEG1Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG1Audio{}}, + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG1Audio{}}, } r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), Frames: frames, }) return nil @@ -217,7 +217,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error { } res := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if res.err != nil { diff --git a/internal/core/srt_source_test.go b/internal/core/srt_source_test.go index 7cb02e4d..bb67aa25 100644 --- a/internal/core/srt_source_test.go +++ b/internal/core/srt_source_test.go @@ -4,9 +4,9 @@ import ( "bufio" "testing" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/datarhei/gosrt" "github.com/pion/rtp" @@ -82,13 +82,13 @@ func TestSRTSource(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - var forma *formats.H264 - medi := medias.FindFormat(&forma) + var forma *format.H264 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { diff --git a/internal/core/udp_source.go b/internal/core/udp_source.go index 399249aa..94593dcb 100644 --- a/internal/core/udp_source.go +++ b/internal/core/udp_source.go @@ -6,8 +6,8 @@ import ( "net" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" "golang.org/x/net/ipv4" @@ -140,7 +140,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error { return err } - var medias media.Medias + var medias []*description.Media //nolint:prealloc var stream *stream.Stream var td *mpegts.TimeDecoder @@ -152,13 +152,13 @@ func (s *udpSource) runReader(pc net.PacketConn) error { } for _, track := range r.Tracks() { //nolint:dupl - var medi *media.Media + var medi *description.Media switch tcodec := track.Codec.(type) { case *mpegts.CodecH264: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, }}, @@ -168,17 +168,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), - AU: au, + AU: au, }) return nil }) case *mpegts.CodecH265: - medi = &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H265{ + medi = &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H265{ PayloadTyp: 96, }}, } @@ -187,17 +187,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), - AU: au, + AU: au, }) return nil }) case *mpegts.CodecMPEG4Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG4Audio{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG4Audio{ PayloadTyp: 96, SizeLength: 13, IndexLength: 3, @@ -210,17 +210,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), AUs: aus, }) return nil }) case *mpegts.CodecOpus: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.Opus{ + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.Opus{ PayloadTyp: 96, IsStereo: (tcodec.ChannelCount == 2), }}, @@ -230,25 +230,25 @@ func (s *udpSource) runReader(pc net.PacketConn) error { stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), Packets: packets, }) return nil }) case *mpegts.CodecMPEG1Audio: - medi = &media.Media{ - Type: media.TypeAudio, - Formats: []formats.Format{&formats.MPEG1Audio{}}, + medi = &description.Media{ + Type: description.MediaTypeAudio, + Formats: []format.Format{&format.MPEG1Audio{}}, } r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ Base: unit.Base{ NTP: time.Now(), + PTS: decodeTime(pts), }, - PTS: decodeTime(pts), Frames: frames, }) return nil @@ -266,7 +266,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error { } res := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if res.err != nil { diff --git a/internal/core/udp_source_test.go b/internal/core/udp_source_test.go index 7ca370bd..f7900c19 100644 --- a/internal/core/udp_source_test.go +++ b/internal/core/udp_source_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -68,13 +68,13 @@ func TestUDPSource(t *testing.T) { require.NoError(t, err) }() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - var forma *formats.H264 - medi := medias.FindFormat(&forma) + var forma *format.H264 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { diff --git a/internal/core/webrtc_incoming_track.go b/internal/core/webrtc_incoming_track.go index b4613dc4..4f4187f2 100644 --- a/internal/core/webrtc_incoming_track.go +++ b/internal/core/webrtc_incoming_track.go @@ -5,9 +5,11 @@ import ( "strings" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/rtptime" "github.com/pion/rtcp" + "github.com/pion/rtp" "github.com/pion/webrtc/v3" "github.com/bluenviron/mediamtx/internal/stream" @@ -22,9 +24,9 @@ type webRTCIncomingTrack struct { receiver *webrtc.RTPReceiver writeRTCP func([]rtcp.Packet) error - mediaType media.Type - format formats.Format - media *media.Media + mediaType description.MediaType + format format.Format + media *description.Media } func newWebRTCIncomingTrack( @@ -40,49 +42,49 @@ func newWebRTCIncomingTrack( switch strings.ToLower(track.Codec().MimeType) { case strings.ToLower(webrtc.MimeTypeAV1): - t.mediaType = media.TypeVideo - t.format = &formats.AV1{ + t.mediaType = description.MediaTypeVideo + t.format = &format.AV1{ PayloadTyp: uint8(track.PayloadType()), } case strings.ToLower(webrtc.MimeTypeVP9): - t.mediaType = media.TypeVideo - t.format = &formats.VP9{ + t.mediaType = description.MediaTypeVideo + t.format = &format.VP9{ PayloadTyp: uint8(track.PayloadType()), } case strings.ToLower(webrtc.MimeTypeVP8): - t.mediaType = media.TypeVideo - t.format = &formats.VP8{ + t.mediaType = description.MediaTypeVideo + t.format = &format.VP8{ PayloadTyp: uint8(track.PayloadType()), } case strings.ToLower(webrtc.MimeTypeH264): - t.mediaType = media.TypeVideo - t.format = &formats.H264{ + t.mediaType = description.MediaTypeVideo + t.format = &format.H264{ PayloadTyp: uint8(track.PayloadType()), PacketizationMode: 1, } case strings.ToLower(webrtc.MimeTypeOpus): - t.mediaType = media.TypeAudio - t.format = &formats.Opus{ + t.mediaType = description.MediaTypeAudio + t.format = &format.Opus{ PayloadTyp: uint8(track.PayloadType()), } case strings.ToLower(webrtc.MimeTypeG722): - t.mediaType = media.TypeAudio - t.format = &formats.G722{} + t.mediaType = description.MediaTypeAudio + t.format = &format.G722{} case strings.ToLower(webrtc.MimeTypePCMU): - t.mediaType = media.TypeAudio - t.format = &formats.G711{ + t.mediaType = description.MediaTypeAudio + t.format = &format.G711{ MULaw: true, } case strings.ToLower(webrtc.MimeTypePCMA): - t.mediaType = media.TypeAudio - t.format = &formats.G711{ + t.mediaType = description.MediaTypeAudio + t.format = &format.G711{ MULaw: false, } @@ -90,15 +92,29 @@ func newWebRTCIncomingTrack( return nil, fmt.Errorf("unsupported codec: %v", track.Codec()) } - t.media = &media.Media{ + t.media = &description.Media{ Type: t.mediaType, - Formats: []formats.Format{t.format}, + Formats: []format.Format{t.format}, } return t, nil } -func (t *webRTCIncomingTrack) start(stream *stream.Stream) { +type webrtcTrackWrapper struct { + clockRate int +} + +func (w webrtcTrackWrapper) ClockRate() int { + return w.clockRate +} + +func (webrtcTrackWrapper) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +func (t *webRTCIncomingTrack) start(stream *stream.Stream, timeDecoder *rtptime.GlobalDecoder) { + trackWrapper := &webrtcTrackWrapper{clockRate: int(t.track.Codec().ClockRate)} + go func() { for { pkt, _, err := t.track.ReadRTP() @@ -111,7 +127,12 @@ func (t *webRTCIncomingTrack) start(stream *stream.Stream) { continue } - stream.WriteRTPPacket(t.media, t.format, pkt, time.Now()) + pts, ok := timeDecoder.Decode(trackWrapper, pkt) + if !ok { + continue + } + + stream.WriteRTPPacket(t.media, t.format, pkt, time.Now(), pts) } }() @@ -126,7 +147,7 @@ func (t *webRTCIncomingTrack) start(stream *stream.Stream) { } }() - if t.mediaType == media.TypeVideo { + if t.mediaType == description.MediaTypeVideo { go func() { keyframeTicker := time.NewTicker(keyFrameInterval) defer keyframeTicker.Stop() diff --git a/internal/core/webrtc_manager_test.go b/internal/core/webrtc_manager_test.go index c670f1b8..ab2c69a5 100644 --- a/internal/core/webrtc_manager_test.go +++ b/internal/core/webrtc_manager_test.go @@ -7,10 +7,10 @@ import ( "testing" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/pion/rtp" "github.com/pion/webrtc/v3" "github.com/stretchr/testify/require" @@ -193,9 +193,9 @@ func TestWebRTCRead(t *testing.T) { a = newTestHTTPAuthenticator(t, "rtsp", "publish") } - medi := &media.Media{ - Type: media.TypeVideo, - Formats: []formats.Format{&formats.H264{ + medi := &description.Media{ + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ PayloadTyp: 96, PacketizationMode: 1, }}, @@ -206,7 +206,8 @@ func TestWebRTCRead(t *testing.T) { Transport: &v, } err := source.StartRecording( - "rtsp://testpublisher:testpass@localhost:8554/teststream?param=value", media.Medias{medi}) + "rtsp://testpublisher:testpass@localhost:8554/teststream?param=value", + &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() @@ -251,7 +252,7 @@ func TestWebRTCRead(t *testing.T) { Timestamp: 45343, SSRC: 563423, }, - Payload: []byte{3}, + Payload: []byte{5, 3}, }) require.NoError(t, err) @@ -269,7 +270,7 @@ func TestWebRTCRead(t *testing.T) { SSRC: pkt.SSRC, CSRC: []uint32{}, }, - Payload: []byte{3}, + Payload: []byte{5, 3}, }, pkt) }) } @@ -407,13 +408,13 @@ func TestWebRTCPublish(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - var forma *formats.VP8 - medi := medias.FindFormat(&forma) + var forma *format.VP8 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) received := make(chan struct{}) diff --git a/internal/core/webrtc_outgoing_track.go b/internal/core/webrtc_outgoing_track.go index 4cfdf462..aeb7ebdc 100644 --- a/internal/core/webrtc_outgoing_track.go +++ b/internal/core/webrtc_outgoing_track.go @@ -5,13 +5,13 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9" + "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer" "github.com/pion/webrtc/v3" "github.com/bluenviron/mediamtx/internal/stream" @@ -20,15 +20,15 @@ import ( type webRTCOutgoingTrack struct { sender *webrtc.RTPSender - media *media.Media - format formats.Format + media *description.Media + format format.Format track *webrtc.TrackLocalStaticRTP cb func(unit.Unit) error } -func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, error) { - var av1Format *formats.AV1 - videoMedia := medias.FindFormat(&av1Format) +func newWebRTCOutgoingTrackVideo(desc *description.Session) (*webRTCOutgoingTrack, error) { + var av1Format *format.AV1 + videoMedia := desc.FindFormat(&av1Format) if videoMedia != nil { webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( @@ -63,12 +63,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err return nil } - packets, err := encoder.Encode(tunit.TU, tunit.PTS) + packets, err := encoder.Encode(tunit.TU) if err != nil { return nil //nolint:nilerr } for _, pkt := range packets { + pkt.Timestamp = tunit.RTPPackets[0].Timestamp webRTCTrak.WriteRTP(pkt) //nolint:errcheck } @@ -77,10 +78,10 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err }, nil } - var vp9Format *formats.VP9 - videoMedia = medias.FindFormat(&vp9Format) + var vp9Format *format.VP9 + videoMedia = desc.FindFormat(&vp9Format) - if videoMedia != nil { + if videoMedia != nil { //nolint:dupl webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webrtc.RTPCodecCapability{ MimeType: webrtc.MimeTypeVP9, @@ -113,12 +114,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err return nil } - packets, err := encoder.Encode(tunit.Frame, tunit.PTS) + packets, err := encoder.Encode(tunit.Frame) if err != nil { return nil //nolint:nilerr } for _, pkt := range packets { + pkt.Timestamp = tunit.RTPPackets[0].Timestamp webRTCTrak.WriteRTP(pkt) //nolint:errcheck } @@ -127,10 +129,10 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err }, nil } - var vp8Format *formats.VP8 - videoMedia = medias.FindFormat(&vp8Format) + var vp8Format *format.VP8 + videoMedia = desc.FindFormat(&vp8Format) - if videoMedia != nil { + if videoMedia != nil { //nolint:dupl webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webrtc.RTPCodecCapability{ MimeType: webrtc.MimeTypeVP8, @@ -163,12 +165,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err return nil } - packets, err := encoder.Encode(tunit.Frame, tunit.PTS) + packets, err := encoder.Encode(tunit.Frame) if err != nil { return nil //nolint:nilerr } for _, pkt := range packets { + pkt.Timestamp = tunit.RTPPackets[0].Timestamp webRTCTrak.WriteRTP(pkt) //nolint:errcheck } @@ -177,8 +180,8 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err }, nil } - var h264Format *formats.H264 - videoMedia = medias.FindFormat(&h264Format) + var h264Format *format.H264 + videoMedia = desc.FindFormat(&h264Format) if videoMedia != nil { webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( @@ -226,12 +229,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err lastPTS = tunit.PTS } - packets, err := encoder.Encode(tunit.AU, tunit.PTS) + packets, err := encoder.Encode(tunit.AU) if err != nil { return nil //nolint:nilerr } for _, pkt := range packets { + pkt.Timestamp = tunit.RTPPackets[0].Timestamp webRTCTrak.WriteRTP(pkt) //nolint:errcheck } @@ -243,9 +247,9 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err return nil, nil } -func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, error) { - var opusFormat *formats.Opus - audioMedia := medias.FindFormat(&opusFormat) +func newWebRTCOutgoingTrackAudio(desc *description.Session) (*webRTCOutgoingTrack, error) { + var opusFormat *format.Opus + audioMedia := desc.FindFormat(&opusFormat) if audioMedia != nil { webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( @@ -275,8 +279,8 @@ func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, err }, nil } - var g722Format *formats.G722 - audioMedia = medias.FindFormat(&g722Format) + var g722Format *format.G722 + audioMedia = desc.FindFormat(&g722Format) if audioMedia != nil { webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( @@ -305,8 +309,8 @@ func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, err }, nil } - var g711Format *formats.G711 - audioMedia = medias.FindFormat(&g711Format) + var g711Format *format.G711 + audioMedia = desc.FindFormat(&g711Format) if audioMedia != nil { var mtyp string diff --git a/internal/core/webrtc_session.go b/internal/core/webrtc_session.go index c7b68085..4aafc46e 100644 --- a/internal/core/webrtc_session.go +++ b/internal/core/webrtc_session.go @@ -10,8 +10,9 @@ import ( "sync" "time" - "github.com/bluenviron/gortsplib/v3/pkg/media" - "github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer" + "github.com/bluenviron/gortsplib/v4/pkg/rtptime" "github.com/google/uuid" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" @@ -25,16 +26,16 @@ type trackRecvPair struct { receiver *webrtc.RTPReceiver } -func webrtcMediasOfOutgoingTracks(tracks []*webRTCOutgoingTrack) media.Medias { - ret := make(media.Medias, len(tracks)) +func webrtcMediasOfOutgoingTracks(tracks []*webRTCOutgoingTrack) []*description.Media { + ret := make([]*description.Media, len(tracks)) for i, track := range tracks { ret[i] = track.media } return ret } -func webrtcMediasOfIncomingTracks(tracks []*webRTCIncomingTrack) media.Medias { - ret := make(media.Medias, len(tracks)) +func webrtcMediasOfIncomingTracks(tracks []*webRTCIncomingTrack) []*description.Media { + ret := make([]*description.Media, len(tracks)) for i, track := range tracks { ret[i] = track.media } @@ -72,10 +73,10 @@ outer: return nil } -func webrtcGatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, error) { +func webrtcGatherOutgoingTracks(desc *description.Session) ([]*webRTCOutgoingTrack, error) { var tracks []*webRTCOutgoingTrack - videoTrack, err := newWebRTCOutgoingTrackVideo(medias) + videoTrack, err := newWebRTCOutgoingTrackVideo(desc) if err != nil { return nil, err } @@ -84,7 +85,7 @@ func webrtcGatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, er tracks = append(tracks, videoTrack) } - audioTrack, err := newWebRTCOutgoingTrackAudio(medias) + audioTrack, err := newWebRTCOutgoingTrackAudio(desc) if err != nil { return nil, err } @@ -395,15 +396,17 @@ func (s *webRTCSession) runPublish() (int, error) { rres := res.path.startPublisher(pathStartPublisherReq{ author: s, - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: false, }) if rres.err != nil { return 0, rres.err } + timeDecoder := rtptime.NewGlobalDecoder() + for _, track := range tracks { - track.start(rres.stream) + track.start(rres.stream, timeDecoder) } select { @@ -447,7 +450,7 @@ func (s *webRTCSession) runRead() (int, error) { defer res.path.removeReader(pathRemoveReaderReq{author: s}) - tracks, err := webrtcGatherOutgoingTracks(res.stream.Medias()) + tracks, err := webrtcGatherOutgoingTracks(res.stream.Desc()) if err != nil { return http.StatusBadRequest, err } diff --git a/internal/core/webrtc_source.go b/internal/core/webrtc_source.go index fe086e53..0d61cf65 100644 --- a/internal/core/webrtc_source.go +++ b/internal/core/webrtc_source.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/rtptime" "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" @@ -144,7 +146,7 @@ func (s *webRTCSource) run(ctx context.Context, cnf *conf.PathConf, _ chan *conf medias := webrtcMediasOfIncomingTracks(tracks) rres := s.parent.setReady(pathSourceStaticSetReadyReq{ - medias: medias, + desc: &description.Session{Medias: medias}, generateRTPPackets: true, }) if rres.err != nil { @@ -153,8 +155,10 @@ func (s *webRTCSource) run(ctx context.Context, cnf *conf.PathConf, _ chan *conf defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{}) + timeDecoder := rtptime.NewGlobalDecoder() + for _, track := range tracks { - track.start(rres.stream) + track.start(rres.stream, timeDecoder) } select { diff --git a/internal/core/webrtc_source_test.go b/internal/core/webrtc_source_test.go index 2e9c9e0a..0be4bed5 100644 --- a/internal/core/webrtc_source_test.go +++ b/internal/core/webrtc_source_test.go @@ -7,9 +7,9 @@ import ( "net/http" "testing" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/url" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/pion/rtp" "github.com/pion/webrtc/v3" "github.com/stretchr/testify/require" @@ -105,7 +105,7 @@ func TestWebRTCSource(t *testing.T) { Timestamp: 45343, SSRC: 563423, }, - Payload: []byte{1}, + Payload: []byte{5, 1}, }) require.NoError(t, err) @@ -118,7 +118,7 @@ func TestWebRTCSource(t *testing.T) { Timestamp: 45343, SSRC: 563423, }, - Payload: []byte{2}, + Payload: []byte{5, 2}, }) require.NoError(t, err) }() @@ -152,19 +152,19 @@ func TestWebRTCSource(t *testing.T) { require.NoError(t, err) defer c.Close() - medias, baseURL, _, err := c.Describe(u) + desc, _, err := c.Describe(u) require.NoError(t, err) - var forma *formats.VP8 - medi := medias.FindFormat(&forma) + var forma *format.VP8 + medi := desc.FindFormat(&forma) - _, err = c.Setup(medi, baseURL, 0, 0) + _, err = c.Setup(desc.BaseURL, medi, 0, 0) require.NoError(t, err) received := make(chan struct{}) c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { - require.Equal(t, []byte{3}, pkt.Payload) + require.Equal(t, []byte{5, 3}, pkt.Payload) close(received) }) @@ -180,7 +180,7 @@ func TestWebRTCSource(t *testing.T) { Timestamp: 45343, SSRC: 563423, }, - Payload: []byte{3}, + Payload: []byte{5, 3}, }) require.NoError(t, err) diff --git a/internal/formatprocessor/av1.go b/internal/formatprocessor/av1.go index 70d8efe8..bee8aa5e 100644 --- a/internal/formatprocessor/av1.go +++ b/internal/formatprocessor/av1.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -14,7 +14,7 @@ import ( type formatProcessorAV1 struct { udpMaxPayloadSize int - format *formats.AV1 + format *format.AV1 log logger.Writer encoder *rtpav1.Encoder @@ -23,7 +23,7 @@ type formatProcessorAV1 struct { func newAV1( udpMaxPayloadSize int, - forma *formats.AV1, + forma *format.AV1, generateRTPPackets bool, log logger.Writer, ) (*formatProcessorAV1, error) { @@ -69,14 +69,13 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error if hasNonRTSPReaders || t.decoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - // DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups - tu, pts, err := t.decoder.DecodeUntilMarker(pkt) + tu, err := t.decoder.Decode(pkt) if err != nil { if err == rtpav1.ErrNonStartingPacketAndNoPrevious || err == rtpav1.ErrMorePacketsNeeded { return nil @@ -85,7 +84,6 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error } tunit.TU = tu - tunit.PTS = pts } // route packet as is @@ -93,20 +91,22 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error } // encode into RTP - pkts, err := t.encoder.Encode(tunit.TU, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.TU) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts return nil } -func (t *formatProcessorAV1) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorAV1) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.AV1{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/base_unit.go b/internal/formatprocessor/base_unit.go new file mode 100644 index 00000000..e8355375 --- /dev/null +++ b/internal/formatprocessor/base_unit.go @@ -0,0 +1,29 @@ +package formatprocessor + +import ( + "time" + + "github.com/pion/rtp" +) + +// BaseUnit contains fields shared across all units. +type BaseUnit struct { + RTPPackets []*rtp.Packet + NTP time.Time + PTS time.Duration +} + +// GetRTPPackets implements Unit. +func (u *BaseUnit) GetRTPPackets() []*rtp.Packet { + return u.RTPPackets +} + +// GetNTP implements Unit. +func (u *BaseUnit) GetNTP() time.Time { + return u.NTP +} + +// GetPTS implements Unit. +func (u *BaseUnit) GetPTS() time.Duration { + return u.PTS +} diff --git a/internal/formatprocessor/generic.go b/internal/formatprocessor/generic.go index 2d52fca8..06427fc1 100644 --- a/internal/formatprocessor/generic.go +++ b/internal/formatprocessor/generic.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -17,7 +17,7 @@ type formatProcessorGeneric struct { func newGeneric( udpMaxPayloadSize int, - forma formats.Format, + forma format.Format, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorGeneric, error) { @@ -47,11 +47,12 @@ func (t *formatProcessorGeneric) Process(u unit.Unit, _ bool) error { return nil } -func (t *formatProcessorGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.Generic{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/generic_test.go b/internal/formatprocessor/generic_test.go index d9c83044..fc9b3a5f 100644 --- a/internal/formatprocessor/generic_test.go +++ b/internal/formatprocessor/generic_test.go @@ -3,7 +3,7 @@ package formatprocessor import ( "testing" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -11,7 +11,7 @@ import ( ) func TestGenericRemovePadding(t *testing.T) { - forma := &formats.Generic{ + forma := &format.Generic{ PayloadTyp: 96, RTPMa: "private/90000", } diff --git a/internal/formatprocessor/h264.go b/internal/formatprocessor/h264.go index c383c4a1..a88faefc 100644 --- a/internal/formatprocessor/h264.go +++ b/internal/formatprocessor/h264.go @@ -4,8 +4,8 @@ import ( "bytes" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/pion/rtp" @@ -72,7 +72,7 @@ func rtpH264ExtractSPSPPS(pkt *rtp.Packet) ([]byte, []byte) { type formatProcessorH264 struct { udpMaxPayloadSize int - format *formats.H264 + format *format.H264 log logger.Writer encoder *rtph264.Encoder @@ -81,7 +81,7 @@ type formatProcessorH264 struct { func newH264( udpMaxPayloadSize int, - forma *formats.H264, + forma *format.H264, generateRTPPackets bool, log logger.Writer, ) (*formatProcessorH264, error) { @@ -92,7 +92,7 @@ func newH264( } if generateRTPPackets { - err := t.createEncoder(nil, nil, nil) + err := t.createEncoder(nil, nil) if err != nil { return nil, err } @@ -102,14 +102,14 @@ func newH264( } func (t *formatProcessorH264) createEncoder( - ssrc *uint32, initialSequenceNumber *uint16, initialTimestamp *uint32, + ssrc *uint32, + initialSequenceNumber *uint16, ) error { t.encoder = &rtph264.Encoder{ PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadType: t.format.PayloadTyp, SSRC: ssrc, InitialSequenceNumber: initialSequenceNumber, - InitialTimestamp: initialTimestamp, PacketizationMode: t.format.PacketizationMode, } return t.encoder.Init() @@ -240,8 +240,7 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error if pkt.MarshalSize() > t.udpMaxPayloadSize { v1 := pkt.SSRC v2 := pkt.SequenceNumber - v3 := pkt.Timestamp - err := t.createEncoder(&v1, &v2, &v3) + err := t.createEncoder(&v1, &v2) if err != nil { return err } @@ -252,27 +251,24 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - if t.encoder != nil { - tunit.RTPPackets = nil - } - - // DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups - au, pts, err := t.decoder.DecodeUntilMarker(pkt) + au, err := t.decoder.Decode(pkt) if err != nil { if err == rtph264.ErrNonStartingPacketAndNoPrevious || err == rtph264.ErrMorePacketsNeeded { + if t.encoder != nil { + tunit.RTPPackets = nil + } return nil } return err } tunit.AU = t.remuxAccessUnit(au) - tunit.PTS = pts } // route packet as is @@ -286,10 +282,11 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error // encode into RTP if len(tunit.AU) != 0 { - pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.AU) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts } else { tunit.RTPPackets = nil @@ -298,11 +295,12 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error return nil } -func (t *formatProcessorH264) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorH264) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.H264{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/h264_test.go b/internal/formatprocessor/h264_test.go index 44ce749e..c509eedc 100644 --- a/internal/formatprocessor/h264_test.go +++ b/internal/formatprocessor/h264_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -13,7 +13,7 @@ import ( ) func TestH264DynamicParams(t *testing.T) { - forma := &formats.H264{ + forma := &format.H264{ PayloadTyp: 96, PacketizationMode: 1, } @@ -21,10 +21,10 @@ func TestH264DynamicParams(t *testing.T) { p, err := New(1472, forma, false, nil) require.NoError(t, err) - enc, err := forma.CreateEncoder2() + enc, err := forma.CreateEncoder() require.NoError(t, err) - pkts, err := enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}}, 0) + pkts, err := enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}}) require.NoError(t, err) data := &unit.H264{ @@ -39,7 +39,7 @@ func TestH264DynamicParams(t *testing.T) { {byte(h264.NALUTypeIDR)}, }, data.AU) - pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}, 0) // SPS + pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}) // SPS require.NoError(t, err) err = p.Process(&unit.H264{ @@ -49,7 +49,7 @@ func TestH264DynamicParams(t *testing.T) { }, false) require.NoError(t, err) - pkts, err = enc.Encode([][]byte{{8, 1}}, 0) // PPS + pkts, err = enc.Encode([][]byte{{8, 1}}) // PPS require.NoError(t, err) err = p.Process(&unit.H264{ @@ -62,7 +62,7 @@ func TestH264DynamicParams(t *testing.T) { require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS) require.Equal(t, []byte{8, 1}, forma.PPS) - pkts, err = enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}}, 0) + pkts, err = enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}}) require.NoError(t, err) data = &unit.H264{ @@ -81,7 +81,7 @@ func TestH264DynamicParams(t *testing.T) { } func TestH264OversizedPackets(t *testing.T) { - forma := &formats.H264{ + forma := &format.H264{ PayloadTyp: 96, SPS: []byte{0x01, 0x02, 0x03, 0x04}, PPS: []byte{0x01, 0x02, 0x03, 0x04}, @@ -186,7 +186,7 @@ func TestH264OversizedPackets(t *testing.T) { } func TestH264EmptyPacket(t *testing.T) { - forma := &formats.H264{ + forma := &format.H264{ PayloadTyp: 96, PacketizationMode: 1, } diff --git a/internal/formatprocessor/h265.go b/internal/formatprocessor/h265.go index 8b44cbb4..c11b4e5b 100644 --- a/internal/formatprocessor/h265.go +++ b/internal/formatprocessor/h265.go @@ -4,8 +4,8 @@ import ( "bytes" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtph265" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph265" "github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/pion/rtp" @@ -79,7 +79,7 @@ func rtpH265ExtractVPSSPSPPS(pkt *rtp.Packet) ([]byte, []byte, []byte) { type formatProcessorH265 struct { udpMaxPayloadSize int - format *formats.H265 + format *format.H265 log logger.Writer encoder *rtph265.Encoder @@ -88,7 +88,7 @@ type formatProcessorH265 struct { func newH265( udpMaxPayloadSize int, - forma *formats.H265, + forma *format.H265, generateRTPPackets bool, log logger.Writer, ) (*formatProcessorH265, error) { @@ -99,7 +99,7 @@ func newH265( } if generateRTPPackets { - err := t.createEncoder(nil, nil, nil) + err := t.createEncoder(nil, nil) if err != nil { return nil, err } @@ -109,14 +109,14 @@ func newH265( } func (t *formatProcessorH265) createEncoder( - ssrc *uint32, initialSequenceNumber *uint16, initialTimestamp *uint32, + ssrc *uint32, + initialSequenceNumber *uint16, ) error { t.encoder = &rtph265.Encoder{ PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadType: t.format.PayloadTyp, SSRC: ssrc, InitialSequenceNumber: initialSequenceNumber, - InitialTimestamp: initialTimestamp, MaxDONDiff: t.format.MaxDONDiff, } return t.encoder.Init() @@ -262,8 +262,7 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error if pkt.MarshalSize() > t.udpMaxPayloadSize { v1 := pkt.SSRC v2 := pkt.SequenceNumber - v3 := pkt.Timestamp - err := t.createEncoder(&v1, &v2, &v3) + err := t.createEncoder(&v1, &v2) if err != nil { return err } @@ -274,27 +273,24 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - if t.encoder != nil { - tunit.RTPPackets = nil - } - - // DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups - au, pts, err := t.decoder.DecodeUntilMarker(pkt) + au, err := t.decoder.Decode(pkt) if err != nil { if err == rtph265.ErrNonStartingPacketAndNoPrevious || err == rtph265.ErrMorePacketsNeeded { + if t.encoder != nil { + tunit.RTPPackets = nil + } return nil } return err } tunit.AU = t.remuxAccessUnit(au) - tunit.PTS = pts } // route packet as is @@ -308,10 +304,11 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error // encode into RTP if len(tunit.AU) != 0 { - pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.AU) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts } else { tunit.RTPPackets = nil @@ -320,11 +317,12 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error return nil } -func (t *formatProcessorH265) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorH265) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.H265{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/h265_test.go b/internal/formatprocessor/h265_test.go index 196a6813..134332ea 100644 --- a/internal/formatprocessor/h265_test.go +++ b/internal/formatprocessor/h265_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -13,17 +13,17 @@ import ( ) func TestH265DynamicParams(t *testing.T) { - forma := &formats.H265{ + forma := &format.H265{ PayloadTyp: 96, } p, err := New(1472, forma, false, nil) require.NoError(t, err) - enc, err := forma.CreateEncoder2() + enc, err := forma.CreateEncoder() require.NoError(t, err) - pkts, err := enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}}, 0) + pkts, err := enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}}) require.NoError(t, err) data := &unit.H265{ @@ -38,7 +38,7 @@ func TestH265DynamicParams(t *testing.T) { {byte(h265.NALUType_CRA_NUT) << 1, 0}, }, data.AU) - pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_VPS_NUT) << 1, 1, 2, 3}}, 0) + pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_VPS_NUT) << 1, 1, 2, 3}}) require.NoError(t, err) err = p.Process(&unit.H265{ @@ -48,7 +48,7 @@ func TestH265DynamicParams(t *testing.T) { }, false) require.NoError(t, err) - pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}}, 0) + pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}}) require.NoError(t, err) err = p.Process(&unit.H265{ @@ -58,7 +58,7 @@ func TestH265DynamicParams(t *testing.T) { }, false) require.NoError(t, err) - pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}}, 0) + pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}}) require.NoError(t, err) err = p.Process(&unit.H265{ @@ -72,7 +72,7 @@ func TestH265DynamicParams(t *testing.T) { require.Equal(t, []byte{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS) require.Equal(t, []byte{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS) - pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}}, 0) + pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}}) require.NoError(t, err) data = &unit.H265{ @@ -92,7 +92,7 @@ func TestH265DynamicParams(t *testing.T) { } func TestH265OversizedPackets(t *testing.T) { - forma := &formats.H265{ + forma := &format.H265{ PayloadTyp: 96, VPS: []byte{byte(h265.NALUType_VPS_NUT) << 1, 10, 11, 12}, SPS: []byte{byte(h265.NALUType_SPS_NUT) << 1, 13, 14, 15}, @@ -185,7 +185,7 @@ func TestH265OversizedPackets(t *testing.T) { } func TestH265EmptyPacket(t *testing.T) { - forma := &formats.H265{ + forma := &format.H265{ PayloadTyp: 96, } diff --git a/internal/formatprocessor/mpeg1audio.go b/internal/formatprocessor/mpeg1audio.go index db5457c1..665e9dd0 100644 --- a/internal/formatprocessor/mpeg1audio.go +++ b/internal/formatprocessor/mpeg1audio.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg1audio" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -14,14 +14,14 @@ import ( type formatProcessorMPEG1Audio struct { udpMaxPayloadSize int - format *formats.MPEG1Audio + format *format.MPEG1Audio encoder *rtpmpeg1audio.Encoder decoder *rtpmpeg1audio.Decoder } func newMPEG1Audio( udpMaxPayloadSize int, - forma *formats.MPEG1Audio, + forma *format.MPEG1Audio, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorMPEG1Audio, error) { @@ -66,13 +66,13 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool) if hasNonRTSPReaders || t.decoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - frames, pts, err := t.decoder.Decode(pkt) + frames, err := t.decoder.Decode(pkt) if err != nil { if err == rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg1audio.ErrMorePacketsNeeded { return nil @@ -81,7 +81,6 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool) } tunit.Frames = frames - tunit.PTS = pts } // route packet as is @@ -89,20 +88,22 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool) } // encode into RTP - pkts, err := t.encoder.Encode(tunit.Frames, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.Frames) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts return nil } -func (t *formatProcessorMPEG1Audio) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorMPEG1Audio) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.MPEG1Audio{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/mpeg4audio_generic.go b/internal/formatprocessor/mpeg4audio_generic.go index 7d2ca990..53061eab 100644 --- a/internal/formatprocessor/mpeg4audio_generic.go +++ b/internal/formatprocessor/mpeg4audio_generic.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audio" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -14,14 +14,14 @@ import ( type formatProcessorMPEG4AudioGeneric struct { udpMaxPayloadSize int - format *formats.MPEG4Audio + format *format.MPEG4Audio encoder *rtpmpeg4audio.Encoder decoder *rtpmpeg4audio.Decoder } func newMPEG4AudioGeneric( udpMaxPayloadSize int, - forma *formats.MPEG4Audio, + forma *format.MPEG4Audio, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorMPEG4AudioGeneric, error) { @@ -44,7 +44,6 @@ func (t *formatProcessorMPEG4AudioGeneric) createEncoder() error { t.encoder = &rtpmpeg4audio.Encoder{ PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadType: t.format.PayloadTyp, - SampleRate: t.format.Config.SampleRate, SizeLength: t.format.SizeLength, IndexLength: t.format.IndexLength, IndexDeltaLength: t.format.IndexDeltaLength, @@ -71,13 +70,13 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader if hasNonRTSPReaders || t.decoder != nil || true { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - aus, pts, err := t.decoder.Decode(pkt) + aus, err := t.decoder.Decode(pkt) if err != nil { if err == rtpmpeg4audio.ErrMorePacketsNeeded { return nil @@ -86,7 +85,6 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader } tunit.AUs = aus - tunit.PTS = pts } // route packet as is @@ -94,20 +92,22 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader } // encode into RTP - pkts, err := t.encoder.Encode(tunit.AUs, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.AUs) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts return nil } -func (t *formatProcessorMPEG4AudioGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorMPEG4AudioGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.MPEG4AudioGeneric{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/mpeg4audio_latm.go b/internal/formatprocessor/mpeg4audio_latm.go index bb1855c8..ea2bf018 100644 --- a/internal/formatprocessor/mpeg4audio_latm.go +++ b/internal/formatprocessor/mpeg4audio_latm.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiolatm" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audiolatm" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -14,14 +14,14 @@ import ( type formatProcessorMPEG4AudioLATM struct { udpMaxPayloadSize int - format *formats.MPEG4AudioLATM + format *format.MPEG4AudioLATM encoder *rtpmpeg4audiolatm.Encoder decoder *rtpmpeg4audiolatm.Decoder } func newMPEG4AudioLATM( udpMaxPayloadSize int, - forma *formats.MPEG4AudioLATM, + forma *format.MPEG4AudioLATM, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorMPEG4AudioLATM, error) { @@ -43,7 +43,6 @@ func newMPEG4AudioLATM( func (t *formatProcessorMPEG4AudioLATM) createEncoder() error { t.encoder = &rtpmpeg4audiolatm.Encoder{ PayloadType: t.format.PayloadTyp, - Config: t.format.Config, } return t.encoder.Init() } @@ -67,13 +66,13 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b if hasNonRTSPReaders || t.decoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - au, pts, err := t.decoder.Decode(pkt) + au, err := t.decoder.Decode(pkt) if err != nil { if err == rtpmpeg4audiolatm.ErrMorePacketsNeeded { return nil @@ -82,7 +81,6 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b } tunit.AU = au - tunit.PTS = pts } // route packet as is @@ -90,20 +88,22 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b } // encode into RTP - pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.AU) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts return nil } -func (t *formatProcessorMPEG4AudioLATM) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorMPEG4AudioLATM) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.MPEG4AudioLATM{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/opus.go b/internal/formatprocessor/opus.go index ddf1a39e..23e1dd5a 100644 --- a/internal/formatprocessor/opus.go +++ b/internal/formatprocessor/opus.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpsimpleaudio" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" "github.com/bluenviron/mediacommon/pkg/codecs/opus" "github.com/pion/rtp" @@ -15,14 +15,14 @@ import ( type formatProcessorOpus struct { udpMaxPayloadSize int - format *formats.Opus + format *format.Opus encoder *rtpsimpleaudio.Encoder decoder *rtpsimpleaudio.Decoder } func newOpus( udpMaxPayloadSize int, - forma *formats.Opus, + forma *format.Opus, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorOpus, error) { @@ -45,7 +45,6 @@ func (t *formatProcessorOpus) createEncoder() error { t.encoder = &rtpsimpleaudio.Encoder{ PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadType: t.format.PayloadTyp, - SampleRate: 48000, } return t.encoder.Init() } @@ -69,19 +68,18 @@ func (t *formatProcessorOpus) Process(u unit.Unit, hasNonRTSPReaders bool) error if hasNonRTSPReaders || t.decoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - packet, pts, err := t.decoder.Decode(pkt) + packet, err := t.decoder.Decode(pkt) if err != nil { return err } tunit.Packets = [][]byte{packet} - tunit.PTS = pts } // route packet as is @@ -92,24 +90,26 @@ func (t *formatProcessorOpus) Process(u unit.Unit, hasNonRTSPReaders bool) error var rtpPackets []*rtp.Packet //nolint:prealloc pts := tunit.PTS for _, packet := range tunit.Packets { - pkt, err := t.encoder.Encode(packet, pts) + pkt, err := t.encoder.Encode(packet) if err != nil { return err } - + setTimestamp([]*rtp.Packet{pkt}, tunit.RTPPackets, t.format.ClockRate(), pts) rtpPackets = append(rtpPackets, pkt) pts += opus.PacketDuration(packet) } + tunit.RTPPackets = rtpPackets return nil } -func (t *formatProcessorOpus) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorOpus) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.Opus{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/processor.go b/internal/formatprocessor/processor.go index a9239476..f1f781b6 100644 --- a/internal/formatprocessor/processor.go +++ b/internal/formatprocessor/processor.go @@ -4,55 +4,75 @@ package formatprocessor import ( "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/unit" ) +// avoid an int64 overflow and preserve resolution by splitting division into two parts: +// first add the integer part, then the decimal part. +func multiplyAndDivide(v, m, d time.Duration) time.Duration { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + +func setTimestamp(newPackets []*rtp.Packet, oldPackets []*rtp.Packet, clockRate int, pts time.Duration) { + if oldPackets != nil { // get timestamp from old packets + for _, pkt := range newPackets { + pkt.Timestamp = oldPackets[0].Timestamp + } + } else { // get timestamp from PTS + for _, pkt := range newPackets { + pkt.Timestamp = uint32(multiplyAndDivide(pts, time.Duration(clockRate), time.Second)) + } + } +} + // Processor cleans and normalizes streams. type Processor interface { // cleans and normalizes a data unit. Process(unit.Unit, bool) error // wraps a RTP packet into a Unit. - UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit + UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit } // New allocates a Processor. func New( udpMaxPayloadSize int, - forma formats.Format, + forma format.Format, generateRTPPackets bool, log logger.Writer, ) (Processor, error) { switch forma := forma.(type) { - case *formats.H264: + case *format.H264: return newH264(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.H265: + case *format.H265: return newH265(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.VP8: + case *format.VP8: return newVP8(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.VP9: + case *format.VP9: return newVP9(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.AV1: + case *format.AV1: return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.MPEG1Audio: + case *format.MPEG1Audio: return newMPEG1Audio(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.MPEG4AudioGeneric: + case *format.MPEG4AudioGeneric: return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.MPEG4AudioLATM: + case *format.MPEG4AudioLATM: return newMPEG4AudioLATM(udpMaxPayloadSize, forma, generateRTPPackets, log) - case *formats.Opus: + case *format.Opus: return newOpus(udpMaxPayloadSize, forma, generateRTPPackets, log) default: diff --git a/internal/formatprocessor/unit.go b/internal/formatprocessor/unit.go new file mode 100644 index 00000000..e6d41995 --- /dev/null +++ b/internal/formatprocessor/unit.go @@ -0,0 +1,19 @@ +package formatprocessor + +import ( + "time" + + "github.com/pion/rtp" +) + +// Unit is the elementary data unit routed across the server. +type Unit interface { + // returns RTP packets contained into the unit. + GetRTPPackets() []*rtp.Packet + + // returns the NTP timestamp of the unit. + GetNTP() time.Time + + // returns the PTS of the unit. + GetPTS() time.Duration +} diff --git a/internal/formatprocessor/vp8.go b/internal/formatprocessor/vp8.go index 47044191..a89ae97f 100644 --- a/internal/formatprocessor/vp8.go +++ b/internal/formatprocessor/vp8.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -14,14 +14,14 @@ import ( type formatProcessorVP8 struct { udpMaxPayloadSize int - format *formats.VP8 + format *format.VP8 encoder *rtpvp8.Encoder decoder *rtpvp8.Decoder } func newVP8( udpMaxPayloadSize int, - forma *formats.VP8, + forma *format.VP8, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorVP8, error) { @@ -67,13 +67,13 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error if hasNonRTSPReaders || t.decoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - frame, pts, err := t.decoder.Decode(pkt) + frame, err := t.decoder.Decode(pkt) if err != nil { if err == rtpvp8.ErrNonStartingPacketAndNoPrevious || err == rtpvp8.ErrMorePacketsNeeded { return nil @@ -82,7 +82,6 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error } tunit.Frame = frame - tunit.PTS = pts } // route packet as is @@ -90,20 +89,22 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error } // encode into RTP - pkts, err := t.encoder.Encode(tunit.Frame, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.Frame) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts return nil } -func (t *formatProcessorVP8) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorVP8) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.VP8{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/formatprocessor/vp9.go b/internal/formatprocessor/vp9.go index 293e3cc4..38f8474d 100644 --- a/internal/formatprocessor/vp9.go +++ b/internal/formatprocessor/vp9.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -14,14 +14,14 @@ import ( type formatProcessorVP9 struct { udpMaxPayloadSize int - format *formats.VP9 + format *format.VP9 encoder *rtpvp9.Encoder decoder *rtpvp9.Decoder } func newVP9( udpMaxPayloadSize int, - forma *formats.VP9, + forma *format.VP9, generateRTPPackets bool, _ logger.Writer, ) (*formatProcessorVP9, error) { @@ -67,13 +67,13 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error if hasNonRTSPReaders || t.decoder != nil { if t.decoder == nil { var err error - t.decoder, err = t.format.CreateDecoder2() + t.decoder, err = t.format.CreateDecoder() if err != nil { return err } } - frame, pts, err := t.decoder.Decode(pkt) + frame, err := t.decoder.Decode(pkt) if err != nil { if err == rtpvp9.ErrNonStartingPacketAndNoPrevious || err == rtpvp9.ErrMorePacketsNeeded { return nil @@ -82,7 +82,6 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error } tunit.Frame = frame - tunit.PTS = pts } // route packet as is @@ -90,20 +89,22 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error } // encode into RTP - pkts, err := t.encoder.Encode(tunit.Frame, tunit.PTS) + pkts, err := t.encoder.Encode(tunit.Frame) if err != nil { return err } + setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS) tunit.RTPPackets = pkts return nil } -func (t *formatProcessorVP9) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit { +func (t *formatProcessorVP9) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit { return &unit.VP9{ Base: unit.Base{ RTPPackets: []*rtp.Packet{pkt}, NTP: ntp, + PTS: pts, }, } } diff --git a/internal/rtmp/reader.go b/internal/rtmp/reader.go index 5f862b71..1decc3b9 100644 --- a/internal/rtmp/reader.go +++ b/internal/rtmp/reader.go @@ -7,7 +7,7 @@ import ( "time" "github.com/abema/go-mp4" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/av1" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h265" @@ -43,14 +43,14 @@ func h265FindNALU(array []mp4.HEVCNaluArray, typ h265.NALUType) []byte { return nil } -func trackFromH264DecoderConfig(data []byte) (formats.Format, error) { +func trackFromH264DecoderConfig(data []byte) (format.Format, error) { var conf h264conf.Conf err := conf.Unmarshal(data) if err != nil { return nil, fmt.Errorf("unable to parse H264 config: %v", err) } - return &formats.H264{ + return &format.H264{ PayloadTyp: 96, SPS: conf.SPS, PPS: conf.PPS, @@ -58,14 +58,14 @@ func trackFromH264DecoderConfig(data []byte) (formats.Format, error) { }, nil } -func trackFromAACDecoderConfig(data []byte) (formats.Format, error) { +func trackFromAACDecoderConfig(data []byte) (format.Format, error) { var mpegConf mpeg4audio.Config err := mpegConf.Unmarshal(data) if err != nil { return nil, err } - return &formats.MPEG4Audio{ + return &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpegConf, SizeLength: 13, @@ -76,7 +76,7 @@ func trackFromAACDecoderConfig(data []byte) (formats.Format, error) { var errEmptyMetadata = errors.New("metadata is empty") -func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, formats.Format, error) { +func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, format.Format, error) { if len(payload) != 1 { return nil, nil, fmt.Errorf("invalid metadata") } @@ -86,8 +86,8 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form return nil, nil, fmt.Errorf("invalid metadata") } - var videoTrack formats.Format - var audioTrack formats.Format + var videoTrack format.Format + var audioTrack format.Format hasVideo, err := func() (bool, error) { v, ok := md.GetV("videocodecid") @@ -131,7 +131,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form return false, nil case message.CodecMPEG1Audio: - audioTrack = &formats.MPEG1Audio{} + audioTrack = &format.MPEG1Audio{} return true, nil case message.CodecMPEG4Audio: @@ -205,7 +205,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form } if vps != nil && sps != nil && pps != nil { - videoTrack = &formats.H265{ + videoTrack = &format.H265{ PayloadTyp: 96, VPS: vps, SPS: sps, @@ -232,7 +232,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form return nil, nil, fmt.Errorf("H265 parameters are missing") } - videoTrack = &formats.H265{ + videoTrack = &format.H265{ PayloadTyp: 96, VPS: vps, SPS: sps, @@ -252,7 +252,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form return nil, nil, fmt.Errorf("invalid AV1 configuration: %v", err) } - videoTrack = &formats.AV1{} + videoTrack = &format.AV1{} default: // VP9 var vpcc mp4.VpcC @@ -261,7 +261,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form return nil, nil, fmt.Errorf("invalid VP9 configuration: %v", err) } - videoTrack = &formats.VP9{} + videoTrack = &format.VP9{} } } @@ -282,10 +282,10 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form } } -func tracksFromMessages(conn *Conn, msg message.Message) (formats.Format, formats.Format, error) { +func tracksFromMessages(conn *Conn, msg message.Message) (format.Format, format.Format, error) { var startTime *time.Duration - var videoTrack formats.Format - var audioTrack formats.Format + var videoTrack format.Format + var audioTrack format.Format // analyze 1 second of packets outer: @@ -359,8 +359,8 @@ outer: // Reader is a wrapper around Conn that provides utilities to demux incoming data. type Reader struct { conn *Conn - videoTrack formats.Format - audioTrack formats.Format + videoTrack format.Format + audioTrack format.Format onDataVideo func(message.Message) error onDataAudio func(*message.Audio) error } @@ -380,7 +380,7 @@ func NewReader(conn *Conn) (*Reader, error) { return r, nil } -func (r *Reader) readTracks() (formats.Format, formats.Format, error) { +func (r *Reader) readTracks() (format.Format, format.Format, error) { msg, err := func() (message.Message, error) { for { msg, err := r.conn.Read() @@ -439,7 +439,7 @@ func (r *Reader) readTracks() (formats.Format, formats.Format, error) { } // Tracks returns detected tracks -func (r *Reader) Tracks() (formats.Format, formats.Format) { +func (r *Reader) Tracks() (format.Format, format.Format) { return r.videoTrack, r.audioTrack } diff --git a/internal/rtmp/reader_test.go b/internal/rtmp/reader_test.go index fee6c168..80d2ff7a 100644 --- a/internal/rtmp/reader_test.go +++ b/internal/rtmp/reader_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/abema/go-mp4" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" @@ -104,18 +104,18 @@ func TestReadTracks(t *testing.T) { for _, ca := range []struct { name string - videoTrack formats.Format - audioTrack formats.Format + videoTrack format.Format + audioTrack format.Format }{ { "video+audio", - &formats.H264{ + &format.H264{ PayloadTyp: 96, SPS: h264SPS, PPS: h264PPS, PacketizationMode: 1, }, - &formats.MPEG4Audio{ + &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -129,7 +129,7 @@ func TestReadTracks(t *testing.T) { }, { "video", - &formats.H264{ + &format.H264{ PayloadTyp: 96, SPS: h264SPS, PPS: h264PPS, @@ -139,13 +139,13 @@ func TestReadTracks(t *testing.T) { }, { "metadata without codec id, video+audio", - &formats.H264{ + &format.H264{ PayloadTyp: 96, SPS: h264SPS, PPS: h264PPS, PacketizationMode: 1, }, - &formats.MPEG4Audio{ + &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -159,7 +159,7 @@ func TestReadTracks(t *testing.T) { }, { "metadata without codec id, video only", - &formats.H264{ + &format.H264{ PayloadTyp: 96, SPS: h264SPS, PPS: h264PPS, @@ -169,13 +169,13 @@ func TestReadTracks(t *testing.T) { }, { "missing metadata, video+audio", - &formats.H264{ + &format.H264{ PayloadTyp: 96, SPS: h264SPS, PPS: h264PPS, PacketizationMode: 1, }, - &formats.MPEG4Audio{ + &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -190,7 +190,7 @@ func TestReadTracks(t *testing.T) { { "missing metadata, audio", nil, - &formats.MPEG4Audio{ + &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -204,13 +204,13 @@ func TestReadTracks(t *testing.T) { }, { "obs studio pre 29.1 h265", - &formats.H265{ + &format.H265{ PayloadTyp: 96, VPS: h265VPS, SPS: h265SPS, PPS: h265PPS, }, - &formats.MPEG4Audio{ + &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, @@ -224,7 +224,7 @@ func TestReadTracks(t *testing.T) { }, { "xplit broadcaster", - &formats.H265{ + &format.H265{ PayloadTyp: 96, VPS: h265VPS, SPS: h265SPS, @@ -234,7 +234,7 @@ func TestReadTracks(t *testing.T) { }, { "obs 30", - &formats.H265{ + &format.H265{ PayloadTyp: 96, VPS: h265VPS, SPS: h265SPS, diff --git a/internal/rtmp/writer.go b/internal/rtmp/writer.go index 3d030f83..f53f5912 100644 --- a/internal/rtmp/writer.go +++ b/internal/rtmp/writer.go @@ -3,7 +3,7 @@ package rtmp import ( "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" @@ -39,7 +39,7 @@ type Writer struct { } // NewWriter allocates a Writer. -func NewWriter(conn *Conn, videoTrack formats.Format, audioTrack formats.Format) (*Writer, error) { +func NewWriter(conn *Conn, videoTrack format.Format, audioTrack format.Format) (*Writer, error) { w := &Writer{ conn: conn, } @@ -52,7 +52,7 @@ func NewWriter(conn *Conn, videoTrack formats.Format, audioTrack formats.Format) return w, nil } -func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Format) error { +func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format) error { err := w.conn.Write(&message.DataAMF0{ ChunkStreamID: 4, MessageStreamID: 0x1000000, @@ -68,7 +68,7 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma K: "videocodecid", V: func() float64 { switch videoTrack.(type) { - case *formats.H264: + case *format.H264: return message.CodecH264 default: @@ -84,10 +84,10 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma K: "audiocodecid", V: func() float64 { switch audioTrack.(type) { - case *formats.MPEG1Audio: + case *format.MPEG1Audio: return message.CodecMPEG1Audio - case *formats.MPEG4AudioGeneric, *formats.MPEG4AudioLATM: + case *format.MPEG4AudioGeneric, *format.MPEG4AudioLATM: return message.CodecMPEG4Audio default: @@ -102,7 +102,7 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma return err } - if videoTrack, ok := videoTrack.(*formats.H264); ok { + if videoTrack, ok := videoTrack.(*format.H264); ok { // write decoder config only if SPS and PPS are available. // if they're not available yet, they're sent later. if sps, pps := videoTrack.SafeParams(); sps != nil && pps != nil { @@ -128,10 +128,10 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma var audioConfig *mpeg4audio.AudioSpecificConfig switch track := audioTrack.(type) { - case *formats.MPEG4Audio: + case *format.MPEG4Audio: audioConfig = track.Config - case *formats.MPEG4AudioLATM: + case *format.MPEG4AudioLATM: audioConfig = track.Config.Programs[0].Layers[0].AudioSpecificConfig } diff --git a/internal/rtmp/writer_test.go b/internal/rtmp/writer_test.go index 46cfbc97..26c6eed2 100644 --- a/internal/rtmp/writer_test.go +++ b/internal/rtmp/writer_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/bluenviron/gortsplib/v3/pkg/formats" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/notedit/rtmp/format/flv/flvio" "github.com/stretchr/testify/require" @@ -14,7 +14,7 @@ import ( ) func TestWriteTracks(t *testing.T) { - videoTrack := &formats.H264{ + videoTrack := &format.H264{ PayloadTyp: 96, SPS: []byte{ 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, @@ -27,7 +27,7 @@ func TestWriteTracks(t *testing.T) { PacketizationMode: 1, } - audioTrack := &formats.MPEG4Audio{ + audioTrack := &format.MPEG4Audio{ PayloadTyp: 96, Config: &mpeg4audio.Config{ Type: 2, diff --git a/internal/stream/stream.go b/internal/stream/stream.go index dac6a455..bb9b28c9 100644 --- a/internal/stream/stream.go +++ b/internal/stream/stream.go @@ -2,11 +2,12 @@ package stream import ( + "sync" "time" - "github.com/bluenviron/gortsplib/v3" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/logger" @@ -16,28 +17,31 @@ import ( // Stream is a media stream. // It stores tracks, readers and allow to write data to readers. type Stream struct { + desc *description.Session bytesReceived *uint64 - rtspStream *gortsplib.ServerStream - smedias map[*media.Media]*streamMedia + smedias map[*description.Media]*streamMedia + mutex sync.RWMutex + rtspStream *gortsplib.ServerStream + rtspsStream *gortsplib.ServerStream } // New allocates a Stream. func New( udpMaxPayloadSize int, - medias media.Medias, + desc *description.Session, generateRTPPackets bool, bytesReceived *uint64, source logger.Writer, ) (*Stream, error) { s := &Stream{ bytesReceived: bytesReceived, - rtspStream: gortsplib.NewServerStream(medias), + desc: desc, } - s.smedias = make(map[*media.Media]*streamMedia) + s.smedias = make(map[*description.Media]*streamMedia) - for _, media := range s.rtspStream.Medias() { + for _, media := range desc.Medias { var err error s.smedias[media], err = newStreamMedia(udpMaxPayloadSize, media, generateRTPPackets, source) if err != nil { @@ -50,21 +54,46 @@ func New( // Close closes all resources of the stream. func (s *Stream) Close() { - s.rtspStream.Close() + if s.rtspStream != nil { + s.rtspStream.Close() + } + if s.rtspsStream != nil { + s.rtspsStream.Close() + } } -// Medias returns medias of the stream. -func (s *Stream) Medias() media.Medias { - return s.rtspStream.Medias() +// Desc returns description of the stream. +func (s *Stream) Desc() *description.Session { + return s.desc } // RTSPStream returns the RTSP stream. -func (s *Stream) RTSPStream() *gortsplib.ServerStream { +func (s *Stream) RTSPStream(server *gortsplib.Server) *gortsplib.ServerStream { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.rtspStream == nil { + s.rtspStream = gortsplib.NewServerStream(server, s.desc) + } return s.rtspStream } +// RTSPSStream returns the RTSPS stream. +func (s *Stream) RTSPSStream(server *gortsplib.Server) *gortsplib.ServerStream { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.rtspsStream == nil { + s.rtspsStream = gortsplib.NewServerStream(server, s.desc) + } + return s.rtspsStream +} + // AddReader adds a reader. -func (s *Stream) AddReader(r interface{}, medi *media.Media, forma formats.Format, cb func(unit.Unit)) { +func (s *Stream) AddReader(r interface{}, medi *description.Media, forma format.Format, cb func(unit.Unit)) { + s.mutex.Lock() + defer s.mutex.Unlock() + sm := s.smedias[medi] sf := sm.formats[forma] sf.addReader(r, cb) @@ -72,6 +101,9 @@ func (s *Stream) AddReader(r interface{}, medi *media.Media, forma formats.Forma // RemoveReader removes a reader. func (s *Stream) RemoveReader(r interface{}) { + s.mutex.Lock() + defer s.mutex.Unlock() + for _, sm := range s.smedias { for _, sf := range sm.formats { sf.removeReader(r) @@ -80,15 +112,29 @@ func (s *Stream) RemoveReader(r interface{}) { } // WriteUnit writes a Unit. -func (s *Stream) WriteUnit(medi *media.Media, forma formats.Format, data unit.Unit) { +func (s *Stream) WriteUnit(medi *description.Media, forma format.Format, data unit.Unit) { sm := s.smedias[medi] sf := sm.formats[forma] + + s.mutex.RLock() + defer s.mutex.RUnlock() + sf.writeUnit(s, medi, data) } // WriteRTPPacket writes a RTP packet. -func (s *Stream) WriteRTPPacket(medi *media.Media, forma formats.Format, pkt *rtp.Packet, ntp time.Time) { +func (s *Stream) WriteRTPPacket( + medi *description.Media, + forma format.Format, + pkt *rtp.Packet, + ntp time.Time, + pts time.Duration, +) { sm := s.smedias[medi] sf := sm.formats[forma] - sf.writeRTPPacket(s, medi, pkt, ntp) + + s.mutex.RLock() + defer s.mutex.RUnlock() + + sf.writeRTPPacket(s, medi, pkt, ntp, pts) } diff --git a/internal/stream/stream_format.go b/internal/stream/stream_format.go index e63a617a..93389caf 100644 --- a/internal/stream/stream_format.go +++ b/internal/stream/stream_format.go @@ -1,12 +1,11 @@ package stream import ( - "sync" "sync/atomic" "time" - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/pion/rtp" "github.com/bluenviron/mediamtx/internal/formatprocessor" @@ -17,13 +16,12 @@ import ( type streamFormat struct { source logger.Writer proc formatprocessor.Processor - mutex sync.RWMutex nonRTSPReaders map[interface{}]func(unit.Unit) } func newStreamFormat( udpMaxPayloadSize int, - forma formats.Format, + forma format.Format, generateRTPPackets bool, source logger.Writer, ) (*streamFormat, error) { @@ -42,21 +40,14 @@ func newStreamFormat( } func (sf *streamFormat) addReader(r interface{}, cb func(unit.Unit)) { - sf.mutex.Lock() - defer sf.mutex.Unlock() sf.nonRTSPReaders[r] = cb } func (sf *streamFormat) removeReader(r interface{}) { - sf.mutex.Lock() - defer sf.mutex.Unlock() delete(sf.nonRTSPReaders, r) } -func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit) { - sf.mutex.RLock() - defer sf.mutex.RUnlock() - +func (sf *streamFormat) writeUnit(s *Stream, medi *description.Media, data unit.Unit) { hasNonRTSPReaders := len(sf.nonRTSPReaders) > 0 err := sf.proc.Process(data, hasNonRTSPReaders) @@ -65,10 +56,22 @@ func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit) return } - // forward RTP packets to RTSP readers + n := uint64(0) for _, pkt := range data.GetRTPPackets() { - atomic.AddUint64(s.bytesReceived, uint64(pkt.MarshalSize())) - s.rtspStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP()) + n += uint64(pkt.MarshalSize()) + } + atomic.AddUint64(s.bytesReceived, n) + + if s.rtspStream != nil { + for _, pkt := range data.GetRTPPackets() { + s.rtspStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP()) //nolint:errcheck + } + } + + if s.rtspsStream != nil { + for _, pkt := range data.GetRTPPackets() { + s.rtspsStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP()) //nolint:errcheck + } } // forward decoded frames to non-RTSP readers @@ -77,6 +80,12 @@ func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit) } } -func (sf *streamFormat) writeRTPPacket(s *Stream, medi *media.Media, pkt *rtp.Packet, ntp time.Time) { - sf.writeUnit(s, medi, sf.proc.UnitForRTPPacket(pkt, ntp)) +func (sf *streamFormat) writeRTPPacket( + s *Stream, + medi *description.Media, + pkt *rtp.Packet, + ntp time.Time, + pts time.Duration, +) { + sf.writeUnit(s, medi, sf.proc.UnitForRTPPacket(pkt, ntp, pts)) } diff --git a/internal/stream/stream_media.go b/internal/stream/stream_media.go index 34d4ab0e..8ea899ac 100644 --- a/internal/stream/stream_media.go +++ b/internal/stream/stream_media.go @@ -1,23 +1,23 @@ package stream import ( - "github.com/bluenviron/gortsplib/v3/pkg/formats" - "github.com/bluenviron/gortsplib/v3/pkg/media" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/mediamtx/internal/logger" ) type streamMedia struct { - formats map[formats.Format]*streamFormat + formats map[format.Format]*streamFormat } func newStreamMedia(udpMaxPayloadSize int, - medi *media.Media, + medi *description.Media, generateRTPPackets bool, source logger.Writer, ) (*streamMedia, error) { sm := &streamMedia{ - formats: make(map[formats.Format]*streamFormat), + formats: make(map[format.Format]*streamFormat), } for _, forma := range medi.Formats { diff --git a/internal/unit/av1.go b/internal/unit/av1.go index 6f8c2463..a26d08c1 100644 --- a/internal/unit/av1.go +++ b/internal/unit/av1.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // AV1 is an AV1 data unit. type AV1 struct { Base - PTS time.Duration - TU [][]byte + TU [][]byte } diff --git a/internal/unit/base.go b/internal/unit/base.go index 868a779e..616f5573 100644 --- a/internal/unit/base.go +++ b/internal/unit/base.go @@ -10,6 +10,7 @@ import ( type Base struct { RTPPackets []*rtp.Packet NTP time.Time + PTS time.Duration } // GetRTPPackets implements Unit. @@ -21,3 +22,8 @@ func (u *Base) GetRTPPackets() []*rtp.Packet { func (u *Base) GetNTP() time.Time { return u.NTP } + +// GetPTS implements Unit. +func (u *Base) GetPTS() time.Duration { + return u.PTS +} diff --git a/internal/unit/h264.go b/internal/unit/h264.go index ae7e8b08..d613a106 100644 --- a/internal/unit/h264.go +++ b/internal/unit/h264.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // H264 is a H264 data unit. type H264 struct { Base - PTS time.Duration - AU [][]byte + AU [][]byte } diff --git a/internal/unit/h265.go b/internal/unit/h265.go index 57fe267d..a5fb9105 100644 --- a/internal/unit/h265.go +++ b/internal/unit/h265.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // H265 is a H265 data unit. type H265 struct { Base - PTS time.Duration - AU [][]byte + AU [][]byte } diff --git a/internal/unit/mpeg1audio.go b/internal/unit/mpeg1audio.go index 902ba8d1..6eb8670f 100644 --- a/internal/unit/mpeg1audio.go +++ b/internal/unit/mpeg1audio.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // MPEG1Audio is a MPEG-1/2 Audio data unit. type MPEG1Audio struct { Base - PTS time.Duration Frames [][]byte } diff --git a/internal/unit/mpeg4audio_generic.go b/internal/unit/mpeg4audio_generic.go index c1cf2823..ede20e66 100644 --- a/internal/unit/mpeg4audio_generic.go +++ b/internal/unit/mpeg4audio_generic.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // MPEG4AudioGeneric is a MPEG-4 Audio data unit. type MPEG4AudioGeneric struct { Base - PTS time.Duration AUs [][]byte } diff --git a/internal/unit/mpeg4audio_latm.go b/internal/unit/mpeg4audio_latm.go index b2cca8e6..a91bccd5 100644 --- a/internal/unit/mpeg4audio_latm.go +++ b/internal/unit/mpeg4audio_latm.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // MPEG4AudioLATM is a MPEG-4 Audio data unit. type MPEG4AudioLATM struct { Base - PTS time.Duration - AU []byte + AU []byte } diff --git a/internal/unit/opus.go b/internal/unit/opus.go index e975c330..cca9ab37 100644 --- a/internal/unit/opus.go +++ b/internal/unit/opus.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // Opus is a Opus data unit. type Opus struct { Base - PTS time.Duration Packets [][]byte } diff --git a/internal/unit/unit.go b/internal/unit/unit.go index 055fdead..55f986c7 100644 --- a/internal/unit/unit.go +++ b/internal/unit/unit.go @@ -14,4 +14,7 @@ type Unit interface { // returns the NTP timestamp of the unit. GetNTP() time.Time + + // returns the PTS of the unit. + GetPTS() time.Duration } diff --git a/internal/unit/vp8.go b/internal/unit/vp8.go index 75c6fbf7..dc5f2e0e 100644 --- a/internal/unit/vp8.go +++ b/internal/unit/vp8.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // VP8 is a VP8 data unit. type VP8 struct { Base - PTS time.Duration Frame []byte } diff --git a/internal/unit/vp9.go b/internal/unit/vp9.go index 0ea9e000..d56b099f 100644 --- a/internal/unit/vp9.go +++ b/internal/unit/vp9.go @@ -1,12 +1,7 @@ package unit -import ( - "time" -) - // VP9 is a VP9 data unit. type VP9 struct { Base - PTS time.Duration Frame []byte }