mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-11-02 20:04:01 +08:00
switch to gortsplib v2 (#1301)
Fixes #1103 gortsplib/v2 supports multiple formats inside a single track (media). This allows to apply the resizing algorithm to single formats inside medias. For instance, if a media contains a a proprietary format and an H264 format, and the latter has oversized packets, they can now be resized.
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.18
|
||||
require (
|
||||
code.cloudfoundry.org/bytefmt v0.0.0-20211005130812-5bb3c17173e5
|
||||
github.com/abema/go-mp4 v0.8.0
|
||||
github.com/aler9/gortsplib v1.0.1
|
||||
github.com/aler9/gortsplib/v2 v2.0.0-20221213180201-60596c32d1bf
|
||||
github.com/asticode/go-astits v1.10.1-0.20220319093903-4abe66a9b757
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -6,8 +6,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/aler9/gortsplib v1.0.1 h1:R13+hxlvg2Hvu98+0hzg0o5fPjyUA9ZPJneMIBxKGXk=
|
||||
github.com/aler9/gortsplib v1.0.1/go.mod h1:BOWNZ/QBkY/eVcRqUzJbPFEsRJshwxaxBT01K260Jeo=
|
||||
github.com/aler9/gortsplib/v2 v2.0.0-20221213180201-60596c32d1bf h1:kPSTROCTa8pyQ13LzDgcA2mYfBZKU8ma6KVi/mwWux4=
|
||||
github.com/aler9/gortsplib/v2 v2.0.0-20221213180201-60596c32d1bf/go.mod h1:zJ+fWtakOMN6cKV169EMNVBLPTITArrJKu/fyM+dov8=
|
||||
github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82 h1:9WgSzBLo3a9ToSVV7sRTBYZ1GGOZUpq4+5H3SN0UZq4=
|
||||
github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82/go.mod h1:qsMrZCbeBf/mCLOeF16KDkPu4gktn/pOWyaq1aYQE7U=
|
||||
github.com/asticode/go-astikit v0.20.0 h1:+7N+J4E4lWx2QOkRdOf6DafWJMv6O4RRfgClwQokrH8=
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2/pkg/headers"
|
||||
)
|
||||
|
||||
// AuthMethods is the authMethods parameter.
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/headers"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
)
|
||||
|
||||
var rePathName = regexp.MustCompile(`^[0-9a-zA-Z_\-/\.~]+$`)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
)
|
||||
|
||||
// Protocol is a RTSP transport.
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
)
|
||||
|
||||
// SourceProtocol is the sourceProtocol parameter.
|
||||
|
||||
@@ -13,14 +13,28 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/rtmp"
|
||||
)
|
||||
|
||||
var testFormatH264 = &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
var testMediaH264 = &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{testFormatH264},
|
||||
}
|
||||
|
||||
func httpRequest(method string, ur string, in interface{}, out interface{}) error {
|
||||
buf, err := func() (io.Reader, error) {
|
||||
if in == nil {
|
||||
@@ -188,32 +202,32 @@ func TestAPIPathsList(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
media0 := testMediaH264
|
||||
|
||||
source := gortsplib.Client{}
|
||||
err := source.StartPublishing(
|
||||
err := source.StartRecording(
|
||||
"rtsp://localhost:8554/mypath",
|
||||
gortsplib.Tracks{
|
||||
&gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
},
|
||||
&gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
IndexDeltaLength: 3,
|
||||
media.Medias{
|
||||
media0,
|
||||
{
|
||||
Type: media.TypeAudio,
|
||||
Formats: []format.Format{&format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
IndexDeltaLength: 3,
|
||||
}},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
source.WritePacketRTP(0, &rtp.Packet{
|
||||
source.WritePacketRTP(media0, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
PayloadType: 96,
|
||||
@@ -256,28 +270,29 @@ func TestAPIPathsList(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
tracks := gortsplib.Tracks{
|
||||
&gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
medias := media.Medias{
|
||||
{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{testFormatH264},
|
||||
},
|
||||
&gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 97,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
IndexDeltaLength: 3,
|
||||
{
|
||||
Type: media.TypeAudio,
|
||||
Formats: []format.Format{&format.MPEG4Audio{
|
||||
PayloadTyp: 97,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
IndexDeltaLength: 3,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
err = source.StartPublishing("rtsps://localhost:8322/mypath", tracks)
|
||||
err = source.StartRecording("rtsps://localhost:8322/mypath", medias)
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -414,19 +429,13 @@ func TestAPIProtocolSpecificList(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
switch ca {
|
||||
case "rtsp conns", "rtsp sessions":
|
||||
source := gortsplib.Client{}
|
||||
|
||||
err := source.StartPublishing("rtsp://localhost:8554/mypath",
|
||||
gortsplib.Tracks{track})
|
||||
err := source.StartRecording("rtsp://localhost:8554/mypath", media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -435,8 +444,7 @@ func TestAPIProtocolSpecificList(t *testing.T) {
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
err := source.StartPublishing("rtsps://localhost:8322/mypath",
|
||||
gortsplib.Tracks{track})
|
||||
err := source.StartRecording("rtsps://localhost:8322/mypath", media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -464,8 +472,8 @@ func TestAPIProtocolSpecificList(t *testing.T) {
|
||||
err = conn.InitializeClient(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{ // 1920x1080 baseline
|
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||
@@ -481,8 +489,8 @@ func TestAPIProtocolSpecificList(t *testing.T) {
|
||||
case "hls":
|
||||
source := gortsplib.Client{}
|
||||
|
||||
err := source.StartPublishing("rtsp://localhost:8554/mypath",
|
||||
gortsplib.Tracks{track})
|
||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -588,19 +596,14 @@ func TestAPIKick(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
switch ca {
|
||||
case "rtsp":
|
||||
source := gortsplib.Client{}
|
||||
|
||||
err := source.StartPublishing("rtsp://localhost:8554/mypath",
|
||||
gortsplib.Tracks{track})
|
||||
err := source.StartRecording("rtsp://localhost:8554/mypath",
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -609,8 +612,8 @@ func TestAPIKick(t *testing.T) {
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
err := source.StartPublishing("rtsps://localhost:8322/mypath",
|
||||
gortsplib.Tracks{track})
|
||||
err := source.StartRecording("rtsps://localhost:8322/mypath",
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -626,8 +629,8 @@ func TestAPIKick(t *testing.T) {
|
||||
err = conn.InitializeClient(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{ // 1920x1080 baseline
|
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os/signal"
|
||||
"reflect"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/pkg/headers"
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -131,7 +132,7 @@ func TestCorePathAutoDeletion(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.StatusNotFound, res.StatusCode)
|
||||
} else {
|
||||
u, err := url.Parse("rtsp://localhost:8554/mypath/trackID=0")
|
||||
u, err := url.Parse("rtsp://localhost:8554/mypath/mediaID=0")
|
||||
require.NoError(t, err)
|
||||
|
||||
byts, _ := base.Request{
|
||||
@@ -182,7 +183,9 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -190,18 +193,21 @@ func main() {
|
||||
panic("environment not set")
|
||||
}
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
medi := &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{&format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}},
|
||||
}
|
||||
|
||||
source := gortsplib.Client{}
|
||||
|
||||
err := source.StartPublishing(
|
||||
err := source.StartRecording(
|
||||
"rtsp://localhost:" + os.Getenv("RTSP_PORT") + "/" + os.Getenv("RTSP_PATH"),
|
||||
gortsplib.Tracks{track})
|
||||
media.Medias{medi})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -269,7 +275,7 @@ func main() {
|
||||
}
|
||||
|
||||
if ca == "setup" || ca == "describe and setup" {
|
||||
u, err := url.Parse("rtsp://localhost:8554/ondemand/trackID=0")
|
||||
u, err := url.Parse("rtsp://localhost:8554/ondemand/mediaID=0")
|
||||
require.NoError(t, err)
|
||||
|
||||
byts, _ := base.Request{
|
||||
@@ -321,18 +327,13 @@ func TestCorePathRunOnReady(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
c := gortsplib.Client{}
|
||||
|
||||
err := c.StartPublishing(
|
||||
err := c.StartRecording(
|
||||
"rtsp://localhost:8554/test",
|
||||
gortsplib.Tracks{track})
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
@@ -358,18 +359,10 @@ func TestCoreHotReloading(t *testing.T) {
|
||||
defer p.Close()
|
||||
|
||||
func() {
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
c := gortsplib.Client{}
|
||||
|
||||
err = c.StartPublishing(
|
||||
"rtsp://localhost:8554/test1",
|
||||
gortsplib.Tracks{track})
|
||||
err = c.StartRecording("rtsp://localhost:8554/test1", media.Medias{medi})
|
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||
}()
|
||||
|
||||
@@ -381,18 +374,10 @@ func TestCoreHotReloading(t *testing.T) {
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
func() {
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
conn := gortsplib.Client{}
|
||||
|
||||
err = conn.StartPublishing(
|
||||
"rtsp://localhost:8554/test1",
|
||||
gortsplib.Tracks{track})
|
||||
err = conn.StartRecording("rtsp://localhost:8554/test1", media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
}()
|
||||
|
||||
@@ -8,21 +8,15 @@ import (
|
||||
|
||||
// data is the data unit routed across the server.
|
||||
type data interface {
|
||||
getTrackID() int
|
||||
getRTPPackets() []*rtp.Packet
|
||||
getNTP() time.Time
|
||||
}
|
||||
|
||||
type dataGeneric struct {
|
||||
trackID int
|
||||
rtpPackets []*rtp.Packet
|
||||
ntp time.Time
|
||||
}
|
||||
|
||||
func (d *dataGeneric) getTrackID() int {
|
||||
return d.trackID
|
||||
}
|
||||
|
||||
func (d *dataGeneric) getRTPPackets() []*rtp.Packet {
|
||||
return d.rtpPackets
|
||||
}
|
||||
@@ -32,17 +26,12 @@ func (d *dataGeneric) getNTP() time.Time {
|
||||
}
|
||||
|
||||
type dataH264 struct {
|
||||
trackID int
|
||||
rtpPackets []*rtp.Packet
|
||||
ntp time.Time
|
||||
pts time.Duration
|
||||
nalus [][]byte
|
||||
}
|
||||
|
||||
func (d *dataH264) getTrackID() int {
|
||||
return d.trackID
|
||||
}
|
||||
|
||||
func (d *dataH264) getRTPPackets() []*rtp.Packet {
|
||||
return d.rtpPackets
|
||||
}
|
||||
@@ -52,17 +41,12 @@ func (d *dataH264) getNTP() time.Time {
|
||||
}
|
||||
|
||||
type dataMPEG4Audio struct {
|
||||
trackID int
|
||||
rtpPackets []*rtp.Packet
|
||||
ntp time.Time
|
||||
pts time.Duration
|
||||
aus [][]byte
|
||||
}
|
||||
|
||||
func (d *dataMPEG4Audio) getTrackID() int {
|
||||
return d.trackID
|
||||
}
|
||||
|
||||
func (d *dataMPEG4Audio) getRTPPackets() []*rtp.Packet {
|
||||
return d.rtpPackets
|
||||
}
|
||||
|
||||
22
internal/core/formatprocessor.go
Normal file
22
internal/core/formatprocessor.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
type formatProcessor interface {
|
||||
process(data, bool) error
|
||||
}
|
||||
|
||||
func newFormatProcessor(forma format.Format, generateRTPPackets bool) (formatProcessor, error) {
|
||||
switch forma := forma.(type) {
|
||||
case *format.H264:
|
||||
return newFormatProcessorH264(forma, generateRTPPackets)
|
||||
|
||||
case *format.MPEG4Audio:
|
||||
return newFormatProcessorMPEG4Audio(forma, generateRTPPackets)
|
||||
|
||||
default:
|
||||
return newFormatProcessorGeneric(forma, generateRTPPackets)
|
||||
}
|
||||
}
|
||||
39
internal/core/formatprocessor_generic.go
Normal file
39
internal/core/formatprocessor_generic.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
const (
|
||||
// 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header)
|
||||
maxPacketSize = 1472
|
||||
)
|
||||
|
||||
type formatProcessorGeneric struct{}
|
||||
|
||||
func newFormatProcessorGeneric(forma format.Format, generateRTPPackets bool) (*formatProcessorGeneric, error) {
|
||||
if generateRTPPackets {
|
||||
return nil, fmt.Errorf("we don't know how to generate RTP packets of format %+v", forma)
|
||||
}
|
||||
|
||||
return &formatProcessorGeneric{}, nil
|
||||
}
|
||||
|
||||
func (t *formatProcessorGeneric) process(dat data, hasNonRTSPReaders bool) error {
|
||||
tdata := dat.(*dataGeneric)
|
||||
|
||||
pkt := tdata.rtpPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Header.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if pkt.MarshalSize() > maxPacketSize {
|
||||
return fmt.Errorf("payload size (%d) is greater than maximum allowed (%d)",
|
||||
pkt.MarshalSize(), maxPacketSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,9 +3,9 @@ package core
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/pkg/rtpcodecs/rtph264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtph264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
@@ -61,53 +61,53 @@ func rtpH264ExtractSPSPPS(pkt *rtp.Packet) ([]byte, []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
type streamTrackH264 struct {
|
||||
track *gortsplib.TrackH264
|
||||
type formatProcessorH264 struct {
|
||||
format *format.H264
|
||||
|
||||
encoder *rtph264.Encoder
|
||||
decoder *rtph264.Decoder
|
||||
}
|
||||
|
||||
func newStreamTrackH264(
|
||||
track *gortsplib.TrackH264,
|
||||
func newFormatProcessorH264(
|
||||
forma *format.H264,
|
||||
allocateEncoder bool,
|
||||
) *streamTrackH264 {
|
||||
t := &streamTrackH264{
|
||||
track: track,
|
||||
) (*formatProcessorH264, error) {
|
||||
t := &formatProcessorH264{
|
||||
format: forma,
|
||||
}
|
||||
|
||||
if allocateEncoder {
|
||||
t.encoder = track.CreateEncoder()
|
||||
t.encoder = forma.CreateEncoder()
|
||||
}
|
||||
|
||||
return t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *streamTrackH264) updateTrackParametersFromRTPPacket(pkt *rtp.Packet) {
|
||||
func (t *formatProcessorH264) updateTrackParametersFromRTPPacket(pkt *rtp.Packet) {
|
||||
sps, pps := rtpH264ExtractSPSPPS(pkt)
|
||||
|
||||
if sps != nil && !bytes.Equal(sps, t.track.SafeSPS()) {
|
||||
t.track.SafeSetSPS(sps)
|
||||
if sps != nil && !bytes.Equal(sps, t.format.SafeSPS()) {
|
||||
t.format.SafeSetSPS(sps)
|
||||
}
|
||||
|
||||
if pps != nil && !bytes.Equal(pps, t.track.SafePPS()) {
|
||||
t.track.SafeSetPPS(pps)
|
||||
if pps != nil && !bytes.Equal(pps, t.format.SafePPS()) {
|
||||
t.format.SafeSetPPS(pps)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *streamTrackH264) updateTrackParametersFromNALUs(nalus [][]byte) {
|
||||
func (t *formatProcessorH264) updateTrackParametersFromNALUs(nalus [][]byte) {
|
||||
for _, nalu := range nalus {
|
||||
typ := h264.NALUType(nalu[0] & 0x1F)
|
||||
|
||||
switch typ {
|
||||
case h264.NALUTypeSPS:
|
||||
if !bytes.Equal(nalu, t.track.SafeSPS()) {
|
||||
t.track.SafeSetSPS(nalu)
|
||||
if !bytes.Equal(nalu, t.format.SafeSPS()) {
|
||||
t.format.SafeSetSPS(nalu)
|
||||
}
|
||||
|
||||
case h264.NALUTypePPS:
|
||||
if !bytes.Equal(nalu, t.track.SafePPS()) {
|
||||
t.track.SafeSetPPS(nalu)
|
||||
if !bytes.Equal(nalu, t.format.SafePPS()) {
|
||||
t.format.SafeSetPPS(nalu)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ func (t *streamTrackH264) updateTrackParametersFromNALUs(nalus [][]byte) {
|
||||
|
||||
// remux is needed to fix corrupted streams and make streams
|
||||
// compatible with all protocols.
|
||||
func (t *streamTrackH264) remuxNALUs(nalus [][]byte) [][]byte {
|
||||
func (t *formatProcessorH264) remuxNALUs(nalus [][]byte) [][]byte {
|
||||
addSPSPPS := false
|
||||
n := 0
|
||||
for _, nalu := range nalus {
|
||||
@@ -143,8 +143,8 @@ func (t *streamTrackH264) remuxNALUs(nalus [][]byte) [][]byte {
|
||||
i := 0
|
||||
|
||||
if addSPSPPS {
|
||||
filteredNALUs[0] = t.track.SafeSPS()
|
||||
filteredNALUs[1] = t.track.SafePPS()
|
||||
filteredNALUs[0] = t.format.SafeSPS()
|
||||
filteredNALUs[1] = t.format.SafePPS()
|
||||
i = 2
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func (t *streamTrackH264) remuxNALUs(nalus [][]byte) [][]byte {
|
||||
return filteredNALUs
|
||||
}
|
||||
|
||||
func (t *streamTrackH264) generateRTPPackets(tdata *dataH264) error {
|
||||
func (t *formatProcessorH264) generateRTPPackets(tdata *dataH264) error {
|
||||
pkts, err := t.encoder.Encode(tdata.nalus, tdata.pts)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -177,7 +177,7 @@ func (t *streamTrackH264) generateRTPPackets(tdata *dataH264) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *streamTrackH264) onData(dat data, hasNonRTSPReaders bool) error {
|
||||
func (t *formatProcessorH264) process(dat data, hasNonRTSPReaders bool) error {
|
||||
tdata := dat.(*dataH264)
|
||||
|
||||
if tdata.rtpPackets != nil {
|
||||
@@ -199,7 +199,7 @@ func (t *streamTrackH264) onData(dat data, hasNonRTSPReaders bool) error {
|
||||
SSRC: &v1,
|
||||
InitialSequenceNumber: &v2,
|
||||
InitialTimestamp: &v3,
|
||||
PacketizationMode: t.track.PacketizationMode,
|
||||
PacketizationMode: t.format.PacketizationMode,
|
||||
}
|
||||
t.encoder.Init()
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func (t *streamTrackH264) onData(dat data, hasNonRTSPReaders bool) error {
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders || t.encoder != nil {
|
||||
if t.decoder == nil {
|
||||
t.decoder = t.track.CreateDecoder()
|
||||
t.decoder = t.format.CreateDecoder()
|
||||
}
|
||||
|
||||
nalus, pts, err := t.decoder.Decode(pkt)
|
||||
@@ -3,32 +3,32 @@ package core
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/rtpcodecs/rtpmpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpmpeg4audio"
|
||||
)
|
||||
|
||||
type streamTrackMPEG4Audio struct {
|
||||
track *gortsplib.TrackMPEG4Audio
|
||||
type formatProcessorMPEG4Audio struct {
|
||||
format *format.MPEG4Audio
|
||||
encoder *rtpmpeg4audio.Encoder
|
||||
decoder *rtpmpeg4audio.Decoder
|
||||
}
|
||||
|
||||
func newStreamTrackMPEG4Audio(
|
||||
track *gortsplib.TrackMPEG4Audio,
|
||||
func newFormatProcessorMPEG4Audio(
|
||||
forma *format.MPEG4Audio,
|
||||
allocateEncoder bool,
|
||||
) *streamTrackMPEG4Audio {
|
||||
t := &streamTrackMPEG4Audio{
|
||||
track: track,
|
||||
) (*formatProcessorMPEG4Audio, error) {
|
||||
t := &formatProcessorMPEG4Audio{
|
||||
format: forma,
|
||||
}
|
||||
|
||||
if allocateEncoder {
|
||||
t.encoder = track.CreateEncoder()
|
||||
t.encoder = forma.CreateEncoder()
|
||||
}
|
||||
|
||||
return t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *streamTrackMPEG4Audio) generateRTPPackets(tdata *dataMPEG4Audio) error {
|
||||
func (t *formatProcessorMPEG4Audio) generateRTPPackets(tdata *dataMPEG4Audio) error {
|
||||
pkts, err := t.encoder.Encode(tdata.aus, tdata.pts)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -38,7 +38,7 @@ func (t *streamTrackMPEG4Audio) generateRTPPackets(tdata *dataMPEG4Audio) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *streamTrackMPEG4Audio) onData(dat data, hasNonRTSPReaders bool) error {
|
||||
func (t *formatProcessorMPEG4Audio) process(dat data, hasNonRTSPReaders bool) error {
|
||||
tdata := dat.(*dataMPEG4Audio)
|
||||
|
||||
if tdata.rtpPackets != nil {
|
||||
@@ -56,7 +56,7 @@ func (t *streamTrackMPEG4Audio) onData(dat data, hasNonRTSPReaders bool) error {
|
||||
// decode from RTP
|
||||
if hasNonRTSPReaders {
|
||||
if t.decoder == nil {
|
||||
t.decoder = t.track.CreateDecoder()
|
||||
t.decoder = t.format.CreateDecoder()
|
||||
}
|
||||
|
||||
aus, pts, err := t.decoder.Decode(pkt)
|
||||
@@ -11,9 +11,10 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/pkg/ringbuffer"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/ringbuffer"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||
@@ -307,32 +308,13 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
|
||||
m.path.readerRemove(pathReaderRemoveReq{author: m})
|
||||
}()
|
||||
|
||||
var videoTrack *gortsplib.TrackH264
|
||||
videoTrackID := -1
|
||||
var audioTrack *gortsplib.TrackMPEG4Audio
|
||||
audioTrackID := -1
|
||||
var videoFormat *format.H264
|
||||
videoMedia := res.stream.medias().FindFormat(&videoFormat)
|
||||
|
||||
for i, track := range res.stream.tracks() {
|
||||
switch tt := track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
if videoTrack != nil {
|
||||
return fmt.Errorf("can't encode track %d with HLS: too many tracks", i+1)
|
||||
}
|
||||
var audioFormat *format.MPEG4Audio
|
||||
audioMedia := res.stream.medias().FindFormat(&audioFormat)
|
||||
|
||||
videoTrack = tt
|
||||
videoTrackID = i
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
if audioTrack != nil {
|
||||
return fmt.Errorf("can't encode track %d with HLS: too many tracks", i+1)
|
||||
}
|
||||
|
||||
audioTrack = tt
|
||||
audioTrackID = i
|
||||
}
|
||||
}
|
||||
|
||||
if videoTrack == nil && audioTrack == nil {
|
||||
if videoFormat == nil && audioFormat == nil {
|
||||
return fmt.Errorf("the stream doesn't contain an H264 track or an AAC track")
|
||||
}
|
||||
|
||||
@@ -343,8 +325,8 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
|
||||
time.Duration(m.hlsSegmentDuration),
|
||||
time.Duration(m.hlsPartDuration),
|
||||
uint64(m.hlsSegmentMaxSize),
|
||||
videoTrack,
|
||||
audioTrack,
|
||||
videoFormat,
|
||||
audioFormat,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("muxer error: %v", err)
|
||||
@@ -355,26 +337,35 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
|
||||
|
||||
m.ringBuffer, _ = ringbuffer.New(uint64(m.readBufferCount))
|
||||
|
||||
m.path.readerStart(pathReaderStartReq{author: m})
|
||||
var medias media.Medias
|
||||
|
||||
var tracks []gortsplib.Track
|
||||
if videoTrack != nil {
|
||||
tracks = append(tracks, videoTrack)
|
||||
if videoMedia != nil {
|
||||
medias = append(medias, videoMedia)
|
||||
|
||||
res.stream.readerAdd(m, videoMedia, videoFormat, func(dat data) {
|
||||
m.ringBuffer.Push(dat)
|
||||
})
|
||||
}
|
||||
if audioTrack != nil {
|
||||
tracks = append(tracks, audioTrack)
|
||||
if audioMedia != nil {
|
||||
medias = append(medias, audioMedia)
|
||||
|
||||
res.stream.readerAdd(m, audioMedia, audioFormat, func(dat data) {
|
||||
m.ringBuffer.Push(dat)
|
||||
})
|
||||
}
|
||||
|
||||
defer res.stream.readerRemove(m)
|
||||
|
||||
m.log(logger.Info, "is converting into HLS, %s",
|
||||
sourceTrackInfo(tracks))
|
||||
sourceMediaInfo(medias))
|
||||
|
||||
writerDone := make(chan error)
|
||||
go func() {
|
||||
writerDone <- m.runWriter(
|
||||
videoTrack,
|
||||
videoTrackID,
|
||||
audioTrack,
|
||||
audioTrackID,
|
||||
videoMedia,
|
||||
videoFormat,
|
||||
audioMedia,
|
||||
audioFormat,
|
||||
)
|
||||
}()
|
||||
|
||||
@@ -403,10 +394,10 @@ func (m *hlsMuxer) runInner(innerCtx context.Context, innerReady chan struct{})
|
||||
}
|
||||
|
||||
func (m *hlsMuxer) runWriter(
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
videoTrackID int,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
audioTrackID int,
|
||||
videoMedia *media.Media,
|
||||
videoFormat *format.H264,
|
||||
audioMedia *media.Media,
|
||||
audioFormat *format.MPEG4Audio,
|
||||
) error {
|
||||
videoStartPTSFilled := false
|
||||
var videoStartPTS time.Duration
|
||||
@@ -420,9 +411,8 @@ func (m *hlsMuxer) runWriter(
|
||||
}
|
||||
data := item.(data)
|
||||
|
||||
if videoTrack != nil && data.getTrackID() == videoTrackID {
|
||||
tdata := data.(*dataH264)
|
||||
|
||||
switch tdata := data.(type) {
|
||||
case *dataH264:
|
||||
if tdata.nalus == nil {
|
||||
continue
|
||||
}
|
||||
@@ -437,9 +427,8 @@ func (m *hlsMuxer) runWriter(
|
||||
if err != nil {
|
||||
return fmt.Errorf("muxer error: %v", err)
|
||||
}
|
||||
} else if audioTrack != nil && data.getTrackID() == audioTrackID {
|
||||
tdata := data.(*dataMPEG4Audio)
|
||||
|
||||
case *dataMPEG4Audio:
|
||||
if tdata.aus == nil {
|
||||
continue
|
||||
}
|
||||
@@ -454,7 +443,7 @@ func (m *hlsMuxer) runWriter(
|
||||
err := m.muxer.WriteAAC(
|
||||
tdata.ntp,
|
||||
pts+time.Duration(i)*mpeg4audio.SamplesPerAccessUnit*
|
||||
time.Second/time.Duration(audioTrack.ClockRate()),
|
||||
time.Second/time.Duration(audioFormat.ClockRate()),
|
||||
au)
|
||||
if err != nil {
|
||||
return fmt.Errorf("muxer error: %v", err)
|
||||
@@ -593,11 +582,6 @@ func (m *hlsMuxer) apiHLSMuxersList(req hlsServerAPIMuxersListSubReq) {
|
||||
}
|
||||
}
|
||||
|
||||
// onReaderData implements reader.
|
||||
func (m *hlsMuxer) onReaderData(data data) {
|
||||
m.ringBuffer.Push(data)
|
||||
}
|
||||
|
||||
// apiReaderDescribe implements reader.
|
||||
func (m *hlsMuxer) apiReaderDescribe() interface{} {
|
||||
return struct {
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls"
|
||||
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||
@@ -41,8 +42,8 @@ func (s *hlsSource) Log(level logger.Level, format string, args ...interface{})
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *hlsSource) run(ctx context.Context) error {
|
||||
var stream *stream
|
||||
var videoTrackID int
|
||||
var audioTrackID int
|
||||
var videoMedia *media.Media
|
||||
var audioMedia *media.Media
|
||||
|
||||
defer func() {
|
||||
if stream != nil {
|
||||
@@ -50,39 +51,44 @@ func (s *hlsSource) run(ctx context.Context) error {
|
||||
}
|
||||
}()
|
||||
|
||||
onTracks := func(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) error {
|
||||
var tracks gortsplib.Tracks
|
||||
onTracks := func(videoFormat *format.H264, audioFormat *format.MPEG4Audio) error {
|
||||
var medias media.Medias
|
||||
|
||||
if videoTrack != nil {
|
||||
videoTrackID = len(tracks)
|
||||
tracks = append(tracks, videoTrack)
|
||||
if videoFormat != nil {
|
||||
videoMedia = &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{videoFormat},
|
||||
}
|
||||
medias = append(medias, videoMedia)
|
||||
}
|
||||
|
||||
if audioTrack != nil {
|
||||
audioTrackID = len(tracks)
|
||||
tracks = append(tracks, audioTrack)
|
||||
if audioFormat != nil {
|
||||
audioMedia = &media.Media{
|
||||
Type: media.TypeAudio,
|
||||
Formats: []format.Format{audioFormat},
|
||||
}
|
||||
medias = append(medias, audioMedia)
|
||||
}
|
||||
|
||||
res := s.parent.sourceStaticImplSetReady(pathSourceStaticSetReadyReq{
|
||||
tracks: tracks,
|
||||
medias: medias,
|
||||
generateRTPPackets: true,
|
||||
})
|
||||
if res.err != nil {
|
||||
return res.err
|
||||
}
|
||||
|
||||
s.Log(logger.Info, "ready: %s", sourceTrackInfo(tracks))
|
||||
s.Log(logger.Info, "ready: %s", sourceMediaInfo(medias))
|
||||
stream = res.stream
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
onVideoData := func(pts time.Duration, nalus [][]byte) {
|
||||
err := stream.writeData(&dataH264{
|
||||
trackID: videoTrackID,
|
||||
pts: pts,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
err := stream.writeData(videoMedia, videoMedia.Formats[0], &dataH264{
|
||||
pts: pts,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
@@ -90,11 +96,10 @@ func (s *hlsSource) run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
onAudioData := func(pts time.Duration, au []byte) {
|
||||
err := stream.writeData(&dataMPEG4Audio{
|
||||
trackID: audioTrackID,
|
||||
pts: pts,
|
||||
aus: [][]byte{au},
|
||||
ntp: time.Now(),
|
||||
err := stream.writeData(audioMedia, audioMedia.Formats[0], &dataMPEG4Audio{
|
||||
pts: pts,
|
||||
aus: [][]byte{au},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pion/rtp"
|
||||
@@ -135,31 +135,7 @@ func TestHLSSource(t *testing.T) {
|
||||
|
||||
frameRecv := make(chan struct{})
|
||||
|
||||
c := gortsplib.Client{
|
||||
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: ctx.Packet.SequenceNumber,
|
||||
Timestamp: ctx.Packet.Timestamp,
|
||||
SSRC: ctx.Packet.SSRC,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{
|
||||
0x18,
|
||||
0x00, 0x04,
|
||||
0x07, 0x01, 0x02, 0x03, // SPS
|
||||
0x00, 0x01,
|
||||
0x08, // PPS
|
||||
0x00, 0x01,
|
||||
0x05, // ODR
|
||||
},
|
||||
}, ctx.Packet)
|
||||
close(frameRecv)
|
||||
},
|
||||
}
|
||||
c := gortsplib.Client{}
|
||||
|
||||
u, err := url.Parse("rtsp://localhost:8554/proxied")
|
||||
require.NoError(t, err)
|
||||
@@ -168,10 +144,37 @@ func TestHLSSource(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.SetupAndPlay(tracks, baseURL)
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: pkt.SequenceNumber,
|
||||
Timestamp: pkt.Timestamp,
|
||||
SSRC: pkt.SSRC,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{
|
||||
0x18,
|
||||
0x00, 0x04,
|
||||
0x07, 0x01, 0x02, 0x03, // SPS
|
||||
0x00, 0x01,
|
||||
0x08, // PPS
|
||||
0x00, 0x01,
|
||||
0x05, // ODR
|
||||
},
|
||||
}, pkt)
|
||||
close(frameRecv)
|
||||
})
|
||||
|
||||
_, err = c.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
<-frameRecv
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/rtmp"
|
||||
@@ -33,22 +35,17 @@ func TestMetrics(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
source := gortsplib.Client{}
|
||||
err = source.StartPublishing("rtsp://localhost:8554/rtsp_path",
|
||||
gortsplib.Tracks{track})
|
||||
err = source.StartRecording("rtsp://localhost:8554/rtsp_path",
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
source2 := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}
|
||||
err = source2.StartPublishing("rtsps://localhost:8322/rtsps_path",
|
||||
gortsplib.Tracks{track})
|
||||
err = source2.StartRecording("rtsps://localhost:8322/rtsps_path",
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source2.Close()
|
||||
|
||||
@@ -63,8 +60,8 @@ func TestMetrics(t *testing.T) {
|
||||
err = conn.InitializeClient(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{ // 1920x1080 baseline
|
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
||||
@@ -67,17 +67,6 @@ type pathParent interface {
|
||||
onPathClose(*path)
|
||||
}
|
||||
|
||||
type pathRTSPSession interface {
|
||||
isRTSPSession()
|
||||
}
|
||||
|
||||
type pathReaderState int
|
||||
|
||||
const (
|
||||
pathReaderStatePrePlay pathReaderState = iota
|
||||
pathReaderStatePlay
|
||||
)
|
||||
|
||||
type pathOnDemandState int
|
||||
|
||||
const (
|
||||
@@ -93,7 +82,7 @@ type pathSourceStaticSetReadyRes struct {
|
||||
}
|
||||
|
||||
type pathSourceStaticSetReadyReq struct {
|
||||
tracks gortsplib.Tracks
|
||||
medias media.Medias
|
||||
generateRTPPackets bool
|
||||
res chan pathSourceStaticSetReadyRes
|
||||
}
|
||||
@@ -151,11 +140,6 @@ type pathPublisherAddReq struct {
|
||||
res chan pathPublisherAnnounceRes
|
||||
}
|
||||
|
||||
type pathReaderStartReq struct {
|
||||
author reader
|
||||
res chan struct{}
|
||||
}
|
||||
|
||||
type pathPublisherRecordRes struct {
|
||||
stream *stream
|
||||
err error
|
||||
@@ -163,16 +147,11 @@ type pathPublisherRecordRes struct {
|
||||
|
||||
type pathPublisherStartReq struct {
|
||||
author publisher
|
||||
tracks gortsplib.Tracks
|
||||
medias media.Medias
|
||||
generateRTPPackets bool
|
||||
res chan pathPublisherRecordRes
|
||||
}
|
||||
|
||||
type pathReaderStopReq struct {
|
||||
author reader
|
||||
res chan struct{}
|
||||
}
|
||||
|
||||
type pathPublisherStopReq struct {
|
||||
author publisher
|
||||
res chan struct{}
|
||||
@@ -225,7 +204,7 @@ type path struct {
|
||||
source source
|
||||
bytesReceived *uint64
|
||||
stream *stream
|
||||
readers map[reader]pathReaderState
|
||||
readers map[reader]struct{}
|
||||
describeRequestsOnHold []pathDescribeReq
|
||||
readerAddRequestsOnHold []pathReaderAddReq
|
||||
onDemandCmd *externalcmd.Cmd
|
||||
@@ -245,10 +224,8 @@ type path struct {
|
||||
chPublisherAdd chan pathPublisherAddReq
|
||||
chPublisherStart chan pathPublisherStartReq
|
||||
chPublisherStop chan pathPublisherStopReq
|
||||
chReaderRemove chan pathReaderRemoveReq
|
||||
chReaderAdd chan pathReaderAddReq
|
||||
chReaderStart chan pathReaderStartReq
|
||||
chReaderStop chan pathReaderStopReq
|
||||
chReaderRemove chan pathReaderRemoveReq
|
||||
chAPIPathsList chan pathAPIPathsListSubReq
|
||||
}
|
||||
|
||||
@@ -283,7 +260,7 @@ func newPath(
|
||||
ctx: ctx,
|
||||
ctxCancel: ctxCancel,
|
||||
bytesReceived: new(uint64),
|
||||
readers: make(map[reader]pathReaderState),
|
||||
readers: make(map[reader]struct{}),
|
||||
onDemandStaticSourceReadyTimer: newEmptyTimer(),
|
||||
onDemandStaticSourceCloseTimer: newEmptyTimer(),
|
||||
onDemandPublisherReadyTimer: newEmptyTimer(),
|
||||
@@ -295,10 +272,8 @@ func newPath(
|
||||
chPublisherAdd: make(chan pathPublisherAddReq),
|
||||
chPublisherStart: make(chan pathPublisherStartReq),
|
||||
chPublisherStop: make(chan pathPublisherStopReq),
|
||||
chReaderRemove: make(chan pathReaderRemoveReq),
|
||||
chReaderAdd: make(chan pathReaderAddReq),
|
||||
chReaderStart: make(chan pathReaderStartReq),
|
||||
chReaderStop: make(chan pathReaderStopReq),
|
||||
chReaderRemove: make(chan pathReaderRemoveReq),
|
||||
chAPIPathsList: make(chan pathAPIPathsListSubReq),
|
||||
}
|
||||
|
||||
@@ -436,7 +411,7 @@ func (pa *path) run() {
|
||||
}
|
||||
|
||||
case req := <-pa.chSourceStaticSetReady:
|
||||
err := pa.sourceSetReady(req.tracks, req.generateRTPPackets)
|
||||
err := pa.sourceSetReady(req.medias, req.generateRTPPackets)
|
||||
if err != nil {
|
||||
req.res <- pathSourceStaticSetReadyRes{err: err}
|
||||
} else {
|
||||
@@ -454,7 +429,7 @@ func (pa *path) run() {
|
||||
pa.describeRequestsOnHold = nil
|
||||
|
||||
for _, req := range pa.readerAddRequestsOnHold {
|
||||
pa.handleReaderSetupPlayPost(req)
|
||||
pa.handleReaderAddPost(req)
|
||||
}
|
||||
pa.readerAddRequestsOnHold = nil
|
||||
}
|
||||
@@ -492,13 +467,20 @@ func (pa *path) run() {
|
||||
}
|
||||
|
||||
case req := <-pa.chPublisherAdd:
|
||||
pa.handlePublisherAnnounce(req)
|
||||
pa.handlePublisherAdd(req)
|
||||
|
||||
case req := <-pa.chPublisherStart:
|
||||
pa.handlePublisherRecord(req)
|
||||
pa.handlePublisherStart(req)
|
||||
|
||||
case req := <-pa.chPublisherStop:
|
||||
pa.handlePublisherPause(req)
|
||||
pa.handlePublisherStop(req)
|
||||
|
||||
if pa.shouldClose() {
|
||||
return fmt.Errorf("not in use")
|
||||
}
|
||||
|
||||
case req := <-pa.chReaderAdd:
|
||||
pa.handleReaderAdd(req)
|
||||
|
||||
if pa.shouldClose() {
|
||||
return fmt.Errorf("not in use")
|
||||
@@ -507,19 +489,6 @@ func (pa *path) run() {
|
||||
case req := <-pa.chReaderRemove:
|
||||
pa.handleReaderRemove(req)
|
||||
|
||||
case req := <-pa.chReaderAdd:
|
||||
pa.handleReaderSetupPlay(req)
|
||||
|
||||
if pa.shouldClose() {
|
||||
return fmt.Errorf("not in use")
|
||||
}
|
||||
|
||||
case req := <-pa.chReaderStart:
|
||||
pa.handleReaderPlay(req)
|
||||
|
||||
case req := <-pa.chReaderStop:
|
||||
pa.handleReaderPause(req)
|
||||
|
||||
case req := <-pa.chAPIPathsList:
|
||||
pa.handleAPIPathsList(req)
|
||||
|
||||
@@ -667,8 +636,8 @@ func (pa *path) onDemandPublisherStop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *path) sourceSetReady(tracks gortsplib.Tracks, allocateEncoder bool) error {
|
||||
stream, err := newStream(tracks, allocateEncoder, pa.bytesReceived)
|
||||
func (pa *path) sourceSetReady(medias media.Medias, allocateEncoder bool) error {
|
||||
stream, err := newStream(medias, allocateEncoder, pa.bytesReceived)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -713,12 +682,6 @@ func (pa *path) sourceSetNotReady() {
|
||||
}
|
||||
|
||||
func (pa *path) doReaderRemove(r reader) {
|
||||
state := pa.readers[r]
|
||||
|
||||
if state == pathReaderStatePlay {
|
||||
pa.stream.readerRemove(r)
|
||||
}
|
||||
|
||||
delete(pa.readers, r)
|
||||
}
|
||||
|
||||
@@ -792,7 +755,7 @@ func (pa *path) handlePublisherRemove(req pathPublisherRemoveReq) {
|
||||
close(req.res)
|
||||
}
|
||||
|
||||
func (pa *path) handlePublisherAnnounce(req pathPublisherAddReq) {
|
||||
func (pa *path) handlePublisherAdd(req pathPublisherAddReq) {
|
||||
if pa.conf.Source != "publisher" {
|
||||
req.res <- pathPublisherAnnounceRes{
|
||||
err: fmt.Errorf("can't publish to path '%s' since 'source' is not 'publisher'", pa.name),
|
||||
@@ -816,13 +779,13 @@ func (pa *path) handlePublisherAnnounce(req pathPublisherAddReq) {
|
||||
req.res <- pathPublisherAnnounceRes{path: pa}
|
||||
}
|
||||
|
||||
func (pa *path) handlePublisherRecord(req pathPublisherStartReq) {
|
||||
func (pa *path) handlePublisherStart(req pathPublisherStartReq) {
|
||||
if pa.source != req.author {
|
||||
req.res <- pathPublisherRecordRes{err: fmt.Errorf("publisher is not assigned to this path anymore")}
|
||||
return
|
||||
}
|
||||
|
||||
err := pa.sourceSetReady(req.tracks, req.generateRTPPackets)
|
||||
err := pa.sourceSetReady(req.medias, req.generateRTPPackets)
|
||||
if err != nil {
|
||||
req.res <- pathPublisherRecordRes{err: err}
|
||||
return
|
||||
@@ -842,7 +805,7 @@ func (pa *path) handlePublisherRecord(req pathPublisherStartReq) {
|
||||
pa.describeRequestsOnHold = nil
|
||||
|
||||
for _, req := range pa.readerAddRequestsOnHold {
|
||||
pa.handleReaderSetupPlayPost(req)
|
||||
pa.handleReaderAddPost(req)
|
||||
}
|
||||
pa.readerAddRequestsOnHold = nil
|
||||
}
|
||||
@@ -850,7 +813,7 @@ func (pa *path) handlePublisherRecord(req pathPublisherStartReq) {
|
||||
req.res <- pathPublisherRecordRes{stream: pa.stream}
|
||||
}
|
||||
|
||||
func (pa *path) handlePublisherPause(req pathPublisherStopReq) {
|
||||
func (pa *path) handlePublisherStop(req pathPublisherStopReq) {
|
||||
if req.author == pa.source && pa.stream != nil {
|
||||
if pa.hasOnDemandPublisher() && pa.onDemandPublisherState != pathOnDemandStateInitial {
|
||||
pa.onDemandPublisherStop()
|
||||
@@ -880,9 +843,9 @@ func (pa *path) handleReaderRemove(req pathReaderRemoveReq) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *path) handleReaderSetupPlay(req pathReaderAddReq) {
|
||||
func (pa *path) handleReaderAdd(req pathReaderAddReq) {
|
||||
if pa.stream != nil {
|
||||
pa.handleReaderSetupPlayPost(req)
|
||||
pa.handleReaderAddPost(req)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -905,8 +868,8 @@ func (pa *path) handleReaderSetupPlay(req pathReaderAddReq) {
|
||||
req.res <- pathReaderSetupPlayRes{err: pathErrNoOnePublishing{pathName: pa.name}}
|
||||
}
|
||||
|
||||
func (pa *path) handleReaderSetupPlayPost(req pathReaderAddReq) {
|
||||
pa.readers[req.author] = pathReaderStatePrePlay
|
||||
func (pa *path) handleReaderAddPost(req pathReaderAddReq) {
|
||||
pa.readers[req.author] = struct{}{}
|
||||
|
||||
if pa.hasOnDemandStaticSource() {
|
||||
if pa.onDemandStaticSourceState == pathOnDemandStateClosing {
|
||||
@@ -928,22 +891,6 @@ func (pa *path) handleReaderSetupPlayPost(req pathReaderAddReq) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *path) handleReaderPlay(req pathReaderStartReq) {
|
||||
pa.readers[req.author] = pathReaderStatePlay
|
||||
|
||||
pa.stream.readerAdd(req.author)
|
||||
|
||||
close(req.res)
|
||||
}
|
||||
|
||||
func (pa *path) handleReaderPause(req pathReaderStopReq) {
|
||||
if state, ok := pa.readers[req.author]; ok && state == pathReaderStatePlay {
|
||||
pa.readers[req.author] = pathReaderStatePrePlay
|
||||
pa.stream.readerRemove(req.author)
|
||||
}
|
||||
close(req.res)
|
||||
}
|
||||
|
||||
func (pa *path) handleAPIPathsList(req pathAPIPathsListSubReq) {
|
||||
req.data.Items[pa.name] = pathAPIPathsListItem{
|
||||
ConfName: pa.confName,
|
||||
@@ -959,7 +906,7 @@ func (pa *path) handleAPIPathsList(req pathAPIPathsListSubReq) {
|
||||
if pa.stream == nil {
|
||||
return []string{}
|
||||
}
|
||||
return sourceTrackNames(pa.stream.tracks())
|
||||
return mediasDescription(pa.stream.medias())
|
||||
}(),
|
||||
BytesReceived: atomic.LoadUint64(pa.bytesReceived),
|
||||
Readers: func() []interface{} {
|
||||
@@ -1025,7 +972,7 @@ func (pa *path) publisherRemove(req pathPublisherRemoveReq) {
|
||||
}
|
||||
}
|
||||
|
||||
// publisherAnnounce is called by a publisher through pathManager.
|
||||
// publisherAdd is called by a publisher through pathManager.
|
||||
func (pa *path) publisherAdd(req pathPublisherAddReq) pathPublisherAnnounceRes {
|
||||
select {
|
||||
case pa.chPublisherAdd <- req:
|
||||
@@ -1035,7 +982,7 @@ func (pa *path) publisherAdd(req pathPublisherAddReq) pathPublisherAnnounceRes {
|
||||
}
|
||||
}
|
||||
|
||||
// publisherRecord is called by a publisher.
|
||||
// publisherStart is called by a publisher.
|
||||
func (pa *path) publisherStart(req pathPublisherStartReq) pathPublisherRecordRes {
|
||||
req.res = make(chan pathPublisherRecordRes)
|
||||
select {
|
||||
@@ -1046,7 +993,7 @@ func (pa *path) publisherStart(req pathPublisherStartReq) pathPublisherRecordRes
|
||||
}
|
||||
}
|
||||
|
||||
// publisherPause is called by a publisher.
|
||||
// publisherStop is called by a publisher.
|
||||
func (pa *path) publisherStop(req pathPublisherStopReq) {
|
||||
req.res = make(chan struct{})
|
||||
select {
|
||||
@@ -1056,17 +1003,7 @@ func (pa *path) publisherStop(req pathPublisherStopReq) {
|
||||
}
|
||||
}
|
||||
|
||||
// readerRemove is called by a reader.
|
||||
func (pa *path) readerRemove(req pathReaderRemoveReq) {
|
||||
req.res = make(chan struct{})
|
||||
select {
|
||||
case pa.chReaderRemove <- req:
|
||||
<-req.res
|
||||
case <-pa.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// readerSetupPlay is called by a reader through pathManager.
|
||||
// readerAdd is called by a reader through pathManager.
|
||||
func (pa *path) readerAdd(req pathReaderAddReq) pathReaderSetupPlayRes {
|
||||
select {
|
||||
case pa.chReaderAdd <- req:
|
||||
@@ -1076,21 +1013,11 @@ func (pa *path) readerAdd(req pathReaderAddReq) pathReaderSetupPlayRes {
|
||||
}
|
||||
}
|
||||
|
||||
// readerPlay is called by a reader.
|
||||
func (pa *path) readerStart(req pathReaderStartReq) {
|
||||
// readerRemove is called by a reader.
|
||||
func (pa *path) readerRemove(req pathReaderRemoveReq) {
|
||||
req.res = make(chan struct{})
|
||||
select {
|
||||
case pa.chReaderStart <- req:
|
||||
<-req.res
|
||||
case <-pa.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// readerPause is called by a reader.
|
||||
func (pa *path) readerStop(req pathReaderStopReq) {
|
||||
req.res = make(chan struct{})
|
||||
select {
|
||||
case pa.chReaderStop <- req:
|
||||
case pa.chReaderRemove <- req:
|
||||
<-req.res
|
||||
case <-pa.ctx.Done():
|
||||
}
|
||||
|
||||
@@ -3,6 +3,5 @@ package core
|
||||
// reader is an entity that can read a stream.
|
||||
type reader interface {
|
||||
close()
|
||||
onReaderData(data)
|
||||
apiReaderDescribe() interface{}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||
"github.com/aler9/rtsp-simple-server/internal/rpicamera"
|
||||
@@ -37,32 +38,34 @@ func (s *rpiCameraSource) Log(level logger.Level, format string, args ...interfa
|
||||
|
||||
// run implements sourceStaticImpl.
|
||||
func (s *rpiCameraSource) run(ctx context.Context) error {
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
PacketizationMode: 1,
|
||||
medi := &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{&format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}},
|
||||
}
|
||||
tracks := gortsplib.Tracks{track}
|
||||
medias := media.Medias{medi}
|
||||
var stream *stream
|
||||
|
||||
onData := func(dts time.Duration, nalus [][]byte) {
|
||||
if stream == nil {
|
||||
res := s.parent.sourceStaticImplSetReady(pathSourceStaticSetReadyReq{
|
||||
tracks: tracks,
|
||||
medias: medias,
|
||||
generateRTPPackets: true,
|
||||
})
|
||||
if res.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Log(logger.Info, "ready: %s", sourceTrackInfo(tracks))
|
||||
s.Log(logger.Info, "ready: %s", sourceMediaInfo(medias))
|
||||
stream = res.stream
|
||||
}
|
||||
|
||||
err := stream.writeData(&dataH264{
|
||||
trackID: 0,
|
||||
pts: dts,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
err := stream.writeData(medi, medi.Formats[0], &dataH264{
|
||||
pts: dts,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/pkg/ringbuffer"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/ringbuffer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/notedit/rtmp/format/flv/flvio"
|
||||
|
||||
@@ -76,7 +77,6 @@ type rtmpConn struct {
|
||||
uuid uuid.UUID
|
||||
created time.Time
|
||||
path *path
|
||||
ringBuffer *ringbuffer.RingBuffer // read
|
||||
state rtmpConnState
|
||||
stateMutex sync.Mutex
|
||||
}
|
||||
@@ -253,55 +253,42 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
||||
c.state = rtmpConnStateRead
|
||||
c.stateMutex.Unlock()
|
||||
|
||||
var videoTrack *gortsplib.TrackH264
|
||||
videoTrackID := -1
|
||||
var audioTrack *gortsplib.TrackMPEG4Audio
|
||||
audioTrackID := -1
|
||||
var videoFormat *format.H264
|
||||
videoMedia := res.stream.medias().FindFormat(&videoFormat)
|
||||
|
||||
for i, track := range res.stream.tracks() {
|
||||
switch tt := track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
if videoTrack != nil {
|
||||
return fmt.Errorf("can't read track %d with RTMP: too many tracks", i+1)
|
||||
}
|
||||
var audioFormat *format.MPEG4Audio
|
||||
audioMedia := res.stream.medias().FindFormat(&audioFormat)
|
||||
|
||||
videoTrack = tt
|
||||
videoTrackID = i
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
if audioTrack != nil {
|
||||
return fmt.Errorf("can't read track %d with RTMP: too many tracks", i+1)
|
||||
}
|
||||
|
||||
audioTrack = tt
|
||||
audioTrackID = i
|
||||
}
|
||||
}
|
||||
|
||||
if videoTrack == nil && audioTrack == nil {
|
||||
if videoFormat == nil && audioFormat == nil {
|
||||
return fmt.Errorf("the stream doesn't contain an H264 track or an AAC track")
|
||||
}
|
||||
|
||||
c.ringBuffer, _ = ringbuffer.New(uint64(c.readBufferCount))
|
||||
ringBuffer, _ := ringbuffer.New(uint64(c.readBufferCount))
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.ringBuffer.Close()
|
||||
ringBuffer.Close()
|
||||
}()
|
||||
|
||||
c.path.readerStart(pathReaderStartReq{
|
||||
author: c,
|
||||
})
|
||||
var medias media.Medias
|
||||
if videoMedia != nil {
|
||||
medias = append(medias, videoMedia)
|
||||
|
||||
var tracks []gortsplib.Track
|
||||
if videoTrack != nil {
|
||||
tracks = append(tracks, videoTrack)
|
||||
res.stream.readerAdd(c, videoMedia, videoFormat, func(dat data) {
|
||||
ringBuffer.Push(dat)
|
||||
})
|
||||
}
|
||||
if audioTrack != nil {
|
||||
tracks = append(tracks, audioTrack)
|
||||
if audioMedia != nil {
|
||||
medias = append(medias, audioMedia)
|
||||
|
||||
res.stream.readerAdd(c, audioMedia, audioFormat, func(dat data) {
|
||||
ringBuffer.Push(dat)
|
||||
})
|
||||
}
|
||||
|
||||
defer res.stream.readerRemove(c)
|
||||
|
||||
c.log(logger.Info, "is reading from path '%s', %s",
|
||||
c.path.Name(), sourceTrackInfo(tracks))
|
||||
c.path.Name(), sourceMediaInfo(medias))
|
||||
|
||||
if c.path.Conf().RunOnRead != "" {
|
||||
c.log(logger.Info, "runOnRead command started")
|
||||
@@ -319,7 +306,7 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
||||
}()
|
||||
}
|
||||
|
||||
err := c.conn.WriteTracks(videoTrack, audioTrack)
|
||||
err := c.conn.WriteTracks(videoFormat, audioFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -337,15 +324,14 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
||||
var videoDTSExtractor *h264.DTSExtractor
|
||||
|
||||
for {
|
||||
item, ok := c.ringBuffer.Pull()
|
||||
item, ok := ringBuffer.Pull()
|
||||
if !ok {
|
||||
return fmt.Errorf("terminated")
|
||||
}
|
||||
data := item.(data)
|
||||
|
||||
if videoTrack != nil && data.getTrackID() == videoTrackID {
|
||||
tdata := data.(*dataH264)
|
||||
|
||||
switch tdata := data.(type) {
|
||||
case *dataH264:
|
||||
if tdata.nalus == nil {
|
||||
continue
|
||||
}
|
||||
@@ -423,9 +409,8 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if audioTrack != nil && data.getTrackID() == audioTrackID {
|
||||
tdata := data.(*dataMPEG4Audio)
|
||||
|
||||
case *dataMPEG4Audio:
|
||||
if tdata.aus == nil {
|
||||
continue
|
||||
}
|
||||
@@ -436,7 +421,7 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
||||
}
|
||||
pts := tdata.pts - audioStartPTS
|
||||
|
||||
if videoTrack != nil {
|
||||
if videoFormat != nil {
|
||||
if !videoFirstIDRFound {
|
||||
continue
|
||||
}
|
||||
@@ -458,7 +443,7 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
||||
AACType: flvio.AAC_RAW,
|
||||
Payload: au,
|
||||
DTS: pts + time.Duration(i)*mpeg4audio.SamplesPerAccessUnit*
|
||||
time.Second/time.Duration(audioTrack.ClockRate()),
|
||||
time.Second/time.Duration(audioFormat.ClockRate()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -502,28 +487,34 @@ func (c *rtmpConn) runPublish(ctx context.Context, u *url.URL) error {
|
||||
c.state = rtmpConnStatePublish
|
||||
c.stateMutex.Unlock()
|
||||
|
||||
videoTrack, audioTrack, err := c.conn.ReadTracks()
|
||||
videoFormat, audioFormat, err := c.conn.ReadTracks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tracks gortsplib.Tracks
|
||||
videoTrackID := -1
|
||||
audioTrackID := -1
|
||||
var medias media.Medias
|
||||
var videoMedia *media.Media
|
||||
var audioMedia *media.Media
|
||||
|
||||
if videoTrack != nil {
|
||||
videoTrackID = len(tracks)
|
||||
tracks = append(tracks, videoTrack)
|
||||
if videoFormat != nil {
|
||||
videoMedia = &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{videoFormat},
|
||||
}
|
||||
medias = append(medias, videoMedia)
|
||||
}
|
||||
|
||||
if audioTrack != nil {
|
||||
audioTrackID = len(tracks)
|
||||
tracks = append(tracks, audioTrack)
|
||||
if audioFormat != nil {
|
||||
audioMedia = &media.Media{
|
||||
Type: media.TypeAudio,
|
||||
Formats: []format.Format{audioFormat},
|
||||
}
|
||||
medias = append(medias, audioMedia)
|
||||
}
|
||||
|
||||
rres := c.path.publisherStart(pathPublisherStartReq{
|
||||
author: c,
|
||||
tracks: tracks,
|
||||
medias: medias,
|
||||
generateRTPPackets: true,
|
||||
})
|
||||
if rres.err != nil {
|
||||
@@ -532,7 +523,7 @@ func (c *rtmpConn) runPublish(ctx context.Context, u *url.URL) error {
|
||||
|
||||
c.log(logger.Info, "is publishing to path '%s', %s",
|
||||
c.path.Name(),
|
||||
sourceTrackInfo(tracks))
|
||||
sourceMediaInfo(medias))
|
||||
|
||||
// disable write deadline to allow outgoing acknowledges
|
||||
c.nconn.SetWriteDeadline(time.Time{})
|
||||
@@ -558,17 +549,16 @@ func (c *rtmpConn) runPublish(ctx context.Context, u *url.URL) error {
|
||||
conf.PPS,
|
||||
}
|
||||
|
||||
err := rres.stream.writeData(&dataH264{
|
||||
trackID: videoTrackID,
|
||||
pts: tmsg.DTS + tmsg.PTSDelta,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
err := rres.stream.writeData(videoMedia, videoFormat, &dataH264{
|
||||
pts: tmsg.DTS + tmsg.PTSDelta,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
c.log(logger.Warn, "%v", err)
|
||||
}
|
||||
} else if tmsg.H264Type == flvio.AVC_NALU {
|
||||
if videoTrack == nil {
|
||||
if videoFormat == nil {
|
||||
return fmt.Errorf("received an H264 packet, but track is not set up")
|
||||
}
|
||||
|
||||
@@ -597,11 +587,10 @@ func (c *rtmpConn) runPublish(ctx context.Context, u *url.URL) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = rres.stream.writeData(&dataH264{
|
||||
trackID: videoTrackID,
|
||||
pts: tmsg.DTS + tmsg.PTSDelta,
|
||||
nalus: validNALUs,
|
||||
ntp: time.Now(),
|
||||
err = rres.stream.writeData(videoMedia, videoFormat, &dataH264{
|
||||
pts: tmsg.DTS + tmsg.PTSDelta,
|
||||
nalus: validNALUs,
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
c.log(logger.Warn, "%v", err)
|
||||
@@ -610,15 +599,14 @@ func (c *rtmpConn) runPublish(ctx context.Context, u *url.URL) error {
|
||||
|
||||
case *message.MsgAudio:
|
||||
if tmsg.AACType == flvio.AAC_RAW {
|
||||
if audioTrack == nil {
|
||||
if audioFormat == nil {
|
||||
return fmt.Errorf("received an AAC packet, but track is not set up")
|
||||
}
|
||||
|
||||
err := rres.stream.writeData(&dataMPEG4Audio{
|
||||
trackID: audioTrackID,
|
||||
pts: tmsg.DTS,
|
||||
aus: [][]byte{tmsg.Payload},
|
||||
ntp: time.Now(),
|
||||
err := rres.stream.writeData(audioMedia, audioFormat, &dataMPEG4Audio{
|
||||
pts: tmsg.DTS,
|
||||
aus: [][]byte{tmsg.Payload},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
c.log(logger.Warn, "%v", err)
|
||||
@@ -674,11 +662,6 @@ func (c *rtmpConn) authenticate(
|
||||
return nil
|
||||
}
|
||||
|
||||
// onReaderData implements reader.
|
||||
func (c *rtmpConn) onReaderData(data data) {
|
||||
c.ringBuffer.Push(data)
|
||||
}
|
||||
|
||||
// apiReaderDescribe implements reader.
|
||||
func (c *rtmpConn) apiReaderDescribe() interface{} {
|
||||
return struct {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/notedit/rtmp/format/flv/flvio"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -68,8 +68,8 @@ func TestRTMPServerPublishRead(t *testing.T) {
|
||||
err = conn1.InitializeClient(u, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{ // 1920x1080 baseline
|
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||
@@ -79,8 +79,8 @@ func TestRTMPServerPublishRead(t *testing.T) {
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
audioTrack := &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
audioTrack := &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -190,8 +190,8 @@ func TestRTMPServerAuth(t *testing.T) {
|
||||
err = conn1.InitializeClient(u1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
|
||||
@@ -254,8 +254,8 @@ func TestRTMPServerAuthFail(t *testing.T) {
|
||||
err = conn1.InitializeClient(u1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
|
||||
@@ -309,8 +309,8 @@ func TestRTMPServerAuthFail(t *testing.T) {
|
||||
err = conn1.InitializeClient(u1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
|
||||
@@ -363,8 +363,8 @@ func TestRTMPServerAuthFail(t *testing.T) {
|
||||
err = conn1.InitializeClient(u1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/notedit/rtmp/format/flv/flvio"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||
@@ -115,34 +116,40 @@ func (s *rtmpSource) run(ctx context.Context) error {
|
||||
|
||||
nconn.SetWriteDeadline(time.Time{})
|
||||
nconn.SetReadDeadline(time.Now().Add(time.Duration(s.readTimeout)))
|
||||
videoTrack, audioTrack, err := conn.ReadTracks()
|
||||
videoFormat, audioFormat, err := conn.ReadTracks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tracks gortsplib.Tracks
|
||||
videoTrackID := -1
|
||||
audioTrackID := -1
|
||||
var medias media.Medias
|
||||
var videoMedia *media.Media
|
||||
var audioMedia *media.Media
|
||||
|
||||
if videoTrack != nil {
|
||||
videoTrackID = len(tracks)
|
||||
tracks = append(tracks, videoTrack)
|
||||
if videoFormat != nil {
|
||||
videoMedia = &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{videoFormat},
|
||||
}
|
||||
medias = append(medias, videoMedia)
|
||||
}
|
||||
|
||||
if audioTrack != nil {
|
||||
audioTrackID = len(tracks)
|
||||
tracks = append(tracks, audioTrack)
|
||||
if audioFormat != nil {
|
||||
audioMedia = &media.Media{
|
||||
Type: media.TypeAudio,
|
||||
Formats: []format.Format{audioFormat},
|
||||
}
|
||||
medias = append(medias, audioMedia)
|
||||
}
|
||||
|
||||
res := s.parent.sourceStaticImplSetReady(pathSourceStaticSetReadyReq{
|
||||
tracks: tracks,
|
||||
medias: medias,
|
||||
generateRTPPackets: true,
|
||||
})
|
||||
if res.err != nil {
|
||||
return res.err
|
||||
}
|
||||
|
||||
s.Log(logger.Info, "ready: %s", sourceTrackInfo(tracks))
|
||||
s.Log(logger.Info, "ready: %s", sourceMediaInfo(medias))
|
||||
|
||||
defer func() {
|
||||
s.parent.sourceStaticImplSetNotReady(pathSourceStaticSetNotReadyReq{})
|
||||
@@ -161,7 +168,7 @@ func (s *rtmpSource) run(ctx context.Context) error {
|
||||
switch tmsg := msg.(type) {
|
||||
case *message.MsgVideo:
|
||||
if tmsg.H264Type == flvio.AVC_NALU {
|
||||
if videoTrack == nil {
|
||||
if videoFormat == nil {
|
||||
return fmt.Errorf("received an H264 packet, but track is not set up")
|
||||
}
|
||||
|
||||
@@ -170,11 +177,10 @@ func (s *rtmpSource) run(ctx context.Context) error {
|
||||
return fmt.Errorf("unable to decode AVCC: %v", err)
|
||||
}
|
||||
|
||||
err = res.stream.writeData(&dataH264{
|
||||
trackID: videoTrackID,
|
||||
pts: tmsg.DTS + tmsg.PTSDelta,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
err = res.stream.writeData(videoMedia, videoFormat, &dataH264{
|
||||
pts: tmsg.DTS + tmsg.PTSDelta,
|
||||
nalus: nalus,
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
@@ -183,15 +189,14 @@ func (s *rtmpSource) run(ctx context.Context) error {
|
||||
|
||||
case *message.MsgAudio:
|
||||
if tmsg.AACType == flvio.AAC_RAW {
|
||||
if audioTrack == nil {
|
||||
if audioFormat == nil {
|
||||
return fmt.Errorf("received an AAC packet, but track is not set up")
|
||||
}
|
||||
|
||||
err := res.stream.writeData(&dataMPEG4Audio{
|
||||
trackID: audioTrackID,
|
||||
pts: tmsg.DTS,
|
||||
aus: [][]byte{tmsg.Payload},
|
||||
ntp: time.Now(),
|
||||
err := res.stream.writeData(audioMedia, audioFormat, &dataMPEG4Audio{
|
||||
pts: tmsg.DTS,
|
||||
aus: [][]byte{tmsg.Payload},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/notedit/rtmp/format/flv/flvio"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/rtmp"
|
||||
@@ -57,8 +59,8 @@ func TestRTMPSource(t *testing.T) {
|
||||
_, _, err = conn.InitializeServer()
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{ // 1920x1080 baseline
|
||||
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||
@@ -68,8 +70,8 @@ func TestRTMPSource(t *testing.T) {
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
audioTrack := &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
audioTrack := &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -114,18 +116,7 @@ func TestRTMPSource(t *testing.T) {
|
||||
defer p.Close()
|
||||
}
|
||||
|
||||
c := gortsplib.Client{
|
||||
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
require.Equal(t, []byte{
|
||||
0x18, 0x0, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
|
||||
0x0, 0x78, 0x2, 0x27, 0xe5, 0x84, 0x0, 0x0,
|
||||
0x3, 0x0, 0x4, 0x0, 0x0, 0x3, 0x0, 0xf0,
|
||||
0x3c, 0x60, 0xc9, 0x20, 0x0, 0x4, 0x8, 0x6,
|
||||
0x7, 0x8, 0x0, 0x4, 0x5, 0x2, 0x3, 0x4,
|
||||
}, ctx.Packet.Payload)
|
||||
close(received)
|
||||
},
|
||||
}
|
||||
c := gortsplib.Client{}
|
||||
|
||||
u, err := url.Parse("rtsp://127.0.0.1:8554/proxied")
|
||||
require.NoError(t, err)
|
||||
@@ -134,10 +125,24 @@ func TestRTMPSource(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.SetupAndPlay(tracks, baseURL)
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||
require.Equal(t, []byte{
|
||||
0x18, 0x0, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9,
|
||||
0x0, 0x78, 0x2, 0x27, 0xe5, 0x84, 0x0, 0x0,
|
||||
0x3, 0x0, 0x4, 0x0, 0x0, 0x3, 0x0, 0xf0,
|
||||
0x3c, 0x60, 0xc9, 0x20, 0x0, 0x4, 0x8, 0x6,
|
||||
0x7, 0x8, 0x0, 0x4, 0x5, 0x2, 0x3, 0x4,
|
||||
}, pkt.Payload)
|
||||
close(received)
|
||||
})
|
||||
|
||||
_, err = c.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
close(connected)
|
||||
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/auth"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/auth"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||
@@ -112,13 +113,14 @@ func (c *rtspConn) ip() net.IP {
|
||||
}
|
||||
|
||||
func (c *rtspConn) authenticate(
|
||||
pathName string,
|
||||
path string,
|
||||
query string,
|
||||
pathIPs []fmt.Stringer,
|
||||
pathUser conf.Credential,
|
||||
pathPass conf.Credential,
|
||||
isPublishing bool,
|
||||
req *base.Request,
|
||||
query string,
|
||||
baseURL *url.URL,
|
||||
) error {
|
||||
if c.externalAuthenticationURL != "" {
|
||||
username := ""
|
||||
@@ -136,7 +138,7 @@ func (c *rtspConn) authenticate(
|
||||
c.ip().String(),
|
||||
username,
|
||||
password,
|
||||
pathName,
|
||||
path,
|
||||
isPublishing,
|
||||
query)
|
||||
if err != nil {
|
||||
@@ -193,7 +195,7 @@ func (c *rtspConn) authenticate(
|
||||
c.authValidator = auth.NewValidator(string(pathUser), string(pathPass), c.authMethods)
|
||||
}
|
||||
|
||||
err := c.authValidator.ValidateRequest(req)
|
||||
err := c.authValidator.ValidateRequest(req, baseURL)
|
||||
if err != nil {
|
||||
c.authFailures++
|
||||
|
||||
@@ -260,7 +262,7 @@ func (c *rtspConn) onDescribe(ctx *gortsplib.ServerHandlerOnDescribeCtx,
|
||||
pathUser conf.Credential,
|
||||
pathPass conf.Credential,
|
||||
) error {
|
||||
return c.authenticate(ctx.Path, pathIPs, pathUser, pathPass, false, ctx.Request, ctx.Query)
|
||||
return c.authenticate(ctx.Path, ctx.Query, pathIPs, pathUser, pathPass, false, ctx.Request, nil)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/pkg/headers"
|
||||
"github.com/aler9/gortsplib/pkg/liberrors"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2/pkg/liberrors"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||
"github.com/aler9/rtsp-simple-server/internal/externalcmd"
|
||||
@@ -349,12 +349,6 @@ func (s *rtspServer) OnPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Resp
|
||||
return se.onPause(ctx)
|
||||
}
|
||||
|
||||
// OnPacketRTP implements gortsplib.ServerHandlerOnPacketRTP.
|
||||
func (s *rtspServer) OnPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
|
||||
se := ctx.Session.UserData().(*rtspSession)
|
||||
se.onPacketRTP(ctx)
|
||||
}
|
||||
|
||||
// OnDecodeError implements gortsplib.ServerHandlerOnOnDecodeError.
|
||||
func (s *rtspServer) OnDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
|
||||
se := ctx.Session.UserData().(*rtspSession)
|
||||
|
||||
@@ -3,8 +3,9 @@ package core
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -44,18 +45,13 @@ func TestRTSPServerAuth(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
source := gortsplib.Client{}
|
||||
|
||||
err := source.StartPublishing(
|
||||
err := source.StartRecording(
|
||||
"rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value",
|
||||
gortsplib.Tracks{track})
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -76,10 +72,13 @@ func TestRTSPServerAuth(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer reader.Close()
|
||||
|
||||
tracks, baseURL, _, err := reader.Describe(u)
|
||||
medias, baseURL, _, err := reader.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = reader.SetupAndPlay(tracks, baseURL)
|
||||
err = reader.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = reader.Play(nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
@@ -94,18 +93,13 @@ func TestRTSPServerAuth(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
source := gortsplib.Client{}
|
||||
|
||||
err := source.StartPublishing(
|
||||
err := source.StartRecording(
|
||||
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream",
|
||||
gortsplib.Tracks{track})
|
||||
media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
})
|
||||
@@ -143,18 +137,13 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
c := gortsplib.Client{}
|
||||
|
||||
err := c.StartPublishing(
|
||||
err := c.StartRecording(
|
||||
"rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream",
|
||||
gortsplib.Tracks{track},
|
||||
media.Medias{medi},
|
||||
)
|
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||
})
|
||||
@@ -214,18 +203,13 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
c := gortsplib.Client{}
|
||||
|
||||
err := c.StartPublishing(
|
||||
err := c.StartRecording(
|
||||
"rtsp://localhost:8554/test/stream",
|
||||
gortsplib.Tracks{track},
|
||||
media.Medias{medi},
|
||||
)
|
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||
})
|
||||
@@ -241,18 +225,13 @@ func TestRTSPServerAuthFail(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer a.close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
c := gortsplib.Client{}
|
||||
|
||||
err = c.StartPublishing(
|
||||
err = c.StartRecording(
|
||||
"rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value",
|
||||
gortsplib.Tracks{track},
|
||||
media.Medias{medi},
|
||||
)
|
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)")
|
||||
})
|
||||
@@ -277,24 +256,17 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
defer p.Close()
|
||||
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
medi := testMediaH264
|
||||
|
||||
s1 := gortsplib.Client{}
|
||||
|
||||
err := s1.StartPublishing("rtsp://localhost:8554/teststream",
|
||||
gortsplib.Tracks{track})
|
||||
err := s1.StartRecording("rtsp://localhost:8554/teststream", media.Medias{medi})
|
||||
require.NoError(t, err)
|
||||
defer s1.Close()
|
||||
|
||||
s2 := gortsplib.Client{}
|
||||
|
||||
err = s2.StartPublishing("rtsp://localhost:8554/teststream",
|
||||
gortsplib.Tracks{track})
|
||||
err = s2.StartRecording("rtsp://localhost:8554/teststream", media.Medias{medi})
|
||||
if ca == "enabled" {
|
||||
require.NoError(t, err)
|
||||
defer s2.Close()
|
||||
@@ -304,16 +276,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
||||
|
||||
frameRecv := make(chan struct{})
|
||||
|
||||
c := gortsplib.Client{
|
||||
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
if ca == "enabled" {
|
||||
require.Equal(t, []byte{0x05, 0x06, 0x07, 0x08}, ctx.Packet.Payload)
|
||||
} else {
|
||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, ctx.Packet.Payload)
|
||||
}
|
||||
close(frameRecv)
|
||||
},
|
||||
}
|
||||
c := gortsplib.Client{}
|
||||
|
||||
u, err := url.Parse("rtsp://localhost:8554/teststream")
|
||||
require.NoError(t, err)
|
||||
@@ -322,16 +285,28 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.SetupAndPlay(tracks, baseURL)
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s1.WritePacketRTP(0, &rtp.Packet{
|
||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||
if ca == "enabled" {
|
||||
require.Equal(t, []byte{0x05, 0x06, 0x07, 0x08}, pkt.Payload)
|
||||
} else {
|
||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload)
|
||||
}
|
||||
close(frameRecv)
|
||||
})
|
||||
|
||||
_, err = c.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = s1.WritePacketRTP(medi, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 0x02,
|
||||
PayloadType: 97,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 57899,
|
||||
Timestamp: 345234345,
|
||||
SSRC: 978651231,
|
||||
@@ -346,10 +321,10 @@ func TestRTSPServerPublisherOverride(t *testing.T) {
|
||||
}
|
||||
|
||||
if ca == "enabled" {
|
||||
err = s2.WritePacketRTP(0, &rtp.Packet{
|
||||
err = s2.WritePacketRTP(medi, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 0x02,
|
||||
PayloadType: 97,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 57899,
|
||||
Timestamp: 345234345,
|
||||
SSRC: 978651231,
|
||||
@@ -388,13 +363,8 @@ func TestRTSPServerFallback(t *testing.T) {
|
||||
defer p1.Close()
|
||||
|
||||
source := gortsplib.Client{}
|
||||
err := source.StartPublishing("rtsp://localhost:8554/path2",
|
||||
gortsplib.Tracks{&gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}})
|
||||
err := source.StartRecording("rtsp://localhost:8554/path2",
|
||||
media.Medias{testMediaH264})
|
||||
require.NoError(t, err)
|
||||
defer source.Close()
|
||||
|
||||
@@ -406,9 +376,9 @@ func TestRTSPServerFallback(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer dest.Close()
|
||||
|
||||
tracks, _, _, err := dest.Describe(u)
|
||||
medias, _, _, err := dest.Describe(u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(tracks))
|
||||
require.Equal(t, 1, len(medias))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
@@ -80,9 +82,6 @@ func (s *rtspSession) close() {
|
||||
s.session.Close()
|
||||
}
|
||||
|
||||
// isRTSPSession implements pathRTSPSession.
|
||||
func (s *rtspSession) isRTSPSession() {}
|
||||
|
||||
func (s *rtspSession) safeState() gortsplib.ServerSessionState {
|
||||
s.stateMutex.Lock()
|
||||
defer s.stateMutex.Unlock()
|
||||
@@ -132,7 +131,7 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
|
||||
pathUser conf.Credential,
|
||||
pathPass conf.Credential,
|
||||
) error {
|
||||
return c.authenticate(ctx.Path, pathIPs, pathUser, pathPass, true, ctx.Request, ctx.Query)
|
||||
return c.authenticate(ctx.Path, ctx.Query, pathIPs, pathUser, pathPass, true, ctx.Request, nil)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -170,7 +169,7 @@ func (s *rtspSession) onAnnounce(c *rtspConn, ctx *gortsplib.ServerHandlerOnAnno
|
||||
func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCtx,
|
||||
) (*base.Response, *gortsplib.ServerStream, error) {
|
||||
// in case the client is setupping a stream with UDP or UDP-multicast, and these
|
||||
// transport protocols are disabled, gortsplib already blocks the request.
|
||||
// transport protocols are disabled, gortsplib/v2 already blocks the request.
|
||||
// we have only to handle the case in which the transport protocol is TCP
|
||||
// and it is disabled.
|
||||
if ctx.Transport == gortsplib.TransportTCP {
|
||||
@@ -191,7 +190,19 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
|
||||
pathUser conf.Credential,
|
||||
pathPass conf.Credential,
|
||||
) error {
|
||||
return c.authenticate(ctx.Path, pathIPs, pathUser, pathPass, false, ctx.Request, ctx.Query)
|
||||
baseURL := &url.URL{
|
||||
Scheme: ctx.Request.URL.Scheme,
|
||||
Host: ctx.Request.URL.Host,
|
||||
Path: func() string {
|
||||
pa := ctx.Path
|
||||
if ctx.Query != "" {
|
||||
pa += "?" + ctx.Query
|
||||
}
|
||||
pa += "/"
|
||||
return pa
|
||||
}(),
|
||||
}
|
||||
return c.authenticate(ctx.Path, ctx.Query, pathIPs, pathUser, pathPass, false, ctx.Request, baseURL)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -222,12 +233,6 @@ func (s *rtspSession) onSetup(c *rtspConn, ctx *gortsplib.ServerHandlerOnSetupCt
|
||||
s.path = res.path
|
||||
s.stream = res.stream
|
||||
|
||||
if ctx.TrackID >= len(res.stream.tracks()) {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusBadRequest,
|
||||
}, nil, fmt.Errorf("track %d does not exist", ctx.TrackID)
|
||||
}
|
||||
|
||||
s.stateMutex.Lock()
|
||||
s.state = gortsplib.ServerSessionStatePrePlay
|
||||
s.stateMutex.Unlock()
|
||||
@@ -248,19 +253,10 @@ func (s *rtspSession) onPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo
|
||||
h := make(base.Header)
|
||||
|
||||
if s.session.State() == gortsplib.ServerSessionStatePrePlay {
|
||||
s.path.readerStart(pathReaderStartReq{author: s})
|
||||
|
||||
tracks := make(gortsplib.Tracks, len(s.session.SetuppedTracks()))
|
||||
n := 0
|
||||
for id := range s.session.SetuppedTracks() {
|
||||
tracks[n] = s.stream.tracks()[id]
|
||||
n++
|
||||
}
|
||||
|
||||
s.log(logger.Info, "is reading from path '%s', with %s, %s",
|
||||
s.path.Name(),
|
||||
s.session.SetuppedTransport(),
|
||||
sourceTrackInfo(tracks))
|
||||
sourceMediaInfo(s.session.SetuppedMedias()))
|
||||
|
||||
if s.path.Conf().RunOnRead != "" {
|
||||
s.log(logger.Info, "runOnRead command started")
|
||||
@@ -289,7 +285,7 @@ func (s *rtspSession) onPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Respo
|
||||
func (s *rtspSession) onRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
|
||||
res := s.path.publisherStart(pathPublisherStartReq{
|
||||
author: s,
|
||||
tracks: s.session.AnnouncedTracks(),
|
||||
medias: s.session.AnnouncedMedias(),
|
||||
generateRTPPackets: false,
|
||||
})
|
||||
if res.err != nil {
|
||||
@@ -301,10 +297,52 @@ func (s *rtspSession) onRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*base.R
|
||||
s.log(logger.Info, "is publishing to path '%s', with %s, %s",
|
||||
s.path.Name(),
|
||||
s.session.SetuppedTransport(),
|
||||
sourceTrackInfo(s.session.AnnouncedTracks()))
|
||||
sourceMediaInfo(s.session.AnnouncedMedias()))
|
||||
|
||||
s.stream = res.stream
|
||||
|
||||
for _, medi := range s.session.AnnouncedMedias() {
|
||||
for _, forma := range medi.Formats {
|
||||
cmedia := medi
|
||||
cformat := forma
|
||||
|
||||
switch forma.(type) {
|
||||
case *format.H264:
|
||||
ctx.Session.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
err := s.stream.writeData(cmedia, cformat, &dataH264{
|
||||
rtpPackets: []*rtp.Packet{pkt},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.log(logger.Warn, "%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
case *format.MPEG4Audio:
|
||||
ctx.Session.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
err := s.stream.writeData(cmedia, cformat, &dataMPEG4Audio{
|
||||
rtpPackets: []*rtp.Packet{pkt},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.log(logger.Warn, "%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
default:
|
||||
ctx.Session.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
err := s.stream.writeData(cmedia, cformat, &dataGeneric{
|
||||
rtpPackets: []*rtp.Packet{pkt},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.log(logger.Warn, "%v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.stateMutex.Lock()
|
||||
s.state = gortsplib.ServerSessionStateRecord
|
||||
s.stateMutex.Unlock()
|
||||
@@ -323,8 +361,6 @@ func (s *rtspSession) onPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Res
|
||||
s.onReadCmd.Close()
|
||||
}
|
||||
|
||||
s.path.readerStop(pathReaderStopReq{author: s})
|
||||
|
||||
s.stateMutex.Lock()
|
||||
s.state = gortsplib.ServerSessionStatePrePlay
|
||||
s.stateMutex.Unlock()
|
||||
@@ -342,11 +378,6 @@ func (s *rtspSession) onPause(ctx *gortsplib.ServerHandlerOnPauseCtx) (*base.Res
|
||||
}, nil
|
||||
}
|
||||
|
||||
// onReaderData implements reader.
|
||||
func (s *rtspSession) onReaderData(data data) {
|
||||
// packets are routed to the session by gortsplib.ServerStream.
|
||||
}
|
||||
|
||||
// apiReaderDescribe implements reader.
|
||||
func (s *rtspSession) apiReaderDescribe() interface{} {
|
||||
var typ string
|
||||
@@ -377,38 +408,6 @@ func (s *rtspSession) apiSourceDescribe() interface{} {
|
||||
}{typ, s.uuid.String()}
|
||||
}
|
||||
|
||||
// onPacketRTP is called by rtspServer.
|
||||
func (s *rtspSession) onPacketRTP(ctx *gortsplib.ServerHandlerOnPacketRTPCtx) {
|
||||
var err error
|
||||
|
||||
switch s.session.AnnouncedTracks()[ctx.TrackID].(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
err = s.stream.writeData(&dataH264{
|
||||
trackID: ctx.TrackID,
|
||||
rtpPackets: []*rtp.Packet{ctx.Packet},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
err = s.stream.writeData(&dataMPEG4Audio{
|
||||
trackID: ctx.TrackID,
|
||||
rtpPackets: []*rtp.Packet{ctx.Packet},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
|
||||
default:
|
||||
err = s.stream.writeData(&dataGeneric{
|
||||
trackID: ctx.TrackID,
|
||||
rtpPackets: []*rtp.Packet{ctx.Packet},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.log(logger.Warn, "%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// onDecodeError is called by rtspServer.
|
||||
func (s *rtspSession) onDecodeError(ctx *gortsplib.ServerHandlerOnDecodeErrorCtx) {
|
||||
s.log(logger.Warn, "%v", ctx.Error)
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/aler9/rtsp-simple-server/internal/conf"
|
||||
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||
)
|
||||
@@ -117,60 +118,69 @@ func (s *rtspSource) run(ctx context.Context) error {
|
||||
readErr := make(chan error)
|
||||
go func() {
|
||||
readErr <- func() error {
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range tracks {
|
||||
_, err := c.Setup(t, baseURL, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := s.parent.sourceStaticImplSetReady(pathSourceStaticSetReadyReq{
|
||||
tracks: tracks,
|
||||
medias: medias,
|
||||
generateRTPPackets: false,
|
||||
})
|
||||
if res.err != nil {
|
||||
return res.err
|
||||
}
|
||||
|
||||
s.Log(logger.Info, "ready: %s", sourceTrackInfo(tracks))
|
||||
s.Log(logger.Info, "ready: %s", sourceMediaInfo(medias))
|
||||
|
||||
defer func() {
|
||||
s.parent.sourceStaticImplSetNotReady(pathSourceStaticSetNotReadyReq{})
|
||||
}()
|
||||
|
||||
c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
var err error
|
||||
for _, medi := range medias {
|
||||
for _, forma := range medi.Formats {
|
||||
cmedia := medi
|
||||
cformat := forma
|
||||
|
||||
switch tracks[ctx.TrackID].(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
err = res.stream.writeData(&dataH264{
|
||||
trackID: ctx.TrackID,
|
||||
rtpPackets: []*rtp.Packet{ctx.Packet},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
switch forma.(type) {
|
||||
case *format.H264:
|
||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
err := res.stream.writeData(cmedia, cformat, &dataH264{
|
||||
rtpPackets: []*rtp.Packet{pkt},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
err = res.stream.writeData(&dataMPEG4Audio{
|
||||
trackID: ctx.TrackID,
|
||||
rtpPackets: []*rtp.Packet{ctx.Packet},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
case *format.MPEG4Audio:
|
||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
err := res.stream.writeData(cmedia, cformat, &dataMPEG4Audio{
|
||||
rtpPackets: []*rtp.Packet{pkt},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
}
|
||||
})
|
||||
|
||||
default:
|
||||
err = res.stream.writeData(&dataGeneric{
|
||||
trackID: ctx.TrackID,
|
||||
rtpPackets: []*rtp.Packet{ctx.Packet},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
default:
|
||||
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) {
|
||||
err := res.stream.writeData(cmedia, cformat, &dataGeneric{
|
||||
rtpPackets: []*rtp.Packet{pkt},
|
||||
ntp: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
s.Log(logger.Warn, "%v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/auth"
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
"github.com/aler9/gortsplib/pkg/conn"
|
||||
"github.com/aler9/gortsplib/pkg/headers"
|
||||
"github.com/aler9/gortsplib/pkg/url"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/auth"
|
||||
"github.com/aler9/gortsplib/v2/pkg/base"
|
||||
"github.com/aler9/gortsplib/v2/pkg/conn"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/headers"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
"github.com/aler9/gortsplib/v2/pkg/url"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -45,14 +47,8 @@ func TestRTSPSource(t *testing.T) {
|
||||
"tls",
|
||||
} {
|
||||
t.Run(source, func(t *testing.T) {
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
stream := gortsplib.NewServerStream(gortsplib.Tracks{track})
|
||||
medi := testMediaH264
|
||||
stream := gortsplib.NewServerStream(media.Medias{medi})
|
||||
|
||||
var authValidator *auth.Validator
|
||||
|
||||
@@ -64,7 +60,7 @@ func TestRTSPSource(t *testing.T) {
|
||||
authValidator = auth.NewValidator("testuser", "testpass", nil)
|
||||
}
|
||||
|
||||
err := authValidator.ValidateRequest(ctx.Request)
|
||||
err := authValidator.ValidateRequest(ctx.Request, nil)
|
||||
if err != nil {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
@@ -86,10 +82,10 @@ func TestRTSPSource(t *testing.T) {
|
||||
onPlay: func(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, error) {
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
stream.WritePacketRTP(0, &rtp.Packet{
|
||||
stream.WritePacketRTP(medi, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 0x02,
|
||||
PayloadType: 97,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 57899,
|
||||
Timestamp: 345234345,
|
||||
SSRC: 978651231,
|
||||
@@ -152,12 +148,7 @@ func TestRTSPSource(t *testing.T) {
|
||||
|
||||
received := make(chan struct{})
|
||||
|
||||
c := gortsplib.Client{
|
||||
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, ctx.Packet.Payload)
|
||||
close(received)
|
||||
},
|
||||
}
|
||||
c := gortsplib.Client{}
|
||||
|
||||
u, err := url.Parse("rtsp://127.0.0.1:8554/proxied")
|
||||
require.NoError(t, err)
|
||||
@@ -166,10 +157,18 @@ func TestRTSPSource(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.SetupAndPlay(tracks, baseURL)
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, pkt.Payload)
|
||||
close(received)
|
||||
})
|
||||
|
||||
_, err = c.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
<-received
|
||||
@@ -178,14 +177,8 @@ func TestRTSPSource(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRTSPSourceNoPassword(t *testing.T) {
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
stream := gortsplib.NewServerStream(gortsplib.Tracks{track})
|
||||
medi := testMediaH264
|
||||
stream := gortsplib.NewServerStream(media.Medias{medi})
|
||||
|
||||
var authValidator *auth.Validator
|
||||
done := make(chan struct{})
|
||||
@@ -197,7 +190,7 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
||||
authValidator = auth.NewValidator("testuser", "", nil)
|
||||
}
|
||||
|
||||
err := authValidator.ValidateRequest(ctx.Request)
|
||||
err := authValidator.ValidateRequest(ctx.Request, nil)
|
||||
if err != nil {
|
||||
return &base.Response{
|
||||
StatusCode: base.StatusUnauthorized,
|
||||
@@ -243,11 +236,15 @@ func TestRTSPSourceNoPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRTSPSourceDynamicH264Params(t *testing.T) {
|
||||
track := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
forma := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
stream := gortsplib.NewServerStream(gortsplib.Tracks{track})
|
||||
medi := &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{forma},
|
||||
}
|
||||
stream := gortsplib.NewServerStream(media.Medias{medi})
|
||||
defer stream.Close()
|
||||
|
||||
s := gortsplib.Server{
|
||||
@@ -285,15 +282,15 @@ func TestRTSPSourceDynamicH264Params(t *testing.T) {
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
enc := track.CreateEncoder()
|
||||
enc := forma.CreateEncoder()
|
||||
|
||||
pkts, err := enc.Encode([][]byte{{7, 1, 2, 3}}, 0) // SPS
|
||||
require.NoError(t, err)
|
||||
stream.WritePacketRTP(0, pkts[0])
|
||||
stream.WritePacketRTP(medi, pkts[0])
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{8}}, 0) // PPS
|
||||
require.NoError(t, err)
|
||||
stream.WritePacketRTP(0, pkts[0])
|
||||
stream.WritePacketRTP(medi, pkts[0])
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
@@ -307,22 +304,21 @@ func TestRTSPSourceDynamicH264Params(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, _, _, err := c.Describe(u)
|
||||
medias, _, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
h264Track, ok := tracks[0].(*gortsplib.TrackH264)
|
||||
require.Equal(t, true, ok)
|
||||
h264Track := medias[0].Formats[0].(*format.H264)
|
||||
require.Equal(t, []byte{7, 1, 2, 3}, h264Track.SafeSPS())
|
||||
require.Equal(t, []byte{8}, h264Track.SafePPS())
|
||||
}()
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}, 0) // SPS
|
||||
require.NoError(t, err)
|
||||
stream.WritePacketRTP(0, pkts[0])
|
||||
stream.WritePacketRTP(medi, pkts[0])
|
||||
|
||||
pkts, err = enc.Encode([][]byte{{8, 1}}, 0) // PPS
|
||||
require.NoError(t, err)
|
||||
stream.WritePacketRTP(0, pkts[0])
|
||||
stream.WritePacketRTP(medi, pkts[0])
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
@@ -336,21 +332,24 @@ func TestRTSPSourceDynamicH264Params(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, _, _, err := c.Describe(u)
|
||||
medias, _, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
h264Track, ok := tracks[0].(*gortsplib.TrackH264)
|
||||
require.Equal(t, true, ok)
|
||||
h264Track := medias[0].Formats[0].(*format.H264)
|
||||
require.Equal(t, []byte{7, 4, 5, 6}, h264Track.SafeSPS())
|
||||
require.Equal(t, []byte{8, 1}, h264Track.SafePPS())
|
||||
}()
|
||||
}
|
||||
|
||||
func TestRTSPSourceRemovePadding(t *testing.T) {
|
||||
stream := gortsplib.NewServerStream(gortsplib.Tracks{&gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
PacketizationMode: 1,
|
||||
}})
|
||||
medi := &media.Media{
|
||||
Type: media.TypeVideo,
|
||||
Formats: []format.Format{&format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}},
|
||||
}
|
||||
stream := gortsplib.NewServerStream(media.Medias{medi})
|
||||
defer stream.Close()
|
||||
|
||||
s := gortsplib.Server{
|
||||
@@ -390,23 +389,7 @@ func TestRTSPSourceRemovePadding(t *testing.T) {
|
||||
|
||||
packetRecv := make(chan struct{})
|
||||
|
||||
c := gortsplib.Client{
|
||||
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
}, ctx.Packet)
|
||||
close(packetRecv)
|
||||
},
|
||||
}
|
||||
c := gortsplib.Client{}
|
||||
|
||||
u, err := url.Parse("rtsp://127.0.0.1:8554/proxied")
|
||||
require.NoError(t, err)
|
||||
@@ -415,13 +398,32 @@ func TestRTSPSourceRemovePadding(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.SetupAndPlay(tracks, baseURL)
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
stream.WritePacketRTP(0, &rtp.Packet{
|
||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
}, pkt)
|
||||
close(packetRecv)
|
||||
})
|
||||
|
||||
_, err = c.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
stream.WritePacketRTP(medi, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
@@ -475,19 +477,15 @@ func TestRTSPSourceOversizedPackets(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Describe, req.Method)
|
||||
|
||||
tracks := gortsplib.Tracks{&gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
SPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PPS: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
PacketizationMode: 1,
|
||||
}}
|
||||
medias := media.Medias{testMediaH264}
|
||||
byts, _ := medias.Marshal(false).Marshal()
|
||||
|
||||
err = conn.WriteResponse(&base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
},
|
||||
Body: tracks.Marshal(false),
|
||||
Body: byts,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -525,7 +523,7 @@ func TestRTSPSourceOversizedPackets(t *testing.T) {
|
||||
|
||||
<-connected
|
||||
|
||||
byts, _ := rtp.Packet{
|
||||
byts, _ = rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
@@ -603,75 +601,7 @@ func TestRTSPSourceOversizedPackets(t *testing.T) {
|
||||
packetRecv := make(chan struct{})
|
||||
i := 0
|
||||
|
||||
c := gortsplib.Client{
|
||||
OnPacketRTP: func(ctx *gortsplib.ClientOnPacketRTPCtx) {
|
||||
switch i {
|
||||
case 0:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
}, ctx.Packet)
|
||||
|
||||
case 1:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 124,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: append(
|
||||
append([]byte{0x1c, 0x81, 0x02, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 363)...),
|
||||
[]byte{0x01, 0x02, 0x03}...,
|
||||
),
|
||||
}, ctx.Packet)
|
||||
|
||||
case 2:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 125,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: append(
|
||||
[]byte{0x1c, 0x41, 0x04},
|
||||
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 135)...,
|
||||
),
|
||||
}, ctx.Packet)
|
||||
|
||||
case 3:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 126,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
}, ctx.Packet)
|
||||
close(packetRecv)
|
||||
}
|
||||
i++
|
||||
},
|
||||
}
|
||||
c := gortsplib.Client{}
|
||||
|
||||
u, err := url.Parse("rtsp://127.0.0.1:8554/proxied")
|
||||
require.NoError(t, err)
|
||||
@@ -680,10 +610,81 @@ func TestRTSPSourceOversizedPackets(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
tracks, baseURL, _, err := c.Describe(u)
|
||||
medias, baseURL, _, err := c.Describe(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = c.SetupAndPlay(tracks, baseURL)
|
||||
err = c.SetupAll(medias, baseURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
c.OnPacketRTP(medias[0], medias[0].Formats[0], func(pkt *rtp.Packet) {
|
||||
switch i {
|
||||
case 0:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 123,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
}, pkt)
|
||||
|
||||
case 1:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 124,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: append(
|
||||
append([]byte{0x1c, 0x81, 0x02, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 363)...),
|
||||
[]byte{0x01, 0x02, 0x03}...,
|
||||
),
|
||||
}, pkt)
|
||||
|
||||
case 2:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 125,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: append(
|
||||
[]byte{0x1c, 0x41, 0x04},
|
||||
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 135)...,
|
||||
),
|
||||
}, pkt)
|
||||
|
||||
case 3:
|
||||
require.Equal(t, &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 126,
|
||||
Timestamp: 45343,
|
||||
SSRC: 563423,
|
||||
CSRC: []uint32{},
|
||||
},
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
}, pkt)
|
||||
close(packetRecv)
|
||||
}
|
||||
i++
|
||||
})
|
||||
|
||||
_, err = c.Play(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
close(connected)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
)
|
||||
|
||||
// source is an entity that can provide a stream.
|
||||
@@ -16,22 +16,30 @@ type source interface {
|
||||
apiSourceDescribe() interface{}
|
||||
}
|
||||
|
||||
func sourceTrackNames(tracks gortsplib.Tracks) []string {
|
||||
ret := make([]string, len(tracks))
|
||||
for i, t := range tracks {
|
||||
ret[i] = t.String()
|
||||
func mediaDescription(media *media.Media) string {
|
||||
ret := make([]string, len(media.Formats))
|
||||
for i, forma := range media.Formats {
|
||||
ret[i] = forma.String()
|
||||
}
|
||||
return strings.Join(ret, "/")
|
||||
}
|
||||
|
||||
func mediasDescription(medias media.Medias) []string {
|
||||
ret := make([]string, len(medias))
|
||||
for i, media := range medias {
|
||||
ret[i] = mediaDescription(media)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func sourceTrackInfo(tracks gortsplib.Tracks) string {
|
||||
func sourceMediaInfo(medias media.Medias) string {
|
||||
return fmt.Sprintf("%d %s (%s)",
|
||||
len(tracks),
|
||||
len(medias),
|
||||
func() string {
|
||||
if len(tracks) == 1 {
|
||||
if len(medias) == 1 {
|
||||
return "track"
|
||||
}
|
||||
return "tracks"
|
||||
}(),
|
||||
strings.Join(sourceTrackNames(tracks), ", "))
|
||||
strings.Join(mediasDescription(medias), ", "))
|
||||
}
|
||||
|
||||
@@ -1,79 +1,32 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
)
|
||||
|
||||
type streamNonRTSPReadersMap struct {
|
||||
mutex sync.RWMutex
|
||||
ma map[reader]struct{}
|
||||
}
|
||||
|
||||
func newStreamNonRTSPReadersMap() *streamNonRTSPReadersMap {
|
||||
return &streamNonRTSPReadersMap{
|
||||
ma: make(map[reader]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *streamNonRTSPReadersMap) close() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.ma = nil
|
||||
}
|
||||
|
||||
func (m *streamNonRTSPReadersMap) add(r reader) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
m.ma[r] = struct{}{}
|
||||
}
|
||||
|
||||
func (m *streamNonRTSPReadersMap) remove(r reader) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
delete(m.ma, r)
|
||||
}
|
||||
|
||||
func (m *streamNonRTSPReadersMap) writeData(data data) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
for c := range m.ma {
|
||||
c.onReaderData(data)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *streamNonRTSPReadersMap) hasReaders() bool {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
return len(m.ma) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
bytesReceived *uint64
|
||||
nonRTSPReaders *streamNonRTSPReadersMap
|
||||
rtspStream *gortsplib.ServerStream
|
||||
streamTracks []streamTrack
|
||||
bytesReceived *uint64
|
||||
rtspStream *gortsplib.ServerStream
|
||||
smedias map[*media.Media]*streamMedia
|
||||
}
|
||||
|
||||
func newStream(
|
||||
tracks gortsplib.Tracks,
|
||||
medias media.Medias,
|
||||
generateRTPPackets bool,
|
||||
bytesReceived *uint64,
|
||||
) (*stream, error) {
|
||||
s := &stream{
|
||||
bytesReceived: bytesReceived,
|
||||
nonRTSPReaders: newStreamNonRTSPReadersMap(),
|
||||
rtspStream: gortsplib.NewServerStream(tracks),
|
||||
bytesReceived: bytesReceived,
|
||||
rtspStream: gortsplib.NewServerStream(medias),
|
||||
}
|
||||
|
||||
s.streamTracks = make([]streamTrack, len(s.rtspStream.Tracks()))
|
||||
s.smedias = make(map[*media.Media]*streamMedia)
|
||||
|
||||
for i, track := range s.rtspStream.Tracks() {
|
||||
for _, media := range s.rtspStream.Medias() {
|
||||
var err error
|
||||
s.streamTracks[i], err = newStreamTrack(track, generateRTPPackets)
|
||||
s.smedias[media], err = newStreamMedia(media, generateRTPPackets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,40 +36,29 @@ func newStream(
|
||||
}
|
||||
|
||||
func (s *stream) close() {
|
||||
s.nonRTSPReaders.close()
|
||||
s.rtspStream.Close()
|
||||
}
|
||||
|
||||
func (s *stream) tracks() gortsplib.Tracks {
|
||||
return s.rtspStream.Tracks()
|
||||
func (s *stream) medias() media.Medias {
|
||||
return s.rtspStream.Medias()
|
||||
}
|
||||
|
||||
func (s *stream) readerAdd(r reader) {
|
||||
if _, ok := r.(pathRTSPSession); !ok {
|
||||
s.nonRTSPReaders.add(r)
|
||||
}
|
||||
func (s *stream) readerAdd(r reader, medi *media.Media, forma format.Format, cb func(data)) {
|
||||
sm := s.smedias[medi]
|
||||
sf := sm.formats[forma]
|
||||
sf.readerAdd(r, cb)
|
||||
}
|
||||
|
||||
func (s *stream) readerRemove(r reader) {
|
||||
if _, ok := r.(pathRTSPSession); !ok {
|
||||
s.nonRTSPReaders.remove(r)
|
||||
for _, sm := range s.smedias {
|
||||
for _, sf := range sm.formats {
|
||||
sf.readerRemove(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) writeData(data data) error {
|
||||
err := s.streamTracks[data.getTrackID()].onData(data, s.nonRTSPReaders.hasReaders())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// forward RTP packets to RTSP readers
|
||||
for _, pkt := range data.getRTPPackets() {
|
||||
atomic.AddUint64(s.bytesReceived, uint64(pkt.MarshalSize()))
|
||||
s.rtspStream.WritePacketRTPWithNTP(data.getTrackID(), pkt, data.getNTP())
|
||||
}
|
||||
|
||||
// forward data to non-RTSP readers
|
||||
s.nonRTSPReaders.writeData(data)
|
||||
|
||||
return nil
|
||||
func (s *stream) writeData(medi *media.Media, forma format.Format, data data) error {
|
||||
sm := s.smedias[medi]
|
||||
sf := sm.formats[forma]
|
||||
return sf.writeData(s, medi, data)
|
||||
}
|
||||
|
||||
66
internal/core/streamformat.go
Normal file
66
internal/core/streamformat.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
)
|
||||
|
||||
type streamFormat struct {
|
||||
proc formatProcessor
|
||||
mutex sync.RWMutex
|
||||
nonRTSPReaders map[reader]func(data)
|
||||
}
|
||||
|
||||
func newStreamFormat(forma format.Format, generateRTPPackets bool) (*streamFormat, error) {
|
||||
proc, err := newFormatProcessor(forma, generateRTPPackets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sf := &streamFormat{
|
||||
proc: proc,
|
||||
nonRTSPReaders: make(map[reader]func(data)),
|
||||
}
|
||||
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
func (sf *streamFormat) readerAdd(r reader, cb func(data)) {
|
||||
sf.mutex.Lock()
|
||||
defer sf.mutex.Unlock()
|
||||
sf.nonRTSPReaders[r] = cb
|
||||
}
|
||||
|
||||
func (sf *streamFormat) readerRemove(r reader) {
|
||||
sf.mutex.Lock()
|
||||
defer sf.mutex.Unlock()
|
||||
delete(sf.nonRTSPReaders, r)
|
||||
}
|
||||
|
||||
func (sf *streamFormat) writeData(s *stream, medi *media.Media, data data) error {
|
||||
sf.mutex.RLock()
|
||||
defer sf.mutex.RUnlock()
|
||||
|
||||
hasNonRTSPReaders := len(sf.nonRTSPReaders) > 0
|
||||
|
||||
err := sf.proc.process(data, hasNonRTSPReaders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// forward RTP packets to RTSP readers
|
||||
for _, pkt := range data.getRTPPackets() {
|
||||
atomic.AddUint64(s.bytesReceived, uint64(pkt.MarshalSize()))
|
||||
s.rtspStream.WritePacketRTPWithNTP(medi, pkt, data.getNTP())
|
||||
}
|
||||
|
||||
// forward data to non-RTSP readers
|
||||
for _, cb := range sf.nonRTSPReaders {
|
||||
cb(data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
26
internal/core/streammedia.go
Normal file
26
internal/core/streammedia.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/media"
|
||||
)
|
||||
|
||||
type streamMedia struct {
|
||||
formats map[format.Format]*streamFormat
|
||||
}
|
||||
|
||||
func newStreamMedia(medi *media.Media, generateRTPPackets bool) (*streamMedia, error) {
|
||||
sm := &streamMedia{
|
||||
formats: make(map[format.Format]*streamFormat),
|
||||
}
|
||||
|
||||
for _, forma := range medi.Formats {
|
||||
var err error
|
||||
sm.formats[forma], err = newStreamFormat(forma, generateRTPPackets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sm, nil
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
)
|
||||
|
||||
type streamTrack interface {
|
||||
onData(data, bool) error
|
||||
}
|
||||
|
||||
func newStreamTrack(track gortsplib.Track, generateRTPPackets bool) (streamTrack, error) {
|
||||
switch ttrack := track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
return newStreamTrackH264(ttrack, generateRTPPackets), nil
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
return newStreamTrackMPEG4Audio(ttrack, generateRTPPackets), nil
|
||||
|
||||
default:
|
||||
if generateRTPPackets {
|
||||
return nil, fmt.Errorf("we don't know how to generate RTP packets of track %+v", track)
|
||||
}
|
||||
return newStreamTrackGeneric(), nil
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
// 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header)
|
||||
maxPacketSize = 1472
|
||||
)
|
||||
|
||||
type streamTrackGeneric struct{}
|
||||
|
||||
func newStreamTrackGeneric() *streamTrackGeneric {
|
||||
return &streamTrackGeneric{}
|
||||
}
|
||||
|
||||
func (t *streamTrackGeneric) onData(dat data, hasNonRTSPReaders bool) error {
|
||||
tdata := dat.(*dataGeneric)
|
||||
|
||||
pkt := tdata.rtpPackets[0]
|
||||
|
||||
// remove padding
|
||||
pkt.Header.Padding = false
|
||||
pkt.PaddingSize = 0
|
||||
|
||||
if pkt.MarshalSize() > maxPacketSize {
|
||||
return fmt.Errorf("payload size (%d) is greater than maximum allowed (%d)",
|
||||
pkt.MarshalSize(), maxPacketSize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/logger"
|
||||
)
|
||||
@@ -35,7 +35,7 @@ type ClientLogger interface {
|
||||
// Client is a HLS client.
|
||||
type Client struct {
|
||||
fingerprint string
|
||||
onTracks func(*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio) error
|
||||
onTracks func(*format.H264, *format.MPEG4Audio) error
|
||||
onVideoData func(time.Duration, [][]byte)
|
||||
onAudioData func(time.Duration, []byte)
|
||||
logger ClientLogger
|
||||
@@ -52,7 +52,7 @@ type Client struct {
|
||||
func NewClient(
|
||||
playlistURLStr string,
|
||||
fingerprint string,
|
||||
onTracks func(*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio) error,
|
||||
onTracks func(*format.H264, *format.MPEG4Audio) error,
|
||||
onVideoData func(time.Duration, [][]byte),
|
||||
onAudioData func(time.Duration, []byte),
|
||||
logger ClientLogger,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
gm3u8 "github.com/grafov/m3u8"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/m3u8"
|
||||
@@ -106,7 +106,7 @@ type clientTimeSync interface{}
|
||||
type clientDownloaderPrimary struct {
|
||||
primaryPlaylistURL *url.URL
|
||||
logger ClientLogger
|
||||
onTracks func(*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio) error
|
||||
onTracks func(*format.H264, *format.MPEG4Audio) error
|
||||
onVideoData func(time.Duration, [][]byte)
|
||||
onAudioData func(time.Duration, []byte)
|
||||
rp *clientRoutinePool
|
||||
@@ -115,7 +115,7 @@ type clientDownloaderPrimary struct {
|
||||
leadingTimeSync clientTimeSync
|
||||
|
||||
// in
|
||||
streamTracks chan []gortsplib.Track
|
||||
streamFormats chan []format.Format
|
||||
|
||||
// out
|
||||
startStreaming chan struct{}
|
||||
@@ -127,7 +127,7 @@ func newClientDownloaderPrimary(
|
||||
fingerprint string,
|
||||
logger ClientLogger,
|
||||
rp *clientRoutinePool,
|
||||
onTracks func(*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio) error,
|
||||
onTracks func(*format.H264, *format.MPEG4Audio) error,
|
||||
onVideoData func(time.Duration, [][]byte),
|
||||
onAudioData func(time.Duration, []byte),
|
||||
) *clientDownloaderPrimary {
|
||||
@@ -163,7 +163,7 @@ func newClientDownloaderPrimary(
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
},
|
||||
streamTracks: make(chan []gortsplib.Track),
|
||||
streamFormats: make(chan []format.Format),
|
||||
startStreaming: make(chan struct{}),
|
||||
leadingTimeSyncReady: make(chan struct{}),
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func (d *clientDownloaderPrimary) run(ctx context.Context) error {
|
||||
plt,
|
||||
d.logger,
|
||||
d.rp,
|
||||
d.onStreamTracks,
|
||||
d.onStreamFormats,
|
||||
d.onSetLeadingTimeSync,
|
||||
d.onGetLeadingTimeSync,
|
||||
d.onVideoData,
|
||||
@@ -215,7 +215,7 @@ func (d *clientDownloaderPrimary) run(ctx context.Context) error {
|
||||
nil,
|
||||
d.logger,
|
||||
d.rp,
|
||||
d.onStreamTracks,
|
||||
d.onStreamFormats,
|
||||
d.onSetLeadingTimeSync,
|
||||
d.onGetLeadingTimeSync,
|
||||
d.onVideoData,
|
||||
@@ -241,7 +241,7 @@ func (d *clientDownloaderPrimary) run(ctx context.Context) error {
|
||||
nil,
|
||||
d.logger,
|
||||
d.rp,
|
||||
d.onStreamTracks,
|
||||
d.onStreamFormats,
|
||||
d.onSetLeadingTimeSync,
|
||||
d.onGetLeadingTimeSync,
|
||||
d.onVideoData,
|
||||
@@ -254,29 +254,29 @@ func (d *clientDownloaderPrimary) run(ctx context.Context) error {
|
||||
return fmt.Errorf("invalid playlist")
|
||||
}
|
||||
|
||||
var tracks []gortsplib.Track
|
||||
var tracks []format.Format
|
||||
|
||||
for i := 0; i < streamCount; i++ {
|
||||
select {
|
||||
case streamTracks := <-d.streamTracks:
|
||||
tracks = append(tracks, streamTracks...)
|
||||
case streamFormats := <-d.streamFormats:
|
||||
tracks = append(tracks, streamFormats...)
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("terminated")
|
||||
}
|
||||
}
|
||||
|
||||
var videoTrack *gortsplib.TrackH264
|
||||
var audioTrack *gortsplib.TrackMPEG4Audio
|
||||
var videoTrack *format.H264
|
||||
var audioTrack *format.MPEG4Audio
|
||||
|
||||
for _, track := range tracks {
|
||||
switch ttrack := track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
case *format.H264:
|
||||
if videoTrack != nil {
|
||||
return fmt.Errorf("multiple video tracks are not supported")
|
||||
}
|
||||
videoTrack = ttrack
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
if audioTrack != nil {
|
||||
return fmt.Errorf("multiple audio tracks are not supported")
|
||||
}
|
||||
@@ -294,9 +294,9 @@ func (d *clientDownloaderPrimary) run(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *clientDownloaderPrimary) onStreamTracks(ctx context.Context, tracks []gortsplib.Track) bool {
|
||||
func (d *clientDownloaderPrimary) onStreamFormats(ctx context.Context, tracks []format.Format) bool {
|
||||
select {
|
||||
case d.streamTracks <- tracks:
|
||||
case d.streamFormats <- tracks:
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
gm3u8 "github.com/grafov/m3u8"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/m3u8"
|
||||
@@ -50,7 +50,7 @@ type clientDownloaderStream struct {
|
||||
initialPlaylist *m3u8.MediaPlaylist
|
||||
logger ClientLogger
|
||||
rp *clientRoutinePool
|
||||
onStreamTracks func(context.Context, []gortsplib.Track) bool
|
||||
onStreamFormats func(context.Context, []format.Format) bool
|
||||
onSetLeadingTimeSync func(clientTimeSync)
|
||||
onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool)
|
||||
onVideoData func(time.Duration, [][]byte)
|
||||
@@ -66,7 +66,7 @@ func newClientDownloaderStream(
|
||||
initialPlaylist *m3u8.MediaPlaylist,
|
||||
logger ClientLogger,
|
||||
rp *clientRoutinePool,
|
||||
onStreamTracks func(context.Context, []gortsplib.Track) bool,
|
||||
onStreamFormats func(context.Context, []format.Format) bool,
|
||||
onSetLeadingTimeSync func(clientTimeSync),
|
||||
onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool),
|
||||
onVideoData func(time.Duration, [][]byte),
|
||||
@@ -79,7 +79,7 @@ func newClientDownloaderStream(
|
||||
initialPlaylist: initialPlaylist,
|
||||
logger: logger,
|
||||
rp: rp,
|
||||
onStreamTracks: onStreamTracks,
|
||||
onStreamFormats: onStreamFormats,
|
||||
onSetLeadingTimeSync: onSetLeadingTimeSync,
|
||||
onGetLeadingTimeSync: onGetLeadingTimeSync,
|
||||
onVideoData: onVideoData,
|
||||
@@ -113,7 +113,7 @@ func (d *clientDownloaderStream) run(ctx context.Context) error {
|
||||
segmentQueue,
|
||||
d.logger,
|
||||
d.rp,
|
||||
d.onStreamTracks,
|
||||
d.onStreamFormats,
|
||||
d.onSetLeadingTimeSync,
|
||||
d.onGetLeadingTimeSync,
|
||||
d.onVideoData,
|
||||
@@ -130,7 +130,7 @@ func (d *clientDownloaderStream) run(ctx context.Context) error {
|
||||
segmentQueue,
|
||||
d.logger,
|
||||
d.rp,
|
||||
d.onStreamTracks,
|
||||
d.onStreamFormats,
|
||||
d.onSetLeadingTimeSync,
|
||||
d.onGetLeadingTimeSync,
|
||||
d.onVideoData,
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
|
||||
)
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func fmp4PickLeadingTrack(init *fmp4.Init) int {
|
||||
// pick first video track
|
||||
for _, track := range init.Tracks {
|
||||
if _, ok := track.Track.(*gortsplib.TrackH264); ok {
|
||||
if _, ok := track.Format.(*format.H264); ok {
|
||||
return track.ID
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func newClientProcessorFMP4(
|
||||
segmentQueue *clientSegmentQueue,
|
||||
logger ClientLogger,
|
||||
rp *clientRoutinePool,
|
||||
onStreamTracks func(context.Context, []gortsplib.Track) bool,
|
||||
onStreamFormats func(context.Context, []format.Format) bool,
|
||||
onSetLeadingTimeSync func(clientTimeSync),
|
||||
onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool),
|
||||
onVideoData func(time.Duration, [][]byte),
|
||||
@@ -73,12 +73,12 @@ func newClientProcessorFMP4(
|
||||
|
||||
p.leadingTrackID = fmp4PickLeadingTrack(&p.init)
|
||||
|
||||
tracks := make([]gortsplib.Track, len(p.init.Tracks))
|
||||
tracks := make([]format.Format, len(p.init.Tracks))
|
||||
for i, track := range p.init.Tracks {
|
||||
tracks[i] = track.Track
|
||||
tracks[i] = track.Format
|
||||
}
|
||||
|
||||
ok := onStreamTracks(ctx, tracks)
|
||||
ok := onStreamFormats(ctx, tracks)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("terminated")
|
||||
}
|
||||
@@ -186,8 +186,8 @@ func (p *clientProcessorFMP4) initializeTrackProcs(ts *clientTimeSyncFMP4) {
|
||||
for _, track := range p.init.Tracks {
|
||||
var cb func(time.Duration, []byte) error
|
||||
|
||||
switch track.Track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
switch track.Format.(type) {
|
||||
case *format.H264:
|
||||
cb = func(pts time.Duration, payload []byte) error {
|
||||
nalus, err := h264.AVCCUnmarshal(payload)
|
||||
if err != nil {
|
||||
@@ -198,7 +198,7 @@ func (p *clientProcessorFMP4) initializeTrackProcs(ts *clientTimeSyncFMP4) {
|
||||
return nil
|
||||
}
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
cb = func(pts time.Duration, payload []byte) error {
|
||||
p.onAudioData(pts, payload)
|
||||
return nil
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/asticode/go-astits"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/mpegts"
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func mpegtsPickLeadingTrack(mpegtsTracks []*mpegts.Track) uint16 {
|
||||
// pick first video track
|
||||
for _, mt := range mpegtsTracks {
|
||||
if _, ok := mt.Track.(*gortsplib.TrackH264); ok {
|
||||
if _, ok := mt.Format.(*format.H264); ok {
|
||||
return mt.ES.ElementaryPID
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ type clientProcessorMPEGTS struct {
|
||||
segmentQueue *clientSegmentQueue
|
||||
logger ClientLogger
|
||||
rp *clientRoutinePool
|
||||
onStreamTracks func(context.Context, []gortsplib.Track) bool
|
||||
onStreamFormats func(context.Context, []format.Format) bool
|
||||
onSetLeadingTimeSync func(clientTimeSync)
|
||||
onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool)
|
||||
onVideoData func(time.Duration, [][]byte)
|
||||
@@ -49,7 +49,7 @@ func newClientProcessorMPEGTS(
|
||||
segmentQueue *clientSegmentQueue,
|
||||
logger ClientLogger,
|
||||
rp *clientRoutinePool,
|
||||
onStreamTracks func(context.Context, []gortsplib.Track) bool,
|
||||
onStreamFormats func(context.Context, []format.Format) bool,
|
||||
onSetLeadingTimeSync func(clientTimeSync),
|
||||
onGetLeadingTimeSync func(context.Context) (clientTimeSync, bool),
|
||||
onVideoData func(time.Duration, [][]byte),
|
||||
@@ -60,7 +60,7 @@ func newClientProcessorMPEGTS(
|
||||
segmentQueue: segmentQueue,
|
||||
logger: logger,
|
||||
rp: rp,
|
||||
onStreamTracks: onStreamTracks,
|
||||
onStreamFormats: onStreamFormats,
|
||||
onSetLeadingTimeSync: onSetLeadingTimeSync,
|
||||
onGetLeadingTimeSync: onGetLeadingTimeSync,
|
||||
onVideoData: onVideoData,
|
||||
@@ -92,12 +92,12 @@ func (p *clientProcessorMPEGTS) processSegment(ctx context.Context, byts []byte)
|
||||
|
||||
p.leadingTrackPID = mpegtsPickLeadingTrack(p.mpegtsTracks)
|
||||
|
||||
tracks := make([]gortsplib.Track, len(p.mpegtsTracks))
|
||||
tracks := make([]format.Format, len(p.mpegtsTracks))
|
||||
for i, mt := range p.mpegtsTracks {
|
||||
tracks[i] = mt.Track
|
||||
tracks[i] = mt.Format
|
||||
}
|
||||
|
||||
ok := p.onStreamTracks(ctx, tracks)
|
||||
ok := p.onStreamFormats(ctx, tracks)
|
||||
if !ok {
|
||||
return fmt.Errorf("terminated")
|
||||
}
|
||||
@@ -177,8 +177,8 @@ func (p *clientProcessorMPEGTS) initializeTrackProcs(ts *clientTimeSyncMPEGTS) {
|
||||
for _, mt := range p.mpegtsTracks {
|
||||
var cb func(time.Duration, []byte) error
|
||||
|
||||
switch mt.Track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
switch mt.Format.(type) {
|
||||
case *format.H264:
|
||||
cb = func(pts time.Duration, payload []byte) error {
|
||||
nalus, err := h264.AnnexBUnmarshal(payload)
|
||||
if err != nil {
|
||||
@@ -190,7 +190,7 @@ func (p *clientProcessorMPEGTS) initializeTrackProcs(ts *clientTimeSyncMPEGTS) {
|
||||
return nil
|
||||
}
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
cb = func(pts time.Duration, payload []byte) error {
|
||||
var adtsPkts mpeg4audio.ADTSPackets
|
||||
err := adtsPkts.Unmarshal(payload)
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -216,7 +216,7 @@ func TestClient(t *testing.T) {
|
||||
c, err := NewClient(
|
||||
prefix+"://localhost:5780/stream.m3u8",
|
||||
"33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739",
|
||||
func(*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio) error {
|
||||
func(*format.H264, *format.MPEG4Audio) error {
|
||||
return nil
|
||||
},
|
||||
func(pts time.Duration, nalus [][]byte) {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
gomp4 "github.com/abema/go-mp4"
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
)
|
||||
|
||||
// Init is a FMP4 initialization file.
|
||||
@@ -105,8 +105,8 @@ func (i *Init) Unmarshal(byts []byte) error {
|
||||
pps = conf.PictureParameterSets[0].NALUnit
|
||||
}
|
||||
|
||||
curTrack.Track = &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
curTrack.Format = &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: sps,
|
||||
PPS: pps,
|
||||
PacketizationMode: 1,
|
||||
@@ -149,8 +149,8 @@ func (i *Init) Unmarshal(byts []byte) error {
|
||||
return nil, fmt.Errorf("invalid MPEG4-audio configuration: %s", err)
|
||||
}
|
||||
|
||||
curTrack.Track = &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
curTrack.Format = &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &c,
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
|
||||
@@ -4,8 +4,8 @@ package fmp4
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -16,15 +16,15 @@ var testSPS = []byte{
|
||||
0x20,
|
||||
}
|
||||
|
||||
var testVideoTrack = &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
var testVideoTrack = &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
var testAudioTrack = &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 97,
|
||||
var testAudioTrack = &format.MPEG4Audio{
|
||||
PayloadTyp: 97,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -42,12 +42,12 @@ func TestInitMarshal(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
TimeScale: 90000,
|
||||
Track: testVideoTrack,
|
||||
Format: testVideoTrack,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
TimeScale: uint32(testAudioTrack.ClockRate()),
|
||||
Track: testAudioTrack,
|
||||
Format: testAudioTrack,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -223,7 +223,7 @@ func TestInitMarshal(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
TimeScale: 90000,
|
||||
Track: testVideoTrack,
|
||||
Format: testVideoTrack,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -337,7 +337,7 @@ func TestInitMarshal(t *testing.T) {
|
||||
{
|
||||
ID: 1,
|
||||
TimeScale: uint32(testAudioTrack.ClockRate()),
|
||||
Track: testAudioTrack,
|
||||
Format: testAudioTrack,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -557,8 +557,8 @@ func TestInitUnmarshal(t *testing.T) {
|
||||
{
|
||||
ID: 256,
|
||||
TimeScale: 10000000,
|
||||
Track: &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
Format: &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
0x67, 0x42, 0xc0, 0x1f, 0xd9, 0x00, 0xf0, 0x11,
|
||||
0x7e, 0xf0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01,
|
||||
@@ -673,8 +673,8 @@ func TestInitUnmarshal(t *testing.T) {
|
||||
{
|
||||
ID: 257,
|
||||
TimeScale: 10000000,
|
||||
Track: &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
Format: &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 48000,
|
||||
|
||||
@@ -2,16 +2,15 @@ package fmp4
|
||||
|
||||
import (
|
||||
gomp4 "github.com/abema/go-mp4"
|
||||
"github.com/aler9/gortsplib"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
)
|
||||
|
||||
// InitTrack is a track of Init.
|
||||
type InitTrack struct {
|
||||
ID int
|
||||
TimeScale uint32
|
||||
Track gortsplib.Track
|
||||
Format format.Format
|
||||
}
|
||||
|
||||
func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
@@ -53,8 +52,8 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
var width int
|
||||
var height int
|
||||
|
||||
switch ttrack := track.Track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
switch ttrack := track.Format.(type) {
|
||||
case *format.H264:
|
||||
sps = ttrack.SafeSPS()
|
||||
pps = ttrack.SafePPS()
|
||||
|
||||
@@ -79,7 +78,7 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
_, err = w.WriteBox(&gomp4.Tkhd{ // <tkhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 3},
|
||||
@@ -107,8 +106,8 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
switch track.Track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
switch track.Format.(type) {
|
||||
case *format.H264:
|
||||
_, err = w.WriteBox(&gomp4.Hdlr{ // <hdlr/>
|
||||
HandlerType: [4]byte{'v', 'i', 'd', 'e'},
|
||||
Name: "VideoHandler",
|
||||
@@ -117,7 +116,7 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
_, err = w.WriteBox(&gomp4.Hdlr{ // <hdlr/>
|
||||
HandlerType: [4]byte{'s', 'o', 'u', 'n'},
|
||||
Name: "SoundHandler",
|
||||
@@ -132,8 +131,8 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
switch track.Track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
switch track.Format.(type) {
|
||||
case *format.H264:
|
||||
_, err = w.WriteBox(&gomp4.Vmhd{ // <vmhd/>
|
||||
FullBox: gomp4.FullBox{
|
||||
Flags: [3]byte{0, 0, 1},
|
||||
@@ -143,7 +142,7 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
_, err = w.WriteBox(&gomp4.Smhd{ // <smhd/>
|
||||
})
|
||||
if err != nil {
|
||||
@@ -194,8 +193,8 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
switch ttrack := track.Track.(type) {
|
||||
case *gortsplib.TrackH264:
|
||||
switch ttrack := track.Format.(type) {
|
||||
case *format.H264:
|
||||
_, err = w.writeBoxStart(&gomp4.VisualSampleEntry{ // <avc1>
|
||||
SampleEntry: gomp4.SampleEntry{
|
||||
AnyTypeBox: gomp4.AnyTypeBox{
|
||||
@@ -256,7 +255,7 @@ func (track *InitTrack) marshal(w *mp4Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case *gortsplib.TrackMPEG4Audio:
|
||||
case *format.MPEG4Audio:
|
||||
_, err = w.writeBoxStart(&gomp4.AudioSampleEntry{ // <mp4a>
|
||||
SampleEntry: gomp4.SampleEntry{
|
||||
AnyTypeBox: gomp4.AnyTypeBox{
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/asticode/go-astits"
|
||||
)
|
||||
|
||||
@@ -38,8 +38,8 @@ func findMPEG4AudioConfig(dem *astits.Demuxer, pid uint16) (*mpeg4audio.Config,
|
||||
|
||||
// Track is a MPEG-TS track.
|
||||
type Track struct {
|
||||
ES *astits.PMTElementaryStream
|
||||
Track gortsplib.Track
|
||||
ES *astits.PMTElementaryStream
|
||||
Format format.Format
|
||||
}
|
||||
|
||||
// FindTracks finds the tracks in a MPEG-TS stream.
|
||||
@@ -77,8 +77,8 @@ func FindTracks(byts []byte) ([]*Track, error) {
|
||||
for _, t := range tracks {
|
||||
switch t.ES.StreamType {
|
||||
case astits.StreamTypeH264Video:
|
||||
t.Track = &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
t.Format = &format.H264{
|
||||
PayloadTyp: 96,
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ func FindTracks(byts []byte) ([]*Track, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Track = &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
t.Format = &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: conf,
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/asticode/go-astits"
|
||||
)
|
||||
|
||||
@@ -24,8 +24,8 @@ func (f writerFunc) Write(p []byte) (int, error) {
|
||||
|
||||
// Writer is a MPEG-TS writer.
|
||||
type Writer struct {
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoFormat *format.H264
|
||||
audioFormat *format.MPEG4Audio
|
||||
|
||||
buf *bytes.Buffer
|
||||
inner *astits.Muxer
|
||||
@@ -34,13 +34,13 @@ type Writer struct {
|
||||
|
||||
// NewWriter allocates a Writer.
|
||||
func NewWriter(
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoFormat *format.H264,
|
||||
audioFormat *format.MPEG4Audio,
|
||||
) *Writer {
|
||||
w := &Writer{
|
||||
videoTrack: videoTrack,
|
||||
audioTrack: audioTrack,
|
||||
buf: bytes.NewBuffer(nil),
|
||||
videoFormat: videoFormat,
|
||||
audioFormat: audioFormat,
|
||||
buf: bytes.NewBuffer(nil),
|
||||
}
|
||||
|
||||
w.inner = astits.NewMuxer(
|
||||
@@ -49,21 +49,21 @@ func NewWriter(
|
||||
return w.buf.Write(p)
|
||||
}))
|
||||
|
||||
if videoTrack != nil {
|
||||
if videoFormat != nil {
|
||||
w.inner.AddElementaryStream(astits.PMTElementaryStream{
|
||||
ElementaryPID: 256,
|
||||
StreamType: astits.StreamTypeH264Video,
|
||||
})
|
||||
}
|
||||
|
||||
if audioTrack != nil {
|
||||
if audioFormat != nil {
|
||||
w.inner.AddElementaryStream(astits.PMTElementaryStream{
|
||||
ElementaryPID: 257,
|
||||
StreamType: astits.StreamTypeAACAudio,
|
||||
})
|
||||
}
|
||||
|
||||
if videoTrack != nil {
|
||||
if videoFormat != nil {
|
||||
w.inner.SetPCRPID(256)
|
||||
} else {
|
||||
w.inner.SetPCRPID(257)
|
||||
@@ -155,9 +155,9 @@ func (w *Writer) WriteAAC(
|
||||
) error {
|
||||
pkts := mpeg4audio.ADTSPackets{
|
||||
{
|
||||
Type: w.audioTrack.Config.Type,
|
||||
SampleRate: w.audioTrack.Config.SampleRate,
|
||||
ChannelCount: w.audioTrack.Config.ChannelCount,
|
||||
Type: w.audioFormat.Config.Type,
|
||||
SampleRate: w.audioFormat.Config.SampleRate,
|
||||
ChannelCount: w.audioFormat.Config.ChannelCount,
|
||||
AU: au,
|
||||
},
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func (w *Writer) WriteAAC(
|
||||
RandomAccessIndicator: true,
|
||||
}
|
||||
|
||||
if w.videoTrack == nil {
|
||||
if w.videoFormat == nil {
|
||||
// send PCR once in a while
|
||||
if w.pcrCounter == 0 {
|
||||
af.HasPCR = true
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -21,15 +21,15 @@ func TestWriter(t *testing.T) {
|
||||
0x20,
|
||||
}
|
||||
|
||||
testVideoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
testVideoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
testAudioTrack := &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 97,
|
||||
testAudioTrack := &format.MPEG4Audio{
|
||||
PayloadTyp: 97,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
// MuxerFileResponse is a response of the Muxer's File() func.
|
||||
@@ -28,8 +28,8 @@ func NewMuxer(
|
||||
segmentDuration time.Duration,
|
||||
partDuration time.Duration,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
) (*Muxer, error) {
|
||||
m := &Muxer{}
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
type muxerPrimaryPlaylist struct {
|
||||
fmp4 bool
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
}
|
||||
|
||||
func newMuxerPrimaryPlaylist(
|
||||
fmp4 bool,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
) *muxerPrimaryPlaylist {
|
||||
return &muxerPrimaryPlaylist{
|
||||
fmp4: fmp4,
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -22,15 +22,15 @@ var testSPS = []byte{
|
||||
}
|
||||
|
||||
func TestMuxerVideoAudio(t *testing.T) {
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
audioTrack := &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 97,
|
||||
audioTrack := &format.MPEG4Audio{
|
||||
PayloadTyp: 97,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -180,8 +180,8 @@ func TestMuxerVideoAudio(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMuxerVideoOnly(t *testing.T) {
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
@@ -293,8 +293,8 @@ func TestMuxerVideoOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMuxerAudioOnly(t *testing.T) {
|
||||
audioTrack := &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 97,
|
||||
audioTrack := &format.MPEG4Audio{
|
||||
PayloadTyp: 97,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -405,8 +405,8 @@ func TestMuxerAudioOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMuxerCloseBeforeFirstSegmentReader(t *testing.T) {
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
@@ -430,8 +430,8 @@ func TestMuxerCloseBeforeFirstSegmentReader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMuxerMaxSegmentSize(t *testing.T) {
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
@@ -449,8 +449,8 @@ func TestMuxerMaxSegmentSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMuxerDoubleRead(t *testing.T) {
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: testSPS,
|
||||
PPS: []byte{0x08},
|
||||
PacketizationMode: 1,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
|
||||
)
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
type muxerVariantFMP4 struct {
|
||||
playlist *muxerVariantFMP4Playlist
|
||||
segmenter *muxerVariantFMP4Segmenter
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
|
||||
mutex sync.Mutex
|
||||
videoLastSPS []byte
|
||||
@@ -29,8 +29,8 @@ func newMuxerVariantFMP4(
|
||||
segmentDuration time.Duration,
|
||||
partDuration time.Duration,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
) *muxerVariantFMP4 {
|
||||
v := &muxerVariantFMP4{
|
||||
videoTrack: videoTrack,
|
||||
@@ -92,7 +92,7 @@ func (v *muxerVariantFMP4) file(name string, msn string, part string, skip strin
|
||||
init.Tracks = append(init.Tracks, &fmp4.InitTrack{
|
||||
ID: trackID,
|
||||
TimeScale: 90000,
|
||||
Track: v.videoTrack,
|
||||
Format: v.videoTrack,
|
||||
})
|
||||
trackID++
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func (v *muxerVariantFMP4) file(name string, msn string, part string, skip strin
|
||||
init.Tracks = append(init.Tracks, &fmp4.InitTrack{
|
||||
ID: trackID,
|
||||
TimeScale: uint32(v.audioTrack.ClockRate()),
|
||||
Track: v.audioTrack,
|
||||
Format: v.audioTrack,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
|
||||
)
|
||||
@@ -17,8 +17,8 @@ func fmp4PartName(id uint64) string {
|
||||
}
|
||||
|
||||
type muxerVariantFMP4Part struct {
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
id uint64
|
||||
|
||||
isIndependent bool
|
||||
@@ -33,8 +33,8 @@ type muxerVariantFMP4Part struct {
|
||||
}
|
||||
|
||||
func newMuxerVariantFMP4Part(
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
id uint64,
|
||||
) *muxerVariantFMP4Part {
|
||||
p := &muxerVariantFMP4Part{
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
type muxerVariantFMP4SegmentOrGap interface {
|
||||
@@ -70,8 +70,8 @@ func partTargetDuration(
|
||||
type muxerVariantFMP4Playlist struct {
|
||||
lowLatency bool
|
||||
segmentCount int
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
|
||||
mutex sync.Mutex
|
||||
cond *sync.Cond
|
||||
@@ -89,8 +89,8 @@ type muxerVariantFMP4Playlist struct {
|
||||
func newMuxerVariantFMP4Playlist(
|
||||
lowLatency bool,
|
||||
segmentCount int,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
) *muxerVariantFMP4Playlist {
|
||||
p := &muxerVariantFMP4Playlist{
|
||||
lowLatency: lowLatency,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
type partsReader struct {
|
||||
@@ -45,8 +45,8 @@ type muxerVariantFMP4Segment struct {
|
||||
startTime time.Time
|
||||
startDTS time.Duration
|
||||
segmentMaxSize uint64
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
genPartID func() uint64
|
||||
onPartFinalized func(*muxerVariantFMP4Part)
|
||||
|
||||
@@ -63,8 +63,8 @@ func newMuxerVariantFMP4Segment(
|
||||
startTime time.Time,
|
||||
startDTS time.Duration,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
genPartID func() uint64,
|
||||
onPartFinalized func(*muxerVariantFMP4Part),
|
||||
) *muxerVariantFMP4Segment {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/fmp4"
|
||||
)
|
||||
@@ -62,8 +62,8 @@ type muxerVariantFMP4Segmenter struct {
|
||||
segmentDuration time.Duration
|
||||
partDuration time.Duration
|
||||
segmentMaxSize uint64
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
onSegmentFinalized func(*muxerVariantFMP4Segment)
|
||||
onPartFinalized func(*muxerVariantFMP4Part)
|
||||
|
||||
@@ -87,8 +87,8 @@ func newMuxerVariantFMP4Segmenter(
|
||||
segmentDuration time.Duration,
|
||||
partDuration time.Duration,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
onSegmentFinalized func(*muxerVariantFMP4Segment),
|
||||
onPartFinalized func(*muxerVariantFMP4Part),
|
||||
) *muxerVariantFMP4Segmenter {
|
||||
|
||||
@@ -3,7 +3,7 @@ package hls
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
)
|
||||
|
||||
type muxerVariantMPEGTS struct {
|
||||
@@ -15,8 +15,8 @@ func newMuxerVariantMPEGTS(
|
||||
segmentCount int,
|
||||
segmentDuration time.Duration,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
) *muxerVariantMPEGTS {
|
||||
v := &muxerVariantMPEGTS{}
|
||||
|
||||
|
||||
@@ -7,15 +7,15 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/mpegts"
|
||||
)
|
||||
|
||||
type muxerVariantMPEGTSSegment struct {
|
||||
segmentMaxSize uint64
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
writer *mpegts.Writer
|
||||
|
||||
size uint64
|
||||
@@ -31,8 +31,8 @@ func newMuxerVariantMPEGTSSegment(
|
||||
id uint64,
|
||||
startTime time.Time,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
writer *mpegts.Writer,
|
||||
) *muxerVariantMPEGTSSegment {
|
||||
t := &muxerVariantMPEGTSSegment{
|
||||
|
||||
@@ -3,8 +3,8 @@ package hls
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/hls/mpegts"
|
||||
)
|
||||
@@ -16,8 +16,8 @@ const (
|
||||
type muxerVariantMPEGTSSegmenter struct {
|
||||
segmentDuration time.Duration
|
||||
segmentMaxSize uint64
|
||||
videoTrack *gortsplib.TrackH264
|
||||
audioTrack *gortsplib.TrackMPEG4Audio
|
||||
videoTrack *format.H264
|
||||
audioTrack *format.MPEG4Audio
|
||||
onSegmentReady func(*muxerVariantMPEGTSSegment)
|
||||
|
||||
writer *mpegts.Writer
|
||||
@@ -31,8 +31,8 @@ type muxerVariantMPEGTSSegmenter struct {
|
||||
func newMuxerVariantMPEGTSSegmenter(
|
||||
segmentDuration time.Duration,
|
||||
segmentMaxSize uint64,
|
||||
videoTrack *gortsplib.TrackH264,
|
||||
audioTrack *gortsplib.TrackMPEG4Audio,
|
||||
videoTrack *format.H264,
|
||||
audioTrack *format.MPEG4Audio,
|
||||
onSegmentReady func(*muxerVariantMPEGTSSegment),
|
||||
) *muxerVariantMPEGTSSegmenter {
|
||||
m := &muxerVariantMPEGTSSegmenter{
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
"github.com/aler9/gortsplib/v2/pkg/h264"
|
||||
)
|
||||
|
||||
//go:embed exe/exe
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/notedit/rtmp/format/flv/flvio"
|
||||
|
||||
"github.com/aler9/rtsp-simple-server/internal/rtmp/bytecounter"
|
||||
@@ -580,30 +580,30 @@ func (c *Conn) WriteMessage(msg message.Message) error {
|
||||
return c.mrw.Write(msg)
|
||||
}
|
||||
|
||||
func trackFromH264DecoderConfig(data []byte) (*gortsplib.TrackH264, error) {
|
||||
func trackFromH264DecoderConfig(data []byte) (*format.H264, error) {
|
||||
var conf h264conf.Conf
|
||||
err := conf.Unmarshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse H264 config: %v", err)
|
||||
}
|
||||
|
||||
return &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
return &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: conf.SPS,
|
||||
PPS: conf.PPS,
|
||||
PacketizationMode: 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func trackFromAACDecoderConfig(data []byte) (*gortsplib.TrackMPEG4Audio, error) {
|
||||
func trackFromAACDecoderConfig(data []byte) (*format.MPEG4Audio, error) {
|
||||
var mpegConf mpeg4audio.Config
|
||||
err := mpegConf.Unmarshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
return &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpegConf,
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
@@ -613,7 +613,7 @@ func trackFromAACDecoderConfig(data []byte) (*gortsplib.TrackMPEG4Audio, error)
|
||||
|
||||
var errEmptyMetadata = errors.New("metadata is empty")
|
||||
|
||||
func (c *Conn) readTracksFromMetadata(payload []interface{}) (*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio, error) {
|
||||
func (c *Conn) readTracksFromMetadata(payload []interface{}) (*format.H264, *format.MPEG4Audio, error) {
|
||||
if len(payload) != 1 {
|
||||
return nil, nil, fmt.Errorf("invalid metadata")
|
||||
}
|
||||
@@ -683,8 +683,8 @@ func (c *Conn) readTracksFromMetadata(payload []interface{}) (*gortsplib.TrackH2
|
||||
return nil, nil, errEmptyMetadata
|
||||
}
|
||||
|
||||
var videoTrack *gortsplib.TrackH264
|
||||
var audioTrack *gortsplib.TrackMPEG4Audio
|
||||
var videoTrack *format.H264
|
||||
var audioTrack *format.MPEG4Audio
|
||||
|
||||
for {
|
||||
msg, err := c.ReadMessage()
|
||||
@@ -733,10 +733,10 @@ func (c *Conn) readTracksFromMetadata(payload []interface{}) (*gortsplib.TrackH2
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) readTracksFromMessages(msg message.Message) (*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio, error) {
|
||||
func (c *Conn) readTracksFromMessages(msg message.Message) (*format.H264, *format.MPEG4Audio, error) {
|
||||
var startTime *time.Duration
|
||||
var videoTrack *gortsplib.TrackH264
|
||||
var audioTrack *gortsplib.TrackMPEG4Audio
|
||||
var videoTrack *format.H264
|
||||
var audioTrack *format.MPEG4Audio
|
||||
|
||||
// analyze 1 second of packets
|
||||
outer:
|
||||
@@ -808,7 +808,7 @@ outer:
|
||||
}
|
||||
|
||||
// ReadTracks reads track informations.
|
||||
func (c *Conn) ReadTracks() (*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio, error) {
|
||||
func (c *Conn) ReadTracks() (*format.H264, *format.MPEG4Audio, error) {
|
||||
msg, err := func() (message.Message, error) {
|
||||
for {
|
||||
msg, err := c.ReadMessage()
|
||||
@@ -867,7 +867,7 @@ func (c *Conn) ReadTracks() (*gortsplib.TrackH264, *gortsplib.TrackMPEG4Audio, e
|
||||
}
|
||||
|
||||
// WriteTracks writes track informations.
|
||||
func (c *Conn) WriteTracks(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackMPEG4Audio) error {
|
||||
func (c *Conn) WriteTracks(videoTrack *format.H264, audioTrack *format.MPEG4Audio) error {
|
||||
err := c.WriteMessage(&message.MsgDataAMF0{
|
||||
ChunkStreamID: 4,
|
||||
MessageStreamID: 0x1000000,
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/aler9/gortsplib"
|
||||
"github.com/aler9/gortsplib/pkg/mpeg4audio"
|
||||
"github.com/aler9/gortsplib/v2/pkg/format"
|
||||
"github.com/aler9/gortsplib/v2/pkg/mpeg4audio"
|
||||
"github.com/notedit/rtmp/format/flv/flvio"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -498,15 +498,15 @@ func TestReadTracks(t *testing.T) {
|
||||
|
||||
switch ca {
|
||||
case "video+audio":
|
||||
require.Equal(t, &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: sps,
|
||||
PPS: pps,
|
||||
PacketizationMode: 1,
|
||||
}, videoTrack)
|
||||
|
||||
require.Equal(t, &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -518,8 +518,8 @@ func TestReadTracks(t *testing.T) {
|
||||
}, audioTrack)
|
||||
|
||||
case "video":
|
||||
require.Equal(t, &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: sps,
|
||||
PPS: pps,
|
||||
PacketizationMode: 1,
|
||||
@@ -528,15 +528,15 @@ func TestReadTracks(t *testing.T) {
|
||||
require.Nil(t, audioTrack)
|
||||
|
||||
case "metadata without codec id":
|
||||
require.Equal(t, &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: sps,
|
||||
PPS: pps,
|
||||
PacketizationMode: 1,
|
||||
}, videoTrack)
|
||||
|
||||
require.Equal(t, &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -548,15 +548,15 @@ func TestReadTracks(t *testing.T) {
|
||||
}, audioTrack)
|
||||
|
||||
case "missing metadata":
|
||||
require.Equal(t, &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: sps,
|
||||
PPS: pps,
|
||||
PacketizationMode: 1,
|
||||
}, videoTrack)
|
||||
|
||||
require.Equal(t, &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
require.Equal(t, &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
@@ -930,8 +930,8 @@ func TestWriteTracks(t *testing.T) {
|
||||
_, _, err = rconn.InitializeServer()
|
||||
require.NoError(t, err)
|
||||
|
||||
videoTrack := &gortsplib.TrackH264{
|
||||
PayloadType: 96,
|
||||
videoTrack := &format.H264{
|
||||
PayloadTyp: 96,
|
||||
SPS: []byte{
|
||||
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
|
||||
@@ -943,8 +943,8 @@ func TestWriteTracks(t *testing.T) {
|
||||
PacketizationMode: 1,
|
||||
}
|
||||
|
||||
audioTrack := &gortsplib.TrackMPEG4Audio{
|
||||
PayloadType: 96,
|
||||
audioTrack := &format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
|
||||
Reference in New Issue
Block a user