package audio import ( "encoding/binary" "fmt" ) // pcmFormat beschreibt rohe PCM-Samples, wie sie oto zur Wiedergabe // braucht. type pcmFormat struct { SampleRate int Channels int BitsPerCoded int // 8 (unsigned), 16 (signed LE), 32 (nur IEEE float) Float bool } // decodeWAV zerlegt RIFF/WAVE-Bytes in Format und PCM-Daten. // // Unterstützt: PCM (AudioFormat 1) mit 8/16 Bit sowie IEEE-Float // (AudioFormat 3) mit 32 Bit; Mono und Stereo. Alles andere — erst recht // komprimierte Formate — ist ein klarer Fehler; die OS-TTS-Wege von // osbuiltin liefern ausschließlich 16-Bit-PCM-WAV. func decodeWAV(data []byte) (pcmFormat, []byte, error) { var f pcmFormat if len(data) < 12 { return f, nil, fmt.Errorf("wav: zu kurz für RIFF-Header (%d Bytes)", len(data)) } if string(data[0:4]) != "RIFF" || string(data[8:12]) != "WAVE" { return f, nil, fmt.Errorf("wav: kein RIFF/WAVE-Header") } var haveFmt bool var payload []byte pos := 12 for pos+8 <= len(data) { id := string(data[pos : pos+4]) size := int(binary.LittleEndian.Uint32(data[pos+4 : pos+8])) body := pos + 8 if body+size > len(data) { return f, nil, fmt.Errorf("wav: Chunk %q überragt die Datei (Size %d)", id, size) } switch id { case "fmt ": if size < 16 { return f, nil, fmt.Errorf("wav: fmt-Chunk zu kurz (%d)", size) } audioFormat := binary.LittleEndian.Uint16(data[body : body+2]) f.Channels = int(binary.LittleEndian.Uint16(data[body+2 : body+4])) f.SampleRate = int(binary.LittleEndian.Uint32(data[body+4 : body+8])) f.BitsPerCoded = int(binary.LittleEndian.Uint16(data[body+14 : body+16])) switch { case audioFormat == 1 && (f.BitsPerCoded == 8 || f.BitsPerCoded == 16): case audioFormat == 3 && f.BitsPerCoded == 32: f.Float = true default: return f, nil, fmt.Errorf("wav: nicht unterstütztes Format (AudioFormat %d, %d Bit) — erlaubt: PCM 8/16 Bit, Float 32 Bit", audioFormat, f.BitsPerCoded) } if f.Channels < 1 || f.Channels > 2 { return f, nil, fmt.Errorf("wav: %d Kanäle (erlaubt: 1, 2)", f.Channels) } if f.SampleRate <= 0 { return f, nil, fmt.Errorf("wav: SampleRate %d", f.SampleRate) } haveFmt = true case "data": payload = data[body : body+size] } pos = body + size if size%2 == 1 { // Chunks sind word-aligned pos++ } } if !haveFmt { return f, nil, fmt.Errorf("wav: kein fmt-Chunk") } if payload == nil { return f, nil, fmt.Errorf("wav: kein data-Chunk") } return f, payload, nil }