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).Close
|
||||||
- (github.com/datarhei/gosrt.Conn).SetReadDeadline
|
- (github.com/datarhei/gosrt.Conn).SetReadDeadline
|
||||||
- (github.com/datarhei/gosrt.Conn).SetWriteDeadline
|
- (github.com/datarhei/gosrt.Conn).SetWriteDeadline
|
||||||
- (*github.com/bluenviron/gortsplib/v3.Client).Close
|
- (*github.com/bluenviron/gortsplib/v4.Client).Close
|
||||||
- (*github.com/bluenviron/gortsplib/v3.Server).Close
|
- (*github.com/bluenviron/gortsplib/v4.Server).Close
|
||||||
- (*github.com/bluenviron/gortsplib/v3.ServerSession).Close
|
- (*github.com/bluenviron/gortsplib/v4.ServerSession).Close
|
||||||
- (*github.com/bluenviron/gortsplib/v3.ServerStream).Close
|
- (*github.com/bluenviron/gortsplib/v4.ServerStream).Close
|
||||||
- (*github.com/bluenviron/gortsplib/v3.ServerConn).Close
|
- (*github.com/bluenviron/gortsplib/v4.ServerConn).Close
|
||||||
|
|
||||||
govet:
|
govet:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
|
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
|||||||
github.com/abema/go-mp4 v0.13.0
|
github.com/abema/go-mp4 v0.13.0
|
||||||
github.com/alecthomas/kong v0.8.0
|
github.com/alecthomas/kong v0.8.0
|
||||||
github.com/bluenviron/gohlslib v1.0.0
|
github.com/bluenviron/gohlslib v1.0.0
|
||||||
github.com/bluenviron/gortsplib/v3 v3.10.0
|
github.com/bluenviron/gortsplib/v4 v4.0.0-20230826160945-3bdae4ed4630
|
||||||
github.com/bluenviron/mediacommon v1.0.0
|
github.com/bluenviron/mediacommon v1.0.0
|
||||||
github.com/datarhei/gosrt v0.5.3
|
github.com/datarhei/gosrt v0.5.3
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
|
4
go.sum
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/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
|
||||||
github.com/bluenviron/gohlslib v1.0.0 h1:UOI7wW7EdXPnnoflPL+WRiUB+bDSyrR9AXtu029n5EY=
|
github.com/bluenviron/gohlslib v1.0.0 h1:UOI7wW7EdXPnnoflPL+WRiUB+bDSyrR9AXtu029n5EY=
|
||||||
github.com/bluenviron/gohlslib v1.0.0/go.mod h1:fwqXogd2G/CJ/0kD6TTALmWI3KAm66nZoI+06O02YKI=
|
github.com/bluenviron/gohlslib v1.0.0/go.mod h1:fwqXogd2G/CJ/0kD6TTALmWI3KAm66nZoI+06O02YKI=
|
||||||
github.com/bluenviron/gortsplib/v3 v3.10.0 h1:E2ytPD1/b6JgzHYVSsyaG2xtXsvaGw9sxTdZ0Wnwsd4=
|
github.com/bluenviron/gortsplib/v4 v4.0.0-20230826160945-3bdae4ed4630 h1:Xe2em0Hv8ftQ1G3YgKVIcnor63h+OuXBHL7sNDMpfZA=
|
||||||
github.com/bluenviron/gortsplib/v3 v3.10.0/go.mod h1:prNU1aMVBmgmmKwlvLiEdjBbTEpTw4BRsqVcqEARgMY=
|
github.com/bluenviron/gortsplib/v4 v4.0.0-20230826160945-3bdae4ed4630/go.mod h1:UU9EkvmpvyXdRIDBBUK7zdFt2LsdnV4iz9cHP3JaB8s=
|
||||||
github.com/bluenviron/mediacommon v1.0.0 h1:hKelTQKfetasCmXaXMiL1ihID0GRmItyWZt1/pqiKKk=
|
github.com/bluenviron/mediacommon v1.0.0 h1:hKelTQKfetasCmXaXMiL1ihID0GRmItyWZt1/pqiKKk=
|
||||||
github.com/bluenviron/mediacommon v1.0.0/go.mod h1:nt5oKCO0WcZ+AH1oc12gs2ldp67xW2vl88c2StNmPlI=
|
github.com/bluenviron/mediacommon v1.0.0/go.mod h1:nt5oKCO0WcZ+AH1oc12gs2ldp67xW2vl88c2StNmPlI=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthMethods is the authMethods parameter.
|
// AuthMethods is the authMethods parameter.
|
||||||
|
@@ -12,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gohlslib"
|
"github.com/bluenviron/gohlslib"
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf/decrypt"
|
"github.com/bluenviron/mediamtx/internal/conf/decrypt"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf/env"
|
"github.com/bluenviron/mediamtx/internal/conf/env"
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/crypto/nacl/secretbox"
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
|
|
||||||
|
@@ -11,8 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/\.~]+$`)
|
var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/\.~]+$`)
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Protocol is a RTSP transport.
|
// Protocol is a RTSP transport.
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SourceProtocol is the sourceProtocol parameter.
|
// SourceProtocol is the sourceProtocol parameter.
|
||||||
|
@@ -14,9 +14,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"github.com/datarhei/gosrt"
|
"github.com/datarhei/gosrt"
|
||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/bluenviron/mediamtx/internal/rtmp"
|
"github.com/bluenviron/mediamtx/internal/rtmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testFormatH264 = &formats.H264{
|
var testFormatH264 = &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{ // 1920x1080 baseline
|
SPS: []byte{ // 1920x1080 baseline
|
||||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||||
@@ -38,9 +38,9 @@ var testFormatH264 = &formats.H264{
|
|||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
var testMediaH264 = &media.Media{
|
var testMediaH264 = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{testFormatH264},
|
Formats: []format.Format{testFormatH264},
|
||||||
}
|
}
|
||||||
|
|
||||||
func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) {
|
func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) {
|
||||||
@@ -245,11 +245,11 @@ func TestAPIPathsList(t *testing.T) {
|
|||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://localhost:8554/mypath",
|
"rtsp://localhost:8554/mypath",
|
||||||
media.Medias{
|
&description.Session{Medias: []*description.Media{
|
||||||
media0,
|
media0,
|
||||||
{
|
{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG4Audio{
|
Formats: []format.Format{&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -261,7 +261,7 @@ func TestAPIPathsList(t *testing.T) {
|
|||||||
IndexDeltaLength: 3,
|
IndexDeltaLength: 3,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
})
|
}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ func TestAPIPathsList(t *testing.T) {
|
|||||||
Version: 2,
|
Version: 2,
|
||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
},
|
},
|
||||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
Payload: []byte{5, 1, 2, 3, 4},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ func TestAPIPathsList(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Ready: true,
|
Ready: true,
|
||||||
Tracks: []string{"H264", "MPEG-4 Audio"},
|
Tracks: []string{"H264", "MPEG-4 Audio"},
|
||||||
BytesReceived: 16,
|
BytesReceived: 17,
|
||||||
}},
|
}},
|
||||||
}, out)
|
}, out)
|
||||||
})
|
})
|
||||||
@@ -311,29 +311,28 @@ func TestAPIPathsList(t *testing.T) {
|
|||||||
|
|
||||||
hc := &http.Client{Transport: &http.Transport{}}
|
hc := &http.Client{Transport: &http.Transport{}}
|
||||||
|
|
||||||
medias := media.Medias{
|
|
||||||
{
|
|
||||||
Type: media.TypeVideo,
|
|
||||||
Formats: []formats.Format{testFormatH264},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: media.TypeAudio,
|
|
||||||
Formats: []formats.Format{&formats.MPEG4Audio{
|
|
||||||
PayloadTyp: 97,
|
|
||||||
Config: &mpeg4audio.Config{
|
|
||||||
Type: 2,
|
|
||||||
SampleRate: 44100,
|
|
||||||
ChannelCount: 2,
|
|
||||||
},
|
|
||||||
SizeLength: 13,
|
|
||||||
IndexLength: 3,
|
|
||||||
IndexDeltaLength: 3,
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||||
err = source.StartRecording("rtsps://localhost:8322/mypath", medias)
|
err = source.StartRecording("rtsps://localhost:8322/mypath",
|
||||||
|
&description.Session{Medias: []*description.Media{
|
||||||
|
{
|
||||||
|
Type: description.MediaTypeVideo,
|
||||||
|
Formats: []format.Format{testFormatH264},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: description.MediaTypeAudio,
|
||||||
|
Formats: []format.Format{&format.MPEG4Audio{
|
||||||
|
PayloadTyp: 97,
|
||||||
|
Config: &mpeg4audio.Config{
|
||||||
|
Type: 2,
|
||||||
|
SampleRate: 44100,
|
||||||
|
ChannelCount: 2,
|
||||||
|
},
|
||||||
|
SizeLength: 13,
|
||||||
|
IndexLength: 3,
|
||||||
|
IndexDeltaLength: 3,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -471,7 +470,8 @@ func TestAPIPathsGet(t *testing.T) {
|
|||||||
|
|
||||||
if ca == "ok" || ca == "ok-nested" {
|
if ca == "ok" || ca == "ok-nested" {
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/"+pathName, media.Medias{testMediaH264})
|
err := source.StartRecording("rtsp://localhost:8554/"+pathName,
|
||||||
|
&description.Session{Medias: []*description.Media{testMediaH264}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -546,7 +546,8 @@ func TestAPIProtocolList(t *testing.T) {
|
|||||||
case "rtsp conns", "rtsp sessions":
|
case "rtsp conns", "rtsp sessions":
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath", media.Medias{medi})
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -555,7 +556,8 @@ func TestAPIProtocolList(t *testing.T) {
|
|||||||
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := source.StartRecording("rtsps://localhost:8322/mypath", media.Medias{medi})
|
err := source.StartRecording("rtsps://localhost:8322/mypath",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -590,7 +592,7 @@ func TestAPIProtocolList(t *testing.T) {
|
|||||||
case "hls":
|
case "hls":
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -647,7 +649,7 @@ func TestAPIProtocolList(t *testing.T) {
|
|||||||
case "webrtc":
|
case "webrtc":
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -665,7 +667,7 @@ func TestAPIProtocolList(t *testing.T) {
|
|||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
Payload: []byte{5, 1, 2, 3, 4},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -830,7 +832,8 @@ func TestAPIProtocolGet(t *testing.T) {
|
|||||||
case "rtsp conns", "rtsp sessions":
|
case "rtsp conns", "rtsp sessions":
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath", media.Medias{medi})
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -839,7 +842,8 @@ func TestAPIProtocolGet(t *testing.T) {
|
|||||||
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := source.StartRecording("rtsps://localhost:8322/mypath", media.Medias{medi})
|
err := source.StartRecording("rtsps://localhost:8322/mypath",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -874,7 +878,7 @@ func TestAPIProtocolGet(t *testing.T) {
|
|||||||
case "hls":
|
case "hls":
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -931,7 +935,7 @@ func TestAPIProtocolGet(t *testing.T) {
|
|||||||
case "webrtc":
|
case "webrtc":
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -949,7 +953,7 @@ func TestAPIProtocolGet(t *testing.T) {
|
|||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
Payload: []byte{5, 1, 2, 3, 4},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -1196,7 +1200,7 @@ func TestAPIProtocolKick(t *testing.T) {
|
|||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
|
|
||||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -1206,7 +1210,7 @@ func TestAPIProtocolKick(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := source.StartRecording("rtsps://localhost:8322/mypath",
|
err := source.StartRecording("rtsps://localhost:8322/mypath",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
@@ -11,10 +11,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/auth"
|
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
@@ -213,29 +213,27 @@ func (p *Core) createResources(initial bool) error {
|
|||||||
p.externalCmdPool = externalcmd.NewPool()
|
p.externalCmdPool = externalcmd.NewPool()
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.Metrics {
|
if p.conf.Metrics &&
|
||||||
if p.metrics == nil {
|
p.metrics == nil {
|
||||||
p.metrics, err = newMetrics(
|
p.metrics, err = newMetrics(
|
||||||
p.conf.MetricsAddress,
|
p.conf.MetricsAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.PPROF {
|
if p.conf.PPROF &&
|
||||||
if p.pprof == nil {
|
p.pprof == nil {
|
||||||
p.pprof, err = newPPROF(
|
p.pprof, err = newPPROF(
|
||||||
p.conf.PPROFAddress,
|
p.conf.PPROFAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,219 +255,212 @@ func (p *Core) createResources(initial bool) error {
|
|||||||
|
|
||||||
if p.conf.RTSP &&
|
if p.conf.RTSP &&
|
||||||
(p.conf.Encryption == conf.EncryptionNo ||
|
(p.conf.Encryption == conf.EncryptionNo ||
|
||||||
p.conf.Encryption == conf.EncryptionOptional) {
|
p.conf.Encryption == conf.EncryptionOptional) &&
|
||||||
if p.rtspServer == nil {
|
p.rtspServer == nil {
|
||||||
_, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)]
|
_, useUDP := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDP)]
|
||||||
_, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)]
|
_, useMulticast := p.conf.Protocols[conf.Protocol(gortsplib.TransportUDPMulticast)]
|
||||||
p.rtspServer, err = newRTSPServer(
|
|
||||||
p.conf.RTSPAddress,
|
p.rtspServer, err = newRTSPServer(
|
||||||
p.conf.AuthMethods,
|
p.conf.RTSPAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.AuthMethods,
|
||||||
p.conf.WriteTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteTimeout,
|
||||||
useUDP,
|
p.conf.WriteQueueSize,
|
||||||
useMulticast,
|
useUDP,
|
||||||
p.conf.RTPAddress,
|
useMulticast,
|
||||||
p.conf.RTCPAddress,
|
p.conf.RTPAddress,
|
||||||
p.conf.MulticastIPRange,
|
p.conf.RTCPAddress,
|
||||||
p.conf.MulticastRTPPort,
|
p.conf.MulticastIPRange,
|
||||||
p.conf.MulticastRTCPPort,
|
p.conf.MulticastRTPPort,
|
||||||
false,
|
p.conf.MulticastRTCPPort,
|
||||||
"",
|
false,
|
||||||
"",
|
"",
|
||||||
p.conf.RTSPAddress,
|
"",
|
||||||
p.conf.Protocols,
|
p.conf.RTSPAddress,
|
||||||
p.conf.RunOnConnect,
|
p.conf.Protocols,
|
||||||
p.conf.RunOnConnectRestart,
|
p.conf.RunOnConnect,
|
||||||
p.externalCmdPool,
|
p.conf.RunOnConnectRestart,
|
||||||
p.metrics,
|
p.externalCmdPool,
|
||||||
p.pathManager,
|
p.metrics,
|
||||||
p,
|
p.pathManager,
|
||||||
)
|
p,
|
||||||
if err != nil {
|
)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.RTSP &&
|
if p.conf.RTSP &&
|
||||||
(p.conf.Encryption == conf.EncryptionStrict ||
|
(p.conf.Encryption == conf.EncryptionStrict ||
|
||||||
p.conf.Encryption == conf.EncryptionOptional) {
|
p.conf.Encryption == conf.EncryptionOptional) &&
|
||||||
if p.rtspsServer == nil {
|
p.rtspsServer == nil {
|
||||||
p.rtspsServer, err = newRTSPServer(
|
p.rtspsServer, err = newRTSPServer(
|
||||||
p.conf.RTSPSAddress,
|
p.conf.RTSPSAddress,
|
||||||
p.conf.AuthMethods,
|
p.conf.AuthMethods,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteTimeout,
|
p.conf.WriteTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteQueueSize,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
p.conf.ServerCert,
|
p.conf.ServerCert,
|
||||||
p.conf.ServerKey,
|
p.conf.ServerKey,
|
||||||
p.conf.RTSPAddress,
|
p.conf.RTSPAddress,
|
||||||
p.conf.Protocols,
|
p.conf.Protocols,
|
||||||
p.conf.RunOnConnect,
|
p.conf.RunOnConnect,
|
||||||
p.conf.RunOnConnectRestart,
|
p.conf.RunOnConnectRestart,
|
||||||
p.externalCmdPool,
|
p.externalCmdPool,
|
||||||
p.metrics,
|
p.metrics,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.RTMP &&
|
if p.conf.RTMP &&
|
||||||
(p.conf.RTMPEncryption == conf.EncryptionNo ||
|
(p.conf.RTMPEncryption == conf.EncryptionNo ||
|
||||||
p.conf.RTMPEncryption == conf.EncryptionOptional) {
|
p.conf.RTMPEncryption == conf.EncryptionOptional) &&
|
||||||
if p.rtmpServer == nil {
|
p.rtmpServer == nil {
|
||||||
p.rtmpServer, err = newRTMPServer(
|
p.rtmpServer, err = newRTMPServer(
|
||||||
p.conf.RTMPAddress,
|
p.conf.RTMPAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteTimeout,
|
p.conf.WriteTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteQueueSize,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
p.conf.RTSPAddress,
|
p.conf.RTSPAddress,
|
||||||
p.conf.RunOnConnect,
|
p.conf.RunOnConnect,
|
||||||
p.conf.RunOnConnectRestart,
|
p.conf.RunOnConnectRestart,
|
||||||
p.externalCmdPool,
|
p.externalCmdPool,
|
||||||
p.metrics,
|
p.metrics,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.RTMP &&
|
if p.conf.RTMP &&
|
||||||
(p.conf.RTMPEncryption == conf.EncryptionStrict ||
|
(p.conf.RTMPEncryption == conf.EncryptionStrict ||
|
||||||
p.conf.RTMPEncryption == conf.EncryptionOptional) {
|
p.conf.RTMPEncryption == conf.EncryptionOptional) &&
|
||||||
if p.rtmpsServer == nil {
|
p.rtmpsServer == nil {
|
||||||
p.rtmpsServer, err = newRTMPServer(
|
p.rtmpsServer, err = newRTMPServer(
|
||||||
p.conf.RTMPSAddress,
|
p.conf.RTMPSAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteTimeout,
|
p.conf.WriteTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteQueueSize,
|
||||||
true,
|
true,
|
||||||
p.conf.RTMPServerCert,
|
p.conf.RTMPServerCert,
|
||||||
p.conf.RTMPServerKey,
|
p.conf.RTMPServerKey,
|
||||||
p.conf.RTSPAddress,
|
p.conf.RTSPAddress,
|
||||||
p.conf.RunOnConnect,
|
p.conf.RunOnConnect,
|
||||||
p.conf.RunOnConnectRestart,
|
p.conf.RunOnConnectRestart,
|
||||||
p.externalCmdPool,
|
p.externalCmdPool,
|
||||||
p.metrics,
|
p.metrics,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.HLS {
|
if p.conf.HLS &&
|
||||||
if p.hlsManager == nil {
|
p.hlsManager == nil {
|
||||||
p.hlsManager, err = newHLSManager(
|
p.hlsManager, err = newHLSManager(
|
||||||
p.conf.HLSAddress,
|
p.conf.HLSAddress,
|
||||||
p.conf.HLSEncryption,
|
p.conf.HLSEncryption,
|
||||||
p.conf.HLSServerKey,
|
p.conf.HLSServerKey,
|
||||||
p.conf.HLSServerCert,
|
p.conf.HLSServerCert,
|
||||||
p.conf.ExternalAuthenticationURL,
|
p.conf.ExternalAuthenticationURL,
|
||||||
p.conf.HLSAlwaysRemux,
|
p.conf.HLSAlwaysRemux,
|
||||||
p.conf.HLSVariant,
|
p.conf.HLSVariant,
|
||||||
p.conf.HLSSegmentCount,
|
p.conf.HLSSegmentCount,
|
||||||
p.conf.HLSSegmentDuration,
|
p.conf.HLSSegmentDuration,
|
||||||
p.conf.HLSPartDuration,
|
p.conf.HLSPartDuration,
|
||||||
p.conf.HLSSegmentMaxSize,
|
p.conf.HLSSegmentMaxSize,
|
||||||
p.conf.HLSAllowOrigin,
|
p.conf.HLSAllowOrigin,
|
||||||
p.conf.HLSTrustedProxies,
|
p.conf.HLSTrustedProxies,
|
||||||
p.conf.HLSDirectory,
|
p.conf.HLSDirectory,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteQueueSize,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p.metrics,
|
p.metrics,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.WebRTC {
|
if p.conf.WebRTC &&
|
||||||
if p.webRTCManager == nil {
|
p.webRTCManager == nil {
|
||||||
p.webRTCManager, err = newWebRTCManager(
|
p.webRTCManager, err = newWebRTCManager(
|
||||||
p.conf.WebRTCAddress,
|
p.conf.WebRTCAddress,
|
||||||
p.conf.WebRTCEncryption,
|
p.conf.WebRTCEncryption,
|
||||||
p.conf.WebRTCServerKey,
|
p.conf.WebRTCServerKey,
|
||||||
p.conf.WebRTCServerCert,
|
p.conf.WebRTCServerCert,
|
||||||
p.conf.WebRTCAllowOrigin,
|
p.conf.WebRTCAllowOrigin,
|
||||||
p.conf.WebRTCTrustedProxies,
|
p.conf.WebRTCTrustedProxies,
|
||||||
p.conf.WebRTCICEServers2,
|
p.conf.WebRTCICEServers2,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteQueueSize,
|
||||||
p.conf.WebRTCICEHostNAT1To1IPs,
|
p.conf.WebRTCICEHostNAT1To1IPs,
|
||||||
p.conf.WebRTCICEUDPMuxAddress,
|
p.conf.WebRTCICEUDPMuxAddress,
|
||||||
p.conf.WebRTCICETCPMuxAddress,
|
p.conf.WebRTCICETCPMuxAddress,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p.metrics,
|
p.metrics,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.SRT {
|
if p.conf.SRT &&
|
||||||
if p.srtServer == nil {
|
p.srtServer == nil {
|
||||||
p.srtServer, err = newSRTServer(
|
p.srtServer, err = newSRTServer(
|
||||||
p.conf.SRTAddress,
|
p.conf.SRTAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf.WriteTimeout,
|
p.conf.WriteTimeout,
|
||||||
p.conf.WriteQueueSize,
|
p.conf.WriteQueueSize,
|
||||||
p.conf.UDPMaxPayloadSize,
|
p.conf.UDPMaxPayloadSize,
|
||||||
p.externalCmdPool,
|
p.externalCmdPool,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.conf.API {
|
if p.conf.API &&
|
||||||
if p.api == nil {
|
p.api == nil {
|
||||||
p.api, err = newAPI(
|
p.api, err = newAPI(
|
||||||
p.conf.APIAddress,
|
p.conf.APIAddress,
|
||||||
p.conf.ReadTimeout,
|
p.conf.ReadTimeout,
|
||||||
p.conf,
|
p.conf,
|
||||||
p.pathManager,
|
p.pathManager,
|
||||||
p.rtspServer,
|
p.rtspServer,
|
||||||
p.rtspsServer,
|
p.rtspsServer,
|
||||||
p.rtmpServer,
|
p.rtmpServer,
|
||||||
p.rtmpsServer,
|
p.rtmpsServer,
|
||||||
p.hlsManager,
|
p.hlsManager,
|
||||||
p.webRTCManager,
|
p.webRTCManager,
|
||||||
p.srtServer,
|
p.srtServer,
|
||||||
p,
|
p,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,8 +6,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,7 +111,8 @@ func TestCoreHotReloading(t *testing.T) {
|
|||||||
medi := testMediaH264
|
medi := testMediaH264
|
||||||
|
|
||||||
c := gortsplib.Client{}
|
c := gortsplib.Client{}
|
||||||
err = c.StartRecording("rtsp://localhost:8554/test1", media.Medias{medi})
|
err = c.StartRecording("rtsp://localhost:8554/test1",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -126,7 +127,8 @@ func TestCoreHotReloading(t *testing.T) {
|
|||||||
medi := testMediaH264
|
medi := testMediaH264
|
||||||
|
|
||||||
conn := gortsplib.Client{}
|
conn := gortsplib.Client{}
|
||||||
err = conn.StartRecording("rtsp://localhost:8554/test1", media.Medias{medi})
|
err = conn.StartRecording("rtsp://localhost:8554/test1",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
}()
|
}()
|
||||||
|
@@ -9,9 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -118,9 +118,9 @@ func TestHLSRead(t *testing.T) {
|
|||||||
require.Equal(t, true, ok)
|
require.Equal(t, true, ok)
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
||||||
medi := &media.Media{
|
medi := &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
SPS: []byte{ // 1920x1080 baseline
|
SPS: []byte{ // 1920x1080 baseline
|
||||||
@@ -136,7 +136,8 @@ func TestHLSRead(t *testing.T) {
|
|||||||
source := gortsplib.Client{
|
source := gortsplib.Client{
|
||||||
Transport: &v,
|
Transport: &v,
|
||||||
}
|
}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/stream", media.Medias{medi})
|
err := source.StartRecording("rtsp://localhost:8554/stream",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
|
@@ -13,9 +13,9 @@ import (
|
|||||||
|
|
||||||
"github.com/bluenviron/gohlslib"
|
"github.com/bluenviron/gohlslib"
|
||||||
"github.com/bluenviron/gohlslib/pkg/codecs"
|
"github.com/bluenviron/gohlslib/pkg/codecs"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
|
"github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
@@ -256,7 +256,7 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
|
|||||||
|
|
||||||
m.ringBuffer, _ = ringbuffer.New(uint64(m.writeQueueSize))
|
m.ringBuffer, _ = ringbuffer.New(uint64(m.writeQueueSize))
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media
|
||||||
|
|
||||||
videoMedia, videoTrack := m.createVideoTrack(res.stream)
|
videoMedia, videoTrack := m.createVideoTrack(res.stream)
|
||||||
if videoMedia != nil {
|
if videoMedia != nil {
|
||||||
@@ -335,14 +335,11 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohlslib.Track) {
|
func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
|
||||||
var videoFormatAV1 *formats.AV1
|
var videoFormatAV1 *format.AV1
|
||||||
videoMedia := stream.Medias().FindFormat(&videoFormatAV1)
|
videoMedia := stream.Desc().FindFormat(&videoFormatAV1)
|
||||||
|
|
||||||
if videoFormatAV1 != nil {
|
if videoFormatAV1 != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, videoMedia, videoFormatAV1, func(u unit.Unit) {
|
stream.AddReader(m, videoMedia, videoFormatAV1, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.AV1)
|
tunit := u.(*unit.AV1)
|
||||||
@@ -351,12 +348,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
err := m.muxer.WriteAV1(tunit.NTP, pts, tunit.TU)
|
err := m.muxer.WriteAV1(tunit.NTP, pts, tunit.TU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("muxer error: %v", err)
|
return fmt.Errorf("muxer error: %v", err)
|
||||||
@@ -371,13 +363,10 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoFormatVP9 *formats.VP9
|
var videoFormatVP9 *format.VP9
|
||||||
videoMedia = stream.Medias().FindFormat(&videoFormatVP9)
|
videoMedia = stream.Desc().FindFormat(&videoFormatVP9)
|
||||||
|
|
||||||
if videoFormatVP9 != nil {
|
if videoFormatVP9 != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, videoMedia, videoFormatVP9, func(u unit.Unit) {
|
stream.AddReader(m, videoMedia, videoFormatVP9, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.VP9)
|
tunit := u.(*unit.VP9)
|
||||||
@@ -386,12 +375,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
err := m.muxer.WriteVP9(tunit.NTP, pts, tunit.Frame)
|
err := m.muxer.WriteVP9(tunit.NTP, pts, tunit.Frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("muxer error: %v", err)
|
return fmt.Errorf("muxer error: %v", err)
|
||||||
@@ -406,13 +390,10 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoFormatH265 *formats.H265
|
var videoFormatH265 *format.H265
|
||||||
videoMedia = stream.Medias().FindFormat(&videoFormatH265)
|
videoMedia = stream.Desc().FindFormat(&videoFormatH265)
|
||||||
|
|
||||||
if videoFormatH265 != nil {
|
if videoFormatH265 != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, videoMedia, videoFormatH265, func(u unit.Unit) {
|
stream.AddReader(m, videoMedia, videoFormatH265, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.H265)
|
tunit := u.(*unit.H265)
|
||||||
@@ -421,12 +402,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU)
|
err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("muxer error: %v", err)
|
return fmt.Errorf("muxer error: %v", err)
|
||||||
@@ -447,13 +423,10 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoFormatH264 *formats.H264
|
var videoFormatH264 *format.H264
|
||||||
videoMedia = stream.Medias().FindFormat(&videoFormatH264)
|
videoMedia = stream.Desc().FindFormat(&videoFormatH264)
|
||||||
|
|
||||||
if videoFormatH264 != nil {
|
if videoFormatH264 != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, videoMedia, videoFormatH264, func(u unit.Unit) {
|
stream.AddReader(m, videoMedia, videoFormatH264, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.H264)
|
tunit := u.(*unit.H264)
|
||||||
@@ -462,12 +435,7 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU)
|
err := m.muxer.WriteH26x(tunit.NTP, pts, tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("muxer error: %v", err)
|
return fmt.Errorf("muxer error: %v", err)
|
||||||
@@ -490,24 +458,16 @@ func (m *hlsMuxer) createVideoTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohlslib.Track) {
|
func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*description.Media, *gohlslib.Track) {
|
||||||
var audioFormatOpus *formats.Opus
|
var audioFormatOpus *format.Opus
|
||||||
audioMedia := stream.Medias().FindFormat(&audioFormatOpus)
|
audioMedia := stream.Desc().FindFormat(&audioFormatOpus)
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
audioStartPTSFilled := false
|
|
||||||
var audioStartPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, audioMedia, audioFormatOpus, func(u unit.Unit) {
|
stream.AddReader(m, audioMedia, audioFormatOpus, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.Opus)
|
tunit := u.(*unit.Opus)
|
||||||
|
|
||||||
if !audioStartPTSFilled {
|
pts := tunit.PTS
|
||||||
audioStartPTSFilled = true
|
|
||||||
audioStartPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - audioStartPTS
|
|
||||||
err := m.muxer.WriteOpus(
|
err := m.muxer.WriteOpus(
|
||||||
tunit.NTP,
|
tunit.NTP,
|
||||||
pts,
|
pts,
|
||||||
@@ -532,13 +492,10 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioFormatMPEG4AudioGeneric *formats.MPEG4AudioGeneric
|
var audioFormatMPEG4AudioGeneric *format.MPEG4AudioGeneric
|
||||||
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioGeneric)
|
audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioGeneric)
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
audioStartPTSFilled := false
|
|
||||||
var audioStartPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, audioMedia, audioFormatMPEG4AudioGeneric, func(u unit.Unit) {
|
stream.AddReader(m, audioMedia, audioFormatMPEG4AudioGeneric, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG4AudioGeneric)
|
tunit := u.(*unit.MPEG4AudioGeneric)
|
||||||
@@ -547,12 +504,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !audioStartPTSFilled {
|
pts := tunit.PTS
|
||||||
audioStartPTSFilled = true
|
|
||||||
audioStartPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - audioStartPTS
|
|
||||||
err := m.muxer.WriteMPEG4Audio(
|
err := m.muxer.WriteMPEG4Audio(
|
||||||
tunit.NTP,
|
tunit.NTP,
|
||||||
pts,
|
pts,
|
||||||
@@ -572,16 +524,13 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioFormatMPEG4AudioLATM *formats.MPEG4AudioLATM
|
var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM
|
||||||
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioLATM)
|
audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioLATM)
|
||||||
|
|
||||||
if audioMedia != nil &&
|
if audioMedia != nil &&
|
||||||
audioFormatMPEG4AudioLATM.Config != nil &&
|
audioFormatMPEG4AudioLATM.Config != nil &&
|
||||||
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 &&
|
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 &&
|
||||||
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 {
|
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 {
|
||||||
audioStartPTSFilled := false
|
|
||||||
var audioStartPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(m, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) {
|
stream.AddReader(m, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) {
|
||||||
m.ringBuffer.Push(func() error {
|
m.ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG4AudioLATM)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
@@ -590,12 +539,7 @@ func (m *hlsMuxer) createAudioTrack(stream *stream.Stream) (*media.Media, *gohls
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !audioStartPTSFilled {
|
pts := tunit.PTS
|
||||||
audioStartPTSFilled = true
|
|
||||||
audioStartPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS - audioStartPTS
|
|
||||||
err := m.muxer.WriteMPEG4Audio(
|
err := m.muxer.WriteMPEG4Audio(
|
||||||
tunit.NTP,
|
tunit.NTP,
|
||||||
pts,
|
pts,
|
||||||
|
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/bluenviron/gohlslib"
|
"github.com/bluenviron/gohlslib"
|
||||||
"github.com/bluenviron/gohlslib/pkg/codecs"
|
"github.com/bluenviron/gohlslib/pkg/codecs"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -69,48 +69,48 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
|
|||||||
s.Log(logger.Warn, err.Error())
|
s.Log(logger.Warn, err.Error())
|
||||||
},
|
},
|
||||||
OnTracks: func(tracks []*gohlslib.Track) error {
|
OnTracks: func(tracks []*gohlslib.Track) error {
|
||||||
var medias media.Medias
|
var medias []*description.Media
|
||||||
|
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
var medi *media.Media
|
var medi *description.Media
|
||||||
|
|
||||||
switch tcodec := track.Codec.(type) {
|
switch tcodec := track.Codec.(type) {
|
||||||
case *codecs.AV1:
|
case *codecs.AV1:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.AV1{}},
|
Formats: []format.Format{&format.AV1{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) {
|
c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) {
|
||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.AV1{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.AV1{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
TU: tu,
|
||||||
TU: tu,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *codecs.VP9:
|
case *codecs.VP9:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.VP9{}},
|
Formats: []format.Format{&format.VP9{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.OnDataVP9(track, func(pts time.Duration, frame []byte) {
|
c.OnDataVP9(track, func(pts time.Duration, frame []byte) {
|
||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.VP9{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.VP9{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
Frame: frame,
|
Frame: frame,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *codecs.H264:
|
case *codecs.H264:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
SPS: tcodec.SPS,
|
SPS: tcodec.SPS,
|
||||||
@@ -122,16 +122,16 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *codecs.H265:
|
case *codecs.H265:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H265{
|
Formats: []format.Format{&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: tcodec.VPS,
|
VPS: tcodec.VPS,
|
||||||
SPS: tcodec.SPS,
|
SPS: tcodec.SPS,
|
||||||
@@ -143,16 +143,16 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *codecs.MPEG4Audio:
|
case *codecs.MPEG4Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG4Audio{
|
Formats: []format.Format{&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SizeLength: 13,
|
SizeLength: 13,
|
||||||
IndexLength: 3,
|
IndexLength: 3,
|
||||||
@@ -165,16 +165,16 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
AUs: aus,
|
AUs: aus,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *codecs.Opus:
|
case *codecs.Opus:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.Opus{
|
Formats: []format.Format{&format.Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
IsStereo: (tcodec.ChannelCount == 2),
|
IsStereo: (tcodec.ChannelCount == 2),
|
||||||
}},
|
}},
|
||||||
@@ -184,8 +184,8 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
Packets: packets,
|
Packets: packets,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -195,7 +195,7 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
|
@@ -8,10 +8,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -135,25 +135,25 @@ func TestHLSSource(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, media.Medias{
|
require.Equal(t, []*description.Media{
|
||||||
{
|
{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Control: medias[0].Control,
|
Control: desc.Medias[0].Control,
|
||||||
Formats: []formats.Format{
|
Formats: []format.Format{
|
||||||
&formats.H264{
|
&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Control: medias[1].Control,
|
Control: desc.Medias[1].Control,
|
||||||
Formats: []formats.Format{
|
Formats: []format.Format{
|
||||||
&formats.MPEG4Audio{
|
&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
ProfileLevelID: 1,
|
ProfileLevelID: 1,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
@@ -167,12 +167,12 @@ func TestHLSSource(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, medias)
|
}, desc.Medias)
|
||||||
|
|
||||||
var forma *formats.H264
|
var forma *format.H264
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||||
|
@@ -232,15 +232,15 @@ func (m *metrics) setHLSManager(s apiHLSManager) {
|
|||||||
m.hlsManager = s
|
m.hlsManager = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// rtspServerSet is called by rtspServer (plain).
|
// setRTSPServer is called by rtspServer (plain).
|
||||||
func (m *metrics) rtspServerSet(s apiRTSPServer) {
|
func (m *metrics) setRTSPServer(s apiRTSPServer) {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
m.rtspServer = s
|
m.rtspServer = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// rtspsServerSet is called by rtspServer (tls).
|
// setRTSPSServer is called by rtspServer (tls).
|
||||||
func (m *metrics) rtspsServerSet(s apiRTSPServer) {
|
func (m *metrics) setRTSPSServer(s apiRTSPServer) {
|
||||||
m.mutex.Lock()
|
m.mutex.Lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.mutex.Unlock()
|
||||||
m.rtspsServer = s
|
m.rtspsServer = s
|
||||||
|
@@ -9,9 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/rtmp"
|
"github.com/bluenviron/mediamtx/internal/rtmp"
|
||||||
@@ -69,13 +69,13 @@ webrtc_sessions_bytes_sent 0
|
|||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err = source.StartRecording("rtsp://localhost:8554/rtsp_path",
|
err = source.StartRecording("rtsp://localhost:8554/rtsp_path",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||||
err = source2.StartRecording("rtsps://localhost:8322/rtsps_path",
|
err = source2.StartRecording("rtsps://localhost:8322/rtsps_path",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source2.Close()
|
defer source2.Close()
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ webrtc_sessions_bytes_sent 0
|
|||||||
conn, err := rtmp.NewClientConn(nconn, u, true)
|
conn, err := rtmp.NewClientConn(nconn, u, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{ // 1920x1080 baseline
|
SPS: []byte{ // 1920x1080 baseline
|
||||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||||
|
@@ -10,8 +10,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
||||||
@@ -56,7 +56,7 @@ type pathSourceStaticSetReadyRes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pathSourceStaticSetReadyReq struct {
|
type pathSourceStaticSetReadyReq struct {
|
||||||
medias media.Medias
|
desc *description.Session
|
||||||
generateRTPPackets bool
|
generateRTPPackets bool
|
||||||
res chan pathSourceStaticSetReadyRes
|
res chan pathSourceStaticSetReadyRes
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ type pathStartPublisherRes struct {
|
|||||||
|
|
||||||
type pathStartPublisherReq struct {
|
type pathStartPublisherReq struct {
|
||||||
author publisher
|
author publisher
|
||||||
medias media.Medias
|
desc *description.Session
|
||||||
generateRTPPackets bool
|
generateRTPPackets bool
|
||||||
res chan pathStartPublisherRes
|
res chan pathStartPublisherRes
|
||||||
}
|
}
|
||||||
@@ -589,10 +589,10 @@ func (pa *path) onDemandStopPublisher() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) setReady(medias media.Medias, allocateEncoder bool) error {
|
func (pa *path) setReady(desc *description.Session, allocateEncoder bool) error {
|
||||||
stream, err := stream.New(
|
stream, err := stream.New(
|
||||||
pa.udpMaxPayloadSize,
|
pa.udpMaxPayloadSize,
|
||||||
medias,
|
desc,
|
||||||
allocateEncoder,
|
allocateEncoder,
|
||||||
pa.bytesReceived,
|
pa.bytesReceived,
|
||||||
pa.source,
|
pa.source,
|
||||||
@@ -654,7 +654,7 @@ func (pa *path) doPublisherRemove() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pa *path) handleSourceStaticSetReady(req pathSourceStaticSetReadyReq) {
|
func (pa *path) handleSourceStaticSetReady(req pathSourceStaticSetReadyReq) {
|
||||||
err := pa.setReady(req.medias, req.generateRTPPackets)
|
err := pa.setReady(req.desc, req.generateRTPPackets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.res <- pathSourceStaticSetReadyRes{err: err}
|
req.res <- pathSourceStaticSetReadyRes{err: err}
|
||||||
return
|
return
|
||||||
@@ -782,7 +782,7 @@ func (pa *path) handleStartPublisher(req pathStartPublisherReq) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := pa.setReady(req.medias, req.generateRTPPackets)
|
err := pa.setReady(req.desc, req.generateRTPPackets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.res <- pathStartPublisherRes{err: err}
|
req.res <- pathStartPublisherRes{err: err}
|
||||||
return
|
return
|
||||||
@@ -790,7 +790,7 @@ func (pa *path) handleStartPublisher(req pathStartPublisherReq) {
|
|||||||
|
|
||||||
req.author.Log(logger.Info, "is publishing to path '%s', %s",
|
req.author.Log(logger.Info, "is publishing to path '%s', %s",
|
||||||
pa.name,
|
pa.name,
|
||||||
sourceMediaInfo(req.medias))
|
sourceMediaInfo(req.desc.Medias))
|
||||||
|
|
||||||
if pa.conf.HasOnDemandPublisher() {
|
if pa.conf.HasOnDemandPublisher() {
|
||||||
pa.onDemandPublisherReadyTimer.Stop()
|
pa.onDemandPublisherReadyTimer.Stop()
|
||||||
@@ -920,7 +920,7 @@ func (pa *path) handleAPIPathsGet(req pathAPIPathsGetReq) {
|
|||||||
if pa.stream == nil {
|
if pa.stream == nil {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
return mediasDescription(pa.stream.Medias())
|
return mediasDescription(pa.stream.Desc().Medias)
|
||||||
}(),
|
}(),
|
||||||
BytesReceived: atomic.LoadUint64(pa.bytesReceived),
|
BytesReceived: atomic.LoadUint64(pa.bytesReceived),
|
||||||
Readers: func() []interface{} {
|
Readers: func() []interface{} {
|
||||||
|
@@ -10,12 +10,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/sdp"
|
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,9 +103,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -113,9 +113,9 @@ func main() {
|
|||||||
panic("environment not set")
|
panic("environment not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
medi := &media.Media{
|
medi := &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
@@ -127,7 +127,7 @@ func main() {
|
|||||||
|
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://localhost:" + os.Getenv("RTSP_PORT") + "/" + os.Getenv("MTX_PATH"),
|
"rtsp://localhost:" + os.Getenv("RTSP_PORT") + "/" + os.Getenv("MTX_PATH"),
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -264,7 +264,7 @@ func TestPathRunOnReady(t *testing.T) {
|
|||||||
|
|
||||||
err := c.StartRecording(
|
err := c.StartRecording(
|
||||||
"rtsp://localhost:8554/test",
|
"rtsp://localhost:8554/test",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ func TestPathMaxReaders(t *testing.T) {
|
|||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://localhost:8554/mystream",
|
"rtsp://localhost:8554/mystream",
|
||||||
media.Medias{testMediaH264})
|
&description.Session{Medias: []*description.Media{testMediaH264}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -298,10 +298,10 @@ func TestPathMaxReaders(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := reader.Describe(u)
|
desc, _, err := reader.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = reader.SetupAll(medias, baseURL)
|
err = reader.SetupAll(desc.BaseURL, desc.Medias)
|
||||||
if i != 1 {
|
if i != 1 {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -75,20 +75,20 @@ func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interfa
|
|||||||
|
|
||||||
// run implements sourceStaticImpl.
|
// run implements sourceStaticImpl.
|
||||||
func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan *conf.PathConf) error {
|
||||||
medi := &media.Media{
|
medi := &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
medias := media.Medias{medi}
|
medias := []*description.Media{medi}
|
||||||
var stream *stream.Stream
|
var stream *stream.Stream
|
||||||
|
|
||||||
onData := func(dts time.Duration, au [][]byte) {
|
onData := func(dts time.Duration, au [][]byte) {
|
||||||
if stream == nil {
|
if stream == nil {
|
||||||
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
@@ -101,9 +101,9 @@ func (s *rpiCameraSource) run(ctx context.Context, cnf *conf.PathConf, reloadCon
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: dts,
|
||||||
},
|
},
|
||||||
PTS: dts,
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,9 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
|
"github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
@@ -247,17 +247,13 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
|
|||||||
ringBuffer.Close()
|
ringBuffer.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media
|
||||||
videoFirstIDRFound := false
|
|
||||||
var videoStartDTS time.Duration
|
|
||||||
var w *rtmp.Writer
|
var w *rtmp.Writer
|
||||||
|
|
||||||
videoMedia, videoFormat := c.setupVideo(
|
videoMedia, videoFormat := c.setupVideo(
|
||||||
&w,
|
&w,
|
||||||
res.stream,
|
res.stream,
|
||||||
ringBuffer,
|
ringBuffer)
|
||||||
&videoFirstIDRFound,
|
|
||||||
&videoStartDTS)
|
|
||||||
if videoMedia != nil {
|
if videoMedia != nil {
|
||||||
medias = append(medias, videoMedia)
|
medias = append(medias, videoMedia)
|
||||||
}
|
}
|
||||||
@@ -265,10 +261,7 @@ func (c *rtmpConn) runRead(conn *rtmp.Conn, u *url.URL) error {
|
|||||||
audioMedia, audioFormat := c.setupAudio(
|
audioMedia, audioFormat := c.setupAudio(
|
||||||
&w,
|
&w,
|
||||||
res.stream,
|
res.stream,
|
||||||
ringBuffer,
|
ringBuffer)
|
||||||
videoFormat,
|
|
||||||
&videoFirstIDRFound,
|
|
||||||
&videoStartDTS)
|
|
||||||
if audioFormat != nil {
|
if audioFormat != nil {
|
||||||
medias = append(medias, audioMedia)
|
medias = append(medias, audioMedia)
|
||||||
}
|
}
|
||||||
@@ -327,15 +320,11 @@ func (c *rtmpConn) setupVideo(
|
|||||||
w **rtmp.Writer,
|
w **rtmp.Writer,
|
||||||
stream *stream.Stream,
|
stream *stream.Stream,
|
||||||
ringBuffer *ringbuffer.RingBuffer,
|
ringBuffer *ringbuffer.RingBuffer,
|
||||||
videoFirstIDRFound *bool,
|
) (*description.Media, format.Format) {
|
||||||
videoStartDTS *time.Duration,
|
var videoFormatH264 *format.H264
|
||||||
) (*media.Media, formats.Format) {
|
videoMedia := stream.Desc().FindFormat(&videoFormatH264)
|
||||||
var videoFormatH264 *formats.H264
|
|
||||||
videoMedia := stream.Medias().FindFormat(&videoFormatH264)
|
|
||||||
|
|
||||||
if videoFormatH264 != nil {
|
if videoFormatH264 != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
var videoDTSExtractor *h264.DTSExtractor
|
var videoDTSExtractor *h264.DTSExtractor
|
||||||
|
|
||||||
stream.AddReader(c, videoMedia, videoFormatH264, func(u unit.Unit) {
|
stream.AddReader(c, videoMedia, videoFormatH264, func(u unit.Unit) {
|
||||||
@@ -346,11 +335,7 @@ func (c *rtmpConn) setupVideo(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
|
|
||||||
idrPresent := false
|
idrPresent := false
|
||||||
nonIDRPresent := false
|
nonIDRPresent := false
|
||||||
@@ -369,12 +354,11 @@ func (c *rtmpConn) setupVideo(
|
|||||||
var dts time.Duration
|
var dts time.Duration
|
||||||
|
|
||||||
// wait until we receive an IDR
|
// wait until we receive an IDR
|
||||||
if !*videoFirstIDRFound {
|
if videoDTSExtractor == nil {
|
||||||
if !idrPresent {
|
if !idrPresent {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
*videoFirstIDRFound = true
|
|
||||||
videoDTSExtractor = h264.NewDTSExtractor()
|
videoDTSExtractor = h264.NewDTSExtractor()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -382,10 +366,6 @@ func (c *rtmpConn) setupVideo(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*videoStartDTS = dts
|
|
||||||
dts = 0
|
|
||||||
pts -= *videoStartDTS
|
|
||||||
} else {
|
} else {
|
||||||
if !idrPresent && !nonIDRPresent {
|
if !idrPresent && !nonIDRPresent {
|
||||||
return nil
|
return nil
|
||||||
@@ -396,9 +376,6 @@ func (c *rtmpConn) setupVideo(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dts -= *videoStartDTS
|
|
||||||
pts -= *videoStartDTS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
@@ -416,17 +393,11 @@ func (c *rtmpConn) setupAudio(
|
|||||||
w **rtmp.Writer,
|
w **rtmp.Writer,
|
||||||
stream *stream.Stream,
|
stream *stream.Stream,
|
||||||
ringBuffer *ringbuffer.RingBuffer,
|
ringBuffer *ringbuffer.RingBuffer,
|
||||||
videoFormat formats.Format,
|
) (*description.Media, format.Format) {
|
||||||
videoFirstIDRFound *bool,
|
var audioFormatMPEG4Generic *format.MPEG4AudioGeneric
|
||||||
videoStartDTS *time.Duration,
|
audioMedia := stream.Desc().FindFormat(&audioFormatMPEG4Generic)
|
||||||
) (*media.Media, formats.Format) {
|
|
||||||
var audioFormatMPEG4Generic *formats.MPEG4AudioGeneric
|
|
||||||
audioMedia := stream.Medias().FindFormat(&audioFormatMPEG4Generic)
|
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(c, audioMedia, audioFormatMPEG4Generic, func(u unit.Unit) {
|
stream.AddReader(c, audioMedia, audioFormatMPEG4Generic, func(u unit.Unit) {
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG4AudioGeneric)
|
tunit := u.(*unit.MPEG4AudioGeneric)
|
||||||
@@ -435,22 +406,7 @@ func (c *rtmpConn) setupAudio(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
|
|
||||||
if videoFormat != nil {
|
|
||||||
if !*videoFirstIDRFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts -= *videoStartDTS
|
|
||||||
if pts < 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, au := range tunit.AUs {
|
for i, au := range tunit.AUs {
|
||||||
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
@@ -471,16 +427,13 @@ func (c *rtmpConn) setupAudio(
|
|||||||
return audioMedia, audioFormatMPEG4Generic
|
return audioMedia, audioFormatMPEG4Generic
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioFormatMPEG4AudioLATM *formats.MPEG4AudioLATM
|
var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM
|
||||||
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG4AudioLATM)
|
audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4AudioLATM)
|
||||||
|
|
||||||
if audioMedia != nil &&
|
if audioMedia != nil &&
|
||||||
audioFormatMPEG4AudioLATM.Config != nil &&
|
audioFormatMPEG4AudioLATM.Config != nil &&
|
||||||
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 &&
|
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 &&
|
||||||
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 {
|
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(c, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) {
|
stream.AddReader(c, audioMedia, audioFormatMPEG4AudioLATM, func(u unit.Unit) {
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG4AudioLATM)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
@@ -489,22 +442,7 @@ func (c *rtmpConn) setupAudio(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
|
|
||||||
if videoFormat != nil {
|
|
||||||
if !*videoFirstIDRFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts -= *videoStartDTS
|
|
||||||
if pts < 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
return (*w).WriteMPEG4Audio(pts, tunit.AU)
|
return (*w).WriteMPEG4Audio(pts, tunit.AU)
|
||||||
@@ -514,33 +452,15 @@ func (c *rtmpConn) setupAudio(
|
|||||||
return audioMedia, audioFormatMPEG4AudioLATM
|
return audioMedia, audioFormatMPEG4AudioLATM
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioFormatMPEG1 *formats.MPEG1Audio
|
var audioFormatMPEG1 *format.MPEG1Audio
|
||||||
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG1)
|
audioMedia = stream.Desc().FindFormat(&audioFormatMPEG1)
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
startPTSFilled := false
|
|
||||||
var startPTS time.Duration
|
|
||||||
|
|
||||||
stream.AddReader(c, audioMedia, audioFormatMPEG1, func(u unit.Unit) {
|
stream.AddReader(c, audioMedia, audioFormatMPEG1, func(u unit.Unit) {
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG1Audio)
|
tunit := u.(*unit.MPEG1Audio)
|
||||||
|
|
||||||
if !startPTSFilled {
|
pts := tunit.PTS
|
||||||
startPTSFilled = true
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
}
|
|
||||||
pts := tunit.PTS - startPTS
|
|
||||||
|
|
||||||
if videoFormat != nil {
|
|
||||||
if !*videoFirstIDRFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts -= *videoStartDTS
|
|
||||||
if pts < 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, frame := range tunit.Frames {
|
for _, frame := range tunit.Frames {
|
||||||
var h mpeg1audio.FrameHeader
|
var h mpeg1audio.FrameHeader
|
||||||
@@ -611,58 +531,58 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
|
|||||||
}
|
}
|
||||||
videoFormat, audioFormat := r.Tracks()
|
videoFormat, audioFormat := r.Tracks()
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media
|
||||||
var stream *stream.Stream
|
var stream *stream.Stream
|
||||||
|
|
||||||
if videoFormat != nil {
|
if videoFormat != nil {
|
||||||
videoMedia := &media.Media{
|
videoMedia := &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{videoFormat},
|
Formats: []format.Format{videoFormat},
|
||||||
}
|
}
|
||||||
medias = append(medias, videoMedia)
|
medias = append(medias, videoMedia)
|
||||||
|
|
||||||
switch videoFormat.(type) {
|
switch videoFormat.(type) {
|
||||||
case *formats.AV1:
|
case *format.AV1:
|
||||||
r.OnDataAV1(func(pts time.Duration, tu [][]byte) {
|
r.OnDataAV1(func(pts time.Duration, tu [][]byte) {
|
||||||
stream.WriteUnit(videoMedia, videoFormat, &unit.AV1{
|
stream.WriteUnit(videoMedia, videoFormat, &unit.AV1{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
TU: tu,
|
||||||
TU: tu,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.VP9:
|
case *format.VP9:
|
||||||
r.OnDataVP9(func(pts time.Duration, frame []byte) {
|
r.OnDataVP9(func(pts time.Duration, frame []byte) {
|
||||||
stream.WriteUnit(videoMedia, videoFormat, &unit.VP9{
|
stream.WriteUnit(videoMedia, videoFormat, &unit.VP9{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
Frame: frame,
|
Frame: frame,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.H265:
|
case *format.H265:
|
||||||
r.OnDataH265(func(pts time.Duration, au [][]byte) {
|
r.OnDataH265(func(pts time.Duration, au [][]byte) {
|
||||||
stream.WriteUnit(videoMedia, videoFormat, &unit.H265{
|
stream.WriteUnit(videoMedia, videoFormat, &unit.H265{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.H264:
|
case *format.H264:
|
||||||
r.OnDataH264(func(pts time.Duration, au [][]byte) {
|
r.OnDataH264(func(pts time.Duration, au [][]byte) {
|
||||||
stream.WriteUnit(videoMedia, videoFormat, &unit.H264{
|
stream.WriteUnit(videoMedia, videoFormat, &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -672,31 +592,31 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if audioFormat != nil { //nolint:dupl
|
if audioFormat != nil { //nolint:dupl
|
||||||
audioMedia := &media.Media{
|
audioMedia := &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{audioFormat},
|
Formats: []format.Format{audioFormat},
|
||||||
}
|
}
|
||||||
medias = append(medias, audioMedia)
|
medias = append(medias, audioMedia)
|
||||||
|
|
||||||
switch audioFormat.(type) {
|
switch audioFormat.(type) {
|
||||||
case *formats.MPEG4AudioGeneric:
|
case *format.MPEG4AudioGeneric:
|
||||||
r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) {
|
r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) {
|
||||||
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{
|
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
AUs: [][]byte{au},
|
AUs: [][]byte{au},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
r.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
|
r.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
|
||||||
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{
|
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
Frames: [][]byte{frame},
|
Frames: [][]byte{frame},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -708,7 +628,7 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
|
|||||||
|
|
||||||
rres := res.path.startPublisher(pathStartPublisherReq{
|
rres := res.path.startPublisher(pathStartPublisherReq{
|
||||||
author: c,
|
author: c,
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if rres.err != nil {
|
if rres.err != nil {
|
||||||
|
@@ -8,7 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ func TestRTMPServer(t *testing.T) {
|
|||||||
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{ // 1920x1080 baseline
|
SPS: []byte{ // 1920x1080 baseline
|
||||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||||
@@ -139,7 +139,7 @@ func TestRTMPServer(t *testing.T) {
|
|||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTrack := &formats.MPEG4Audio{
|
audioTrack := &format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -238,7 +238,7 @@ func TestRTMPServerAuthFail(t *testing.T) {
|
|||||||
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{
|
SPS: []byte{
|
||||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||||
@@ -290,7 +290,7 @@ func TestRTMPServerAuthFail(t *testing.T) {
|
|||||||
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{
|
SPS: []byte{
|
||||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||||
@@ -343,7 +343,7 @@ func TestRTMPServerAuthFail(t *testing.T) {
|
|||||||
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
conn1, err := rtmp.NewClientConn(nconn1, u1, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{
|
SPS: []byte{
|
||||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||||
|
@@ -8,8 +8,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -113,25 +113,25 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error {
|
|||||||
|
|
||||||
videoFormat, audioFormat := mc.Tracks()
|
videoFormat, audioFormat := mc.Tracks()
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media
|
||||||
var stream *stream.Stream
|
var stream *stream.Stream
|
||||||
|
|
||||||
if videoFormat != nil {
|
if videoFormat != nil {
|
||||||
videoMedia := &media.Media{
|
videoMedia := &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{videoFormat},
|
Formats: []format.Format{videoFormat},
|
||||||
}
|
}
|
||||||
medias = append(medias, videoMedia)
|
medias = append(medias, videoMedia)
|
||||||
|
|
||||||
switch videoFormat.(type) {
|
switch videoFormat.(type) {
|
||||||
case *formats.H264:
|
case *format.H264:
|
||||||
mc.OnDataH264(func(pts time.Duration, au [][]byte) {
|
mc.OnDataH264(func(pts time.Duration, au [][]byte) {
|
||||||
stream.WriteUnit(videoMedia, videoFormat, &unit.H264{
|
stream.WriteUnit(videoMedia, videoFormat, &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -141,31 +141,31 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if audioFormat != nil { //nolint:dupl
|
if audioFormat != nil { //nolint:dupl
|
||||||
audioMedia := &media.Media{
|
audioMedia := &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{audioFormat},
|
Formats: []format.Format{audioFormat},
|
||||||
}
|
}
|
||||||
medias = append(medias, audioMedia)
|
medias = append(medias, audioMedia)
|
||||||
|
|
||||||
switch audioFormat.(type) {
|
switch audioFormat.(type) {
|
||||||
case *formats.MPEG4AudioGeneric:
|
case *format.MPEG4AudioGeneric:
|
||||||
mc.OnDataMPEG4Audio(func(pts time.Duration, au []byte) {
|
mc.OnDataMPEG4Audio(func(pts time.Duration, au []byte) {
|
||||||
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{
|
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
AUs: [][]byte{au},
|
AUs: [][]byte{au},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
mc.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
|
mc.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
|
||||||
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{
|
stream.WriteUnit(audioMedia, audioFormat, &unit.MPEG1Audio{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
PTS: pts,
|
|
||||||
Frames: [][]byte{frame},
|
Frames: [][]byte{frame},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -176,7 +176,7 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
|
@@ -6,9 +6,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -56,7 +56,7 @@ func TestRTMPSource(t *testing.T) {
|
|||||||
conn, _, _, err := rtmp.NewServerConn(nconn)
|
conn, _, _, err := rtmp.NewServerConn(nconn)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{ // 1920x1080 baseline
|
SPS: []byte{ // 1920x1080 baseline
|
||||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||||
@@ -67,7 +67,7 @@ func TestRTMPSource(t *testing.T) {
|
|||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTrack := &formats.MPEG4Audio{
|
audioTrack := &format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -116,13 +116,13 @@ func TestRTMPSource(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var forma *formats.H264
|
var forma *format.H264
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||||
|
@@ -5,10 +5,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/auth"
|
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
@@ -22,6 +22,8 @@ const (
|
|||||||
|
|
||||||
type rtspConnParent interface {
|
type rtspConnParent interface {
|
||||||
logger.Writer
|
logger.Writer
|
||||||
|
getISTLS() bool
|
||||||
|
getServer() *gortsplib.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
type rtspConn struct {
|
type rtspConn struct {
|
||||||
@@ -138,7 +140,7 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
|||||||
|
|
||||||
if c.authNonce == "" {
|
if c.authNonce == "" {
|
||||||
var err error
|
var err error
|
||||||
c.authNonce, err = auth.GenerateNonce2()
|
c.authNonce, err = auth.GenerateNonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusInternalServerError,
|
StatusCode: base.StatusInternalServerError,
|
||||||
@@ -186,9 +188,16 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
|||||||
}, nil, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stream *gortsplib.ServerStream
|
||||||
|
if !c.parent.getISTLS() {
|
||||||
|
stream = res.stream.RTSPStream(c.parent.getServer())
|
||||||
|
} else {
|
||||||
|
stream = res.stream.RTSPSStream(c.parent.getServer())
|
||||||
|
}
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
}, res.stream.RTSPStream(), nil
|
}, stream, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) {
|
func (c *rtspConn) handleAuthError(authErr error) (*base.Response, error) {
|
||||||
|
@@ -9,10 +9,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/liberrors"
|
"github.com/bluenviron/gortsplib/v4/pkg/liberrors"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
@@ -108,12 +108,11 @@ func newRTSPServer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.srv = &gortsplib.Server{
|
s.srv = &gortsplib.Server{
|
||||||
Handler: s,
|
Handler: s,
|
||||||
ReadTimeout: time.Duration(readTimeout),
|
ReadTimeout: time.Duration(readTimeout),
|
||||||
WriteTimeout: time.Duration(writeTimeout),
|
WriteTimeout: time.Duration(writeTimeout),
|
||||||
ReadBufferCount: writeQueueSize,
|
WriteQueueSize: writeQueueSize,
|
||||||
WriteBufferCount: writeQueueSize,
|
RTSPAddress: address,
|
||||||
RTSPAddress: address,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if useUDP {
|
if useUDP {
|
||||||
@@ -145,9 +144,9 @@ func newRTSPServer(
|
|||||||
|
|
||||||
if metrics != nil {
|
if metrics != nil {
|
||||||
if !isTLS {
|
if !isTLS {
|
||||||
metrics.rtspServerSet(s)
|
metrics.setRTSPServer(s)
|
||||||
} else {
|
} else {
|
||||||
metrics.rtspsServerSet(s)
|
metrics.setRTSPSServer(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +166,14 @@ func (s *rtspServer) Log(level logger.Level, format string, args ...interface{})
|
|||||||
s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
|
s.parent.Log(level, "[%s] "+format, append([]interface{}{label}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *rtspServer) getISTLS() bool {
|
||||||
|
return s.isTLS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rtspServer) getServer() *gortsplib.Server {
|
||||||
|
return s.srv
|
||||||
|
}
|
||||||
|
|
||||||
func (s *rtspServer) close() {
|
func (s *rtspServer) close() {
|
||||||
s.Log(logger.Info, "listener is closing")
|
s.Log(logger.Info, "listener is closing")
|
||||||
s.ctxCancel()
|
s.ctxCancel()
|
||||||
@@ -197,9 +204,9 @@ outer:
|
|||||||
|
|
||||||
if s.metrics != nil {
|
if s.metrics != nil {
|
||||||
if !s.isTLS {
|
if !s.isTLS {
|
||||||
s.metrics.rtspServerSet(nil)
|
s.metrics.setRTSPServer(nil)
|
||||||
} else {
|
} else {
|
||||||
s.metrics.rtspsServerSet(nil)
|
s.metrics.setRTSPSServer(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -29,7 +29,7 @@ func TestRTSPServerRunOnConnect(t *testing.T) {
|
|||||||
|
|
||||||
err = source.StartRecording(
|
err = source.StartRecording(
|
||||||
"rtsp://127.0.0.1:8554/mypath",
|
"rtsp://127.0.0.1:8554/mypath",
|
||||||
media.Medias{testMediaH264})
|
&description.Session{Medias: []*description.Media{testMediaH264}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ func TestRTSPServer(t *testing.T) {
|
|||||||
|
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value",
|
"rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -107,10 +107,10 @@ func TestRTSPServer(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := reader.Describe(u)
|
desc, _, err := reader.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = reader.SetupAll(medias, baseURL)
|
err = reader.SetupAll(desc.BaseURL, desc.Medias)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = reader.Play(nil)
|
_, err = reader.Play(nil)
|
||||||
@@ -137,7 +137,7 @@ func TestRTSPServerAuthHashed(t *testing.T) {
|
|||||||
|
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream",
|
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream",
|
||||||
media.Medias{medi})
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
|||||||
|
|
||||||
err := c.StartRecording(
|
err := c.StartRecording(
|
||||||
"rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream",
|
"rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream",
|
||||||
media.Medias{medi},
|
&description.Session{Medias: []*description.Media{medi}},
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||||
})
|
})
|
||||||
@@ -228,7 +228,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
_, _, _, err = c.Describe(u)
|
_, _, err = c.Describe(u)
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -249,7 +249,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
|||||||
|
|
||||||
err := c.StartRecording(
|
err := c.StartRecording(
|
||||||
"rtsp://localhost:8554/test/stream",
|
"rtsp://localhost:8554/test/stream",
|
||||||
media.Medias{medi},
|
&description.Session{Medias: []*description.Media{medi}},
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||||
})
|
})
|
||||||
@@ -270,7 +270,7 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
|||||||
|
|
||||||
err := c.StartRecording(
|
err := c.StartRecording(
|
||||||
"rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value",
|
"rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value",
|
||||||
media.Medias{medi},
|
&description.Session{Medias: []*description.Media{medi}},
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||||
})
|
})
|
||||||
@@ -298,13 +298,15 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
|||||||
|
|
||||||
s1 := gortsplib.Client{}
|
s1 := gortsplib.Client{}
|
||||||
|
|
||||||
err := s1.StartRecording("rtsp://localhost:8554/teststream", media.Medias{medi})
|
err := s1.StartRecording("rtsp://localhost:8554/teststream",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s1.Close()
|
defer s1.Close()
|
||||||
|
|
||||||
s2 := gortsplib.Client{}
|
s2 := gortsplib.Client{}
|
||||||
|
|
||||||
err = s2.StartRecording("rtsp://localhost:8554/teststream", media.Medias{medi})
|
err = s2.StartRecording("rtsp://localhost:8554/teststream",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
if ca == "enabled" {
|
if ca == "enabled" {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s2.Close()
|
defer s2.Close()
|
||||||
@@ -323,17 +325,17 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = c.SetupAll(medias, baseURL)
|
err = c.SetupAll(desc.BaseURL, desc.Medias)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
c.OnPacketRTP(desc.Medias[0], desc.Medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||||
if ca == "enabled" {
|
if ca == "enabled" {
|
||||||
require.Equal(t, []byte{0x05, 0x06, 0x07, 0x08}, pkt.Payload)
|
require.Equal(t, []byte{5, 15, 16, 17, 18}, pkt.Payload)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload)
|
require.Equal(t, []byte{5, 11, 12, 13, 14}, pkt.Payload)
|
||||||
}
|
}
|
||||||
close(frameRecv)
|
close(frameRecv)
|
||||||
})
|
})
|
||||||
@@ -354,7 +356,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
|||||||
SSRC: 978651231,
|
SSRC: 978651231,
|
||||||
Marker: true,
|
Marker: true,
|
||||||
},
|
},
|
||||||
Payload: []byte{0x05, 0x06, 0x07, 0x08},
|
Payload: []byte{5, 15, 16, 17, 18},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
@@ -367,7 +369,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
|||||||
SSRC: 978651231,
|
SSRC: 978651231,
|
||||||
Marker: true,
|
Marker: true,
|
||||||
},
|
},
|
||||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
Payload: []byte{5, 11, 12, 13, 14},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -402,7 +404,7 @@ func TestRTSPServerFallback(t *testing.T) {
|
|||||||
|
|
||||||
source := gortsplib.Client{}
|
source := gortsplib.Client{}
|
||||||
err := source.StartRecording("rtsp://localhost:8554/path2",
|
err := source.StartRecording("rtsp://localhost:8554/path2",
|
||||||
media.Medias{testMediaH264})
|
&description.Session{Medias: []*description.Media{testMediaH264}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -414,9 +416,9 @@ func TestRTSPServerFallback(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer dest.Close()
|
defer dest.Close()
|
||||||
|
|
||||||
medias, _, _, err := dest.Describe(u)
|
desc, _, err := dest.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(medias))
|
require.Equal(t, 1, len(desc.Medias))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,10 +7,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/auth"
|
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ type rtspSessionPathManager interface {
|
|||||||
|
|
||||||
type rtspSessionParent interface {
|
type rtspSessionParent interface {
|
||||||
logger.Writer
|
logger.Writer
|
||||||
|
getISTLS() bool
|
||||||
|
getServer() *gortsplib.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
type rtspSession struct {
|
type rtspSession struct {
|
||||||
@@ -124,7 +126,7 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
|
|||||||
|
|
||||||
if c.authNonce == "" {
|
if c.authNonce == "" {
|
||||||
var err error
|
var err error
|
||||||
c.authNonce, err = auth.GenerateNonce2()
|
c.authNonce, err = auth.GenerateNonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusInternalServerError,
|
StatusCode: base.StatusInternalServerError,
|
||||||
@@ -209,7 +211,7 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
|
|||||||
|
|
||||||
if c.authNonce == "" {
|
if c.authNonce == "" {
|
||||||
var err error
|
var err error
|
||||||
c.authNonce, err = auth.GenerateNonce2()
|
c.authNonce, err = auth.GenerateNonce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusInternalServerError,
|
StatusCode: base.StatusInternalServerError,
|
||||||
@@ -257,9 +259,16 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
|
|||||||
s.pathName = ctx.Path
|
s.pathName = ctx.Path
|
||||||
s.mutex.Unlock()
|
s.mutex.Unlock()
|
||||||
|
|
||||||
|
var stream *gortsplib.ServerStream
|
||||||
|
if !s.parent.getISTLS() {
|
||||||
|
stream = res.stream.RTSPStream(s.parent.getServer())
|
||||||
|
} else {
|
||||||
|
stream = res.stream.RTSPSStream(s.parent.getServer())
|
||||||
|
}
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
}, res.stream.RTSPStream(), nil
|
}, stream, nil
|
||||||
|
|
||||||
default: // record
|
default: // record
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
@@ -308,7 +317,7 @@ func (s *rtspSession) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Respons
|
|||||||
func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||||
res := s.path.startPublisher(pathStartPublisherReq{
|
res := s.path.startPublisher(pathStartPublisherReq{
|
||||||
author: s,
|
author: s,
|
||||||
medias: s.session.AnnouncedMedias(),
|
desc: s.session.AnnouncedDescription(),
|
||||||
generateRTPPackets: false,
|
generateRTPPackets: false,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
@@ -319,13 +328,18 @@ func (s *rtspSession) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Res
|
|||||||
|
|
||||||
s.stream = res.stream
|
s.stream = res.stream
|
||||||
|
|
||||||
for _, medi := range s.session.AnnouncedMedias() {
|
for _, medi := range s.session.AnnouncedDescription().Medias {
|
||||||
for _, forma := range medi.Formats {
|
for _, forma := range medi.Formats {
|
||||||
cmedi := medi
|
cmedi := medi
|
||||||
cforma := forma
|
cforma := forma
|
||||||
|
|
||||||
s.session.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
|
s.session.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
|
||||||
res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now())
|
pts, ok := s.session.PacketPTS(cmedi, pkt)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now(), pts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,12 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/headers"
|
"github.com/bluenviron/gortsplib/v4/pkg/headers"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
)
|
)
|
||||||
@@ -95,13 +95,12 @@ func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf cha
|
|||||||
s.Log(logger.Debug, "connecting")
|
s.Log(logger.Debug, "connecting")
|
||||||
|
|
||||||
c := &gortsplib.Client{
|
c := &gortsplib.Client{
|
||||||
Transport: cnf.SourceProtocol.Transport,
|
Transport: cnf.SourceProtocol.Transport,
|
||||||
TLSConfig: tlsConfigForFingerprint(cnf.SourceFingerprint),
|
TLSConfig: tlsConfigForFingerprint(cnf.SourceFingerprint),
|
||||||
ReadTimeout: time.Duration(s.readTimeout),
|
ReadTimeout: time.Duration(s.readTimeout),
|
||||||
WriteTimeout: time.Duration(s.writeTimeout),
|
WriteTimeout: time.Duration(s.writeTimeout),
|
||||||
ReadBufferCount: s.writeQueueSize,
|
WriteQueueSize: s.writeQueueSize,
|
||||||
WriteBufferCount: s.writeQueueSize,
|
AnyPortEnable: cnf.SourceAnyPortEnable,
|
||||||
AnyPortEnable: cnf.SourceAnyPortEnable,
|
|
||||||
OnRequest: func(req *base.Request) {
|
OnRequest: func(req *base.Request) {
|
||||||
s.Log(logger.Debug, "c->s %v", req)
|
s.Log(logger.Debug, "c->s %v", req)
|
||||||
},
|
},
|
||||||
@@ -133,18 +132,18 @@ func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf cha
|
|||||||
readErr := make(chan error)
|
readErr := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
readErr <- func() error {
|
readErr <- func() error {
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.SetupAll(medias, baseURL)
|
err = c.SetupAll(desc.BaseURL, desc.Medias)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: desc,
|
||||||
generateRTPPackets: false,
|
generateRTPPackets: false,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
@@ -153,13 +152,18 @@ func (s *rtspSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf cha
|
|||||||
|
|
||||||
defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{})
|
defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{})
|
||||||
|
|
||||||
for _, medi := range medias {
|
for _, medi := range desc.Medias {
|
||||||
for _, forma := range medi.Formats {
|
for _, forma := range medi.Formats {
|
||||||
cmedi := medi
|
cmedi := medi
|
||||||
cforma := forma
|
cforma := forma
|
||||||
|
|
||||||
c.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(cmedi, cforma, func(pkt *rtp.Packet) {
|
||||||
res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now())
|
pts, ok := c.PacketPTS(cmedi, pkt)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.stream.WriteRTPPacket(cmedi, cforma, pkt, time.Now(), pts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/auth"
|
"github.com/bluenviron/gortsplib/v4/pkg/auth"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/base"
|
"github.com/bluenviron/gortsplib/v4/pkg/base"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -43,9 +43,9 @@ func TestRTSPSource(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(source, func(t *testing.T) {
|
t.Run(source, func(t *testing.T) {
|
||||||
serverMedia := testMediaH264
|
serverMedia := testMediaH264
|
||||||
stream := gortsplib.NewServerStream(media.Medias{serverMedia})
|
var stream *gortsplib.ServerStream
|
||||||
|
|
||||||
nonce, err := auth.GenerateNonce2()
|
nonce, err := auth.GenerateNonce()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s := gortsplib.Server{
|
s := gortsplib.Server{
|
||||||
@@ -74,7 +74,7 @@ func TestRTSPSource(t *testing.T) {
|
|||||||
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
stream.WritePacketRTP(serverMedia, &rtp.Packet{
|
err := stream.WritePacketRTP(serverMedia, &rtp.Packet{
|
||||||
Header: rtp.Header{
|
Header: rtp.Header{
|
||||||
Version: 0x02,
|
Version: 0x02,
|
||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
@@ -83,8 +83,9 @@ func TestRTSPSource(t *testing.T) {
|
|||||||
SSRC: 978651231,
|
SSRC: 978651231,
|
||||||
Marker: true,
|
Marker: true,
|
||||||
},
|
},
|
||||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
Payload: []byte{5, 1, 2, 3, 4},
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return &base.Response{
|
return &base.Response{
|
||||||
@@ -120,6 +121,9 @@ func TestRTSPSource(t *testing.T) {
|
|||||||
defer s.Wait() //nolint:errcheck
|
defer s.Wait() //nolint:errcheck
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{serverMedia}})
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
if source == "udp" || source == "tcp" {
|
if source == "udp" || source == "tcp" {
|
||||||
p, ok := newInstance("paths:\n" +
|
p, ok := newInstance("paths:\n" +
|
||||||
" proxied:\n" +
|
" proxied:\n" +
|
||||||
@@ -149,17 +153,17 @@ func TestRTSPSource(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var forma *formats.H264
|
var forma *format.H264
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload)
|
require.Equal(t, []byte{5, 1, 2, 3, 4}, pkt.Payload)
|
||||||
close(received)
|
close(received)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -172,9 +176,9 @@ func TestRTSPSource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRTSPSourceNoPassword(t *testing.T) {
|
func TestRTSPSourceNoPassword(t *testing.T) {
|
||||||
stream := gortsplib.NewServerStream(media.Medias{testMediaH264})
|
var stream *gortsplib.ServerStream
|
||||||
|
|
||||||
nonce, err := auth.GenerateNonce2()
|
nonce, err := auth.GenerateNonce()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@@ -210,11 +214,15 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
|||||||
},
|
},
|
||||||
RTSPAddress: "127.0.0.1:8555",
|
RTSPAddress: "127.0.0.1:8555",
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s.Wait() //nolint:errcheck
|
defer s.Wait() //nolint:errcheck
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
p, ok := newInstance("rtmp: no\n" +
|
p, ok := newInstance("rtmp: no\n" +
|
||||||
"hls: no\n" +
|
"hls: no\n" +
|
||||||
"webrtc: no\n" +
|
"webrtc: no\n" +
|
||||||
@@ -231,7 +239,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
|||||||
func TestRTSPSourceRange(t *testing.T) {
|
func TestRTSPSourceRange(t *testing.T) {
|
||||||
for _, ca := range []string{"clock", "npt", "smpte"} {
|
for _, ca := range []string{"clock", "npt", "smpte"} {
|
||||||
t.Run(ca, func(t *testing.T) {
|
t.Run(ca, func(t *testing.T) {
|
||||||
stream := gortsplib.NewServerStream(media.Medias{testMediaH264})
|
var stream *gortsplib.ServerStream
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
s := gortsplib.Server{
|
s := gortsplib.Server{
|
||||||
@@ -266,11 +274,15 @@ func TestRTSPSourceRange(t *testing.T) {
|
|||||||
},
|
},
|
||||||
RTSPAddress: "127.0.0.1:8555",
|
RTSPAddress: "127.0.0.1:8555",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Start()
|
err := s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer s.Wait() //nolint:errcheck
|
defer s.Wait() //nolint:errcheck
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
|
stream = gortsplib.NewServerStream(&s, &description.Session{Medias: []*description.Media{testMediaH264}})
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
var addConf string
|
var addConf string
|
||||||
switch ca {
|
switch ca {
|
||||||
case "clock":
|
case "clock":
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
)
|
)
|
||||||
@@ -19,7 +19,7 @@ type source interface {
|
|||||||
apiSourceDescribe() pathAPISourceOrReader
|
apiSourceDescribe() pathAPISourceOrReader
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaDescription(media *media.Media) string {
|
func mediaDescription(media *description.Media) string {
|
||||||
ret := make([]string, len(media.Formats))
|
ret := make([]string, len(media.Formats))
|
||||||
for i, forma := range media.Formats {
|
for i, forma := range media.Formats {
|
||||||
ret[i] = forma.Codec()
|
ret[i] = forma.Codec()
|
||||||
@@ -27,7 +27,7 @@ func mediaDescription(media *media.Media) string {
|
|||||||
return strings.Join(ret, "/")
|
return strings.Join(ret, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediasDescription(medias media.Medias) []string {
|
func mediasDescription(medias []*description.Media) []string {
|
||||||
ret := make([]string, len(medias))
|
ret := make([]string, len(medias))
|
||||||
for i, media := range medias {
|
for i, media := range medias {
|
||||||
ret[i] = mediaDescription(media)
|
ret[i] = mediaDescription(media)
|
||||||
@@ -35,7 +35,7 @@ func mediasDescription(medias media.Medias) []string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceMediaInfo(medias media.Medias) string {
|
func sourceMediaInfo(medias []*description.Media) string {
|
||||||
return fmt.Sprintf("%d %s (%s)",
|
return fmt.Sprintf("%d %s (%s)",
|
||||||
len(medias),
|
len(medias),
|
||||||
func() string {
|
func() string {
|
||||||
|
@@ -224,7 +224,7 @@ func (s *sourceStatic) setReady(req pathSourceStaticSetReadyReq) pathSourceStati
|
|||||||
res := <-req.res
|
res := <-req.res
|
||||||
|
|
||||||
if res.err == nil {
|
if res.err == nil {
|
||||||
s.impl.Log(logger.Info, "ready: %s", sourceMediaInfo(req.medias))
|
s.impl.Log(logger.Info, "ready: %s", sourceMediaInfo(req.desc.Medias))
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@@ -10,9 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
|
"github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
@@ -235,7 +235,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media //nolint:prealloc
|
||||||
var stream *stream.Stream
|
var stream *stream.Stream
|
||||||
|
|
||||||
var td *mpegts.TimeDecoder
|
var td *mpegts.TimeDecoder
|
||||||
@@ -247,13 +247,13 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, track := range r.Tracks() { //nolint:dupl
|
for _, track := range r.Tracks() { //nolint:dupl
|
||||||
var medi *media.Media
|
var medi *description.Media
|
||||||
|
|
||||||
switch tcodec := track.Codec.(type) {
|
switch tcodec := track.Codec.(type) {
|
||||||
case *mpegts.CodecH264:
|
case *mpegts.CodecH264:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}},
|
}},
|
||||||
@@ -263,17 +263,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecH265:
|
case *mpegts.CodecH265:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H265{
|
Formats: []format.Format{&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
@@ -282,17 +282,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG4Audio:
|
case *mpegts.CodecMPEG4Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG4Audio{
|
Formats: []format.Format{&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SizeLength: 13,
|
SizeLength: 13,
|
||||||
IndexLength: 3,
|
IndexLength: 3,
|
||||||
@@ -305,17 +305,17 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
AUs: aus,
|
AUs: aus,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecOpus:
|
case *mpegts.CodecOpus:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.Opus{
|
Formats: []format.Format{&format.Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
IsStereo: (tcodec.ChannelCount == 2),
|
IsStereo: (tcodec.ChannelCount == 2),
|
||||||
}},
|
}},
|
||||||
@@ -325,25 +325,25 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
Packets: packets,
|
Packets: packets,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG1Audio:
|
case *mpegts.CodecMPEG1Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG1Audio{}},
|
Formats: []format.Format{&format.MPEG1Audio{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
Frames: frames,
|
Frames: frames,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@@ -362,7 +362,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|||||||
|
|
||||||
rres := path.startPublisher(pathStartPublisherReq{
|
rres := path.startPublisher(pathStartPublisherReq{
|
||||||
author: c,
|
author: c,
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if rres.err != nil {
|
if rres.err != nil {
|
||||||
@@ -424,14 +424,10 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
|
|
||||||
var w *mpegts.Writer
|
var w *mpegts.Writer
|
||||||
var tracks []*mpegts.Track
|
var tracks []*mpegts.Track
|
||||||
var medias media.Medias
|
var medias []*description.Media
|
||||||
bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize))
|
bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize))
|
||||||
|
|
||||||
leadingTrackChosen := false
|
addTrack := func(medi *description.Media, codec mpegts.Codec) *mpegts.Track {
|
||||||
leadingTrackInitialized := false
|
|
||||||
var leadingTrackStartDTS time.Duration
|
|
||||||
|
|
||||||
addTrack := func(medi *media.Media, codec mpegts.Codec) *mpegts.Track {
|
|
||||||
track := &mpegts.Track{
|
track := &mpegts.Track{
|
||||||
Codec: codec,
|
Codec: codec,
|
||||||
}
|
}
|
||||||
@@ -440,37 +436,22 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, medi := range res.stream.Medias() {
|
for _, medi := range res.stream.Desc().Medias {
|
||||||
for _, format := range medi.Formats {
|
for _, forma := range medi.Formats {
|
||||||
switch format := format.(type) {
|
switch forma := forma.(type) {
|
||||||
case *formats.H265: //nolint:dupl
|
case *format.H265: //nolint:dupl
|
||||||
track := addTrack(medi, &mpegts.CodecH265{})
|
track := addTrack(medi, &mpegts.CodecH265{})
|
||||||
|
|
||||||
var startPTS time.Duration
|
|
||||||
startPTSFilled := false
|
|
||||||
|
|
||||||
var isLeadingTrack bool
|
|
||||||
if !leadingTrackChosen {
|
|
||||||
isLeadingTrack = true
|
|
||||||
} else {
|
|
||||||
isLeadingTrack = false
|
|
||||||
}
|
|
||||||
|
|
||||||
randomAccessReceived := false
|
randomAccessReceived := false
|
||||||
dtsExtractor := h265.NewDTSExtractor()
|
dtsExtractor := h265.NewDTSExtractor()
|
||||||
|
|
||||||
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
|
res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.H265)
|
tunit := u.(*unit.H265)
|
||||||
if tunit.AU == nil {
|
if tunit.AU == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
startPTSFilled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
randomAccess := h265.IsRandomAccess(tunit.AU)
|
randomAccess := h265.IsRandomAccess(tunit.AU)
|
||||||
|
|
||||||
if !randomAccessReceived {
|
if !randomAccessReceived {
|
||||||
@@ -480,24 +461,12 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
randomAccessReceived = true
|
randomAccessReceived = true
|
||||||
}
|
}
|
||||||
|
|
||||||
pts := tunit.PTS - startPTS
|
pts := tunit.PTS
|
||||||
dts, err := dtsExtractor.Extract(tunit.AU, pts)
|
dts, err := dtsExtractor.Extract(tunit.AU, pts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !leadingTrackInitialized {
|
|
||||||
if isLeadingTrack {
|
|
||||||
leadingTrackStartDTS = dts
|
|
||||||
leadingTrackInitialized = true
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dts -= leadingTrackStartDTS
|
|
||||||
pts -= leadingTrackStartDTS
|
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), randomAccess, tunit.AU)
|
err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), randomAccess, tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -507,34 +476,19 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.H264: //nolint:dupl
|
case *format.H264: //nolint:dupl
|
||||||
track := addTrack(medi, &mpegts.CodecH264{})
|
track := addTrack(medi, &mpegts.CodecH264{})
|
||||||
|
|
||||||
var startPTS time.Duration
|
|
||||||
startPTSFilled := false
|
|
||||||
|
|
||||||
var isLeadingTrack bool
|
|
||||||
if !leadingTrackChosen {
|
|
||||||
isLeadingTrack = true
|
|
||||||
} else {
|
|
||||||
isLeadingTrack = false
|
|
||||||
}
|
|
||||||
|
|
||||||
firstIDRReceived := false
|
firstIDRReceived := false
|
||||||
dtsExtractor := h264.NewDTSExtractor()
|
dtsExtractor := h264.NewDTSExtractor()
|
||||||
|
|
||||||
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
|
res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.H264)
|
tunit := u.(*unit.H264)
|
||||||
if tunit.AU == nil {
|
if tunit.AU == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
startPTSFilled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
idrPresent := h264.IDRPresent(tunit.AU)
|
idrPresent := h264.IDRPresent(tunit.AU)
|
||||||
|
|
||||||
if !firstIDRReceived {
|
if !firstIDRReceived {
|
||||||
@@ -544,24 +498,12 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
firstIDRReceived = true
|
firstIDRReceived = true
|
||||||
}
|
}
|
||||||
|
|
||||||
pts := tunit.PTS - startPTS
|
pts := tunit.PTS
|
||||||
dts, err := dtsExtractor.Extract(tunit.AU, pts)
|
dts, err := dtsExtractor.Extract(tunit.AU, pts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !leadingTrackInitialized {
|
|
||||||
if isLeadingTrack {
|
|
||||||
leadingTrackStartDTS = dts
|
|
||||||
leadingTrackInitialized = true
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dts -= leadingTrackStartDTS
|
|
||||||
pts -= leadingTrackStartDTS
|
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), idrPresent, tunit.AU)
|
err = w.WriteH26x(track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), idrPresent, tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -571,33 +513,19 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.MPEG4AudioGeneric:
|
case *format.MPEG4AudioGeneric:
|
||||||
track := addTrack(medi, &mpegts.CodecMPEG4Audio{
|
track := addTrack(medi, &mpegts.CodecMPEG4Audio{
|
||||||
Config: *format.Config,
|
Config: *forma.Config,
|
||||||
})
|
})
|
||||||
|
|
||||||
var startPTS time.Duration
|
res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
|
||||||
startPTSFilled := false
|
|
||||||
|
|
||||||
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
|
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG4AudioGeneric)
|
tunit := u.(*unit.MPEG4AudioGeneric)
|
||||||
if tunit.AUs == nil {
|
if tunit.AUs == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
startPTSFilled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if leadingTrackChosen && !leadingTrackInitialized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS
|
pts := tunit.PTS
|
||||||
pts -= startPTS
|
|
||||||
pts -= leadingTrackStartDTS
|
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), tunit.AUs)
|
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), tunit.AUs)
|
||||||
@@ -608,36 +536,22 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.MPEG4AudioLATM:
|
case *format.MPEG4AudioLATM:
|
||||||
if format.Config != nil &&
|
if forma.Config != nil &&
|
||||||
len(format.Config.Programs) == 1 &&
|
len(forma.Config.Programs) == 1 &&
|
||||||
len(format.Config.Programs[0].Layers) == 1 {
|
len(forma.Config.Programs[0].Layers) == 1 {
|
||||||
track := addTrack(medi, &mpegts.CodecMPEG4Audio{
|
track := addTrack(medi, &mpegts.CodecMPEG4Audio{
|
||||||
Config: *format.Config.Programs[0].Layers[0].AudioSpecificConfig,
|
Config: *forma.Config.Programs[0].Layers[0].AudioSpecificConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
var startPTS time.Duration
|
res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
|
||||||
startPTSFilled := false
|
|
||||||
|
|
||||||
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
|
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG4AudioLATM)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
if tunit.AU == nil {
|
if tunit.AU == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
startPTSFilled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if leadingTrackChosen && !leadingTrackInitialized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS
|
pts := tunit.PTS
|
||||||
pts -= startPTS
|
|
||||||
pts -= leadingTrackStartDTS
|
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), [][]byte{tunit.AU})
|
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(pts), [][]byte{tunit.AU})
|
||||||
@@ -649,38 +563,24 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
case *formats.Opus:
|
case *format.Opus:
|
||||||
track := addTrack(medi, &mpegts.CodecOpus{
|
track := addTrack(medi, &mpegts.CodecOpus{
|
||||||
ChannelCount: func() int {
|
ChannelCount: func() int {
|
||||||
if format.IsStereo {
|
if forma.IsStereo {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
}(),
|
}(),
|
||||||
})
|
})
|
||||||
|
|
||||||
var startPTS time.Duration
|
res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
|
||||||
startPTSFilled := false
|
|
||||||
|
|
||||||
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
|
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.Opus)
|
tunit := u.(*unit.Opus)
|
||||||
if tunit.Packets == nil {
|
if tunit.Packets == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
startPTSFilled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if leadingTrackChosen && !leadingTrackInitialized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS
|
pts := tunit.PTS
|
||||||
pts -= startPTS
|
|
||||||
pts -= leadingTrackStartDTS
|
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
err = w.WriteOpus(track, durationGoToMPEGTS(pts), tunit.Packets)
|
err = w.WriteOpus(track, durationGoToMPEGTS(pts), tunit.Packets)
|
||||||
@@ -691,31 +591,17 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
case *formats.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
track := addTrack(medi, &mpegts.CodecMPEG1Audio{})
|
track := addTrack(medi, &mpegts.CodecMPEG1Audio{})
|
||||||
|
|
||||||
var startPTS time.Duration
|
res.stream.AddReader(c, medi, forma, func(u unit.Unit) {
|
||||||
startPTSFilled := false
|
|
||||||
|
|
||||||
res.stream.AddReader(c, medi, format, func(u unit.Unit) {
|
|
||||||
ringBuffer.Push(func() error {
|
ringBuffer.Push(func() error {
|
||||||
tunit := u.(*unit.MPEG1Audio)
|
tunit := u.(*unit.MPEG1Audio)
|
||||||
if tunit.Frames == nil {
|
if tunit.Frames == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !startPTSFilled {
|
|
||||||
startPTS = tunit.PTS
|
|
||||||
startPTSFilled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if leadingTrackChosen && !leadingTrackInitialized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pts := tunit.PTS
|
pts := tunit.PTS
|
||||||
pts -= startPTS
|
|
||||||
pts -= leadingTrackStartDTS
|
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
||||||
err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(pts), tunit.Frames)
|
err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(pts), tunit.Frames)
|
||||||
|
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"github.com/datarhei/gosrt"
|
"github.com/datarhei/gosrt"
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media //nolint:prealloc
|
||||||
var stream *stream.Stream
|
var stream *stream.Stream
|
||||||
|
|
||||||
var td *mpegts.TimeDecoder
|
var td *mpegts.TimeDecoder
|
||||||
@@ -103,13 +103,13 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, track := range r.Tracks() { //nolint:dupl
|
for _, track := range r.Tracks() { //nolint:dupl
|
||||||
var medi *media.Media
|
var medi *description.Media
|
||||||
|
|
||||||
switch tcodec := track.Codec.(type) {
|
switch tcodec := track.Codec.(type) {
|
||||||
case *mpegts.CodecH264:
|
case *mpegts.CodecH264:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}},
|
}},
|
||||||
@@ -119,17 +119,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecH265:
|
case *mpegts.CodecH265:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H265{
|
Formats: []format.Format{&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
@@ -138,17 +138,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG4Audio:
|
case *mpegts.CodecMPEG4Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG4Audio{
|
Formats: []format.Format{&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SizeLength: 13,
|
SizeLength: 13,
|
||||||
IndexLength: 3,
|
IndexLength: 3,
|
||||||
@@ -161,17 +161,17 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
AUs: aus,
|
AUs: aus,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecOpus:
|
case *mpegts.CodecOpus:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.Opus{
|
Formats: []format.Format{&format.Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
IsStereo: (tcodec.ChannelCount == 2),
|
IsStereo: (tcodec.ChannelCount == 2),
|
||||||
}},
|
}},
|
||||||
@@ -181,25 +181,25 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
Packets: packets,
|
Packets: packets,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG1Audio:
|
case *mpegts.CodecMPEG1Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG1Audio{}},
|
Formats: []format.Format{&format.MPEG1Audio{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
Frames: frames,
|
Frames: frames,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@@ -217,7 +217,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
|
@@ -4,9 +4,9 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"github.com/datarhei/gosrt"
|
"github.com/datarhei/gosrt"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
@@ -82,13 +82,13 @@ func TestSRTSource(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var forma *formats.H264
|
var forma *format.H264
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||||
|
@@ -6,8 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var medias media.Medias
|
var medias []*description.Media //nolint:prealloc
|
||||||
var stream *stream.Stream
|
var stream *stream.Stream
|
||||||
|
|
||||||
var td *mpegts.TimeDecoder
|
var td *mpegts.TimeDecoder
|
||||||
@@ -152,13 +152,13 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, track := range r.Tracks() { //nolint:dupl
|
for _, track := range r.Tracks() { //nolint:dupl
|
||||||
var medi *media.Media
|
var medi *description.Media
|
||||||
|
|
||||||
switch tcodec := track.Codec.(type) {
|
switch tcodec := track.Codec.(type) {
|
||||||
case *mpegts.CodecH264:
|
case *mpegts.CodecH264:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}},
|
}},
|
||||||
@@ -168,17 +168,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecH265:
|
case *mpegts.CodecH265:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H265{
|
Formats: []format.Format{&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
@@ -187,17 +187,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
AU: au,
|
||||||
AU: au,
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG4Audio:
|
case *mpegts.CodecMPEG4Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG4Audio{
|
Formats: []format.Format{&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SizeLength: 13,
|
SizeLength: 13,
|
||||||
IndexLength: 3,
|
IndexLength: 3,
|
||||||
@@ -210,17 +210,17 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
AUs: aus,
|
AUs: aus,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecOpus:
|
case *mpegts.CodecOpus:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.Opus{
|
Formats: []format.Format{&format.Opus{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
IsStereo: (tcodec.ChannelCount == 2),
|
IsStereo: (tcodec.ChannelCount == 2),
|
||||||
}},
|
}},
|
||||||
@@ -230,25 +230,25 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
Packets: packets,
|
Packets: packets,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG1Audio:
|
case *mpegts.CodecMPEG1Audio:
|
||||||
medi = &media.Media{
|
medi = &description.Media{
|
||||||
Type: media.TypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
Formats: []formats.Format{&formats.MPEG1Audio{}},
|
Formats: []format.Format{&format.MPEG1Audio{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
||||||
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
NTP: time.Now(),
|
NTP: time.Now(),
|
||||||
|
PTS: decodeTime(pts),
|
||||||
},
|
},
|
||||||
PTS: decodeTime(pts),
|
|
||||||
Frames: frames,
|
Frames: frames,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@@ -266,7 +266,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
res := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
|
@@ -6,9 +6,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -68,13 +68,13 @@ func TestUDPSource(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var forma *formats.H264
|
var forma *format.H264
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||||
|
@@ -5,9 +5,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
|
||||||
"github.com/pion/rtcp"
|
"github.com/pion/rtcp"
|
||||||
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/stream"
|
"github.com/bluenviron/mediamtx/internal/stream"
|
||||||
@@ -22,9 +24,9 @@ type webRTCIncomingTrack struct {
|
|||||||
receiver *webrtc.RTPReceiver
|
receiver *webrtc.RTPReceiver
|
||||||
writeRTCP func([]rtcp.Packet) error
|
writeRTCP func([]rtcp.Packet) error
|
||||||
|
|
||||||
mediaType media.Type
|
mediaType description.MediaType
|
||||||
format formats.Format
|
format format.Format
|
||||||
media *media.Media
|
media *description.Media
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebRTCIncomingTrack(
|
func newWebRTCIncomingTrack(
|
||||||
@@ -40,49 +42,49 @@ func newWebRTCIncomingTrack(
|
|||||||
|
|
||||||
switch strings.ToLower(track.Codec().MimeType) {
|
switch strings.ToLower(track.Codec().MimeType) {
|
||||||
case strings.ToLower(webrtc.MimeTypeAV1):
|
case strings.ToLower(webrtc.MimeTypeAV1):
|
||||||
t.mediaType = media.TypeVideo
|
t.mediaType = description.MediaTypeVideo
|
||||||
t.format = &formats.AV1{
|
t.format = &format.AV1{
|
||||||
PayloadTyp: uint8(track.PayloadType()),
|
PayloadTyp: uint8(track.PayloadType()),
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypeVP9):
|
case strings.ToLower(webrtc.MimeTypeVP9):
|
||||||
t.mediaType = media.TypeVideo
|
t.mediaType = description.MediaTypeVideo
|
||||||
t.format = &formats.VP9{
|
t.format = &format.VP9{
|
||||||
PayloadTyp: uint8(track.PayloadType()),
|
PayloadTyp: uint8(track.PayloadType()),
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypeVP8):
|
case strings.ToLower(webrtc.MimeTypeVP8):
|
||||||
t.mediaType = media.TypeVideo
|
t.mediaType = description.MediaTypeVideo
|
||||||
t.format = &formats.VP8{
|
t.format = &format.VP8{
|
||||||
PayloadTyp: uint8(track.PayloadType()),
|
PayloadTyp: uint8(track.PayloadType()),
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypeH264):
|
case strings.ToLower(webrtc.MimeTypeH264):
|
||||||
t.mediaType = media.TypeVideo
|
t.mediaType = description.MediaTypeVideo
|
||||||
t.format = &formats.H264{
|
t.format = &format.H264{
|
||||||
PayloadTyp: uint8(track.PayloadType()),
|
PayloadTyp: uint8(track.PayloadType()),
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypeOpus):
|
case strings.ToLower(webrtc.MimeTypeOpus):
|
||||||
t.mediaType = media.TypeAudio
|
t.mediaType = description.MediaTypeAudio
|
||||||
t.format = &formats.Opus{
|
t.format = &format.Opus{
|
||||||
PayloadTyp: uint8(track.PayloadType()),
|
PayloadTyp: uint8(track.PayloadType()),
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypeG722):
|
case strings.ToLower(webrtc.MimeTypeG722):
|
||||||
t.mediaType = media.TypeAudio
|
t.mediaType = description.MediaTypeAudio
|
||||||
t.format = &formats.G722{}
|
t.format = &format.G722{}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypePCMU):
|
case strings.ToLower(webrtc.MimeTypePCMU):
|
||||||
t.mediaType = media.TypeAudio
|
t.mediaType = description.MediaTypeAudio
|
||||||
t.format = &formats.G711{
|
t.format = &format.G711{
|
||||||
MULaw: true,
|
MULaw: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.ToLower(webrtc.MimeTypePCMA):
|
case strings.ToLower(webrtc.MimeTypePCMA):
|
||||||
t.mediaType = media.TypeAudio
|
t.mediaType = description.MediaTypeAudio
|
||||||
t.format = &formats.G711{
|
t.format = &format.G711{
|
||||||
MULaw: false,
|
MULaw: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,15 +92,29 @@ func newWebRTCIncomingTrack(
|
|||||||
return nil, fmt.Errorf("unsupported codec: %v", track.Codec())
|
return nil, fmt.Errorf("unsupported codec: %v", track.Codec())
|
||||||
}
|
}
|
||||||
|
|
||||||
t.media = &media.Media{
|
t.media = &description.Media{
|
||||||
Type: t.mediaType,
|
Type: t.mediaType,
|
||||||
Formats: []formats.Format{t.format},
|
Formats: []format.Format{t.format},
|
||||||
}
|
}
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *webRTCIncomingTrack) start(stream *stream.Stream) {
|
type webrtcTrackWrapper struct {
|
||||||
|
clockRate int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w webrtcTrackWrapper) ClockRate() int {
|
||||||
|
return w.clockRate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (webrtcTrackWrapper) PTSEqualsDTS(*rtp.Packet) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *webRTCIncomingTrack) start(stream *stream.Stream, timeDecoder *rtptime.GlobalDecoder) {
|
||||||
|
trackWrapper := &webrtcTrackWrapper{clockRate: int(t.track.Codec().ClockRate)}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
pkt, _, err := t.track.ReadRTP()
|
pkt, _, err := t.track.ReadRTP()
|
||||||
@@ -111,7 +127,12 @@ func (t *webRTCIncomingTrack) start(stream *stream.Stream) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.WriteRTPPacket(t.media, t.format, pkt, time.Now())
|
pts, ok := timeDecoder.Decode(trackWrapper, pkt)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.WriteRTPPacket(t.media, t.format, pkt, time.Now(), pts)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -126,7 +147,7 @@ func (t *webRTCIncomingTrack) start(stream *stream.Stream) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if t.mediaType == media.TypeVideo {
|
if t.mediaType == description.MediaTypeVideo {
|
||||||
go func() {
|
go func() {
|
||||||
keyframeTicker := time.NewTicker(keyFrameInterval)
|
keyframeTicker := time.NewTicker(keyFrameInterval)
|
||||||
defer keyframeTicker.Stop()
|
defer keyframeTicker.Stop()
|
||||||
|
@@ -7,10 +7,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -193,9 +193,9 @@ func TestWebRTCRead(t *testing.T) {
|
|||||||
a = newTestHTTPAuthenticator(t, "rtsp", "publish")
|
a = newTestHTTPAuthenticator(t, "rtsp", "publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
medi := &media.Media{
|
medi := &description.Media{
|
||||||
Type: media.TypeVideo,
|
Type: description.MediaTypeVideo,
|
||||||
Formats: []formats.Format{&formats.H264{
|
Formats: []format.Format{&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}},
|
}},
|
||||||
@@ -206,7 +206,8 @@ func TestWebRTCRead(t *testing.T) {
|
|||||||
Transport: &v,
|
Transport: &v,
|
||||||
}
|
}
|
||||||
err := source.StartRecording(
|
err := source.StartRecording(
|
||||||
"rtsp://testpublisher:testpass@localhost:8554/teststream?param=value", media.Medias{medi})
|
"rtsp://testpublisher:testpass@localhost:8554/teststream?param=value",
|
||||||
|
&description.Session{Medias: []*description.Media{medi}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
@@ -251,7 +252,7 @@ func TestWebRTCRead(t *testing.T) {
|
|||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{3},
|
Payload: []byte{5, 3},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ func TestWebRTCRead(t *testing.T) {
|
|||||||
SSRC: pkt.SSRC,
|
SSRC: pkt.SSRC,
|
||||||
CSRC: []uint32{},
|
CSRC: []uint32{},
|
||||||
},
|
},
|
||||||
Payload: []byte{3},
|
Payload: []byte{5, 3},
|
||||||
}, pkt)
|
}, pkt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -407,13 +408,13 @@ func TestWebRTCPublish(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var forma *formats.VP8
|
var forma *format.VP8
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
received := make(chan struct{})
|
received := make(chan struct{})
|
||||||
|
@@ -5,13 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
|
"github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/stream"
|
"github.com/bluenviron/mediamtx/internal/stream"
|
||||||
@@ -20,15 +20,15 @@ import (
|
|||||||
|
|
||||||
type webRTCOutgoingTrack struct {
|
type webRTCOutgoingTrack struct {
|
||||||
sender *webrtc.RTPSender
|
sender *webrtc.RTPSender
|
||||||
media *media.Media
|
media *description.Media
|
||||||
format formats.Format
|
format format.Format
|
||||||
track *webrtc.TrackLocalStaticRTP
|
track *webrtc.TrackLocalStaticRTP
|
||||||
cb func(unit.Unit) error
|
cb func(unit.Unit) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, error) {
|
func newWebRTCOutgoingTrackVideo(desc *description.Session) (*webRTCOutgoingTrack, error) {
|
||||||
var av1Format *formats.AV1
|
var av1Format *format.AV1
|
||||||
videoMedia := medias.FindFormat(&av1Format)
|
videoMedia := desc.FindFormat(&av1Format)
|
||||||
|
|
||||||
if videoMedia != nil {
|
if videoMedia != nil {
|
||||||
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
@@ -63,12 +63,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
packets, err := encoder.Encode(tunit.TU, tunit.PTS)
|
packets, err := encoder.Encode(tunit.TU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil //nolint:nilerr
|
return nil //nolint:nilerr
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkt := range packets {
|
for _, pkt := range packets {
|
||||||
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
||||||
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +78,10 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var vp9Format *formats.VP9
|
var vp9Format *format.VP9
|
||||||
videoMedia = medias.FindFormat(&vp9Format)
|
videoMedia = desc.FindFormat(&vp9Format)
|
||||||
|
|
||||||
if videoMedia != nil {
|
if videoMedia != nil { //nolint:dupl
|
||||||
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
webrtc.RTPCodecCapability{
|
webrtc.RTPCodecCapability{
|
||||||
MimeType: webrtc.MimeTypeVP9,
|
MimeType: webrtc.MimeTypeVP9,
|
||||||
@@ -113,12 +114,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
packets, err := encoder.Encode(tunit.Frame, tunit.PTS)
|
packets, err := encoder.Encode(tunit.Frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil //nolint:nilerr
|
return nil //nolint:nilerr
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkt := range packets {
|
for _, pkt := range packets {
|
||||||
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
||||||
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +129,10 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var vp8Format *formats.VP8
|
var vp8Format *format.VP8
|
||||||
videoMedia = medias.FindFormat(&vp8Format)
|
videoMedia = desc.FindFormat(&vp8Format)
|
||||||
|
|
||||||
if videoMedia != nil {
|
if videoMedia != nil { //nolint:dupl
|
||||||
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
webrtc.RTPCodecCapability{
|
webrtc.RTPCodecCapability{
|
||||||
MimeType: webrtc.MimeTypeVP8,
|
MimeType: webrtc.MimeTypeVP8,
|
||||||
@@ -163,12 +165,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
packets, err := encoder.Encode(tunit.Frame, tunit.PTS)
|
packets, err := encoder.Encode(tunit.Frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil //nolint:nilerr
|
return nil //nolint:nilerr
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkt := range packets {
|
for _, pkt := range packets {
|
||||||
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
||||||
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,8 +180,8 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var h264Format *formats.H264
|
var h264Format *format.H264
|
||||||
videoMedia = medias.FindFormat(&h264Format)
|
videoMedia = desc.FindFormat(&h264Format)
|
||||||
|
|
||||||
if videoMedia != nil {
|
if videoMedia != nil {
|
||||||
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
@@ -226,12 +229,13 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
lastPTS = tunit.PTS
|
lastPTS = tunit.PTS
|
||||||
}
|
}
|
||||||
|
|
||||||
packets, err := encoder.Encode(tunit.AU, tunit.PTS)
|
packets, err := encoder.Encode(tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil //nolint:nilerr
|
return nil //nolint:nilerr
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkt := range packets {
|
for _, pkt := range packets {
|
||||||
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
||||||
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,9 +247,9 @@ func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, error) {
|
func newWebRTCOutgoingTrackAudio(desc *description.Session) (*webRTCOutgoingTrack, error) {
|
||||||
var opusFormat *formats.Opus
|
var opusFormat *format.Opus
|
||||||
audioMedia := medias.FindFormat(&opusFormat)
|
audioMedia := desc.FindFormat(&opusFormat)
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
@@ -275,8 +279,8 @@ func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var g722Format *formats.G722
|
var g722Format *format.G722
|
||||||
audioMedia = medias.FindFormat(&g722Format)
|
audioMedia = desc.FindFormat(&g722Format)
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
||||||
@@ -305,8 +309,8 @@ func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var g711Format *formats.G711
|
var g711Format *format.G711
|
||||||
audioMedia = medias.FindFormat(&g711Format)
|
audioMedia = desc.FindFormat(&g711Format)
|
||||||
|
|
||||||
if audioMedia != nil {
|
if audioMedia != nil {
|
||||||
var mtyp string
|
var mtyp string
|
||||||
|
@@ -10,8 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
|
"github.com/bluenviron/gortsplib/v4/pkg/ringbuffer"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
@@ -25,16 +26,16 @@ type trackRecvPair struct {
|
|||||||
receiver *webrtc.RTPReceiver
|
receiver *webrtc.RTPReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
func webrtcMediasOfOutgoingTracks(tracks []*webRTCOutgoingTrack) media.Medias {
|
func webrtcMediasOfOutgoingTracks(tracks []*webRTCOutgoingTrack) []*description.Media {
|
||||||
ret := make(media.Medias, len(tracks))
|
ret := make([]*description.Media, len(tracks))
|
||||||
for i, track := range tracks {
|
for i, track := range tracks {
|
||||||
ret[i] = track.media
|
ret[i] = track.media
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func webrtcMediasOfIncomingTracks(tracks []*webRTCIncomingTrack) media.Medias {
|
func webrtcMediasOfIncomingTracks(tracks []*webRTCIncomingTrack) []*description.Media {
|
||||||
ret := make(media.Medias, len(tracks))
|
ret := make([]*description.Media, len(tracks))
|
||||||
for i, track := range tracks {
|
for i, track := range tracks {
|
||||||
ret[i] = track.media
|
ret[i] = track.media
|
||||||
}
|
}
|
||||||
@@ -72,10 +73,10 @@ outer:
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func webrtcGatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, error) {
|
func webrtcGatherOutgoingTracks(desc *description.Session) ([]*webRTCOutgoingTrack, error) {
|
||||||
var tracks []*webRTCOutgoingTrack
|
var tracks []*webRTCOutgoingTrack
|
||||||
|
|
||||||
videoTrack, err := newWebRTCOutgoingTrackVideo(medias)
|
videoTrack, err := newWebRTCOutgoingTrackVideo(desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,7 @@ func webrtcGatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, er
|
|||||||
tracks = append(tracks, videoTrack)
|
tracks = append(tracks, videoTrack)
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTrack, err := newWebRTCOutgoingTrackAudio(medias)
|
audioTrack, err := newWebRTCOutgoingTrackAudio(desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -395,15 +396,17 @@ func (s *webRTCSession) runPublish() (int, error) {
|
|||||||
|
|
||||||
rres := res.path.startPublisher(pathStartPublisherReq{
|
rres := res.path.startPublisher(pathStartPublisherReq{
|
||||||
author: s,
|
author: s,
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: false,
|
generateRTPPackets: false,
|
||||||
})
|
})
|
||||||
if rres.err != nil {
|
if rres.err != nil {
|
||||||
return 0, rres.err
|
return 0, rres.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timeDecoder := rtptime.NewGlobalDecoder()
|
||||||
|
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
track.start(rres.stream)
|
track.start(rres.stream, timeDecoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -447,7 +450,7 @@ func (s *webRTCSession) runRead() (int, error) {
|
|||||||
|
|
||||||
defer res.path.removeReader(pathRemoveReaderReq{author: s})
|
defer res.path.removeReader(pathRemoveReaderReq{author: s})
|
||||||
|
|
||||||
tracks, err := webrtcGatherOutgoingTracks(res.stream.Medias())
|
tracks, err := webrtcGatherOutgoingTracks(res.stream.Desc())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/rtptime"
|
||||||
"github.com/pion/sdp/v3"
|
"github.com/pion/sdp/v3"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ func (s *webRTCSource) run(ctx context.Context, cnf *conf.PathConf, _ chan *conf
|
|||||||
medias := webrtcMediasOfIncomingTracks(tracks)
|
medias := webrtcMediasOfIncomingTracks(tracks)
|
||||||
|
|
||||||
rres := s.parent.setReady(pathSourceStaticSetReadyReq{
|
rres := s.parent.setReady(pathSourceStaticSetReadyReq{
|
||||||
medias: medias,
|
desc: &description.Session{Medias: medias},
|
||||||
generateRTPPackets: true,
|
generateRTPPackets: true,
|
||||||
})
|
})
|
||||||
if rres.err != nil {
|
if rres.err != nil {
|
||||||
@@ -153,8 +155,10 @@ func (s *webRTCSource) run(ctx context.Context, cnf *conf.PathConf, _ chan *conf
|
|||||||
|
|
||||||
defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{})
|
defer s.parent.setNotReady(pathSourceStaticSetNotReadyReq{})
|
||||||
|
|
||||||
|
timeDecoder := rtptime.NewGlobalDecoder()
|
||||||
|
|
||||||
for _, track := range tracks {
|
for _, track := range tracks {
|
||||||
track.start(rres.stream)
|
track.start(rres.stream, timeDecoder)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@@ -7,9 +7,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/url"
|
"github.com/bluenviron/gortsplib/v4/pkg/url"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -105,7 +105,7 @@ func TestWebRTCSource(t *testing.T) {
|
|||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{1},
|
Payload: []byte{5, 1},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ func TestWebRTCSource(t *testing.T) {
|
|||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{2},
|
Payload: []byte{5, 2},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
@@ -152,19 +152,19 @@ func TestWebRTCSource(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
medias, baseURL, _, err := c.Describe(u)
|
desc, _, err := c.Describe(u)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var forma *formats.VP8
|
var forma *format.VP8
|
||||||
medi := medias.FindFormat(&forma)
|
medi := desc.FindFormat(&forma)
|
||||||
|
|
||||||
_, err = c.Setup(medi, baseURL, 0, 0)
|
_, err = c.Setup(desc.BaseURL, medi, 0, 0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
received := make(chan struct{})
|
received := make(chan struct{})
|
||||||
|
|
||||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||||
require.Equal(t, []byte{3}, pkt.Payload)
|
require.Equal(t, []byte{5, 3}, pkt.Payload)
|
||||||
close(received)
|
close(received)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ func TestWebRTCSource(t *testing.T) {
|
|||||||
Timestamp: 45343,
|
Timestamp: 45343,
|
||||||
SSRC: 563423,
|
SSRC: 563423,
|
||||||
},
|
},
|
||||||
Payload: []byte{3},
|
Payload: []byte{5, 3},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type formatProcessorAV1 struct {
|
type formatProcessorAV1 struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.AV1
|
format *format.AV1
|
||||||
log logger.Writer
|
log logger.Writer
|
||||||
|
|
||||||
encoder *rtpav1.Encoder
|
encoder *rtpav1.Encoder
|
||||||
@@ -23,7 +23,7 @@ type formatProcessorAV1 struct {
|
|||||||
|
|
||||||
func newAV1(
|
func newAV1(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.AV1,
|
forma *format.AV1,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
log logger.Writer,
|
log logger.Writer,
|
||||||
) (*formatProcessorAV1, error) {
|
) (*formatProcessorAV1, error) {
|
||||||
@@ -69,14 +69,13 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if hasNonRTSPReaders || t.decoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups
|
tu, err := t.decoder.Decode(pkt)
|
||||||
tu, pts, err := t.decoder.DecodeUntilMarker(pkt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtpav1.ErrNonStartingPacketAndNoPrevious || err == rtpav1.ErrMorePacketsNeeded {
|
if err == rtpav1.ErrNonStartingPacketAndNoPrevious || err == rtpav1.ErrMorePacketsNeeded {
|
||||||
return nil
|
return nil
|
||||||
@@ -85,7 +84,6 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunit.TU = tu
|
tunit.TU = tu
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -93,20 +91,22 @@ func (t *formatProcessorAV1) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
pkts, err := t.encoder.Encode(tunit.TU, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.TU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorAV1) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorAV1) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.AV1{
|
return &unit.AV1{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -17,7 +17,7 @@ type formatProcessorGeneric struct {
|
|||||||
|
|
||||||
func newGeneric(
|
func newGeneric(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma formats.Format,
|
forma format.Format,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorGeneric, error) {
|
) (*formatProcessorGeneric, error) {
|
||||||
@@ -47,11 +47,12 @@ func (t *formatProcessorGeneric) Process(u unit.Unit, _ bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.Generic{
|
return &unit.Generic{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ package formatprocessor
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGenericRemovePadding(t *testing.T) {
|
func TestGenericRemovePadding(t *testing.T) {
|
||||||
forma := &formats.Generic{
|
forma := &format.Generic{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
RTPMa: "private/90000",
|
RTPMa: "private/90000",
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ func rtpH264ExtractSPSPPS(pkt *rtp.Packet) ([]byte, []byte) {
|
|||||||
|
|
||||||
type formatProcessorH264 struct {
|
type formatProcessorH264 struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.H264
|
format *format.H264
|
||||||
log logger.Writer
|
log logger.Writer
|
||||||
|
|
||||||
encoder *rtph264.Encoder
|
encoder *rtph264.Encoder
|
||||||
@@ -81,7 +81,7 @@ type formatProcessorH264 struct {
|
|||||||
|
|
||||||
func newH264(
|
func newH264(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.H264,
|
forma *format.H264,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
log logger.Writer,
|
log logger.Writer,
|
||||||
) (*formatProcessorH264, error) {
|
) (*formatProcessorH264, error) {
|
||||||
@@ -92,7 +92,7 @@ func newH264(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if generateRTPPackets {
|
if generateRTPPackets {
|
||||||
err := t.createEncoder(nil, nil, nil)
|
err := t.createEncoder(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -102,14 +102,14 @@ func newH264(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorH264) createEncoder(
|
func (t *formatProcessorH264) createEncoder(
|
||||||
ssrc *uint32, initialSequenceNumber *uint16, initialTimestamp *uint32,
|
ssrc *uint32,
|
||||||
|
initialSequenceNumber *uint16,
|
||||||
) error {
|
) error {
|
||||||
t.encoder = &rtph264.Encoder{
|
t.encoder = &rtph264.Encoder{
|
||||||
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
||||||
PayloadType: t.format.PayloadTyp,
|
PayloadType: t.format.PayloadTyp,
|
||||||
SSRC: ssrc,
|
SSRC: ssrc,
|
||||||
InitialSequenceNumber: initialSequenceNumber,
|
InitialSequenceNumber: initialSequenceNumber,
|
||||||
InitialTimestamp: initialTimestamp,
|
|
||||||
PacketizationMode: t.format.PacketizationMode,
|
PacketizationMode: t.format.PacketizationMode,
|
||||||
}
|
}
|
||||||
return t.encoder.Init()
|
return t.encoder.Init()
|
||||||
@@ -240,8 +240,7 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if pkt.MarshalSize() > t.udpMaxPayloadSize {
|
if pkt.MarshalSize() > t.udpMaxPayloadSize {
|
||||||
v1 := pkt.SSRC
|
v1 := pkt.SSRC
|
||||||
v2 := pkt.SequenceNumber
|
v2 := pkt.SequenceNumber
|
||||||
v3 := pkt.Timestamp
|
err := t.createEncoder(&v1, &v2)
|
||||||
err := t.createEncoder(&v1, &v2, &v3)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -252,27 +251,24 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.encoder != nil {
|
au, err := t.decoder.Decode(pkt)
|
||||||
tunit.RTPPackets = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups
|
|
||||||
au, pts, err := t.decoder.DecodeUntilMarker(pkt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtph264.ErrNonStartingPacketAndNoPrevious || err == rtph264.ErrMorePacketsNeeded {
|
if err == rtph264.ErrNonStartingPacketAndNoPrevious || err == rtph264.ErrMorePacketsNeeded {
|
||||||
|
if t.encoder != nil {
|
||||||
|
tunit.RTPPackets = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tunit.AU = t.remuxAccessUnit(au)
|
tunit.AU = t.remuxAccessUnit(au)
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -286,10 +282,11 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
if len(tunit.AU) != 0 {
|
if len(tunit.AU) != 0 {
|
||||||
pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
} else {
|
} else {
|
||||||
tunit.RTPPackets = nil
|
tunit.RTPPackets = nil
|
||||||
@@ -298,11 +295,12 @@ func (t *formatProcessorH264) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorH264) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorH264) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.H264{
|
return &unit.H264{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestH264DynamicParams(t *testing.T) {
|
func TestH264DynamicParams(t *testing.T) {
|
||||||
forma := &formats.H264{
|
forma := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
@@ -21,10 +21,10 @@ func TestH264DynamicParams(t *testing.T) {
|
|||||||
p, err := New(1472, forma, false, nil)
|
p, err := New(1472, forma, false, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
enc, err := forma.CreateEncoder2()
|
enc, err := forma.CreateEncoder()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pkts, err := enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}}, 0)
|
pkts, err := enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data := &unit.H264{
|
data := &unit.H264{
|
||||||
@@ -39,7 +39,7 @@ func TestH264DynamicParams(t *testing.T) {
|
|||||||
{byte(h264.NALUTypeIDR)},
|
{byte(h264.NALUTypeIDR)},
|
||||||
}, data.AU)
|
}, data.AU)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}, 0) // SPS
|
pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}) // SPS
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.Process(&unit.H264{
|
err = p.Process(&unit.H264{
|
||||||
@@ -49,7 +49,7 @@ func TestH264DynamicParams(t *testing.T) {
|
|||||||
}, false)
|
}, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{8, 1}}, 0) // PPS
|
pkts, err = enc.Encode([][]byte{{8, 1}}) // PPS
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.Process(&unit.H264{
|
err = p.Process(&unit.H264{
|
||||||
@@ -62,7 +62,7 @@ func TestH264DynamicParams(t *testing.T) {
|
|||||||
require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS)
|
require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS)
|
||||||
require.Equal(t, []byte{8, 1}, forma.PPS)
|
require.Equal(t, []byte{8, 1}, forma.PPS)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}}, 0)
|
pkts, err = enc.Encode([][]byte{{byte(h264.NALUTypeIDR)}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data = &unit.H264{
|
data = &unit.H264{
|
||||||
@@ -81,7 +81,7 @@ func TestH264DynamicParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestH264OversizedPackets(t *testing.T) {
|
func TestH264OversizedPackets(t *testing.T) {
|
||||||
forma := &formats.H264{
|
forma := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||||
@@ -186,7 +186,7 @@ func TestH264OversizedPackets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestH264EmptyPacket(t *testing.T) {
|
func TestH264EmptyPacket(t *testing.T) {
|
||||||
forma := &formats.H264{
|
forma := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph265"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph265"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ func rtpH265ExtractVPSSPSPPS(pkt *rtp.Packet) ([]byte, []byte, []byte) {
|
|||||||
|
|
||||||
type formatProcessorH265 struct {
|
type formatProcessorH265 struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.H265
|
format *format.H265
|
||||||
log logger.Writer
|
log logger.Writer
|
||||||
|
|
||||||
encoder *rtph265.Encoder
|
encoder *rtph265.Encoder
|
||||||
@@ -88,7 +88,7 @@ type formatProcessorH265 struct {
|
|||||||
|
|
||||||
func newH265(
|
func newH265(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.H265,
|
forma *format.H265,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
log logger.Writer,
|
log logger.Writer,
|
||||||
) (*formatProcessorH265, error) {
|
) (*formatProcessorH265, error) {
|
||||||
@@ -99,7 +99,7 @@ func newH265(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if generateRTPPackets {
|
if generateRTPPackets {
|
||||||
err := t.createEncoder(nil, nil, nil)
|
err := t.createEncoder(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -109,14 +109,14 @@ func newH265(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorH265) createEncoder(
|
func (t *formatProcessorH265) createEncoder(
|
||||||
ssrc *uint32, initialSequenceNumber *uint16, initialTimestamp *uint32,
|
ssrc *uint32,
|
||||||
|
initialSequenceNumber *uint16,
|
||||||
) error {
|
) error {
|
||||||
t.encoder = &rtph265.Encoder{
|
t.encoder = &rtph265.Encoder{
|
||||||
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
||||||
PayloadType: t.format.PayloadTyp,
|
PayloadType: t.format.PayloadTyp,
|
||||||
SSRC: ssrc,
|
SSRC: ssrc,
|
||||||
InitialSequenceNumber: initialSequenceNumber,
|
InitialSequenceNumber: initialSequenceNumber,
|
||||||
InitialTimestamp: initialTimestamp,
|
|
||||||
MaxDONDiff: t.format.MaxDONDiff,
|
MaxDONDiff: t.format.MaxDONDiff,
|
||||||
}
|
}
|
||||||
return t.encoder.Init()
|
return t.encoder.Init()
|
||||||
@@ -262,8 +262,7 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if pkt.MarshalSize() > t.udpMaxPayloadSize {
|
if pkt.MarshalSize() > t.udpMaxPayloadSize {
|
||||||
v1 := pkt.SSRC
|
v1 := pkt.SSRC
|
||||||
v2 := pkt.SequenceNumber
|
v2 := pkt.SequenceNumber
|
||||||
v3 := pkt.Timestamp
|
err := t.createEncoder(&v1, &v2)
|
||||||
err := t.createEncoder(&v1, &v2, &v3)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -274,27 +273,24 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.encoder != nil {
|
au, err := t.decoder.Decode(pkt)
|
||||||
tunit.RTPPackets = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeUntilMarker() is necessary, otherwise Encode() generates partial groups
|
|
||||||
au, pts, err := t.decoder.DecodeUntilMarker(pkt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtph265.ErrNonStartingPacketAndNoPrevious || err == rtph265.ErrMorePacketsNeeded {
|
if err == rtph265.ErrNonStartingPacketAndNoPrevious || err == rtph265.ErrMorePacketsNeeded {
|
||||||
|
if t.encoder != nil {
|
||||||
|
tunit.RTPPackets = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tunit.AU = t.remuxAccessUnit(au)
|
tunit.AU = t.remuxAccessUnit(au)
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -308,10 +304,11 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
if len(tunit.AU) != 0 {
|
if len(tunit.AU) != 0 {
|
||||||
pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
} else {
|
} else {
|
||||||
tunit.RTPPackets = nil
|
tunit.RTPPackets = nil
|
||||||
@@ -320,11 +317,12 @@ func (t *formatProcessorH265) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorH265) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorH265) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.H265{
|
return &unit.H265{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -13,17 +13,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestH265DynamicParams(t *testing.T) {
|
func TestH265DynamicParams(t *testing.T) {
|
||||||
forma := &formats.H265{
|
forma := &format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := New(1472, forma, false, nil)
|
p, err := New(1472, forma, false, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
enc, err := forma.CreateEncoder2()
|
enc, err := forma.CreateEncoder()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pkts, err := enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}}, 0)
|
pkts, err := enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data := &unit.H265{
|
data := &unit.H265{
|
||||||
@@ -38,7 +38,7 @@ func TestH265DynamicParams(t *testing.T) {
|
|||||||
{byte(h265.NALUType_CRA_NUT) << 1, 0},
|
{byte(h265.NALUType_CRA_NUT) << 1, 0},
|
||||||
}, data.AU)
|
}, data.AU)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_VPS_NUT) << 1, 1, 2, 3}}, 0)
|
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_VPS_NUT) << 1, 1, 2, 3}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.Process(&unit.H265{
|
err = p.Process(&unit.H265{
|
||||||
@@ -48,7 +48,7 @@ func TestH265DynamicParams(t *testing.T) {
|
|||||||
}, false)
|
}, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}}, 0)
|
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.Process(&unit.H265{
|
err = p.Process(&unit.H265{
|
||||||
@@ -58,7 +58,7 @@ func TestH265DynamicParams(t *testing.T) {
|
|||||||
}, false)
|
}, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}}, 0)
|
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.Process(&unit.H265{
|
err = p.Process(&unit.H265{
|
||||||
@@ -72,7 +72,7 @@ func TestH265DynamicParams(t *testing.T) {
|
|||||||
require.Equal(t, []byte{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS)
|
require.Equal(t, []byte{byte(h265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS)
|
||||||
require.Equal(t, []byte{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS)
|
require.Equal(t, []byte{byte(h265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS)
|
||||||
|
|
||||||
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}}, 0)
|
pkts, err = enc.Encode([][]byte{{byte(h265.NALUType_CRA_NUT) << 1, 0}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data = &unit.H265{
|
data = &unit.H265{
|
||||||
@@ -92,7 +92,7 @@ func TestH265DynamicParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestH265OversizedPackets(t *testing.T) {
|
func TestH265OversizedPackets(t *testing.T) {
|
||||||
forma := &formats.H265{
|
forma := &format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: []byte{byte(h265.NALUType_VPS_NUT) << 1, 10, 11, 12},
|
VPS: []byte{byte(h265.NALUType_VPS_NUT) << 1, 10, 11, 12},
|
||||||
SPS: []byte{byte(h265.NALUType_SPS_NUT) << 1, 13, 14, 15},
|
SPS: []byte{byte(h265.NALUType_SPS_NUT) << 1, 13, 14, 15},
|
||||||
@@ -185,7 +185,7 @@ func TestH265OversizedPackets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestH265EmptyPacket(t *testing.T) {
|
func TestH265EmptyPacket(t *testing.T) {
|
||||||
forma := &formats.H265{
|
forma := &format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg1audio"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
|
|
||||||
type formatProcessorMPEG1Audio struct {
|
type formatProcessorMPEG1Audio struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.MPEG1Audio
|
format *format.MPEG1Audio
|
||||||
encoder *rtpmpeg1audio.Encoder
|
encoder *rtpmpeg1audio.Encoder
|
||||||
decoder *rtpmpeg1audio.Decoder
|
decoder *rtpmpeg1audio.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMPEG1Audio(
|
func newMPEG1Audio(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.MPEG1Audio,
|
forma *format.MPEG1Audio,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorMPEG1Audio, error) {
|
) (*formatProcessorMPEG1Audio, error) {
|
||||||
@@ -66,13 +66,13 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool)
|
|||||||
if hasNonRTSPReaders || t.decoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frames, pts, err := t.decoder.Decode(pkt)
|
frames, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg1audio.ErrMorePacketsNeeded {
|
if err == rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg1audio.ErrMorePacketsNeeded {
|
||||||
return nil
|
return nil
|
||||||
@@ -81,7 +81,6 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunit.Frames = frames
|
tunit.Frames = frames
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -89,20 +88,22 @@ func (t *formatProcessorMPEG1Audio) Process(u unit.Unit, hasNonRTSPReaders bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
pkts, err := t.encoder.Encode(tunit.Frames, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.Frames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorMPEG1Audio) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorMPEG1Audio) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.MPEG1Audio{
|
return &unit.MPEG1Audio{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audio"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
|
|
||||||
type formatProcessorMPEG4AudioGeneric struct {
|
type formatProcessorMPEG4AudioGeneric struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.MPEG4Audio
|
format *format.MPEG4Audio
|
||||||
encoder *rtpmpeg4audio.Encoder
|
encoder *rtpmpeg4audio.Encoder
|
||||||
decoder *rtpmpeg4audio.Decoder
|
decoder *rtpmpeg4audio.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMPEG4AudioGeneric(
|
func newMPEG4AudioGeneric(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.MPEG4Audio,
|
forma *format.MPEG4Audio,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorMPEG4AudioGeneric, error) {
|
) (*formatProcessorMPEG4AudioGeneric, error) {
|
||||||
@@ -44,7 +44,6 @@ func (t *formatProcessorMPEG4AudioGeneric) createEncoder() error {
|
|||||||
t.encoder = &rtpmpeg4audio.Encoder{
|
t.encoder = &rtpmpeg4audio.Encoder{
|
||||||
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
||||||
PayloadType: t.format.PayloadTyp,
|
PayloadType: t.format.PayloadTyp,
|
||||||
SampleRate: t.format.Config.SampleRate,
|
|
||||||
SizeLength: t.format.SizeLength,
|
SizeLength: t.format.SizeLength,
|
||||||
IndexLength: t.format.IndexLength,
|
IndexLength: t.format.IndexLength,
|
||||||
IndexDeltaLength: t.format.IndexDeltaLength,
|
IndexDeltaLength: t.format.IndexDeltaLength,
|
||||||
@@ -71,13 +70,13 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader
|
|||||||
if hasNonRTSPReaders || t.decoder != nil || true {
|
if hasNonRTSPReaders || t.decoder != nil || true {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aus, pts, err := t.decoder.Decode(pkt)
|
aus, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtpmpeg4audio.ErrMorePacketsNeeded {
|
if err == rtpmpeg4audio.ErrMorePacketsNeeded {
|
||||||
return nil
|
return nil
|
||||||
@@ -86,7 +85,6 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunit.AUs = aus
|
tunit.AUs = aus
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -94,20 +92,22 @@ func (t *formatProcessorMPEG4AudioGeneric) Process(u unit.Unit, hasNonRTSPReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
pkts, err := t.encoder.Encode(tunit.AUs, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.AUs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorMPEG4AudioGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorMPEG4AudioGeneric) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.MPEG4AudioGeneric{
|
return &unit.MPEG4AudioGeneric{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiolatm"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audiolatm"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
|
|
||||||
type formatProcessorMPEG4AudioLATM struct {
|
type formatProcessorMPEG4AudioLATM struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.MPEG4AudioLATM
|
format *format.MPEG4AudioLATM
|
||||||
encoder *rtpmpeg4audiolatm.Encoder
|
encoder *rtpmpeg4audiolatm.Encoder
|
||||||
decoder *rtpmpeg4audiolatm.Decoder
|
decoder *rtpmpeg4audiolatm.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMPEG4AudioLATM(
|
func newMPEG4AudioLATM(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.MPEG4AudioLATM,
|
forma *format.MPEG4AudioLATM,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorMPEG4AudioLATM, error) {
|
) (*formatProcessorMPEG4AudioLATM, error) {
|
||||||
@@ -43,7 +43,6 @@ func newMPEG4AudioLATM(
|
|||||||
func (t *formatProcessorMPEG4AudioLATM) createEncoder() error {
|
func (t *formatProcessorMPEG4AudioLATM) createEncoder() error {
|
||||||
t.encoder = &rtpmpeg4audiolatm.Encoder{
|
t.encoder = &rtpmpeg4audiolatm.Encoder{
|
||||||
PayloadType: t.format.PayloadTyp,
|
PayloadType: t.format.PayloadTyp,
|
||||||
Config: t.format.Config,
|
|
||||||
}
|
}
|
||||||
return t.encoder.Init()
|
return t.encoder.Init()
|
||||||
}
|
}
|
||||||
@@ -67,13 +66,13 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b
|
|||||||
if hasNonRTSPReaders || t.decoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
au, pts, err := t.decoder.Decode(pkt)
|
au, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtpmpeg4audiolatm.ErrMorePacketsNeeded {
|
if err == rtpmpeg4audiolatm.ErrMorePacketsNeeded {
|
||||||
return nil
|
return nil
|
||||||
@@ -82,7 +81,6 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunit.AU = au
|
tunit.AU = au
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -90,20 +88,22 @@ func (t *formatProcessorMPEG4AudioLATM) Process(u unit.Unit, hasNonRTSPReaders b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
pkts, err := t.encoder.Encode(tunit.AU, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.AU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorMPEG4AudioLATM) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorMPEG4AudioLATM) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.MPEG4AudioLATM{
|
return &unit.MPEG4AudioLATM{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpsimpleaudio"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/opus"
|
"github.com/bluenviron/mediacommon/pkg/codecs/opus"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -15,14 +15,14 @@ import (
|
|||||||
|
|
||||||
type formatProcessorOpus struct {
|
type formatProcessorOpus struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.Opus
|
format *format.Opus
|
||||||
encoder *rtpsimpleaudio.Encoder
|
encoder *rtpsimpleaudio.Encoder
|
||||||
decoder *rtpsimpleaudio.Decoder
|
decoder *rtpsimpleaudio.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpus(
|
func newOpus(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.Opus,
|
forma *format.Opus,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorOpus, error) {
|
) (*formatProcessorOpus, error) {
|
||||||
@@ -45,7 +45,6 @@ func (t *formatProcessorOpus) createEncoder() error {
|
|||||||
t.encoder = &rtpsimpleaudio.Encoder{
|
t.encoder = &rtpsimpleaudio.Encoder{
|
||||||
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
PayloadMaxSize: t.udpMaxPayloadSize - 12,
|
||||||
PayloadType: t.format.PayloadTyp,
|
PayloadType: t.format.PayloadTyp,
|
||||||
SampleRate: 48000,
|
|
||||||
}
|
}
|
||||||
return t.encoder.Init()
|
return t.encoder.Init()
|
||||||
}
|
}
|
||||||
@@ -69,19 +68,18 @@ func (t *formatProcessorOpus) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if hasNonRTSPReaders || t.decoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
packet, pts, err := t.decoder.Decode(pkt)
|
packet, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tunit.Packets = [][]byte{packet}
|
tunit.Packets = [][]byte{packet}
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -92,24 +90,26 @@ func (t *formatProcessorOpus) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
var rtpPackets []*rtp.Packet //nolint:prealloc
|
var rtpPackets []*rtp.Packet //nolint:prealloc
|
||||||
pts := tunit.PTS
|
pts := tunit.PTS
|
||||||
for _, packet := range tunit.Packets {
|
for _, packet := range tunit.Packets {
|
||||||
pkt, err := t.encoder.Encode(packet, pts)
|
pkt, err := t.encoder.Encode(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp([]*rtp.Packet{pkt}, tunit.RTPPackets, t.format.ClockRate(), pts)
|
||||||
rtpPackets = append(rtpPackets, pkt)
|
rtpPackets = append(rtpPackets, pkt)
|
||||||
pts += opus.PacketDuration(packet)
|
pts += opus.PacketDuration(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
tunit.RTPPackets = rtpPackets
|
tunit.RTPPackets = rtpPackets
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorOpus) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorOpus) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.Opus{
|
return &unit.Opus{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,55 +4,75 @@ package formatprocessor
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
"github.com/bluenviron/mediamtx/internal/unit"
|
"github.com/bluenviron/mediamtx/internal/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// avoid an int64 overflow and preserve resolution by splitting division into two parts:
|
||||||
|
// first add the integer part, then the decimal part.
|
||||||
|
func multiplyAndDivide(v, m, d time.Duration) time.Duration {
|
||||||
|
secs := v / d
|
||||||
|
dec := v % d
|
||||||
|
return (secs*m + dec*m/d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTimestamp(newPackets []*rtp.Packet, oldPackets []*rtp.Packet, clockRate int, pts time.Duration) {
|
||||||
|
if oldPackets != nil { // get timestamp from old packets
|
||||||
|
for _, pkt := range newPackets {
|
||||||
|
pkt.Timestamp = oldPackets[0].Timestamp
|
||||||
|
}
|
||||||
|
} else { // get timestamp from PTS
|
||||||
|
for _, pkt := range newPackets {
|
||||||
|
pkt.Timestamp = uint32(multiplyAndDivide(pts, time.Duration(clockRate), time.Second))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Processor cleans and normalizes streams.
|
// Processor cleans and normalizes streams.
|
||||||
type Processor interface {
|
type Processor interface {
|
||||||
// cleans and normalizes a data unit.
|
// cleans and normalizes a data unit.
|
||||||
Process(unit.Unit, bool) error
|
Process(unit.Unit, bool) error
|
||||||
|
|
||||||
// wraps a RTP packet into a Unit.
|
// wraps a RTP packet into a Unit.
|
||||||
UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit
|
UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// New allocates a Processor.
|
// New allocates a Processor.
|
||||||
func New(
|
func New(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma formats.Format,
|
forma format.Format,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
log logger.Writer,
|
log logger.Writer,
|
||||||
) (Processor, error) {
|
) (Processor, error) {
|
||||||
switch forma := forma.(type) {
|
switch forma := forma.(type) {
|
||||||
case *formats.H264:
|
case *format.H264:
|
||||||
return newH264(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newH264(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.H265:
|
case *format.H265:
|
||||||
return newH265(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newH265(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.VP8:
|
case *format.VP8:
|
||||||
return newVP8(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newVP8(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.VP9:
|
case *format.VP9:
|
||||||
return newVP9(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newVP9(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.AV1:
|
case *format.AV1:
|
||||||
return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
return newMPEG1Audio(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newMPEG1Audio(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.MPEG4AudioGeneric:
|
case *format.MPEG4AudioGeneric:
|
||||||
return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.MPEG4AudioLATM:
|
case *format.MPEG4AudioLATM:
|
||||||
return newMPEG4AudioLATM(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newMPEG4AudioLATM(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
case *formats.Opus:
|
case *format.Opus:
|
||||||
return newOpus(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
return newOpus(udpMaxPayloadSize, forma, generateRTPPackets, log)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
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"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
|
|
||||||
type formatProcessorVP8 struct {
|
type formatProcessorVP8 struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.VP8
|
format *format.VP8
|
||||||
encoder *rtpvp8.Encoder
|
encoder *rtpvp8.Encoder
|
||||||
decoder *rtpvp8.Decoder
|
decoder *rtpvp8.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVP8(
|
func newVP8(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.VP8,
|
forma *format.VP8,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorVP8, error) {
|
) (*formatProcessorVP8, error) {
|
||||||
@@ -67,13 +67,13 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if hasNonRTSPReaders || t.decoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame, pts, err := t.decoder.Decode(pkt)
|
frame, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtpvp8.ErrNonStartingPacketAndNoPrevious || err == rtpvp8.ErrMorePacketsNeeded {
|
if err == rtpvp8.ErrNonStartingPacketAndNoPrevious || err == rtpvp8.ErrMorePacketsNeeded {
|
||||||
return nil
|
return nil
|
||||||
@@ -82,7 +82,6 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunit.Frame = frame
|
tunit.Frame = frame
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -90,20 +89,22 @@ func (t *formatProcessorVP8) Process(y unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
pkts, err := t.encoder.Encode(tunit.Frame, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.Frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorVP8) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorVP8) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.VP8{
|
return &unit.VP8{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
|
|
||||||
type formatProcessorVP9 struct {
|
type formatProcessorVP9 struct {
|
||||||
udpMaxPayloadSize int
|
udpMaxPayloadSize int
|
||||||
format *formats.VP9
|
format *format.VP9
|
||||||
encoder *rtpvp9.Encoder
|
encoder *rtpvp9.Encoder
|
||||||
decoder *rtpvp9.Decoder
|
decoder *rtpvp9.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVP9(
|
func newVP9(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma *formats.VP9,
|
forma *format.VP9,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
_ logger.Writer,
|
_ logger.Writer,
|
||||||
) (*formatProcessorVP9, error) {
|
) (*formatProcessorVP9, error) {
|
||||||
@@ -67,13 +67,13 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
if hasNonRTSPReaders || t.decoder != nil {
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
if t.decoder == nil {
|
if t.decoder == nil {
|
||||||
var err error
|
var err error
|
||||||
t.decoder, err = t.format.CreateDecoder2()
|
t.decoder, err = t.format.CreateDecoder()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame, pts, err := t.decoder.Decode(pkt)
|
frame, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == rtpvp9.ErrNonStartingPacketAndNoPrevious || err == rtpvp9.ErrMorePacketsNeeded {
|
if err == rtpvp9.ErrNonStartingPacketAndNoPrevious || err == rtpvp9.ErrMorePacketsNeeded {
|
||||||
return nil
|
return nil
|
||||||
@@ -82,7 +82,6 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
tunit.Frame = frame
|
tunit.Frame = frame
|
||||||
tunit.PTS = pts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// route packet as is
|
// route packet as is
|
||||||
@@ -90,20 +89,22 @@ func (t *formatProcessorVP9) Process(u unit.Unit, hasNonRTSPReaders bool) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encode into RTP
|
// encode into RTP
|
||||||
pkts, err := t.encoder.Encode(tunit.Frame, tunit.PTS)
|
pkts, err := t.encoder.Encode(tunit.Frame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setTimestamp(pkts, tunit.RTPPackets, t.format.ClockRate(), tunit.PTS)
|
||||||
tunit.RTPPackets = pkts
|
tunit.RTPPackets = pkts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *formatProcessorVP9) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) unit.Unit {
|
func (t *formatProcessorVP9) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time, pts time.Duration) Unit {
|
||||||
return &unit.VP9{
|
return &unit.VP9{
|
||||||
Base: unit.Base{
|
Base: unit.Base{
|
||||||
RTPPackets: []*rtp.Packet{pkt},
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
NTP: ntp,
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/abema/go-mp4"
|
"github.com/abema/go-mp4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/av1"
|
"github.com/bluenviron/mediacommon/pkg/codecs/av1"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
||||||
@@ -43,14 +43,14 @@ func h265FindNALU(array []mp4.HEVCNaluArray, typ h265.NALUType) []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackFromH264DecoderConfig(data []byte) (formats.Format, error) {
|
func trackFromH264DecoderConfig(data []byte) (format.Format, error) {
|
||||||
var conf h264conf.Conf
|
var conf h264conf.Conf
|
||||||
err := conf.Unmarshal(data)
|
err := conf.Unmarshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to parse H264 config: %v", err)
|
return nil, fmt.Errorf("unable to parse H264 config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &formats.H264{
|
return &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: conf.SPS,
|
SPS: conf.SPS,
|
||||||
PPS: conf.PPS,
|
PPS: conf.PPS,
|
||||||
@@ -58,14 +58,14 @@ func trackFromH264DecoderConfig(data []byte) (formats.Format, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackFromAACDecoderConfig(data []byte) (formats.Format, error) {
|
func trackFromAACDecoderConfig(data []byte) (format.Format, error) {
|
||||||
var mpegConf mpeg4audio.Config
|
var mpegConf mpeg4audio.Config
|
||||||
err := mpegConf.Unmarshal(data)
|
err := mpegConf.Unmarshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &formats.MPEG4Audio{
|
return &format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpegConf,
|
Config: &mpegConf,
|
||||||
SizeLength: 13,
|
SizeLength: 13,
|
||||||
@@ -76,7 +76,7 @@ func trackFromAACDecoderConfig(data []byte) (formats.Format, error) {
|
|||||||
|
|
||||||
var errEmptyMetadata = errors.New("metadata is empty")
|
var errEmptyMetadata = errors.New("metadata is empty")
|
||||||
|
|
||||||
func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, formats.Format, error) {
|
func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, format.Format, error) {
|
||||||
if len(payload) != 1 {
|
if len(payload) != 1 {
|
||||||
return nil, nil, fmt.Errorf("invalid metadata")
|
return nil, nil, fmt.Errorf("invalid metadata")
|
||||||
}
|
}
|
||||||
@@ -86,8 +86,8 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
return nil, nil, fmt.Errorf("invalid metadata")
|
return nil, nil, fmt.Errorf("invalid metadata")
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoTrack formats.Format
|
var videoTrack format.Format
|
||||||
var audioTrack formats.Format
|
var audioTrack format.Format
|
||||||
|
|
||||||
hasVideo, err := func() (bool, error) {
|
hasVideo, err := func() (bool, error) {
|
||||||
v, ok := md.GetV("videocodecid")
|
v, ok := md.GetV("videocodecid")
|
||||||
@@ -131,7 +131,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
return false, nil
|
return false, nil
|
||||||
|
|
||||||
case message.CodecMPEG1Audio:
|
case message.CodecMPEG1Audio:
|
||||||
audioTrack = &formats.MPEG1Audio{}
|
audioTrack = &format.MPEG1Audio{}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
||||||
case message.CodecMPEG4Audio:
|
case message.CodecMPEG4Audio:
|
||||||
@@ -205,7 +205,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
}
|
}
|
||||||
|
|
||||||
if vps != nil && sps != nil && pps != nil {
|
if vps != nil && sps != nil && pps != nil {
|
||||||
videoTrack = &formats.H265{
|
videoTrack = &format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: vps,
|
VPS: vps,
|
||||||
SPS: sps,
|
SPS: sps,
|
||||||
@@ -232,7 +232,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
return nil, nil, fmt.Errorf("H265 parameters are missing")
|
return nil, nil, fmt.Errorf("H265 parameters are missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
videoTrack = &formats.H265{
|
videoTrack = &format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: vps,
|
VPS: vps,
|
||||||
SPS: sps,
|
SPS: sps,
|
||||||
@@ -252,7 +252,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
return nil, nil, fmt.Errorf("invalid AV1 configuration: %v", err)
|
return nil, nil, fmt.Errorf("invalid AV1 configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
videoTrack = &formats.AV1{}
|
videoTrack = &format.AV1{}
|
||||||
|
|
||||||
default: // VP9
|
default: // VP9
|
||||||
var vpcc mp4.VpcC
|
var vpcc mp4.VpcC
|
||||||
@@ -261,7 +261,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
return nil, nil, fmt.Errorf("invalid VP9 configuration: %v", err)
|
return nil, nil, fmt.Errorf("invalid VP9 configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
videoTrack = &formats.VP9{}
|
videoTrack = &format.VP9{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,10 +282,10 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tracksFromMessages(conn *Conn, msg message.Message) (formats.Format, formats.Format, error) {
|
func tracksFromMessages(conn *Conn, msg message.Message) (format.Format, format.Format, error) {
|
||||||
var startTime *time.Duration
|
var startTime *time.Duration
|
||||||
var videoTrack formats.Format
|
var videoTrack format.Format
|
||||||
var audioTrack formats.Format
|
var audioTrack format.Format
|
||||||
|
|
||||||
// analyze 1 second of packets
|
// analyze 1 second of packets
|
||||||
outer:
|
outer:
|
||||||
@@ -359,8 +359,8 @@ outer:
|
|||||||
// Reader is a wrapper around Conn that provides utilities to demux incoming data.
|
// Reader is a wrapper around Conn that provides utilities to demux incoming data.
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
conn *Conn
|
conn *Conn
|
||||||
videoTrack formats.Format
|
videoTrack format.Format
|
||||||
audioTrack formats.Format
|
audioTrack format.Format
|
||||||
onDataVideo func(message.Message) error
|
onDataVideo func(message.Message) error
|
||||||
onDataAudio func(*message.Audio) error
|
onDataAudio func(*message.Audio) error
|
||||||
}
|
}
|
||||||
@@ -380,7 +380,7 @@ func NewReader(conn *Conn) (*Reader, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readTracks() (formats.Format, formats.Format, error) {
|
func (r *Reader) readTracks() (format.Format, format.Format, error) {
|
||||||
msg, err := func() (message.Message, error) {
|
msg, err := func() (message.Message, error) {
|
||||||
for {
|
for {
|
||||||
msg, err := r.conn.Read()
|
msg, err := r.conn.Read()
|
||||||
@@ -439,7 +439,7 @@ func (r *Reader) readTracks() (formats.Format, formats.Format, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tracks returns detected tracks
|
// Tracks returns detected tracks
|
||||||
func (r *Reader) Tracks() (formats.Format, formats.Format) {
|
func (r *Reader) Tracks() (format.Format, format.Format) {
|
||||||
return r.videoTrack, r.audioTrack
|
return r.videoTrack, r.audioTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/abema/go-mp4"
|
"github.com/abema/go-mp4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
@@ -104,18 +104,18 @@ func TestReadTracks(t *testing.T) {
|
|||||||
|
|
||||||
for _, ca := range []struct {
|
for _, ca := range []struct {
|
||||||
name string
|
name string
|
||||||
videoTrack formats.Format
|
videoTrack format.Format
|
||||||
audioTrack formats.Format
|
audioTrack format.Format
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"video+audio",
|
"video+audio",
|
||||||
&formats.H264{
|
&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: h264SPS,
|
SPS: h264SPS,
|
||||||
PPS: h264PPS,
|
PPS: h264PPS,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
},
|
},
|
||||||
&formats.MPEG4Audio{
|
&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -129,7 +129,7 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"video",
|
"video",
|
||||||
&formats.H264{
|
&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: h264SPS,
|
SPS: h264SPS,
|
||||||
PPS: h264PPS,
|
PPS: h264PPS,
|
||||||
@@ -139,13 +139,13 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadata without codec id, video+audio",
|
"metadata without codec id, video+audio",
|
||||||
&formats.H264{
|
&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: h264SPS,
|
SPS: h264SPS,
|
||||||
PPS: h264PPS,
|
PPS: h264PPS,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
},
|
},
|
||||||
&formats.MPEG4Audio{
|
&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -159,7 +159,7 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metadata without codec id, video only",
|
"metadata without codec id, video only",
|
||||||
&formats.H264{
|
&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: h264SPS,
|
SPS: h264SPS,
|
||||||
PPS: h264PPS,
|
PPS: h264PPS,
|
||||||
@@ -169,13 +169,13 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"missing metadata, video+audio",
|
"missing metadata, video+audio",
|
||||||
&formats.H264{
|
&format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: h264SPS,
|
SPS: h264SPS,
|
||||||
PPS: h264PPS,
|
PPS: h264PPS,
|
||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
},
|
},
|
||||||
&formats.MPEG4Audio{
|
&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -190,7 +190,7 @@ func TestReadTracks(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"missing metadata, audio",
|
"missing metadata, audio",
|
||||||
nil,
|
nil,
|
||||||
&formats.MPEG4Audio{
|
&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -204,13 +204,13 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"obs studio pre 29.1 h265",
|
"obs studio pre 29.1 h265",
|
||||||
&formats.H265{
|
&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: h265VPS,
|
VPS: h265VPS,
|
||||||
SPS: h265SPS,
|
SPS: h265SPS,
|
||||||
PPS: h265PPS,
|
PPS: h265PPS,
|
||||||
},
|
},
|
||||||
&formats.MPEG4Audio{
|
&format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
@@ -224,7 +224,7 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"xplit broadcaster",
|
"xplit broadcaster",
|
||||||
&formats.H265{
|
&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: h265VPS,
|
VPS: h265VPS,
|
||||||
SPS: h265SPS,
|
SPS: h265SPS,
|
||||||
@@ -234,7 +234,7 @@ func TestReadTracks(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"obs 30",
|
"obs 30",
|
||||||
&formats.H265{
|
&format.H265{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
VPS: h265VPS,
|
VPS: h265VPS,
|
||||||
SPS: h265SPS,
|
SPS: h265SPS,
|
||||||
|
@@ -3,7 +3,7 @@ package rtmp
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
@@ -39,7 +39,7 @@ type Writer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewWriter allocates a Writer.
|
// NewWriter allocates a Writer.
|
||||||
func NewWriter(conn *Conn, videoTrack formats.Format, audioTrack formats.Format) (*Writer, error) {
|
func NewWriter(conn *Conn, videoTrack format.Format, audioTrack format.Format) (*Writer, error) {
|
||||||
w := &Writer{
|
w := &Writer{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func NewWriter(conn *Conn, videoTrack formats.Format, audioTrack formats.Format)
|
|||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Format) error {
|
func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format) error {
|
||||||
err := w.conn.Write(&message.DataAMF0{
|
err := w.conn.Write(&message.DataAMF0{
|
||||||
ChunkStreamID: 4,
|
ChunkStreamID: 4,
|
||||||
MessageStreamID: 0x1000000,
|
MessageStreamID: 0x1000000,
|
||||||
@@ -68,7 +68,7 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
|
|||||||
K: "videocodecid",
|
K: "videocodecid",
|
||||||
V: func() float64 {
|
V: func() float64 {
|
||||||
switch videoTrack.(type) {
|
switch videoTrack.(type) {
|
||||||
case *formats.H264:
|
case *format.H264:
|
||||||
return message.CodecH264
|
return message.CodecH264
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -84,10 +84,10 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
|
|||||||
K: "audiocodecid",
|
K: "audiocodecid",
|
||||||
V: func() float64 {
|
V: func() float64 {
|
||||||
switch audioTrack.(type) {
|
switch audioTrack.(type) {
|
||||||
case *formats.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
return message.CodecMPEG1Audio
|
return message.CodecMPEG1Audio
|
||||||
|
|
||||||
case *formats.MPEG4AudioGeneric, *formats.MPEG4AudioLATM:
|
case *format.MPEG4AudioGeneric, *format.MPEG4AudioLATM:
|
||||||
return message.CodecMPEG4Audio
|
return message.CodecMPEG4Audio
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -102,7 +102,7 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if videoTrack, ok := videoTrack.(*formats.H264); ok {
|
if videoTrack, ok := videoTrack.(*format.H264); ok {
|
||||||
// write decoder config only if SPS and PPS are available.
|
// write decoder config only if SPS and PPS are available.
|
||||||
// if they're not available yet, they're sent later.
|
// if they're not available yet, they're sent later.
|
||||||
if sps, pps := videoTrack.SafeParams(); sps != nil && pps != nil {
|
if sps, pps := videoTrack.SafeParams(); sps != nil && pps != nil {
|
||||||
@@ -128,10 +128,10 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
|
|||||||
var audioConfig *mpeg4audio.AudioSpecificConfig
|
var audioConfig *mpeg4audio.AudioSpecificConfig
|
||||||
|
|
||||||
switch track := audioTrack.(type) {
|
switch track := audioTrack.(type) {
|
||||||
case *formats.MPEG4Audio:
|
case *format.MPEG4Audio:
|
||||||
audioConfig = track.Config
|
audioConfig = track.Config
|
||||||
|
|
||||||
case *formats.MPEG4AudioLATM:
|
case *format.MPEG4AudioLATM:
|
||||||
audioConfig = track.Config.Programs[0].Layers[0].AudioSpecificConfig
|
audioConfig = track.Config.Programs[0].Layers[0].AudioSpecificConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
||||||
"github.com/notedit/rtmp/format/flv/flvio"
|
"github.com/notedit/rtmp/format/flv/flvio"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestWriteTracks(t *testing.T) {
|
func TestWriteTracks(t *testing.T) {
|
||||||
videoTrack := &formats.H264{
|
videoTrack := &format.H264{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
SPS: []byte{
|
SPS: []byte{
|
||||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||||
@@ -27,7 +27,7 @@ func TestWriteTracks(t *testing.T) {
|
|||||||
PacketizationMode: 1,
|
PacketizationMode: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTrack := &formats.MPEG4Audio{
|
audioTrack := &format.MPEG4Audio{
|
||||||
PayloadTyp: 96,
|
PayloadTyp: 96,
|
||||||
Config: &mpeg4audio.Config{
|
Config: &mpeg4audio.Config{
|
||||||
Type: 2,
|
Type: 2,
|
||||||
|
@@ -2,11 +2,12 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3"
|
"github.com/bluenviron/gortsplib/v4"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -16,28 +17,31 @@ import (
|
|||||||
// Stream is a media stream.
|
// Stream is a media stream.
|
||||||
// It stores tracks, readers and allow to write data to readers.
|
// It stores tracks, readers and allow to write data to readers.
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
|
desc *description.Session
|
||||||
bytesReceived *uint64
|
bytesReceived *uint64
|
||||||
|
|
||||||
rtspStream *gortsplib.ServerStream
|
smedias map[*description.Media]*streamMedia
|
||||||
smedias map[*media.Media]*streamMedia
|
mutex sync.RWMutex
|
||||||
|
rtspStream *gortsplib.ServerStream
|
||||||
|
rtspsStream *gortsplib.ServerStream
|
||||||
}
|
}
|
||||||
|
|
||||||
// New allocates a Stream.
|
// New allocates a Stream.
|
||||||
func New(
|
func New(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
medias media.Medias,
|
desc *description.Session,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
bytesReceived *uint64,
|
bytesReceived *uint64,
|
||||||
source logger.Writer,
|
source logger.Writer,
|
||||||
) (*Stream, error) {
|
) (*Stream, error) {
|
||||||
s := &Stream{
|
s := &Stream{
|
||||||
bytesReceived: bytesReceived,
|
bytesReceived: bytesReceived,
|
||||||
rtspStream: gortsplib.NewServerStream(medias),
|
desc: desc,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.smedias = make(map[*media.Media]*streamMedia)
|
s.smedias = make(map[*description.Media]*streamMedia)
|
||||||
|
|
||||||
for _, media := range s.rtspStream.Medias() {
|
for _, media := range desc.Medias {
|
||||||
var err error
|
var err error
|
||||||
s.smedias[media], err = newStreamMedia(udpMaxPayloadSize, media, generateRTPPackets, source)
|
s.smedias[media], err = newStreamMedia(udpMaxPayloadSize, media, generateRTPPackets, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,21 +54,46 @@ func New(
|
|||||||
|
|
||||||
// Close closes all resources of the stream.
|
// Close closes all resources of the stream.
|
||||||
func (s *Stream) Close() {
|
func (s *Stream) Close() {
|
||||||
s.rtspStream.Close()
|
if s.rtspStream != nil {
|
||||||
|
s.rtspStream.Close()
|
||||||
|
}
|
||||||
|
if s.rtspsStream != nil {
|
||||||
|
s.rtspsStream.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Medias returns medias of the stream.
|
// Desc returns description of the stream.
|
||||||
func (s *Stream) Medias() media.Medias {
|
func (s *Stream) Desc() *description.Session {
|
||||||
return s.rtspStream.Medias()
|
return s.desc
|
||||||
}
|
}
|
||||||
|
|
||||||
// RTSPStream returns the RTSP stream.
|
// RTSPStream returns the RTSP stream.
|
||||||
func (s *Stream) RTSPStream() *gortsplib.ServerStream {
|
func (s *Stream) RTSPStream(server *gortsplib.Server) *gortsplib.ServerStream {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.rtspStream == nil {
|
||||||
|
s.rtspStream = gortsplib.NewServerStream(server, s.desc)
|
||||||
|
}
|
||||||
return s.rtspStream
|
return s.rtspStream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RTSPSStream returns the RTSPS stream.
|
||||||
|
func (s *Stream) RTSPSStream(server *gortsplib.Server) *gortsplib.ServerStream {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.rtspsStream == nil {
|
||||||
|
s.rtspsStream = gortsplib.NewServerStream(server, s.desc)
|
||||||
|
}
|
||||||
|
return s.rtspsStream
|
||||||
|
}
|
||||||
|
|
||||||
// AddReader adds a reader.
|
// AddReader adds a reader.
|
||||||
func (s *Stream) AddReader(r interface{}, medi *media.Media, forma formats.Format, cb func(unit.Unit)) {
|
func (s *Stream) AddReader(r interface{}, medi *description.Media, forma format.Format, cb func(unit.Unit)) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
sm := s.smedias[medi]
|
sm := s.smedias[medi]
|
||||||
sf := sm.formats[forma]
|
sf := sm.formats[forma]
|
||||||
sf.addReader(r, cb)
|
sf.addReader(r, cb)
|
||||||
@@ -72,6 +101,9 @@ func (s *Stream) AddReader(r interface{}, medi *media.Media, forma formats.Forma
|
|||||||
|
|
||||||
// RemoveReader removes a reader.
|
// RemoveReader removes a reader.
|
||||||
func (s *Stream) RemoveReader(r interface{}) {
|
func (s *Stream) RemoveReader(r interface{}) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
for _, sm := range s.smedias {
|
for _, sm := range s.smedias {
|
||||||
for _, sf := range sm.formats {
|
for _, sf := range sm.formats {
|
||||||
sf.removeReader(r)
|
sf.removeReader(r)
|
||||||
@@ -80,15 +112,29 @@ func (s *Stream) RemoveReader(r interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteUnit writes a Unit.
|
// WriteUnit writes a Unit.
|
||||||
func (s *Stream) WriteUnit(medi *media.Media, forma formats.Format, data unit.Unit) {
|
func (s *Stream) WriteUnit(medi *description.Media, forma format.Format, data unit.Unit) {
|
||||||
sm := s.smedias[medi]
|
sm := s.smedias[medi]
|
||||||
sf := sm.formats[forma]
|
sf := sm.formats[forma]
|
||||||
|
|
||||||
|
s.mutex.RLock()
|
||||||
|
defer s.mutex.RUnlock()
|
||||||
|
|
||||||
sf.writeUnit(s, medi, data)
|
sf.writeUnit(s, medi, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteRTPPacket writes a RTP packet.
|
// WriteRTPPacket writes a RTP packet.
|
||||||
func (s *Stream) WriteRTPPacket(medi *media.Media, forma formats.Format, pkt *rtp.Packet, ntp time.Time) {
|
func (s *Stream) WriteRTPPacket(
|
||||||
|
medi *description.Media,
|
||||||
|
forma format.Format,
|
||||||
|
pkt *rtp.Packet,
|
||||||
|
ntp time.Time,
|
||||||
|
pts time.Duration,
|
||||||
|
) {
|
||||||
sm := s.smedias[medi]
|
sm := s.smedias[medi]
|
||||||
sf := sm.formats[forma]
|
sf := sm.formats[forma]
|
||||||
sf.writeRTPPacket(s, medi, pkt, ntp)
|
|
||||||
|
s.mutex.RLock()
|
||||||
|
defer s.mutex.RUnlock()
|
||||||
|
|
||||||
|
sf.writeRTPPacket(s, medi, pkt, ntp, pts)
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/formatprocessor"
|
"github.com/bluenviron/mediamtx/internal/formatprocessor"
|
||||||
@@ -17,13 +16,12 @@ import (
|
|||||||
type streamFormat struct {
|
type streamFormat struct {
|
||||||
source logger.Writer
|
source logger.Writer
|
||||||
proc formatprocessor.Processor
|
proc formatprocessor.Processor
|
||||||
mutex sync.RWMutex
|
|
||||||
nonRTSPReaders map[interface{}]func(unit.Unit)
|
nonRTSPReaders map[interface{}]func(unit.Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStreamFormat(
|
func newStreamFormat(
|
||||||
udpMaxPayloadSize int,
|
udpMaxPayloadSize int,
|
||||||
forma formats.Format,
|
forma format.Format,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
source logger.Writer,
|
source logger.Writer,
|
||||||
) (*streamFormat, error) {
|
) (*streamFormat, error) {
|
||||||
@@ -42,21 +40,14 @@ func newStreamFormat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sf *streamFormat) addReader(r interface{}, cb func(unit.Unit)) {
|
func (sf *streamFormat) addReader(r interface{}, cb func(unit.Unit)) {
|
||||||
sf.mutex.Lock()
|
|
||||||
defer sf.mutex.Unlock()
|
|
||||||
sf.nonRTSPReaders[r] = cb
|
sf.nonRTSPReaders[r] = cb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sf *streamFormat) removeReader(r interface{}) {
|
func (sf *streamFormat) removeReader(r interface{}) {
|
||||||
sf.mutex.Lock()
|
|
||||||
defer sf.mutex.Unlock()
|
|
||||||
delete(sf.nonRTSPReaders, r)
|
delete(sf.nonRTSPReaders, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit) {
|
func (sf *streamFormat) writeUnit(s *Stream, medi *description.Media, data unit.Unit) {
|
||||||
sf.mutex.RLock()
|
|
||||||
defer sf.mutex.RUnlock()
|
|
||||||
|
|
||||||
hasNonRTSPReaders := len(sf.nonRTSPReaders) > 0
|
hasNonRTSPReaders := len(sf.nonRTSPReaders) > 0
|
||||||
|
|
||||||
err := sf.proc.Process(data, hasNonRTSPReaders)
|
err := sf.proc.Process(data, hasNonRTSPReaders)
|
||||||
@@ -65,10 +56,22 @@ func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// forward RTP packets to RTSP readers
|
n := uint64(0)
|
||||||
for _, pkt := range data.GetRTPPackets() {
|
for _, pkt := range data.GetRTPPackets() {
|
||||||
atomic.AddUint64(s.bytesReceived, uint64(pkt.MarshalSize()))
|
n += uint64(pkt.MarshalSize())
|
||||||
s.rtspStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP())
|
}
|
||||||
|
atomic.AddUint64(s.bytesReceived, n)
|
||||||
|
|
||||||
|
if s.rtspStream != nil {
|
||||||
|
for _, pkt := range data.GetRTPPackets() {
|
||||||
|
s.rtspStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP()) //nolint:errcheck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.rtspsStream != nil {
|
||||||
|
for _, pkt := range data.GetRTPPackets() {
|
||||||
|
s.rtspsStream.WritePacketRTPWithNTP(medi, pkt, data.GetNTP()) //nolint:errcheck
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// forward decoded frames to non-RTSP readers
|
// forward decoded frames to non-RTSP readers
|
||||||
@@ -77,6 +80,12 @@ func (sf *streamFormat) writeUnit(s *Stream, medi *media.Media, data unit.Unit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sf *streamFormat) writeRTPPacket(s *Stream, medi *media.Media, pkt *rtp.Packet, ntp time.Time) {
|
func (sf *streamFormat) writeRTPPacket(
|
||||||
sf.writeUnit(s, medi, sf.proc.UnitForRTPPacket(pkt, ntp))
|
s *Stream,
|
||||||
|
medi *description.Media,
|
||||||
|
pkt *rtp.Packet,
|
||||||
|
ntp time.Time,
|
||||||
|
pts time.Duration,
|
||||||
|
) {
|
||||||
|
sf.writeUnit(s, medi, sf.proc.UnitForRTPPacket(pkt, ntp, pts))
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,23 @@
|
|||||||
package stream
|
package stream
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/formats"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v3/pkg/media"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type streamMedia struct {
|
type streamMedia struct {
|
||||||
formats map[formats.Format]*streamFormat
|
formats map[format.Format]*streamFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStreamMedia(udpMaxPayloadSize int,
|
func newStreamMedia(udpMaxPayloadSize int,
|
||||||
medi *media.Media,
|
medi *description.Media,
|
||||||
generateRTPPackets bool,
|
generateRTPPackets bool,
|
||||||
source logger.Writer,
|
source logger.Writer,
|
||||||
) (*streamMedia, error) {
|
) (*streamMedia, error) {
|
||||||
sm := &streamMedia{
|
sm := &streamMedia{
|
||||||
formats: make(map[formats.Format]*streamFormat),
|
formats: make(map[format.Format]*streamFormat),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, forma := range medi.Formats {
|
for _, forma := range medi.Formats {
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AV1 is an AV1 data unit.
|
// AV1 is an AV1 data unit.
|
||||||
type AV1 struct {
|
type AV1 struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
TU [][]byte
|
||||||
TU [][]byte
|
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
type Base struct {
|
type Base struct {
|
||||||
RTPPackets []*rtp.Packet
|
RTPPackets []*rtp.Packet
|
||||||
NTP time.Time
|
NTP time.Time
|
||||||
|
PTS time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRTPPackets implements Unit.
|
// GetRTPPackets implements Unit.
|
||||||
@@ -21,3 +22,8 @@ func (u *Base) GetRTPPackets() []*rtp.Packet {
|
|||||||
func (u *Base) GetNTP() time.Time {
|
func (u *Base) GetNTP() time.Time {
|
||||||
return u.NTP
|
return u.NTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPTS implements Unit.
|
||||||
|
func (u *Base) GetPTS() time.Duration {
|
||||||
|
return u.PTS
|
||||||
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// H264 is a H264 data unit.
|
// H264 is a H264 data unit.
|
||||||
type H264 struct {
|
type H264 struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
AU [][]byte
|
||||||
AU [][]byte
|
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// H265 is a H265 data unit.
|
// H265 is a H265 data unit.
|
||||||
type H265 struct {
|
type H265 struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
AU [][]byte
|
||||||
AU [][]byte
|
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MPEG1Audio is a MPEG-1/2 Audio data unit.
|
// MPEG1Audio is a MPEG-1/2 Audio data unit.
|
||||||
type MPEG1Audio struct {
|
type MPEG1Audio struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
|
||||||
Frames [][]byte
|
Frames [][]byte
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MPEG4AudioGeneric is a MPEG-4 Audio data unit.
|
// MPEG4AudioGeneric is a MPEG-4 Audio data unit.
|
||||||
type MPEG4AudioGeneric struct {
|
type MPEG4AudioGeneric struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
|
||||||
AUs [][]byte
|
AUs [][]byte
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MPEG4AudioLATM is a MPEG-4 Audio data unit.
|
// MPEG4AudioLATM is a MPEG-4 Audio data unit.
|
||||||
type MPEG4AudioLATM struct {
|
type MPEG4AudioLATM struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
AU []byte
|
||||||
AU []byte
|
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Opus is a Opus data unit.
|
// Opus is a Opus data unit.
|
||||||
type Opus struct {
|
type Opus struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
|
||||||
Packets [][]byte
|
Packets [][]byte
|
||||||
}
|
}
|
||||||
|
@@ -14,4 +14,7 @@ type Unit interface {
|
|||||||
|
|
||||||
// returns the NTP timestamp of the unit.
|
// returns the NTP timestamp of the unit.
|
||||||
GetNTP() time.Time
|
GetNTP() time.Time
|
||||||
|
|
||||||
|
// returns the PTS of the unit.
|
||||||
|
GetPTS() time.Duration
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VP8 is a VP8 data unit.
|
// VP8 is a VP8 data unit.
|
||||||
type VP8 struct {
|
type VP8 struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
|
||||||
Frame []byte
|
Frame []byte
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,7 @@
|
|||||||
package unit
|
package unit
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VP9 is a VP9 data unit.
|
// VP9 is a VP9 data unit.
|
||||||
type VP9 struct {
|
type VP9 struct {
|
||||||
Base
|
Base
|
||||||
PTS time.Duration
|
|
||||||
Frame []byte
|
Frame []byte
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user