]> Sergey Matveev's repositories - go-opus.git/commitdiff
(untested) libopusfile simple decoder API
authorHraban Luyat <hraban@0brg.net>
Sun, 5 Jul 2015 19:28:08 +0000 (20:28 +0100)
committerHraban Luyat <hraban@0brg.net>
Sun, 5 Jul 2015 19:28:08 +0000 (20:28 +0100)
callback_proxy.c [new file with mode: 0644]
callback_proxy.h [new file with mode: 0644]
encoder.go
errors.go [new file with mode: 0644]
opus.go
stream.go [new file with mode: 0644]

diff --git a/callback_proxy.c b/callback_proxy.c
new file mode 100644 (file)
index 0000000..682debf
--- /dev/null
@@ -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 (file)
index 0000000..f8456c8
--- /dev/null
@@ -0,0 +1,3 @@
+#pragma once
+
+int c_readproxy(void *p, unsigned char *buf, int nbytes);
index 54383730ee8fbf620ee68f119565e9ca9c8be384..700d51563c7d2900b6f44b1918ef1d28a820b575 100644 (file)
@@ -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 (file)
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 <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))))
+}
diff --git a/opus.go b/opus.go
index 518399ddcdaccdb2cc3f7c5d059a23951be906ce..47caf59c74f8dbc90da8d99f7473610a653d135f 100644 (file)
--- 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 <opus/opus.h>
 */
@@ -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 (file)
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 <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
+}