add MP4A-LATM decoder and encoder (#299)

This commit is contained in:
Alessandro Ros
2023-06-01 20:07:47 +02:00
committed by GitHub
parent 84048960b4
commit 80cf861ec7
87 changed files with 1795 additions and 699 deletions

View File

@@ -43,9 +43,9 @@ Features:
* Generate RTCP sender reports
* Utilities
* Parse RTSP elements
* Encode/decode format-specific frames into/from RTP packets. The following formats are supported:
* Encode/decode codec-specific frames into/from RTP packets. The following codecs are supported:
* Video: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), M-JPEG
* Audio: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G722, G711 (PCMA, PCMU), LPCM
* Audio: Opus, MPEG-4 Audio (AAC) (two formats: MPEG4-Generic and MP4A-LATM), MPEG-1/2 Audio (MP3), G722, G711 (PCMA, PCMU), LPCM
## Table of contents

View File

@@ -44,7 +44,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -44,7 +44,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -71,7 +71,10 @@ func main() {
}
// setup RTP/H264 -> H264 decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup H264 -> raw frames decoder
h264RawDec, err := newH264Decoder()

View File

@@ -45,7 +45,10 @@ func main() {
}
// setup RTP/H264 -> H264 decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup H264 -> MPEG-TS muxer
mpegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS)

View File

@@ -48,7 +48,10 @@ func main() {
}
// setup RTP/H264 -> H264 decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup H264 -> raw frames decoder
h264RawDec, err := newH264Decoder()

View File

@@ -45,7 +45,10 @@ func main() {
}
// setup RTP/H265 -> H265 decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -44,7 +44,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -48,7 +48,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -44,7 +44,10 @@ func main() {
}
// setup RTP/MPEG4-audio -> MPEG4-audio decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup MPEG4-audio -> MPEG-TS muxer
mpegtsMuxer, err := newMPEGTSMuxer(forma.Config)

View File

@@ -44,7 +44,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -44,7 +44,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -45,7 +45,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -45,7 +45,10 @@ func main() {
}
// create decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup a single media
_, err = c.Setup(medi, baseURL, 0, 0)

View File

@@ -76,7 +76,10 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
}
// setup RTP/H264 -> H264 decoder
rtpDec := forma.CreateDecoder()
rtpDec, err := forma.CreateDecoder2()
if err != nil {
panic(err)
}
// setup H264 -> MPEGTS muxer
mpegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS)

View File

