Skip to content

Commit

Permalink
Settings.MP3_parseExactDuration : Unit tests and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Zeugma440 committed Sep 29, 2024
1 parent ac8ec93 commit 9c337a4
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 47 deletions.
63 changes: 47 additions & 16 deletions ATL.unit-test/IO/AudioData/AudioData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,53 @@ private void testReader(
[TestMethod]
public void Audio_MP3()
{
// VBR
testGenericAudio("MP3/01 - Title Screen.mp3", 3866, 129, -1, 44100, true, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 2048, 62342);
// Malpositioned header
testGenericAudio("MP3/headerPatternIsNotHeader.mp3", 184, 192, -1, 44100, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 1252, 3340);
// Malpositioned header 2
testGenericAudio("MP3/truncated_frame.mp3", 520, 320, -1, 48000, false, CF_LOSSY, STEREO, "MPEG Audio (Layer III)", 954, 19908);
// MPEG1 Layer 1
testGenericAudio("MP3/mp1Layer1.mp1", 520, 384, -1, 44100, false, CF_LOSSY, STEREO, "MPEG Audio (Layer I)", 0, 25080);
// MPEG1 Layer 2
testGenericAudio("MP3/mp1Layer2.mp1", 752, 384, -1, 44100, false, CF_LOSSY, STEREO, "MPEG Audio (Layer II)", 0, 36362);
// MPEG2 Layer 1
testGenericAudio("MP3/mp2Layer1.mp2", 1408, 128, -1, 22050, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer I)", 0, 22572);
// MPEG2 Layer 2
testGenericAudio("MP3/mp2Layer2.mp2", 1296, 160, -1, 24000, false, CF_LOSSY, STEREO, "MPEG Audio (Layer II)", 0, 25920);
// Contradictory frames
testGenericAudio("MP3/different_bitrates_modes.mp3", 6432, 128, -1, 44100, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 45, 103025);
var initialSetting = ATL.Settings.MP3_parseExactDuration;
try
{
ATL.Settings.MP3_parseExactDuration = false;

// MPEG1 Layer 1
testGenericAudio("MP3/mp1Layer1.mp1", 522, 384, -1, 44100, false, CF_LOSSY, STEREO, "MPEG Audio (Layer I)", 0, 25080);
// MPEG1 Layer 2
testGenericAudio("MP3/mp1Layer2.mp1", 758, 384, -1, 44100, false, CF_LOSSY, STEREO, "MPEG Audio (Layer II)", 0, 36362);
// MPEG2 Layer 1
testGenericAudio("MP3/mp2Layer1.mp2", 1411, 128, -1, 22050, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer I)", 0, 22572);
// MPEG2 Layer 2
testGenericAudio("MP3/mp2Layer2.mp2", 1296, 160, -1, 24000, false, CF_LOSSY, STEREO, "MPEG Audio (Layer II)", 0, 25920);

// VBR
testGenericAudio("MP3/01 - Title Screen.mp3", 3866, 129, -1, 44100, true, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 2048, 62342);
// Malpositioned header
testGenericAudio("MP3/headerPatternIsNotHeader.mp3", 139, 192, -1, 44100, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 1252, 3340);
// Malpositioned header 2
testGenericAudio("MP3/truncated_frame.mp3", 498, 320, -1, 48000, false, CF_LOSSY, STEREO, "MPEG Audio (Layer III)", 954, 19908);
// Contradictory frames
testGenericAudio("MP3/different_bitrates_modes.mp3", 6439, 128, -1, 44100, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 45, 103025);

ATL.Settings.MP3_parseExactDuration = true;

// MPEG1 Layer 1
testGenericAudio("MP3/mp1Layer1.mp1", 522, 384, -1, 44100, false, CF_LOSSY, STEREO, "MPEG Audio (Layer I)", 0, 25080);
// MPEG1 Layer 2
testGenericAudio("MP3/mp1Layer2.mp1", 758, 384, -1, 44100, false, CF_LOSSY, STEREO, "MPEG Audio (Layer II)", 0, 36362);
// MPEG2 Layer 1
testGenericAudio("MP3/mp2Layer1.mp2", 1411, 128, -1, 22050, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer I)", 0, 22572);
// MPEG2 Layer 2
testGenericAudio("MP3/mp2Layer2.mp2", 1296, 160, -1, 24000, false, CF_LOSSY, STEREO, "MPEG Audio (Layer II)", 0, 25920);

// VBR
testGenericAudio("MP3/01 - Title Screen.mp3", 3866, 129, -1, 44100, true, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 2048, 62346);
// Malpositioned header
testGenericAudio("MP3/headerPatternIsNotHeader.mp3", 156, 192, -1, 44100, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 1252, 3756);
// Malpositioned header 2
testGenericAudio("MP3/truncated_frame.mp3", 504, 320, -1, 48000, false, CF_LOSSY, STEREO, "MPEG Audio (Layer III)", 954, 20160);
// Contradictory frames
testGenericAudio("MP3/different_bitrates_modes.mp3", 6439, 128, -1, 44100, false, CF_LOSSY, JOINT_STEREO, "MPEG Audio (Layer III)", 45, 103025);
}
finally
{
ATL.Settings.MP3_parseExactDuration = initialSetting;
}
}

[TestMethod]
Expand Down
53 changes: 22 additions & 31 deletions ATL/AudioData/IO/MPEGaudio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public sealed class FrameHeader
private bool OriginalBit; // True if original media
private byte EmphasisID; // Emphasis ID

private int m_size; // Frame size (bytes)
private int m_size; // Frame size (bytes)

public void Reset()
{
Expand Down Expand Up @@ -221,46 +221,47 @@ public void LoadFromByteArray(byte[] data)
public string Layer => MPEG_LAYER[LayerID];
public ushort BitRate => MPEG_BIT_RATE[VersionID, LayerID, BitRateID];
public ushort SampleRate => MPEG_SAMPLE_RATE[VersionID, SampleRateID];
public int Padding => PaddingBit ? 1 : 0;


// This formula only works for Layers II and III
public int Size
{
get
{
if (m_size < 1) m_size = (int)Math.Floor(Coefficient * BitRate * 1000.0 / SampleRate) + Padding;
if (m_size < 1)
{
if (MPEG_LAYER_I == LayerID)
{
m_size = (int)Math.Floor((Coefficient * BitRate * 1000.0 / SampleRate) + Padding) * 4;
}
else // Layers II and III
{
m_size = (int)Math.Floor(Coefficient * BitRate * 1000.0 / SampleRate) + Padding;
}
}

return m_size;
}
}

/// <summary>
/// Get frame size coefficient
/// https://stackoverflow.com/a/62539671
/// </summary>
public byte Coefficient
{
get
{
if (MPEG_VERSION_1 == VersionID)
if (MPEG_LAYER_I == LayerID) return 48;
if (MPEG_LAYER_I == LayerID) return 12;
else return 144;
else
if (MPEG_LAYER_I == LayerID) return 24;
if (MPEG_LAYER_I == LayerID) return 12;
else if (MPEG_LAYER_II == LayerID) return 144;
else return 72;
}
}

public byte Padding
{
get
{
if (PaddingBit)
if (MPEG_LAYER_I == LayerID) return 4;
else return 1;
else return 0;
}
}

public byte VBRDeviation
{
get
Expand Down Expand Up @@ -289,18 +290,11 @@ public ChannelsArrangement ChannelsArrangement
}
}

public double Duration
{
get
{
return Coefficient * 1000.0 / SampleRate * 8.0;
}
}
public double Duration => Size * 1.0 / BitRate * 8.0;
}

