add additional checks on always-available streams (#5408)
make sure that published streams match MPEG-4 audio, G711 and LPCM parameters declared in configuration or used by the offline file.
This commit is contained in:
@@ -170,6 +170,7 @@ func mediasFromAlwaysAvailableTracks(alwaysAvailableTracks []conf.AlwaysAvailabl
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: track.SampleRate,
|
||||
ChannelConfig: uint8(track.ChannelCount),
|
||||
ChannelCount: track.ChannelCount,
|
||||
},
|
||||
}},
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"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/mediamtx/internal/conf"
|
||||
"github.com/bluenviron/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
@@ -1391,3 +1392,113 @@ func TestStreamAlwaysAvailable(t *testing.T) {
|
||||
|
||||
<-recv2
|
||||
}
|
||||
|
||||
func TestStreamAlwaysAvailableErrors(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
tracks []conf.AlwaysAvailableTrack
|
||||
desc *description.Session
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"wrong tracks",
|
||||
[]conf.AlwaysAvailableTrack{
|
||||
{Codec: "H264"},
|
||||
{Codec: "H265"},
|
||||
},
|
||||
&description.Session{
|
||||
Medias: []*description.Media{
|
||||
{
|
||||
Type: description.MediaTypeVideo,
|
||||
Formats: []format.Format{&format.H264{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"wants to publish [H264], but stream expects [H264 H265]",
|
||||
},
|
||||
{
|
||||
"wrong mpeg-4 audio config",
|
||||
[]conf.AlwaysAvailableTrack{
|
||||
{Codec: "MPEG4Audio", SampleRate: 44100, ChannelCount: 2},
|
||||
},
|
||||
&description.Session{
|
||||
Medias: []*description.Media{
|
||||
{
|
||||
Type: description.MediaTypeAudio,
|
||||
Formats: []format.Format{&format.MPEG4Audio{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 48000,
|
||||
ChannelConfig: 1,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"MPEG-4 audio configuration does not match, is type=2, sampleRate=48000, " +
|
||||
"channelCount=1, but stream expects type=2, sampleRate=44100, channelCount=2",
|
||||
},
|
||||
{
|
||||
"wrong g711 config",
|
||||
[]conf.AlwaysAvailableTrack{
|
||||
{Codec: "G711", MULaw: true, SampleRate: 8000, ChannelCount: 2},
|
||||
},
|
||||
&description.Session{
|
||||
Medias: []*description.Media{
|
||||
{
|
||||
Type: description.MediaTypeAudio,
|
||||
Formats: []format.Format{&format.G711{
|
||||
MULaw: false,
|
||||
SampleRate: 8000,
|
||||
ChannelCount: 2,
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"G711 configuration does not match, is MULaw=false, sampleRate=8000, " +
|
||||
"channelCount=2, but stream expects MULaw=true, sampleRate=8000, channelCount=2",
|
||||
},
|
||||
{
|
||||
"wrong lpcm config",
|
||||
[]conf.AlwaysAvailableTrack{
|
||||
{Codec: "LPCM", SampleRate: 44100, ChannelCount: 2},
|
||||
},
|
||||
&description.Session{
|
||||
Medias: []*description.Media{
|
||||
{
|
||||
Type: description.MediaTypeAudio,
|
||||
Formats: []format.Format{&format.LPCM{
|
||||
BitDepth: 16,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"LPCM configuration does not match, is bitDepth=16, sampleRate=48000, " +
|
||||
"channelCount=2, but stream expects bitDepth=16, sampleRate=44100, channelCount=2",
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
strm := &Stream{
|
||||
AlwaysAvailable: true,
|
||||
AlwaysAvailableTracks: ca.tracks,
|
||||
WriteQueueSize: 512,
|
||||
RTPMaxPayloadSize: 1450,
|
||||
ReplaceNTP: true,
|
||||
Parent: &nilLogger{},
|
||||
}
|
||||
err := strm.Initialize()
|
||||
require.NoError(t, err)
|
||||
defer strm.Close()
|
||||
|
||||
subStream := &SubStream{
|
||||
Stream: strm,
|
||||
CurDesc: ca.desc,
|
||||
UseRTPPackets: false,
|
||||
}
|
||||
err = subStream.Initialize()
|
||||
require.EqualError(t, err, ca.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"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/mediamtx/internal/logger"
|
||||
"github.com/bluenviron/mediamtx/internal/unit"
|
||||
)
|
||||
@@ -43,24 +44,76 @@ func mediasToCodecs(medias []*description.Media) []string {
|
||||
return FormatsToCodecs(gatherFormats(medias))
|
||||
}
|
||||
|
||||
func mediasAreCompatible(medias1 []*description.Media, medias2 []*description.Media) bool {
|
||||
func formatMPEG4AudioConfig(asc *mpeg4audio.AudioSpecificConfig) string {
|
||||
return fmt.Sprintf("type=%d, sampleRate=%d, channelCount=%d",
|
||||
asc.Type, asc.SampleRate, asc.ChannelConfig)
|
||||
}
|
||||
|
||||
func formatG711Config(f *format.G711) string {
|
||||
return fmt.Sprintf("MULaw=%v, sampleRate=%d, channelCount=%d",
|
||||
f.MULaw, f.SampleRate, f.ChannelCount)
|
||||
}
|
||||
|
||||
func formatLPCMConfig(f *format.LPCM) string {
|
||||
return fmt.Sprintf("bitDepth=%d, sampleRate=%d, channelCount=%d",
|
||||
f.BitDepth, f.SampleRate, f.ChannelCount)
|
||||
}
|
||||
|
||||
func mediasAreCompatible(medias1 []*description.Media, medias2 []*description.Media) error {
|
||||
if len(medias1) != len(medias2) {
|
||||
return false
|
||||
return fmt.Errorf("wants to publish %v, but stream expects %v",
|
||||
mediasToCodecs(medias2), mediasToCodecs(medias1))
|
||||
}
|
||||
|
||||
for i := range medias1 {
|
||||
if len(medias1[i].Formats) != len(medias2[i].Formats) {
|
||||
return false
|
||||
return fmt.Errorf("wants to publish %v, but stream expects %v",
|
||||
mediasToCodecs(medias2), mediasToCodecs(medias1))
|
||||
}
|
||||
|
||||
for j := range medias1[i].Formats {
|
||||
if reflect.TypeOf(medias1[i].Formats[j]) != reflect.TypeOf(medias2[i].Formats[j]) {
|
||||
return false
|
||||
return fmt.Errorf("wants to publish %v, but stream expects %v",
|
||||
mediasToCodecs(medias2), mediasToCodecs(medias1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
for i := range medias1 {
|
||||
for j := range medias1[i].Formats {
|
||||
switch format1 := medias1[i].Formats[j].(type) {
|
||||
case *format.MPEG4Audio:
|
||||
format2 := medias2[i].Formats[j].(*format.MPEG4Audio)
|
||||
|
||||
if !reflect.DeepEqual(format1.Config, format2.Config) {
|
||||
return fmt.Errorf("MPEG-4 audio configuration does not match, is %s, but stream expects %s",
|
||||
formatMPEG4AudioConfig(format2.Config), formatMPEG4AudioConfig(format1.Config))
|
||||
}
|
||||
|
||||
case *format.G711:
|
||||
format2 := medias2[i].Formats[j].(*format.G711)
|
||||
|
||||
if format1.MULaw != format2.MULaw ||
|
||||
format1.SampleRate != format2.SampleRate ||
|
||||
format1.ChannelCount != format2.ChannelCount {
|
||||
return fmt.Errorf("G711 configuration does not match, is %s, but stream expects %s",
|
||||
formatG711Config(format2), formatG711Config(format1))
|
||||
}
|
||||
|
||||
case *format.LPCM:
|
||||
format2 := medias2[i].Formats[j].(*format.LPCM)
|
||||
|
||||
if format1.BitDepth != format2.BitDepth ||
|
||||
format1.SampleRate != format2.SampleRate ||
|
||||
format1.ChannelCount != format2.ChannelCount {
|
||||
return fmt.Errorf("LPCM configuration does not match, is %s, but stream expects %s",
|
||||
formatLPCMConfig(format2), formatLPCMConfig(format1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubStream is a Stream without interruptions.
|
||||
@@ -87,9 +140,9 @@ func (ss *SubStream) Initialize() error {
|
||||
panic("should not happen")
|
||||
}
|
||||
|
||||
if !mediasAreCompatible(ss.Stream.Desc.Medias, ss.CurDesc.Medias) {
|
||||
return fmt.Errorf("want to publish %v, but stream expects %v",
|
||||
mediasToCodecs(ss.CurDesc.Medias), mediasToCodecs(ss.Stream.Desc.Medias))
|
||||
err := mediasAreCompatible(ss.Stream.Desc.Medias, ss.CurDesc.Medias)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user