diff --git a/README.md b/README.md index 7ff96690..3237b4a5 100644 --- a/README.md +++ b/README.md @@ -97,42 +97,41 @@ Features: ## RTP Payload Formats -In RTSP, media streams are routed between server and clients by using RTP packets. Conversion between RTP packets and codec-specific frames happens by using a payload format. This library recognizes the following payload formats: +In RTSP, media streams are routed between server and clients by using RTP packets, which are encoded in a specific, codec-dependent, format, that is declared during the handshake. This library supports the following formats: ### Video -|format / codec|variant|documentation|encoder and decoder available| -|--------------|-------|-------------|-----------------------------| -|AV1||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#AV1)|:heavy_check_mark:| -|VP9||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#VP9)|:heavy_check_mark:| -|VP8||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#VP8)|:heavy_check_mark:| -|H265||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#H265)|:heavy_check_mark:| -|H264||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#H264)|:heavy_check_mark:| -|MPEG-4 Video (H263, Xvid)||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4VideoES)|:heavy_check_mark:| -|MPEG-1/2 Video||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG1Video)|:heavy_check_mark:| -|M-JPEG||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MJPEG)|:heavy_check_mark:| +|format|documentation|encoder and decoder available| +|------|-------------|-----------------------------| +|AV1|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#AV1)|:heavy_check_mark:| +|VP9|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#VP9)|:heavy_check_mark:| +|VP8|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#VP8)|:heavy_check_mark:| +|H265|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#H265)|:heavy_check_mark:| +|H264|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#H264)|:heavy_check_mark:| +|MPEG-4 Video (H263, Xvid)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4Video)|:heavy_check_mark:| +|MPEG-1/2 Video|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG1Video)|:heavy_check_mark:| +|M-JPEG|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MJPEG)|:heavy_check_mark:| ### Audio -|format / codec|variant|documentation|encoder and decoder available| -|--------------|-------|-------------|-----------------------------| -|Opus||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Opus)|:heavy_check_mark:| -|Vorbis||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Vorbis)|| -|MPEG-4 Audio (AAC)|Generic (RFC3640)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4AudioGeneric)|:heavy_check_mark:| -|MPEG-4 Audio (AAC)|LATM (RFC6416)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4AudioLATM)|:heavy_check_mark:| -|MPEG-1/2 Audio (MP3)||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG1Audio)|:heavy_check_mark:| -|AC-3||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#AC3)|:heavy_check_mark:| -|Speex||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Speex)|| -|G726||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G726)|| -|G722||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G722)|:heavy_check_mark:| -|G711 (PCMA, PCMU)||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G711)|:heavy_check_mark:| -|LPCM||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#LPCM)|:heavy_check_mark:| +|format|documentation|encoder and decoder available| +|------|-------------|-----------------------------| +|Opus|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Opus)|:heavy_check_mark:| +|Vorbis|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Vorbis)|| +|MPEG-4 Audio (AAC)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4Audio)|:heavy_check_mark:| +|MPEG-1/2 Audio (MP3)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG1Audio)|:heavy_check_mark:| +|AC-3|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#AC3)|:heavy_check_mark:| +|Speex|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Speex)|| +|G726|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G726)|| +|G722|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G722)|:heavy_check_mark:| +|G711 (PCMA, PCMU)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G711)|:heavy_check_mark:| +|LPCM|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#LPCM)|:heavy_check_mark:| ### Other -|format / codec|variant|documentation|encoder and decoder available| -|--------------|-------|-------------|-----------------------------| -|MPEG-TS||[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEGTS)|| +|format|documentation|encoder and decoder available| +|------|-------------|-----------------------------| +|MPEG-TS|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEGTS)|| ## Specifications diff --git a/pkg/format/format.go b/pkg/format/format.go index de21927d..b0c67d90 100644 --- a/pkg/format/format.go +++ b/pkg/format/format.go @@ -92,11 +92,8 @@ func Unmarshal(mediaType string, payloadType uint8, rtpMap string, fmtp map[stri case codec == "vorbis": return &Vorbis{} - case codec == "mpeg4-generic": - return &MPEG4AudioGeneric{} - - case codec == "mp4a-latm": - return &MPEG4AudioLATM{} + case codec == "mpeg4-generic", codec == "mp4a-latm": + return &MPEG4Audio{} case payloadType == 14: return &MPEG1Audio{} diff --git a/pkg/format/format_test.go b/pkg/format/format_test.go index fbf8d84d..7f6ffe1c 100644 --- a/pkg/format/format_test.go +++ b/pkg/format/format_test.go @@ -343,12 +343,13 @@ var casesFormat = []struct { "object": "2", "config": "400026203fc0", }, - &MPEG4AudioLATM{ + &MPEG4Audio{ + LATM: true, PayloadTyp: 96, ProfileLevelID: 1, Bitrate: intPtr(64000), CPresent: false, - Config: &mpeg4audio.StreamMuxConfig{ + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ AudioSpecificConfig: &mpeg4audio.Config{ @@ -382,12 +383,13 @@ var casesFormat = []struct { "config": "400026103fc0", "sbr-enabled": "1", }, - &MPEG4AudioLATM{ + &MPEG4Audio{ + LATM: true, PayloadTyp: 110, ProfileLevelID: 15, CPresent: false, SBREnabled: boolPtr(true), - Config: &mpeg4audio.StreamMuxConfig{ + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ AudioSpecificConfig: &mpeg4audio.Config{ @@ -421,13 +423,14 @@ var casesFormat = []struct { "config": "40005623101fe0", "sbr-enabled": "1", }, - &MPEG4AudioLATM{ + &MPEG4Audio{ + LATM: true, PayloadTyp: 110, ProfileLevelID: 44, CPresent: false, SBREnabled: boolPtr(true), Bitrate: intPtr(64000), - Config: &mpeg4audio.StreamMuxConfig{ + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ AudioSpecificConfig: &mpeg4audio.Config{ @@ -463,12 +466,13 @@ var casesFormat = []struct { "cpresent": "0", "config": "4001d613101fe0", }, - &MPEG4AudioLATM{ + &MPEG4Audio{ + LATM: true, PayloadTyp: 110, ProfileLevelID: 48, Bitrate: intPtr(64000), CPresent: false, - Config: &mpeg4audio.StreamMuxConfig{ + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ AudioSpecificConfig: &mpeg4audio.Config{ @@ -501,11 +505,12 @@ var casesFormat = []struct { "cpresent": "0", "config": "40002310", }, - &MPEG4AudioLATM{ + &MPEG4Audio{ + LATM: true, PayloadTyp: 110, ProfileLevelID: 30, CPresent: false, - Config: &mpeg4audio.StreamMuxConfig{ + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ AudioSpecificConfig: &mpeg4audio.Config{ @@ -1141,7 +1146,7 @@ func FuzzUnmarshalLPCM(f *testing.F) { }) } -func FuzzUnmarshalMPEG4VideoES(f *testing.F) { +func FuzzUnmarshalMPEG4Video(f *testing.F) { f.Fuzz(func(t *testing.T, a, b string) { Unmarshal("video", 96, "MP4V-ES/90000", map[string]string{ //nolint:errcheck "profile-level-id": a, diff --git a/pkg/format/mpeg4_audio.go b/pkg/format/mpeg4_audio.go new file mode 100644 index 00000000..633cc162 --- /dev/null +++ b/pkg/format/mpeg4_audio.go @@ -0,0 +1,332 @@ +package format + +import ( + "encoding/hex" + "fmt" + "strconv" + "strings" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" +) + +// MPEG4Audio is a RTP format for a MPEG-4 Audio codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type MPEG4Audio struct { + // payload type of packets. + PayloadTyp uint8 + + // use RFC6416 (LATM) instead of RFC3640 (generic). + LATM bool + + // profile level ID. + ProfileLevelID int + + // generic only + Config *mpeg4audio.Config + SizeLength int + IndexLength int + IndexDeltaLength int + + // LATM only + Bitrate *int + CPresent bool + StreamMuxConfig *mpeg4audio.StreamMuxConfig + SBREnabled *bool +} + +func (f *MPEG4Audio) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + f.LATM = (ctx.codec != "mpeg4-generic") + + if !f.LATM { + for key, val := range ctx.fmtp { + switch key { + case "streamtype": + if val != "5" { // AudioStream in ISO 14496-1 + return fmt.Errorf("streamtype of AAC must be 5") + } + + case "mode": + if strings.ToLower(val) != "aac-hbr" { + return fmt.Errorf("unsupported AAC mode: %v", val) + } + + case "profile-level-id": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile-level-id: %v", val) + } + + f.ProfileLevelID = int(tmp) + + case "config": + enc, err := hex.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", val) + } + + f.Config = &mpeg4audio.Config{} + err = f.Config.Unmarshal(enc) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", val) + } + + case "sizelength": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil || n > 100 { + return fmt.Errorf("invalid AAC SizeLength: %v", val) + } + f.SizeLength = int(n) + + case "indexlength": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil || n > 100 { + return fmt.Errorf("invalid AAC IndexLength: %v", val) + } + f.IndexLength = int(n) + + case "indexdeltalength": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil || n > 100 { + return fmt.Errorf("invalid AAC IndexDeltaLength: %v", val) + } + f.IndexDeltaLength = int(n) + } + } + + if f.Config == nil { + return fmt.Errorf("config is missing") + } + + if f.SizeLength == 0 { + return fmt.Errorf("sizelength is missing") + } + } else { + // default value set by specification + f.ProfileLevelID = 30 + f.CPresent = true + + for key, val := range ctx.fmtp { + switch key { + case "profile-level-id": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile-level-id: %v", val) + } + + f.ProfileLevelID = int(tmp) + + case "bitrate": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid bitrate: %v", val) + } + + v := int(tmp) + f.Bitrate = &v + + case "cpresent": + f.CPresent = (val == "1") + + case "config": + enc, err := hex.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", val) + } + + f.StreamMuxConfig = &mpeg4audio.StreamMuxConfig{} + err = f.StreamMuxConfig.Unmarshal(enc) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", err) + } + + case "sbr-enabled": + v := (val == "1") + f.SBREnabled = &v + } + } + + if f.CPresent { + if f.StreamMuxConfig != nil { + return fmt.Errorf("config and cpresent can't be used at the same time") + } + } else { + if f.StreamMuxConfig == nil { + return fmt.Errorf("config is missing") + } + } + } + + return nil +} + +// Codec implements Format. +func (f *MPEG4Audio) Codec() string { + return "MPEG-4 Audio" +} + +// ClockRate implements Format. +func (f *MPEG4Audio) ClockRate() int { + if !f.LATM { + return f.Config.SampleRate + } + return f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.SampleRate +} + +// PayloadType implements Format. +func (f *MPEG4Audio) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *MPEG4Audio) RTPMap() string { + if !f.LATM { + sampleRate := f.Config.SampleRate + if f.Config.ExtensionSampleRate != 0 { + sampleRate = f.Config.ExtensionSampleRate + } + + channelCount := f.Config.ChannelCount + if f.Config.ExtensionType == mpeg4audio.ObjectTypePS { + channelCount = 2 + } + + return "mpeg4-generic/" + strconv.FormatInt(int64(sampleRate), 10) + + "/" + strconv.FormatInt(int64(channelCount), 10) + } + + aoc := f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig + + sampleRate := aoc.SampleRate + if aoc.ExtensionSampleRate != 0 { + sampleRate = aoc.ExtensionSampleRate + } + + channelCount := aoc.ChannelCount + if aoc.ExtensionType == mpeg4audio.ObjectTypePS { + channelCount = 2 + } + + return "MP4A-LATM/" + strconv.FormatInt(int64(sampleRate), 10) + + "/" + strconv.FormatInt(int64(channelCount), 10) +} + +// FMTP implements Format. +func (f *MPEG4Audio) FMTP() map[string]string { + if !f.LATM { + enc, err := f.Config.Marshal() + if err != nil { + return nil + } + + profileLevelID := f.ProfileLevelID + if profileLevelID == 0 { // support legacy definition which didn't include profile-level-id + profileLevelID = 1 + } + + fmtp := map[string]string{ + "streamtype": "5", + "mode": "AAC-hbr", + "profile-level-id": strconv.FormatInt(int64(profileLevelID), 10), + } + + if f.SizeLength > 0 { + fmtp["sizelength"] = strconv.FormatInt(int64(f.SizeLength), 10) + } + + if f.IndexLength > 0 { + fmtp["indexlength"] = strconv.FormatInt(int64(f.IndexLength), 10) + } + + if f.IndexDeltaLength > 0 { + fmtp["indexdeltalength"] = strconv.FormatInt(int64(f.IndexDeltaLength), 10) + } + + fmtp["config"] = hex.EncodeToString(enc) + + return fmtp + } + + enc, err := f.StreamMuxConfig.Marshal() + if err != nil { + return nil + } + + fmtp := map[string]string{ + "profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10), + "config": hex.EncodeToString(enc), + "object": strconv.FormatInt(int64(f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.Type), 10), + } + + if f.Bitrate != nil { + fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10) + } + + if f.CPresent { + fmtp["cpresent"] = "1" + } else { + fmtp["cpresent"] = "0" + } + + if f.SBREnabled != nil { + if *f.SBREnabled { + fmtp["SBR-enabled"] = "1" + } else { + fmtp["SBR-enabled"] = "0" + } + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *MPEG4Audio) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MPEG4Audio) CreateDecoder() (*rtpmpeg4audio.Decoder, error) { + d := &rtpmpeg4audio.Decoder{ + LATM: f.LATM, + SizeLength: f.SizeLength, + IndexLength: f.IndexLength, + IndexDeltaLength: f.IndexDeltaLength, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MPEG4Audio) CreateEncoder() (*rtpmpeg4audio.Encoder, error) { + e := &rtpmpeg4audio.Encoder{ + LATM: f.LATM, + PayloadType: f.PayloadTyp, + SizeLength: f.SizeLength, + IndexLength: f.IndexLength, + IndexDeltaLength: f.IndexDeltaLength, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} + +// GetConfig returns the MPEG-4 Audio configuration. +func (f *MPEG4Audio) GetConfig() *mpeg4audio.Config { + if !f.LATM { + return f.Config + } + return f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig +} diff --git a/pkg/format/mpeg4_audio_generic.go b/pkg/format/mpeg4_audio_generic.go deleted file mode 100644 index 421022d9..00000000 --- a/pkg/format/mpeg4_audio_generic.go +++ /dev/null @@ -1,200 +0,0 @@ -package format - -import ( - "encoding/hex" - "fmt" - "strconv" - "strings" - - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" - "github.com/pion/rtp" - - "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" -) - -// MPEG4Audio is an alias for MPEG4AudioGeneric. -type MPEG4Audio = MPEG4AudioGeneric - -// MPEG4AudioGeneric is a RTP format for a MPEG-4 Audio codec. -// Specification: https://datatracker.ietf.org/doc/html/rfc3640 -type MPEG4AudioGeneric struct { - PayloadTyp uint8 - ProfileLevelID int - Config *mpeg4audio.Config - SizeLength int - IndexLength int - IndexDeltaLength int -} - -func (f *MPEG4AudioGeneric) unmarshal(ctx *unmarshalContext) error { - f.PayloadTyp = ctx.payloadType - - for key, val := range ctx.fmtp { - switch key { - case "streamtype": - if val != "5" { // AudioStream in ISO 14496-1 - return fmt.Errorf("streamtype of AAC must be 5") - } - - case "mode": - if strings.ToLower(val) != "aac-hbr" { - return fmt.Errorf("unsupported AAC mode: %v", val) - } - - case "profile-level-id": - tmp, err := strconv.ParseUint(val, 10, 31) - if err != nil { - return fmt.Errorf("invalid profile-level-id: %v", val) - } - - f.ProfileLevelID = int(tmp) - - case "config": - enc, err := hex.DecodeString(val) - if err != nil { - return fmt.Errorf("invalid AAC config: %v", val) - } - - f.Config = &mpeg4audio.Config{} - err = f.Config.Unmarshal(enc) - if err != nil { - return fmt.Errorf("invalid AAC config: %v", val) - } - - case "sizelength": - n, err := strconv.ParseUint(val, 10, 31) - if err != nil || n > 100 { - return fmt.Errorf("invalid AAC SizeLength: %v", val) - } - f.SizeLength = int(n) - - case "indexlength": - n, err := strconv.ParseUint(val, 10, 31) - if err != nil || n > 100 { - return fmt.Errorf("invalid AAC IndexLength: %v", val) - } - f.IndexLength = int(n) - - case "indexdeltalength": - n, err := strconv.ParseUint(val, 10, 31) - if err != nil || n > 100 { - return fmt.Errorf("invalid AAC IndexDeltaLength: %v", val) - } - f.IndexDeltaLength = int(n) - } - } - - if f.Config == nil { - return fmt.Errorf("config is missing") - } - - if f.SizeLength == 0 { - return fmt.Errorf("sizelength is missing") - } - - return nil -} - -// Codec implements Format. -func (f *MPEG4AudioGeneric) Codec() string { - return "MPEG-4 Audio" -} - -// ClockRate implements Format. -func (f *MPEG4AudioGeneric) ClockRate() int { - return f.Config.SampleRate -} - -// PayloadType implements Format. -func (f *MPEG4AudioGeneric) PayloadType() uint8 { - return f.PayloadTyp -} - -// RTPMap implements Format. -func (f *MPEG4AudioGeneric) RTPMap() string { - sampleRate := f.Config.SampleRate - if f.Config.ExtensionSampleRate != 0 { - sampleRate = f.Config.ExtensionSampleRate - } - - channelCount := f.Config.ChannelCount - if f.Config.ExtensionType == mpeg4audio.ObjectTypePS { - channelCount = 2 - } - - return "mpeg4-generic/" + strconv.FormatInt(int64(sampleRate), 10) + - "/" + strconv.FormatInt(int64(channelCount), 10) -} - -// FMTP implements Format. -func (f *MPEG4AudioGeneric) FMTP() map[string]string { - enc, err := f.Config.Marshal() - if err != nil { - return nil - } - - profileLevelID := f.ProfileLevelID - if profileLevelID == 0 { // support legacy definition which didn't include profile-level-id - profileLevelID = 1 - } - - fmtp := map[string]string{ - "streamtype": "5", - "mode": "AAC-hbr", - "profile-level-id": strconv.FormatInt(int64(profileLevelID), 10), - } - - if f.SizeLength > 0 { - fmtp["sizelength"] = strconv.FormatInt(int64(f.SizeLength), 10) - } - - if f.IndexLength > 0 { - fmtp["indexlength"] = strconv.FormatInt(int64(f.IndexLength), 10) - } - - if f.IndexDeltaLength > 0 { - fmtp["indexdeltalength"] = strconv.FormatInt(int64(f.IndexDeltaLength), 10) - } - - fmtp["config"] = hex.EncodeToString(enc) - - return fmtp -} - -// PTSEqualsDTS implements Format. -func (f *MPEG4AudioGeneric) PTSEqualsDTS(*rtp.Packet) bool { - return true -} - -// CreateDecoder creates a decoder able to decode the content of the format. -func (f *MPEG4AudioGeneric) CreateDecoder() (*rtpmpeg4audio.Decoder, error) { - d := &rtpmpeg4audio.Decoder{ - SizeLength: f.SizeLength, - IndexLength: f.IndexLength, - IndexDeltaLength: f.IndexDeltaLength, - } - - err := d.Init() - if err != nil { - return nil, err - } - - return d, nil -} - -// CreateEncoder creates an encoder able to encode the content of the format. -func (f *MPEG4AudioGeneric) CreateEncoder() (*rtpmpeg4audio.Encoder, error) { - e := &rtpmpeg4audio.Encoder{ - PayloadType: f.PayloadTyp, - SizeLength: f.SizeLength, - IndexLength: f.IndexLength, - IndexDeltaLength: f.IndexDeltaLength, - } - - err := e.Init() - if err != nil { - return nil, err - } - - return e, nil -} diff --git a/pkg/format/mpeg4_audio_generic_test.go b/pkg/format/mpeg4_audio_generic_test.go deleted file mode 100644 index 5819833e..00000000 --- a/pkg/format/mpeg4_audio_generic_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package format - -import ( - "testing" - - "github.com/pion/rtp" - "github.com/stretchr/testify/require" - - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" -) - -func TestMPEG4AudioGenericAttributes(t *testing.T) { - format := &MPEG4AudioGeneric{ - PayloadTyp: 96, - Config: &mpeg4audio.Config{ - Type: mpeg4audio.ObjectTypeAACLC, - SampleRate: 48000, - ChannelCount: 2, - }, - SizeLength: 13, - IndexLength: 3, - IndexDeltaLength: 3, - } - require.Equal(t, "MPEG-4 Audio", format.Codec()) - require.Equal(t, 48000, format.ClockRate()) - require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) -} - -func TestMPEG4AudioGenericDecEncoder(t *testing.T) { - format := &MPEG4AudioGeneric{ - PayloadTyp: 96, - Config: &mpeg4audio.Config{ - Type: mpeg4audio.ObjectTypeAACLC, - SampleRate: 48000, - ChannelCount: 2, - }, - SizeLength: 13, - IndexLength: 3, - IndexDeltaLength: 3, - } - - enc, err := format.CreateEncoder() - require.NoError(t, err) - - pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}) - require.NoError(t, err) - require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - - dec, err := format.CreateDecoder() - require.NoError(t, err) - - byts, err := dec.Decode(pkts[0]) - require.NoError(t, err) - require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) -} diff --git a/pkg/format/mpeg4_audio_latm.go b/pkg/format/mpeg4_audio_latm.go deleted file mode 100644 index 7ddce528..00000000 --- a/pkg/format/mpeg4_audio_latm.go +++ /dev/null @@ -1,181 +0,0 @@ -package format - -import ( - "encoding/hex" - "fmt" - "strconv" - - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" - "github.com/pion/rtp" - - "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audiolatm" -) - -// MPEG4AudioLATM is a RTP format for a MPEG-4 Audio codec. -// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 -type MPEG4AudioLATM struct { - PayloadTyp uint8 - ProfileLevelID int - Bitrate *int - CPresent bool - Config *mpeg4audio.StreamMuxConfig - SBREnabled *bool -} - -func (f *MPEG4AudioLATM) unmarshal(ctx *unmarshalContext) error { - f.PayloadTyp = ctx.payloadType - - // default value set by specification - f.ProfileLevelID = 30 - f.CPresent = true - - for key, val := range ctx.fmtp { - switch key { - case "profile-level-id": - tmp, err := strconv.ParseUint(val, 10, 31) - if err != nil { - return fmt.Errorf("invalid profile-level-id: %v", val) - } - - f.ProfileLevelID = int(tmp) - - case "bitrate": - tmp, err := strconv.ParseUint(val, 10, 31) - if err != nil { - return fmt.Errorf("invalid bitrate: %v", val) - } - - v := int(tmp) - f.Bitrate = &v - - case "cpresent": - f.CPresent = (val == "1") - - case "config": - enc, err := hex.DecodeString(val) - if err != nil { - return fmt.Errorf("invalid AAC config: %v", val) - } - - f.Config = &mpeg4audio.StreamMuxConfig{} - err = f.Config.Unmarshal(enc) - if err != nil { - return fmt.Errorf("invalid AAC config: %v", err) - } - - case "sbr-enabled": - v := (val == "1") - f.SBREnabled = &v - } - } - - 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 -} - -// Codec implements Format. -func (f *MPEG4AudioLATM) Codec() string { - return "MPEG-4 Audio" -} - -// ClockRate implements Format. -func (f *MPEG4AudioLATM) ClockRate() int { - return f.Config.Programs[0].Layers[0].AudioSpecificConfig.SampleRate -} - -// PayloadType implements Format. -func (f *MPEG4AudioLATM) PayloadType() uint8 { - return f.PayloadTyp -} - -// RTPMap implements Format. -func (f *MPEG4AudioLATM) RTPMap() string { - aoc := f.Config.Programs[0].Layers[0].AudioSpecificConfig - - sampleRate := aoc.SampleRate - if aoc.ExtensionSampleRate != 0 { - sampleRate = aoc.ExtensionSampleRate - } - - channelCount := aoc.ChannelCount - if aoc.ExtensionType == mpeg4audio.ObjectTypePS { - channelCount = 2 - } - - return "MP4A-LATM/" + strconv.FormatInt(int64(sampleRate), 10) + - "/" + strconv.FormatInt(int64(channelCount), 10) -} - -// FMTP implements Format. -func (f *MPEG4AudioLATM) FMTP() map[string]string { - enc, err := f.Config.Marshal() - if err != nil { - return nil - } - - fmtp := map[string]string{ - "profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10), - "config": hex.EncodeToString(enc), - "object": strconv.FormatInt(int64(f.Config.Programs[0].Layers[0].AudioSpecificConfig.Type), 10), - } - - if f.Bitrate != nil { - fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10) - } - - if f.CPresent { - fmtp["cpresent"] = "1" - } else { - fmtp["cpresent"] = "0" - } - - if f.SBREnabled != nil { - if *f.SBREnabled { - fmtp["SBR-enabled"] = "1" - } else { - fmtp["SBR-enabled"] = "0" - } - } - - return fmtp -} - -// PTSEqualsDTS implements Format. -func (f *MPEG4AudioLATM) PTSEqualsDTS(*rtp.Packet) bool { - return true -} - -// CreateDecoder creates a decoder able to decode the content of the format. -func (f *MPEG4AudioLATM) CreateDecoder() (*rtpmpeg4audiolatm.Decoder, error) { - d := &rtpmpeg4audiolatm.Decoder{} - - err := d.Init() - if err != nil { - return nil, err - } - - return d, nil -} - -// CreateEncoder creates an encoder able to encode the content of the format. -func (f *MPEG4AudioLATM) CreateEncoder() (*rtpmpeg4audiolatm.Encoder, error) { - e := &rtpmpeg4audiolatm.Encoder{ - PayloadType: f.PayloadTyp, - } - - err := e.Init() - if err != nil { - return nil, err - } - - return e, nil -} diff --git a/pkg/format/mpeg4_audio_latm_test.go b/pkg/format/mpeg4_audio_latm_test.go deleted file mode 100644 index c6c906f3..00000000 --- a/pkg/format/mpeg4_audio_latm_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package format - -import ( - "testing" - - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" -) - -func TestMPEG4AudioLATMAttributes(t *testing.T) { - format := &MPEG4AudioLATM{ - PayloadTyp: 96, - ProfileLevelID: 1, - Config: &mpeg4audio.StreamMuxConfig{ - Programs: []*mpeg4audio.StreamMuxConfigProgram{{ - Layers: []*mpeg4audio.StreamMuxConfigLayer{{ - AudioSpecificConfig: &mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }, - LatmBufferFullness: 255, - }}, - }}, - }, - } - require.Equal(t, "MPEG-4 Audio", format.Codec()) - 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.CreateEncoder() - require.NoError(t, err) - - pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}) - require.NoError(t, err) - require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - - dec, err := format.CreateDecoder() - require.NoError(t, err) - - byts, err := dec.Decode(pkts[0]) - require.NoError(t, err) - require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) -} diff --git a/pkg/format/mpeg4_audio_test.go b/pkg/format/mpeg4_audio_test.go new file mode 100644 index 00000000..da680b8c --- /dev/null +++ b/pkg/format/mpeg4_audio_test.go @@ -0,0 +1,126 @@ +package format + +import ( + "testing" + + "github.com/pion/rtp" + "github.com/stretchr/testify/require" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" +) + +func TestMPEG4AudioAttributes(t *testing.T) { + t.Run("generic", func(t *testing.T) { + format := &MPEG4Audio{ + PayloadTyp: 96, + Config: &mpeg4audio.Config{ + Type: mpeg4audio.ObjectTypeAACLC, + SampleRate: 48000, + ChannelCount: 2, + }, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + } + require.Equal(t, "MPEG-4 Audio", format.Codec()) + require.Equal(t, 48000, format.ClockRate()) + require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) + require.Equal(t, &mpeg4audio.Config{ + Type: mpeg4audio.ObjectTypeAACLC, + SampleRate: 48000, + ChannelCount: 2, + }, format.GetConfig()) + }) + + t.Run("latm", func(t *testing.T) { + format := &MPEG4Audio{ + LATM: true, + PayloadTyp: 96, + ProfileLevelID: 1, + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + } + require.Equal(t, "MPEG-4 Audio", format.Codec()) + require.Equal(t, 44100, format.ClockRate()) + require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) + require.Equal(t, &mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }, format.GetConfig()) + }) +} + +func TestMPEG4AudioDecEncoder(t *testing.T) { + t.Run("generic", func(t *testing.T) { + format := &MPEG4Audio{ + PayloadTyp: 96, + Config: &mpeg4audio.Config{ + Type: mpeg4audio.ObjectTypeAACLC, + SampleRate: 48000, + ChannelCount: 2, + }, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + } + + enc, err := format.CreateEncoder() + require.NoError(t, err) + + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}) + require.NoError(t, err) + require.Equal(t, format.PayloadType(), pkts[0].PayloadType) + + dec, err := format.CreateDecoder() + require.NoError(t, err) + + byts, err := dec.Decode(pkts[0]) + require.NoError(t, err) + require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) + }) + + t.Run("latm", func(t *testing.T) { + format := &MPEG4Audio{ + LATM: true, + PayloadTyp: 96, + ProfileLevelID: 1, + StreamMuxConfig: &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.Config{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + } + + enc, err := format.CreateEncoder() + require.NoError(t, err) + + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}) + require.NoError(t, err) + require.Equal(t, format.PayloadType(), pkts[0].PayloadType) + + dec, err := format.CreateDecoder() + require.NoError(t, err) + + byts, err := dec.Decode(pkts[0]) + require.NoError(t, err) + require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) + }) +} diff --git a/pkg/format/mpeg4_video_es.go b/pkg/format/mpeg4_video.go similarity index 74% rename from pkg/format/mpeg4_video_es.go rename to pkg/format/mpeg4_video.go index 14bc67ba..1b28c45c 100644 --- a/pkg/format/mpeg4_video_es.go +++ b/pkg/format/mpeg4_video.go @@ -13,12 +13,9 @@ import ( "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video" ) -// MPEG4Video is an alias for MPEG4VideoES. -type MPEG4Video = MPEG4VideoES - -// MPEG4VideoES is a RTP format for a MPEG-4 Video codec. +// MPEG4Video is a RTP format for a MPEG-4 Video codec. // Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.1 -type MPEG4VideoES struct { +type MPEG4Video struct { PayloadTyp uint8 ProfileLevelID int Config []byte @@ -26,7 +23,7 @@ type MPEG4VideoES struct { mutex sync.RWMutex } -func (f *MPEG4VideoES) unmarshal(ctx *unmarshalContext) error { +func (f *MPEG4Video) unmarshal(ctx *unmarshalContext) error { f.PayloadTyp = ctx.payloadType f.ProfileLevelID = 1 // default value imposed by specification @@ -58,27 +55,27 @@ func (f *MPEG4VideoES) unmarshal(ctx *unmarshalContext) error { } // Codec implements Format. -func (f *MPEG4VideoES) Codec() string { +func (f *MPEG4Video) Codec() string { return "MPEG-4 Video" } // ClockRate implements Format. -func (f *MPEG4VideoES) ClockRate() int { +func (f *MPEG4Video) ClockRate() int { return 90000 } // PayloadType implements Format. -func (f *MPEG4VideoES) PayloadType() uint8 { +func (f *MPEG4Video) PayloadType() uint8 { return f.PayloadTyp } // RTPMap implements Format. -func (f *MPEG4VideoES) RTPMap() string { +func (f *MPEG4Video) RTPMap() string { return "MP4V-ES/90000" } // FMTP implements Format. -func (f *MPEG4VideoES) FMTP() map[string]string { +func (f *MPEG4Video) FMTP() map[string]string { fmtp := map[string]string{ "profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10), } @@ -91,12 +88,12 @@ func (f *MPEG4VideoES) FMTP() map[string]string { } // PTSEqualsDTS implements Format. -func (f *MPEG4VideoES) PTSEqualsDTS(*rtp.Packet) bool { +func (f *MPEG4Video) PTSEqualsDTS(*rtp.Packet) bool { return true } // CreateDecoder creates a decoder able to decode the content of the format. -func (f *MPEG4VideoES) CreateDecoder() (*rtpmpeg4video.Decoder, error) { +func (f *MPEG4Video) CreateDecoder() (*rtpmpeg4video.Decoder, error) { d := &rtpmpeg4video.Decoder{} err := d.Init() @@ -108,7 +105,7 @@ func (f *MPEG4VideoES) CreateDecoder() (*rtpmpeg4video.Decoder, error) { } // CreateEncoder creates an encoder able to encode the content of the format. -func (f *MPEG4VideoES) CreateEncoder() (*rtpmpeg4video.Encoder, error) { +func (f *MPEG4Video) CreateEncoder() (*rtpmpeg4video.Encoder, error) { e := &rtpmpeg4video.Encoder{ PayloadType: f.PayloadTyp, } @@ -122,14 +119,14 @@ func (f *MPEG4VideoES) CreateEncoder() (*rtpmpeg4video.Encoder, error) { } // SafeSetParams sets the codec parameters. -func (f *MPEG4VideoES) SafeSetParams(config []byte) { +func (f *MPEG4Video) SafeSetParams(config []byte) { f.mutex.Lock() defer f.mutex.Unlock() f.Config = config } // SafeParams returns the codec parameters. -func (f *MPEG4VideoES) SafeParams() []byte { +func (f *MPEG4Video) SafeParams() []byte { f.mutex.RLock() defer f.mutex.RUnlock() return f.Config diff --git a/pkg/format/mpeg4_video_generic.go b/pkg/format/mpeg4_video_generic.go deleted file mode 100644 index 478f94ad..00000000 --- a/pkg/format/mpeg4_video_generic.go +++ /dev/null @@ -1,3 +0,0 @@ -package format - -// TODO diff --git a/pkg/format/mpeg4_video_generic_test.go b/pkg/format/mpeg4_video_generic_test.go deleted file mode 100644 index 478f94ad..00000000 --- a/pkg/format/mpeg4_video_generic_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package format - -// TODO diff --git a/pkg/format/mpeg4_video_es_test.go b/pkg/format/mpeg4_video_test.go similarity index 83% rename from pkg/format/mpeg4_video_es_test.go rename to pkg/format/mpeg4_video_test.go index abc15e8b..15df5428 100644 --- a/pkg/format/mpeg4_video_es_test.go +++ b/pkg/format/mpeg4_video_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestMPEG4VideoESAttributes(t *testing.T) { - format := &MPEG4VideoES{ +func TestMPEG4VideoAttributes(t *testing.T) { + format := &MPEG4Video{ PayloadTyp: 96, ProfileLevelID: 1, Config: []byte{0x01, 0x02, 0x03}, @@ -18,8 +18,8 @@ func TestMPEG4VideoESAttributes(t *testing.T) { require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) } -func TestMPEG4VideoESDecEncoder(t *testing.T) { - format := &MPEG4VideoES{ +func TestMPEG4VideoDecEncoder(t *testing.T) { + format := &MPEG4Video{ PayloadTyp: 96, } diff --git a/pkg/format/rtpmpeg4audio/decoder.go b/pkg/format/rtpmpeg4audio/decoder.go index 4ee73a17..7b20aee3 100644 --- a/pkg/format/rtpmpeg4audio/decoder.go +++ b/pkg/format/rtpmpeg4audio/decoder.go @@ -3,11 +3,54 @@ package rtpmpeg4audio import ( "errors" - "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audiogeneric" + "github.com/pion/rtp" ) -// ErrMorePacketsNeeded is an alis for rtpmpeg4audiogeneric.ErrMorePacketsNeeded. +// ErrMorePacketsNeeded is returned when more packets are needed. var ErrMorePacketsNeeded = errors.New("need more packets") -// Decoder is an alias for rtpmpeg4audiogeneric.Decoder. -type Decoder = rtpmpeg4audiogeneric.Decoder +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 +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type Decoder struct { + // use RFC6416 (LATM) instead of RFC3640 (generic). + LATM bool + + // Generic-only + // 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 + + firstAUParsed bool + adtsMode bool + fragments [][]byte + fragmentsSize int + fragmentsExpected int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + 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, error) { + if !d.LATM { + return d.decodeGeneric(pkt) + } + return d.decodeLATM(pkt) +} diff --git a/pkg/format/rtpmpeg4audiogeneric/decoder.go b/pkg/format/rtpmpeg4audio/decoder_generic.go similarity index 77% rename from pkg/format/rtpmpeg4audiogeneric/decoder.go rename to pkg/format/rtpmpeg4audio/decoder_generic.go index 8d0869a7..d40a4fc0 100644 --- a/pkg/format/rtpmpeg4audiogeneric/decoder.go +++ b/pkg/format/rtpmpeg4audio/decoder_generic.go @@ -1,7 +1,6 @@ -package rtpmpeg4audiogeneric +package rtpmpeg4audio import ( - "errors" "fmt" "github.com/bluenviron/mediacommon/pkg/bits" @@ -9,45 +8,7 @@ import ( "github.com/pion/rtp" ) -// 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 { - // 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 - - firstAUParsed bool - adtsMode bool - fragments [][]byte - fragmentsSize int -} - -// Init initializes the decoder. -func (d *Decoder) Init() error { - 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, error) { +func (d *Decoder) decodeGeneric(pkt *rtp.Packet) ([][]byte, error) { if len(pkt.Payload) < 2 { d.fragments = d.fragments[:0] // discard pending fragments return nil, fmt.Errorf("payload is too short") @@ -130,12 +91,7 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { d.fragments = d.fragments[:0] } - aus, err = d.removeADTS(aus) - if err != nil { - return nil, err - } - - return aus, nil + return d.removeADTS(aus) } func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) { diff --git a/pkg/format/rtpmpeg4audiogeneric/decoder_test.go b/pkg/format/rtpmpeg4audio/decoder_generic_test.go similarity index 91% rename from pkg/format/rtpmpeg4audiogeneric/decoder_test.go rename to pkg/format/rtpmpeg4audio/decoder_generic_test.go index 7b791963..fd8ccaab 100644 --- a/pkg/format/rtpmpeg4audiogeneric/decoder_test.go +++ b/pkg/format/rtpmpeg4audio/decoder_generic_test.go @@ -1,4 +1,4 @@ -package rtpmpeg4audiogeneric +package rtpmpeg4audio import ( "testing" @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestDecode(t *testing.T) { - for _, ca := range cases { +func TestDecodeGeneric(t *testing.T) { + for _, ca := range casesGeneric { t.Run(ca.name, func(t *testing.T) { d := &Decoder{ SizeLength: ca.sizeLength, @@ -41,7 +41,7 @@ func TestDecode(t *testing.T) { } } -func TestDecodeADTS(t *testing.T) { +func TestDecodeGenericADTS(t *testing.T) { d := &Decoder{ SizeLength: 13, IndexLength: 3, @@ -69,7 +69,7 @@ func TestDecodeADTS(t *testing.T) { } } -func FuzzDecoder(f *testing.F) { +func FuzzDecoderGeneric(f *testing.F) { f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) { d := &Decoder{ SizeLength: 13, diff --git a/pkg/format/rtpmpeg4audiolatm/decoder.go b/pkg/format/rtpmpeg4audio/decoder_latm.go similarity index 59% rename from pkg/format/rtpmpeg4audiolatm/decoder.go rename to pkg/format/rtpmpeg4audio/decoder_latm.go index 31bf669d..3b092edd 100644 --- a/pkg/format/rtpmpeg4audiolatm/decoder.go +++ b/pkg/format/rtpmpeg4audio/decoder_latm.go @@ -1,41 +1,13 @@ -package rtpmpeg4audiolatm +package rtpmpeg4audio import ( - "errors" "fmt" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/pion/rtp" ) -// 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 { - fragments [][]byte - fragmentsSize int - fragmentsExpected int -} - -// Init initializes the decoder. -func (d *Decoder) Init() error { - return nil -} - -// Decode decodes an AU from a RTP packet. -// It returns the AU and its PTS. -func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { +func (d *Decoder) decodeLATM(pkt *rtp.Packet) ([][]byte, error) { var au []byte buf := pkt.Payload @@ -79,5 +51,5 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { d.fragments = d.fragments[:0] } - return au, nil + return [][]byte{au}, nil } diff --git a/pkg/format/rtpmpeg4audiolatm/decoder_test.go b/pkg/format/rtpmpeg4audio/decoder_latm_test.go similarity index 74% rename from pkg/format/rtpmpeg4audiolatm/decoder_test.go rename to pkg/format/rtpmpeg4audio/decoder_latm_test.go index 21f8dcd1..e6f0d322 100644 --- a/pkg/format/rtpmpeg4audiolatm/decoder_test.go +++ b/pkg/format/rtpmpeg4audio/decoder_latm_test.go @@ -1,4 +1,4 @@ -package rtpmpeg4audiolatm +package rtpmpeg4audio import ( "testing" @@ -7,19 +7,21 @@ import ( "github.com/stretchr/testify/require" ) -func TestDecode(t *testing.T) { - for _, ca := range cases { +func TestDecodeLATM(t *testing.T) { + for _, ca := range casesLATM { t.Run(ca.name, func(t *testing.T) { - d := &Decoder{} + d := &Decoder{ + LATM: true, + } err := d.Init() require.NoError(t, err) - var au []byte + var aus [][]byte for _, pkt := range ca.pkts { clone := pkt.Clone() - au, err = d.Decode(pkt) + aus, err = d.Decode(pkt) // test input integrity require.Equal(t, clone, pkt) @@ -31,17 +33,19 @@ func TestDecode(t *testing.T) { require.NoError(t, err) } - require.Equal(t, ca.au, au) + require.Equal(t, ca.au, aus[0]) }) } } -func TestDecodeOtherData(t *testing.T) { - d := &Decoder{} +func TestDecodeLATMOtherData(t *testing.T) { + d := &Decoder{ + LATM: true, + } err := d.Init() require.NoError(t, err) - au, err := d.Decode(&rtp.Packet{ + aus, err := d.Decode(&rtp.Packet{ Header: rtp.Header{ Version: 2, Marker: true, @@ -55,12 +59,14 @@ func TestDecodeOtherData(t *testing.T) { }) require.NoError(t, err) - require.Equal(t, []byte{1, 2, 3, 4}, au) + require.Equal(t, []byte{1, 2, 3, 4}, aus[0]) } -func FuzzDecoder(f *testing.F) { +func FuzzDecoderLATM(f *testing.F) { f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) { - d := &Decoder{} + d := &Decoder{ + LATM: true, + } d.Init() //nolint:errcheck d.Decode(&rtp.Packet{ //nolint:errcheck diff --git a/pkg/format/rtpmpeg4audio/encoder.go b/pkg/format/rtpmpeg4audio/encoder.go index 57597cc1..b1f442cb 100644 --- a/pkg/format/rtpmpeg4audio/encoder.go +++ b/pkg/format/rtpmpeg4audio/encoder.go @@ -1,8 +1,88 @@ package rtpmpeg4audio import ( - "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audiogeneric" + "crypto/rand" + + "github.com/pion/rtp" ) -// Encoder is an alias for rtpmpeg4audiogeneric.Encoder. -type Encoder = rtpmpeg4audiogeneric.Encoder +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/MPEG4-audio encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // use RFC6416 (LATM) instead of RFC3640 (generic). + LATM bool + + // 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 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes AUs into RTP packets. +func (e *Encoder) Encode(aus [][]byte) ([]*rtp.Packet, error) { + if !e.LATM { + return e.encodeGeneric(aus) + } + return e.encodeLATM(aus) +} diff --git a/pkg/format/rtpmpeg4audiogeneric/encoder.go b/pkg/format/rtpmpeg4audio/encoder_generic.go similarity index 56% rename from pkg/format/rtpmpeg4audiogeneric/encoder.go rename to pkg/format/rtpmpeg4audio/encoder_generic.go index f9e9bbfa..bde46305 100644 --- a/pkg/format/rtpmpeg4audiogeneric/encoder.go +++ b/pkg/format/rtpmpeg4audio/encoder_generic.go @@ -1,29 +1,13 @@ -package rtpmpeg4audiogeneric +package rtpmpeg4audio import ( - "crypto/rand" - "github.com/pion/rtp" "github.com/bluenviron/mediacommon/pkg/bits" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" ) -const ( - rtpVersion = 2 - defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) -) - -func randUint32() (uint32, error) { - var b [4]byte - _, err := rand.Read(b[:]) - if err != nil { - return 0, err - } - return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil -} - -func packetCount(avail, le int) int { +func packetCountGeneric(avail, le int) int { n := le / avail if (le % avail) != 0 { n++ @@ -31,76 +15,20 @@ func packetCount(avail, le int) int { return n } -// Encoder is a RTP/MPEG4-audio encoder. -// Specification: https://datatracker.ietf.org/doc/html/rfc3640 -type Encoder struct { - // payload type of packets. - PayloadType uint8 - - // 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 - - // SSRC of packets (optional). - // It defaults to a random value. - SSRC *uint32 - - // initial sequence number of packets (optional). - // It defaults to a random value. - InitialSequenceNumber *uint16 - - // maximum size of packet payloads (optional). - // It defaults to 1460. - PayloadMaxSize int - - sequenceNumber uint16 -} - -// Init initializes the encoder. -func (e *Encoder) Init() error { - if e.SSRC == nil { - v, err := randUint32() - if err != nil { - return err - } - e.SSRC = &v - } - if e.InitialSequenceNumber == nil { - v, err := randUint32() - if err != nil { - return err - } - v2 := uint16(v) - e.InitialSequenceNumber = &v2 - } - if e.PayloadMaxSize == 0 { - e.PayloadMaxSize = defaultPayloadMaxSize - } - - e.sequenceNumber = *e.InitialSequenceNumber - return nil -} - -// Encode encodes AUs into RTP packets. -func (e *Encoder) Encode(aus [][]byte) ([]*rtp.Packet, error) { +func (e *Encoder) encodeGeneric(aus [][]byte) ([]*rtp.Packet, error) { var rets []*rtp.Packet var batch [][]byte timestamp := uint32(0) // split AUs into batches for _, au := range aus { - if e.lenAggregated(batch, au) <= e.PayloadMaxSize { + if e.lenGenericAggregated(batch, au) <= e.PayloadMaxSize { // add to existing batch batch = append(batch, au) } else { // write current batch if batch != nil { - pkts, err := e.writeBatch(batch, timestamp) + pkts, err := e.writeGenericBatch(batch, timestamp) if err != nil { return nil, err } @@ -114,7 +42,7 @@ func (e *Encoder) Encode(aus [][]byte) ([]*rtp.Packet, error) { } // write last batch - pkts, err := e.writeBatch(batch, timestamp) + pkts, err := e.writeGenericBatch(batch, timestamp) if err != nil { return nil, err } @@ -123,15 +51,15 @@ func (e *Encoder) Encode(aus [][]byte) ([]*rtp.Packet, error) { return rets, nil } -func (e *Encoder) writeBatch(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) { - if len(aus) != 1 || e.lenAggregated(aus, nil) < e.PayloadMaxSize { - return e.writeAggregated(aus, timestamp) +func (e *Encoder) writeGenericBatch(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + if len(aus) != 1 || e.lenGenericAggregated(aus, nil) < e.PayloadMaxSize { + return e.writeGenericAggregated(aus, timestamp) } - return e.writeFragmented(aus[0], timestamp) + return e.writeGenericFragmented(aus[0], timestamp) } -func (e *Encoder) writeFragmented(au []byte, timestamp uint32) ([]*rtp.Packet, error) { +func (e *Encoder) writeGenericFragmented(au []byte, timestamp uint32) ([]*rtp.Packet, error) { auHeadersLen := e.SizeLength + e.IndexLength auHeadersLenBytes := auHeadersLen / 8 if (auHeadersLen % 8) != 0 { @@ -140,7 +68,7 @@ func (e *Encoder) writeFragmented(au []byte, timestamp uint32) ([]*rtp.Packet, e avail := e.PayloadMaxSize - 2 - auHeadersLenBytes le := len(au) - packetCount := packetCount(avail, le) + packetCount := packetCountGeneric(avail, le) ret := make([]*rtp.Packet, packetCount) le = avail @@ -183,7 +111,7 @@ func (e *Encoder) writeFragmented(au []byte, timestamp uint32) ([]*rtp.Packet, e return ret, nil } -func (e *Encoder) lenAggregated(aus [][]byte, addAU []byte) int { +func (e *Encoder) lenGenericAggregated(aus [][]byte, addAU []byte) int { n := 2 // AU-headers-length // AU-headers @@ -218,8 +146,8 @@ func (e *Encoder) lenAggregated(aus [][]byte, addAU []byte) int { return n } -func (e *Encoder) writeAggregated(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) { - payload := make([]byte, e.lenAggregated(aus, nil)) +func (e *Encoder) writeGenericAggregated(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + payload := make([]byte, e.lenGenericAggregated(aus, nil)) // AU-headers written := 0 diff --git a/pkg/format/rtpmpeg4audiogeneric/encoder_test.go b/pkg/format/rtpmpeg4audio/encoder_generic_test.go similarity index 97% rename from pkg/format/rtpmpeg4audiogeneric/encoder_test.go rename to pkg/format/rtpmpeg4audio/encoder_generic_test.go index 232d1876..c7a1af00 100644 --- a/pkg/format/rtpmpeg4audiogeneric/encoder_test.go +++ b/pkg/format/rtpmpeg4audio/encoder_generic_test.go @@ -1,4 +1,4 @@ -package rtpmpeg4audiogeneric +package rtpmpeg4audio import ( "bytes" @@ -32,7 +32,7 @@ func mergeBytes(vals ...[]byte) []byte { return res } -var cases = []struct { +var casesGeneric = []struct { name string sizeLength int indexLength int @@ -498,8 +498,8 @@ var cases = []struct { }, } -func TestEncode(t *testing.T) { - for _, ca := range cases { +func TestEncodeGeneric(t *testing.T) { + for _, ca := range casesGeneric { t.Run(ca.name, func(t *testing.T) { e := &Encoder{ PayloadType: 96, @@ -518,16 +518,3 @@ func TestEncode(t *testing.T) { }) } } - -func TestEncodeRandomInitialState(t *testing.T) { - e := &Encoder{ - PayloadType: 96, - SizeLength: 13, - IndexLength: 3, - IndexDeltaLength: 3, - } - err := e.Init() - require.NoError(t, err) - require.NotEqual(t, nil, e.SSRC) - require.NotEqual(t, nil, e.InitialSequenceNumber) -} diff --git a/pkg/format/rtpmpeg4audio/encoder_latm.go b/pkg/format/rtpmpeg4audio/encoder_latm.go new file mode 100644 index 00000000..7d07d58c --- /dev/null +++ b/pkg/format/rtpmpeg4audio/encoder_latm.go @@ -0,0 +1,76 @@ +package rtpmpeg4audio + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" +) + +func (e *Encoder) packetCountLATM(auLen int, plil int) int { + totalLen := plil + auLen + n := totalLen / e.PayloadMaxSize + if (totalLen % e.PayloadMaxSize) != 0 { + n++ + } + return n +} + +func (e *Encoder) encodeLATM(aus [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + + for i, au := range aus { + timestamp := uint32(i) * mpeg4audio.SamplesPerAccessUnit + + add, err := e.encodeLATMSingle(au, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, add...) + } + + return rets, nil +} + +func (e *Encoder) encodeLATMSingle(au []byte, timestamp uint32) ([]*rtp.Packet, error) { + auLen := len(au) + plil := payloadLengthInfoEncodeSize(auLen) + packetCount := e.packetCountLATM(auLen, plil) + + ret := make([]*rtp.Packet, packetCount) + le := e.PayloadMaxSize - plil + + for i := range ret { + if i == (packetCount - 1) { + le = len(au) + } + + var payload []byte + + if i == 0 { + payload = make([]byte, plil+le) + payloadLengthInfoEncode(plil, auLen, payload) + copy(payload[plil:], au[:le]) + au = au[le:] + le = e.PayloadMaxSize + } else { + payload = au[:le] + au = au[le:] + } + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: (i == packetCount-1), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} diff --git a/pkg/format/rtpmpeg4audiolatm/encoder_test.go b/pkg/format/rtpmpeg4audio/encoder_latm_test.go similarity index 78% rename from pkg/format/rtpmpeg4audiolatm/encoder_test.go rename to pkg/format/rtpmpeg4audio/encoder_latm_test.go index 2e9103b6..4434beff 100644 --- a/pkg/format/rtpmpeg4audiolatm/encoder_test.go +++ b/pkg/format/rtpmpeg4audio/encoder_latm_test.go @@ -1,4 +1,4 @@ -package rtpmpeg4audiolatm +package rtpmpeg4audio import ( "bytes" @@ -8,31 +8,7 @@ import ( "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 { +var casesLATM = []struct { name string au []byte pkts []*rtp.Packet @@ -137,10 +113,11 @@ var cases = []struct { }, } -func TestEncode(t *testing.T) { - for _, ca := range cases { +func TestEncodeLATM(t *testing.T) { + for _, ca := range casesLATM { t.Run(ca.name, func(t *testing.T) { e := &Encoder{ + LATM: true, PayloadType: 96, SSRC: uint32Ptr(0x9dbb7812), InitialSequenceNumber: uint16Ptr(0x44ed), @@ -148,19 +125,9 @@ func TestEncode(t *testing.T) { err := e.Init() require.NoError(t, err) - pkts, err := e.Encode(ca.au) + pkts, err := e.Encode([][]byte{ca.au}) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) } } - -func TestEncodeRandomInitialState(t *testing.T) { - e := &Encoder{ - PayloadType: 96, - } - err := e.Init() - require.NoError(t, err) - require.NotEqual(t, nil, e.SSRC) - require.NotEqual(t, nil, e.InitialSequenceNumber) -} diff --git a/pkg/format/rtpmpeg4audio/encoder_test.go b/pkg/format/rtpmpeg4audio/encoder_test.go new file mode 100644 index 00000000..6fa2ff0c --- /dev/null +++ b/pkg/format/rtpmpeg4audio/encoder_test.go @@ -0,0 +1,20 @@ +package rtpmpeg4audio + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEncodeRandomInitialState(t *testing.T) { + e := &Encoder{ + PayloadType: 96, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + } + err := e.Init() + require.NoError(t, err) + require.NotEqual(t, nil, e.SSRC) + require.NotEqual(t, nil, e.InitialSequenceNumber) +} diff --git a/pkg/format/rtpmpeg4audiolatm/payload_length_info.go b/pkg/format/rtpmpeg4audio/payload_length_info.go similarity index 95% rename from pkg/format/rtpmpeg4audiolatm/payload_length_info.go rename to pkg/format/rtpmpeg4audio/payload_length_info.go index 23542a1f..ab248a7a 100644 --- a/pkg/format/rtpmpeg4audiolatm/payload_length_info.go +++ b/pkg/format/rtpmpeg4audio/payload_length_info.go @@ -1,4 +1,4 @@ -package rtpmpeg4audiolatm +package rtpmpeg4audio import ( "fmt" diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/0133a2a9bd638ceb b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/0133a2a9bd638ceb similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/0133a2a9bd638ceb rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/0133a2a9bd638ceb diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/079a435e5445c35b b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/079a435e5445c35b similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/079a435e5445c35b rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/079a435e5445c35b diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/571808e383aba6a5 b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/571808e383aba6a5 similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/571808e383aba6a5 rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/571808e383aba6a5 diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/8f3e197fb87140b2 b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/8f3e197fb87140b2 similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/8f3e197fb87140b2 rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/8f3e197fb87140b2 diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/9418bac23c1c4f72 b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/9418bac23c1c4f72 similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/9418bac23c1c4f72 rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/9418bac23c1c4f72 diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/97f31764971cea9c b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/97f31764971cea9c similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/97f31764971cea9c rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/97f31764971cea9c diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/a68704dd42c38106 b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/a68704dd42c38106 similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/a68704dd42c38106 rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/a68704dd42c38106 diff --git a/pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/c7480ec341b553d6 b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/c7480ec341b553d6 similarity index 100% rename from pkg/format/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/c7480ec341b553d6 rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderGeneric/c7480ec341b553d6 diff --git a/pkg/format/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b b/pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderLATM/079a435e5445c35b similarity index 100% rename from pkg/format/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b rename to pkg/format/rtpmpeg4audio/testdata/fuzz/FuzzDecoderLATM/079a435e5445c35b diff --git a/pkg/format/rtpmpeg4audiogeneric/rtpmpeg4audio_generic.go b/pkg/format/rtpmpeg4audiogeneric/rtpmpeg4audio_generic.go deleted file mode 100644 index 4ae065fd..00000000 --- a/pkg/format/rtpmpeg4audiogeneric/rtpmpeg4audio_generic.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package rtpmpeg4audiogeneric contains a RTP/MPEG-4 Audio decoder and encoder. -package rtpmpeg4audiogeneric diff --git a/pkg/format/rtpmpeg4audiolatm/encoder.go b/pkg/format/rtpmpeg4audiolatm/encoder.go deleted file mode 100644 index 4308e042..00000000 --- a/pkg/format/rtpmpeg4audiolatm/encoder.go +++ /dev/null @@ -1,120 +0,0 @@ -package rtpmpeg4audiolatm - -import ( - "crypto/rand" - - "github.com/pion/rtp" -) - -const ( - rtpVersion = 2 - defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) -) - -func randUint32() (uint32, error) { - var b [4]byte - _, err := rand.Read(b[:]) - if err != nil { - return 0, err - } - return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil -} - -// Encoder is a RTP/MPEG4-audio encoder. -// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 -type Encoder struct { - // payload type of packets. - PayloadType uint8 - - // SSRC of packets (optional). - // It defaults to a random value. - SSRC *uint32 - - // initial sequence number of packets (optional). - // It defaults to a random value. - InitialSequenceNumber *uint16 - - // maximum size of packet payloads (optional). - // It defaults to 1460. - PayloadMaxSize int - - sequenceNumber uint16 -} - -// Init initializes the encoder. -func (e *Encoder) Init() error { - if e.SSRC == nil { - v, err := randUint32() - if err != nil { - return err - } - e.SSRC = &v - } - if e.InitialSequenceNumber == nil { - v, err := randUint32() - if err != nil { - return err - } - v2 := uint16(v) - e.InitialSequenceNumber = &v2 - } - if e.PayloadMaxSize == 0 { - e.PayloadMaxSize = defaultPayloadMaxSize - } - - e.sequenceNumber = *e.InitialSequenceNumber - return nil -} - -func (e *Encoder) packetCount(auLen int, plil int) int { - totalLen := plil + auLen - n := totalLen / e.PayloadMaxSize - if (totalLen % e.PayloadMaxSize) != 0 { - n++ - } - return n -} - -// Encode encodes an access unit into RTP packets. -func (e *Encoder) Encode(au []byte) ([]*rtp.Packet, error) { - auLen := len(au) - plil := payloadLengthInfoEncodeSize(auLen) - packetCount := e.packetCount(auLen, plil) - - ret := make([]*rtp.Packet, packetCount) - le := e.PayloadMaxSize - plil - - for i := range ret { - if i == (packetCount - 1) { - le = len(au) - } - - var payload []byte - - if i == 0 { - payload = make([]byte, plil+le) - payloadLengthInfoEncode(plil, auLen, payload) - copy(payload[plil:], au[:le]) - au = au[le:] - le = e.PayloadMaxSize - } else { - payload = au[:le] - au = au[le:] - } - - ret[i] = &rtp.Packet{ - Header: rtp.Header{ - Version: rtpVersion, - PayloadType: e.PayloadType, - SequenceNumber: e.sequenceNumber, - SSRC: *e.SSRC, - Marker: (i == packetCount-1), - }, - Payload: payload, - } - - e.sequenceNumber++ - } - - return ret, nil -} diff --git a/pkg/format/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go b/pkg/format/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go deleted file mode 100644 index 9521630a..00000000 --- a/pkg/format/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package rtpmpeg4audiolatm contains a RTP/MPEG-4 Audio decoder and encoder. -package rtpmpeg4audiolatm