forked from LeenkxTeam/LNXSDK
		
	
		
			
				
	
	
		
			858 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
| }
 |