audio: add initial wav streamer implementation
This commit is contained in:
parent
eb429bea68
commit
a748a0cdce
|
@ -0,0 +1,123 @@
|
|||
package wav
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Streamer struct {
|
||||
rc io.ReadCloser
|
||||
h header
|
||||
pos int32
|
||||
err error
|
||||
}
|
||||
|
||||
func NewStreamer(rc io.ReadCloser) (*Streamer, error) {
|
||||
var (
|
||||
d Streamer
|
||||
err error
|
||||
)
|
||||
d.rc = rc
|
||||
d.h, err = readHeader(rc)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return nil, errors.Wrap(err, "wav")
|
||||
}
|
||||
if d.h.formatType != 1 {
|
||||
rc.Close()
|
||||
return nil, errors.New("wav: unsupported format type")
|
||||
}
|
||||
if d.h.numChans <= 0 {
|
||||
rc.Close()
|
||||
return nil, errors.New("wav: invalid number of channels (less than 1)")
|
||||
}
|
||||
if d.h.bitsPerSample != 8 && d.h.bitsPerSample != 16 {
|
||||
rc.Close()
|
||||
return nil, errors.New("wav: unsupported number of bits per sample, 8 or 16 are supported")
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (d *Streamer) Err() error {
|
||||
return d.err
|
||||
}
|
||||
|
||||
func (d *Streamer) Duration() time.Duration {
|
||||
numBytes := time.Duration(d.h.dataSize)
|
||||
perFrame := time.Duration(d.h.bytesPerFrame)
|
||||
sampRate := time.Duration(d.h.sampleRate)
|
||||
return numBytes / perFrame * time.Second / sampRate
|
||||
}
|
||||
|
||||
func (d *Streamer) Stream(samples [][2]float64) (n int, ok bool) {
|
||||
if d.pos >= d.h.dataSize {
|
||||
return 0, false
|
||||
}
|
||||
switch {
|
||||
case d.h.bitsPerSample == 8 && d.h.numChans == 1:
|
||||
width := 1
|
||||
p := make([]byte, len(samples)*width)
|
||||
n, err := d.rc.Read(p)
|
||||
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
|
||||
val := float64(p[i])/255*2 - 1
|
||||
samples[j][0] = val
|
||||
samples[j][1] = val
|
||||
}
|
||||
if err != nil {
|
||||
d.err = err
|
||||
}
|
||||
d.pos += int32(n)
|
||||
return n / width, true
|
||||
case d.h.bitsPerSample == 8 && d.h.numChans >= 2:
|
||||
width := int(d.h.numChans)
|
||||
p := make([]byte, len(samples)*width)
|
||||
n, err := d.rc.Read(p)
|
||||
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
|
||||
samples[j][0] = float64(p[i+0])/255*2 - 1
|
||||
samples[j][1] = float64(p[i+1])/255*2 - 1
|
||||
}
|
||||
if err != nil {
|
||||
d.err = err
|
||||
}
|
||||
d.pos += int32(n)
|
||||
return n / width, true
|
||||
case d.h.bitsPerSample == 16 && d.h.numChans == 1:
|
||||
width := 2
|
||||
p := make([]byte, len(samples)*width)
|
||||
n, err := d.rc.Read(p)
|
||||
for i, j := 0, 0; i < n-width; i, j = i+width, j+1 {
|
||||
val := float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 1)
|
||||
samples[j][0] = val
|
||||
samples[j][1] = val
|
||||
}
|
||||
if err != nil {
|
||||
d.err = err
|
||||
}
|
||||
d.pos += int32(n)
|
||||
return n / width, true
|
||||
case d.h.bitsPerSample == 16 && d.h.numChans >= 2:
|
||||
width := int(d.h.numChans) * 2
|
||||
p := make([]byte, len(samples)*width)
|
||||
n, err := d.rc.Read(p)
|
||||
for i, j := 0, 0; i <= n-width; i, j = i+width, j+1 {
|
||||
samples[j][0] = float64(int16(p[i+0])+int16(p[i+1])*(1<<8)) / (1<<15 - 1)
|
||||
samples[j][1] = float64(int16(p[i+2])+int16(p[i+3])*(1<<8)) / (1<<15 - 1)
|
||||
}
|
||||
if err != nil {
|
||||
d.err = err
|
||||
}
|
||||
d.pos += int32(n)
|
||||
return n / width, true
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (d *Streamer) Close() error {
|
||||
err := d.rc.Close()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "wav")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package wav
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
fileSize int32
|
||||
formatSize int32
|
||||
formatType int16
|
||||
numChans int16
|
||||
sampleRate int32
|
||||
byteRate int32
|
||||
bytesPerFrame int16
|
||||
bitsPerSample int16
|
||||
dataSize int32
|
||||
}
|
||||
|
||||
func readHeader(r io.Reader) (header, error) {
|
||||
var (
|
||||
h header
|
||||
er errReader
|
||||
)
|
||||
err := er.
|
||||
ReadString(r, "RIFF", errors.New("missing RIFF at the beginning")).
|
||||
ReadBinary(r, binary.LittleEndian, &h.fileSize).
|
||||
ReadString(r, "WAVE", errors.New("unsupported file type")).
|
||||
ReadString(r, "fmt\x00", errors.New("missing format chunk marker")).
|
||||
ReadBinary(r, binary.LittleEndian, &h.formatSize).
|
||||
ReadBinary(r, binary.LittleEndian, &h.formatType).
|
||||
ReadBinary(r, binary.LittleEndian, &h.numChans).
|
||||
ReadBinary(r, binary.LittleEndian, &h.sampleRate).
|
||||
ReadBinary(r, binary.LittleEndian, &h.byteRate).
|
||||
ReadBinary(r, binary.LittleEndian, &h.bytesPerFrame).
|
||||
ReadBinary(r, binary.LittleEndian, &h.bitsPerSample).
|
||||
ReadString(r, "data", errors.New("missing data chunk marker")).
|
||||
ReadBinary(r, binary.LittleEndian, &h.dataSize).
|
||||
Err()
|
||||
return h, err
|
||||
}
|
||||
|
||||
type errReader struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *errReader) ReadString(r io.Reader, s string, notThereErr error) *errReader {
|
||||
if e.err != nil {
|
||||
return e
|
||||
}
|
||||
buf := make([]byte, len(s))
|
||||
_, err := r.Read(buf)
|
||||
if err != nil {
|
||||
e.err = errors.Wrap(err, "error while reading header")
|
||||
return e
|
||||
}
|
||||
if string(buf) != s {
|
||||
e.err = errors.Wrap(err, "invalid header")
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *errReader) ReadBinary(r io.Reader, order binary.ByteOrder, data interface{}) *errReader {
|
||||
if e.err != nil {
|
||||
return e
|
||||
}
|
||||
err := binary.Read(r, order, data)
|
||||
if err != nil {
|
||||
e.err = errors.Wrap(err, "invalid header")
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *errReader) Err() error {
|
||||
return e.err
|
||||
}
|
Loading…
Reference in New Issue