use the same filtering process for every codec (#5324)

this allows to apply features that were previously implemented for
single codecs (like RTP packet resizing), to any codec, and simplifies
future development.
This commit is contained in:
Alessandro Ros 2026-01-16 14:03:13 +01:00 committed by GitHub
parent 2109ade5b4
commit 653d10fb75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 2765 additions and 3931 deletions

View file

@ -1,100 +0,0 @@
package codecprocessor
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type ac3 struct {
RTPMaxPayloadSize int
Format *format.AC3
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpac3.Encoder
decoder *rtpac3.Decoder
randomStart uint32
}
func (t *ac3) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *ac3) createEncoder() error {
t.encoder = &rtpac3.Encoder{
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *ac3) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadAC3))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *ac3) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frames, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpac3.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpac3.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadAC3(frames)
}
return nil
}

View file

@ -1,137 +0,0 @@
package codecprocessor //nolint:dupl
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1"
mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type av1 struct {
RTPMaxPayloadSize int
Format *format.AV1
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpav1.Encoder
decoder *rtpav1.Decoder
randomStart uint32
}
func (t *av1) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *av1) createEncoder() error {
t.encoder = &rtpav1.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *av1) remuxTemporalUnit(tu unit.PayloadAV1) unit.PayloadAV1 {
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(u *unit.Unit) error { //nolint:dupl
u.Payload = t.remuxTemporalUnit(u.Payload.(unit.PayloadAV1))
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadAV1))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *av1) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
tu, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpav1.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpav1.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = t.remuxTemporalUnit(tu)
}
return nil
}

View file

@ -1,32 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/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.Unit{
PTS: 30000,
Payload: unit.PayloadAV1{
{byte(mcav1.OBUTypeTemporalDelimiter) << 3},
{5},
},
}
err = p.ProcessUnit(u)
require.NoError(t, err)
require.Equal(t, unit.PayloadAV1{
{5},
}, u.Payload)
}

View file

@ -1,98 +0,0 @@
package codecprocessor //nolint:dupl
import (
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type g711 struct {
RTPMaxPayloadSize int
Format *format.G711
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtplpcm.Encoder
decoder *rtplpcm.Decoder
randomStart uint32
}
func (t *g711) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *g711) createEncoder() error {
t.encoder = &rtplpcm.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadType(),
BitDepth: 8,
ChannelCount: t.Format.ChannelCount,
}
return t.encoder.Init()
}
func (t *g711) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadG711))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *g711) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
samples, err := t.decoder.Decode(pkt)
if err != nil {
return err
}
u.Payload = unit.PayloadG711(samples)
}
return nil
}

View file

@ -1,70 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestG711ProcessUnit(t *testing.T) {
t.Run("alaw", func(t *testing.T) {
forma := &format.G711{
PayloadTyp: 8,
MULaw: false,
SampleRate: 8000,
ChannelCount: 1,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.Unit{
Payload: unit.PayloadG711{1, 2, 3, 4},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
require.Equal(t, []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
PayloadType: 8,
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
Timestamp: unit.RTPPackets[0].Timestamp,
SSRC: unit.RTPPackets[0].SSRC,
},
Payload: []byte{1, 2, 3, 4},
}}, unit.RTPPackets)
})
t.Run("mulaw", func(t *testing.T) {
forma := &format.G711{
PayloadTyp: 0,
MULaw: true,
SampleRate: 8000,
ChannelCount: 1,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.Unit{
Payload: unit.PayloadG711{1, 2, 3, 4},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
require.Equal(t, []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
PayloadType: 0,
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
Timestamp: unit.RTPPackets[0].Timestamp,
SSRC: unit.RTPPackets[0].SSRC,
},
Payload: []byte{1, 2, 3, 4},
}}, unit.RTPPackets)
})
}

View file

@ -1,47 +0,0 @@
package codecprocessor
import (
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type generic struct {
RTPMaxPayloadSize int
Format format.Format
GenerateRTPPackets bool
Parent logger.Writer
}
func (t *generic) initialize() error {
if t.GenerateRTPPackets {
return fmt.Errorf("we don't know how to generate RTP packets of format %T", t.Format)
}
return nil
}
func (t *generic) ProcessUnit(_ *unit.Unit) error {
return fmt.Errorf("using a generic unit without RTP is not supported")
}
func (t *generic) ProcessRTPPacket(
u *unit.Unit,
_ bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
return nil
}

View file

@ -1,53 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestGenericProcessRTPPacket(t *testing.T) {
forma := &format.Generic{
PayloadTyp: 96,
RTPMa: "private/90000",
}
err := forma.Init()
require.NoError(t, err)
p, err := New(1450, forma, false, nil)
require.NoError(t, err)
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
Padding: true,
},
Payload: []byte{1, 2, 3, 4},
PaddingSize: 20,
}
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
// check that padding has been removed
require.Equal(t, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
},
Payload: []byte{1, 2, 3, 4},
}, pkt)
}

View file

@ -1,311 +0,0 @@
package codecprocessor
import (
"bytes"
"errors"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph264"
mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
// H264-related parameters
var (
H264DefaultSPS = []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
}
H264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08}
)
// extract SPS and PPS without decoding RTP packets
func rtpH264ExtractParams(payload []byte) ([]byte, []byte) {
if len(payload) < 1 {
return nil, nil
}
typ := mch264.NALUType(payload[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS:
return payload, nil
case mch264.NALUTypePPS:
return nil, payload
case mch264.NALUTypeSTAPA:
payload = payload[1:]
var sps []byte
var pps []byte
for len(payload) > 0 {
if len(payload) < 2 {
break
}
size := uint16(payload[0])<<8 | uint16(payload[1])
payload = payload[2:]
if size == 0 {
break
}
if int(size) > len(payload) {
return nil, nil
}
nalu := payload[:size]
payload = payload[size:]
typ = mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS:
sps = nalu
case mch264.NALUTypePPS:
pps = nalu
}
}
return sps, pps
default:
return nil, nil
}
}
type h264 struct {
RTPMaxPayloadSize int
Format *format.H264
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtph264.Encoder
decoder *rtph264.Decoder
randomStart uint32
}
func (t *h264) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder(nil, nil)
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *h264) createEncoder(
ssrc *uint32,
initialSequenceNumber *uint16,
) error {
t.encoder = &rtph264.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
PacketizationMode: t.Format.PacketizationMode,
}
return t.encoder.Init()
}
func (t *h264) updateTrackParametersFromRTPPacket(payload []byte) {
sps, pps := rtpH264ExtractParams(payload)
if (sps != nil && !bytes.Equal(sps, t.Format.SPS)) ||
(pps != nil && !bytes.Equal(pps, t.Format.PPS)) {
if sps == nil {
sps = t.Format.SPS
}
if pps == nil {
pps = t.Format.PPS
}
t.Format.SafeSetParams(sps, pps)
}
}
func (t *h264) updateTrackParametersFromAU(au unit.PayloadH264) {
sps := t.Format.SPS
pps := t.Format.PPS
update := false
for _, nalu := range au {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS:
if !bytes.Equal(nalu, sps) {
sps = nalu
update = true
}
case mch264.NALUTypePPS:
if !bytes.Equal(nalu, pps) {
pps = nalu
update = true
}
}
}
if update {
t.Format.SafeSetParams(sps, pps)
}
}
func (t *h264) remuxAccessUnit(au unit.PayloadH264) unit.PayloadH264 {
isKeyFrame := false
n := 0
for _, nalu := range au {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
continue
case mch264.NALUTypeAccessUnitDelimiter:
continue
case mch264.NALUTypeIDR:
if !isKeyFrame {
isKeyFrame = true
// prepend parameters
if t.Format.SPS != nil && t.Format.PPS != nil {
n += 2
}
}
}
n++
}
if n == 0 {
return nil
}
filteredAU := make([][]byte, n)
i := 0
if isKeyFrame && t.Format.SPS != nil && t.Format.PPS != nil {
filteredAU[0] = t.Format.SPS
filteredAU[1] = t.Format.PPS
i = 2
}
for _, nalu := range au {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
continue
case mch264.NALUTypeAccessUnitDelimiter:
continue
}
filteredAU[i] = nalu
i++
}
return filteredAU
}
func (t *h264) ProcessUnit(u *unit.Unit) error {
t.updateTrackParametersFromAU(u.Payload.(unit.PayloadH264))
u.Payload = t.remuxAccessUnit(u.Payload.(unit.PayloadH264))
if !u.NilPayload() {
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH264))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
}
return nil
}
func (t *h264) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
t.updateTrackParametersFromRTPPacket(pkt.Payload)
if t.encoder == nil {
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
// RTP packets exceed maximum size: start re-encoding them
if len(pkt.Payload) > t.RTPMaxPayloadSize {
t.Parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones")
v1 := pkt.SSRC
v2 := pkt.SequenceNumber
err := t.createEncoder(&v1, &v2)
if err != nil {
return err
}
}
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
au, err := t.decoder.Decode(pkt)
if t.encoder != nil {
u.RTPPackets = nil
}
if err != nil {
if errors.Is(err, rtph264.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtph264.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = t.remuxAccessUnit(au)
}
// encode into RTP
if t.encoder != nil && !u.NilPayload() {
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH264))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, newPKT := range u.RTPPackets {
newPKT.Timestamp = pkt.Timestamp
}
}
return nil
}

View file

@ -1,307 +0,0 @@
package codecprocessor
import (
"bytes"
"fmt"
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type testLogger struct {
cb func(level logger.Level, format string, args ...any)
}
func (l *testLogger) Log(level logger.Level, format string, args ...any) {
l.cb(level, format, args...)
}
// Logger returns a dummy logger.
func Logger(cb func(logger.Level, string, ...any)) logger.Writer {
return &testLogger{cb: cb}
}
func TestH264RemoveAUD(t *testing.T) {
forma := &format.H264{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u := &unit.Unit{
PTS: 30000,
Payload: unit.PayloadH264{
{9, 24}, // AUD
{5, 1}, // IDR
},
}
err = p.ProcessUnit(u)
require.NoError(t, err)
require.Equal(t, unit.PayloadH264{
{5, 1}, // IDR
}, u.Payload)
}
func TestH264AddParams(t *testing.T) {
forma := &format.H264{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u1 := &unit.Unit{
PTS: 30000,
Payload: unit.PayloadH264{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
{5, 1}, // IDR
},
}
err = p.ProcessUnit(u1)
require.NoError(t, err)
require.Equal(t, unit.PayloadH264{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
{5, 1}, // IDR
}, u1.Payload)
u2 := &unit.Unit{
PTS: 30000 * 2,
Payload: unit.PayloadH264{
{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, unit.PayloadH264{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
{5, 2}, // IDR
}, u2.Payload)
// test that timestamp has increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}
func TestH264ProcessEmptyUnit(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.Unit{
Payload: unit.PayloadH264{
{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 TestH264RTPExtractParams(t *testing.T) {
for _, ca := range []string{"standard", "aggregated"} {
t.Run(ca, func(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
PacketizationMode: 1,
}
p, err := New(1450, forma, false, nil)
require.NoError(t, err)
enc, err := forma.CreateEncoder()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{byte(mch264.NALUTypeIDR)}})
require.NoError(t, err)
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, true)
require.NoError(t, err)
require.Equal(t, unit.PayloadH264{
{byte(mch264.NALUTypeIDR)},
}, u.Payload)
if ca == "standard" {
pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}) // SPS
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
pkts, err = enc.Encode([][]byte{{8, 1}}) // PPS
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
} else {
pkts, err = enc.Encode([][]byte{
{7, 4, 5, 6}, // SPS
{8, 1}, // PPS
})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
}
require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS)
require.Equal(t, []byte{8, 1}, forma.PPS)
pkts, err = enc.Encode([][]byte{{byte(mch264.NALUTypeIDR)}})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, true)
require.NoError(t, err)
require.Equal(t, unit.PayloadH264{
{0x07, 4, 5, 6},
{0x08, 1},
{byte(mch264.NALUTypeIDR)},
}, u.Payload)
})
}
}
func TestH264RTPOversized(t *testing.T) {
forma := &format.H264{
PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04},
PPS: []byte{0x01, 0x02, 0x03, 0x04},
PacketizationMode: 1,
}
logged := false
p, err := New(1460, forma, false,
Logger(func(_ logger.Level, s string, i ...any) {
require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...))
logged = true
}))
require.NoError(t, err)
var out []*rtp.Packet //nolint:prealloc
for _, pkt := range []*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
Padding: true,
},
Payload: []byte{0x01, 0x02, 0x03, 0x04},
},
{
Header: rtp.Header{
Version: 2,
Marker: false,
PayloadType: 96,
SequenceNumber: 124,
Timestamp: 45343,
SSRC: 563423,
Padding: true,
},
Payload: append([]byte{0x1c, 0b10000000}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 2000/4)...),
},
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 125,
Timestamp: 45343,
SSRC: 563423,
Padding: true,
},
Payload: []byte{0x1c, 0b01000000, 0x01, 0x02, 0x03, 0x04},
},
} {
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
out = append(out, u.RTPPackets...)
}
require.Equal(t, []*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
},
Payload: []byte{0x01, 0x02, 0x03, 0x04},
},
{
Header: rtp.Header{
Version: 2,
Marker: false,
PayloadType: 96,
SequenceNumber: 124,
Timestamp: 45343,
SSRC: 563423,
},
Payload: append(
append([]byte{0x1c, 0x80}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 364)...),
[]byte{0x01, 0x02}...,
),
},
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 125,
Timestamp: 45343,
SSRC: 563423,
},
Payload: append(
[]byte{0x1c, 0x40, 0x03, 0x04},
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 136)...,
),
},
}, out)
require.True(t, logged)
}
func FuzzRTPH264ExtractParams(f *testing.F) {
f.Fuzz(func(_ *testing.T, b []byte) {
rtpH264ExtractParams(b)
})
}