@@ -98,17 +98,43 @@ func (f *AV1) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *AV1) CreateDecoder() *rtpav1.Decoder {
d := &rtpav1.Decoder{}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *AV1) CreateDecoder2() (*rtpav1.Decoder, error) {
d := &rtpav1.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.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *AV1) CreateEncoder() *rtpav1.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *AV1) CreateEncoder2() (*rtpav1.Encoder, error) {
e := &rtpav1.Encoder{
PayloadType: f.PayloadTyp,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -19,12 +19,16 @@ func TestAV1Attributes(t *testing.T) {
func TestAV1DecEncoder(t *testing.T) {
format := &AV1{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts)

View File

@@ -281,7 +281,7 @@ var casesFormat = []struct {
PayloadTyp: 96,
ProfileLevelID: 1,
Bitrate: intPtr(64000),
CPresent: boolPtr(false),
CPresent: false,
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
@@ -319,7 +319,7 @@ var casesFormat = []struct {
&MPEG4AudioLATM{
PayloadTyp: 110,
ProfileLevelID: 15,
CPresent: boolPtr(false),
CPresent: false,
SBREnabled: boolPtr(true),
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
@@ -358,7 +358,7 @@ var casesFormat = []struct {
&MPEG4AudioLATM{
PayloadTyp: 110,
ProfileLevelID: 44,
CPresent: boolPtr(false),
CPresent: false,
SBREnabled: boolPtr(true),
Bitrate: intPtr(64000),
Config: &mpeg4audio.StreamMuxConfig{
@@ -401,7 +401,7 @@ var casesFormat = []struct {
PayloadTyp: 110,
ProfileLevelID: 48,
Bitrate: intPtr(64000),
CPresent: boolPtr(false),
CPresent: false,
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
@@ -438,7 +438,7 @@ var casesFormat = []struct {
&MPEG4AudioLATM{
PayloadTyp: 110,
ProfileLevelID: 30,
CPresent: boolPtr(false),
CPresent: false,
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
@@ -911,17 +911,7 @@ func TestUnmarshalMPEG4AudioLATMErrors(t *testing.T) {
require.Error(t, err)
_, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{
"object": "aaa",
})
require.Error(t, err)
_, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{
"object": "120",
})
require.Error(t, err)
_, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{
"cpresent": "aaa",
"cpresent": "0",
})
require.Error(t, err)
@@ -930,11 +920,6 @@ func TestUnmarshalMPEG4AudioLATMErrors(t *testing.T) {
})
require.Error(t, err)
_, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{
"sbr-enabled": "aaa",
})
require.Error(t, err)
_, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{
"profile-level-id": "15",
"object": "2",

View File

@@ -55,20 +55,46 @@ func (f *G711) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *G711) CreateDecoder() *rtpsimpleaudio.Decoder {
d := &rtpsimpleaudio.Decoder{
SampleRate: 8000,
}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *G711) CreateDecoder2() (*rtpsimpleaudio.Decoder, error) {
d := &rtpsimpleaudio.Decoder{
SampleRate: 8000,
}
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *G711) CreateEncoder() *rtpsimpleaudio.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *G711) CreateEncoder2() (*rtpsimpleaudio.Encoder, error) {
e := &rtpsimpleaudio.Encoder{
PayloadType: f.PayloadType(),
SampleRate: 8000,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -23,12 +23,16 @@ func TestG711Attributes(t *testing.T) {
func TestG711DecEncoder(t *testing.T) {
format := &G711{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkt.PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkt)
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)

View File

@@ -45,20 +45,46 @@ func (f *G722) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *G722) CreateDecoder() *rtpsimpleaudio.Decoder {
d := &rtpsimpleaudio.Decoder{
SampleRate: 8000,
}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *G722) CreateDecoder2() (*rtpsimpleaudio.Decoder, error) {
d := &rtpsimpleaudio.Decoder{
SampleRate: 8000,
}
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *G722) CreateEncoder() *rtpsimpleaudio.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *G722) CreateEncoder2() (*rtpsimpleaudio.Encoder, error) {
e := &rtpsimpleaudio.Encoder{
PayloadType: 9,
SampleRate: 8000,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -17,12 +17,16 @@ func TestG722Attributes(t *testing.T) {
func TestG722DecEncoder(t *testing.T) {
format := &G722{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkt.PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkt)
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)

View File

@@ -170,22 +170,48 @@ func (f *H264) PTSEqualsDTS(pkt *rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *H264) CreateDecoder() *rtph264.Decoder {
d := &rtph264.Decoder{
PacketizationMode: f.PacketizationMode,
}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *H264) CreateDecoder2() (*rtph264.Decoder, error) {
d := &rtph264.Decoder{
PacketizationMode: f.PacketizationMode,
}
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *H264) CreateEncoder() *rtph264.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *H264) CreateEncoder2() (*rtph264.Encoder, error) {
e := &rtph264.Encoder{
PayloadType: f.PayloadTyp,
PacketizationMode: f.PacketizationMode,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}
// SafeSetParams sets the codec parameters.

View File

@@ -47,12 +47,16 @@ func TestH264PTSEqualsDTS(t *testing.T) {
func TestH264DecEncoder(t *testing.T) {
format := &H264{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts)

View File

@@ -109,22 +109,48 @@ func (f *H265) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *H265) CreateDecoder() *rtph265.Decoder {
d := &rtph265.Decoder{
MaxDONDiff: f.MaxDONDiff,
}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *H265) CreateDecoder2() (*rtph265.Decoder, error) {
d := &rtph265.Decoder{
MaxDONDiff: f.MaxDONDiff,
}
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *H265) CreateEncoder() *rtph265.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *H265) CreateEncoder2() (*rtph265.Encoder, error) {
e := &rtph265.Encoder{
PayloadType: f.PayloadTyp,
MaxDONDiff: f.MaxDONDiff,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}
// SafeSetParams sets the codec parameters.

View File

@@ -34,12 +34,16 @@ func TestH265Attributes(t *testing.T) {
func TestH265DecEncoder(t *testing.T) {
format := &H265{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts)

View File

@@ -97,24 +97,50 @@ func (f *LPCM) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *LPCM) CreateDecoder() *rtplpcm.Decoder {
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *LPCM) CreateDecoder2() (*rtplpcm.Decoder, error) {
d := &rtplpcm.Decoder{
BitDepth: f.BitDepth,
SampleRate: f.SampleRate,
ChannelCount: f.ChannelCount,
}
d.Init()
return d
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *LPCM) CreateEncoder() *rtplpcm.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *LPCM) CreateEncoder2() (*rtplpcm.Encoder, error) {
e := &rtplpcm.Encoder{
PayloadType: f.PayloadTyp,
BitDepth: f.BitDepth,
SampleRate: f.SampleRate,
ChannelCount: f.ChannelCount,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -27,12 +27,16 @@ func TestLPCMDecEncoder(t *testing.T) {
ChannelCount: 2,
}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)

View File

@@ -45,15 +45,41 @@ func (f *MJPEG) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *MJPEG) CreateDecoder() *rtpmjpeg.Decoder {
d := &rtpmjpeg.Decoder{}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *MJPEG) CreateDecoder2() (*rtpmjpeg.Decoder, error) {
d := &rtpmjpeg.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.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *MJPEG) CreateEncoder() *rtpmjpeg.Encoder {
e := &rtpmjpeg.Encoder{}
e.Init()
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *MJPEG) CreateEncoder2() (*rtpmjpeg.Encoder, error) {
e := &rtpmjpeg.Encoder{}
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -278,12 +278,16 @@ func TestMJPEGDecEncoder(t *testing.T) {
0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9,
}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode(b, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
var byts []byte
for _, pkt := range pkts {
byts, _, _ = dec.Decode(pkt)

View File

@@ -45,15 +45,41 @@ func (f *MPEG2Audio) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *MPEG2Audio) CreateDecoder() *rtpmpeg2audio.Decoder {
d := &rtpmpeg2audio.Decoder{}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *MPEG2Audio) CreateDecoder2() (*rtpmpeg2audio.Decoder, error) {
d := &rtpmpeg2audio.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.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *MPEG2Audio) CreateEncoder() *rtpmpeg2audio.Encoder {
e := &rtpmpeg2audio.Encoder{}
e.Init()
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *MPEG2Audio) CreateEncoder2() (*rtpmpeg2audio.Encoder, error) {
e := &rtpmpeg2audio.Encoder{}
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -17,7 +17,9 @@ func TestMPEG2AudioAttributes(t *testing.T) {
func TestMPEG2AudioDecEncoder(t *testing.T) {
format := &MPEG2Audio{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{
0xff, 0xfb, 0x14, 0x64, 0x00, 0x0f, 0xf0, 0x00,
0x00, 0x69, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
@@ -35,7 +37,9 @@ func TestMPEG2AudioDecEncoder(t *testing.T) {
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, [][]byte{{

View File

@@ -6,10 +6,10 @@ import (
"strconv"
"strings"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
)
// MPEG4Audio is an alias for MPEG4AudioGeneric.
@@ -170,19 +170,40 @@ func (f *MPEG4AudioGeneric) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *MPEG4AudioGeneric) CreateDecoder() *rtpmpeg4audio.Decoder {
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *MPEG4AudioGeneric) CreateDecoder2() (*rtpmpeg4audio.Decoder, error) {
d := &rtpmpeg4audio.Decoder{
SampleRate: f.Config.SampleRate,
SizeLength: f.SizeLength,
IndexLength: f.IndexLength,
IndexDeltaLength: f.IndexDeltaLength,
}
d.Init()
return d
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *MPEG4AudioGeneric) CreateEncoder() *rtpmpeg4audio.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *MPEG4AudioGeneric) CreateEncoder2() (*rtpmpeg4audio.Encoder, error) {
e := &rtpmpeg4audio.Encoder{
PayloadType: f.PayloadTyp,
SampleRate: f.Config.SampleRate,
@@ -190,6 +211,11 @@ func (f *MPEG4AudioGeneric) CreateEncoder() *rtpmpeg4audio.Encoder {
IndexLength: f.IndexLength,
IndexDeltaLength: f.IndexDeltaLength,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -39,12 +39,16 @@ func TestMPEG4AudioGenericDecEncoder(t *testing.T) {
IndexDeltaLength: 3,
}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts)

View File

@@ -7,6 +7,8 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiolatm"
)
// MPEG4AudioLATM is a RTP format that uses a MPEG-4 Audio codec.
@@ -15,7 +17,7 @@ type MPEG4AudioLATM struct {
PayloadTyp uint8
ProfileLevelID int
Bitrate *int
CPresent *bool
CPresent bool
Config *mpeg4audio.StreamMuxConfig
SBREnabled *bool
}
@@ -25,7 +27,10 @@ func (f *MPEG4AudioLATM) unmarshal(
_ string, fmtp map[string]string,
) error {
f.PayloadTyp = payloadType
f.ProfileLevelID = 30 // default value defined by specification
// default value set by specification
f.ProfileLevelID = 30
f.CPresent = true
for key, val := range fmtp {
switch key {
@@ -47,8 +52,7 @@ func (f *MPEG4AudioLATM) unmarshal(
f.Bitrate = &v
case "cpresent":
v := (val == "1")
f.CPresent = &v
f.CPresent = (val == "1")
case "config":
enc, err := hex.DecodeString(val)
@@ -68,9 +72,15 @@ func (f *MPEG4AudioLATM) unmarshal(
}
}
if f.CPresent {
if f.Config != nil {
return fmt.Errorf("config and cpresent can't be used at the same time")
}
} else {
if f.Config == nil {
return fmt.Errorf("config is missing")
}
}
return nil
}
@@ -125,13 +135,11 @@ func (f *MPEG4AudioLATM) FMTP() map[string]string {
fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10)
}
if f.CPresent != nil {
if *f.CPresent {
if f.CPresent {
fmtp["cpresent"] = "1"
} else {
fmtp["cpresent"] = "0"
}
}
if f.SBREnabled != nil {
if *f.SBREnabled {
@@ -148,3 +156,48 @@ func (f *MPEG4AudioLATM) FMTP() map[string]string {
func (f *MPEG4AudioLATM) PTSEqualsDTS(*rtp.Packet) bool {
return true
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *MPEG4AudioLATM) CreateDecoder() *rtpmpeg4audiolatm.Decoder {
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *MPEG4AudioLATM) CreateDecoder2() (*rtpmpeg4audiolatm.Decoder, error) {
d := &rtpmpeg4audiolatm.Decoder{
Config: f.Config,
}
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *MPEG4AudioLATM) CreateEncoder() *rtpmpeg4audiolatm.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *MPEG4AudioLATM) CreateEncoder2() (*rtpmpeg4audiolatm.Encoder, error) {
e := &rtpmpeg4audiolatm.Encoder{
PayloadType: f.PayloadTyp,
Config: f.Config,
}
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -29,3 +29,36 @@ func TestMPEG4AudioLATMAttributes(t *testing.T) {
require.Equal(t, 44100, format.ClockRate())
require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{}))
}
func TestMPEG4AudioLATMDecEncoder(t *testing.T) {
format := &MPEG4AudioLATM{
PayloadTyp: 96,
ProfileLevelID: 1,
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
AudioSpecificConfig: &mpeg4audio.Config{
Type: 2,
SampleRate: 48000,
ChannelCount: 2,
},
LatmBufferFullness: 255,
}},
}},
},
}
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)
}

View File

@@ -87,17 +87,43 @@ func (f *MPEG4VideoES) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *MPEG4VideoES) CreateDecoder() *rtpmpeg4video.Decoder {
d := &rtpmpeg4video.Decoder{}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *MPEG4VideoES) CreateDecoder2() (*rtpmpeg4video.Decoder, error) {
d := &rtpmpeg4video.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.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *MPEG4VideoES) CreateEncoder() *rtpmpeg4video.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *MPEG4VideoES) CreateEncoder2() (*rtpmpeg4video.Encoder, error) {
e := &rtpmpeg4video.Encoder{
PayloadType: f.PayloadTyp,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -23,12 +23,16 @@ func TestMPEG4VideoESDecEncoder(t *testing.T) {
PayloadTyp: 96,
}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)

View File

@@ -87,20 +87,46 @@ func (f *Opus) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *Opus) CreateDecoder() *rtpsimpleaudio.Decoder {
d := &rtpsimpleaudio.Decoder{
SampleRate: 48000,
}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *Opus) CreateDecoder2() (*rtpsimpleaudio.Decoder, error) {
d := &rtpsimpleaudio.Decoder{
SampleRate: 48000,
}
err := d.Init()
if err != nil {
return nil, err
}
return d, nil
}
// CreateEncoder creates an encoder able to encode the content of the format.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *Opus) CreateEncoder() *rtpsimpleaudio.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *Opus) CreateEncoder2() (*rtpsimpleaudio.Encoder, error) {
e := &rtpsimpleaudio.Encoder{
PayloadType: f.PayloadTyp,
SampleRate: 48000,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -20,12 +20,16 @@ func TestOpusAttributes(t *testing.T) {
func TestOpusDecEncoder(t *testing.T) {
format := &Opus{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkt.PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkt)
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)

View File

@@ -45,8 +45,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(90000)
return nil
}
// Decode decodes OBUs from a RTP packet.

View File

@@ -47,7 +47,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -66,6 +66,7 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp)
return nil
}
// Encode encodes OBUs into RTP packets.

View File

@@ -49,16 +49,17 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
if d.PacketizationMode >= 2 {
return fmt.Errorf("PacketizationMode >= 2 is not supported")
}
d.timeDecoder = rtptime.NewDecoder(rtpClockRate)
return nil
}
// Decode decodes NALUs from a RTP packet.
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
if d.PacketizationMode >= 2 {
return nil, 0, fmt.Errorf("PacketizationMode >= 2 is not supported")
}
if len(pkt.Payload) < 1 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("payload is too short")
@@ -113,7 +114,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
}
nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
d.fragments = d.fragments[:0]
case h264.NALUTypeSTAPA:

View File

@@ -21,15 +21,16 @@ func TestDecode(t *testing.T) {
clone := pkt.Clone()
addNALUs, _, err := d.Decode(pkt)
// test input integrity
require.Equal(t, clone, pkt)
if err == ErrMorePacketsNeeded {
continue
}
require.NoError(t, err)
nalus = append(nalus, addNALUs...)
// test input integrity
require.Equal(t, clone, pkt)
}
require.Equal(t, ca.nalus, nalus)

View File

@@ -50,7 +50,11 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.PacketizationMode >= 2 {
return fmt.Errorf("PacketizationMode >= 2 is not supported")
}
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -69,14 +73,11 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp)
return nil
}
// Encode encodes NALUs into RTP/H264 packets.
func (e *Encoder) Encode(nalus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
if e.PacketizationMode >= 2 {
return nil, fmt.Errorf("PacketizationMode >= 2 is not supported")
}
var rets []*rtp.Packet
var batch [][]byte

View File

@@ -47,16 +47,17 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
if d.MaxDONDiff != 0 {
return fmt.Errorf("MaxDONDiff != 0 is not supported (yet)")
}
d.timeDecoder = rtptime.NewDecoder(rtpClockRate)
return nil
}
// Decode decodes NALUs from a RTP packet.
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
if d.MaxDONDiff != 0 {
return nil, 0, fmt.Errorf("MaxDONDiff != 0 is not supported (yet)")
}
if len(pkt.Payload) < 2 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("payload is too short")
@@ -143,7 +144,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
}
nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
d.fragments = d.fragments[:0]
case h265.NALUType_PACI:

View File

@@ -20,15 +20,16 @@ func TestDecode(t *testing.T) {
clone := pkt.Clone()
addNALUs, _, err := d.Decode(pkt)
// test input integrity
require.Equal(t, clone, pkt)
if err == ErrMorePacketsNeeded {
continue
}
require.NoError(t, err)
nalus = append(nalus, addNALUs...)
// test input integrity
require.Equal(t, clone, pkt)
}
require.Equal(t, ca.nalus, nalus)

View File

@@ -50,7 +50,11 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.MaxDONDiff != 0 {
return fmt.Errorf("MaxDONDiff != 0 is not supported (yet)")
}
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -69,14 +73,11 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp)
return nil
}
// Encode encodes NALUs into RTP/H265 packets.
func (e *Encoder) Encode(nalus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
if e.MaxDONDiff != 0 {
return nil, fmt.Errorf("MaxDONDiff != 0 is not supported (yet)")
}
var rets []*rtp.Packet
var batch [][]byte

View File

@@ -21,9 +21,10 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(d.SampleRate)
d.sampleSize = d.BitDepth * d.ChannelCount / 8
return nil
}
// Decode decodes audio samples from a RTP packet.

View File

@@ -53,7 +53,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -74,6 +74,16 @@ func (e *Encoder) Init() {
e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp)
e.sampleSize = e.BitDepth * e.ChannelCount / 8
e.maxPayloadSize = (e.PayloadMaxSize / e.sampleSize) * e.sampleSize
return nil
}
func (e *Encoder) packetCount(slen int) int {
n := (slen / e.maxPayloadSize)
if (slen % e.maxPayloadSize) != 0 {
n++
}
return n
}
// Encode encodes audio samples into RTP packets.
@@ -83,12 +93,8 @@ func (e *Encoder) Encode(samples []byte, pts time.Duration) ([]*rtp.Packet, erro
return nil, fmt.Errorf("invalid samples")
}
n := (slen / e.maxPayloadSize)
if (slen % e.maxPayloadSize) != 0 {
n++
}
ret := make([]*rtp.Packet, n)
packetCount := e.packetCount(slen)
ret := make([]*rtp.Packet, packetCount)
i := 0
pos := 0
payloadSize := e.maxPayloadSize

View File

@@ -115,8 +115,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(rtpClockRate)
return nil
}
// Decode decodes an image from a RTP packet.
@@ -181,7 +182,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
}
data := joinFragments(d.fragments, d.fragmentsSize)
d.fragments = d.fragments[:0]
d.fragmentsSize = 0

View File

@@ -47,7 +47,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -66,6 +66,7 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp)
return nil
}
// Encode encodes an image into RTP/M-JPEG packets.

View File

@@ -41,8 +41,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(90000)
return nil
}
// Decode decodes frames from a RTP packet.
@@ -124,7 +125,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
}
frames = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
d.fragments = d.fragments[:0]
d.fragmentsSize = 0
}

View File

@@ -52,7 +52,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -71,6 +71,7 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp)
return nil
}
// Encode encodes frames into RTP packets.

