Beispiel #1
0
        /// <summary>
        /// checks if the four bytes represent a valid header,
        /// if they are, will parse the values into Mp3Frame
        /// </summary>
        private static bool IsValidHeader(byte[] headerBytes, Mp3Frame frame)
        {
            if ((headerBytes[0] == 0xFF) && ((headerBytes[1] & 0xE0) == 0xE0))
            {
                // TODO: could do with a bitstream class here
                frame.MpegVersion = (MpegVersion)((headerBytes[1] & 0x18) >> 3);
                if (frame.MpegVersion == MpegVersion.Reserved)
                {
                    //throw new FormatException("Unsupported MPEG Version");
                    return(false);
                }

                frame.MpegLayer = (MpegLayer)((headerBytes[1] & 0x06) >> 1);

                if (frame.MpegLayer == MpegLayer.Reserved)
                {
                    return(false);
                }
                int layerIndex = frame.MpegLayer == MpegLayer.Layer1 ? 0 : frame.MpegLayer == MpegLayer.Layer2 ? 1 : 2;
                frame.CrcPresent   = (headerBytes[1] & 0x01) == 0x00;
                frame.BitRateIndex = (headerBytes[2] & 0xF0) >> 4;
                if (frame.BitRateIndex == 15)
                {
                    // invalid index
                    return(false);
                }
                int versionIndex = frame.MpegVersion == MpegVersion.Version1 ? 0 : 1;
                frame.BitRate = bitRates[versionIndex, layerIndex, frame.BitRateIndex] * 1000;
                if (frame.BitRate == 0)
                {
                    return(false);
                }
                int sampleFrequencyIndex = (headerBytes[2] & 0x0C) >> 2;
                if (sampleFrequencyIndex == 3)
                {
                    return(false);
                }

                if (frame.MpegVersion == MpegVersion.Version1)
                {
                    frame.SampleRate = sampleRatesVersion1[sampleFrequencyIndex];
                }
                else if (frame.MpegVersion == MpegVersion.Version2)
                {
                    frame.SampleRate = sampleRatesVersion2[sampleFrequencyIndex];
                }
                else
                {
                    // mpegVersion == MpegVersion.Version25
                    frame.SampleRate = sampleRatesVersion25[sampleFrequencyIndex];
                }

                bool padding    = (headerBytes[2] & 0x02) == 0x02;
                bool privateBit = (headerBytes[2] & 0x01) == 0x01;
                frame.ChannelMode      = (ChannelMode)((headerBytes[3] & 0xC0) >> 6);
                frame.ChannelExtension = (headerBytes[3] & 0x30) >> 4;
                if (frame.ChannelExtension != 0 && frame.ChannelMode != ChannelMode.JointStereo)
                {
                    return(false);
                }


                frame.Copyright = (headerBytes[3] & 0x08) == 0x08;
                bool original = (headerBytes[3] & 0x04) == 0x04;
                int  emphasis = (headerBytes[3] & 0x03);

                int nPadding = padding ? 1 : 0;

                frame.SampleCount = samplesPerFrame[versionIndex, layerIndex];
                int coefficient = frame.SampleCount / 8;
                if (frame.MpegLayer == MpegLayer.Layer1)
                {
                    frame.FrameLength = (coefficient * frame.BitRate / frame.SampleRate + nPadding) * 4;
                }
                else
                {
                    frame.FrameLength = (coefficient * frame.BitRate) / frame.SampleRate + nPadding;
                }

                if (frame.FrameLength > MaxFrameLength)
                {
                    return(false);
                }
                return(true);
            }
            return(false);
        }
