// Copyright © Go Opus Authors (see AUTHORS file) // // License for use of this code is detailed in the LICENSE file //go:build !nolibopusfile // +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 } const ChannelCountMax = int(C.OPUS_CHANNEL_COUNT_MAX) // Information a mapped OpusHead struct, see https://opus-codec.org/docs/opusfile_api-0.5/structOpusHead.html type Information struct { Version int ChannelCount int PreSkip uint InputSampleRate uint OutputGain int MappingFamily int StreamCount int CoupledCount int Mapping [ChannelCountMax]uint8 } //Info Returns the information of the current link func (s *Stream) Info() *Information { if s.oggfile == nil { return nil } head := C.op_head(s.oggfile, C.int(-1)) if head == nil { return nil } info := &Information{ Version: int(head.version), ChannelCount: int(head.channel_count), PreSkip: uint(head.pre_skip), InputSampleRate: uint(head.input_sample_rate), OutputGain: int(head.output_gain), MappingFamily: int(head.mapping_family), StreamCount: int(head.stream_count), CoupledCount: int(head.coupled_count), } copy(info.Mapping[:], unsafe.Slice((*uint8)(&head.mapping[0]), ChannelCountMax)) return info } //Channels Returns the channel count of the current link func (s *Stream) Channels() int { if s.oggfile == nil { return 0 } return int(C.op_channel_count(s.oggfile, C.int(-1))) } // 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 } // ReadStereo is the same as Read, but decodes to always two channels func (s *Stream) ReadStereo(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_stereo( s.oggfile, (*C.opus_int16)(&pcm[0]), C.int(len(pcm))) if n < 0 { return 0, StreamError(n) } if n == 0 { return 0, io.EOF } return int(n), nil } // ReadStereoFloat32 is the same as ReadStereo, but decodes to float32 instead of int16. func (s *Stream) ReadStereoFloat32(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_stereo( s.oggfile, (*C.float)(&pcm[0]), C.int(len(pcm))) 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 }