Skip to content

Commit

Permalink
better error handling for zero length MP3 files
Browse files Browse the repository at this point in the history
  • Loading branch information
markheath committed Feb 13, 2015
1 parent e819c22 commit 425ba3d
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 59 deletions.
7 changes: 4 additions & 3 deletions NAudio/FileFormats/Mp3/Id3v2Tag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,10 @@ static Stream CreateId3v2TagStream(IEnumerable<KeyValuePair<string, string>> tag
private Id3v2Tag(Stream input)
{
tagStartPosition = input.Position;
BinaryReader reader = new BinaryReader(input);
var reader = new BinaryReader(input);
byte[] headerBytes = reader.ReadBytes(10);
if ((headerBytes[0] == (byte)'I') &&
if ((headerBytes.Length >= 3) &&
(headerBytes[0] == (byte)'I') &&
(headerBytes[1] == (byte)'D') &&
(headerBytes[2] == '3'))
{
Expand Down Expand Up @@ -214,7 +215,7 @@ private Id3v2Tag(Stream input)
}
else
{
input.Position -= 10;
input.Position = tagStartPosition;
throw new FormatException("Not an ID3v2 tag");
}
tagEndPosition = input.Position;
Expand Down
124 changes: 68 additions & 56 deletions NAudio/Wave/WaveStreams/Mp3FileReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,67 +83,79 @@ public Mp3FileReader(Stream inputStream)
/// <param name="frameDecompressorBuilder">Factory method to build a frame decompressor</param>
public Mp3FileReader(Stream inputStream, FrameDecompressorBuilder frameDecompressorBuilder)
{
// Calculated as a double to minimize rounding errors

mp3Stream = inputStream;
id3v2Tag = Id3v2Tag.ReadTag(mp3Stream);

dataStartPosition = mp3Stream.Position;
var firstFrame = Mp3Frame.LoadFromStream(mp3Stream);
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))
if (inputStream == null) throw new ArgumentNullException("inputStream");
try
{
// 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;
}
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;
}

this.mp3DataLength = mp3Stream.Length - dataStartPosition;
this.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;
this.mp3DataLength -= 128;
}
// 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;
this.mp3DataLength -= 128;
}

mp3Stream.Position = dataStartPosition;

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

CreateTableOfContents();
this.tocIndex = 0;
mp3Stream.Position = dataStartPosition;

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

// Note: in audio, 1 kilobit = 1000 bits.
bitRate = (mp3DataLength * 8.0 / TotalSeconds());

mp3Stream.Position = dataStartPosition;

// now we know the real bitrate we can create an accurate
this.Mp3WaveFormat = new Mp3WaveFormat(firstFrame.SampleRate, firstFrame.ChannelMode == ChannelMode.Mono ? 1 : 2, firstFrame.FrameLength, (int)bitRate);
decompressor = frameDecompressorBuilder(Mp3WaveFormat);
this.waveFormat = decompressor.OutputFormat;
this.bytesPerSample = (decompressor.OutputFormat.BitsPerSample) / 8 * decompressor.OutputFormat.Channels;
// no MP3 frames have more than 1152 samples in them
// some MP3s I seem to get double
this.decompressBuffer = new byte[1152 * bytesPerSample * 2];
// create a temporary MP3 format before we know the real bitrate
this.Mp3WaveFormat = new Mp3WaveFormat(firstFrame.SampleRate,
firstFrame.ChannelMode == ChannelMode.Mono ? 1 : 2, firstFrame.FrameLength, (int) bitRate);

CreateTableOfContents();
this.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
this.Mp3WaveFormat = new Mp3WaveFormat(firstFrame.SampleRate,
firstFrame.ChannelMode == ChannelMode.Mono ? 1 : 2, firstFrame.FrameLength, (int) bitRate);
decompressor = frameDecompressorBuilder(Mp3WaveFormat);
this.waveFormat = decompressor.OutputFormat;
this.bytesPerSample = (decompressor.OutputFormat.BitsPerSample)/8*decompressor.OutputFormat.Channels;
// no MP3 frames have more than 1152 samples in them
// some MP3s I seem to get double
this.decompressBuffer = new byte[1152*bytesPerSample*2];
}
catch (Exception)
{
if (ownInputStream) inputStream.Dispose();
throw;
}
}

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions NAudioTests/Mp3/Mp3FileReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@ public void CanLoadAndReadVariousProblemMp3Files()
}
}

[Test]
public void CopesWithZeroLengthMp3()
{
var ms = new MemoryStream(new byte[0]);
Assert.Throws<InvalidDataException>(() => new Mp3FileReader(ms));
}
}
}

0 comments on commit 425ba3d

Please sign in to comment.