private VBRData vbrData = new VBRData();
private FrameHeader FirstFrame = new FrameHeader();
private double exactDuration = -1;
private readonly Format audioFormat;


Expand Down Expand Up @@ -474,7 +468,6 @@ private static VBRData findVBR(Stream source, long position)

private double getDuration()
{
if (exactDuration > -1) return exactDuration;
if (FirstFrame.Found)
if (vbrData.Found && (vbrData.Frames > 0))
return vbrData.Frames * FirstFrame.Coefficient * 8.0 * 1000.0 / FirstFrame.SampleRate;
Expand Down Expand Up @@ -641,18 +634,16 @@ private static FrameHeader findFirstFrame(Stream source, ref VBRData oVBR, SizeI
return result;
}

private void parseExactDuration(BufferedBinaryReader reader)
private long parseExactAudioDataSize(BufferedBinaryReader reader)
{
byte[] buffer = new byte[4];
double totalDuration = FirstFrame.Duration;
reader.Seek(FirstFrame.Offset, SeekOrigin.Begin);
FrameHeader nextFrame = findNextFrame(reader, FirstFrame, buffer);
while (nextFrame.Found)
{
totalDuration += nextFrame.Duration;
nextFrame = findNextFrame(reader, nextFrame, buffer);
}
exactDuration = totalDuration;
return reader.Position - FirstFrame.Offset;
}

public bool Read(Stream source, SizeInfo sizeNfo, MetaDataIO.ReadTagParams readTagParams)
Expand All @@ -672,8 +663,8 @@ public bool Read(Stream source, SizeInfo sizeNfo, MetaDataIO.ReadTagParams readT
}

AudioDataOffset = FirstFrame.Offset;
AudioDataSize = sizeNfo.FileSize - sizeNfo.APESize - sizeNfo.ID3v1Size - AudioDataOffset;
if (Settings.MP3_parseExactDuration) parseExactDuration(reader);
if (Settings.MP3_parseExactDuration) AudioDataSize = parseExactAudioDataSize(reader);
else AudioDataSize = sizeNfo.FileSize - sizeNfo.APESize - sizeNfo.ID3v1Size - AudioDataOffset;

return true;
}
Expand Down

0 comments on commit 9c337a4

Please sign in to comment.