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
@@ -0,0 +1,69 @@
package pcm
import (
"errors"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/shell"
"github.com/pion/rtp"
)
type Backchannel struct {
core.Connection
cmd *shell.Command
}
func NewBackchannel(cmd *shell.Command, audio string) (core.Producer, error) {
var codec *core.Codec
if audio == "" {
// default codec
codec = &core.Codec{Name: core.CodecPCML, ClockRate: 16000}
} else if codec = core.ParseCodecString(audio); codec == nil {
return nil, errors.New("pcm: unsupported audio format: " + audio)
}
medias := []*core.Media{
{
Kind: core.KindAudio,
Direction: core.DirectionSendonly,
Codecs: []*core.Codec{codec},
},
}
return &Backchannel{
Connection: core.Connection{
ID: core.NewID(),
FormatName: "pcm",
Protocol: "pipe",
Medias: medias,
Transport: cmd,
},
cmd: cmd,
}, nil
}
func (c *Backchannel) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
return nil, core.ErrCantGetTrack
}
func (c *Backchannel) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
wr, err := c.cmd.StdinPipe()
if err != nil {
return err
}
sender := core.NewSender(media, track.Codec)
sender.Handler = func(packet *rtp.Packet) {
if n, err := wr.Write(packet.Payload); err != nil {
c.Send += n
}
}
sender.HandleRTP(track)
c.Senders = append(c.Senders, sender)
return nil
}
func (c *Backchannel) Start() error {
return c.cmd.Run()
}
+151
View File
@@ -0,0 +1,151 @@
// Package pcm - support raw (verbatim) PCM 16 bit in the FLAC container:
// - only 1 channel
// - only 16 bit per sample
// - only 8000, 16000, 24000, 48000 sample rate
package pcm
import (
"encoding/binary"
"unicode/utf8"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/pion/rtp"
"github.com/sigurn/crc16"
"github.com/sigurn/crc8"
)
func FLACHeader(magic bool, sampleRate uint32) []byte {
b := make([]byte, 42)
if magic {
copy(b, "fLaC") // [0..3]
}
// https://xiph.org/flac/format.html#metadata_block_header
b[4] = 0x80 // [4] lastMetadata=1 (1 bit), blockType=0 - STREAMINFO (7 bit)
b[7] = 0x22 // [5..7] blockLength=34 (24 bit)
// Important for Apple QuickTime player:
// 1. Both values should be same
// 2. Maximum value = 32768
binary.BigEndian.PutUint16(b[8:], 32768) // [8..9] info.BlockSizeMin=16 (16 bit)
binary.BigEndian.PutUint16(b[10:], 32768) // [10..11] info.BlockSizeMin=65535 (16 bit)
// [12..14] info.FrameSizeMin=0 (24 bit)
// [15..17] info.FrameSizeMax=0 (24 bit)
b[18] = byte(sampleRate >> 12)
b[19] = byte(sampleRate >> 4)
b[20] = byte(sampleRate << 4) // [18..20] info.SampleRate=8000 (20 bit), info.NChannels=1-1 (3 bit)
b[21] = 0xF0 // [21..25] info.BitsPerSample=16-1 (5 bit), info.NSamples (36 bit)
// [26..41] MD5sum (16 bytes)
return b
}
var table8 *crc8.Table
var table16 *crc16.Table
func FLACEncoder(codecName string, clockRate uint32, handler core.HandlerFunc) core.HandlerFunc {
var sr byte
switch clockRate {
case 8000:
sr = 0b0100
case 16000:
sr = 0b0101
case 22050:
sr = 0b0110
case 24000:
sr = 0b0111
case 32000:
sr = 0b1000
case 44100:
sr = 0b1001
case 48000:
sr = 0b1010
case 96000:
sr = 0b1011
default:
return nil
}
if table8 == nil {
table8 = crc8.MakeTable(crc8.CRC8)
}
if table16 == nil {
table16 = crc16.MakeTable(crc16.CRC16_BUYPASS)
}
var sampleNumber int32
return func(packet *rtp.Packet) {
samples := uint16(len(packet.Payload))
if codecName == core.CodecPCM || codecName == core.CodecPCML {
samples /= 2
}
// https://xiph.org/flac/format.html#frame_header
buf := make([]byte, samples*2+30)
// 1. Frame header
buf[0] = 0xFF
buf[1] = 0xF9 // [0..1] syncCode=0xFFF8 - reserved (15 bit), blockStrategy=1 - variable-blocksize (1 bit)
buf[2] = 0x70 | sr // blockSizeType=7 (4 bit), sampleRate=4 - 8000 (4 bit)
buf[3] = 0x08 // channels=1-1 (4 bit), sampleSize=4 - 16 (3 bit), reserved=0 (1 bit)
n := 4 + utf8.EncodeRune(buf[4:], sampleNumber) // 4 bytes max
sampleNumber += int32(samples)
// this is wrong but very simple frame block size value
binary.BigEndian.PutUint16(buf[n:], samples-1)
n += 2
buf[n] = crc8.Checksum(buf[:n], table8)
n += 1
// 2. Subframe header
buf[n] = 0x02 // padding=0 (1 bit), subframeType=1 - verbatim (6 bit), wastedFlag=0 (1 bit)
n += 1
// 3. Subframe
switch codecName {
case core.CodecPCMA:
for _, b := range packet.Payload {
s16 := PCMAtoPCM(b)
buf[n] = byte(s16 >> 8)
buf[n+1] = byte(s16)
n += 2
}
case core.CodecPCMU:
for _, b := range packet.Payload {
s16 := PCMUtoPCM(b)
buf[n] = byte(s16 >> 8)
buf[n+1] = byte(s16)
n += 2
}
case core.CodecPCM:
n += copy(buf[n:], packet.Payload)
case core.CodecPCML:
// reverse endian from little to big
size := len(packet.Payload)
for i := 0; i < size; i += 2 {
buf[n] = packet.Payload[i+1]
buf[n+1] = packet.Payload[i]
n += 2
}
}
// 4. Frame footer
crc := crc16.Checksum(buf[:n], table16)
binary.BigEndian.PutUint16(buf[n:], crc)
n += 2
clone := *packet
clone.Payload = buf[:n]
handler(&clone)
}
}
+109
View File
@@ -0,0 +1,109 @@
package pcm
import (
"sync"
"time"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/pion/rtp"
)
// RepackG711 - Repack G.711 PCMA/PCMU into frames of size 1024
// 1. Fixes WebRTC audio quality issue (monotonic timestamp)
// 2. Fixes Reolink Doorbell backchannel issue (zero timestamp)
// https://github.com/AlexxIT/go2rtc/issues/331
func RepackG711(zeroTS bool, handler core.HandlerFunc) core.HandlerFunc {
const PacketSize = 1024
var buf []byte
var seq uint16
var ts uint32
// fix https://github.com/AlexxIT/go2rtc/issues/432
var mu sync.Mutex
return func(packet *rtp.Packet) {
mu.Lock()
buf = append(buf, packet.Payload...)
if len(buf) < PacketSize {
mu.Unlock()
return
}
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true, // should be true
PayloadType: packet.PayloadType, // will be owerwriten
SequenceNumber: seq,
SSRC: packet.SSRC,
},
Payload: buf[:PacketSize],
}
seq++
// don't know if zero TS important for Reolink Doorbell
// don't have this strange devices for tests
if !zeroTS {
pkt.Timestamp = ts
ts += PacketSize
}
buf = buf[PacketSize:]
mu.Unlock()
handler(pkt)
}
}
// LittleToBig - convert PCM little endian to PCM big endian
func LittleToBig(handler core.HandlerFunc) core.HandlerFunc {
return func(packet *rtp.Packet) {
clone := *packet
clone.Payload = FlipEndian(packet.Payload)
handler(&clone)
}
}
func TranscodeHandler(dst, src *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
var ts uint32
k := float32(BytesPerFrame(dst)) / float32(BytesPerFrame(src))
f := Transcode(dst, src)
return func(packet *rtp.Packet) {
ts += uint32(k * float32(len(packet.Payload)))
clone := *packet
clone.Payload = f(packet.Payload)
clone.Timestamp = ts
handler(&clone)
}
}
func BytesPerSample(codec *core.Codec) int {
switch codec.Name {
case core.CodecPCML, core.CodecPCM:
return 2
case core.CodecPCMU, core.CodecPCMA:
return 1
}
return 0
}
func BytesPerFrame(codec *core.Codec) int {
if codec.Channels <= 1 {
return BytesPerSample(codec)
}
return int(codec.Channels) * BytesPerSample(codec)
}
func FramesPerDuration(codec *core.Codec, duration time.Duration) int {
return int(time.Duration(codec.ClockRate) * duration / time.Second)
}
func BytesPerDuration(codec *core.Codec, duration time.Duration) int {
return BytesPerFrame(codec) * FramesPerDuration(codec, duration)
}
+220
View File
@@ -0,0 +1,220 @@
package pcm
import (
"math"
"github.com/AlexxIT/go2rtc/pkg/core"
)
func ceil(x float32) int {
d, fract := math.Modf(float64(x))
if fract == 0.0 {
return int(d)
}
return int(d) + 1
}
func Downsample(k float32) func([]int16) []int16 {
var sampleN, sampleSum float32
return func(src []int16) (dst []int16) {
var i int
dst = make([]int16, ceil((float32(len(src))+sampleN)/k))
for _, sample := range src {
sampleSum += float32(sample)
sampleN++
if sampleN >= k {
dst[i] = int16(sampleSum / k)
i++
sampleSum = 0
sampleN -= k
}
}
return
}
}
func Upsample(k float32) func([]int16) []int16 {
var sampleN float32
return func(src []int16) (dst []int16) {
var i int
dst = make([]int16, ceil(k*float32(len(src))))
for _, sample := range src {
sampleN += k
for sampleN > 0 {
dst[i] = sample
i++
sampleN -= 1
}
}
return
}
}
func FlipEndian(src []byte) (dst []byte) {
var i, j int
n := len(src)
dst = make([]byte, n)
for i < n {
x := src[i]
i++
dst[j] = src[i]
j++
i++
dst[j] = x
j++
}
return
}
func Transcode(dst, src *core.Codec) func([]byte) []byte {
var reader func([]byte) []int16
var writer func([]int16) []byte
var filters []func([]int16) []int16
switch src.Name {
case core.CodecPCML:
reader = func(src []byte) (dst []int16) {
var i, j int
n := len(src)
dst = make([]int16, n/2)
for i < n {
lo := src[i]
i++
hi := src[i]
i++
dst[j] = int16(hi)<<8 | int16(lo)
j++
}
return
}
case core.CodecPCM:
reader = func(src []byte) (dst []int16) {
var i, j int
n := len(src)
dst = make([]int16, n/2)
for i < n {
hi := src[i]
i++
lo := src[i]
i++
dst[j] = int16(hi)<<8 | int16(lo)
j++
}
return
}
case core.CodecPCMU:
reader = func(src []byte) (dst []int16) {
var i int
dst = make([]int16, len(src))
for _, sample := range src {
dst[i] = PCMUtoPCM(sample)
i++
}
return
}
case core.CodecPCMA:
reader = func(src []byte) (dst []int16) {
var i int
dst = make([]int16, len(src))
for _, sample := range src {
dst[i] = PCMAtoPCM(sample)
i++
}
return
}
}
if src.Channels > 1 {
filters = append(filters, Downsample(float32(src.Channels)))
}
if src.ClockRate > dst.ClockRate {
filters = append(filters, Downsample(float32(src.ClockRate)/float32(dst.ClockRate)))
} else if src.ClockRate < dst.ClockRate {
filters = append(filters, Upsample(float32(dst.ClockRate)/float32(src.ClockRate)))
}
if dst.Channels > 1 {
filters = append(filters, Upsample(float32(dst.Channels)))
}
switch dst.Name {
case core.CodecPCML:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src)*2)
for _, sample := range src {
dst[i] = byte(sample)
i++
dst[i] = byte(sample >> 8)
i++
}
return
}
case core.CodecPCM:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src)*2)
for _, sample := range src {
dst[i] = byte(sample >> 8)
i++
dst[i] = byte(sample)
i++
}
return
}
case core.CodecPCMU:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src))
for _, sample := range src {
dst[i] = PCMtoPCMU(sample)
i++
}
return
}
case core.CodecPCMA:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src))
for _, sample := range src {
dst[i] = PCMtoPCMA(sample)
i++
}
return
}
}
return func(b []byte) []byte {
samples := reader(b)
for _, filter := range filters {
samples = filter(samples)
}
return writer(samples)
}
}
func ConsumerCodecs() []*core.Codec {
return []*core.Codec{
{Name: core.CodecPCML},
{Name: core.CodecPCM},
{Name: core.CodecPCMA},
{Name: core.CodecPCMU},
}
}
func ProducerCodecs() []*core.Codec {
return []*core.Codec{
{Name: core.CodecPCML, ClockRate: 16000},
{Name: core.CodecPCM, ClockRate: 16000},
{Name: core.CodecPCML, ClockRate: 8000},
{Name: core.CodecPCM, ClockRate: 8000},
{Name: core.CodecPCMA, ClockRate: 8000},
{Name: core.CodecPCMU, ClockRate: 8000},
{Name: core.CodecPCML, ClockRate: 22050}, // wyoming-snd-external
}
}
@@ -0,0 +1,79 @@
package pcm
import (
"encoding/hex"
"fmt"
"testing"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/stretchr/testify/require"
)
func TestTranscode(t *testing.T) {
tests := []struct {
name string
src core.Codec
dst core.Codec
source string
expect string
}{
{
name: "s16be->s16be",
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
expect: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
},
{
name: "s16be->s16le",
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
dst: core.Codec{Name: core.CodecPCML, ClockRate: 8000, Channels: 1},
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
expect: "CAFC1300430328061308510B9E0D760FDA101111EA13BD15F2168216D4156115",
},
{
name: "s16be->mulaw",
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
dst: core.Codec{Name: core.CodecPCMU, ClockRate: 8000, Channels: 1},
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
expect: "52FDD1C5BEB8B3B0AEAEABA9A8A8A9AA",
},
{
name: "s16be->alaw",
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
dst: core.Codec{Name: core.CodecPCMA, ClockRate: 8000, Channels: 1},
source: "FCCA00130343062808130B510D9E0F7610DA111113EA15BD16F2168215D41561",
expect: "7CD4FFED95939E9B8584868083838080",
},
{
name: "2ch->1ch",
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 2},
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
source: "FCCAFCCA001300130343034306280628081308130B510B510D9E0D9E0F760F76",
expect: "FCCA00130343062808130B510D9E0F76",
},
{
name: "1ch->2ch",
src: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 2},
source: "FCCA00130343062808130B510D9E0F76",
expect: "FCCAFCCA001300130343034306280628081308130B510B510D9E0D9E0F760F76",
},
{
name: "16khz->8khz",
src: core.Codec{Name: core.CodecPCM, ClockRate: 16000, Channels: 1},
dst: core.Codec{Name: core.CodecPCM, ClockRate: 8000, Channels: 1},
source: "FCCAFCCA001300130343034306280628081308130B510B510D9E0D9E0F760F76",
expect: "FCCA00130343062808130B510D9E0F76",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
f := Transcode(&test.dst, &test.src)
b, _ := hex.DecodeString(test.source)
b = f(b)
s := fmt.Sprintf("%X", b)
require.Equal(t, test.expect, s)
})
}
}
+53
View File
@@ -0,0 +1,53 @@
// Package pcm
// https://www.codeproject.com/Articles/14237/Using-the-G711-standard
package pcm
const alawMax = 0x7FFF
func PCMAtoPCM(alaw byte) int16 {
alaw ^= 0xD5
data := int16(((alaw & 0x0F) << 4) + 8)
exponent := (alaw & 0x70) >> 4
if exponent != 0 {
data |= 0x100
}
if exponent > 1 {
data <<= exponent - 1
}
// sign
if alaw&0x80 == 0 {
return data
} else {
return -data
}
}
func PCMtoPCMA(pcm int16) byte {
var alaw byte
if pcm < 0 {
pcm = -pcm
alaw = 0x80
}
if pcm > alawMax {
pcm = alawMax
}
exponent := byte(7)
for expMask := int16(0x4000); (pcm&expMask) == 0 && exponent > 0; expMask >>= 1 {
exponent--
}
if exponent == 0 {
alaw |= byte(pcm>>4) & 0x0F
} else {
alaw |= (exponent << 4) | (byte(pcm>>(exponent+3)) & 0x0F)
}
return alaw ^ 0xD5
}
+51
View File
@@ -0,0 +1,51 @@
// Package pcm
// https://www.codeproject.com/Articles/14237/Using-the-G711-standard
package pcm
const bias = 0x84 // 132 or 1000 0100
const ulawMax = alawMax - bias
func PCMUtoPCM(ulaw byte) int16 {
ulaw = ^ulaw
exponent := (ulaw & 0x70) >> 4
data := (int16((((ulaw&0x0F)|0x10)<<1)+1) << (exponent + 2)) - bias
// sign
if ulaw&0x80 == 0 {
return data
} else if data == 0 {
return -1
} else {
return -data
}
}
func PCMtoPCMU(pcm int16) byte {
var ulaw byte
if pcm < 0 {
pcm = -pcm
ulaw = 0x80
}
if pcm > ulawMax {
pcm = ulawMax
}
pcm += bias
exponent := byte(7)
for expMask := int16(0x4000); (pcm & expMask) == 0; expMask >>= 1 {
exponent--
}
// mantisa
ulaw |= byte(pcm>>(exponent+3)) & 0x0F
if exponent > 0 {
ulaw |= exponent << 4
}
return ^ulaw
}
@@ -0,0 +1,55 @@
package pcm
import (
"io"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/pion/rtp"
)
type Producer struct {
core.Connection
rd io.Reader
}
func Open(rd io.Reader) (*Producer, error) {
medias := []*core.Media{
{
Kind: core.KindAudio,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{Name: core.CodecPCMU, ClockRate: 8000},
},
},
}
return &Producer{
core.Connection{
ID: core.NewID(),
FormatName: "pcm",
Medias: medias,
Transport: rd,
},
rd,
}, nil
}
func (c *Producer) Start() error {
for {
payload := make([]byte, 1024)
if _, err := io.ReadFull(c.rd, payload); err != nil {
return err
}
c.Recv += 1024
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,96 @@
package pcm
import (
"io"
"time"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/pion/rtp"
)
type ProducerSync struct {
core.Connection
src *core.Codec
rd io.Reader
onClose func()
}
func OpenSync(codec *core.Codec, rd io.Reader) *ProducerSync {
medias := []*core.Media{
{
Kind: core.KindAudio,
Direction: core.DirectionRecvonly,
Codecs: ProducerCodecs(),
},
}
return &ProducerSync{
Connection: core.Connection{
ID: core.NewID(),
FormatName: "pcm",
Medias: medias,
Transport: rd,
},
src: codec,
rd: rd,
}
}
func (p *ProducerSync) OnClose(f func()) {
p.onClose = f
}
func (p *ProducerSync) Start() error {
if len(p.Receivers) == 0 {
return nil
}
var pktSeq uint16
var pktTS uint32 // time in frames
var pktTime time.Duration // time in seconds
t0 := time.Now()
dst := p.Receivers[0].Codec
transcode := Transcode(dst, p.src)
const chunkDuration = 20 * time.Millisecond
chunkBytes := BytesPerDuration(p.src, chunkDuration)
chunkFrames := uint32(FramesPerDuration(dst, chunkDuration))
for {
buf := make([]byte, chunkBytes)
n, _ := io.ReadFull(p.rd, buf)
if n == 0 {
break
}
pkt := &core.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
SequenceNumber: pktSeq,
Timestamp: pktTS,
},
Payload: transcode(buf[:n]),
}
if d := pktTime - time.Since(t0); d > 0 {
time.Sleep(d)
}
p.Receivers[0].WriteRTP(pkt)
p.Recv += n
pktSeq++
pktTS += chunkFrames
pktTime += chunkDuration
}
if p.onClose != nil {
p.onClose()
}
return nil
}
@@ -0,0 +1,42 @@
package s16le
func PeaksRMS(b []byte) int16 {
// RMS of sine wave = peak / sqrt2
// https://en.wikipedia.org/wiki/Root_mean_square
// https://www.youtube.com/watch?v=MUDkL4KZi0I
var peaks int32
var peaksSum int32
var prevSample int16
var prevUp bool
var i int
for n := len(b); i < n; {
lo := b[i]
i++
hi := b[i]
i++
sample := int16(hi)<<8 | int16(lo)
up := sample >= prevSample
if i >= 4 {
if up != prevUp {
if prevSample >= 0 {
peaksSum += int32(prevSample)
} else {
peaksSum -= int32(prevSample)
}
peaks++
}
}
prevSample = sample
prevUp = up
}
if peaks == 0 {
return 0
}
return int16(peaksSum / peaks)
}
+155
View File
@@ -0,0 +1,155 @@
// Package v1
// http://web.archive.org/web/20110719132013/http://hazelware.luggle.com/tutorials/mulawcompression.html
package v1
const cBias = 0x84
const cClip = 32635
var MuLawCompressTable = [256]byte{
0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
}
func LinearToMuLawSample(sample int16) byte {
sign := byte(sample>>8) & 0x80
if sign != 0 {
sample = -sample
}
if sample > cClip {
sample = cClip
}
sample = sample + cBias
exponent := MuLawCompressTable[(sample>>7)&0xFF]
mantissa := byte(sample>>(exponent+3)) & 0x0F
compressedByte := ^(sign | (exponent << 4) | mantissa)
return compressedByte
}
var ALawCompressTable = [128]byte{
1, 1, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
}
func LinearToALawSample(sample int16) byte {
sign := byte((^sample)>>8) & 0x80
if sign == 0 {
sample = -sample
}
if sample > cClip {
sample = cClip
}
var compressedByte byte
if sample >= 256 {
exponent := ALawCompressTable[(sample>>8)&0x7F]
mantissa := byte(sample>>(exponent+3)) & 0x0F
compressedByte = (exponent << 4) | mantissa
} else {
compressedByte = byte(sample >> 4)
}
compressedByte ^= sign ^ 0x55
return compressedByte
}
var MuLawDecompressTable = [256]int16{
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, -1,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
56, 48, 40, 32, 24, 16, 8, 0,
}
var ALawDecompressTable = [256]int16{
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
-7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
-2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
-3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
-22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
-30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
-11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472,
-15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
-344, -328, -376, -360, -280, -264, -312, -296,
-472, -456, -504, -488, -408, -392, -440, -424,
-88, -72, -120, -104, -24, -8, -56, -40,
-216, -200, -248, -232, -152, -136, -184, -168,
-1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
-1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
-688, -656, -752, -720, -560, -528, -624, -592,
-944, -912, -1008, -976, -816, -784, -880, -848,
5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
344, 328, 376, 360, 280, 264, 312, 296,
472, 456, 504, 488, 408, 392, 440, 424,
88, 72, 120, 104, 24, 8, 56, 40,
216, 200, 248, 232, 152, 136, 184, 168,
1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
688, 656, 752, 720, 560, 528, 624, 592,
944, 912, 1008, 976, 816, 784, 880, 848,
}
@@ -0,0 +1,40 @@
package v1
import (
"testing"
v2 "github.com/AlexxIT/go2rtc/pkg/pcm"
"github.com/stretchr/testify/require"
)
func TestPCMUtoPCM(t *testing.T) {
for pcmu := byte(0); pcmu < 255; pcmu++ {
pcm1 := MuLawDecompressTable[pcmu]
pcm2 := v2.PCMUtoPCM(pcmu)
require.Equal(t, pcm1, pcm2)
}
}
func TestPCMAtoPCM(t *testing.T) {
for pcma := byte(0); pcma < 255; pcma++ {
pcm1 := ALawDecompressTable[pcma]
pcm2 := v2.PCMAtoPCM(pcma)
require.Equal(t, pcm1, pcm2)
}
}
func TestPCMtoPCMU(t *testing.T) {
for pcm := int16(-32768); pcm < 32767; pcm++ {
pcmu1 := LinearToMuLawSample(pcm)
pcmu2 := v2.PCMtoPCMU(pcm)
require.Equal(t, pcmu1, pcmu2)
}
}
func TestPCMtoPCMA(t *testing.T) {
for pcm := int16(-32768); pcm < 32767; pcm++ {
pcma1 := LinearToALawSample(pcm)
pcma2 := v2.PCMtoPCMA(pcm)
require.Equal(t, pcma1, pcma2)
}
}