View File

@@ -2,240 +2,12 @@ package rtpmpeg4audio
import (
"errors"
"fmt"
"time"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/rtptime"
"github.com/bluenviron/mediacommon/pkg/bits"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiogeneric"
)
// ErrMorePacketsNeeded is returned when more packets are needed.
// ErrMorePacketsNeeded is an alis for rtpmpeg4audiogeneric.ErrMorePacketsNeeded.
var ErrMorePacketsNeeded = errors.New("need more packets")
func joinFragments(fragments [][]byte, size int) []byte {
ret := make([]byte, size)
n := 0
for _, p := range fragments {
n += copy(ret[n:], p)
}
return ret
}
// Decoder is a RTP/MPEG-4 Audio decoder.
// Specification: https://datatracker.ietf.org/doc/html/rfc3640
type Decoder struct {
// sample rate of input packets.
SampleRate int
// The number of bits in which the AU-size field is encoded in the AU-header.
SizeLength int
// The number of bits in which the AU-Index is encoded in the first AU-header.
IndexLength int
// The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header.
IndexDeltaLength int
timeDecoder *rtptime.Decoder
firstAUParsed bool
adtsMode bool
fragments [][]byte
fragmentsSize int
}
// Init initializes the decoder.
func (d *Decoder) Init() {
d.timeDecoder = rtptime.NewDecoder(d.SampleRate)
}
// Decode decodes AUs from a RTP packet.
// It returns the AUs and the PTS of the first AU.
// The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate.
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
if len(pkt.Payload) < 2 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("payload is too short")
}
// AU-headers-length (16 bits)
headersLen := int(uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1]))
if headersLen == 0 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("invalid AU-headers-length")
}
payload := pkt.Payload[2:]
// AU-headers
dataLens, err := d.readAUHeaders(payload, headersLen)
if err != nil {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, err
}
pos := (headersLen / 8)
if (headersLen % 8) != 0 {
pos++
}
payload = payload[pos:]
var aus [][]byte
if len(d.fragments) == 0 {
if pkt.Marker {
// AUs
aus = make([][]byte, len(dataLens))
for i, dataLen := range dataLens {
if len(payload) < int(dataLen) {
return nil, 0, fmt.Errorf("payload is too short")
}
aus[i] = payload[:dataLen]
payload = payload[dataLen:]
}
} else {
if len(dataLens) != 1 {
return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
}
if len(payload) < int(dataLens[0]) {
return nil, 0, fmt.Errorf("payload is too short")
}
d.fragmentsSize = int(dataLens[0])
d.fragments = append(d.fragments, payload[:dataLens[0]])
return nil, 0, ErrMorePacketsNeeded
}
} else {
// we are decoding a fragmented AU
if len(dataLens) != 1 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
}
if len(payload) < int(dataLens[0]) {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("payload is too short")
}
d.fragmentsSize += int(dataLens[0])
if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("AU size (%d) is too big, maximum is %d", d.fragmentsSize, mpeg4audio.MaxAccessUnitSize)
}
d.fragments = append(d.fragments, payload[:dataLens[0]])
if !pkt.Marker {
return nil, 0, ErrMorePacketsNeeded
}
aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
d.fragments = d.fragments[:0]
}
aus, err = d.removeADTS(aus)
if err != nil {
return nil, 0, err
}
return aus, d.timeDecoder.Decode(pkt.Timestamp), nil
}
func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) {
firstRead := false
count := 0
for i := 0; i < headersLen; {
if i == 0 {
i += d.SizeLength
i += d.IndexLength
} else {
i += d.SizeLength
i += d.IndexDeltaLength
}
count++
}
dataLens := make([]uint64, count)
pos := 0
i := 0
for headersLen > 0 {
dataLen, err := bits.ReadBits(buf, &pos, d.SizeLength)
if err != nil {
return nil, err
}
headersLen -= d.SizeLength
if !firstRead {
firstRead = true
if d.IndexLength > 0 {
auIndex, err := bits.ReadBits(buf, &pos, d.IndexLength)
if err != nil {
return nil, err
}
headersLen -= d.IndexLength
if auIndex != 0 {
return nil, fmt.Errorf("AU-index different than zero is not supported")
}
}
} else if d.IndexDeltaLength > 0 {
auIndexDelta, err := bits.ReadBits(buf, &pos, d.IndexDeltaLength)
if err != nil {
return nil, err
}
headersLen -= d.IndexDeltaLength
if auIndexDelta != 0 {
return nil, fmt.Errorf("AU-index-delta different than zero is not supported")
}
}
dataLens[i] = dataLen
i++
}
return dataLens, nil
}
// some cameras wrap AUs into ADTS
func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) {
if !d.firstAUParsed {
d.firstAUParsed = true
if len(aus) == 1 && len(aus[0]) >= 2 {
if aus[0][0] == 0xFF && (aus[0][1]&0xF0) == 0xF0 {
var pkts mpeg4audio.ADTSPackets
err := pkts.Unmarshal(aus[0])
if err == nil && len(pkts) == 1 {
d.adtsMode = true
aus[0] = pkts[0].AU
}
}
}
} else if d.adtsMode {
if len(aus) != 1 {
return nil, fmt.Errorf("multiple AUs in ADTS mode are not supported")
}
var pkts mpeg4audio.ADTSPackets
err := pkts.Unmarshal(aus[0])
if err != nil {
return nil, fmt.Errorf("unable to decode ADTS: %s", err)
}
if len(pkts) != 1 {
return nil, fmt.Errorf("multiple ADTS packets are not supported")
}
aus[0] = pkts[0].AU
}
return aus, nil
}
// Decoder is an alias for rtpmpeg4audiogeneric.Decoder.
type Decoder = rtpmpeg4audiogeneric.Decoder

