commit - d0a2114cd0f9dda0212b0195364e81fb5e69a68d
commit + a0d3d6fb41f760d18ebc49d3ff47c5a97954f90c
blob - f1b47d3c3c7c83af3ccdd5e90da8ee7d827d04ef
blob + 21cda9fcfa3140b729d6912115dfa50edf958cbc
--- wav/wav.go
+++ wav/wav.go
-// Package wav provides an encoder and decoder of WAVE data.
-// https://en.wikipedia.org/wiki/WAV
+// Package wav implements access to WAV (or WAVE) files.
+// WAVE is a file format for storing digitised audio,
+// most commonly as raw PCM signals.
+//
+// See also https://en.wikipedia.org/wiki/WAV
package wav
import (
+ "bytes"
"encoding/binary"
"fmt"
"io"
Bitstream io.Reader
}
+const (
+ headerLength = 44
+ extensionLength = 24
+)
+
+const (
+ AudioFormatPCMInteger uint16 = 1
+ AudioFormatFloat uint16 = 3
+ AudioFormatExtensible uint16 = 0xfffe
+)
+
+var (
+ riffID = [4]byte{'R', 'I', 'F', 'F'}
+ waveID = [4]byte{'W', 'A', 'V', 'E'}
+)
+
+var formatChunkID = [4]byte{'f', 'm', 't', ' '}
+
+var dataChunkID = [4]byte{'d', 'a', 't', 'a'}
+
type Header struct {
FileSize uint32
- BlocSize uint32
AudioFormat uint16
ChannelCount uint16
Frequency uint32
BytesPerSecond uint32
BytesPerBloc uint16
BitsPerSample uint16
+ Extension *FormatExtension
DataSize uint32
}
+func ReadFile(r io.Reader) (*File, error) {
+ h, err := readHeader(r)
+ if err != nil {
+ return nil, fmt.Errorf("read header: %w", err)
+ }
+ return &File{
+ Header: Header{
+ h.File.Length,
+ h.Format.AudioFormat,
+ h.Format.ChannelCount,
+ h.Format.Frequency,
+ h.Format.BytesPerSecond,
+ h.Format.BytesPerBloc,
+ h.Format.BitsPerSample,
+ h.FormatExtension,
+ h.Data.Length,
+ },
+ Bitstream: r,
+ }, nil
+}
+
+func EncodeHeader(hdr Header) []byte {
+ h := header{
+ File: fileChunk{riffID, hdr.FileSize, waveID},
+ Format: formatChunk{
+ formatChunkID,
+ formatChunkLength,
+ hdr.AudioFormat,
+ hdr.ChannelCount,
+ hdr.Frequency,
+ hdr.BytesPerSecond,
+ hdr.BytesPerBloc,
+ hdr.BitsPerSample,
+ },
+ FormatExtension: hdr.Extension,
+ Data: dataChunk{dataChunkID, hdr.DataSize},
+ }
+ if h.Format.AudioFormat == AudioFormatExtensible {
+ h.Format.Length = extendedFormatChunkLength
+ }
+
+ bcap := headerLength
+ if h.Format.AudioFormat == AudioFormatExtensible {
+ bcap += extensionLength
+ }
+ buf := bytes.NewBuffer(make([]byte, 0, bcap))
+ binary.Write(buf, binary.LittleEndian, h.File)
+ binary.Write(buf, binary.LittleEndian, h.Format)
+ if h.FormatExtension != nil {
+ binary.Write(buf, binary.LittleEndian, h.FormatExtension)
+ }
+ binary.Write(buf, binary.LittleEndian, h.Data)
+ return buf.Bytes()
+}
+
type header struct {
- FileBlocID [4]byte
- FileSize uint32
- FileFormatID [4]byte
+ File fileChunk
+ Format formatChunk
+ // Extension holds optional extra audio format information for
+ FormatExtension *FormatExtension
+ Data dataChunk
+}
- FormatBlocID [4]byte
- BlocSize uint32
+type fileChunk struct {
+ ID [4]byte
+ Length uint32
+ FormatID [4]byte
+}
+
+const (
+ formatChunkLength = 16
+ extendedFormatChunkLength = formatChunkLength + extensionLength
+)
+
+type formatChunk struct {
+ ID [4]byte
+ Length uint32
AudioFormat uint16
ChannelCount uint16
Frequency uint32
BytesPerSecond uint32
BytesPerBloc uint16
BitsPerSample uint16
+}
- DataBlocID [4]byte
- DataSize uint32
+type dataChunk struct {
+ ID [4]byte
+ Length uint32
}
-var (
- fileBlocID = [4]byte{'R', 'I', 'F', 'F'}
- fileFormatID = [4]byte{'W', 'A', 'V', 'E'}
-)
+type FormatExtension struct {
+ Length uint16
+ ValidBits uint16
+ ChannelMask uint32
+ SubFormat [16]byte
+}
-var formatBlocID = [4]byte{'f', 'm', 't', ' '}
-
-var dataBlocID = [4]byte{'d', 'a', 't', 'a'}
-
func readHeader(rd io.Reader) (*header, error) {
- var h header
- if err := binary.Read(rd, binary.LittleEndian, &h); err != nil {
- return nil, err
+ var head header
+ var fchunk fileChunk
+ if err := binary.Read(rd, binary.LittleEndian, &fchunk); err != nil {
+ return nil, fmt.Errorf("read file chunk: %w", err)
}
- if h.FileBlocID != fileBlocID {
- return nil, fmt.Errorf("bad file block id %x", h.FileBlocID)
- } else if h.FileFormatID != fileFormatID {
- return nil, fmt.Errorf("bad file format id %x", h.FileFormatID)
- } else if h.FormatBlocID != formatBlocID {
- return nil, fmt.Errorf("bad format block id %x", h.FileFormatID)
- } else if h.DataBlocID != dataBlocID {
- return nil, fmt.Errorf("bad data block id %x", h.DataBlocID)
+ if fchunk.ID != riffID {
+ return nil, fmt.Errorf("bad RIFF id %x", fchunk.ID)
+ } else if fchunk.FormatID != waveID {
+ return nil, fmt.Errorf("bad WAVE file format id %x", fchunk.FormatID)
}
- return &h, nil
-}
+ head.File = fchunk
-func EncodeHeader(hdr Header) [44]byte {
- var buf [44]byte
- h := header{
- fileBlocID,
- hdr.FileSize,
- fileFormatID,
- formatBlocID,
- hdr.BlocSize,
- hdr.AudioFormat,
- hdr.ChannelCount,
- hdr.Frequency,
- hdr.BytesPerSecond,
- hdr.BytesPerBloc,
- hdr.BitsPerSample,
- dataBlocID,
- hdr.DataSize,
+ var fmtchunk formatChunk
+ if err := binary.Read(rd, binary.LittleEndian, &fmtchunk); err != nil {
+ return nil, fmt.Errorf("read file chunk: %w", err)
}
- binary.Encode(buf[:], binary.LittleEndian, h)
- return buf
-}
+ if fmtchunk.ID != formatChunkID {
+ return nil, fmt.Errorf("bad format chunk id %x", fmtchunk.ID)
+ }
+ head.Format = fmtchunk
-func ReadFile(r io.Reader) (*File, error) {
- h, err := readHeader(r)
- if err != nil {
- return nil, fmt.Errorf("read header: %w", err)
+ if fmtchunk.AudioFormat == AudioFormatExtensible {
+ var ext FormatExtension
+ if err := binary.Read(rd, binary.LittleEndian, &ext); err != nil {
+ return nil, fmt.Errorf("read format chunk extension: %w", err)
+ }
+ head.FormatExtension = &ext
}
- return &File{
- Header: Header{
- h.FileSize,
- h.BlocSize,
- h.AudioFormat,
- h.ChannelCount,
- h.Frequency,
- h.BytesPerSecond,
- h.BytesPerBloc,
- h.BitsPerSample,
- h.DataSize,
- },
- Bitstream: r,
- }, nil
+
+ var data dataChunk
+ if err := binary.Read(rd, binary.LittleEndian, &data); err != nil {
+ return nil, fmt.Errorf("read data chunk: %w", err)
+ }
+ head.Data = data
+ return &head, nil
}
blob - 17d0e50710ffa13942ad76429c8fcc586e3d87e7
blob + acca55fc18743d5daf5d4b94da5f4f90ca2e2f74
--- wav/wav_test.go
+++ wav/wav_test.go
import (
"io"
"os"
+ "reflect"
"testing"
)
func TestDecodeEncode(t *testing.T) {
- var source [44]byte
+ source := make([]byte, headerLength+extensionLength)
f, err := os.Open("test.wav")
if err != nil {
t.Fatal(err)
t.Fatal(err)
}
header := EncodeHeader(file.Header)
- if source != header {
+ if !reflect.DeepEqual(source, header) {
t.Errorf("encode header: want %v, got %v", source, header)
}
}