Reading MP3 Headers  
Stats
  Rating: 4.81 out of 5 by 106 users
  Submitted: 11/24/01
Robert Wlodarczyk (Robert.Wlodarczyk@stonybrook.edu)

 
Overview
Ever wanted to be able to read in information about those MP3 files that we're all familiar with? (Like getting the bitrate, length of the song, mode of the song ("stereo", "joint stereo", etc...), and the frequecy; this doesn't include the musical part of the MP3.)

Well, after searching the web, there are many resources available for doing this. There are a few C++ classes available, however, none that have been done yet using the Microsoft .NET Framework. So, essentially what I did was take one of these C++ MP3 classes and re-write it using C#.

The C# MP3Header Class
As I wrote in the previous paragraph, I used an existing C++ MP3 Header reading class and streamlined it and converted it into a C# class. Below if the entire C# class for reading in the headers of MP3 files.
/* ----------------------------------------------------------

   original C++ code by:
                        Gustav "Grim Reaper" Munkby
                        http://floach.pimpin.net/grd/
                        grimreaperdesigns@gmx.net
   
   modified and converted to C# by:
                        Robert A. Wlodarczyk
                        http://rob.wincereview.com:8080
                        rwlodarc@hotmail.com
   ---------------------------------------------------------- */


using System;
using System.IO;
public class MP3Header
{
    // Public variables for storing the information about the MP3
    public int intBitRate;
    public string strFileName;
    public long lngFileSize;
    public int intFrequency;
    public string strMode;
    public int intLength;
    public string strLengthFormatted;

    // Private variables used in the process of reading in the MP3 files
    private ulong bithdr;
    private bool boolVBitRate;
    private int intVFrames;

    public bool ReadMP3Information(string FileName)
    {
        FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
        // Set the filename not including the path information
        strFileName = @fs.Name;
        char[] chrSeparators = new char[]{'\\','/'};
        string[] strSeparator = strFileName.Split(chrSeparators);
        int intUpper = strSeparator.GetUpperBound(0);
        strFileName = strSeparator[intUpper];
        
        // Replace ' with '' for the SQL INSERT statement
        strFileName = strFileName.Replace("'", "''");
        
        // Set the file size
        lngFileSize = fs.Length;

        byte[] bytHeader = new byte[4];
        byte[] bytVBitRate = new byte[12];
        int intPos = 0;
        
        // Keep reading 4 bytes from the header until we know for sure that in 
        // fact it's an MP3
        do
        {
            fs.Position = intPos;
            fs.Read(bytHeader,0,4);
            intPos++;
            LoadMP3Header(bytHeader);
        }
        while(!IsValidHeader() && (fs.Position!=fs.Length));
        
        // If the current file stream position is equal to the length, 
        // that means that we've read the entire file and it's not a valid MP3 file
        if(fs.Position != fs.Length)
        {
            intPos += 3;

            if(getVersionIndex() == 3)    // MPEG Version 1
            {
                if(getModeIndex() == 3)    // Single Channel
                {
                    intPos += 17;
                }
                else
                {
                    intPos += 32;
                }
            }
            else                        // MPEG Version 2.0 or 2.5
            {
                if(getModeIndex() == 3)    // Single Channel
                {
                    intPos += 9;
                }
                else
                {
                    intPos += 17;
                }
            }
            
            // Check to see if the MP3 has a variable bitrate
            fs.Position = intPos;
            fs.Read(bytVBitRate,0,12);
            boolVBitRate = LoadVBRHeader(bytVBitRate);

            // Once the file's read in, then assign the properties of the file to the public variables
            intBitRate = getBitrate();
            intFrequency = getFrequency();
            strMode = getMode();
            intLength = getLengthInSeconds();
            strLengthFormatted = getFormattedLength();
            fs.Close();
            return true;
        }
        return false;
    }

