--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#pragma once
+
+int c_readproxy(void *p, unsigned char *buf, int nbytes);
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]),
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]),
--- /dev/null
+package opus
+
+import (
+ "errors"
+ "fmt"
+)
+
+/*
+#cgo CFLAGS: -std=c99 -Wall -Werror -pedantic -Ibuild/include
+#include <opus/opus.h>
+*/
+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))))
+}
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
// 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 <opus/opus.h>
*/
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))))
-}
--- /dev/null
+package opus
+
+import (
+ "fmt"
+ "io"
+ "unsafe"
+)
+
+/*
+#cgo CFLAGS: -std=c99 -Wall -Werror -pedantic -Ibuild/include/opus
+#include <opusfile.h>
+#include <string.h>
+#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
+}