View file

@ -1,343 +0,0 @@
package codecprocessor
import (
"bytes"
"errors"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph265"
mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
// H265-related parameters
var (
H265DefaultVPS = []byte{
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20,
0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24,
}
H265DefaultSPS = []byte{
0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03,
0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d,
0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88,
0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9,
0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc,
0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a,
0x02, 0x02, 0x02, 0x01,
}
H265DefaultPPS = []byte{
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
}
)
// extract VPS, SPS and PPS without decoding RTP packets
func rtpH265ExtractParams(payload []byte) ([]byte, []byte, []byte) {
if len(payload) < 2 {
return nil, nil, nil
}
typ := mch265.NALUType((payload[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT:
return payload, nil, nil
case mch265.NALUType_SPS_NUT:
return nil, payload, nil
case mch265.NALUType_PPS_NUT:
return nil, nil, payload
case mch265.NALUType_AggregationUnit:
payload = payload[2:]
var vps []byte
var sps []byte
var pps []byte
for len(payload) > 0 {
if len(payload) < 2 {
break
}
size := uint16(payload[0])<<8 | uint16(payload[1])
payload = payload[2:]
if size == 0 {
break
}
if int(size) > len(payload) {
return nil, nil, nil
}
nalu := payload[:size]
payload = payload[size:]
typ = mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT:
vps = nalu
case mch265.NALUType_SPS_NUT:
sps = nalu
case mch265.NALUType_PPS_NUT:
pps = nalu
}
}
return vps, sps, pps
default:
return nil, nil, nil
}
}
type h265 struct {
RTPMaxPayloadSize int
Format *format.H265
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtph265.Encoder
decoder *rtph265.Decoder
randomStart uint32
}
func (t *h265) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder(nil, nil)
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *h265) createEncoder(
ssrc *uint32,
initialSequenceNumber *uint16,
) error {
t.encoder = &rtph265.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
MaxDONDiff: t.Format.MaxDONDiff,
}
return t.encoder.Init()
}
func (t *h265) updateTrackParametersFromRTPPacket(payload []byte) {
vps, sps, pps := rtpH265ExtractParams(payload)
if (vps != nil && !bytes.Equal(vps, t.Format.VPS)) ||
(sps != nil && !bytes.Equal(sps, t.Format.SPS)) ||
(pps != nil && !bytes.Equal(pps, t.Format.PPS)) {
if vps == nil {
vps = t.Format.VPS
}
if sps == nil {
sps = t.Format.SPS
}
if pps == nil {
pps = t.Format.PPS
}
t.Format.SafeSetParams(vps, sps, pps)
}
}
func (t *h265) updateTrackParametersFromAU(au unit.PayloadH265) {
vps := t.Format.VPS
sps := t.Format.SPS
pps := t.Format.PPS
update := false
for _, nalu := range au {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT:
if !bytes.Equal(nalu, t.Format.VPS) {
vps = nalu
update = true
}
case mch265.NALUType_SPS_NUT:
if !bytes.Equal(nalu, t.Format.SPS) {
sps = nalu
update = true
}
case mch265.NALUType_PPS_NUT:
if !bytes.Equal(nalu, t.Format.PPS) {
pps = nalu
update = true
}
}
}
if update {
t.Format.SafeSetParams(vps, sps, pps)
}
}
func (t *h265) remuxAccessUnit(au unit.PayloadH265) unit.PayloadH265 {
isKeyFrame := false
n := 0
for _, nalu := range au {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
continue
case mch265.NALUType_AUD_NUT:
continue
case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT:
if !isKeyFrame {
isKeyFrame = true
// prepend parameters
if t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil {
n += 3
}
}
}
n++
}
if n == 0 {
return nil
}
filteredAU := make([][]byte, n)
i := 0
if isKeyFrame && t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil {
filteredAU[0] = t.Format.VPS
filteredAU[1] = t.Format.SPS
filteredAU[2] = t.Format.PPS
i = 3
}
for _, nalu := range au {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
continue
case mch265.NALUType_AUD_NUT:
continue
}
filteredAU[i] = nalu
i++
}
return filteredAU
}
func (t *h265) ProcessUnit(u *unit.Unit) error { //nolint:dupl
t.updateTrackParametersFromAU(u.Payload.(unit.PayloadH265))
u.Payload = t.remuxAccessUnit(u.Payload.(unit.PayloadH265))
if !u.NilPayload() {
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH265))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
}
return nil
}
func (t *h265) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
t.updateTrackParametersFromRTPPacket(pkt.Payload)
if t.encoder == nil {
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
// RTP packets exceed maximum size: start re-encoding them
if len(pkt.Payload) > t.RTPMaxPayloadSize {
t.Parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones")
v1 := pkt.SSRC
v2 := pkt.SequenceNumber
err := t.createEncoder(&v1, &v2)
if err != nil {
return err
}
}
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
au, err := t.decoder.Decode(pkt)
if t.encoder != nil {
u.RTPPackets = nil
}
if err != nil {
if errors.Is(err, rtph265.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtph265.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = t.remuxAccessUnit(au)
}
// encode into RTP
if t.encoder != nil && !u.NilPayload() {
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH265))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, newPKT := range u.RTPPackets {
newPKT.Timestamp = pkt.Timestamp
}
}
return nil
}

View file

@ -1,295 +0,0 @@
package codecprocessor
import (
"bytes"
"fmt"
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
func TestH265RemoveAUD(t *testing.T) {
forma := &format.H265{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u := &unit.Unit{
PTS: 30000,
Payload: unit.PayloadH265{
{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, unit.PayloadH265{
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
}, u.Payload)
}
func TestH265AddParams(t *testing.T) {
forma := &format.H265{}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
u1 := &unit.Unit{
PTS: 30000,
Payload: unit.PayloadH265{
{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, unit.PayloadH265{
{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.Payload)
u2 := &unit.Unit{
PTS: 30000 * 2,
Payload: unit.PayloadH265{
{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, unit.PayloadH265{
{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.Payload)
// test that timestamp has increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}
func TestH265ProcessEmptyUnit(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.Unit{
Payload: unit.PayloadH265{
{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 TestH265RTPExtractParams(t *testing.T) {
for _, ca := range []string{"standard", "aggregated"} {
t.Run(ca, func(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
}
p, err := New(1450, forma, false, nil)
require.NoError(t, err)
enc, err := forma.CreateEncoder()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{byte(mch265.NALUType_CRA_NUT) << 1, 0}})
require.NoError(t, err)
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, true)
require.NoError(t, err)
require.Equal(t, unit.PayloadH265{
{byte(mch265.NALUType_CRA_NUT) << 1, 0},
}, u.Payload)
if ca == "standard" {
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
} else {
pkts, err = enc.Encode([][]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},
})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
}
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)
pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_CRA_NUT) << 1, 0}})
require.NoError(t, err)
u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}}
err = p.ProcessRTPPacket(u, true)
require.NoError(t, err)
require.Equal(t, unit.PayloadH265{
{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},
}, u.Payload)
})
}
}
func TestH265RTPOversized(t *testing.T) {
forma := &format.H265{
PayloadTyp: 96,
VPS: []byte{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12},
SPS: []byte{byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15},
PPS: []byte{byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18},
}
logged := false
p, err := New(1460, forma, false,
Logger(func(_ logger.Level, s string, i ...any) {
require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...))
logged = true
}))
require.NoError(t, err)
var out []*rtp.Packet //nolint:prealloc
for _, pkt := range []*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
Padding: true,
},
Payload: []byte{0x01, 0x02, 0x03, 0x04},
},
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 124,
Timestamp: 45343,
SSRC: 563423,
Padding: true,
},
Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 2000/4),
},
} {
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
out = append(out, u.RTPPackets...)
}
require.Equal(t, []*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 123,
Timestamp: 45343,
SSRC: 563423,
},
Payload: []byte{0x01, 0x02, 0x03, 0x04},
},
{
Header: rtp.Header{
Version: 2,
Marker: false,
PayloadType: 96,
SequenceNumber: 124,
Timestamp: 45343,
SSRC: 563423,
},
Payload: append(
append([]byte{0x63, 0x02, 0x80, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 363)...),
[]byte{0x01, 0x02, 0x03}...,
),
},
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 125,
Timestamp: 45343,
SSRC: 563423,
},
Payload: append(
[]byte{0x63, 0x02, 0x40, 0x04},
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 135)...,
),
},
}, out)
require.True(t, logged)
}
func FuzzRTPH265ExtractParams(f *testing.F) {
f.Fuzz(func(_ *testing.T, b []byte) {
rtpH265ExtractParams(b)
})
}

View file

@ -1,96 +0,0 @@
package codecprocessor
import (
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type klv struct {
RTPMaxPayloadSize int
Format *format.KLV
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpklv.Encoder
decoder *rtpklv.Decoder
randomStart uint32
}
func (t *klv) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *klv) createEncoder() error {
t.encoder = &rtpklv.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *klv) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadKLV))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *klv) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
un, err := t.decoder.Decode(pkt)
if err != nil {
return err
}
u.Payload = unit.PayloadKLV(un)
}
return nil
}

View file

@ -1,80 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestKlvCreateEncoder(t *testing.T) {
forma := &format.KLV{
PayloadTyp: 96,
}
p, err := New(1472, forma, false, nil)
require.NoError(t, err)
klvProc := p.(*klv)
err = klvProc.createEncoder()
require.NoError(t, err)
}
func TestKlvProcessUnit(t *testing.T) {
forma := &format.KLV{
PayloadTyp: 96,
}
p, err := New(1472, forma, true, nil)
require.NoError(t, err)
// create test Unit
when := int64(5000000000) // 5 seconds in nanoseconds
u := unit.Unit{
RTPPackets: nil,
PTS: when,
Payload: unit.PayloadKLV{1, 2, 3, 4},
}
uu := &u
// process the unit
err = p.ProcessUnit(uu)
require.NoError(t, err)
}
func TestKlvProcessRTPPacket(t *testing.T) {
forma := &format.KLV{
PayloadTyp: 96,
}
p, err := New(1472, forma, false, nil)
require.NoError(t, err)
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 3446,
Timestamp: 175349,
SSRC: 563423,
Padding: true,
},
Payload: []byte{1, 2, 3, 4},
PaddingSize: 20,
}
u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}}
err = p.ProcessRTPPacket(u, false)
require.NoError(t, err)
require.Equal(t, &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 3446,
Timestamp: 175349,
SSRC: 563423,
},
Payload: []byte{1, 2, 3, 4},
}, pkt)
}

