switch to gortsplib/v4 (#2244)

This commit is contained in:
Alessandro Ros
2023-08-26 18:54:28 +02:00
committed by GitHub
parent 055767fef0
commit cf86dbb303
79 changed files with 1241 additions and 1325 deletions

View File

@@ -52,11 +52,11 @@ linters-settings:
- (github.com/datarhei/gosrt.Conn).Close - (github.com/datarhei/gosrt.Conn).Close
- (github.com/datarhei/gosrt.Conn).SetReadDeadline - (github.com/datarhei/gosrt.Conn).SetReadDeadline
- (github.com/datarhei/gosrt.Conn).SetWriteDeadline - (github.com/datarhei/gosrt.Conn).SetWriteDeadline
- (*github.com/bluenviron/gortsplib/v3.Client).Close - (*github.com/bluenviron/gortsplib/v4.Client).Close
- (*github.com/bluenviron/gortsplib/v3.Server).Close - (*github.com/bluenviron/gortsplib/v4.Server).Close
- (*github.com/bluenviron/gortsplib/v3.ServerSession).Close - (*github.com/bluenviron/gortsplib/v4.ServerSession).Close
- (*github.com/bluenviron/gortsplib/v3.ServerStream).Close - (*github.com/bluenviron/gortsplib/v4.ServerStream).Close
- (*github.com/bluenviron/gortsplib/v3.ServerConn).Close - (*github.com/bluenviron/gortsplib/v4.ServerConn).Close
govet: govet:
enable-all: true enable-all: true

2
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/abema/go-mp4 v0.13.0 github.com/abema/go-mp4 v0.13.0
github.com/alecthomas/kong v0.8.0 github.com/alecthomas/kong v0.8.0
github.com/bluenviron/gohlslib v1.0.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/bluenviron/mediacommon v1.0.0
github.com/datarhei/gosrt v0.5.3 github.com/datarhei/gosrt v0.5.3
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0

4
go.sum
View File

@@ -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/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 h1:UOI7wW7EdXPnnoflPL+WRiUB+bDSyrR9AXtu029n5EY=
github.com/bluenviron/gohlslib v1.0.0/go.mod h1:fwqXogd2G/CJ/0kD6TTALmWI3KAm66nZoI+06O02YKI= 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/v4 v4.0.0-20230826160945-3bdae4ed4630 h1:Xe2em0Hv8ftQ1G3YgKVIcnor63h+OuXBHL7sNDMpfZA=
github.com/bluenviron/gortsplib/v3 v3.10.0/go.mod h1:prNU1aMVBmgmmKwlvLiEdjBbTEpTw4BRsqVcqEARgMY= 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 h1:hKelTQKfetasCmXaXMiL1ihID0GRmItyWZt1/pqiKKk=
github.com/bluenviron/mediacommon v1.0.0/go.mod h1:nt5oKCO0WcZ+AH1oc12gs2ldp67xW2vl88c2StNmPlI= 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= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=

View File

@@ -6,7 +6,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
) )
// AuthMethods is the authMethods parameter. // AuthMethods is the authMethods parameter.

View File

@@ -12,8 +12,8 @@ import (
"time" "time"
"github.com/bluenviron/gohlslib" "github.com/bluenviron/gohlslib"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/mediamtx/internal/conf/decrypt" "github.com/bluenviron/mediamtx/internal/conf/decrypt"
"github.com/bluenviron/mediamtx/internal/conf/env" "github.com/bluenviron/mediamtx/internal/conf/env"

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/nacl/secretbox"

View File

@@ -11,8 +11,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
) )
var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/\.~]+$`) var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/\.~]+$`)

View File

@@ -6,7 +6,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
) )
// Protocol is a RTSP transport. // Protocol is a RTSP transport.

View File

@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
) )
// SourceProtocol is the sourceProtocol parameter. // SourceProtocol is the sourceProtocol parameter.

View File

