diff --git a/Readme.md b/Readme.md index 74af993..eb21492 100644 --- a/Readme.md +++ b/Readme.md @@ -1,186 +1,178 @@ # g711 - import "github.com/zaf/g711" -Package g711 implements encoding and decoding of G711 PCM sound data. G.711 is -an ITU-T standard for audio companding. +[![GoDoc](https://img.shields.io/badge/pkg.go.dev-doc-blue)](http://pkg.go.dev/.) -For usage details please see the code snippets in the cmd folder. +Package g711 implements encoding and decoding of G711 PCM sound data. +G.711 is an ITU-T standard for audio companding. -## Usage +The package exposes a high level API for encoding and decoding through +an io.WriteCloser. But also a low level API using preallocated buffers +for cases where performance and memory handling are critical. -```go +For usage details please see the code snippet in the cmd folder. + +## Constants + +```golang const ( - // Input and output formats - Alaw = iota // Alaw G711 encoded PCM data - Ulaw // Ulaw G711 encoded PCM data - Lpcm // Lpcm 16bit signed linear data + // Input and output formats + Alaw = iota + 1 // Alaw G711 encoded PCM data + Ulaw // Ulaw G711 encoded PCM data + Lpcm // Lpcm 16bit signed linear data ) ``` -#### func Alaw2Ulaw +## Types + +### type [Coder](/g711.go#L32) + +`type Coder struct { ... }` + +Coder encodes 16bit 8000Hz LPCM data to G711 PCM, or +decodes G711 PCM data to 16bit 8000Hz LPCM data, or +directly transcodes between A-law and u-law + +#### func (*Coder) [Close](/g711.go#L96) + +`func (w *Coder) Close() error` + +Close closes the Encoder, it implements the io.Closer interface. + +#### func (*Coder) [Reset](/g711.go#L105) + +`func (w *Coder) Reset(writer io.Writer) error` + +Reset discards the Encoder state. This permits reusing an Encoder rather than allocating a new one. + +#### func (*Coder) [Write](/g711.go#L119) + +`func (w *Coder) Write(p []byte) (int, error)` + +Write encodes/decodes/transcodes sound data. Writes len(p) bytes from p to the underlying data stream, +returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered +that caused the write to stop early. + + +## Functions + +### func [Alaw2Ulaw](/alaw.go#L129) + +`func Alaw2Ulaw(alaw []byte) []byte` -```go -func Alaw2Ulaw(alaw []byte) []byte -``` Alaw2Ulaw performs direct A-law to u-law data conversion -#### func Alaw2UlawFrame +### func [Alaw2UlawFrame](/alaw.go#L145) + +`func Alaw2UlawFrame(frame uint8) uint8` -```go -func Alaw2UlawFrame(frame uint8) uint8 -``` Alaw2UlawFrame directly converts an A-law frame to u-law -#### func DecodeAlaw +### func [Alaw2UlawTo](/alaw.go#L138) + +`func Alaw2UlawTo(alaw, ulaw []byte)` + +Alaw2UlawTo performs direct A-law to u-law data conversion +using an already allocated buffer provided by the user. +The user is responsible for ensuring that the buffer is large enough (the size of the A-law data). + +### func [DecodeAlaw](/alaw.go#L106) + +`func DecodeAlaw(pcm []byte) []byte` -```go -func DecodeAlaw(pcm []byte) []byte -``` DecodeAlaw decodes A-law PCM data to 16bit LPCM -#### func DecodeAlawFrame +### func [DecodeAlawFrame](/alaw.go#L124) + +`func DecodeAlawFrame(frame uint8) int16` -```go -func DecodeAlawFrame(frame uint8) int16 -``` DecodeAlawFrame decodes an A-law PCM frame to 16bit LPCM -#### func DecodeUlaw +### func [DecodeAlawTo](/alaw.go#L115) -```go -func DecodeUlaw(pcm []byte) []byte -``` -DecodeUlaw decodes u-law PCM data to 16bit LPCM +`func DecodeAlawTo(pcm, lpcm []byte)` -#### func DecodeUlawFrame +DecodeAlawTo decodes A-law PCM data to 16bit LPCM +using an already allocated buffer provided by the user. +The user is responsible for ensuring that the buffer is large enough (double the size of the PCM data). -```go -func DecodeUlawFrame(frame uint8) int16 -``` -DecodeUlawFrame decodes a u-law PCM frame to 16bit LPCM +### func [DecodeUlaw](/ulaw.go#L110) -#### func EncodeAlaw +`func DecodeUlaw(pcm []byte) []byte` -```go -func EncodeAlaw(lpcm []byte) []byte -``` -EncodeAlaw encodes 16bit LPCM data to G711 A-law PCM +DecodeUlaw decodes u-law PCM data to 16bit LPCM -#### func EncodeAlawFrame +### func [DecodeUlawFrame](/ulaw.go#L128) -```go -func EncodeAlawFrame(frame int16) uint8 -``` -EncodeAlawFrame encodes a 16bit LPCM frame to G711 A-law PCM +`func DecodeUlawFrame(frame uint8) int16` -#### func EncodeUlaw +DecodeUlawFrame decodes a u-law PCM frame to 16bit LPCM -```go -func EncodeUlaw(lpcm []byte) []byte -``` -EncodeUlaw encodes 16bit LPCM data to G711 u-law PCM +### func [DecodeUlawTo](/ulaw.go#L119) -#### func EncodeUlawFrame +`func DecodeUlawTo(pcm, lpcm []byte)` -```go -func EncodeUlawFrame(frame int16) uint8 -``` -EncodeUlawFrame encodes a 16bit LPCM frame to G711 u-law PCM +DecodeUlawTo decodes u-law PCM data to 16bit LPCM +using an already allocated buffer provided by the user. +The user is responsible for ensuring that the buffer is large enough (double the size of the PCM data). -#### func Ulaw2Alaw +### func [EncodeAlaw](/alaw.go#L74) -```go -func Ulaw2Alaw(ulaw []byte) []byte -``` -Ulaw2Alaw performs direct u-law to A-law data conversion +`func EncodeAlaw(lpcm []byte) []byte` -#### func Ulaw2AlawFrame +EncodeAlaw encodes 16bit LPCM data to G711 A-law PCM -```go -func Ulaw2AlawFrame(frame uint8) uint8 -``` -Ulaw2AlawFrame directly converts a u-law frame to A-law +### func [EncodeAlawFrame](/alaw.go#L90) -#### type Decoder +`func EncodeAlawFrame(frame int16) uint8` -```go -type Decoder struct { -} -``` +EncodeAlawFrame encodes a 16bit LPCM frame to G711 A-law PCM -Decoder reads G711 PCM data and decodes it to 16bit 8000Hz LPCM +### func [EncodeAlawTo](/alaw.go#L83) -#### func NewAlawDecoder +`func EncodeAlawTo(lpcm, alaw []byte)` -```go -func NewAlawDecoder(reader io.Reader) (*Decoder, error) -``` -NewAlawDecoder returns a pointer to a Decoder that implements an io.Reader. It -takes as input the source data Reader. +EncodeAlawTo encodes 16bit LPCM data to G711 A-law PCM +using an already allocated buffer provided by the user. +The user is responsible for ensuring that the buffer is large enough (half the size of the LPCM data). -#### func NewUlawDecoder +### func [EncodeUlaw](/ulaw.go#L79) -```go -func NewUlawDecoder(reader io.Reader) (*Decoder, error) -``` -NewUlawDecoder returns a pointer to a Decoder that implements an io.Reader. It -takes as input the source data Reader. +`func EncodeUlaw(lpcm []byte) []byte` -#### func (*Decoder) Read +EncodeUlaw encodes 16bit LPCM data to G711 u-law PCM -```go -func (r *Decoder) Read(p []byte) (i int, err error) -``` -Read decodes G711 data. Reads up to len(p) bytes into p, returns the number of -bytes read and any error encountered. +### func [EncodeUlawFrame](/ulaw.go#L95) -#### func (*Decoder) Reset +`func EncodeUlawFrame(frame int16) uint8` -```go -func (r *Decoder) Reset(reader io.Reader) error -``` -Reset discards the Decoder state. This permits reusing a Decoder rather than -allocating a new one. +EncodeUlawFrame encodes a 16bit LPCM frame to G711 u-law PCM -#### type Encoder +### func [EncodeUlawTo](/ulaw.go#L88) -```go -type Encoder struct { -} -``` +`func EncodeUlawTo(lpcm, ulaw []byte)` -Encoder encodes 16bit 8000Hz LPCM data to G711 PCM or directly transcodes -between A-law and u-law +EncodeUlawTo encodes 16bit LPCM data to G711 u-law PCM +using an already allocated buffer provided by the user. +The user is responsible for ensuring that the buffer is large enough (half the size of the LPCM data). -#### func NewAlawEncoder +### func [Ulaw2Alaw](/ulaw.go#L133) -```go -func NewAlawEncoder(writer io.Writer, input int) (*Encoder, error) -``` -NewAlawEncoder returns a pointer to an Encoder that implements an io.Writer. It -takes as input the destination data Writer and the input encoding format. +`func Ulaw2Alaw(ulaw []byte) []byte` -#### func NewUlawEncoder +Ulaw2Alaw performs direct u-law to A-law data conversion -```go -func NewUlawEncoder(writer io.Writer, input int) (*Encoder, error) -``` -NewUlawEncoder returns a pointer to an Encoder that implements an io.Writer. It -takes as input the destination data Writer and the input encoding format. +### func [Ulaw2AlawFrame](/ulaw.go#L149) -#### func (*Encoder) Reset +`func Ulaw2AlawFrame(frame uint8) uint8` -```go -func (w *Encoder) Reset(writer io.Writer) error -``` -Reset discards the Encoder state. This permits reusing an Encoder rather than -allocating a new one. +Ulaw2AlawFrame directly converts a u-law frame to A-law -#### func (*Encoder) Write +### func [Ulaw2AlawTo](/ulaw.go#L142) -```go -func (w *Encoder) Write(p []byte) (i int, err error) -``` -Write encodes G711 Data. Writes len(p) bytes from p to the underlying data -stream, returns the number of bytes written from p (0 <= n <= len(p)) and any -error encountered that caused the write to stop early. +`func Ulaw2AlawTo(ulaw, alaw []byte)` + +Ulaw2AlawTo performs direct u-law to A-law data conversion +using an already allocated buffer provided by the user. +The user is responsible for ensuring that the buffer is large enough (the size of the A-law data). + +--- diff --git a/alaw.go b/alaw.go index 5120fb8..3e761f4 100644 --- a/alaw.go +++ b/alaw.go @@ -72,26 +72,29 @@ var ( // EncodeAlaw encodes 16bit LPCM data to G711 A-law PCM func EncodeAlaw(lpcm []byte) []byte { - if len(lpcm) < 2 { - return []byte{} - } - alaw := make([]byte, len(lpcm)/2) - for i, j := 0, 0; j <= len(lpcm)-2; i, j = i+1, j+2 { - alaw[i] = EncodeAlawFrame(int16(lpcm[j]) | int16(lpcm[j+1])<<8) - } + alaw := make([]byte, len(lpcm)>>1) + EncodeAlawTo(lpcm, alaw) return alaw } +// EncodeAlawTo encodes 16bit LPCM data to G711 A-law PCM +// using an already allocated buffer provided by the user. +// The user is responsible for ensuring that the buffer is large enough (half the size of the LPCM data). +func EncodeAlawTo(lpcm, alaw []byte) { + for i := 0; i < len(lpcm)-1; i += 2 { + alaw[i>>1] = EncodeAlawFrame(int16(lpcm[i]) | int16(lpcm[i+1])<<8) + } +} + // EncodeAlawFrame encodes a 16bit LPCM frame to G711 A-law PCM func EncodeAlawFrame(frame int16) uint8 { - var compressedByte, seg, sign int16 - sign = ((^frame) >> 8) & 0x80 + sign := ((^frame) >> 8) & 0x80 if sign == 0 { frame = ^frame } - compressedByte = frame >> 4 + compressedByte := frame >> 4 if compressedByte > 15 { - seg = int16(12 - bits.LeadingZeros16(uint16(compressedByte))) + seg := int16(12 - bits.LeadingZeros16(uint16(compressedByte))) compressedByte >>= seg - 1 compressedByte -= 16 compressedByte += seg << 4 @@ -102,12 +105,19 @@ func EncodeAlawFrame(frame int16) uint8 { // DecodeAlaw decodes A-law PCM data to 16bit LPCM func DecodeAlaw(pcm []byte) []byte { lpcm := make([]byte, len(pcm)*2) - for i, j := 0, 0; i < len(pcm); i, j = i+1, j+2 { + DecodeAlawTo(pcm, lpcm) + return lpcm +} + +// DecodeAlawTo decodes A-law PCM data to 16bit LPCM +// using an already allocated buffer provided by the user. +// The user is responsible for ensuring that the buffer is large enough (double the size of the PCM data). +func DecodeAlawTo(pcm, lpcm []byte) { + for i := 0; i < len(pcm); i++ { frame := alaw2lpcm[pcm[i]] - lpcm[j] = byte(frame) - lpcm[j+1] = byte(frame >> 8) + lpcm[i*2] = byte(frame) + lpcm[i*2+1] = byte(frame >> 8) } - return lpcm } // DecodeAlawFrame decodes an A-law PCM frame to 16bit LPCM @@ -118,10 +128,17 @@ func DecodeAlawFrame(frame uint8) int16 { // Alaw2Ulaw performs direct A-law to u-law data conversion func Alaw2Ulaw(alaw []byte) []byte { ulaw := make([]byte, len(alaw)) + Alaw2UlawTo(alaw, ulaw) + return ulaw +} + +// Alaw2UlawTo performs direct A-law to u-law data conversion +// using an already allocated buffer provided by the user. +// The user is responsible for ensuring that the buffer is large enough (the size of the A-law data). +func Alaw2UlawTo(alaw, ulaw []byte) { for i := 0; i < len(alaw); i++ { ulaw[i] = alaw2ulaw[alaw[i]] } - return ulaw } // Alaw2UlawFrame directly converts an A-law frame to u-law diff --git a/alaw_test.go b/alaw_test.go index f3c0f52..3722d3d 100644 --- a/alaw_test.go +++ b/alaw_test.go @@ -25,7 +25,23 @@ func BenchmarkEncodeAlaw(b *testing.B) { b.SetBytes(int64(len(rawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - EncodeAlaw(rawData) + alaw := EncodeAlaw(rawData) + _ = alaw + } +} + +// Benchmark EncodeAlawTo +func BenchmarkEncodeAlawTo(b *testing.B) { + rawData, err := os.ReadFile("testing/speech.raw") + if err != nil { + b.Fatalf("Failed to read test data: %s\n", err) + } + alaw := make([]byte, len(rawData)>>1) + b.SetBytes(int64(len(rawData))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + EncodeAlawTo(rawData, alaw) + _ = alaw } } @@ -38,7 +54,23 @@ func BenchmarkDecodeAlaw(b *testing.B) { b.SetBytes(int64(len(aData))) b.ResetTimer() for i := 0; i < b.N; i++ { - DecodeAlaw(aData) + lpcm := DecodeAlaw(aData) + _ = lpcm + } +} + +// Benchmark DecodeAlawTo +func BenchmarkDecodeAlawTo(b *testing.B) { + aData, err := os.ReadFile("testing/speech.alaw") + if err != nil { + b.Fatalf("Failed to read test data: %s\n", err) + } + lpcm := make([]byte, len(aData)<<1) + b.SetBytes(int64(len(aData))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + DecodeAlawTo(aData, lpcm) + _ = lpcm } } @@ -51,6 +83,22 @@ func BenchmarkAlaw2Ulaw(b *testing.B) { b.SetBytes(int64(len(aData))) b.ResetTimer() for i := 0; i < b.N; i++ { - Alaw2Ulaw(aData) + ulaw := Alaw2Ulaw(aData) + _ = ulaw + } +} + +// Benchmark Alaw2UlawTo +func BenchmarkAlaw2UlawTo(b *testing.B) { + aData, err := os.ReadFile("testing/speech.alaw") + if err != nil { + b.Fatalf("Failed to read test data: %s\n", err) + } + ulaw := make([]byte, len(aData)) + b.SetBytes(int64(len(aData))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Alaw2UlawTo(aData, ulaw) + _ = ulaw } } diff --git a/cmd/g711/main.go b/cmd/g711/main.go new file mode 100644 index 0000000..142e931 --- /dev/null +++ b/cmd/g711/main.go @@ -0,0 +1,86 @@ +/* + Copyright (C) 2016 - 2024, Lefteris Zafiris + + 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. + + g711 encodes 16bit 8kHz LPCM data to 8bit G711 PCM, + or decodes 8bit G711 PCM to 16bit 8kHz LPCM, + or converts between A-law and u-law formats. + Input can be 16bit 8kHz wav or raw LPCM files, or ulaw/alaw encoded PCM data. + + Usage: g711 -in -out + Valid formats are: alaw, ulaw, lpcm + +*/ + +package main + +import ( + "flag" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/zaf/g711" +) + +const wavHeader = 44 + +var ( + in = flag.String("in", "", "input format: alaw, ulaw, lpcm") + out = flag.String("out", "", "output format: alaw, ulaw, lpcm") +) + +var formats = map[string]int{ + "alaw": g711.Alaw, + "ulaw": g711.Ulaw, + "lpcm": g711.Lpcm, +} + +func main() { + flag.Parse() + var exitCode int + for _, file := range flag.Args() { + err := translate(file) + if err != nil { + fmt.Println("Error while processing", file, err) + exitCode = 1 + } + } + os.Exit(exitCode) +} + +func translate(file string) error { + input, err := os.Open(file) + if err != nil { + return err + } + inExtension := strings.ToLower(filepath.Ext(file)) + + inFormat := formats[strings.ToLower(*in)] + outFormat := formats[strings.ToLower(*out)] + outName := strings.TrimSuffix(file, filepath.Ext(file)) + "." + strings.ToLower(*out) + outFile, err := os.Create(outName) + if err != nil { + return err + } + defer outFile.Close() + // Create a new translator, it implements io.WriteCloser + translator, err := g711.NewCoder(outFile, inFormat, outFormat) + if err != nil { + os.Remove(outName) + return err + } + defer translator.Close() + if inExtension == ".wav" { + input.Seek(wavHeader, 0) // Skip wav header + } + // enc/dec/transcode the input file data and write to the output file + _, err = io.Copy(translator, input) + outFile.Sync() + return err +} diff --git a/cmd/g711dec/g711dec.go b/cmd/g711dec/g711dec.go deleted file mode 100644 index 0a2ab20..0000000 --- a/cmd/g711dec/g711dec.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright (C) 2016 - 2024, Lefteris Zafiris - - 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. - - g711dec decodes 8bit G711 PCM data to 16 Bit signed LPCM raw data - -*/ - -package main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/zaf/g711" -) - -func main() { - if len(os.Args) == 1 || os.Args[1] == "help" || os.Args[1] == "--help" { - fmt.Printf("%s Decodes 8bit G711 PCM data to raw 16 Bit signed LPCM\n", os.Args[0]) - fmt.Println("The program takes as input a list A-law or u-law encoded files") - fmt.Println("decodes them to LPCM and saves the files with a \".raw\" extension.") - fmt.Printf("\nUsage: %s [files]\n", os.Args[0]) - os.Exit(1) - } - var exitCode int - for _, file := range os.Args[1:] { - err := decodeG711(file) - if err != nil { - fmt.Println(err) - exitCode = 1 - } - } - os.Exit(exitCode) -} - -func decodeG711(file string) error { - input, err := os.Open(file) - if err != nil { - return err - } - defer input.Close() - - extension := strings.ToLower(filepath.Ext(file)) - decoder := new(g711.Decoder) - if extension == ".alaw" || extension == ".al" { - decoder, err = g711.NewAlawDecoder(input) - if err != nil { - return err - } - } else if extension == ".ulaw" || extension == ".ul" { - decoder, err = g711.NewUlawDecoder(input) - if err != nil { - return err - } - } else { - err = fmt.Errorf("unrecognised format for file: %s", file) - return err - } - outName := strings.TrimSuffix(file, filepath.Ext(file)) + ".raw" - outFile, err := os.Create(outName) - if err != nil { - return err - } - defer outFile.Close() - _, err = io.Copy(outFile, decoder) - return err -} diff --git a/cmd/g711enc/g711enc.go b/cmd/g711enc/g711enc.go deleted file mode 100644 index 49d9655..0000000 --- a/cmd/g711enc/g711enc.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright (C) 2016 - 2024, Lefteris Zafiris - - 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. - - g711enc encodes 16bit 8kHz LPCM data to 8bit G711 PCM. - It works with wav or raw files as input. - -*/ - -package main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/zaf/g711" -) - -const wavHeader = 44 - -func main() { - if len(os.Args) < 3 || os.Args[1] == "help" || os.Args[1] == "--help" || (os.Args[1] != "ulaw" && os.Args[1] != "alaw") { - fmt.Printf("%s Encodes 16bit 8kHz LPCM data to 8bit G711 PCM\n", os.Args[0]) - fmt.Println("The program takes as input a list or wav or raw files, encodes them") - fmt.Println("to G711 PCM and saves them with the proper extension.") - fmt.Printf("\nUsage: %s [encoding format] [files]\n", os.Args[0]) - fmt.Println("encoding format can be either alaw or ulaw") - os.Exit(1) - } - var exitCode int - format := os.Args[1] - for _, file := range os.Args[2:] { - err := encodeG711(file, format) - if err != nil { - fmt.Println(err) - exitCode = 1 - } - } - os.Exit(exitCode) -} - -func encodeG711(file, format string) error { - input, err := os.Open(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) - return err - } - outName := strings.TrimSuffix(file, filepath.Ext(file)) + "." + format - outFile, err := os.Create(outName) - if err != nil { - return err - } - defer outFile.Close() - encoder := new(g711.Encoder) - if format == "alaw" { - encoder, err = g711.NewAlawEncoder(outFile, g711.Lpcm) - if err != nil { - return err - } - } else { - encoder, err = g711.NewUlawEncoder(outFile, g711.Lpcm) - if err != nil { - return err - } - } - if extension == ".wav" { - input.Seek(wavHeader, 0) // Skip wav header - } - _, err = io.Copy(encoder, input) - return err -} diff --git a/g711.go b/g711.go index 3d9479c..c6ddd74 100644 --- a/g711.go +++ b/g711.go @@ -10,7 +10,10 @@ Package g711 implements encoding and decoding of G711 PCM sound data. G.711 is an ITU-T standard for audio companding. -For usage details please see the code snippets in the cmd folder. +The package exposes a high level API for encoding and decoding through +an io.WriteCloser. But also a low level API using preallocated buffers +for cases where performance and memory handling are critical. +For usage details please see the code snippet in the cmd folder. */ package g711 @@ -21,99 +24,91 @@ import ( const ( // Input and output formats - Alaw = iota // Alaw G711 encoded PCM data - Ulaw // Ulaw G711 encoded PCM data - Lpcm // Lpcm 16bit signed linear data + Alaw = iota + 1 // Alaw G711 encoded PCM data + Ulaw // Ulaw G711 encoded PCM data + Lpcm // Lpcm 16bit signed linear data ) -// Decoder reads G711 PCM data and decodes it to 16bit 8000Hz LPCM -type Decoder struct { - decode func([]byte) []byte // decoding function - source io.Reader // source data -} - -// Encoder encodes 16bit 8000Hz LPCM data to G711 PCM or +// Coder encodes 16bit 8000Hz LPCM data to G711 PCM, or +// decodes G711 PCM data to 16bit 8000Hz LPCM data, or // directly transcodes between A-law and u-law -type Encoder struct { - input int // input format - encode func([]byte) []byte // encoding function - transcode func([]byte) []byte // transcoding function +type Coder struct { + translate func([]byte) []byte // enc/decoding function destination io.Writer // output data + written int8 // reported written size } -// NewAlawDecoder returns a pointer to a Decoder that implements an io.Reader. -// It takes as input the source data Reader. -func NewAlawDecoder(reader io.Reader) (*Decoder, error) { - if reader == nil { - return nil, errors.New("io.Reader is nil") - } - r := Decoder{ - decode: DecodeAlaw, - source: reader, - } - return &r, nil -} - -// NewUlawDecoder returns a pointer to a Decoder that implements an io.Reader. -// It takes as input the source data Reader. -func NewUlawDecoder(reader io.Reader) (*Decoder, error) { - if reader == nil { - return nil, errors.New("io.Reader is nil") - } - r := Decoder{ - decode: DecodeUlaw, - source: reader, - } - return &r, nil -} - -// NewAlawEncoder returns a pointer to an Encoder that implements an io.Writer. -// It takes as input the destination data Writer and the input encoding format. -func NewAlawEncoder(writer io.Writer, input int) (*Encoder, error) { - if writer == nil { - return nil, errors.New("io.Writer is nil") - } - if input != Ulaw && input != Lpcm { - return nil, errors.New("invalid input format") - } - w := Encoder{ - input: input, - encode: EncodeAlaw, - transcode: Ulaw2Alaw, - destination: writer, - } - return &w, nil -} +const ( + // written parameters + double = iota + 1 + half +) -// NewUlawEncoder returns a pointer to an Encoder that implements an io.Writer. -// It takes as input the destination data Writer and the input encoding format. -func NewUlawEncoder(writer io.Writer, input int) (*Encoder, error) { +// NewCoder returns a pointer to a Coder that implements an io.WriteCloser. +// It takes as input the destination data Writer and the input/output encoding formats. +func NewCoder(writer io.Writer, input, output int) (*Coder, error) { if writer == nil { return nil, errors.New("io.Writer is nil") } - if input != Alaw && input != Lpcm { + var translate func([]byte) []byte + var written int8 + switch input { + case Lpcm: + switch output { + case Alaw: + translate = EncodeAlaw + written = double + case Ulaw: + translate = EncodeUlaw + written = double + default: + return nil, errors.New("invalid output format") + } + case Alaw: + switch output { + case Lpcm: + translate = DecodeAlaw + written = half + case Ulaw: + translate = Alaw2Ulaw + default: + return nil, errors.New("invalid output format") + } + case Ulaw: + switch output { + case Lpcm: + translate = DecodeUlaw + written = half + case Alaw: + translate = Ulaw2Alaw + default: + return nil, errors.New("invalid output format") + } + default: return nil, errors.New("invalid input format") } - w := Encoder{ - input: input, - encode: EncodeUlaw, - transcode: Alaw2Ulaw, + w := Coder{ + translate: translate, destination: writer, + written: written, } return &w, nil } -// Reset discards the Decoder state. This permits reusing a Decoder rather than allocating a new one. -func (r *Decoder) Reset(reader io.Reader) error { - if reader == nil { - return errors.New("io.Reader is nil") - } - r.source = reader +// Close closes the Encoder, it implements the io.Closer interface. +func (w *Coder) Close() error { + w.destination = nil + w.translate = nil + w.written = 0 + w = nil return nil } // Reset discards the Encoder state. This permits reusing an Encoder rather than allocating a new one. -func (w *Encoder) Reset(writer io.Writer) error { +func (w *Coder) Reset(writer io.Writer) error { + if w == nil || w.translate == nil || w.destination == nil { + return errors.New("coder is uninitialized or closed") + } if writer == nil { return errors.New("io.Writer is nil") } @@ -121,34 +116,22 @@ func (w *Encoder) Reset(writer io.Writer) error { return nil } -// Read decodes G711 data. Reads up to len(p) bytes into p, returns the number -// of bytes read and any error encountered. -func (r *Decoder) Read(p []byte) (i int, err error) { - if len(p) == 0 { - return - } - b := make([]byte, len(p)/2) - i, err = r.source.Read(b) - copy(p, r.decode(b)) - i *= 2 // Report back the correct number of bytes - return -} - -// Write encodes G711 Data. Writes len(p) bytes from p to the underlying data stream, +// Write encodes/decodes/transcodes sound data. Writes len(p) bytes from p to the underlying data stream, // returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered // that caused the write to stop early. -func (w *Encoder) Write(p []byte) (i int, err error) { +func (w *Coder) Write(p []byte) (int, error) { if len(p) == 0 { - return + return 0, nil } - 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") - } - i *= 2 // Report back the correct number of bytes written from p - } else { // Trans-code - i, err = w.destination.Write(w.transcode(p)) + i, err := w.destination.Write(w.translate(p)) + // If we are encoding to g711 we need to multiply the number of bytes written by 2 to avoid reporting short writes + // this happens because 2 bytes of input data are encoded to 1 byte of output data. + // In a similar manner if we are decoding from g711 we need to divide the number of bytes written by 2. + switch { + case w.written == double: + i <<= 1 + case w.written == half: + i >>= 1 } - return + return i, err } diff --git a/g711_test.go b/g711_test.go index 58f9b1d..127ef39 100644 --- a/g711_test.go +++ b/g711_test.go @@ -16,14 +16,67 @@ import ( "io" "os" "testing" - "testing/iotest" ) +func TestNewCoder(t *testing.T) { + writer := bytes.NewBuffer([]byte{}) + input := Lpcm + output := Alaw + // Test case: Valid input + coder, err := NewCoder(writer, input, output) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if coder == nil { + t.Error("Expected coder to not be nil") + } + // Test case: Nil writer + _, err = NewCoder(nil, input, output) + if err == nil { + t.Error("Expected error, got nil") + } + // Test case: Invalid input format + _, err = NewCoder(writer, 999, output) + if err == nil { + t.Error("Expected error, got nil") + } + // Test case: Invalid output format + _, err = NewCoder(writer, input, 999) + if err == nil { + t.Error("Expected error, got nil") + } +} + +func TestReset(t *testing.T) { + // Test case 1: Coder is not nil, writer is not nil + w, _ := NewCoder(io.Discard, Lpcm, Alaw) + writer := bytes.NewBufferString("") + err := w.Reset(writer) + if err != nil { + t.Errorf("Expected no error, but got: %v", err) + } + w.Close() + // Test case 2: Coder is nil + nilCoder, _ := NewCoder(io.Discard, Lpcm, Alaw) + nilCoder.Close() + err = nilCoder.Reset(writer) + if err == nil || err.Error() != "coder is uninitialized or closed" { + t.Errorf("Expected error: 'coder is uninitialized or closed', but got: %v", err) + } + // Test case 3: writer is nil + w, _ = NewCoder(io.Discard, Lpcm, Alaw) + err = w.Reset(nil) + if err == nil || err.Error() != "io.Writer is nil" { + t.Errorf("Expected error: 'io.Writer is nil', but got: %v", err) + } +} + var EncoderTest = []struct { data []byte expected int }{ {[]byte{}, 0}, + {[]byte{0x01}, 0}, {[]byte{0x01, 0x00}, 2}, {[]byte{0x01, 0x00, 0x7c, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde}, 12}, {[]byte{0x01, 0x00, 0x7c, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde, 0xd9}, 12}, @@ -34,9 +87,10 @@ var DecoderTest = []struct { expected int }{ {[]byte{}, 0}, - {[]byte{0x01, 0x00}, 4}, - {[]byte{0x01, 0x00, 0x7c, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde}, 24}, - {[]byte{0x01, 0x00, 0xdc, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde, 0xd9}, 26}, + {[]byte{0x01}, 1}, + {[]byte{0x01, 0x00}, 2}, + {[]byte{0x01, 0x00, 0x7c, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde}, 12}, + {[]byte{0x01, 0x00, 0xdc, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde, 0xd9}, 13}, } var TranscoderTest = []struct { @@ -44,6 +98,7 @@ var TranscoderTest = []struct { expected int }{ {[]byte{}, 0}, + {[]byte{0x01}, 1}, {[]byte{0x01, 0x00}, 2}, {[]byte{0x01, 0x00, 0x7c, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde}, 12}, {[]byte{0x01, 0x00, 0x7c, 0x7f, 0xd1, 0xd0, 0xd3, 0xd2, 0xdd, 0xdc, 0xdf, 0xde, 0xd9}, 13}, @@ -51,77 +106,50 @@ var TranscoderTest = []struct { // Test Encoding func TestEncode(t *testing.T) { - aenc, _ := NewAlawEncoder(io.Discard, Lpcm) + aenc, _ := NewCoder(io.Discard, Lpcm, Alaw) 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(io.Discard, Lpcm) + aenc.Close() + uenc, _ := NewCoder(io.Discard, Lpcm, Ulaw) 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(io.Discard, Alaw) + uenc.Close() + utrans, _ := NewCoder(io.Discard, Alaw, Ulaw) for _, tc := range TranscoderTest { i, _ := utrans.Write(tc.data) if i != tc.expected { t.Errorf("ulaw Transcode: expected: %d , actual: %d", tc.expected, i) } } + utrans.Close() } // Test Decoding func TestDecode(t *testing.T) { - b := new(bytes.Buffer) - adec, _ := NewAlawDecoder(b) + adec, _ := NewCoder(io.Discard, Alaw, Lpcm) for _, tc := range DecoderTest { - b.Write(tc.data) - p := make([]byte, 16) - var err error - var i, n int - for err == nil { - n, err = adec.Read(p) - i += n - } + i, _ := adec.Write(tc.data) if i != tc.expected { t.Errorf("Alaw Decode: expected: %d , actual: %d", tc.expected, i) } } - b.Reset() - udec, _ := NewUlawDecoder(b) - for _, tc := range DecoderTest { - b.Write(tc.data) - p := make([]byte, 16) - var err error - var i, n int - for err == nil { - n, err = udec.Read(p) - i += n - } - if i != tc.expected { - t.Errorf("ulaw Decode: expected: %d , actual: %d", tc.expected, i) - } - } - b.Reset() - // Edge Case - udec, _ = NewUlawDecoder(iotest.TimeoutReader(b)) + adec.Close() + udec, _ := NewCoder(io.Discard, Ulaw, Lpcm) for _, tc := range DecoderTest { - b.Write(tc.data) - p := make([]byte, 16) - var err error - var i, n int - for err == nil || err.Error() == "timeout" { - n, err = udec.Read(p) - i += n - } + i, _ := udec.Write(tc.data) if i != tc.expected { t.Errorf("ulaw Decode: expected: %d , actual: %d", tc.expected, i) } } + udec.Close() } // Benchmark Encoding data to Alaw @@ -133,14 +161,15 @@ func BenchmarkAEncode(b *testing.B) { b.SetBytes(int64(len(rawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - encoder, err := NewAlawEncoder(io.Discard, Lpcm) + encoder, err := NewCoder(io.Discard, Lpcm, Alaw) if err != nil { - b.Fatalf("Failed to create Writer: %s\n", err) + b.Fatalf("Failed to create Encoder: %s\n", err) } _, err = encoder.Write(rawData) if err != nil { b.Fatalf("Encoding failed: %s\n", err) } + encoder.Close() } } @@ -154,9 +183,9 @@ func BenchmarkUEncode(b *testing.B) { b.SetBytes(int64(len(rawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - encoder, err := NewUlawEncoder(io.Discard, Lpcm) + encoder, err := NewCoder(io.Discard, Lpcm, Ulaw) if err != nil { - b.Fatalf("Failed to create Writer: %s\n", err) + b.Fatalf("Failed to create Encoder: %s\n", err) } _, err = encoder.Write(rawData) @@ -164,6 +193,7 @@ func BenchmarkUEncode(b *testing.B) { b.Fatalf("Encoding failed: %s\n", err) } + encoder.Close() } } @@ -177,9 +207,9 @@ func BenchmarkTranscode(b *testing.B) { b.SetBytes(int64(len(alawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - transcoder, err := NewAlawEncoder(io.Discard, Ulaw) + transcoder, err := NewCoder(io.Discard, Ulaw, Alaw) if err != nil { - b.Fatalf("Failed to create Writer: %s\n", err) + b.Fatalf("Failed to create Transcoder: %s\n", err) } _, err = transcoder.Write(alawData) @@ -187,6 +217,7 @@ func BenchmarkTranscode(b *testing.B) { b.Fatalf("Transcoding failed: %s\n", err) } + transcoder.Close() } } @@ -200,16 +231,17 @@ func BenchmarkUDecode(b *testing.B) { b.SetBytes(int64(len(ulawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - decoder, err := NewUlawDecoder(bytes.NewReader(ulawData)) + decoder, err := NewCoder(io.Discard, Ulaw, Lpcm) if err != nil { b.Fatalf("Failed to create Reader: %s\n", err) } - _, err = io.Copy(io.Discard, decoder) + _, err = decoder.Write(ulawData) if err != nil { b.Fatalf("Decoding failed: %s\n", err) } + decoder.Close() } } @@ -223,15 +255,16 @@ func BenchmarkADecode(b *testing.B) { b.SetBytes(int64(len(alawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - decoder, err := NewAlawDecoder(bytes.NewReader(alawData)) + decoder, err := NewCoder(io.Discard, Alaw, Lpcm) if err != nil { b.Fatalf("Failed to create Reader: %s\n", err) } - _, err = io.Copy(io.Discard, decoder) + _, err = decoder.Write(alawData) if err != nil { b.Fatalf("Decoding failed: %s\n", err) } + decoder.Close() } } diff --git a/testing/Readme.md b/testing/Readme.md index 2ca9fcd..0ca2836 100644 --- a/testing/Readme.md +++ b/testing/Readme.md @@ -3,6 +3,7 @@ ## Sound files ``` dtmf-1234.raw : Signed 16 bit Little Endian, Rate 8000 Hz, Mono +piano.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 diff --git a/testing/piano.raw b/testing/piano.raw new file mode 100644 index 0000000..76b52fb Binary files /dev/null and b/testing/piano.raw differ diff --git a/ulaw.go b/ulaw.go index f0cf821..2a91537 100644 --- a/ulaw.go +++ b/ulaw.go @@ -77,20 +77,23 @@ var ( // EncodeUlaw encodes 16bit LPCM data to G711 u-law PCM func EncodeUlaw(lpcm []byte) []byte { - if len(lpcm) < 2 { - return []byte{} - } - ulaw := make([]byte, len(lpcm)/2) - for i, j := 0, 0; j <= len(lpcm)-2; i, j = i+1, j+2 { - ulaw[i] = EncodeUlawFrame(int16(lpcm[j]) | int16(lpcm[j+1])<<8) - } + ulaw := make([]byte, len(lpcm)>>1) + EncodeUlawTo(lpcm, ulaw) return ulaw } +// EncodeUlawTo encodes 16bit LPCM data to G711 u-law PCM +// using an already allocated buffer provided by the user. +// The user is responsible for ensuring that the buffer is large enough (half the size of the LPCM data). +func EncodeUlawTo(lpcm, ulaw []byte) { + for i := 0; i < len(lpcm)-1; i += 2 { + ulaw[i>>1] = EncodeUlawFrame(int16(lpcm[i]) | int16(lpcm[i+1])<<8) + } +} + // EncodeUlawFrame encodes a 16bit LPCM frame to G711 u-law PCM func EncodeUlawFrame(frame int16) uint8 { - var lowNibble, seg, sign int16 - sign = ((^frame) >> 8) & 0x80 + sign := ((^frame) >> 8) & 0x80 if sign == 0 { frame = ^frame } @@ -98,20 +101,27 @@ func EncodeUlawFrame(frame int16) uint8 { if frame > ulawClip { frame = ulawClip } - seg = int16(16 - bits.LeadingZeros16(uint16(frame>>5))) - lowNibble = 0x000F - ((frame >> (seg)) & 0x000F) + seg := int16(16 - bits.LeadingZeros16(uint16(frame>>5))) + lowNibble := 0x000F - ((frame >> (seg)) & 0x000F) return uint8(sign | ((8 - seg) << 4) | lowNibble) } // DecodeUlaw decodes u-law PCM data to 16bit LPCM func DecodeUlaw(pcm []byte) []byte { lpcm := make([]byte, len(pcm)*2) - for i, j := 0, 0; i < len(pcm); i, j = i+1, j+2 { + DecodeUlawTo(pcm, lpcm) + return lpcm +} + +// DecodeUlawTo decodes u-law PCM data to 16bit LPCM +// using an already allocated buffer provided by the user. +// The user is responsible for ensuring that the buffer is large enough (double the size of the PCM data). +func DecodeUlawTo(pcm, lpcm []byte) { + for i := 0; i < len(pcm); i++ { frame := ulaw2lpcm[pcm[i]] - lpcm[j] = byte(frame) - lpcm[j+1] = byte(frame >> 8) + lpcm[i*2] = byte(frame) + lpcm[i*2+1] = byte(frame >> 8) } - return lpcm } // DecodeUlawFrame decodes a u-law PCM frame to 16bit LPCM @@ -122,10 +132,17 @@ func DecodeUlawFrame(frame uint8) int16 { // Ulaw2Alaw performs direct u-law to A-law data conversion func Ulaw2Alaw(ulaw []byte) []byte { alaw := make([]byte, len(ulaw)) + Ulaw2AlawTo(ulaw, alaw) + return alaw +} + +// Ulaw2AlawTo performs direct u-law to A-law data conversion +// using an already allocated buffer provided by the user. +// The user is responsible for ensuring that the buffer is large enough (the size of the A-law data). +func Ulaw2AlawTo(ulaw, alaw []byte) { for i := 0; i < len(alaw); i++ { alaw[i] = ulaw2alaw[ulaw[i]] } - return alaw } // Ulaw2AlawFrame directly converts a u-law frame to A-law diff --git a/ulaw_test.go b/ulaw_test.go index b9efdd6..4ea4489 100644 --- a/ulaw_test.go +++ b/ulaw_test.go @@ -25,7 +25,23 @@ func BenchmarkEncodeUlaw(b *testing.B) { b.SetBytes(int64(len(rawData))) b.ResetTimer() for i := 0; i < b.N; i++ { - EncodeUlaw(rawData) + ualw := EncodeUlaw(rawData) + _ = ualw + } +} + +// Benchmark EncodeUlawTo +func BenchmarkEncodeUlawTo(b *testing.B) { + 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))) + ulaw := make([]byte, len(rawData)>>1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + EncodeUlawTo(rawData, ulaw) + _ = ulaw } } @@ -38,7 +54,23 @@ func BenchmarkDecodeUlaw(b *testing.B) { b.SetBytes(int64(len(uData))) b.ResetTimer() for i := 0; i < b.N; i++ { - DecodeUlaw(uData) + lpcm := DecodeUlaw(uData) + _ = lpcm + } +} + +// Benchmark DecodeUlawTo +func BenchmarkDecodeUlawTo(b *testing.B) { + uData, err := os.ReadFile("testing/speech.ulaw") + if err != nil { + b.Fatalf("Failed to read test data: %s\n", err) + } + b.SetBytes(int64(len(uData))) + lpcm := make([]byte, len(uData)<<1) + b.ResetTimer() + for i := 0; i < b.N; i++ { + DecodeUlawTo(uData, lpcm) + _ = lpcm } } @@ -51,6 +83,22 @@ func BenchmarkUlaw2Alaw(b *testing.B) { b.SetBytes(int64(len(uData))) b.ResetTimer() for i := 0; i < b.N; i++ { - Ulaw2Alaw(uData) + alaw := Ulaw2Alaw(uData) + _ = alaw + } +} + +// Benchmark Ulaw2AlawTo +func BenchmarkUlaw2AlawTo(b *testing.B) { + uData, err := os.ReadFile("testing/speech.ulaw") + if err != nil { + b.Fatalf("Failed to read test data: %s\n", err) + } + b.SetBytes(int64(len(uData))) + alaw := make([]byte, len(uData)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Ulaw2AlawTo(uData, alaw) + _ = alaw } }