View file

@ -1,98 +0,0 @@
package codecprocessor //nolint:dupl
import (
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type lpcm struct {
RTPMaxPayloadSize int
Format *format.LPCM
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtplpcm.Encoder
decoder *rtplpcm.Decoder
randomStart uint32
}
func (t *lpcm) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *lpcm) createEncoder() error {
t.encoder = &rtplpcm.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
BitDepth: t.Format.BitDepth,
ChannelCount: t.Format.ChannelCount,
}
return t.encoder.Init()
}
func (t *lpcm) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadLPCM))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *lpcm) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
samples, err := t.decoder.Decode(pkt)
if err != nil {
return err
}
u.Payload = unit.PayloadLPCM(samples)
}
return nil
}

View file

@ -1,39 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestLPCMProcessUnit(t *testing.T) {
forma := &format.LPCM{
PayloadTyp: 96,
BitDepth: 16,
SampleRate: 44100,
ChannelCount: 2,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.Unit{
Payload: unit.PayloadLPCM{1, 2, 3, 4},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
require.Equal(t, []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
PayloadType: 96,
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
Timestamp: unit.RTPPackets[0].Timestamp,
SSRC: unit.RTPPackets[0].SSRC,
},
Payload: []byte{1, 2, 3, 4},
}}, unit.RTPPackets)
}

View file

@ -1,101 +0,0 @@
package codecprocessor //nolint:dupl
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type mjpeg struct {
RTPMaxPayloadSize int
Format *format.MJPEG
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpmjpeg.Encoder
decoder *rtpmjpeg.Decoder
randomStart uint32
}
func (t *mjpeg) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *mjpeg) createEncoder() error {
t.encoder = &rtpmjpeg.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
}
return t.encoder.Init()
}
func (t *mjpeg) ProcessUnit(u *unit.Unit) error { //nolint:dupl
// encode into RTP
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMJPEG))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *mjpeg) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frame, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpmjpeg.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpmjpeg.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadMJPEG(frame)
}
return nil
}

View file

@ -1,100 +0,0 @@
package codecprocessor //nolint:dupl
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type mpeg1Audio struct {
RTPMaxPayloadSize int
Format *format.MPEG1Audio
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpmpeg1audio.Encoder
decoder *rtpmpeg1audio.Decoder
randomStart uint32
}
func (t *mpeg1Audio) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *mpeg1Audio) createEncoder() error {
t.encoder = &rtpmpeg1audio.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
}
return t.encoder.Init()
}
func (t *mpeg1Audio) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG1Audio))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *mpeg1Audio) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frames, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpmpeg1audio.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadMPEG1Audio(frames)
}
return nil
}

View file

@ -1,110 +0,0 @@
package codecprocessor //nolint:dupl
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
// MPEG-1 video related parameters
var (
MPEG1VideoDefaultConfig = []byte{
0x00, 0x00, 0x01, 0xb3, 0x78, 0x04, 0x38, 0x35,
0xff, 0xff, 0xe0, 0x18, 0x00, 0x00, 0x01, 0xb5,
0x14, 0x4a, 0x00, 0x01, 0x00, 0x00,
}
)
type mpeg1Video struct {
RTPMaxPayloadSize int
Format *format.MPEG1Video
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpmpeg1video.Encoder
decoder *rtpmpeg1video.Decoder
randomStart uint32
}
func (t *mpeg1Video) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *mpeg1Video) createEncoder() error {
t.encoder = &rtpmpeg1video.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
}
return t.encoder.Init()
}
func (t *mpeg1Video) ProcessUnit(u *unit.Unit) error { //nolint:dupl
// encode into RTP
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG1Video))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *mpeg1Video) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frame, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg1video.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpmpeg1video.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadMPEG1Video(frame)
}
return nil
}

View file

@ -1,103 +0,0 @@
package codecprocessor
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type mpeg4Audio struct {
RTPMaxPayloadSize int
Format *format.MPEG4Audio
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpmpeg4audio.Encoder
decoder *rtpmpeg4audio.Decoder
randomStart uint32
}
func (t *mpeg4Audio) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *mpeg4Audio) createEncoder() error {
t.encoder = &rtpmpeg4audio.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
SizeLength: t.Format.SizeLength,
IndexLength: t.Format.IndexLength,
IndexDeltaLength: t.Format.IndexDeltaLength,
}
return t.encoder.Init()
}
func (t *mpeg4Audio) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4Audio))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *mpeg4Audio) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
aus, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadMPEG4Audio(aus)
}
return nil
}

View file

@ -1,101 +0,0 @@
package codecprocessor
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type mpeg4AudioLATM struct {
RTPMaxPayloadSize int
Format *format.MPEG4AudioLATM
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpfragmented.Encoder
decoder *rtpfragmented.Decoder
randomStart uint32
}
func (t *mpeg4AudioLATM) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *mpeg4AudioLATM) createEncoder() error {
t.encoder = &rtpfragmented.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *mpeg4AudioLATM) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4AudioLATM))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *mpeg4AudioLATM) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
el, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadMPEG4AudioLATM(el)
}
return nil
}

View file

@ -1,159 +0,0 @@
package codecprocessor //nolint:dupl
import (
"bytes"
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
// MPEG-4 video related parameters
var (
MPEG4VideoDefaultConfig = []byte{
0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01,
0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x88, 0x00,
0xf5, 0x3c, 0x04, 0x87, 0x14, 0x63, 0x00, 0x00,
0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38,
0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30,
}
)
type mpeg4Video struct {
RTPMaxPayloadSize int
Format *format.MPEG4Video
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpfragmented.Encoder
decoder *rtpfragmented.Decoder
randomStart uint32
}
func (t *mpeg4Video) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *mpeg4Video) createEncoder() error {
t.encoder = &rtpfragmented.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *mpeg4Video) updateTrackParameters(frame unit.PayloadMPEG4Video) {
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 {
return
}
conf := frame[:end+4]
if !bytes.Equal(conf, t.Format.Config) {
t.Format.SafeSetParams(conf)
}
}
}
func (t *mpeg4Video) remuxFrame(frame unit.PayloadMPEG4Video) unit.PayloadMPEG4Video {
// 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 {
frame = frame[end+4:]
}
}
// 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)
copy(f[n:], frame)
frame = f
}
if len(frame) == 0 {
return nil
}
return frame
}
func (t *mpeg4Video) ProcessUnit(u *unit.Unit) error { //nolint:dupl
t.updateTrackParameters(u.Payload.(unit.PayloadMPEG4Video))
u.Payload = t.remuxFrame(u.Payload.(unit.PayloadMPEG4Video))
if !u.NilPayload() {
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4Video))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
}
return nil
}
func (t *mpeg4Video) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
t.updateTrackParameters(pkt.Payload)
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frame, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = t.remuxFrame(frame)
}
return nil
}

View file

@ -1,67 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/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.Unit{
PTS: 30000,
Payload: unit.PayloadMPEG4Video{
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, unit.PayloadMPEG4Video{
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
0, 0, 1, 0xFF,
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
0, 0, 1, 0xF0,
}, u1.Payload)
u2 := &unit.Unit{
PTS: 30000 * 2,
Payload: unit.PayloadMPEG4Video{
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, unit.PayloadMPEG4Video{
0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode),
0, 0, 1, 0xFF,
0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode),
0, 0, 1, 0xF1,
}, u2.Payload)
// test that timestamp has increased
require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp)
}

View file

@ -1,105 +0,0 @@
package codecprocessor
import (
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio"
mcopus "github.com/bluenviron/mediacommon/v2/pkg/codecs/opus"
"github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type opus struct {
RTPMaxPayloadSize int
Format *format.Opus
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpsimpleaudio.Encoder
decoder *rtpsimpleaudio.Decoder
randomStart uint32
}
func (t *opus) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *opus) createEncoder() error {
t.encoder = &rtpsimpleaudio.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *opus) ProcessUnit(u *unit.Unit) error { //nolint:dupl
var rtpPackets []*rtp.Packet //nolint:prealloc
pts := u.PTS
for _, packet := range u.Payload.(unit.PayloadOpus) {
pkt, err := t.encoder.Encode(packet)
if err != nil {
return err
}
pkt.Timestamp += t.randomStart + uint32(pts)
rtpPackets = append(rtpPackets, pkt)
pts += mcopus.PacketDuration2(packet)
}
u.RTPPackets = rtpPackets
return nil
}
func (t *opus) ProcessRTPPacket(
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
packet, err := t.decoder.Decode(pkt)
if err != nil {
return err
}
u.Payload = unit.PayloadOpus{packet}
}
return nil
}

View file

