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]) } }