Skip to content

Commit

Permalink
ulaw to wav pcm
Browse files Browse the repository at this point in the history
  • Loading branch information
pablodz committed Dec 25, 2023
1 parent b27f405 commit b311430
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 173 deletions.
53 changes: 8 additions & 45 deletions audio/formats/pcm/pcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,25 @@
package pcm

import (
"math"
"log"

"github.com/sopro-dev/sopro-core/audio"
)

type PCMFormat struct{}

func (f *PCMFormat) Decode(data []byte, info audio.AudioInfo) []float64 {
sampleSize := info.BitDepth / 8
numSamples := len(data) / (sampleSize * info.Channels)
pcm := make([]float64, numSamples*info.Channels)

for i := 0; i < numSamples; i++ {
for ch := 0; ch < info.Channels; ch++ {
startIndex := (i*info.Channels + ch) * sampleSize
endIndex := startIndex + sampleSize
sample := int64(0)

for j := startIndex; j < endIndex; j++ {
sample |= int64(data[j]) << ((j - startIndex) * 8)
}

divider := math.Pow(2, float64(info.BitDepth-1))
if info.FloatFormat {
divider = math.Pow(2, float64(info.BitDepth))
}

pcm[i*info.Channels+ch] = float64(sample) / divider
}
}

return pcm
log.Printf("Not implemented")
return nil
}

func (f *PCMFormat) Encode(audioData []float64, info audio.AudioInfo) []byte {
sampleSize := info.BitDepth / 8
numSamples := len(audioData) / info.Channels
encoded := make([]byte, numSamples*info.Channels*sampleSize)

for i := 0; i < numSamples; i++ {
for ch := 0; ch < info.Channels; ch++ {
sampleIndex := i*info.Channels + ch
sample := audioData[sampleIndex]

sample = math.Max(-1.0, math.Min(sample, 1.0))

if !info.FloatFormat {
sample *= 0.5
}
sampleInt := int64((1 << (info.BitDepth - 1)) * int(sample))
for j := 0; j < sampleSize; j++ {
shift := uint(j * 8)
encoded[i*info.Channels*sampleSize+ch*sampleSize+j] = byte((sampleInt >> shift) & 0xFF)
}
}
// convert float64 to byte
data := make([]byte, len(audioData))
for i := 0; i < len(audioData); i++ {
data[i] = byte(audioData[i])
}

return encoded
return data
}
28 changes: 28 additions & 0 deletions audio/formats/ulaw/mulaw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package mulaw

import (
"log"

"github.com/sopro-dev/sopro-core/audio"
"github.com/sopro-dev/sopro-core/audio/utils"
)

type MuLawFormat struct{}

func (f *MuLawFormat) Decode(data []byte, info audio.AudioInfo) []float64 {

pcmData := make([]float64, len(data))
for i := 0; i < len(data); i++ {
pcmData[i] = float64(utils.DecodeFromULaw(data[i]))
}

log.Println("[Bytes][Org]", data[0:100])
log.Println("[Bytes][Pcm]", pcmData[0:100])

return pcmData
}

func (f *MuLawFormat) Encode(audioData []float64, info audio.AudioInfo) []byte {
log.Printf("Not implemented")
return nil
}
37 changes: 0 additions & 37 deletions audio/formats/ulaw/ulaw.go

This file was deleted.

34 changes: 31 additions & 3 deletions audio/transcoder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package audio

import "unsafe"
import (
"errors"
"unsafe"
)

type Transcoder struct {
InputFormat AudioFormat
Expand All @@ -14,9 +17,34 @@ func NewTranscoder(inputFormat, outputFormat AudioFormat) *Transcoder {
}
}

func (t *Transcoder) Transcode(inputData []byte, info AudioInfo) []byte {
func (t *Transcoder) Transcode(inputData []byte, info AudioInfo) ([]byte, error) {
err := validateAudioInfo(info)
if err != nil {
return nil, err
}
audioData := t.InputFormat.Decode(inputData, info)
return t.OutputFormat.Encode(audioData, info)
return t.OutputFormat.Encode(audioData, info), nil
}

var (
errInvalidBitDepth = errors.New("invalid bit depth")
errInvalidNumChannels = errors.New("invalid number of channels")
errInvalidSampleRate = errors.New("invalid sample rate")
)

func validateAudioInfo(info AudioInfo) error {
if info.BitDepth != 8 && info.BitDepth != 16 && info.BitDepth != 24 && info.BitDepth != 32 {
return errInvalidBitDepth
}

if info.Channels < 1 || info.Channels > 2 {
return errInvalidNumChannels
}

if info.SampleRate < 1 {
return errInvalidSampleRate
}
return nil
}

// IMPORTANT: DO NOT REMOVE THIS LINE
Expand Down
42 changes: 42 additions & 0 deletions audio/transcoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package audio

import "testing"

func TestValidateAudioInfo(t *testing.T) {
// Test case 1: Valid audio info
info := AudioInfo{BitDepth: 16, Channels: 2, SampleRate: 44100}
err := validateAudioInfo(info)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

// Test case 2: Invalid bit depth
info = AudioInfo{BitDepth: 5, Channels: 2, SampleRate: 44100}
err = validateAudioInfo(info)
if err == nil {
t.Error("Expected an error for invalid bit depth")
}
if err != errInvalidBitDepth {
t.Errorf("Expected error %v, got %v", errInvalidBitDepth, err)
}

// Test case 3: Invalid number of channels
info = AudioInfo{BitDepth: 16, Channels: 0, SampleRate: 44100}
err = validateAudioInfo(info)
if err == nil {
t.Error("Expected an error for invalid number of channels")
}
if err != errInvalidNumChannels {
t.Errorf("Expected error %v, got %v", errInvalidNumChannels, err)
}

// Test case 4: Invalid sample rate
info = AudioInfo{BitDepth: 16, Channels: 2, SampleRate: 0}
err = validateAudioInfo(info)
if err == nil {
t.Error("Expected an error for invalid sample rate")
}
if err != errInvalidSampleRate {
t.Errorf("Expected error %v, got %v", errInvalidSampleRate, err)
}
}
31 changes: 31 additions & 0 deletions audio/utils/bytes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package utils

import (
"encoding/binary"
)

// MergeSliceOfBytes merges multiple slices of bytes into a single slice
func MergeSliceOfBytes(slices ...[]byte) []byte {
var result []byte
for _, s := range slices {
result = append(result, s...)
}
return result
}

// IntToBytes converts an unsigned integer to a little-endian byte slice
func IntToBytes(i interface{}) []byte {
switch v := i.(type) {
case uint32:
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, v)
return buf
case uint16:
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, v)
return buf
default:

}
return nil
}
48 changes: 48 additions & 0 deletions audio/utils/bytes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package utils

