install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
## AAC-LD and AAC-ELD
|
||||
|
||||
| Codec | Rate | QuickTime | ffmpeg | VLC |
|
||||
|---------|-------|-----------|--------|-----|
|
||||
| AAC-LD | 8000 | yes | no | no |
|
||||
| AAC-LD | 16000 | yes | no | no |
|
||||
| AAC-LD | 22050 | yes | yes | no |
|
||||
| AAC-LD | 24000 | yes | yes | no |
|
||||
| AAC-LD | 32000 | yes | yes | no |
|
||||
| AAC-ELD | 8000 | yes | no | no |
|
||||
| AAC-ELD | 16000 | yes | no | no |
|
||||
| AAC-ELD | 22050 | yes | yes | yes |
|
||||
| AAC-ELD | 24000 | yes | yes | yes |
|
||||
| AAC-ELD | 32000 | yes | yes | yes |
|
||||
|
||||
## Useful links
|
||||
|
||||
- [4.6.20 Enhanced Low Delay Codec](https://csclub.uwaterloo.ca/~ehashman/ISO14496-3-2009.pdf)
|
||||
- https://stackoverflow.com/questions/40014508/aac-adts-for-aacobject-eld-packets
|
||||
- https://code.videolan.org/videolan/vlc/-/blob/master/modules/packetizer/mpeg4audio.c
|
||||
@@ -0,0 +1,126 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/bits"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeAACMain = 1
|
||||
TypeAACLC = 2 // Low Complexity
|
||||
TypeAACLD = 23 // Low Delay (48000, 44100, 32000, 24000, 22050)
|
||||
TypeESCAPE = 31
|
||||
TypeAACELD = 39 // Enhanced Low Delay
|
||||
|
||||
AUTime = 1024
|
||||
|
||||
// FMTP streamtype=5 - audio stream
|
||||
FMTP = "streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config="
|
||||
)
|
||||
|
||||
var sampleRates = [16]uint32{
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
|
||||
0, 0, 0, // protection from request sampleRates[15]
|
||||
}
|
||||
|
||||
func ConfigToCodec(conf []byte) *core.Codec {
|
||||
// https://en.wikipedia.org/wiki/MPEG-4_Part_3#MPEG-4_Audio_Object_Types
|
||||
rd := bits.NewReader(conf)
|
||||
|
||||
codec := &core.Codec{
|
||||
FmtpLine: FMTP + hex.EncodeToString(conf),
|
||||
PayloadType: core.PayloadTypeRAW,
|
||||
}
|
||||
|
||||
objType := rd.ReadBits(5)
|
||||
if objType == TypeESCAPE {
|
||||
objType = 32 + rd.ReadBits(6)
|
||||
}
|
||||
|
||||
switch objType {
|
||||
case TypeAACLC, TypeAACLD, TypeAACELD:
|
||||
codec.Name = core.CodecAAC
|
||||
default:
|
||||
codec.Name = fmt.Sprintf("AAC-%X", objType)
|
||||
}
|
||||
|
||||
if sampleRateIdx := rd.ReadBits8(4); sampleRateIdx < 0x0F {
|
||||
codec.ClockRate = sampleRates[sampleRateIdx]
|
||||
} else {
|
||||
codec.ClockRate = rd.ReadBits(24)
|
||||
}
|
||||
|
||||
codec.Channels = rd.ReadBits8(4)
|
||||
|
||||
return codec
|
||||
}
|
||||
|
||||
func DecodeConfig(b []byte) (objType, sampleFreqIdx, channels byte, sampleRate uint32) {
|
||||
rd := bits.NewReader(b)
|
||||
|
||||
objType = rd.ReadBits8(5)
|
||||
if objType == 0b11111 {
|
||||
objType = 32 + rd.ReadBits8(6)
|
||||
}
|
||||
|
||||
sampleFreqIdx = rd.ReadBits8(4)
|
||||
if sampleFreqIdx == 0b1111 {
|
||||
sampleRate = rd.ReadBits(24)
|
||||
} else {
|
||||
sampleRate = sampleRates[sampleFreqIdx]
|
||||
}
|
||||
|
||||
channels = rd.ReadBits8(4)
|
||||
return
|
||||
}
|
||||
|
||||
func EncodeConfig(objType byte, sampleRate uint32, channels byte, shortFrame bool) []byte {
|
||||
wr := bits.NewWriter(nil)
|
||||
|
||||
if objType < TypeESCAPE {
|
||||
wr.WriteBits8(objType, 5)
|
||||
} else {
|
||||
wr.WriteBits8(TypeESCAPE, 5)
|
||||
wr.WriteBits8(objType-32, 6)
|
||||
}
|
||||
|
||||
i := indexUint32(sampleRates[:], sampleRate)
|
||||
if i >= 0 {
|
||||
wr.WriteBits8(byte(i), 4)
|
||||
} else {
|
||||
wr.WriteBits8(0xF, 4)
|
||||
wr.WriteBits(sampleRate, 24)
|
||||
}
|
||||
|
||||
wr.WriteBits8(channels, 4)
|
||||
|
||||
switch objType {
|
||||
case TypeAACLD:
|
||||
// https://github.com/FFmpeg/FFmpeg/blob/67d392b97941bb51fb7af3a3c9387f5ab895fa46/libavcodec/aacdec_template.c#L841
|
||||
wr.WriteBool(shortFrame)
|
||||
wr.WriteBit(0) // dependsOnCoreCoder
|
||||
wr.WriteBit(0) // extension_flag
|
||||
wr.WriteBits8(0, 2) // ep_config
|
||||
case TypeAACELD:
|
||||
// https://github.com/FFmpeg/FFmpeg/blob/67d392b97941bb51fb7af3a3c9387f5ab895fa46/libavcodec/aacdec_template.c#L922
|
||||
wr.WriteBool(shortFrame)
|
||||
wr.WriteBits8(0, 3) // res_flags
|
||||
wr.WriteBit(0) // ldSbrPresentFlag
|
||||
wr.WriteBits8(0, 4) // ELDEXT_TERM
|
||||
wr.WriteBits8(0, 2) // ep_config
|
||||
}
|
||||
|
||||
return wr.Bytes()
|
||||
}
|
||||
|
||||
func indexUint32(s []uint32, v uint32) int {
|
||||
for i := range s {
|
||||
if v == s[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigToCodec(t *testing.T) {
|
||||
s := "profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=F8EC3000"
|
||||
s = core.Between(s, "config=", ";")
|
||||
src, err := hex.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
codec := ConfigToCodec(src)
|
||||
require.Equal(t, core.CodecAAC, codec.Name)
|
||||
require.Equal(t, uint32(24000), codec.ClockRate)
|
||||
require.Equal(t, uint16(1), codec.Channels)
|
||||
|
||||
dst := EncodeConfig(TypeAACELD, 24000, 1, true)
|
||||
require.Equal(t, src, dst)
|
||||
}
|
||||
|
||||
func TestADTS(t *testing.T) {
|
||||
// FFmpeg MPEG-TS AAC (one packet)
|
||||
s := "fff15080021ffc210049900219002380fff15080021ffc212049900219002380" //...
|
||||
src, err := hex.DecodeString(s)
|
||||
require.Nil(t, err)
|
||||
|
||||
codec := ADTSToCodec(src)
|
||||
require.Equal(t, uint32(44100), codec.ClockRate)
|
||||
require.Equal(t, uint16(2), codec.Channels)
|
||||
|
||||
size := ReadADTSSize(src)
|
||||
require.Equal(t, uint16(16), size)
|
||||
|
||||
dst := CodecToADTS(codec)
|
||||
WriteADTSSize(dst, size)
|
||||
|
||||
require.Equal(t, src[:len(dst)], dst)
|
||||
}
|
||||
|
||||
func TestEncodeConfig(t *testing.T) {
|
||||
conf := EncodeConfig(TypeAACLC, 48000, 1, false)
|
||||
require.Equal(t, "1188", hex.EncodeToString(conf))
|
||||
conf = EncodeConfig(TypeAACLC, 16000, 1, false)
|
||||
require.Equal(t, "1408", hex.EncodeToString(conf))
|
||||
conf = EncodeConfig(TypeAACLC, 8000, 1, false)
|
||||
require.Equal(t, "1588", hex.EncodeToString(conf))
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/bits"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const ADTSHeaderSize = 7
|
||||
|
||||
func ADTSHeaderLen(b []byte) int {
|
||||
if HasCRC(b) {
|
||||
return 9 // 7 bytes header + 2 bytes CRC
|
||||
}
|
||||
return ADTSHeaderSize
|
||||
}
|
||||
|
||||
func IsADTS(b []byte) bool {
|
||||
// AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
|
||||
// A 12 Syncword, all bits must be set to 1.
|
||||
// C 2 Layer, always set to 0.
|
||||
return len(b) >= ADTSHeaderSize && b[0] == 0xFF && b[1]&0b1111_0110 == 0xF0
|
||||
}
|
||||
|
||||
func HasCRC(b []byte) bool {
|
||||
// AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
|
||||
// D 1 Protection absence, set to 1 if there is no CRC and 0 if there is CRC.
|
||||
return b[1]&0b1 == 0
|
||||
}
|
||||
|
||||
func ADTSToCodec(b []byte) *core.Codec {
|
||||
// 1. Check ADTS header
|
||||
if !IsADTS(b) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. Decode ADTS params
|
||||
// https://wiki.multimedia.cx/index.php/ADTS
|
||||
rd := bits.NewReader(b)
|
||||
_ = rd.ReadBits(12) // Syncword, all bits must be set to 1
|
||||
_ = rd.ReadBit() // MPEG Version, set to 0 for MPEG-4 and 1 for MPEG-2
|
||||
_ = rd.ReadBits(2) // Layer, always set to 0
|
||||
_ = rd.ReadBit() // Protection absence, set to 1 if there is no CRC and 0 if there is CRC
|
||||
objType := rd.ReadBits8(2) + 1 // Profile, the MPEG-4 Audio Object Type minus 1
|
||||
sampleRateIdx := rd.ReadBits8(4) // MPEG-4 Sampling Frequency Index
|
||||
_ = rd.ReadBit() // Private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding
|
||||
channels := rd.ReadBits8(3) // MPEG-4 Channel Configuration
|
||||
|
||||
//_ = rd.ReadBit() // Originality, set to 1 to signal originality of the audio and 0 otherwise
|
||||
//_ = rd.ReadBit() // Home, set to 1 to signal home usage of the audio and 0 otherwise
|
||||
//_ = rd.ReadBit() // Copyright ID bit
|
||||
//_ = rd.ReadBit() // Copyright ID start
|
||||
//_ = rd.ReadBits(13) // Frame length
|
||||
//_ = rd.ReadBits(11) // Buffer fullness
|
||||
//_ = rd.ReadBits(2) // Number of AAC frames (Raw Data Blocks) in ADTS frame minus 1
|
||||
//_ = rd.ReadBits(16) // CRC check
|
||||
|
||||
// 3. Encode RTP config
|
||||
wr := bits.NewWriter(nil)
|
||||
wr.WriteBits8(objType, 5)
|
||||
wr.WriteBits8(sampleRateIdx, 4)
|
||||
wr.WriteBits8(channels, 4)
|
||||
conf := wr.Bytes()
|
||||
|
||||
codec := &core.Codec{
|
||||
Name: core.CodecAAC,
|
||||
ClockRate: sampleRates[sampleRateIdx],
|
||||
Channels: channels,
|
||||
FmtpLine: FMTP + hex.EncodeToString(conf),
|
||||
}
|
||||
return codec
|
||||
}
|
||||
|
||||
func ReadADTSSize(b []byte) uint16 {
|
||||
// AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
|
||||
_ = b[5] // bounds
|
||||
return uint16(b[3]&0b11)<<11 | uint16(b[4])<<3 | uint16(b[5]>>5)
|
||||
}
|
||||
|
||||
func WriteADTSSize(b []byte, size uint16) {
|
||||
// AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
|
||||
_ = b[5] // bounds
|
||||
b[3] |= byte(size >> (8 + 3))
|
||||
b[4] = byte(size >> 3)
|
||||
b[5] |= byte(size << 5)
|
||||
return
|
||||
}
|
||||
|
||||
func ADTSTimeSize(b []byte) uint32 {
|
||||
var units uint32
|
||||
for len(b) > ADTSHeaderSize {
|
||||
auSize := ReadADTSSize(b)
|
||||
b = b[auSize:]
|
||||
units++
|
||||
}
|
||||
return units * AUTime
|
||||
}
|
||||
|
||||
func CodecToADTS(codec *core.Codec) []byte {
|
||||
s := core.Between(codec.FmtpLine, "config=", ";")
|
||||
conf, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
objType, sampleFreqIdx, channels, _ := DecodeConfig(conf)
|
||||
profile := objType - 1
|
||||
|
||||
wr := bits.NewWriter(nil)
|
||||
wr.WriteAllBits(1, 12) // Syncword, all bits must be set to 1
|
||||
wr.WriteBit(0) // MPEG Version, set to 0 for MPEG-4 and 1 for MPEG-2
|
||||
wr.WriteBits8(0, 2) // Layer, always set to 0
|
||||
wr.WriteBit(1) // Protection absence, set to 1 if there is no CRC and 0 if there is CRC
|
||||
wr.WriteBits8(profile, 2) // Profile, the MPEG-4 Audio Object Type minus 1
|
||||
wr.WriteBits8(sampleFreqIdx, 4) // MPEG-4 Sampling Frequency Index
|
||||
wr.WriteBit(0) // Private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding
|
||||
wr.WriteBits8(channels, 3) // MPEG-4 Channel Configuration
|
||||
wr.WriteBit(0) // Originality, set to 1 to signal originality of the audio and 0 otherwise
|
||||
wr.WriteBit(0) // Home, set to 1 to signal home usage of the audio and 0 otherwise
|
||||
wr.WriteBit(0) // Copyright ID bit
|
||||
wr.WriteBit(0) // Copyright ID start
|
||||
wr.WriteBits16(0, 13) // Frame length
|
||||
wr.WriteAllBits(1, 11) // Buffer fullness (variable bitrate)
|
||||
wr.WriteBits8(0, 2) // Number of AAC frames (Raw Data Blocks) in ADTS frame minus 1
|
||||
|
||||
return wr.Bytes()
|
||||
}
|
||||
|
||||
func EncodeToADTS(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
adts := CodecToADTS(codec)
|
||||
|
||||
return func(packet *rtp.Packet) {
|
||||
if !IsADTS(packet.Payload) {
|
||||
b := make([]byte, ADTSHeaderSize+len(packet.Payload))
|
||||
copy(b, adts)
|
||||
copy(b[ADTSHeaderSize:], packet.Payload)
|
||||
WriteADTSSize(b, uint16(len(b)))
|
||||
|
||||
clone := *packet
|
||||
clone.Payload = b
|
||||
handler(&clone)
|
||||
} else {
|
||||
handler(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type Consumer struct {
|
||||
core.Connection
|
||||
wr *core.WriteBuffer
|
||||
}
|
||||
|
||||
func NewConsumer() *Consumer {
|
||||
medias := []*core.Media{
|
||||
{
|
||||
Kind: core.KindAudio,
|
||||
Direction: core.DirectionSendonly,
|
||||
Codecs: []*core.Codec{
|
||||
{Name: core.CodecAAC},
|
||||
},
|
||||
},
|
||||
}
|
||||
wr := core.NewWriteBuffer(nil)
|
||||
return &Consumer{
|
||||
Connection: core.Connection{
|
||||
ID: core.NewID(),
|
||||
FormatName: "adts",
|
||||
Medias: medias,
|
||||
Transport: wr,
|
||||
},
|
||||
wr: wr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Consumer) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
|
||||
sender := core.NewSender(media, track.Codec)
|
||||
|
||||
sender.Handler = func(pkt *rtp.Packet) {
|
||||
if n, err := c.wr.Write(pkt.Payload); err == nil {
|
||||
c.Send += n
|
||||
}
|
||||
}
|
||||
|
||||
if track.Codec.IsRTP() {
|
||||
sender.Handler = RTPToADTS(track.Codec, sender.Handler)
|
||||
} else {
|
||||
sender.Handler = EncodeToADTS(track.Codec, sender.Handler)
|
||||
}
|
||||
|
||||
sender.HandleRTP(track)
|
||||
c.Senders = append(c.Senders, sender)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Consumer) WriteTo(wr io.Writer) (int64, error) {
|
||||
return c.wr.WriteTo(wr)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type Producer struct {
|
||||
core.Connection
|
||||
rd *bufio.Reader
|
||||
}
|
||||
|
||||
func Open(r io.Reader) (*Producer, error) {
|
||||
rd := bufio.NewReader(r)
|
||||
|
||||
b, err := rd.Peek(ADTSHeaderSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
codec := ADTSToCodec(b)
|
||||
if codec == nil {
|
||||
return nil, errors.New("adts: wrong header")
|
||||
}
|
||||
codec.PayloadType = core.PayloadTypeRAW
|
||||
|
||||
medias := []*core.Media{
|
||||
{
|
||||
Kind: core.KindAudio,
|
||||
Direction: core.DirectionRecvonly,
|
||||
Codecs: []*core.Codec{codec},
|
||||
},
|
||||
}
|
||||
return &Producer{
|
||||
Connection: core.Connection{
|
||||
ID: core.NewID(),
|
||||
FormatName: "adts",
|
||||
Medias: medias,
|
||||
Transport: r,
|
||||
},
|
||||
rd: rd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Producer) Start() error {
|
||||
for {
|
||||
// read ADTS header
|
||||
adts := make([]byte, ADTSHeaderSize)
|
||||
if _, err := io.ReadFull(c.rd, adts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auSize := ReadADTSSize(adts) - ADTSHeaderSize
|
||||
|
||||
if HasCRC(adts) {
|
||||
// skip CRC after header
|
||||
if _, err := c.rd.Discard(2); err != nil {
|
||||
return err
|
||||
}
|
||||
auSize -= 2
|
||||
}
|
||||
|
||||
// read AAC payload after header
|
||||
payload := make([]byte, auSize)
|
||||
if _, err := io.ReadFull(c.rd, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Recv += int(auSize)
|
||||
|
||||
if len(c.Receivers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pkt := &rtp.Packet{
|
||||
Header: rtp.Header{Timestamp: core.Now90000()},
|
||||
Payload: payload,
|
||||
}
|
||||
c.Receivers[0].WriteRTP(pkt)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const RTPPacketVersionAAC = 0
|
||||
|
||||
func RTPDepay(handler core.HandlerFunc) core.HandlerFunc {
|
||||
var timestamp uint32
|
||||
|
||||
return func(packet *rtp.Packet) {
|
||||
// support ONLY 2 bytes header size!
|
||||
// streamtype=5;profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1408
|
||||
// https://datatracker.ietf.org/doc/html/rfc3640
|
||||
headersSize := binary.BigEndian.Uint16(packet.Payload) >> 3
|
||||
|
||||
//log.Printf("[RTP/AAC] units: %d, size: %4d, ts: %10d, %t", headersSize/2, len(packet.Payload), packet.Timestamp, packet.Marker)
|
||||
|
||||
if len(packet.Payload) < int(2+headersSize) {
|
||||
// In very rare cases noname cameras may send data not according to the standard
|
||||
// https://github.com/AlexxIT/go2rtc/issues/1328
|
||||
if IsADTS(packet.Payload) {
|
||||
clone := *packet
|
||||
clone.Version = RTPPacketVersionAAC
|
||||
clone.Timestamp = timestamp
|
||||
clone.Payload = clone.Payload[ADTSHeaderSize:]
|
||||
handler(&clone)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
headers := packet.Payload[2 : 2+headersSize]
|
||||
units := packet.Payload[2+headersSize:]
|
||||
|
||||
for len(headers) >= 2 {
|
||||
unitSize := binary.BigEndian.Uint16(headers) >> 3
|
||||
|
||||
if len(units) < int(unitSize) {
|
||||
return
|
||||
}
|
||||
|
||||
unit := units[:unitSize]
|
||||
|
||||
headers = headers[2:]
|
||||
units = units[unitSize:]
|
||||
|
||||
timestamp += AUTime
|
||||
|
||||
clone := *packet
|
||||
clone.Version = RTPPacketVersionAAC
|
||||
clone.Timestamp = timestamp
|
||||
if IsADTS(unit) {
|
||||
clone.Payload = unit[ADTSHeaderSize:]
|
||||
} else {
|
||||
clone.Payload = unit
|
||||
}
|
||||
handler(&clone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RTPPay(handler core.HandlerFunc) core.HandlerFunc {
|
||||
var seq uint16
|
||||
var ts uint32
|
||||
|
||||
return func(packet *rtp.Packet) {
|
||||
if packet.Version != RTPPacketVersionAAC {
|
||||
handler(packet)
|
||||
return
|
||||
}
|
||||
|
||||
// support ONLY one unit in payload
|
||||
auSize := uint16(len(packet.Payload))
|
||||
// 2 bytes header size + 2 bytes first payload size
|
||||
payload := make([]byte, 2+2+auSize)
|
||||
payload[1] = 16 // header size in bits
|
||||
binary.BigEndian.PutUint16(payload[2:], auSize<<3)
|
||||
copy(payload[4:], packet.Payload)
|
||||
|
||||
clone := rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
SequenceNumber: seq,
|
||||
Timestamp: ts,
|
||||
},
|
||||
Payload: payload,
|
||||
}
|
||||
handler(&clone)
|
||||
|
||||
seq++
|
||||
ts += AUTime
|
||||
}
|
||||
}
|
||||
|
||||
func ADTStoRTP(src []byte) (dst []byte) {
|
||||
dst = make([]byte, 2) // header bytes
|
||||
for i, n := 0, len(src)-ADTSHeaderSize; i < n; {
|
||||
auSize := ReadADTSSize(src[i:])
|
||||
dst = append(dst, byte(auSize>>5), byte(auSize<<3)) // size in bits
|
||||
i += int(auSize)
|
||||
}
|
||||
hdrSize := uint16(len(dst) - 2)
|
||||
binary.BigEndian.PutUint16(dst, hdrSize<<3) // size in bits
|
||||
return append(dst, src...)
|
||||
}
|
||||
|
||||
func RTPTimeSize(b []byte) uint32 {
|
||||
// convert RTP header size to units count
|
||||
units := binary.BigEndian.Uint16(b) >> 4
|
||||
return uint32(units) * AUTime
|
||||
}
|
||||
|
||||
func RTPToADTS(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
||||
adts := CodecToADTS(codec)
|
||||
|
||||
return func(packet *rtp.Packet) {
|
||||
src := packet.Payload
|
||||
dst := make([]byte, 0, len(src))
|
||||
|
||||
headersSize := binary.BigEndian.Uint16(src) >> 3
|
||||
headers := src[2 : 2+headersSize]
|
||||
units := src[2+headersSize:]
|
||||
|
||||
for len(headers) > 0 {
|
||||
unitSize := binary.BigEndian.Uint16(headers) >> 3
|
||||
headers = headers[2:]
|
||||
unit := units[:unitSize]
|
||||
units = units[unitSize:]
|
||||
|
||||
if !IsADTS(unit) {
|
||||
i := len(dst)
|
||||
dst = append(dst, adts...)
|
||||
WriteADTSSize(dst[i:], ADTSHeaderSize+uint16(len(unit)))
|
||||
}
|
||||
|
||||
dst = append(dst, unit...)
|
||||
}
|
||||
|
||||
clone := *packet
|
||||
clone.Version = RTPPacketVersionAAC
|
||||
clone.Payload = dst
|
||||
handler(&clone)
|
||||
}
|
||||
}
|
||||
|
||||
func RTPToCodec(b []byte) *core.Codec {
|
||||
hdrSize := binary.BigEndian.Uint16(b) / 8
|
||||
return ADTSToCodec(b[2+hdrSize:])
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package aac
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuggy_RTSP_AAC(t *testing.T) {
|
||||
// https: //github.com/AlexxIT/go2rtc/issues/1328
|
||||
payload, _ := hex.DecodeString("fff16080431ffc211ad4458aa309a1c0a8761a230502b7c74b2b5499252a010555e32e460128303c8ace4fd3260d654a424f7e7c65eddc96735fc6f1ac0edf94fdefa0e0bd6370da1c07b9c0e77a9d6e86b196a1ac7439dcafadcffcf6d89f60ac67f8884868e931383ad3e40cf5495470d1f606ef6f7624d285b951ebfa0e42641ab98f1371182b237d14f1bd16ad714fa2f1c6a7d23ebde7a0e34a2eca156a608a4caec49d9dca4b6fe2a09e9cdbf762c5b4148a3914abb7959c991228b0837b5988334b9fc18b8fac689b5ca1e4661573bbb8b253a86cae7ec14ace49969a9a76fd571ab6e650764cb59114d61dcedf07ac61b39e4ac66adebfd0d0ab45d518dd3c161049823f150864d977cf0855172ac8482e4b25fe911325d19617558c5405af74aff5492e4599bee53f2dbdf0503730af37078550f84c956b7ee89aae83c154fa2fa6e6792c5ddd5cd5cf6bb96bf055fee7f93bed59ffb039daee5ea7e5593cb194e9091e417c67d8f73026a6a6ae056e808f7c65c03d1b9197d3709ceb63bc7b979f7ba71df5e7c6395d99d6ea229000a6bc16fb4346d6b27d32f5d8d1200736d9366d59c0c9547210813b602473da9c46f9015bbb37594c1dd90cd6a36e96bd5d6a1445ab93c9e65505ec2c722bb4cc27a10600139a48c83594dde145253c386f6627d8c6e5102fe3828a590c709bc87f55b37e97d1ae72b017b09c6bb2c13299817bb45cc67318e10b6822075b97c6a03ec1c0")
|
||||
packet := &rtp.Packet{
|
||||
Header: rtp.Header{
|
||||
Version: 2,
|
||||
Marker: true,
|
||||
SequenceNumber: 36944,
|
||||
Timestamp: 4217191328,
|
||||
SSRC: 12892774,
|
||||
},
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
var size int
|
||||
|
||||
RTPDepay(func(packet *core.Packet) {
|
||||
size = len(packet.Payload)
|
||||
})(packet)
|
||||
|
||||
require.Equal(t, len(payload), size+ADTSHeaderSize)
|
||||
}
|
||||
Reference in New Issue
Block a user