87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
//go:build windows
|
|
|
|
package audio
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
// The DVK/recorder pipeline uses a single fixed PCM format end-to-end: 16 kHz
|
|
// mono 16-bit. That's plenty for SSB voice (3 kHz audio bandwidth), keeps files
|
|
// tiny (~32 KB/s), and — fed through WASAPI's AUTOCONVERTPCM — plays/records on
|
|
// any device regardless of its native mix format.
|
|
const (
|
|
sampleRate = 16000
|
|
channels = 1
|
|
bitsPerSample = 16
|
|
blockAlign = channels * bitsPerSample / 8 // bytes per frame (=2)
|
|
bytesPerSec = sampleRate * blockAlign // =32000
|
|
)
|
|
|
|
// writeWAV writes 16-bit PCM as a canonical RIFF/WAVE file.
|
|
func writeWAV(path string, pcm []byte) error {
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
dataLen := len(pcm)
|
|
put := func(v any) { _ = binary.Write(f, binary.LittleEndian, v) }
|
|
f.WriteString("RIFF")
|
|
put(uint32(36 + dataLen))
|
|
f.WriteString("WAVE")
|
|
f.WriteString("fmt ")
|
|
put(uint32(16)) // PCM fmt chunk size
|
|
put(uint16(1)) // WAVE_FORMAT_PCM
|
|
put(uint16(channels)) //
|
|
put(uint32(sampleRate)) //
|
|
put(uint32(bytesPerSec)) // byte rate
|
|
put(uint16(blockAlign)) //
|
|
put(uint16(bitsPerSample)) //
|
|
f.WriteString("data")
|
|
put(uint32(dataLen))
|
|
_, err = f.Write(pcm)
|
|
return err
|
|
}
|
|
|
|
// readWAV reads a PCM WAV and returns the raw sample bytes plus its format.
|
|
// Handles arbitrary chunk ordering (walks the RIFF chunk list).
|
|
func readWAV(path string) (pcm []byte, rate, ch, bits int, err error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, 0, 0, 0, err
|
|
}
|
|
if len(b) < 12 || string(b[0:4]) != "RIFF" || string(b[8:12]) != "WAVE" {
|
|
return nil, 0, 0, 0, fmt.Errorf("not a WAVE file")
|
|
}
|
|
i := 12
|
|
for i+8 <= len(b) {
|
|
id := string(b[i : i+4])
|
|
size := int(binary.LittleEndian.Uint32(b[i+4 : i+8]))
|
|
body := i + 8
|
|
if body+size > len(b) {
|
|
size = len(b) - body
|
|
}
|
|
switch id {
|
|
case "fmt ":
|
|
if size >= 16 {
|
|
ch = int(binary.LittleEndian.Uint16(b[body+2 : body+4]))
|
|
rate = int(binary.LittleEndian.Uint32(b[body+4 : body+8]))
|
|
bits = int(binary.LittleEndian.Uint16(b[body+14 : body+16]))
|
|
}
|
|
case "data":
|
|
pcm = b[body : body+size]
|
|
}
|
|
i = body + size
|
|
if size%2 == 1 {
|
|
i++ // chunks are word-aligned
|
|
}
|
|
}
|
|
if pcm == nil || rate == 0 {
|
|
return nil, 0, 0, 0, fmt.Errorf("WAV missing fmt/data")
|
|
}
|
|
return pcm, rate, ch, bits, nil
|
|
}
|