import (
"bytes"
"testing"
)

func TestMergeSliceOfBytes(t *testing.T) {
slices := [][]byte{
[]byte("hello"),
[]byte(" "),
[]byte("world"),
[]byte(""),
}
expected := []byte("hello world")

result := MergeSliceOfBytes(slices...)

if !bytes.Equal(result, expected) {
t.Errorf("Expected %s but got %s", expected, result)
}
}

func TestIntToBytes(t *testing.T) {
tests := []struct {
input interface{}
expected []byte
}{
{uint32(123456), []byte{64, 226, 1, 0}}, // 123456 in little-endian bytes
{uint16(0), []byte{0, 0}}, // 0 in little-endian bytes
{uint16(65535), []byte{255, 255}}, // 65535 in little-endian bytes
{uint16(1), []byte{1, 0}}, // 65536 in little-endian bytes
{uint32(2084), []byte{24, 8, 0, 0}}, // 65536 in little-endian bytes
// Add more test cases as needed
}

for _, test := range tests {
result := IntToBytes(test.input)
if !bytesEqual(result, test.expected) {
t.Errorf("For input %v, expected %v, but got %v", test.input, test.expected, result)
}
}
}

// bytesEqual compares two byte slices for equality
func bytesEqual(a, b []byte) bool {
return len(a) == len(b) && (len(a) == 0 || (a[0] == b[0] && bytesEqual(a[1:], b[1:])))
}
37 changes: 37 additions & 0 deletions audio/utils/tables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package utils

// ulawDecode is a lookup table for u-law to LPCM
var ulawDecode = [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,
}
32 changes: 2 additions & 30 deletions audio/utils/ulaw_utils.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,6 @@
package utils

func DecodeFromULaw(uLaw byte) int16 {
uLaw = ^uLaw
sign := int16(1)
if uLaw&0x80 != 0 {
sign = -1
}
exponent := int((uLaw >> 4) & 0x07)
mantissa := int(uLaw&0x0F) + 16
return sign * int16(mantissa) << uint(exponent+3)
}

func EncodeToULaw(sample int16) byte {
sign := byte(0)
if sample < 0 {
sign = 0x80
sample = -sample
}

sample = sample + 33
if sample > 0x7FFF {
sample = 0x7FFF
}

exponent := byte(7)
for (sample & 0x4000) == 0 {
exponent--
sample <<= 1
}

mantissa := byte(sample >> 6)
return ^byte(sign | (exponent << 4) | mantissa)
pcm := ulawDecode[uLaw]
return int16(pcm)
}
Loading

0 comments on commit b311430

Please sign in to comment.