rtmp: support additional enhanced RTMP features (#4168) (#4321) (#4954)
Some checks are pending
code_lint / go (push) Waiting to run
code_lint / go_mod (push) Waiting to run
code_lint / docs (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

* support reading AV1, VP9, H265, Opus, AC-3, G711, LPCM
* support reading multiple video or audio tracks at once
This commit is contained in:
Alessandro Ros 2025-09-11 23:18:46 +02:00 committed by GitHub
parent 2be164acd6
commit 9318107779
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 3029 additions and 649 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
"github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger"
@ -48,9 +49,44 @@ func (t *av1) createEncoder() error {
return t.encoder.Init()
}
func (t *av1) remuxTemporalUnit(tu [][]byte) [][]byte {
n := 0
for _, obu := range tu {
typ := mcav1.OBUType((obu[0] >> 3) & 0b1111)
if typ == mcav1.OBUTypeTemporalDelimiter {
continue
}
n++
}
if n == 0 {
return nil
}
filteredTU := make([][]byte, n)
i := 0
for _, obu := range tu {
typ := mcav1.OBUType((obu[0] >> 3) & 0b1111)
if typ == mcav1.OBUTypeTemporalDelimiter {
continue
}
filteredTU[i] = obu
i++
}
return filteredTU
}
func (t *av1) ProcessUnit(uu unit.Unit) error { //nolint:dupl
u := uu.(*unit.AV1)
u.TU = t.remuxTemporalUnit(u.TU)
pkts, err := t.encoder.Encode(u.TU)
if err != nil {
return err
@ -106,7 +142,7 @@ func (t *av1) ProcessRTPPacket( //nolint:dupl
return nil, err
}
u.TU = tu
u.TU = t.remuxTemporalUnit(tu)
}
// route packet as is

View file

@ -0,0 +1,34 @@
package formatprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v4/pkg/format"
mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/stretchr/testify/require"
)
func TestAV1RemoveTUD(t *testing.T) {
forma := &format.AV1{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u := &unit.AV1{
Base: unit.Base{
PTS: 30000,
},
TU: [][]byte{
{byte(mcav1.OBUTypeTemporalDelimiter) << 3},
{5},
},
}
err = p.ProcessUnit(u)
require.NoError(t, err)
require.Equal(t, [][]byte{
{5},
}, u.TU)
}

View file

@ -174,13 +174,13 @@ func (t *h264) remuxAccessUnit(au [][]byte) [][]byte {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS, mch264.NALUTypePPS: // parameters: remove
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
continue
case mch264.NALUTypeAccessUnitDelimiter: // AUD: remove
case mch264.NALUTypeAccessUnitDelimiter:
continue
case mch264.NALUTypeIDR: // key frame
case mch264.NALUTypeIDR:
if !isKeyFrame {
isKeyFrame = true
@ -197,12 +197,12 @@ func (t *h264) remuxAccessUnit(au [][]byte) [][]byte {
return nil
}
filteredNALUs := make([][]byte, n)
filteredAU := make([][]byte, n)
i := 0
if isKeyFrame && t.Format.SPS != nil && t.Format.PPS != nil {
filteredNALUs[0] = t.Format.SPS
filteredNALUs[1] = t.Format.PPS
filteredAU[0] = t.Format.SPS
filteredAU[1] = t.Format.PPS
i = 2
}
@ -217,11 +217,11 @@ func (t *h264) remuxAccessUnit(au [][]byte) [][]byte {
continue
}
filteredNALUs[i] = nalu
filteredAU[i] = nalu
i++
}
return filteredNALUs
return filteredAU
}
func (t *h264) ProcessUnit(uu unit.Unit) error {

View file

@ -28,7 +28,31 @@ func Logger(cb func(logger.Level, string, ...interface{})) logger.Writer {
return &testLogger{cb: cb}
}
func TestH264ProcessUnit(t *testing.T) {
func TestH264RemoveAUD(t *testing.T) {
forma := &format.H264{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u := &unit.H264{
Base: unit.Base{
PTS: 30000,
},
AU: [][]byte{
{9, 24}, // AUD
{5, 1}, // IDR
},
}
err = p.ProcessUnit(u)
require.NoError(t, err)
require.Equal(t, [][]byte{
{5, 1}, // IDR
}, u.AU)
}
func TestH264AddParams(t *testing.T) {
forma := &format.H264{}
p, err := New(1450, forma, true, nil)
@ -77,11 +101,11 @@ func TestH264ProcessUnit(t *testing.T) {
{5, 2}, // IDR
}, u2.AU)
// test that timestamp had increased
// test that timestamp has increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}
func TestH264ProcessUnitEmpty(t *testing.T) {
func TestH264ProcessEmptyUnit(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
@ -104,7 +128,7 @@ func TestH264ProcessUnitEmpty(t *testing.T) {
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
}
func TestH264ProcessRTPPacketUpdateParams(t *testing.T) {
func TestH264RTPExtractParams(t *testing.T) {
for _, ca := range []string{"standard", "aggregated"} {
t.Run(ca, func(t *testing.T) {
forma := &format.H264{
@ -169,7 +193,7 @@ func TestH264ProcessRTPPacketUpdateParams(t *testing.T) {
}
}
func TestH264ProcessRTPPacketOversized(t *testing.T) {
func TestH264RTPOversized(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},

View file

@ -205,13 +205,13 @@ func (t *h265) remuxAccessUnit(au [][]byte) [][]byte {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT: // parameters: remove
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
continue
case mch265.NALUType_AUD_NUT: // AUD: remove
case mch265.NALUType_AUD_NUT:
continue
case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT: // key frame
case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT:
if !isKeyFrame {
isKeyFrame = true
@ -228,13 +228,13 @@ func (t *h265) remuxAccessUnit(au [][]byte) [][]byte {
return nil
}
filteredNALUs := make([][]byte, n)
filteredAU := make([][]byte, n)
i := 0
if isKeyFrame && t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil {
filteredNALUs[0] = t.Format.VPS
filteredNALUs[1] = t.Format.SPS
filteredNALUs[2] = t.Format.PPS
filteredAU[0] = t.Format.VPS
filteredAU[1] = t.Format.SPS
filteredAU[2] = t.Format.PPS
i = 3
}
@ -249,11 +249,11 @@ func (t *h265) remuxAccessUnit(au [][]byte) [][]byte {
continue
}
filteredNALUs[i] = nalu
filteredAU[i] = nalu
i++
}
return filteredNALUs
return filteredAU
}
func (t *h265) ProcessUnit(uu unit.Unit) error { //nolint:dupl

View file

@ -15,7 +15,31 @@ import (
"github.com/bluenviron/mediamtx/internal/unit"
)
func TestH265ProcessUnit(t *testing.T) {
func TestH265RemoveAUD(t *testing.T) {
forma := &format.H265{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u := &unit.H265{
Base: unit.Base{
PTS: 30000,
},
AU: [][]byte{
{byte(mch265.NALUType_AUD_NUT) << 1, 0},
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
},
}
err = p.ProcessUnit(u)
require.NoError(t, err)
require.Equal(t, [][]byte{
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
}, u.AU)
}
func TestH265AddParams(t *testing.T) {
forma := &format.H265{}
p, err := New(1450, forma, true, nil)
@ -68,11 +92,11 @@ func TestH265ProcessUnit(t *testing.T) {
{byte(mch265.NALUType_CRA_NUT) << 1, 1},
}, u2.AU)
// test that timestamp had increased
// test that timestamp has increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}
func TestH265ProcessUnitEmpty(t *testing.T) {
func TestH265ProcessEmptyUnit(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
}
@ -95,7 +119,7 @@ func TestH265ProcessUnitEmpty(t *testing.T) {
require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets)
}
func TestH265ProcessRTPPacketUpdateParams(t *testing.T) {
func TestH265RTPExtractParams(t *testing.T) {
for _, ca := range []string{"standard", "aggregated"} {
t.Run(ca, func(t *testing.T) {
forma := &format.H265{
@ -168,7 +192,7 @@ func TestH265ProcessRTPPacketUpdateParams(t *testing.T) {
}
}
func TestH265ProcessRTPPacketOversized(t *testing.T) {
func TestH265RTPOversized(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
VPS: []byte{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12},

View file

@ -66,6 +66,6 @@ func TestMPEG4VideoProcessUnit(t *testing.T) {
0, 0, 1, 0xF1,
}, u2.Frame)
// test that timestamp had increased
// test that timestamp has increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}