Files
mediamtx/internal/stream/stream.go
T
Alessandro RosandGitHub 4ebfcde8e2 fix MPEG-4 audio configuration not matching error (#5468) (#5533)
This happened when using alwaysAvailableFile and a MPEG-4 audio track.
2026-02-28 20:17:44 +01:00

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
}
}
}