mirror of
https://github.com/aler9/gortsplib
synced 2025-10-19 13:34:43 +08:00
add aac utilities
This commit is contained in:
2
pkg/aac/aac.go
Normal file
2
pkg/aac/aac.go
Normal 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
136
pkg/aac/adts.go
Normal 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
68
pkg/aac/adts_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package rtpaac
|
||||
package aac
|
||||
|
||||
import (
|
||||
"bytes"
|
@@ -1,4 +1,4 @@
|
||||
package rtpaac
|
||||
package aac
|
||||
|
||||
import (
|
||||
"testing"
|
@@ -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
|
||||
|
8
track.go
8
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])
|
||||
|
Reference in New Issue
Block a user