@@ -14,9 +14,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/datarhei/gosrt" "github.com/datarhei/gosrt"
@@ -27,7 +27,7 @@ import (
"github.com/bluenviron/mediamtx/internal/rtmp" "github.com/bluenviron/mediamtx/internal/rtmp"
) )
var testFormatH264 = &formats.H264{ var testFormatH264 = &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ // 1920x1080 baseline SPS: []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
@@ -38,9 +38,9 @@ var testFormatH264 = &formats.H264{
PacketizationMode: 1, PacketizationMode: 1,
} }
var testMediaH264 = &media.Media{ var testMediaH264 = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{testFormatH264}, Formats: []format.Format{testFormatH264},
} }
func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) { 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{} source := gortsplib.Client{}
err := source.StartRecording( err := source.StartRecording(
"rtsp://localhost:8554/mypath", "rtsp://localhost:8554/mypath",
media.Medias{ &description.Session{Medias: []*description.Media{
media0, media0,
{ {
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -261,7 +261,7 @@ func TestAPIPathsList(t *testing.T) {
IndexDeltaLength: 3, IndexDeltaLength: 3,
}}, }},
}, },
}) }})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -270,7 +270,7 @@ func TestAPIPathsList(t *testing.T) {
Version: 2, Version: 2,
PayloadType: 96, PayloadType: 96,
}, },
Payload: []byte{0x01, 0x02, 0x03, 0x04}, Payload: []byte{5, 1, 2, 3, 4},
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -286,7 +286,7 @@ func TestAPIPathsList(t *testing.T) {
}, },
Ready: true, Ready: true,
Tracks: []string{"H264", "MPEG-4 Audio"}, Tracks: []string{"H264", "MPEG-4 Audio"},
BytesReceived: 16, BytesReceived: 17,
}}, }},
}, out) }, out)
}) })
@@ -311,29 +311,28 @@ func TestAPIPathsList(t *testing.T) {
hc := &http.Client{Transport: &http.Transport{}} 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}} 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -471,7 +470,8 @@ func TestAPIPathsGet(t *testing.T) {
if ca == "ok" || ca == "ok-nested" { if ca == "ok" || ca == "ok-nested" {
source := gortsplib.Client{} 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -546,7 +546,8 @@ func TestAPIProtocolList(t *testing.T) {
case "rtsp conns", "rtsp sessions": case "rtsp conns", "rtsp sessions":
source := gortsplib.Client{} 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -555,7 +556,8 @@ func TestAPIProtocolList(t *testing.T) {
TLSConfig: &tls.Config{InsecureSkipVerify: true}, 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -590,7 +592,7 @@ func TestAPIProtocolList(t *testing.T) {
case "hls": case "hls":
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/mypath", err := source.StartRecording("rtsp://localhost:8554/mypath",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -647,7 +649,7 @@ func TestAPIProtocolList(t *testing.T) {
case "webrtc": case "webrtc":
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/mypath", err := source.StartRecording("rtsp://localhost:8554/mypath",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -665,7 +667,7 @@ func TestAPIProtocolList(t *testing.T) {
Timestamp: 45343, Timestamp: 45343,
SSRC: 563423, SSRC: 563423,
}, },
Payload: []byte{0x01, 0x02, 0x03, 0x04}, Payload: []byte{5, 1, 2, 3, 4},
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -830,7 +832,8 @@ func TestAPIProtocolGet(t *testing.T) {
case "rtsp conns", "rtsp sessions": case "rtsp conns", "rtsp sessions":
source := gortsplib.Client{} 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -839,7 +842,8 @@ func TestAPIProtocolGet(t *testing.T) {
TLSConfig: &tls.Config{InsecureSkipVerify: true}, 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -874,7 +878,7 @@ func TestAPIProtocolGet(t *testing.T) {
case "hls": case "hls":
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/mypath", err := source.StartRecording("rtsp://localhost:8554/mypath",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -931,7 +935,7 @@ func TestAPIProtocolGet(t *testing.T) {
case "webrtc": case "webrtc":
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/mypath", err := source.StartRecording("rtsp://localhost:8554/mypath",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -949,7 +953,7 @@ func TestAPIProtocolGet(t *testing.T) {
Timestamp: 45343, Timestamp: 45343,
SSRC: 563423, SSRC: 563423,
}, },
Payload: []byte{0x01, 0x02, 0x03, 0x04}, Payload: []byte{5, 1, 2, 3, 4},
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1196,7 +1200,7 @@ func TestAPIProtocolKick(t *testing.T) {
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/mypath", err := source.StartRecording("rtsp://localhost:8554/mypath",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -1206,7 +1210,7 @@ func TestAPIProtocolKick(t *testing.T) {
} }
err := source.StartRecording("rtsps://localhost:8322/mypath", err := source.StartRecording("rtsps://localhost:8322/mypath",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()

View File

@@ -11,10 +11,10 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/bluenviron/gortsplib/v3/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"

View File

@@ -9,7 +9,7 @@ import (
"reflect" "reflect"
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
@@ -213,29 +213,27 @@ func (p *Core) createResources(initial bool) error {
p.externalCmdPool = externalcmd.NewPool() p.externalCmdPool = externalcmd.NewPool()
} }
if p.conf.Metrics { if p.conf.Metrics &&
if p.metrics == nil { p.metrics == nil {
p.metrics, err = newMetrics( p.metrics, err = newMetrics(
p.conf.MetricsAddress, p.conf.MetricsAddress,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.PPROF { if p.conf.PPROF &&
if p.pprof == nil { p.pprof == nil {
p.pprof, err = newPPROF( p.pprof, err = newPPROF(
p.conf.PPROFAddress, p.conf.PPROFAddress,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
@@ -257,219 +255,212 @@ func (p *Core) createResources(initial bool) error {
if p.conf.RTSP && if p.conf.RTSP &&
(p.conf.Encryption == conf.EncryptionNo || (p.conf.Encryption == conf.EncryptionNo ||
p.conf.Encryption == conf.EncryptionOptional) { p.conf.Encryption == conf.EncryptionOptional) &&
if p.rtspServer == nil { p.rtspServer == nil {
_, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)] _, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)]
_, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)] _, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)]
p.rtspServer, err = newRTSPServer(
p.conf.RTSPAddress, p.rtspServer, err = newRTSPServer(
p.conf.AuthMethods, p.conf.RTSPAddress,
p.conf.ReadTimeout, p.conf.AuthMethods,
p.conf.WriteTimeout, p.conf.ReadTimeout,
p.conf.WriteQueueSize, p.conf.WriteTimeout,
useUDP, p.conf.WriteQueueSize,
useMulticast, useUDP,
p.conf.RTPAddress, useMulticast,
p.conf.RTCPAddress, p.conf.RTPAddress,
p.conf.MulticastIPRange, p.conf.RTCPAddress,
p.conf.MulticastRTPPort, p.conf.MulticastIPRange,
p.conf.MulticastRTCPPort, p.conf.MulticastRTPPort,
false, p.conf.MulticastRTCPPort,
"", false,
"", "",
p.conf.RTSPAddress, "",
p.conf.Protocols, p.conf.RTSPAddress,
p.conf.RunOnConnect, p.conf.Protocols,
p.conf.RunOnConnectRestart, p.conf.RunOnConnect,
p.externalCmdPool, p.conf.RunOnConnectRestart,
p.metrics, p.externalCmdPool,
p.pathManager, p.metrics,
p, p.pathManager,
) p,
if err != nil { )
return err if err != nil {
} return err
} }
} }
if p.conf.RTSP && if p.conf.RTSP &&
(p.conf.Encryption == conf.EncryptionStrict || (p.conf.Encryption == conf.EncryptionStrict ||
p.conf.Encryption == conf.EncryptionOptional) { p.conf.Encryption == conf.EncryptionOptional) &&
if p.rtspsServer == nil { p.rtspsServer == nil {
p.rtspsServer, err = newRTSPServer( p.rtspsServer, err = newRTSPServer(
p.conf.RTSPSAddress, p.conf.RTSPSAddress,
p.conf.AuthMethods, p.conf.AuthMethods,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf.WriteTimeout, p.conf.WriteTimeout,
p.conf.WriteQueueSize, p.conf.WriteQueueSize,
false, false,
false, false,
"", "",
"", "",
"", "",
0, 0,
0, 0,
true, true,
p.conf.ServerCert, p.conf.ServerCert,
p.conf.ServerKey, p.conf.ServerKey,
p.conf.RTSPAddress, p.conf.RTSPAddress,
p.conf.Protocols, p.conf.Protocols,
p.conf.RunOnConnect, p.conf.RunOnConnect,
p.conf.RunOnConnectRestart, p.conf.RunOnConnectRestart,
p.externalCmdPool, p.externalCmdPool,
p.metrics, p.metrics,
p.pathManager, p.pathManager,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.RTMP && if p.conf.RTMP &&
(p.conf.RTMPEncryption == conf.EncryptionNo || (p.conf.RTMPEncryption == conf.EncryptionNo ||
p.conf.RTMPEncryption == conf.EncryptionOptional) { p.conf.RTMPEncryption == conf.EncryptionOptional) &&
if p.rtmpServer == nil { p.rtmpServer == nil {
p.rtmpServer, err = newRTMPServer( p.rtmpServer, err = newRTMPServer(
p.conf.RTMPAddress, p.conf.RTMPAddress,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf.WriteTimeout, p.conf.WriteTimeout,
p.conf.WriteQueueSize, p.conf.WriteQueueSize,
false, false,
"", "",
"", "",
p.conf.RTSPAddress, p.conf.RTSPAddress,
p.conf.RunOnConnect, p.conf.RunOnConnect,
p.conf.RunOnConnectRestart, p.conf.RunOnConnectRestart,
p.externalCmdPool, p.externalCmdPool,
p.metrics, p.metrics,
p.pathManager, p.pathManager,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.RTMP && if p.conf.RTMP &&
(p.conf.RTMPEncryption == conf.EncryptionStrict || (p.conf.RTMPEncryption == conf.EncryptionStrict ||
p.conf.RTMPEncryption == conf.EncryptionOptional) { p.conf.RTMPEncryption == conf.EncryptionOptional) &&
if p.rtmpsServer == nil { p.rtmpsServer == nil {
p.rtmpsServer, err = newRTMPServer( p.rtmpsServer, err = newRTMPServer(
p.conf.RTMPSAddress, p.conf.RTMPSAddress,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf.WriteTimeout, p.conf.WriteTimeout,
p.conf.WriteQueueSize, p.conf.WriteQueueSize,
true, true,
p.conf.RTMPServerCert, p.conf.RTMPServerCert,
p.conf.RTMPServerKey, p.conf.RTMPServerKey,
p.conf.RTSPAddress, p.conf.RTSPAddress,
p.conf.RunOnConnect, p.conf.RunOnConnect,
p.conf.RunOnConnectRestart, p.conf.RunOnConnectRestart,
p.externalCmdPool, p.externalCmdPool,
p.metrics, p.metrics,
p.pathManager, p.pathManager,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.HLS { if p.conf.HLS &&
if p.hlsManager == nil { p.hlsManager == nil {
p.hlsManager, err = newHLSManager( p.hlsManager, err = newHLSManager(
p.conf.HLSAddress, p.conf.HLSAddress,
p.conf.HLSEncryption, p.conf.HLSEncryption,
p.conf.HLSServerKey, p.conf.HLSServerKey,
p.conf.HLSServerCert, p.conf.HLSServerCert,
p.conf.ExternalAuthenticationURL, p.conf.ExternalAuthenticationURL,
p.conf.HLSAlwaysRemux, p.conf.HLSAlwaysRemux,
p.conf.HLSVariant, p.conf.HLSVariant,
p.conf.HLSSegmentCount, p.conf.HLSSegmentCount,
p.conf.HLSSegmentDuration, p.conf.HLSSegmentDuration,
p.conf.HLSPartDuration, p.conf.HLSPartDuration,
p.conf.HLSSegmentMaxSize, p.conf.HLSSegmentMaxSize,
p.conf.HLSAllowOrigin, p.conf.HLSAllowOrigin,
p.conf.HLSTrustedProxies, p.conf.HLSTrustedProxies,
p.conf.HLSDirectory, p.conf.HLSDirectory,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf.WriteQueueSize, p.conf.WriteQueueSize,
p.pathManager, p.pathManager,
p.metrics, p.metrics,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.WebRTC { if p.conf.WebRTC &&
if p.webRTCManager == nil { p.webRTCManager == nil {
p.webRTCManager, err = newWebRTCManager( p.webRTCManager, err = newWebRTCManager(
p.conf.WebRTCAddress, p.conf.WebRTCAddress,
p.conf.WebRTCEncryption, p.conf.WebRTCEncryption,
p.conf.WebRTCServerKey, p.conf.WebRTCServerKey,
p.conf.WebRTCServerCert, p.conf.WebRTCServerCert,
p.conf.WebRTCAllowOrigin, p.conf.WebRTCAllowOrigin,
p.conf.WebRTCTrustedProxies, p.conf.WebRTCTrustedProxies,
p.conf.WebRTCICEServers2, p.conf.WebRTCICEServers2,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf.WriteQueueSize, p.conf.WriteQueueSize,
p.conf.WebRTCICEHostNAT1To1IPs, p.conf.WebRTCICEHostNAT1To1IPs,
p.conf.WebRTCICEUDPMuxAddress, p.conf.WebRTCICEUDPMuxAddress,
p.conf.WebRTCICETCPMuxAddress, p.conf.WebRTCICETCPMuxAddress,
p.pathManager, p.pathManager,
p.metrics, p.metrics,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.SRT { if p.conf.SRT &&
if p.srtServer == nil { p.srtServer == nil {
p.srtServer, err = newSRTServer( p.srtServer, err = newSRTServer(
p.conf.SRTAddress, p.conf.SRTAddress,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf.WriteTimeout, p.conf.WriteTimeout,
p.conf.WriteQueueSize, p.conf.WriteQueueSize,
p.conf.UDPMaxPayloadSize, p.conf.UDPMaxPayloadSize,
p.externalCmdPool, p.externalCmdPool,
p.pathManager, p.pathManager,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }
if p.conf.API { if p.conf.API &&
if p.api == nil { p.api == nil {
p.api, err = newAPI( p.api, err = newAPI(
p.conf.APIAddress, p.conf.APIAddress,
p.conf.ReadTimeout, p.conf.ReadTimeout,
p.conf, p.conf,
p.pathManager, p.pathManager,
p.rtspServer, p.rtspServer,
p.rtspsServer, p.rtspsServer,
p.rtmpServer, p.rtmpServer,
p.rtmpsServer, p.rtmpsServer,
p.hlsManager, p.hlsManager,
p.webRTCManager, p.webRTCManager,
p.srtServer, p.srtServer,
p, p,
) )
if err != nil { if err != nil {
return err return err
}
} }
} }

View File

@@ -6,8 +6,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -111,7 +111,8 @@ func TestCoreHotReloading(t *testing.T) {
medi := testMediaH264 medi := testMediaH264
c := gortsplib.Client{} 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)") require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
}() }()
@@ -126,7 +127,8 @@ func TestCoreHotReloading(t *testing.T) {
medi := testMediaH264 medi := testMediaH264
conn := gortsplib.Client{} 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) require.NoError(t, err)
defer conn.Close() defer conn.Close()
}() }()

View File

@@ -9,9 +9,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -118,9 +118,9 @@ func TestHLSRead(t *testing.T) {
require.Equal(t, true, ok) require.Equal(t, true, ok)
defer p.Close() defer p.Close()
medi := &media.Media{ medi := &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
SPS: []byte{ // 1920x1080 baseline SPS: []byte{ // 1920x1080 baseline
@@ -136,7 +136,8 @@ func TestHLSRead(t *testing.T) {
source := gortsplib.Client{ source := gortsplib.Client{
Transport: &v, 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) require.NoError(t, err)
defer source.Close() defer source.Close()

View File

@@ -13,9 +13,9 @@ import (
"github.com/bluenviron/gohlslib" "github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/pkg/codecs" "github.com/bluenviron/gohlslib/pkg/codecs"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/bluenviron/mediamtx/internal/conf" "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)) m.ringBuffer, _ = ringbuffer.New(uint64(m.writeQueueSize))
var medias media.Medias var medias []*description.Media
videoMedia, videoTrack := m.createVideoTrack(res.stream) videoMedia, videoTrack := m.createVideoTrack(res.stream)
if videoMedia != nil { 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) { func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
var videoFormatAV1 *formats.AV1 var videoFormatAV1 *format.AV1
videoMedia := stream.Medias().FindFormat(&videoFormatAV1) videoMedia := stream.Desc().FindFormat(&videoFormatAV1)
if videoFormatAV1 != nil { if videoFormatAV1 != nil {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(m, videoMedia, videoFormatAV1, func(u unit.Unit) { stream.AddReader(m, videoMedia, videoFormatAV1, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.AV1) tunit := u.(*unit.AV1)
@@ -351,12 +348,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
err := m.muxer.WriteAV1(tunit.NTP, pts, tunit.TU) err := m.muxer.WriteAV1(tunit.NTP, pts, tunit.TU)
if err != nil { if err != nil {
return fmt.Errorf("muxer error: %v", err) 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 var videoFormatVP9 *format.VP9
videoMedia = stream.Medias().FindFormat(&videoFormatVP9) videoMedia = stream.Desc().FindFormat(&videoFormatVP9)
if videoFormatVP9 != nil { if videoFormatVP9 != nil {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(m, videoMedia, videoFormatVP9, func(u unit.Unit) { stream.AddReader(m, videoMedia, videoFormatVP9, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.VP9) tunit := u.(*unit.VP9)
@@ -386,12 +375,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
err := m.muxer.WriteVP9(tunit.NTP, pts, tunit.Frame) err := m.muxer.WriteVP9(tunit.NTP, pts, tunit.Frame)
if err != nil { if err != nil {
return fmt.Errorf("muxer error: %v", err) 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 var videoFormatH265 *format.H265
videoMedia = stream.Medias().FindFormat(&videoFormatH265) videoMedia = stream.Desc().FindFormat(&videoFormatH265)
if videoFormatH265 != nil { if videoFormatH265 != nil {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(m, videoMedia, videoFormatH265, func(u unit.Unit) { stream.AddReader(m, videoMedia, videoFormatH265, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.H265) tunit := u.(*unit.H265)
@@ -421,12 +402,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU) err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU)
if err != nil { if err != nil {
return fmt.Errorf("muxer error: %v", err) 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 var videoFormatH264 *format.H264
videoMedia = stream.Medias().FindFormat(&videoFormatH264) videoMedia = stream.Desc().FindFormat(&videoFormatH264)
if videoFormatH264 != nil { if videoFormatH264 != nil {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(m, videoMedia, videoFormatH264, func(u unit.Unit) { stream.AddReader(m, videoMedia, videoFormatH264, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.H264) tunit := u.(*unit.H264)
@@ -462,12 +435,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU) err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU)
if err != nil { if err != nil {
return fmt.Errorf("muxer error: %v", err) return fmt.Errorf("muxer error: %v", err)
@@ -490,24 +458,16 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
return nil, nil return nil, nil
} }
func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohlslib.Track) { func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
var audioFormatOpus *formats.Opus var audioFormatOpus *format.Opus
audioMedia := stream.Medias().FindFormat(&audioFormatOpus) audioMedia := stream.Desc().FindFormat(&audioFormatOpus)
if audioMedia != nil { if audioMedia != nil {
audioStartPTSFilled := false
var audioStartPTS time.Duration
stream.AddReader(m, audioMedia, audioFormatOpus, func(u unit.Unit) { stream.AddReader(m, audioMedia, audioFormatOpus, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.Opus) tunit := u.(*unit.Opus)
if !audioStartPTSFilled { pts := tunit.PTS
audioStartPTSFilled = true
audioStartPTS = tunit.PTS
}
pts := tunit.PTS - audioStartPTS
err := m.muxer.WriteOpus( err := m.muxer.WriteOpus(
tunit.NTP, tunit.NTP,
pts, pts,
@@ -532,13 +492,10 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
} }
} }
var audioFormatMPEG4AudioGeneric *formats.MPEG4AudioGeneric var audioFormatMPEG4AudioGeneric *format.MPEG4AudioGeneric
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioGeneric) audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioGeneric)
if audioMedia != nil { if audioMedia != nil {
audioStartPTSFilled := false
var audioStartPTS time.Duration
stream.AddReader(m, audioMedia, audioFormatMPEG4AudioGeneric, func(u unit.Unit) { stream.AddReader(m, audioMedia, audioFormatMPEG4AudioGeneric, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG4AudioGeneric) tunit := u.(*unit.MPEG4AudioGeneric)
@@ -547,12 +504,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
return nil return nil
} }
if !audioStartPTSFilled { pts := tunit.PTS
audioStartPTSFilled = true
audioStartPTS = tunit.PTS
}
pts := tunit.PTS - audioStartPTS
err := m.muxer.WriteMPEG4Audio( err := m.muxer.WriteMPEG4Audio(
tunit.NTP, tunit.NTP,
pts, pts,
@@ -572,16 +524,13 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
} }
} }
var audioFormatMPEG4AudioLATM *formats.MPEG4AudioLATM var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioLATM) audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioLATM)
if audioMedia != nil && if audioMedia != nil &&
audioFormatMPEG4AudioLATM.Config != nil && audioFormatMPEG4AudioLATM.Config != nil &&
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 && len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 &&
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 { len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 {
audioStartPTSFilled := false
var audioStartPTS time.Duration
stream.AddReader(m, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) { stream.AddReader(m, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) {
m.ringBuffer.Push(func() error { m.ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG4AudioLATM) tunit := u.(*unit.MPEG4AudioLATM)
@@ -590,12 +539,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
return nil return nil
} }
if !audioStartPTSFilled { pts := tunit.PTS
audioStartPTSFilled = true
audioStartPTS = tunit.PTS
}
pts := tunit.PTS - audioStartPTS
err := m.muxer.WriteMPEG4Audio( err := m.muxer.WriteMPEG4Audio(
tunit.NTP, tunit.NTP,
pts, pts,

View File

@@ -7,8 +7,8 @@ import (
"github.com/bluenviron/gohlslib" "github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/pkg/codecs" "github.com/bluenviron/gohlslib/pkg/codecs"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/logger" "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()) s.Log(logger.Warn, err.Error())
}, },
OnTracks: func(tracks []*gohlslib.Track) error { OnTracks: func(tracks []*gohlslib.Track) error {
var medias media.Medias var medias []*description.Media
for _, track := range tracks { for _, track := range tracks {
var medi *media.Media var medi *description.Media
switch tcodec := track.Codec.(type) { switch tcodec := track.Codec.(type) {
case *codecs.AV1: case *codecs.AV1:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.AV1{}}, Formats: []format.Format{&format.AV1{}},
} }
c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) { c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) {
stream.WriteUnit(medi, medi.Formats[0], &unit.AV1{ stream.WriteUnit(medi, medi.Formats[0], &unit.AV1{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts, TU: tu,
TU: tu,
}) })
}) })
case *codecs.VP9: case *codecs.VP9:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.VP9{}}, Formats: []format.Format{&format.VP9{}},
} }
c.OnDataVP9(track, func(pts time.Duration, frame []byte) { c.OnDataVP9(track, func(pts time.Duration, frame []byte) {
stream.WriteUnit(medi, medi.Formats[0], &unit.VP9{ stream.WriteUnit(medi, medi.Formats[0], &unit.VP9{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
Frame: frame, Frame: frame,
}) })
}) })
case *codecs.H264: case *codecs.H264:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
SPS: tcodec.SPS, 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{ stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts, AU: au,
AU: au,
}) })
}) })
case *codecs.H265: case *codecs.H265:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H265{ Formats: []format.Format{&format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: tcodec.VPS, VPS: tcodec.VPS,
SPS: tcodec.SPS, 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{ stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts, AU: au,
AU: au,
}) })
}) })
case *codecs.MPEG4Audio: case *codecs.MPEG4Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
SizeLength: 13, SizeLength: 13,
IndexLength: 3, 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{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
AUs: aus, AUs: aus,
}) })
}) })
case *codecs.Opus: case *codecs.Opus:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.Opus{ Formats: []format.Format{&format.Opus{
PayloadTyp: 96, PayloadTyp: 96,
IsStereo: (tcodec.ChannelCount == 2), 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{ stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
Packets: packets, Packets: packets,
}) })
}) })
@@ -195,7 +195,7 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
} }
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if res.err != nil { if res.err != nil {

View File

@@ -8,10 +8,10 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -135,25 +135,25 @@ func TestHLSSource(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, media.Medias{ require.Equal(t, []*description.Media{
{ {
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Control: medias[0].Control, Control: desc.Medias[0].Control,
Formats: []formats.Format{ Formats: []format.Format{
&formats.H264{ &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
}, },
}, },
}, },
{ {
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Control: medias[1].Control, Control: desc.Medias[1].Control,
Formats: []formats.Format{ Formats: []format.Format{
&formats.MPEG4Audio{ &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
ProfileLevelID: 1, ProfileLevelID: 1,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
@@ -167,12 +167,12 @@ func TestHLSSource(t *testing.T) {
}, },
}, },
}, },
}, medias) }, desc.Medias)
var forma *formats.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {

View File

@@ -232,15 +232,15 @@ func (m *metrics) setHLSManager(s apiHLSManager) {
m.hlsManager = s m.hlsManager = s
} }
// rtspServerSet is called by rtspServer (plain). // setRTSPServer is called by rtspServer (plain).
func (m *metrics) rtspServerSet(s apiRTSPServer) { func (m *metrics) setRTSPServer(s apiRTSPServer) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
m.rtspServer = s m.rtspServer = s
} }
// rtspsServerSet is called by rtspServer (tls). // setRTSPSServer is called by rtspServer (tls).
func (m *metrics) rtspsServerSet(s apiRTSPServer) { func (m *metrics) setRTSPSServer(s apiRTSPServer) {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
m.rtspsServer = s m.rtspsServer = s

View File

@@ -9,9 +9,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/rtmp" "github.com/bluenviron/mediamtx/internal/rtmp"
@@ -69,13 +69,13 @@ webrtc_sessions_bytes_sent 0
source := gortsplib.Client{} source := gortsplib.Client{}
err = source.StartRecording("rtsp://localhost:8554/rtsp_path", err = source.StartRecording("rtsp://localhost:8554/rtsp_path",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}} source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
err = source2.StartRecording("rtsps://localhost:8322/rtsps_path", err = source2.StartRecording("rtsps://localhost:8322/rtsps_path",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source2.Close() defer source2.Close()
@@ -89,7 +89,7 @@ webrtc_sessions_bytes_sent 0
conn, err := rtmp.NewClientConn(nconn, u, true) conn, err := rtmp.NewClientConn(nconn, u, true)
require.NoError(t, err) require.NoError(t, err)
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ // 1920x1080 baseline SPS: []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,

View File

@@ -10,8 +10,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"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/conf"
"github.com/bluenviron/mediamtx/internal/externalcmd" "github.com/bluenviron/mediamtx/internal/externalcmd"
@@ -56,7 +56,7 @@ type pathSourceStaticSetReadyRes struct {
} }
type pathSourceStaticSetReadyReq struct { type pathSourceStaticSetReadyReq struct {
medias media.Medias desc *description.Session
generateRTPPackets bool generateRTPPackets bool
res chan pathSourceStaticSetReadyRes res chan pathSourceStaticSetReadyRes
} }
@@ -135,7 +135,7 @@ type pathStartPublisherRes struct {
type pathStartPublisherReq struct { type pathStartPublisherReq struct {
author publisher author publisher
medias media.Medias desc *description.Session
generateRTPPackets bool generateRTPPackets bool
res chan pathStartPublisherRes 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( stream, err := stream.New(
pa.udpMaxPayloadSize, pa.udpMaxPayloadSize,
medias, desc,
allocateEncoder, allocateEncoder,
pa.bytesReceived, pa.bytesReceived,
pa.source, pa.source,
@@ -654,7 +654,7 @@ func (pa *path) doPublisherRemove() {
} }
func (pa *path) handleSourceStaticSetReady(req pathSourceStaticSetReadyReq) { func (pa *path) handleSourceStaticSetReady(req pathSourceStaticSetReadyReq) {
err := pa.setReady(req.medias, req.generateRTPPackets) err := pa.setReady(req.desc, req.generateRTPPackets)
if err != nil { if err != nil {
req.res <- pathSourceStaticSetReadyRes{err: err} req.res <- pathSourceStaticSetReadyRes{err: err}
return return
@@ -782,7 +782,7 @@ func (pa *path) handleStartPublisher(req pathStartPublisherReq) {
return return
} }
err := pa.setReady(req.medias, req.generateRTPPackets) err := pa.setReady(req.desc, req.generateRTPPackets)
if err != nil { if err != nil {
req.res <- pathStartPublisherRes{err: err} req.res <- pathStartPublisherRes{err: err}
return return
@@ -790,7 +790,7 @@ func (pa *path) handleStartPublisher(req pathStartPublisherReq) {
req.author.Log(logger.Info, "is publishing to path '%s', %s", req.author.Log(logger.Info, "is publishing to path '%s', %s",
pa.name, pa.name,
sourceMediaInfo(req.medias)) sourceMediaInfo(req.desc.Medias))
if pa.conf.HasOnDemandPublisher() { if pa.conf.HasOnDemandPublisher() {
pa.onDemandPublisherReadyTimer.Stop() pa.onDemandPublisherReadyTimer.Stop()
@@ -920,7 +920,7 @@ func (pa *path) handleAPIPathsGet(req pathAPIPathsGetReq) {
if pa.stream == nil { if pa.stream == nil {
return []string{} return []string{}
} }
return mediasDescription(pa.stream.Medias()) return mediasDescription(pa.stream.Desc().Medias)
}(), }(),
BytesReceived: atomic.LoadUint64(pa.bytesReceived), BytesReceived: atomic.LoadUint64(pa.bytesReceived),
Readers: func() []interface{} { Readers: func() []interface{} {

View File

@@ -10,12 +10,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v3/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -103,9 +103,9 @@ import (
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
) )
func main() { func main() {
@@ -113,9 +113,9 @@ func main() {
panic("environment not set") panic("environment not set")
} }
medi := &media.Media{ medi := &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04}, SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04}, PPS: []byte{0x01, 0x02, 0x03, 0x04},
@@ -127,7 +127,7 @@ func main() {
err := source.StartRecording( err := source.StartRecording(
"rtsp://localhost:" + os.Getenv("RTSP_PORT") + "/" + os.Getenv("MTX_PATH"), "rtsp://localhost:" + os.Getenv("RTSP_PORT") + "/" + os.Getenv("MTX_PATH"),
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -264,7 +264,7 @@ func TestPathRunOnReady(t *testing.T) {
err := c.StartRecording( err := c.StartRecording(
"rtsp://localhost:8554/test", "rtsp://localhost:8554/test",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
@@ -284,7 +284,7 @@ func TestPathMaxReaders(t *testing.T) {
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording( err := source.StartRecording(
"rtsp://localhost:8554/mystream", "rtsp://localhost:8554/mystream",
media.Medias{testMediaH264}) &description.Session{Medias: []*description.Media{testMediaH264}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -298,10 +298,10 @@ func TestPathMaxReaders(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer reader.Close() defer reader.Close()
medias, baseURL, _, err := reader.Describe(u) desc, _, err := reader.Describe(u)
require.NoError(t, err) require.NoError(t, err)
err = reader.SetupAll(medias, baseURL) err = reader.SetupAll(desc.BaseURL, desc.Medias)
if i != 1 { if i != 1 {
require.NoError(t, err) require.NoError(t, err)
} else { } else {

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -75,20 +75,20 @@ func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interfa
// run implements sourceStaticImpl. // run implements sourceStaticImpl.
func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error { func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
medi := &media.Media{ medi := &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
}}, }},
} }
medias := media.Medias{medi} medias := []*description.Media{medi}
var stream *stream.Stream var stream *stream.Stream
onData := func(dts time.Duration, au [][]byte) { onData := func(dts time.Duration, au [][]byte) {
if stream == nil { if stream == nil {
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if res.err != nil { 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{ stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: dts,
}, },
PTS: dts, AU: au,
AU: au,
}) })
} }

View File

@@ -10,9 +10,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
@@ -247,17 +247,13 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
ringBuffer.Close() ringBuffer.Close()
}() }()
var medias media.Medias var medias []*description.Media
videoFirstIDRFound := false
var videoStartDTS time.Duration
var w *rtmp.Writer var w *rtmp.Writer
videoMedia, videoFormat := c.setupVideo( videoMedia, videoFormat := c.setupVideo(
&w, &w,
res.stream, res.stream,
ringBuffer, ringBuffer)
&videoFirstIDRFound,
&videoStartDTS)
if videoMedia != nil { if videoMedia != nil {
medias = append(medias, videoMedia) medias = append(medias, videoMedia)
} }
@@ -265,10 +261,7 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
audioMedia, audioFormat := c.setupAudio( audioMedia, audioFormat := c.setupAudio(
&w, &w,
res.stream, res.stream,
ringBuffer, ringBuffer)
videoFormat,
&videoFirstIDRFound,
&videoStartDTS)
if audioFormat != nil { if audioFormat != nil {
medias = append(medias, audioMedia) medias = append(medias, audioMedia)
} }
@@ -327,15 +320,11 @@ func (c *rtmpConn) setupVideo(
w **rtmp.Writer, w **rtmp.Writer,
stream *stream.Stream, stream *stream.Stream,
ringBuffer *ringbuffer.RingBuffer, ringBuffer *ringbuffer.RingBuffer,
videoFirstIDRFound *bool, ) (*description.Media, format.Format) {
videoStartDTS *time.Duration, var videoFormatH264 *format.H264
) (*media.Media, formats.Format) { videoMedia := stream.Desc().FindFormat(&videoFormatH264)
var videoFormatH264 *formats.H264
videoMedia := stream.Medias().FindFormat(&videoFormatH264)
if videoFormatH264 != nil { if videoFormatH264 != nil {
startPTSFilled := false
var startPTS time.Duration
var videoDTSExtractor *h264.DTSExtractor var videoDTSExtractor *h264.DTSExtractor
stream.AddReader(c, videoMedia, videoFormatH264, func(u unit.Unit) { stream.AddReader(c, videoMedia, videoFormatH264, func(u unit.Unit) {
@@ -346,11 +335,7 @@ func (c *rtmpConn) setupVideo(
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
idrPresent := false idrPresent := false
nonIDRPresent := false nonIDRPresent := false
@@ -369,12 +354,11 @@ func (c *rtmpConn) setupVideo(
var dts time.Duration var dts time.Duration
// wait until we receive an IDR // wait until we receive an IDR
if !*videoFirstIDRFound { if videoDTSExtractor == nil {
if !idrPresent { if !idrPresent {
return nil return nil
} }
*videoFirstIDRFound = true
videoDTSExtractor = h264.NewDTSExtractor() videoDTSExtractor = h264.NewDTSExtractor()
var err error var err error
@@ -382,10 +366,6 @@ func (c *rtmpConn) setupVideo(
if err != nil { if err != nil {
return err return err
} }
*videoStartDTS = dts
dts = 0
pts -= *videoStartDTS
} else { } else {
if !idrPresent && !nonIDRPresent { if !idrPresent && !nonIDRPresent {
return nil return nil
@@ -396,9 +376,6 @@ func (c *rtmpConn) setupVideo(
if err != nil { if err != nil {
return err return err
} }
dts -= *videoStartDTS
pts -= *videoStartDTS
} }
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
@@ -416,17 +393,11 @@ func (c *rtmpConn) setupAudio(
w **rtmp.Writer, w **rtmp.Writer,
stream *stream.Stream, stream *stream.Stream,
ringBuffer *ringbuffer.RingBuffer, ringBuffer *ringbuffer.RingBuffer,
videoFormat formats.Format, ) (*description.Media, format.Format) {
videoFirstIDRFound *bool, var audioFormatMPEG4Generic *format.MPEG4AudioGeneric
videoStartDTS *time.Duration, audioMedia := stream.Desc().FindFormat(&audioFormatMPEG4Generic)
) (*media.Media, formats.Format) {
var audioFormatMPEG4Generic *formats.MPEG4AudioGeneric
audioMedia := stream.Medias().FindFormat(&audioFormatMPEG4Generic)
if audioMedia != nil { if audioMedia != nil {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(c, audioMedia, audioFormatMPEG4Generic, func(u unit.Unit) { stream.AddReader(c, audioMedia, audioFormatMPEG4Generic, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG4AudioGeneric) tunit := u.(*unit.MPEG4AudioGeneric)
@@ -435,22 +406,7 @@ func (c *rtmpConn) setupAudio(
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
if videoFormat != nil {
if !*videoFirstIDRFound {
return nil
}
pts -= *videoStartDTS
if pts < 0 {
return nil
}
}
for i, au := range tunit.AUs { for i, au := range tunit.AUs {
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
@@ -471,16 +427,13 @@ func (c *rtmpConn) setupAudio(
return audioMedia, audioFormatMPEG4Generic return audioMedia, audioFormatMPEG4Generic
} }
var audioFormatMPEG4AudioLATM *formats.MPEG4AudioLATM var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioLATM) audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioLATM)
if audioMedia != nil && if audioMedia != nil &&
audioFormatMPEG4AudioLATM.Config != nil && audioFormatMPEG4AudioLATM.Config != nil &&
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 && len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 &&
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 { len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(c, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) { stream.AddReader(c, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG4AudioLATM) tunit := u.(*unit.MPEG4AudioLATM)
@@ -489,22 +442,7 @@ func (c *rtmpConn) setupAudio(
return nil return nil
} }
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
if videoFormat != nil {
if !*videoFirstIDRFound {
return nil
}
pts -= *videoStartDTS
if pts < 0 {
return nil
}
}
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
return (*w).WriteMPEG4Audio(pts, tunit.AU) return (*w).WriteMPEG4Audio(pts, tunit.AU)
@@ -514,33 +452,15 @@ func (c *rtmpConn) setupAudio(
return audioMedia, audioFormatMPEG4AudioLATM return audioMedia, audioFormatMPEG4AudioLATM
} }
var audioFormatMPEG1 *formats.MPEG1Audio var audioFormatMPEG1 *format.MPEG1Audio
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG1) audioMedia = stream.Desc().FindFormat(&audioFormatMPEG1)
if audioMedia != nil { if audioMedia != nil {
startPTSFilled := false
var startPTS time.Duration
stream.AddReader(c, audioMedia, audioFormatMPEG1, func(u unit.Unit) { stream.AddReader(c, audioMedia, audioFormatMPEG1, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG1Audio) tunit := u.(*unit.MPEG1Audio)
if !startPTSFilled { pts := tunit.PTS
startPTSFilled = true
startPTS = tunit.PTS
}
pts := tunit.PTS - startPTS
if videoFormat != nil {
if !*videoFirstIDRFound {
return nil
}
pts -= *videoStartDTS
if pts < 0 {
return nil
}
}
for _, frame := range tunit.Frames { for _, frame := range tunit.Frames {
var h mpeg1audio.FrameHeader var h mpeg1audio.FrameHeader
@@ -611,58 +531,58 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
} }
videoFormat, audioFormat := r.Tracks() videoFormat, audioFormat := r.Tracks()
var medias media.Medias var medias []*description.Media
var stream *stream.Stream var stream *stream.Stream
if videoFormat != nil { if videoFormat != nil {
videoMedia := &media.Media{ videoMedia := &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{videoFormat}, Formats: []format.Format{videoFormat},
} }
medias = append(medias, videoMedia) medias = append(medias, videoMedia)
switch videoFormat.(type) { switch videoFormat.(type) {
case *formats.AV1: case *format.AV1:
r.OnDataAV1(func(pts time.Duration, tu [][]byte) { r.OnDataAV1(func(pts time.Duration, tu [][]byte) {
stream.WriteUnit(videoMedia, videoFormat, &unit.AV1{ stream.WriteUnit(videoMedia, videoFormat, &unit.AV1{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), 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) { r.OnDataVP9(func(pts time.Duration, frame []byte) {
stream.WriteUnit(videoMedia, videoFormat, &unit.VP9{ stream.WriteUnit(videoMedia, videoFormat, &unit.VP9{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
Frame: frame, Frame: frame,
}) })
}) })
case *formats.H265: case *format.H265:
r.OnDataH265(func(pts time.Duration, au [][]byte) { r.OnDataH265(func(pts time.Duration, au [][]byte) {
stream.WriteUnit(videoMedia, videoFormat, &unit.H265{ stream.WriteUnit(videoMedia, videoFormat, &unit.H265{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), 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) { r.OnDataH264(func(pts time.Duration, au [][]byte) {
stream.WriteUnit(videoMedia, videoFormat, &unit.H264{ stream.WriteUnit(videoMedia, videoFormat, &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), 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 if audioFormat != nil { //nolint:dupl
audioMedia := &media.Media{ audioMedia := &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{audioFormat}, Formats: []format.Format{audioFormat},
} }
medias = append(medias, audioMedia) medias = append(medias, audioMedia)
switch audioFormat.(type) { switch audioFormat.(type) {
case *formats.MPEG4AudioGeneric: case *format.MPEG4AudioGeneric:
r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) { r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) {
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{ stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
AUs: [][]byte{au}, AUs: [][]byte{au},
}) })
}) })
case *formats.MPEG1Audio: case *format.MPEG1Audio:
r.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) { r.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{ stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
Frames: [][]byte{frame}, Frames: [][]byte{frame},
}) })
}) })
@@ -708,7 +628,7 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
rres := res.path.startPublisher(pathStartPublisherReq{ rres := res.path.startPublisher(pathStartPublisherReq{
author: c, author: c,
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if rres.err != nil { if rres.err != nil {

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -128,7 +128,7 @@ func TestRTMPServer(t *testing.T) {
conn1, err := rtmp.NewClientConn(nconn1, u1, true) conn1, err := rtmp.NewClientConn(nconn1, u1, true)
require.NoError(t, err) require.NoError(t, err)
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ // 1920x1080 baseline SPS: []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
@@ -139,7 +139,7 @@ func TestRTMPServer(t *testing.T) {
PacketizationMode: 1, PacketizationMode: 1,
} }
audioTrack := &formats.MPEG4Audio{ audioTrack := &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -238,7 +238,7 @@ func TestRTMPServerAuthFail(t *testing.T) {
conn1, err := rtmp.NewClientConn(nconn1, u1, true) conn1, err := rtmp.NewClientConn(nconn1, u1, true)
require.NoError(t, err) require.NoError(t, err)
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
@@ -290,7 +290,7 @@ func TestRTMPServerAuthFail(t *testing.T) {
conn1, err := rtmp.NewClientConn(nconn1, u1, true) conn1, err := rtmp.NewClientConn(nconn1, u1, true)
require.NoError(t, err) require.NoError(t, err)
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
@@ -343,7 +343,7 @@ func TestRTMPServerAuthFail(t *testing.T) {
conn1, err := rtmp.NewClientConn(nconn1, u1, true) conn1, err := rtmp.NewClientConn(nconn1, u1, true)
require.NoError(t, err) require.NoError(t, err)
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,

View File

@@ -8,8 +8,8 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/logger" "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() videoFormat, audioFormat := mc.Tracks()
var medias media.Medias var medias []*description.Media
var stream *stream.Stream var stream *stream.Stream
if videoFormat != nil { if videoFormat != nil {
videoMedia := &media.Media{ videoMedia := &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{videoFormat}, Formats: []format.Format{videoFormat},
} }
medias = append(medias, videoMedia) medias = append(medias, videoMedia)
switch videoFormat.(type) { switch videoFormat.(type) {
case *formats.H264: case *format.H264:
mc.OnDataH264(func(pts time.Duration, au [][]byte) { mc.OnDataH264(func(pts time.Duration, au [][]byte) {
stream.WriteUnit(videoMedia, videoFormat, &unit.H264{ stream.WriteUnit(videoMedia, videoFormat, &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), 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 if audioFormat != nil { //nolint:dupl
audioMedia := &media.Media{ audioMedia := &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{audioFormat}, Formats: []format.Format{audioFormat},
} }
medias = append(medias, audioMedia) medias = append(medias, audioMedia)
switch audioFormat.(type) { switch audioFormat.(type) {
case *formats.MPEG4AudioGeneric: case *format.MPEG4AudioGeneric:
mc.OnDataMPEG4Audio(func(pts time.Duration, au []byte) { mc.OnDataMPEG4Audio(func(pts time.Duration, au []byte) {
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{ stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
AUs: [][]byte{au}, AUs: [][]byte{au},
}) })
}) })
case *formats.MPEG1Audio: case *format.MPEG1Audio:
mc.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) { mc.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{ stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: pts,
}, },
PTS: pts,
Frames: [][]byte{frame}, Frames: [][]byte{frame},
}) })
}) })
@@ -176,7 +176,7 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error {
} }
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if res.err != nil { if res.err != nil {

View File

@@ -6,9 +6,9 @@ import (
"os" "os"
"testing" "testing"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -56,7 +56,7 @@ func TestRTMPSource(t *testing.T) {
conn, _, _, err := rtmp.NewServerConn(nconn) conn, _, _, err := rtmp.NewServerConn(nconn)
require.NoError(t, err) require.NoError(t, err)
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ // 1920x1080 baseline SPS: []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
@@ -67,7 +67,7 @@ func TestRTMPSource(t *testing.T) {
PacketizationMode: 1, PacketizationMode: 1,
} }
audioTrack := &formats.MPEG4Audio{ audioTrack := &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -116,13 +116,13 @@ func TestRTMPSource(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
var forma *formats.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {

View File

@@ -5,10 +5,10 @@ import (
"net" "net"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
@@ -22,6 +22,8 @@ const (
type rtspConnParent interface { type rtspConnParent interface {
logger.Writer logger.Writer
getISTLS() bool
getServer() *gortsplib.Server
} }
type rtspConn struct { type rtspConn struct {
@@ -138,7 +140,7 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
if c.authNonce == "" { if c.authNonce == "" {
var err error var err error
c.authNonce, err = auth.GenerateNonce2() c.authNonce, err = auth.GenerateNonce()
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusInternalServerError, StatusCode: base.StatusInternalServerError,
@@ -186,9 +188,16 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
}, nil, nil }, 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{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, res.stream.RTSPStream(), nil }, stream, nil
} }
func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) { func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) {

View File

@@ -9,10 +9,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v3/pkg/liberrors" "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf"
@@ -108,12 +108,11 @@ func newRTSPServer(
} }
s.srv = &gortsplib.Server{ s.srv = &gortsplib.Server{
Handler: s, Handler: s,
ReadTimeout: time.Duration(readTimeout), ReadTimeout: time.Duration(readTimeout),
WriteTimeout: time.Duration(writeTimeout), WriteTimeout: time.Duration(writeTimeout),
ReadBufferCount: writeQueueSize, WriteQueueSize: writeQueueSize,
WriteBufferCount: writeQueueSize, RTSPAddress: address,
RTSPAddress: address,
} }
if useUDP { if useUDP {
@@ -145,9 +144,9 @@ func newRTSPServer(
if metrics != nil { if metrics != nil {
if !isTLS { if !isTLS {
metrics.rtspServerSet(s) metrics.setRTSPServer(s)
} else { } 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...)...) 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() { func (s *rtspServer) close() {
s.Log(logger.Info, "listener is closing") s.Log(logger.Info, "listener is closing")
s.ctxCancel() s.ctxCancel()
@@ -197,9 +204,9 @@ outer:
if s.metrics != nil { if s.metrics != nil {
if !s.isTLS { if !s.isTLS {
s.metrics.rtspServerSet(nil) s.metrics.setRTSPServer(nil)
} else { } else {
s.metrics.rtspsServerSet(nil) s.metrics.setRTSPSServer(nil)
} }
} }
} }

View File

@@ -5,9 +5,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -29,7 +29,7 @@ func TestRTSPServerRunOnConnect(t *testing.T) {
err = source.StartRecording( err = source.StartRecording(
"rtsp://127.0.0.1:8554/mypath", "rtsp://127.0.0.1:8554/mypath",
media.Medias{testMediaH264}) &description.Session{Medias: []*description.Media{testMediaH264}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -88,7 +88,7 @@ func TestRTSPServer(t *testing.T) {
err := source.StartRecording( err := source.StartRecording(
"rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value", "rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -107,10 +107,10 @@ func TestRTSPServer(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer reader.Close() defer reader.Close()
medias, baseURL, _, err := reader.Describe(u) desc, _, err := reader.Describe(u)
require.NoError(t, err) require.NoError(t, err)
err = reader.SetupAll(medias, baseURL) err = reader.SetupAll(desc.BaseURL, desc.Medias)
require.NoError(t, err) require.NoError(t, err)
_, err = reader.Play(nil) _, err = reader.Play(nil)
@@ -137,7 +137,7 @@ func TestRTSPServerAuthHashed(t *testing.T) {
err := source.StartRecording( err := source.StartRecording(
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream", "rtsp://testuser:testpass@127.0.0.1:8554/test/stream",
media.Medias{medi}) &description.Session{Medias: []*description.Media{medi}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
} }
@@ -181,7 +181,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
err := c.StartRecording( err := c.StartRecording(
"rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream", "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)") require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
}) })
@@ -228,7 +228,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
_, _, _, err = c.Describe(u) _, _, err = c.Describe(u)
require.EqualError(t, err, "bad status code: 401 (Unauthorized)") require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
}) })
} }
@@ -249,7 +249,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
err := c.StartRecording( err := c.StartRecording(
"rtsp://localhost:8554/test/stream", "rtsp://localhost:8554/test/stream",
media.Medias{medi}, &description.Session{Medias: []*description.Media{medi}},
) )
require.EqualError(t, err, "bad status code: 401 (Unauthorized)") require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
}) })
@@ -270,7 +270,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
err := c.StartRecording( err := c.StartRecording(
"rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value", "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)") require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
}) })
@@ -298,13 +298,15 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
s1 := gortsplib.Client{} 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) require.NoError(t, err)
defer s1.Close() defer s1.Close()
s2 := gortsplib.Client{} 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" { if ca == "enabled" {
require.NoError(t, err) require.NoError(t, err)
defer s2.Close() defer s2.Close()
@@ -323,17 +325,17 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
err = c.SetupAll(medias, baseURL) err = c.SetupAll(desc.BaseURL, desc.Medias)
require.NoError(t, err) 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" { 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 { } else {
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload) require.Equal(t, []byte{5, 11, 12, 13, 14}, pkt.Payload)
} }
close(frameRecv) close(frameRecv)
}) })
@@ -354,7 +356,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
SSRC: 978651231, SSRC: 978651231,
Marker: true, Marker: true,
}, },
Payload: []byte{0x05, 0x06, 0x07, 0x08}, Payload: []byte{5, 15, 16, 17, 18},
}) })
require.NoError(t, err) require.NoError(t, err)
} else { } else {
@@ -367,7 +369,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
SSRC: 978651231, SSRC: 978651231,
Marker: true, Marker: true,
}, },
Payload: []byte{0x01, 0x02, 0x03, 0x04}, Payload: []byte{5, 11, 12, 13, 14},
}) })
require.NoError(t, err) require.NoError(t, err)
} }
@@ -402,7 +404,7 @@ func TestRTSPServerFallback(t *testing.T) {
source := gortsplib.Client{} source := gortsplib.Client{}
err := source.StartRecording("rtsp://localhost:8554/path2", err := source.StartRecording("rtsp://localhost:8554/path2",
media.Medias{testMediaH264}) &description.Session{Medias: []*description.Media{testMediaH264}})
require.NoError(t, err) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -414,9 +416,9 @@ func TestRTSPServerFallback(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer dest.Close() defer dest.Close()
medias, _, _, err := dest.Describe(u) desc, _, err := dest.Describe(u)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(medias)) require.Equal(t, 1, len(desc.Medias))
}) })
} }
} }

View File

@@ -7,10 +7,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -27,6 +27,8 @@ type rtspSessionPathManager interface {
type rtspSessionParent interface { type rtspSessionParent interface {
logger.Writer logger.Writer
getISTLS() bool
getServer() *gortsplib.Server
} }
type rtspSession struct { type rtspSession struct {
@@ -124,7 +126,7 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
if c.authNonce == "" { if c.authNonce == "" {
var err error var err error
c.authNonce, err = auth.GenerateNonce2() c.authNonce, err = auth.GenerateNonce()
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusInternalServerError, StatusCode: base.StatusInternalServerError,
@@ -209,7 +211,7 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
if c.authNonce == "" { if c.authNonce == "" {
var err error var err error
c.authNonce, err = auth.GenerateNonce2() c.authNonce, err = auth.GenerateNonce()
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusInternalServerError, StatusCode: base.StatusInternalServerError,
@@ -257,9 +259,16 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
s.pathName = ctx.Path s.pathName = ctx.Path
s.mutex.Unlock() 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{ return &base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
}, res.stream.RTSPStream(), nil }, stream, nil
default: // record default: // record
return &base.Response{ return &base.Response{
@@ -308,7 +317,7 @@ func (s *rtspSession) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Respons
func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
res := s.path.startPublisher(pathStartPublisherReq{ res := s.path.startPublisher(pathStartPublisherReq{
author: s, author: s,
medias: s.session.AnnouncedMedias(), desc: s.session.AnnouncedDescription(),
generateRTPPackets: false, generateRTPPackets: false,
}) })
if res.err != nil { if res.err != nil {
@@ -319,13 +328,18 @@ func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Res
s.stream = res.stream s.stream = res.stream
for _, medi := range s.session.AnnouncedMedias() { for _, medi := range s.session.AnnouncedDescription().Medias {
for _, forma := range medi.Formats { for _, forma := range medi.Formats {
cmedi := medi cmedi := medi
cforma := forma cforma := forma
s.session.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { 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)
}) })
} }
} }

View File

@@ -4,12 +4,12 @@ import (
"context" "context"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/pion/rtp" "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/conf"
"github.com/bluenviron/mediamtx/internal/logger" "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") s.Log(logger.Debug, "connecting")
c := &gortsplib.Client{ c := &gortsplib.Client{
Transport: cnf.SourceProtocol.Transport, Transport: cnf.SourceProtocol.Transport,
TLSConfig: tlsConfigForFingerprint(cnf.SourceFingerprint), TLSConfig: tlsConfigForFingerprint(cnf.SourceFingerprint),
ReadTimeout: time.Duration(s.readTimeout), ReadTimeout: time.Duration(s.readTimeout),
WriteTimeout: time.Duration(s.writeTimeout), WriteTimeout: time.Duration(s.writeTimeout),
ReadBufferCount: s.writeQueueSize, WriteQueueSize: s.writeQueueSize,
WriteBufferCount: s.writeQueueSize, AnyPortEnable: cnf.SourceAnyPortEnable,
AnyPortEnable: cnf.SourceAnyPortEnable,
OnRequest: func(req *base.Request) { OnRequest: func(req *base.Request) {
s.Log(logger.Debug, "c->s %v", req) 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) readErr := make(chan error)
go func() { go func() {
readErr <- func() error { readErr <- func() error {
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
return err return err
} }
err = c.SetupAll(medias, baseURL) err = c.SetupAll(desc.BaseURL, desc.Medias)
if err != nil { if err != nil {
return err return err
} }
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: desc,
generateRTPPackets: false, generateRTPPackets: false,
}) })
if res.err != nil { 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{}) defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{})
for _, medi := range medias { for _, medi := range desc.Medias {
for _, forma := range medi.Formats { for _, forma := range medi.Formats {
cmedi := medi cmedi := medi
cforma := forma cforma := forma
c.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) { 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)
}) })
} }
} }

View File

@@ -6,12 +6,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -43,9 +43,9 @@ func TestRTSPSource(t *testing.T) {
} { } {
t.Run(source, func(t *testing.T) { t.Run(source, func(t *testing.T) {
serverMedia := testMediaH264 serverMedia := testMediaH264
stream := gortsplib.NewServerStream(media.Medias{serverMedia}) var stream *gortsplib.ServerStream
nonce, err := auth.GenerateNonce2() nonce, err := auth.GenerateNonce()
require.NoError(t, err) require.NoError(t, err)
s := gortsplib.Server{ s := gortsplib.Server{
@@ -74,7 +74,7 @@ func TestRTSPSource(t *testing.T) {
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) { onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
go func() { go func() {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
stream.WritePacketRTP(serverMedia, &rtp.Packet{ err := stream.WritePacketRTP(serverMedia, &rtp.Packet{
Header: rtp.Header{ Header: rtp.Header{
Version: 0x02, Version: 0x02,
PayloadType: 96, PayloadType: 96,
@@ -83,8 +83,9 @@ func TestRTSPSource(t *testing.T) {
SSRC: 978651231, SSRC: 978651231,
Marker: true, Marker: true,
}, },
Payload: []byte{0x01, 0x02, 0x03, 0x04}, Payload: []byte{5, 1, 2, 3, 4},
}) })
require.NoError(t, err)
}() }()
return &base.Response{ return &base.Response{
@@ -120,6 +121,9 @@ func TestRTSPSource(t *testing.T) {
defer s.Wait() //nolint:errcheck defer s.Wait() //nolint:errcheck
defer s.Close() defer s.Close()
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{serverMedia}})
defer stream.Close()
if source == "udp" || source == "tcp" { if source == "udp" || source == "tcp" {
p, ok := newInstance("paths:\n" + p, ok := newInstance("paths:\n" +
" proxied:\n" + " proxied:\n" +
@@ -149,17 +153,17 @@ func TestRTSPSource(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
var forma *formats.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { 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) close(received)
}) })
@@ -172,9 +176,9 @@ func TestRTSPSource(t *testing.T) {
} }
func TestRTSPSourceNoPassword(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) require.NoError(t, err)
done := make(chan struct{}) done := make(chan struct{})
@@ -210,11 +214,15 @@ func TestRTSPSourceNoPassword(t *testing.T) {
}, },
RTSPAddress: "127.0.0.1:8555", RTSPAddress: "127.0.0.1:8555",
} }
err = s.Start() err = s.Start()
require.NoError(t, err) require.NoError(t, err)
defer s.Wait() //nolint:errcheck defer s.Wait() //nolint:errcheck
defer s.Close() defer s.Close()
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
defer stream.Close()
p, ok := newInstance("rtmp: no\n" + p, ok := newInstance("rtmp: no\n" +
"hls: no\n" + "hls: no\n" +
"webrtc: no\n" + "webrtc: no\n" +
@@ -231,7 +239,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
func TestRTSPSourceRange(t *testing.T) { func TestRTSPSourceRange(t *testing.T) {
for _, ca := range []string{"clock", "npt", "smpte"} { for _, ca := range []string{"clock", "npt", "smpte"} {
t.Run(ca, func(t *testing.T) { t.Run(ca, func(t *testing.T) {
stream := gortsplib.NewServerStream(media.Medias{testMediaH264}) var stream *gortsplib.ServerStream
done := make(chan struct{}) done := make(chan struct{})
s := gortsplib.Server{ s := gortsplib.Server{
@@ -266,11 +274,15 @@ func TestRTSPSourceRange(t *testing.T) {
}, },
RTSPAddress: "127.0.0.1:8555", RTSPAddress: "127.0.0.1:8555",
} }
err := s.Start() err := s.Start()
require.NoError(t, err) require.NoError(t, err)
defer s.Wait() //nolint:errcheck defer s.Wait() //nolint:errcheck
defer s.Close() defer s.Close()
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
defer stream.Close()
var addConf string var addConf string
switch ca { switch ca {
case "clock": case "clock":

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
) )
@@ -19,7 +19,7 @@ type source interface {
apiSourceDescribe() pathAPISourceOrReader apiSourceDescribe() pathAPISourceOrReader
} }
func mediaDescription(media *media.Media) string { func mediaDescription(media *description.Media) string {
ret := make([]string, len(media.Formats)) ret := make([]string, len(media.Formats))
for i, forma := range media.Formats { for i, forma := range media.Formats {
ret[i] = forma.Codec() ret[i] = forma.Codec()
@@ -27,7 +27,7 @@ func mediaDescription(media *media.Media) string {
return strings.Join(ret, "/") return strings.Join(ret, "/")
} }
func mediasDescription(medias media.Medias) []string { func mediasDescription(medias []*description.Media) []string {
ret := make([]string, len(medias)) ret := make([]string, len(medias))
for i, media := range medias { for i, media := range medias {
ret[i] = mediaDescription(media) ret[i] = mediaDescription(media)
@@ -35,7 +35,7 @@ func mediasDescription(medias media.Medias) []string {
return ret return ret
} }
func sourceMediaInfo(medias media.Medias) string { func sourceMediaInfo(medias []*description.Media) string {
return fmt.Sprintf("%d %s (%s)", return fmt.Sprintf("%d %s (%s)",
len(medias), len(medias),
func() string { func() string {

View File

@@ -224,7 +224,7 @@ func (s *sourceStatic) setReady(req pathSourceStaticSetReadyReq) pathSourceStati
res := <-req.res res := <-req.res
if res.err == nil { 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 return res

View File

@@ -10,9 +10,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
@@ -235,7 +235,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
return err return err
} }
var medias media.Medias var medias []*description.Media //nolint:prealloc
var stream *stream.Stream var stream *stream.Stream
var td *mpegts.TimeDecoder 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 for _, track := range r.Tracks() { //nolint:dupl
var medi *media.Media var medi *description.Media
switch tcodec := track.Codec.(type) { switch tcodec := track.Codec.(type) {
case *mpegts.CodecH264: case *mpegts.CodecH264:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
}}, }},
@@ -263,17 +263,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts), AU: au,
AU: au,
}) })
return nil return nil
}) })
case *mpegts.CodecH265: case *mpegts.CodecH265:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H265{ Formats: []format.Format{&format.H265{
PayloadTyp: 96, PayloadTyp: 96,
}}, }},
} }
@@ -282,17 +282,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts), AU: au,
AU: au,
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG4Audio: case *mpegts.CodecMPEG4Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
SizeLength: 13, SizeLength: 13,
IndexLength: 3, IndexLength: 3,
@@ -305,17 +305,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
AUs: aus, AUs: aus,
}) })
return nil return nil
}) })
case *mpegts.CodecOpus: case *mpegts.CodecOpus:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.Opus{ Formats: []format.Format{&format.Opus{
PayloadTyp: 96, PayloadTyp: 96,
IsStereo: (tcodec.ChannelCount == 2), 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{ stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
Packets: packets, Packets: packets,
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG1Audio: case *mpegts.CodecMPEG1Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG1Audio{}}, Formats: []format.Format{&format.MPEG1Audio{}},
} }
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
Frames: frames, Frames: frames,
}) })
return nil return nil
@@ -362,7 +362,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
rres := path.startPublisher(pathStartPublisherReq{ rres := path.startPublisher(pathStartPublisherReq{
author: c, author: c,
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if rres.err != nil { if rres.err != nil {
@@ -424,14 +424,10 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
var w *mpegts.Writer var w *mpegts.Writer
var tracks []*mpegts.Track var tracks []*mpegts.Track
var medias media.Medias var medias []*description.Media
bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize)) bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize))
leadingTrackChosen := false addTrack := func(medi *description.Media, codec mpegts.Codec) *mpegts.Track {
leadingTrackInitialized := false
var leadingTrackStartDTS time.Duration
addTrack := func(medi *media.Media, codec mpegts.Codec) *mpegts.Track {
track := &mpegts.Track{ track := &mpegts.Track{
Codec: codec, Codec: codec,
} }
@@ -440,37 +436,22 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
return track return track
} }
for _, medi := range res.stream.Medias() { for _, medi := range res.stream.Desc().Medias {
for _, format := range medi.Formats { for _, forma := range medi.Formats {
switch format := format.(type) { switch forma := forma.(type) {
case *formats.H265: //nolint:dupl case *format.H265: //nolint:dupl
track := addTrack(medi, &mpegts.CodecH265{}) track := addTrack(medi, &mpegts.CodecH265{})
var startPTS time.Duration
startPTSFilled := false
var isLeadingTrack bool
if !leadingTrackChosen {
isLeadingTrack = true
} else {
isLeadingTrack = false
}
randomAccessReceived := false randomAccessReceived := false
dtsExtractor := h265.NewDTSExtractor() 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 { ringBuffer.Push(func() error {
tunit := u.(*unit.H265) tunit := u.(*unit.H265)
if tunit.AU == nil { if tunit.AU == nil {
return nil return nil
} }
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
randomAccess := h265.IsRandomAccess(tunit.AU) randomAccess := h265.IsRandomAccess(tunit.AU)
if !randomAccessReceived { if !randomAccessReceived {
@@ -480,24 +461,12 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
randomAccessReceived = true randomAccessReceived = true
} }
pts := tunit.PTS - startPTS pts := tunit.PTS
dts, err := dtsExtractor.Extract(tunit.AU, pts) dts, err := dtsExtractor.Extract(tunit.AU, pts)
if err != nil { if err != nil {
return err 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))) sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), randomAccess, tunit.AU) err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), randomAccess, tunit.AU)
if err != nil { 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{}) track := addTrack(medi, &mpegts.CodecH264{})
var startPTS time.Duration
startPTSFilled := false
var isLeadingTrack bool
if !leadingTrackChosen {
isLeadingTrack = true
} else {
isLeadingTrack = false
}
firstIDRReceived := false firstIDRReceived := false
dtsExtractor := h264.NewDTSExtractor() 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 { ringBuffer.Push(func() error {
tunit := u.(*unit.H264) tunit := u.(*unit.H264)
if tunit.AU == nil { if tunit.AU == nil {
return nil return nil
} }
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
idrPresent := h264.IDRPresent(tunit.AU) idrPresent := h264.IDRPresent(tunit.AU)
if !firstIDRReceived { if !firstIDRReceived {
@@ -544,24 +498,12 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
firstIDRReceived = true firstIDRReceived = true
} }
pts := tunit.PTS - startPTS pts := tunit.PTS
dts, err := dtsExtractor.Extract(tunit.AU, pts) dts, err := dtsExtractor.Extract(tunit.AU, pts)
if err != nil { if err != nil {
return err 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))) sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), idrPresent, tunit.AU) err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), idrPresent, tunit.AU)
if err != nil { 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{ track := addTrack(medi, &mpegts.CodecMPEG4Audio{
Config: *format.Config, Config: *forma.Config,
}) })
var startPTS time.Duration res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
startPTSFilled := false
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG4AudioGeneric) tunit := u.(*unit.MPEG4AudioGeneric)
if tunit.AUs == nil { if tunit.AUs == nil {
return nil return nil
} }
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
if leadingTrackChosen && !leadingTrackInitialized {
return nil
}
pts := tunit.PTS pts := tunit.PTS
pts -= startPTS
pts -= leadingTrackStartDTS
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), tunit.AUs) 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: case *format.MPEG4AudioLATM:
if format.Config != nil && if forma.Config != nil &&
len(format.Config.Programs) == 1 && len(forma.Config.Programs) == 1 &&
len(format.Config.Programs[0].Layers) == 1 { len(forma.Config.Programs[0].Layers) == 1 {
track := addTrack(medi, &mpegts.CodecMPEG4Audio{ 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 res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
startPTSFilled := false
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG4AudioLATM) tunit := u.(*unit.MPEG4AudioLATM)
if tunit.AU == nil { if tunit.AU == nil {
return nil return nil
} }
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
if leadingTrackChosen && !leadingTrackInitialized {
return nil
}
pts := tunit.PTS pts := tunit.PTS
pts -= startPTS
pts -= leadingTrackStartDTS
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), [][]byte{tunit.AU}) 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{ track := addTrack(medi, &mpegts.CodecOpus{
ChannelCount: func() int { ChannelCount: func() int {
if format.IsStereo { if forma.IsStereo {
return 2 return 2
} }
return 1 return 1
}(), }(),
}) })
var startPTS time.Duration res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
startPTSFilled := false
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.Opus) tunit := u.(*unit.Opus)
if tunit.Packets == nil { if tunit.Packets == nil {
return nil return nil
} }
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
if leadingTrackChosen && !leadingTrackInitialized {
return nil
}
pts := tunit.PTS pts := tunit.PTS
pts -= startPTS
pts -= leadingTrackStartDTS
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteOpus(track, durationGoToMPEGTS(pts), tunit.Packets) 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{}) track := addTrack(medi, &mpegts.CodecMPEG1Audio{})
var startPTS time.Duration res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
startPTSFilled := false
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := u.(*unit.MPEG1Audio) tunit := u.(*unit.MPEG1Audio)
if tunit.Frames == nil { if tunit.Frames == nil {
return nil return nil
} }
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
if leadingTrackChosen && !leadingTrackInitialized {
return nil
}
pts := tunit.PTS pts := tunit.PTS
pts -= startPTS
pts -= leadingTrackStartDTS
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(pts), tunit.Frames) err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(pts), tunit.Frames)

View File

@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/datarhei/gosrt" "github.com/datarhei/gosrt"
@@ -91,7 +91,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
return err return err
} }
var medias media.Medias var medias []*description.Media //nolint:prealloc
var stream *stream.Stream var stream *stream.Stream
var td *mpegts.TimeDecoder var td *mpegts.TimeDecoder
@@ -103,13 +103,13 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
} }
for _, track := range r.Tracks() { //nolint:dupl for _, track := range r.Tracks() { //nolint:dupl
var medi *media.Media var medi *description.Media
switch tcodec := track.Codec.(type) { switch tcodec := track.Codec.(type) {
case *mpegts.CodecH264: case *mpegts.CodecH264:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
}}, }},
@@ -119,17 +119,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts), AU: au,
AU: au,
}) })
return nil return nil
}) })
case *mpegts.CodecH265: case *mpegts.CodecH265:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H265{ Formats: []format.Format{&format.H265{
PayloadTyp: 96, PayloadTyp: 96,
}}, }},
} }
@@ -138,17 +138,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts), AU: au,
AU: au,
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG4Audio: case *mpegts.CodecMPEG4Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
SizeLength: 13, SizeLength: 13,
IndexLength: 3, IndexLength: 3,
@@ -161,17 +161,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
AUs: aus, AUs: aus,
}) })
return nil return nil
}) })
case *mpegts.CodecOpus: case *mpegts.CodecOpus:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.Opus{ Formats: []format.Format{&format.Opus{
PayloadTyp: 96, PayloadTyp: 96,
IsStereo: (tcodec.ChannelCount == 2), IsStereo: (tcodec.ChannelCount == 2),
}}, }},
@@ -181,25 +181,25 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{ stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
Packets: packets, Packets: packets,
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG1Audio: case *mpegts.CodecMPEG1Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG1Audio{}}, Formats: []format.Format{&format.MPEG1Audio{}},
} }
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
Frames: frames, Frames: frames,
}) })
return nil return nil
@@ -217,7 +217,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
} }
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if res.err != nil { if res.err != nil {

View File

@@ -4,9 +4,9 @@ import (
"bufio" "bufio"
"testing" "testing"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/datarhei/gosrt" "github.com/datarhei/gosrt"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -82,13 +82,13 @@ func TestSRTSource(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
var forma *formats.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {

View File

@@ -6,8 +6,8 @@ import (
"net" "net"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
@@ -140,7 +140,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
return err return err
} }
var medias media.Medias var medias []*description.Media //nolint:prealloc
var stream *stream.Stream var stream *stream.Stream
var td *mpegts.TimeDecoder var td *mpegts.TimeDecoder
@@ -152,13 +152,13 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
} }
for _, track := range r.Tracks() { //nolint:dupl for _, track := range r.Tracks() { //nolint:dupl
var medi *media.Media var medi *description.Media
switch tcodec := track.Codec.(type) { switch tcodec := track.Codec.(type) {
case *mpegts.CodecH264: case *mpegts.CodecH264:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
}}, }},
@@ -168,17 +168,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{ stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts), AU: au,
AU: au,
}) })
return nil return nil
}) })
case *mpegts.CodecH265: case *mpegts.CodecH265:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H265{ Formats: []format.Format{&format.H265{
PayloadTyp: 96, PayloadTyp: 96,
}}, }},
} }
@@ -187,17 +187,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{ stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts), AU: au,
AU: au,
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG4Audio: case *mpegts.CodecMPEG4Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
SizeLength: 13, SizeLength: 13,
IndexLength: 3, IndexLength: 3,
@@ -210,17 +210,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
AUs: aus, AUs: aus,
}) })
return nil return nil
}) })
case *mpegts.CodecOpus: case *mpegts.CodecOpus:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.Opus{ Formats: []format.Format{&format.Opus{
PayloadTyp: 96, PayloadTyp: 96,
IsStereo: (tcodec.ChannelCount == 2), IsStereo: (tcodec.ChannelCount == 2),
}}, }},
@@ -230,25 +230,25 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{ stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
Packets: packets, Packets: packets,
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG1Audio: case *mpegts.CodecMPEG1Audio:
medi = &media.Media{ medi = &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []formats.Format{&formats.MPEG1Audio{}}, Formats: []format.Format{&format.MPEG1Audio{}},
} }
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error { r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{ stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
Base: unit.Base{ Base: unit.Base{
NTP: time.Now(), NTP: time.Now(),
PTS: decodeTime(pts),
}, },
PTS: decodeTime(pts),
Frames: frames, Frames: frames,
}) })
return nil return nil
@@ -266,7 +266,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
} }
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if res.err != nil { if res.err != nil {

View File

@@ -6,9 +6,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/bluenviron/mediacommon/pkg/formats/mpegts" "github.com/bluenviron/mediacommon/pkg/formats/mpegts"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -68,13 +68,13 @@ func TestUDPSource(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}() }()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
var forma *formats.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {

View File

@@ -5,9 +5,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/stream"
@@ -22,9 +24,9 @@ type webRTCIncomingTrack struct {
receiver *webrtc.RTPReceiver receiver *webrtc.RTPReceiver
writeRTCP func([]rtcp.Packet) error writeRTCP func([]rtcp.Packet) error
mediaType media.Type mediaType description.MediaType
format formats.Format format format.Format
media *media.Media media *description.Media
} }
func newWebRTCIncomingTrack( func newWebRTCIncomingTrack(
@@ -40,49 +42,49 @@ func newWebRTCIncomingTrack(
switch strings.ToLower(track.Codec().MimeType) { switch strings.ToLower(track.Codec().MimeType) {
case strings.ToLower(webrtc.MimeTypeAV1): case strings.ToLower(webrtc.MimeTypeAV1):
t.mediaType = media.TypeVideo t.mediaType = description.MediaTypeVideo
t.format = &formats.AV1{ t.format = &format.AV1{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeVP9): case strings.ToLower(webrtc.MimeTypeVP9):
t.mediaType = media.TypeVideo t.mediaType = description.MediaTypeVideo
t.format = &formats.VP9{ t.format = &format.VP9{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeVP8): case strings.ToLower(webrtc.MimeTypeVP8):
t.mediaType = media.TypeVideo t.mediaType = description.MediaTypeVideo
t.format = &formats.VP8{ t.format = &format.VP8{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeH264): case strings.ToLower(webrtc.MimeTypeH264):
t.mediaType = media.TypeVideo t.mediaType = description.MediaTypeVideo
t.format = &formats.H264{ t.format = &format.H264{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
PacketizationMode: 1, PacketizationMode: 1,
} }
case strings.ToLower(webrtc.MimeTypeOpus): case strings.ToLower(webrtc.MimeTypeOpus):
t.mediaType = media.TypeAudio t.mediaType = description.MediaTypeAudio
t.format = &formats.Opus{ t.format = &format.Opus{
PayloadTyp: uint8(track.PayloadType()), PayloadTyp: uint8(track.PayloadType()),
} }
case strings.ToLower(webrtc.MimeTypeG722): case strings.ToLower(webrtc.MimeTypeG722):
t.mediaType = media.TypeAudio t.mediaType = description.MediaTypeAudio
t.format = &formats.G722{} t.format = &format.G722{}
case strings.ToLower(webrtc.MimeTypePCMU): case strings.ToLower(webrtc.MimeTypePCMU):
t.mediaType = media.TypeAudio t.mediaType = description.MediaTypeAudio
t.format = &formats.G711{ t.format = &format.G711{
MULaw: true, MULaw: true,
} }
case strings.ToLower(webrtc.MimeTypePCMA): case strings.ToLower(webrtc.MimeTypePCMA):
t.mediaType = media.TypeAudio t.mediaType = description.MediaTypeAudio
t.format = &formats.G711{ t.format = &format.G711{
MULaw: false, MULaw: false,
} }
@@ -90,15 +92,29 @@ func newWebRTCIncomingTrack(
return nil, fmt.Errorf("unsupported codec: %v", track.Codec()) return nil, fmt.Errorf("unsupported codec: %v", track.Codec())
} }
t.media = &media.Media{ t.media = &description.Media{
Type: t.mediaType, Type: t.mediaType,
Formats: []formats.Format{t.format}, Formats: []format.Format{t.format},
} }
return t, nil 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() { go func() {
for { for {
pkt, _, err := t.track.ReadRTP() pkt, _, err := t.track.ReadRTP()
@@ -111,7 +127,12 @@ func (t *webRTCIncomingTrack) start(stream *stream.Stream) {
continue 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() { go func() {
keyframeTicker := time.NewTicker(keyFrameInterval) keyframeTicker := time.NewTicker(keyFrameInterval)
defer keyframeTicker.Stop() defer keyframeTicker.Stop()

View File

@@ -7,10 +7,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -193,9 +193,9 @@ func TestWebRTCRead(t *testing.T) {
a = newTestHTTPAuthenticator(t, "rtsp", "publish") a = newTestHTTPAuthenticator(t, "rtsp", "publish")
} }
medi := &media.Media{ medi := &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []formats.Format{&formats.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
}}, }},
@@ -206,7 +206,8 @@ func TestWebRTCRead(t *testing.T) {
Transport: &v, Transport: &v,
} }
err := source.StartRecording( 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) require.NoError(t, err)
defer source.Close() defer source.Close()
@@ -251,7 +252,7 @@ func TestWebRTCRead(t *testing.T) {
Timestamp: 45343, Timestamp: 45343,
SSRC: 563423, SSRC: 563423,
}, },
Payload: []byte{3}, Payload: []byte{5, 3},
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -269,7 +270,7 @@ func TestWebRTCRead(t *testing.T) {
SSRC: pkt.SSRC, SSRC: pkt.SSRC,
CSRC: []uint32{}, CSRC: []uint32{},
}, },
Payload: []byte{3}, Payload: []byte{5, 3},
}, pkt) }, pkt)
}) })
} }
@@ -407,13 +408,13 @@ func TestWebRTCPublish(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
var forma *formats.VP8 var forma *format.VP8
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
received := make(chan struct{}) received := make(chan struct{})

View File

@@ -5,13 +5,13 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/stream"
@@ -20,15 +20,15 @@ import (
type webRTCOutgoingTrack struct { type webRTCOutgoingTrack struct {
sender *webrtc.RTPSender sender *webrtc.RTPSender
media *media.Media media *description.Media
format formats.Format format format.Format
track *webrtc.TrackLocalStaticRTP track *webrtc.TrackLocalStaticRTP
cb func(unit.Unit) error cb func(unit.Unit) error
} }
func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, error) { func newWebRTCOutgoingTrackVideo(desc *description.Session) (*webRTCOutgoingTrack, error) {
var av1Format *formats.AV1 var av1Format *format.AV1
videoMedia := medias.FindFormat(&av1Format) videoMedia := desc.FindFormat(&av1Format)
if videoMedia != nil { if videoMedia != nil {
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
@@ -63,12 +63,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
return nil return nil
} }
packets, err := encoder.Encode(tunit.TU, tunit.PTS) packets, err := encoder.Encode(tunit.TU)
if err != nil { if err != nil {
return nil //nolint:nilerr return nil //nolint:nilerr
} }
for _, pkt := range packets { for _, pkt := range packets {
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
webRTCTrak.WriteRTP(pkt) //nolint:errcheck webRTCTrak.WriteRTP(pkt) //nolint:errcheck
} }
@@ -77,10 +78,10 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
}, nil }, nil
} }
var vp9Format *formats.VP9 var vp9Format *format.VP9
videoMedia = medias.FindFormat(&vp9Format) videoMedia = desc.FindFormat(&vp9Format)
if videoMedia != nil { if videoMedia != nil { //nolint:dupl
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{ webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP9, MimeType: webrtc.MimeTypeVP9,
@@ -113,12 +114,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
return nil return nil
} }
packets, err := encoder.Encode(tunit.Frame, tunit.PTS) packets, err := encoder.Encode(tunit.Frame)
if err != nil { if err != nil {
return nil //nolint:nilerr return nil //nolint:nilerr
} }
for _, pkt := range packets { for _, pkt := range packets {
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
webRTCTrak.WriteRTP(pkt) //nolint:errcheck webRTCTrak.WriteRTP(pkt) //nolint:errcheck
} }
@@ -127,10 +129,10 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
}, nil }, nil
} }
var vp8Format *formats.VP8 var vp8Format *format.VP8
videoMedia = medias.FindFormat(&vp8Format) videoMedia = desc.FindFormat(&vp8Format)
if videoMedia != nil { if videoMedia != nil { //nolint:dupl
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{ webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8, MimeType: webrtc.MimeTypeVP8,
@@ -163,12 +165,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
return nil return nil
} }
packets, err := encoder.Encode(tunit.Frame, tunit.PTS) packets, err := encoder.Encode(tunit.Frame)
if err != nil { if err != nil {
return nil //nolint:nilerr return nil //nolint:nilerr
} }
for _, pkt := range packets { for _, pkt := range packets {
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
webRTCTrak.WriteRTP(pkt) //nolint:errcheck webRTCTrak.WriteRTP(pkt) //nolint:errcheck
} }
@@ -177,8 +180,8 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
}, nil }, nil
} }
var h264Format *formats.H264 var h264Format *format.H264
videoMedia = medias.FindFormat(&h264Format) videoMedia = desc.FindFormat(&h264Format)
if videoMedia != nil { if videoMedia != nil {
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
@@ -226,12 +229,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
lastPTS = tunit.PTS lastPTS = tunit.PTS
} }
packets, err := encoder.Encode(tunit.AU, tunit.PTS) packets, err := encoder.Encode(tunit.AU)
if err != nil { if err != nil {
return nil //nolint:nilerr return nil //nolint:nilerr
} }
for _, pkt := range packets { for _, pkt := range packets {
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
webRTCTrak.WriteRTP(pkt) //nolint:errcheck webRTCTrak.WriteRTP(pkt) //nolint:errcheck
} }
@@ -243,9 +247,9 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
return nil, nil return nil, nil
} }
func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, error) { func newWebRTCOutgoingTrackAudio(desc *description.Session) (*webRTCOutgoingTrack, error) {
var opusFormat *formats.Opus var opusFormat *format.Opus
audioMedia := medias.FindFormat(&opusFormat) audioMedia := desc.FindFormat(&opusFormat)
if audioMedia != nil { if audioMedia != nil {
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
@@ -275,8 +279,8 @@ func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, err
}, nil }, nil
} }
var g722Format *formats.G722 var g722Format *format.G722
audioMedia = medias.FindFormat(&g722Format) audioMedia = desc.FindFormat(&g722Format)
if audioMedia != nil { if audioMedia != nil {
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
@@ -305,8 +309,8 @@ func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, err
}, nil }, nil
} }
var g711Format *formats.G711 var g711Format *format.G711
audioMedia = medias.FindFormat(&g711Format) audioMedia = desc.FindFormat(&g711Format)
if audioMedia != nil { if audioMedia != nil {
var mtyp string var mtyp string

View File

@@ -10,8 +10,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pion/sdp/v3" "github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
@@ -25,16 +26,16 @@ type trackRecvPair struct {
receiver *webrtc.RTPReceiver receiver *webrtc.RTPReceiver
} }
func webrtcMediasOfOutgoingTracks(tracks []*webRTCOutgoingTrack) media.Medias { func webrtcMediasOfOutgoingTracks(tracks []*webRTCOutgoingTrack) []*description.Media {
ret := make(media.Medias, len(tracks)) ret := make([]*description.Media, len(tracks))
for i, track := range tracks { for i, track := range tracks {
ret[i] = track.media ret[i] = track.media
} }
return ret return ret
} }
func webrtcMediasOfIncomingTracks(tracks []*webRTCIncomingTrack) media.Medias { func webrtcMediasOfIncomingTracks(tracks []*webRTCIncomingTrack) []*description.Media {
ret := make(media.Medias, len(tracks)) ret := make([]*description.Media, len(tracks))
for i, track := range tracks { for i, track := range tracks {
ret[i] = track.media ret[i] = track.media
} }
@@ -72,10 +73,10 @@ outer:
return nil return nil
} }
func webrtcGatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, error) { func webrtcGatherOutgoingTracks(desc *description.Session) ([]*webRTCOutgoingTrack, error) {
var tracks []*webRTCOutgoingTrack var tracks []*webRTCOutgoingTrack
videoTrack, err := newWebRTCOutgoingTrackVideo(medias) videoTrack, err := newWebRTCOutgoingTrackVideo(desc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -84,7 +85,7 @@ func webrtcGatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, er
tracks = append(tracks, videoTrack) tracks = append(tracks, videoTrack)
} }
audioTrack, err := newWebRTCOutgoingTrackAudio(medias) audioTrack, err := newWebRTCOutgoingTrackAudio(desc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -395,15 +396,17 @@ func (s *webRTCSession) runPublish() (int, error) {
rres := res.path.startPublisher(pathStartPublisherReq{ rres := res.path.startPublisher(pathStartPublisherReq{
author: s, author: s,
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: false, generateRTPPackets: false,
}) })
if rres.err != nil { if rres.err != nil {
return 0, rres.err return 0, rres.err
} }
timeDecoder := rtptime.NewGlobalDecoder()
for _, track := range tracks { for _, track := range tracks {
track.start(rres.stream) track.start(rres.stream, timeDecoder)
} }
select { select {
@@ -447,7 +450,7 @@ func (s *webRTCSession) runRead() (int, error) {
defer res.path.removeReader(pathRemoveReaderReq{author: s}) defer res.path.removeReader(pathRemoveReaderReq{author: s})
tracks, err := webrtcGatherOutgoingTracks(res.stream.Medias()) tracks, err := webrtcGatherOutgoingTracks(res.stream.Desc())
if err != nil { if err != nil {
return http.StatusBadRequest, err return http.StatusBadRequest, err
} }

View File

@@ -8,6 +8,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
"github.com/pion/sdp/v3" "github.com/pion/sdp/v3"
"github.com/pion/webrtc/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) medias := webrtcMediasOfIncomingTracks(tracks)
rres := s.parent.setReady(pathSourceStaticSetReadyReq{ rres := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, desc: &description.Session{Medias: medias},
generateRTPPackets: true, generateRTPPackets: true,
}) })
if rres.err != nil { 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{}) defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{})
timeDecoder := rtptime.NewGlobalDecoder()
for _, track := range tracks { for _, track := range tracks {
track.start(rres.stream) track.start(rres.stream, timeDecoder)
} }
select { select {

View File

@@ -7,9 +7,9 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -105,7 +105,7 @@ func TestWebRTCSource(t *testing.T) {
Timestamp: 45343, Timestamp: 45343,
SSRC: 563423, SSRC: 563423,
}, },
Payload: []byte{1}, Payload: []byte{5, 1},
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -118,7 +118,7 @@ func TestWebRTCSource(t *testing.T) {
Timestamp: 45343, Timestamp: 45343,
SSRC: 563423, SSRC: 563423,
}, },
Payload: []byte{2}, Payload: []byte{5, 2},
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -152,19 +152,19 @@ func TestWebRTCSource(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
var forma *formats.VP8 var forma *format.VP8
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
_, err = c.Setup(medi, baseURL, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
require.NoError(t, err) require.NoError(t, err)
received := make(chan struct{}) received := make(chan struct{})
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { 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) close(received)
}) })
@@ -180,7 +180,7 @@ func TestWebRTCSource(t *testing.T) {
Timestamp: 45343, Timestamp: 45343,
SSRC: 563423, SSRC: 563423,
}, },
Payload: []byte{3}, Payload: []byte{5, 3},
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -14,7 +14,7 @@ import (
type formatProcessorAV1 struct { type formatProcessorAV1 struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.AV1 format *format.AV1
log logger.Writer log logger.Writer
encoder *rtpav1.Encoder encoder *rtpav1.Encoder
@@ -23,7 +23,7 @@ type formatProcessorAV1 struct {
func newAV1( func newAV1(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.AV1, forma *format.AV1,
generateRTPPackets bool, generateRTPPackets bool,
log logger.Writer, log logger.Writer,
) (*formatProcessorAV1, error) { ) (*formatProcessorAV1, error) {
@@ -69,14 +69,13 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error
if hasNonRTSPReaders || t.decoder != nil { if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
// DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups tu, err := t.decoder.Decode(pkt)
tu, pts, err := t.decoder.DecodeUntilMarker(pkt)
if err != nil { if err != nil {
if err == rtpav1.ErrNonStartingPacketAndNoPrevious || err == rtpav1.ErrMorePacketsNeeded { if err == rtpav1.ErrNonStartingPacketAndNoPrevious || err == rtpav1.ErrMorePacketsNeeded {
return nil return nil
@@ -85,7 +84,6 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error
} }
tunit.TU = tu tunit.TU = tu
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -93,20 +91,22 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error
} }
// encode into RTP // encode into RTP
pkts, err := t.encoder.Encode(tunit.TU, tunit.PTS) pkts, err := t.encoder.Encode(tunit.TU)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
return nil 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{ return &unit.AV1{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

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

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -17,7 +17,7 @@ type formatProcessorGeneric struct {
func newGeneric( func newGeneric(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma formats.Format, forma format.Format,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorGeneric, error) { ) (*formatProcessorGeneric, error) {
@@ -47,11 +47,12 @@ func (t *formatProcessorGeneric) Process(u unit.Unit, _ bool) error {
return nil 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{ return &unit.Generic{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -3,7 +3,7 @@ package formatprocessor
import ( import (
"testing" "testing"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -11,7 +11,7 @@ import (
) )
func TestGenericRemovePadding(t *testing.T) { func TestGenericRemovePadding(t *testing.T) {
forma := &formats.Generic{ forma := &format.Generic{
PayloadTyp: 96, PayloadTyp: 96,
RTPMa: "private/90000", RTPMa: "private/90000",
} }

View File

@@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -72,7 +72,7 @@ func rtpH264ExtractSPSPPS(pkt *rtp.Packet) ([]byte, []byte) {
type formatProcessorH264 struct { type formatProcessorH264 struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.H264 format *format.H264
log logger.Writer log logger.Writer
encoder *rtph264.Encoder encoder *rtph264.Encoder
@@ -81,7 +81,7 @@ type formatProcessorH264 struct {
func newH264( func newH264(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.H264, forma *format.H264,
generateRTPPackets bool, generateRTPPackets bool,
log logger.Writer, log logger.Writer,
) (*formatProcessorH264, error) { ) (*formatProcessorH264, error) {
@@ -92,7 +92,7 @@ func newH264(
} }
if generateRTPPackets { if generateRTPPackets {
err := t.createEncoder(nil, nil, nil) err := t.createEncoder(nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -102,14 +102,14 @@ func newH264(
} }
func (t *formatProcessorH264) createEncoder( func (t *formatProcessorH264) createEncoder(
ssrc *uint32, initialSequenceNumber *uint16, initialTimestamp *uint32, ssrc *uint32,
initialSequenceNumber *uint16,
) error { ) error {
t.encoder = &rtph264.Encoder{ t.encoder = &rtph264.Encoder{
PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadMaxSize: t.udpMaxPayloadSize - 12,
PayloadType: t.format.PayloadTyp, PayloadType: t.format.PayloadTyp,
SSRC: ssrc, SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber, InitialSequenceNumber: initialSequenceNumber,
InitialTimestamp: initialTimestamp,
PacketizationMode: t.format.PacketizationMode, PacketizationMode: t.format.PacketizationMode,
} }
return t.encoder.Init() return t.encoder.Init()
@@ -240,8 +240,7 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
if pkt.MarshalSize() > t.udpMaxPayloadSize { if pkt.MarshalSize() > t.udpMaxPayloadSize {
v1 := pkt.SSRC v1 := pkt.SSRC
v2 := pkt.SequenceNumber v2 := pkt.SequenceNumber
v3 := pkt.Timestamp err := t.createEncoder(&v1, &v2)
err := t.createEncoder(&v1, &v2, &v3)
if err != nil { if err != nil {
return err 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 hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
if t.encoder != nil { au, err := t.decoder.Decode(pkt)
tunit.RTPPackets = nil
}
// DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups
au, pts, err := t.decoder.DecodeUntilMarker(pkt)
if err != nil { if err != nil {
if err == rtph264.ErrNonStartingPacketAndNoPrevious || err == rtph264.ErrMorePacketsNeeded { if err == rtph264.ErrNonStartingPacketAndNoPrevious || err == rtph264.ErrMorePacketsNeeded {
if t.encoder != nil {
tunit.RTPPackets = nil
}
return nil return nil
} }
return err return err
} }
tunit.AU = t.remuxAccessUnit(au) tunit.AU = t.remuxAccessUnit(au)
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -286,10 +282,11 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
// encode into RTP // encode into RTP
if len(tunit.AU) != 0 { if len(tunit.AU) != 0 {
pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS) pkts, err := t.encoder.Encode(tunit.AU)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
} else { } else {
tunit.RTPPackets = nil tunit.RTPPackets = nil
@@ -298,11 +295,12 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
return nil 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{ return &unit.H264{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"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/h264"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -13,7 +13,7 @@ import (
) )
func TestH264DynamicParams(t *testing.T) { func TestH264DynamicParams(t *testing.T) {
forma := &formats.H264{ forma := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
} }
@@ -21,10 +21,10 @@ func TestH264DynamicParams(t *testing.T) {
p, err := New(1472, forma, false, nil) p, err := New(1472, forma, false, nil)
require.NoError(t, err) require.NoError(t, err)
enc, err := forma.CreateEncoder2() enc, err := forma.CreateEncoder()
require.NoError(t, err) 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) require.NoError(t, err)
data := &unit.H264{ data := &unit.H264{
@@ -39,7 +39,7 @@ func TestH264DynamicParams(t *testing.T) {
{byte(h264.NALUTypeIDR)}, {byte(h264.NALUTypeIDR)},
}, data.AU) }, 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) require.NoError(t, err)
err = p.Process(&unit.H264{ err = p.Process(&unit.H264{
@@ -49,7 +49,7 @@ func TestH264DynamicParams(t *testing.T) {
}, false) }, false)
require.NoError(t, err) 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) require.NoError(t, err)
err = p.Process(&unit.H264{ 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{7, 4, 5, 6}, forma.SPS)
require.Equal(t, []byte{8, 1}, forma.PPS) 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) require.NoError(t, err)
data = &unit.H264{ data = &unit.H264{
@@ -81,7 +81,7 @@ func TestH264DynamicParams(t *testing.T) {
} }
func TestH264OversizedPackets(t *testing.T) { func TestH264OversizedPackets(t *testing.T) {
forma := &formats.H264{ forma := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04}, SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []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) { func TestH264EmptyPacket(t *testing.T) {
forma := &formats.H264{ forma := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
PacketizationMode: 1, PacketizationMode: 1,
} }

View File

@@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph265" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph265"
"github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -79,7 +79,7 @@ func rtpH265ExtractVPSSPSPPS(pkt *rtp.Packet) ([]byte, []byte, []byte) {
type formatProcessorH265 struct { type formatProcessorH265 struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.H265 format *format.H265
log logger.Writer log logger.Writer
encoder *rtph265.Encoder encoder *rtph265.Encoder
@@ -88,7 +88,7 @@ type formatProcessorH265 struct {
func newH265( func newH265(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.H265, forma *format.H265,
generateRTPPackets bool, generateRTPPackets bool,
log logger.Writer, log logger.Writer,
) (*formatProcessorH265, error) { ) (*formatProcessorH265, error) {
@@ -99,7 +99,7 @@ func newH265(
} }
if generateRTPPackets { if generateRTPPackets {
err := t.createEncoder(nil, nil, nil) err := t.createEncoder(nil, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -109,14 +109,14 @@ func newH265(
} }
func (t *formatProcessorH265) createEncoder( func (t *formatProcessorH265) createEncoder(
ssrc *uint32, initialSequenceNumber *uint16, initialTimestamp *uint32, ssrc *uint32,
initialSequenceNumber *uint16,
) error { ) error {
t.encoder = &rtph265.Encoder{ t.encoder = &rtph265.Encoder{
PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadMaxSize: t.udpMaxPayloadSize - 12,
PayloadType: t.format.PayloadTyp, PayloadType: t.format.PayloadTyp,
SSRC: ssrc, SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber, InitialSequenceNumber: initialSequenceNumber,
InitialTimestamp: initialTimestamp,
MaxDONDiff: t.format.MaxDONDiff, MaxDONDiff: t.format.MaxDONDiff,
} }
return t.encoder.Init() return t.encoder.Init()
@@ -262,8 +262,7 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
if pkt.MarshalSize() > t.udpMaxPayloadSize { if pkt.MarshalSize() > t.udpMaxPayloadSize {
v1 := pkt.SSRC v1 := pkt.SSRC
v2 := pkt.SequenceNumber v2 := pkt.SequenceNumber
v3 := pkt.Timestamp err := t.createEncoder(&v1, &v2)
err := t.createEncoder(&v1, &v2, &v3)
if err != nil { if err != nil {
return err 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 hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
if t.encoder != nil { au, err := t.decoder.Decode(pkt)
tunit.RTPPackets = nil
}
// DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups
au, pts, err := t.decoder.DecodeUntilMarker(pkt)
if err != nil { if err != nil {
if err == rtph265.ErrNonStartingPacketAndNoPrevious || err == rtph265.ErrMorePacketsNeeded { if err == rtph265.ErrNonStartingPacketAndNoPrevious || err == rtph265.ErrMorePacketsNeeded {
if t.encoder != nil {
tunit.RTPPackets = nil
}
return nil return nil
} }
return err return err
} }
tunit.AU = t.remuxAccessUnit(au) tunit.AU = t.remuxAccessUnit(au)
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -308,10 +304,11 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
// encode into RTP // encode into RTP
if len(tunit.AU) != 0 { if len(tunit.AU) != 0 {
pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS) pkts, err := t.encoder.Encode(tunit.AU)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
} else { } else {
tunit.RTPPackets = nil tunit.RTPPackets = nil
@@ -320,11 +317,12 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
return nil 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{ return &unit.H265{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -13,17 +13,17 @@ import (
) )
func TestH265DynamicParams(t *testing.T) { func TestH265DynamicParams(t *testing.T) {
forma := &formats.H265{ forma := &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
} }
p, err := New(1472, forma, false, nil) p, err := New(1472, forma, false, nil)
require.NoError(t, err) require.NoError(t, err)
enc, err := forma.CreateEncoder2() enc, err := forma.CreateEncoder()
require.NoError(t, err) 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) require.NoError(t, err)
data := &unit.H265{ data := &unit.H265{
@@ -38,7 +38,7 @@ func TestH265DynamicParams(t *testing.T) {
{byte(h265.NALUType_CRA_NUT) << 1, 0}, {byte(h265.NALUType_CRA_NUT) << 1, 0},
}, data.AU) }, 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) require.NoError(t, err)
err = p.Process(&unit.H265{ err = p.Process(&unit.H265{
@@ -48,7 +48,7 @@ func TestH265DynamicParams(t *testing.T) {
}, false) }, false)
require.NoError(t, err) 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) require.NoError(t, err)
err = p.Process(&unit.H265{ err = p.Process(&unit.H265{
@@ -58,7 +58,7 @@ func TestH265DynamicParams(t *testing.T) {
}, false) }, false)
require.NoError(t, err) 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) require.NoError(t, err)
err = p.Process(&unit.H265{ 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_SPS_NUT) << 1, 4, 5, 6}, forma.SPS)
require.Equal(t, []byte{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS) 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) require.NoError(t, err)
data = &unit.H265{ data = &unit.H265{
@@ -92,7 +92,7 @@ func TestH265DynamicParams(t *testing.T) {
} }
func TestH265OversizedPackets(t *testing.T) { func TestH265OversizedPackets(t *testing.T) {
forma := &formats.H265{ forma := &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: []byte{byte(h265.NALUType_VPS_NUT) << 1, 10, 11, 12}, VPS: []byte{byte(h265.NALUType_VPS_NUT) << 1, 10, 11, 12},
SPS: []byte{byte(h265.NALUType_SPS_NUT) << 1, 13, 14, 15}, 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) { func TestH265EmptyPacket(t *testing.T) {
forma := &formats.H265{ forma := &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
} }

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg1audio" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -14,14 +14,14 @@ import (
type formatProcessorMPEG1Audio struct { type formatProcessorMPEG1Audio struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.MPEG1Audio format *format.MPEG1Audio
encoder *rtpmpeg1audio.Encoder encoder *rtpmpeg1audio.Encoder
decoder *rtpmpeg1audio.Decoder decoder *rtpmpeg1audio.Decoder
} }
func newMPEG1Audio( func newMPEG1Audio(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.MPEG1Audio, forma *format.MPEG1Audio,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorMPEG1Audio, error) { ) (*formatProcessorMPEG1Audio, error) {
@@ -66,13 +66,13 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool)
if hasNonRTSPReaders || t.decoder != nil { if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
frames, pts, err := t.decoder.Decode(pkt) frames, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
if err == rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg1audio.ErrMorePacketsNeeded { if err == rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg1audio.ErrMorePacketsNeeded {
return nil return nil
@@ -81,7 +81,6 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool)
} }
tunit.Frames = frames tunit.Frames = frames
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -89,20 +88,22 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool)
} }
// encode into RTP // encode into RTP
pkts, err := t.encoder.Encode(tunit.Frames, tunit.PTS) pkts, err := t.encoder.Encode(tunit.Frames)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
return nil 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{ return &unit.MPEG1Audio{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audio" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -14,14 +14,14 @@ import (
type formatProcessorMPEG4AudioGeneric struct { type formatProcessorMPEG4AudioGeneric struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.MPEG4Audio format *format.MPEG4Audio
encoder *rtpmpeg4audio.Encoder encoder *rtpmpeg4audio.Encoder
decoder *rtpmpeg4audio.Decoder decoder *rtpmpeg4audio.Decoder
} }
func newMPEG4AudioGeneric( func newMPEG4AudioGeneric(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.MPEG4Audio, forma *format.MPEG4Audio,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorMPEG4AudioGeneric, error) { ) (*formatProcessorMPEG4AudioGeneric, error) {
@@ -44,7 +44,6 @@ func (t *formatProcessorMPEG4AudioGeneric) createEncoder() error {
t.encoder = &rtpmpeg4audio.Encoder{ t.encoder = &rtpmpeg4audio.Encoder{
PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadMaxSize: t.udpMaxPayloadSize - 12,
PayloadType: t.format.PayloadTyp, PayloadType: t.format.PayloadTyp,
SampleRate: t.format.Config.SampleRate,
SizeLength: t.format.SizeLength, SizeLength: t.format.SizeLength,
IndexLength: t.format.IndexLength, IndexLength: t.format.IndexLength,
IndexDeltaLength: t.format.IndexDeltaLength, IndexDeltaLength: t.format.IndexDeltaLength,
@@ -71,13 +70,13 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader
if hasNonRTSPReaders || t.decoder != nil || true { if hasNonRTSPReaders || t.decoder != nil || true {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
aus, pts, err := t.decoder.Decode(pkt) aus, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
if err == rtpmpeg4audio.ErrMorePacketsNeeded { if err == rtpmpeg4audio.ErrMorePacketsNeeded {
return nil return nil
@@ -86,7 +85,6 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader
} }
tunit.AUs = aus tunit.AUs = aus
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -94,20 +92,22 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader
} }
// encode into RTP // encode into RTP
pkts, err := t.encoder.Encode(tunit.AUs, tunit.PTS) pkts, err := t.encoder.Encode(tunit.AUs)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
return nil 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{ return &unit.MPEG4AudioGeneric{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiolatm" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audiolatm"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -14,14 +14,14 @@ import (
type formatProcessorMPEG4AudioLATM struct { type formatProcessorMPEG4AudioLATM struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.MPEG4AudioLATM format *format.MPEG4AudioLATM
encoder *rtpmpeg4audiolatm.Encoder encoder *rtpmpeg4audiolatm.Encoder
decoder *rtpmpeg4audiolatm.Decoder decoder *rtpmpeg4audiolatm.Decoder
} }
func newMPEG4AudioLATM( func newMPEG4AudioLATM(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.MPEG4AudioLATM, forma *format.MPEG4AudioLATM,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorMPEG4AudioLATM, error) { ) (*formatProcessorMPEG4AudioLATM, error) {
@@ -43,7 +43,6 @@ func newMPEG4AudioLATM(
func (t *formatProcessorMPEG4AudioLATM) createEncoder() error { func (t *formatProcessorMPEG4AudioLATM) createEncoder() error {
t.encoder = &rtpmpeg4audiolatm.Encoder{ t.encoder = &rtpmpeg4audiolatm.Encoder{
PayloadType: t.format.PayloadTyp, PayloadType: t.format.PayloadTyp,
Config: t.format.Config,
} }
return t.encoder.Init() return t.encoder.Init()
} }
@@ -67,13 +66,13 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b
if hasNonRTSPReaders || t.decoder != nil { if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
au, pts, err := t.decoder.Decode(pkt) au, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
if err == rtpmpeg4audiolatm.ErrMorePacketsNeeded { if err == rtpmpeg4audiolatm.ErrMorePacketsNeeded {
return nil return nil
@@ -82,7 +81,6 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b
} }
tunit.AU = au tunit.AU = au
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -90,20 +88,22 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b
} }
// encode into RTP // encode into RTP
pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS) pkts, err := t.encoder.Encode(tunit.AU)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
return nil 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{ return &unit.MPEG4AudioLATM{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpsimpleaudio" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio"
"github.com/bluenviron/mediacommon/pkg/codecs/opus" "github.com/bluenviron/mediacommon/pkg/codecs/opus"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -15,14 +15,14 @@ import (
type formatProcessorOpus struct { type formatProcessorOpus struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.Opus format *format.Opus
encoder *rtpsimpleaudio.Encoder encoder *rtpsimpleaudio.Encoder
decoder *rtpsimpleaudio.Decoder decoder *rtpsimpleaudio.Decoder
} }
func newOpus( func newOpus(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.Opus, forma *format.Opus,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorOpus, error) { ) (*formatProcessorOpus, error) {
@@ -45,7 +45,6 @@ func (t *formatProcessorOpus) createEncoder() error {
t.encoder = &rtpsimpleaudio.Encoder{ t.encoder = &rtpsimpleaudio.Encoder{
PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadMaxSize: t.udpMaxPayloadSize - 12,
PayloadType: t.format.PayloadTyp, PayloadType: t.format.PayloadTyp,
SampleRate: 48000,
} }
return t.encoder.Init() return t.encoder.Init()
} }
@@ -69,19 +68,18 @@ func (t *formatProcessorOpus) Process(u unit.Unit, hasNonRTSPReaders bool) error
if hasNonRTSPReaders || t.decoder != nil { if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
packet, pts, err := t.decoder.Decode(pkt) packet, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
return err return err
} }
tunit.Packets = [][]byte{packet} tunit.Packets = [][]byte{packet}
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -92,24 +90,26 @@ func (t *formatProcessorOpus) Process(u unit.Unit, hasNonRTSPReaders bool) error
var rtpPackets []*rtp.Packet //nolint:prealloc var rtpPackets []*rtp.Packet //nolint:prealloc
pts := tunit.PTS pts := tunit.PTS
for _, packet := range tunit.Packets { for _, packet := range tunit.Packets {
pkt, err := t.encoder.Encode(packet, pts) pkt, err := t.encoder.Encode(packet)
if err != nil { if err != nil {
return err return err
} }
setTimestamp([]*rtp.Packet{pkt}, tunit.RTPPackets, t.format.ClockRate(), pts)
rtpPackets = append(rtpPackets, pkt) rtpPackets = append(rtpPackets, pkt)
pts += opus.PacketDuration(packet) pts += opus.PacketDuration(packet)
} }
tunit.RTPPackets = rtpPackets tunit.RTPPackets = rtpPackets
return nil 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{ return &unit.Opus{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,55 +4,75 @@ package formatprocessor
import ( import (
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit" "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. // Processor cleans and normalizes streams.
type Processor interface { type Processor interface {
// cleans and normalizes a data unit. // cleans and normalizes a data unit.
Process(unit.Unit, bool) error Process(unit.Unit, bool) error
// wraps a RTP packet into a Unit. // 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. // New allocates a Processor.
func New( func New(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma formats.Format, forma format.Format,
generateRTPPackets bool, generateRTPPackets bool,
log logger.Writer, log logger.Writer,
) (Processor, error) { ) (Processor, error) {
switch forma := forma.(type) { switch forma := forma.(type) {
case *formats.H264: case *format.H264:
return newH264(udpMaxPayloadSize, forma, generateRTPPackets, log) return newH264(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.H265: case *format.H265:
return newH265(udpMaxPayloadSize, forma, generateRTPPackets, log) return newH265(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.VP8: case *format.VP8:
return newVP8(udpMaxPayloadSize, forma, generateRTPPackets, log) return newVP8(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.VP9: case *format.VP9:
return newVP9(udpMaxPayloadSize, forma, generateRTPPackets, log) return newVP9(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.AV1: case *format.AV1:
return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log) return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.MPEG1Audio: case *format.MPEG1Audio:
return newMPEG1Audio(udpMaxPayloadSize, forma, generateRTPPackets, log) return newMPEG1Audio(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.MPEG4AudioGeneric: case *format.MPEG4AudioGeneric:
return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log) return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.MPEG4AudioLATM: case *format.MPEG4AudioLATM:
return newMPEG4AudioLATM(udpMaxPayloadSize, forma, generateRTPPackets, log) return newMPEG4AudioLATM(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.Opus: case *format.Opus:
return newOpus(udpMaxPayloadSize, forma, generateRTPPackets, log) return newOpus(udpMaxPayloadSize, forma, generateRTPPackets, log)
default: default:

View File

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

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -14,14 +14,14 @@ import (
type formatProcessorVP8 struct { type formatProcessorVP8 struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.VP8 format *format.VP8
encoder *rtpvp8.Encoder encoder *rtpvp8.Encoder
decoder *rtpvp8.Decoder decoder *rtpvp8.Decoder
} }
func newVP8( func newVP8(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.VP8, forma *format.VP8,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorVP8, error) { ) (*formatProcessorVP8, error) {
@@ -67,13 +67,13 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error
if hasNonRTSPReaders || t.decoder != nil { if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
frame, pts, err := t.decoder.Decode(pkt) frame, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
if err == rtpvp8.ErrNonStartingPacketAndNoPrevious || err == rtpvp8.ErrMorePacketsNeeded { if err == rtpvp8.ErrNonStartingPacketAndNoPrevious || err == rtpvp8.ErrMorePacketsNeeded {
return nil return nil
@@ -82,7 +82,6 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error
} }
tunit.Frame = frame tunit.Frame = frame
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -90,20 +89,22 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error
} }
// encode into RTP // encode into RTP
pkts, err := t.encoder.Encode(tunit.Frame, tunit.PTS) pkts, err := t.encoder.Encode(tunit.Frame)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
return nil 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{ return &unit.VP8{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9" "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -14,14 +14,14 @@ import (
type formatProcessorVP9 struct { type formatProcessorVP9 struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.VP9 format *format.VP9
encoder *rtpvp9.Encoder encoder *rtpvp9.Encoder
decoder *rtpvp9.Decoder decoder *rtpvp9.Decoder
} }
func newVP9( func newVP9(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.VP9, forma *format.VP9,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorVP9, error) { ) (*formatProcessorVP9, error) {
@@ -67,13 +67,13 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error
if hasNonRTSPReaders || t.decoder != nil { if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil { if t.decoder == nil {
var err error var err error
t.decoder, err = t.format.CreateDecoder2() t.decoder, err = t.format.CreateDecoder()
if err != nil { if err != nil {
return err return err
} }
} }
frame, pts, err := t.decoder.Decode(pkt) frame, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
if err == rtpvp9.ErrNonStartingPacketAndNoPrevious || err == rtpvp9.ErrMorePacketsNeeded { if err == rtpvp9.ErrNonStartingPacketAndNoPrevious || err == rtpvp9.ErrMorePacketsNeeded {
return nil return nil
@@ -82,7 +82,6 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error
} }
tunit.Frame = frame tunit.Frame = frame
tunit.PTS = pts
} }
// route packet as is // route packet as is
@@ -90,20 +89,22 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error
} }
// encode into RTP // encode into RTP
pkts, err := t.encoder.Encode(tunit.Frame, tunit.PTS) pkts, err := t.encoder.Encode(tunit.Frame)
if err != nil { if err != nil {
return err return err
} }
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
tunit.RTPPackets = pkts tunit.RTPPackets = pkts
return nil 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{ return &unit.VP9{
Base: unit.Base{ Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,
PTS: pts,
}, },
} }
} }

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/abema/go-mp4" "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/av1"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/h265"
@@ -43,14 +43,14 @@ func h265FindNALU(array []mp4.HEVCNaluArray, typ h265.NALUType) []byte {
return nil return nil
} }
func trackFromH264DecoderConfig(data []byte) (formats.Format, error) { func trackFromH264DecoderConfig(data []byte) (format.Format, error) {
var conf h264conf.Conf var conf h264conf.Conf
err := conf.Unmarshal(data) err := conf.Unmarshal(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse H264 config: %v", err) return nil, fmt.Errorf("unable to parse H264 config: %v", err)
} }
return &formats.H264{ return &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: conf.SPS, SPS: conf.SPS,
PPS: conf.PPS, PPS: conf.PPS,
@@ -58,14 +58,14 @@ func trackFromH264DecoderConfig(data []byte) (formats.Format, error) {
}, nil }, nil
} }
func trackFromAACDecoderConfig(data []byte) (formats.Format, error) { func trackFromAACDecoderConfig(data []byte) (format.Format, error) {
var mpegConf mpeg4audio.Config var mpegConf mpeg4audio.Config
err := mpegConf.Unmarshal(data) err := mpegConf.Unmarshal(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &formats.MPEG4Audio{ return &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpegConf, Config: &mpegConf,
SizeLength: 13, SizeLength: 13,
@@ -76,7 +76,7 @@ func trackFromAACDecoderConfig(data []byte) (formats.Format, error) {
var errEmptyMetadata = errors.New("metadata is empty") 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 { if len(payload) != 1 {
return nil, nil, fmt.Errorf("invalid metadata") 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") return nil, nil, fmt.Errorf("invalid metadata")
} }
var videoTrack formats.Format var videoTrack format.Format
var audioTrack formats.Format var audioTrack format.Format
hasVideo, err := func() (bool, error) { hasVideo, err := func() (bool, error) {
v, ok := md.GetV("videocodecid") v, ok := md.GetV("videocodecid")
@@ -131,7 +131,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
return false, nil return false, nil
case message.CodecMPEG1Audio: case message.CodecMPEG1Audio:
audioTrack = &formats.MPEG1Audio{} audioTrack = &format.MPEG1Audio{}
return true, nil return true, nil
case message.CodecMPEG4Audio: case message.CodecMPEG4Audio:
@@ -205,7 +205,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
} }
if vps != nil && sps != nil && pps != nil { if vps != nil && sps != nil && pps != nil {
videoTrack = &formats.H265{ videoTrack = &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: vps, VPS: vps,
SPS: sps, SPS: sps,
@@ -232,7 +232,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
return nil, nil, fmt.Errorf("H265 parameters are missing") return nil, nil, fmt.Errorf("H265 parameters are missing")
} }
videoTrack = &formats.H265{ videoTrack = &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: vps, VPS: vps,
SPS: sps, 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) return nil, nil, fmt.Errorf("invalid AV1 configuration: %v", err)
} }
videoTrack = &formats.AV1{} videoTrack = &format.AV1{}
default: // VP9 default: // VP9
var vpcc mp4.VpcC 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) 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 startTime *time.Duration
var videoTrack formats.Format var videoTrack format.Format
var audioTrack formats.Format var audioTrack format.Format
// analyze 1 second of packets // analyze 1 second of packets
outer: outer:
@@ -359,8 +359,8 @@ outer:
// Reader is a wrapper around Conn that provides utilities to demux incoming data. // Reader is a wrapper around Conn that provides utilities to demux incoming data.
type Reader struct { type Reader struct {
conn *Conn conn *Conn
videoTrack formats.Format videoTrack format.Format
audioTrack formats.Format audioTrack format.Format
onDataVideo func(message.Message) error onDataVideo func(message.Message) error
onDataAudio func(*message.Audio) error onDataAudio func(*message.Audio) error
} }
@@ -380,7 +380,7 @@ func NewReader(conn *Conn) (*Reader, error) {
return r, nil 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) { msg, err := func() (message.Message, error) {
for { for {
msg, err := r.conn.Read() msg, err := r.conn.Read()
@@ -439,7 +439,7 @@ func (r *Reader) readTracks() (formats.Format, formats.Format, error) {
} }
// Tracks returns detected tracks // 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 return r.videoTrack, r.audioTrack
} }

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/abema/go-mp4" "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/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
@@ -104,18 +104,18 @@ func TestReadTracks(t *testing.T) {
for _, ca := range []struct { for _, ca := range []struct {
name string name string
videoTrack formats.Format videoTrack format.Format
audioTrack formats.Format audioTrack format.Format
}{ }{
{ {
"video+audio", "video+audio",
&formats.H264{ &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: h264SPS, SPS: h264SPS,
PPS: h264PPS, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
&formats.MPEG4Audio{ &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -129,7 +129,7 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"video", "video",
&formats.H264{ &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: h264SPS, SPS: h264SPS,
PPS: h264PPS, PPS: h264PPS,
@@ -139,13 +139,13 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"metadata without codec id, video+audio", "metadata without codec id, video+audio",
&formats.H264{ &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: h264SPS, SPS: h264SPS,
PPS: h264PPS, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
&formats.MPEG4Audio{ &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -159,7 +159,7 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"metadata without codec id, video only", "metadata without codec id, video only",
&formats.H264{ &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: h264SPS, SPS: h264SPS,
PPS: h264PPS, PPS: h264PPS,
@@ -169,13 +169,13 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"missing metadata, video+audio", "missing metadata, video+audio",
&formats.H264{ &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: h264SPS, SPS: h264SPS,
PPS: h264PPS, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
&formats.MPEG4Audio{ &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -190,7 +190,7 @@ func TestReadTracks(t *testing.T) {
{ {
"missing metadata, audio", "missing metadata, audio",
nil, nil,
&formats.MPEG4Audio{ &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -204,13 +204,13 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"obs studio pre 29.1 h265", "obs studio pre 29.1 h265",
&formats.H265{ &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: h265VPS, VPS: h265VPS,
SPS: h265SPS, SPS: h265SPS,
PPS: h265PPS, PPS: h265PPS,
}, },
&formats.MPEG4Audio{ &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,
@@ -224,7 +224,7 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"xplit broadcaster", "xplit broadcaster",
&formats.H265{ &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: h265VPS, VPS: h265VPS,
SPS: h265SPS, SPS: h265SPS,
@@ -234,7 +234,7 @@ func TestReadTracks(t *testing.T) {
}, },
{ {
"obs 30", "obs 30",
&formats.H265{ &format.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: h265VPS, VPS: h265VPS,
SPS: h265SPS, SPS: h265SPS,

View File

@@ -3,7 +3,7 @@ package rtmp
import ( import (
"time" "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/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
@@ -39,7 +39,7 @@ type Writer struct {
} }
// NewWriter allocates a Writer. // 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{ w := &Writer{
conn: conn, conn: conn,
} }
@@ -52,7 +52,7 @@ func NewWriter(conn *Conn, videoTrack formats.Format, audioTrack formats.Format)
return w, nil 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{ err := w.conn.Write(&message.DataAMF0{
ChunkStreamID: 4, ChunkStreamID: 4,
MessageStreamID: 0x1000000, MessageStreamID: 0x1000000,
@@ -68,7 +68,7 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
K: "videocodecid", K: "videocodecid",
V: func() float64 { V: func() float64 {
switch videoTrack.(type) { switch videoTrack.(type) {
case *formats.H264: case *format.H264:
return message.CodecH264 return message.CodecH264
default: default:
@@ -84,10 +84,10 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
K: "audiocodecid", K: "audiocodecid",
V: func() float64 { V: func() float64 {
switch audioTrack.(type) { switch audioTrack.(type) {
case *formats.MPEG1Audio: case *format.MPEG1Audio:
return message.CodecMPEG1Audio return message.CodecMPEG1Audio
case *formats.MPEG4AudioGeneric, *formats.MPEG4AudioLATM: case *format.MPEG4AudioGeneric, *format.MPEG4AudioLATM:
return message.CodecMPEG4Audio return message.CodecMPEG4Audio
default: default:
@@ -102,7 +102,7 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
return err 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. // write decoder config only if SPS and PPS are available.
// if they're not available yet, they're sent later. // if they're not available yet, they're sent later.
if sps, pps := videoTrack.SafeParams(); sps != nil && pps != nil { 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 var audioConfig *mpeg4audio.AudioSpecificConfig
switch track := audioTrack.(type) { switch track := audioTrack.(type) {
case *formats.MPEG4Audio: case *format.MPEG4Audio:
audioConfig = track.Config audioConfig = track.Config
case *formats.MPEG4AudioLATM: case *format.MPEG4AudioLATM:
audioConfig = track.Config.Programs[0].Layers[0].AudioSpecificConfig audioConfig = track.Config.Programs[0].Layers[0].AudioSpecificConfig
} }

View File

@@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio" "github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -14,7 +14,7 @@ import (
) )
func TestWriteTracks(t *testing.T) { func TestWriteTracks(t *testing.T) {
videoTrack := &formats.H264{ videoTrack := &format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{ SPS: []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
@@ -27,7 +27,7 @@ func TestWriteTracks(t *testing.T) {
PacketizationMode: 1, PacketizationMode: 1,
} }
audioTrack := &formats.MPEG4Audio{ audioTrack := &format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
Type: 2, Type: 2,

View File

@@ -2,11 +2,12 @@
package stream package stream
import ( import (
"sync"
"time" "time"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
@@ -16,28 +17,31 @@ import (
// Stream is a media stream. // Stream is a media stream.
// It stores tracks, readers and allow to write data to readers. // It stores tracks, readers and allow to write data to readers.
type Stream struct { type Stream struct {
desc *description.Session
bytesReceived *uint64 bytesReceived *uint64
rtspStream *gortsplib.ServerStream smedias map[*description.Media]*streamMedia
smedias map[*media.Media]*streamMedia mutex sync.RWMutex
rtspStream *gortsplib.ServerStream
rtspsStream *gortsplib.ServerStream
} }
// New allocates a Stream. // New allocates a Stream.
func New( func New(
udpMaxPayloadSize int, udpMaxPayloadSize int,
medias media.Medias, desc *description.Session,
generateRTPPackets bool, generateRTPPackets bool,
bytesReceived *uint64, bytesReceived *uint64,
source logger.Writer, source logger.Writer,
) (*Stream, error) { ) (*Stream, error) {
s := &Stream{ s := &Stream{
bytesReceived: bytesReceived, 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 var err error
s.smedias[media], err = newStreamMedia(udpMaxPayloadSize, media, generateRTPPackets, source) s.smedias[media], err = newStreamMedia(udpMaxPayloadSize, media, generateRTPPackets, source)
if err != nil { if err != nil {
@@ -50,21 +54,46 @@ func New(
// Close closes all resources of the stream. // Close closes all resources of the stream.
func (s *Stream) Close() { 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. // Desc returns description of the stream.
func (s *Stream) Medias() media.Medias { func (s *Stream) Desc() *description.Session {
return s.rtspStream.Medias() return s.desc
} }
// RTSPStream returns the RTSP stream. // 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 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. // 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] sm := s.smedias[medi]
sf := sm.formats[forma] sf := sm.formats[forma]
sf.addReader(r, cb) sf.addReader(r, cb)
@@ -72,6 +101,9 @@ func (s *Stream) AddReader(r interface{}, medi *media.Media, forma formats.Forma
// RemoveReader removes a reader. // RemoveReader removes a reader.
func (s *Stream) RemoveReader(r interface{}) { func (s *Stream) RemoveReader(r interface{}) {
s.mutex.Lock()
defer s.mutex.Unlock()
for _, sm := range s.smedias { for _, sm := range s.smedias {
for _, sf := range sm.formats { for _, sf := range sm.formats {
sf.removeReader(r) sf.removeReader(r)
@@ -80,15 +112,29 @@ func (s *Stream) RemoveReader(r interface{}) {
} }
// WriteUnit writes a Unit. // 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] sm := s.smedias[medi]
sf := sm.formats[forma] sf := sm.formats[forma]
s.mutex.RLock()
defer s.mutex.RUnlock()
sf.writeUnit(s, medi, data) sf.writeUnit(s, medi, data)
} }
// WriteRTPPacket writes a RTP packet. // 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] sm := s.smedias[medi]
sf := sm.formats[forma] sf := sm.formats[forma]
sf.writeRTPPacket(s, medi, pkt, ntp)
s.mutex.RLock()
defer s.mutex.RUnlock()
sf.writeRTPPacket(s, medi, pkt, ntp, pts)
} }

View File

@@ -1,12 +1,11 @@
package stream package stream
import ( import (
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/formatprocessor" "github.com/bluenviron/mediamtx/internal/formatprocessor"
@@ -17,13 +16,12 @@ import (
type streamFormat struct { type streamFormat struct {
source logger.Writer source logger.Writer
proc formatprocessor.Processor proc formatprocessor.Processor
mutex sync.RWMutex
nonRTSPReaders map[interface{}]func(unit.Unit) nonRTSPReaders map[interface{}]func(unit.Unit)
} }
func newStreamFormat( func newStreamFormat(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma formats.Format, forma format.Format,
generateRTPPackets bool, generateRTPPackets bool,
source logger.Writer, source logger.Writer,
) (*streamFormat, error) { ) (*streamFormat, error) {
@@ -42,21 +40,14 @@ func newStreamFormat(
} }
func (sf *streamFormat) addReader(r interface{}, cb func(unit.Unit)) { func (sf *streamFormat) addReader(r interface{}, cb func(unit.Unit)) {
sf.mutex.Lock()
defer sf.mutex.Unlock()
sf.nonRTSPReaders[r] = cb sf.nonRTSPReaders[r] = cb
} }
func (sf *streamFormat) removeReader(r interface{}) { func (sf *streamFormat) removeReader(r interface{}) {
sf.mutex.Lock()
defer sf.mutex.Unlock()
delete(sf.nonRTSPReaders, r) delete(sf.nonRTSPReaders, r)
} }
func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit) { func (sf *streamFormat) writeUnit(s *Stream, medi *description.Media, data unit.Unit) {
sf.mutex.RLock()
defer sf.mutex.RUnlock()
hasNonRTSPReaders := len(sf.nonRTSPReaders) > 0 hasNonRTSPReaders := len(sf.nonRTSPReaders) > 0
err := sf.proc.Process(data, hasNonRTSPReaders) err := sf.proc.Process(data, hasNonRTSPReaders)
@@ -65,10 +56,22 @@ func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit)
return return
} }
// forward RTP packets to RTSP readers n := uint64(0)
for _, pkt := range data.GetRTPPackets() { for _, pkt := range data.GetRTPPackets() {
atomic.AddUint64(s.bytesReceived, uint64(pkt.MarshalSize())) n += uint64(pkt.MarshalSize())
s.rtspStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP()) }
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 // 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) { func (sf *streamFormat) writeRTPPacket(
sf.writeUnit(s, medi, sf.proc.UnitForRTPPacket(pkt, ntp)) s *Stream,
medi *description.Media,
pkt *rtp.Packet,
ntp time.Time,
pts time.Duration,
) {
sf.writeUnit(s, medi, sf.proc.UnitForRTPPacket(pkt, ntp, pts))
} }

View File

@@ -1,23 +1,23 @@
package stream package stream
import ( import (
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
) )
type streamMedia struct { type streamMedia struct {
formats map[formats.Format]*streamFormat formats map[format.Format]*streamFormat
} }
func newStreamMedia(udpMaxPayloadSize int, func newStreamMedia(udpMaxPayloadSize int,
medi *media.Media, medi *description.Media,
generateRTPPackets bool, generateRTPPackets bool,
source logger.Writer, source logger.Writer,
) (*streamMedia, error) { ) (*streamMedia, error) {
sm := &streamMedia{ sm := &streamMedia{
formats: make(map[formats.Format]*streamFormat), formats: make(map[format.Format]*streamFormat),
} }
for _, forma := range medi.Formats { for _, forma := range medi.Formats {

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// AV1 is an AV1 data unit. // AV1 is an AV1 data unit.
type AV1 struct { type AV1 struct {
Base Base
PTS time.Duration TU [][]byte
TU [][]byte
} }

View File

@@ -10,6 +10,7 @@ import (
type Base struct { type Base struct {
RTPPackets []*rtp.Packet RTPPackets []*rtp.Packet
NTP time.Time NTP time.Time
PTS time.Duration
} }
// GetRTPPackets implements Unit. // GetRTPPackets implements Unit.
@@ -21,3 +22,8 @@ func (u *Base) GetRTPPackets() []*rtp.Packet {
func (u *Base) GetNTP() time.Time { func (u *Base) GetNTP() time.Time {
return u.NTP return u.NTP
} }
// GetPTS implements Unit.
func (u *Base) GetPTS() time.Duration {
return u.PTS
}

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// H264 is a H264 data unit. // H264 is a H264 data unit.
type H264 struct { type H264 struct {
Base Base
PTS time.Duration AU [][]byte
AU [][]byte
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// H265 is a H265 data unit. // H265 is a H265 data unit.
type H265 struct { type H265 struct {
Base Base
PTS time.Duration AU [][]byte
AU [][]byte
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// MPEG1Audio is a MPEG-1/2 Audio data unit. // MPEG1Audio is a MPEG-1/2 Audio data unit.
type MPEG1Audio struct { type MPEG1Audio struct {
Base Base
PTS time.Duration
Frames [][]byte Frames [][]byte
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// MPEG4AudioGeneric is a MPEG-4 Audio data unit. // MPEG4AudioGeneric is a MPEG-4 Audio data unit.
type MPEG4AudioGeneric struct { type MPEG4AudioGeneric struct {
Base Base
PTS time.Duration
AUs [][]byte AUs [][]byte
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// MPEG4AudioLATM is a MPEG-4 Audio data unit. // MPEG4AudioLATM is a MPEG-4 Audio data unit.
type MPEG4AudioLATM struct { type MPEG4AudioLATM struct {
Base Base
PTS time.Duration AU []byte
AU []byte
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// Opus is a Opus data unit. // Opus is a Opus data unit.
type Opus struct { type Opus struct {
Base Base
PTS time.Duration
Packets [][]byte Packets [][]byte
} }

View File

@@ -14,4 +14,7 @@ type Unit interface {
// returns the NTP timestamp of the unit. // returns the NTP timestamp of the unit.
GetNTP() time.Time GetNTP() time.Time
// returns the PTS of the unit.
GetPTS() time.Duration
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// VP8 is a VP8 data unit. // VP8 is a VP8 data unit.
type VP8 struct { type VP8 struct {
Base Base
PTS time.Duration
Frame []byte Frame []byte
} }

View File

@@ -1,12 +1,7 @@
package unit package unit
import (
"time"
)
// VP9 is a VP9 data unit. // VP9 is a VP9 data unit.
type VP9 struct { type VP9 struct {
Base Base
PTS time.Duration
Frame []byte Frame []byte
} }