View File

@@ -1,269 +1,8 @@
package rtpmpeg4audio
import (
"crypto/rand"
"time"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/rtptime"
"github.com/bluenviron/mediacommon/pkg/bits"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiogeneric"
)
const (
rtpVersion = 2
)
func randUint32() uint32 {
var b [4]byte
rand.Read(b[:])
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
}
// Encoder is a RTP/MPEG4-audio encoder.
// Specification: https://datatracker.ietf.org/doc/html/rfc3640
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
// initial timestamp of packets (optional).
// It defaults to a random value.
InitialTimestamp *uint32
// maximum size of packet payloads (optional).
// It defaults to 1460.
PayloadMaxSize int
// sample rate of packets.
SampleRate int
// The number of bits in which the AU-size field is encoded in the AU-header.
SizeLength int
// The number of bits in which the AU-Index is encoded in the first AU-header.
IndexLength int
// The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header.
IndexDeltaLength int
sequenceNumber uint16
timeEncoder *rtptime.Encoder
}
// Init initializes the encoder.
func (e *Encoder) Init() {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
}
if e.InitialSequenceNumber == nil {
v := uint16(randUint32())
e.InitialSequenceNumber = &v
}
if e.InitialTimestamp == nil {
v := randUint32()
e.InitialTimestamp = &v
}
if e.PayloadMaxSize == 0 {
e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header)
}
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp)
}
// Encode encodes AUs into RTP packets.
func (e *Encoder) Encode(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
var rets []*rtp.Packet
var batch [][]byte
// split AUs into batches
for _, au := range aus {
if e.lenAggregated(batch, au) <= e.PayloadMaxSize {
// add to existing batch
batch = append(batch, au)
} else {
// write last batch
if batch != nil {
pkts, err := e.writeBatch(batch, pts)
if err != nil {
return nil, err
}
rets = append(rets, pkts...)
pts += time.Duration(len(batch)) * mpeg4audio.SamplesPerAccessUnit * time.Second / time.Duration(e.SampleRate)
}
// initialize new batch
batch = [][]byte{au}
}
}
// write last batch
pkts, err := e.writeBatch(batch, pts)
if err != nil {
return nil, err
}
rets = append(rets, pkts...)
return rets, nil
}
func (e *Encoder) writeBatch(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
if len(aus) != 1 || e.lenAggregated(aus, nil) < e.PayloadMaxSize {
return e.writeAggregated(aus, pts)
}
return e.writeFragmented(aus[0], pts)
}
func (e *Encoder) writeFragmented(au []byte, pts time.Duration) ([]*rtp.Packet, error) {
auHeadersLen := e.SizeLength + e.IndexLength
auHeadersLenBytes := auHeadersLen / 8
if (auHeadersLen % 8) != 0 {
auHeadersLenBytes++
}
avail := e.PayloadMaxSize - 2 - auHeadersLenBytes
le := len(au)
packetCount := le / avail
lastPacketSize := le % avail
if lastPacketSize > 0 {
packetCount++
}
ret := make([]*rtp.Packet, packetCount)
encPTS := e.timeEncoder.Encode(pts)
for i := range ret {
var le int
if i != (packetCount - 1) {
le = avail
} else {
le = lastPacketSize
}
payload := make([]byte, 2+auHeadersLenBytes+le)
// AU-headers-length
payload[0] = byte(auHeadersLen >> 8)
payload[1] = byte(auHeadersLen)
// AU-headers
pos := 0
bits.WriteBits(payload[2:], &pos, uint64(le), e.SizeLength)
bits.WriteBits(payload[2:], &pos, 0, e.IndexLength)
// AU
copy(payload[2+auHeadersLenBytes:], au[:le])
au = au[le:]
ret[i] = &rtp.Packet{
Header: rtp.Header{
Version: rtpVersion,
PayloadType: e.PayloadType,
SequenceNumber: e.sequenceNumber,
Timestamp: encPTS,
SSRC: *e.SSRC,
Marker: (i == (packetCount - 1)),
},
Payload: payload,
}
e.sequenceNumber++
}
return ret, nil
}
func (e *Encoder) lenAggregated(aus [][]byte, addAU []byte) int {
ret := 2 // AU-headers-length
// AU-headers
auHeadersLen := 0
i := 0
for range aus {
if i == 0 {
auHeadersLen += e.SizeLength + e.IndexLength
} else {
auHeadersLen += e.SizeLength + e.IndexDeltaLength
}
i++
}
if addAU != nil {
if i == 0 {
auHeadersLen += e.SizeLength + e.IndexLength
} else {
auHeadersLen += e.SizeLength + e.IndexDeltaLength
}
}
ret += auHeadersLen / 8
if (auHeadersLen % 8) != 0 {
ret++
}
// AU
for _, au := range aus {
ret += len(au)
}
ret += len(addAU)
return ret
}
func (e *Encoder) writeAggregated(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
payload := make([]byte, e.lenAggregated(aus, nil))
// AU-headers
written := 0
pos := 0
for i, au := range aus {
bits.WriteBits(payload[2:], &pos, uint64(len(au)), e.SizeLength)
written += e.SizeLength
if i == 0 {
bits.WriteBits(payload[2:], &pos, 0, e.IndexLength)
written += e.IndexLength
} else {
bits.WriteBits(payload[2:], &pos, 0, e.IndexDeltaLength)
written += e.IndexDeltaLength
}
}
pos = 2 + (written / 8)
if (written % 8) != 0 {
pos++
}
// AU-headers-length
payload[0] = byte(written >> 8)
payload[1] = byte(written)
// AUs
for _, au := range aus {
auLen := copy(payload[pos:], au)
pos += auLen
}
pkt := &rtp.Packet{
Header: rtp.Header{
Version: rtpVersion,
PayloadType: e.PayloadType,
SequenceNumber: e.sequenceNumber,
Timestamp: e.timeEncoder.Encode(pts),
SSRC: *e.SSRC,
Marker: true,
},
Payload: payload,
}
e.sequenceNumber++
return []*rtp.Packet{pkt}, nil
}
// Encoder is an alias for rtpmpeg4audiogeneric.Encoder.
type Encoder = rtpmpeg4audiogeneric.Encoder