@ -1,139 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func TestOpusProcessUnit(t *testing.T) {
forma := &format.Opus{
PayloadTyp: 96,
ChannelCount: 2,
}
p, err := New(1450, forma, true, nil)
require.NoError(t, err)
unit := &unit.Unit{
Payload: unit.PayloadOpus{
{
0xfc, 0x1e, 0x61, 0x96, 0xfc, 0xf7, 0x9b, 0x23,
0x5b, 0xc9, 0x56, 0xad, 0x05, 0x12, 0x2f, 0x6c,
0xc0, 0x0c, 0x2c, 0x17, 0x7b, 0x1a, 0xde, 0x1b,
0x37, 0x89, 0xc5, 0xbb, 0x34, 0xbb, 0x1c, 0x74,
0x7c, 0x18, 0x0a, 0xde, 0xa1, 0x2b, 0x86, 0x1d,
0x60, 0xa2, 0xb6, 0xce, 0xe7, 0x0e, 0x17, 0x1b,
0xc7, 0xd4, 0xd1, 0x2a, 0x68, 0x1f, 0x05, 0x2b,
0x22, 0x80, 0x68, 0x12, 0x0c, 0x45, 0xbc, 0x3a,
0xd2, 0x1b, 0xf2, 0x8a, 0x77, 0x5f, 0x2b, 0x34,
0x97, 0x34, 0x09, 0x6d, 0x05, 0x5f, 0x48, 0x0c,
0x45, 0xb5, 0xae, 0x2a, 0x90, 0x21, 0xda, 0xfb,
0x5b, 0x10,
},
{
0xfc, 0x1d, 0x61, 0x96, 0xfa, 0x7a, 0x90, 0x59,
0xb7, 0x10, 0xd7, 0x03, 0x84, 0x27, 0x3f, 0x52,
0x9f, 0xd7, 0x38, 0x9f, 0xbc, 0xff, 0x7d, 0x62,
0xe8, 0x60, 0x64, 0x54, 0x9d, 0x1a, 0xd1, 0x7c,
0x1a, 0x0a, 0x76, 0xd2, 0x30, 0x9b, 0xbe, 0xc7,
0x5d, 0x37, 0x42, 0xe7, 0xdd, 0xfc, 0xc7, 0x03,
0xab, 0x90, 0xd9, 0x9b, 0xad, 0xf4, 0x88, 0xd3,
0x81, 0xbf, 0xd0, 0x68, 0x10, 0x0c, 0x46, 0xa4,
0xe8, 0x83, 0xd9, 0x6b, 0x7a, 0x25, 0xed, 0x81,
0xf6, 0x92, 0x14, 0x70, 0x6c, 0x48, 0x0c, 0x45,
0xb4, 0xf2, 0x61, 0x9b, 0xd7, 0x62, 0x58, 0x87,
},
{
0xfc, 0x1e, 0x61, 0x96, 0xfa, 0x7f, 0x61, 0x51,
0x2f, 0x10, 0x81, 0x22, 0x01, 0x81, 0x46, 0x5e,
0xbd, 0xb0, 0x87, 0xcb, 0x0a, 0xa6, 0xd6, 0xd3,
0xec, 0x7c, 0x9e, 0xf5, 0x07, 0x5a, 0x07, 0x1b,
0x7c, 0x19, 0x0a, 0xde, 0xa1, 0x38, 0xe4, 0x51,
0x7f, 0x54, 0x6f, 0x91, 0x9f, 0xda, 0x2b, 0x40,
0x80, 0x36, 0xeb, 0xe3, 0xc2, 0x58, 0x12, 0x55,
0x80, 0x65, 0x14, 0x68, 0x12, 0x0c, 0x46, 0xa4,
0xdc, 0x6e, 0x62, 0x79, 0xf9, 0x09, 0x28, 0x11,
0xab, 0xab, 0xd3, 0x84, 0x7d, 0x93, 0x34, 0x48,
0x0c, 0x46, 0x9a, 0x0c, 0xb0, 0xfe, 0x7a, 0x5b,
0x22, 0x85, 0x4a, 0x73, 0x0d, 0xd0,
},
},
}
err = p.ProcessUnit(unit)
require.NoError(t, err)
require.Equal(t, []*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
PayloadType: 96,
SequenceNumber: unit.RTPPackets[0].SequenceNumber,
Timestamp: unit.RTPPackets[0].Timestamp,
SSRC: unit.RTPPackets[0].SSRC,
},
Payload: []byte{
0xfc, 0x1e, 0x61, 0x96, 0xfc, 0xf7, 0x9b, 0x23,
0x5b, 0xc9, 0x56, 0xad, 0x05, 0x12, 0x2f, 0x6c,
0xc0, 0x0c, 0x2c, 0x17, 0x7b, 0x1a, 0xde, 0x1b,
0x37, 0x89, 0xc5, 0xbb, 0x34, 0xbb, 0x1c, 0x74,
0x7c, 0x18, 0x0a, 0xde, 0xa1, 0x2b, 0x86, 0x1d,
0x60, 0xa2, 0xb6, 0xce, 0xe7, 0x0e, 0x17, 0x1b,
0xc7, 0xd4, 0xd1, 0x2a, 0x68, 0x1f, 0x05, 0x2b,
0x22, 0x80, 0x68, 0x12, 0x0c, 0x45, 0xbc, 0x3a,
0xd2, 0x1b, 0xf2, 0x8a, 0x77, 0x5f, 0x2b, 0x34,
0x97, 0x34, 0x09, 0x6d, 0x05, 0x5f, 0x48, 0x0c,
0x45, 0xb5, 0xae, 0x2a, 0x90, 0x21, 0xda, 0xfb,
0x5b, 0x10,
},
},
{
Header: rtp.Header{
Version: 2,
PayloadType: 96,
SequenceNumber: unit.RTPPackets[0].SequenceNumber + 1,
Timestamp: unit.RTPPackets[0].Timestamp + 960,
SSRC: unit.RTPPackets[0].SSRC,
},
Payload: []byte{
0xfc, 0x1d, 0x61, 0x96, 0xfa, 0x7a, 0x90, 0x59,
0xb7, 0x10, 0xd7, 0x03, 0x84, 0x27, 0x3f, 0x52,
0x9f, 0xd7, 0x38, 0x9f, 0xbc, 0xff, 0x7d, 0x62,
0xe8, 0x60, 0x64, 0x54, 0x9d, 0x1a, 0xd1, 0x7c,
0x1a, 0x0a, 0x76, 0xd2, 0x30, 0x9b, 0xbe, 0xc7,
0x5d, 0x37, 0x42, 0xe7, 0xdd, 0xfc, 0xc7, 0x03,
0xab, 0x90, 0xd9, 0x9b, 0xad, 0xf4, 0x88, 0xd3,
0x81, 0xbf, 0xd0, 0x68, 0x10, 0x0c, 0x46, 0xa4,
0xe8, 0x83, 0xd9, 0x6b, 0x7a, 0x25, 0xed, 0x81,
0xf6, 0x92, 0x14, 0x70, 0x6c, 0x48, 0x0c, 0x45,
0xb4, 0xf2, 0x61, 0x9b, 0xd7, 0x62, 0x58, 0x87,
},
},
{
Header: rtp.Header{
Version: 2,
PayloadType: 96,
SequenceNumber: unit.RTPPackets[0].SequenceNumber + 2,
Timestamp: unit.RTPPackets[0].Timestamp + 960*2,
SSRC: unit.RTPPackets[0].SSRC,
},
Payload: []byte{
0xfc, 0x1e, 0x61, 0x96, 0xfa, 0x7f, 0x61, 0x51,
0x2f, 0x10, 0x81, 0x22, 0x01, 0x81, 0x46, 0x5e,
0xbd, 0xb0, 0x87, 0xcb, 0x0a, 0xa6, 0xd6, 0xd3,
0xec, 0x7c, 0x9e, 0xf5, 0x07, 0x5a, 0x07, 0x1b,
0x7c, 0x19, 0x0a, 0xde, 0xa1, 0x38, 0xe4, 0x51,
0x7f, 0x54, 0x6f, 0x91, 0x9f, 0xda, 0x2b, 0x40,
0x80, 0x36, 0xeb, 0xe3, 0xc2, 0x58, 0x12, 0x55,
0x80, 0x65, 0x14, 0x68, 0x12, 0x0c, 0x46, 0xa4,
0xdc, 0x6e, 0x62, 0x79, 0xf9, 0x09, 0x28, 0x11,
0xab, 0xab, 0xd3, 0x84, 0x7d, 0x93, 0x34, 0x48,
0x0c, 0x46, 0x9a, 0x0c, 0xb0, 0xfe, 0x7a, 0x5b,
0x22, 0x85, 0x4a, 0x73, 0x0d, 0xd0,
},
},
}, unit.RTPPackets)
}

View file

