Skip to content

Commit

Permalink
Avoid unnecessary byte conversion for PCM.
Browse files Browse the repository at this point in the history
  • Loading branch information
dennwc committed Dec 6, 2023
1 parent c3bd233 commit 87e8055
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 102 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
github.com/zaf/g711 v0.0.0-20220109202201-cf0017bf0359
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302
gopkg.in/yaml.v3 v3.0.1
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,6 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zaf/g711 v0.0.0-20220109202201-cf0017bf0359 h1:P9yeMx2iNJxJqXEwLtMjSwWcD2a0AlFmFByeosMZhLM=
github.com/zaf/g711 v0.0.0-20220109202201-cf0017bf0359/go.mod h1:ySLGJD8AQluMQuu5JDvfJrwsBra+8iX1jFsKS8KfB2I=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
Expand Down
42 changes: 42 additions & 0 deletions pkg/media/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

package media

import "sync/atomic"

type Writer[T any] interface {
WriteSample(sample T) error
}
Expand All @@ -23,3 +25,43 @@ type WriterFunc[T any] func(in T) error
func (fnc WriterFunc[T]) WriteSample(in T) error {
return fnc(in)
}

type SwitchWriter[T any] struct {
ptr atomic.Pointer[Writer[T]]
}

func (s *SwitchWriter[T]) Get() Writer[T] {
ptr := s.ptr.Load()
if ptr == nil {
return nil
}
return *ptr
}

func (s *SwitchWriter[T]) Set(w Writer[T]) {
if w == nil {
s.ptr.Store(nil)
} else {
s.ptr.Store(&w)
}
}

func (s *SwitchWriter[T]) WriteSample(sample T) error {
w := s.Get()
if w == nil {
return nil
}
return w.WriteSample(sample)
}

type MultiWriter[T any] []Writer[T]

func (s MultiWriter[T]) WriteSample(sample T) error {
var last error
for _, w := range s {
if err := w.WriteSample(sample); err != nil {
last = err
}
}
return last
}
31 changes: 0 additions & 31 deletions pkg/media/pcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package media

