audio/vorbis: Avoid copying by using C.short values directly

This commit is contained in:
Hajime Hoshi 2016-04-02 21:56:09 +09:00
parent d5ac3f831d
commit 764c755dd0
3 changed files with 88 additions and 24 deletions

View File

@ -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
}

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build js
package vorbis
import (

View File

@ -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
}