/// <summary> /// Initializes a new instance of the <see cref="VbriHeader"/> class. /// </summary> /// <param name="firstFrame">The first frame.</param> /// <param name="firstFrameBuffer">The first frame buffer.</param> /// <param name="offset">The offset.</param> public VbriHeader(MpaFrame firstFrame, StreamBuffer firstFrameBuffer, long offset) : base(firstFrame, firstFrameBuffer, offset, VbrHeaderType.Vbri) { /* FhG VBRI Header size description 4 'VBRI' (ID) 2 version 2 delay 2 quality 4 # bytes 4 # frames 2 table size (for TOC) 2 table scale (for TOC) 2 size of a table entry (max. size = 4 byte (must be stored in an integer)) 2 frames per table entry ?? dynamic table consisting out of frames with size 1-4 whole length in table size! (for TOC) */ // name Name = firstFrameBuffer.ReadString(4); // version Version = (short)firstFrameBuffer.ReadBigEndianInt16(); // delay _delay = firstFrameBuffer.ReadBigEndianInt16(); // quality Quality = firstFrameBuffer.ReadBigEndianInt16(); // size of the file, in bytes, of all the data FileSize = firstFrameBuffer.ReadBigEndianInt32(); // amount of frames FrameCount = firstFrameBuffer.ReadBigEndianInt32(); // number of entries in the table (for TOC) TableEntries = (short)firstFrameBuffer.ReadBigEndianInt16(); // table scale (for TOC) TableScale = (short)firstFrameBuffer.ReadBigEndianInt16(); // size of a table entry (in bytes) TableEntrySize = (short)firstFrameBuffer.ReadBigEndianInt16(); // frames per table entry FramesPerTableEntry = (short)firstFrameBuffer.ReadBigEndianInt16(); // dynamic table consisting out of frames TableLength = TableEntries * TableEntrySize; Toc = new int[TableEntries + 1]; for (int i = 0; i <= TableEntries; i++) { int value = firstFrameBuffer.ReadBigEndianInt(TableEntrySize); Toc[i] = value * TableScale; } _totalLengthMilliseconds = firstFrame.AudioLength * FrameCount; }
////------------------------------------------------------------------------------------------------------------------------------ /// <summary> /// Reads a <see cref="MpaFrame"/> from a <see cref="Stream"/>. /// </summary> /// <param name="stream">The stream.</param> /// <returns> /// true if found; otherwise, null. /// </returns> /// <exception cref="System.ArgumentNullException">Thrown if stream is null.</exception> public static MpaFrame ReadFrame(Stream stream) { if (stream == null) throw new ArgumentNullException("stream"); MpaFrame frame = new MpaFrame(); return frame.ReadFrame(stream as StreamBuffer ?? new StreamBuffer(stream)) ? frame : null; }
/// <summary> /// Initializes a new instance of the <see cref="XingHeader"/> class. /// </summary> /// <param name="firstFrame">The first frame.</param> /// <param name="firstFrameBuffer">The first frame buffer.</param> /// <param name="offset">The offset.</param> public XingHeader(MpaFrame firstFrame, StreamBuffer firstFrameBuffer, long offset) : base(firstFrame, firstFrameBuffer, offset, VbrHeaderType.Xing) { /* XING VBR-Header size description 4 'Xing' or 'Info' 4 flags (indicates which fields are used) 4 frames (optional) 4 bytes (optional) 100 toc (optional) 4 a VBR quality indicator: 0=best 100=worst (optional) --------- + 120 bytes * * NOTE: the frames (frameCount) in the XING header does not include its own frame. * So the total frames is actually XING framecount + 1 */ // name of the tag as found in the file Name = firstFrameBuffer.ReadString(4); // The flags indicate which fields are used in the XING header Flags = firstFrameBuffer.ReadBigEndianInt32(); // Extract total frames in the file (XING header excludes it's own frame) if ((Flags & XingHeaderFlags.FrameCountFlag) != 0) FrameCount = firstFrameBuffer.ReadBigEndianInt32(); // Extract size of the file, in bytes if ((Flags & XingHeaderFlags.FileSizeFlag) != 0) FileSize = firstFrameBuffer.ReadBigEndianInt32(); // Extract TOC (Table of Contents) for more accurate seeking if ((Flags & XingHeaderFlags.TocFlag) != 0) { Toc = new int[100]; for (int i = 0; i < 100; i++) Toc[i] = firstFrameBuffer.ReadByte(); } if ((Flags & XingHeaderFlags.VbrScaleFlag) != 0) Quality = firstFrameBuffer.ReadBigEndianInt32(); // The LAME tag is always 120 bytes after the XING header - regardless of which fields are used LameTag = LameTag.FindTag(firstFrameBuffer, offset + 120); }
/// <summary> /// Initializes a new instance of the <see cref="VbrHeader"/> class. /// </summary> /// <param name="firstFrame">The first frame.</param> /// <param name="firstFrameBuffer">The first frame buffer.</param> /// <param name="offset">The offset.</param> /// <param name="headerType">Type of the header.</param> protected VbrHeader(MpaFrame firstFrame, StreamBuffer firstFrameBuffer, long offset, VbrHeaderType headerType) { if (firstFrame == null) throw new ArgumentNullException("firstFrame"); if (firstFrameBuffer == null) throw new ArgumentNullException("firstFrameBuffer"); ////if (headerType == null) ////throw new ArgumentNullException("headerType"); // first frame contains the vbr header FirstFrame = firstFrame; // Offset of this header in the first frame. Offset = offset; // VBR Header type, currently only XING and VBRI HeaderType = headerType; }
/// <summary> /// Finds the <see cref="VbriHeader"/> header within the <paramref name="firstFrame"/>. /// </summary> /// <param name="firstFrame">The first frame.</param> /// <returns>The VBRI header if found; otherwise, null.</returns> /// <remarks> /// The VBRI header is located exactly 32 bytes after the end of the first MPEG audio header in the file. /// It will compare the first 4 bytes against the <see cref="HeaderIndicator"/> /// to see if the header contains a <see cref="VbriHeader"/> or not. /// </remarks> public static new VbriHeader FindHeader(MpaFrame firstFrame) { if (firstFrame == null) throw new ArgumentNullException("firstFrame"); using (StreamBuffer buffer = new StreamBuffer()) { byte[] data = firstFrame.ToByteArray(); buffer.Write(data); // 32 bytes = data indicating silence const long Offset = MpaFrame.FrameHeaderSize + SilenceDataSize; buffer.Seek(Offset, SeekOrigin.Begin); string tagName = buffer.ReadString(4, false, false); return String.Compare(tagName, HeaderIndicator, StringComparison.OrdinalIgnoreCase) == 0 ? new VbriHeader(firstFrame, buffer, Offset) : null; } }
/// <summary> /// Finds the <see cref="VbriHeader"/> header within the <paramref name="firstFrame"/>. /// </summary> /// <param name="firstFrame">The first frame.</param> /// <returns>The VBRI header if found; otherwise, null.</returns> /// <remarks> /// The XING header is located after the side information in Layer III in the first MPEG audio header in the file. /// It will compare the first 4 bytes against the <see cref="VbrHeaderIndicator"/> /// to see if the header contains a <see cref="XingHeader"/> or not. /// </remarks> public static new XingHeader FindHeader(MpaFrame firstFrame) { if (firstFrame == null) throw new ArgumentNullException("firstFrame"); long offset = MpaFrame.FrameHeaderSize + firstFrame.SideInfoSize; using (StreamBuffer buffer = new StreamBuffer()) { byte[] data = firstFrame.ToByteArray(); buffer.Write(data); buffer.Seek(offset, SeekOrigin.Begin); string tagName = buffer.ReadString(4, false, false); if ((String.Compare(tagName, VbrHeaderIndicator, StringComparison.OrdinalIgnoreCase) == 0) || (String.Compare(tagName, CbrHeaderIndicator, StringComparison.OrdinalIgnoreCase) == 0)) return new XingHeader(firstFrame, buffer, offset); } return null; }
////------------------------------------------------------------------------------------------------------------------------------ private static bool IsValidFirstFrame(MpaFrame firstFrame, MpaFrame secondFrame) { // The free bitrate must remain constant // We need to calculate the bitrate manually to see if its constant // (If VBR is used, the bitrate can be different for each frame, this check is only for free bitrate frames) if (secondFrame.Bitrate == 0) return (firstFrame.Bitrate == secondFrame.Bitrate); // See if the next frame is directly after the current frame // FrameLength is calculated using the bitrate as a calculation factor // This will fail if free bitrate is used as its not implemented yet ////if ((curFrame.GetOffset() + curFrame.GetFrameLength() != nextFrame.GetOffset()) && (curFrameHeader.BitrateIndex != 0)) ////{ ////curFrame = nextFrame; ////nextFrame = GetNextFrame(curFrame); ////continue; ////} // Check the values of the next header, some have to be the same as the previous: // ** version // ** layer // ** samples (per sec) // ** channel type (can only be mono or stereo) // ** emphasis return ((secondFrame.AudioVersion == firstFrame.AudioVersion) && (secondFrame.LayerVersion == firstFrame.LayerVersion) && (secondFrame.SamplingRate == firstFrame.SamplingRate) && (secondFrame.IsMono == firstFrame.IsMono) && (secondFrame.Emphasis == firstFrame.Emphasis)); }
/// <summary> /// Find a <see cref="VbrHeader"/> in the supplied frame. /// A <see cref="VbrHeader"/> could follow the MPA header in the first frame. /// </summary> /// <remarks> /// Only the first frame can contain a <see cref="VbrHeader"/>. /// Possible VBR headers could be <see cref="XingHeader"/> or <see cref="VbriHeader"/>. /// </remarks> /// <param name="firstFrame">The first frame.</param> /// <returns>A VBR header if found; otherwise null.</returns> public static VbrHeader FindHeader(MpaFrame firstFrame) { if (firstFrame == null) throw new ArgumentNullException("firstFrame"); return XingHeader.FindHeader(firstFrame) ?? (VbrHeader)VbriHeader.FindHeader(firstFrame); }