Home  |  FAQ  |  About  |  Contact  |  View Source   
 
SEARCH:
 
BROWSE:
    My Hood
Edit My Info
View Events
Read Tutorials
Training Modules
View Presentations
Download Tools
Scan News
Get Jobs
Message Forums
School Forums
Member Directory
   
CONTRIBUTE:
    Sign me up!
Post an Event
Submit Tutorials
Upload Tools
Link News
Post Jobs
   
   
Home >  Tutorials >  C# >  Reading MP3 Headers
Add to MyHood
   Reading MP3 Headers   [ printer friendly ]
Stats
  Rating: 4.82 out of 5 by 103 users
  Submitted: 11/24/01
Robert Wlodarczyk ()

 
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/
                        
   
   modified and converted to C# by:
                        Robert A. Wlodarczyk
                        http://rob.wincereview.com:8080
                        
   ---------------------------------------------------------- */


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 . 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.

Return to Browsing Tutorials

Email this Tutorial to a Friend

Rate this Content:  
low quality  1 2 3 4 5  high quality

Reader's Comments Post a Comment
 
Neat stuff. Quite useful, actualy. And of course superb as a C# code sample. Could use a bit more encapsulation - it just seems a bit "flat" against the left border... purely astetic though.
-- Adam Azarchs, November 26, 2001
 
Very interesting topic. Keep up the good work!
-- Jimmy Wong, November 27, 2001
 
Neat. Keep up the good work.
-- Kevin Lai, December 03, 2001
 
This is pretty cool. Thanks for writing it and posting it.
-- Jim Jones, December 07, 2001
 
Great stuff..Does anyone have code samples dealing with (.wav) file headers, and readers

Anything Media related let me know.
-- Arif Gursel, December 11, 2001
 
Good job - great reading.
-- Vijay Venkatachalam, December 11, 2001
 
very cool tutorial! Thanks for the post!
-- Chris Roberts, December 14, 2001
 
Cool stuff - looks like C# is going to be a really great way to make useful apps quickly and easily.
-- Steven Onorato, December 19, 2001
 
Nice, but how do you get the ID3 Tag fields? (e.g. Author, Album, Title, etc.)
-- Steven Padfield, January 01, 2002
 
I actually have been looking for something like this. Now I've found it! Thanks
-- Vincent Chan, January 14, 2002
 
A pretty good tutorial, especially about how to get this information. However, keeping with the C# paradigm and using Properties instead of getter methods would be better. Mark all your fields as private and make read-only properties like so:

public int BitRate {
get {
return bitrate; // or some other code to run, such as IsValidHeader() does
}
}
-- Heath Stewart, January 22, 2002
 
This is the one I really wanted.
-- Krishnan Subramanian, January 29, 2002
 
Cool! I always wondered how the mp3 header stuff worked. Gonne have to check out that tool you made too!
-- Jun Nozaki, January 29, 2002
 
This is neat. When I read it I thought I was reading java code.
-- Wayne Szeto, February 06, 2002
 
Cool tutorial.
-- Brian Simoneau, February 26, 2002
 
Very nice!
-- Kuniaki Tran, March 03, 2002
 
Really kool stuff! I always wanted to do do that.
-- Christopher Broussard, March 17, 2002
 
pretty cool to do something like this...
-- Duk Lee, March 24, 2002
 
cool tutorial, nice work
-- can comertoglu, April 10, 2002
 
This tutorial is great! Now I can add this on to the MP3 sorting program that I'm building!
-- Jonathan Larson, April 11, 2002
 
This is going to be very useful. Great work!
-- Bassam Islam, April 11, 2002
 
Thank you this is really great.. I can move on with my MP3 db now :)
-- Bertan Aygun, April 12, 2002
 
Wow, lots of code for a tutorial, but I'm sure all that info will help me later. It's also good to find out about MP3 headers.
-- Laurent Vauthrin, April 16, 2002
 
hmm neat stuff!
-- John Woo, November 01, 2002
 
Copyright © 2001 DevHood® All Rights Reserved