Skip to content

Commit b311430

Browse files
committed
ulaw to wav pcm
1 parent b27f405 commit b311430

File tree

13 files changed

+442
-173
lines changed

13 files changed

+442
-173
lines changed

audio/formats/pcm/pcm.go

+8-45
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,25 @@
22
package pcm
33

44
import (
5-
"math"
5+
"log"
66

77
"github.com/sopro-dev/sopro-core/audio"
88
)
99

1010
type PCMFormat struct{}
1111

1212
func (f *PCMFormat) Decode(data []byte, info audio.AudioInfo) []float64 {
13-
sampleSize := info.BitDepth / 8
14-
numSamples := len(data) / (sampleSize * info.Channels)
15-
pcm := make([]float64, numSamples*info.Channels)
16-
17-
for i := 0; i < numSamples; i++ {
18-
for ch := 0; ch < info.Channels; ch++ {
19-
startIndex := (i*info.Channels + ch) * sampleSize
20-
endIndex := startIndex + sampleSize
21-
sample := int64(0)
22-
23-
for j := startIndex; j < endIndex; j++ {
24-
sample |= int64(data[j]) << ((j - startIndex) * 8)
25-
}
26-
27-
divider := math.Pow(2, float64(info.BitDepth-1))
28-
if info.FloatFormat {
29-
divider = math.Pow(2, float64(info.BitDepth))
30-
}
31-
32-
pcm[i*info.Channels+ch] = float64(sample) / divider
33-
}
34-
}
35-
36-
return pcm
13+
log.Printf("Not implemented")
14+
return nil
3715
}
3816

