From c68d9d3bf5e827765b80da330dc7e41055725d16 Mon Sep 17 00:00:00 2001 From: vsariola Date: Thu, 31 Dec 2020 16:24:34 +0200 Subject: [PATCH] refactor(oto): reuse temp buffers for repeated calls to convert buffer --- oto/convertbuffer.go | 22 +++++++++------------- oto/convertbuffer_test.go | 10 ++-------- oto/oto.go | 16 ++++++++++------ 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/oto/convertbuffer.go b/oto/convertbuffer.go index 7a3ac88..9ac72c6 100644 --- a/oto/convertbuffer.go +++ b/oto/convertbuffer.go @@ -1,29 +1,25 @@ package oto import ( - "bytes" - "encoding/binary" - "fmt" "math" ) // FloatBufferTo16BitLE is a naive helper method to convert []float32 buffers to -// 16-bit little-endian integer buffers. -// TODO: optimize/refactor this, current is far from the best solution -func FloatBufferTo16BitLE(buff []float32) ([]byte, error) { - var buf bytes.Buffer - for i, v := range buff { +// 16-bit little-endian, but encoded in byte buffer +// +// Appends the encoded bytes into "to" slice, allowing you to preallocate the +// capacity or just use nil +func FloatBufferTo16BitLE(from []float32, to []byte) []byte { + for _, v := range from { var uv int16 if v < -1.0 { - uv = -math.MaxInt16 + uv = -math.MaxInt16 // we are a bit lazy: -1.0 is encoded as -32767, as this makes math easier, and -32768 is unused } else if v > 1.0 { uv = math.MaxInt16 } else { uv = int16(v * math.MaxInt16) } - if err := binary.Write(&buf, binary.LittleEndian, uv); err != nil { - return nil, fmt.Errorf("error converting buffer (@ %v, value %v) to bytes: %w", i, v, err) - } + to = append(to, byte(uv&255), byte(uv>>8)) } - return buf.Bytes(), nil + return to } diff --git a/oto/convertbuffer_test.go b/oto/convertbuffer_test.go index 7928ec6..4c5ac2c 100644 --- a/oto/convertbuffer_test.go +++ b/oto/convertbuffer_test.go @@ -10,10 +10,7 @@ import ( func TestFloatBufferToBytes(t *testing.T) { floats := []float32{0, 0.000489128, 0, 0.0019555532, 0, 0.0043964, 0, 0.007806882, 0, 0.012180306, 0, 0.017508084, 0, 0.023779746, 0, 0.030982954, 0, 0.039103523, 0, 0.04812544, 0, 0.05803088, 0, 0.068800256, 0, 0.08041221, 0, 0.09284368, 0, 0.10606992, 0, 0.120064534, 0, 0.13479951, 0, 0.1502453, 0, 0.16637078, 0, 0.18314338, 0, 0.20052913, 0, 0.21849263, 0, 0.23699719, 0, 0.2560048, 0, 0.27547634, 0, 0.29537144, 0, 0.31564865, 0, 0.33626547, 0, 0.35717854, 0, 0.37834346, 0, 0.39971504, 0, 0.4212474, 0, 0.4428938, 0, 0.46460703, 0, 0.48633927, 0, 0.50804216, 0, 0.52966696, 0, 0.5511646, 0, 0.57248586, 0, 0.5935812, 0, 0.6144009, 0, 0.63489544, 0, 0.6550152, 0, 0.67471063, 0, 0.6939326, 0, 0.712632, 0, 0.7307603, 0, 0.7482692, 0, 0.7651111, 0, 0.7812389} bytes := []byte{0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x8f, 0x1, 0x0, 0x0, 0x3d, 0x2, 0x0, 0x0, 0xb, 0x3, 0x0, 0x0, 0xf7, 0x3, 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x28, 0x6, 0x0, 0x0, 0x6d, 0x7, 0x0, 0x0, 0xce, 0x8, 0x0, 0x0, 0x4a, 0xa, 0x0, 0x0, 0xe2, 0xb, 0x0, 0x0, 0x93, 0xd, 0x0, 0x0, 0x5e, 0xf, 0x0, 0x0, 0x40, 0x11, 0x0, 0x0, 0x3b, 0x13, 0x0, 0x0, 0x4b, 0x15, 0x0, 0x0, 0x71, 0x17, 0x0, 0x0, 0xaa, 0x19, 0x0, 0x0, 0xf7, 0x1b, 0x0, 0x0, 0x55, 0x1e, 0x0, 0x0, 0xc4, 0x20, 0x0, 0x0, 0x42, 0x23, 0x0, 0x0, 0xce, 0x25, 0x0, 0x0, 0x66, 0x28, 0x0, 0x0, 0xa, 0x2b, 0x0, 0x0, 0xb7, 0x2d, 0x0, 0x0, 0x6d, 0x30, 0x0, 0x0, 0x29, 0x33, 0x0, 0x0, 0xeb, 0x35, 0x0, 0x0, 0xb0, 0x38, 0x0, 0x0, 0x77, 0x3b, 0x0, 0x0, 0x3f, 0x3e, 0x0, 0x0, 0x7, 0x41, 0x0, 0x0, 0xcb, 0x43, 0x0, 0x0, 0x8c, 0x46, 0x0, 0x0, 0x46, 0x49, 0x0, 0x0, 0xf9, 0x4b, 0x0, 0x0, 0xa4, 0x4e, 0x0, 0x0, 0x43, 0x51, 0x0, 0x0, 0xd6, 0x53, 0x0, 0x0, 0x5c, 0x56, 0x0, 0x0, 0xd2, 0x58, 0x0, 0x0, 0x36, 0x5b, 0x0, 0x0, 0x88, 0x5d, 0x0, 0x0, 0xc6, 0x5f, 0x0, 0x0, 0xee, 0x61, 0x0, 0x0, 0xfe, 0x63} - converted, err := oto.FloatBufferTo16BitLE(floats) - if err != nil { - t.Fatalf("FloatBufferTo16BitLE threw an error") - } + converted := oto.FloatBufferTo16BitLE(floats, nil) for i, v := range converted { if bytes[i] != v { t.Fail() @@ -34,10 +31,7 @@ func TestFloatBufferToBytesLimits(t *testing.T) { 0xDE, 0x7F, // float 0.999 = 0x7FDE = 0111111111011110 0x22, 0x80, // float -0.999 = 0x8022 = 1000000000100010 } - converted, err := oto.FloatBufferTo16BitLE(floats) - if err != nil { - t.Fatalf("FloatBufferTo16BitLE threw an error") - } + converted := oto.FloatBufferTo16BitLE(floats, nil) for i, v := range converted { if bytes[i] != v { t.Fail() diff --git a/oto/oto.go b/oto/oto.go index 5473d03..d4ccd29 100644 --- a/oto/oto.go +++ b/oto/oto.go @@ -8,10 +8,13 @@ import ( ) type OtoContext oto.Context -type OtoOutput oto.Player +type OtoOutput struct { + player *oto.Player + tmpBuffer []byte +} func (c *OtoContext) Output() sointu.AudioSink { - return (*OtoOutput)((*oto.Context)(c).NewPlayer()) + return &OtoOutput{player: (*oto.Context)(c).NewPlayer(), tmpBuffer: make([]byte, 0)} } const otoBufferSize = 8192 @@ -34,9 +37,10 @@ func (c *OtoContext) Close() error { // Play implements the audio.Player interface for OtoPlayer func (o *OtoOutput) WriteAudio(floatBuffer []float32) (err error) { - if byteBuffer, err := FloatBufferTo16BitLE(floatBuffer); err != nil { - return fmt.Errorf("cannot convert buffer to bytes: %w", err) - } else if _, err := (*oto.Player)(o).Write(byteBuffer); err != nil { + // we reuse the old capacity tmpBuffer by setting its length to zero. then, + // we save the tmpBuffer so we can reuse it next time + o.tmpBuffer = FloatBufferTo16BitLE(floatBuffer, o.tmpBuffer[:0]) + if _, err := o.player.Write(o.tmpBuffer); err != nil { return fmt.Errorf("cannot write to player: %w", err) } return nil @@ -44,7 +48,7 @@ func (o *OtoOutput) WriteAudio(floatBuffer []float32) (err error) { // Close disposes of resources func (o *OtoOutput) Close() error { - if err := (*oto.Player)(o).Close(); err != nil { + if err := o.player.Close(); err != nil { return fmt.Errorf("cannot close oto player: %w", err) } return nil