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).
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:
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.
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 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.
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.
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.
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.
Copyright newObjects and Michael Palazov - Elfial 2002-2003