import (
"encoding/binary"
"time"

"github.com/pion/webrtc/v3/pkg/media"
Expand All @@ -37,38 +36,8 @@ func PlayAudio[T any](w Writer[T], sampleDur time.Duration, frames []T) error {
return nil
}

type LPCM16Sample []byte

func (s LPCM16Sample) Decode() PCM16Sample {
out := make(PCM16Sample, len(s)/2)
for i := 0; i < len(s); i += 2 {
out[i/2] = int16(binary.LittleEndian.Uint16(s[i:]))
}
return out
}

type PCM16Sample []int16

func (s PCM16Sample) Encode() LPCM16Sample {
out := make(LPCM16Sample, len(s)*2)
for i, v := range s {
binary.LittleEndian.PutUint16(out[2*i:], uint16(v))
}
return out
}

func DecodePCM(w Writer[PCM16Sample]) Writer[LPCM16Sample] {
return WriterFunc[LPCM16Sample](func(in LPCM16Sample) error {
return w.WriteSample(in.Decode())
})
}

func EncodePCM(w Writer[LPCM16Sample]) Writer[PCM16Sample] {
return WriterFunc[PCM16Sample](func(in PCM16Sample) error {
return w.WriteSample(in.Encode())
})
}

type MediaSampleWriter interface {
WriteSample(sample media.Sample) error
}
Expand Down
155 changes: 155 additions & 0 deletions pkg/media/ulaw/g711.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
Copyright (C) 2016 - 2017, Lefteris Zafiris <[email protected]>
This program is free software, distributed under the terms of
the BSD 3-Clause License. See the LICENSE file
at the top of the source tree.
Package g711 implements encoding and decoding of G711 PCM sound data.
G.711 is an ITU-T standard for audio companding.
*/

package ulaw

const (
uLawBias = 0x84
uLawClip = 0x7F7B
)

var (
// u-law quantization segment lookup table
ulawSegment = [256]uint8{
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,
}
// u-law to LPCM conversion lookup table
ulaw2lpcm = [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, 0,
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,
}
// u-law to A-law conversion lookup table based on the ITU-T G.711 specification
ulaw2alaw = [256]uint8{
42, 43, 40, 41, 46, 47, 44, 45, 34, 35, 32, 33, 38, 39, 36, 37,
58, 59, 56, 57, 62, 63, 60, 61, 50, 51, 48, 49, 54, 55, 52, 53,
10, 11, 8, 9, 14, 15, 12, 13, 2, 3, 0, 1, 6, 7, 4, 26,
27, 24, 25, 30, 31, 28, 29, 18, 19, 16, 17, 22, 23, 20, 21, 106,
104, 105, 110, 111, 108, 109, 98, 99, 96, 97, 102, 103, 100, 101, 122, 120,
126, 127, 124, 125, 114, 115, 112, 113, 118, 119, 116, 117, 75, 73, 79, 77,
66, 67, 64, 65, 70, 71, 68, 69, 90, 91, 88, 89, 94, 95, 92, 93,
82, 82, 83, 83, 80, 80, 81, 81, 86, 86, 87, 87, 84, 84, 85, 85,
170, 171, 168, 169, 174, 175, 172, 173, 162, 163, 160, 161, 166, 167, 164, 165,
186, 187, 184, 185, 190, 191, 188, 189, 178, 179, 176, 177, 182, 183, 180, 181,
138, 139, 136, 137, 142, 143, 140, 141, 130, 131, 128, 129, 134, 135, 132, 154,
155, 152, 153, 158, 159, 156, 157, 146, 147, 144, 145, 150, 151, 148, 149, 234,
232, 233, 238, 239, 236, 237, 226, 227, 224, 225, 230, 231, 228, 229, 250, 248,
254, 255, 252, 253, 242, 243, 240, 241, 246, 247, 244, 245, 203, 201, 207, 205,
194, 195, 192, 193, 198, 199, 196, 197, 218, 219, 216, 217, 222, 223, 220, 221,
210, 210, 211, 211, 208, 208, 209, 209, 214, 214, 215, 215, 212, 212, 213, 213,
}
)

// EncodeUlaw encodes 16bit LPCM data to G711 u-law PCM
func EncodeUlaw(lpcm []int16) []byte {
out := make([]byte, len(lpcm))
for i := range lpcm {
out[i] = EncodeUlawFrame(lpcm[i])
}
return out
}

// EncodeUlawFrame encodes a 16bit LPCM frame to G711 u-law PCM
func EncodeUlawFrame(frame int16) uint8 {
/*
The algorithm first stores off the sign. It then adds in a bias value
which (due to wrapping) will cause high valued samples to lose precision.
The top five most significant bits are pulled out of the sample.
Then, the bottom three bits of the compressed byte are generated using the
segment look-up table, based on the biased value of the source sample.
The 8-bit compressed sample is then finally created by logically OR'ing together
the 5 most important bits, the 3 lower bits, and the sign when applicable. The bits
are then logically NOT'ed for transmission.
*/
sign := (frame >> 8) & 0x80
if sign != 0 {
frame = -frame
}
if frame > uLawClip {
frame = uLawClip
}
frame += uLawBias
segment := ulawSegment[(frame>>7)&0xFF]
bottom := (frame >> (segment + 3)) & 0x0F
return uint8(^(sign | (int16(segment) << 4) | bottom))
}

// DecodeUlaw decodes u-law PCM data to 16bit PCM.
func DecodeUlaw(pcm []byte) []int16 {
out := make([]int16, len(pcm))
for i := 0; i < len(pcm); i++ {
out[i] = ulaw2lpcm[pcm[i]]
}
return out
}

// DecodeUlawFrame decodes a u-law PCM frame to 16bit LPCM
func DecodeUlawFrame(frame uint8) int16 {
return ulaw2lpcm[frame]
}

// Ulaw2Alaw performs direct u-law to A-law data conversion
func Ulaw2Alaw(ulaw []byte) []byte {
alaw := make([]byte, len(ulaw))
for i := 0; i < len(alaw); i++ {
alaw[i] = ulaw2alaw[ulaw[i]]
}
return ulaw
}

// Ulaw2AlawFrame directly converts a u-law frame to A-law
func Ulaw2AlawFrame(frame uint8) uint8 {
return ulaw2alaw[frame]
}
16 changes: 7 additions & 9 deletions pkg/media/ulaw/ulaw.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,28 @@
package ulaw

import (
"github.com/zaf/g711"

"github.com/livekit/sip/pkg/media"
)

type Sample []byte

func (s Sample) Decode() media.LPCM16Sample {
return g711.DecodeUlaw(s)
func (s Sample) Decode() media.PCM16Sample {
return DecodeUlaw(s)
}

func (s *Sample) Encode(data media.LPCM16Sample) {
*s = g711.EncodeUlaw(data)
func (s *Sample) Encode(data media.PCM16Sample) {
*s = EncodeUlaw(data)
}

func Encode(w media.Writer[media.LPCM16Sample]) media.Writer[Sample] {
func Decode(w media.Writer[media.PCM16Sample]) media.Writer[Sample] {
return media.WriterFunc[Sample](func(in Sample) error {
out := in.Decode()
return w.WriteSample(out)
})
}

func Decode(w media.Writer[Sample]) media.Writer[media.LPCM16Sample] {
return media.WriterFunc[media.LPCM16Sample](func(in media.LPCM16Sample) error {
func Encode(w media.Writer[Sample]) media.Writer[media.PCM16Sample] {
return media.WriterFunc[media.PCM16Sample](func(in media.PCM16Sample) error {
var s Sample
s.Encode(in)
return w.WriteSample(s)
Expand Down
29 changes: 13 additions & 16 deletions pkg/mixer/mixer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,30 +43,30 @@ type Input struct {
type Mixer struct {
mu sync.Mutex

onSample func([]byte)
inputs map[*Input]struct{}
out media.Writer[media.PCM16Sample]
inputs map[*Input]struct{}

ticker *time.Ticker
mixSize int

stopped core.Fuse
}

func NewMixer(onSample func([]byte), sampleRate int) *Mixer {
m := createMixer(onSample, sampleRate)
func NewMixer(out media.Writer[media.PCM16Sample], sampleRate int) *Mixer {
m := createMixer(out, sampleRate)

go m.start()

return m
}

func createMixer(onSample func([]byte), sampleRate int) *Mixer {
func createMixer(out media.Writer[media.PCM16Sample], sampleRate int) *Mixer {
m := &Mixer{
onSample: onSample,
ticker: time.NewTicker(mixerTickDuration),
mixSize: int(time.Duration(sampleRate) * mixerTickDuration / time.Second),
stopped: core.NewFuse(),
inputs: make(map[*Input]struct{}),
out: out,
ticker: time.NewTicker(mixerTickDuration),
mixSize: int(time.Duration(sampleRate) * mixerTickDuration / time.Second),
stopped: core.NewFuse(),
inputs: make(map[*Input]struct{}),
}

return m
Expand Down Expand Up @@ -107,21 +107,18 @@ func (m *Mixer) doMix() {
input.mu.Unlock()
}

out := make([]byte, 2*m.mixSize)
out := make(media.PCM16Sample, m.mixSize)
for i, sample := range mixed {
if sample > 0x7FFF {
sample = 0x7FFF
}
if sample < -0x7FFF {
sample = -0x7FFF
}

// Encoder expects little endian data (???)
out[2*i] = byte(sample & 0xFF)
out[2*i+1] = byte(sample >> 8)
out[i] = int16(sample)
}

m.onSample(out)
_ = m.out.WriteSample(out)
}

func (m *Mixer) start() {
Expand Down
Loading

0 comments on commit 87e8055

Please sign in to comment.