@ -1,185 +0,0 @@
// Package codecprocessor contains codec-specific processing.
package codecprocessor
import (
"crypto/rand"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
func randUint32() (uint32, error) {
var b [4]byte
_, err := rand.Read(b[:])
if err != nil {
return 0, err
}
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
}
// Processor is the codec-specific part of the processing that happens inside stream.Stream.
type Processor interface {
// process a Unit.
ProcessUnit(*unit.Unit) error
// process a RTP packet.
ProcessRTPPacket(
u *unit.Unit,
hasNonRTSPReaders bool,
) error
initialize() error
}
// New allocates a Processor.
func New(
rtpMaxPayloadSize int,
forma format.Format,
generateRTPPackets bool,
parent logger.Writer,
) (Processor, error) {
var proc Processor
switch forma := forma.(type) {
case *format.AV1:
proc = &av1{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.VP9:
proc = &vp9{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.VP8:
proc = &vp8{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.H265:
proc = &h265{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.H264:
proc = &h264{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.MPEG4Video:
proc = &mpeg4Video{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.MPEG1Video:
proc = &mpeg1Video{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.MJPEG:
proc = &mjpeg{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.Opus:
proc = &opus{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.KLV:
proc = &klv{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.MPEG4Audio:
proc = &mpeg4Audio{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.MPEG4AudioLATM:
proc = &mpeg4AudioLATM{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.MPEG1Audio:
proc = &mpeg1Audio{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.AC3:
proc = &ac3{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.G711:
proc = &g711{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
case *format.LPCM:
proc = &lpcm{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
default:
proc = &generic{
RTPMaxPayloadSize: rtpMaxPayloadSize,
Format: forma,
GenerateRTPPackets: generateRTPPackets,
Parent: parent,
}
}
err := proc.initialize()
return proc, err
}

View file

@ -1,105 +0,0 @@
package codecprocessor
import (
"testing"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
for _, ca := range []struct {
name string
in format.Format
out Processor
}{
{
"av1",
&format.AV1{},
&av1{},
},
{
"vp9",
&format.VP9{},
&vp9{},
},
{
"vp8",
&format.VP8{},
&vp8{},
},
{
"h265",
&format.H265{},
&h265{},
},
{
"h264",
&format.H264{},
&h264{},
},
{
"mpeg4 video",
&format.MPEG4Video{},
&mpeg4Video{},
},
{
"mpeg1 video",
&format.MPEG1Video{},
&mpeg1Video{},
},
{
"mpeg1 mjpeg",
&format.MPEG1Audio{},
&mpeg1Audio{},
},
{
"opus",
&format.Opus{},
&opus{},
},
{
"mpeg4 audio",
&format.MPEG4Audio{},
&mpeg4Audio{},
},
{
"mpeg1 audio",
&format.MPEG1Audio{},
&mpeg1Audio{},
},
{
"ac3",
&format.AC3{},
&ac3{},
},
{
"g711",
&format.G711{},
&g711{},
},
{
"lpcm",
&format.LPCM{},
&lpcm{},
},
{
"klv",
&format.KLV{
PayloadTyp: 96,
},
&klv{},
},
{
"generic",
&format.Generic{},
&generic{},
},
} {
t.Run(ca.name, func(t *testing.T) {
p, err := New(1450, ca.in, false, nil)
require.NoError(t, err)
require.IsType(t, ca.out, p)
})
}
}

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("800")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("8\x00\x00")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("80")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("a00")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("a0\x00\x00")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("0")

View file

@ -1,2 +0,0 @@
go test fuzz v1
[]byte("a000")

View file

@ -1,101 +0,0 @@
package codecprocessor //nolint:dupl
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type vp8 struct {
RTPMaxPayloadSize int
Format *format.VP8
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpvp8.Encoder
decoder *rtpvp8.Decoder
randomStart uint32
}
func (t *vp8) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *vp8) createEncoder() error {
t.encoder = &rtpvp8.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *vp8) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadVP8))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *vp8) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frame, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpvp8.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpvp8.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadVP8(frame)
}
return nil
}

View file

@ -1,101 +0,0 @@
package codecprocessor //nolint:dupl
import (
"errors"
"fmt"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
type vp9 struct {
RTPMaxPayloadSize int
Format *format.VP9
GenerateRTPPackets bool
Parent logger.Writer
encoder *rtpvp9.Encoder
decoder *rtpvp9.Decoder
randomStart uint32
}
func (t *vp9) initialize() error {
if t.GenerateRTPPackets {
err := t.createEncoder()
if err != nil {
return err
}
t.randomStart, err = randUint32()
if err != nil {
return err
}
}
return nil
}
func (t *vp9) createEncoder() error {
t.encoder = &rtpvp9.Encoder{
PayloadMaxSize: t.RTPMaxPayloadSize,
PayloadType: t.Format.PayloadTyp,
}
return t.encoder.Init()
}
func (t *vp9) ProcessUnit(u *unit.Unit) error { //nolint:dupl
pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadVP9))
if err != nil {
return err
}
u.RTPPackets = pkts
for _, pkt := range u.RTPPackets {
pkt.Timestamp += t.randomStart + uint32(u.PTS)
}
return nil
}
func (t *vp9) ProcessRTPPacket( //nolint:dupl
u *unit.Unit,
hasNonRTSPReaders bool,
) error {
pkt := u.RTPPackets[0]
// remove padding
pkt.Padding = false
pkt.PaddingSize = 0
if len(pkt.Payload) > t.RTPMaxPayloadSize {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), t.RTPMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.Format.CreateDecoder()
if err != nil {
return err
}
}
frame, err := t.decoder.Decode(pkt)
if err != nil {
if errors.Is(err, rtpvp9.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpvp9.ErrMorePacketsNeeded) {
return nil
}
return err
}
u.Payload = unit.PayloadVP9(frame)
}
return nil
}

View file

@ -379,7 +379,7 @@ func (pa *path) doReloadConf(newConf *conf.Path) {
}
func (pa *path) doSourceStaticSetReady(req defs.PathSourceStaticSetReadyReq) {
err := pa.setReady(req.Desc, req.GenerateRTPPackets, req.FillNTP)
err := pa.setReady(req.Desc, req.UseRTPPackets, req.ReplaceNTP)
if err != nil {
req.Res <- defs.PathSourceStaticSetReadyRes{Err: err}
return
@ -476,7 +476,7 @@ func (pa *path) doAddPublisher(req defs.PathAddPublisherReq) {
pa.source = req.Author
pa.publisherQuery = req.AccessRequest.Query
err := pa.setReady(req.Desc, req.GenerateRTPPackets, req.FillNTP)
err := pa.setReady(req.Desc, req.UseRTPPackets, req.ReplaceNTP)
if err != nil {
pa.source = nil
req.Res <- defs.PathAddPublisherRes{Err: err}
@ -688,14 +688,14 @@ func (pa *path) onDemandPublisherStop(reason string) {
pa.onDemandPublisherState = pathOnDemandStateInitial
}
func (pa *path) setReady(desc *description.Session, generateRTPPackets bool, fillNTP bool) error {
func (pa *path) setReady(desc *description.Session, useRTPPackets bool, replaceNTP bool) error {
pa.stream = &stream.Stream{
WriteQueueSize: pa.writeQueueSize,
RTPMaxPayloadSize: pa.rtpMaxPayloadSize,
Desc: desc,
GenerateRTPPackets: generateRTPPackets,
FillNTP: fillNTP,
Parent: pa.source,
Desc: desc,
UseRTPPackets: useRTPPackets,
WriteQueueSize: pa.writeQueueSize,
RTPMaxPayloadSize: pa.rtpMaxPayloadSize,
ReplaceNTP: replaceNTP,
Parent: pa.source,
}
err := pa.stream.Initialize()
if err != nil {

View file

@ -64,13 +64,13 @@ type PathAddPublisherRes struct {
// PathAddPublisherReq contains arguments of AddPublisher().
type PathAddPublisherReq struct {
Author Publisher
Desc *description.Session
GenerateRTPPackets bool
FillNTP bool
ConfToCompare *conf.Path
AccessRequest PathAccessRequest
Res chan PathAddPublisherRes
Author Publisher
Desc *description.Session
UseRTPPackets bool
ReplaceNTP bool
ConfToCompare *conf.Path
AccessRequest PathAccessRequest
Res chan PathAddPublisherRes
}
// PathRemovePublisherReq contains arguments of RemovePublisher().
@ -107,10 +107,10 @@ type PathSourceStaticSetReadyRes struct {
// PathSourceStaticSetReadyReq contains arguments of SetReady().
type PathSourceStaticSetReadyReq struct {
Desc *description.Session
GenerateRTPPackets bool
FillNTP bool
Res chan PathSourceStaticSetReadyRes
Desc *description.Session
UseRTPPackets bool
ReplaceNTP bool
Res chan PathSourceStaticSetReadyRes
}
// PathSourceStaticSetNotReadyReq contains arguments of SetNotReady().

View file

@ -108,23 +108,31 @@ func TestToStream(t *testing.T) {
}}, medias)
strm = &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err2 = strm.Initialize()
require.NoError(t, err2)
n := 0
r.OnData(
medias[0],
medias[0].Formats[0],
func(u *unit.Unit) error {
if !u.NilPayload() {
switch n {
case 0:
require.True(t, u.NilPayload())
case 1:
require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP)
close(done)
default:
t.Error("should not happen")
}
n++
return nil
})

View file

@ -12,7 +12,6 @@ import (
"github.com/bluenviron/gortmplib/pkg/codecs"
"github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/codecprocessor"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/test"
@ -20,6 +19,37 @@ import (
"github.com/stretchr/testify/require"
)
var (
h265DefaultVPS = []byte{
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20,
0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24,
}
h265DefaultSPS = []byte{
0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03,
0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d,
0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88,
0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9,
0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc,
0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a,
0x02, 0x02, 0x02, 0x01,
}
h265DefaultPPS = []byte{
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
}
h264DefaultSPS = []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
}
h264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08}
)
func TestFromStream(t *testing.T) {
h265VPS := []byte{
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
@ -449,13 +479,13 @@ func TestFromStream(t *testing.T) {
},
expectedTracks: []*gortmplib.Track{
{Codec: &codecs.H265{
VPS: codecprocessor.H265DefaultVPS,
SPS: codecprocessor.H265DefaultSPS,
PPS: codecprocessor.H265DefaultPPS,
VPS: h265DefaultVPS,
SPS: h265DefaultSPS,
PPS: h265DefaultPPS,
}},
{Codec: &codecs.H264{
SPS: codecprocessor.H264DefaultSPS,
PPS: codecprocessor.H264DefaultPPS,
SPS: h264DefaultSPS,
PPS: h264DefaultPPS,
}},
{Codec: &codecs.VP9{}},
{Codec: &codecs.AV1{}},
@ -494,8 +524,8 @@ func TestFromStream(t *testing.T) {
strm.WriteUnit(medias[1], medias[1].Formats[0], &unit.Unit{
Payload: unit.PayloadH264{
codecprocessor.H264DefaultSPS,
codecprocessor.H264DefaultPPS,
h264DefaultSPS,
h264DefaultPPS,
{5, 2}, // IDR
},
})
@ -532,11 +562,11 @@ func TestFromStream(t *testing.T) {
medias := tc.medias
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)

View file

@ -10,6 +10,7 @@ import (
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
)
@ -82,7 +83,11 @@ func ToStream(
return
}
(*strm).WriteRTPPacket(cmedi, cforma, pkt, ntp, pts)
(*strm).WriteUnit(cmedi, cforma, &unit.Unit{
PTS: pts,
NTP: ntp,
RTPPackets: []*rtp.Packet{pkt},
})
})
}
}

View file

@ -11,6 +11,7 @@ import (
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
@ -85,8 +86,6 @@ func TestFromStream(t *testing.T) {
func TestFromStreamResampleOpus(t *testing.T) {
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: &description.Session{Medias: []*description.Media{
{
Type: description.MediaTypeAudio,
@ -95,8 +94,11 @@ func TestFromStreamResampleOpus(t *testing.T) {
}},
},
}},
GenerateRTPPackets: true,
Parent: test.NilLogger,
UseRTPPackets: true,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
ReplaceNTP: false,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -149,29 +151,37 @@ func TestFromStreamResampleOpus(t *testing.T) {
strm.AddReader(r)
defer strm.RemoveReader(r)
strm.WriteRTPPacket(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{1},
}, time.Now(), 0)
strm.WriteUnit(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: time.Now(),
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{1},
}},
})
strm.WriteRTPPacket(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1124,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{1},
}, time.Now(), 0)
strm.WriteUnit(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: time.Now(),
RTPPackets: []*rtp.Packet{{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1124,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{1},
}},
})
err = pc1.GatherIncomingTracks()
require.NoError(t, err)

View file

@ -12,6 +12,7 @@ import (
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
)
@ -196,7 +197,11 @@ func ToStream(
return
}
(*strm).WriteRTPPacket(medi, forma, pkt, ntp, pts)
(*strm).WriteUnit(medi, forma, &unit.Unit{
PTS: pts,
NTP: ntp,
RTPPackets: []*rtp.Packet{pkt},
})
}
medias = append(medias, medi)

View file

@ -21,15 +21,60 @@ import (
"github.com/bluenviron/mediacommon/v2/pkg/formats/fmp4"
mcodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mp4/codecs"
"github.com/bluenviron/mediamtx/internal/codecprocessor"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/unit"
)
var av1DefaultSequenceHeader = []byte{
8, 0, 0, 0, 66, 167, 191, 228, 96, 13, 0, 64,
}
var (
av1DefaultSequenceHeader = []byte{
8, 0, 0, 0, 66, 167, 191, 228, 96, 13, 0, 64,
}
h265DefaultVPS = []byte{
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20,
0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24,
}
h265DefaultSPS = []byte{
0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03,
0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d,
0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88,
0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9,
0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc,
0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a,
0x02, 0x02, 0x02, 0x01,
}
h265DefaultPPS = []byte{
0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40,
}
h264DefaultSPS = []byte{ // 1920x1080 baseline
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
}
h264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08}
mpeg4VideoDefaultConfig = []byte{
0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01,
0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x88, 0x00,
0xf5, 0x3c, 0x04, 0x87, 0x14, 0x63, 0x00, 0x00,
0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38,
0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30,
}
mpeg1VideoDefaultConfig = []byte{
0x00, 0x00, 0x01, 0xb3, 0x78, 0x04, 0x38, 0x35,
0xff, 0xff, 0xe0, 0x18, 0x00, 0x00, 0x01, 0xb5,
0x14, 0x4a, 0x00, 0x01, 0x00, 0x00,
}
)
func mpeg1audioChannelCount(cm mpeg1audio.ChannelMode) int {
switch cm {
@ -276,9 +321,9 @@ func (f *formatFMP4) initialize() bool {
case *rtspformat.H265:
vps, sps, pps := forma.SafeParams()
if vps == nil || sps == nil || pps == nil {
vps = codecprocessor.H265DefaultVPS
sps = codecprocessor.H265DefaultSPS
pps = codecprocessor.H265DefaultPPS
vps = h265DefaultVPS
sps = h265DefaultSPS
pps = h265DefaultPPS
}
codec := &mcodecs.H265{
@ -361,8 +406,8 @@ func (f *formatFMP4) initialize() bool {
case *rtspformat.H264:
sps, pps := forma.SafeParams()
if sps == nil || pps == nil {
sps = codecprocessor.H264DefaultSPS
pps = codecprocessor.H264DefaultPPS
sps = h264DefaultSPS
pps = h264DefaultPPS
}
codec := &mcodecs.H264{
@ -438,7 +483,7 @@ func (f *formatFMP4) initialize() bool {
config := forma.SafeParams()
if config == nil {
config = codecprocessor.MPEG4VideoDefaultConfig
config = mpeg4VideoDefaultConfig
}
codec := &mcodecs.MPEG4Video{
@ -496,7 +541,7 @@ func (f *formatFMP4) initialize() bool {
case *rtspformat.MPEG1Video:
codec := &mcodecs.MPEG1Video{
Config: codecprocessor.MPEG1VideoDefaultConfig,
Config: mpeg1VideoDefaultConfig,
}
track := addTrack(forma, codec)

View file

@ -130,11 +130,11 @@ func TestRecorder(t *testing.T) {
for _, ca := range []string{"fmp4", "mpegts"} {
t.Run(ca, func(t *testing.T) {
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -418,11 +418,11 @@ func TestRecorderFMP4NegativeInitialDTS(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -507,11 +507,11 @@ func TestRecorderFMP4NegativeDTSDiff(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -601,11 +601,11 @@ func TestRecorderSkipTracksPartial(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -663,11 +663,11 @@ func TestRecorderSkipTracksFull(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -727,11 +727,11 @@ func TestRecorderFMP4SegmentSwitch(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -850,11 +850,11 @@ func TestRecorderTimeDriftDetector(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)

View file

@ -185,11 +185,12 @@ func TestServerRead(t *testing.T) {
}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
ReplaceNTP: false,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -406,11 +407,11 @@ func TestServerDirectory(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err = strm.Initialize()
require.NoError(t, err)
@ -455,11 +456,11 @@ func TestServerDynamicAlwaysRemux(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)

View file

@ -238,10 +238,10 @@ func (c *conn) runPublish() error {
var path defs.Path
path, strm, err = c.pathManager.AddPublisher(defs.PathAddPublisherReq{
Author: c,
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: true,
Author: c,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
ReplaceNTP: true,
AccessRequest: defs.PathAccessRequest{
Name: pathName,
Query: c.rconn.URL.RawQuery,

View file

@ -76,11 +76,11 @@ func TestServerPublish(t *testing.T) {
require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass)
strm = &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: req.Desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: req.Desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -206,11 +206,11 @@ func TestServerRead(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)

View file

@ -87,11 +87,11 @@ func TestServerPublish(t *testing.T) {
require.True(t, req.AccessRequest.SkipAuth)
strm = &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: req.Desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: req.Desc,
UseRTPPackets: true,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -175,11 +175,11 @@ func TestServerRead(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -319,11 +319,11 @@ func TestServerRedirect(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: true,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)

View file

@ -311,11 +311,11 @@ func (s *session) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, e
// onRecord is called by rtspServer.
func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) {
path, stream, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{
Author: s,
Desc: s.rsession.AnnouncedDescription(),
GenerateRTPPackets: false,
FillNTP: !s.pathConf.UseAbsoluteTimestamp,
ConfToCompare: s.pathConf,
Author: s,
Desc: s.rsession.AnnouncedDescription(),
UseRTPPackets: true,
ReplaceNTP: !s.pathConf.UseAbsoluteTimestamp,
ConfToCompare: s.pathConf,
AccessRequest: defs.PathAccessRequest{
Name: s.rsession.Path()[1:],
Query: s.rsession.Query(),

View file

@ -219,11 +219,11 @@ func (c *conn) runPublishReader(sconn srt.Conn, streamID *streamID, pathConf *co
var path defs.Path
path, strm, err = c.pathManager.AddPublisher(defs.PathAddPublisherReq{
Author: c,
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: true,
ConfToCompare: pathConf,
Author: c,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
ReplaceNTP: true,
ConfToCompare: pathConf,
AccessRequest: defs.PathAccessRequest{
Name: streamID.path,
Query: streamID.query,

View file

@ -66,11 +66,11 @@ func TestServerPublish(t *testing.T) {
require.True(t, req.AccessRequest.SkipAuth)
strm = &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: req.Desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: req.Desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -181,11 +181,11 @@ func TestServerRead(t *testing.T) {
desc := &description.Session{Medias: []*description.Media{test.MediaH264}}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: false,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)

View file

@ -217,11 +217,11 @@ func TestServerPublish(t *testing.T) {
require.True(t, req.AccessRequest.SkipAuth)
strm = &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: req.Desc,
GenerateRTPPackets: true,
Parent: test.NilLogger,
Desc: req.Desc,
UseRTPPackets: true,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -466,11 +466,11 @@ func TestServerRead(t *testing.T) {
desc := &description.Session{Medias: ca.medias}
strm := &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: desc,
GenerateRTPPackets: ca.unit.Payload != nil,
Parent: test.NilLogger,
Desc: desc,
UseRTPPackets: (ca.unit.Payload == nil),
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Parent: test.NilLogger,
}
err := strm.Initialize()
require.NoError(t, err)
@ -537,7 +537,11 @@ func TestServerRead(t *testing.T) {
if ca.unit.Payload == nil {
clone := *ca.unit.RTPPackets[0]
strm.WriteRTPPacket(desc.Medias[0], desc.Medias[0].Formats[0], &clone, time.Time{}, 0)
strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.Unit{
PTS: 0,
NTP: time.Time{},
RTPPackets: []*rtp.Packet{&clone},
})
} else {
strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], r.Interface().(*unit.Unit))
}

View file

@ -242,11 +242,11 @@ func (s *session) runPublish() (int, error) {
var path defs.Path
path, strm, err = s.pathManager.AddPublisher(defs.PathAddPublisherReq{
Author: s,
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: false,
FillNTP: !pathConf.UseAbsoluteTimestamp,
ConfToCompare: pathConf,
Author: s,
Desc: &description.Session{Medias: medias},
UseRTPPackets: true,
ReplaceNTP: !pathConf.UseAbsoluteTimestamp,
ConfToCompare: pathConf,
AccessRequest: defs.PathAccessRequest{
Name: s.req.pathName,
Query: s.req.httpRequest.URL.RawQuery,

View file

@ -97,8 +97,9 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
}
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
ReplaceNTP: false,
})
if res.Err != nil {
return res.Err

View file

@ -121,9 +121,9 @@ func (s *Source) runReader(nc net.Conn) error {
}
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: true,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
ReplaceNTP: true,
})
if res.Err != nil {
return res.Err

View file

@ -158,8 +158,9 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
initializeStream := func() {
if strm == nil {
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: false,
Desc: &description.Session{Medias: medias},
UseRTPPackets: true,
ReplaceNTP: false,
})
if res.Err != nil {
panic("should not happen")
@ -189,7 +190,11 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
for _, pkt := range pkts {
pkt.Timestamp = uint32(pts)
strm.WriteRTPPacket(medi, medi.Formats[0], pkt, ntp, pts)
strm.WriteUnit(medi, medi.Formats[0], &unit.Unit{
PTS: pts,
NTP: ntp,
RTPPackets: []*rtp.Packet{pkt},
})
}
}
@ -216,7 +221,11 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error {
for _, pkt := range pkts {
pkt.Timestamp = uint32(pts)
pkt.PayloadType = 96
strm.WriteRTPPacket(mediaSecondary, mediaSecondary.Formats[0], pkt, ntp, pts)
strm.WriteUnit(mediaSecondary, mediaSecondary.Formats[0], &unit.Unit{
PTS: pts,
NTP: ntp,
RTPPackets: []*rtp.Packet{pkt},
})
}
}
}
@ -275,8 +284,8 @@ func (s *Source) runSecondary(params defs.StaticSourceRunParams) error {
}
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: []*description.Media{media}},
GenerateRTPPackets: false,
Desc: &description.Session{Medias: []*description.Media{media}},
UseRTPPackets: true,
})
if res.Err != nil {
return res.Err
@ -296,7 +305,11 @@ func (s *Source) runSecondary(params defs.StaticSourceRunParams) error {
}
newPkt.PayloadType = 26
res.Stream.WriteRTPPacket(media, media.Formats[0], newPkt, u.NTP, u.PTS)
res.Stream.WriteUnit(media, media.Formats[0], &unit.Unit{
PTS: u.PTS,
NTP: u.NTP,
RTPPackets: []*rtp.Packet{newPkt},
})
return nil
})

View file

@ -113,9 +113,9 @@ func (s *Source) runReader(conn *gortmplib.Client) error {
}
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: true,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
ReplaceNTP: true,
})
if res.Err != nil {
return res.Err

View file

@ -18,6 +18,7 @@ import (
"github.com/bluenviron/mediamtx/internal/protocols/udp"
"github.com/bluenviron/mediamtx/internal/protocols/unix"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
)
@ -175,9 +176,9 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error {
if strm == nil {
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: desc,
GenerateRTPPackets: false,
FillNTP: true,
Desc: desc,
UseRTPPackets: true,
ReplaceNTP: true,
})
if res.Err != nil {
return res.Err
@ -207,7 +208,10 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error {
continue
}
strm.WriteRTPPacket(media.desc, forma.desc, pkt, time.Time{}, pts)
strm.WriteUnit(media.desc, forma.desc, &unit.Unit{
PTS: pts,
RTPPackets: []*rtp.Packet{pkt},
})
}
}
}

View file

@ -244,9 +244,9 @@ func (s *Source) runInner(c *gortsplib.Client, u *base.URL, pathConf *conf.Path)
s)
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: desc2,
GenerateRTPPackets: false,
FillNTP: !pathConf.UseAbsoluteTimestamp,
Desc: desc2,
UseRTPPackets: true,
ReplaceNTP: !pathConf.UseAbsoluteTimestamp,
})
if res.Err != nil {
return res.Err

View file

@ -106,9 +106,9 @@ func (s *Source) runReader(sconn srt.Conn) error {
}
res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: true,
Desc: &description.Session{Medias: medias},
UseRTPPackets: false,
ReplaceNTP: true,
})
if res.Err != nil {
return res.Err

View file

@ -76,9 +76,9 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error {
}
rres := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: !params.Conf.UseAbsoluteTimestamp,
Desc: &description.Session{Medias: medias},
UseRTPPackets: true,
ReplaceNTP: !params.Conf.UseAbsoluteTimestamp,
})
if rres.Err != nil {
client.Close() //nolint:errcheck

View file

@ -0,0 +1,113 @@
package stream
import (
"bytes"
"github.com/bluenviron/gortsplib/v5/pkg/format"
mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
"github.com/bluenviron/mediamtx/internal/unit"
)
type formatUpdater func(format.Format, unit.Payload)
func formatUpdaterH265(forma format.Format, payload unit.Payload) {
formatH265 := forma.(*format.H265)
au := payload.(unit.PayloadH265)
vps, sps, pps := formatH265.VPS, formatH265.SPS, formatH265.PPS
update := false
for _, nalu := range au {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT:
if !bytes.Equal(nalu, formatH265.VPS) {
vps = nalu
update = true
}
case mch265.NALUType_SPS_NUT:
if !bytes.Equal(nalu, formatH265.SPS) {
sps = nalu
update = true
}
case mch265.NALUType_PPS_NUT:
if !bytes.Equal(nalu, formatH265.PPS) {
pps = nalu
update = true
}
}
}
if update {
formatH265.SafeSetParams(vps, sps, pps)
}
}
func formatUpdaterH264(forma format.Format, payload unit.Payload) {
formatH264 := forma.(*format.H264)
au := payload.(unit.PayloadH264)
sps, pps := formatH264.SPS, formatH264.PPS
update := false
for _, nalu := range au {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS:
if !bytes.Equal(nalu, sps) {
sps = nalu
update = true
}
case mch264.NALUTypePPS:
if !bytes.Equal(nalu, pps) {
pps = nalu
update = true
}
}
}
if update {
formatH264.SafeSetParams(sps, pps)
}
}
func formatUpdaterMPEG4Video(forma format.Format, payload unit.Payload) {
formatMPEG4Video := forma.(*format.MPEG4Video)
frame := payload.(unit.PayloadMPEG4Video)
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 {
return
}
conf := frame[:end+4]
if !bytes.Equal(conf, formatMPEG4Video.Config) {
formatMPEG4Video.SafeSetParams(conf)
}
}
}
func newFormatUpdater(forma format.Format) formatUpdater {
switch forma.(type) {
case *format.H265:
return formatUpdaterH265
case *format.H264:
return formatUpdaterH264
case *format.MPEG4Video:
return formatUpdaterMPEG4Video
default:
return formatUpdater(func(_ format.Format, _ unit.Payload) {
})
}
}

View file

@ -0,0 +1,366 @@
package stream
import (
"errors"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph264"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph265"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
)
type rtpDecoder interface {
decode(*rtp.Packet) (unit.Payload, error)
}
type rtpDecoderAV1 rtpav1.Decoder
func (d *rtpDecoderAV1) decode(pkt *rtp.Packet) (unit.Payload, error) {
tu, err := (*rtpav1.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpav1.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpav1.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadAV1(tu), nil
}
type rtpDecoderVP9 rtpvp9.Decoder
func (d *rtpDecoderVP9) decode(pkt *rtp.Packet) (unit.Payload, error) {
frame, err := (*rtpvp9.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpvp9.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpvp9.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadVP9(frame), nil
}
type rtpDecoderVP8 rtpvp8.Decoder
func (d *rtpDecoderVP8) decode(pkt *rtp.Packet) (unit.Payload, error) {
frame, err := (*rtpvp8.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpvp8.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpvp8.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadVP8(frame), nil
}
type rtpDecoderH265 rtph265.Decoder
func (d *rtpDecoderH265) decode(pkt *rtp.Packet) (unit.Payload, error) {
au, err := (*rtph265.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtph265.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtph265.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadH265(au), nil
}
type rtpDecoderH264 rtph264.Decoder
func (d *rtpDecoderH264) decode(pkt *rtp.Packet) (unit.Payload, error) {
au, err := (*rtph264.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtph264.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtph264.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadH264(au), nil
}
type rtpDecoderMPEG4Video rtpfragmented.Decoder
func (d *rtpDecoderMPEG4Video) decode(pkt *rtp.Packet) (unit.Payload, error) {
frame, err := (*rtpfragmented.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadMPEG4Video(frame), nil
}
type rtpDecoderMPEG1Video rtpmpeg1video.Decoder
func (d *rtpDecoderMPEG1Video) decode(pkt *rtp.Packet) (unit.Payload, error) {
frame, err := (*rtpmpeg1video.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg1video.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpmpeg1video.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadMPEG1Video(frame), nil
}
type rtpDecoderMJPEG rtpmjpeg.Decoder
func (d *rtpDecoderMJPEG) decode(pkt *rtp.Packet) (unit.Payload, error) {
frame, err := (*rtpmjpeg.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpmjpeg.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpmjpeg.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadMJPEG(frame), nil
}
type rtpDecoderOpus rtpsimpleaudio.Decoder
func (d *rtpDecoderOpus) decode(pkt *rtp.Packet) (unit.Payload, error) {
packet, err := (*rtpsimpleaudio.Decoder)(d).Decode(pkt)
if err != nil {
return nil, err
}
return unit.PayloadOpus{packet}, nil
}
type rtpDecoderMPEG4Audio rtpmpeg4audio.Decoder
func (d *rtpDecoderMPEG4Audio) decode(pkt *rtp.Packet) (unit.Payload, error) {
aus, err := (*rtpmpeg4audio.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadMPEG4Audio(aus), nil
}
type rtpDecoderMPEG4AudioLATM rtpfragmented.Decoder
func (d *rtpDecoderMPEG4AudioLATM) decode(pkt *rtp.Packet) (unit.Payload, error) {
payload, err := (*rtpfragmented.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadMPEG4AudioLATM(payload), nil
}
type rtpDecoderMPEG1Audio rtpmpeg1audio.Decoder
func (d *rtpDecoderMPEG1Audio) decode(pkt *rtp.Packet) (unit.Payload, error) {
frames, err := (*rtpmpeg1audio.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious) ||
errors.Is(err, rtpmpeg1audio.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadMPEG1Audio(frames), nil
}
type rtpDecoderAC3 rtpac3.Decoder
func (d *rtpDecoderAC3) decode(pkt *rtp.Packet) (unit.Payload, error) {
frames, err := (*rtpac3.Decoder)(d).Decode(pkt)
if err != nil {
if errors.Is(err, rtpac3.ErrMorePacketsNeeded) {
return nil, nil
}
return nil, err
}
return unit.PayloadAC3(frames), nil
}
type rtpDecoderG711 rtplpcm.Decoder
func (d *rtpDecoderG711) decode(pkt *rtp.Packet) (unit.Payload, error) {
samples, err := (*rtplpcm.Decoder)(d).Decode(pkt)
if err != nil {
return nil, err
}
return unit.PayloadG711(samples), nil
}
type rtpDecoderLPCM rtplpcm.Decoder
func (d *rtpDecoderLPCM) decode(pkt *rtp.Packet) (unit.Payload, error) {
samples, err := (*rtplpcm.Decoder)(d).Decode(pkt)
if err != nil {
return nil, err
}
return unit.PayloadLPCM(samples), nil
}
type rtpDecoderKLV rtpklv.Decoder
func (d *rtpDecoderKLV) decode(pkt *rtp.Packet) (unit.Payload, error) {
payload, err := (*rtpklv.Decoder)(d).Decode(pkt)
if err != nil {
return nil, err
}
return unit.PayloadKLV(payload), nil
}
func newRTPDecoder(forma format.Format) (rtpDecoder, error) {
switch forma := forma.(type) {
case *format.AV1:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderAV1)(wrapped), nil
case *format.VP9:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderVP9)(wrapped), nil
case *format.VP8:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderVP8)(wrapped), nil
case *format.H265:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderH265)(wrapped), nil
case *format.H264:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderH264)(wrapped), nil
case *format.MPEG4Video:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderMPEG4Video)(wrapped), nil
case *format.MPEG1Video:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderMPEG1Video)(wrapped), nil
case *format.MJPEG:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderMJPEG)(wrapped), nil
case *format.Opus:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderOpus)(wrapped), nil
case *format.MPEG4Audio:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderMPEG4Audio)(wrapped), nil
case *format.MPEG4AudioLATM:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderMPEG4AudioLATM)(wrapped), nil
case *format.MPEG1Audio:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderMPEG1Audio)(wrapped), nil
case *format.AC3:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderAC3)(wrapped), nil
case *format.G711:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderG711)(wrapped), nil
case *format.LPCM:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderLPCM)(wrapped), nil
case *format.KLV:
wrapped, err := forma.CreateDecoder()
if err != nil {
return nil, err
}
return (*rtpDecoderKLV)(wrapped), nil
default:
return nil, nil
}
}

View file

@ -0,0 +1,382 @@
package stream
import (
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph264"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtph265"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8"
"github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9"
mcopus "github.com/bluenviron/mediacommon/v2/pkg/codecs/opus"
"github.com/bluenviron/mediamtx/internal/unit"
"github.com/pion/rtp"
)
func ptrOf[T any](v T) *T {
return &v
}
type rtpEncoder interface {
encode(unit.Payload) ([]*rtp.Packet, error)
}
type rtpEncoderH265 rtph265.Encoder
func (e *rtpEncoderH265) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtph265.Encoder)(e).Encode(payload.(unit.PayloadH265))
}
type rtpEncoderH264 rtph264.Encoder
func (e *rtpEncoderH264) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtph264.Encoder)(e).Encode(payload.(unit.PayloadH264))
}
type rtpEncoderAV1 rtpav1.Encoder
func (e *rtpEncoderAV1) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpav1.Encoder)(e).Encode(payload.(unit.PayloadAV1))
}
type rtpEncoderVP9 rtpvp9.Encoder
func (e *rtpEncoderVP9) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpvp9.Encoder)(e).Encode(payload.(unit.PayloadVP9))
}
type rtpEncoderVP8 rtpvp8.Encoder
func (e *rtpEncoderVP8) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpvp8.Encoder)(e).Encode(payload.(unit.PayloadVP8))
}
type rtpEncoderMPEG4Video rtpfragmented.Encoder
func (e *rtpEncoderMPEG4Video) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpfragmented.Encoder)(e).Encode(payload.(unit.PayloadMPEG4Video))
}
type rtpEncoderMPEG1Video rtpmpeg1video.Encoder
func (e *rtpEncoderMPEG1Video) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpmpeg1video.Encoder)(e).Encode(payload.(unit.PayloadMPEG1Video))
}
type rtpEncoderMJPEG rtpmjpeg.Encoder
func (e *rtpEncoderMJPEG) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpmjpeg.Encoder)(e).Encode(payload.(unit.PayloadMJPEG))
}
type rtpEncoderOpus rtpsimpleaudio.Encoder
func (e *rtpEncoderOpus) encode(payload unit.Payload) ([]*rtp.Packet, error) {
pts := int64(0)
packets := make([]*rtp.Packet, len(payload.(unit.PayloadOpus)))
for i, packet := range payload.(unit.PayloadOpus) {
pkt, err := (*rtpsimpleaudio.Encoder)(e).Encode(packet)
if err != nil {
return nil, err
}
pkt.Timestamp += uint32(pts)
pts += mcopus.PacketDuration2(packet)
packets[i] = pkt
}
return packets, nil
}
type rtpEncoderMPEG4Audio rtpmpeg4audio.Encoder
func (e *rtpEncoderMPEG4Audio) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpmpeg4audio.Encoder)(e).Encode(payload.(unit.PayloadMPEG4Audio))
}
type rtpEncoderMPEG4AudioLATM rtpfragmented.Encoder
func (e *rtpEncoderMPEG4AudioLATM) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpfragmented.Encoder)(e).Encode(payload.(unit.PayloadMPEG4AudioLATM))
}
type rtpEncoderMPEG1Audio rtpmpeg1audio.Encoder
func (e *rtpEncoderMPEG1Audio) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpmpeg1audio.Encoder)(e).Encode(payload.(unit.PayloadMPEG1Audio))
}
type rtpEncoderAC3 rtpac3.Encoder
func (e *rtpEncoderAC3) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpac3.Encoder)(e).Encode(payload.(unit.PayloadAC3))
}
type rtpEncoderG711 rtplpcm.Encoder
func (e *rtpEncoderG711) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtplpcm.Encoder)(e).Encode(payload.(unit.PayloadG711))
}
type rtpEncoderLPCM rtplpcm.Encoder
func (e *rtpEncoderLPCM) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtplpcm.Encoder)(e).Encode(payload.(unit.PayloadLPCM))
}
type rtpEncoderKLV rtpklv.Encoder
func (e *rtpEncoderKLV) encode(payload unit.Payload) ([]*rtp.Packet, error) {
return (*rtpklv.Encoder)(e).Encode(payload.(unit.PayloadKLV))
}
func newRTPEncoder(
forma format.Format,
rtpMaxPayloadSize int,
ssrc *uint32,
initialSequenceNumber *uint16,
) (rtpEncoder, error) {
switch forma := forma.(type) {
case *format.H265:
wrapped := &rtph265.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
MaxDONDiff: forma.MaxDONDiff,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderH265)(wrapped), nil
case *format.H264:
wrapped := &rtph264.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
PacketizationMode: forma.PacketizationMode,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderH264)(wrapped), nil
case *format.AV1:
wrapped := &rtpav1.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderAV1)(wrapped), nil
case *format.VP9:
wrapped := &rtpvp9.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
InitialPictureID: ptrOf(uint16(0x35af)),
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderVP9)(wrapped), nil
case *format.VP8:
wrapped := &rtpvp8.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderVP8)(wrapped), nil
case *format.MPEG4Video:
wrapped := &rtpfragmented.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderMPEG4Video)(wrapped), nil
case *format.MPEG1Video:
wrapped := &rtpmpeg1video.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderMPEG1Video)(wrapped), nil
case *format.MJPEG:
wrapped := &rtpmjpeg.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderMJPEG)(wrapped), nil
case *format.Opus:
wrapped := &rtpsimpleaudio.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderOpus)(wrapped), nil
case *format.MPEG4Audio:
wrapped := &rtpmpeg4audio.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
SizeLength: forma.SizeLength,
IndexLength: forma.IndexLength,
IndexDeltaLength: forma.IndexDeltaLength,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderMPEG4Audio)(wrapped), nil
case *format.MPEG4AudioLATM:
wrapped := &rtpfragmented.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderMPEG4AudioLATM)(wrapped), nil
case *format.MPEG1Audio:
wrapped := &rtpmpeg1audio.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderMPEG1Audio)(wrapped), nil
case *format.AC3:
wrapped := &rtpac3.Encoder{
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderAC3)(wrapped), nil
case *format.G711:
wrapped := &rtplpcm.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadType(),
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
BitDepth: 8,
ChannelCount: forma.ChannelCount,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderG711)(wrapped), nil
case *format.LPCM:
wrapped := &rtplpcm.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
BitDepth: forma.BitDepth,
ChannelCount: forma.ChannelCount,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderLPCM)(wrapped), nil
case *format.KLV:
wrapped := &rtpklv.Encoder{
PayloadMaxSize: rtpMaxPayloadSize,
PayloadType: forma.PayloadTyp,
SSRC: ssrc,
InitialSequenceNumber: initialSequenceNumber,
}
err := wrapped.Init()
if err != nil {
return nil, err
}
return (*rtpEncoderKLV)(wrapped), nil
default:
return nil, nil
}
}