View File

@@ -0,0 +1,241 @@
package rtpmpeg4audiogeneric
import (
"errors"
"fmt"
"time"
"github.com/bluenviron/mediacommon/pkg/bits"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/rtptime"
)
// ErrMorePacketsNeeded is returned when more packets are needed.
var ErrMorePacketsNeeded = errors.New("need more packets")
func joinFragments(fragments [][]byte, size int) []byte {
ret := make([]byte, size)
n := 0
for _, p := range fragments {
n += copy(ret[n:], p)
}
return ret
}
// Decoder is a RTP/MPEG-4 Audio decoder.
// Specification: https://datatracker.ietf.org/doc/html/rfc3640
type Decoder struct {
// sample rate of input packets.
SampleRate int
// The number of bits in which the AU-size field is encoded in the AU-header.
SizeLength int
// The number of bits in which the AU-Index is encoded in the first AU-header.
IndexLength int
// The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header.
IndexDeltaLength int
timeDecoder *rtptime.Decoder
firstAUParsed bool
adtsMode bool
fragments [][]byte
fragmentsSize int
}
// Init initializes the decoder.
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(d.SampleRate)
return nil
}
// Decode decodes AUs from a RTP packet.
// It returns the AUs and the PTS of the first AU.
// The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate.
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) {
if len(pkt.Payload) < 2 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("payload is too short")
}
// AU-headers-length (16 bits)
headersLen := int(uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1]))
if headersLen == 0 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("invalid AU-headers-length")
}
payload := pkt.Payload[2:]
// AU-headers
dataLens, err := d.readAUHeaders(payload, headersLen)
if err != nil {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, err
}
pos := (headersLen / 8)
if (headersLen % 8) != 0 {
pos++
}
payload = payload[pos:]
var aus [][]byte
if len(d.fragments) == 0 {
if pkt.Marker {
// AUs
aus = make([][]byte, len(dataLens))
for i, dataLen := range dataLens {
if len(payload) < int(dataLen) {
return nil, 0, fmt.Errorf("payload is too short")
}
aus[i] = payload[:dataLen]
payload = payload[dataLen:]
}
} else {
if len(dataLens) != 1 {
return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
}
if len(payload) < int(dataLens[0]) {
return nil, 0, fmt.Errorf("payload is too short")
}
d.fragmentsSize = int(dataLens[0])
d.fragments = append(d.fragments, payload[:dataLens[0]])
return nil, 0, ErrMorePacketsNeeded
}
} else {
// we are decoding a fragmented AU
if len(dataLens) != 1 {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU")
}
if len(payload) < int(dataLens[0]) {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("payload is too short")
}
d.fragmentsSize += int(dataLens[0])
if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("AU size (%d) is too big, maximum is %d", d.fragmentsSize, mpeg4audio.MaxAccessUnitSize)
}
d.fragments = append(d.fragments, payload[:dataLens[0]])
if !pkt.Marker {
return nil, 0, ErrMorePacketsNeeded
}
aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
d.fragments = d.fragments[:0]
}
aus, err = d.removeADTS(aus)
if err != nil {
return nil, 0, err
}
return aus, d.timeDecoder.Decode(pkt.Timestamp), nil
}
func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) {
firstRead := false
count := 0
for i := 0; i < headersLen; {
if i == 0 {
i += d.SizeLength
i += d.IndexLength
} else {
i += d.SizeLength
i += d.IndexDeltaLength
}
count++
}
dataLens := make([]uint64, count)
pos := 0
i := 0
for headersLen > 0 {
dataLen, err := bits.ReadBits(buf, &pos, d.SizeLength)
if err != nil {
return nil, err
}
headersLen -= d.SizeLength
if !firstRead {
firstRead = true
if d.IndexLength > 0 {
auIndex, err := bits.ReadBits(buf, &pos, d.IndexLength)
if err != nil {
return nil, err
}
headersLen -= d.IndexLength
if auIndex != 0 {
return nil, fmt.Errorf("AU-index different than zero is not supported")
}
}
} else if d.IndexDeltaLength > 0 {
auIndexDelta, err := bits.ReadBits(buf, &pos, d.IndexDeltaLength)
if err != nil {
return nil, err
}
headersLen -= d.IndexDeltaLength
if auIndexDelta != 0 {
return nil, fmt.Errorf("AU-index-delta different than zero is not supported")
}
}
dataLens[i] = dataLen
i++
}
return dataLens, nil
}
// some cameras wrap AUs into ADTS
func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) {
if !d.firstAUParsed {
d.firstAUParsed = true
if len(aus) == 1 && len(aus[0]) >= 2 {
if aus[0][0] == 0xFF && (aus[0][1]&0xF0) == 0xF0 {
var pkts mpeg4audio.ADTSPackets
err := pkts.Unmarshal(aus[0])
if err == nil && len(pkts) == 1 {
d.adtsMode = true
aus[0] = pkts[0].AU
}
}
}
} else if d.adtsMode {
if len(aus) != 1 {
return nil, fmt.Errorf("multiple AUs in ADTS mode are not supported")
}
var pkts mpeg4audio.ADTSPackets
err := pkts.Unmarshal(aus[0])
if err != nil {
return nil, fmt.Errorf("unable to decode ADTS: %s", err)
}
if len(pkts) != 1 {
return nil, fmt.Errorf("multiple ADTS packets are not supported")
}
aus[0] = pkts[0].AU
}
return aus, nil
}

