From: Hraban Luyat Date: Sun, 5 Jul 2015 19:28:08 +0000 (+0100) Subject: (untested) libopusfile simple decoder API X-Git-Tag: v2.0.0~100 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=b5680242529cb4530789b6397c9f6625efd4102f;p=go-opus.git (untested) libopusfile simple decoder API --- diff --git a/callback_proxy.c b/callback_proxy.c new file mode 100644 index 0000000..682debf --- /dev/null +++ b/callback_proxy.c @@ -0,0 +1,13 @@ +/* + * Named proxy callback function, see + * + * https://code.google.com/p/go-wiki/wiki/cgo + */ + +int go_readproxy(void *p, unsigned char *buf, int nbytes); + +int +c_readproxy(void *p, unsigned char *buf, int nbytes) +{ + return go_readproxy(p, buf, nbytes); +} diff --git a/callback_proxy.h b/callback_proxy.h new file mode 100644 index 0000000..f8456c8 --- /dev/null +++ b/callback_proxy.h @@ -0,0 +1,3 @@ +#pragma once + +int c_readproxy(void *p, unsigned char *buf, int nbytes); diff --git a/encoder.go b/encoder.go index 5438373..700d515 100644 --- a/encoder.go +++ b/encoder.go @@ -67,8 +67,7 @@ func (enc *Encoder) Encode(pcm []int16) ([]byte, error) { if pcm == nil || len(pcm) == 0 { return nil, fmt.Errorf("opus: no data supplied") } - // I never know how much to allocate - data := make([]byte, 10000) + data := make([]byte, maxEncodedFrameSize) n := int(C.opus_encode( enc.p, (*C.opus_int16)(&pcm[0]), @@ -88,7 +87,7 @@ func (enc *Encoder) EncodeFloat32(pcm []float32) ([]byte, error) { if pcm == nil || len(pcm) == 0 { return nil, fmt.Errorf("opus: no data supplied") } - data := make([]byte, 10000) + data := make([]byte, maxEncodedFrameSize) n := int(C.opus_encode_float( enc.p, (*C.float)(&pcm[0]), diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..3ddfab3 --- /dev/null +++ b/errors.go @@ -0,0 +1,45 @@ +package opus + +import ( + "errors" + "fmt" +) + +/* +#cgo CFLAGS: -std=c99 -Wall -Werror -pedantic -Ibuild/include +#include +*/ +import "C" + +var opusfileErrcodes = map[C.int]error{ + -1: errors.New("OP_FALSE"), + -2: errors.New("OP_EOF"), + -3: errors.New("OP_HOLE"), + -128: errors.New("OP_EREAD"), + -129: errors.New("OP_EFAULT"), + -130: errors.New("OP_EIMPL"), + -131: errors.New("OP_EINVAL"), + -132: errors.New("OP_ENOTFORMAT"), + -133: errors.New("OP_EBADHEADER"), + -134: errors.New("OP_EVERSION"), + -135: errors.New("OP_ENOTAUDIO"), + -136: errors.New("OP_EBADPACKET"), + -137: errors.New("OP_EBADLINK"), + -138: errors.New("OP_ENOSEEK"), + -139: errors.New("OP_EBADTIMESTAMP"), +} + +// opusfileerr maps libopusfile error codes to human readable strings +func opusfileerr(code C.int) error { + err, ok := opusfileErrcodes[code] + if ok { + return err + } + return fmt.Errorf("libopus error: %d (unknown code)", int(code)) +} + +// opuserr translates libopus (not libopusfile) error codes to human readable +// strings +func opuserr(code int) error { + return fmt.Errorf("opus: %s", C.GoString(C.opus_strerror(C.int(code)))) +} diff --git a/opus.go b/opus.go index 518399d..47caf59 100644 --- a/opus.go +++ b/opus.go @@ -4,10 +4,6 @@ package opus -import ( - "fmt" -) - /* // Statically link libopus. Requires a libopus.a in every directory you use this // as a dependency in. Not great, but CGO doesn't offer anything better, right @@ -18,7 +14,7 @@ import ( // not the user. // // If I missed something, and somebody knows a better way: please let me know. -#cgo LDFLAGS: libopus.a -lm +#cgo LDFLAGS: libopusfile.a libopus.a -logg -lm #cgo CFLAGS: -std=c99 -Wall -Werror -pedantic -Ibuild/include #include */ @@ -41,12 +37,11 @@ const ( xMAX_BITRATE = 48000 xMAX_FRAME_SIZE_MS = 60 xMAX_FRAME_SIZE = xMAX_BITRATE * xMAX_FRAME_SIZE_MS / 1000 + // Maximum size of an encoded frame. I actually have no idea, but this + // looks like it's big enough. + maxEncodedFrameSize = 10000 ) func Version() string { return C.GoString(C.opus_get_version_string()) } - -func opuserr(code int) error { - return fmt.Errorf("opus: %s", C.GoString(C.opus_strerror(C.int(code)))) -} diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..31f8dae --- /dev/null +++ b/stream.go @@ -0,0 +1,94 @@ +package opus + +import ( + "fmt" + "io" + "unsafe" +) + +/* +#cgo CFLAGS: -std=c99 -Wall -Werror -pedantic -Ibuild/include/opus +#include +#include +#include "callback_proxy.h" +*/ +import "C" + +type Stream struct { + oggfile *C.OggOpusFile + read io.Reader + // Preallocated buffer to pass to the reader + buf []byte +} + +//export go_readproxy +func go_readproxy(p unsafe.Pointer, cbuf *C.uchar, cmaxbytes C.int) C.int { + stream := (*Stream)(p) + maxbytes := int(cmaxbytes) + if maxbytes > cap(stream.buf) { + maxbytes = cap(stream.buf) + } + // Don't bother cleaning up old data because that's not required by the + // io.Reader API. + n, err := stream.read.Read(stream.buf[:maxbytes]) + if err != nil || n == 0 { + return 0 + } + C.memcpy(unsafe.Pointer(cbuf), unsafe.Pointer(&stream.buf[0]), C.size_t(n)) + return C.int(n) +} + +var callbacks = C.struct_OpusFileCallbacks{ + read: C.op_read_func(C.c_readproxy), + seek: nil, + tell: nil, + close: nil, +} + +func NewStream(read io.Reader) (*Stream, error) { + var s Stream + err := s.Init(read) + if err != nil { + return nil, err + } + return &s, nil +} + +// Init initializes a stream with an io.Reader to fetch opus encoded data from +// on demand. Errors from the reader are all transformed to an EOF, any actual +// error information is lost. The same happens when a read returns succesfully, +// but with zero bytes. +func (s *Stream) Init(read io.Reader) error { + if s.oggfile != nil { + return fmt.Errorf("opus stream is already initialized") + } + if read == nil { + return fmt.Errorf("Reader function must be non-nil") + } + var errno C.int + oggfile := C.op_open_callbacks( + unsafe.Pointer(s), + &callbacks, + nil, + 0, + &errno) + if errno != 0 { + return opusfileerr(errno) + } + s.oggfile = oggfile + s.buf = make([]byte, maxEncodedFrameSize) + return nil +} + +func (s *Stream) Read() ([]int16, error) { + pcm := make([]int16, xMAX_FRAME_SIZE) + n := C.op_read( + s.oggfile, + (*C.opus_int16)(&pcm[0]), + C.int(cap(pcm)), + nil) + if n < 0 { + return nil, opusfileerr(n) + } + return pcm[:n], nil +}