View file

@ -17,14 +17,14 @@ import (
)
// Stream is a media stream.
// It stores tracks, readers and allows to write data to readers, converting it when needed.
// It stores tracks, readers and allows to write data to readers, remuxing it when needed.
type Stream struct {
WriteQueueSize int
RTPMaxPayloadSize int
Desc *description.Session
GenerateRTPPackets bool
FillNTP bool
Parent logger.Writer
Desc *description.Session
UseRTPPackets bool
WriteQueueSize int
RTPMaxPayloadSize int
ReplaceNTP bool
Parent logger.Writer
bytesReceived *uint64
bytesSent *uint64
@ -56,12 +56,15 @@ func (s *Stream) Initialize() error {
for _, media := range s.Desc.Medias {
s.medias[media] = &streamMedia{
rtpMaxPayloadSize: s.RTPMaxPayloadSize,
media: media,
generateRTPPackets: s.GenerateRTPPackets,
fillNTP: s.FillNTP,
processingErrors: s.processingErrors,
parent: s.Parent,
media: media,
useRTPPackets: s.UseRTPPackets,
rtpMaxPayloadSize: s.RTPMaxPayloadSize,
replaceNTP: s.ReplaceNTP,
onBytesReceived: s.onBytesReceived,
onBytesSent: s.onBytesSent,
writeRTSP: s.writeRTSP,
processingErrors: s.processingErrors,
parent: s.Parent,
}
err := s.medias[media].initialize()
if err != nil {
@ -193,22 +196,27 @@ func (s *Stream) WriteUnit(medi *description.Media, forma format.Format, u *unit
s.mutex.RLock()
defer s.mutex.RUnlock()
sf.writeUnit(s, medi, u)
sf.writeUnit(u)
}
// WriteRTPPacket writes a RTP packet.
func (s *Stream) WriteRTPPacket(
medi *description.Media,
forma format.Format,
pkt *rtp.Packet,
ntp time.Time,
pts int64,
) {
sm := s.medias[medi]
sf := sm.formats[forma]
s.mutex.RLock()
defer s.mutex.RUnlock()
sf.writeRTPPacket(s, medi, pkt, ntp, pts)
func (s *Stream) onBytesReceived(v uint64) {
atomic.AddUint64(s.bytesReceived, v)
}
func (s *Stream) onBytesSent(v uint64) {
atomic.AddUint64(s.bytesSent, v)
}
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
}
}
}

View file

@ -1,14 +1,14 @@
package stream
import (
"sync/atomic"
"crypto/rand"
"fmt"
"time"
"github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/codecprocessor"
"github.com/bluenviron/mediamtx/internal/errordumper"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/ntpestimator"
@ -23,98 +23,172 @@ func unitSize(u *unit.Unit) uint64 {
return n
}
type streamFormat struct {
rtpMaxPayloadSize int
format format.Format
generateRTPPackets bool
fillNTP bool
processingErrors *errordumper.Dumper
parent logger.Writer
func randUint32() (uint32, error) {
var b [4]byte
_, err := rand.Read(b[:])
if err != nil {
return 0, err
}
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
}
proc codecprocessor.Processor
ntpEstimator *ntpestimator.Estimator
onDatas map[*Reader]OnDataFunc
type streamFormat struct {
format format.Format
media *description.Media
useRTPPackets bool
rtpMaxPayloadSize int
replaceNTP bool
processingErrors *errordumper.Dumper
onBytesReceived func(uint64)
onBytesSent func(uint64)
writeRTSP func(*description.Media, []*rtp.Packet, time.Time)
parent logger.Writer
rtpDecoder rtpDecoder
formatUpdater formatUpdater
unitRemuxer unitRemuxer
rtpEncoder rtpEncoder
ptsOffset uint32
ntpEstimator *ntpestimator.Estimator
onDatas map[*Reader]OnDataFunc
}
func (sf *streamFormat) initialize() error {
sf.onDatas = make(map[*Reader]OnDataFunc)
var err error
sf.proc, err = codecprocessor.New(sf.rtpMaxPayloadSize, sf.format, sf.generateRTPPackets, sf.parent)
if err != nil {
return err
if sf.useRTPPackets {
var err error
sf.rtpDecoder, err = newRTPDecoder(sf.format)
if err != nil {
return err
}
}
sf.ntpEstimator = &ntpestimator.Estimator{
ClockRate: sf.format.ClockRate(),
sf.formatUpdater = newFormatUpdater(sf.format)
sf.unitRemuxer = newUnitRemuxer(sf.format)
if !sf.useRTPPackets {
var err error
sf.rtpEncoder, err = newRTPEncoder(sf.format, sf.rtpMaxPayloadSize, nil, nil)
if err != nil {
return err
}
if sf.rtpEncoder == nil {
return fmt.Errorf("RTP encoder not available for format %T", sf.format)
}
sf.ptsOffset, err = randUint32()
if err != nil {
return err
}
}
if sf.replaceNTP {
sf.ntpEstimator = &ntpestimator.Estimator{
ClockRate: sf.format.ClockRate(),
}
}
return nil
}
func (sf *streamFormat) writeUnit(s *Stream, medi *description.Media, u *unit.Unit) {
err := sf.proc.ProcessUnit(u)
func (sf *streamFormat) writeUnit(u *unit.Unit) {
err := sf.writeUnitInner(u)
if err != nil {
sf.processingErrors.Add(err)
return
}
sf.writeUnitInner(s, medi, u)
}
func (sf *streamFormat) writeRTPPacket(
s *Stream,
medi *description.Media,
pkt *rtp.Packet,
ntp time.Time,
pts int64,
) {
hasNonRTSPReaders := len(sf.onDatas) > 0
func (sf *streamFormat) writeUnitInner(u *unit.Unit) error {
if sf.useRTPPackets {
if len(u.RTPPackets) != 1 {
panic("should not happen")
}
if !u.NilPayload() {
panic("should not happen")
}
u := &unit.Unit{
PTS: pts,
NTP: ntp,
RTPPackets: []*rtp.Packet{pkt},
if sf.rtpDecoder != nil {
var err error
u.Payload, err = sf.rtpDecoder.decode(u.RTPPackets[0])
if err != nil {
return err
}
}
if sf.rtpEncoder == nil {
for _, pkt := range u.RTPPackets {
if len(pkt.Payload) > sf.rtpMaxPayloadSize {
var err error
sf.rtpEncoder, err = newRTPEncoder(sf.format, sf.rtpMaxPayloadSize, ptrOf(pkt.SSRC), ptrOf(pkt.SequenceNumber))
if err != nil {
return err
}
if sf.rtpEncoder == nil {
return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)",
len(pkt.Payload), sf.rtpMaxPayloadSize)
}
sf.ptsOffset = pkt.Timestamp - uint32(u.PTS)
sf.parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones")
break
}
}
}
if sf.rtpEncoder != nil {
u.RTPPackets = nil
}
} else {
if len(u.RTPPackets) != 0 {
panic("should not happen")
}
if u.NilPayload() {
panic("should not happen")
}
}
err := sf.proc.ProcessRTPPacket(u, hasNonRTSPReaders)
if err != nil {
sf.processingErrors.Add(err)
return
if !u.NilPayload() {
sf.formatUpdater(sf.format, u.Payload)
u.Payload = sf.unitRemuxer(sf.format, u.Payload)
if sf.rtpEncoder != nil && !u.NilPayload() {
var err error
u.RTPPackets, err = sf.rtpEncoder.encode(u.Payload)
if err != nil {
return err
}
for _, pkt := range u.RTPPackets {
pkt.Timestamp += sf.ptsOffset + uint32(u.PTS)
}
}
}
sf.writeUnitInner(s, medi, u)
}
func (sf *streamFormat) writeUnitInner(s *Stream, medi *description.Media, u *unit.Unit) {
if sf.fillNTP {
if sf.replaceNTP {
u.NTP = sf.ntpEstimator.Estimate(u.PTS)
}
size := unitSize(u)
sf.onBytesReceived(size)
atomic.AddUint64(s.bytesReceived, size)
if s.rtspStream != nil {
for _, pkt := range u.RTPPackets {
s.rtspStream.WritePacketRTPWithNTP(medi, pkt, u.NTP) //nolint:errcheck
}
}
if s.rtspsStream != nil {
for _, pkt := range u.RTPPackets {
s.rtspsStream.WritePacketRTPWithNTP(medi, pkt, u.NTP) //nolint:errcheck
}
}
sf.writeRTSP(sf.media, u.RTPPackets, u.NTP)
for sr, onData := range sf.onDatas {
csr := sr
cOnData := onData
sr.push(func() error {
if !csr.SkipBytesSent {
atomic.AddUint64(s.bytesSent, size)
sf.onBytesSent(size)
}
return cOnData(u)
})
}
return nil
}

View file

@ -1,19 +1,25 @@
package stream
import (
"time"
"github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/bluenviron/gortsplib/v5/pkg/format"
"github.com/bluenviron/mediamtx/internal/errordumper"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/pion/rtp"
)
type streamMedia struct {
rtpMaxPayloadSize int
media *description.Media
generateRTPPackets bool
fillNTP bool
processingErrors *errordumper.Dumper
parent logger.Writer
media *description.Media
useRTPPackets bool
rtpMaxPayloadSize int
replaceNTP bool
onBytesReceived func(uint64)
onBytesSent func(uint64)
writeRTSP func(*description.Media, []*rtp.Packet, time.Time)
processingErrors *errordumper.Dumper
parent logger.Writer
formats map[format.Format]*streamFormat
}
@ -23,12 +29,16 @@ func (sm *streamMedia) initialize() error {
for _, forma := range sm.media.Formats {
sf := &streamFormat{
rtpMaxPayloadSize: sm.rtpMaxPayloadSize,
format: forma,
generateRTPPackets: sm.generateRTPPackets,
fillNTP: sm.fillNTP,
processingErrors: sm.processingErrors,
parent: sm.parent,
format: forma,
media: sm.media,
useRTPPackets: sm.useRTPPackets,
rtpMaxPayloadSize: sm.rtpMaxPayloadSize,
replaceNTP: sm.replaceNTP,
processingErrors: sm.processingErrors,
onBytesReceived: sm.onBytesReceived,
onBytesSent: sm.onBytesSent,
writeRTSP: sm.writeRTSP,
parent: sm.parent,
}
err := sf.initialize()
if err != nil {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,181 @@
package stream
import (
"bytes"
"github.com/bluenviron/gortsplib/v5/pkg/format"
mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264"
mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
"github.com/bluenviron/mediamtx/internal/unit"
)
type unitRemuxer func(format.Format, unit.Payload) unit.Payload
func unitRemuxerH265(forma format.Format, payload unit.Payload) unit.Payload {
formatH265 := forma.(*format.H265)
au := payload.(unit.PayloadH265)
isKeyFrame := false
n := 0
for _, nalu := range au {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
continue
case mch265.NALUType_AUD_NUT:
continue
case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT:
if !isKeyFrame {
isKeyFrame = true
// prepend parameters
if formatH265.VPS != nil && formatH265.SPS != nil && formatH265.PPS != nil {
n += 3
}
}
}
n++
}
if n == 0 {
return unit.PayloadH265(nil)
}
filteredAU := make([][]byte, n)
i := 0
if isKeyFrame && formatH265.VPS != nil && formatH265.SPS != nil && formatH265.PPS != nil {
filteredAU[0] = formatH265.VPS
filteredAU[1] = formatH265.SPS
filteredAU[2] = formatH265.PPS
i = 3
}
for _, nalu := range au {
typ := mch265.NALUType((nalu[0] >> 1) & 0b111111)
switch typ {
case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT:
continue
case mch265.NALUType_AUD_NUT:
continue
}
filteredAU[i] = nalu
i++
}
return unit.PayloadH265(filteredAU)
}
func unitRemuxerH264(forma format.Format, payload unit.Payload) unit.Payload {
formatH264 := forma.(*format.H264)
au := payload.(unit.PayloadH264)
isKeyFrame := false
n := 0
for _, nalu := range au {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
continue
case mch264.NALUTypeAccessUnitDelimiter:
continue
case mch264.NALUTypeIDR:
if !isKeyFrame {
isKeyFrame = true
// prepend parameters
if formatH264.SPS != nil && formatH264.PPS != nil {
n += 2
}
}
}
n++
}
if n == 0 {
return unit.PayloadH264(nil)
}
filteredAU := make([][]byte, n)
i := 0
if isKeyFrame && formatH264.SPS != nil && formatH264.PPS != nil {
filteredAU[0] = formatH264.SPS
filteredAU[1] = formatH264.PPS
i = 2
}
for _, nalu := range au {
typ := mch264.NALUType(nalu[0] & 0x1F)
switch typ {
case mch264.NALUTypeSPS, mch264.NALUTypePPS:
continue
case mch264.NALUTypeAccessUnitDelimiter:
continue
}
filteredAU[i] = nalu
i++
}
return unit.PayloadH264(filteredAU)
}
func unitRemuxerMPEG4Video(forma format.Format, payload unit.Payload) unit.Payload {
formatMPEG4Video := forma.(*format.MPEG4Video)
frame := payload.(unit.PayloadMPEG4Video)
// 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 {
frame = frame[end+4:]
}
}
// add config
if bytes.Contains(frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) {
f := make([]byte, len(formatMPEG4Video.Config)+len(frame))
n := copy(f, formatMPEG4Video.Config)
copy(f[n:], frame)
frame = f
}
if len(frame) == 0 {
return unit.PayloadMPEG4Video(nil)
}
return frame
}
func newUnitRemuxer(forma format.Format) unitRemuxer {
switch forma.(type) {
case *format.H265:
return unitRemuxerH265
case *format.H264:
return unitRemuxerH264
case *format.MPEG4Video:
return unitRemuxerMPEG4Video
default:
return unitRemuxer(func(_ format.Format, payload unit.Payload) unit.Payload {
return payload
})
}
}

View file

@ -30,11 +30,12 @@ func (p *StaticSourceParent) Close() {
// SetReady implements parent.
func (p *StaticSourceParent) SetReady(req defs.PathSourceStaticSetReadyReq) defs.PathSourceStaticSetReadyRes {
p.stream = &stream.Stream{
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
Desc: req.Desc,
GenerateRTPPackets: req.GenerateRTPPackets,
Parent: p,
Desc: req.Desc,
UseRTPPackets: req.UseRTPPackets,
WriteQueueSize: 512,
RTPMaxPayloadSize: 1450,
ReplaceNTP: req.ReplaceNTP,
Parent: p,
}
err := p.stream.Initialize()
if err != nil {