LNXSDK/Kha/Sources/kha/audio2/ogg/vorbis/VorbisDecodeState.hx
2025-01-22 16:18:30 +01:00

858 lines
27 KiB
Haxe

package kha.audio2.ogg.vorbis;
import haxe.ds.Vector;
import haxe.Int64;
import haxe.io.Bytes;
import haxe.io.Eof;
import haxe.io.Input;
import haxe.io.Output;
import kha.audio2.ogg.tools.Crc32;
import kha.audio2.ogg.tools.MathTools;
import kha.audio2.ogg.vorbis.data.Codebook;
import kha.audio2.ogg.vorbis.data.Floor.Floor1;
import kha.audio2.ogg.vorbis.data.Header;
import kha.audio2.ogg.vorbis.data.Mode;
import kha.audio2.ogg.vorbis.data.Page;
import kha.audio2.ogg.vorbis.data.ProbedPage;
import kha.audio2.ogg.vorbis.data.ReaderError;
import kha.audio2.ogg.vorbis.data.Page;
import kha.audio2.ogg.vorbis.data.Residue;
import kha.audio2.ogg.vorbis.data.Setting;
import kha.audio2.ogg.vorbis.VorbisDecoder.DecodeInitialResult;
/**
* ...
* @author shohei909
*/
class VorbisDecodeState
{
public static inline var INVALID_BITS = -1;
public var page(default, null):Page;
public var eof(default, null):Bool;
public var pFirst(default, null):ProbedPage;
public var pLast(default, null):ProbedPage;
public var validBits(default, null):Int = 0;
public var inputPosition(default, null):Int;
public var input(default, null):Input;
public var discardSamplesDeferred:Int;
public var segments(default, null):Vector<Int>;
public var bytesInSeg:Int = 0; // uint8
// decode buffer
public var channelBuffers:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
public var channelBufferStart:Int;
public var channelBufferEnd:Int;
public var currentSample(default, null):Int;
public var previousWindow:Vector<Vector<Float>>; //var *[STB_VORBIS_MAX_CHANNELS];
public var previousLength:Int;
public var finalY:Vector<Array<Int>>; // [STB_VORBIS_MAX_CHANNELS];
var firstDecode:Bool = false;
var nextSeg:Int = 0;
var acc:UInt;
var lastSeg:Bool; // flag that we're on the last decodeState
var lastSegWhich:Int; // what was the decodeState number of the l1ast seg?
var endSegWithKnownLoc:Int;
var knownLocForPacket:Int;
var error:ReaderError;
var currentLoc:Int; //uint32 sample location of next frame to decode
var currentLocValid:Int;
var firstAudioPageOffset:UInt;
public function new(input:Input)
{
this.input = input;
inputPosition = 0;
page = new Page();
Crc32.init();
}
public function setup(loc0:Int, loc1:Int) {
var segmentCount = readByte();
this.segments = read(segmentCount);
// assume we Don't_ know any the sample position of any segments
this.endSegWithKnownLoc = -2;
if (loc0 != 0xFFFFFFFF || loc1 != 0xFFFFFFFF) {
var i:Int = segmentCount - 1;
while (i >= 0) {
if (segments.get(i) < 255) {
break;
}
if (i >= 0) {
this.endSegWithKnownLoc = i;
this.knownLocForPacket = loc0;
}
i--;
}
}
if (firstDecode) {
var i:Int = 0;
var len:Int = 0;
var p = new ProbedPage();
for (i in 0...segmentCount) {
len += segments.get(i);
}
len += 27 + segmentCount;
p.pageStart = firstAudioPageOffset;
p.pageEnd = p.pageStart + len;
p.firstDecodedSample = 0;
p.lastDecodedSample = loc0;
pFirst = p;
}
nextSeg = 0;
}
public function clone(seekFunc:Int->Void)
{
var state = Type.createEmptyInstance(VorbisDecodeState);
seekFunc(inputPosition);
state.input = input;
// primitive
state.eof = eof;
state.validBits = validBits;
state.discardSamplesDeferred = discardSamplesDeferred;
state.firstDecode = firstDecode;
state.nextSeg = nextSeg;
state.bytesInSeg = bytesInSeg;
state.acc = state.acc;
state.lastSeg = lastSeg;
state.lastSegWhich = lastSegWhich;
state.currentLoc = currentLoc;
state.currentLocValid = currentLocValid;
state.inputPosition = inputPosition;
state.firstAudioPageOffset = firstAudioPageOffset;
// sharrow copy
state.error = error;
state.segments = segments;
state.pFirst = pFirst;
state.pLast = pLast;
// deep copy
state.page = page.clone();
return state;
}
// nextSegment
public function next():Int {
if (lastSeg) {
return 0;
}
if (nextSeg == -1) {
lastSegWhich = segments.length - 1; // in case startPage fails
try {
page.start(this);
} catch(e:ReaderError) {
lastSeg = true;
error = e;
return 0;
}
if ((page.flag & PageFlag.CONTINUED_PACKET) == 0) {
throw new ReaderError(ReaderErrorType.CONTINUED_PACKET_FLAG_INVALID);
}
}
var len = segments.get(nextSeg++);
if (len < 255) {
lastSeg = true;
lastSegWhich = nextSeg - 1;
}
if (nextSeg >= segments.length) {
nextSeg = -1;
}
VorbisTools.assert(bytesInSeg == 0);
bytesInSeg = len;
return len;
}
public function startPacket() {
while (nextSeg == -1) {
page.start(this);
if ((page.flag & PageFlag.CONTINUED_PACKET) != 0) {
throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
}
}
lastSeg = false;
validBits = 0;
bytesInSeg = 0;
}
public function maybeStartPacket():Bool
{
if (nextSeg == -1) {
var eof = false;
var x = try {
readByte();
} catch (e:Eof) {
eof = true;
0;
}
if (eof) {
return false; // EOF at page boundary is not an error!
}
if (x != 0x4f || readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
}
page.startWithoutCapturePattern(this);
}
startPacket();
return true;
}
// public inline function readBits(n:Int):Int
public function readBits(n:Int):Int // Kha: reduce output size
{
if (validBits < 0) {
return 0;
} else if (validBits < n) {
if (n > 24) {
// the accumulator technique below would not work correctly in this case
return readBits(24) + ((readBits(n - 24) << 24));
} else {
if (validBits == 0) {
acc = 0;
}
do {
if (bytesInSeg == 0 && (lastSeg || next() == 0)) {
validBits = INVALID_BITS;
break;
} else {
bytesInSeg--;
acc += (readByte() << validBits);
validBits += 8;
}
} while (validBits < n);
if (validBits < 0) {
return 0;
} else {
var z = acc & ((1 << n) - 1);
acc >>>= n;
validBits -= n;
return z;
}
}
} else {
var z = acc & ((1 << n) - 1);
acc >>>= n;
validBits -= n;
return z;
}
}
inline function readPacketRaw():Int {
return if (bytesInSeg == 0 && (lastSeg || next() == 0)) { // CLANG!
VorbisTools.EOP;
} else {
//VorbisTools.assert(bytesInSeg > 0);
bytesInSeg--;
readByte();
}
}
public inline function readPacket():Int
{
var x = readPacketRaw();
validBits = 0;
return x;
}
public inline function flushPacket():Void {
while (bytesInSeg != 0 || (!lastSeg && next() != 0)) {
bytesInSeg--;
readByte();
}
}
public inline function vorbisValidate() {
var header = Bytes.alloc(6);
for (i in 0...6) {
header.set(i, readPacket());
}
if (header.toString() != "vorbis") {
throw new ReaderError(ReaderErrorType.INVALID_SETUP, "vorbis header");
}
}
public function firstPageValidate()
{
if (segments.length != 1) {
throw new ReaderError(INVALID_FIRST_PAGE, "segmentCount");
}
if (segments.get(0) != 30) {
throw new ReaderError(INVALID_FIRST_PAGE, "decodeState head");
}
}
public function startFirstDecode()
{
firstAudioPageOffset = inputPosition;
firstDecode = true;
}
public inline function capturePattern()
{
if (readByte() != 0x4f || readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
throw new ReaderError(ReaderErrorType.MISSING_CAPTURE_PATTERN);
}
}
inline function skip(len:Int)
{
read(len);
}
function prepHuffman()
{
if (validBits <= 24) {
if (validBits == 0) {
acc = 0;
}
do {
if (bytesInSeg == 0 && (lastSeg || next() == 0)) { // CLANG!
return;
} else {
bytesInSeg--;
acc += readByte() << validBits;
validBits += 8;
}
} while (validBits <= 24);
}
}
public inline function decode(c:Codebook):Int {
var val = decodeRaw(c);
if (c.sparse) {
val = c.sortedValues[val];
}
return val;
}
public inline function decodeRaw(c:Codebook)
{
if (validBits < Setting.FAST_HUFFMAN_LENGTH){
prepHuffman();
}
// fast huffman table lookup
var i = c.fastHuffman[acc & Setting.FAST_HUFFMAN_TABLE_MASK];
return if (i >= 0) {
var l = c.codewordLengths[i];
acc >>>= l;
validBits -= l;
if (validBits < 0) {
validBits = 0;
-1;
} else {
i;
}
} else {
decodeScalarRaw(c);
}
}
public inline function isLastByte()
{
return bytesInSeg == 0 && lastSeg;
}
public function finishDecodePacket(previousLength:Int, n:Int, r:DecodeInitialResult)
{
var left = r.left.start;
var currentLocValid = false;
var n2 = n >> 1;
if (firstDecode) {
// assume we start so first non-discarded sample is sample 0
// this isn't to spec, but spec would require us to read ahead
// and decode the size of all current frames--could be done,
// but presumably it's not a commonly used feature
currentLoc = -n2; // start of first frame is positioned for discard
// we might have to discard samples "from" the next frame too,
// if we're lapping a large block then a small at the start?
discardSamplesDeferred = n - r.right.end;
currentLocValid = true;
firstDecode = false;
} else if (discardSamplesDeferred != 0) {
r.left.start += discardSamplesDeferred;
left = r.left.start;
discardSamplesDeferred = 0;
} else if (previousLength == 0 && currentLocValid) {
// we're recovering from a seek... that means we're going to discard
// the samples from this packet even though we know our position from
// the last page header, so we need to update the position based on
// the discarded samples here
// but wait, the code below is going to add this in itself even
// on a discard, so we don't need to do it here...
}
// check if we have ogg information about the sample # for this packet
if (lastSegWhich == endSegWithKnownLoc) {
// if we have a valid current loc, and this is final:
if (currentLocValid && (page.flag & PageFlag.LAST_PAGE) != 0) {
var currentEnd = knownLocForPacket - (n - r.right.end);
// then let's infer the size of the (probably) short final frame
if (currentEnd < currentLoc + r.right.end) {
var len = if (currentEnd < currentLoc) {
// negative truncation, that's impossible!
0;
} else {
currentEnd - currentLoc;
}
len += r.left.start;
currentLoc += len;
return {
len : len,
left : left,
right : r.right.start,
}
}
}
// otherwise, just set our sample loc
// guess that the ogg granule pos refers to the Middle_ of the
// last frame?
// set currentLoc to the position of leftStart
currentLoc = knownLocForPacket - (n2-r.left.start);
currentLocValid = true;
}
if (currentLocValid) {
currentLoc += (r.right.start - r.left.start);
}
// if (alloc.allocBuffer)
//assert(alloc.allocBufferLengthInBytes == tempOffset);
return {
len : r.right.end,
left : left,
right : r.right.start,
}
}
public inline function readInt32():Int
{
inputPosition += 4;
return input.readInt32();
}
public inline function readByte():Int
{
inputPosition += 1;
return input.readByte();
}
public inline function read(n:Int):Vector<Int> {
inputPosition += n;
var vec = new Vector(n);
for (i in 0...n) {
vec[i] = input.readByte();
}
return vec;
}
public inline function readBytes(n:Int):Bytes {
inputPosition += n;
return input.read(n);
}
public inline function readString(n:Int):String
{
inputPosition += n;
return input.readString(n);
}
public function getSampleNumber(seekFunc:Int->Void, inputLength:UInt):Int {
// first, store the current decode position so we can restore it
var restoreOffset = inputPosition;
// now we want to seek back 64K from the end (the last page must
// be at most a little less than 64K, but let's allow a little slop)
var previousSafe = if (inputLength >= 65536 && inputLength - 65536 >= firstAudioPageOffset) {
inputLength - 65536;
} else {
firstAudioPageOffset;
}
setInputOffset(seekFunc, previousSafe);
// previousSafe is now our candidate 'earliest known place that seeking
// to will lead to the final page'
var end = 0;
var last = false;
switch (findPage(seekFunc, inputLength)) {
case Found(e, l):
end = e;
last = l;
case NotFound:
throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
}
// check if there are more pages
var lastPageLoc = inputPosition;
// stop when the lastPage flag is set, not when we reach eof;
// this allows us to stop short of a 'fileSection' end without
// explicitly checking the length of the section
while (!last) {
setInputOffset(seekFunc, end);
switch (findPage(seekFunc, inputLength)) {
case Found(e, l):
end = e;
last = l;
case NotFound:
// the last page we found didn't have the 'last page' flag
// set. whoops!
break;
}
previousSafe = lastPageLoc + 1;
lastPageLoc = inputPosition;
}
setInputOffset(seekFunc, lastPageLoc);
// parse the header
var vorbisHeader = read(6);
// extract the absolute granule position
var lo = readInt32();
var hi = readInt32();
if (lo == 0xffffffff && hi == 0xffffffff || hi > 0) {
throw new ReaderError(ReaderErrorType.CANT_FIND_LAST_PAGE);
}
pLast = new ProbedPage();
pLast.pageStart = lastPageLoc;
pLast.pageEnd = end;
pLast.lastDecodedSample = lo;
pLast.firstDecodedSample = null;
pLast.afterPreviousPageStart = previousSafe;
setInputOffset(seekFunc, restoreOffset);
return lo;
}
public inline function forcePageResync()
{
nextSeg = -1;
}
public inline function setInputOffset(seekFunc:Int->Void, n:Int)
{
seekFunc(inputPosition = n);
}
public function findPage(seekFunc:Int->Void, inputLength:Int):FindPageResult {
try {
while (true) {
var n = readByte();
if (n == 0x4f) { // page header
var retryLoc = inputPosition;
// check if we're off the end of a fileSection stream
if (retryLoc - 25 > inputLength) {
return FindPageResult.NotFound;
}
if (readByte() != 0x67 || readByte() != 0x67 || readByte() != 0x53) {
continue;
}
var header = new Vector<UInt>(27);
header[0] = 0x4f;
header[1] = 0x67;
header[2] = 0x67;
header[3] = 0x53;
for (i in 4...27) {
header[i] = readByte();
}
if (header[4] != 0) {
setInputOffset(seekFunc, retryLoc);
continue;
}
var goal:UInt = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24);
for (i in 22...26) {
header[i] = 0;
}
var crc:UInt = 0;
for (i in 0...27){
crc = Crc32.update(crc, header[i]);
}
var len = 0;
try {
for (i in 0...header[26]) {
var s = readByte();
crc = Crc32.update(crc, s);
len += s;
}
for (i in 0...len) {
crc = Crc32.update(crc, readByte());
}
} catch (e:Eof) {
return FindPageResult.NotFound;
}
// finished parsing probable page
if (crc == goal) {
// we could now check that it's either got the last
// page flag set, OR it's followed by the capture
// pattern, but I guess TECHNICALLY you could have
// a file with garbage between each ogg page and recover
// from it automatically? So even though that paranoia
// might decrease the chance of an invalid decode by
// another 2^32, not worth it since it would hose those
// invalid-but-useful files?
var end = inputPosition;
setInputOffset(seekFunc, retryLoc - 1);
return FindPageResult.Found(end, (header[5] & 0x04 != 0));
}
}
}
} catch (e:Eof) {
return FindPageResult.NotFound;
}
}
public function analyzePage(seekFunc:Int->Void, h:Header)
{
var z:ProbedPage = new ProbedPage();
var packetType = new Vector<Bool>(255);
// record where the page starts
z.pageStart = inputPosition;
// parse the header
var pageHeader = read(27);
VorbisTools.assert(pageHeader.get(0) == 0x4f && pageHeader.get(1) == 0x67 && pageHeader.get(2) == 0x67 && pageHeader.get(3) == 0x53);
var lacing = read(pageHeader.get(26));
// determine the length of the payload
var len = 0;
for (i in 0...pageHeader.get(26)){
len += lacing.get(i);
}
// this implies where the page ends
z.pageEnd = z.pageStart + 27 + pageHeader.get(26) + len;
// read the last-decoded sample out of the data
z.lastDecodedSample = pageHeader.get(6) + (pageHeader.get(7) << 8) + (pageHeader.get(8) << 16) + (pageHeader.get(9) << 16);
if ((pageHeader.get(5) & 4) != 0) {
// if this is the last page, it's not possible to work
// backwards to figure out the first sample! whoops! fuck.
z.firstDecodedSample = null;
setInputOffset(seekFunc, z.pageStart);
return z;
}
// scan through the frames to determine the sample-count of each one...
// our goal is the sample # of the first fully-decoded sample on the
// page, which is the first decoded sample of the 2nd packet
var numPacket = 0;
var packetStart = ((pageHeader.get(5) & 1) == 0);
var modeCount = h.modes.length;
for (i in 0...pageHeader.get(26)) {
if (packetStart) {
if (lacing.get(i) == 0) {
setInputOffset(seekFunc, z.pageStart);
return null; // trying to read from zero-length packet
}
var n = readByte();
// if bottom bit is non-zero, we've got corruption
if (n & 1 != 0) {
setInputOffset(seekFunc, z.pageStart);
return null;
}
n >>= 1;
var b = MathTools.ilog(modeCount - 1);
n &= (1 << b) - 1;
if (n >= modeCount) {
setInputOffset(seekFunc, z.pageStart);
return null;
}
packetType[numPacket++] = h.modes[n].blockflag;
skip(lacing.get(i)-1);
} else {
skip(lacing.get(i));
}
packetStart = (lacing.get(i) < 255);
}
// now that we know the sizes of all the pages, we can start determining
// how much sample data there is.
var samples = 0;
// for the last packet, we step by its whole length, because the definition
// is that we encoded the end sample loc of the 'last packet completed',
// where 'completed' refers to packets being split, and we are left to guess
// what 'end sample loc' means. we assume it means ignoring the fact that
// the last half of the data is useless without windowing against the next
// packet... (so it's not REALLY complete in that sense)
if (numPacket > 1) {
samples += packetType[numPacket-1] ? h.blocksize1 : h.blocksize0;
}
var i = numPacket - 2;
while (i >= 1) {
i--;
// now, for this packet, how many samples do we have that
// do not overlap the following packet?
if (packetType[i]) {
if (packetType[i + 1]) {
samples += h.blocksize1 >> 1;
} else {
samples += ((h.blocksize1 - h.blocksize0) >> 2) + (h.blocksize0 >> 1);
}
} else {
samples += h.blocksize0 >> 1;
}
i--;
}
// now, at this point, we've rewound to the very beginning of the
// Second_ packet. if we entirely discard the first packet after
// a seek, this will be exactly the right sample number. HOWEVER!
// we can't as easily compute this number for the LAST page. The
// only way to get the sample offset of the LAST page is to use
// the end loc from the previous page. But what that returns us
// is _exactly_ the place where we get our first non-overlapped
// sample. (I think. Stupid spec for being ambiguous.) So for
// consistency it's better to do that here, too. However, that
// will then require us to NOT discard all of the first frame we
// decode, in some cases, which means an even weirder frame size
// and extra code. what a fucking pain.
// we're going to discard the first packet if we
// start the seek here, so we don't care about it. (we could actually
// do better; if the first packet is long, and the previous packet
// is short, there's actually data in the first half of the first
// packet that doesn't need discarding... but not worth paying the
// effort of tracking that of that here and in the seeking logic)
// except crap, if we infer it from the Previous_ packet's end
// location, we DO need to use that definition... and we HAVE to
// infer the start loc of the LAST packet from the previous packet's
// end location. fuck you, ogg vorbis.
z.firstDecodedSample = z.lastDecodedSample - samples;
// restore file state to where we were
setInputOffset(seekFunc, z.pageStart);
return z;
}
function decodeScalarRaw(c:Codebook):Int
{
prepHuffman();
VorbisTools.assert(c.sortedCodewords != null || c.codewords != null);
// cases to use binary search: sortedCodewords && !codewords
var codewordLengths = c.codewordLengths;
var codewords = c.codewords;
var sortedCodewords = c.sortedCodewords;
if (c.entries > 8 ? (sortedCodewords != null) : codewords != null) {
// binary search
var code = VorbisTools.bitReverse(acc);
var x = 0;
var n = c.sortedEntries;
while (n > 1) {
// invariant: sc[x] <= code < sc[x+n]
var m = x + (n >> 1);
if (sortedCodewords[m] <= code) {
x = m;
n -= (n>>1);
} else {
n >>= 1;
}
}
// x is now the sorted index
if (!c.sparse) {
x = c.sortedValues[x];
}
// x is now sorted index if sparse, or symbol otherwise
var len = codewordLengths[x];
if (validBits >= len) {
acc >>>= len;
validBits -= len;
return x;
}
validBits = 0;
return -1;
}
// if small, linear search
VorbisTools.assert(!c.sparse);
for (i in 0...c.entries) {
var cl = codewordLengths[i];
if (cl == Codebook.NO_CODE) {
continue;
}
if (codewords[i] == (acc & ((1 << cl)-1))) {
if (validBits >= cl) {
acc >>>= cl;
validBits -= cl;
return i;
}
validBits = 0;
return -1;
}
}
error = new ReaderError(INVALID_STREAM);
validBits = 0;
return -1;
}
}
private enum FindPageResult {
Found(end:Int, last:Bool);
NotFound;
}