mirror of
https://github.com/aler9/gortsplib
synced 2025-10-02 14:02:16 +08:00
add MP4A-LATM decoder and encoder (#299)
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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",
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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.
|
||||
|
@@ -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)
|
||||
|
@@ -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.
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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{{
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
241
pkg/formats/rtpmpeg4audiogeneric/decoder.go
Normal file
241
pkg/formats/rtpmpeg4audiogeneric/decoder.go
Normal 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
|
||||
}
|
@@ -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)
|
270
pkg/formats/rtpmpeg4audiogeneric/encoder.go
Normal file
270
pkg/formats/rtpmpeg4audiogeneric/encoder.go
Normal 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
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package rtpmpeg4audio
|
||||
package rtpmpeg4audiogeneric
|
||||
|
||||
import (
|
||||
"bytes"
|
@@ -0,0 +1,2 @@
|
||||
// Package rtpmpeg4audiogeneric contains a RTP/MPEG-4 Audio decoder and encoder.
|
||||
package rtpmpeg4audiogeneric
|
116
pkg/formats/rtpmpeg4audiolatm/decoder.go
Normal file
116
pkg/formats/rtpmpeg4audiolatm/decoder.go
Normal 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
|
||||
}
|
84
pkg/formats/rtpmpeg4audiolatm/decoder_test.go
Normal file
84
pkg/formats/rtpmpeg4audiolatm/decoder_test.go
Normal 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,
|
||||
})
|
||||
})
|
||||
}
|
156
pkg/formats/rtpmpeg4audiolatm/encoder.go
Normal file
156
pkg/formats/rtpmpeg4audiolatm/encoder.go
Normal 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
|
||||
}
|
177
pkg/formats/rtpmpeg4audiolatm/encoder_test.go
Normal file
177
pkg/formats/rtpmpeg4audiolatm/encoder_test.go
Normal 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)
|
||||
}
|
2
pkg/formats/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go
Normal file
2
pkg/formats/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package rtpmpeg4audiolatm contains a RTP/MPEG-4 Audio decoder and encoder.
|
||||
package rtpmpeg4audiolatm
|
5
pkg/formats/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b
vendored
Normal file
5
pkg/formats/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
go test fuzz v1
|
||||
[]byte("")
|
||||
bool(false)
|
||||
[]byte("")
|
||||
bool(false)
|
@@ -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.
|
||||
|
@@ -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++
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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]
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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]
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user