package go_vorbis /* #cgo pkg-config: vorbisenc #cgo pkg-config: vorbis #cgo pkg-config: ogg #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); */ import "C" import ( "errors" "io" "math/rand" "unsafe" ) var cStr = C.CString("ENCODER") type Encoder struct { vorbisInfo *C.vorbis_info //allocated vorbisInfo memory vorbisInfoMem []byte vorbisComment *C.vorbis_comment //allocated vorbisComment memory vorbisCommentMem []byte vorbisDspState *C.vorbis_dsp_state //allocated vorbisDspState memory vorbisDspStateMem []byte vorbisBlock *C.vorbis_block //allocated vorbisDspState memory vorbisBlockMem []byte oggPage *C.ogg_page //allocated oggPage memory oggPageMem []byte oggPacket *C.ogg_packet //allocated oggPage memory oggPacketMem []byte oggStreamState *C.ogg_stream_state //allocated oggStreamState memory oggStreamStateMem []byte channels int writer io.Writer } //NewEncoderCBR Creates a new encoder instance. maxBitRate, nominalBitRate, minBitrate can be set to -1 to let encoder pick. func NewEncoderCBR(writer io.Writer, channels, sampleRate int, maxBitRate, nominalBitRate, minBitrate int64) (*Encoder, error) { e := &Encoder{ writer: writer, channels: channels, } e.initStructs() if ret := C.vorbis_encode_init(e.vorbisInfo, C.long(channels), C.long(sampleRate), C.long(maxBitRate), C.long(nominalBitRate), C.long(minBitrate)); ret != 0 { switch ret { case C.OV_EFAULT: return nil, errors.New("OV_EFAULT") case C.OV_EINVAL: return nil, errors.New("OV_EINVAL") case C.OV_EIMPL: return nil, errors.New("OV_EIMPL") default: return nil, errors.New("unknown error") } } e.initStructs() e.start() return e, nil } //NewEncoderVBR Creates a new encoder instance. func NewEncoderVBR(writer io.Writer, channels, sampleRate int, quality float32) (*Encoder, error) { e := &Encoder{ writer: writer, channels: channels, } e.initStructs() if ret := C.vorbis_encode_init_vbr(e.vorbisInfo, C.long(channels), C.long(sampleRate), C.float(quality)); ret != 0 { switch ret { case C.OV_EFAULT: return nil, errors.New("OV_EFAULT") case C.OV_EINVAL: return nil, errors.New("OV_EINVAL") case C.OV_EIMPL: return nil, errors.New("OV_EIMPL") default: return nil, errors.New("unknown error") } } e.initStructs() e.start() return e, nil } func (e *Encoder) start() { C.vorbis_comment_add_tag(e.vorbisComment, cStr, C.vorbis_version_string()) var header, headerComm, headerCode C.ogg_packet C.vorbis_analysis_headerout(e.vorbisDspState, e.vorbisComment, &header, &headerComm, &headerCode) C.ogg_stream_packetin(e.oggStreamState, &header) C.ogg_stream_packetin(e.oggStreamState, &headerComm) C.ogg_stream_packetin(e.oggStreamState, &headerCode) for { if C.ogg_stream_flush(e.oggStreamState, e.oggPage) == 0 { break } e.pageWrite() } } func (e *Encoder) WriteFloat(buf []float32) error { if (len(buf) % e.channels) != 0 { return errors.New("non-divisible buffer length") } samples := len(buf) / e.channels target := C.vorbis_analysis_buffer(e.vorbisDspState, C.int(samples)) ptrSlice := unsafe.Slice(target, e.channels) for ch, ptr := range ptrSlice { floatSlice := unsafe.Slice((*float32)(ptr), samples) for i := range floatSlice { floatSlice[i] = buf[i*e.channels+ch] } } C.vorbis_analysis_wrote(e.vorbisDspState, C.int(samples)) e.process() return nil } func (e *Encoder) Flush() { C.vorbis_analysis_wrote(e.vorbisDspState, 0) e.process() } func (e *Encoder) process() { for C.vorbis_analysis_blockout(e.vorbisDspState, e.vorbisBlock) == 1 { C.vorbis_analysis(e.vorbisBlock, nil) C.vorbis_bitrate_addblock(e.vorbisBlock) for { ret := C.vorbis_bitrate_flushpacket(e.vorbisDspState, e.oggPacket) if ret == 0 { break } C.ogg_stream_packetin(e.oggStreamState, e.oggPacket) for { if C.ogg_stream_pageout(e.oggStreamState, e.oggPage) == 0 { break } e.pageWrite() if C.ogg_page_eos(e.oggPage) > 0 { return } } } } } func (e *Encoder) pageWrite() { e.writer.Write(unsafe.Slice((*byte)(unsafe.Pointer(e.oggPage.header)), int(e.oggPage.header_len))) e.writer.Write(unsafe.Slice((*byte)(unsafe.Pointer(e.oggPage.body)), int(e.oggPage.body_len))) } func (e *Encoder) initStructs() { if e.vorbisDspState == nil && e.vorbisInfo != nil { e.vorbisDspStateMem = make([]byte, unsafe.Sizeof(C.vorbis_dsp_state{})) e.vorbisDspState = (*C.vorbis_dsp_state)(unsafe.Pointer(&e.vorbisDspStateMem[0])) C.vorbis_analysis_init(e.vorbisDspState, e.vorbisInfo) } if e.vorbisBlock == nil && e.vorbisInfo != nil { e.vorbisBlockMem = make([]byte, unsafe.Sizeof(C.vorbis_block{})) e.vorbisBlock = (*C.vorbis_block)(unsafe.Pointer(&e.vorbisBlockMem[0])) C.vorbis_block_init(e.vorbisDspState, e.vorbisBlock) } if e.vorbisInfo == nil { e.vorbisInfoMem = make([]byte, unsafe.Sizeof(C.vorbis_info{})) e.vorbisInfo = (*C.vorbis_info)(unsafe.Pointer(&e.vorbisInfoMem[0])) C.vorbis_info_init(e.vorbisInfo) } if e.vorbisComment == nil { e.vorbisCommentMem = make([]byte, unsafe.Sizeof(C.vorbis_comment{})) e.vorbisComment = (*C.vorbis_comment)(unsafe.Pointer(&e.vorbisCommentMem[0])) C.vorbis_comment_init(e.vorbisComment) } if e.oggStreamState == nil { e.oggStreamStateMem = make([]byte, unsafe.Sizeof(C.ogg_stream_state{})) e.oggStreamState = (*C.ogg_stream_state)(unsafe.Pointer(&e.oggStreamStateMem[0])) C.ogg_stream_init(e.oggStreamState, C.int(rand.Int())) } if e.oggPage == nil { e.oggPageMem = make([]byte, unsafe.Sizeof(C.ogg_page{})) e.oggPage = (*C.ogg_page)(unsafe.Pointer(&e.oggPageMem[0])) } if e.oggPacket == nil { e.oggPacketMem = make([]byte, unsafe.Sizeof(C.ogg_packet{})) e.oggPacket = (*C.ogg_packet)(unsafe.Pointer(&e.oggPacketMem[0])) } } func (e *Encoder) Close() { if e.oggStreamState != nil { C.ogg_stream_clear(e.oggStreamState) e.oggStreamState = nil } if e.vorbisBlock != nil { C.vorbis_block_clear(e.vorbisBlock) e.vorbisBlock = nil } if e.vorbisDspState != nil { C.vorbis_dsp_clear(e.vorbisDspState) e.vorbisDspState = nil } if e.vorbisInfo != nil { C.vorbis_info_clear(e.vorbisInfo) e.vorbisInfo = nil } if e.vorbisComment != nil { C.vorbis_comment_clear(e.vorbisComment) e.vorbisComment = nil } if e.vorbisInfo != nil { C.vorbis_info_clear(e.vorbisInfo) e.vorbisInfo = nil } }