/// <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 ApeItem ReadItem(ApeVersion version, StreamBuffer sb, long maximumItemSize) { if (sb == null) throw new ArgumentNullException("sb"); // Find the item. long startPosition = sb.Position; long bytesToSkip = GetBytesUntilNextItem(version, sb, maximumItemSize); // Not found; return null. if (bytesToSkip == -1) return null; // Move the stream position to the start of the frame. sb.Position = startPosition + bytesToSkip; int valueSize = sb.ReadLittleEndianInt32(); if ((valueSize <= 0) || (valueSize > ApeTag.MaxAllowedSize)) return null; int flags = sb.ReadLittleEndianInt32(); maximumItemSize -= 8; int itemKeyLengthBytes = 0; long maximumBytesToRead = maximumItemSize; if (maximumBytesToRead > 0) { for (int y = 0; y < maximumBytesToRead; y++) { int character = sb.ReadByte(); if (character == 0x00) // 0x00 byte - string terminator. break; itemKeyLengthBytes++; } } // Name sb.Seek(-(itemKeyLengthBytes + 1), SeekOrigin.Current); string key = sb.ReadString(itemKeyLengthBytes, Encoding.UTF8); if ((key.Length < MinKeyLengthCharacters) || (itemKeyLengthBytes > MaxKeyLengthBytes)) return null; // Skip Item key terminator (0x00 byte) int keyTerminator = sb.ReadByte(); if (keyTerminator != 0x00) return null; ApeItem item = GetItem(version, key, flags); return item.ReadItem(version, valueSize, sb, maximumItemSize) ? item : null; }
public void SeekTest() { StreamBuffer target = new StreamBuffer(); target.WritePadding(0x00, 1024); const long Offset = 100; const SeekOrigin Loc = new SeekOrigin(); const long Expected = 100; long actual= target.Seek(Offset, Loc); Assert.AreEqual(Expected, actual); }
////------------------------------------------------------------------------------------------------------------------------------ /// <summary> /// Finds a <see cref="LameTag"/> in the given stream. /// </summary> /// <param name="firstFrameBuffer">The first frame buffer.</param> /// <param name="offset">The offset.</param> /// <returns>The <see cref="LameTag"/> when found; otherwise, null.</returns> public static LameTag FindTag(StreamBuffer firstFrameBuffer, long offset) { if (firstFrameBuffer == null) throw new ArgumentNullException("firstFrameBuffer"); // If limiting the LAME string to 9 bytes "LAME X.YZu", the extension revision 0 could take 27 bytes and it would still fit a 64 kbit 48kHz frame. firstFrameBuffer.Seek(offset, SeekOrigin.Begin); string tagName = firstFrameBuffer.ReadString(4, false, false); return String.Compare(tagName, "LAME", StringComparison.OrdinalIgnoreCase) == 0 ? new LameTag(firstFrameBuffer) : null; }
////------------------------------------------------------------------------------------------------------------------------------ /// <summary> /// Initializes a new instance of the <see cref="LameTag"/> class. /// </summary> /// <param name="firstFrameBuffer">The first frame buffer containing a <see cref="LameTag"/>.</param> public LameTag(StreamBuffer firstFrameBuffer) { // string lameTag = "LAME" firstFrameBuffer.ReadString(4); float ver; float.TryParse(firstFrameBuffer.ReadString(4), out ver); Version = ver; firstFrameBuffer.Seek(-8, SeekOrigin.Current); if (Version < 3.90f) { // Initial LAME info, 20 bytes for LAME tag. for example, "LAME3.12 (beta 6)" // LAME prior to 3.90 writes only a 20 byte encoder string. EncoderVersion = firstFrameBuffer.ReadString(20); } else { EncoderVersion = firstFrameBuffer.ReadString(9); // Revision Information Tag + VBR Info int infoAndVbr = firstFrameBuffer.ReadByte(); // Revision information in 4 MSB InfoTagRevision = infoAndVbr >> 4; if (InfoTagRevision == Formats.InfoTagRevision.Reserved) throw new ArgumentException("InfoTagRevision bit is set to reserved (0xF)"); // VBR info in 4 LSB VbrMethod = infoAndVbr & 0x0F; // lowpass information, multiply by 100 to get hz LowpassFilterValue = firstFrameBuffer.ReadByte() * 100; // Radio replay gain fields // Peak signal amplitude PeakSignalAmplitude = firstFrameBuffer.ReadFloat(); // Radio Replay Gain RadioReplayGain = firstFrameBuffer.ReadInt16(); // Audiophile Replay Gain AudiophileReplayGain = firstFrameBuffer.ReadInt16(); // Encoding Flags + ATH type int encodingFlagsAndAthType = firstFrameBuffer.ReadByte(); // Encoding Flags in 4 MSB EncodingFlags = encodingFlagsAndAthType >> 4; // LAME ATH Type in 4 LSB AthType = encodingFlagsAndAthType & 0x0F; // If ABR, this will be the specified bitrate // Otherwise (CBR/VBR), the minimal bitrate (255 means 255 or bigger) BitRate = firstFrameBuffer.ReadByte(); // the 12 bit values (0-4095) of how many samples were added at start (encoder delay) // in X and how many 0-samples were padded at the end in Y to complete the last frame. EncoderDelays = firstFrameBuffer.ReadInt(3); EncoderDelaySamples = EncoderDelays & 0xFFF; EncoderDelayPaddingSamples = EncoderDelays >> 12; ////int numberSamplesInOriginalWav = frameCount Misc = firstFrameBuffer.ReadByte(); // Any mp3 can be amplified by a factor 2 ^ ( x * 0.25) in a lossless manner by a tool like eg. mp3gain // if done so, this 8-bit field can be used to log such transformation happened so that any given time it can be undone. Mp3Gain = firstFrameBuffer.ReadByte(); // Preset and surround info PresetSurroundInfo = firstFrameBuffer.ReadInt16(); // 32 bit integer filed containing the exact length in bytes of the mp3 file originally made by LAME excluded ID3 tag info at the end. MusicLength = firstFrameBuffer.ReadBigEndianInt32(); // Contains a CRC-16 of the complete mp3 music data as made originally by LAME. _musicCrc = (short)firstFrameBuffer.ReadBigEndianInt16(); // contains a CRC-16 of the first 190 bytes (0x00 - 0xBD) of the Info header frame. // This field is calculated at the end, once all other fields are completed. _infoTagCrc = (short)firstFrameBuffer.ReadBigEndianInt16(); } }