fix generating timestamp of non-RTSP MPEG-4 video streams (#4758)
Some checks are pending
code_lint / golangci_lint (push) Waiting to run
code_lint / mod_tidy (push) Waiting to run
code_lint / api_docs (push) Waiting to run
code_test / test_64 (push) Waiting to run
code_test / test_32 (push) Waiting to run
code_test / test_e2e (push) Waiting to run

This commit is contained in:
Alessandro Ros 2025-07-20 12:20:31 +02:00 committed by GitHub
parent b1b288b6df
commit bc95f6240b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 244 additions and 64 deletions

View file

@ -13,13 +13,6 @@ import (
"github.com/bluenviron/mediamtx/internal/unit"
)
// AV1-related parameters
var (
AV1DefaultSequenceHeader = []byte{
8, 0, 0, 0, 66, 167, 191, 228, 96, 13, 0, 64,
}
)
type av1 struct {
RTPMaxPayloadSize int
Format *format.AV1

View file

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestG711Encode(t *testing.T) {
func TestG711ProcessUnit(t *testing.T) {
t.Run("alaw", func(t *testing.T) {
forma := &format.G711{
PayloadTyp: 8,

View file

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestGenericRemovePadding(t *testing.T) {
func TestGenericProcessRTPPacket(t *testing.T) {
forma := &format.Generic{
PayloadTyp: 96,
RTPMa: "private/90000",
@ -37,6 +37,7 @@ func TestGenericRemovePadding(t *testing.T) {
_, err = p.ProcessRTPPacket(pkt, time.Time{}, 0, false)
require.NoError(t, err)
// check that padding has been removed
require.Equal(t, &rtp.Packet{
Header: rtp.Header{
Version: 2,

View file

@ -28,7 +28,83 @@ func Logger(cb func(logger.Level, string, ...interface{})) logger.Writer {
return &testLogger{cb: cb}
}
func TestH264DynamicParams(t *testing.T) {
func TestH264ProcessUnit(t *testing.T) {
forma := &format.H264{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u1 := &unit.H264{
Base: unit.Base{
PTS: 30000,
},
AU: [][]byte{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
{5, 1}, // IDR
},
}
err = p.ProcessUnit(u1)
require.NoError(t, err)
require.Equal(t, [][]byte{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
{5, 1}, // IDR
}, u1.AU)
u2 := &unit.H264{
Base: unit.Base{
PTS: 30000 * 2,
},
AU: [][]byte{
{5, 2}, // IDR
},
}
err = p.ProcessUnit(u2)
require.NoError(t, err)
// test that params have been added to the SDP
require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS)
require.Equal(t, []byte{8, 1}, forma.PPS)
// test that params have been added to the frame
require.Equal(t, [][]byte{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
{5, 2}, // IDR
}, u2.AU)
// test that timestamp had increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}
func TestH264ProcessUnitEmpty(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.H264{
AU: [][]byte{
{0x07, 0x01, 0x02, 0x03}, // SPS
{0x08, 0x01, 0x02}, // PPS
},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
// if all NALUs have been removed, no RTP packets shall be generated.
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
}
func TestH264ProcessRTPPacketUpdateParams(t *testing.T) {
for _, ca := range []string{"standard", "aggregated"} {
t.Run(ca, func(t *testing.T) {
forma := &format.H264{
@ -93,7 +169,7 @@ func TestH264DynamicParams(t *testing.T) {
}
}
func TestH264OversizedPackets(t *testing.T) {
func TestH264ProcessRTPPacketOversized(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
@ -201,29 +277,6 @@ func TestH264OversizedPackets(t *testing.T) {
require.True(t, logged)
}
func TestH264EmptyPacket(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.H264{
AU: [][]byte{
{0x07, 0x01, 0x02, 0x03}, // SPS
{0x08, 0x01, 0x02}, // PPS
},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
// if all NALUs have been removed, no RTP packets must be generated.
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
}
func FuzzRTPH264ExtractParams(f *testing.F) {
f.Fuzz(func(_ *testing.T, b []byte) {
rtpH264ExtractParams(b)

View file

@ -15,7 +15,87 @@ import (
"github.com/bluenviron/mediamtx/internal/unit"
)
func TestH265DynamicParams(t *testing.T) {
func TestH265ProcessUnit(t *testing.T) {
forma := &format.H265{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u1 := &unit.H265{
Base: unit.Base{
PTS: 30000,
},
AU: [][]byte{
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
},
}
err = p.ProcessUnit(u1)
require.NoError(t, err)
require.Equal(t, [][]byte{
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
}, u1.AU)
u2 := &unit.H265{
Base: unit.Base{
PTS: 30000 * 2,
},
AU: [][]byte{
{byte(mch265.NALUType_CRA_NUT) << 1, 1},
},
}
err = p.ProcessUnit(u2)
require.NoError(t, err)
// test that params have been added to the SDP
require.Equal(t, []byte{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, forma.VPS)
require.Equal(t, []byte{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS)
require.Equal(t, []byte{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS)
// test that params have been added to the frame
require.Equal(t, [][]byte{
{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3},
{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6},
{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9},
{byte(mch265.NALUType_CRA_NUT) << 1, 1},
}, u2.AU)
// test that timestamp had increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}
func TestH265ProcessUnitEmpty(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.H265{
AU: [][]byte{
{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12}, // VPS
{byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15}, // SPS
{byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18}, // PPS
},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
// if all NALUs have been removed, no RTP packets shall be generated.
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
}
func TestH265ProcessRTPPacketUpdateParams(t *testing.T) {
for _, ca := range []string{"standard", "aggregated"} {
t.Run(ca, func(t *testing.T) {
forma := &format.H265{
@ -88,7 +168,7 @@ func TestH265DynamicParams(t *testing.T) {
}
}
func TestH265OversizedPackets(t *testing.T) {
func TestH265ProcessRTPPacketOversized(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
VPS: []byte{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12},
@ -184,29 +264,6 @@ func TestH265OversizedPackets(t *testing.T) {
require.True(t, logged)
}
func TestH265EmptyPacket(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.H265{
AU: [][]byte{
{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12}, // VPS
{byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15}, // SPS
{byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18}, // PPS
},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
// if all NALUs have been removed, no RTP packets must be generated.
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
}
func FuzzRTPH265ExtractParams(f *testing.F) {
f.Fuzz(func(_ *testing.T, b []byte) {
rtpH265ExtractParams(b)

View file

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestLPCMEncode(t *testing.T) {
func TestLPCMProcessUnit(t *testing.T) {
forma := &format.LPCM{
PayloadTyp: 96,
BitDepth: 16,

View file

@ -77,6 +77,7 @@ func (t *mpeg4Video) updateTrackParameters(frame []byte) {
}
func (t *mpeg4Video) remuxFrame(frame []byte) []byte {
// remove config
if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) {
end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)})
if end >= 0 {
@ -84,6 +85,7 @@ func (t *mpeg4Video) remuxFrame(frame []byte) []byte {
}
}
// add config
if bytes.Contains(frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) {
f := make([]byte, len(t.Format.Config)+len(frame))
n := copy(f, t.Format.Config)
@ -105,12 +107,11 @@ func (t *mpeg4Video) ProcessUnit(uu unit.Unit) error { //nolint:dupl
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
u.RTPPackets = pkts
}
return nil

View file

@ -0,0 +1,71 @@
package formatprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/stretchr/testify/require"
)
func TestMPEG4VideoProcessUnit(t *testing.T) {
forma := &format.MPEG4Video{
PayloadTyp: 96,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u1 := &unit.MPEG4Video{
Base: unit.Base{
PTS: 30000,
},
Frame: []byte{
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
0, 0, 1, 0xFF,
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
0, 0, 1, 0xF0,
},
}
err = p.ProcessUnit(u1)
require.NoError(t, err)
require.Equal(t, []byte{
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
0, 0, 1, 0xFF,
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
0, 0, 1, 0xF0,
}, u1.Frame)
u2 := &unit.MPEG4Video{
Base: unit.Base{
PTS: 30000 * 2,
},
Frame: []byte{
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
0, 0, 1, 0xF1,
},
}
err = p.ProcessUnit(u2)
require.NoError(t, err)
// test that params have been added to the SDP
require.Equal(t, []byte{
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
0, 0, 1, 0xFF,
}, forma.Config)
// test that params have been added to the frame
require.Equal(t, []byte{
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
0, 0, 1, 0xFF,
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
0, 0, 1, 0xF1,
}, u2.Frame)
// test that timestamp had increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}

View file

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestOpusEncode(t *testing.T) {
func TestOpusProcessUnit(t *testing.T) {
forma := &format.Opus{
PayloadTyp: 96,
ChannelCount: 2,