This happened when using alwaysAvailableFile and a MPEG-4 audio track.
512 lines
12 KiB
Go
512 lines
12 KiB
Go
// Package stream contains the Stream object.
|
|
package stream
|
|
|
|
import (
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/bluenviron/gortsplib/v5"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/description"
|
|
"github.com/bluenviron/gortsplib/v5/pkg/format"
|
|
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
|
"github.com/bluenviron/mediacommon/v2/pkg/formats/mp4/codecs"
|
|
"github.com/bluenviron/mediacommon/v2/pkg/formats/pmp4"
|
|
"github.com/pion/rtp"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/conf"
|
|
"github.com/bluenviron/mediamtx/internal/errordumper"
|
|
"github.com/bluenviron/mediamtx/internal/logger"
|
|
)
|
|
|
|
func mediasFromAlwaysAvailableFile(alwaysAvailableFile string) ([]*description.Media, error) {
|
|
f, err := os.Open(alwaysAvailableFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
var presentation pmp4.Presentation
|
|
err = presentation.Unmarshal(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var medias []*description.Media
|
|
|
|
for _, track := range presentation.Tracks {
|
|
switch codec := track.Codec.(type) {
|
|
case *codecs.AV1:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.AV1{
|
|
PayloadTyp: 96,
|
|
}},
|
|
})
|
|
|
|
case *codecs.VP9:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.VP9{
|
|
PayloadTyp: 96,
|
|
}},
|
|
})
|
|
|
|
case *codecs.H265:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.H265{
|
|
PayloadTyp: 96,
|
|
VPS: codec.VPS,
|
|
SPS: codec.SPS,
|
|
PPS: codec.PPS,
|
|
}},
|
|
})
|
|
|
|
case *codecs.H264:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.H264{
|
|
PayloadTyp: 96,
|
|
PacketizationMode: 1,
|
|
SPS: codec.SPS,
|
|
PPS: codec.PPS,
|
|
}},
|
|
})
|
|
|
|
case *codecs.Opus:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.Opus{
|
|
PayloadTyp: 96,
|
|
ChannelCount: codec.ChannelCount,
|
|
}},
|
|
})
|
|
|
|
case *codecs.MPEG4Audio:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.MPEG4Audio{
|
|
PayloadTyp: 96,
|
|
SizeLength: 13,
|
|
IndexLength: 3,
|
|
IndexDeltaLength: 3,
|
|
Config: &mpeg4audio.AudioSpecificConfig{
|
|
Type: mpeg4audio.ObjectTypeAACLC,
|
|
SampleRate: codec.Config.SampleRate,
|
|
ChannelConfig: codec.Config.ChannelConfig,
|
|
ChannelCount: codec.Config.ChannelCount, //nolint:staticcheck
|
|
},
|
|
}},
|
|
})
|
|
|
|
case *codecs.LPCM:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.LPCM{
|
|
PayloadTyp: 96,
|
|
BitDepth: codec.BitDepth,
|
|
SampleRate: codec.SampleRate,
|
|
ChannelCount: codec.ChannelCount,
|
|
}},
|
|
})
|
|
}
|
|
}
|
|
|
|
return medias, nil
|
|
}
|
|
|
|
func mediasFromAlwaysAvailableTracks(alwaysAvailableTracks []conf.AlwaysAvailableTrack) []*description.Media {
|
|
var medias []*description.Media
|
|
|
|
for _, track := range alwaysAvailableTracks {
|
|
switch track.Codec {
|
|
case conf.CodecAV1:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.AV1{
|
|
PayloadTyp: 96,
|
|
}},
|
|
})
|
|
|
|
case conf.CodecVP9:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.VP9{
|
|
PayloadTyp: 96,
|
|
}},
|
|
})
|
|
|
|
case conf.CodecH265:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.H265{
|
|
PayloadTyp: 96,
|
|
}},
|
|
})
|
|
|
|
case conf.CodecH264:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeVideo,
|
|
Formats: []format.Format{&format.H264{
|
|
PayloadTyp: 96,
|
|
PacketizationMode: 1,
|
|
}},
|
|
})
|
|
|
|
case conf.CodecOpus:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.Opus{
|
|
PayloadTyp: 96,
|
|
ChannelCount: 2,
|
|
}},
|
|
})
|
|
|
|
case conf.CodecMPEG4Audio:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.MPEG4Audio{
|
|
PayloadTyp: 96,
|
|
SizeLength: 13,
|
|
IndexLength: 3,
|
|
IndexDeltaLength: 3,
|
|
Config: &mpeg4audio.AudioSpecificConfig{
|
|
Type: mpeg4audio.ObjectTypeAACLC,
|
|
SampleRate: track.SampleRate,
|
|
ChannelConfig: uint8(track.ChannelCount),
|
|
ChannelCount: track.ChannelCount,
|
|
},
|
|
}},
|
|
})
|
|
|
|
case conf.CodecG711:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.G711{
|
|
PayloadTyp: func() uint8 {
|
|
switch {
|
|
case track.ChannelCount == 1 && track.MULaw:
|
|
return 0
|
|
case track.ChannelCount == 1 && !track.MULaw:
|
|
return 8
|
|
default:
|
|
return 96
|
|
}
|
|
}(),
|
|
MULaw: track.MULaw,
|
|
SampleRate: track.SampleRate,
|
|
ChannelCount: track.ChannelCount,
|
|
}},
|
|
})
|
|
|
|
case conf.CodecLPCM:
|
|
medias = append(medias, &description.Media{
|
|
Type: description.MediaTypeAudio,
|
|
Formats: []format.Format{&format.LPCM{
|
|
PayloadTyp: 96,
|
|
BitDepth: 16,
|
|
SampleRate: track.SampleRate,
|
|
ChannelCount: track.ChannelCount,
|
|
}},
|
|
})
|
|
}
|
|
}
|
|
|
|
return medias
|
|
}
|
|
|
|
// Stream is a media stream.
|
|
// It stores tracks, readers and allows to write data to readers, remuxing it when needed.
|
|
type Stream struct {
|
|
Desc *description.Session
|
|
AlwaysAvailable bool
|
|
AlwaysAvailableTracks []conf.AlwaysAvailableTrack
|
|
AlwaysAvailableFile string
|
|
WriteQueueSize int
|
|
RTPMaxPayloadSize int
|
|
ReplaceNTP bool
|
|
Parent logger.Writer
|
|
|
|
mutex sync.RWMutex
|
|
subStream *SubStream
|
|
offlineSubStream *offlineSubStream
|
|
bytesReceived *uint64
|
|
bytesSent *uint64
|
|
medias map[*description.Media]*streamMedia
|
|
rtspStream *gortsplib.ServerStream
|
|
rtspsStream *gortsplib.ServerStream
|
|
readers map[*Reader]struct{}
|
|
processingErrors *errordumper.Dumper
|
|
|
|
timeMutex sync.Mutex
|
|
firstTimeReceived bool
|
|
lastPTS time.Duration
|
|
lastSystemTime time.Time
|
|
|
|
hasReaders chan struct{}
|
|
}
|
|
|
|
// Initialize initializes a Stream.
|
|
func (s *Stream) Initialize() error {
|
|
if s.AlwaysAvailable {
|
|
if s.Desc != nil {
|
|
panic("should not happen")
|
|
}
|
|
if !s.ReplaceNTP {
|
|
panic("should not happen")
|
|
}
|
|
|
|
var medias []*description.Media
|
|
|
|
if s.AlwaysAvailableFile != "" {
|
|
var err error
|
|
medias, err = mediasFromAlwaysAvailableFile(s.AlwaysAvailableFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
medias = mediasFromAlwaysAvailableTracks(s.AlwaysAvailableTracks)
|
|
}
|
|
|
|
s.Desc = &description.Session{
|
|
Medias: medias,
|
|
}
|
|
}
|
|
|
|
s.bytesReceived = new(uint64)
|
|
s.bytesSent = new(uint64)
|
|
s.medias = make(map[*description.Media]*streamMedia)
|
|
s.readers = make(map[*Reader]struct{})
|
|
s.hasReaders = make(chan struct{})
|
|
|
|
s.processingErrors = &errordumper.Dumper{
|
|
OnReport: func(val uint64, last error) {
|
|
if val == 1 {
|
|
s.Parent.Log(logger.Warn, "processing error: %v", last)
|
|
} else {
|
|
s.Parent.Log(logger.Warn, "%d processing errors, last was: %v", val, last)
|
|
}
|
|
},
|
|
}
|
|
s.processingErrors.Start()
|
|
|
|
s.lastSystemTime = time.Now()
|
|
|
|
for _, media := range s.Desc.Medias {
|
|
sm := &streamMedia{
|
|
media: media,
|
|
alwaysAvailable: s.AlwaysAvailable,
|
|
rtpMaxPayloadSize: s.RTPMaxPayloadSize,
|
|
replaceNTP: s.ReplaceNTP,
|
|
addBytesReceived: s.addBytesReceived,
|
|
addBytesSent: s.addBytesSent,
|
|
updateLastTime: s.updateLastTime,
|
|
writeRTSP: s.writeRTSP,
|
|
processingErrors: s.processingErrors,
|
|
parent: s.Parent,
|
|
}
|
|
err := sm.initialize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.medias[media] = sm
|
|
}
|
|
|
|
if s.AlwaysAvailable {
|
|
err := s.StartOfflineSubStream()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes all resources of the stream.
|
|
func (s *Stream) Close() {
|
|
if s.offlineSubStream != nil {
|
|
s.offlineSubStream.close(false)
|
|
}
|
|
|
|
s.processingErrors.Stop()
|
|
|
|
if s.rtspStream != nil {
|
|
s.rtspStream.Close()
|
|
}
|
|
if s.rtspsStream != nil {
|
|
s.rtspsStream.Close()
|
|
}
|
|
}
|
|
|
|
// StartOfflineSubStream starts the offline substream.
|
|
func (s *Stream) StartOfflineSubStream() error {
|
|
if !s.AlwaysAvailable {
|
|
panic("should not happen")
|
|
}
|
|
|
|
oss := &offlineSubStream{
|
|
stream: s,
|
|
}
|
|
err := oss.initialize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if s.offlineSubStream != nil {
|
|
s.Parent.Log(logger.Info, "stream is offline")
|
|
}
|
|
|
|
s.offlineSubStream = oss
|
|
|
|
return nil
|
|
}
|
|
|
|
// BytesReceived returns received bytes.
|
|
func (s *Stream) BytesReceived() uint64 {
|
|
return atomic.LoadUint64(s.bytesReceived)
|
|
}
|
|
|
|
// BytesSent returns sent bytes.
|
|
func (s *Stream) BytesSent() uint64 {
|
|
bytesSent := atomic.LoadUint64(s.bytesSent)
|
|
|
|
s.mutex.RLock()
|
|
defer s.mutex.RUnlock()
|
|
|
|
if s.rtspStream != nil {
|
|
stats := s.rtspStream.Stats()
|
|
bytesSent += stats.BytesSent
|
|
}
|
|
if s.rtspsStream != nil {
|
|
stats := s.rtspsStream.Stats()
|
|
bytesSent += stats.BytesSent
|
|
}
|
|
|
|
return bytesSent
|
|
}
|
|
|
|
// RTSPStream returns the RTSP stream.
|
|
func (s *Stream) RTSPStream(server *gortsplib.Server) *gortsplib.ServerStream {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.rtspStream == nil {
|
|
s.rtspStream = &gortsplib.ServerStream{
|
|
Server: server,
|
|
Desc: s.Desc,
|
|
}
|
|
err := s.rtspStream.Initialize()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return s.rtspStream
|
|
}
|
|
|
|
// RTSPSStream returns the RTSPS stream.
|
|
func (s *Stream) RTSPSStream(server *gortsplib.Server) *gortsplib.ServerStream {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.rtspsStream == nil {
|
|
s.rtspsStream = &gortsplib.ServerStream{
|
|
Server: server,
|
|
Desc: s.Desc,
|
|
}
|
|
err := s.rtspsStream.Initialize()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return s.rtspsStream
|
|
}
|
|
|
|
// AddReader adds a reader.
|
|
// Used by all protocols except RTSP.
|
|
func (s *Stream) AddReader(r *Reader) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
s.readers[r] = struct{}{}
|
|
|
|
for medi, formats := range r.onDatas {
|
|
sm := s.medias[medi]
|
|
|
|
for forma, onData := range formats {
|
|
sf := sm.formats[forma]
|
|
sf.onDatas[r] = onData
|
|
}
|
|
}
|
|
|
|
r.queueSize = s.WriteQueueSize
|
|
r.start()
|
|
|
|
select {
|
|
case <-s.hasReaders:
|
|
default:
|
|
close(s.hasReaders)
|
|
}
|
|
}
|
|
|
|
// RemoveReader removes a reader.
|
|
// Used by all protocols except RTSP.
|
|
func (s *Stream) RemoveReader(r *Reader) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
r.stop()
|
|
|
|
for medi, formats := range r.onDatas {
|
|
sm := s.medias[medi]
|
|
|
|
for forma := range formats {
|
|
sf := sm.formats[forma]
|
|
delete(sf.onDatas, r)
|
|
}
|
|
}
|
|
|
|
delete(s.readers, r)
|
|
}
|
|
|
|
// WaitForReaders waits for the stream to have at least one reader.
|
|
func (s *Stream) WaitForReaders() {
|
|
<-s.hasReaders
|
|
}
|
|
|
|
func (s *Stream) addBytesReceived(v uint64) {
|
|
atomic.AddUint64(s.bytesReceived, v)
|
|
}
|
|
|
|
func (s *Stream) addBytesSent(v uint64) {
|
|
atomic.AddUint64(s.bytesSent, v)
|
|
}
|
|
|
|
func (s *Stream) updateLastTime(pts time.Duration) {
|
|
s.timeMutex.Lock()
|
|
defer s.timeMutex.Unlock()
|
|
|
|
s.firstTimeReceived = true
|
|
|
|
if pts > s.lastPTS {
|
|
s.lastPTS = pts
|
|
}
|
|
|
|
s.lastSystemTime = time.Now()
|
|
}
|
|
|
|
func (s *Stream) writeRTSP(medi *description.Media, pkts []*rtp.Packet, ntp time.Time) {
|
|
if s.rtspStream != nil {
|
|
for _, pkt := range pkts {
|
|
s.rtspStream.WritePacketRTPWithNTP(medi, pkt, ntp) //nolint:errcheck
|
|
}
|
|
}
|
|
|
|
if s.rtspsStream != nil {
|
|
for _, pkt := range pkts {
|
|
s.rtspsStream.WritePacketRTPWithNTP(medi, pkt, ntp) //nolint:errcheck
|
|
}
|
|
}
|
|
}
|