mirror of
https://github.com/aler9/gortsplib
synced 2025-10-04 23:02:45 +08:00
move MPEG-4 audio LATM to dedicated format (bluenviron/mediamtx#4403) (#834)
This commit is contained in:
11
README.md
11
README.md
@@ -106,11 +106,11 @@ Features:
|
||||
|
||||
## RTP Payload Formats
|
||||
|
||||
In RTSP, media streams are transmitted by using RTP packets, which are encoded in a specific, codec-dependent, format. This library supports formats for the following codecs:
|
||||
In RTSP, media streams are transmitted by using RTP packets, which are encoded in a specific, codec-dependent, format. This library supports the following formats:
|
||||
|
||||
### Video
|
||||
|
||||
|codec|documentation|encoder and decoder available|
|
||||
|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:|
|
||||
@@ -123,11 +123,12 @@ In RTSP, media streams are transmitted by using RTP packets, which are encoded i
|
||||
|
||||
### Audio
|
||||
|
||||
|codec|documentation|encoder and decoder available|
|
||||
|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-4 Audio LATM (AAC-LATM)|[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)||
|
||||
@@ -138,7 +139,7 @@ In RTSP, media streams are transmitted by using RTP packets, which are encoded i
|
||||
|
||||
### Other
|
||||
|
||||
|codec|documentation|encoder and decoder available|
|
||||
|format|documentation|encoder and decoder available|
|
||||
|------|-------------|-----------------------------|
|
||||
|MPEG-TS|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEGTS)||
|
||||
|KLV|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#KLV)|:heavy_check_mark:|
|
||||
@@ -165,7 +166,7 @@ In RTSP, media streams are transmitted by using RTP packets, which are encoded i
|
||||
|[Multiopus in libwebrtc](https://webrtc-review.googlesource.com/c/src/+/129768)|payload formats / Opus|
|
||||
|[RFC5215, RTP Payload Format for Vorbis Encoded Audio](https://datatracker.ietf.org/doc/html/rfc5215)|payload formats / Vorbis|
|
||||
|[RFC4184, RTP Payload Format for AC-3 Audio](https://datatracker.ietf.org/doc/html/rfc4184)|payload formats / AC-3|
|
||||
|[RFC6416, RTP Payload Format for MPEG-4 Audio/Visual Streams](https://datatracker.ietf.org/doc/html/rfc6416)|payload formats / MPEG-4 audio|
|
||||
|[RFC6416, RTP Payload Format for MPEG-4 Audio/Visual Streams](https://datatracker.ietf.org/doc/html/rfc6416)|payload formats / MPEG-4 audio LATM|
|
||||
|[RFC5574, RTP Payload Format for the Speex Codec](https://datatracker.ietf.org/doc/html/rfc5574)|payload formats / Speex|
|
||||
|[RFC3551, RTP Profile for Audio and Video Conferences with Minimal Control](https://datatracker.ietf.org/doc/html/rfc3551)|payload formats / G726, G722, G711, LPCM|
|
||||
|[RFC3190, RTP Payload Format for 12-bit DAT Audio and 20- and 24-bit Linear Sampled Audio](https://datatracker.ietf.org/doc/html/rfc3190)|payload formats / LPCM|
|
||||
|
@@ -122,7 +122,7 @@ func TestClientPlayFormats(t *testing.T) {
|
||||
Type: description.MediaTypeAudio,
|
||||
Formats: []format.Format{&format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
@@ -137,7 +137,7 @@ func TestClientPlayFormats(t *testing.T) {
|
||||
Type: description.MediaTypeAudio,
|
||||
Formats: []format.Format{&format.MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.Config{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 96000,
|
||||
ChannelCount: 2,
|
||||
|
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/bluenviron/gortsplib/v4"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||
@@ -149,7 +148,7 @@ func main() {
|
||||
err := r.Read()
|
||||
if err != nil {
|
||||
// file has ended
|
||||
if errors.Is(err, astits.ErrNoMorePackets) {
|
||||
if errors.Is(err, io.EOF) {
|
||||
log.Printf("file has ended, rewinding")
|
||||
|
||||
// rewind to start position
|
||||
|
@@ -9,7 +9,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/bluenviron/gortsplib/v4"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts"
|
||||
@@ -138,7 +137,7 @@ func (r *fileStreamer) run() {
|
||||
err := mr.Read()
|
||||
if err != nil {
|
||||
// file has ended
|
||||
if errors.Is(err, astits.ErrNoMorePackets) {
|
||||
if errors.Is(err, io.EOF) {
|
||||
log.Printf("file has ended, rewinding")
|
||||
|
||||
// rewind to start position
|
||||
|
4
go.mod
4
go.mod
@@ -3,8 +3,7 @@ module github.com/bluenviron/gortsplib/v4
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/asticode/go-astits v1.13.0
|
||||
github.com/bluenviron/mediacommon/v2 v2.3.0
|
||||
github.com/bluenviron/mediacommon/v2 v2.3.1-0.20250720151422-8fb6595e0eac
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/pion/rtcp v1.2.15
|
||||
github.com/pion/rtp v1.8.21
|
||||
@@ -16,6 +15,7 @@ require (
|
||||
|
||||
require (
|
||||
github.com/asticode/go-astikit v0.30.0 // indirect
|
||||
github.com/asticode/go-astits v1.13.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pion/logging v0.2.3 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
|
4
go.sum
4
go.sum
@@ -2,8 +2,8 @@ github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflx
|
||||
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||
github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwfKZ1c=
|
||||
github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
|
||||
github.com/bluenviron/mediacommon/v2 v2.3.0 h1:FigBBZi0UUFNhvvcQi1n9vd5uZKts2Uv1Ramq1VFUV8=
|
||||
github.com/bluenviron/mediacommon/v2 v2.3.0/go.mod h1:a6MbPmXtYda9mKibKVMZlW20GYLLrX2R7ZkUE+1pwV0=
|
||||
github.com/bluenviron/mediacommon/v2 v2.3.1-0.20250720151422-8fb6595e0eac h1:EgZce5Y9ZV6lcvc6p9ZqywjJKkCdpx4V0h5sAgHrLGo=
|
||||
github.com/bluenviron/mediacommon/v2 v2.3.1-0.20250720151422-8fb6595e0eac/go.mod h1:a6MbPmXtYda9mKibKVMZlW20GYLLrX2R7ZkUE+1pwV0=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@@ -160,9 +160,12 @@ func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error)
|
||||
case codec == "vorbis" && payloadType >= 96 && payloadType <= 127:
|
||||
return &Vorbis{}
|
||||
|
||||
case (codec == "mpeg4-generic" || codec == "mp4a-latm") && payloadType >= 96 && payloadType <= 127:
|
||||
case codec == "mpeg4-generic" && payloadType >= 96 && payloadType <= 127:
|
||||
return &MPEG4Audio{}
|
||||
|
||||
case codec == "mp4a-latm" && payloadType >= 96 && payloadType <= 127:
|
||||
return &MPEG4AudioLATM{}
|
||||
|
||||
case codec == "ac3" && payloadType >= 96 && payloadType <= 127:
|
||||
return &AC3{}
|
||||
|
||||
|
@@ -282,7 +282,7 @@ var casesFormat = []struct {
|
||||
&MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
ProfileLevelID: 1,
|
||||
Config: &mpeg4audio.Config{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
@@ -314,7 +314,7 @@ var casesFormat = []struct {
|
||||
&MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
ProfileLevelID: 1,
|
||||
Config: &mpeg4audio.Config{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
@@ -346,7 +346,7 @@ var casesFormat = []struct {
|
||||
&MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
ProfileLevelID: 14,
|
||||
Config: &mpeg4audio.Config{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
@@ -374,7 +374,7 @@ var casesFormat = []struct {
|
||||
&MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
ProfileLevelID: 48,
|
||||
Config: &mpeg4audio.Config{
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
ExtensionType: 29,
|
||||
ExtensionSampleRate: 48000,
|
||||
@@ -401,8 +401,7 @@ var casesFormat = []struct {
|
||||
"a=rtpmap:96 MP4A-LATM/24000/2\n" +
|
||||
"a=fmtp:96 profile-level-id=1; " +
|
||||
"bitrate=64000; cpresent=0; object=2; config=400026203fc0\n",
|
||||
&MPEG4Audio{
|
||||
LATM: true,
|
||||
&MPEG4AudioLATM{
|
||||
PayloadTyp: 96,
|
||||
ProfileLevelID: 1,
|
||||
Bitrate: intPtr(64000),
|
||||
@@ -410,7 +409,7 @@ var casesFormat = []struct {
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.Config{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 24000,
|
||||
ChannelCount: 2,
|
||||
@@ -438,8 +437,7 @@ var casesFormat = []struct {
|
||||
"a=rtpmap:110 MP4A-LATM/24000/1\n" +
|
||||
"a=fmtp:110 profile-level-id=15; " +
|
||||
"cpresent=0; object=2; config=400026103fc0; sbr-enabled=1\n",
|
||||
&MPEG4Audio{
|
||||
LATM: true,
|
||||
&MPEG4AudioLATM{
|
||||
PayloadTyp: 110,
|
||||
ProfileLevelID: 15,
|
||||
CPresent: false,
|
||||
@@ -447,7 +445,7 @@ var casesFormat = []struct {
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.Config{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 24000,
|
||||
ChannelCount: 1,
|
||||
@@ -475,8 +473,7 @@ var casesFormat = []struct {
|
||||
"a=rtpmap:110 MP4A-LATM/48000/2\n" +
|
||||
"a=fmtp:110 profile-level-id=44; " +
|
||||
"bitrate=64000; cpresent=0; config=40005623101fe0; sbr-enabled=1\n",
|
||||
&MPEG4Audio{
|
||||
LATM: true,
|
||||
&MPEG4AudioLATM{
|
||||
PayloadTyp: 110,
|
||||
ProfileLevelID: 44,
|
||||
CPresent: false,
|
||||
@@ -485,7 +482,7 @@ var casesFormat = []struct {
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.Config{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
ExtensionType: 5,
|
||||
ExtensionSampleRate: 48000,
|
||||
@@ -516,8 +513,7 @@ var casesFormat = []struct {
|
||||
"a=rtpmap:110 MP4A-LATM/48000/2\n" +
|
||||
"a=fmtp:110 profile-level-id=48; " +
|
||||
"bitrate=64000; cpresent=0; config=4001d613101fe0\n",
|
||||
&MPEG4Audio{
|
||||
LATM: true,
|
||||
&MPEG4AudioLATM{
|
||||
PayloadTyp: 110,
|
||||
ProfileLevelID: 48,
|
||||
Bitrate: intPtr(64000),
|
||||
@@ -525,7 +521,7 @@ var casesFormat = []struct {
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.Config{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
ExtensionType: 29,
|
||||
ExtensionSampleRate: 48000,
|
||||
@@ -555,15 +551,14 @@ var casesFormat = []struct {
|
||||
"a=rtpmap:110 MP4A-LATM/48000\n" +
|
||||
"a=fmtp:110 profile-level-id=30; " +
|
||||
"cpresent=0; config=40002310\n",
|
||||
&MPEG4Audio{
|
||||
LATM: true,
|
||||
&MPEG4AudioLATM{
|
||||
PayloadTyp: 110,
|
||||
ProfileLevelID: 30,
|
||||
CPresent: false,
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.Config{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 1,
|
||||
@@ -587,16 +582,15 @@ var casesFormat = []struct {
|
||||
"v=0\n" +
|
||||
"s=\n" +
|
||||
"m=audio 0 RTP/AVP 96\n" +
|
||||
"a=rtpmap:96 MP4A-LATM/48000/2\n" +
|
||||
"a=rtpmap:96 MP4A-LATM/90000/1\n" +
|
||||
"a=fmtp:96 cpresent=1\n",
|
||||
&MPEG4Audio{
|
||||
&MPEG4AudioLATM{
|
||||
PayloadTyp: 96,
|
||||
LATM: true,
|
||||
ProfileLevelID: 30,
|
||||
CPresent: true,
|
||||
},
|
||||
96,
|
||||
"MP4A-LATM/16000/1",
|
||||
"MP4A-LATM/90000/1",
|
||||
map[string]string{
|
||||
"cpresent": "1",
|
||||
"profile-level-id": "30",
|
||||
|
@@ -14,153 +14,83 @@ import (
|
||||
|
||||
// MPEG4Audio is the RTP format for a MPEG-4 Audio codec.
|
||||
// Specification: RFC3640
|
||||
// Specification: RFC6416, section 7.3
|
||||
type MPEG4Audio struct {
|
||||
// payload type of packets.
|
||||
PayloadTyp uint8
|
||||
|
||||
// use LATM format (RFC6416) instead of generic format (RFC3640).
|
||||
LATM bool
|
||||
|
||||
// profile level ID.
|
||||
ProfileLevelID int
|
||||
|
||||
// generic only
|
||||
Config *mpeg4audio.Config
|
||||
ProfileLevelID int
|
||||
Config *mpeg4audio.AudioSpecificConfig
|
||||
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" && 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: %w", err)
|
||||
}
|
||||
|
||||
f.Config = &mpeg4audio.Config{}
|
||||
err = f.Config.Unmarshal(enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid AAC config: %w", err)
|
||||
}
|
||||
|
||||
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)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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: %w", err)
|
||||
}
|
||||
|
||||
f.StreamMuxConfig = &mpeg4audio.StreamMuxConfig{}
|
||||
err = f.StreamMuxConfig.Unmarshal(enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid AAC config: %w", err)
|
||||
}
|
||||
|
||||
case "sbr-enabled":
|
||||
v := (val == "1")
|
||||
f.SBREnabled = &v
|
||||
case "mode":
|
||||
if strings.ToLower(val) != "aac-hbr" && strings.ToLower(val) != "aac_hbr" {
|
||||
return fmt.Errorf("unsupported AAC mode: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
if f.CPresent {
|
||||
if f.StreamMuxConfig != nil {
|
||||
return fmt.Errorf("config and cpresent can't be used at the same time")
|
||||
case "profile-level-id":
|
||||
tmp, err := strconv.ParseUint(val, 10, 31)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid profile-level-id: %v", val)
|
||||
}
|
||||
} else {
|
||||
if f.StreamMuxConfig == nil {
|
||||
return fmt.Errorf("config is missing")
|
||||
|
||||
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.AudioSpecificConfig{}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -171,13 +101,7 @@ func (f *MPEG4Audio) Codec() string {
|
||||
|
||||
// ClockRate implements Format.
|
||||
func (f *MPEG4Audio) ClockRate() int {
|
||||
if !f.LATM {
|
||||
return f.Config.SampleRate
|
||||
}
|
||||
if f.CPresent {
|
||||
return 16000
|
||||
}
|
||||
return f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.SampleRate
|
||||
return f.Config.SampleRate
|
||||
}
|
||||
|
||||
// PayloadType implements Format.
|
||||
@@ -187,107 +111,52 @@ func (f *MPEG4Audio) PayloadType() uint8 {
|
||||
|
||||
// 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)
|
||||
sampleRate := f.Config.SampleRate
|
||||
if f.Config.ExtensionSampleRate != 0 {
|
||||
sampleRate = f.Config.ExtensionSampleRate
|
||||
}
|
||||
|
||||
if f.CPresent {
|
||||
return "MP4A-LATM/16000/1"
|
||||
}
|
||||
|
||||
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 := f.Config.ChannelCount
|
||||
if f.Config.ExtensionType == mpeg4audio.ObjectTypePS {
|
||||
channelCount = 2
|
||||
}
|
||||
|
||||
return "MP4A-LATM/" + strconv.FormatInt(int64(sampleRate), 10) +
|
||||
return "mpeg4-generic/" + 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
|
||||
}
|
||||
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
|
||||
profileLevelID := f.ProfileLevelID
|
||||
if profileLevelID == 0 { // support legacy definition which didn't include profile-level-id
|
||||
profileLevelID = 1
|
||||
}
|
||||
|
||||
fmtp := map[string]string{
|
||||
"profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10),
|
||||
"streamtype": "5",
|
||||
"mode": "AAC-hbr",
|
||||
"profile-level-id": strconv.FormatInt(int64(profileLevelID), 10),
|
||||
}
|
||||
|
||||
if f.Bitrate != nil {
|
||||
fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10)
|
||||
if f.SizeLength > 0 {
|
||||
fmtp["sizelength"] = strconv.FormatInt(int64(f.SizeLength), 10)
|
||||
}
|
||||
|
||||
if f.CPresent {
|
||||
fmtp["cpresent"] = "1"
|
||||
} else {
|
||||
fmtp["cpresent"] = "0"
|
||||
|
||||
enc, err := f.StreamMuxConfig.Marshal()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmtp["config"] = hex.EncodeToString(enc)
|
||||
fmtp["object"] = strconv.FormatInt(int64(f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.Type), 10)
|
||||
if f.IndexLength > 0 {
|
||||
fmtp["indexlength"] = strconv.FormatInt(int64(f.IndexLength), 10)
|
||||
}
|
||||
|
||||
if f.SBREnabled != nil {
|
||||
if *f.SBREnabled {
|
||||
fmtp["SBR-enabled"] = "1"
|
||||
} else {
|
||||
fmtp["SBR-enabled"] = "0"
|
||||
}
|
||||
if f.IndexDeltaLength > 0 {
|
||||
fmtp["indexdeltalength"] = strconv.FormatInt(int64(f.IndexDeltaLength), 10)
|
||||
}
|
||||
|
||||
fmtp["config"] = hex.EncodeToString(enc)
|
||||
|
||||
return fmtp
|
||||
}
|
||||
|
||||
@@ -299,7 +168,6 @@ func (f *MPEG4Audio) PTSEqualsDTS(*rtp.Packet) bool {
|
||||
// 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,
|
||||
@@ -316,7 +184,6 @@ func (f *MPEG4Audio) CreateDecoder() (*rtpmpeg4audio.Decoder, error) {
|
||||
// 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,
|
||||
@@ -332,12 +199,8 @@ func (f *MPEG4Audio) CreateEncoder() (*rtpmpeg4audio.Encoder, error) {
|
||||
}
|
||||
|
||||
// GetConfig returns the MPEG-4 Audio configuration.
|
||||
//
|
||||
// Deprecated: redundant. Use f.Config.
|
||||
func (f *MPEG4Audio) GetConfig() *mpeg4audio.Config {
|
||||
if !f.LATM {
|
||||
return f.Config
|
||||
}
|
||||
if f.CPresent {
|
||||
return nil
|
||||
}
|
||||
return f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig
|
||||
return f.Config
|
||||
}
|
||||
|
223
pkg/format/mpeg4_audio_latm.go
Normal file
223
pkg/format/mpeg4_audio_latm.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpfragmented"
|
||||
)
|
||||
|
||||
func allLayersHaveSameTypeRateChannelsExtType(c *mpeg4audio.StreamMuxConfig) bool {
|
||||
typ := c.Programs[0].Layers[0].AudioSpecificConfig.Type
|
||||
rate := c.Programs[0].Layers[0].AudioSpecificConfig.SampleRate
|
||||
channels := c.Programs[0].Layers[0].AudioSpecificConfig.ChannelCount
|
||||
extensionType := c.Programs[0].Layers[0].AudioSpecificConfig.ExtensionType
|
||||
|
||||
for i, p := range c.Programs {
|
||||
for j, l := range p.Layers {
|
||||
if i == 0 && j == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if l.AudioSpecificConfig.Type != typ ||
|
||||
l.AudioSpecificConfig.SampleRate != rate ||
|
||||
l.AudioSpecificConfig.ChannelCount != channels ||
|
||||
l.AudioSpecificConfig.ExtensionType != extensionType {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MPEG4AudioLATM is the RTP format for a MPEG-4 Audio codec, LATM-encoded.
|
||||
// Specification: RFC6416, section 7.3
|
||||
type MPEG4AudioLATM struct {
|
||||
// payload type of packets.
|
||||
PayloadTyp uint8
|
||||
|
||||
ProfileLevelID int
|
||||
Bitrate *int
|
||||
CPresent bool
|
||||
StreamMuxConfig *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.StreamMuxConfig = &mpeg4audio.StreamMuxConfig{}
|
||||
err = f.StreamMuxConfig.Unmarshal(enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid AAC config: %w", 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")
|
||||
}
|
||||
|
||||
if ctx.clock != "90000/1" {
|
||||
return fmt.Errorf("when cpresent=1, clock rate must be 90000/1, but is %s", ctx.clock)
|
||||
}
|
||||
} else {
|
||||
if f.StreamMuxConfig == nil {
|
||||
return fmt.Errorf("config is missing")
|
||||
}
|
||||
|
||||
if !allLayersHaveSameTypeRateChannelsExtType(f.StreamMuxConfig) {
|
||||
return fmt.Errorf("all LATM layers must have the same type, rate, channel count, extension type")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Codec implements Format.
|
||||
func (f *MPEG4AudioLATM) Codec() string {
|
||||
return "MPEG-4 Audio LATM"
|
||||
}
|
||||
|
||||
// ClockRate implements Format.
|
||||
func (f *MPEG4AudioLATM) ClockRate() int {
|
||||
if f.CPresent {
|
||||
return 90000
|
||||
}
|
||||
|
||||
return f.StreamMuxConfig.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 {
|
||||
if f.CPresent {
|
||||
return "MP4A-LATM/90000/1"
|
||||
}
|
||||
|
||||
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 *MPEG4AudioLATM) FMTP() map[string]string {
|
||||
fmtp := map[string]string{
|
||||
"profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10),
|
||||
}
|
||||
|
||||
if f.Bitrate != nil {
|
||||
fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10)
|
||||
}
|
||||
|
||||
if f.CPresent {
|
||||
fmtp["cpresent"] = "1"
|
||||
} else {
|
||||
fmtp["cpresent"] = "0"
|
||||
|
||||
enc, err := f.StreamMuxConfig.Marshal()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmtp["config"] = hex.EncodeToString(enc)
|
||||
fmtp["object"] = strconv.FormatInt(int64(f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.Type), 10)
|
||||
}
|
||||
|
||||
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() (*rtpfragmented.Decoder, error) {
|
||||
d := &rtpfragmented.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() (*rtpfragmented.Encoder, error) {
|
||||
e := &rtpfragmented.Encoder{
|
||||
PayloadType: f.PayloadTyp,
|
||||
}
|
||||
|
||||
err := e.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
64
pkg/format/mpeg4_audio_latm_test.go
Normal file
64
pkg/format/mpeg4_audio_latm_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMPEG4AudioLATMAttributes(t *testing.T) {
|
||||
format := &MPEG4AudioLATM{
|
||||
PayloadTyp: 96,
|
||||
ProfileLevelID: 1,
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 44100,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
LatmBufferFullness: 255,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
require.Equal(t, "MPEG-4 Audio LATM", 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,
|
||||
StreamMuxConfig: &mpeg4audio.StreamMuxConfig{
|
||||
Programs: []*mpeg4audio.StreamMuxConfigProgram{{
|
||||
Layers: []*mpeg4audio.StreamMuxConfigLayer{{
|
||||
AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{
|
||||
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)
|
||||
}
|
@@ -10,117 +10,51 @@ import (
|
||||
)
|
||||
|
||||
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{
|
||||
format := &MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
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())
|
||||
})
|
||||
},
|
||||
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.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 48000,
|
||||
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,
|
||||
}
|
||||
format := &MPEG4Audio{
|
||||
PayloadTyp: 96,
|
||||
Config: &mpeg4audio.AudioSpecificConfig{
|
||||
Type: mpeg4audio.ObjectTypeAACLC,
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
SizeLength: 13,
|
||||
IndexLength: 3,
|
||||
IndexDeltaLength: 3,
|
||||
}
|
||||
|
||||
enc, err := format.CreateEncoder()
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
})
|
||||
byts, err := dec.Decode(pkts[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts)
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpfragmented"
|
||||
)
|
||||
|
||||
// MPEG4Video is the RTP format for a MPEG-4 Video codec.
|
||||
@@ -93,8 +93,8 @@ func (f *MPEG4Video) PTSEqualsDTS(*rtp.Packet) bool {
|
||||
}
|
||||
|
||||
// CreateDecoder creates a decoder able to decode the content of the format.
|
||||
func (f *MPEG4Video) CreateDecoder() (*rtpmpeg4video.Decoder, error) {
|
||||
d := &rtpmpeg4video.Decoder{}
|
||||
func (f *MPEG4Video) CreateDecoder() (*rtpfragmented.Decoder, error) {
|
||||
d := &rtpfragmented.Decoder{}
|
||||
|
||||
err := d.Init()
|
||||
if err != nil {
|
||||
@@ -105,8 +105,8 @@ func (f *MPEG4Video) CreateDecoder() (*rtpmpeg4video.Decoder, error) {
|
||||
}
|
||||
|
||||
// CreateEncoder creates an encoder able to encode the content of the format.
|
||||
func (f *MPEG4Video) CreateEncoder() (*rtpmpeg4video.Encoder, error) {
|
||||
e := &rtpmpeg4video.Encoder{
|
||||
func (f *MPEG4Video) CreateEncoder() (*rtpfragmented.Encoder, error) {
|
||||
e := &rtpfragmented.Encoder{
|
||||
PayloadType: f.PayloadTyp,
|
||||
}
|
||||
|
||||
|
81
pkg/format/rtpfragmented/decoder.go
Normal file
81
pkg/format/rtpfragmented/decoder.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package rtpfragmented
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||
)
|
||||
|
||||
// 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 decoder for codecs with access units that can be fragmented.
|
||||
type Decoder struct {
|
||||
fragments [][]byte
|
||||
fragmentsSize int
|
||||
fragmentNextSeqNum uint16
|
||||
}
|
||||
|
||||
// Init initializes the decoder.
|
||||
func (d *Decoder) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) resetFragments() {
|
||||
d.fragments = d.fragments[:0]
|
||||
d.fragmentsSize = 0
|
||||
}
|
||||
|
||||
// Decode decodes a frame from a RTP packet.
|
||||
func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) {
|
||||
var frame []byte
|
||||
|
||||
if d.fragmentsSize == 0 {
|
||||
if pkt.Marker {
|
||||
frame = pkt.Payload
|
||||
} else {
|
||||
d.fragmentsSize = len(pkt.Payload)
|
||||
d.fragments = append(d.fragments, pkt.Payload)
|
||||
d.fragmentNextSeqNum = pkt.SequenceNumber + 1
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
} else {
|
||||
if pkt.SequenceNumber != d.fragmentNextSeqNum {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("discarding frame since a RTP packet is missing")
|
||||
}
|
||||
|
||||
d.fragmentsSize += len(pkt.Payload)
|
||||
|
||||
if d.fragmentsSize > mpeg4video.MaxFrameSize {
|
||||
errSize := d.fragmentsSize
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("frame size (%d) is too big, maximum is %d",
|
||||
errSize, mpeg4video.MaxFrameSize)
|
||||
}
|
||||
|
||||
d.fragments = append(d.fragments, pkt.Payload)
|
||||
d.fragmentNextSeqNum++
|
||||
|
||||
if !pkt.Marker {
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
|
||||
frame = joinFragments(d.fragments, d.fragmentsSize)
|
||||
d.resetFragments()
|
||||
}
|
||||
|
||||
return frame, nil
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
package rtpmpeg4audio
|
||||
package rtpfragmented
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
@@ -9,25 +8,17 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodeLATM(t *testing.T) {
|
||||
for _, ca := range casesLATM {
|
||||
func TestDecode(t *testing.T) {
|
||||
for _, ca := range cases {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
d := &Decoder{
|
||||
LATM: true,
|
||||
}
|
||||
d := &Decoder{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
var aus [][]byte
|
||||
var frame []byte
|
||||
|
||||
for _, pkt := range ca.pkts {
|
||||
clone := pkt.Clone()
|
||||
|
||||
aus, err = d.Decode(pkt)
|
||||
|
||||
// test input integrity
|
||||
require.Equal(t, clone, pkt)
|
||||
|
||||
frame, err = d.Decode(pkt)
|
||||
if errors.Is(err, ErrMorePacketsNeeded) {
|
||||
continue
|
||||
}
|
||||
@@ -35,37 +26,13 @@ func TestDecodeLATM(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Equal(t, ca.au, aus[0])
|
||||
require.Equal(t, ca.frame, frame)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeLATMOtherData(t *testing.T) {
|
||||
d := &Decoder{
|
||||
LATM: true,
|
||||
}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
aus, err := d.Decode(&rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 2646308882,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x04, 0x01, 0x02, 0x03, 0x04, 5, 6,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, []byte{1, 2, 3, 4}, aus[0])
|
||||
}
|
||||
|
||||
func TestDecodeLATMErrorMissingPacket(t *testing.T) {
|
||||
d := &Decoder{LATM: true}
|
||||
func TestDecodeErrorMissingPacket(t *testing.T) {
|
||||
d := &Decoder{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -77,12 +44,7 @@ func TestDecodeLATMErrorMissingPacket(t *testing.T) {
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
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},
|
||||
),
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
})
|
||||
require.Equal(t, ErrMorePacketsNeeded, err)
|
||||
|
||||
@@ -94,20 +56,14 @@ func TestDecodeLATMErrorMissingPacket(t *testing.T) {
|
||||
SequenceNumber: 17647,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
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},
|
||||
),
|
||||
Payload: []byte{0x01, 0x02, 0x03, 0x04},
|
||||
})
|
||||
require.EqualError(t, err, "discarding frame since a RTP packet is missing")
|
||||
}
|
||||
|
||||
func FuzzDecoderLATM(f *testing.F) {
|
||||
func FuzzDecoder(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) {
|
||||
d := &Decoder{
|
||||
LATM: true,
|
||||
}
|
||||
d := &Decoder{}
|
||||
err := d.Init()
|
||||
require.NoError(t, err)
|
||||
|
107
pkg/format/rtpfragmented/encoder.go
Normal file
107
pkg/format/rtpfragmented/encoder.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package rtpfragmented
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const (
|
||||
rtpVersion = 2
|
||||
defaultPayloadMaxSize = 1450 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) - 10 (SRTP overhead)
|
||||
)
|
||||
|
||||
func randUint32() (uint32, error) {
|
||||
var b [4]byte
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
|
||||
}
|
||||
|
||||
func packetCount(avail, le int) int {
|
||||
n := le / avail
|
||||
if (le % avail) != 0 {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Encoder is a RTP encoder for codecs with access units that can be fragmented.
|
||||
type Encoder struct {
|
||||
// payload type of packets.
|
||||
PayloadType uint8
|
||||
|
||||
// SSRC of packets (optional).
|
||||
// It defaults to a random value.
|
||||
SSRC *uint32
|
||||
|
||||
// initial sequence number of packets (optional).
|
||||
// It defaults to a random value.
|
||||
InitialSequenceNumber *uint16
|
||||
|
||||
// maximum size of packet payloads (optional).
|
||||
// It defaults to 1450.
|
||||
PayloadMaxSize int
|
||||
|
||||
sequenceNumber uint16
|
||||
}
|
||||
|
||||
// Init initializes the encoder.
|
||||
func (e *Encoder) Init() error {
|
||||
if e.SSRC == nil {
|
||||
v, err := randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.SSRC = &v
|
||||
}
|
||||
if e.InitialSequenceNumber == nil {
|
||||
v, err := randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v2 := uint16(v)
|
||||
e.InitialSequenceNumber = &v2
|
||||
}
|
||||
if e.PayloadMaxSize == 0 {
|
||||
e.PayloadMaxSize = defaultPayloadMaxSize
|
||||
}
|
||||
|
||||
e.sequenceNumber = *e.InitialSequenceNumber
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes a frame into RTP packets.
|
||||
func (e *Encoder) Encode(frame []byte) ([]*rtp.Packet, error) {
|
||||
avail := e.PayloadMaxSize
|
||||
le := len(frame)
|
||||
packetCount := packetCount(avail, le)
|
||||
|
||||
ret := make([]*rtp.Packet, packetCount)
|
||||
pos := 0
|
||||
le = avail
|
||||
|
||||
for i := range ret {
|
||||
if i == (packetCount - 1) {
|
||||
le = len(frame[pos:])
|
||||
}
|
||||
|
||||
ret[i] = &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: rtpVersion,
|
||||
PayloadType: e.PayloadType,
|
||||
SequenceNumber: e.sequenceNumber,
|
||||
SSRC: *e.SSRC,
|
||||
Marker: (i == packetCount-1),
|
||||
},
|
||||
Payload: frame[pos : pos+le],
|
||||
}
|
||||
|
||||
pos += le
|
||||
e.sequenceNumber++
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
97
pkg/format/rtpfragmented/encoder_test.go
Normal file
97
pkg/format/rtpfragmented/encoder_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package rtpfragmented
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func uint16Ptr(v uint16) *uint16 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func uint32Ptr(v uint32) *uint32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
var cases = []struct {
|
||||
name string
|
||||
frame []byte
|
||||
pkts []*rtp.Packet
|
||||
}{
|
||||
{
|
||||
"single",
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented",
|
||||
bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 150/4),
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 100/4),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 50/4),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
for _, ca := range cases {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
e := &Encoder{
|
||||
PayloadType: 96,
|
||||
SSRC: uint32Ptr(0x9dbb7812),
|
||||
InitialSequenceNumber: uint16Ptr(0x44ed),
|
||||
PayloadMaxSize: 100,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := e.Encode(ca.frame)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.pkts, pkts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeRandomInitialState(t *testing.T) {
|
||||
e := &Encoder{
|
||||
PayloadType: 96,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, nil, e.SSRC)
|
||||
require.NotEqual(t, nil, e.InitialSequenceNumber)
|
||||
}
|
2
pkg/format/rtpfragmented/rtpfragmented.go
Normal file
2
pkg/format/rtpfragmented/rtpfragmented.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package rtpfragmented contains a RTP decoder and encoder for codecs with access units that can be fragmented.
|
||||
package rtpfragmented
|
@@ -2,7 +2,10 @@ package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/bits"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
@@ -20,16 +23,13 @@ func joinFragments(fragments [][]byte, size int) []byte {
|
||||
|
||||
// Decoder is a RTP/MPEG-4 Audio decoder.
|
||||
// Specification: RFC3640
|
||||
// Specification: 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
|
||||
|
||||
@@ -37,7 +37,6 @@ type Decoder struct {
|
||||
adtsMode bool
|
||||
fragments [][]byte
|
||||
fragmentsSize int
|
||||
fragmentsExpected int
|
||||
fragmentNextSeqNum uint16
|
||||
}
|
||||
|
||||
@@ -51,10 +50,195 @@ func (d *Decoder) resetFragments() {
|
||||
d.fragmentsSize = 0
|
||||
}
|
||||
|
||||
// Decode decodes AUs from a RTP packet.
|
||||
// Decode decodes AUs (non-LATM) or audioMuxElements (LATM) from a RTP packet.
|
||||
func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) {
|
||||
if !d.LATM {
|
||||
return d.decodeGeneric(pkt)
|
||||
if len(pkt.Payload) < 2 {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
return d.decodeLATM(pkt)
|
||||
|
||||
// AU-headers-length (16 bits)
|
||||
headersLen := int(uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1]))
|
||||
if headersLen == 0 {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("invalid AU-headers-length")
|
||||
}
|
||||
payload := pkt.Payload[2:]
|
||||
|
||||
// AU-headers
|
||||
dataLens, err := d.readAUHeaders(payload, headersLen)
|
||||
if err != nil {
|
||||
d.resetFragments()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pos := (headersLen / 8)
|
||||
if (headersLen % 8) != 0 {
|
||||
pos++
|
||||
}
|
||||
payload = payload[pos:]
|
||||
|
||||
var aus [][]byte
|
||||
|
||||
if d.fragmentsSize == 0 {
|
||||
d.resetFragments()
|
||||
|
||||
if pkt.Marker {
|
||||
// AUs
|
||||
aus = make([][]byte, len(dataLens))
|
||||
for i, dataLen := range dataLens {
|
||||
if len(payload) < int(dataLen) {
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
|
||||
aus[i] = payload[:dataLen]
|
||||
payload = payload[dataLen:]
|
||||
}
|
||||
} else {
|
||||
if len(dataLens) != 1 {
|
||||
return nil, fmt.Errorf("a fragmented packet can only contain one AU")
|
||||
}
|
||||
|
||||
if len(payload) < int(dataLens[0]) {
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
|
||||
d.fragmentsSize = int(dataLens[0])
|
||||
d.fragments = append(d.fragments, payload[:dataLens[0]])
|
||||
d.fragmentNextSeqNum = pkt.SequenceNumber + 1
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
} else {
|
||||
// we are decoding a fragmented AU
|
||||
if len(dataLens) != 1 {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("a fragmented packet can only contain one AU")
|
||||
}
|
||||
|
||||
if len(payload) < int(dataLens[0]) {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
|
||||
if pkt.SequenceNumber != d.fragmentNextSeqNum {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("discarding frame since a RTP packet is missing")
|
||||
}
|
||||
|
||||
d.fragmentsSize += int(dataLens[0])
|
||||
|
||||
if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize {
|
||||
errSize := d.fragmentsSize
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("access unit size (%d) is too big, maximum is %d",
|
||||
errSize, mpeg4audio.MaxAccessUnitSize)
|
||||
}
|
||||
|
||||
d.fragments = append(d.fragments, payload[:dataLens[0]])
|
||||
d.fragmentNextSeqNum++
|
||||
|
||||
if !pkt.Marker {
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
|
||||
aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
|
||||
d.resetFragments()
|
||||
}
|
||||
|
||||
return d.removeADTS(aus)
|
||||
}
|
||||
|
||||
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: %w", err)
|
||||
}
|
||||
|
||||
if len(pkts) != 1 {
|
||||
return nil, fmt.Errorf("multiple ADTS packets are not supported")
|
||||
}
|
||||
|
||||
aus[0] = pkts[0].AU
|
||||
}
|
||||
|
||||
return aus, nil
|
||||
}
|
||||
|
@@ -1,201 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/bits"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
func (d *Decoder) decodeGeneric(pkt *rtp.Packet) ([][]byte, error) {
|
||||
if len(pkt.Payload) < 2 {
|
||||
d.resetFragments()
|
||||
return nil, 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.resetFragments()
|
||||
return nil, fmt.Errorf("invalid AU-headers-length")
|
||||
}
|
||||
payload := pkt.Payload[2:]
|
||||
|
||||
// AU-headers
|
||||
dataLens, err := d.readAUHeaders(payload, headersLen)
|
||||
if err != nil {
|
||||
d.resetFragments()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pos := (headersLen / 8)
|
||||
if (headersLen % 8) != 0 {
|
||||
pos++
|
||||
}
|
||||
payload = payload[pos:]
|
||||
|
||||
var aus [][]byte
|
||||
|
||||
if d.fragmentsSize == 0 {
|
||||
d.resetFragments()
|
||||
|
||||
if pkt.Marker {
|
||||
// AUs
|
||||
aus = make([][]byte, len(dataLens))
|
||||
for i, dataLen := range dataLens {
|
||||
if len(payload) < int(dataLen) {
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
|
||||
aus[i] = payload[:dataLen]
|
||||
payload = payload[dataLen:]
|
||||
}
|
||||
} else {
|
||||
if len(dataLens) != 1 {
|
||||
return nil, fmt.Errorf("a fragmented packet can only contain one AU")
|
||||
}
|
||||
|
||||
if len(payload) < int(dataLens[0]) {
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
|
||||
d.fragmentsSize = int(dataLens[0])
|
||||
d.fragments = append(d.fragments, payload[:dataLens[0]])
|
||||
d.fragmentNextSeqNum = pkt.SequenceNumber + 1
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
} else {
|
||||
// we are decoding a fragmented AU
|
||||
if len(dataLens) != 1 {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("a fragmented packet can only contain one AU")
|
||||
}
|
||||
|
||||
if len(payload) < int(dataLens[0]) {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("payload is too short")
|
||||
}
|
||||
|
||||
if pkt.SequenceNumber != d.fragmentNextSeqNum {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("discarding frame since a RTP packet is missing")
|
||||
}
|
||||
|
||||
d.fragmentsSize += int(dataLens[0])
|
||||
|
||||
if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize {
|
||||
errSize := d.fragmentsSize
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("access unit size (%d) is too big, maximum is %d",
|
||||
errSize, mpeg4audio.MaxAccessUnitSize)
|
||||
}
|
||||
|
||||
d.fragments = append(d.fragments, payload[:dataLens[0]])
|
||||
d.fragmentNextSeqNum++
|
||||
|
||||
if !pkt.Marker {
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
|
||||
aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
|
||||
d.resetFragments()
|
||||
}
|
||||
|
||||
return d.removeADTS(aus)
|
||||
}
|
||||
|
||||
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: %w", err)
|
||||
}
|
||||
|
||||
if len(pkts) != 1 {
|
||||
return nil, fmt.Errorf("multiple ADTS packets are not supported")
|
||||
}
|
||||
|
||||
aus[0] = pkts[0].AU
|
||||
}
|
||||
|
||||
return aus, nil
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
func (d *Decoder) decodeLATM(pkt *rtp.Packet) ([][]byte, error) {
|
||||
var au []byte
|
||||
buf := pkt.Payload
|
||||
|
||||
if d.fragmentsSize == 0 {
|
||||
pl, n, err := payloadLengthInfoDecode(buf)
|
||||
if err != nil {
|
||||
return nil, 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 {
|
||||
errSize := pl
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("access unit size (%d) is too big, maximum is %d",
|
||||
errSize, mpeg4audio.MaxAccessUnitSize)
|
||||
}
|
||||
|
||||
d.fragments = append(d.fragments, buf)
|
||||
d.fragmentsSize = pl
|
||||
d.fragmentsExpected = pl - bl
|
||||
d.fragmentNextSeqNum = pkt.SequenceNumber + 1
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
} else {
|
||||
if pkt.SequenceNumber != d.fragmentNextSeqNum {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("discarding frame since a RTP packet is missing")
|
||||
}
|
||||
|
||||
bl := len(buf)
|
||||
|
||||
if d.fragmentsExpected > bl {
|
||||
d.fragments = append(d.fragments, buf)
|
||||
d.fragmentsExpected -= bl
|
||||
d.fragmentNextSeqNum++
|
||||
return nil, 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.resetFragments()
|
||||
}
|
||||
|
||||
return [][]byte{au}, nil
|
||||
}
|
@@ -3,6 +3,8 @@ package rtpmpeg4audio
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/bits"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
@@ -20,16 +22,20 @@ func randUint32() (uint32, error) {
|
||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
|
||||
}
|
||||
|
||||
func packetCountGeneric(avail, le int) int {
|
||||
n := le / avail
|
||||
if (le % avail) != 0 {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Encoder is a RTP/MPEG-4 audio encoder.
|
||||
// Specification: RFC3640
|
||||
// Specification: 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
|
||||
|
||||
@@ -81,8 +87,181 @@ func (e *Encoder) Init() error {
|
||||
|
||||
// Encode encodes AUs (non-LATM) or AudioMuxElements (LATM) into RTP packets.
|
||||
func (e *Encoder) Encode(aus [][]byte) ([]*rtp.Packet, error) {
|
||||
if !e.LATM {
|
||||
return e.encodeGeneric(aus)
|
||||
var rets []*rtp.Packet
|
||||
var batch [][]byte
|
||||
timestamp := uint32(0)
|
||||
|
||||
// split AUs into batches
|
||||
for _, au := range aus {
|
||||
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.writeGenericBatch(batch, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rets = append(rets, pkts...)
|
||||
timestamp += uint32(len(batch)) * mpeg4audio.SamplesPerAccessUnit
|
||||
}
|
||||
|
||||
// initialize new batch
|
||||
batch = [][]byte{au}
|
||||
}
|
||||
}
|
||||
return e.encodeLATM(aus)
|
||||
|
||||
// write last batch
|
||||
pkts, err := e.writeGenericBatch(batch, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rets = append(rets, pkts...)
|
||||
|
||||
return rets, nil
|
||||
}
|
||||
|
||||
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.writeGenericFragmented(aus[0], timestamp)
|
||||
}
|
||||
|
||||
func (e *Encoder) writeGenericFragmented(au []byte, timestamp uint32) ([]*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 := packetCountGeneric(avail, le)
|
||||
|
||||
ret := make([]*rtp.Packet, packetCount)
|
||||
le = avail
|
||||
|
||||
for i := range ret {
|
||||
if i == (packetCount - 1) {
|
||||
le = len(au)
|
||||
}
|
||||
|
||||
payload := make([]byte, 2+auHeadersLenBytes+le)
|
||||
|
||||
// AU-headers-length
|
||||
payload[0] = byte(auHeadersLen >> 8)
|
||||
payload[1] = byte(auHeadersLen)
|
||||
|
||||
// AU-headers
|
||||
pos := 0
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, uint64(le), e.SizeLength)
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexLength)
|
||||
|
||||
// AU
|
||||
copy(payload[2+auHeadersLenBytes:], au)
|
||||
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
|
||||
}
|
||||
|
||||
func (e *Encoder) lenGenericAggregated(aus [][]byte, addAU []byte) int {
|
||||
n := 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
|
||||
}
|
||||
}
|
||||
n += auHeadersLen / 8
|
||||
if (auHeadersLen % 8) != 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
// AU
|
||||
for _, au := range aus {
|
||||
n += len(au)
|
||||
}
|
||||
n += len(addAU)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *Encoder) writeGenericAggregated(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) {
|
||||
payload := make([]byte, e.lenGenericAggregated(aus, nil))
|
||||
|
||||
// AU-headers
|
||||
written := 0
|
||||
pos := 0
|
||||
for i, au := range aus {
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, uint64(len(au)), e.SizeLength)
|
||||
written += e.SizeLength
|
||||
if i == 0 {
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexLength)
|
||||
written += e.IndexLength
|
||||
} else {
|
||||
bits.WriteBitsUnsafe(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: timestamp,
|
||||
SSRC: *e.SSRC,
|
||||
Marker: true,
|
||||
},
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
e.sequenceNumber++
|
||||
|
||||
return []*rtp.Packet{pkt}, nil
|
||||
}
|
||||
|
@@ -1,196 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/bits"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
)
|
||||
|
||||
func packetCountGeneric(avail, le int) int {
|
||||
n := le / avail
|
||||
if (le % avail) != 0 {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
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.lenGenericAggregated(batch, au) <= e.PayloadMaxSize {
|
||||
// add to existing batch
|
||||
batch = append(batch, au)
|
||||
} else {
|
||||
// write current batch
|
||||
if batch != nil {
|
||||
pkts, err := e.writeGenericBatch(batch, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rets = append(rets, pkts...)
|
||||
timestamp += uint32(len(batch)) * mpeg4audio.SamplesPerAccessUnit
|
||||
}
|
||||
|
||||
// initialize new batch
|
||||
batch = [][]byte{au}
|
||||
}
|
||||
}
|
||||
|
||||
// write last batch
|
||||
pkts, err := e.writeGenericBatch(batch, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rets = append(rets, pkts...)
|
||||
|
||||
return rets, nil
|
||||
}
|
||||
|
||||
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.writeGenericFragmented(aus[0], timestamp)
|
||||
}
|
||||
|
||||
func (e *Encoder) writeGenericFragmented(au []byte, timestamp uint32) ([]*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 := packetCountGeneric(avail, le)
|
||||
|
||||
ret := make([]*rtp.Packet, packetCount)
|
||||
le = avail
|
||||
|
||||
for i := range ret {
|
||||
if i == (packetCount - 1) {
|
||||
le = len(au)
|
||||
}
|
||||
|
||||
payload := make([]byte, 2+auHeadersLenBytes+le)
|
||||
|
||||
// AU-headers-length
|
||||
payload[0] = byte(auHeadersLen >> 8)
|
||||
payload[1] = byte(auHeadersLen)
|
||||
|
||||
// AU-headers
|
||||
pos := 0
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, uint64(le), e.SizeLength)
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexLength)
|
||||
|
||||
// AU
|
||||
copy(payload[2+auHeadersLenBytes:], au)
|
||||
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
|
||||
}
|
||||
|
||||
func (e *Encoder) lenGenericAggregated(aus [][]byte, addAU []byte) int {
|
||||
n := 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
|
||||
}
|
||||
}
|
||||
n += auHeadersLen / 8
|
||||
if (auHeadersLen % 8) != 0 {
|
||||
n++
|
||||
}
|
||||
|
||||
// AU
|
||||
for _, au := range aus {
|
||||
n += len(au)
|
||||
}
|
||||
n += len(addAU)
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (e *Encoder) writeGenericAggregated(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) {
|
||||
payload := make([]byte, e.lenGenericAggregated(aus, nil))
|
||||
|
||||
// AU-headers
|
||||
written := 0
|
||||
pos := 0
|
||||
for i, au := range aus {
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, uint64(len(au)), e.SizeLength)
|
||||
written += e.SizeLength
|
||||
if i == 0 {
|
||||
bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexLength)
|
||||
written += e.IndexLength
|
||||
} else {
|
||||
bits.WriteBitsUnsafe(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: timestamp,
|
||||
SSRC: *e.SSRC,
|
||||
Marker: true,
|
||||
},
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
e.sequenceNumber++
|
||||
|
||||
return []*rtp.Packet{pkt}, nil
|
||||
}
|
@@ -1,488 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"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 casesGeneric = []struct {
|
||||
name string
|
||||
sizeLength int
|
||||
indexLength int
|
||||
indexDeltaLength int
|
||||
aus [][]byte
|
||||
pkts []*rtp.Packet
|
||||
}{
|
||||
{
|
||||
"single",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
{
|
||||
0x21, 0x1a, 0xd4, 0xf5, 0x9e, 0x20, 0xc5, 0x42,
|
||||
0x89, 0x40, 0xa2, 0x9b, 0x3c, 0x94, 0xdd, 0x28,
|
||||
0x94, 0x48, 0xd5, 0x8b, 0xb0, 0x2, 0xdb, 0x1b,
|
||||
0xeb, 0xe0, 0xfa, 0x9f, 0xea, 0x91, 0xa7, 0x3,
|
||||
0xe8, 0x6b, 0xe5, 0x5, 0x95, 0x6, 0x62, 0x88,
|
||||
0x13, 0xa, 0x15, 0xa0, 0xeb, 0xef, 0x40, 0x82,
|
||||
0xdf, 0x49, 0xf2, 0xe0, 0x26, 0xfc, 0x52, 0x5b,
|
||||
0x6c, 0x2a, 0x2d, 0xe8, 0xa5, 0x70, 0xc5, 0xaf,
|
||||
0xfc, 0x98, 0x9a, 0x2f, 0x1f, 0xbb, 0xa2, 0xcb,
|
||||
0xb8, 0x26, 0xb6, 0x6e, 0x4c, 0x15, 0x6c, 0x21,
|
||||
0x3d, 0x35, 0xf6, 0xcf, 0xa4, 0x3b, 0x72, 0x26,
|
||||
0xe1, 0x3a, 0x3a, 0x99, 0xd8, 0x2d, 0x6a, 0x22,
|
||||
0xcd, 0x97, 0xa, 0xef, 0x52, 0x9c, 0x5f, 0xcd,
|
||||
0x5c, 0xd9, 0xd3, 0x12, 0x7e, 0x45, 0x45, 0xb3,
|
||||
0x24, 0xef, 0xd3, 0x4f, 0x2f, 0x96, 0xd9, 0x8b,
|
||||
0x9c, 0xc2, 0xcd, 0x54, 0xb, 0x6e, 0x19, 0x84,
|
||||
0x56, 0xeb, 0x85, 0x52, 0x63, 0x64, 0x28, 0xb2,
|
||||
0xf2, 0xcf, 0xb8, 0xa8, 0x71, 0x53, 0x6, 0x82,
|
||||
0x88, 0xf2, 0xc4, 0xe1, 0x7d, 0x65, 0x54, 0xe0,
|
||||
0x5e, 0xc8, 0x38, 0x75, 0x9d, 0xb0, 0x58, 0x65,
|
||||
0x41, 0xa2, 0xcd, 0xdb, 0x1b, 0x9e, 0xac, 0xd1,
|
||||
0xbe, 0xc9, 0x22, 0xf5, 0xe9, 0xc6, 0x6f, 0xaf,
|
||||
0xf8, 0xb1, 0x4c, 0xcb, 0xa2, 0x56, 0x11, 0xa4,
|
||||
0xd7, 0xfd, 0xe5, 0xef, 0x8e, 0xbf, 0xce, 0x4b,
|
||||
0xef, 0xe1, 0xd, 0xc0, 0x27, 0x18, 0xe2, 0x64,
|
||||
0x63, 0x5, 0x16, 0x6, 0xc, 0x34, 0xe, 0xf3, 0x62,
|
||||
0xc2, 0xd6, 0x42, 0x5d, 0x66, 0x81, 0x4, 0x65,
|
||||
0x76, 0xaa, 0xe7, 0x39, 0xdd, 0x8e, 0xfe, 0x48,
|
||||
0x23, 0x3a, 0x1, 0xc4, 0xd3, 0x65, 0x80, 0x28,
|
||||
0x6f, 0x9b, 0xc9, 0xb7, 0x4e, 0x44, 0x4c, 0x98,
|
||||
0x6a, 0x5f, 0x3b, 0x97, 0x81, 0x9b, 0xa9, 0xab,
|
||||
0xfd, 0xcf, 0x8e, 0x78, 0xbd, 0x4d, 0x70, 0x81,
|
||||
0x9b, 0x2d, 0x85, 0x94, 0x74, 0x2a, 0x3a, 0xb4,
|
||||
0xff, 0x4a, 0x13, 0x70, 0x76, 0x2c, 0x2f, 0x13,
|
||||
0x5b, 0x43, 0xf9, 0x17, 0xee, 0x26, 0x37, 0x1,
|
||||
0xbc, 0x9f, 0xb, 0xe, 0x68, 0xcb, 0x87, 0x65,
|
||||
0x86, 0xcc, 0x4c, 0x2f, 0x7a, 0x14, 0xd, 0xd1,
|
||||
0xb9, 0x57, 0xbd, 0x50, 0xb6, 0x95, 0x44, 0x1a,
|
||||
0xd, 0xc0, 0x15, 0xf, 0xd2, 0xc3, 0x72, 0x4d,
|
||||
0x6e, 0x4f, 0x8e, 0x6d, 0x64, 0xdc, 0x64, 0x1f,
|
||||
0x33, 0x53, 0x4e, 0xd8, 0xa4, 0x74, 0xf3, 0x33,
|
||||
0x4, 0x68, 0xd9, 0x92, 0xf3, 0x6e, 0xb7, 0x5b,
|
||||
0xe6, 0xf6, 0xc3, 0x55, 0x14, 0x54, 0x87, 0x0,
|
||||
0xaf, 0x7,
|
||||
},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x10, 0x0a, 0xd8,
|
||||
0x21, 0x1a, 0xd4, 0xf5, 0x9e, 0x20, 0xc5, 0x42,
|
||||
0x89, 0x40, 0xa2, 0x9b, 0x3c, 0x94, 0xdd, 0x28,
|
||||
0x94, 0x48, 0xd5, 0x8b, 0xb0, 0x02, 0xdb, 0x1b,
|
||||
0xeb, 0xe0, 0xfa, 0x9f, 0xea, 0x91, 0xa7, 0x03,
|
||||
0xe8, 0x6b, 0xe5, 0x05, 0x95, 0x06, 0x62, 0x88,
|
||||
0x13, 0x0a, 0x15, 0xa0, 0xeb, 0xef, 0x40, 0x82,
|
||||
0xdf, 0x49, 0xf2, 0xe0, 0x26, 0xfc, 0x52, 0x5b,
|
||||
0x6c, 0x2a, 0x2d, 0xe8, 0xa5, 0x70, 0xc5, 0xaf,
|
||||
0xfc, 0x98, 0x9a, 0x2f, 0x1f, 0xbb, 0xa2, 0xcb,
|
||||
0xb8, 0x26, 0xb6, 0x6e, 0x4c, 0x15, 0x6c, 0x21,
|
||||
0x3d, 0x35, 0xf6, 0xcf, 0xa4, 0x3b, 0x72, 0x26,
|
||||
0xe1, 0x3a, 0x3a, 0x99, 0xd8, 0x2d, 0x6a, 0x22,
|
||||
0xcd, 0x97, 0x0a, 0xef, 0x52, 0x9c, 0x5f, 0xcd,
|
||||
0x5c, 0xd9, 0xd3, 0x12, 0x7e, 0x45, 0x45, 0xb3,
|
||||
0x24, 0xef, 0xd3, 0x4f, 0x2f, 0x96, 0xd9, 0x8b,
|
||||
0x9c, 0xc2, 0xcd, 0x54, 0x0b, 0x6e, 0x19, 0x84,
|
||||
0x56, 0xeb, 0x85, 0x52, 0x63, 0x64, 0x28, 0xb2,
|
||||
0xf2, 0xcf, 0xb8, 0xa8, 0x71, 0x53, 0x06, 0x82,
|
||||
0x88, 0xf2, 0xc4, 0xe1, 0x7d, 0x65, 0x54, 0xe0,
|
||||
0x5e, 0xc8, 0x38, 0x75, 0x9d, 0xb0, 0x58, 0x65,
|
||||
0x41, 0xa2, 0xcd, 0xdb, 0x1b, 0x9e, 0xac, 0xd1,
|
||||
0xbe, 0xc9, 0x22, 0xf5, 0xe9, 0xc6, 0x6f, 0xaf,
|
||||
0xf8, 0xb1, 0x4c, 0xcb, 0xa2, 0x56, 0x11, 0xa4,
|
||||
0xd7, 0xfd, 0xe5, 0xef, 0x8e, 0xbf, 0xce, 0x4b,
|
||||
0xef, 0xe1, 0x0d, 0xc0, 0x27, 0x18, 0xe2, 0x64,
|
||||
0x63, 0x05, 0x16, 0x06, 0x0c, 0x34, 0x0e, 0xf3,
|
||||
0x62, 0xc2, 0xd6, 0x42, 0x5d, 0x66, 0x81, 0x04,
|
||||
0x65, 0x76, 0xaa, 0xe7, 0x39, 0xdd, 0x8e, 0xfe,
|
||||
0x48, 0x23, 0x3a, 0x01, 0xc4, 0xd3, 0x65, 0x80,
|
||||
0x28, 0x6f, 0x9b, 0xc9, 0xb7, 0x4e, 0x44, 0x4c,
|
||||
0x98, 0x6a, 0x5f, 0x3b, 0x97, 0x81, 0x9b, 0xa9,
|
||||
0xab, 0xfd, 0xcf, 0x8e, 0x78, 0xbd, 0x4d, 0x70,
|
||||
0x81, 0x9b, 0x2d, 0x85, 0x94, 0x74, 0x2a, 0x3a,
|
||||
0xb4, 0xff, 0x4a, 0x13, 0x70, 0x76, 0x2c, 0x2f,
|
||||
0x13, 0x5b, 0x43, 0xf9, 0x17, 0xee, 0x26, 0x37,
|
||||
0x01, 0xbc, 0x9f, 0x0b, 0x0e, 0x68, 0xcb, 0x87,
|
||||
0x65, 0x86, 0xcc, 0x4c, 0x2f, 0x7a, 0x14, 0x0d,
|
||||
0xd1, 0xb9, 0x57, 0xbd, 0x50, 0xb6, 0x95, 0x44,
|
||||
0x1a, 0x0d, 0xc0, 0x15, 0x0f, 0xd2, 0xc3, 0x72,
|
||||
0x4d, 0x6e, 0x4f, 0x8e, 0x6d, 0x64, 0xdc, 0x64,
|
||||
0x1f, 0x33, 0x53, 0x4e, 0xd8, 0xa4, 0x74, 0xf3,
|
||||
0x33, 0x04, 0x68, 0xd9, 0x92, 0xf3, 0x6e, 0xb7,
|
||||
0x5b, 0xe6, 0xf6, 0xc3, 0x55, 0x14, 0x54, 0x87,
|
||||
0x00, 0xaf, 0x07,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"aggregated",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
{0x00, 0x01, 0x02, 0x03},
|
||||
{0x04, 0x05, 0x06, 0x07},
|
||||
{0x08, 0x09, 0x0A, 0x0B},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x0, 0x30, 0x0, 0x20,
|
||||
0x0, 0x20, 0x0, 0x20, 0x0, 0x1, 0x2, 0x3,
|
||||
0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ //nolint:dupl
|
||||
"fragmented",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
},
|
||||
[]*rtp.Packet{ //nolint:dupl
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1, 2, 3},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x00, 0x10, 0x0f, 0xa0},
|
||||
[]byte{4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented to the limit",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{bytes.Repeat([]byte{1}, 1992)},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{1}, 996),
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{1}, 996),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"aggregated followed by fragmented",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
{0x00, 0x01, 0x02, 0x03},
|
||||
{0x04, 0x05, 0x06, 0x07},
|
||||
{0x08, 0x09, 0x0A, 0x0B},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x0, 0x30, 0x0, 0x20,
|
||||
0x0, 0x20, 0x0, 0x20, 0x0, 0x1, 0x2, 0x3,
|
||||
0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
Timestamp: 3072,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1, 2, 3},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17647,
|
||||
Timestamp: 3072,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x00, 0x10, 0x0f, 0xa0},
|
||||
[]byte{4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"single, custom sized",
|
||||
6,
|
||||
2,
|
||||
2,
|
||||
[][]byte{
|
||||
{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x08, 0x10,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"single, custom sized, padded",
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
[][]byte{
|
||||
{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x0d, 0x00, 0x20,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"aggregated, custom sized, padded",
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
[][]byte{
|
||||
{0x01, 0x02, 0x03, 0x04},
|
||||
{0x05, 0x06, 0x07, 0x08},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x1a, 0x00, 0x20, 0x01, 0x00,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented, custom sized",
|
||||
21,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x18, 0x00, 0x1f, 0x18},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1, 2},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x00, 0x18, 0x00, 0x0f, 0xa8},
|
||||
[]byte{3, 4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ //nolint:dupl
|
||||
"fragmented, custom sized, padded",
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
[][]byte{
|
||||
bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 187),
|
||||
},
|
||||
[]*rtp.Packet{ //nolint:dupl
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x0d, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 124),
|
||||
[]byte{0x08, 0x09, 0x0A, 0x0B},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x0d, 0x0f, 0xa0},
|
||||
[]byte{0x0C, 0x0D, 0x0E, 0x0F},
|
||||
bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEncodeGeneric(t *testing.T) {
|
||||
for _, ca := range casesGeneric {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
e := &Encoder{
|
||||
PayloadType: 96,
|
||||
SSRC: uint32Ptr(0x9dbb7812),
|
||||
InitialSequenceNumber: uint16Ptr(0x44ed),
|
||||
SizeLength: ca.sizeLength,
|
||||
IndexLength: ca.indexLength,
|
||||
IndexDeltaLength: ca.indexDeltaLength,
|
||||
PayloadMaxSize: 1000,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := e.Encode(ca.aus)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.pkts, pkts)
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/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
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var casesLATM = []struct {
|
||||
name string
|
||||
au []byte
|
||||
pkts []*rtp.Packet
|
||||
}{
|
||||
{
|
||||
"single",
|
||||
[]byte{1, 2, 3, 4},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 2646308882,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x04, 0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented",
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 2646308882,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
bytes.Repeat([]byte{0xff}, 5),
|
||||
[]byte{0xdd},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 2646308882,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{2, 3, 4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented to the limit",
|
||||
bytes.Repeat([]byte{1}, 1992),
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 2646308882,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
bytes.Repeat([]byte{0xff}, 7),
|
||||
[]byte{0xcf},
|
||||
bytes.Repeat([]byte{1}, 992),
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 2646308882,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
bytes.Repeat([]byte{1}, 1000),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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),
|
||||
PayloadMaxSize: 1000,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := e.Encode([][]byte{ca.au})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.pkts, pkts)
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,8 +1,10 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -18,3 +20,482 @@ func TestEncodeRandomInitialState(t *testing.T) {
|
||||
require.NotEqual(t, nil, e.SSRC)
|
||||
require.NotEqual(t, nil, e.InitialSequenceNumber)
|
||||
}
|
||||
|
||||
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 casesGeneric = []struct {
|
||||
name string
|
||||
sizeLength int
|
||||
indexLength int
|
||||
indexDeltaLength int
|
||||
aus [][]byte
|
||||
pkts []*rtp.Packet
|
||||
}{
|
||||
{
|
||||
"single",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
{
|
||||
0x21, 0x1a, 0xd4, 0xf5, 0x9e, 0x20, 0xc5, 0x42,
|
||||
0x89, 0x40, 0xa2, 0x9b, 0x3c, 0x94, 0xdd, 0x28,
|
||||
0x94, 0x48, 0xd5, 0x8b, 0xb0, 0x2, 0xdb, 0x1b,
|
||||
0xeb, 0xe0, 0xfa, 0x9f, 0xea, 0x91, 0xa7, 0x3,
|
||||
0xe8, 0x6b, 0xe5, 0x5, 0x95, 0x6, 0x62, 0x88,
|
||||
0x13, 0xa, 0x15, 0xa0, 0xeb, 0xef, 0x40, 0x82,
|
||||
0xdf, 0x49, 0xf2, 0xe0, 0x26, 0xfc, 0x52, 0x5b,
|
||||
0x6c, 0x2a, 0x2d, 0xe8, 0xa5, 0x70, 0xc5, 0xaf,
|
||||
0xfc, 0x98, 0x9a, 0x2f, 0x1f, 0xbb, 0xa2, 0xcb,
|
||||
0xb8, 0x26, 0xb6, 0x6e, 0x4c, 0x15, 0x6c, 0x21,
|
||||
0x3d, 0x35, 0xf6, 0xcf, 0xa4, 0x3b, 0x72, 0x26,
|
||||
0xe1, 0x3a, 0x3a, 0x99, 0xd8, 0x2d, 0x6a, 0x22,
|
||||
0xcd, 0x97, 0xa, 0xef, 0x52, 0x9c, 0x5f, 0xcd,
|
||||
0x5c, 0xd9, 0xd3, 0x12, 0x7e, 0x45, 0x45, 0xb3,
|
||||
0x24, 0xef, 0xd3, 0x4f, 0x2f, 0x96, 0xd9, 0x8b,
|
||||
0x9c, 0xc2, 0xcd, 0x54, 0xb, 0x6e, 0x19, 0x84,
|
||||
0x56, 0xeb, 0x85, 0x52, 0x63, 0x64, 0x28, 0xb2,
|
||||
0xf2, 0xcf, 0xb8, 0xa8, 0x71, 0x53, 0x6, 0x82,
|
||||
0x88, 0xf2, 0xc4, 0xe1, 0x7d, 0x65, 0x54, 0xe0,
|
||||
0x5e, 0xc8, 0x38, 0x75, 0x9d, 0xb0, 0x58, 0x65,
|
||||
0x41, 0xa2, 0xcd, 0xdb, 0x1b, 0x9e, 0xac, 0xd1,
|
||||
0xbe, 0xc9, 0x22, 0xf5, 0xe9, 0xc6, 0x6f, 0xaf,
|
||||
0xf8, 0xb1, 0x4c, 0xcb, 0xa2, 0x56, 0x11, 0xa4,
|
||||
0xd7, 0xfd, 0xe5, 0xef, 0x8e, 0xbf, 0xce, 0x4b,
|
||||
0xef, 0xe1, 0xd, 0xc0, 0x27, 0x18, 0xe2, 0x64,
|
||||
0x63, 0x5, 0x16, 0x6, 0xc, 0x34, 0xe, 0xf3, 0x62,
|
||||
0xc2, 0xd6, 0x42, 0x5d, 0x66, 0x81, 0x4, 0x65,
|
||||
0x76, 0xaa, 0xe7, 0x39, 0xdd, 0x8e, 0xfe, 0x48,
|
||||
0x23, 0x3a, 0x1, 0xc4, 0xd3, 0x65, 0x80, 0x28,
|
||||
0x6f, 0x9b, 0xc9, 0xb7, 0x4e, 0x44, 0x4c, 0x98,
|
||||
0x6a, 0x5f, 0x3b, 0x97, 0x81, 0x9b, 0xa9, 0xab,
|
||||
0xfd, 0xcf, 0x8e, 0x78, 0xbd, 0x4d, 0x70, 0x81,
|
||||
0x9b, 0x2d, 0x85, 0x94, 0x74, 0x2a, 0x3a, 0xb4,
|
||||
0xff, 0x4a, 0x13, 0x70, 0x76, 0x2c, 0x2f, 0x13,
|
||||
0x5b, 0x43, 0xf9, 0x17, 0xee, 0x26, 0x37, 0x1,
|
||||
0xbc, 0x9f, 0xb, 0xe, 0x68, 0xcb, 0x87, 0x65,
|
||||
0x86, 0xcc, 0x4c, 0x2f, 0x7a, 0x14, 0xd, 0xd1,
|
||||
0xb9, 0x57, 0xbd, 0x50, 0xb6, 0x95, 0x44, 0x1a,
|
||||
0xd, 0xc0, 0x15, 0xf, 0xd2, 0xc3, 0x72, 0x4d,
|
||||
0x6e, 0x4f, 0x8e, 0x6d, 0x64, 0xdc, 0x64, 0x1f,
|
||||
0x33, 0x53, 0x4e, 0xd8, 0xa4, 0x74, 0xf3, 0x33,
|
||||
0x4, 0x68, 0xd9, 0x92, 0xf3, 0x6e, 0xb7, 0x5b,
|
||||
0xe6, 0xf6, 0xc3, 0x55, 0x14, 0x54, 0x87, 0x0,
|
||||
0xaf, 0x7,
|
||||
},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x10, 0x0a, 0xd8,
|
||||
0x21, 0x1a, 0xd4, 0xf5, 0x9e, 0x20, 0xc5, 0x42,
|
||||
0x89, 0x40, 0xa2, 0x9b, 0x3c, 0x94, 0xdd, 0x28,
|
||||
0x94, 0x48, 0xd5, 0x8b, 0xb0, 0x02, 0xdb, 0x1b,
|
||||
0xeb, 0xe0, 0xfa, 0x9f, 0xea, 0x91, 0xa7, 0x03,
|
||||
0xe8, 0x6b, 0xe5, 0x05, 0x95, 0x06, 0x62, 0x88,
|
||||
0x13, 0x0a, 0x15, 0xa0, 0xeb, 0xef, 0x40, 0x82,
|
||||
0xdf, 0x49, 0xf2, 0xe0, 0x26, 0xfc, 0x52, 0x5b,
|
||||
0x6c, 0x2a, 0x2d, 0xe8, 0xa5, 0x70, 0xc5, 0xaf,
|
||||
0xfc, 0x98, 0x9a, 0x2f, 0x1f, 0xbb, 0xa2, 0xcb,
|
||||
0xb8, 0x26, 0xb6, 0x6e, 0x4c, 0x15, 0x6c, 0x21,
|
||||
0x3d, 0x35, 0xf6, 0xcf, 0xa4, 0x3b, 0x72, 0x26,
|
||||
0xe1, 0x3a, 0x3a, 0x99, 0xd8, 0x2d, 0x6a, 0x22,
|
||||
0xcd, 0x97, 0x0a, 0xef, 0x52, 0x9c, 0x5f, 0xcd,
|
||||
0x5c, 0xd9, 0xd3, 0x12, 0x7e, 0x45, 0x45, 0xb3,
|
||||
0x24, 0xef, 0xd3, 0x4f, 0x2f, 0x96, 0xd9, 0x8b,
|
||||
0x9c, 0xc2, 0xcd, 0x54, 0x0b, 0x6e, 0x19, 0x84,
|
||||
0x56, 0xeb, 0x85, 0x52, 0x63, 0x64, 0x28, 0xb2,
|
||||
0xf2, 0xcf, 0xb8, 0xa8, 0x71, 0x53, 0x06, 0x82,
|
||||
0x88, 0xf2, 0xc4, 0xe1, 0x7d, 0x65, 0x54, 0xe0,
|
||||
0x5e, 0xc8, 0x38, 0x75, 0x9d, 0xb0, 0x58, 0x65,
|
||||
0x41, 0xa2, 0xcd, 0xdb, 0x1b, 0x9e, 0xac, 0xd1,
|
||||
0xbe, 0xc9, 0x22, 0xf5, 0xe9, 0xc6, 0x6f, 0xaf,
|
||||
0xf8, 0xb1, 0x4c, 0xcb, 0xa2, 0x56, 0x11, 0xa4,
|
||||
0xd7, 0xfd, 0xe5, 0xef, 0x8e, 0xbf, 0xce, 0x4b,
|
||||
0xef, 0xe1, 0x0d, 0xc0, 0x27, 0x18, 0xe2, 0x64,
|
||||
0x63, 0x05, 0x16, 0x06, 0x0c, 0x34, 0x0e, 0xf3,
|
||||
0x62, 0xc2, 0xd6, 0x42, 0x5d, 0x66, 0x81, 0x04,
|
||||
0x65, 0x76, 0xaa, 0xe7, 0x39, 0xdd, 0x8e, 0xfe,
|
||||
0x48, 0x23, 0x3a, 0x01, 0xc4, 0xd3, 0x65, 0x80,
|
||||
0x28, 0x6f, 0x9b, 0xc9, 0xb7, 0x4e, 0x44, 0x4c,
|
||||
0x98, 0x6a, 0x5f, 0x3b, 0x97, 0x81, 0x9b, 0xa9,
|
||||
0xab, 0xfd, 0xcf, 0x8e, 0x78, 0xbd, 0x4d, 0x70,
|
||||
0x81, 0x9b, 0x2d, 0x85, 0x94, 0x74, 0x2a, 0x3a,
|
||||
0xb4, 0xff, 0x4a, 0x13, 0x70, 0x76, 0x2c, 0x2f,
|
||||
0x13, 0x5b, 0x43, 0xf9, 0x17, 0xee, 0x26, 0x37,
|
||||
0x01, 0xbc, 0x9f, 0x0b, 0x0e, 0x68, 0xcb, 0x87,
|
||||
0x65, 0x86, 0xcc, 0x4c, 0x2f, 0x7a, 0x14, 0x0d,
|
||||
0xd1, 0xb9, 0x57, 0xbd, 0x50, 0xb6, 0x95, 0x44,
|
||||
0x1a, 0x0d, 0xc0, 0x15, 0x0f, 0xd2, 0xc3, 0x72,
|
||||
0x4d, 0x6e, 0x4f, 0x8e, 0x6d, 0x64, 0xdc, 0x64,
|
||||
0x1f, 0x33, 0x53, 0x4e, 0xd8, 0xa4, 0x74, 0xf3,
|
||||
0x33, 0x04, 0x68, 0xd9, 0x92, 0xf3, 0x6e, 0xb7,
|
||||
0x5b, 0xe6, 0xf6, 0xc3, 0x55, 0x14, 0x54, 0x87,
|
||||
0x00, 0xaf, 0x07,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"aggregated",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
{0x00, 0x01, 0x02, 0x03},
|
||||
{0x04, 0x05, 0x06, 0x07},
|
||||
{0x08, 0x09, 0x0A, 0x0B},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x0, 0x30, 0x0, 0x20,
|
||||
0x0, 0x20, 0x0, 0x20, 0x0, 0x1, 0x2, 0x3,
|
||||
0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ //nolint:dupl
|
||||
"fragmented",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
},
|
||||
[]*rtp.Packet{ //nolint:dupl
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1, 2, 3},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x00, 0x10, 0x0f, 0xa0},
|
||||
[]byte{4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented to the limit",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{bytes.Repeat([]byte{1}, 1992)},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{1}, 996),
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{1}, 996),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"aggregated followed by fragmented",
|
||||
13,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
{0x00, 0x01, 0x02, 0x03},
|
||||
{0x04, 0x05, 0x06, 0x07},
|
||||
{0x08, 0x09, 0x0A, 0x0B},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x0, 0x30, 0x0, 0x20,
|
||||
0x0, 0x20, 0x0, 0x20, 0x0, 0x1, 0x2, 0x3,
|
||||
0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
Timestamp: 3072,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x10, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1, 2, 3},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17647,
|
||||
Timestamp: 3072,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x00, 0x10, 0x0f, 0xa0},
|
||||
[]byte{4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"single, custom sized",
|
||||
6,
|
||||
2,
|
||||
2,
|
||||
[][]byte{
|
||||
{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x08, 0x10,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"single, custom sized, padded",
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
[][]byte{
|
||||
{0x01, 0x02, 0x03, 0x04},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x0d, 0x00, 0x20,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"aggregated, custom sized, padded",
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
[][]byte{
|
||||
{0x01, 0x02, 0x03, 0x04},
|
||||
{0x05, 0x06, 0x07, 0x08},
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: []byte{
|
||||
0x00, 0x1a, 0x00, 0x20, 0x01, 0x00,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"fragmented, custom sized",
|
||||
21,
|
||||
3,
|
||||
3,
|
||||
[][]byte{
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 187),
|
||||
},
|
||||
[]*rtp.Packet{
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x18, 0x00, 0x1f, 0x18},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 124),
|
||||
[]byte{0, 1, 2},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x00, 0x18, 0x00, 0x0f, 0xa8},
|
||||
[]byte{3, 4, 5, 6, 7},
|
||||
bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ //nolint:dupl
|
||||
"fragmented, custom sized, padded",
|
||||
13,
|
||||
0,
|
||||
0,
|
||||
[][]byte{
|
||||
bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 187),
|
||||
},
|
||||
[]*rtp.Packet{ //nolint:dupl
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: false,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17645,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x0d, 0x1f, 0x20},
|
||||
bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 124),
|
||||
[]byte{0x08, 0x09, 0x0A, 0x0B},
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
PayloadType: 96,
|
||||
SequenceNumber: 17646,
|
||||
SSRC: 0x9dbb7812,
|
||||
},
|
||||
Payload: mergeBytes(
|
||||
[]byte{0x0, 0x0d, 0x0f, 0xa0},
|
||||
[]byte{0x0C, 0x0D, 0x0E, 0x0F},
|
||||
bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 62),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEncodeGeneric(t *testing.T) {
|
||||
for _, ca := range casesGeneric {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
e := &Encoder{
|
||||
PayloadType: 96,
|
||||
SSRC: uint32Ptr(0x9dbb7812),
|
||||
InitialSequenceNumber: uint16Ptr(0x44ed),
|
||||
SizeLength: ca.sizeLength,
|
||||
IndexLength: ca.indexLength,
|
||||
IndexDeltaLength: ca.indexDeltaLength,
|
||||
PayloadMaxSize: 1000,
|
||||
}
|
||||
err := e.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
pkts, err := e.Encode(ca.aus)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.pkts, pkts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,38 +0,0 @@
|
||||
package rtpmpeg4audio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func payloadLengthInfoDecode(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 != 255 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return l, n, nil
|
||||
}
|
||||
|
||||
func payloadLengthInfoEncodeSize(auLen int) int {
|
||||
return auLen/255 + 1
|
||||
}
|
||||
|
||||
func payloadLengthInfoEncode(plil int, auLen int, buf []byte) {
|
||||
for i := 0; i < (plil - 1); i++ {
|
||||
buf[i] = 255
|
||||
}
|
||||
buf[plil-1] = byte(auLen % 255)
|
||||
}
|
@@ -1,82 +1,15 @@
|
||||
package rtpmpeg4video
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpfragmented"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
//
|
||||
// Deprecated: replaced by rtpfragmented.ErrMorePacketsNeeded
|
||||
var ErrMorePacketsNeeded = rtpfragmented.ErrMorePacketsNeeded
|
||||
|
||||
// Decoder is a RTP/MPEG-4 Video decoder.
|
||||
// Specification: RFC6416
|
||||
type Decoder struct {
|
||||
fragments [][]byte
|
||||
fragmentsSize int
|
||||
fragmentNextSeqNum uint16
|
||||
}
|
||||
|
||||
// Init initializes the decoder.
|
||||
func (d *Decoder) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) resetFragments() {
|
||||
d.fragments = d.fragments[:0]
|
||||
d.fragmentsSize = 0
|
||||
}
|
||||
|
||||
// Decode decodes a frame from a RTP packet.
|
||||
func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) {
|
||||
var frame []byte
|
||||
|
||||
if d.fragmentsSize == 0 {
|
||||
if pkt.Marker {
|
||||
frame = pkt.Payload
|
||||
} else {
|
||||
d.fragmentsSize = len(pkt.Payload)
|
||||
d.fragments = append(d.fragments, pkt.Payload)
|
||||
d.fragmentNextSeqNum = pkt.SequenceNumber + 1
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
} else {
|
||||
if pkt.SequenceNumber != d.fragmentNextSeqNum {
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("discarding frame since a RTP packet is missing")
|
||||
}
|
||||
|
||||
d.fragmentsSize += len(pkt.Payload)
|
||||
|
||||
if d.fragmentsSize > mpeg4video.MaxFrameSize {
|
||||
errSize := d.fragmentsSize
|
||||
d.resetFragments()
|
||||
return nil, fmt.Errorf("frame size (%d) is too big, maximum is %d",
|
||||
errSize, mpeg4video.MaxFrameSize)
|
||||
}
|
||||
|
||||
d.fragments = append(d.fragments, pkt.Payload)
|
||||
d.fragmentNextSeqNum++
|
||||
|
||||
if !pkt.Marker {
|
||||
return nil, ErrMorePacketsNeeded
|
||||
}
|
||||
|
||||
frame = joinFragments(d.fragments, d.fragmentsSize)
|
||||
d.resetFragments()
|
||||
}
|
||||
|
||||
return frame, nil
|
||||
}
|
||||
//
|
||||
// Deprecated: replaced by rtpfragmented.Decoder
|
||||
type Decoder = rtpfragmented.Decoder
|
||||
|
@@ -1,108 +1,10 @@
|
||||
package rtpmpeg4video
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpfragmented"
|
||||
)
|
||||
|
||||
const (
|
||||
rtpVersion = 2
|
||||
defaultPayloadMaxSize = 1450 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) - 10 (SRTP overhead)
|
||||
)
|
||||
|
||||
func randUint32() (uint32, error) {
|
||||
var b [4]byte
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil
|
||||
}
|
||||
|
||||
func packetCount(avail, le int) int {
|
||||
n := le / avail
|
||||
if (le % avail) != 0 {
|
||||
n++
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Encoder is a RTP/MPEG-4 Video encoder.
|
||||
// Specification: RFC6416
|
||||
type Encoder struct {
|
||||
// payload type of packets.
|
||||
PayloadType uint8
|
||||
|
||||
// SSRC of packets (optional).
|
||||
// It defaults to a random value.
|
||||
SSRC *uint32
|
||||
|
||||
// initial sequence number of packets (optional).
|
||||
// It defaults to a random value.
|
||||
InitialSequenceNumber *uint16
|
||||
|
||||
// maximum size of packet payloads (optional).
|
||||
// It defaults to 1450.
|
||||
PayloadMaxSize int
|
||||
|
||||
sequenceNumber uint16
|
||||
}
|
||||
|
||||
// Init initializes the encoder.
|
||||
func (e *Encoder) Init() error {
|
||||
if e.SSRC == nil {
|
||||
v, err := randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.SSRC = &v
|
||||
}
|
||||
if e.InitialSequenceNumber == nil {
|
||||
v, err := randUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v2 := uint16(v)
|
||||
e.InitialSequenceNumber = &v2
|
||||
}
|
||||
if e.PayloadMaxSize == 0 {
|
||||
e.PayloadMaxSize = defaultPayloadMaxSize
|
||||
}
|
||||
|
||||
e.sequenceNumber = *e.InitialSequenceNumber
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encode encodes a frame into RTP packets.
|
||||
func (e *Encoder) Encode(frame []byte) ([]*rtp.Packet, error) {
|
||||
avail := e.PayloadMaxSize
|
||||
le := len(frame)
|
||||
packetCount := packetCount(avail, le)
|
||||
|
||||
ret := make([]*rtp.Packet, packetCount)
|
||||
pos := 0
|
||||
le = avail
|
||||
|
||||
for i := range ret {
|
||||
if i == (packetCount - 1) {
|
||||
le = len(frame[pos:])
|
||||
}
|
||||
|
||||
ret[i] = &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: rtpVersion,
|
||||
PayloadType: e.PayloadType,
|
||||
SequenceNumber: e.sequenceNumber,
|
||||
SSRC: *e.SSRC,
|
||||
Marker: (i == packetCount-1),
|
||||
},
|
||||
Payload: frame[pos : pos+le],
|
||||
}
|
||||
|
||||
pos += le
|
||||
e.sequenceNumber++
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
//
|
||||
// Deprecated: replaced by rtpfragmented.Encoder
|
||||
type Encoder = rtpfragmented.Encoder
|
||||
|
Reference in New Issue
Block a user