mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
support KLV tracks (#808)
Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
This commit is contained in:

committed by
GitHub

parent
ff94dc956b
commit
e02c447868
@@ -141,6 +141,7 @@ In RTSP, media streams are transmitted by using RTP packets, which are encoded i
|
||||
|codec|documentation|encoder and decoder available|
|
||||
|------|-------------|-----------------------------|
|
||||
|MPEG-TS|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEGTS)||
|
||||
|KLV|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#KLV)|:heavy_check_mark:|
|
||||
|
||||
## Specifications
|
||||
|
||||
@@ -168,6 +169,7 @@ In RTSP, media streams are transmitted by using RTP packets, which are encoded i
|
||||
|[RFC5574, RTP Payload Format for the Speex Codec](https://datatracker.ietf.org/doc/html/rfc5574)|payload formats / Speex|
|
||||
|[RFC3551, RTP Profile for Audio and Video Conferences with Minimal Control](https://datatracker.ietf.org/doc/html/rfc3551)|payload formats / G726, G722, G711, LPCM|
|
||||
|[RFC3190, RTP Payload Format for 12-bit DAT Audio and 20- and 24-bit Linear Sampled Audio](https://datatracker.ietf.org/doc/html/rfc3190)|payload formats / LPCM|
|
||||
|[RFC6597, RTP Payload Format for Society of Motion Picture and Television Engineers (SMPTE) ST 336 Encoded Data](https://datatracker.ietf.org/doc/html/rfc6597)|payload formats / KLV|
|
||||
|[Codec specifications](https://github.com/bluenviron/mediacommon#specifications)|codecs|
|
||||
|[Golang project layout](https://github.com/golang-standards/project-layout)|project layout|
|
||||
|
||||
|
@@ -185,6 +185,11 @@ func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error)
|
||||
case codec == "l8", codec == "l16", codec == "l24" && payloadType >= 96 && payloadType <= 127:
|
||||
return &LPCM{}
|
||||
|
||||
// application
|
||||
|
||||
case codec == "smtpe336m" && payloadType >= 96 && payloadType <= 127:
|
||||
return &KLV{}
|
||||
|
||||
/*
|
||||
* static payload types
|
||||
**/
|
||||
|
@@ -1194,6 +1194,19 @@ var casesFormat = []struct {
|
||||
"TP-LINK/90000",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"application klv",
|
||||
"v=0\n" +
|
||||
"s=\n" +
|
||||
"m=application 0 RTP/AVP 97\n" +
|
||||
"a=rtpmap:97 smtpe336m/90000\n",
|
||||
&KLV{
|
||||
PayloadTyp: 97,
|
||||
},
|
||||
97,
|
||||
"smtpe336m/90000",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"audio aac from AVOIP (issue mediamtx/4183)",
|
||||
"v=0\r\n" +
|
||||
|
74
pkg/format/klv.go
Normal file
74
pkg/format/klv.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpklv"
|
||||
)
|
||||
|
||||
// KLV is the RTP format for KLV data.
|
||||
// Specification: https://datatracker.ietf.org/doc/html/rfc6597
|
||||
type KLV struct {
|
||||
PayloadTyp uint8
|
||||
}
|
||||
|
||||
func (f *KLV) unmarshal(ctx *unmarshalContext) error {
|
||||
f.PayloadTyp = ctx.payloadType
|
||||
return nil
|
||||
}
|
||||
|
||||
// Codec implements Format.
|
||||
func (f *KLV) Codec() string {
|
||||
return "KLV"
|
||||
}
|
||||
|
||||
// ClockRate implements Format.
|
||||
func (f *KLV) ClockRate() int {
|
||||
return 90000
|
||||
}
|
||||
|
||||
// PayloadType implements Format.
|
||||
func (f *KLV) PayloadType() uint8 {
|
||||
return f.PayloadTyp
|
||||
}
|
||||
|
||||
// RTPMap implements Format.
|
||||
func (f *KLV) RTPMap() string {
|
||||
return "smtpe336m/90000"
|
||||
}
|
||||
|
||||
// FMTP implements Format.
|
||||
func (f *KLV) FMTP() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PTSEqualsDTS implements Format.
|
||||
func (f *KLV) PTSEqualsDTS(*rtp.Packet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// CreateDecoder creates a decoder able to decode the content of the format.
|
||||
func (f *KLV) CreateDecoder() (*rtpklv.Decoder, error) {
|
||||
d := &rtpklv.Decoder{}
|
||||
|
||||
err := d.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// CreateEncoder creates an encoder able to encode the content of the format.
|
||||
func (f *KLV) CreateEncoder() (*rtpklv.Encoder, error) {
|
||||
e := &rtpklv.Encoder{
|
||||
PayloadType: f.PayloadTyp,
|
||||
}
|
||||
|
||||
err := e.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
17
pkg/format/klv_test.go
Normal file
17
pkg/format/klv_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKLVAttributes(t *testing.T) {
|
||||
format := &KLV{
|
||||
PayloadTyp: 96,
|
||||
}
|
||||
require.Equal(t, "KLV", format.Codec())
|
||||
require.Equal(t, 90000, format.ClockRate())
|
||||
require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{}))
|
||||
}
|
195
pkg/format/rtpklv/decoder.go
Normal file
195
pkg/format/rtpklv/decoder.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package rtpklv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// ErrMorePacketsNeeded is returned when more packets are needed to complete a KLV unit.
|
||||
var ErrMorePacketsNeeded = errors.New("need more packets")
|
||||
|
||||
// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting
|
||||
// packet of a fragmented KLV unit and we didn't receive anything before.
|
||||
// It's normal to receive this when decoding a stream that has been already
|
||||
// running for some time.
|
||||
var ErrNonStartingPacketAndNoPrevious = errors.New(
|
||||
"received a non-starting fragment without any previous starting fragment")
|
||||
|
||||
// Decoder is a RTP/KLV decoder.
|
||||
// Specification: https://datatracker.ietf.org/doc/html/rfc6597
|
||||
type Decoder struct {
|
||||
// buffer for accumulating KLV unit data across multiple packets
|
||||
buffer []byte
|
||||
// expected total size of the current KLV unit being assembled
|
||||
expectedSize int
|
||||
// timestamp of the current KLV unit being assembled
|
||||
currentTimestamp uint32
|
||||
// whether we're currently assembling a KLV unit
|
||||
assembling bool
|
||||
// sequence number of the last processed packet
|
||||
lastSeqNum uint16
|
||||
// whether we've received the first packet
|
||||
firstPacketReceived bool
|
||||
}
|
||||
|
||||
// Init initializes the decoder.
|
||||
func (d *Decoder) Init() error {
|
||||
d.reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset clears the decoder state.
|
||||
func (d *Decoder) reset() {
|
||||
d.buffer = d.buffer[:0]
|
||||
d.expectedSize = 0
|
||||
d.currentTimestamp = 0
|
||||
d.assembling = false
|
||||
d.firstPacketReceived = false
|
||||
}
|
||||
|
||||
// parseKLVLength parses the KLV length field according to SMPTE ST 336.
|
||||
// Returns the length value and the number of bytes consumed for the length field.
|
||||
func parseKLVLength(data []byte) (uint, uint, error) {
|
||||
if len(data) < 1 {
|
||||
return 0, 0, fmt.Errorf("buffer is too short")
|
||||
}
|
||||
|
||||
firstByte := data[0]
|
||||
|
||||
// Short form: if bit 7 is 0, the length is in the lower 7 bits
|
||||
if (firstByte & 0x80) == 0 {
|
||||
return uint(firstByte & 0x7f), 1, nil
|
||||
}
|
||||
|
||||
// Long form: bit 7 is 1, lower 7 bits indicate number of subsequent length bytes
|
||||
lengthBytes := uint(firstByte & 0x7f)
|
||||
if lengthBytes == 0 || lengthBytes > 8 {
|
||||
return 0, 0, fmt.Errorf("invalid length field: %d bytes", lengthBytes)
|
||||
}
|
||||
|
||||
totalLengthSize := 1 + lengthBytes
|
||||
if int(totalLengthSize) > len(data) {
|
||||
return 0, 0, fmt.Errorf("insufficient data for length field")
|
||||
}
|
||||
|
||||
// Parse the length value from the subsequent bytes
|
||||
var lengthValue uint
|
||||
for i := range lengthBytes {
|
||||
lengthValue = (lengthValue << 8) | uint(data[1+i])
|
||||
}
|
||||
|
||||
return lengthValue, totalLengthSize, nil
|
||||
}
|
||||
|
||||
// isKLVStart checks if the payload starts with a KLV Universal Label Key.
|
||||
// KLV Universal Label Keys start with the 4-byte prefix: 0x060e2b34
|
||||
func isKLVStart(payload []byte) bool {
|
||||
if len(payload) < 4 {
|
||||
return false
|
||||
}
|
||||
return payload[0] == 0x06 && payload[1] == 0x0e && payload[2] == 0x2b && payload[3] == 0x34
|
||||
}
|
||||
|
||||
func splitKLVUnit(buf []byte) ([][]byte, error) {
|
||||
var klvUnit [][]byte
|
||||
for {
|
||||
if len(buf) < 16 {
|
||||
return nil, fmt.Errorf("buffer is too short")
|
||||
}
|
||||
|
||||
n := uint(16)
|
||||
le, leSize, err := parseKLVLength(buf[n:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n += leSize
|
||||
|
||||
if uint(len(buf[n:])) < le {
|
||||
return nil, fmt.Errorf("buffer is too short")
|
||||
}
|
||||
n += le
|
||||
|
||||
klv := buf[:n]
|
||||
buf = buf[n:]
|
||||
klvUnit = append(klvUnit, klv)
|
||||
|
||||
if len(buf) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return klvUnit, nil
|
||||
}
|
||||
|
||||
// Decode decodes a KLV unit from RTP packets.
|
||||
// It returns the complete KLV unit when all packets have been received,
|
||||
// or ErrMorePacketsNeeded if more packets are needed.
|
||||
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) {
|
||||
payload := pkt.Payload
|
||||
marker := pkt.Marker
|
||||
timestamp := pkt.Timestamp
|
||||
seqNum := pkt.SequenceNumber
|
||||
|
||||
// Check for sequence number gaps (packet loss)
|
||||
if d.firstPacketReceived {
|
||||
expectedSeq := d.lastSeqNum + 1
|
||||
if seqNum != expectedSeq {
|
||||
// Packet loss detected, reset state
|
||||
d.reset()
|
||||
return nil, fmt.Errorf("packet loss detected: expected seq %d, got %d", expectedSeq, seqNum)
|
||||
}
|
||||
}
|
||||
d.lastSeqNum = seqNum
|
||||
d.firstPacketReceived = true
|
||||
|
||||
// If we're not currently assembling and this packet doesn't start a new KLV unit
|
||||
if !d.assembling {
|
||||
// Check if this looks like the start of a KLV unit
|
||||
if !isKLVStart(payload) {
|
||||
return nil, ErrNonStartingPacketAndNoPrevious
|
||||
}
|
||||
|
||||
// This is the start of a new KLV unit
|
||||
d.currentTimestamp = timestamp
|
||||
d.assembling = true
|
||||
d.buffer = append(d.buffer[:0], payload...)
|
||||
|
||||
// Try to determine the expected size if we have enough data
|
||||
if len(payload) >= 17 { // 16 bytes for Universal Label Key + at least 1 byte for length
|
||||
valueLength, lengthSize, err := parseKLVLength(payload[16:])
|
||||
if err == nil {
|
||||
d.expectedSize = 16 + int(lengthSize) + int(valueLength)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We're assembling a KLV unit
|
||||
if timestamp != d.currentTimestamp {
|
||||
// Timestamp changed, this is a new KLV unit
|
||||
// The previous unit was incomplete
|
||||
d.reset()
|
||||
return nil, fmt.Errorf("incomplete KLV unit: timestamp changed from %d to %d", d.currentTimestamp, timestamp)
|
||||
}
|
||||
|
||||
// Append this packet's payload to the buffer
|
||||
d.buffer = append(d.buffer, payload...)
|
||||
}
|
||||
|
||||
// Check if we have a complete KLV unit
|
||||
if marker {
|
||||
result := d.buffer
|
||||
d.reset()
|
||||
return splitKLVUnit(result)
|
||||
}
|
||||
|
||||
// If we know the expected size and have reached it, return the complete unit
|
||||
if d.expectedSize > 0 && len(d.buffer) >= d.expectedSize {
|
||||
result := d.buffer[:d.expectedSize]
|
||||
d.reset()
|
||||
return splitKLVUnit(result)
|
||||
}
|
||||
|
||||
// Need more packets
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
77
pkg/format/rtpklv/decoder_test.go
Normal file
77
pkg/format/rtpklv/decoder_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package rtpklv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
for _, ca := range cases {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
d := &Decoder{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
var klvUnit [][]byte
|
||||
|
||||
for _, pkt := range ca.pkts {
|
||||
clone := pkt.Clone()
|
||||
|
||||
addUnits, err := d.Decode(pkt)
|
||||
|
||||
// test input integrity
|
||||
require.Equal(t, clone, pkt)
|
||||
|
||||
if errors.Is(err, ErrMorePacketsNeeded) {
|
||||
continue
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
klvUnit = append(klvUnit, addUnits...)
|
||||
}
|
||||
|
||||
require.Equal(t, ca.klvUnit, klvUnit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzDecoder(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) {
|
||||
d := &Decoder{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
klvUnit, err := d.Decode(&rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Marker: am,
|
||||
SequenceNumber: 17645,
|
||||
},
|
||||
Payload: a,
|
||||
})
|
||||
|
||||
if errors.Is(err, ErrMorePacketsNeeded) {
|
||||
klvUnit, err = d.Decode(&rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Marker: bm,
|
||||
SequenceNumber: 17646,
|
||||
},
|
||||
Payload: b,
|
||||
})
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if len(klvUnit) == 0 {
|
||||
t.Errorf("should not happen")
|
||||
}
|
||||
|
||||
for _, nalu := range klvUnit {
|
||||
if len(nalu) == 0 {
|
||||
t.Errorf("should not happen")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
129
pkg/format/rtpklv/encoder.go
Normal file
129
pkg/format/rtpklv/encoder.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package rtpklv
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const (
|
||||
rtpVersion = 2
|
||||
defaultPayloadMaxSize = 1450 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) - 10 (SRTP overhead)
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Encoder is a RTP/KLV encoder.
|
||||
// Specification: https://datatracker.ietf.org/doc/html/rfc6597
|
||||
type Encoder struct {
|
||||
// payload type of packets.
|
||||
PayloadType uint8
|
||||
|
||||
// SSRC of packets (optional).
|
||||
// It defaults to a random value.
|
||||
SSRC *uint32
|
||||
|
||||
// initial sequence number of packets (optional).
|
||||
// It defaults to a random value.
|
||||
InitialSequenceNumber *uint16
|
||||
|
||||
// maximum size of packet payloads (optional).
|
||||
// It defaults to 1450.
|
||||
PayloadMaxSize int
|
||||
|
||||
sequenceNumber uint16
|
||||
}
|
||||
|
||||
// Init initializes the encoder.
|
||||
func (e *Encoder) Init() error {
|
||||
if e.SSRC == nil {
|
||||
v, err := randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.SSRC = &v
|
||||
}
|
||||
if e.InitialSequenceNumber == nil {
|
||||
v, err := randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v2 := uint16(v)
|
||||
e.InitialSequenceNumber = &v2
|
||||
}
|
||||
if e.PayloadMaxSize == 0 {
|
||||
e.PayloadMaxSize = defaultPayloadMaxSize
|
||||
}
|
||||
|
||||
e.sequenceNumber = *e.InitialSequenceNumber
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes a KLV unit into RTP packets.
|
||||
func (e *Encoder) Encode(unit [][]byte) ([]*rtp.Packet, error) {
|
||||
size := 0
|
||||
for _, item := range unit {
|
||||
size += len(item)
|
||||
}
|
||||
|
||||
encodedUnit := make([]byte, size)
|
||||
n := 0
|
||||
for _, item := range unit {
|
||||
n += copy(encodedUnit[n:], item)
|
||||
}
|
||||
|
||||
var packets []*rtp.Packet
|
||||
|
||||
if len(encodedUnit) <= e.PayloadMaxSize {
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: rtpVersion,
|
||||
PayloadType: e.PayloadType,
|
||||
SequenceNumber: e.sequenceNumber,
|
||||
SSRC: *e.SSRC,
|
||||
Marker: true, // Single packet, so this is the last (and only) packet
|
||||
},
|
||||
Payload: encodedUnit,
|
||||
}
|
||||
e.sequenceNumber++
|
||||
return []*rtp.Packet{pkt}, nil
|
||||
}
|
||||
|
||||
// KLV unit needs to be fragmented across multiple packets
|
||||
offset := 0
|
||||
for offset < len(encodedUnit) {
|
||||
// Calculate payload size for this packet
|
||||
payloadSize := e.PayloadMaxSize
|
||||
if offset+payloadSize > len(encodedUnit) {
|
||||
payloadSize = len(encodedUnit) - offset
|
||||
}
|
||||
|
||||
// Determine if this is the last packet
|
||||
isLast := (offset + payloadSize) >= len(encodedUnit)
|
||||
|
||||
// Create the packet
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: rtpVersion,
|
||||
PayloadType: e.PayloadType,
|
||||
SequenceNumber: e.sequenceNumber,
|
||||
SSRC: *e.SSRC,
|
||||
Marker: isLast, // Set marker bit only on the last packet
|
||||
},
|
||||
Payload: encodedUnit[offset : offset+payloadSize],
|
||||
}
|
||||
|
||||
packets = append(packets, pkt)
|
||||
e.sequenceNumber++
|
||||
offset += payloadSize
|
||||
}
|
||||
|
||||
return packets, nil
|
||||
}
|
156
pkg/format/rtpklv/encoder_test.go
Normal file
156
pkg/format/rtpklv/encoder_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package rtpklv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uint32Ptr(v uint32) *uint32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func uint16Ptr(v uint16) *uint16 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func mergeBytes(vals ...[]byte) []byte {
|
||||
size := 0
|
||||
for _, v := range vals {
|
||||
size += len(v)
|
||||
}
|
||||
res := make([]byte, size)
|
||||
|
||||
pos := 0
|
||||
for _, v := range vals {
|
||||
n := copy(res[pos:], v)
|
||||
pos += n
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
var cases = []struct {
|
||||
name string
|
||||
klvUnit [][]byte
|
||||
pkts []*rtp.Packet
|
||||
}{
|
||||
{
|
||||
"single",
|
||||
[][]byte{
|
||||
{
|
||||
0x06, 0x0e, 0x2b, 0x34, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x03, // Length = 3
|
||||
0x41, 0x42, 0x43, // "ABC"
|
||||
},
|
||||
{
|
||||
0x06, 0x0e, 0x2b, 0x34, 0x02, 0x02, 0x02, 0x02,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x03, // Length = 3
|
||||
0x44, 0x45, 0x46, // "DEF"
|
||||
},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x06, 0x0e, 0x2b, 0x34, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x03, 0x41, 0x42, 0x43, 0x06, 0x0e, 0x2b, 0x34,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x02, 0x02, 0x02, 0x02, 0x03, 0x44, 0x45, 0x46,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented",
|
||||
[][]byte{
|
||||
append(
|
||||
[]byte{
|
||||
0x06, 0x0e, 0x2b, 0x34, 0x02, 0x02, 0x02, 0x02,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0b10000001, 240, // Length = 240
|
||||
},
|
||||
bytes.Repeat([]byte{1, 2, 3, 4}, 240/4)...,
|
||||
),
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{
|
||||
0x06, 0x0e, 0x2b, 0x34, 0x02, 0x02, 0x02, 0x02,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x81, 0xf0,
|
||||
},
|
||||
bytes.Repeat([]byte{1, 2, 3, 4}, 182/4),
|
||||
[]byte{1, 2},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02,
|
||||
0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
for _, ca := range cases {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
e := &Encoder{
|
||||
PayloadType: 96,
|
||||
SSRC: uint32Ptr(0x9dbb7812),
|
||||
InitialSequenceNumber: uint16Ptr(0x44ed),
|
||||
PayloadMaxSize: 200,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := e.Encode(ca.klvUnit)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.pkts, pkts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeRandomInitialState(t *testing.T) {
|
||||
e := &Encoder{
|
||||
PayloadType: 96,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, nil, e.SSRC)
|
||||
require.NotEqual(t, nil, e.InitialSequenceNumber)
|
||||
}
|
2
pkg/format/rtpklv/rtpklv.go
Normal file
2
pkg/format/rtpklv/rtpklv.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package rtpklv contains a RTP decoder and encoder for KLV data.
|
||||
package rtpklv
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/0b807280d1f6dd3b
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/0b807280d1f6dd3b
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x06\x0e+4000000000000\x88%%%%\x88000000")
|
||||
bool(true)
|
||||
[]byte("\x06")
|
||||
bool(true)
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/43d5b963b118a444
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/43d5b963b118a444
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x06\x0e+4")
|
||||
bool(true)
|
||||
[]byte("0")
|
||||
bool(true)
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/4b821f8269e67265
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/4b821f8269e67265
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x06\x0e+4000000000000\x00")
|
||||
bool(false)
|
||||
[]byte("0")
|
||||
bool(false)
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/58629cc1b39aefc0
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/58629cc1b39aefc0
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x06\x0e+400000000000")
|
||||
bool(false)
|
||||
[]byte("0")
|
||||
bool(true)
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/5c3924785d154da4
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/5c3924785d154da4
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("0")
|
||||
bool(false)
|
||||
[]byte("")
|
||||
bool(false)
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/7b035722d1aef450
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/7b035722d1aef450
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x06\x0e+4000000000000\x88\x88000000")
|
||||
bool(false)
|
||||
[]byte("0")
|
||||
bool(true)
|
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/fa7a29fa0f39e09f
vendored
Normal file
5
pkg/format/rtpklv/testdata/fuzz/FuzzDecoder/fa7a29fa0f39e09f
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("\x06\x0e+4")
|
||||
bool(false)
|
||||
[]byte("000000000000\xc5")
|
||||
bool(true)
|
Reference in New Issue
Block a user