    private void LoadMP3Header(byte[] c)
    {
        // this thing is quite interesting, it works like the following
        // c[0] = 00000011
        // c[1] = 00001100
        // c[2] = 00110000
        // c[3] = 11000000
        // the operator << means that we'll move the bits in that direction
        // 00000011 << 24 = 00000011000000000000000000000000
        // 00001100 << 16 =         000011000000000000000000
        // 00110000 << 24 =                 0011000000000000
        // 11000000       =                         11000000
        //                +_________________________________
        //                  00000011000011000011000011000000
        bithdr = (ulong)(((c[0] & 255) << 24) | ((c[1] & 255) << 16) | ((c[2] & 255) <<  8) | ((c[3] & 255))); 
    }

    private bool LoadVBRHeader(byte[] inputheader)
    {
        // If it's a variable bitrate MP3, the first 4 bytes will read 'Xing'
        // since they're the ones who added variable bitrate-edness to MP3s
        if(inputheader[0] == 88 && inputheader[1] == 105 && 
            inputheader[2] == 110 && inputheader[3] == 103)
        {
            int flags = (int)(((inputheader[4] & 255) << 24) | ((inputheader[5] & 255) << 16) | ((inputheader[6] & 255) <<  8) | ((inputheader[7] & 255)));
            if((flags & 0x0001) == 1)
            {
                intVFrames = (int)(((inputheader[8] & 255) << 24) | ((inputheader[9] & 255) << 16) | ((inputheader[10] & 255) <<  8) | ((inputheader[11] & 255)));
                return true;
            }
            else
            {
                intVFrames = -1;
                return true;
            }
        }
        return false;
    }

    private bool IsValidHeader() 
    {
        return (((getFrameSync()      & 2047)==2047) &&
                ((getVersionIndex()   &    3)!=   1) &&
                ((getLayerIndex()     &    3)!=   0) && 
                ((getBitrateIndex()   &   15)!=   0) &&
                ((getBitrateIndex()   &   15)!=  15) &&
                ((getFrequencyIndex() &    3)!=   3) &&
                ((getEmphasisIndex()  &    3)!=   2)    );
    }

    private int getFrameSync()     
    {
        return (int)((bithdr>>21) & 2047); 
    }

    private int getVersionIndex()  
    { 
        return (int)((bithdr>>19) & 3);  
    }

    private int getLayerIndex()    
    { 
        return (int)((bithdr>>17) & 3);  
    }

    private int getProtectionBit() 
    { 
        return (int)((bithdr>>16) & 1);  
    }

    private int getBitrateIndex()  
    { 
        return (int)((bithdr>>12) & 15); 
    }

    private int getFrequencyIndex()
    { 
        return (int)((bithdr>>10) & 3);  
    }

    private int getPaddingBit()    
    { 
        return (int)((bithdr>>9) & 1);  
    }

    private int getPrivateBit()    
    { 
        return (int)((bithdr>>8) & 1);  
    }

    private int getModeIndex()     
    { 
        return (int)((bithdr>>6) & 3);  
    }

    private int getModeExtIndex()  
    { 
        return (int)((bithdr>>4) & 3);  
    }

    private int getCoprightBit()   
    { 
        return (int)((bithdr>>3) & 1);  
    }

    private int getOrginalBit()    
    { 
        return (int)((bithdr>>2) & 1);  
    }
    
    private int getEmphasisIndex() 
    { 
        return (int)(bithdr & 3);  
    }

    private double getVersion() 
    {
        double[] table = {2.5, 0.0, 2.0, 1.0};
        return table[getVersionIndex()];
    }

    private int getLayer() 
    {
        return (int)(4 - getLayerIndex());
    }

