From 764c755dd0493853994fb98fe87fa7aedde9ad6f Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 2 Apr 2016 21:56:09 +0900 Subject: [PATCH] audio/vorbis: Avoid copying by using C.short values directly --- exp/audio/vorbis/decode.go | 25 +++++++++-- exp/audio/vorbis/stream.go | 2 + exp/audio/vorbis/vorbis.go | 85 ++++++++++++++++++++++++++++---------- 3 files changed, 88 insertions(+), 24 deletions(-) diff --git a/exp/audio/vorbis/decode.go b/exp/audio/vorbis/decode.go index 99f843696..72ab9e950 100644 --- a/exp/audio/vorbis/decode.go +++ b/exp/audio/vorbis/decode.go @@ -17,15 +17,34 @@ package vorbis import ( - "bytes" "errors" "fmt" "github.com/hajimehoshi/ebiten/exp/audio" ) -// TODO: src should be ReadCloser? +type Stream struct { + decoded *decoded +} +func (s *Stream) Read(p []byte) (int, error) { + return s.decoded.Read(p) +} + +func (s *Stream) Seek(offset int64, whence int) (int64, error) { + return s.decoded.Seek(offset, whence) +} + +func (s *Stream) Close() error { + return s.decoded.Close() +} + +// TODO: Should be int? +func (s *Stream) Size() int64 { + return s.decoded.Size() +} + +// TODO: src should be ReadCloser? func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) { decoded, channelNum, sampleRate, err := decode(src) if err != nil { @@ -39,7 +58,7 @@ func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) { return nil, fmt.Errorf("vorbis: sample rate must be %d but %d", context.SampleRate(), sampleRate) } s := &Stream{ - buf: bytes.NewReader(decoded), + decoded: decoded, } return s, nil } diff --git a/exp/audio/vorbis/stream.go b/exp/audio/vorbis/stream.go index ff3abd3e1..7b0a1d074 100644 --- a/exp/audio/vorbis/stream.go +++ b/exp/audio/vorbis/stream.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// +build js + package vorbis import ( diff --git a/exp/audio/vorbis/vorbis.go b/exp/audio/vorbis/vorbis.go index 5c5a62e72..daeaf60b9 100644 --- a/exp/audio/vorbis/vorbis.go +++ b/exp/audio/vorbis/vorbis.go @@ -21,7 +21,7 @@ import ( "io" "io/ioutil" "math" - "reflect" + "runtime" "unsafe" ) @@ -5488,7 +5488,9 @@ int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in #endif // STB_VORBIS_HEADER_ONLY -static inline short shortPIndex(short* s, int i) { return s[i]; } +static inline short shortValue(short* data, int i) { + return data[i]; +} */ import "C" @@ -5537,18 +5539,63 @@ func go_assert(x C.int, sentence *C.char) { panic(fmt.Sprintf("go-vorbis: assertion error: %s", str)) } -func cFloatsToSlice(p *C.float, n int) []float32 { - s := reflect.SliceHeader{ - Data: uintptr(unsafe.Pointer(p)), - Len: n, - Cap: n, +type decoded struct { + data *C.short + sampleNum int + posInBytes int + bytesPerSample int +} + +func (d *decoded) at(offset int) C.short { + // return *(*C.short)(unsafe.Pointer(uintptr(unsafe.Pointer(d.data)) + uintptr(offset))) + return C.shortValue(d.data, C.int(offset)) +} + +func (d *decoded) Read(b []byte) (int, error) { + l := d.sampleNum*d.bytesPerSample - d.posInBytes + if l > len(b) { + l = len(b) } - return *(*[]float32)(unsafe.Pointer(&s)) + // l must be even so that d.posInBytes is always even. + l = l / 2 * 2 + for i := 0; i < l/2; i++ { + s := d.at(d.posInBytes/2 + i) + b[2*i] = byte(s) + b[2*i+1] = byte(s >> 8) + } + d.posInBytes += l + if d.posInBytes == d.sampleNum*d.bytesPerSample { + return l, io.EOF + } + return l, nil +} + +func (d *decoded) Seek(offset int64, whence int) (int64, error) { + next := int64(0) + switch whence { + case 0: + next = offset + case 1: + next = int64(d.posInBytes) + offset + case 2: + next = int64(d.sampleNum*d.bytesPerSample) + offset + } + d.posInBytes = int(next) + return next, nil +} + +func (d *decoded) Close() error { + runtime.SetFinalizer(d, nil) + C.free(unsafe.Pointer(d.data)) + return nil +} + +func (d *decoded) Size() int64 { + return int64(d.sampleNum * d.bytesPerSample) } // decode accepts an ogg stream and returns a decorded stream. -// The decorded format is 1 or 2-channel interleaved littleendian int16 values. -func decode(in io.ReadCloser) ([]byte, int, int, error) { +func decode(in io.ReadCloser) (*decoded, int, int, error) { mem, err := ioutil.ReadAll(in) if err != nil { return nil, 0, 0, err @@ -5560,16 +5607,12 @@ func decode(in io.ReadCloser) ([]byte, int, int, error) { sampleRate := C.int(0) output := (*C.short)(nil) sampleNum := C.stb_vorbis_decode_memory((*C.uchar)(&mem[0]), C.int(len(mem)), &channelNum, &sampleRate, &output) - defer C.free(unsafe.Pointer(output)) - b := make([]byte, sampleNum*4) - // What if we don't have to copy to []byte and use C.short directly? - for i := 0; i < int(sampleNum); i++ { - l := C.shortPIndex(output, C.int(2*i)) - r := C.shortPIndex(output, C.int(2*i+1)) - b[4*i] = byte(l) - b[4*i+1] = byte(l >> 8) - b[4*i+2] = byte(r) - b[4*i+3] = byte(r >> 8) + d := &decoded{ + data: output, + sampleNum: int(sampleNum), + posInBytes: 0, + bytesPerSample: 4, } - return b, int(channelNum), int(sampleRate), nil + runtime.SetFinalizer(d, (*decoded).Close) + return d, int(channelNum), int(sampleRate), nil }