mirror of
https://github.com/bluenviron/mediamtx.git
synced 2026-01-26 21:39:16 -08:00
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:
parent
2109ade5b4
commit
653d10fb75
71 changed files with 2765 additions and 3931 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("800")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("8\x00\x00")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("80")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("a00")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("a0\x00\x00")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("0")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
go test fuzz v1
|
||||
[]byte("a000")
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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().
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
113
internal/stream/format_updater.go
Normal file
113
internal/stream/format_updater.go
Normal 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) {
|
||||
})
|
||||
}
|
||||
}
|
||||
366
internal/stream/rtp_decoder.go
Normal file
366
internal/stream/rtp_decoder.go
Normal 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
|
||||
}
|
||||
}
|
||||
382
internal/stream/rtp_encoder.go
Normal file
382
internal/stream/rtp_encoder.go
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
181
internal/stream/unit_remuxer.go
Normal file
181
internal/stream/unit_remuxer.go
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue