Added CBR / VBR encoder

This commit is contained in:
DataHoarder 2022-07-28 14:31:28 +02:00
parent af4ded4c0a
commit 1413086e90
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
4 changed files with 378 additions and 5 deletions

View file

@ -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
}

View file

@ -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
View 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
View 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()
}