View File

@@ -1,4 +1,4 @@
package rtpmpeg4audio
package rtpmpeg4audiogeneric
import (
"testing"
@@ -24,15 +24,16 @@ func TestDecode(t *testing.T) {
clone := pkt.Clone()
addAUs, _, err := d.Decode(pkt)
// test input integrity
require.Equal(t, clone, pkt)
if err == ErrMorePacketsNeeded {
continue
}
require.NoError(t, err)
aus = append(aus, addAUs...)
// test input integrity
require.Equal(t, clone, pkt)
}
require.Equal(t, ca.aus, aus)

View File

@@ -0,0 +1,270 @@
package rtpmpeg4audiogeneric
import (
"crypto/rand"
"time"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/rtptime"
"github.com/bluenviron/mediacommon/pkg/bits"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
)
const (
rtpVersion = 2
)
func randUint32() uint32 {
var b [4]byte
rand.Read(b[:])
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
}
// Encoder is a RTP/MPEG4-audio encoder.
// Specification: https://datatracker.ietf.org/doc/html/rfc3640
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
// initial timestamp of packets (optional).
// It defaults to a random value.
InitialTimestamp *uint32
// maximum size of packet payloads (optional).
// It defaults to 1460.
PayloadMaxSize int
// sample rate of packets.
SampleRate int
// The number of bits in which the AU-size field is encoded in the AU-header.
SizeLength int
// The number of bits in which the AU-Index is encoded in the first AU-header.
IndexLength int
// The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header.
IndexDeltaLength int
sequenceNumber uint16
timeEncoder *rtptime.Encoder
}
// Init initializes the encoder.
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
}
if e.InitialSequenceNumber == nil {
v := uint16(randUint32())
e.InitialSequenceNumber = &v
}
if e.InitialTimestamp == nil {
v := randUint32()
e.InitialTimestamp = &v
}
if e.PayloadMaxSize == 0 {
e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header)
}
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp)
return nil
}
// Encode encodes AUs into RTP packets.
func (e *Encoder) Encode(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
var rets []*rtp.Packet
var batch [][]byte
// split AUs into batches
for _, au := range aus {
if e.lenAggregated(batch, au) <= e.PayloadMaxSize {
// add to existing batch
batch = append(batch, au)
} else {
// write last batch
if batch != nil {
pkts, err := e.writeBatch(batch, pts)
if err != nil {
return nil, err
}
rets = append(rets, pkts...)
pts += time.Duration(len(batch)) * mpeg4audio.SamplesPerAccessUnit * time.Second / time.Duration(e.SampleRate)
}
// initialize new batch
batch = [][]byte{au}
}
}
// write last batch
pkts, err := e.writeBatch(batch, pts)
if err != nil {
return nil, err
}
rets = append(rets, pkts...)
return rets, nil
}
func (e *Encoder) writeBatch(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
if len(aus) != 1 || e.lenAggregated(aus, nil) < e.PayloadMaxSize {
return e.writeAggregated(aus, pts)
}
return e.writeFragmented(aus[0], pts)
}
func (e *Encoder) writeFragmented(au []byte, pts time.Duration) ([]*rtp.Packet, error) {
auHeadersLen := e.SizeLength + e.IndexLength
auHeadersLenBytes := auHeadersLen / 8
if (auHeadersLen % 8) != 0 {
auHeadersLenBytes++
}
avail := e.PayloadMaxSize - 2 - auHeadersLenBytes
le := len(au)
packetCount := le / avail
lastPacketSize := le % avail
if lastPacketSize > 0 {
packetCount++
}
ret := make([]*rtp.Packet, packetCount)
encPTS := e.timeEncoder.Encode(pts)
for i := range ret {
var le int
if i != (packetCount - 1) {
le = avail
} else {
le = lastPacketSize
}
payload := make([]byte, 2+auHeadersLenBytes+le)
// AU-headers-length
payload[0] = byte(auHeadersLen >> 8)
payload[1] = byte(auHeadersLen)
// AU-headers
pos := 0
bits.WriteBits(payload[2:], &pos, uint64(le), e.SizeLength)
bits.WriteBits(payload[2:], &pos, 0, e.IndexLength)
// AU
copy(payload[2+auHeadersLenBytes:], au[:le])
au = au[le:]
ret[i] = &rtp.Packet{
Header: rtp.Header{
Version: rtpVersion,
PayloadType: e.PayloadType,
SequenceNumber: e.sequenceNumber,
Timestamp: encPTS,
SSRC: *e.SSRC,
Marker: (i == (packetCount - 1)),
},
Payload: payload,
}
e.sequenceNumber++
}
return ret, nil
}
func (e *Encoder) lenAggregated(aus [][]byte, addAU []byte) int {
ret := 2 // AU-headers-length
// AU-headers
auHeadersLen := 0
i := 0
for range aus {
if i == 0 {
auHeadersLen += e.SizeLength + e.IndexLength
} else {
auHeadersLen += e.SizeLength + e.IndexDeltaLength
}
i++
}
if addAU != nil {
if i == 0 {
auHeadersLen += e.SizeLength + e.IndexLength
} else {
auHeadersLen += e.SizeLength + e.IndexDeltaLength
}
}
ret += auHeadersLen / 8
if (auHeadersLen % 8) != 0 {
ret++
}
// AU
for _, au := range aus {
ret += len(au)
}
ret += len(addAU)
return ret
}
func (e *Encoder) writeAggregated(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) {
payload := make([]byte, e.lenAggregated(aus, nil))
// AU-headers
written := 0
pos := 0
for i, au := range aus {
bits.WriteBits(payload[2:], &pos, uint64(len(au)), e.SizeLength)
written += e.SizeLength
if i == 0 {
bits.WriteBits(payload[2:], &pos, 0, e.IndexLength)
written += e.IndexLength
} else {
bits.WriteBits(payload[2:], &pos, 0, e.IndexDeltaLength)
written += e.IndexDeltaLength
}
}
pos = 2 + (written / 8)
if (written % 8) != 0 {
pos++
}
// AU-headers-length
payload[0] = byte(written >> 8)
payload[1] = byte(written)
// AUs
for _, au := range aus {
auLen := copy(payload[pos:], au)
pos += auLen
}
pkt := &rtp.Packet{
Header: rtp.Header{
Version: rtpVersion,
PayloadType: e.PayloadType,
SequenceNumber: e.sequenceNumber,
Timestamp: e.timeEncoder.Encode(pts),
SSRC: *e.SSRC,
Marker: true,
},
Payload: payload,
}
e.sequenceNumber++
return []*rtp.Packet{pkt}, nil
}

View File