    private int getBitrate() 
    {
        // If the file has a variable bitrate, then we return an integer average bitrate,
        // otherwise, we use a lookup table to return the bitrate
        if(boolVBitRate)
        {
            double medFrameSize = (double)lngFileSize / (double)getNumberOfFrames();
            return (int)((medFrameSize * (double)getFrequency()) / (1000.0 * ((getLayerIndex()==3) ? 12.0 : 144.0)));
        }
        else
        {
            int[,,] table =        {
                                { // MPEG 2 & 2.5
                                    {0,  8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0}, // Layer III
                                    {0,  8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0}, // Layer II
                                    {0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0}  // Layer I
                                },
                                { // MPEG 1
                                    {0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, // Layer III
                                    {0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, // Layer II
                                    {0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0}  // Layer I
                                }
                                };

            return table[getVersionIndex() & 1, getLayerIndex()-1, getBitrateIndex()];
        }
    }

    private int getFrequency() 
    {
        int[,] table =    {    
                            {32000, 16000,  8000}, // MPEG 2.5
                            {    0,     0,     0}, // reserved
                            {22050, 24000, 16000}, // MPEG 2
                            {44100, 48000, 32000}  // MPEG 1
                        };

        return table[getVersionIndex(), getFrequencyIndex()];
    }

    private string getMode() 
    {
        switch(getModeIndex()) 
        {
            default:
                return "Stereo";
            case 1:
                return "Joint Stereo";
            case 2:
                return "Dual Channel";
            case 3:
                return "Single Channel";
        }
    }

    private int getLengthInSeconds() 
    {
        // "intKilBitFileSize" made by dividing by 1000 in order to match the "Kilobits/second"
        int intKiloBitFileSize = (int)((8 * lngFileSize) / 1000);
        return (int)(intKiloBitFileSize/getBitrate());
    }

    private string getFormattedLength() 
    {
        // Complete number of seconds
        int s  = getLengthInSeconds();

        // Seconds to display
        int ss = s%60;

        // Complete number of minutes
        int m  = (s-ss)/60;

        // Minutes to display
        int mm = m%60;

        // Complete number of hours
        int h = (m-mm)/60;

        // Make "hh:mm:ss"
        return h.ToString("D2") + ":" + mm.ToString("D2") + ":" + ss.ToString("D2");
    }

    private int getNumberOfFrames() 
    {
        // Again, the number of MPEG frames is dependant on whether it's a variable bitrate MP3 or not
        if (!boolVBitRate) 
        {
            double medFrameSize = (double)(((getLayerIndex()==3) ? 12 : 144) *((1000.0 * (float)getBitrate())/(float)getFrequency()));
            return (int)(lngFileSize/medFrameSize);
        }
        else 
            return intVFrames;
    }
}


This should be copied and pasted into a file called 'MP3Header.cs'.

Usage in your application
Ok, now that you've got this MP3Header class, you're probably wondering how do I use this in my own application. The original purpose for me to do this was for using it in an application for cataloging MP3s (which I've also written and will be submitted under 'Tools'). Below you'll find a modified code snippet from that application on using this library:
MP3Header mp3hdr = new MP3Header();
bool boolIsMP3 = mp3hdr.ReadMP3Information(args[0]);
if(boolIsMP3)
{
    Console.WriteLine(mp3hdr.strFileName);
    Console.WriteLine(mp3hdr.lngFileSize.ToString());
    Console.WriteLine(mp3hdr.intBitRate.ToString());
    Console.WriteLine(mp3hdr.intFrequency.ToString());
    Console.WriteLine(mp3hdr.strMode);
    Console.WriteLine(mp3hdr.strLengthFormatted);
    Console.WriteLine(mp3hdr.intLength.ToString());
}


Impressions of converting code from C++ to C#
Before I even began to look into this conversion, I thought that this would be a real pain and quite difficult to do. Once I found this simple C++ class online and actually began to convert the code to C#, I found that it was quite simple to do. The whole conversion was quite painless. Working with C# is beautiful.

Sources
I'd like to thank Gustav Munkby for giving me permission to post a revised version of his original C++ class. The original code can be found on his website. I found that his original C++ classes were the easiest to read out of the 2 or 3 different ones available online. One of those other ones was a library written in VB 6 with many different classes which made the code quite difficult to follow.

Copyright © 2001 DevHood® All Rights Reserved