Reading MP3 information in ASP (bit rate, ID tag etc.)

Author: Michael Elfial

Suppose you want to expose MP3 files on your site or may be burn a CD. Everything is ok but you will reach a point where you will need to describe them, to strip information about the MP3 settings - bit rate, sampling frequency etc. Well you can use some tools to export that information in text files, HTML or even DB, but still what to do if (on a WEB site) the content is dynamic and you want to be able to just place the MP3 file and and leave the rest to the ASP page which will show/list it?

This article gives an answer and provides sample code you will be able to use in your ASP pages on a WEB site or on CD (using ALP). The important part of the code is implemented in the mp3tools.asp file. It contains 2 VBScript classes from which the first one is more important. The file is designed as an include file and the other two sample pages are using it to do their job. The code is based on certain classes from the ActiveX Pack1 and you will need it (see the downloads at the bottom).

The MP3 format

I am not going to list the full details. However I'll try to explain the most important ones and point you the documentation you may need if you want to dive down in MPEG encoding details.

MP3 files are MPEG files, but they contain only audio data. In the last few years the people began to realize that it is much more convenient to combine the data with meta data or in human language to combine the data with its description. So MP3 files became a complex format which can split in 3 parts in this order in the file stream: 

  1. ID3 tag - extensive data block. May contain everything from short textual comments to pictures and even entire WEB pages.
  2. Audio data - The MP3 audio stream
  3. The old ID tag - Fixed format for short description. ID3 tags will replace it, but it is still the most popular MP3 description format supported by all the MP3 oriented applications.

This article will only ignore the ID3 tag. In future articles I'll concentrate on it. I decided to do so because the ID3 tag itself is probably more complex than the MP3 format so it deserves special attention.

So let us skip the ID3 tag (if it exists)

Skipping the tag is very easy because of special requirement about it called unsynchronization. This feature makes the work with it really hard, but skipping it is just a matter of finding the beginning of the audio stream. Thus we need to find the first 11 bits in the file which are all set.

The audio stream

The audio stream is kept in the so called frames. The frames have 4 bytes header and body calculated over the data contained in the header. We will not try to decode the body and play the stream so we are not interested in it - all we want is the header and length of each frame. So the header is 4 bytes or 32 bits. A brief information about it:

Bits Length Description
31-21 11 All the bits are set. This is used for synchronization purposes
20-19 2 MPEG Audio version
18-17 2 Layer description
16 1 Protection bit (CRC16 or none)
15-12 4 Bitrate index. The values depend on the MPEG version and the layer..
11-10 2 Sampling rate frequency index. The values depend on the MPEG version and the layer.
9 1 Set if the frame is padded. Some bit rates require padding because it uses only part of the frame.
8 1 Private bit
7-6 2 Channel mode - stereo, joint stereo, mono
5-4 2 Mode extension.
3 1 Copyright bit
2 1 Original or copy
1-0 2 Emphasis

The sample code contains the mentioned tables with values I fetched from the MP3 documentation (see the links at the bottom). Most of the above information is not important for us - we will only display it. But the bit rate, MPEG version, sampling frequency and the layer description are both - the most interesting information about the file and required if we want to be able to calculate the frame size.

As we mentioned above the unsynchronization helps us to skip the ID3 tag if it exists. So we are sure the first 11 bits set we will find in an MP3 file will be the beginning of the first frame header. In most cases it will be enough to read this frame in order to show user friendly information about the file. However it is quite possible to find a file which contains different frames - for example some frames encoded at 128kbit/s rate and some at 256kbit/s rate. So without reading all the frames in the file we cannot be sure the information we have is correct. However this requires considerable time and is not recommended for applications that want to show only brief information. 

The CMP3Frame class in the mp3tools.asp file uses the SFStream and SFFilter objects from the ActiveX Pack1 to read the header. To find a frame we will need to read 4 sequential bytes and put them in a long integer (4 bytes) numeric variable in order to simplify further work with them. To do so we use the following technique:

I am using the SFFilter object for this purpose only. So I set its properties in the class constructor (Class_Initialize):

Set flt = Server.CreateObject("newObjects.utilctls.SFFilter")
flt.buffSize = 4

This means that any call to the flt.Read(some_stream) will read 4 bytes from the stream and fill the SFFilter's internal buffer. Once read we can continue and construct a long integer from them:

b = flt.GetVar(vbByte,1)
head = bitLogic.BitOr(head,bitLogic.BitLeft(b,24))
b = flt.GetVar(vbByte,1)
head = bitLogic.BitOr(head,bitLogic.BitLeft(b,16))
b = flt.GetVar(vbByte,1)
head = bitLogic.BitOr(head,bitLogic.BitLeft(b,8))
b = flt.GetVar(vbByte,1)
head = bitLogic.BitOr(head,b)

The head is the long integer we wanted. Of course this can be done otherwise - for example inspecting every byte one by one but I think it is more convenient to have the entire header in one variable and then use logical operations over it. 

The MP3 information is stripped in the CalcProps private function of the class. I placed the test for the first 11 bits in it so it returns false if it is unable to decode the header. The first task it does is to check those bits:

If bitLogic.BitAnd(&HFFE00000,h) <> &HFFE00000 Then
  ' Not a frame
  CalcProps = False
  Exit Function
End If

I am using the TypeConvertor object from ActiveX Pack1. It gives the VBScript applications chance to perform bit wise logical operations. However we must be careful because it works with long integers only but we cannot control the conversions VBScript will do while passing the parameters. For example if the value passed to a bit logic function contains zeros for all the 16 most significant bits, VBScript may decide to put it in Short integer (2 bytes) and depending on the actual content it can be translated as negative short number and thus it will have some bits set in mistake. 

In the CalcProps function you will see code like this:

nLayer = bitLogic.BitAnd(&H00000003,bitLogic.BitRight(h,17))
Select Case nLayer
  Case 0
    Layer = "Unknown"
  Case 1
    Layer = "Layer III"
  Case 2
    Layer = "Layer II"
  Case 3
    Layer = "Layer I"
End Select

As it can be seen the value from the stream is first shifted right and then AND-ed with the mask. This order of the operations is the most easier way to avoid the potential problem described above.

The entire function looks like the above code sample. Some bits are read and then their meaning is translated in more convenient values in the properties of the object (in the code above Layer is one of the public properties of the class).

After all the calculations the frame characteristics are known. May be the most important of them is the frame size. Here is the code that calculates it:

If IsNumeric(BitRate) And IsNumeric(Freq) Then
    If nLayer = 3 Then
        ' Layer 1
        If nVersion = 3 Then
            ' Mpeg ver 1
            FrameSize = Int((48 * CDbl(BitRate * 1000))/CDbl(Freq) + nPadded)
        Else
            ' Mpeg ver 2 and 2.5
            FrameSize = Int((24 * CDbl(BitRate * 1000))/CDbl(Freq) + nPadded)
        End If
    ElseIf nLayer = 2 Or nLayer = 1 Then
        ' Layer 2 and 3
        If nVersion = 3 Then
            ' Mpeg 1
            FrameSize = Int((144 * CDbl(BitRate * 1000))/CDbl(Freq) + nPadded)
        Else
            ' Mpeg ver 2 and 2.5
            FrameSize = Int((72 * CDbl(BitRate * 1000))/CDbl(Freq) + nPadded)
        End If
    Else
        FrameSize = 0
    End If
    
End If

Evidently we must know many of the other parameters in order to be able to calculate the frame size - such as frequency and the bit rate. Knowing this we can read the MP3 file frame by frame, count the frames in the stream and so on. The FindFrame and ReadFrame functions position the stream on the byte after the frame header. Thus if the application wants to extract this frame it can learn its size and then read the corresponding number of bytes from the stream.  

The ID2 tag

Most MP3 files support it. The newest MP3 utilities usually fill it even if they support the ID3 tag. So a WEB application can benefit of its simplicity (in a WEB application speed can be important). The ID3 tag is fixed 128 byte record in the end of the stream. So we can use a SFRecord to read it and check if it is really an ID tag (code from the ReadIDTag function):

Dim pos, rec
pos = stream.Pos
Set rec = Server.CreateObject("newObjects.utilctls.SFRecord")
rec.AddField "TAG", vbString, 3
rec.AddField "Title", vbString, 30
rec.AddField "Artist", vbString, 30
rec.AddField "Album", vbString, 30
rec.AddField "Year", vbString, 4
rec.AddField "Comment", vbString, 30
rec.AddField "Genre", vbByte, 1
rec.BindTo stream
rec.Filter.unicodeText = False
rec.ReBind
stream.Pos = stream.Size - 128
If Not rec.Read Then
    ReadIDTag = False
    stream.Pos = pos
    Exit Function
End If
stream.Pos = pos
If rec("TAG").Value <> "TAG" Then
    ReadIDTag = False
    Exit Function
End If
Title = rec("Title").Value
Album = rec("Album").Value
Artist = rec("Artist").Value
Year = rec("Year").Value
Comment = rec("Comment").Value
GenreCode = rec("Genre").Value
If GenreCode <= UBound(Genre) Then
    GenreText = Genre(GenreCode)
Else
    GenreText = "Unknown"
End If 

We create a SFRecord object, add the fields we expect, position it 128 bytes before the stream end and read it. Then everything is a matter of inspecting the record's fields and see what they contain.

Appendix

The samples in the archive are designed for ALP (Active Local Pages) but they can be easily adapted for IIS. They use upload form but they do not perform real upload - instead they are using the file name passed to access the file directly in the local file system. In the real world you will probably determine the file location using other methods or after accepting upload - so that part depends on the application specifics.

Downloads

mp3.zip -  This article and the sample scripts and pages. you will need also the ActiveX Pack1 but if you have ALP installed you already have it. If you need to install it on IIS server machine take it from the ALP package and register it on the server, or alternatively download the pack separately.

More information on MP3 format

http://www.id3lib.org

http://www.mp3-tech.org

Copyright newObjects and Michael Palazov - Elfial 2002-2003