DataHoarder
01ef1ed311
All checks were successful
continuous-integration/drone/push Build is passing
322 lines
7.3 KiB
Go
322 lines
7.3 KiB
Go
package tta
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
|
|
"git.gammaspectra.live/S.O.N.G/go-tta/filter"
|
|
"git.gammaspectra.live/S.O.N.G/go-tta/wave"
|
|
)
|
|
|
|
type Decoder struct {
|
|
codecs [maxNCH]codec // 1 per channel
|
|
channels int // number of channels/codecs
|
|
data [8]byte // codec initialization data
|
|
fifo fifo
|
|
passwordSet bool // password protection flag
|
|
seekAllowed bool // seek table flag
|
|
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
|
|
}
|
|
|
|
func Decompress(infile io.ReadWriteSeeker, outfile io.WriteSeeker, passwd string, cb Callback) (err error) {
|
|
decoder := MakeDecoder(infile)
|
|
if len(passwd) > 0 {
|
|
decoder.SetPassword(passwd)
|
|
}
|
|
info := Info{}
|
|
if err = decoder.GetInfo(&info, 0); err != nil {
|
|
return
|
|
}
|
|
smpSize := info.Nch * ((info.Bps + 7) / 8)
|
|
dataSize := info.Samples * smpSize
|
|
waveHdr := wave.NewHeader(dataSize, uint16(info.Nch), info.Sps, uint16(info.Bps), uint16(smpSize))
|
|
if _, err = waveHdr.WriteTo(outfile); err != nil {
|
|
return
|
|
}
|
|
bufSize := pcmBufferLength * smpSize
|
|
buffer := make([]byte, bufSize)
|
|
var writeLen int
|
|
for {
|
|
if writeLen = int(uint32(decoder.ProcessStream(buffer, cb)) * smpSize); writeLen == 0 {
|
|
break
|
|
}
|
|
buf := buffer[:writeLen]
|
|
if writeLen, err = outfile.Write(buf); err != nil {
|
|
return
|
|
} else if writeLen != len(buf) {
|
|
err = errPartialWritten
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func MakeDecoder(iocb io.ReadWriteSeeker) *Decoder {
|
|
dec := Decoder{}
|
|
dec.fifo.io = iocb
|
|
return &dec
|
|
}
|
|
|
|
func (d *Decoder) ProcessStream(out []byte, cb Callback) int32 {
|
|
var cache [maxNCH]int32
|
|
var value int32
|
|
var ret int32
|
|
i := 0
|
|
outClone := out[:]
|
|
for d.fpos < d.flen && len(outClone) > 0 {
|
|
value = d.fifo.getValue(&d.codecs[i].adapter)
|
|
// decompress stage 1: adaptive hybrid filter
|
|
d.codecs[i].filter.Decode(&value)
|
|
// decompress stage 2: fixed order 1 prediction
|
|
value += ((d.codecs[i].prev * ((1 << 5) - 1)) >> 5)
|
|
d.codecs[i].prev = value
|
|
cache[i] = value
|
|
if i < d.channels-1 {
|
|
i++
|
|
} else {
|
|
if d.channels == 1 {
|
|
writeBuffer(value, outClone, d.depth)
|
|
outClone = outClone[d.depth:]
|
|
} else {
|
|
k := i - 1
|
|
cache[i] += cache[k] / 2
|
|
for k > 0 {
|
|
cache[k] = cache[k+1] - cache[k]
|
|
k--
|
|
}
|
|
cache[k] = cache[k+1] - cache[k]
|
|
for k <= i {
|
|
writeBuffer(cache[k], outClone, d.depth)
|
|
outClone = outClone[d.depth:]
|
|
k++
|
|
}
|
|
}
|
|
i = 0
|
|
d.fpos++
|
|
ret++
|
|
}
|
|
if d.fpos == d.flen {
|
|
// check frame crc
|
|
crcFlag := !d.fifo.readCrc32()
|
|
if crcFlag {
|
|
for i := 0; i < len(out); i++ {
|
|
out[i] = 0
|
|
}
|
|
if !d.seekAllowed {
|
|
break
|
|
}
|
|
}
|
|
d.fnum++
|
|
|
|
// update dynamic info
|
|
d.rate = (d.fifo.count << 3) / 1070
|
|
if cb != nil {
|
|
cb(d.rate, d.fnum, d.frames)
|
|
}
|
|
if d.fnum == d.frames {
|
|
break
|
|
}
|
|
d.frameInit(d.fnum, crcFlag)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (d *Decoder) ProcessFrame(inSize uint32, out []byte) int32 {
|
|
i := 0
|
|
var cache [maxNCH]int32
|
|
var value int32
|
|
var ret int32
|
|
outClone := out[:]
|
|
for d.fifo.count < inSize && len(outClone) > 0 {
|
|
value = d.fifo.getValue(&d.codecs[i].adapter)
|
|
// decompress stage 1: adaptive hybrid filter
|
|
d.codecs[i].filter.Decode(&value)
|
|
// decompress stage 2: fixed order 1 prediction
|
|
value += ((d.codecs[i].prev * ((1 << 5) - 1)) >> 5)
|
|
d.codecs[i].prev = value
|
|
cache[i] = value
|
|
if i < d.channels-1 {
|
|
i++
|
|
} else {
|
|
if d.channels == 1 {
|
|
writeBuffer(value, outClone, d.depth)
|
|
outClone = outClone[d.depth:]
|
|
} else {
|
|
j := i
|
|
k := i - 1
|
|
cache[i] += cache[k] / 2
|
|
for k > 0 {
|
|
cache[k] = cache[j] - cache[k]
|
|
j--
|
|
k--
|
|
}
|
|
cache[k] = cache[j] - cache[k]
|
|
for k <= i {
|
|
writeBuffer(cache[k], outClone, d.depth)
|
|
outClone = outClone[d.depth:]
|
|
k++
|
|
}
|
|
}
|
|
i = 0
|
|
d.fpos++
|
|
ret++
|
|
}
|
|
|
|
if d.fpos == d.flen || d.fifo.count == inSize-4 {
|
|
// check frame crc
|
|
if !d.fifo.readCrc32() {
|
|
for i := 0; i < len(out); i++ {
|
|
out[i] = 0
|
|
}
|
|
}
|
|
// update dynamic info
|
|
d.rate = (d.fifo.count << 3) / 1070
|
|
break
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (d *Decoder) readSeekTable() bool {
|
|
if d.seekTable == nil {
|
|
return false
|
|
}
|
|
d.fifo.reset()
|
|
tmp := d.offset + uint64(d.frames+1)*4
|
|
for i := uint32(0); i < d.frames; i++ {
|
|
d.seekTable[i] = tmp
|
|
tmp += uint64(d.fifo.readUint32())
|
|
}
|
|
return d.fifo.readCrc32()
|
|
}
|
|
|
|
func (d *Decoder) SetPassword(pass string) {
|
|
d.data = computeKeyDigits(convertPassword(pass))
|
|
d.passwordSet = true
|
|
}
|
|
|
|
func (d *Decoder) frameInit(frame uint32, seekNeeded bool) (err error) {
|
|
if frame >= d.frames {
|
|
return
|
|
}
|
|
shift := shifts[d.depth-1]
|
|
d.fnum = frame
|
|
if seekNeeded && d.seekAllowed {
|
|
pos := d.seekTable[d.fnum]
|
|
if pos != 0 {
|
|
if _, err = d.fifo.io.Seek(int64(pos), os.SEEK_SET); err != nil {
|
|
return errSeek
|
|
}
|
|
}
|
|
d.fifo.readStart()
|
|
}
|
|
if d.fnum == d.frames-1 {
|
|
d.flen = d.flenLast
|
|
} else {
|
|
d.flen = d.flenStd
|
|
}
|
|
for i := 0; i < d.channels; i++ {
|
|
d.codecs[i].filter = filter.New(d.data, shift)
|
|
d.codecs[i].adapter.init(10, 10)
|
|
d.codecs[i].prev = 0
|
|
}
|
|
d.fpos = 0
|
|
d.fifo.reset()
|
|
return
|
|
}
|
|
|
|
func (d *Decoder) frameReset(frame uint32, iocb io.ReadWriteSeeker) {
|
|
d.fifo.io = iocb
|
|
d.fifo.readStart()
|
|
d.frameInit(frame, false)
|
|
}
|
|
|
|
func (d *Decoder) SetPosition(seconds uint32) (newPos uint32, err error) {
|
|
var frame = (245 * (seconds) / 256)
|
|
newPos = (256 * (frame) / 245)
|
|
if !d.seekAllowed || frame >= d.frames {
|
|
err = errSeek
|
|
return
|
|
}
|
|
d.frameInit(frame, true)
|
|
return
|
|
}
|
|
|
|
func (d *Decoder) ReadHeader(info *Info) (uint32, error) {
|
|
size := d.fifo.skipId3v2()
|
|
d.fifo.reset()
|
|
if 'T' != d.fifo.readByte() ||
|
|
'T' != d.fifo.readByte() ||
|
|
'A' != d.fifo.readByte() ||
|
|
'1' != d.fifo.readByte() {
|
|
return 0, errFormat
|
|
}
|
|
info.Format = uint32(d.fifo.readUint16())
|
|
info.Nch = uint32(d.fifo.readUint16())
|
|
info.Bps = uint32(d.fifo.readUint16())
|
|
info.Sps = d.fifo.readUint32()
|
|
info.Samples = d.fifo.readUint32()
|
|
if !d.fifo.readCrc32() {
|
|
return 0, errFile
|
|
}
|
|
size += 22
|
|
return size, nil
|
|
}
|
|
|
|
func (d *Decoder) GetInfo(info *Info, pos int64) (err error) {
|
|
if pos != 0 {
|
|
if _, err = d.fifo.io.Seek(pos, os.SEEK_SET); err != nil {
|
|
err = errSeek
|
|
return
|
|
}
|
|
}
|
|
d.fifo.readStart()
|
|
var p uint32
|
|
if p, err = d.ReadHeader(info); err != nil {
|
|
return
|
|
}
|
|
if info.Format > 2 ||
|
|
info.Bps < minBPS ||
|
|
info.Bps > maxBPS ||
|
|
info.Nch > maxNCH {
|
|
return errFormat
|
|
}
|
|
if info.Format == formatEncrypted {
|
|
if !d.passwordSet {
|
|
return errPassword
|
|
}
|
|
} else {
|
|
// disregard password if file is not encrypted
|
|
d.passwordSet = false
|
|
d.data = [8]byte{}
|
|
}
|
|
d.offset = uint64(pos) + uint64(p)
|
|
d.format = info.Format
|
|
d.depth = (info.Bps + 7) / 8
|
|
d.flenStd = (256 * (info.Sps) / 245)
|
|
d.flenLast = info.Samples % d.flenStd
|
|
d.frames = info.Samples / d.flenStd
|
|
if d.flenLast != 0 {
|
|
d.frames++
|
|
} else {
|
|
d.flenLast = d.flenStd
|
|
}
|
|
d.rate = 0
|
|
d.seekTable = make([]uint64, d.frames)
|
|
d.seekAllowed = d.readSeekTable()
|
|
d.channels = int(info.Nch)
|
|
d.frameInit(0, false)
|
|
return
|
|
}
|