/// <summary> /// construct AudioFrame from supplied bytes /// </summary> /// <param name="frameBuffer"></param> /// <remarks>buffer is correct size, even for free bitrate files</remarks> public AudioFrame(byte[] frameBuffer) { // find and parse frame header, rewind stream to start, or throw. _header = new AudioFrameHeader(frameBuffer); _headerBytes = _header.HeaderSize; _frameBuffer = frameBuffer; }
/// <summary> /// Creates Header /// </summary> /// <remarks> /// n.b. doesn't rewind the stream to the start of the frame. /// If the caller wants to read the entire frame in one block, they'll have to rewind it themselves. /// </remarks> /// <param name="stream"></param> /// <param name="remainingBytes"></param> /// <returns>valid audio header, or null</returns> static AudioFrameHeader CreateHeader(Stream stream, uint remainingBytes) { // save the start of the skip operation, for error messages long streamStartpos = stream.Position; int framesSkipped = 0; // apply an upper limit of 64k to the number of bytes it will skip, // to prevent it trying to read the whole of a corrupt 5meg file // (MPAHeaderInfo skips 3 frames but I think it's worth checking further than that) if (remainingBytes > 65536) { remainingBytes = 65536; } long endPos = streamStartpos + (long)remainingBytes; do { long remaining = endPos - (uint)stream.Position; int numskipped = Seek(stream, remaining); if (numskipped < 0) { //throw new InvalidAudioFrameException( // string.Format("MPEG Audio Frame: No header found from offset {0} to the end", streamStartpos)); return(null); } //else if( numskipped > 0 ) // Trace.WriteLine( string.Format( "{0} bytes skipped to start of frame at offset {1}", // numskipped, stream.Position - numskipped ) ); // save the start of the real frame, i.e. after the rubbish is skipped long frameStartPos = stream.Position; AudioFrameHeader parsedHeader = new AudioFrameHeader(ReadHeader(stream)); if (parsedHeader.Valid) { if (frameStartPos > streamStartpos) { if (framesSkipped > 0) { Trace.WriteLine(string.Format("total {0} bytes and {1} invalid frame headers skipped to get to the start of a valid frame at stream offset {2}", frameStartPos - streamStartpos, framesSkipped, frameStartPos)); } else { Trace.WriteLine(string.Format("total {0} bytes skipped to get to the start of a valid frame at stream offset {1}", frameStartPos - streamStartpos, frameStartPos)); } } return(parsedHeader); } // header is invalid mp3 frame, so skip a char and look again stream.Position = frameStartPos; ++framesSkipped; /*byte skipchar = (byte)*/ stream.ReadByte(); //Trace.WriteLine(string.Format(" Invalid MP3 frame; skipping 0x{0:X} at {1}", skipchar, frameStartPos + 1)); }while(true); }
/// <summary> /// compare headers /// </summary> // return true if identical or related // return false if no similarities // (from an idea in CMPAHeader, http://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx) bool IsCompatible(AudioFrameHeader destHeader) { // version change never possible if (destHeader.RawMpegVersion != RawMpegVersion) { return(false); } // layer change never possible if (destHeader.RawLayer != RawLayer) { return(false); } // sampling rate change never possible if (destHeader.RawSampleFreq != RawSampleFreq) { return(false); } // from mono to stereo never possible if (destHeader.IsMono != IsMono) { return(false); } if (destHeader.RawEmphasis != RawEmphasis) { return(false); } return(true); }
/// <summary> /// construct AudioFrame from a larger portion of the stream; don't rewind stream when done /// </summary> /// <param name="stream">source stream</param> /// <param name="header">parsed header</param> /// <param name="frameSize">size from header, or scanning for second frame of free bitrate file</param> /// <param name="remainingBytes">number of bytes in audio block, as reported by the caller</param> public AudioFrame(Stream stream, AudioFrameHeader header, uint frameSize, uint remainingBytes) { Debug.Assert(header != null); _header = header; _frameBuffer = new byte[frameSize]; _headerBytes = _header.HeaderSize; int numgot = stream.Read(_frameBuffer, 0, (int)frameSize); if( numgot < (int)frameSize ) throw new InvalidAudioFrameException( string.Format("MPEG Audio AudioFrame: only {0} bytes of frame found when {1} bytes declared", numgot, frameSize)); }
/// <summary> /// seek and create derived type of AudioFrame from stream /// </summary> /// <param name="stream">source stream, advanced by length of the frame on read</param> /// <param name="remainingBytes">number of bytes in audio block, as reported by the caller</param> /// <returns>wrapper for derived type of AudioFrame</returns> static public AudioFrame CreateFrame(Stream stream, uint remainingBytes) { // find and parse frame header, then rewind stream back to start. // if reach the end of the file, return null // if any other error, throw long firstFrameStart = stream.Position; AudioFrameHeader header = CreateHeader(stream, remainingBytes); if (header == null) { return(null); } uint frameFullSize; // if free rate file, find the start of the next frame and use the difference as the frame size. // NB. This won't be very efficient! if (header.IsFreeBitRate) { uint firstFrameHdrSize = (uint)(stream.Position - firstFrameStart); frameFullSize = firstFrameHdrSize + GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); //GetNextFrameOffset(stream, remainingBytes - (uint)(stream.Position - firstFrameStart)); } else { uint?frameLengthInBytes = header.FrameLengthInBytes; Trace.Assert(frameLengthInBytes != null); frameFullSize = (uint)header.FrameLengthInBytes; } // rewind the stream to the start of the frame, so we can read it all in one chunk stream.Position = firstFrameStart; AudioFrame firstTry = new AudioFrame(stream, header, frameFullSize, remainingBytes); return(CreateSpecialisedHeaderFrame(firstTry)); }
/// <summary> /// skip to start of next frame /// </summary> /// <param name="stream"></param> /// <param name="remainingBytes"></param> /// <returns>number of bytes skipped, not length of frame found!</returns> static private uint GetNextFrameOffset(Stream stream, uint remainingBytes) { long prevHeaderEnd = stream.Position; AudioFrameHeader header = CreateHeader(stream, remainingBytes); if (header == null) { return(0); } Trace.WriteLine(String.Format("next frame is {0} bytes further", stream.Position - prevHeaderEnd)); Trace.WriteLine(header.DebugString); return((uint)(stream.Position - prevHeaderEnd)); }
/// <summary> /// construct AudioFrame from a larger portion of the stream; don't rewind stream when done /// </summary> /// <param name="stream">source stream</param> /// <param name="header">parsed header</param> /// <param name="frameSize">size from header, or scanning for second frame of free bitrate file</param> /// <param name="remainingBytes">number of bytes in audio block, as reported by the caller</param> public AudioFrame(Stream stream, AudioFrameHeader header, uint frameSize, uint remainingBytes) { Debug.Assert(header != null); _header = header; _frameBuffer = new byte[frameSize]; _headerBytes = _header.HeaderSize; int numgot = stream.Read(_frameBuffer, 0, (int)frameSize); if (numgot < (int)frameSize) { throw new InvalidAudioFrameException( string.Format("MPEG Audio AudioFrame: only {0} bytes of frame found when {1} bytes declared", numgot, frameSize)); } }
/// <summary> /// compare headers /// </summary> // return true if identical or related // return false if no similarities // (from an idea in CMPAHeader, http://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx) bool IsCompatible( AudioFrameHeader destHeader ) { // version change never possible if (destHeader.RawMpegVersion != RawMpegVersion) return false; // layer change never possible if (destHeader.RawLayer != RawLayer) return false; // sampling rate change never possible if (destHeader.RawSampleFreq != RawSampleFreq) return false; // from mono to stereo never possible if (destHeader.IsMono != IsMono) return false; if (destHeader.RawEmphasis != RawEmphasis) return false; return true; }
/// <summary> /// copy construct AudioFrame for derived classes /// </summary> /// <param name="other"></param> protected AudioFrame(AudioFrame other) { _frameBuffer = other._frameBuffer; _header = other._header; _headerBytes = other._headerBytes; }
/// <summary> /// Creates Header /// </summary> /// <remarks> /// n.b. doesn't rewind the stream to the start of the frame. /// If the caller wants to read the entire frame in one block, they'll have to rewind it themselves. /// </remarks> /// <param name="stream"></param> /// <param name="remainingBytes"></param> /// <returns>valid audio header, or null</returns> static AudioFrameHeader CreateHeader( Stream stream, uint remainingBytes ) { // save the start of the skip operation, for error messages long streamStartpos = stream.Position; int framesSkipped = 0; // apply an upper limit of 64k to the number of bytes it will skip, // to prevent it trying to read the whole of a corrupt 5meg file // (MPAHeaderInfo skips 3 frames but I think it's worth checking further than that) if( remainingBytes > 65536 ) remainingBytes = 65536; long endPos = streamStartpos + (long)remainingBytes; do { long remaining = endPos - (uint)stream.Position; int numskipped = Seek( stream, remaining ); if( numskipped < 0 ) { //throw new InvalidAudioFrameException( // string.Format("MPEG Audio Frame: No header found from offset {0} to the end", streamStartpos)); return null; } //else if( numskipped > 0 ) // Trace.WriteLine( string.Format( "{0} bytes skipped to start of frame at offset {1}", // numskipped, stream.Position - numskipped ) ); // save the start of the real frame, i.e. after the rubbish is skipped long frameStartPos = stream.Position; AudioFrameHeader parsedHeader = new AudioFrameHeader( ReadHeader( stream ) ); if( parsedHeader.Valid ) { if( frameStartPos > streamStartpos ) { if( framesSkipped > 0 ) Trace.WriteLine(string.Format("total {0} bytes and {1} invalid frame headers skipped to get to the start of a valid frame at stream offset {2}", frameStartPos - streamStartpos, framesSkipped, frameStartPos)); else Trace.WriteLine(string.Format("total {0} bytes skipped to get to the start of a valid frame at stream offset {1}", frameStartPos - streamStartpos, frameStartPos)); } return parsedHeader; } // header is invalid mp3 frame, so skip a char and look again stream.Position = frameStartPos; ++framesSkipped; /*byte skipchar = (byte)*/stream.ReadByte(); //Trace.WriteLine(string.Format(" Invalid MP3 frame; skipping 0x{0:X} at {1}", skipchar, frameStartPos + 1)); } while( true ); }