148 lines
4.7 KiB
Go
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
|
|
}
|