install go2rtc on bob

This commit is contained in:
2026-04-04 19:36:14 +02:00
parent f0b56e63d1
commit ccf88187b8
537 changed files with 69213 additions and 0 deletions
+20
View File
@@ -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
+126
View File
@@ -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))
}
+148
View File
@@ -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)
}
}
+154
View File
@@ -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)
}