@@ -1,4 +1,4 @@
package rtpmpeg4audio
package rtpmpeg4audiogeneric
import (
"bytes"

View File

@@ -0,0 +1,2 @@
// Package rtpmpeg4audiogeneric contains a RTP/MPEG-4 Audio decoder and encoder.
package rtpmpeg4audiogeneric

View File

@@ -0,0 +1,116 @@
package rtpmpeg4audiolatm
import (
"errors"
"fmt"
"time"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/rtptime"
)
// ErrMorePacketsNeeded is returned when more packets are needed.
var ErrMorePacketsNeeded = errors.New("need more packets")
func joinFragments(fragments [][]byte, size int) []byte {
ret := make([]byte, size)
n := 0
for _, p := range fragments {
n += copy(ret[n:], p)
}
return ret
}
// Decoder is a RTP/MPEG-4 Audio decoder.
// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3
type Decoder struct {
// StreamMuxConfig.
Config *mpeg4audio.StreamMuxConfig
timeDecoder *rtptime.Decoder
fragments [][]byte
fragmentsSize int
fragmentsExpected int
}
// Init initializes the decoder.
func (d *Decoder) Init() error {
if d.Config == nil || len(d.Config.Programs) != 1 || len(d.Config.Programs[0].Layers) != 1 {
return fmt.Errorf("unsupported StreamMuxConfig")
}
d.timeDecoder = rtptime.NewDecoder(d.Config.Programs[0].Layers[0].AudioSpecificConfig.SampleRate)
return nil
}
func decodePayloadLengthInfo(buf []byte) (int, int, error) {
lb := len(buf)
l := 0
n := 0
for {
if (lb - n) == 0 {
return 0, 0, fmt.Errorf("not enough bytes")
}
b := buf[n]
n++
l += int(b)
if b != 0xFF {
break
}
}
return l, n, nil
}
// Decode decodes an AU from a RTP packet.
// It returns the AU and its PTS.
func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
var au []byte
buf := pkt.Payload
if len(d.fragments) == 0 {
pl, n, err := decodePayloadLengthInfo(buf)
if err != nil {
return nil, 0, err
}
buf = buf[n:]
bl := len(buf)
if pl <= bl {
au = buf[:pl]
// there could be other data, due to otherDataPresent. Ignore it.
} else {
if pl > mpeg4audio.MaxAccessUnitSize {
d.fragments = d.fragments[:0] // discard pending fragments
return nil, 0, fmt.Errorf("AU size (%d) is too big, maximum is %d", pl, mpeg4audio.MaxAccessUnitSize)
}
d.fragments = append(d.fragments, buf)
d.fragmentsSize = pl
d.fragmentsExpected = pl - bl
return nil, 0, ErrMorePacketsNeeded
}
} else {
bl := len(buf)
if d.fragmentsExpected > bl {
d.fragments = append(d.fragments, buf)
d.fragmentsExpected -= bl
return nil, 0, ErrMorePacketsNeeded
}
d.fragments = append(d.fragments, buf[:d.fragmentsExpected])
// there could be other data, due to otherDataPresent. Ignore it.
au = joinFragments(d.fragments, d.fragmentsSize)
d.fragments = d.fragments[:0]
}
return au, d.timeDecoder.Decode(pkt.Timestamp), nil
}

View File

@@ -0,0 +1,84 @@
package rtpmpeg4audiolatm
import (
"testing"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"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{
Config: ca.config,
}
d.Init()
var au []byte
var err error
for _, pkt := range ca.pkts {
clone := pkt.Clone()
au, _, err = d.Decode(pkt)
// test input integrity
require.Equal(t, clone, pkt)
if err == ErrMorePacketsNeeded {
continue
}
require.NoError(t, err)
}
require.Equal(t, ca.au, au)
})
}
}
func FuzzDecoder(f *testing.F) {
f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) {
d := &Decoder{
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
Type: 2,
SampleRate: 48000,
ChannelCount: 2,
},
LatmBufferFullness: 255,
}},
}},
},
}
d.Init()
d.Decode(&rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: am,
PayloadType: 96,
SequenceNumber: 17645,
Timestamp: 2289527317,
SSRC: 0x9dbb7812,
},
Payload: a,
})
d.Decode(&rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: bm,
PayloadType: 96,
SequenceNumber: 17646,
Timestamp: 2289527317,
SSRC: 0x9dbb7812,
},
Payload: b,
})
})
}

View File

@@ -0,0 +1,156 @@
package rtpmpeg4audiolatm
import (
"crypto/rand"
"fmt"
"time"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v3/pkg/rtptime"
)
const (
rtpVersion = 2
)
func randUint32() uint32 {
var b [4]byte
rand.Read(b[:])
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
}
// Encoder is a RTP/MPEG4-audio encoder.
// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3
type Encoder struct {
// payload type of packets.
PayloadType uint8
// StreamMuxConfig.
Config *mpeg4audio.StreamMuxConfig
// 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
// initial timestamp of packets (optional).
// It defaults to a random value.
InitialTimestamp *uint32
// maximum size of packet payloads (optional).
// It defaults to 1460.
PayloadMaxSize int
sequenceNumber uint16
timeEncoder *rtptime.Encoder
}
// Init initializes the encoder.
func (e *Encoder) Init() error {
if e.Config == nil || len(e.Config.Programs) != 1 || len(e.Config.Programs[0].Layers) != 1 {
return fmt.Errorf("unsupported StreamMuxConfig")
}
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
}
if e.InitialSequenceNumber == nil {
v := uint16(randUint32())
e.InitialSequenceNumber = &v
}
if e.InitialTimestamp == nil {
v := randUint32()
e.InitialTimestamp = &v
}
if e.PayloadMaxSize == 0 {
e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header)
}
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(e.Config.Programs[0].Layers[0].AudioSpecificConfig.SampleRate, *e.InitialTimestamp)
return nil
}
func payloadLengthInfoLen(auLen int) int {
return auLen/255 + 1
}
func payloadLengthInfo(plil int, auLen int, buf []byte) {
for i := 0; i < plil; i++ {
buf[i] = 255
}
buf[plil-1] = byte(auLen % 255)
}
func (e *Encoder) packetCount(auLen int, plil int) int {
totalLen := plil + auLen
packetCount := totalLen / e.PayloadMaxSize
lastPacketSize := totalLen % e.PayloadMaxSize
if lastPacketSize > 0 {
packetCount++
}
return packetCount
}
// Encode encodes AUs into RTP packets.
func (e *Encoder) Encode(au []byte, pts time.Duration) ([]*rtp.Packet, error) {
auLen := len(au)
plil := payloadLengthInfoLen(auLen)
packetCount := e.packetCount(auLen, plil)
avail := e.PayloadMaxSize - plil
ret := make([]*rtp.Packet, packetCount)
encPTS := e.timeEncoder.Encode(pts)
for i := range ret {
var final bool
var l int
if len(au) < avail {
l = len(au)
final = true
} else {
l = avail
final = false
}
var payload []byte
if i == 0 {
payload = make([]byte, plil+l)
payloadLengthInfo(plil, auLen, payload)
copy(payload[plil:], au[:l])
} else {
payload = au[:l]
}
ret[i] = &rtp.Packet{
Header: rtp.Header{
Version: rtpVersion,
PayloadType: e.PayloadType,
SequenceNumber: e.sequenceNumber,
Timestamp: encPTS,
SSRC: *e.SSRC,
Marker: final,
},
Payload: payload,
}
e.sequenceNumber++
if final {
break
}
au = au[l:]
avail = e.PayloadMaxSize
}
return ret, nil
}

View File

