add aac utilities

This commit is contained in:
aler9
2021-09-23 20:19:11 +02:00
parent 13c6b69d30
commit 321fe06c6d
7 changed files with 213 additions and 7 deletions

2
pkg/aac/aac.go Normal file
View File

@@ -0,0 +1,2 @@
// Package aac contains utilities to work with the MPEG-AAC codec.
package aac

136
pkg/aac/adts.go Normal file
View File

@@ -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
}

68
pkg/aac/adts_test.go Normal file
View File

@@ -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)
})
}
}

View File

@@ -1,4 +1,4 @@
package rtpaac
package aac
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package rtpaac
package aac
import (
"testing"

View File

@@ -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

View File

@@ -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])