2023-07-31 19:20:09 +00:00
|
|
|
package core
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
|
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
2023-07-31 19:20:09 +00:00
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
|
|
|
|
"github.com/bluenviron/mediacommon/pkg/formats/mpegts"
|
|
|
|
"github.com/datarhei/gosrt"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
2023-07-31 19:20:09 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/conf"
|
2023-08-05 15:18:04 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/externalcmd"
|
2023-07-31 19:20:09 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/logger"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/stream"
|
2023-08-25 16:11:02 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/unit"
|
2023-07-31 19:20:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func durationGoToMPEGTS(v time.Duration) int64 {
|
|
|
|
return int64(v.Seconds() * 90000)
|
|
|
|
}
|
|
|
|
|
|
|
|
type srtConnState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
srtConnStateRead srtConnState = iota + 1
|
|
|
|
srtConnStatePublish
|
|
|
|
)
|
|
|
|
|
|
|
|
type srtConnPathManager interface {
|
|
|
|
addReader(req pathAddReaderReq) pathAddReaderRes
|
|
|
|
addPublisher(req pathAddPublisherReq) pathAddPublisherRes
|
|
|
|
}
|
|
|
|
|
|
|
|
type srtConnParent interface {
|
|
|
|
logger.Writer
|
|
|
|
closeConn(*srtConn)
|
|
|
|
}
|
|
|
|
|
|
|
|
type srtConn struct {
|
|
|
|
readTimeout conf.StringDuration
|
|
|
|
writeTimeout conf.StringDuration
|
2023-08-26 11:25:21 +00:00
|
|
|
writeQueueSize int
|
2023-07-31 19:20:09 +00:00
|
|
|
udpMaxPayloadSize int
|
|
|
|
connReq srt.ConnRequest
|
|
|
|
wg *sync.WaitGroup
|
2023-08-05 15:18:04 +00:00
|
|
|
externalCmdPool *externalcmd.Pool
|
2023-07-31 19:20:09 +00:00
|
|
|
pathManager srtConnPathManager
|
|
|
|
parent srtConnParent
|
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
ctxCancel func()
|
|
|
|
created time.Time
|
|
|
|
uuid uuid.UUID
|
|
|
|
mutex sync.RWMutex
|
|
|
|
state srtConnState
|
|
|
|
pathName string
|
|
|
|
conn srt.Conn
|
|
|
|
|
|
|
|
chNew chan srtNewConnReq
|
|
|
|
chSetConn chan srt.Conn
|
|
|
|
}
|
|
|
|
|
|
|
|
func newSRTConn(
|
|
|
|
parentCtx context.Context,
|
|
|
|
readTimeout conf.StringDuration,
|
|
|
|
writeTimeout conf.StringDuration,
|
2023-08-26 11:25:21 +00:00
|
|
|
writeQueueSize int,
|
2023-07-31 19:20:09 +00:00
|
|
|
udpMaxPayloadSize int,
|
|
|
|
connReq srt.ConnRequest,
|
|
|
|
wg *sync.WaitGroup,
|
2023-08-05 15:18:04 +00:00
|
|
|
externalCmdPool *externalcmd.Pool,
|
2023-07-31 19:20:09 +00:00
|
|
|
pathManager srtConnPathManager,
|
|
|
|
parent srtConnParent,
|
|
|
|
) *srtConn {
|
|
|
|
ctx, ctxCancel := context.WithCancel(parentCtx)
|
|
|
|
|
|
|
|
c := &srtConn{
|
|
|
|
readTimeout: readTimeout,
|
|
|
|
writeTimeout: writeTimeout,
|
2023-08-26 11:25:21 +00:00
|
|
|
writeQueueSize: writeQueueSize,
|
2023-07-31 19:20:09 +00:00
|
|
|
udpMaxPayloadSize: udpMaxPayloadSize,
|
|
|
|
connReq: connReq,
|
|
|
|
wg: wg,
|
2023-08-05 15:18:04 +00:00
|
|
|
externalCmdPool: externalCmdPool,
|
2023-07-31 19:20:09 +00:00
|
|
|
pathManager: pathManager,
|
|
|
|
parent: parent,
|
|
|
|
ctx: ctx,
|
|
|
|
ctxCancel: ctxCancel,
|
|
|
|
created: time.Now(),
|
|
|
|
uuid: uuid.New(),
|
|
|
|
chNew: make(chan srtNewConnReq),
|
|
|
|
chSetConn: make(chan srt.Conn),
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Log(logger.Info, "opened")
|
|
|
|
|
|
|
|
c.wg.Add(1)
|
|
|
|
go c.run()
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) close() {
|
|
|
|
c.ctxCancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) Log(level logger.Level, format string, args ...interface{}) {
|
|
|
|
c.parent.Log(level, "[conn %v] "+format, append([]interface{}{c.connReq.RemoteAddr()}, args...)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) ip() net.IP {
|
|
|
|
return c.connReq.RemoteAddr().(*net.UDPAddr).IP
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) run() {
|
|
|
|
defer c.wg.Done()
|
|
|
|
|
|
|
|
err := c.runInner()
|
|
|
|
|
|
|
|
c.ctxCancel()
|
|
|
|
|
|
|
|
c.parent.closeConn(c)
|
|
|
|
|
2023-09-09 21:37:56 +00:00
|
|
|
c.Log(logger.Info, "closed: %v", err)
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) runInner() error {
|
|
|
|
var req srtNewConnReq
|
|
|
|
select {
|
|
|
|
case req = <-c.chNew:
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
return errors.New("terminated")
|
|
|
|
}
|
|
|
|
|
|
|
|
answerSent, err := c.runInner2(req)
|
|
|
|
|
|
|
|
if !answerSent {
|
|
|
|
req.res <- nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) runInner2(req srtNewConnReq) (bool, error) {
|
|
|
|
parts := strings.Split(req.connReq.StreamId(), ":")
|
|
|
|
if (len(parts) != 2 && len(parts) != 4) || (parts[0] != "read" && parts[0] != "publish") {
|
|
|
|
return false, fmt.Errorf("invalid streamid '%s':"+
|
|
|
|
" it must be 'action:pathname' or 'action:pathname:user:pass', "+
|
|
|
|
"where action is either read or publish, pathname is the path name, user and pass are the credentials",
|
|
|
|
req.connReq.StreamId())
|
|
|
|
}
|
|
|
|
|
|
|
|
pathName := parts[1]
|
|
|
|
user := ""
|
|
|
|
pass := ""
|
|
|
|
|
|
|
|
if len(parts) == 4 {
|
|
|
|
user, pass = parts[2], parts[3]
|
|
|
|
}
|
|
|
|
|
|
|
|
if parts[0] == "publish" {
|
|
|
|
return c.runPublish(req, pathName, user, pass)
|
|
|
|
}
|
|
|
|
return c.runRead(req, pathName, user, pass)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) runPublish(req srtNewConnReq, pathName string, user string, pass string) (bool, error) {
|
|
|
|
res := c.pathManager.addPublisher(pathAddPublisherReq{
|
|
|
|
author: c,
|
|
|
|
pathName: pathName,
|
|
|
|
credentials: authCredentials{
|
|
|
|
ip: c.ip(),
|
|
|
|
user: user,
|
|
|
|
pass: pass,
|
|
|
|
proto: authProtocolSRT,
|
|
|
|
id: &c.uuid,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
if res.err != nil {
|
|
|
|
if terr, ok := res.err.(*errAuthentication); ok {
|
|
|
|
// TODO: re-enable. Currently this freezes the listener.
|
|
|
|
// wait some seconds to stop brute force attacks
|
|
|
|
// <-time.After(srtPauseAfterAuthError)
|
|
|
|
return false, terr
|
|
|
|
}
|
|
|
|
return false, res.err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer res.path.removePublisher(pathRemovePublisherReq{author: c})
|
|
|
|
|
|
|
|
sconn, err := c.exchangeRequestWithConn(req)
|
|
|
|
if err != nil {
|
|
|
|
return true, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.mutex.Lock()
|
|
|
|
c.state = srtConnStatePublish
|
|
|
|
c.pathName = pathName
|
|
|
|
c.conn = sconn
|
|
|
|
c.mutex.Unlock()
|
|
|
|
|
|
|
|
readerErr := make(chan error)
|
|
|
|
go func() {
|
|
|
|
readerErr <- c.runPublishReader(sconn, res.path)
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case err := <-readerErr:
|
|
|
|
sconn.Close()
|
|
|
|
return true, err
|
|
|
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
sconn.Close()
|
|
|
|
<-readerErr
|
|
|
|
return true, errors.New("terminated")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
|
|
|
|
sconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout)))
|
|
|
|
r, err := mpegts.NewReader(mpegts.NewBufferedReader(sconn))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
decodeErrLogger := logger.NewLimitedLogger(c)
|
2023-08-26 21:34:39 +00:00
|
|
|
|
|
|
|
r.OnDecodeError(func(err error) {
|
|
|
|
decodeErrLogger.Log(logger.Warn, err.Error())
|
|
|
|
})
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
var medias []*description.Media //nolint:prealloc
|
2023-07-31 19:20:09 +00:00
|
|
|
var stream *stream.Stream
|
|
|
|
|
|
|
|
var td *mpegts.TimeDecoder
|
|
|
|
decodeTime := func(t int64) time.Duration {
|
|
|
|
if td == nil {
|
|
|
|
td = mpegts.NewTimeDecoder(t)
|
|
|
|
}
|
|
|
|
return td.Decode(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, track := range r.Tracks() { //nolint:dupl
|
2023-08-26 16:54:28 +00:00
|
|
|
var medi *description.Media
|
2023-07-31 19:20:09 +00:00
|
|
|
|
|
|
|
switch tcodec := track.Codec.(type) {
|
2023-08-31 21:01:47 +00:00
|
|
|
case *mpegts.CodecH265:
|
2023-08-26 16:54:28 +00:00
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeVideo,
|
2023-08-31 21:01:47 +00:00
|
|
|
Formats: []format.Format{&format.H265{
|
|
|
|
PayloadTyp: 96,
|
2023-07-31 19:20:09 +00:00
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
r.OnDataH26x(track, func(pts int64, _ int64, au [][]byte) error {
|
2023-08-31 21:01:47 +00:00
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H265{
|
2023-08-25 16:11:02 +00:00
|
|
|
Base: unit.Base{
|
2023-07-31 19:20:09 +00:00
|
|
|
NTP: time.Now(),
|
2023-08-26 16:54:28 +00:00
|
|
|
PTS: decodeTime(pts),
|
2023-07-31 19:20:09 +00:00
|
|
|
},
|
2023-08-26 16:54:28 +00:00
|
|
|
AU: au,
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2023-08-31 21:01:47 +00:00
|
|
|
case *mpegts.CodecH264:
|
2023-08-26 16:54:28 +00:00
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeVideo,
|
2023-08-31 21:01:47 +00:00
|
|
|
Formats: []format.Format{&format.H264{
|
|
|
|
PayloadTyp: 96,
|
|
|
|
PacketizationMode: 1,
|
2023-07-31 19:20:09 +00:00
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
r.OnDataH26x(track, func(pts int64, _ int64, au [][]byte) error {
|
2023-08-31 21:01:47 +00:00
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.H264{
|
2023-08-25 16:11:02 +00:00
|
|
|
Base: unit.Base{
|
2023-07-31 19:20:09 +00:00
|
|
|
NTP: time.Now(),
|
2023-08-26 16:54:28 +00:00
|
|
|
PTS: decodeTime(pts),
|
2023-07-31 19:20:09 +00:00
|
|
|
},
|
2023-08-26 16:54:28 +00:00
|
|
|
AU: au,
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2023-09-16 15:23:40 +00:00
|
|
|
case *mpegts.CodecMPEG4Video:
|
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeVideo,
|
|
|
|
Formats: []format.Format{&format.MPEG4Video{
|
|
|
|
PayloadTyp: 96,
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
r.OnDataMPEGxVideo(track, func(pts int64, frame []byte) error {
|
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4Video{
|
|
|
|
Base: unit.Base{
|
|
|
|
NTP: time.Now(),
|
|
|
|
PTS: decodeTime(pts),
|
|
|
|
},
|
|
|
|
Frame: frame,
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
case *mpegts.CodecMPEG1Video:
|
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeVideo,
|
|
|
|
Formats: []format.Format{&format.MPEG1Video{}},
|
|
|
|
}
|
|
|
|
|
|
|
|
r.OnDataMPEGxVideo(track, func(pts int64, frame []byte) error {
|
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Video{
|
|
|
|
Base: unit.Base{
|
|
|
|
NTP: time.Now(),
|
|
|
|
PTS: decodeTime(pts),
|
|
|
|
},
|
|
|
|
Frame: frame,
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2023-08-31 21:01:47 +00:00
|
|
|
case *mpegts.CodecOpus:
|
2023-08-26 16:54:28 +00:00
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeAudio,
|
2023-08-31 21:01:47 +00:00
|
|
|
Formats: []format.Format{&format.Opus{
|
|
|
|
PayloadTyp: 96,
|
|
|
|
IsStereo: (tcodec.ChannelCount == 2),
|
2023-07-31 19:20:09 +00:00
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
2023-08-31 21:01:47 +00:00
|
|
|
r.OnDataOpus(track, func(pts int64, packets [][]byte) error {
|
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.Opus{
|
2023-08-25 16:11:02 +00:00
|
|
|
Base: unit.Base{
|
2023-07-31 19:20:09 +00:00
|
|
|
NTP: time.Now(),
|
2023-08-26 16:54:28 +00:00
|
|
|
PTS: decodeTime(pts),
|
2023-07-31 19:20:09 +00:00
|
|
|
},
|
2023-08-31 21:01:47 +00:00
|
|
|
Packets: packets,
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2023-08-31 21:01:47 +00:00
|
|
|
case *mpegts.CodecMPEG4Audio:
|
2023-08-26 16:54:28 +00:00
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeAudio,
|
2023-08-31 21:01:47 +00:00
|
|
|
Formats: []format.Format{&format.MPEG4Audio{
|
|
|
|
PayloadTyp: 96,
|
|
|
|
SizeLength: 13,
|
|
|
|
IndexLength: 3,
|
|
|
|
IndexDeltaLength: 3,
|
|
|
|
Config: &tcodec.Config,
|
2023-07-31 19:20:09 +00:00
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
2023-08-31 21:01:47 +00:00
|
|
|
r.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) error {
|
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioGeneric{
|
2023-08-25 16:11:02 +00:00
|
|
|
Base: unit.Base{
|
2023-07-31 19:20:09 +00:00
|
|
|
NTP: time.Now(),
|
2023-08-26 16:54:28 +00:00
|
|
|
PTS: decodeTime(pts),
|
2023-07-31 19:20:09 +00:00
|
|
|
},
|
2023-08-31 21:01:47 +00:00
|
|
|
AUs: aus,
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
2023-08-05 12:47:20 +00:00
|
|
|
|
|
|
|
case *mpegts.CodecMPEG1Audio:
|
2023-08-26 16:54:28 +00:00
|
|
|
medi = &description.Media{
|
|
|
|
Type: description.MediaTypeAudio,
|
|
|
|
Formats: []format.Format{&format.MPEG1Audio{}},
|
2023-08-05 12:47:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
|
2023-08-25 16:11:02 +00:00
|
|
|
stream.WriteUnit(medi, medi.Formats[0], &unit.MPEG1Audio{
|
|
|
|
Base: unit.Base{
|
2023-08-05 12:47:20 +00:00
|
|
|
NTP: time.Now(),
|
2023-08-26 16:54:28 +00:00
|
|
|
PTS: decodeTime(pts),
|
2023-08-05 12:47:20 +00:00
|
|
|
},
|
|
|
|
Frames: frames,
|
|
|
|
})
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
default:
|
|
|
|
continue
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
medias = append(medias, medi)
|
|
|
|
}
|
|
|
|
|
2023-08-05 12:47:20 +00:00
|
|
|
if len(medias) == 0 {
|
|
|
|
return fmt.Errorf("no supported tracks found")
|
|
|
|
}
|
|
|
|
|
2023-07-31 19:20:09 +00:00
|
|
|
rres := path.startPublisher(pathStartPublisherReq{
|
|
|
|
author: c,
|
2023-08-26 16:54:28 +00:00
|
|
|
desc: &description.Session{Medias: medias},
|
2023-07-31 19:20:09 +00:00
|
|
|
generateRTPPackets: true,
|
|
|
|
})
|
|
|
|
if rres.err != nil {
|
|
|
|
return rres.err
|
|
|
|
}
|
|
|
|
|
|
|
|
stream = rres.stream
|
|
|
|
|
|
|
|
for {
|
|
|
|
err := r.Read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass string) (bool, error) {
|
|
|
|
res := c.pathManager.addReader(pathAddReaderReq{
|
|
|
|
author: c,
|
|
|
|
pathName: pathName,
|
|
|
|
credentials: authCredentials{
|
|
|
|
ip: c.ip(),
|
|
|
|
user: user,
|
|
|
|
pass: pass,
|
|
|
|
proto: authProtocolSRT,
|
|
|
|
id: &c.uuid,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
if res.err != nil {
|
|
|
|
if terr, ok := res.err.(*errAuthentication); ok {
|
|
|
|
// TODO: re-enable. Currently this freezes the listener.
|
|
|
|
// wait some seconds to stop brute force attacks
|
|
|
|
// <-time.After(srtPauseAfterAuthError)
|
|
|
|
return false, terr
|
|
|
|
}
|
|
|
|
return false, res.err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer res.path.removeReader(pathRemoveReaderReq{author: c})
|
|
|
|
|
|
|
|
sconn, err := c.exchangeRequestWithConn(req)
|
|
|
|
if err != nil {
|
|
|
|
return true, err
|
|
|
|
}
|
|
|
|
defer sconn.Close()
|
|
|
|
|
|
|
|
c.mutex.Lock()
|
|
|
|
c.state = srtConnStateRead
|
|
|
|
c.pathName = pathName
|
|
|
|
c.conn = sconn
|
|
|
|
c.mutex.Unlock()
|
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
writer := asyncwriter.New(c.writeQueueSize, c)
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-31 21:01:47 +00:00
|
|
|
defer res.stream.RemoveReader(writer)
|
|
|
|
|
2023-07-31 19:20:09 +00:00
|
|
|
var w *mpegts.Writer
|
|
|
|
var tracks []*mpegts.Track
|
2023-08-26 16:54:28 +00:00
|
|
|
var medias []*description.Media
|
2023-07-31 19:20:09 +00:00
|
|
|
bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize))
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
addTrack := func(medi *description.Media, codec mpegts.Codec) *mpegts.Track {
|
2023-08-05 12:47:20 +00:00
|
|
|
track := &mpegts.Track{
|
|
|
|
Codec: codec,
|
|
|
|
}
|
|
|
|
tracks = append(tracks, track)
|
|
|
|
medias = append(medias, medi)
|
|
|
|
return track
|
|
|
|
}
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
for _, medi := range res.stream.Desc().Medias {
|
|
|
|
for _, forma := range medi.Formats {
|
|
|
|
switch forma := forma.(type) {
|
|
|
|
case *format.H265: //nolint:dupl
|
2023-08-05 12:47:20 +00:00
|
|
|
track := addTrack(medi, &mpegts.CodecH265{})
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
var dtsExtractor *h265.DTSExtractor
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.H265)
|
|
|
|
if tunit.AU == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
randomAccess := h265.IsRandomAccess(tunit.AU)
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
if dtsExtractor == nil {
|
|
|
|
if !randomAccess {
|
|
|
|
return nil
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
2023-08-30 09:24:14 +00:00
|
|
|
dtsExtractor = h265.NewDTSExtractor()
|
|
|
|
}
|
|
|
|
|
|
|
|
dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
|
|
|
err = w.WriteH26x(track, durationGoToMPEGTS(tunit.PTS), durationGoToMPEGTS(dts), randomAccess, tunit.AU)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.H264: //nolint:dupl
|
2023-08-05 12:47:20 +00:00
|
|
|
track := addTrack(medi, &mpegts.CodecH264{})
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
var dtsExtractor *h264.DTSExtractor
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.H264)
|
|
|
|
if tunit.AU == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
idrPresent := h264.IDRPresent(tunit.AU)
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
if dtsExtractor == nil {
|
|
|
|
if !idrPresent {
|
|
|
|
return nil
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
2023-08-30 09:24:14 +00:00
|
|
|
dtsExtractor = h264.NewDTSExtractor()
|
|
|
|
}
|
|
|
|
|
|
|
|
dts, err := dtsExtractor.Extract(tunit.AU, tunit.PTS)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
|
|
|
err = w.WriteH26x(track, durationGoToMPEGTS(tunit.PTS), durationGoToMPEGTS(dts), idrPresent, tunit.AU)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
|
2023-09-16 15:23:40 +00:00
|
|
|
case *format.MPEG4Video:
|
|
|
|
track := addTrack(medi, &mpegts.CodecMPEG4Video{})
|
|
|
|
|
2023-09-16 15:27:07 +00:00
|
|
|
firstReceived := false
|
|
|
|
var lastPTS time.Duration
|
|
|
|
|
2023-09-16 15:23:40 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.MPEG4Video)
|
|
|
|
if tunit.Frame == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-16 15:27:07 +00:00
|
|
|
if !firstReceived {
|
|
|
|
firstReceived = true
|
|
|
|
} else if tunit.PTS < lastPTS {
|
|
|
|
return fmt.Errorf("MPEG-4 Video streams with B-frames are not supported (yet)")
|
|
|
|
}
|
|
|
|
lastPTS = tunit.PTS
|
|
|
|
|
2023-09-16 15:23:40 +00:00
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
2023-09-16 15:27:07 +00:00
|
|
|
err = w.WriteMPEG4Video(track, durationGoToMPEGTS(tunit.PTS), tunit.Frame)
|
2023-09-16 15:23:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
|
|
|
})
|
|
|
|
|
|
|
|
case *format.MPEG1Video:
|
|
|
|
track := addTrack(medi, &mpegts.CodecMPEG1Video{})
|
|
|
|
|
2023-09-16 15:27:07 +00:00
|
|
|
firstReceived := false
|
|
|
|
var lastPTS time.Duration
|
|
|
|
|
2023-09-16 15:23:40 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.MPEG1Video)
|
|
|
|
if tunit.Frame == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-16 15:27:07 +00:00
|
|
|
if !firstReceived {
|
|
|
|
firstReceived = true
|
|
|
|
} else if tunit.PTS < lastPTS {
|
|
|
|
return fmt.Errorf("MPEG-1 Video streams with B-frames are not supported (yet)")
|
|
|
|
}
|
|
|
|
lastPTS = tunit.PTS
|
|
|
|
|
2023-09-16 15:23:40 +00:00
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
2023-09-16 15:27:07 +00:00
|
|
|
err = w.WriteMPEG1Video(track, durationGoToMPEGTS(tunit.PTS), tunit.Frame)
|
2023-09-16 15:23:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
|
|
|
})
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.MPEG4AudioGeneric:
|
2023-08-05 12:47:20 +00:00
|
|
|
track := addTrack(medi, &mpegts.CodecMPEG4Audio{
|
2023-08-26 16:54:28 +00:00
|
|
|
Config: *forma.Config,
|
2023-08-05 12:47:20 +00:00
|
|
|
})
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.MPEG4AudioGeneric)
|
|
|
|
if tunit.AUs == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
|
|
|
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(tunit.PTS), tunit.AUs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.MPEG4AudioLATM:
|
|
|
|
if forma.Config != nil &&
|
|
|
|
len(forma.Config.Programs) == 1 &&
|
|
|
|
len(forma.Config.Programs[0].Layers) == 1 {
|
2023-08-05 12:47:20 +00:00
|
|
|
track := addTrack(medi, &mpegts.CodecMPEG4Audio{
|
2023-08-26 16:54:28 +00:00
|
|
|
Config: *forma.Config.Programs[0].Layers[0].AudioSpecificConfig,
|
2023-08-05 12:47:20 +00:00
|
|
|
})
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.MPEG4AudioLATM)
|
|
|
|
if tunit.AU == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
|
|
|
err = w.WriteMPEG4Audio(track, durationGoToMPEGTS(tunit.PTS), [][]byte{tunit.AU})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.Opus:
|
2023-08-05 12:47:20 +00:00
|
|
|
track := addTrack(medi, &mpegts.CodecOpus{
|
|
|
|
ChannelCount: func() int {
|
2023-08-26 16:54:28 +00:00
|
|
|
if forma.IsStereo {
|
2023-08-05 12:47:20 +00:00
|
|
|
return 2
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}(),
|
|
|
|
})
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.Opus)
|
|
|
|
if tunit.Packets == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
|
|
|
err = w.WriteOpus(track, durationGoToMPEGTS(tunit.PTS), tunit.Packets)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
2023-07-31 19:20:09 +00:00
|
|
|
})
|
2023-08-05 12:47:20 +00:00
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.MPEG1Audio:
|
2023-08-05 12:47:20 +00:00
|
|
|
track := addTrack(medi, &mpegts.CodecMPEG1Audio{})
|
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
res.stream.AddReader(writer, medi, forma, func(u unit.Unit) error {
|
|
|
|
tunit := u.(*unit.MPEG1Audio)
|
|
|
|
if tunit.Frames == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
|
|
|
|
err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(tunit.PTS), tunit.Frames)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return bw.Flush()
|
2023-08-05 12:47:20 +00:00
|
|
|
})
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(tracks) == 0 {
|
|
|
|
return true, fmt.Errorf(
|
2023-08-06 15:56:11 +00:00
|
|
|
"the stream doesn't contain any supported codec, which are currently H265, H264, Opus, MPEG-4 Audio")
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c.Log(logger.Info, "is reading from path '%s', %s",
|
|
|
|
res.path.name, sourceMediaInfo(medias))
|
|
|
|
|
2023-08-05 15:18:04 +00:00
|
|
|
pathConf := res.path.safeConf()
|
|
|
|
|
|
|
|
if pathConf.RunOnRead != "" {
|
|
|
|
c.Log(logger.Info, "runOnRead command started")
|
|
|
|
onReadCmd := externalcmd.NewCmd(
|
|
|
|
c.externalCmdPool,
|
|
|
|
pathConf.RunOnRead,
|
|
|
|
pathConf.RunOnReadRestart,
|
|
|
|
res.path.externalCmdEnv(),
|
|
|
|
func(err error) {
|
|
|
|
c.Log(logger.Info, "runOnRead command exited: %v", err)
|
|
|
|
})
|
|
|
|
defer func() {
|
|
|
|
onReadCmd.Close()
|
|
|
|
c.Log(logger.Info, "runOnRead command stopped")
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2023-07-31 19:20:09 +00:00
|
|
|
w = mpegts.NewWriter(bw, tracks)
|
|
|
|
|
|
|
|
// disable read deadline
|
|
|
|
sconn.SetReadDeadline(time.Time{})
|
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
writer.Start()
|
2023-07-31 19:20:09 +00:00
|
|
|
|
2023-08-26 17:45:10 +00:00
|
|
|
select {
|
|
|
|
case <-c.ctx.Done():
|
2023-08-30 09:24:14 +00:00
|
|
|
writer.Stop()
|
2023-08-26 17:45:10 +00:00
|
|
|
return true, fmt.Errorf("terminated")
|
|
|
|
|
2023-08-30 09:24:14 +00:00
|
|
|
case err := <-writer.Error():
|
2023-08-26 17:45:10 +00:00
|
|
|
return true, err
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) exchangeRequestWithConn(req srtNewConnReq) (srt.Conn, error) {
|
|
|
|
req.res <- c
|
|
|
|
|
|
|
|
select {
|
|
|
|
case sconn := <-c.chSetConn:
|
|
|
|
return sconn, nil
|
|
|
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
return nil, errors.New("terminated")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// new is called by srtListener through srtServer.
|
|
|
|
func (c *srtConn) new(req srtNewConnReq) *srtConn {
|
|
|
|
select {
|
|
|
|
case c.chNew <- req:
|
|
|
|
return <-req.res
|
|
|
|
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// setConn is called by srtListener .
|
|
|
|
func (c *srtConn) setConn(sconn srt.Conn) {
|
|
|
|
select {
|
|
|
|
case c.chSetConn <- sconn:
|
|
|
|
case <-c.ctx.Done():
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// apiReaderDescribe implements reader.
|
|
|
|
func (c *srtConn) apiReaderDescribe() pathAPISourceOrReader {
|
|
|
|
return pathAPISourceOrReader{
|
|
|
|
Type: "srtConn",
|
|
|
|
ID: c.uuid.String(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// apiSourceDescribe implements source.
|
|
|
|
func (c *srtConn) apiSourceDescribe() pathAPISourceOrReader {
|
|
|
|
return c.apiReaderDescribe()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *srtConn) apiItem() *apiSRTConn {
|
|
|
|
c.mutex.RLock()
|
|
|
|
defer c.mutex.RUnlock()
|
|
|
|
|
|
|
|
bytesReceived := uint64(0)
|
|
|
|
bytesSent := uint64(0)
|
|
|
|
|
|
|
|
if c.conn != nil {
|
|
|
|
var s srt.Statistics
|
|
|
|
c.conn.Stats(&s)
|
|
|
|
bytesReceived = s.Accumulated.ByteRecv
|
|
|
|
bytesSent = s.Accumulated.ByteSent
|
|
|
|
}
|
|
|
|
|
|
|
|
return &apiSRTConn{
|
|
|
|
ID: c.uuid,
|
|
|
|
Created: c.created,
|
|
|
|
RemoteAddr: c.connReq.RemoteAddr().String(),
|
2023-08-05 15:10:48 +00:00
|
|
|
State: func() apiSRTConnState {
|
2023-07-31 19:20:09 +00:00
|
|
|
switch c.state {
|
|
|
|
case srtConnStateRead:
|
2023-08-05 15:10:48 +00:00
|
|
|
return apiSRTConnStateRead
|
2023-07-31 19:20:09 +00:00
|
|
|
|
|
|
|
case srtConnStatePublish:
|
2023-08-05 15:10:48 +00:00
|
|
|
return apiSRTConnStatePublish
|
2023-07-31 19:20:09 +00:00
|
|
|
|
|
|
|
default:
|
2023-08-05 15:10:48 +00:00
|
|
|
return apiSRTConnStateIdle
|
2023-07-31 19:20:09 +00:00
|
|
|
}
|
|
|
|
}(),
|
|
|
|
Path: c.pathName,
|
|
|
|
BytesReceived: bytesReceived,
|
|
|
|
BytesSent: bytesSent,
|
|
|
|
}
|
|
|
|
}
|