184 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			184 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 <opusfile.h>
 | |
| #include <stdint.h>
 | |
| #include <string.h>
 | |
| 
 | |
| 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
 | |
| }
 |