package go_vorbis /* #cgo pkg-config: vorbisfile #include typedef size_t (*vorbisfile_read_func)(void *ptr, size_t size, size_t nmemb, void *datasource); typedef int (*vorbisfile_seek_func)(void *datasource, ogg_int64_t offset, int whence); typedef long (*vorbisfile_tell_func)(void *datasource); typedef int (*vorbisfile_close_func)(void *datasource); size_t cgoReadCallback(void *ptr, size_t size, size_t nmemb, void *datasource); int cgoSeekCallback(void *datasource, ogg_int64_t offset, int whence); long cgoTellCallback(void *datasource); int cgoCloseCallback(void *datasource); int handle_ov_open_callbacks(uintptr_t datasource, OggVorbis_File *vf, char *initial, long ibytes, ov_callbacks callbacks); */ import "C" import ( "errors" "io" "runtime/cgo" "unsafe" ) type Decoder struct { file *C.OggVorbis_File //allocated file memory mem []byte handle cgo.Handle reader io.Reader } func NewDecoder(reader io.Reader) (*Decoder, error) { d := &Decoder{ reader: reader, mem: make([]byte, unsafe.Sizeof(C.OggVorbis_File{})), } d.file = (*C.OggVorbis_File)(unsafe.Pointer(&d.mem[0])) d.handle = cgo.NewHandle(d) if ret := C.handle_ov_open_callbacks(C.uintptr_t(d.handle), d.file, nil, 0, C.ov_callbacks{ read_func: C.vorbisfile_read_func(C.cgoReadCallback), seek_func: nil, close_func: nil, tell_func: nil, }); ret != 0 { d.handle.Delete() switch ret { case C.OV_EREAD: return nil, errors.New("a read from media returned an error") case C.OV_ENOTVORBIS: return nil, errors.New("bitstream does not contain any Vorbis data") case C.OV_EVERSION: return nil, errors.New("vorbis version mismatch") case C.OV_EBADHEADER: return nil, errors.New("invalid Vorbis bitstream header") case C.OV_EFAULT: return nil, errors.New("internal logic fault") default: return nil, errors.New("unknown error") } } return d, nil } func NewSeekerDecoder(reader io.ReadSeeker) (*Decoder, error) { d := &Decoder{ reader: reader, mem: make([]byte, unsafe.Sizeof(C.OggVorbis_File{})), } d.file = (*C.OggVorbis_File)(unsafe.Pointer(&d.mem[0])) d.handle = cgo.NewHandle(d) if ret := C.handle_ov_open_callbacks(C.uintptr_t(d.handle), d.file, nil, 0, C.ov_callbacks{ read_func: C.vorbisfile_read_func(C.cgoReadCallback), seek_func: C.vorbisfile_seek_func(C.cgoSeekCallback), close_func: nil, tell_func: C.vorbisfile_tell_func(C.cgoTellCallback), }); ret != 0 { d.handle.Delete() switch ret { case C.OV_EREAD: return nil, errors.New("a read from media returned an error") case C.OV_ENOTVORBIS: return nil, errors.New("bitstream does not contain any Vorbis data") case C.OV_EVERSION: return nil, errors.New("vorbis version mismatch") case C.OV_EBADHEADER: return nil, errors.New("invalid Vorbis bitstream header") case C.OV_EFAULT: return nil, errors.New("internal logic fault") default: return nil, errors.New("unknown error") } } return d, nil } func NewSeekCloserDecoder(reader io.ReadSeekCloser) (*Decoder, error) { d := &Decoder{ reader: reader, mem: make([]byte, unsafe.Sizeof(C.OggVorbis_File{})), } d.file = (*C.OggVorbis_File)(unsafe.Pointer(&d.mem[0])) d.handle = cgo.NewHandle(d) if ret := C.handle_ov_open_callbacks(C.uintptr_t(d.handle), d.file, nil, 0, C.ov_callbacks{ read_func: C.vorbisfile_read_func(C.cgoReadCallback), seek_func: C.vorbisfile_seek_func(C.cgoSeekCallback), close_func: C.vorbisfile_close_func(C.cgoCloseCallback), tell_func: C.vorbisfile_tell_func(C.cgoTellCallback), }); ret != 0 { d.handle.Delete() switch ret { case C.OV_EREAD: return nil, errors.New("a read from media returned an error") case C.OV_ENOTVORBIS: return nil, errors.New("bitstream does not contain any Vorbis data") case C.OV_EVERSION: return nil, errors.New("vorbis version mismatch") case C.OV_EBADHEADER: return nil, errors.New("invalid Vorbis bitstream header") case C.OV_EFAULT: return nil, errors.New("internal logic fault") default: return nil, errors.New("unknown error") } } return d, nil } func (d *Decoder) ReadShort() ([]int16, int, error) { info := C.ov_info(d.file, -1) if info == nil { return nil, 0, errors.New("unknown error") } channelCount := int(info.channels) buf := make([]int16, 2048) var bitstream C.int ret := C.ov_read(d.file, (*C.char)(unsafe.Pointer(&buf[0])), C.int(len(buf)*2), C.int(0), C.int(2), C.int(1), &bitstream) switch ret { case C.OV_HOLE: return nil, channelCount, errors.New("OV_HOLE") case C.OV_EBADLINK: return nil, channelCount, errors.New("OV_EBADLINK") case C.OV_EINVAL: return nil, channelCount, errors.New("OV_EINVAL") case 0: return nil, channelCount, io.EOF default: return buf[:ret/2], channelCount, nil } } func (d *Decoder) ReadFloat() ([]float32, int, error) { info := C.ov_info(d.file, -1) if info == nil { return nil, 0, errors.New("unknown error") } channelCount := int(info.channels) var bitstream C.int var pcmChannels **C.float ret := C.ov_read_float(d.file, &pcmChannels, 1024, &bitstream) switch ret { case C.OV_HOLE: return nil, channelCount, errors.New("OV_HOLE") case C.OV_EBADLINK: return nil, channelCount, errors.New("OV_EBADLINK") case C.OV_EINVAL: return nil, channelCount, errors.New("OV_EINVAL") case 0: return nil, channelCount, io.EOF default: buf := make([]float32, int(ret)*channelCount) ptrSlice := unsafe.Slice(pcmChannels, channelCount) for ch, ptr := range ptrSlice { floatSlice := unsafe.Slice((*float32)(ptr), int(ret)) for i, sample := range floatSlice { buf[i*channelCount+ch] = sample } } return buf, channelCount, nil } } type VorbisInformation struct { Version int Channels int Rate int64 Bitrate struct { Upper int64 Nominal int64 Lower int64 Window int64 } } func (d *Decoder) Information() *VorbisInformation { info := C.ov_info(d.file, -1) if info == nil { return nil } return &VorbisInformation{ Version: int(info.version), Channels: int(info.channels), Rate: int64(info.rate), Bitrate: struct { Upper int64 Nominal int64 Lower int64 Window int64 }{ Upper: int64(info.bitrate_upper), Nominal: int64(info.bitrate_nominal), Lower: int64(info.bitrate_lower), Window: int64(info.bitrate_window), }, } } func (d *Decoder) Close() { if d.file != nil { defer d.handle.Delete() C.ov_clear(d.file) d.file = nil return } } //export cgoReadCallback func cgoReadCallback(ptr unsafe.Pointer, size C.size_t, nmemb C.size_t, datasource unsafe.Pointer) C.size_t { decoder := (cgo.Handle(datasource)).Value().(*Decoder) slice := unsafe.Slice((*byte)(ptr), size*nmemb) n, _ := io.ReadFull(decoder.reader, slice) return C.size_t(n) } //export cgoSeekCallback func cgoSeekCallback(datasource unsafe.Pointer, offset C.ogg_int64_t, whence C.int) C.int { decoder := (cgo.Handle(datasource)).Value().(*Decoder) _, err := decoder.reader.(io.Seeker).Seek(int64(offset), int(whence)) if err != nil { return -1 } return 0 } //export cgoTellCallback func cgoTellCallback(datasource unsafe.Pointer) C.long { decoder := (cgo.Handle(datasource)).Value().(*Decoder) n, err := decoder.reader.(io.Seeker).Seek(0, io.SeekCurrent) if err != nil { return -1 } return C.long(n) } //export cgoCloseCallback func cgoCloseCallback(datasource unsafe.Pointer) C.int { decoder := (cgo.Handle(datasource)).Value().(*Decoder) err := decoder.reader.(io.Closer).Close() if err != nil { return C.EOF } return 0 }