Added CBR / VBR encoder
This commit is contained in:
parent
af4ded4c0a
commit
1413086e90
|
@ -188,11 +188,6 @@ func (d *Decoder) ReadFloat() ([]float32, int, error) {
|
|||
buf[i*channelCount+ch] = sample
|
||||
}
|
||||
}
|
||||
for i := 0; i < int(ret); i++ {
|
||||
for ch := 0; ch < channelCount; ch++ {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return buf, channelCount, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func TestDecoder(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, err := os.Open("test.vorbis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
@ -43,6 +44,7 @@ func TestDecoder(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDecoderFloat(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, err := os.Open("test.vorbis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
265
encoder.go
Normal file
265
encoder.go
Normal file
|
@ -0,0 +1,265 @@
|
|||
package go_vorbis
|
||||
|
||||
/*
|
||||
#cgo pkg-config: vorbisenc
|
||||
#cgo pkg-config: vorbis
|
||||
#cgo pkg-config: ogg
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
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
|
||||
}
|
||||
}
|
111
encoder_test.go
Normal file
111
encoder_test.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package go_vorbis
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncoderCBR(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, err := os.Open("test.vorbis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
decoder, err := NewSeekCloserDecoder(f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer decoder.Close()
|
||||
|
||||
target, err := os.CreateTemp("/tmp", "encode_test_*.vorbis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
name := target.Name()
|
||||
target.Close()
|
||||
os.Remove(name)
|
||||
}()
|
||||
|
||||
encoder, err := NewEncoderCBR(target, decoder.Information().Channels, int(decoder.Information().Rate), -1, 128*1000, -1)
|
||||
defer encoder.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
buf, _, err := decoder.ReadFloat()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err = encoder.WriteFloat(buf); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
encoder.Flush()
|
||||
}
|
||||
|
||||
func TestEncoderVBR(t *testing.T) {
|
||||
t.Parallel()
|
||||
f, err := os.Open("test.vorbis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
decoder, err := NewSeekCloserDecoder(f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer decoder.Close()
|
||||
|
||||
target, err := os.CreateTemp("/tmp", "encode_test_*.vorbis")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
name := target.Name()
|
||||
target.Close()
|
||||
os.Remove(name)
|
||||
}()
|
||||
|
||||
encoder, err := NewEncoderVBR(target, decoder.Information().Channels, int(decoder.Information().Rate), 0.5)
|
||||
defer encoder.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
buf, _, err := decoder.ReadFloat()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err = encoder.WriteFloat(buf); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
encoder.Flush()
|
||||
}
|
Loading…
Reference in a new issue