|
|
|
@ -13,20 +13,16 @@ import (
@@ -13,20 +13,16 @@ import (
|
|
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/formats" |
|
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/media" |
|
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" |
|
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/av1" |
|
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264" |
|
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg2audio" |
|
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" |
|
|
|
|
"github.com/google/uuid" |
|
|
|
|
"github.com/notedit/rtmp/format/flv/flvio" |
|
|
|
|
|
|
|
|
|
"github.com/bluenviron/mediamtx/internal/conf" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/externalcmd" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/formatprocessor" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/logger" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/rtmp" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/rtmp/h264conf" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/rtmp/message" |
|
|
|
|
"github.com/bluenviron/mediamtx/internal/stream" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
@ -42,158 +38,6 @@ func pathNameAndQuery(inURL *url.URL) (string, url.Values, string) {
@@ -42,158 +38,6 @@ func pathNameAndQuery(inURL *url.URL) (string, url.Values, string) {
|
|
|
|
|
return pathName, ur.Query(), ur.RawQuery |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type rtmpWriteFunc func(msg interface{}) error |
|
|
|
|
|
|
|
|
|
func getRTMPWriteFunc(medi *media.Media, format formats.Format, stream *stream.Stream) rtmpWriteFunc { |
|
|
|
|
switch format.(type) { |
|
|
|
|
case *formats.H264: |
|
|
|
|
return func(msg interface{}) error { |
|
|
|
|
tmsg := msg.(*message.Video) |
|
|
|
|
|
|
|
|
|
switch tmsg.Type { |
|
|
|
|
case message.VideoTypeConfig: |
|
|
|
|
var conf h264conf.Conf |
|
|
|
|
err := conf.Unmarshal(tmsg.Payload) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("unable to parse H264 config: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
au := [][]byte{ |
|
|
|
|
conf.SPS, |
|
|
|
|
conf.PPS, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitH264{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS + tmsg.PTSDelta, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
case message.VideoTypeAU: |
|
|
|
|
au, err := h264.AVCCUnmarshal(tmsg.Payload) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("unable to decode AVCC: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitH264{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS + tmsg.PTSDelta, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case *formats.H265: |
|
|
|
|
return func(msg interface{}) error { |
|
|
|
|
switch tmsg := msg.(type) { |
|
|
|
|
case *message.Video: |
|
|
|
|
au, err := h264.AVCCUnmarshal(tmsg.Payload) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("unable to decode AVCC: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitH265{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS + tmsg.PTSDelta, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
case *message.ExtendedFramesX: |
|
|
|
|
au, err := h264.AVCCUnmarshal(tmsg.Payload) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("unable to decode AVCC: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitH265{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
case *message.ExtendedCodedFrames: |
|
|
|
|
au, err := h264.AVCCUnmarshal(tmsg.Payload) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("unable to decode AVCC: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitH265{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS + tmsg.PTSDelta, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case *formats.AV1: |
|
|
|
|
return func(msg interface{}) error { |
|
|
|
|
if tmsg, ok := msg.(*message.ExtendedCodedFrames); ok { |
|
|
|
|
obus, err := av1.BitstreamUnmarshal(tmsg.Payload, true) |
|
|
|
|
if err != nil { |
|
|
|
|
return fmt.Errorf("unable to decode bitstream: %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitAV1{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS, |
|
|
|
|
OBUs: obus, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case *formats.MPEG2Audio: |
|
|
|
|
return func(msg interface{}) error { |
|
|
|
|
tmsg := msg.(*message.Audio) |
|
|
|
|
|
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitMPEG2Audio{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS, |
|
|
|
|
Frames: [][]byte{tmsg.Payload}, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case *formats.MPEG4Audio: |
|
|
|
|
return func(msg interface{}) error { |
|
|
|
|
tmsg := msg.(*message.Audio) |
|
|
|
|
|
|
|
|
|
if tmsg.AACType == message.AudioAACTypeAU { |
|
|
|
|
stream.WriteUnit(medi, format, &formatprocessor.UnitMPEG4AudioGeneric{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: tmsg.DTS, |
|
|
|
|
AUs: [][]byte{tmsg.Payload}, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type rtmpConnState int |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
@ -209,7 +53,7 @@ type rtmpConnPathManager interface {
@@ -209,7 +53,7 @@ type rtmpConnPathManager interface {
|
|
|
|
|
|
|
|
|
|
type rtmpConnParent interface { |
|
|
|
|
logger.Writer |
|
|
|
|
connClose(*rtmpConn) |
|
|
|
|
closeConn(*rtmpConn) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type rtmpConn struct { |
|
|
|
@ -322,36 +166,32 @@ func (c *rtmpConn) run() {
@@ -322,36 +166,32 @@ func (c *rtmpConn) run() {
|
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(c.ctx) |
|
|
|
|
runErr := make(chan error) |
|
|
|
|
go func() { |
|
|
|
|
runErr <- c.runInner(ctx) |
|
|
|
|
}() |
|
|
|
|
err := func() error { |
|
|
|
|
readerErr := make(chan error) |
|
|
|
|
go func() { |
|
|
|
|
readerErr <- c.runReader() |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
select { |
|
|
|
|
case err = <-runErr: |
|
|
|
|
cancel() |
|
|
|
|
|
|
|
|
|
case <-c.ctx.Done(): |
|
|
|
|
cancel() |
|
|
|
|
<-runErr |
|
|
|
|
err = errors.New("terminated") |
|
|
|
|
} |
|
|
|
|
select { |
|
|
|
|
case err := <-readerErr: |
|
|
|
|
c.nconn.Close() |
|
|
|
|
return err |
|
|
|
|
|
|
|
|
|
case <-c.ctx.Done(): |
|
|
|
|
c.nconn.Close() |
|
|
|
|
<-readerErr |
|
|
|
|
return errors.New("terminated") |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
c.ctxCancel() |
|
|
|
|
|
|
|
|
|
c.parent.connClose(c) |
|
|
|
|
c.parent.closeConn(c) |
|
|
|
|
|
|
|
|
|
c.Log(logger.Info, "closed (%v)", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *rtmpConn) runInner(ctx context.Context) error { |
|
|
|
|
go func() { |
|
|
|
|
<-ctx.Done() |
|
|
|
|
c.nconn.Close() |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
func (c *rtmpConn) runReader() error { |
|
|
|
|
c.nconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout))) |
|
|
|
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) |
|
|
|
|
u, publish, err := c.conn.InitializeServer() |
|
|
|
@ -360,12 +200,12 @@ func (c *rtmpConn) runInner(ctx context.Context) error {
@@ -360,12 +200,12 @@ func (c *rtmpConn) runInner(ctx context.Context) error {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !publish { |
|
|
|
|
return c.runRead(ctx, u) |
|
|
|
|
return c.runRead(u) |
|
|
|
|
} |
|
|
|
|
return c.runPublish(u) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error { |
|
|
|
|
func (c *rtmpConn) runRead(u *url.URL) error { |
|
|
|
|
pathName, query, rawQuery := pathNameAndQuery(u) |
|
|
|
|
|
|
|
|
|
res := c.pathManager.readerAdd(pathReaderAddReq{ |
|
|
|
@ -399,22 +239,32 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
@@ -399,22 +239,32 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
|
|
|
|
|
|
|
|
|
ringBuffer, _ := ringbuffer.New(uint64(c.readBufferCount)) |
|
|
|
|
go func() { |
|
|
|
|
<-ctx.Done() |
|
|
|
|
<-c.ctx.Done() |
|
|
|
|
ringBuffer.Close() |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
var medias media.Medias |
|
|
|
|
videoFirstIDRFound := false |
|
|
|
|
var videoStartDTS time.Duration |
|
|
|
|
|
|
|
|
|
videoMedia, videoFormat := c.findVideoFormat(res.stream, ringBuffer, |
|
|
|
|
&videoFirstIDRFound, &videoStartDTS) |
|
|
|
|
var w *rtmp.Writer |
|
|
|
|
|
|
|
|
|
videoMedia, videoFormat := c.setupVideo( |
|
|
|
|
&w, |
|
|
|
|
res.stream, |
|
|
|
|
ringBuffer, |
|
|
|
|
&videoFirstIDRFound, |
|
|
|
|
&videoStartDTS) |
|
|
|
|
if videoMedia != nil { |
|
|
|
|
medias = append(medias, videoMedia) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
audioMedia, audioFormat := c.findAudioFormat(res.stream, ringBuffer, |
|
|
|
|
videoFormat, &videoFirstIDRFound, &videoStartDTS) |
|
|
|
|
audioMedia, audioFormat := c.setupAudio( |
|
|
|
|
&w, |
|
|
|
|
res.stream, |
|
|
|
|
ringBuffer, |
|
|
|
|
videoFormat, |
|
|
|
|
&videoFirstIDRFound, |
|
|
|
|
&videoStartDTS) |
|
|
|
|
if audioFormat != nil { |
|
|
|
|
medias = append(medias, audioMedia) |
|
|
|
|
} |
|
|
|
@ -447,7 +297,8 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
@@ -447,7 +297,8 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err := c.conn.WriteTracks(videoFormat, audioFormat) |
|
|
|
|
var err error |
|
|
|
|
w, err = rtmp.NewWriter(c.conn, videoFormat, audioFormat) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
@ -468,15 +319,19 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
@@ -468,15 +319,19 @@ func (c *rtmpConn) runRead(ctx context.Context, u *url.URL) error {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer.RingBuffer, |
|
|
|
|
videoFirstIDRFound *bool, videoStartDTS *time.Duration, |
|
|
|
|
func (c *rtmpConn) setupVideo( |
|
|
|
|
w **rtmp.Writer, |
|
|
|
|
stream *stream.Stream, |
|
|
|
|
ringBuffer *ringbuffer.RingBuffer, |
|
|
|
|
videoFirstIDRFound *bool, |
|
|
|
|
videoStartDTS *time.Duration, |
|
|
|
|
) (*media.Media, formats.Format) { |
|
|
|
|
var videoFormatH264 *formats.H264 |
|
|
|
|
videoMedia := stream.Medias().FindFormat(&videoFormatH264) |
|
|
|
|
|
|
|
|
|
if videoFormatH264 != nil { |
|
|
|
|
videoStartPTSFilled := false |
|
|
|
|
var videoStartPTS time.Duration |
|
|
|
|
startPTSFilled := false |
|
|
|
|
var startPTS time.Duration |
|
|
|
|
var videoDTSExtractor *h264.DTSExtractor |
|
|
|
|
|
|
|
|
|
stream.AddReader(c, videoMedia, videoFormatH264, func(unit formatprocessor.Unit) { |
|
|
|
@ -487,11 +342,11 @@ func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer
@@ -487,11 +342,11 @@ func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !videoStartPTSFilled { |
|
|
|
|
videoStartPTSFilled = true |
|
|
|
|
videoStartPTS = tunit.PTS |
|
|
|
|
if !startPTSFilled { |
|
|
|
|
startPTSFilled = true |
|
|
|
|
startPTS = tunit.PTS |
|
|
|
|
} |
|
|
|
|
pts := tunit.PTS - videoStartPTS |
|
|
|
|
pts := tunit.PTS - startPTS |
|
|
|
|
|
|
|
|
|
idrPresent := false |
|
|
|
|
nonIDRPresent := false |
|
|
|
@ -542,27 +397,8 @@ func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer
@@ -542,27 +397,8 @@ func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer
|
|
|
|
|
pts -= *videoStartDTS |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
avcc, err := h264.AVCCMarshal(tunit.AU) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) |
|
|
|
|
err = c.conn.WriteMessage(&message.Video{ |
|
|
|
|
ChunkStreamID: message.VideoChunkStreamID, |
|
|
|
|
MessageStreamID: 0x1000000, |
|
|
|
|
Codec: message.CodecH264, |
|
|
|
|
IsKeyFrame: idrPresent, |
|
|
|
|
Type: message.VideoTypeAU, |
|
|
|
|
Payload: avcc, |
|
|
|
|
DTS: dts, |
|
|
|
|
PTSDelta: pts - dts, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
return (*w).WriteH264(pts, dts, idrPresent, tunit.AU) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
@ -572,7 +408,8 @@ func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer
@@ -572,7 +408,8 @@ func (c *rtmpConn) findVideoFormat(stream *stream.Stream, ringBuffer *ringbuffer
|
|
|
|
|
return nil, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *rtmpConn) findAudioFormat( |
|
|
|
|
func (c *rtmpConn) setupAudio( |
|
|
|
|
w **rtmp.Writer, |
|
|
|
|
stream *stream.Stream, |
|
|
|
|
ringBuffer *ringbuffer.RingBuffer, |
|
|
|
|
videoFormat formats.Format, |
|
|
|
@ -583,8 +420,8 @@ func (c *rtmpConn) findAudioFormat(
@@ -583,8 +420,8 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
audioMedia := stream.Medias().FindFormat(&audioFormatMPEG4Generic) |
|
|
|
|
|
|
|
|
|
if audioMedia != nil { |
|
|
|
|
audioStartPTSFilled := false |
|
|
|
|
var audioStartPTS time.Duration |
|
|
|
|
startPTSFilled := false |
|
|
|
|
var startPTS time.Duration |
|
|
|
|
|
|
|
|
|
stream.AddReader(c, audioMedia, audioFormatMPEG4Generic, func(unit formatprocessor.Unit) { |
|
|
|
|
ringBuffer.Push(func() error { |
|
|
|
@ -594,11 +431,11 @@ func (c *rtmpConn) findAudioFormat(
@@ -594,11 +431,11 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !audioStartPTSFilled { |
|
|
|
|
audioStartPTSFilled = true |
|
|
|
|
audioStartPTS = tunit.PTS |
|
|
|
|
if !startPTSFilled { |
|
|
|
|
startPTSFilled = true |
|
|
|
|
startPTS = tunit.PTS |
|
|
|
|
} |
|
|
|
|
pts := tunit.PTS - audioStartPTS |
|
|
|
|
pts := tunit.PTS - startPTS |
|
|
|
|
|
|
|
|
|
if videoFormat != nil { |
|
|
|
|
if !*videoFirstIDRFound { |
|
|
|
@ -613,18 +450,11 @@ func (c *rtmpConn) findAudioFormat(
@@ -613,18 +450,11 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
|
|
|
|
|
for i, au := range tunit.AUs { |
|
|
|
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) |
|
|
|
|
err := c.conn.WriteMessage(&message.Audio{ |
|
|
|
|
ChunkStreamID: message.AudioChunkStreamID, |
|
|
|
|
MessageStreamID: 0x1000000, |
|
|
|
|
Codec: message.CodecMPEG4Audio, |
|
|
|
|
Rate: flvio.SOUND_44Khz, |
|
|
|
|
Depth: flvio.SOUND_16BIT, |
|
|
|
|
Channels: flvio.SOUND_STEREO, |
|
|
|
|
AACType: message.AudioAACTypeAU, |
|
|
|
|
Payload: au, |
|
|
|
|
DTS: pts + time.Duration(i)*mpeg4audio.SamplesPerAccessUnit* |
|
|
|
|
err := (*w).WriteMPEG4Audio( |
|
|
|
|
pts+time.Duration(i)*mpeg4audio.SamplesPerAccessUnit* |
|
|
|
|
time.Second/time.Duration(audioFormatMPEG4Generic.ClockRate()), |
|
|
|
|
}) |
|
|
|
|
au, |
|
|
|
|
) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
@ -644,8 +474,8 @@ func (c *rtmpConn) findAudioFormat(
@@ -644,8 +474,8 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
audioFormatMPEG4AudioLATM.Config != nil && |
|
|
|
|
len(audioFormatMPEG4AudioLATM.Config.Programs) == 1 && |
|
|
|
|
len(audioFormatMPEG4AudioLATM.Config.Programs[0].Layers) == 1 { |
|
|
|
|
audioStartPTSFilled := false |
|
|
|
|
var audioStartPTS time.Duration |
|
|
|
|
startPTSFilled := false |
|
|
|
|
var startPTS time.Duration |
|
|
|
|
|
|
|
|
|
stream.AddReader(c, audioMedia, audioFormatMPEG4AudioLATM, func(unit formatprocessor.Unit) { |
|
|
|
|
ringBuffer.Push(func() error { |
|
|
|
@ -655,11 +485,11 @@ func (c *rtmpConn) findAudioFormat(
@@ -655,11 +485,11 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !audioStartPTSFilled { |
|
|
|
|
audioStartPTSFilled = true |
|
|
|
|
audioStartPTS = tunit.PTS |
|
|
|
|
if !startPTSFilled { |
|
|
|
|
startPTSFilled = true |
|
|
|
|
startPTS = tunit.PTS |
|
|
|
|
} |
|
|
|
|
pts := tunit.PTS - audioStartPTS |
|
|
|
|
pts := tunit.PTS - startPTS |
|
|
|
|
|
|
|
|
|
if videoFormat != nil { |
|
|
|
|
if !*videoFirstIDRFound { |
|
|
|
@ -673,22 +503,7 @@ func (c *rtmpConn) findAudioFormat(
@@ -673,22 +503,7 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) |
|
|
|
|
err := c.conn.WriteMessage(&message.Audio{ |
|
|
|
|
ChunkStreamID: message.AudioChunkStreamID, |
|
|
|
|
MessageStreamID: 0x1000000, |
|
|
|
|
Codec: message.CodecMPEG4Audio, |
|
|
|
|
Rate: flvio.SOUND_44Khz, |
|
|
|
|
Depth: flvio.SOUND_16BIT, |
|
|
|
|
Channels: flvio.SOUND_STEREO, |
|
|
|
|
AACType: message.AudioAACTypeAU, |
|
|
|
|
Payload: tunit.AU, |
|
|
|
|
DTS: pts, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
return (*w).WriteMPEG4Audio(pts, tunit.AU) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
@ -699,18 +514,18 @@ func (c *rtmpConn) findAudioFormat(
@@ -699,18 +514,18 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG2) |
|
|
|
|
|
|
|
|
|
if audioMedia != nil { |
|
|
|
|
audioStartPTSFilled := false |
|
|
|
|
var audioStartPTS time.Duration |
|
|
|
|
startPTSFilled := false |
|
|
|
|
var startPTS time.Duration |
|
|
|
|
|
|
|
|
|
stream.AddReader(c, audioMedia, audioFormatMPEG2, func(unit formatprocessor.Unit) { |
|
|
|
|
ringBuffer.Push(func() error { |
|
|
|
|
tunit := unit.(*formatprocessor.UnitMPEG2Audio) |
|
|
|
|
|
|
|
|
|
if !audioStartPTSFilled { |
|
|
|
|
audioStartPTSFilled = true |
|
|
|
|
audioStartPTS = tunit.PTS |
|
|
|
|
if !startPTSFilled { |
|
|
|
|
startPTSFilled = true |
|
|
|
|
startPTS = tunit.PTS |
|
|
|
|
} |
|
|
|
|
pts := tunit.PTS - audioStartPTS |
|
|
|
|
pts := tunit.PTS - startPTS |
|
|
|
|
|
|
|
|
|
if videoFormat != nil { |
|
|
|
|
if !*videoFirstIDRFound { |
|
|
|
@ -734,34 +549,8 @@ func (c *rtmpConn) findAudioFormat(
@@ -734,34 +549,8 @@ func (c *rtmpConn) findAudioFormat(
|
|
|
|
|
return fmt.Errorf("RTMP only supports MPEG-1 layer 3 audio") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
channels := uint8(flvio.SOUND_STEREO) |
|
|
|
|
if h.ChannelMode == mpeg2audio.ChannelModeMono { |
|
|
|
|
channels = flvio.SOUND_MONO |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
rate := uint8(flvio.SOUND_44Khz) |
|
|
|
|
switch h.SampleRate { |
|
|
|
|
case 5500: |
|
|
|
|
rate = flvio.SOUND_5_5Khz |
|
|
|
|
case 11025: |
|
|
|
|
rate = flvio.SOUND_11Khz |
|
|
|
|
case 22050: |
|
|
|
|
rate = flvio.SOUND_22Khz |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
msg := &message.Audio{ |
|
|
|
|
ChunkStreamID: message.AudioChunkStreamID, |
|
|
|
|
MessageStreamID: 0x1000000, |
|
|
|
|
Codec: message.CodecMPEG2Audio, |
|
|
|
|
Rate: rate, |
|
|
|
|
Depth: flvio.SOUND_16BIT, |
|
|
|
|
Channels: channels, |
|
|
|
|
Payload: frame, |
|
|
|
|
DTS: pts, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) |
|
|
|
|
err = c.conn.WriteMessage(msg) |
|
|
|
|
err = (*w).WriteMPEG2Audio(pts, &h, frame) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
@ -812,29 +601,88 @@ func (c *rtmpConn) runPublish(u *url.URL) error {
@@ -812,29 +601,88 @@ func (c *rtmpConn) runPublish(u *url.URL) error {
|
|
|
|
|
c.pathName = pathName |
|
|
|
|
c.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
videoFormat, audioFormat, err := c.conn.ReadTracks() |
|
|
|
|
r, err := rtmp.NewReader(c.conn) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
videoFormat, audioFormat := r.Tracks() |
|
|
|
|
|
|
|
|
|
var medias media.Medias |
|
|
|
|
var videoMedia *media.Media |
|
|
|
|
var audioMedia *media.Media |
|
|
|
|
var stream *stream.Stream |
|
|
|
|
|
|
|
|
|
if videoFormat != nil { |
|
|
|
|
videoMedia = &media.Media{ |
|
|
|
|
videoMedia := &media.Media{ |
|
|
|
|
Type: media.TypeVideo, |
|
|
|
|
Formats: []formats.Format{videoFormat}, |
|
|
|
|
} |
|
|
|
|
medias = append(medias, videoMedia) |
|
|
|
|
|
|
|
|
|
switch videoFormat.(type) { |
|
|
|
|
case *formats.AV1: |
|
|
|
|
r.OnDataAV1(func(pts time.Duration, obus [][]byte) { |
|
|
|
|
stream.WriteUnit(videoMedia, videoFormat, &formatprocessor.UnitAV1{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: pts, |
|
|
|
|
OBUs: obus, |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
case *formats.H265: |
|
|
|
|
r.OnDataH265(func(pts time.Duration, au [][]byte) { |
|
|
|
|
stream.WriteUnit(videoMedia, videoFormat, &formatprocessor.UnitH265{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: pts, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
case *formats.H264: |
|
|
|
|
r.OnDataH264(func(pts time.Duration, au [][]byte) { |
|
|
|
|
stream.WriteUnit(videoMedia, videoFormat, &formatprocessor.UnitH264{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: pts, |
|
|
|
|
AU: au, |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if audioFormat != nil { |
|
|
|
|
audioMedia = &media.Media{ |
|
|
|
|
if audioFormat != nil { //nolint:dupl
|
|
|
|
|
audioMedia := &media.Media{ |
|
|
|
|
Type: media.TypeAudio, |
|
|
|
|
Formats: []formats.Format{audioFormat}, |
|
|
|
|
} |
|
|
|
|
medias = append(medias, audioMedia) |
|
|
|
|
|
|
|
|
|
switch audioFormat.(type) { |
|
|
|
|
case *formats.MPEG4AudioGeneric: |
|
|
|
|
r.OnDataMPEG4Audio(func(pts time.Duration, au []byte) { |
|
|
|
|
stream.WriteUnit(audioMedia, audioFormat, &formatprocessor.UnitMPEG4AudioGeneric{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: pts, |
|
|
|
|
AUs: [][]byte{au}, |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
case *formats.MPEG2Audio: |
|
|
|
|
r.OnDataMPEG2Audio(func(pts time.Duration, frame []byte) { |
|
|
|
|
stream.WriteUnit(audioMedia, audioFormat, &formatprocessor.UnitMPEG2Audio{ |
|
|
|
|
BaseUnit: formatprocessor.BaseUnit{ |
|
|
|
|
NTP: time.Now(), |
|
|
|
|
}, |
|
|
|
|
PTS: pts, |
|
|
|
|
Frames: [][]byte{frame}, |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
rres := res.path.publisherStart(pathPublisherStartReq{ |
|
|
|
@ -850,40 +698,17 @@ func (c *rtmpConn) runPublish(u *url.URL) error {
@@ -850,40 +698,17 @@ func (c *rtmpConn) runPublish(u *url.URL) error {
|
|
|
|
|
res.path.name, |
|
|
|
|
sourceMediaInfo(medias)) |
|
|
|
|
|
|
|
|
|
stream = rres.stream |
|
|
|
|
|
|
|
|
|
// disable write deadline to allow outgoing acknowledges
|
|
|
|
|
c.nconn.SetWriteDeadline(time.Time{}) |
|
|
|
|
|
|
|
|
|
videoWriteFunc := getRTMPWriteFunc(videoMedia, videoFormat, rres.stream) |
|
|
|
|
audioWriteFunc := getRTMPWriteFunc(audioMedia, audioFormat, rres.stream) |
|
|
|
|
|
|
|
|
|
for { |
|
|
|
|
c.nconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout))) |
|
|
|
|
msg, err := c.conn.ReadMessage() |
|
|
|
|
err := r.Read() |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch msg.(type) { |
|
|
|
|
case *message.Video, *message.ExtendedFramesX, *message.ExtendedCodedFrames: |
|
|
|
|
if videoFormat == nil { |
|
|
|
|
return fmt.Errorf("received a video packet, but track is not set up") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err := videoWriteFunc(msg) |
|
|
|
|
if err != nil { |
|
|
|
|
c.Log(logger.Warn, "%v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case *message.Audio: |
|
|
|
|
if audioFormat == nil { |
|
|
|
|
return fmt.Errorf("received an audio packet, but track is not set up") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err := audioWriteFunc(msg) |
|
|
|
|
if err != nil { |
|
|
|
|
c.Log(logger.Warn, "%v", err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|