@@ -0,0 +1,177 @@
package rtpmpeg4audiolatm
import (
"bytes"
"testing"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp"
"github.com/stretchr/testify/require"
)
func uint16Ptr(v uint16) *uint16 {
return &v
}
func uint32Ptr(v uint32) *uint32 {
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
config *mpeg4audio.StreamMuxConfig
au []byte
pkts []*rtp.Packet
}{
{
"single",
&mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
Type: 2,
SampleRate: 48000,
ChannelCount: 2,
},
LatmBufferFullness: 255,
}},
}},
},
[]byte{1, 2, 3, 4},
[]*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 17645,
Timestamp: 2289526357,
SSRC: 2646308882,
},
Payload: []byte{
0x04, 0x01, 0x02, 0x03, 0x04,
},
},
},
},
{
"fragmented",
&mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
Type: 2,
SampleRate: 48000,
ChannelCount: 2,
},
LatmBufferFullness: 255,
}},
}},
},
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 512),
[]*rtp.Packet{
{
Header: rtp.Header{
Version: 2,
Marker: false,
PayloadType: 96,
SequenceNumber: 17645,
Timestamp: 2289526357,
SSRC: 2646308882,
},
Payload: mergeBytes(
bytes.Repeat([]byte{0xff}, 16),
[]byte{0x10},
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 180),
[]byte{0, 1, 2},
),
},
{
Header: rtp.Header{
Version: 2,
Marker: false,
PayloadType: 96,
SequenceNumber: 17646,
Timestamp: 2289526357,
SSRC: 2646308882,
},
Payload: mergeBytes(
[]byte{3, 4, 5, 6, 7},
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 181),
[]byte{0, 1, 2, 3, 4, 5, 6},
),
},
{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 17647,
Timestamp: 2289526357,
SSRC: 2646308882,
},
Payload: mergeBytes(
[]byte{7},
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 149),
),
},
},
},
}
func TestEncode(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
e := &Encoder{
PayloadType: 96,
Config: ca.config,
SSRC: uint32Ptr(0x9dbb7812),
InitialSequenceNumber: uint16Ptr(0x44ed),
InitialTimestamp: uint32Ptr(0x88776655),
}
e.Init()
pkts, err := e.Encode(ca.au, 0)
require.NoError(t, err)
require.Equal(t, ca.pkts, pkts)
})
}
}
func TestEncodeRandomInitialState(t *testing.T) {
e := &Encoder{
PayloadType: 96,
Config: &mpeg4audio.StreamMuxConfig{
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
Type: 2,
SampleRate: 48000,
ChannelCount: 2,
},
LatmBufferFullness: 255,
}},
}},
},
}
e.Init()
require.NotEqual(t, nil, e.SSRC)
require.NotEqual(t, nil, e.InitialSequenceNumber)
require.NotEqual(t, nil, e.InitialTimestamp)
}

View File

@@ -0,0 +1,2 @@
// Package rtpmpeg4audiolatm contains a RTP/MPEG-4 Audio decoder and encoder.
package rtpmpeg4audiolatm

View File

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

View File

@@ -35,8 +35,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(90000)
return nil
}
// Decode decodes a frame from a RTP packet.

View File

@@ -46,7 +46,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -65,17 +65,23 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp)
return nil
}
func packetCount(avail, le int) int {
packetCount := le / avail
lastPacketSize := le % avail
if lastPacketSize > 0 {
packetCount++
}
return packetCount
}
// Encode encodes a frame into RTP packets.
func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error) {
avail := e.PayloadMaxSize
le := len(frame)
packetCount := le / avail
lastPacketSize := le % avail
if lastPacketSize > 0 {
packetCount++
}
packetCount := packetCount(avail, le)
pos := 0
ret := make([]*rtp.Packet, packetCount)
@@ -86,7 +92,7 @@ func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error)
if i != (packetCount - 1) {
le = avail
} else {
le = lastPacketSize
le = len(frame[pos:])
}
ret[i] = &rtp.Packet{
@@ -101,6 +107,7 @@ func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error)
Payload: frame[pos : pos+le],
}
pos += le
e.sequenceNumber++
}

View File

@@ -16,8 +16,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(d.SampleRate)
return nil
}
// Decode decodes an audio frame from a RTP packet.

View File

@@ -48,7 +48,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -67,6 +67,7 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp)
return nil
}
// Encode encodes an audio frame into a RTP packet.

View File

@@ -39,8 +39,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(rtpClockRate)
return nil
}
// Decode decodes a VP8 frame from a RTP packet.
@@ -90,7 +91,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
}
frame = joinFragments(d.fragments, n)
d.fragments = d.fragments[:0]
}

View File

@@ -49,7 +49,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -68,6 +68,7 @@ func (e *Encoder) Init() {
e.sequenceNumber = *e.InitialSequenceNumber
e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp)
return nil
}
// Encode encodes a VP8 frame into RTP/VP8 packets.

View File

@@ -39,8 +39,9 @@ type Decoder struct {
}
// Init initializes the decoder.
func (d *Decoder) Init() {
func (d *Decoder) Init() error {
d.timeDecoder = rtptime.NewDecoder(rtpClockRate)
return nil
}
// Decode decodes a VP9 frame from a RTP packet.
@@ -85,7 +86,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) {
}
frame = joinFragments(d.fragments, n)
d.fragments = d.fragments[:0]
}

View File

@@ -53,7 +53,7 @@ type Encoder struct {
}
// Init initializes the encoder.
func (e *Encoder) Init() {
func (e *Encoder) Init() error {
if e.SSRC == nil {
v := randUint32()
e.SSRC = &v
@@ -80,6 +80,8 @@ func (e *Encoder) Init() {
e.vp.InitialPictureIDFn = func() uint16 {
return *e.InitialPictureID
}
return nil
}
// Encode encodes a VP9 frame into RTP/VP9 packets.

View File

@@ -86,17 +86,43 @@ func (f *VP8) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *VP8) CreateDecoder() *rtpvp8.Decoder {
d := &rtpvp8.Decoder{}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *VP8) CreateDecoder2() (*rtpvp8.Decoder, error) {
d := &rtpvp8.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.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *VP8) CreateEncoder() *rtpvp8.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *VP8) CreateEncoder2() (*rtpvp8.Encoder, error) {
e := &rtpvp8.Encoder{
PayloadType: f.PayloadTyp,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -19,12 +19,16 @@ func TestVP8ttributes(t *testing.T) {
func TestVP8DecEncoder(t *testing.T) {
format := &VP8{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)

View File

@@ -98,17 +98,43 @@ func (f *VP9) PTSEqualsDTS(*rtp.Packet) bool {
}
// CreateDecoder creates a decoder able to decode the content of the format.
//
// Deprecated: this has been replaced by CreateDecoder2() that can also return an error.
func (f *VP9) CreateDecoder() *rtpvp9.Decoder {
d := &rtpvp9.Decoder{}
d.Init()
d, _ := f.CreateDecoder2()
return d
}
// CreateDecoder2 creates a decoder able to decode the content of the format.
func (f *VP9) CreateDecoder2() (*rtpvp9.Decoder, error) {
d := &rtpvp9.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.
//
// Deprecated: this has been replaced by CreateEncoder2() that can also return an error.
func (f *VP9) CreateEncoder() *rtpvp9.Encoder {
e, _ := f.CreateEncoder2()
return e
}
// CreateEncoder2 creates an encoder able to encode the content of the format.
func (f *VP9) CreateEncoder2() (*rtpvp9.Encoder, error) {
e := &rtpvp9.Encoder{
PayloadType: f.PayloadTyp,
}
e.Init()
return e
err := e.Init()
if err != nil {
return nil, err
}
return e, nil
}

View File

@@ -19,12 +19,16 @@ func TestVP9Attributes(t *testing.T) {
func TestVP9DecEncoder(t *testing.T) {
format := &VP9{}
enc := format.CreateEncoder()
enc, err := format.CreateEncoder2()
require.NoError(t, err)
pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0)
require.NoError(t, err)
require.Equal(t, format.PayloadType(), pkts[0].PayloadType)
dec := format.CreateDecoder()
dec, err := format.CreateDecoder2()
require.NoError(t, err)
byts, _, err := dec.Decode(pkts[0])
require.NoError(t, err)
require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)