Beispiel #2
0
        /// <summary>
        /// Reads decompressed PCM data from our MP3 file.
        /// </summary>
        public override int Read(Span <byte> sampleBuffer)
        {
            int bytesRead = 0;
            var numBytes  = sampleBuffer.Length; // the requested number of bytes

            lock (repositionLock)
            {
                if (decompressLeftovers != 0)
                {
                    int toCopy = Math.Min(decompressLeftovers, sampleBuffer.Length);
                    decompressBuffer.Slice(decompressBufferOffset, toCopy).Span.CopyTo(sampleBuffer);

                    decompressLeftovers -= toCopy;
                    if (decompressLeftovers == 0)
                    {
                        decompressBufferOffset = 0;
                    }
                    else
                    {
                        decompressBufferOffset += toCopy;
                    }
                    bytesRead   += toCopy;
                    sampleBuffer = sampleBuffer.Slice(toCopy);
                }

                int targetTocIndex = tocIndex; // the frame index that contains the requested data

                if (repositionedFlag)
                {
                    decompressor.Reset();

                    // Seek back a few frames of the stream to get the reset decoder decode a few
                    // warm-up frames before reading the requested data. Without the warm-up phase,
                    // the first half of the frame after the reset is attenuated and does not resemble
                    // the data as it would be when reading sequentially from the beginning, because
                    // the decoder is missing the required overlap from the previous frame.
                    tocIndex           = Math.Max(0, tocIndex - 3); // no warm-up at the beginning of the stream
                    mp3Stream.Position = tableOfContents[tocIndex].FilePosition;

                    repositionedFlag = false;
                }

                while (bytesRead < numBytes)
                {
                    Mp3Frame frame = ReadNextFrame(true); // internal read - should not advance position
                    if (frame != null)
                    {
                        int decompressed = decompressor.DecompressFrame(frame, decompressBuffer.Span);

                        if (tocIndex <= targetTocIndex || decompressed == 0)
                        {
                            // The first frame after a reset usually does not immediately yield decoded samples.
                            // Because the next instructions will fail if a buffer offset is set and the frame
                            // decoding didn't return data, we skip the part.
                            // We skip the following instructions also after decoding a warm-up frame.
                            continue;
                        }
                        // Two special cases can happen here:
                        // 1. We are interested in the first frame of the stream, but need to read the second frame too
                        //    for the decoder to return decoded data
                        // 2. We are interested in the second frame of the stream, but because reading the first frame
                        //    as warm-up didn't yield any data (because the decoder needs two frames to return data), we
                        //    get data from the first and second frame.
                        //    This case needs special handling, and we have to purge the data of the first frame.
                        else if (tocIndex == targetTocIndex + 1 && decompressed == bytesPerDecodedFrame * 2)
                        {
                            // Purge the first frame's data
                            decompressBuffer.Slice(bytesPerDecodedFrame, bytesPerDecodedFrame).CopyTo(decompressBuffer);
                            // question - why not just set decompressBufferOffset += bytesDecodedPerFrame here?
                            //Array.Copy(decompressBuffer, bytesPerDecodedFrame, decompressBuffer, 0, bytesPerDecodedFrame);
                            decompressed = bytesPerDecodedFrame;
                        }

                        int toCopy = Math.Min(decompressed - decompressBufferOffset, numBytes - bytesRead);
                        decompressBuffer.Slice(decompressBufferOffset, toCopy).Span.CopyTo(sampleBuffer);
                        //Array.Copy(decompressBuffer, decompressBufferOffset, sampleBuffer, offset, toCopy);
                        if ((toCopy + decompressBufferOffset) < decompressed)
                        {
                            decompressBufferOffset = toCopy + decompressBufferOffset;
                            decompressLeftovers    = decompressed - decompressBufferOffset;
                        }
                        else
                        {
                            // no lefovers
                            decompressBufferOffset = 0;
                            decompressLeftovers    = 0;
                        }
                        sampleBuffer = sampleBuffer.Slice(toCopy);
                        bytesRead   += toCopy;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            Debug.Assert(bytesRead <= numBytes, "MP3 File Reader read too much");
            position += bytesRead;
            return(bytesRead);
        }
Beispiel #3
0
        private Mp3FileReader(Stream inputStream, FrameDecompressorBuilder frameDecompressorBuilder, bool ownInputStream)
        {
            if (inputStream == null)
            {
                throw new ArgumentNullException(nameof(inputStream));
            }
            if (frameDecompressorBuilder == null)
            {
                throw new ArgumentNullException(nameof(frameDecompressorBuilder));
            }
            this.ownInputStream = ownInputStream;
            try
            {
                mp3Stream = inputStream;
                Id3v2Tag  = Id3v2Tag.ReadTag(mp3Stream);

                dataStartPosition = mp3Stream.Position;
                var firstFrame = Mp3Frame.LoadFromStream(mp3Stream);
                if (firstFrame == null)
                {
                    throw new InvalidDataException("Invalid MP3 file - no MP3 Frames Detected");
                }
                double bitRate = firstFrame.BitRate;
                xingHeader = XingHeader.LoadXingHeader(firstFrame);
                // If the header exists, we can skip over it when decoding the rest of the file
                if (xingHeader != null)
                {
                    dataStartPosition = mp3Stream.Position;
                }

                // workaround for a longstanding issue with some files failing to load
                // because they report a spurious sample rate change
                var secondFrame = Mp3Frame.LoadFromStream(mp3Stream);
                if (secondFrame != null &&
                    (secondFrame.SampleRate != firstFrame.SampleRate ||
                     secondFrame.ChannelMode != firstFrame.ChannelMode))
                {
                    // assume that the first frame was some kind of VBR/LAME header that we failed to recognise properly
                    dataStartPosition = secondFrame.FileOffset;
                    // forget about the first frame, the second one is the first one we really care about
                    firstFrame = secondFrame;
                }

                mp3DataLength = mp3Stream.Length - dataStartPosition;

                // try for an ID3v1 tag as well
                mp3Stream.Position = mp3Stream.Length - 128;
                byte[] tag = new byte[128];
                mp3Stream.Read(tag, 0, 128);
                if (tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G')
                {
                    Id3v1Tag       = tag;
                    mp3DataLength -= 128;
                }

                mp3Stream.Position = dataStartPosition;

                // create a temporary MP3 format before we know the real bitrate
                Mp3WaveFormat = new Mp3WaveFormat(firstFrame.SampleRate,
                                                  firstFrame.ChannelMode == ChannelMode.Mono ? 1 : 2, firstFrame.FrameLength, (int)bitRate);

                CreateTableOfContents();
                tocIndex = 0;

                // [Bit rate in Kilobits/sec] = [Length in kbits] / [time in seconds]
                //                            = [Length in bits ] / [time in milliseconds]

                // Note: in audio, 1 kilobit = 1000 bits.
                // Calculated as a double to minimize rounding errors
                bitRate = (mp3DataLength * 8.0 / TotalSeconds());

                mp3Stream.Position = dataStartPosition;

                // now we know the real bitrate we can create an accurate MP3 WaveFormat
                Mp3WaveFormat = new Mp3WaveFormat(firstFrame.SampleRate,
                                                  firstFrame.ChannelMode == ChannelMode.Mono ? 1 : 2, firstFrame.FrameLength, (int)bitRate);
                decompressor   = frameDecompressorBuilder(Mp3WaveFormat);
                waveFormat     = decompressor.OutputFormat;
                bytesPerSample = (decompressor.OutputFormat.BitsPerSample) / 8 * decompressor.OutputFormat.Channels;
                // no MP3 frames have more than 1152 samples in them
                bytesPerDecodedFrame = 1152 * bytesPerSample;
                // some MP3s I seem to get double
                decompressBuffer = new byte[bytesPerDecodedFrame * 2];
            }
            catch (Exception)
            {
                if (ownInputStream)
                {
                    inputStream.Dispose();
                }
                throw;
            }
        }
Beispiel #4
0
        /// <summary>
        /// Load Xing Header
        /// </summary>
        /// <param name="frame">Frame</param>
        /// <returns>Xing Header</returns>
        public static XingHeader LoadXingHeader(Mp3Frame frame)
        {
            XingHeader xingHeader = new XingHeader();

            xingHeader.frame = frame;
            int offset = 0;

            if (frame.MpegVersion == MpegVersion.Version1)
            {
                if (frame.ChannelMode != ChannelMode.Mono)
                {
                    offset = 32 + 4;
                }
                else
                {
                    offset = 17 + 4;
                }
            }
            else if (frame.MpegVersion == MpegVersion.Version2)
            {
                if (frame.ChannelMode != ChannelMode.Mono)
                {
                    offset = 17 + 4;
                }
                else
                {
                    offset = 9 + 4;
                }
            }
            else
            {
                return(null);
                // throw new FormatException("Unsupported MPEG Version");
            }

            if ((frame.RawData[offset + 0] == 'X') &&
                (frame.RawData[offset + 1] == 'i') &&
                (frame.RawData[offset + 2] == 'n') &&
                (frame.RawData[offset + 3] == 'g'))
            {
                xingHeader.startOffset = offset;
                offset += 4;
            }
            else
            {
                return(null);
            }

            XingHeaderOptions flags = (XingHeaderOptions)ReadBigEndian(frame.RawData, offset);

            offset += 4;

            if ((flags & XingHeaderOptions.Frames) != 0)
            {
                xingHeader.framesOffset = offset;
                offset += 4;
            }
            if ((flags & XingHeaderOptions.Bytes) != 0)
            {
                xingHeader.bytesOffset = offset;
                offset += 4;
            }
            if ((flags & XingHeaderOptions.Toc) != 0)
            {
                xingHeader.tocOffset = offset;
                offset += 100;
            }
            if ((flags & XingHeaderOptions.VbrScale) != 0)
            {
                xingHeader.vbrScale = ReadBigEndian(frame.RawData, offset);
                offset += 4;
            }
            xingHeader.endOffset = offset;
            return(xingHeader);
        }