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; public var bytesInSeg:Int = 0; // uint8 // decode buffer public var channelBuffers:Vector>; //var *[STB_VORBIS_MAX_CHANNELS]; public var channelBufferStart:Int; public var channelBufferEnd:Int; public var currentSample(default, null):Int; public var previousWindow:Vector>; //var *[STB_VORBIS_MAX_CHANNELS]; public var previousLength:Int; public var finalY:Vector>; // [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 { 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(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(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; }