Files
go-sip/internal/audio/wav_test.go
T

148 lines
4.7 KiB
Go

package audio
import (
"bytes"
"encoding/binary"
"testing"
)
func TestDecodeWAVPCM16MonoToInternalFormat(t *testing.T) {
want := []int16{1000, -1000, 32767, -32768}
encoded := testPCM16WAV(16000, 1, want)
got, rate, err := DecodeWAVToPCM16Mono(encoded, 16000, 1<<20)
if err != nil {
t.Fatal(err)
}
if rate != 16000 {
t.Fatalf("source rate = %d, want 16000", rate)
}
if !bytes.Equal(got, pcm16Bytes(want)) {
t.Fatalf("decoded PCM16 = %v, want %v", got, pcm16Bytes(want))
}
}
func TestDecodeWAVPCM16StereoDownmixesAndResamples(t *testing.T) {
encoded := testPCM16WAV(8000, 2, []int16{1000, 3000, 2000, 4000})
want := []int16{2000, 2500, 3000, 3000}
got, rate, err := DecodeWAVToPCM16Mono(encoded, 16000, 1<<20)
if err != nil {
t.Fatal(err)
}
if rate != 8000 {
t.Fatalf("source rate = %d, want 8000", rate)
}
if !bytes.Equal(got, pcm16Bytes(want)) {
t.Fatalf("decoded PCM16 = %v, want %v", got, pcm16Bytes(want))
}
}
func TestDecodeWAVRejectsMalformedOrUnsupportedAudio(t *testing.T) {
valid := testPCM16WAV(16000, 1, []int16{1000, -1000})
truncated := append([]byte(nil), valid[:len(valid)-1]...)
wrongCodec := append([]byte(nil), valid...)
binary.LittleEndian.PutUint16(wrongCodec[20:22], 3)
wrongRate := testPCM16WAV(96000, 1, []int16{1000, -1000})
badAlignment := testWAV(1, 16000, 1, 16, []byte{0x01})
for name, input := range map[string][]byte{
"empty": nil,
"invalid header": []byte("not a wave file"),
"truncated data": truncated,
"unsupported codec": wrongCodec,
"unsupported rate": wrongRate,
"incomplete sample": badAlignment,
} {
t.Run(name, func(t *testing.T) {
if _, _, err := DecodeWAVToPCM16Mono(input, 16000, 1<<20); err == nil {
t.Fatal("invalid WAV was accepted")
}
})
}
}
func TestDecodeWAVReportsDeclaredRIFFAndDataLengths(t *testing.T) {
encoded := testPCM16WAV(8000, 1, []int16{1000, -1000})
binary.LittleEndian.PutUint32(encoded[4:8], uint32(len(encoded)+32-8))
binary.LittleEndian.PutUint32(encoded[40:44], uint32(len(encoded)-44+16))
_, _, err := DecodeWAVToPCM16Mono(encoded, 16000, 1<<20)
if err == nil {
t.Fatal("truncated WAV was accepted")
}
const want = "RIFF/WAVE length invalid or truncated: RIFF declared 80 bytes, received 48 bytes; data chunk declared 20 bytes, available 4 bytes"
if err.Error() != want {
t.Fatalf("error = %q, want %q", err, want)
}
}
func TestDecodeWAVPCMIntegerDepthsConvertToPCM16(t *testing.T) {
tests := []struct {
name string
bits uint16
data []byte
want []int16
}{
{name: "8-bit unsigned", bits: 8, data: []byte{0, 128, 255}, want: []int16{-32768, 0, 32512}},
{name: "24-bit signed", bits: 24, data: []byte{0, 0, 0x80, 0xff, 0xff, 0x7f}, want: []int16{-32768, 32767}},
{name: "32-bit signed", bits: 32, data: []byte{0, 0, 0, 0x80, 0xff, 0xff, 0xff, 0x7f}, want: []int16{-32768, 32767}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
encoded := testWAV(1, 16000, 1, tt.bits, tt.data)
got, rate, err := DecodeWAVToPCM16Mono(encoded, 16000, 1<<20)
if err != nil {
t.Fatal(err)
}
if rate != 16000 || !bytes.Equal(got, pcm16Bytes(tt.want)) {
t.Fatalf("decoded rate=%d PCM16=%v, want rate=16000 PCM16=%v", rate, got, pcm16Bytes(tt.want))
}
})
}
}
func TestDecodeWAVRejectsOutputOverBound(t *testing.T) {
samples := make([]int16, 100)
encoded := testPCM16WAV(8000, 1, samples)
if len(encoded) >= 300 {
t.Fatalf("test WAV is unexpectedly large: %d", len(encoded))
}
if _, _, err := DecodeWAVToPCM16Mono(encoded, 16000, 300); err == nil {
t.Fatal("WAV output over the configured bound was accepted")
}
}
func testPCM16WAV(sampleRate uint32, channels uint16, samples []int16) []byte {
pcm := pcm16Bytes(samples)
return testWAV(1, sampleRate, channels, 16, pcm)
}
func pcm16Bytes(samples []int16) []byte {
data := make([]byte, len(samples)*2)
for i, sample := range samples {
binary.LittleEndian.PutUint16(data[i*2:], uint16(sample))
}
return data
}
func testWAV(format, sampleRate uint32, channels uint16, bits uint16, pcm []byte) []byte {
blockAlign := channels * (bits / 8)
data := make([]byte, 44+len(pcm)+(len(pcm)&1))
copy(data[0:4], "RIFF")
binary.LittleEndian.PutUint32(data[4:8], uint32(len(data)-8))
copy(data[8:12], "WAVE")
copy(data[12:16], "fmt ")
binary.LittleEndian.PutUint32(data[16:20], 16)
binary.LittleEndian.PutUint16(data[20:22], uint16(format))
binary.LittleEndian.PutUint16(data[22:24], channels)
binary.LittleEndian.PutUint32(data[24:28], sampleRate)
binary.LittleEndian.PutUint32(data[28:32], sampleRate*uint32(blockAlign))
binary.LittleEndian.PutUint16(data[32:34], blockAlign)
binary.LittleEndian.PutUint16(data[34:36], bits)
copy(data[36:40], "data")
binary.LittleEndian.PutUint32(data[40:44], uint32(len(pcm)))
copy(data[44:], pcm)
return data
}