3917
func (f *PCMFormat) Encode(audioData []float64, info audio.AudioInfo) []byte {
40-
sampleSize := info.BitDepth / 8
41-
numSamples := len(audioData) / info.Channels
42-
encoded := make([]byte, numSamples*info.Channels*sampleSize)
43-
44-
for i := 0; i < numSamples; i++ {
45-
for ch := 0; ch < info.Channels; ch++ {
46-
sampleIndex := i*info.Channels + ch
47-
sample := audioData[sampleIndex]
48-
49-
sample = math.Max(-1.0, math.Min(sample, 1.0))
5018

51-
if !info.FloatFormat {
52-
sample *= 0.5
53-
}
54-
sampleInt := int64((1 << (info.BitDepth - 1)) * int(sample))
55-
for j := 0; j < sampleSize; j++ {
56-
shift := uint(j * 8)
57-
encoded[i*info.Channels*sampleSize+ch*sampleSize+j] = byte((sampleInt >> shift) & 0xFF)
58-
}
59-
}
19+
// convert float64 to byte
20+
data := make([]byte, len(audioData))
21+
for i := 0; i < len(audioData); i++ {
22+
data[i] = byte(audioData[i])
6023
}
6124

62-
return encoded
25+
return data
6326
}

audio/formats/ulaw/mulaw.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package mulaw
2+
3+
import (
4+
"log"
5+
6+
"github.com/sopro-dev/sopro-core/audio"
7+
"github.com/sopro-dev/sopro-core/audio/utils"
8+
)
9+
10+
type MuLawFormat struct{}
11+
12+
func (f *MuLawFormat) Decode(data []byte, info audio.AudioInfo) []float64 {
13+
14+
pcmData := make([]float64, len(data))
15+
for i := 0; i < len(data); i++ {
16+
pcmData[i] = float64(utils.DecodeFromULaw(data[i]))
17+
}
18+
19+
log.Println("[Bytes][Org]", data[0:100])
20+
log.Println("[Bytes][Pcm]", pcmData[0:100])
21+
22+
return pcmData
23+
}
24+
25+
func (f *MuLawFormat) Encode(audioData []float64, info audio.AudioInfo) []byte {
26+
log.Printf("Not implemented")
27+
return nil
28+
}

audio/formats/ulaw/ulaw.go

-37
This file was deleted.

audio/transcoder.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package audio
22

3-
import "unsafe"
3+
import (
4+
"errors"
5+
"unsafe"
6+
)
47

58
type Transcoder struct {
69
InputFormat AudioFormat
@@ -14,9 +17,34 @@ func NewTranscoder(inputFormat, outputFormat AudioFormat) *Transcoder {
1417
}
1518
}
1619

17-
func (t *Transcoder) Transcode(inputData []byte, info AudioInfo) []byte {
20+
func (t *Transcoder) Transcode(inputData []byte, info AudioInfo) ([]byte, error) {
21+
err := validateAudioInfo(info)
22+
if err != nil {
23+
return nil, err
24+
}
1825
audioData := t.InputFormat.Decode(inputData, info)
19-
return t.OutputFormat.Encode(audioData, info)
26+
return t.OutputFormat.Encode(audioData, info), nil
27+
}
28+
29+
var (
30+
errInvalidBitDepth = errors.New("invalid bit depth")
31+
errInvalidNumChannels = errors.New("invalid number of channels")
32+
errInvalidSampleRate = errors.New("invalid sample rate")
33+
)
34+
35+
func validateAudioInfo(info AudioInfo) error {
36+
if info.BitDepth != 8 && info.BitDepth != 16 && info.BitDepth != 24 && info.BitDepth != 32 {
37+
return errInvalidBitDepth
38+
}
39+
40+
if info.Channels < 1 || info.Channels > 2 {
41+
return errInvalidNumChannels
42+
}
43+
44+
if info.SampleRate < 1 {
45+
return errInvalidSampleRate
46+
}
47+
return nil
2048
}
2149

2250
// IMPORTANT: DO NOT REMOVE THIS LINE

audio/transcoder_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package audio
2+
3+
import "testing"
4+
5+
func TestValidateAudioInfo(t *testing.T) {
6+
// Test case 1: Valid audio info
7+
info := AudioInfo{BitDepth: 16, Channels: 2, SampleRate: 44100}
8+
err := validateAudioInfo(info)
9+
if err != nil {
10+
t.Errorf("Unexpected error: %v", err)
11+
}
12+
13+
// Test case 2: Invalid bit depth
14+
info = AudioInfo{BitDepth: 5, Channels: 2, SampleRate: 44100}
15+
err = validateAudioInfo(info)
16+
if err == nil {
17+
t.Error("Expected an error for invalid bit depth")
18+
}
19+
if err != errInvalidBitDepth {
20+
t.Errorf("Expected error %v, got %v", errInvalidBitDepth, err)
21+
}
22+
23+
// Test case 3: Invalid number of channels
24+
info = AudioInfo{BitDepth: 16, Channels: 0, SampleRate: 44100}
25+
err = validateAudioInfo(info)
26+
if err == nil {
27+
t.Error("Expected an error for invalid number of channels")
28+
}
29+
if err != errInvalidNumChannels {
30+
t.Errorf("Expected error %v, got %v", errInvalidNumChannels, err)
31+
}
32+
33+
// Test case 4: Invalid sample rate
34+
info = AudioInfo{BitDepth: 16, Channels: 2, SampleRate: 0}
35+
err = validateAudioInfo(info)
36+
if err == nil {
37+
t.Error("Expected an error for invalid sample rate")
38+
}
39+
if err != errInvalidSampleRate {
40+
t.Errorf("Expected error %v, got %v", errInvalidSampleRate, err)
41+
}
42+
}

audio/utils/bytes.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package utils
2+
3+
import (
4+
"encoding/binary"
5+
)
6+
7+
// MergeSliceOfBytes merges multiple slices of bytes into a single slice
8+
func MergeSliceOfBytes(slices ...[]byte) []byte {
9+
var result []byte
10+
for _, s := range slices {
11+
result = append(result, s...)
12+
}
13+
return result
14+
}
15+
16+
// IntToBytes converts an unsigned integer to a little-endian byte slice
17+
func IntToBytes(i interface{}) []byte {
18+
switch v := i.(type) {
19+
case uint32:
20+
buf := make([]byte, 4)
21+
binary.LittleEndian.PutUint32(buf, v)
22+
return buf
23+
case uint16:
24+
buf := make([]byte, 2)
25+
binary.LittleEndian.PutUint16(buf, v)
26+
return buf
27+
default:
28+
29+
}
30+
return nil
31+
}

audio/utils/bytes_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package utils
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestMergeSliceOfBytes(t *testing.T) {
9+
slices := [][]byte{
10+
[]byte("hello"),
11+
[]byte(" "),
12+
[]byte("world"),
13+
[]byte(""),
14+
}
15+
expected := []byte("hello world")
16+
17+
result := MergeSliceOfBytes(slices...)
18+
19+
if !bytes.Equal(result, expected) {
20+
t.Errorf("Expected %s but got %s", expected, result)
21+
}
22+
}
23+
24+
func TestIntToBytes(t *testing.T) {
25+
tests := []struct {
26+
input interface{}
27+
expected []byte
28+
}{
29+
{uint32(123456), []byte{64, 226, 1, 0}}, // 123456 in little-endian bytes
30+
{uint16(0), []byte{0, 0}}, // 0 in little-endian bytes
31+
{uint16(65535), []byte{255, 255}}, // 65535 in little-endian bytes
32+
{uint16(1), []byte{1, 0}}, // 65536 in little-endian bytes
33+
{uint32(2084), []byte{24, 8, 0, 0}}, // 65536 in little-endian bytes
34+
// Add more test cases as needed
35+
}
36+
37+
for _, test := range tests {
38+
result := IntToBytes(test.input)
39+
if !bytesEqual(result, test.expected) {
40+
t.Errorf("For input %v, expected %v, but got %v", test.input, test.expected, result)
41+
}
42+
}
43+
}
44+
45+
// bytesEqual compares two byte slices for equality
46+
func bytesEqual(a, b []byte) bool {
47+
return len(a) == len(b) && (len(a) == 0 || (a[0] == b[0] && bytesEqual(a[1:], b[1:])))
48+
}

audio/utils/tables.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package utils
2+
3+
// ulawDecode is a lookup table for u-law to LPCM
4+
var ulawDecode = [256]int16{
5+
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
6+
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
7+
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
8+
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
9+
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
10+
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
11+
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
12+
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
13+
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
14+
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
15+
-876, -844, -812, -780, -748, -716, -684, -652,
16+
-620, -588, -556, -524, -492, -460, -428, -396,
17+
-372, -356, -340, -324, -308, -292, -276, -260,
18+
-244, -228, -212, -196, -180, -164, -148, -132,
19+
-120, -112, -104, -96, -88, -80, -72, -64,
20+
-56, -48, -40, -32, -24, -16, -8, 0,
21+
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
22+
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
23+
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
24+
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
25+
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
26+
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
27+
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
28+
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
29+
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
30+
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
31+
876, 844, 812, 780, 748, 716, 684, 652,
32+
620, 588, 556, 524, 492, 460, 428, 396,
33+
372, 356, 340, 324, 308, 292, 276, 260,
34+
244, 228, 212, 196, 180, 164, 148, 132,
35+
120, 112, 104, 96, 88, 80, 72, 64,
36+
56, 48, 40, 32, 24, 16, 8, 0,
37+
}

audio/utils/ulaw_utils.go

+2-30
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,6 @@
11
package utils
22

33
func DecodeFromULaw(uLaw byte) int16 {
4-
uLaw = ^uLaw
5-
sign := int16(1)
6-
if uLaw&0x80 != 0 {
7-
sign = -1
8-
}
9-
exponent := int((uLaw >> 4) & 0x07)
10-
mantissa := int(uLaw&0x0F) + 16
11-
return sign * int16(mantissa) << uint(exponent+3)
12-
}
13-
14-
func EncodeToULaw(sample int16) byte {
15-
sign := byte(0)
16-
if sample < 0 {
17-
sign = 0x80
18-
sample = -sample
19-
}
20-
21-
sample = sample + 33
22-
if sample > 0x7FFF {
23-
sample = 0x7FFF
24-
}
25-
26-
exponent := byte(7)
27-
for (sample & 0x4000) == 0 {
28-
exponent--
29-
sample <<= 1
30-
}
31-
32-
mantissa := byte(sample >> 6)
33-
return ^byte(sign | (exponent << 4) | mantissa)
4+
pcm := ulawDecode[uLaw]
5+
return int16(pcm)
346
}

0 commit comments

Comments
 (0)