From 321fe06c6dab8768519b2bc7642a043366b8b002 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Thu, 23 Sep 2021 20:19:11 +0200 Subject: [PATCH] add aac utilities --- pkg/aac/aac.go | 2 + pkg/aac/adts.go | 136 +++++++++++++++++++ pkg/aac/adts_test.go | 68 ++++++++++ pkg/{rtpaac => aac}/mpeg4audioconfig.go | 2 +- pkg/{rtpaac => aac}/mpeg4audioconfig_test.go | 2 +- pkg/h264/h264.go | 2 +- track.go | 8 +- 7 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 pkg/aac/aac.go create mode 100644 pkg/aac/adts.go create mode 100644 pkg/aac/adts_test.go rename pkg/{rtpaac => aac}/mpeg4audioconfig.go (99%) rename pkg/{rtpaac => aac}/mpeg4audioconfig_test.go (99%) diff --git a/pkg/aac/aac.go b/pkg/aac/aac.go new file mode 100644 index 00000000..2cd41580 --- /dev/null +++ b/pkg/aac/aac.go @@ -0,0 +1,2 @@ +// Package aac contains utilities to work with the MPEG-AAC codec. +package aac diff --git a/pkg/aac/adts.go b/pkg/aac/adts.go new file mode 100644 index 00000000..64b3567b --- /dev/null +++ b/pkg/aac/adts.go @@ -0,0 +1,136 @@ +package aac + +import ( + "fmt" +) + +// ADTSPacket is an ADTS packet +type ADTSPacket struct { + Type int + SampleRate int + ChannelCount int + AU []byte +} + +// DecodeADTS decodes an ADTS stream into ADTS packets. +func DecodeADTS(byts []byte) ([]*ADTSPacket, error) { + // refs: https://wiki.multimedia.cx/index.php/ADTS + + var ret []*ADTSPacket + + for len(byts) > 0 { + syncWord := (uint16(byts[0]) << 4) | (uint16(byts[1]) >> 4) + if syncWord != 0xfff { + return nil, fmt.Errorf("invalid syncword") + } + + protectionAbsent := byts[1] & 0x01 + if protectionAbsent != 1 { + return nil, fmt.Errorf("CRC is not supported") + } + + pkt := &ADTSPacket{} + + pkt.Type = int((byts[2] >> 6) + 1) + + switch MPEG4AudioType(pkt.Type) { + case MPEG4AudioTypeAACLC: + + default: + return nil, fmt.Errorf("unsupported object type: %d", pkt.Type) + } + + sampleRateIndex := (byts[2] >> 2) & 0x0F + + switch { + case sampleRateIndex <= 12: + pkt.SampleRate = sampleRates[sampleRateIndex] + + default: + return nil, fmt.Errorf("invalid sample rate index: %d", sampleRateIndex) + } + + channelConfig := ((byts[2] & 0x01) << 2) | ((byts[3] >> 6) & 0x03) + + switch { + case channelConfig >= 1 && channelConfig <= 7: + pkt.ChannelCount = channelCounts[channelConfig-1] + + default: + return nil, fmt.Errorf("invalid channel configuration: %d", channelConfig) + } + + frameLen := int(((uint16(byts[3])&0x03)<<11)| + (uint16(byts[4])<<3)| + ((uint16(byts[5])>>5)&0x07)) - 7 + + // fullness := ((uint16(byts[5]) & 0x1F) << 6) | ((uint16(byts[6]) >> 2) & 0x3F) + + frameCount := byts[6] & 0x03 + if frameCount != 0 { + return nil, fmt.Errorf("multiple frame count not supported") + } + + if len(byts[7:]) < frameLen { + return nil, fmt.Errorf("invalid frame length") + } + + pkt.AU = byts[7 : 7+frameLen] + byts = byts[7+frameLen:] + + ret = append(ret, pkt) + } + + return ret, nil +} + +// EncodeADTS encodes ADTS packets into an ADTS stream. +func EncodeADTS(pkts []*ADTSPacket) ([]byte, error) { + var ret []byte + + for _, pkt := range pkts { + sampleRateIndex := func() int { + for i, s := range sampleRates { + if s == pkt.SampleRate { + return i + } + } + return -1 + }() + + if sampleRateIndex == -1 { + return nil, fmt.Errorf("invalid sample rate: %d", pkt.SampleRate) + } + + channelConfig := func() int { + for i, co := range channelCounts { + if co == pkt.ChannelCount { + return i + 1 + } + } + return -1 + }() + + if channelConfig == -1 { + return nil, fmt.Errorf("invalid channel count: %d", pkt.ChannelCount) + } + + frameLen := len(pkt.AU) + 7 + + fullness := 0x07FF // like ffmpeg does + + header := make([]byte, 7) + header[0] = 0xFF + header[1] = 0xF1 + header[2] = uint8(((pkt.Type - 1) << 6) | (sampleRateIndex << 2) | ((channelConfig >> 2) & 0x01)) + header[3] = uint8((channelConfig&0x03)<<6 | (frameLen>>11)&0x03) + header[4] = uint8((frameLen >> 3) & 0xFF) + header[5] = uint8((frameLen&0x07)<<5 | ((fullness >> 6) & 0x1F)) + header[6] = uint8((fullness & 0x3F) << 2) + ret = append(ret, header...) + + ret = append(ret, pkt.AU...) + } + + return ret, nil +} diff --git a/pkg/aac/adts_test.go b/pkg/aac/adts_test.go new file mode 100644 index 00000000..e942c2f1 --- /dev/null +++ b/pkg/aac/adts_test.go @@ -0,0 +1,68 @@ +package aac + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var casesADTS = []struct { + name string + byts []byte + pkts []*ADTSPacket +}{ + { + "single", + []byte{0xff, 0xf1, 0x4c, 0x80, 0x1, 0x3f, 0xfc, 0xaa, 0xbb}, + []*ADTSPacket{ + { + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + AU: []byte{0xaa, 0xbb}, + }, + }, + }, + { + "multiple", + []byte{ + 0xff, 0xf1, 0x50, 0x40, 0x1, 0x3f, 0xfc, 0xaa, + 0xbb, 0xff, 0xf1, 0x4c, 0x80, 0x1, 0x3f, 0xfc, + 0xcc, 0xdd, + }, + []*ADTSPacket{ + { + Type: 2, + SampleRate: 44100, + ChannelCount: 1, + AU: []byte{0xaa, 0xbb}, + }, + { + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + AU: []byte{0xcc, 0xdd}, + }, + }, + }, +} + +func TestDecodeADTS(t *testing.T) { + for _, ca := range casesADTS { + t.Run(ca.name, func(t *testing.T) { + pkts, err := DecodeADTS(ca.byts) + require.NoError(t, err) + require.Equal(t, ca.pkts, pkts) + }) + } +} + +func TestEncodeADTS(t *testing.T) { + for _, ca := range casesADTS { + t.Run(ca.name, func(t *testing.T) { + byts, err := EncodeADTS(ca.pkts) + require.NoError(t, err) + require.Equal(t, ca.byts, byts) + }) + } +} diff --git a/pkg/rtpaac/mpeg4audioconfig.go b/pkg/aac/mpeg4audioconfig.go similarity index 99% rename from pkg/rtpaac/mpeg4audioconfig.go rename to pkg/aac/mpeg4audioconfig.go index a8e213b9..fa30236f 100644 --- a/pkg/rtpaac/mpeg4audioconfig.go +++ b/pkg/aac/mpeg4audioconfig.go @@ -1,4 +1,4 @@ -package rtpaac +package aac import ( "bytes" diff --git a/pkg/rtpaac/mpeg4audioconfig_test.go b/pkg/aac/mpeg4audioconfig_test.go similarity index 99% rename from pkg/rtpaac/mpeg4audioconfig_test.go rename to pkg/aac/mpeg4audioconfig_test.go index 598a9a4d..0d0ada24 100644 --- a/pkg/rtpaac/mpeg4audioconfig_test.go +++ b/pkg/aac/mpeg4audioconfig_test.go @@ -1,4 +1,4 @@ -package rtpaac +package aac import ( "testing" diff --git a/pkg/h264/h264.go b/pkg/h264/h264.go index 537f00fd..718e80ea 100644 --- a/pkg/h264/h264.go +++ b/pkg/h264/h264.go @@ -1,2 +1,2 @@ -// Package h264 contains utilities to work with H264 streams. +// Package h264 contains utilities to work with the H264 codec. package h264 diff --git a/track.go b/track.go index 80444f5b..291a5424 100644 --- a/track.go +++ b/track.go @@ -9,8 +9,8 @@ import ( psdp "github.com/pion/sdp/v3" + "github.com/aler9/gortsplib/pkg/aac" "github.com/aler9/gortsplib/pkg/base" - "github.com/aler9/gortsplib/pkg/rtpaac" "github.com/aler9/gortsplib/pkg/sdp" ) @@ -244,8 +244,8 @@ func (t *Track) ExtractConfigH264() (*TrackConfigH264, error) { // NewTrackAAC initializes an AAC track. func NewTrackAAC(payloadType uint8, conf *TrackConfigAAC) (*Track, error) { - mpegConf, err := rtpaac.MPEG4AudioConfig{ - Type: rtpaac.MPEG4AudioType(conf.Type), + mpegConf, err := aac.MPEG4AudioConfig{ + Type: aac.MPEG4AudioType(conf.Type), SampleRate: conf.SampleRate, ChannelCount: conf.ChannelCount, AOTSpecificConfig: conf.AOTSpecificConfig, @@ -332,7 +332,7 @@ func (t *Track) ExtractConfigAAC() (*TrackConfigAAC, error) { return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1]) } - var mpegConf rtpaac.MPEG4AudioConfig + var mpegConf aac.MPEG4AudioConfig err = mpegConf.Decode(enc) if err != nil { return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1])