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