// Copyright © Go Opus Authors (see AUTHORS file) // // License for use of this code is detailed in the LICENSE file // +build !nolibopusfile package opus import ( "fmt" "io" "unsafe" ) /* #cgo pkg-config: opusfile #include #include #include OggOpusFile *my_open_callbacks(uintptr_t p, int *error); */ import "C" // Stream wraps a io.Reader in a decoding layer. It provides an API similar to // io.Reader, but it provides raw PCM data instead of the encoded Opus data. // // This is not the same as directly decoding the bytes on the io.Reader; opus // streams are Ogg Opus audio streams, which package raw Opus data. // // This wraps libopusfile. For more information, see the api docs on xiph.org: // // https://www.opus-codec.org/docs/opusfile_api-0.7/index.html type Stream struct { id uintptr oggfile *C.OggOpusFile read io.Reader // Preallocated buffer to pass to the reader buf []byte } var streams = newStreamsMap() //export go_readcallback func go_readcallback(p unsafe.Pointer, cbuf *C.uchar, cmaxbytes C.int) C.int { streamId := uintptr(p) stream := streams.Get(streamId) if stream == nil { // This is bad return -1 } 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]) // Go allows returning non-nil error (like EOF) and n>0, libopusfile doesn't // expect that. So return n first to indicate the valid bytes, let the // subsequent call (which will be n=0, same-error) handle the actual error. if n == 0 && err != nil { if err == io.EOF { return 0 } else { return -1 } } C.memcpy(unsafe.Pointer(cbuf), unsafe.Pointer(&stream.buf[0]), C.size_t(n)) return C.int(n) } // NewStream creates and initializes a new stream. Don't call .Init() on this. 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 must be non-nil") } s.read = read s.buf = make([]byte, maxEncodedFrameSize) s.id = streams.NextId() var errno C.int // Immediately delete the stream after .Init to avoid leaking if the // caller forgets to (/ doesn't want to) call .Close(). No need for that, // since the callback is only ever called during a .Read operation; just // Save and Delete from the map around that every time a reader function is // called. streams.Save(s) defer streams.Del(s) oggfile := C.my_open_callbacks(C.uintptr_t(s.id), &errno) if errno != 0 { return StreamError(errno) } s.oggfile = oggfile return nil } // Read a chunk of raw opus data from the stream and decode it. Returns the // number of decoded samples per channel. This means that a dual channel // (stereo) feed will have twice as many samples as the value returned. // // Read may successfully read less bytes than requested, but it will never read // exactly zero bytes succesfully if a non-zero buffer is supplied. // // The number of channels in the output data must be known in advance. It is // possible to extract this information from the stream itself, but I'm not // motivated to do that. Feel free to send a pull request. func (s *Stream) Read(pcm []int16) (int, error) { if s.oggfile == nil { return 0, fmt.Errorf("opus stream is uninitialized or already closed") } if len(pcm) == 0 { return 0, nil } streams.Save(s) defer streams.Del(s) n := C.op_read( s.oggfile, (*C.opus_int16)(&pcm[0]), C.int(len(pcm)), nil) if n < 0 { return 0, StreamError(n) } if n == 0 { return 0, io.EOF } return int(n), nil } // ReadFloat32 is the same as Read, but decodes to float32 instead of int16. func (s *Stream) ReadFloat32(pcm []float32) (int, error) { if s.oggfile == nil { return 0, fmt.Errorf("opus stream is uninitialized or already closed") } if len(pcm) == 0 { return 0, nil } streams.Save(s) defer streams.Del(s) n := C.op_read_float( s.oggfile, (*C.float)(&pcm[0]), C.int(len(pcm)), nil) if n < 0 { return 0, StreamError(n) } if n == 0 { return 0, io.EOF } return int(n), nil } func (s *Stream) Close() error { if s.oggfile == nil { return fmt.Errorf("opus stream is uninitialized or already closed") } C.op_free(s.oggfile) if closer, ok := s.read.(io.Closer); ok { return closer.Close() } return nil }