mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-11-03 01:43:48 +08:00
2
go.mod
2
go.mod
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/alecthomas/kong v1.12.0
|
github.com/alecthomas/kong v1.12.0
|
||||||
github.com/asticode/go-astits v1.13.0
|
github.com/asticode/go-astits v1.13.0
|
||||||
github.com/bluenviron/gohlslib/v2 v2.2.2
|
github.com/bluenviron/gohlslib/v2 v2.2.2
|
||||||
github.com/bluenviron/gortsplib/v4 v4.15.0
|
github.com/bluenviron/gortsplib/v4 v4.16.0
|
||||||
github.com/bluenviron/mediacommon/v2 v2.4.0
|
github.com/bluenviron/mediacommon/v2 v2.4.0
|
||||||
github.com/datarhei/gosrt v0.9.0
|
github.com/datarhei/gosrt v0.9.0
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -35,8 +35,8 @@ github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYh
|
|||||||
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
|
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
|
||||||
github.com/bluenviron/gohlslib/v2 v2.2.2 h1:Q86VloPjwONKF8pu6jSEh9ENm4UzdMl5SzYvtjneL5k=
|
github.com/bluenviron/gohlslib/v2 v2.2.2 h1:Q86VloPjwONKF8pu6jSEh9ENm4UzdMl5SzYvtjneL5k=
|
||||||
github.com/bluenviron/gohlslib/v2 v2.2.2/go.mod h1:3Lby/VMDD/cN0B3uJPd3bEEiJZ34LqXs71FEvN/fq2k=
|
github.com/bluenviron/gohlslib/v2 v2.2.2/go.mod h1:3Lby/VMDD/cN0B3uJPd3bEEiJZ34LqXs71FEvN/fq2k=
|
||||||
github.com/bluenviron/gortsplib/v4 v4.15.0 h1:R5mimKNlmzpUqcAVfCqkSznGk/2hl4Kk9LPFo2KZJeU=
|
github.com/bluenviron/gortsplib/v4 v4.16.0 h1:qzJxlZXCv11oxNkNTAFMaeX0uEXJE0L6lDv3CKUYT/k=
|
||||||
github.com/bluenviron/gortsplib/v4 v4.15.0/go.mod h1:mqAxRuombKOUHREiKuKJ4VBjEC4U7VeMar4/G4Sbq04=
|
github.com/bluenviron/gortsplib/v4 v4.16.0/go.mod h1:pcSNf/GToNEwdWy74moR4Tp5JWIEDJ0d9CzCSUPkiwM=
|
||||||
github.com/bluenviron/mediacommon/v2 v2.4.0 h1:Ss1T7AMxTrICJ+a/N5urS/1lp1ZpsF+3iJq3B/RLDMw=
|
github.com/bluenviron/mediacommon/v2 v2.4.0 h1:Ss1T7AMxTrICJ+a/N5urS/1lp1ZpsF+3iJq3B/RLDMw=
|
||||||
github.com/bluenviron/mediacommon/v2 v2.4.0/go.mod h1:a6MbPmXtYda9mKibKVMZlW20GYLLrX2R7ZkUE+1pwV0=
|
github.com/bluenviron/mediacommon/v2 v2.4.0/go.mod h1:a6MbPmXtYda9mKibKVMZlW20GYLLrX2R7ZkUE+1pwV0=
|
||||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
|
|||||||
114
internal/formatprocessor/mpeg4_audio_latm.go
Normal file
114
internal/formatprocessor/mpeg4_audio_latm.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package formatprocessor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpfragmented"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
|
"github.com/bluenviron/mediamtx/internal/unit"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mpeg4AudioLATM struct {
|
||||||
|
RTPMaxPayloadSize int
|
||||||
|
Format *format.MPEG4AudioLATM
|
||||||
|
GenerateRTPPackets bool
|
||||||
|
Parent logger.Writer
|
||||||
|
|
||||||
|
encoder *rtpfragmented.Encoder
|
||||||
|
decoder *rtpfragmented.Decoder
|
||||||
|
randomStart uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mpeg4AudioLATM) initialize() error {
|
||||||
|
if t.GenerateRTPPackets {
|
||||||
|
err := t.createEncoder()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.randomStart, err = randUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mpeg4AudioLATM) createEncoder() error {
|
||||||
|
t.encoder = &rtpfragmented.Encoder{
|
||||||
|
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||||
|
PayloadType: t.Format.PayloadTyp,
|
||||||
|
}
|
||||||
|
return t.encoder.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mpeg4AudioLATM) ProcessUnit(uu unit.Unit) error { //nolint:dupl
|
||||||
|
u := uu.(*unit.MPEG4AudioLATM)
|
||||||
|
|
||||||
|
pkts, err := t.encoder.Encode(u.Element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.RTPPackets = pkts
|
||||||
|
|
||||||
|
for _, pkt := range u.RTPPackets {
|
||||||
|
pkt.Timestamp += t.randomStart + uint32(u.PTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mpeg4AudioLATM) ProcessRTPPacket( //nolint:dupl
|
||||||
|
pkt *rtp.Packet,
|
||||||
|
ntp time.Time,
|
||||||
|
pts int64,
|
||||||
|
hasNonRTSPReaders bool,
|
||||||
|
) (unit.Unit, error) {
|
||||||
|
u := &unit.MPEG4AudioLATM{
|
||||||
|
Base: unit.Base{
|
||||||
|
RTPPackets: []*rtp.Packet{pkt},
|
||||||
|
NTP: ntp,
|
||||||
|
PTS: pts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove padding
|
||||||
|
pkt.Padding = false
|
||||||
|
pkt.PaddingSize = 0
|
||||||
|
|
||||||
|
if len(pkt.Payload) > t.RTPMaxPayloadSize {
|
||||||
|
return nil, fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
|
||||||
|
len(pkt.Payload), t.RTPMaxPayloadSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode from RTP
|
||||||
|
if hasNonRTSPReaders || t.decoder != nil {
|
||||||
|
if t.decoder == nil {
|
||||||
|
var err error
|
||||||
|
t.decoder, err = t.Format.CreateDecoder()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
el, err := t.decoder.Decode(pkt)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) {
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Element = el
|
||||||
|
}
|
||||||
|
|
||||||
|
// route packet as is
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video"
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpfragmented"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
|
|
||||||
@@ -33,8 +33,8 @@ type mpeg4Video struct {
|
|||||||
GenerateRTPPackets bool
|
GenerateRTPPackets bool
|
||||||
Parent logger.Writer
|
Parent logger.Writer
|
||||||
|
|
||||||
encoder *rtpmpeg4video.Encoder
|
encoder *rtpfragmented.Encoder
|
||||||
decoder *rtpmpeg4video.Decoder
|
decoder *rtpfragmented.Decoder
|
||||||
randomStart uint32
|
randomStart uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ func (t *mpeg4Video) initialize() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *mpeg4Video) createEncoder() error {
|
func (t *mpeg4Video) createEncoder() error {
|
||||||
t.encoder = &rtpmpeg4video.Encoder{
|
t.encoder = &rtpfragmented.Encoder{
|
||||||
PayloadMaxSize: t.RTPMaxPayloadSize,
|
PayloadMaxSize: t.RTPMaxPayloadSize,
|
||||||
PayloadType: t.Format.PayloadTyp,
|
PayloadType: t.Format.PayloadTyp,
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func (t *mpeg4Video) ProcessRTPPacket( //nolint:dupl
|
|||||||
|
|
||||||
frame, err := t.decoder.Decode(pkt)
|
frame, err := t.decoder.Decode(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, rtpmpeg4video.ErrMorePacketsNeeded) {
|
if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) {
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -135,6 +135,14 @@ func New(
|
|||||||
Parent: parent,
|
Parent: parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *format.MPEG4AudioLATM:
|
||||||
|
proc = &mpeg4AudioLATM{
|
||||||
|
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||||
|
Format: forma,
|
||||||
|
GenerateRTPPackets: generateRTPPackets,
|
||||||
|
Parent: parent,
|
||||||
|
}
|
||||||
|
|
||||||
case *format.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
proc = &mpeg1Audio{
|
proc = &mpeg1Audio{
|
||||||
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
RTPMaxPayloadSize: rtpMaxPayloadSize,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/bluenviron/gohlslib/v2/pkg/codecs"
|
"github.com/bluenviron/gohlslib/v2/pkg/codecs"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
"github.com/bluenviron/mediamtx/internal/stream"
|
"github.com/bluenviron/mediamtx/internal/stream"
|
||||||
"github.com/bluenviron/mediamtx/internal/unit"
|
"github.com/bluenviron/mediamtx/internal/unit"
|
||||||
@@ -233,11 +234,41 @@ func setupAudioTracks(
|
|||||||
})
|
})
|
||||||
|
|
||||||
case *format.MPEG4Audio:
|
case *format.MPEG4Audio:
|
||||||
co := forma.GetConfig()
|
track := &gohlslib.Track{
|
||||||
if co != nil {
|
Codec: &codecs.MPEG4Audio{
|
||||||
|
Config: *forma.Config,
|
||||||
|
},
|
||||||
|
ClockRate: forma.ClockRate(),
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack(
|
||||||
|
media,
|
||||||
|
forma,
|
||||||
|
track,
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
tunit := u.(*unit.MPEG4Audio)
|
||||||
|
|
||||||
|
if tunit.AUs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := muxer.WriteMPEG4Audio(
|
||||||
|
track,
|
||||||
|
tunit.NTP,
|
||||||
|
tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate
|
||||||
|
tunit.AUs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("muxer error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
case *format.MPEG4AudioLATM:
|
||||||
|
if !forma.CPresent {
|
||||||
track := &gohlslib.Track{
|
track := &gohlslib.Track{
|
||||||
Codec: &codecs.MPEG4Audio{
|
Codec: &codecs.MPEG4Audio{
|
||||||
Config: *co,
|
Config: *forma.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig,
|
||||||
},
|
},
|
||||||
ClockRate: forma.ClockRate(),
|
ClockRate: forma.ClockRate(),
|
||||||
}
|
}
|
||||||
@@ -247,22 +278,24 @@ func setupAudioTracks(
|
|||||||
forma,
|
forma,
|
||||||
track,
|
track,
|
||||||
func(u unit.Unit) error {
|
func(u unit.Unit) error {
|
||||||
tunit := u.(*unit.MPEG4Audio)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
|
|
||||||
if tunit.AUs == nil {
|
if tunit.Element == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := muxer.WriteMPEG4Audio(
|
var ame mpeg4audio.AudioMuxElement
|
||||||
|
ame.StreamMuxConfig = forma.StreamMuxConfig
|
||||||
|
err := ame.Unmarshal(tunit.Element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return muxer.WriteMPEG4Audio(
|
||||||
track,
|
track,
|
||||||
tunit.NTP,
|
tunit.NTP,
|
||||||
tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate
|
tunit.PTS, // no conversion is needed since we set gohlslib.Track.ClockRate = format.ClockRate
|
||||||
tunit.AUs)
|
[][]byte{ame.Payloads[0][0][0]})
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("muxer error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
internal/protocols/mpegts/enhanced_reader.go
Normal file
78
internal/protocols/mpegts/enhanced_reader.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package mpegts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||||
|
mcmpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/rewindablereader"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnhancedReader is a mpegts.Reader wrapper
|
||||||
|
// That provides additional informations that are needed in order
|
||||||
|
// to perform conversion to RTSP.
|
||||||
|
type EnhancedReader struct {
|
||||||
|
R io.Reader
|
||||||
|
|
||||||
|
*mcmpegts.Reader
|
||||||
|
|
||||||
|
latmConfigs map[uint16]*mpeg4audio.StreamMuxConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize initializes EnhancedReader.
|
||||||
|
func (r *EnhancedReader) Initialize() error {
|
||||||
|
rr := &rewindablereader.Reader{R: r.R}
|
||||||
|
mr := &mcmpegts.Reader{R: rr}
|
||||||
|
err := mr.Initialize()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.latmConfigs = make(map[uint16]*mpeg4audio.StreamMuxConfig)
|
||||||
|
tracksToParse := 0
|
||||||
|
|
||||||
|
for _, track := range mr.Tracks() {
|
||||||
|
if _, ok := track.Codec.(*mcmpegts.CodecMPEG4AudioLATM); ok {
|
||||||
|
cpid := track.PID
|
||||||
|
done := false
|
||||||
|
tracksToParse++
|
||||||
|
|
||||||
|
mr.OnDataMPEG4AudioLATM(track, func(_ int64, els [][]byte) error {
|
||||||
|
if done {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ame mpeg4audio.AudioMuxElement
|
||||||
|
ame.MuxConfigPresent = true
|
||||||
|
err2 := ame.Unmarshal(els[0])
|
||||||
|
if err2 != nil {
|
||||||
|
return nil //nolint:nilerr
|
||||||
|
}
|
||||||
|
|
||||||
|
if ame.MuxConfigPresent {
|
||||||
|
r.latmConfigs[cpid] = ame.StreamMuxConfig
|
||||||
|
tracksToParse--
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tracksToParse > 0 {
|
||||||
|
err = mr.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rr.Rewind()
|
||||||
|
r.Reader = &mcmpegts.Reader{R: rr}
|
||||||
|
err = r.Reader.Initialize()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||||
mcmpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
mcmpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||||
srt "github.com/datarhei/gosrt"
|
srt "github.com/datarhei/gosrt"
|
||||||
|
|
||||||
@@ -255,27 +256,89 @@ func FromStream(
|
|||||||
})
|
})
|
||||||
|
|
||||||
case *format.MPEG4Audio:
|
case *format.MPEG4Audio:
|
||||||
co := forma.GetConfig()
|
track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG4Audio{
|
||||||
if co != nil {
|
Config: *forma.Config,
|
||||||
track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG4Audio{
|
}}
|
||||||
Config: *co,
|
|
||||||
}}
|
|
||||||
|
|
||||||
|
addTrack(
|
||||||
|
media,
|
||||||
|
forma,
|
||||||
|
track,
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
tunit := u.(*unit.MPEG4Audio)
|
||||||
|
if tunit.AUs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sconn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||||
|
err := (*w).WriteMPEG4Audio(
|
||||||
|
track,
|
||||||
|
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
||||||
|
tunit.AUs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bw.Flush()
|
||||||
|
})
|
||||||
|
|
||||||
|
case *format.MPEG4AudioLATM:
|
||||||
|
track := &mcmpegts.Track{Codec: &mcmpegts.CodecMPEG4AudioLATM{}}
|
||||||
|
|
||||||
|
if !forma.CPresent {
|
||||||
addTrack(
|
addTrack(
|
||||||
media,
|
media,
|
||||||
forma,
|
forma,
|
||||||
track,
|
track,
|
||||||
func(u unit.Unit) error {
|
func(u unit.Unit) error {
|
||||||
tunit := u.(*unit.MPEG4Audio)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
if tunit.AUs == nil {
|
if tunit.Element == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var elIn mpeg4audio.AudioMuxElement
|
||||||
|
elIn.MuxConfigPresent = false
|
||||||
|
elIn.StreamMuxConfig = forma.StreamMuxConfig
|
||||||
|
err := elIn.Unmarshal(tunit.Element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var elOut mpeg4audio.AudioMuxElement
|
||||||
|
elOut.MuxConfigPresent = true
|
||||||
|
elOut.StreamMuxConfig = forma.StreamMuxConfig
|
||||||
|
elOut.UseSameStreamMux = false
|
||||||
|
elOut.Payloads = elIn.Payloads
|
||||||
|
buf, err := elOut.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sconn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||||
|
err = (*w).WriteMPEG4AudioLATM(
|
||||||
|
track,
|
||||||
|
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
||||||
|
[][]byte{buf})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bw.Flush()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addTrack(
|
||||||
|
media,
|
||||||
|
forma,
|
||||||
|
track,
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
|
if tunit.Element == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sconn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
sconn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||||
err := (*w).WriteMPEG4Audio(
|
err := (*w).WriteMPEG4AudioLATM(
|
||||||
track,
|
track,
|
||||||
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
||||||
tunit.AUs)
|
[][]byte{tunit.Element})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -353,10 +416,5 @@ func FromStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
w = &mcmpegts.Writer{W: bw, Tracks: tracks}
|
w = &mcmpegts.Writer{W: bw, Tracks: tracks}
|
||||||
err := w.Initialize()
|
return w.Initialize()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package mpegts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
@@ -20,7 +23,7 @@ var errNoSupportedCodecs = errors.New(
|
|||||||
|
|
||||||
// ToStream maps a MPEG-TS stream to a MediaMTX stream.
|
// ToStream maps a MPEG-TS stream to a MediaMTX stream.
|
||||||
func ToStream(
|
func ToStream(
|
||||||
r *mpegts.Reader,
|
r *EnhancedReader,
|
||||||
stream **stream.Stream,
|
stream **stream.Stream,
|
||||||
l logger.Writer,
|
l logger.Writer,
|
||||||
) ([]*description.Media, error) {
|
) ([]*description.Media, error) {
|
||||||
@@ -184,6 +187,64 @@ func ToStream(
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
case *mpegts.CodecMPEG4AudioLATM:
|
||||||
|
// We are dealing with a LATM stream with in-band configuration.
|
||||||
|
// Although in theory this can be streamed with RTSP (RFC6416 with cpresent=1),
|
||||||
|
// in practice there is no player that supports it.
|
||||||
|
// Therefore, convert the stream to a LATM stream with out-of-band configuration.
|
||||||
|
streamMuxConfig := r.latmConfigs[track.PID]
|
||||||
|
medi = &description.Media{
|
||||||
|
Type: description.MediaTypeAudio,
|
||||||
|
Formats: []format.Format{&format.MPEG4AudioLATM{
|
||||||
|
PayloadTyp: 96,
|
||||||
|
CPresent: false,
|
||||||
|
ProfileLevelID: 30,
|
||||||
|
StreamMuxConfig: streamMuxConfig,
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
clockRate := medi.Formats[0].ClockRate()
|
||||||
|
|
||||||
|
r.OnDataMPEG4AudioLATM(track, func(pts int64, els [][]byte) error {
|
||||||
|
pts = td.Decode(pts)
|
||||||
|
|
||||||
|
pts = multiplyAndDivide(pts, int64(clockRate), 90000)
|
||||||
|
|
||||||
|
for _, el := range els {
|
||||||
|
var elIn mpeg4audio.AudioMuxElement
|
||||||
|
elIn.MuxConfigPresent = true
|
||||||
|
elIn.StreamMuxConfig = streamMuxConfig
|
||||||
|
err := elIn.Unmarshal(el)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(elIn.StreamMuxConfig, streamMuxConfig) {
|
||||||
|
return fmt.Errorf("dynamic stream mux config is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var elOut mpeg4audio.AudioMuxElement
|
||||||
|
elOut.MuxConfigPresent = false
|
||||||
|
elOut.StreamMuxConfig = streamMuxConfig
|
||||||
|
elOut.Payloads = elIn.Payloads
|
||||||
|
buf, err := elOut.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
(*stream).WriteUnit(medi, medi.Formats[0], &unit.MPEG4AudioLATM{
|
||||||
|
Base: unit.Base{
|
||||||
|
NTP: time.Now(),
|
||||||
|
PTS: pts,
|
||||||
|
},
|
||||||
|
Element: buf,
|
||||||
|
})
|
||||||
|
|
||||||
|
pts += mpeg4audio.SamplesPerAccessUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
case *mpegts.CodecMPEG1Audio:
|
case *mpegts.CodecMPEG1Audio:
|
||||||
medi = &description.Media{
|
medi = &description.Media{
|
||||||
Type: description.MediaTypeAudio,
|
Type: description.MediaTypeAudio,
|
||||||
|
|||||||
@@ -7,12 +7,148 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/asticode/go-astits"
|
"github.com/asticode/go-astits"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||||
"github.com/bluenviron/mediamtx/internal/logger"
|
"github.com/bluenviron/mediamtx/internal/logger"
|
||||||
"github.com/bluenviron/mediamtx/internal/test"
|
"github.com/bluenviron/mediamtx/internal/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestToStream(t *testing.T) {
|
||||||
|
for _, ca := range []string{
|
||||||
|
"h265",
|
||||||
|
"h264",
|
||||||
|
"mpeg-4 audio latm",
|
||||||
|
} {
|
||||||
|
t.Run(ca, func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
mux := astits.NewMuxer(context.Background(), &buf)
|
||||||
|
|
||||||
|
switch ca {
|
||||||
|
case "h265":
|
||||||
|
err := mux.AddElementaryStream(astits.PMTElementaryStream{
|
||||||
|
ElementaryPID: 122,
|
||||||
|
StreamType: astits.StreamTypeH265Video,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mux.SetPCRPID(122)
|
||||||
|
|
||||||
|
_, err = mux.WriteTables()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
case "h264":
|
||||||
|
err := mux.AddElementaryStream(astits.PMTElementaryStream{
|
||||||
|
ElementaryPID: 122,
|
||||||
|
StreamType: astits.StreamTypeH264Video,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mux.SetPCRPID(122)
|
||||||
|
|
||||||
|
_, err = mux.WriteTables()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
case "mpeg-4 audio latm":
|
||||||
|
err := mux.AddElementaryStream(astits.PMTElementaryStream{
|
||||||
|
ElementaryPID: 122,
|
||||||
|
StreamType: astits.StreamTypeAACLATMAudio,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mux.SetPCRPID(122)
|
||||||
|
|
||||||
|
enc1, err := mpeg4audio.AudioMuxElement{
|
||||||
|
MuxConfigPresent: true,
|
||||||
|
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||||
|
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||||
|
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||||
|
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||||
|
Type: 2,
|
||||||
|
SampleRate: 48000,
|
||||||
|
ChannelCount: 2,
|
||||||
|
},
|
||||||
|
LatmBufferFullness: 255,
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
Payloads: [][][][]byte{{{{1, 2, 3, 4}}}},
|
||||||
|
}.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
enc2, err := mpeg4audio.AudioSyncStream{
|
||||||
|
AudioMuxElements: [][]byte{enc1},
|
||||||
|
}.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = mux.WriteData(&astits.MuxerData{
|
||||||
|
PID: 122,
|
||||||
|
PES: &astits.PESData{
|
||||||
|
Header: &astits.PESHeader{
|
||||||
|
OptionalHeader: &astits.PESOptionalHeader{
|
||||||
|
MarkerBits: 2,
|
||||||
|
PTSDTSIndicator: astits.PTSDTSIndicatorOnlyPTS,
|
||||||
|
PTS: &astits.ClockReference{Base: 90000},
|
||||||
|
},
|
||||||
|
StreamID: 192,
|
||||||
|
},
|
||||||
|
Data: enc2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &EnhancedReader{R: &buf}
|
||||||
|
err := r.Initialize()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
desc, err := ToStream(r, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
switch ca {
|
||||||
|
case "h265":
|
||||||
|
require.Equal(t, []*description.Media{{
|
||||||
|
Type: description.MediaTypeVideo,
|
||||||
|
Formats: []format.Format{&format.H265{
|
||||||
|
PayloadTyp: 96,
|
||||||
|
}},
|
||||||
|
}}, desc)
|
||||||
|
|
||||||
|
case "h264":
|
||||||
|
require.Equal(t, []*description.Media{{
|
||||||
|
Type: description.MediaTypeVideo,
|
||||||
|
Formats: []format.Format{&format.H264{
|
||||||
|
PayloadTyp: 96,
|
||||||
|
PacketizationMode: 1,
|
||||||
|
}},
|
||||||
|
}}, desc)
|
||||||
|
|
||||||
|
case "mpeg-4 audio latm":
|
||||||
|
require.Equal(t, []*description.Media{{
|
||||||
|
Type: description.MediaTypeAudio,
|
||||||
|
Formats: []format.Format{&format.MPEG4AudioLATM{
|
||||||
|
PayloadTyp: 96,
|
||||||
|
ProfileLevelID: 30,
|
||||||
|
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||||
|
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||||
|
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||||
|
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||||
|
Type: 2,
|
||||||
|
SampleRate: 48000,
|
||||||
|
ChannelCount: 2,
|
||||||
|
},
|
||||||
|
LatmBufferFullness: 255,
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}}, desc)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestToStreamNoSupportedCodecs(t *testing.T) {
|
func TestToStreamNoSupportedCodecs(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
mux := astits.NewMuxer(context.Background(), &buf)
|
mux := astits.NewMuxer(context.Background(), &buf)
|
||||||
@@ -28,7 +164,7 @@ func TestToStreamNoSupportedCodecs(t *testing.T) {
|
|||||||
_, err = mux.WriteTables()
|
_, err = mux.WriteTables()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
r := &mpegts.Reader{R: &buf}
|
r := &EnhancedReader{R: &buf}
|
||||||
err = r.Initialize()
|
err = r.Initialize()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -60,7 +196,7 @@ func TestToStreamSkipUnsupportedTracks(t *testing.T) {
|
|||||||
_, err = mux.WriteTables()
|
_, err = mux.WriteTables()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
r := &mpegts.Reader{R: &buf}
|
r := &EnhancedReader{R: &buf}
|
||||||
err = r.Initialize()
|
err = r.Initialize()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,37 @@ func setupAudio(
|
|||||||
return audioFormatMPEG4Audio
|
return audioFormatMPEG4Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var audioFormatMPEG4AudioLATM *format.MPEG4AudioLATM
|
||||||
|
audioMedia = str.Desc.FindFormat(&audioFormatMPEG4AudioLATM)
|
||||||
|
|
||||||
|
if audioMedia != nil && !audioFormatMPEG4AudioLATM.CPresent {
|
||||||
|
str.AddReader(
|
||||||
|
reader,
|
||||||
|
audioMedia,
|
||||||
|
audioFormatMPEG4AudioLATM,
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
|
if tunit.Element == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ame mpeg4audio.AudioMuxElement
|
||||||
|
ame.StreamMuxConfig = audioFormatMPEG4AudioLATM.StreamMuxConfig
|
||||||
|
err := ame.Unmarshal(tunit.Element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nconn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||||
|
return (*w).WriteMPEG4Audio(
|
||||||
|
timestampToDuration(tunit.PTS, audioFormatMPEG4AudioLATM.ClockRate()),
|
||||||
|
ame.Payloads[0][0][0],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return audioFormatMPEG4AudioLATM
|
||||||
|
}
|
||||||
|
|
||||||
var audioFormatMPEG1 *format.MPEG1Audio
|
var audioFormatMPEG1 *format.MPEG1Audio
|
||||||
audioMedia = str.Desc.FindFormat(&audioFormatMPEG1)
|
audioMedia = str.Desc.FindFormat(&audioFormatMPEG1)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
var magicSignature = []byte{'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}
|
var magicSignature = []byte{'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}
|
||||||
|
|
||||||
// OpusIDHeader is an Opus identification header.
|
// OpusIDHeader is an Opus identification header.
|
||||||
// Specification: https://datatracker.ietf.org/doc/html/rfc7845#section-5.1
|
// Specification: RFC7845, section 5.1
|
||||||
type OpusIDHeader struct {
|
type OpusIDHeader struct {
|
||||||
Version uint8
|
Version uint8
|
||||||
ChannelCount uint8
|
ChannelCount uint8
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (w *Writer) writeTracks() error {
|
|||||||
case *format.MPEG1Audio:
|
case *format.MPEG1Audio:
|
||||||
return message.CodecMPEG1Audio
|
return message.CodecMPEG1Audio
|
||||||
|
|
||||||
case *format.MPEG4Audio:
|
case *format.MPEG4Audio, *format.MPEG4AudioLATM:
|
||||||
return message.CodecMPEG4Audio
|
return message.CodecMPEG4Audio
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -133,14 +133,16 @@ func (w *Writer) writeTracks() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioConfig *mpeg4audio.AudioSpecificConfig
|
var audioConf *mpeg4audio.AudioSpecificConfig
|
||||||
|
|
||||||
if track, ok := w.AudioTrack.(*format.MPEG4Audio); ok {
|
if track, ok := w.AudioTrack.(*format.MPEG4Audio); ok {
|
||||||
audioConfig = track.GetConfig()
|
audioConf = track.Config
|
||||||
|
} else if track, ok := w.AudioTrack.(*format.MPEG4AudioLATM); ok {
|
||||||
|
audioConf = track.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
if audioConfig != nil {
|
if audioConf != nil {
|
||||||
enc, err := audioConfig.Marshal()
|
enc, err := audioConf.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -640,10 +640,43 @@ func (f *formatFMP4) initialize() bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
case *rtspformat.MPEG4Audio:
|
case *rtspformat.MPEG4Audio:
|
||||||
co := forma.GetConfig()
|
codec := &mp4.CodecMPEG4Audio{
|
||||||
if co != nil {
|
Config: *forma.Config,
|
||||||
|
}
|
||||||
|
track := addTrack(forma, codec)
|
||||||
|
|
||||||
|
f.ri.stream.AddReader(
|
||||||
|
f.ri,
|
||||||
|
media,
|
||||||
|
forma,
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
tunit := u.(*unit.MPEG4Audio)
|
||||||
|
if tunit.AUs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, au := range tunit.AUs {
|
||||||
|
pts := tunit.PTS + int64(i)*mpeg4audio.SamplesPerAccessUnit
|
||||||
|
|
||||||
|
err := track.write(&sample{
|
||||||
|
Sample: &fmp4.Sample{
|
||||||
|
Payload: au,
|
||||||
|
},
|
||||||
|
dts: pts,
|
||||||
|
ntp: tunit.NTP.Add(timestampToDuration(pts-tunit.PTS, clockRate)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
case *rtspformat.MPEG4AudioLATM:
|
||||||
|
if !forma.CPresent {
|
||||||
codec := &mp4.CodecMPEG4Audio{
|
codec := &mp4.CodecMPEG4Audio{
|
||||||
Config: *co,
|
Config: *forma.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig,
|
||||||
}
|
}
|
||||||
track := addTrack(forma, codec)
|
track := addTrack(forma, codec)
|
||||||
|
|
||||||
@@ -652,27 +685,25 @@ func (f *formatFMP4) initialize() bool {
|
|||||||
media,
|
media,
|
||||||
forma,
|
forma,
|
||||||
func(u unit.Unit) error {
|
func(u unit.Unit) error {
|
||||||
tunit := u.(*unit.MPEG4Audio)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
if tunit.AUs == nil {
|
if tunit.Element == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, au := range tunit.AUs {
|
var ame mpeg4audio.AudioMuxElement
|
||||||
pts := tunit.PTS + int64(i)*mpeg4audio.SamplesPerAccessUnit
|
ame.StreamMuxConfig = forma.StreamMuxConfig
|
||||||
|
err := ame.Unmarshal(tunit.Element)
|
||||||
err := track.write(&sample{
|
if err != nil {
|
||||||
Sample: &fmp4.Sample{
|
return err
|
||||||
Payload: au,
|
|
||||||
},
|
|
||||||
dts: pts,
|
|
||||||
ntp: tunit.NTP.Add(timestampToDuration(pts-tunit.PTS, clockRate)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return track.write(&sample{
|
||||||
|
Sample: &fmp4.Sample{
|
||||||
|
Payload: ame.Payloads[0][0][0],
|
||||||
|
},
|
||||||
|
dts: tunit.PTS,
|
||||||
|
ntp: tunit.NTP,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
|
||||||
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||||
|
|
||||||
@@ -306,10 +307,38 @@ func (f *formatMPEGTS) initialize() bool {
|
|||||||
})
|
})
|
||||||
|
|
||||||
case *rtspformat.MPEG4Audio:
|
case *rtspformat.MPEG4Audio:
|
||||||
co := forma.GetConfig()
|
track := addTrack(forma, &mpegts.CodecMPEG4Audio{
|
||||||
if co != nil {
|
Config: *forma.Config,
|
||||||
|
})
|
||||||
|
|
||||||
|
f.ri.stream.AddReader(
|
||||||
|
f.ri,
|
||||||
|
media,
|
||||||
|
forma,
|
||||||
|
func(u unit.Unit) error {
|
||||||
|
tunit := u.(*unit.MPEG4Audio)
|
||||||
|
if tunit.AUs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.write(
|
||||||
|
timestampToDuration(tunit.PTS, clockRate),
|
||||||
|
tunit.NTP,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
func() error {
|
||||||
|
return f.mw.WriteMPEG4Audio(
|
||||||
|
track,
|
||||||
|
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
||||||
|
tunit.AUs)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
case *rtspformat.MPEG4AudioLATM:
|
||||||
|
if !forma.CPresent {
|
||||||
track := addTrack(forma, &mpegts.CodecMPEG4Audio{
|
track := addTrack(forma, &mpegts.CodecMPEG4Audio{
|
||||||
Config: *co,
|
Config: *forma.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
f.ri.stream.AddReader(
|
f.ri.stream.AddReader(
|
||||||
@@ -317,11 +346,18 @@ func (f *formatMPEGTS) initialize() bool {
|
|||||||
media,
|
media,
|
||||||
forma,
|
forma,
|
||||||
func(u unit.Unit) error {
|
func(u unit.Unit) error {
|
||||||
tunit := u.(*unit.MPEG4Audio)
|
tunit := u.(*unit.MPEG4AudioLATM)
|
||||||
if tunit.AUs == nil {
|
if tunit.Element == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ame mpeg4audio.AudioMuxElement
|
||||||
|
ame.StreamMuxConfig = forma.StreamMuxConfig
|
||||||
|
err := ame.Unmarshal(tunit.Element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return f.write(
|
return f.write(
|
||||||
timestampToDuration(tunit.PTS, clockRate),
|
timestampToDuration(tunit.PTS, clockRate),
|
||||||
tunit.NTP,
|
tunit.NTP,
|
||||||
@@ -331,7 +367,7 @@ func (f *formatMPEGTS) initialize() bool {
|
|||||||
return f.mw.WriteMPEG4Audio(
|
return f.mw.WriteMPEG4Audio(
|
||||||
track,
|
track,
|
||||||
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
multiplyAndDivide(tunit.PTS, 90000, int64(clockRate)),
|
||||||
tunit.AUs)
|
[][]byte{ame.Payloads[0][0][0]})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
mcmpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
|
||||||
srt "github.com/datarhei/gosrt"
|
srt "github.com/datarhei/gosrt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
@@ -204,7 +203,7 @@ func (c *conn) runPublish(streamID *streamID) error {
|
|||||||
|
|
||||||
func (c *conn) runPublishReader(sconn srt.Conn, path defs.Path) error {
|
func (c *conn) runPublishReader(sconn srt.Conn, path defs.Path) error {
|
||||||
sconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout)))
|
sconn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout)))
|
||||||
r := &mcmpegts.Reader{R: sconn}
|
r := &mpegts.EnhancedReader{R: sconn}
|
||||||
err := r.Initialize()
|
err := r.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
mcmpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
|
||||||
srt "github.com/datarhei/gosrt"
|
srt "github.com/datarhei/gosrt"
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
@@ -70,7 +69,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
|||||||
|
|
||||||
func (s *Source) runReader(sconn srt.Conn) error {
|
func (s *Source) runReader(sconn srt.Conn) error {
|
||||||
sconn.SetReadDeadline(time.Now().Add(time.Duration(s.ReadTimeout)))
|
sconn.SetReadDeadline(time.Now().Add(time.Duration(s.ReadTimeout)))
|
||||||
r := &mcmpegts.Reader{R: sconn}
|
r := &mpegts.EnhancedReader{R: sconn}
|
||||||
err := r.Initialize()
|
err := r.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||||
"github.com/bluenviron/gortsplib/v4/pkg/multicast"
|
"github.com/bluenviron/gortsplib/v4/pkg/multicast"
|
||||||
mcmpegts "github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
|
||||||
|
|
||||||
"github.com/bluenviron/mediamtx/internal/conf"
|
"github.com/bluenviron/mediamtx/internal/conf"
|
||||||
"github.com/bluenviron/mediamtx/internal/counterdumper"
|
"github.com/bluenviron/mediamtx/internal/counterdumper"
|
||||||
@@ -137,7 +136,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
|
|||||||
func (s *Source) runReader(pc net.PacketConn, sourceIP net.IP) error {
|
func (s *Source) runReader(pc net.PacketConn, sourceIP net.IP) error {
|
||||||
pc.SetReadDeadline(time.Now().Add(time.Duration(s.ReadTimeout)))
|
pc.SetReadDeadline(time.Now().Add(time.Duration(s.ReadTimeout)))
|
||||||
pcr := &packetConnReader{pc: pc, sourceIP: sourceIP}
|
pcr := &packetConnReader{pc: pc, sourceIP: sourceIP}
|
||||||
r := &mcmpegts.Reader{R: pcr}
|
r := &mpegts.EnhancedReader{R: pcr}
|
||||||
err := r.Initialize()
|
err := r.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
7
internal/unit/mpeg4_audio_latm.go
Normal file
7
internal/unit/mpeg4_audio_latm.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package unit
|
||||||
|
|
||||||
|
// MPEG4AudioLATM is a MPEG-4 Audio LATM data unit.
|
||||||
|
type MPEG4AudioLATM struct {
|
||||||
|
Base
|
||||||
|
Element []byte
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user