public static int[] GetBlockFrameLengths(EbmlLaceType laceType, int dataLength, Stream stm, out int bytesConsumed) { // Matroska uses 'lacing' to store more than one frame of data in a single block, thereby saving the overhead of a full block per frame // this method determines the length of each frame so they can be re-separated and searched. See the Matroska specs for details... bytesConsumed = 0; int laceFrameCount = 1; if (laceType != EbmlLaceType.None) { laceFrameCount = stm.ReadByte() + 1; bytesConsumed++; } int[] frameSizes = new int[laceFrameCount]; for (int i = 0; i < laceFrameCount; i++) { if (laceType == EbmlLaceType.None) { frameSizes[i] = dataLength; } else if (laceType == EbmlLaceType.Fixed) { frameSizes[i] = dataLength / laceFrameCount; } else if (laceType == EbmlLaceType.Xiph) { if (i < laceFrameCount - 1) { int nextByte; do { nextByte = stm.ReadByte(); bytesConsumed++; frameSizes[i] += nextByte; } while (nextByte == 0xFF); } else { frameSizes[i] = dataLength - bytesConsumed; for (int j = 0; j < i; j++) { frameSizes[i] -= frameSizes[j]; } } } else // EbmlLaceType.Ebml { int bc = 0; if (i == 0) { frameSizes[i] = (int)GetEbmlUInt(stm, out bc); } else if (i < laceFrameCount - 1) { // convert UInt to SInt then add to previous int len = (int)GetEbmlUInt(stm, out bc); len -= ((1 << (bc * 8 - (bc + 1))) - 1); frameSizes[i] = frameSizes[i - 1] + len; } else { frameSizes[i] = dataLength - bytesConsumed; for (int j = 0; j < i; j++) { frameSizes[i] -= frameSizes[j]; } } bytesConsumed += bc; } } return(frameSizes); }
public bool Read() { Debug.Assert(readReady || (mode == EbmlReadMode.SRS && ElementType == EbmlElementType.Block), "Read() is invalid at this time", "MoveToChild(), ReadContents(), or SkipContents() must be called before Read() can be called again"); long elementStartPos = ebmlStream.Position; byte idLengthDescriptor = 0; byte dataLengthDescriptor = 0; if (elementStartPos + 2 > fileLength) { return(false); } currentElement = null; readReady = false; idLengthDescriptor = (byte)ebmlStream.ReadByte(); elementHeader[0] = idLengthDescriptor; idLengthDescriptor = EbmlHelper.GetUIntLength(idLengthDescriptor); ebmlStream.Read(elementHeader, 1, idLengthDescriptor - 1); dataLengthDescriptor = (byte)ebmlStream.ReadByte(); elementHeader[idLengthDescriptor] = dataLengthDescriptor; dataLengthDescriptor = EbmlHelper.GetUIntLength(dataLengthDescriptor); ebmlStream.Read(elementHeader, idLengthDescriptor + 1, dataLengthDescriptor - 1); // these comparisons are ordered by the frequency with which they will be encountered to avoid unnecessary processing if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Block, idLengthDescriptor) || ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.SimpleBlock, idLengthDescriptor)) { ElementType = EbmlElementType.Block; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.BlockGroup, idLengthDescriptor)) { ElementType = EbmlElementType.BlockGroup; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Cluster, idLengthDescriptor)) { ElementType = EbmlElementType.Cluster; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Timecode, idLengthDescriptor)) { ElementType = EbmlElementType.Timecode; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Segment, idLengthDescriptor)) { ElementType = EbmlElementType.Segment; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.TimecodeScale, idLengthDescriptor)) { ElementType = EbmlElementType.TimecodeScale; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Crc32, idLengthDescriptor)) { ElementType = EbmlElementType.Crc32; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.AttachmentList, idLengthDescriptor)) { ElementType = EbmlElementType.AttachmentList; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.Attachment, idLengthDescriptor)) { ElementType = EbmlElementType.Attachment; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.AttachedFileName, idLengthDescriptor)) { ElementType = EbmlElementType.AttachedFileName; } else if (ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.AttachedFileData, idLengthDescriptor)) { ElementType = EbmlElementType.AttachedFileData; } else if (mode == EbmlReadMode.SRS && ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.ReSample, idLengthDescriptor)) { ElementType = EbmlElementType.ReSample; } else if (mode == EbmlReadMode.SRS && ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.ReSampleFile, idLengthDescriptor)) { ElementType = EbmlElementType.ReSampleFile; } else if (mode == EbmlReadMode.SRS && ByteArrayComparer.AreEqual(elementHeader, EbmlElementIDs.ReSampleTrack, idLengthDescriptor)) { ElementType = EbmlElementType.ReSampleTrack; } else { ElementType = EbmlElementType.Unknown; } long elementLength = EbmlHelper.GetEbmlUInt(elementHeader, idLengthDescriptor, dataLengthDescriptor); // sanity check on element length. skip check on Segment element so we can still report expected size. this is only applied on samples since a partial movie might still be useful long endOffset = elementStartPos + idLengthDescriptor + dataLengthDescriptor + elementLength; if (mode == EbmlReadMode.Sample && ElementType != EbmlElementType.Segment && endOffset > fileLength) { throw new InvalidDataException(string.Format("Invalid element length at 0x{0:x8}", elementStartPos)); } if (ElementType != EbmlElementType.Block) { byte[] rawHeader = new byte[idLengthDescriptor + dataLengthDescriptor]; Buffer.BlockCopy(elementHeader, 0, rawHeader, 0, idLengthDescriptor + dataLengthDescriptor); currentElement = new EbmlElement() { ElementStartPos = elementStartPos, RawHeader = rawHeader, Length = elementLength }; } else { // first thing in the block is the track number byte trackDescriptor = (byte)ebmlStream.ReadByte(); byte[] blockHeader = new byte[4]; blockHeader[0] = trackDescriptor; trackDescriptor = EbmlHelper.GetUIntLength(trackDescriptor); // incredibly unlikely the track number is > 1 byte, but just to be safe... if (trackDescriptor > 1) { byte[] newBlockHeader = new byte[trackDescriptor + 3]; newBlockHeader[0] = blockHeader[0]; ebmlStream.Read(newBlockHeader, 1, trackDescriptor - 1); blockHeader = newBlockHeader; } int trackno = (int)EbmlHelper.GetEbmlUInt(blockHeader, 0, trackDescriptor); // read in time code (2 bytes) and flags (1 byte) ebmlStream.Read(blockHeader, trackDescriptor, 3); short timecode = (short)((blockHeader[blockHeader.Length - 3] << 8) + blockHeader[blockHeader.Length - 2]); // need to grab the flags (last byte of the header) to check for lacing EbmlLaceType laceType = (EbmlLaceType)(blockHeader[blockHeader.Length - 1] & (byte)EbmlLaceType.Ebml); int dataLength = (int)elementLength - blockHeader.Length; int bytesConsumed; int[] frameSizes = EbmlHelper.GetBlockFrameLengths(laceType, dataLength, ebmlStream, out bytesConsumed); if (bytesConsumed > 0) { byte[] newBlockHeader = new byte[blockHeader.Length + bytesConsumed]; Buffer.BlockCopy(blockHeader, 0, newBlockHeader, 0, blockHeader.Length); ebmlStream.Seek(-bytesConsumed, SeekOrigin.Current); ebmlStream.Read(newBlockHeader, blockHeader.Length, bytesConsumed); blockHeader = newBlockHeader; } elementLength -= blockHeader.Length; byte[] rawHeader = new byte[idLengthDescriptor + dataLengthDescriptor]; Buffer.BlockCopy(elementHeader, 0, rawHeader, 0, idLengthDescriptor + dataLengthDescriptor); currentElement = new BlockElement() { ElementStartPos = elementStartPos, RawHeader = rawHeader, Length = elementLength, FrameLengths = frameSizes, TrackNumber = trackno, Timecode = timecode, RawBlockHeader = blockHeader }; } // the following line will write mkvinfo-like output from the parser (extremely useful for debugging) //Console.WriteLine("{0}: {1} bytes @ {2}", ElementType, elementLength, elementStartPos); return(true); }