Files
Cyrene_v2/internal/capability/transcode/wav_test.go
T
AskaEth 4a2d276881
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
2026-10-05 13:02:28 +08:00

111 lines
3.9 KiB
Go

package transcode
import (
"encoding/binary"
"errors"
"math"
"testing"
)
// TestWAVHeaderFields 逐字段断言 44 字节头(design/25 §3.1:不重采样、8 kHz、
// 单声道、PCM16)。
func TestWAVHeaderFields(t *testing.T) {
const samples = 160
dataBytes := samples * wavPCMBytesPerSample
hdr, err := wavHeader(dataBytes, amrSampleRate, amrChannels)
if err != nil {
t.Fatalf("wavHeader: %v", err)
}
if len(hdr) != wavHeaderBytes {
t.Fatalf("header length = %d, want %d", len(hdr), wavHeaderBytes)
}
if string(hdr[wavOffRiffID:wavOffChunkSize]) != wavRiffID ||
string(hdr[wavOffWaveID:wavOffFmtID]) != wavWaveID ||
string(hdr[wavOffFmtID:wavOffFmtSize]) != wavFmtID ||
string(hdr[wavOffDataID:wavOffDataSize]) != wavDataID {
t.Fatalf("chunk ids wrong: %q %q %q %q",
hdr[wavOffRiffID:wavOffChunkSize], hdr[wavOffWaveID:wavOffFmtID],
hdr[wavOffFmtID:wavOffFmtSize], hdr[wavOffDataID:wavOffDataSize])
}
checks := []struct {
name string
got uint32
want uint32
}{
{"RIFF size", binary.LittleEndian.Uint32(hdr[wavOffChunkSize:]), uint32(riffChunkSizeBase + dataBytes)},
{"fmt size", binary.LittleEndian.Uint32(hdr[wavOffFmtSize:]), wavFmtChunkBytes},
{"sample rate", binary.LittleEndian.Uint32(hdr[wavOffSampleRate:]), uint32(amrSampleRate)},
{"byte rate", binary.LittleEndian.Uint32(hdr[wavOffByteRate:]), uint32(amrSampleRate * wavPCMBytesPerSample)},
{"data size", binary.LittleEndian.Uint32(hdr[wavOffDataSize:]), uint32(dataBytes)},
}
for _, c := range checks {
if c.got != c.want {
t.Errorf("%s = %d, want %d", c.name, c.got, c.want)
}
}
if got := binary.LittleEndian.Uint16(hdr[wavOffAudioFormat:]); got != wavFormatPCM {
t.Errorf("audio format = %d, want %d (PCM)", got, wavFormatPCM)
}
if got := binary.LittleEndian.Uint16(hdr[wavOffChannels:]); got != amrChannels {
t.Errorf("channels = %d, want %d", got, amrChannels)
}
if got := binary.LittleEndian.Uint16(hdr[wavOffBlockAlign:]); got != wavPCMBytesPerSample {
t.Errorf("block align = %d, want %d", got, wavPCMBytesPerSample)
}
if got := binary.LittleEndian.Uint16(hdr[wavOffBitsPerSamp:]); got != wavBitsPerSample {
t.Errorf("bits per sample = %d, want %d", got, wavBitsPerSample)
}
}
// TestEncodePCM16WAVRoundTrip 断言样本 → WAV → 样本往返无损(小端正确)。
func TestEncodePCM16WAVRoundTrip(t *testing.T) {
samples := []int16{0, 1, -1, math.MaxInt16, math.MinInt16, 1234, -4321}
wav, err := encodePCM16WAV(samples, amrSampleRate, amrChannels)
if err != nil {
t.Fatalf("encodePCM16WAV: %v", err)
}
got := wavDataSamples(t, wav)
if len(got) != len(samples) {
t.Fatalf("round trip samples = %d, want %d", len(got), len(samples))
}
for i := range samples {
if got[i] != samples[i] {
t.Fatalf("sample %d = %d, want %d", i, got[i], samples[i])
}
}
}
// TestWAVHeaderRejectsImpossibleDeclarations 断言不可能的头参数被拒(不写出
// 一个长度撒谎的头)。
func TestWAVHeaderRejectsImpossibleDeclarations(t *testing.T) {
cases := []struct {
name string
dataBytes, rate, chans int
}{
{"zero rate", 2, 0, 1},
{"negative rate", 2, -1, 1},
{"zero channels", 2, amrSampleRate, 0},
{"negative data", -1, amrSampleRate, 1},
{"data beyond RIFF limit", int(wavMaxDataBytes) + 1, amrSampleRate, 1},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if _, err := wavHeader(tc.dataBytes, tc.rate, tc.chans); !errors.Is(err, ErrEncodeFailed) {
t.Fatalf("err = %v, want ErrEncodeFailed", err)
}
})
}
}
// TestWritePCM16LEIsLittleEndian 断言字节序:-2 = 0xFE 0xFF。
func TestWritePCM16LEIsLittleEndian(t *testing.T) {
dst := make([]byte, 2*wavPCMBytesPerSample)
writePCM16LE(dst, []int16{-2, 258})
if dst[0] != 0xFE || dst[1] != 0xFF {
t.Fatalf("-2 写成 %#x %#x, want 0xfe 0xff", dst[0], dst[1])
}
if dst[2] != 0x02 || dst[3] != 0x01 {
t.Fatalf("258 写成 %#x %#x, want 0x02 0x01", dst[2], dst[3])
}
}