Skip to content

Commit

Permalink
Cleaner, faster implementation, other small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
zaf committed Dec 29, 2023
1 parent cf0017b commit 4090c85
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 125 deletions.
62 changes: 14 additions & 48 deletions alaw.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,9 @@

package g711

const alawClip = 0x7F7B
import "math/bits"

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

Expand All @@ -103,34 +84,19 @@ func EncodeAlaw(lpcm []byte) []byte {

// EncodeAlawFrame encodes a 16bit LPCM frame to G711 A-law PCM
func EncodeAlawFrame(frame int16) uint8 {
/*
The algorithm first stores off the sign. Then the code branches.
If the absolute value of the source sample is less than 256, the 16-bit sample is simply
shifted down 4 bits and converted to an 8-bit value, thus losing the top 4 bits in the process.
However, if it is more than 256, a logarithmic algorithm is applied to the sample to
determine the precision to keep. In that case, the sample is shifted down to access the
seven most significant bits of the sample. Those seven bits are then used to determine the
precision of the bottom 4 bits (segment). Finally, the top seven bits are shifted back up four bits
to make room for the bottom 4 bits. The two are then logically OR'd together to create the
eight bit compressed sample. The sign is then applied, and the entire compressed sample
is logically XOR'd for transmission.
*/
sign := ((^frame) >> 8) & 0x80
var compressedByte, seg, sign int16
sign = ((^frame) >> 8) & 0x80
if sign == 0 {
frame = -frame
}
if frame > alawClip {
frame = alawClip
frame = ^frame
}
var compressedByte uint8
if frame >= 256 {
segment := alawSegment[(frame>>8)&0x7F]
bottom := (frame >> (segment + 3)) & 0x0F
compressedByte = uint8(((int16(segment) << 4) | bottom))
} else {
compressedByte = uint8(frame >> 4)
compressedByte = frame >> 4
if compressedByte > 15 {
seg = int16(12 - bits.LeadingZeros16(uint16(compressedByte)))
compressedByte >>= seg - 1
compressedByte -= 16
compressedByte += seg << 4
}
return compressedByte ^ uint8(sign^0x55)
return uint8((sign | compressedByte) ^ 0x0055)
}

// DecodeAlaw decodes A-law PCM data to 16bit LPCM
Expand Down
8 changes: 4 additions & 4 deletions alaw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
package g711

import (
"io/ioutil"
"os"
"testing"
)

// Benchmark EncodeAlaw
func BenchmarkEncodeAlaw(b *testing.B) {
rawData, err := ioutil.ReadFile("testing/speech.raw")
rawData, err := os.ReadFile("testing/speech.raw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)
}
Expand All @@ -31,7 +31,7 @@ func BenchmarkEncodeAlaw(b *testing.B) {

// Benchmark DecodeAlaw
func BenchmarkDecodeAlaw(b *testing.B) {
aData, err := ioutil.ReadFile("testing/speech.alaw")
aData, err := os.ReadFile("testing/speech.alaw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)
}
Expand All @@ -44,7 +44,7 @@ func BenchmarkDecodeAlaw(b *testing.B) {

// Benchmark Alaw2Ulaw
func BenchmarkAlaw2Ulaw(b *testing.B) {
aData, err := ioutil.ReadFile("testing/speech.alaw")
aData, err := os.ReadFile("testing/speech.alaw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/g711dec/g711dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func decodeG711(file string) error {
return err
}
} else {
err = fmt.Errorf("Unrecognised format for file: %s", file)
err = fmt.Errorf("unrecognised format for file: %s", file)
return err
}
outName := strings.TrimSuffix(file, filepath.Ext(file)) + ".raw"
Expand Down
5 changes: 2 additions & 3 deletions cmd/g711enc/g711enc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ package main

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -44,14 +43,14 @@ func main() {
}

func encodeG711(file, format string) error {
input, err := ioutil.ReadFile(file)
input, err := os.ReadFile(file)
if err != nil {
return err
}

extension := strings.ToLower(filepath.Ext(file))
if extension != ".wav" && extension != ".raw" && extension != ".sln" {
err = fmt.Errorf("Unrecognised format for input file: %s", file)
err = fmt.Errorf("unrecognised format for input file: %s", file)
return err
}
outName := strings.TrimSuffix(file, filepath.Ext(file)) + "." + format
Expand Down
6 changes: 3 additions & 3 deletions g711.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func NewAlawEncoder(writer io.Writer, input int) (*Encoder, error) {
return nil, errors.New("io.Writer is nil")
}
if input != Ulaw && input != Lpcm {
return nil, errors.New("Invalid input format")
return nil, errors.New("invalid input format")
}
w := Encoder{
input: input,
Expand All @@ -92,7 +92,7 @@ func NewUlawEncoder(writer io.Writer, input int) (*Encoder, error) {
return nil, errors.New("io.Writer is nil")
}
if input != Alaw && input != Lpcm {
return nil, errors.New("Invalid input format")
return nil, errors.New("invalid input format")
}
w := Encoder{
input: input,
Expand Down Expand Up @@ -144,7 +144,7 @@ func (w *Encoder) Write(p []byte) (i int, err error) {
if w.input == Lpcm { // Encode LPCM data to G711
i, err = w.destination.Write(w.encode(p))
if err == nil && len(p)%2 != 0 {
err = errors.New("Odd number of LPCM bytes, incomplete frame")
err = errors.New("odd number of LPCM bytes, incomplete frame")
}
i *= 2 // Report back the correct number of bytes written from p
} else { // Trans-code
Expand Down
28 changes: 14 additions & 14 deletions g711_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ package g711
import (
"bytes"
"io"
"io/ioutil"
"os"
"testing"
"testing/iotest"
)
Expand Down Expand Up @@ -51,21 +51,21 @@ var TranscoderTest = []struct {

// Test Encoding
func TestEncode(t *testing.T) {
aenc, _ := NewAlawEncoder(ioutil.Discard, Lpcm)
aenc, _ := NewAlawEncoder(io.Discard, Lpcm)
for _, tc := range EncoderTest {
i, _ := aenc.Write(tc.data)
if i != tc.expected {
t.Errorf("Alaw Encode: expected: %d , actual: %d", tc.expected, i)
}
}
uenc, _ := NewUlawEncoder(ioutil.Discard, Lpcm)
uenc, _ := NewUlawEncoder(io.Discard, Lpcm)
for _, tc := range EncoderTest {
i, _ := uenc.Write(tc.data)
if i != tc.expected {
t.Errorf("ulaw Encode: expected: %d , actual: %d", tc.expected, i)
}
}
utrans, _ := NewUlawEncoder(ioutil.Discard, Alaw)
utrans, _ := NewUlawEncoder(io.Discard, Alaw)
for _, tc := range TranscoderTest {
i, _ := utrans.Write(tc.data)
if i != tc.expected {
Expand Down Expand Up @@ -126,14 +126,14 @@ func TestDecode(t *testing.T) {

// Benchmark Encoding data to Alaw
func BenchmarkAEncode(b *testing.B) {
rawData, err := ioutil.ReadFile("testing/speech.raw")
rawData, err := os.ReadFile("testing/speech.raw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)
}
b.SetBytes(int64(len(rawData)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
encoder, err := NewAlawEncoder(ioutil.Discard, Lpcm)
encoder, err := NewAlawEncoder(io.Discard, Lpcm)
if err != nil {
b.Fatalf("Failed to create Writer: %s\n", err)
}
Expand All @@ -146,15 +146,15 @@ func BenchmarkAEncode(b *testing.B) {

// Benchmark Encoding data to Ulaw
func BenchmarkUEncode(b *testing.B) {
rawData, err := ioutil.ReadFile("testing/speech.raw")
rawData, err := os.ReadFile("testing/speech.raw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)

}
b.SetBytes(int64(len(rawData)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
encoder, err := NewUlawEncoder(ioutil.Discard, Lpcm)
encoder, err := NewUlawEncoder(io.Discard, Lpcm)
if err != nil {
b.Fatalf("Failed to create Writer: %s\n", err)

Expand All @@ -169,15 +169,15 @@ func BenchmarkUEncode(b *testing.B) {

// Benchmark transcoding g711 data
func BenchmarkTranscode(b *testing.B) {
alawData, err := ioutil.ReadFile("testing/speech.alaw")
alawData, err := os.ReadFile("testing/speech.alaw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)

}
b.SetBytes(int64(len(alawData)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
transcoder, err := NewAlawEncoder(ioutil.Discard, Ulaw)
transcoder, err := NewAlawEncoder(io.Discard, Ulaw)
if err != nil {
b.Fatalf("Failed to create Writer: %s\n", err)

Expand All @@ -192,7 +192,7 @@ func BenchmarkTranscode(b *testing.B) {

// Benchmark Decoding Ulaw data
func BenchmarkUDecode(b *testing.B) {
ulawData, err := ioutil.ReadFile("testing/speech.ulaw")
ulawData, err := os.ReadFile("testing/speech.ulaw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)

Expand All @@ -205,7 +205,7 @@ func BenchmarkUDecode(b *testing.B) {
b.Fatalf("Failed to create Reader: %s\n", err)

}
_, err = io.Copy(ioutil.Discard, decoder)
_, err = io.Copy(io.Discard, decoder)
if err != nil {
b.Fatalf("Decoding failed: %s\n", err)

Expand All @@ -215,7 +215,7 @@ func BenchmarkUDecode(b *testing.B) {

// Benchmark Decoding Alaw data
func BenchmarkADecode(b *testing.B) {
alawData, err := ioutil.ReadFile("testing/speech.alaw")
alawData, err := os.ReadFile("testing/speech.alaw")
if err != nil {
b.Fatalf("Failed to read test data: %s\n", err)

Expand All @@ -228,7 +228,7 @@ func BenchmarkADecode(b *testing.B) {
b.Fatalf("Failed to create Reader: %s\n", err)

}
_, err = io.Copy(ioutil.Discard, decoder)
_, err = io.Copy(io.Discard, decoder)
if err != nil {
b.Fatalf("Decoding failed: %s\n", err)

Expand Down
21 changes: 21 additions & 0 deletions testing/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Test data

## Sound files
```
dtmf-1234.raw : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
silence-1s.raw : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
sine-440Hz-1s.raw : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
speech.alaw : A-Law, Rate 8000 Hz, Mono
speech.raw : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
speech.ulaw : Mu-Law, Rate 8000 Hz, Mono
````
### To playback raw data:
```
aplay -f S16_LE -r 8000 [filename]
```
### To playback G711 data:
```
aplay -f [MU_LAW|A_LAW] [filename]
```
Loading

0 comments on commit 4090c85

Please sign in to comment.