go-tta/encoder.go
DataHoarder 01ef1ed311
All checks were successful
continuous-integration/drone/push Build is passing
Updated URLs / CI
2022-02-26 14:45:39 +01:00

322 lines
6.9 KiB
Go

package tta
import (
"fmt"
"io"
"os"
"git.gammaspectra.live/S.O.N.G/go-tta/filter"
"git.gammaspectra.live/S.O.N.G/go-tta/wave"
)
type Encoder struct {
codecs [maxNCH]codec // 1 per channel
channels int // number of channels/codecs
data [8]byte // codec initialization data
fifo fifo
seekTable []uint64 // the playing position table
format uint32 // tta data format
rate uint32 // bitrate (kbps)
offset uint64 // data start position (header size, bytes)
frames uint32 // total count of frames
depth uint32 // bytes per sample
flenStd uint32 // default frame length in samples
flenLast uint32 // last frame length in samples
flen uint32 // current frame length in samples
fnum uint32 // currently playing frame index
fpos uint32 // the current position in frame
shiftBits uint32 // packing int to pcm
}
func Compress(infile io.ReadSeeker, outfile io.ReadWriteSeeker, passwd string, cb Callback) (err error) {
waveHdr, dataSize, err := wave.ReadHeader(infile)
if err != nil {
return
} else if dataSize >= 0x7FFFFFFF {
err = fmt.Errorf("incorrect data size info in wav file: %x", dataSize)
return
}
if !waveHdr.Validate(maxNCH, maxBPS) {
err = errFormat
return
}
encoder := MakeEncoder(outfile)
smpSize := uint32(waveHdr.NumChannels * ((waveHdr.BitsPerSample + 7) / 8))
info := Info{
Nch: uint32(waveHdr.NumChannels),
Bps: uint32(waveHdr.BitsPerSample),
Sps: waveHdr.SampleRate,
Format: formatSimple,
Samples: dataSize / smpSize,
}
if len(passwd) > 0 {
encoder.SetPassword(passwd)
info.Format = formatEncrypted
}
bufSize := pcmBufferLength * smpSize
buffer := make([]byte, bufSize)
if err = encoder.SetInfo(&info); err != nil {
return
}
var readLen int
for dataSize > 0 {
if bufSize >= dataSize {
bufSize = dataSize
}
if readLen, err = infile.Read(buffer[:bufSize]); err != nil || readLen != int(bufSize) {
err = errRead
return
}
encoder.ProcessStream(buffer[:bufSize], cb)
dataSize -= bufSize
}
encoder.Close()
return
}
func MakeEncoder(iocb io.ReadWriteSeeker) *Encoder {
enc := Encoder{}
enc.fifo.io = iocb
return &enc
}
func (e *Encoder) ProcessStream(in []byte, cb Callback) {
if len(in) == 0 {
return
}
var res, curr, next, tmp int32
next = readBuffer(in, e.depth)
in = in[e.depth:]
tmp = next << e.shiftBits
i := 0
index := 0
for {
curr = next
if index < len(in) {
next = readBuffer(in[index:], e.depth)
tmp = next << e.shiftBits
} else {
next = 0
tmp = 0
}
index += int(e.depth)
// transform data
if e.channels > 1 {
if i < e.channels-1 {
res = next - curr
curr = res
} else {
curr -= res / 2
}
}
// compress stage 1: fixed order 1 prediction
tmp = curr
curr -= ((e.codecs[i].prev * ((1 << 5) - 1)) >> 5)
e.codecs[i].prev = tmp
// compress stage 2: adaptive hybrid filter
e.codecs[i].filter.Encode(&curr)
e.fifo.putValue(&e.codecs[i].adapter, curr)
if i < e.channels-1 {
i++
} else {
i = 0
e.fpos++
}
if e.fpos == e.flen {
e.fifo.flushBitCache()
e.seekTable[e.fnum] = uint64(e.fifo.count)
e.fnum++
// update dynamic info
e.rate = (e.fifo.count << 3) / 1070
if cb != nil {
cb(e.rate, e.fnum, e.frames)
}
e.frameInit(e.fnum)
}
if index >= int(e.depth)+len(in) {
break
}
}
}
func (e *Encoder) ProcessFrame(in []byte) {
if len(in) == 0 {
return
}
var res, curr, next, tmp int32
next = readBuffer(in, e.depth)
in = in[e.depth:]
tmp = next << e.shiftBits
i := 0
index := 0
for {
curr = next
if index < len(in) {
next = readBuffer(in[index:], e.depth)
tmp = next << e.shiftBits
} else {
next = 0
tmp = 0
}
index += int(e.depth)
// transform data
if e.channels > 1 {
if i < e.channels-1 {
res = next - curr
curr = res
} else {
curr -= res / 2
}
}
// compress stage 1: fixed order 1 prediction
tmp = curr
curr -= ((e.codecs[i].prev * ((1 << 5) - 1)) >> 5)
e.codecs[i].prev = tmp
// compress stage 2: adaptive hybrid filter
e.codecs[i].filter.Encode(&curr)
e.fifo.putValue(&e.codecs[i].adapter, curr)
if i < e.channels-1 {
i++
} else {
i = 0
e.fpos++
}
if e.fpos == e.flen {
e.fifo.flushBitCache()
// update dynamic info
e.rate = (e.fifo.count << 3) / 1070
break
}
if index >= int(e.depth)+len(in) {
break
}
}
}
func (e *Encoder) writeSeekTable() (err error) {
if e.seekTable == nil {
return
}
if _, err = e.fifo.io.Seek(int64(e.offset), os.SEEK_SET); err != nil {
return
}
e.fifo.pos = 0
e.fifo.reset()
for i := uint32(0); i < e.frames; i++ {
e.fifo.writeUint32(uint32(e.seekTable[i] & 0xFFFFFFFF))
}
e.fifo.writeCrc32()
e.fifo.writeDone()
return
}
func (e *Encoder) SetPassword(pass string) {
e.data = computeKeyDigits(convertPassword(pass))
}
func (e *Encoder) frameInit(frame uint32) (err error) {
if frame >= e.frames {
return
}
shift := shifts[e.depth-1]
e.fnum = frame
if e.fnum == e.frames-1 {
e.flen = e.flenLast
} else {
e.flen = e.flenStd
}
// init entropy encoder
for i := 0; i < e.channels; i++ {
e.codecs[i].filter = filter.New(e.data, shift)
e.codecs[i].adapter.init(10, 10)
e.codecs[i].prev = 0
}
e.fpos = 0
e.fifo.reset()
return
}
func (e *Encoder) frameReset(frame uint32, iocb io.ReadWriteSeeker) {
e.fifo.io = iocb
e.fifo.readStart()
e.frameInit(frame)
}
func (e *Encoder) WriteHeader(info *Info) (size uint32, err error) {
e.fifo.reset()
// write TTA1 signature
if err = e.fifo.writeByte('T'); err != nil {
return
}
if err = e.fifo.writeByte('T'); err != nil {
return
}
if err = e.fifo.writeByte('A'); err != nil {
return
}
if err = e.fifo.writeByte('1'); err != nil {
return
}
if err = e.fifo.writeUint16(uint16(info.Format)); err != nil {
return
}
if err = e.fifo.writeUint16(uint16(info.Nch)); err != nil {
return
}
if err = e.fifo.writeUint16(uint16(info.Bps)); err != nil {
return
}
if err = e.fifo.writeUint32(info.Sps); err != nil {
return
}
if err = e.fifo.writeUint32(info.Samples); err != nil {
return
}
if err = e.fifo.writeCrc32(); err != nil {
return
}
size = 22
return
}
func (e *Encoder) SetInfo(info *Info) (err error) {
if info.Format > 2 ||
info.Bps < minBPS ||
info.Bps > maxBPS ||
info.Nch > maxNCH {
return errFormat
}
var p uint32
if p, err = e.WriteHeader(info); err != nil {
return
}
e.offset = uint64(p)
e.format = info.Format
e.depth = (info.Bps + 7) / 8
e.flenStd = (256 * (info.Sps) / 245)
e.flenLast = info.Samples % e.flenStd
e.frames = info.Samples / e.flenStd
if e.flenLast != 0 {
e.frames++
} else {
e.flenLast = e.flenStd
}
e.rate = 0
e.fifo.writeSkipBytes((e.frames + 1) * 4)
e.seekTable = make([]uint64, e.frames)
e.channels = int(info.Nch)
e.shiftBits = (4 - e.depth) << 3
e.frameInit(0)
return
}
func (e *Encoder) Close() {
e.Finalize()
}
func (e *Encoder) Finalize() {
e.fifo.writeDone()
e.writeSeekTable()
}